Skip to content

Commit

Permalink
autorefresh: run on a separate thread
Browse files Browse the repository at this point in the history
running on the main thread locks UI on windows under some conditions
perhaps device enumerate takes too long
it looks like SDL previously ran into a similar issue libsdl-org/SDL#3071
  • Loading branch information
xyzz committed Apr 17, 2022
1 parent 0c2a63c commit c3d563e
Showing 1 changed file with 101 additions and 35 deletions.
136 changes: 101 additions & 35 deletions src/main/python/autorefresh.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import json
import time
from multiprocessing import RLock

from PyQt5.QtCore import QTimer, QObject, pyqtSignal
from PyQt5.QtCore import QObject, pyqtSignal, QThread

from util import find_vial_devices

Expand All @@ -17,71 +19,127 @@ def __exit__(self):
self.autorefresh._unlock()


class Autorefresh(QObject):
class AutorefreshThread(QThread):

instance = None
devices_updated = pyqtSignal(object, bool)

def __init__(self):
super().__init__()

self.current_device = None
self.devices = []
self.locked = False
self.current_device = None
self.mutex = RLock()

self.sideload_json = None
self.sideload_vid = self.sideload_pid = -1
# create empty VIA definitions. Easier than setting it to none and handling a bunch of exceptions
self.via_stack_json = {"definitions": {}}

self.timer = QTimer()
self.timer.timeout.connect(self.update)
self.timer.start(1000)
def run(self):
while True:
self.update()
time.sleep(1)

Autorefresh.instance = self
def lock(self):
with self.mutex:
self.locked = True

def unlock(self):
with self.mutex:
self.locked = False

# note that this method is called from both inside and outside of this thread
def update(self, quiet=True, hard=False):
if self.locked:
return
# if lock()ed then just do nothing
with self.mutex:
if self.locked:
return
# can be modified out of mutex so create local copies here
via_stack_json = self.via_stack_json
sideload_vid = self.sideload_vid
sideload_pid = self.sideload_pid

# this can take a long (~seconds) time on Windows, so run outside of mutex
# to make sure calling lock() and unlock() is instant
new_devices = find_vial_devices(via_stack_json, sideload_vid, sideload_pid, quiet=quiet)

# this is fast again but discard results if we got lock()ed in between
with self.mutex:
if self.locked:
return

# if the set of the devices didn't change at all, don't need to update the combobox
old_paths = set(d.desc["path"] for d in self.devices)
new_paths = set(d.desc["path"] for d in new_devices)
if old_paths == new_paths and not hard:
return

# trigger update and report whether a hard-reload is needed (if current device went away)
self.devices = new_devices
old_path = "blank"
if self.current_device is not None:
old_path = self.current_device.desc["path"]

self.devices_updated.emit(new_devices, (old_path not in new_paths) or hard)

def load_dummy(self, data):
with self.mutex:
self.sideload_json = json.loads(data)
self.sideload_vid = self.sideload_pid = 0
self.update()

new_devices = find_vial_devices(self.via_stack_json, self.sideload_vid, self.sideload_pid,
quiet=quiet)
def sideload_via_json(self, data):
with self.mutex:
self.sideload_json = json.loads(data)
self.sideload_vid = int(self.sideload_json["vendorId"], 16)
self.sideload_pid = int(self.sideload_json["productId"], 16)
self.update()

# if the set of the devices didn't change at all, don't need to update the combobox
old_paths = set(d.desc["path"] for d in self.devices)
new_paths = set(d.desc["path"] for d in new_devices)
if old_paths == new_paths and not hard:
return
def load_via_stack(self, data):
with self.mutex:
self.via_stack_json = json.loads(data)

# trigger update and report whether a hard-reload is needed (if current device went away)
self.devices = new_devices
old_path = "blank"
if self.current_device is not None:
old_path = self.current_device.desc["path"]
self.devices_updated.emit(new_devices, (old_path not in new_paths) or hard)
def set_device(self, current_device):
with self.mutex:
self.current_device = current_device


class Autorefresh(QObject):

instance = None
devices_updated = pyqtSignal(object, bool)

def __init__(self):
super().__init__()

self.devices = []
self.current_device = None

Autorefresh.instance = self

self.thread = AutorefreshThread()
self.thread.devices_updated.connect(self.on_devices_updated)
self.thread.start()

def _lock(self):
self.locked = True
self.thread.lock()

def _unlock(self):
self.locked = False
self.thread.unlock()

@classmethod
def lock(cls):
return AutorefreshLocker(cls.instance)

def load_dummy(self, data):
self.sideload_json = json.loads(data)
self.sideload_vid = self.sideload_pid = 0
self.update()
self.thread.load_dummy(data)

def sideload_via_json(self, data):
self.sideload_json = json.loads(data)
self.sideload_vid = int(self.sideload_json["vendorId"], 16)
self.sideload_pid = int(self.sideload_json["productId"], 16)
self.update()
self.thread.sideload_via_json(data)

def load_via_stack(self, data):
self.via_stack_json = json.loads(data)
self.thread.load_via_stack(data)

def select_device(self, idx):
if self.current_device is not None:
Expand All @@ -92,8 +150,16 @@ def select_device(self, idx):

if self.current_device is not None:
if self.current_device.sideload:
self.current_device.open(self.sideload_json)
self.current_device.open(self.thread.sideload_json)
elif self.current_device.via_stack:
self.current_device.open(self.via_stack_json["definitions"][self.current_device.via_id])
self.current_device.open(self.thread.via_stack_json["definitions"][self.current_device.via_id])
else:
self.current_device.open(None)
self.thread.set_device(self.current_device)

def on_devices_updated(self, devices, changed):
self.devices = devices
self.devices_updated.emit(devices, changed)

def update(self, quiet=True, hard=False):
self.thread.update(quiet, hard)

0 comments on commit c3d563e

Please sign in to comment.