Jump to content
  • Advertisement
Sign in to follow this  
sjaakiejj

Events, EventManagement and EventListeners - Suggestions?

This topic is 2528 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi,

I've been wondering about this for around a week now, and I'm still not quite sure how to solve it. I come up with different ideas every once in a while, but none of them seem to be "The Best" way of doing it, and some have downright ugly disadvantages.

So here's my question. I've got an Event Management System that allows EventListeners to register with the EventManager, and that allows any class in the system to dispatch an Event. These events can be of an arbitrary number of different types, including but not limited to Movement Events (by both the Player and AI), State Change Events, Game Exit Events and Interaction Events. All of these events need their own specific body, for instance, a Movement Event might need a source (The object that does the moving) and a direction vector. A State Change Event needs to know about the next state. Game Exit Event doesn't need any information, and an Interaction event needs a source and a destination object.

This diversity forms a problem for me, as the first structure I thought of was having an abstract class "Event" and an abstract class "EventListener". EventListener has a method called "Notify" and "handleEvents", and any class that needs to be an event listener can simply inherit those methods. Any Event will just be a subclass of the "Event" class. Having simply one notify method would mean that Casting has to be used, as far as I can see, and I want to avoid that.

A solution to casting I thought of is to have many different "Notify" methods, one for each event. Each of these would have an implementation in the EventListener interface (though the implementation would be to just do nothing with the event), and inheriting classes would simply override the one they're interested in. This would leave the interface rather bloated, with a lot of code, and not a lot of flexibility.

Other options I came across were void pointers, which again imply casting; Further abstracting the Event objects, making a more defined structure and being able to split the events at a higher level, but this would make notification more complicated due to code duplication.

I'd love to hear how you guys approach this problem, as I'm running out of ideas. I'm not looking for a "Quick Hack" as you might've already figured out, as I want the fundamental design to be solid and high quality for this project.

Share this post


Link to post
Share on other sites
Advertisement
Do you really need a general event system in the first place? A regular function call is an event.

If you do need to arbitrarily plug callbacks into each other in a completely ad-hoc manner though, check out boost::function, it will help a lot.

Share this post


Link to post
Share on other sites
If your compiler supports variadic templates and you don't care about recompiling when adding/changing events you could use something like this:



template< class ...MessageTypes >
class MessageListener;

template<>
class MessageListener<>
{
};
template< class Head , class ...Tail >
class MessageListener< Head , Tail... >
: public MessageListener< Tail... >
{
public:
virtual void listen( const Head &Message )
{
}
};




Then you register messages:


struct MessageA
{
//....
};
struct MessageB
{
//...
};
//I don't think MessageA and MessageB have to be complete types... not completely sure though, try it
//(if so, just forward declare and include a definition where you need it to avoid recompiling)

typedef MessageListener< MessageA , MessageB > Listener;




and use it like this:



class SomeSpecificListener
: public Listener
{
public:
void listen( const MessageA & ) //override
{
//...
}
};

int main()
{
SomeSpecificListener special_listener;
Listener &base = special_listener;
base.listen( MessageA() ); //calls your override
base.listen( MessageB() ); //doesn't do anything
}

Share this post


Link to post
Share on other sites
For simplicity, I recommend you avoid the more complex implementations, and simply go with a system of void pointers, a bunch of different structs for each message, and an enum for the type of message. This method is fast, simple, and robust, and you can handle any dynamic dispatch manually using a switch statement.

For many years I messed about with different message systems, but in the end, I settled for this.

Share this post


Link to post
Share on other sites
For simplicity, I recommend you avoid the more complex implementations, and simply go with a system of void pointers, a bunch of different structs for each message, and an enum for the type of message. This method is fast, simple, and robust, and you can handle any dynamic dispatch manually using a switch statement.

For many years I messed about with different message systems, but in the end, I settled for this. [/quote]Do you have any code/pseudo-code?

Share this post


