Archived

This topic is now archived and is closed to further replies.

SeanHowe

Messaging System

Recommended Posts

Hey everyone, I''m writing a gem on messaging systems for Game Programming Gems 2, so I thought I''d drop in here and check in with you guys about what you think about them, broaden my spectrum. So, where do you use them? How do you implement them? Anything else you''d like to say about them? Thanks for any input!

Share this post


Link to post
Share on other sites
what do you mean exactly? passing messages to 'things' (ie entities) in your game? like telling an entity to remove itself (for example)???

if so, i have a template class called CMsgTarget (for instance):

      
template<class T> class CMsgTarget
{
typedef (T::*MemberFunction)(const char* arguments);

private:
struct MessageResponse
{
const char* messageIdentifier;
MemberFunction responseFunc;
};

MessageResponse m_responses[];

public:
void PostMessage(const char* msgID, const char* args)
{ /* find proper entry in m_responses and call it's responseFunc */ };

};


then each class that is to recieve message contains one of these classes:

        
class Entity
{
/* ... */

static CMsgTarget<Entity> cmd;

void Remove(const char*);
void Foo(const char*);
};


then in the implementation file:

        
CMsgTarget<Entity> CMsgTarget<Entity>::m_responses[] = {
{"remove", Remove},
{"foo", Foo},
{NULL, NULL} //this is here so when searching, we know when to stop.

};


then, to post a message, we do this:

        
Entity someEnt;

someEnt.cmd.PostMessage("remove", "");



this is off the top of my head, so expect errors, and in the real code, there is more functionality (like climbing class hirearchies to let a base class handle the message if the derived class doesnt, etc) - but this is the general idea.

hope this is what you meant, this took like 10 mins to write
it not, oh well - i got nothing better to do

Edited by - Yorvik on January 30, 2001 7:10:01 PM

Share this post


Link to post
Share on other sites
That''s exactly what I meant, actually! Thanks a bundle! Any more posts like that by anybody else would me much appreciated as well.

Share this post


Link to post
Share on other sites
I have a class called CGameEngine that has a method:

    
CGameEngine::ProcessMessage(int cmd, void* params, void* returns);


I then wrote a wrapper around the Windows Messaging system that includes the following global functions:


PostEngineMessage(int which, int command, void* params, void* returns )
{
return PostMessage( GetWindowHandle(which), command, params, returns );
}


And one for SendEngineMessage() -> SendMessage()...

I have an EngineWinProc() function that all Engine components have assigned to their respective types.


BOOL EngineWinProc( command, params, returns )
{
CGameEngine* Engine = (CGameEngine*) GetWindowLong(..., USER_DATA);
Engine->ProcessMessage(command, params, returns);
}



Each engine type (CGameEngine*) either does or does not know what Protected Method to call based on the command and passes that method the Params and Returns. Params are used for parameters passed in and returns is used to return the results back to the original caller.

Pretty complicated really - but only moderately slower than running directly through windows messaging system (5-8% slower due to v-table lookups and class hierarchy) and ALL commands are encapsulated within the respective classes.

If you are interested in the source, email me - address is in profile - and I will send it to you as an example if you like.



Regards,
Jumpster


Semper Fi

Edit:

This provides the ability to do something such as this and seems to work pretty good for some less cpu intensive games:

  

// Post on the queue, play when you get the chance...

PostEngineMessage( itSOUND, WF_PLAYSOUND, &soundtoplay, NULL);

// Send right now and immediately return...

SendEngineMessage( itINPUT, WF_READDEVICED, &device_data, NULL);





Edited by - Jumpster on January 30, 2001 8:38:52 PM

Share this post


Link to post
Share on other sites
quote:

If you are interested in the source, email me - address is in profile - and I will send it to you as an example if you like.



same goes for me, share the wisdom n''all

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Personally, I prefer to stay away from these for a few reasons.
1. Type safety. Passing arguments with the message usually involves a void* or a CCommandParamBase* or a union of some kind. It''s error-prone since compiler cannot ensure that correct data is passed to the handler function. You can make up for it somewhat by coding preconditions in every message handler, but I''d rather not use void* at all and let the compiler do type checking.
2. Once you have central message dispatcher any object can talk to any other object. This breaks encapsulation and makes it harder to figure out execution flow.

There''s another (better, IMHO) way to sending/receiving events. COM calls it event source/sink, there are other names: observer pattern, listener pattern, publisher-subscriber, etc. Look it up if you are interested.

Share this post


Link to post
Share on other sites
AP post above is mine - just in case you want to know where send flames Sorry ''bout that - forgot to enter username/pwd.

