254 lines
6.4 KiB
Python
254 lines
6.4 KiB
Python
"""
|
|
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
|