Skip to main content
GameDev.net gamedev.net
🔒 Locked

Events, EventManagement and EventListeners - Suggestions?

Started by sjaakiejj Aug 8, 2011 at 10:39 AM 33 replies 6.3k views
Original Post
sjaakiejj
sjaakiejj
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.
Hodgman
Hodgman
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.
GorbGorb
GorbGorb
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
}

speciesUnknown
speciesUnknown
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.
Don't thank me, thank the moon's gravitation pull! Post in My Journal and help me to not procrastinate!
Frank Taylor
Frank Taylor
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?
GorbGorb
GorbGorb
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).
sjaakiejj
sjaakiejj
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!
GorbGorb
GorbGorb
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?
speciesUnknown
speciesUnknown

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).


Your example pretty much demonstrates exactly what I would do. Template based message filtering is one of the things I tried but again, for simplicity i recommend the message ID + casting method. The amount of boilerplate required to handle messages in a simple scenario seems to be increased, but as your number of message types increases, your template based version will use huge amounts of memory and be considerably complicated. I'm not sure how you would implement decorators, relays, or other types of message handlers with the templated version either - would it require you to do something like,
MessageHandler
huh.gif
Don't thank me, thank the moon's gravitation pull! Post in My Journal and help me to not procrastinate!
sjaakiejj
sjaakiejj

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?



I'm definitely familiar with templates. From your comment on my code, that's indeed something I thought about, and the main issue for me would've been to somehow enforce a certain order in the list. I'm interested in the first snippet you posted, which I'm trying to understand but I have no real experience with variadic templates, so I'll try to read up on that.

I'm not sure if my compiler supports it though, and if it doesn't I'll probably go with an easier solution such as the one suggested by speciesUnknown, and detailed by you.
GorbGorb
GorbGorb

Your example pretty much demonstrates exactly what I would do. Template based message filtering is one of the things I tried but again, for simplicity i recommend the message ID + casting method. The amount of boilerplate required to handle messages in a simple scenario seems to be increased, but as your number of message types increases, your template based version will use huge amounts of memory and be considerably complicated. I'm not sure how you would implement decorators, relays, or other types of message handlers with the templated version either - would it require you to do something like,
MessageHandler
huh.gif



Yes, the memory is indeed something that bugs me... In the situation I used it, the types of all listeners were static, so I didn't have to use virtual functions there.
What exactly do you mean by MessageHandlers? Subclassing existing messages? (sry, I'm not very familiar with patterns since I'm still in school)
Frank Taylor
Frank Taylor
My Engine revolves around its GUI. I'm closer to electing the publish/subscribe model for message handling. The Pubscriber is implemented as a specialize entity component (module).

Pubsub.png

1. Create a Channel.
2. Build Subscriber List on Channel; Channel Maintains list of of Subscribers.
3. Build Message.
4. Publisher Submits Message to Channel.
5. Channel Pushes Message to Subscriber's Message Queue.
6. Clear Channel.
sjaakiejj
sjaakiejj

I'm closer to electing the publish/subscribe model for message handling.

Pubsub.png

1. Create a Channel.
2. Build Subscriber List on Channel; Channel Maintains list of of Subscribers.
3. Build Message.
4. Publisher Submits Message to Channel.
5. Channel Pushes Message to Subscriber's Message Queue.
6. Clear Channel.


I quite like this approach as well, though I'm slightly unclear about Channels. From what I gather you can make a comparison to a forum by saying:

Publishers are posters (Me in this topic)
Messages are topics
Channels are the different sections on the forum (Game Programming, Graphics Programming, etc.)

A subscriber then simply looks at the sections he's interested in, and selects the messages that he can use. If that analogy is about right, then I don't believe this approach should be very hard to implement. You could have a templated channel that accepts messages of a certain type that is specified in the template. Publisher submits them to the channel, the Channel forwards them to the subscribers.
speciesUnknown
speciesUnknown

[quote name='speciesUnknown' timestamp='1312983628' post='4847151']
Your example pretty much demonstrates exactly what I would do. Template based message filtering is one of the things I tried but again, for simplicity i recommend the message ID + casting method. The amount of boilerplate required to handle messages in a simple scenario seems to be increased, but as your number of message types increases, your template based version will use huge amounts of memory and be considerably complicated. I'm not sure how you would implement decorators, relays, or other types of message handlers with the templated version either - would it require you to do something like,
MessageHandler
huh.gif



Yes, the memory is indeed something that bugs me... In the situation I used it, the types of all listeners were static, so I didn't have to use virtual functions there.
What exactly do you mean by MessageHandlers? Subclassing existing messages? (sry, I'm not very familiar with patterns since I'm still in school)
[/quote]

Don't worry about design patterns. By message handlers, I was talking about the objects which receive messages and do things with them. There are a variety of different things which might be done with a message.

One handler may directly act when it receives a message - for example, your main game class may exit the game when it gets a certain message.

Another type of handler you might want to use is a filter, which only passes certain message types through.

A third type of handler you might have is a relay - a message handler which, when it receives messages, passes that message to a list of other handlers. For example, if you have an AI controller which has a list of AI creatures which are hunting the player. When one of these AI creatures sees the player, it needs to inform the others where it saw them, so it might send a message to the relay which then sends it to all the appropriate AI creatures in that area.

You might want to put a filter on top of your relay, to cut out messages which are never going to be handled by the AI creatures, thereby cutting out some of the overhead of handling messages which are irrelevant.

A flexible message system enables you to implement all these different types of message handling routines. What is important is not how cool or sophisticated the low level message handing mechanism is, but rather, what high level functionality you need, especially filters and relays.

