Skip to content
This repository has been archived by the owner on Sep 20, 2024. It is now read-only.

General: Event system #3499

Merged
merged 5 commits into from
Jul 13, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 113 additions & 15 deletions openpype/lib/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
from openpype.lib.python_2_comp import WeakMethod


class MissingEventSystem(Exception):
pass


class EventCallback(object):
"""Callback registered to a topic.

Expand Down Expand Up @@ -176,16 +180,20 @@ class Event(object):
topic (str): Identifier of event.
data (Any): Data specific for event. Dictionary is recommended.
source (str): Identifier of source.
event_system (EventSystem): Event system in which can be event
triggered.
"""

_data = {}

def __init__(self, topic, data=None, source=None):
def __init__(self, topic, data=None, source=None, event_system=None):
self._id = str(uuid4())
self._topic = topic
if data is None:
data = {}
self._data = data
self._source = source
self._event_system = event_system

def __getitem__(self, key):
return self._data[key]
Expand All @@ -211,28 +219,118 @@ def topic(self):

def emit(self):
"""Emit event and trigger callbacks."""
StoredCallbacks.emit_event(self)
if self._event_system is None:
raise MissingEventSystem(
"Can't emit event {}. Does not have set event system.".format(
str(repr(self))
)
)
self._event_system.emit_event(self)


class StoredCallbacks:
_registered_callbacks = []
class EventSystem(object):
"""Encapsulate event handling into an object.

System wraps registered callbacks and triggered events into single object
so it is possible to create mutltiple independent systems that have their
topics and callbacks.


"""

def __init__(self):
self._registered_callbacks = []

def add_callback(self, topic, callback):
"""Register callback in event system.

Args:
topic (str): Topic for EventCallback.
callback (Callable): Function or method that will be called
when topic is triggered.

Returns:
EventCallback: Created callback object which can be used to
stop listening.
"""

@classmethod
def add_callback(cls, topic, callback):
callback = EventCallback(topic, callback)
cls._registered_callbacks.append(callback)
self._registered_callbacks.append(callback)
return callback

@classmethod
def emit_event(cls, event):
def create_event(self, topic, data, source):
"""Create new event which is bound to event system.

Args:
topic (str): Event topic.
data (dict): Data related to event.
source (str): Source of event.

Returns:
Event: Object of event.
"""

return Event(topic, data, source, self)

def emit(self, topic, data, source):
"""Create event based on passed data and emit it.

This is easiest way how to trigger event in an event system.

Args:
topic (str): Event topic.
data (dict): Data related to event.
source (str): Source of event.

Returns:
Event: Created and emitted event.
"""

event = self.create_event(topic, data, source)
event.emit()
return event

def emit_event(self, event):
"""Emit event object.

Args:
event (Event): Prepared event with topic and data.
"""

invalid_callbacks = []
for callback in cls._registered_callbacks:
for callback in self._registered_callbacks:
callback.process_event(event)
if not callback.is_ref_valid:
invalid_callbacks.append(callback)

for callback in invalid_callbacks:
cls._registered_callbacks.remove(callback)
self._registered_callbacks.remove(callback)


class GlobalEventSystem:
"""Event system living in global scope of process.

This is primarily used in host implementation to trigger events
related to DCC changes or changes of context in the host implementation.
"""

_global_event_system = None

@classmethod
def get_global_event_system(cls):
if cls._global_event_system is None:
cls._global_event_system = EventSystem()
return cls._global_event_system

@classmethod
def add_callback(cls, topic, callback):
event_system = cls.get_global_event_system()
return event_system.add_callback(topic, callback)

@classmethod
def emit(cls, topic, data, source):
event_system = cls.get_global_event_system()
return event_system.emit(topic, data, source)


def register_event_callback(topic, callback):
Expand All @@ -249,7 +347,8 @@ def register_event_callback(topic, callback):
enable/disable listening to a topic or remove the callback from
the topic completely.
"""
return StoredCallbacks.add_callback(topic, callback)

return GlobalEventSystem.add_callback(topic, callback)


def emit_event(topic, data=None, source=None):
Expand All @@ -263,6 +362,5 @@ def emit_event(topic, data=None, source=None):
Returns:
Event: Object of event that was emitted.
"""
event = Event(topic, data, source)
event.emit()
return event

return GlobalEventSystem.emit(topic, data, source)