Link to post
Share on other sites
I think he means something like



class Listener
{
public:
virtual void listen( unsigned int MessageID , void *AdditionalInformation )
{
}
//or pure virtual
};

struct MessageA
{
const static unsigned int ID = 0; //or put IDs in a seperate enum
//no data
};
struct MessageB
{
const static unsigned int ID = 1;
char data[10];
};

class SomeListener
: public Listener
{
public:
void listen( unsigned int ID , void *AdditionalInformation )
{
switch( ID )
{
case MessageA::ID:
//....
break;
case MessageB::ID:
MessageB &message = *reinterpret_cast< MessageB* >( AdditionalInformation );
//.....
break;
}
};
};


int main()
{
SomeListener derived;
Listener &base = derived;
base.listen( MessageA::ID , &MessageA() );
MessageB message_b;
//set data
base.listen( MessageB::ID , &message_b );
}



I don't see how this approach is simpler then the one I showed, it definitely has more boilerplate than my example.
I may be wrong, but I think the template solution is faster:

template version overhead:
- virtual function call with 1 parameter: constant time

cast - version:
- virtual function call with 2 parameters: constant time
- switch statement: depending on compiler optimizations either constant time (jump table) , logarithmic (binary search) or linear (naive search)

However, since the compiler has to create a vtable with entries for each message for each specific listener, the template version consumes probably more memory, increasing with both the number of your messages and the number of your listeners (classes).

Share this post


Link to post
Share on other sites
First of all, thanks for all the responses, I'll have a look at all the given suggestions.

For simplicity, I recommend you avoid the more complex implementations, and simply go with a system of void pointers, a bunch of different structs for each message, and an enum for the type of message. This method is fast, simple, and robust, and you can handle any dynamic dispatch manually using a switch statement.

For many years I messed about with different message systems, but in the end, I settled for this.



In fact, I'm trying a system at the moment which is similar to this. I have it structured as such:




class Message{
public:
enum Subject{
MOVEMENTEVENT,
INTERACTIONEVENT,
STATECHANGEEVENT,
STATEPAUSEEVENT,
STATEPUSHEVENT,
GAME_EXIT_EVENT
};

struct Body{
std::string name;
union Object{
GameObject* object;
Vector3 vector;
State* state;
};
};


public:
Message(Subject lSubject, std::list<Body>* lBody = 0);
~Message();

virtual Subject getSubject();
virtual std::list<Body>* getBody();

private:
std::list<Body>* body;
Subject subject;
};


Then in the code itself I simply run a switch statement, checking if this particular listener got any messages. It's rather similar to a Map based approach, as well as the approach you mentioned. The plus-side for me is that I don't need casting, the downside is how many lines it takes to create a message. I haven't really looked into performance yet, but so far it seems to work. What do you guys feel about this?

Thanks again for your time and responses!

Edit:
GorbGorb, I really like your approach, though I really need to take some time and figure out why it works. I'm not familiar with the coding approach you've taken, so any additional explanation would be great!

Share this post


Link to post
Share on other sites
Are you familiar with templates? If not, you should probably first look into a tutorial about them, and afterwards something about variadic templates (c++0x feature).
If you don't want to spend many hours trying to understand them (well, it took me that long... maybe you learn faster),
you should probably just use the void pointer solution.
To your code: It feels a little bit bloated to me. Think of how to receive a message:
- check subject with getSubject()
- iterate over the std::list from getBody()
- check which actual data is in an element with something like "if( name == "..." ) "
- access the actual data with something like "iterator->object.object"
(I assume you want an instance of that union in your struct?)
- probably even more ifs and whiles for actually processing the message

I don't think you'll be happy with that kind of hard to understand code.
suggestions:
- why do you need a std::list with bodies for each event?
- why do you store the type of a body in a string? Enums are much faster and just as easy to read (maybe even better)

PS: Are you interested in the first or the second code snippet I posted?

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!