LoafyLemon
fc794f969a
* Added a UI lock mechanism to avoid render stalls * Fixed initialization issue due to python init offset for android devices * Fixed a hang caused by joining threads on android devices * Fixed a race condition when forcefully stopping threads
99 lines
3.2 KiB
Plaintext
99 lines
3.2 KiB
Plaintext
init python early:
|
|
import threading
|
|
import queue
|
|
|
|
class DollThread(threading.Thread, NoRollback):
|
|
__lock = threading.RLock()
|
|
_count = 0
|
|
_instances = _list()
|
|
_interval = 0.05 if renpy.android else 0.025
|
|
|
|
def __init__(self, interval=None, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None):
|
|
threading.Thread.__init__(self, group, target, name, args, kwargs, daemon=daemon)
|
|
|
|
self._return = None
|
|
self.interval = interval or self._interval
|
|
self._delay = threading.Timer(self.interval, self._execute)
|
|
self._stop = threading.Event()
|
|
|
|
def start(self):
|
|
DollThread._instances.append(self)
|
|
DollThread._count += 1
|
|
super().start()
|
|
|
|
def run(self):
|
|
with DollThread.__lock:
|
|
self._delay.start()
|
|
self._delay.join()
|
|
self.stop()
|
|
|
|
def _execute(self):
|
|
while not self._stop.is_set():
|
|
if self._target is not None:
|
|
self._return = self._target(*self._args, **self._kwargs)
|
|
renpy.restart_interaction()
|
|
break
|
|
|
|
def join(self, timeout=None):
|
|
super().join(timeout=timeout)
|
|
return self._return
|
|
|
|
def stop(self):
|
|
self._stop.set()
|
|
|
|
if self in DollThread._instances:
|
|
DollThread._instances.remove(self)
|
|
DollThread._count -= 1
|
|
|
|
if DollThread._count <= 0:
|
|
renpy.restart_interaction()
|
|
|
|
@classmethod
|
|
def stop_all(cls):
|
|
for thread in cls._instances:
|
|
# Allow threads to exit gracefully before forceful termination
|
|
thread.stop()
|
|
|
|
# if not renpy.android:
|
|
# # Then forcefully terminate the remainders (except on android because that stalls the renderer)
|
|
# for thread in cls._instances:
|
|
# thread.join()
|
|
|
|
class DefaultQueue(queue.Queue, NoRollback):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.last_item = None
|
|
|
|
def put(self, item, block=True, timeout=None):
|
|
super().put(item, block, timeout)
|
|
self.last_item = item
|
|
|
|
def get_with_default(self, default):
|
|
try:
|
|
return self.get(block=False)
|
|
except queue.Empty:
|
|
return self.last_item or default
|
|
|
|
def __getstate__(self):
|
|
state = self.__dict__.copy()
|
|
del state['last_item']
|
|
del state['mutex']
|
|
del state['not_empty']
|
|
del state['not_full']
|
|
del state['all_tasks_done']
|
|
return state
|
|
|
|
def __setstate__(self, state):
|
|
self.__dict__.update(state)
|
|
self.last_item = None
|
|
self.mutex = threading.Lock()
|
|
self.not_empty = threading.Condition(self.mutex)
|
|
self.not_full = threading.Condition(self.mutex)
|
|
self.all_tasks_done = threading.Condition(self.mutex)
|
|
|
|
def __reduce__(self):
|
|
return (DefaultQueue, ())
|
|
|
|
def __reduce_ex__(self, protocol):
|
|
return DefaultQueue, (), self.__getstate__(), None, iter([])
|