hydrus/include/ClientGUIAsync.py

217 lines
5.3 KiB
Python

from . import ClientData
from . import ClientConstants as CC
from . import HydrusConstants as HC
from . import HydrusData
from . import HydrusExceptions
from . import HydrusGlobals as HG
from . import HydrusText
import threading
import traceback
from qtpy import QtCore as QC
from qtpy import QtWidgets as QW
from qtpy import QtGui as QG
from . import QtPorting as QP
class AsyncQtUpdater( object ):
def __init__( self, win ):
# ultimate improvement here is to move to QObject/QThread and do the notifications through signals and slots (which will disconnect on object deletion)
self._win = win
self._calllater_waiting = False
self._work_needs_to_restart = False
self._is_working = False
self._lock = threading.Lock()
def _getResult( self ):
raise NotImplementedError()
def _publishLoading( self ):
pass
def _publishResult( self, result ):
raise NotImplementedError()
def _doWork( self ):
def deliver_result( result ):
if not self._win or not QP.isValid( self._win ):
self._win = None
return
self._publishResult( result )
with self._lock:
self._calllater_waiting = False
self._work_needs_to_restart = False
self._is_working = True
try:
result = self._getResult()
try:
HG.client_controller.CallBlockingToQt( self._win, deliver_result, result )
except ( HydrusExceptions.QtDeadWindowException, HydrusExceptions.ShutdownException ):
self._win = None
return
finally:
with self._lock:
self._is_working = False
if self._work_needs_to_restart and not self._calllater_waiting:
QP.CallAfter( self.update )
def _startWork( self ):
HG.client_controller.CallToThread( self._doWork )
def update( self ):
if not self._win or not QP.isValid( self._win ):
self._win = None
return
with self._lock:
if self._is_working:
self._work_needs_to_restart = True
elif not self._calllater_waiting:
self._publishLoading()
self._calllater_waiting = True
self._startWork()
class FastThreadToGUIUpdater( object ):
def __init__( self, win, func ):
self._win = win
self._func = func
self._lock = threading.Lock()
self._args = None
self._kwargs = None
self._callafter_waiting = False
self._work_needs_to_restart = False
self._is_working = False
def QtDoIt( self ):
if not self._win or not QP.isValid( self._win ):
self._win = None
return
with self._lock:
self._callafter_waiting = False
self._work_needs_to_restart = False
self._is_working = True
args = self._args
kwargs = self._kwargs
try:
self._func( *args, **kwargs )
except HydrusExceptions.ShutdownException:
pass
finally:
with self._lock:
self._is_working = False
if self._work_needs_to_restart and not self._callafter_waiting:
self._callafter_waiting = True
QP.CallAfter( self.QtDoIt )
# the point here is that we can spam this a hundred times a second, updating the args and kwargs, and Qt will catch up to it when it can
# if Qt feels like running fast, it'll update at 60fps
# if not, we won't get bungled up with 10,000+ pubsub events in the event queue
def Update( self, *args, **kwargs ):
if HG.model_shutdown:
return
if self._win is None:
return
with self._lock:
self._args = args
self._kwargs = kwargs
if self._is_working:
self._work_needs_to_restart = True
elif not self._callafter_waiting:
QP.CallAfter( self.QtDoIt )