Events - m1keall1son/ofxMediaSystem GitHub Wiki
ofxMediaSystem implements a pattern very similar to the observer pattern for passing data around during runtime. The idea is to decouple object interactions from a rigid interface. The best practice is to use built-in events and create user-defined specialized ones that any object in the system can subscribe to or trigger. All of these events would pass though an event manager at the Scene scope (inter-scene communication) or the Global scope (scene-to-scene/app-to-scenes communication).
All events in ofxMediaSystem derive from IEvent
which enforces that all events have a ms::type_id_t getType()
method. The user must guarantee that the returned result from getType()
is unique. There is a convenience wrapper for automatically overriding the IEvent class with a unique type identifier. ms::Event<typename myEventType>
. Which uses the user provided type to create the unique identifier.
//the wrapper from mediasystem/events/IEvent.h
//base class for all events
namespace mediasystem {
struct IEvent {
virtual type_id_t getType() const = 0;
};
//convenience wrapper for easy overloading of pure virtual function in base class
template<typename EventType>
struct Event : IEvent {
type_id_t getType() const override { return type_id<EventType>; }
};
}//end namespace mediasystem
//see the type_id_t page in the wiki for more info on how type_id<typename> works.
...
//use the above tools to create our own event
class MyImportantEvent : public ms::Event<MyImportantEvent> {
public:
...
//our own unique data here...
};
IEventRef
is an alias for std::shared_ptr<IEvent>
, events leverage polymorphism to pass different types of data through one interface
There are a number of built-in events (some global and most scene-local).
see mediasystem/events/SceneEvents.h
and mediasystem/events/GlobalEvents.h
Scene Top-Level Events:
-
Init
- dispatched each timeSceneManager::initScenes()
orScene::notifyInit()
is called. Used for initializing data, loading resources, creating and populating entities etc. -
PostInit
- dispatched each timeSceneManager::initScenes()
orScene::notifyPostInit()
is called. Used for second stage initialization where dependencies can be resolved between scenes or systems. -
Shutdown
- dispatched each timeSceneManager::shutdownScenes()
orScene::notifyShutdown()
is called. Used for freeing of all resources before app closure, should only be called once duringofApp::exit()
, if called at all. -
Reset
- there is no built-in functionality for the this event hook. However, it should be used reset state back to the initial state within the scene.
General Scene Events:
Order of general Scene
events:
Start > TransitionIn > TransitionUpdate... > TransitionInComplete > (Update > Draw)... > SceneChange > TransitionOut > TransitionUpdate... > TransitionOutComplete > Stop
-
Start
- dispatched each timeScene::notifyStart()
is called (usually by theSceneManager
). Used for setting or resuming state each time aScene
is beginning. -
Stop
- dispatched each timeScene::notifyStop()
is called (usually by theSceneManager
). Used for saving or cleaning up state each time aScene
is ending. -
Update
- dispatched each timeSceneManager::updateScenes()
orScene::notifyUpdate()
is called. Hook for use by built-in or user-defined systems and scenes to update state. Should be called inofApp::update()
. -
Draw
- dispatched each timeSceneManager::drawScenes()
orScene::notifyDraw()
is called. Hook for use by built-in or user-defined renderers to draw stuff to the screen. Should be called inofApp::draw()
. -
SceneChange
- this event is dispatched by one scene requesting the transition to another scene.SceneManager
is adds its own delegate to each scene created through it listening for this event to trigger the transitions. Note: scenes are cross-faded bySceneManager
meaning that two scenes will simultaneously be emitting all their events during the process of a transition until the former scene'sstop()
method is called at the end of the transition
//inside some significant logic within a scene
scene->triggerEvent<SceneChange>(*scene, nextSceneSharedPtr); //by shared_ptr to the next scene
//or
scene->queueEvent<SceneChange>(*scene, "Next Scene Name"); //by next scene name
-
TransitionIn
- dispatched each timeSceneManager::changeSceneTo(Scene&)
is called or aSceneChage
event is dispatched. -
TransitionInComplete
- dispatched automatically when a scene's transition in is complete. -
TransitionOut
- dispatched each timeSceneManager::changeSceneTo(Scene&)
is called or aSceneChage
event is dispatched. -
TransitionOutComplete
- dispatched automatically when a scene's transition in is complete. -
TransitionUpdate
- dispatched during every update in which a transition is still in progress.
To receive an event, the user can use a free function, member function or a lamda (with closure capture or not) with the signature mediasystem::EventStatus(const mediasystem::IEventRef&)
. This means that all events are delivered as a polymorphic base class and the user is responsible for properly casting the event to the expected type to retrieve the data.
ms::EventStatus myEventHandler(const ms::IEventRef& event){
auto myEvent = std::static_pointer_cast<MyImportantEvent>(event);
...
//or with extra safety...
if(auto myEvent = std::dynamic_pointer_cast<MyImportantEvent>(event)){
...
}else{
ofLogError("myEventHandler") << "failed to convert event to MyImportantEvent";
return ms::EventStatus::FAILED;
}
return ms::EventStatus::SUCCESS;
}
Each even handler returns a special enum type that notifies the dispatching EventManager
of what happened in the handler and what should be done about it.
EventStatus return mechanisms:*
-
SUCCESS
- event was processed normally, continue dispatching this event to other delegates -
FAILED
- event encountered a problem, print an error and continue dispatching this event to other delegates. TODO: there should be an optional failed event processing handler in theEventManager
class to handle these -
ABORT_THIS_EVENT
- do not dispatch this event to any other delegates. -
ABORT_ALL_QUEUED_EVENTS_OF_THIS_TYPE
- do not dispatch this event to any other delegates and erase all currently queued events of this same type from the message queues. -
REMOVE_THIS_DELEGATE
- following the return of the handler, remove the delegate from the handlers list. Will receive no future events. NOTE: use this instead of callingremoveDelegate<...>(...);
inside a handler to avoid undefined behavior.* -
DEFER_EVENT
- do not dispatch this event to any other delegates and place this same message on the queue for processing in the next call toEventManager::processEvents()
/*
Copyright (C) 2017 by Sergey A Kryukov: derived work
http://www.SAKryukov.org
http://www.codeproject.com/Members/SAKryukov
Based on original work by Sergey Ryazanov:
"The Impossibly Fast C++ Delegates", 18 Jul 2005
https://www.codeproject.com/articles/11015/the-impossibly-fast-c-delegates
MIT license:
http://en.wikipedia.org/wiki/MIT_License
Original publication: https://www.codeproject.com/Articles/1170503/The-Impossibly-Fast-Cplusplus-Delegates-Fixed
*/
ofxMediaSystem makes extensive use of type-erased function delegates. These concept of these delegates is borrowed from c# and .NET and essentially act as a generic means to store a pointer to a function with a fast-as-possible call operator. They are comparable for equality as well (via the underlying pointer to the calling member or function) so they can be stored and found again by using an exact copy of the original delegate. Delegates are created through a factory function as follows:
void somefn(int arg){
...
}
class MyClass {
public:
void someOtherFn(int arg){
...
}
static void staticFn(int arg){
...
}
};
...
auto freeDelegate = SA::delegate<void(int)>::create<&somefn();
freeDelegate(12);
MyClass myInstance;
auto classDelegate = SA::delegate<void(int)>::create<MyClass, &MyClass::someOtherFn>(&myInstance);
auto dcompare = SA::delegate<void(int)>::create<MyClass, &MyClass::someOtherFn>(&myInstance);
//can check if delegates are the same
assert(classDelegate == dcompare); //can use this with std::find to find a specific delegate in a std:: container
classDelegate(1);
dcompare(2); //calls same fn on same instance
auto staticDelegate = SA::delegate<void(int)>::create<&MyClass::staticFn>();
staticDelegate(del);
int touchPoint = 1;
auto lambda = [&touchPoint](int someInt){ //can use closure capture!
touchPoint += someInt;
};
delegate<void(int)> dlambda;
dlambda = lambda; // template instantiation deduced (inferred)
dlambda(10);
assert(touchPoint == 11);
//using an alias for SA::delegate<ms::EventStatus<const ms::IEventRef&>> in ofxMediaSystem
auto myEventDelegate = ms::EventDelegate::create<...>(...);
An ofxMediaSystem EventManger
is a class that supports both single and multithreaded event queueing and immediate event triggering which will dispatch events to any added event delegate.
ms::EventManager mManager;
//add delegate to listen for messages typed `MyEventType`
mManager.addDelegate<MyEventType>(ms::EventDelegate::create<MyClass, &MyClass::myHandler>(myInstancePtr));
...
//trigger an event instantly in place
mManager.triggerEvent<MyEventType>(args...); //immediately calls MyClass::myHandler()
//or
auto event = std::make_shared<MyEventType>(args...);
mManager.triggerEvent(event); //immediately calls MyClass::myHandler()
...
//queue an event for deferred execution
mManager.queueEvent<MyEventType>(args...);
//or
auto event = std::make_shared<MyEventType>(args...);
mManager.queueEvent(event);
//sometime later
mManger.processEvents(); //calls MyClass::myHandler()
...
//to remove delegate from manager, pass the EXACT same delegate
mManager.removeDelegate<MyEventType>(ms::EventDelegate::create<MyClass, &MyClass::myHandler>(myInstancePtr));
Event managers contain a thread-safe queue as well so messages can be queued safely from anywhere.
//some thread A
auto event = std::make_shared<MyEventType>(args...);
mManager.queueThreadedEvent(event);
...
//some thread B
mManager.queueThreadedEvent<MyEventType>(args...);
...
//main thread
mManager.processEvents(); //will process all threaded events first, followed by single threaded events
NOTE: it is critical to avoid undefined behavior that an EventDelegate not outlive its instance. It is a best practice to call removeDelegate
in destructors or shutdown or stop methods to avoid this dangling pointer issue.
ofxMediaSystem uses its Singleton<typename>
util to give global access to a static EventManager
. This event manager is convenient to use for events triggered or queued from objects outside of any scene, like a hardware interface or a network connection or some library integration, etc. The global event manager ties itself into ofApp::update
via the ofEvent automatically.
triggerGlobalEvent<...>(...);
queueGlobalEvent<...>(...);
addGlobalEventDelegate<...>(...);
removeGlobalEventDelegate<...>(...);
//etc.
NOTE: it is critical to avoid undefined behavior that an EventDelegate not outlive its instance. It is a best practice to call removeGlobalEventDelegate...
when an object is going out of scope.
Another important note is that because the global event manager is global, any member of any scene that subscribes to it will receive its messages regardless of whether or not it is the current scene. For that reason it is best practice to subscribe to global events in a scene's Start
moment and unsubscribe in a scene's Stop
moment.