Events, EventManagement and EventListeners - Suggestions?

Started by
33 comments, last by freddyscoming4you 12 years, 8 months ago

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<MessageA, MessageB, MessageC, MessageD, MessageE, MessageF, MessageG, MessageH, MessageI, MessageJ, MessageK, MessageL>
huh.gif
Don't thank me, thank the moon's gravitation pull! Post in My Journal and help me to not procrastinate!
Advertisement

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.

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<MessageA, MessageB, MessageC, MessageD, MessageE, MessageF, MessageG, MessageH, MessageI, MessageJ, MessageK, MessageL>
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)
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.

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.

[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<MessageA, MessageB, MessageC, MessageD, MessageE, MessageF, MessageG, MessageH, MessageI, MessageJ, MessageK, MessageL>
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!
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*?

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.

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?

...

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!

This topic is closed to new replies.

Advertisement