Update
This commit is contained in:
253
2023/scripts/rigging_tools/ngskintools2/signal.py
Normal file
253
2023/scripts/rigging_tools/ngskintools2/signal.py
Normal file
@@ -0,0 +1,253 @@
|
||||
"""
|
||||
Signal is the previous method of emiting/subscribing to signals.
|
||||
* Subscribers are dependant on emiter's instances
|
||||
* There's only one global queue
|
||||
|
||||
New system is changing to allow decoupling subscribers and receivers, and allowing the code to work even when there's no need to process signals
|
||||
* Subscribers need to be able to subsribe prior to instantiation of emitters
|
||||
|
||||
|
||||
"""
|
||||
from functools import partial
|
||||
|
||||
from ngSkinTools2 import cleanup
|
||||
from ngSkinTools2.api.log import getLogger
|
||||
from ngSkinTools2.api.python_compatibility import Object
|
||||
|
||||
log = getLogger("signal")
|
||||
|
||||
|
||||
class SignalQueue(Object):
|
||||
def __init__(self):
|
||||
self.max_length = 100
|
||||
self.queue = []
|
||||
|
||||
def emit(self, handler):
|
||||
if len(self.queue) > self.max_length:
|
||||
log.error("queue max length reached: emitting too many events?")
|
||||
raise Exception("queue max length reached: emitting too many events?")
|
||||
|
||||
should_start = len(self.queue) == 0
|
||||
|
||||
self.queue.append(handler)
|
||||
if should_start:
|
||||
self.process()
|
||||
|
||||
def process(self):
|
||||
current_handler = 0
|
||||
queue = self.queue
|
||||
|
||||
while current_handler < len(queue):
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
queue[current_handler]()
|
||||
except Exception:
|
||||
import ngSkinTools2
|
||||
|
||||
if ngSkinTools2.DEBUG_MODE:
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
traceback.print_exc(file=sys.__stderr__)
|
||||
|
||||
current_handler += 1
|
||||
|
||||
if len(self.queue) > 50:
|
||||
log.info("handler queue finished with %d items", len(self.queue))
|
||||
self.queue = []
|
||||
|
||||
|
||||
# noinspection PyBroadException
|
||||
class Signal(Object):
|
||||
"""
|
||||
Signal class collects observers, interested in some particular event,and handles
|
||||
signaling them all when some event occurs. Both handling and signaling happens outside
|
||||
of signal's own code
|
||||
|
||||
|
||||
Handlers are processed breath first, in a queue based system.
|
||||
1. root signal fires, adds all it's handlers to the queue;
|
||||
2. queue starts being processed
|
||||
3. handlers fire more signals, in turn adding more handlers to the end of the queue.
|
||||
"""
|
||||
|
||||
all = []
|
||||
|
||||
queue = SignalQueue()
|
||||
|
||||
def __init__(self, name):
|
||||
if name is None:
|
||||
raise Exception("need name for debug purposes later")
|
||||
self.name = name
|
||||
self.handlers = []
|
||||
self.executing = False
|
||||
self.enabled = True
|
||||
|
||||
self.reset()
|
||||
Signal.all.append(self)
|
||||
cleanup.registerCleanupHandler(self.reset)
|
||||
|
||||
def reset(self):
|
||||
self.handlers = []
|
||||
self.executing = False
|
||||
|
||||
def emit_deferred(self, *args):
|
||||
import maya.utils as mu
|
||||
|
||||
mu.executeDeferred(self.emit, *args)
|
||||
|
||||
def emit(self, *args):
|
||||
"""
|
||||
emit mostly just adds handlers to the processing queue,
|
||||
but if nobody is processing handlers at the emit time,
|
||||
it is started here as well.
|
||||
"""
|
||||
if not self.enabled:
|
||||
return
|
||||
|
||||
# log.info("emit: %s", self.name)
|
||||
if self.executing:
|
||||
raise Exception('Nested emit on %s detected' % self.name)
|
||||
|
||||
for i in self.handlers[:]:
|
||||
Signal.queue.emit(partial(i, *args))
|
||||
|
||||
def addHandler(self, handler, qtParent=None):
|
||||
if hasattr(handler, 'emit'):
|
||||
handler = handler.emit
|
||||
|
||||
self.handlers.append(handler)
|
||||
|
||||
def remove():
|
||||
return self.removeHandler(handler)
|
||||
|
||||
if qtParent is not None:
|
||||
qtParent.destroyed.connect(remove)
|
||||
|
||||
return remove
|
||||
|
||||
def removeHandler(self, handler):
|
||||
try:
|
||||
self.handlers.remove(handler)
|
||||
except ValueError:
|
||||
# not found in list? no biggie.
|
||||
pass
|
||||
|
||||
|
||||
def on(*signals, **kwargs):
|
||||
"""
|
||||
decorator for function: list signals that should fire for this function.
|
||||
|
||||
@signal.on(signalReference)
|
||||
def something():
|
||||
...
|
||||
"""
|
||||
|
||||
def decorator(fn):
|
||||
for i in signals:
|
||||
i.addHandler(fn, **kwargs)
|
||||
return fn
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
# --------------------------------------
|
||||
# rework:
|
||||
# * decoupled emitters and subscribers
|
||||
# * subscribers are resolved only at the time of event
|
||||
# * can emit events even when there's no active sessions (acts as no-op)
|
||||
|
||||
|
||||
class Event(Object):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def __or__(self, other):
|
||||
return EventList() | self | other
|
||||
|
||||
def __iter__(self):
|
||||
yield self
|
||||
|
||||
def emit(self, *args, **kwargs):
|
||||
for hub in SignalHub.active_hubs:
|
||||
hub.emit(self, *args, **kwargs)
|
||||
|
||||
|
||||
class EventList(Object):
|
||||
"""
|
||||
helper to build and iterate over a list of "or"-ed events
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.items = []
|
||||
|
||||
def __or__(self, other):
|
||||
self.items.append(other)
|
||||
return self
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.items)
|
||||
|
||||
|
||||
class SignalHub(Object):
|
||||
active_hubs = set()
|
||||
|
||||
def __init__(self):
|
||||
self.handlers = {}
|
||||
self.queue = SignalQueue()
|
||||
|
||||
def activate(self):
|
||||
self.active_hubs.add(self)
|
||||
|
||||
def deactivate(self):
|
||||
self.active_hubs.remove(self)
|
||||
|
||||
def subscribe(self, event, handler):
|
||||
"""
|
||||
:param Event event: event to subscribe to
|
||||
:param Callable handler: callback which will be called when event is emitted
|
||||
:return: unsubscribe function: call it to terminate this subscription
|
||||
"""
|
||||
self.handlers.setdefault(event, []).append(handler)
|
||||
|
||||
def unsubscribe():
|
||||
try:
|
||||
self.handlers[event].remove(handler)
|
||||
except ValueError:
|
||||
# not found in list? no biggie.
|
||||
pass
|
||||
|
||||
return unsubscribe
|
||||
|
||||
def emit(self, event):
|
||||
if not event in self.handlers:
|
||||
return
|
||||
|
||||
for i in self.handlers[event]:
|
||||
self.queue.emit(i)
|
||||
|
||||
def on(self, events, scope=None):
|
||||
"""
|
||||
decorator for function: bind function to signals
|
||||
|
||||
@hub.on(event1 | event2, scope=qt_object)
|
||||
def something():
|
||||
...
|
||||
"""
|
||||
|
||||
def decorator(fn):
|
||||
unsubscribe_handlers = []
|
||||
try:
|
||||
unsubscribe_handlers.append(scope.destroyed.connect)
|
||||
except:
|
||||
pass
|
||||
|
||||
for e in events:
|
||||
unsub = self.subscribe(e, fn)
|
||||
if unsubscribe_handlers:
|
||||
for i in unsubscribe_handlers:
|
||||
i(unsub)
|
||||
|
||||
return fn
|
||||
|
||||
return decorator
|
||||
Reference in New Issue
Block a user