I was exploring C++ 11 and somehow I ended up writing a simple event system. The design is kind of based on Mike McShaffry's, as seen in his book, Game Coding Complete. However, I dislike having to hardcode the hash for each of the event defined, which is quite cumbersome. So I decided to use compile-time string hashing.
Defining an event goes like this:
struct MyCustomEvent : public BaseEvent<hash<"MyCustomEvent">> { MyCustomEvent(const std::string &message) // The first parameter is for debug purposes // (since you can't pass a string as a template parameter); // I might remove it for release build : BaseEvent("MyCustomEvent", time(NULL)) , message(message) { } const std::string message; };
It's quite long and error-prone, but that can be solved using macros:
#define EVENT_DEF(__s__) struct __s__ : public BaseEvent<hash(#__s__)> #define EVENT_DEF_CTOR(__s__, ...) __s__(__VA_ARGS__): BaseEvent(#__s__, time(NULL)) // ... EVENT_DEF(MyCustomEvent) { EVENT_DEF_CTOR(const std::string &message) , message(message) { // <-- This still looks weird though... } const std::string message; };
The main feature of this event system is that it supports anonymous listeners:
auto dispatcher = std::make_shared<EventDispatcher>(); // ... // Registers an anonymous listener to listen to any MyCustomEvent events // It returns an id, which is used for removing it later auto listenerId = dispatcher->addAnonymousListener<MyCustomEvent>( [](const std::shared_ptr<MyCustomEvent> event) -> bool { printf("Message: %s\n", event->message.c_str()); // yada yada yada return true; });
And not to forget object listeners:
Triggering an event is quite straightforward:
dispatcher->triggerEvent<MyCustomEvent>("Hello, world!");
An event can also be set to trigger in the future:
// Trigger the event 5 seconds in the future dispatcher->queueEvent<MyCustomEvent>(5.0f, "Hello, future!"); // ... // You need to call the update function in your update loop // or somewhere to update the event dispatcher dispatcher->update(dt);
Removing listeners is also easy:
// This removes an anonymous listener (the id is obtained when you call addAnonymousListener) // (I really should rename this function to removeAnonymousListener...) dispatcher->removeListener(listenerId); // This removes an object listener for the event MyCustomEvent // Currently, my design makes it impossible to remove all listeners // associated with the object, but hopefully I will resolve it someday dispatcher->removeObjectListener<MyCustomEvent>(myObserver);
The source code is available in Github (the source code is still quite messy, so please forgive me ). I'm looking forward to feedbacks.
]]>I don't see the point for all of this complication when you can just write a simple signal/slot system.
Additionally, I don't see the point in having a numerical event type (that you use the hash function to compute). What's wrong with just the event name?
Lastly... the countdowns are not implemented efficiently or accurately. Instead of decrementing a lifetime of the event every update call, you should store the time when the event is meant to fire and then compare it to the passed time.
I.e.
This method avoids updating a bazillion float variables for no reason, avoids floating point errors accumulating from adding dt's all the time, allows the user to control how the time is stored, amongst other benefits. And don't use float for storing time. Even using double is questionable.
]]>Additionally, I don't see the point in having a numerical event type (that you use the hash function to compute). What's wrong with just the event name?
If you have a catch-all event handler function (though I'm not sure if my current implementation allows that), you can use a switch statement :
bool MyObserver::onEvent(const EventPtr event) { switch (event->type) { case EnemyCreatedEvent::GetType() : // yada yada break; case EnemyDestroyedEvent::GetType() : // yada yada break; } }
Without it, you have to resort to comparing strings, which can be slow if the event handler is called lots of times.
Lastly... the countdowns are not implemented efficiently or accurately.
Yes, I just realized that. Thank you very much.
]]>Uhm, I don't see the need for deriving an event class at all. All objects / entities / whatever can share the same kind of id without subclassing and using templates.
In my gui, I have a simple int NextId() {static int i = 0;return i++;} and then there is a Message class :
class Msg { public : int topic; Widget* from; int message;// bit field or value relaying pertinent information };
and then each new widget grabs an id to use in the event messaging system.
extern const int MY_WIDGET_ID; //const int MY_WIDGET_ID = NextId();// In cpp file class MyWidget : public Widget { public : int id; MyWidget() : id(MY_WIDGET_ID) {} };
And then use that id in all your messages. That separates events into topics or types, and then you give the address of the caller, and then give the message stored in a bitfield or value in an int.
]]>If you want every type to have a unique id, why don't you do this instead:
Header:
unsigned GetNextId(); template<typename T> unsigned GetId(){ static unsigned id = GetNextId(); return id; }
Source:
static unsigned curId = 0; unsigned GetNextId(){ return curId++; }
It cuts down on the amount of work you need to do to get a new type id. No extern consts necessary.
]]>Well, it just comes down to the difference between widget->GetId() and MY_WIDGET_ID. I don't think it matters but your way is good too.
]]>Uhm, I don't see the need for deriving an event class at all. All objects / entities / whatever can share the same kind of id without subclassing and using templates.
The reason I subclass is to not restrict how the message payload is stored. Having an integer variable to store my payload is very limiting. If I were to stick to one class, then I will have to do something like this:
While that doesn't seem pretty bad, what if I want to attach some custom data structure to the message? I can't just magically convert it from and to a string.
If you want every type to have a unique id, why don't you do this instead:
The problem with that approach is that for each subclass to have a unique id, I must re-implement the GetID() method for each subclass:
]]>
No you don't.
Not to mention, wherever you want to, you can get the id of MyCustomEvent via GetId<MyCustomEvent>().
]]>Static globals have the downsides of not being threadsafe and being non-deterministic relative to the ordering of your code. An alternative strategy is to cast a static member to an int and use that as the id.
]]>
An alternative strategy is to cast a static member to an int and use that as the id.
Awesome trick!
]]>