Share this post


Link to post
Share on other sites
quote:
Original post by Anonymous Poster

Personally, I prefer to stay away from these for a few reasons.
1. Type safety. Passing arguments with the message usually involves a void* or a CCommandParamBase* or a union of some kind. It''s error-prone since compiler cannot ensure that correct data is passed to the handler function. You can make up for it somewhat by coding preconditions in every message handler, but I''d rather not use void* at all and let the compiler do type checking.
2. Once you have central message dispatcher any object can talk to any other object. This breaks encapsulation and makes it harder to figure out execution flow.

There''s another (better, IMHO) way to sending/receiving events. COM calls it event source/sink, there are other names: observer pattern, listener pattern, publisher-subscriber, etc. Look it up if you are interested.



I absolutely agree with item number one above. In fact, that''s why I am phasing it out of my system. It works great though for a small game - like a magic numbers type game - where the required data structures are minimal.

As for number two: I tend to disagree. The main reason I did things this way was because I didn''t want objects to rely on each other''s existance. For example: A CAnimation class must know that the gfx/sound class exists in some way. Either as global variables - in which an assumption is made that the variable names are never changed or the pointer to the gfx/sound class is otherwise fed to the CAnimation class - perhaps on instantiation. Rather than passing pointers to the gfx/sound etc class - I chose this method.

This provided the benefit that any class does not realy on the fact that any other class (for this purpose - engine) exists. In effect the programmer can say, "I don''t know if there is a sound engine, but if there is - Play this sound!"

As I said, it works wonderfully for simpler games. But the additional overhead required and complexity (due to type conversion to void*) make it unwieldly for larger games.

Regards,
Jumpster

Share this post


Link to post
Share on other sites
quote:

As I said, it works wonderfully for simpler games. But the additional overhead required and complexity (due to type conversion to void*) make it unwieldly for larger games.



there is no need to do any void* casting - a clever ''variant'' class could (and should) be used instead. void*''s make me ill. the variant class would use templates heavily so no actual conversions would happen and there would be no speed loss.

Share this post


Link to post
Share on other sites
quote:
Original post by Yorvik

[quote]
As I said, it works wonderfully for simpler games. But the additional overhead required and complexity (due to type conversion to void*) make it unwieldly for larger games.



quote:

there is no need to do any void* casting - a clever 'variant' class could (and should) be used instead. void*'s make me ill. the variant class would use templates heavily so no actual conversions would happen and there would be no speed loss.



I always say, "You learn something new everyday, or else you ain't living!"

Teach me oh wise one... Please provide an example.

Converting a function that takes a void* to a function that takes a variant for I do not know Obie Won...

Regards,
Jumpster

Edited by - Jumpster on February 1, 2001 10:18:22 PM

Share this post


Link to post
Share on other sites
class CAvatar: public CWorldObserver
{
}

Obviously needs a lot of work, but I was thinking that Avatar would ''emit'' events into the world, and the world would decide what other avatars could possibly notice the event and then send notification of the event via the WorldObserver.

The instance class gets first crack at handling the event, and then it''s passed up the derived "Chain of Command" until it''s either handled or discarded.

Magmai Kai Holmlor
- The disgruntled & disillusioned

Share this post


Link to post
Share on other sites
quote:

I always say, "You learn something new everyday, or else you ain''t living!"

Teach me oh wise one... Please provide an example.

Converting a function that takes a void* to a function that takes a variant for I do not know Obie Won...



there was a thread about this kind of thing a few weeks back:

http://www.gamedev.net/community/forums/topic.asp?topic_id=36449

scroll down to wilkas big reply.

Share this post


Link to post
Share on other sites
quote:

a clever ''variant'' class could (and should) be used instead


I admit, variant is a bit better then void* but you still are not guaranteed that you handler receives correct data:
  
//

// code in the message source

//


// store a long in the variant

VARAINT var;
var.vt = VT_I4;
var.lVal = 42;

SendCommand(MY_MESSAGE, var);

//

// code in the message handler

//

OnCommand(message, VARIANT param)
{
if(MY_MESSAGE== message)
{
// handler expects a BSTR

DoSomething(param.bstrVal);
}
}

There’s an obvious bug in the code above but it would compile just fine because VARIANT hides exact type from the compiler. Yeah, you can check it at runtime:
  
// handler expects a BSTR

if(VT_BSTR != var.vt)
{
// handle error

}
DoSomething(param.bstrVal);

But you still have a runtime error and you need to figure out what to do with it. I’d rather write code that _can’t_ have this type of errors:
  
