• entries
383
1075
• views
354061

# Message Passing System

317 views

Quote from Ravuya:
Quote:
 I highly recommend a robust message passing system. It will clean up your code in ways almost nothing else can, and helps make it thread safe if you plan to multi-thread it later.

I vaguely knew I was going to have write one of these sooner or later, especially since I was going to add a GUI to my games eventually. However I can't seem to find a good tutorial or article which outlines what I need to do to construct a simple yet robust, functional message passing system. I'm sure I've read something about how to construct a message pump and the appropriate data structures to use somewhere before, but I'm drawing a blank right now.

GPG4 (or maybe GPG5; I think it's GPG5) has a great article on it. Basically, he has an enumerated message type, and every entity uses this method:

void HandleMessage(MessageType q, int data1, int data2);

It's kind of useful because you can use data1 and data2 to trade out pointers. So you could send an "I am dying!" message with a pointer to yourself so that anything targeting you has a chance to drop that pointer before you are freed.

Basically, to handle the message you'd do something like:
void SandwichVendor::HandleMessage(MessageType type, int data1, int data2) {
switch(type) {
case I_AM_DYING:
if(data1 == myTarget) {
myTarget = NULL;
}
break;
case GIVE_ME_A_SANDWICH_BITCH:
if(distance(data1) < ARMS_LENGTH) {
if(hasItem("Sandwich")) {
SendMessage(HERE_IS_SANDWICH, this, getItem("s
Sandwich"));
}
}
break;
default:
::HandleMessage(type,data1,data2);
}
}



I used sort of the same system for NPC interaction in Freezer 2, but it could be extended to large portions of the game code, including the internals. You'll need a message pump of course.

I assume the message pump is just a queue, which regularly sends them on? That makes sense if the pump is a separate thread, but if the application is single threaded wouldn't the pump just automatically pass on the message when it is received?

Also do objects have to register to receive messages of a certain type, or am I confusing this with an event handler?

The message pump should just be a queue, and it doesn't make much sense if you are single threaded (unless you are somehow batching messages together for greater efficiency or filtering them some other way). Sending them immediately seems cleaner, but YMMV -- depends on how clean you want to make your SendMessage code.

Objects shouldn't have to "register", their handler function will just add a catch for the message type if they can deal with it.

The really cool thing about this is that you can start coughing out events (like FOOTSTEP_NOISE) and then write the handler functionality into NPCs later without having to do any coupling whatsoever -- it's totally passive.

So messages aren't specially "addressed" to any one entity; they are passed to every entity that can read messages and they determine if the message is relevant to act upon?

i.e. your HERE_IS_SANDWICH message would be passed to every entity, but only the one requesting the sandwich would deal with it?

I'd be super tempted to add a time field to the message queue and index it on the time.

Imagine this for a firework (in pseudocode)

SendMessage("FIREWORK_EXPLODE",getTime()+3.0)

If time now is 12:00:00 then you queue might look like this:

MESSAGE TIME
...
SOMEOTHER_MESSAGE 12:00:01
...
...
FIREWORK_EXPLODE 12:00:03

Every tick you check your message queue for messages to process. In this the firework is hapily flying along and then eventually you get to the message that you send 3 seconds ago and that triggers to code to make it explode.

This is actually similar to how Quake worked with nextThink functions and can be used to implement AI.

void Think () {
DoSomething;
NextThink(3);
}

where NextThink pumps a message in to the queue to call that objects Think function after CurrentTime + 3.

I'd also be tempted to have destinations (and maybe groups) because if you have 10,000 objects you don't want them to process every message. Let an object register itself to a group so if you want to listen so sound events then register to sound_listeners then send all sound messages (like FOOTSTEP) to the sound_listeners group?

Sending it to everyone defeats the purpose of Messages. You are effectively Polling.

Quote:
 So messages aren't specially "addressed" to any one entity; they are passed to every entity that can read messages and they determine if the message is relevant to act upon? i.e. your HERE_IS_SANDWICH message would be passed to every entity, but only the one requesting the sandwich would deal with it?

Yes. You can do some intelligent filtering (i.e. only people capable of receiving sandwiches would have the message sent to them) to improve performance, but that's the general idea.

Thanks to both of you! I think I've got the general principle of how it works now. I'll give a message passing architecture a go with this game and see how it turns out.

I couldn't trust myself with the above code. Integers that could be pointers or lengths or whatever depending on context would almost certainly cause me some awful bug that I couldn't find. So instead of passing an enumerated message and two ints I'd pass a message class.

interface IMessage
{
// Possibly store the sender as an object type
// Possibly store time information or group ...
}



Then I'd have a number of subclasses like Give Item

class GiveItem : IMessage
{
Npc giver;
Item item;

public GiveItem(Npc giver, Npc receiver, Item item)
{
this.giver = giver;
this.item = item;
}

}



Quite a lot to type so I'd probably make a VS.net snippet to create message classes for me. Then I'd make my switch statement work on type rather than an enumeration.

public HandleMessages(IMessage message)
{
switch(message.Type)
{
case typeof(GiveItem) // I'm not sure if this is the correct code
{
GiveItem giveItem = message as GiveItem;
if(giveItem.giver == this || giveItem.receiver == this)
{
//Handle it.
}
}
}
}



...
...
...

Worse than that, I'd probably become drunk on power and decided I want more flexibilty so I'd create MessageReceiver so they'd each have a hash table. The key would be the type of message, the value would be a delegate (function pointer).

This means I could change responses to messages at runtime or add new responses and more easily use a scripting language.

Also I don't think I can spell receiver, therefore that's probably a bad variable name :D

## Create an account

Register a new account