Implementing an EventManager

Started by
5 comments, last by NicoG 15 years, 8 months ago
Hi Folks. I have no problem with the class right now, I just wonder if it is really that easy or if I can get in Performance-troubles one day. :D Ok to the case. Behold some code:

class IEventData
{ 
};

class IEventReceiver
{
   void OnEvent(int evt, IEventData* data) = 0;
};

struct Events
{ 
    int m_event;
    std::list<IEventReceiver*> m_records;
};

class EventManager
{
    // Registers an Event. This adds a new List to m_eventList.
    void RegisterEvent(int evt);
    // Removes the Event and all listeners(receivers) from the List
    void UnregisterEvent(int evt);
    // Registers an Receiver. A receiver can be added multiple times for different events to receive more than one event.
    void RegisterReceiver(int evt, IEventReceiver* rec);
    // Unregisters the receiver for the specified event.
    void UnregisterReceiver(int evt, IEventReceiver* rec);

    // Walks thorugh our list and searches for the event. If the event was 
    // found, he calls all listed receivers and calls them. IEventData must 
    // be casted with reinterpret_cast to the type of event-data which is
    // passed, e.g. KeyDownEvtData etc.
    void Notify(int evt, IEventData* data = NULL);

    // Holds all events and their receivers in a list.
    std::list<Events> m_eventlist;
};
Ok, this is the code in short. What do you think about it? Is there another way to achive this? Is this solution eating too much performance one day? What would you change to maximize the performance? Thanks in advance for all your advices! regards Nico edit: Added IEventReceiver-Definition
If you say "pls", because it is shorter than "please", I will say "no", because it is shorter than "yes"
http://nightlight2d.de/
Advertisement
You need to make some functions virtual in order for this to work. Your design also breaks type safety by doing an upcast from IEventData to it's appropriate subclass. You also store key-value pairs in a list (std::list<Events> eventlist), while the appropriate solution would be to use an std::map<int, std::list<IEventReciever*> >. std::list searches are slower than map searches.

However, writing this kind of an event system is a waste of time. There's libsigc++ (used by GTKmm) or boost.signals you could use. They are widely available and widely used. Along with some cool functors (boost.bind, etc) you can have a system that's way more versatile and nicer to use that a Java-ish interface based design that's about as flexible as a crowbar.

-Riku
Thanks for your reply.
Yeah the virtuals are done correctly here in my real code, i just typed this fast into the window to break the code down to the essential stuff. Didn't want to bore you with my doxygen-tags. So the code works.

But thanks for the Tip with the map... my old C-School-Knowledge strikes again here... sorry. I'll change for map.

About ligsigc++ and boost:
I don't like boost. Its too big and too much work to get it into my programs in my mind.
I tried libsigc++ also, but its just to much for the little functionality I need. It seemed for me at least.
My class is rather easy to use:
// from the class where the event happens:EventManager::RegisterEvent(EVT_MYEVENT);//when the event happens:MyEventData* data = new MyEventData("Hello Event");EventManager::Notfiy(EVT_MYEVENT, data);// In the class where you want to receive the event:EventManager::RegisterReceiver(EVT_MYEVENT, this);void OnEvent(int evt, IEventData* data){    switch(evt)   {    case EVT_MYEVENT:         foobar(); break;   }}


Quote:
Your design also breaks type safety by doing an upcast from IEventData to it's appropriate subclass.

I understand the issue, but how would you solve this?
Passing a void-Pointer and reinterpreting it would basically be the same huh?
Is there a way to solve this?
Thank you.
regards
If you say "pls", because it is shorter than "please", I will say "no", because it is shorter than "yes"
http://nightlight2d.de/
That's... complex. Verily.

Let's look at the core functionality that you wish to implement: you have "receivers" which are functions that are called in response to an "event", along with the corresponding data. The receivers connected to a certain type of event are dynamic (that is, you can add and remove them).

Therefore, you're looking at an interface like this one (using ML types):

type 'a eventReceiver = 'a -> unittype 'a event val add_receiver    : 'a event -> 'a eventReceiver -> unitval remove_receiver : 'a event -> 'a eventReceiver -> unitval notify          : 'a event -> 'a               -> unit


That's about all you need to manipulate events. Of course, implementing this in C++ is a tad harder than implementing it in ML. You have to make some adaptations. However, as a whole, I'd go with this:

