aboutsummaryrefslogtreecommitdiff
path: root/src/EventManager.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/EventManager.py')
-rw-r--r--src/EventManager.py226
1 files changed, 226 insertions, 0 deletions
diff --git a/src/EventManager.py b/src/EventManager.py
new file mode 100644
index 00000000..604ae337
--- /dev/null
+++ b/src/EventManager.py
@@ -0,0 +1,226 @@
+import itertools, time, traceback
+
+PRIORITY_URGENT = 0
+PRIORITY_HIGH = 1
+PRIORITY_MEDIUM = 2
+PRIORITY_LOW = 3
+PRIORITY_MONITOR = 4
+
+DEFAULT_PRIORITY = PRIORITY_MEDIUM
+DEFAULT_DELIMITER = "."
+
+class Event(object):
+ def __init__(self, bot, name, **kwargs):
+ self.bot = bot
+ self.name = name
+ self.kwargs = kwargs
+ self.eaten = False
+ def __getitem__(self, key):
+ return self.kwargs[key]
+ def get(self, key, default=None):
+ return self.kwargs.get(key, default)
+ def __contains__(self, key):
+ return key in self.kwargs
+ def eat(self):
+ self.eaten = True
+
+class EventCallback(object):
+ def __init__(self, function, bot, priority, kwargs):
+ self.function = function
+ self.bot = bot
+ self.priority = priority
+ self.kwargs = kwargs
+ def call(self, event):
+ return self.function(event)
+
+class MultipleEventHook(object):
+ def __init__(self):
+ self._event_hooks = set([])
+ def _add(self, event_hook):
+ self._event_hooks.add(event_hook)
+
+ def hook(self, function, **kwargs):
+ for event_hook in self._event_hooks:
+ event_hook.hook(function, **kwargs)
+
+ def call_limited(self, maximum, **kwargs):
+ returns = []
+ for event_hook in self._event_hooks:
+ returns.append(event_hook.call_limited(maximum, **kwargs))
+ return returns
+ def call(self, **kwargs):
+ returns = []
+ for event_hook in self._event_hooks:
+ returns.append(event_hook.call(**kwargs))
+ return returns
+
+class EventHookContext(object):
+ def __init__(self, parent, context):
+ self._parent = parent
+ self.context = context
+ def hook(self, function, priority=DEFAULT_PRIORITY, replay=False,
+ **kwargs):
+ self._parent._context_hook(self.context, function, priority, replay,
+ kwargs)
+ def on(self, subevent, *extra_subevents, delimiter=DEFAULT_DELIMITER):
+ return self._parent._context_on(self.context, subevent,
+ extra_subevents, delimiter)
+ def call_for_result(self, default=None, **kwargs):
+ return self._parent.call_for_result(default, **kwargs)
+ def assure_call(self, **kwargs):
+ self._parent.assure_call(**kwargs)
+ def call(self, **kwargs):
+ return self._parent.call(**kwargs)
+ def call_limited(self, maximum, **kwargs):
+ return self._parent.call_limited(maximum, **kwargs)
+ def get_hooks(self):
+ return self._parent.get_hooks()
+ def get_children(self):
+ return self._parent.get_children()
+
+class EventHook(object):
+ def __init__(self, bot, name=None, parent=None):
+ self.bot = bot
+ self.name = name
+ self.parent = parent
+ self._children = {}
+ self._hooks = []
+ self._stored_events = []
+ self._context_hooks = {}
+
+ def _make_event(self, kwargs):
+ return Event(self.bot, self.name, **kwargs)
+
+ def _get_path(self):
+ path = []
+ parent = self
+ while not parent == None and not parent.name == None:
+ path.append(parent.name)
+ parent = parent.parent
+ return DEFAULT_DELIMITER.join(path[::-1])
+
+ def new_context(self, context):
+ return EventHookContext(self, context)
+
+ def hook(self, function, priority=DEFAULT_PRIORITY, replay=False,
+ **kwargs):
+ self._hook(function, None, priority, replay, kwargs)
+ def _context_hook(self, context, function, priority, replay, kwargs):
+ self._hook(function, context, priority, replay, kwargs)
+ def _hook(self, function, context, priority, replay, kwargs):
+ callback = EventCallback(function, self.bot, priority, kwargs)
+
+ if context == None:
+ self._hooks.append(callback)
+ else:
+ if not context in self._context_hooks:
+ self._context_hooks[context] = []
+ self._context_hooks[context].append(callback)
+
+ if replay and not self._stored_events == None:
+ for kwargs in self._stored_events:
+ self._call(kwargs)
+ self._stored_events = None
+
+ def on(self, subevent, *extra_subevents, delimiter=DEFAULT_DELIMITER):
+ return self._on(subevent, extra_subevents, None, delimiter)
+ def _context_on(self, context, subevent, extra_subevents,
+ delimiter=DEFAULT_DELIMITER):
+ return self._on(subevent, extra_subevents, context, delimiter)
+ def _on(self, subevent, extra_subevents, context, delimiter):
+ if delimiter in subevent:
+ event_chain = subevent.split(delimiter)
+ event_obj = self
+ for event_name in event_chain:
+ event_obj = event_obj.get_child(event_name)
+ if not context == None:
+ return event_obj.new_context(context)
+ return event_obj
+
+ if extra_subevents:
+ multiple_event_hook = MultipleEventHook()
+ for extra_subevent in (subevent,)+extra_subevents:
+ child = self.get_child(extra_subevent)
+ if not context == None:
+ child = child.new_context(context)
+ multiple_event_hook._add(child)
+ return multiple_event_hook
+
+ child = self.get_child(subevent)
+ if not context == None:
+ child = child.new_context(context)
+ return child
+
+ def call_for_result(self, default=None, **kwargs):
+ results = self.call_limited(1, **kwargs)
+ return default if not len(results) else results[0]
+ def assure_call(self, **kwargs):
+ if not self._stored_events == None:
+ self._stored_events.append(kwargs)
+ else:
+ self._call(kwargs)
+ def call(self, **kwargs):
+ return self._call(kwargs)
+ def call_limited(self, maximum, **kwargs):
+ return self._call(kwargs, maximum=maximum)
+ def _call(self, kwargs, maximum=None):
+ event_path = self._get_path()
+ self.bot.log.debug("calling event: \"%s\" (params: %s)",
+ [event_path, kwargs])
+ start = time.monotonic()
+
+ event = self._make_event(kwargs)
+ returns = []
+ for hook in self.get_hooks()[:maximum]:
+ if event.eaten:
+ break
+ try:
+ returns.append(hook.call(event))
+ except Exception as e:
+ traceback.print_exc()
+ self.bot.log.error("failed to call event \"%s\"", [
+ event_path], exc_info=True)
+
+ total_milliseconds = (time.monotonic() - start) * 1000
+ self.bot.log.debug("event \"%s\" called in %fms", [
+ event_path, total_milliseconds])
+
+ self.check_purge()
+
+ return returns
+
+ def get_child(self, child_name):
+ child_name_lower = child_name.lower()
+ if not child_name_lower in self._children:
+ self._children[child_name_lower] = EventHook(self.bot,
+ child_name_lower, self)
+ return self._children[child_name_lower]
+ def remove_child(self, child_name):
+ child_name_lower = child_name.lower()
+ if child_name_lower in self._children:
+ del self._children[child_name_lower]
+
+ def check_purge(self):
+ if self.is_empty() and not self.parent == None:
+ self.parent.remove_child(self.name)
+ self.parent.check_purge()
+
+ def remove_context(self, context):
+ del self._context_hooks[context]
+ def has_context(self, context):
+ return context in self._context_hooks
+ def purge_context(self, context):
+ if self.has_context(context):
+ self.remove_context(context)
+
+ for child_name in self.get_children()[:]:
+ child = self.get_child(child_name)
+ child.purge_context(context)
+
+ def get_hooks(self):
+ return sorted(self._hooks + sum(self._context_hooks.values(), []),
+ key=lambda e: e.priority)
+ def get_children(self):
+ return list(self._children.keys())
+ def is_empty(self):
+ return len(self.get_hooks() + self.get_children()) == 0