I got frustrated with template based message relays because I found that I spent a silly amount of time debugging them. C++ does not have an efficient RTTI mechanism, or any kind of dynamic dispatch, so I've found that manual dynamic dispatch and manual type testing is the simplest solution.
Don't thank me, thank the moon's gravitation pull! Post in My Journal and help me to not procrastinate!
sjaakiejj
sjaakiejj
If I were to go with the approach that speciesUnknown mentioned, I'd prefer using a more type-safe pointer than a void. I looked at which functionality Boost might offer, and I came across boost::any and boost::variant, either of which seem to do the things I want them to. Are there any particular disadvantages over using them instead of void*?
Frank Taylor
Frank Taylor

I quite like this approach as well, though I'm slightly unclear about Channels. From what I gather you can make a comparison to a forum by saying:

Publishers are posters (Me in this topic)
Messages are topics
Channels are the different sections on the forum (Game Programming, Graphics Programming, etc.)

A subscriber then simply looks at the sections he's interested in, and selects the messages that he can use. If that analogy is about right, then I don't believe this approach should be very hard to implement. You could have a templated channel that accepts messages of a certain type that is specified in the template. Publisher submits them to the channel, the Channel forwards them to the subscribers.

sjaakiejj,

I edited the post to add some more info: My Engine revolves around its GUI. The Messaging System called Pubscriber is implemented as a specialized entity component (module). Based on the wiki publish/subscribe article, the model provides a mechanism for selecting messages for reception and processing called message filtering. There are two common forms of filtering: topic-based and content-based:

  • topic-based system, messages are published to "topics" or named logical channels. Subscribers in a topic-based system will receive all messages published to the topics to which they subscribe, and all subscribers to a topic will receive the same messages. The publisher is responsible for defining the classes of messages to which subscribers can subscribe.
  • content-based system, messages are only delivered to a subscriber if the attributes or content of those messages match constraints defined by the subscriber. The subscriber is responsible for classifying the messages.
  • topic-content hybrid system; publishers post messages to a topic while subscribers register content-based subscriptions to one or more topics.
    I'm pursuing topic-content hybrid filtering. Entities are components-based and the Messaging System (Pubscriber) Components {Publishers, Subscribers, Channels} are automatically added to an entity upon creation to handle inter-communications between other Components.

    Pubscriber components are inspired by the event-driven architecture event-flow layers. The Publisher (Generator), Channel, Subscriber (Processor). The Subscriber component maintains a message queue and processes the messages.
GorbGorb
GorbGorb

Don't worry about design patterns. By message handlers, I was talking about the objects which receive messages and do things with them. There are a variety of different things which might be done with a message.

One handler may directly act when it receives a message - for example, your main game class may exit the game when it gets a certain message.

Another type of handler you might want to use is a filter, which only passes certain message types through.

A third type of handler you might have is a relay - a message handler which, when it receives messages, passes that message to a list of other handlers. For example, if you have an AI controller which has a list of AI creatures which are hunting the player. When one of these AI creatures sees the player, it needs to inform the others where it saw them, so it might send a message to the relay which then sends it to all the appropriate AI creatures in that area.

You might want to put a filter on top of your relay, to cut out messages which are never going to be handled by the AI creatures, thereby cutting out some of the overhead of handling messages which are irrelevant.

A flexible message system enables you to implement all these different types of message handling routines. What is important is not how cool or sophisticated the low level message handing mechanism is, but rather, what high level functionality you need, especially filters and relays.

I got frustrated with template based message relays because I found that I spent a silly amount of time debugging them. C++ does not have an efficient RTTI mechanism, or any kind of dynamic dispatch, so I've found that manual dynamic dispatch and manual type testing is the simplest solution.


What do you think about this? Have you faced problems where this is not flexible enough?
(I've not yet implemented it, I first want to know what you think about it)


struct MA{};
struct MB{};
struct MC{};

typedef Set< MA , MB , MC > Messages;

int main()
{
ListenerA first_listener;
ListenerB second_listener;
Relay< Messages > relay;
relay.addListener( first_listener );
relay.addListener( second_listener );
//all message sent to relay are forwarded to first_listener and second_listener
Filter< Messages , MessageB , MessageC > filter( relay ); //sends all messages except MessageB and MessageC a listener ( here: relay )
filter.listen( MessageA() ); //first_listener and second_listener receive MessageA
filter.listen( MessageB() ); //message is filtered away
};


I really like the idea of filters and relays... how do you use them?
speciesUnknown
speciesUnknown

...

I really like the idea of filters and relays... how do you use them?


Basically, anything which can handle messages must implement an interface (or in c++, an abstract base class) IMessageHandler. Here is some psudocode:



// lets say this is your enum of message types. There are many other data types you can use, but enums are the simplest. You could also use some form of type ID, or use multiple enum types to categorise your messages - this example uses only 1.

enum MessageType
{
GameExit,
AIPlayerSeen,
AIPlayerKilledNPC,
AIPlayerDied
}

// this is the interface:
interface IMessageHandler
{
public:
void handleMessage(MessageType type, void * data) = 0;
}

// this is one possible message filter, to filter out only AI messages. This could also be done via a range comparison.
class AIMessageFilter : public IMessageHandler
{
protected:
IMessageHandler * output;

public:

AIMessageFilter(IMessageHandler * output)
: output(output) { }

void handleMessage(MessageType type, void * data)
{
switch(type)
{
case (AIPlayerSeen):
case (AIPlayerKilledNPC):
case (AIPlayerDied):
this->output->handleMessage(type, data);
break;
default:
//
break;
}
}
}




edit: ok, this isnt actually psudocode, but ill pretend its psudocode, as it probably won;t compile xD
Don't thank me, thank the moon's gravitation pull! Post in My Journal and help me to not procrastinate!

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.