Skip to main content
gamedev.net
🔒 Locked

Message Passing

Started by Telgin Mar 2, 2009 at 8:18 PM 11 replies 3.1k views
Original Post
Telgin
Telgin
I would have included this in my previous thread on engine design questions, but it just came to my attention today. I did a search on the topic but didn't find anything immediately specific to my question so here goes: I read about message passing being used in computer games as a means of inter-object communication instead of relying on pure function calls. That makes sense at first glance, instead of doing something like:

if (weapon1.hitTest(enemy2) == true)
{
     enemy2.takeDamage(weapon1.damage);
}
I could do something like this:

Message newMsg* = new Message("attack", "enemy2", weapon1.damage);

MessageHandler->dispatch(newMsg);
That way the objects aren't coupled too tightly and it leaves a lot of freedom in design. You can let objects just check their messages periodically and react to them instead of each object serially searching through object lists and invoking direct functions. That's a design style I'd probably have never thought of, and it leaves me a lot of questions. My brief search on Wikipedia said that message passing in gaming is often just relegated to networked multiplayer to allow a server to decide what really happens when considering lag and all. That makes great sense, but the impression I've gotten is that at least some games use it throughout their design in a manner like I presented above. Is that really true? It seems to me that it's a neat way to abstract things and make it more extensible / modular, but it also sort of seems inefficient and potentially error prone or complicated. I'm still in the phases of designing my next game project, and if this really is a common design pattern then I'd like to use this as an opportunity to learn about it. Thanks for any input.
Success requires no explanation. Failure allows none.
Codeka
Codeka
At the end of the day, you need some coupling. By changing "enemy2.takeDamage()" to "enemy2.dispatch(new Message("take-damage"))" or whatever, you're just trading a compile-time error for a runtime one (if enemy2 doesn't support a "take damage" message).

Personally, I prefer the component-based approach, where you write something like this:
HealthComponent *component = enemy2.findComponent<HealthComponent>();if (component != 0) {    component->takeDamage(...);}
Technically, it's quite similar to your second approach, but it's a little more type-safe (i.e. you can mis-type the parameters to your "Message" class, but you can't mistype the name of the HealthComponent without a compile error)
Telgin
Telgin
Thanks for the input.

I think I agree almost completely. That was actually the design method I was going to go through with before I heard about message passing, but I thought perhaps there was some sort of unseen advantage or disadvantage to message passing.

What about the other potential uses of a message passing system? In previous game development attempts I've made I never had anything like a messaging system. Are there important and/or other practical uses for it? Outside of networking I'm not coming up with much, and this game design isn't going to have any sort of networking at all.

The only other things I can really think of are things that could happen to anything / everything in the game. A message for something like "Quit" could be dispatched when it's time to quit the game. That's fine, each object receives the message and when they get around to it they deinitialize. It would be almost as easy (and probably more efficient / to the point) to just loop through the object lists and just call deinitialization functions.

So in the end, does it really all boil down to just a matter of taste?
Success requires no explanation. Failure allows none.
frob
frob
Quote:
Original post by Telgin
So in the end, does it really all boil down to just a matter of taste?

No, there should be some real thought behind it. There are size and performance differences between various systems.



Your code block:
Message newMsg* = new Message("attack", "enemy2", weapon1.damage);MessageHandler->dispatch(newMsg);delete newMsg;
has a serious issue of the allocation and deletion.

You are better off with either a temporary or a local variable:
Message msg("attack", "enemy2", weapon1.damage);MessageHandler->dispatch(&Msg);
to avoid the extra allocation work. It still requires construction/destruction of the message packet, and will quite likely involve some polymorphic lookup because you will likely want to dispatch many different specialized messages.

Your simpler form of
enemy2.takeDamage(weapon1.damage);
gets directly to the point. It will have almost no processing overhead, but it will not easily scale as the project grows. If you eventually need to assign different types of damage or damage modifiers it could be very difficult to change. I cannot say if this is good or bad in your situation because I don't know all the details. Often the quick-and-dirty simple version is best, but many times it isn't.

The event listener pattern is very common for this sort of thing because it lets any interested system get the messages. It has drawbacks too: You have the time and complexity of maintaining the listener list, the overhead of the list when empty, and so on.

A global event bus is also a valuable pattern for that type of thing, although this particular event probably wouldn't belong on it. The global bus allows more flexibility in scheduling and logging, but at the increased cost of discarding messages.



While personal taste is a part of it, you can use sound principles to trade off size, performance, and complexity, while following YAGNI, all while keeping long-term scalability, maintenance, and developer's sanity in mind.

Doing it well requires experience. Experience comes from doing it poorly.
caleb_yau
caleb_yau
Come on SmallTalk, let this be the beginning of your magical comeback!
ToohrVyk
ToohrVyk
Message-passing is already provided by C++ : member functions. To send the message "inflictDamage" to object "monster" with the argument "10", you write:

monster.inflictDamage(10);


Any additional contraption for representing messages above this structure is only going to reinvent something that the language already handles for you.

Of course, you may wish to delay the delivery of a message. In that case, you can simply use a closure to represent the delayed delivery:

boost::function<void (Monster&)> deliver =   boost::bind(_1, &Monster::inflictDamage, 10);// laterdeliver(monster);


This avoids introducing strange contraptions such as messages, message handlers, and similar reinventions.
Halifax2
Halifax2
Quote:
Original post by ToohrVyk
Message-passing is already provided by C++ : member functions. To send the message "inflictDamage" to object "monster" with the argument "10", you write:

monster.inflictDamage(10);


Any additional contraption for representing messages above this structure is only going to reinvent something that the language already handles for you.

Of course, you may wish to delay the delivery of a message. In that case, you can simply use a closure to represent the delayed delivery:

boost::function<void (Monster&)> deliver =   boost::bind(_1, &Monster::inflictDamage, 10);// laterdeliver(monster);


This avoids introducing strange contraptions such as messages, message handlers, and similar reinventions.

Yes, but what about when you want to read a set of messages from a configuration file for scripting? C++ does provide static message passing, but there is no support for dynamic linking of messages which is a key requirement for some people.

Denzel Morris (@drdizzy) :: Software Engineer :: SkyTech Enterprises, Inc.
"When men are most sure and arrogant they are commonly most mistaken, giving views to passion without that proper deliberation which alone can secure them from the grossest absurdities." - David Hume
ToohrVyk
ToohrVyk
Quote:
Original post by Halifax2
Yes, but what about when you want to read a set of messages from a configuration file for scripting? C++ does provide static message passing, but there is no support for dynamic linking of messages which is a key requirement for some people.
This is correct, although your particular example is typical of C++ (you always have to write serialization functions yourself. On the other hand, sending messages over a network in a proxy-like fashion would probably require some kind of code generator or a registration process that normal function calls do not allow.

Telgin
Telgin
Quote:
Original post by frob...


Very insightful, thank you.

I suppose then my next few questions are:

1. Some games do actually use a system like this for most of their inter-object communication? Even for something like shooting / determining who was hit? Seems like potentially a large amount of overhead, but maybe I'm over estimating the number of messages involved.

2. Does this system have serious performance hits? In my case the game is going to be fairly small (opposed to a commercial game anyway), so it might be overkill. A player and maybe ten enemies at most for instance.

3. Is it worth the effort to build a system like this into a fairly small hobby game? Probably going to be a matter of many more details than that, but in my case I don't see the listener pattern giving a huge benefit since there probably won't be all that many listening objects.

Which probably makes you wonder why I'm even bothering with the questions then, but it's a matter of me wanting to get a feel for the way real games are built instead of the way I've been doing it for the last 7 years or so. It's really time to learn a little and move on for me.
Success requires no explanation. Failure allows none.
Kylotan
Kylotan
Quote:
Original post by Telgin
1. Some games do actually use a system like this for most of their inter-object communication? Even for something like shooting / determining who was hit? Seems like potentially a large amount of overhead, but maybe I'm over estimating the number of messages involved.

Given that about 50% of posts here on GameDev.net seem to be about this topic or the closely related 'component' model, I'd say the answer is either:
A) Yes.
B) No, but people assume it is Yes.