void DoSomething(BSTR);

long some_long = 42;
DoSomething(some_long);

This won’t even compile which is exactly the point – you catch and fix the bug at compile time.

quote:

the variant class would use templates heavily so no actual conversions would happen and there would be no speed loss.


Templates got nothing to do with it. Wilka’s example that you are quoting relies on having a base class, template just provides generic implementations for derived classes. So, you will have a base class pointer that you will need to downcast to the correct type – that’s almost as bad as using void*.

It does not matter which mechanism you use – you are still disabling type checking somehow. That’s the only way to code a message dispatcher that accepts any type of arguments.

quote:

As for number two: I tend to disagree. The main reason I did things this way was because I didn''t want objects to rely on each other''s existance. For example: A CAnimation class must know that the gfx/sound class exists in some way. Either as global variables - in which an assumption is made that the variable names are never changed or the pointer to the gfx/sound class is otherwise fed to the CAnimation class - perhaps on instantiation. Rather than passing pointers to the gfx/sound etc class - I chose this method.
This provided the benefit that any class does not realy on the fact that any other class (for this purpose - engine) exists. In effect the programmer can say, "I don''t know if there is a sound engine, but if there is - Play this sound!"


Interesting... The way I see it sound system is accessed from so many places that it needs to be global in some sense. How you do that – message dispatcher or global variables (ugh!) or singletons – does not really matter (although I do prefer singletons). Alternatively, you could pass a sound system reference to every object that needs to play a sound but I think it clutters interfaces.
What I was trying to say is that any object hooked up to the central message dispatcher becomes globally accessible. It’s ok for something like a sound system, but exposing all your game entities (for example) is a debugging nightmare waiting to happen. I’d rather see them safely hidden inside a CGame class or something like that.

quote:

As I said, it works wonderfully for simpler games. But the additional overhead required and complexity (due to type conversion to void*) make it unwieldly for larger games.


Yep. Conversion is not the only issue. Message dispatcher is just too convenient. On a large project it can lead to “Deadline is tomorrow and I have no time for proper design so I just gonna send a message from here and handle it there” mindset. Design goes down the drain. End result - a system with several hundreds objects, a hundred or so messages, and not one person who knows what’s going on.

I’m not saying that message dispatchers are totally evil. I’m just saying they have certain drawbacks and usually you can implement exact same functionality in a different (better, IMHO) way.

Share this post


Link to post
Share on other sites
I know I started this to get input from other people, but let me just put something in for you guys.
If you want to pass arguments, since you''re not going to get typechecking any way you go about it, why not just use va_list''s? They''re much cleaner for the programmer than filling a structure for the parameters and then passing a pointer to it.

(For those of you who don''t know, a va_list is what functions like printf use to acheive multiple parameters like that)

Share this post


Link to post
Share on other sites
quote:
Original post by SeanHowe

I know I started this to get input from other people, but let me just put something in for you guys.
If you want to pass arguments, since you''re not going to get typechecking any way you go about it, why not just use va_list''s? They''re much cleaner for the programmer than filling a structure for the parameters and then passing a pointer to it.

(For those of you who don''t know, a va_list is what functions like printf use to acheive multiple parameters like that)

How would you pass those from the dispatcher to individual handlers?

Share this post


Link to post
Share on other sites
quote:

Interesting... The way I see it sound system is accessed from so many places that it needs to be global in some sense. How you do that – message dispatcher or global variables (ugh!) or singletons – does not really matter (although I do prefer singletons). Alternatively, you could pass a sound system reference to every object that needs to play a sound but I think it clutters interfaces.



I use the messaging system!

If I need a particular value from the Graphics Engine, say "PixelDepth" I simply send a message to it as such:

SendEngineMessage(itGRAPHICS, WF_PIXELDEPTH, NULL, &pixels);

and I get the appropriate value.

Now, because it''s going through the messaging system, by the time &pixels gets to the Graphics Engine, it''s a (void*) instead of int.

That''s where the problems with (void*) come into play - as you all have pointed out - typechecking!

I have to assume that if (returns != NULL) then it must be an int - which I agree is bad voodoo but it''s the only way to achieve my goals of the system.

and so the code looks like so:


CGRAPHICS::GetPixel(void* params, void* returns)
{
if (returns != NULL)
{
int* temp = (int*) returns; // Alias the return value...
(*returns) = PixelDepth; // Update it''s value...
}
}



Regards,
Jumpster

Share this post


Link to post
Share on other sites
Just send the handler the va_list that results from the ... parameters. It can then extract the variables it wants from it.

Share this post


Link to post
Share on other sites