template <typename T> class IEventReceiver {public:  virtual void operator() (const T&) = 0;  typedef boost::shared_ptr< IEventReceiver<T> > shared_ptr;};template <typename T> class Event{public:  typedef typename IEventReceiver<T>::shared_ptr receiver_type; private:  struct call   {    const T &t;    call(const T& t) : t(t) {}    void operator() (const receiver_type &r) const { (*r)(t); }  };  std::vector<receiver_type> receivers;public:  void Add(const receiver_type &r)  {    receivers.push_back(r);  }  void Remove(const receiver_type &r)  {    receivers.remove(r);  }  void Notify(const T& t)   {    std::for_each(receivers.begin(),receivers.end(), call(t));  }};


An example using your own example but this technique.

// from the class where the event happens:struct EventManager{  Event<MyEventData*> myEvent;};//when the event happens:MyEventData* data = new MyEventData("Hello Event");eventManager.myEvent.Notify(data);// In the class where you want to receive the event:class MyEventReceiver : public IEventReceiver<MyEventData*>{public:   void operator()(MyEventData* data)  {      foobar();  }};eventManager.myEvent.Add(new MyEventReceiver);


That's it: you get the same amount of dynamism yet also type-safety and compile-time checking.
Quote:Original post by NicoG

I understand the issue, but how would you solve this?
Passing a void-Pointer and reinterpreting it would basically be the same huh?
Is there a way to solve this?
Thank you.
regards


The way boost solves it, for example.

Or by using typed events. Or through function pointers. It depends, but there's plenty of options.

Part of the problem lies in existence of "Manager". Why manager? Why not just have an event that you can fire?

struct Event {  void AddListener(EventListener *);  void RemoveListener(EventListener *);  void fire();};


Of course, eventually events will need some parameters.
template < class P1 >struct EventListener{  virtual void onEvent(P1 p1) = 0;};template < class P1 >struct Event {  void AddListener(EventListener<P1> *);  void RemoveListener(EventListener<P1> *);  void fire(P1 p1);};


But obviously, this will result in conflicts (all handlers are called onEvent), and requires you to inherit from event listeners.

So you convert the above code to function pointers, and end up what every other even handling library does (sometimes they are called signal/slot, there's for example sigslot library). They also take care of automatic registration and deregistration, and more. I won't derive from here, since there's simply too many libraries out there that have beaten this horse into a pulp already (events, callbacks, signal slot, publisher/observer, same thing, different nuance).

But the key benefit is compile-time checked type safety. While good for coding, it also allows more optimizations to be performed.

Quote:if I can get in Performance-troubles one day


Of course you can. If you need to dispatch 5 billion events per second. Or 20. Or you're running a debug build. Or you subscribe 500,000 listeners to 500,000 events. Or you are running on 486. Or you are on embedded platform. So many situations....
Thanks for your replies!
@Antheus
I use a Manager, because I think it is more performant to use one global list than one list in every event. And I am able to register many classes for just one event, or just one class for all events, they will receive them all.

@ToohrVyk
I tried to avoid templates where possible, because this whole code lives in a dll. So the user should be possible to add new events without recompiling the dll (its OpenSource anyway). And it breaks up my intend of using this class. I don't want the user to need to make a new class for every event he records. I want the user to be able to have one function which receives all events or to do multiple. Just like the user wants to do. If you have to use multiple classes, there is always trouble. Thats my experience. I have already tried boost::signals some day. But it was too complex in my mind. But I see that intend flying out of the window right now.. :(.

Quote:
Of course you can. If you need to dispatch 5 billion events per second. Or 20. Or you're running a debug build. Or you subscribe 500,000 listeners to 500,000 events. Or you are running on 486. Or you are on embedded platform. So many situations....

Hehe, you're right. Well, lets see what the profilers say when the whole lib is done....

Ok, I'll try your advices and I will see how they will fit into the whole thing and if I am comfortable with it.
Thanks for now, I'll report back with some results. I am @work 1 hour to go here till weekend :D.
regards
If you say "pls", because it is shorter than "please", I will say "no", because it is shorter than "yes"
http://nightlight2d.de/
HI there.
I have a solution for now.
A friend of mine told me, that my EventHandle looks similar to the EventHandling in Irrlich Engine.
So I have looked at it and they solved the problem.
I will add a class, which holds all possible events of my library and 4 different structs for custom event-data which can be generated by the user of the library.
This will be implemented as singleton and the eventhandler will transport it via const-reference through the events.
I think this is the best solution.
So the idea is "stolen" from Irrlicht, but I think there is nobody
who sues me for that(hopefully)... :D
But its a way to keep the code small and simple.
Thanks for all your help.
If you say "pls", because it is shorter than "please", I will say "no", because it is shorter than "yes"
http://nightlight2d.de/

This topic is closed to new replies.

Advertisement