;)

Quote:
2. Does this system have serious performance hits?

No system has an intrinsic performance hit - all you can do is compare it to an equivalent system for achieving the same goal. What existing system would you compare to, and for which goals?

Quote:
3. Is it worth the effort to build a system like this into a fairly small hobby game?

My rule of thumb is that if you have to ask, the answer is yes. Learn the tools and then when you encounter a problem you'll have methods available for solving it. Far better to do it that way than to attempt to add systems you don't know you need yet.

Quote:
Which probably makes you wonder why I'm even bothering with the questions then, but it's a matter of me wanting to get a feel for the way real games are built instead of the way I've been doing it for the last 7 years or so.

The only thing that distinguishes a real game from a game that is not real, is that someone actually finished the real game. That means that at some point they stopped worrying about the best or the most efficient or the cleanest way of achieving a task, and instead just got the task done.
frob
frob
Quote:

1. Some games do actually use a system like this for most of their inter-object communication? Even for something like shooting / determining who was hit? Seems like potentially a large amount of overhead, but maybe I'm over estimating the number of messages involved.
As I said in my earlier post, most often the direct function call is best.

There are many times where a direct member function call is not sufficient or will not scale with the system, which is when you need to use some other method.
Quote:
2. Does this system have serious performance hits? In my case the game is going to be fairly small (opposed to a commercial game anyway), so it might be overkill. A player and maybe ten enemies at most for instance.
Again from my earlier post, there should be some real thought behind it. There are size and performance differences between various systems.

You need to determine the costs and trade-offs involved. Since you don't have experience in it, use your best judgment and build the system accordingly. You will then gain some experience to improve your judgment for next time.
Quote:
3. Is it worth the effort to build a system like this into a fairly small hobby game?
None of them are very difficult to build.

Direct function calls are obviously trivial.

The listener pattern is little more than a container class, and add/remove/invoke functions. Many languages have this built in. In C++ it is just a functor.

The visitor pattern just uses a bit of polymorphism. It takes some thought but is very easy to implement once you've identified the key points.

A global event bus is just the listener pattern applied to more objects, accompanied by some additional processing every frame.



Apply some thought as to what you think is appropriate, then use it. Don't get bogged down in using the "best", just make the decision and move on. When you are done you should reflect on what you learned, and consider if another method would have been better suited for the problem.
MGB
MGB
The AAA game I'm currently writing at work is using message-passing and components. The main drive behind message passing was the network team's plans, and I must say I don't like working with it myself. Despite some template trickery to automate things, it's a pain in the ass to create messages for everything you need to send. It's also harder to debug, with some huge call stack traces and hard-to-find originators. Also, you don't get a return value back ;)
On the plus side, it enabled us to create a flexible and powerful game logic editor separate from the game, where messages between actors can be specified easily.
Telgin
Telgin
Ok, seeing good things from both sides of the fence, so I guess for the moment I'll plan on trying to implement it as opposed ot hard coded direct function calls, with a mind that if the design looks like that will end up over complicating things that I'll scrap it before I commit too much efford to it.

Thanks again for all the input. In the end I suppose the only way to learn these sorts of things is to try them myself, but at least now I know that it's not a completely silly idea and is a goal worth pursuing.
Success requires no explanation. Failure allows none.

Topic Locked

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

Sign in to reply to this topic.