architecture for messages

Started by
24 comments, last by ddn3 15 years, 5 months ago
I'm trying to figure out how to do a messaging control system in my RPG. this will be a networked game but i'm trying to first deal with the message passing within the system. for example: actor A decides to attack actor B. i have two main options architecturally. I can have a 'global' message cache that actors can subscribe to and say 'i want messages of this type, or this id, or etc etc' then at each update frame the actor requests it's messages then processes them. I like this system because a centralized messaging system means i have one area of interest for messaging errors as well as having a single interface thats consistent for access to messages. Also this means i have less overhead for creation of individual "message box's" finally this also means i can use either a fire and forget system or a specifically addressed message system. unfortunately this might make later work more difficult, i'm not sure of this but it's possible. the other option is to create a central message directing system and have individual message box's for each actor, then using a flyweight pattern i pass messages to individual message box's on each actor. the reason i dislike this system is that it will use a larger amount of memory simply for the overhead of the individual message box's also it means that i will have to create messages to pass to each individual box when i get a 'global' message because in this system it would be like bulk mailing i have such and such message and you need to send it to each id. any suggestions on this higher level architectural decision?
Smile, it won't let you live longer, but it is funny when they die with a smile.
Advertisement
I'd recommend staying away from messages as much as possible. They are a debugging nightmare. The fact that they dont execute immediately after being instantiated is very difficult to deal with. You dont know who sent the message, unless you account for that, and they often pile up when you're debugging and create an annoying lag when you want to resume.

Ideally all your logic code would execute immediately, things that trigger events process those events right then. Those events could queue up more expensive tasks like cueing a sound buffer, cueing a network packet, or loading an image

Player::Attack()
{
Game::DealDamage(this, myTarget, myStrength);
}

DealDamage in this case will actually do all the tasks related to dealing damage, it wont cue it up to be processed later. But, everything that it does is written to be as efficient as possible, delaying expensive tasks to a more appropriate time.

When you're debugging logic, you'll easilly be able to break at the points were the tasks are being accumulated, without waiting to process some message. There is a second place then where you can debug the processing of hte more expensive tasks, seperate from the logic.

Use queues not messages. Queues have a single type.
There's nothing inherent in a messaging system which requires that messages aren't dispatched and acted on immediately -- The purpose of the central management is not necessarily to queue messages (though that can, final sentence of this paragraph notwithstanding, be a part of it) but simply to dispatch messages between "interested parties". In fact, due to the single responsibility principle, any queuing or time-delay functionality is best handled by another class entirely.

They do still add some complexity to debugging of course, but the flexibility is very much worthwhile. Also, due to the necessarily centralized nature of the system, they lend themselves well to logging or other techniques which can greatly simplify debugging.

I'd say that the second approach is kind of redundant -- in order to pass a message directly to another entity you must have some "address" or reference to them, which might be a pointer, reference, or some other token like a string ID -- but the point is, in my mind, if you have enough contact information for them in the first place, then a messaging system between the two doesn't buy you much in itself; there has to be some other abstraction being performed for this middle-man to make any sense (and if there is another abstraction taking place, you have to examine whether or not it makes sense to combine those two functionalities, dispatch + abstraction, in one object -- single responsibly principle again).

throw table_exception("(? ???)? ? ???");

Here is how we are doing messages for a game right now. It is definitely not the best solution, but it works for our needs! It is a little bit like the Win32 API.

Here is the basics:

- We have a generic message as a base. It contains data such as the type of message, the priority, and also a void* to the actual message type (I would have much preferred using inheritance in C++ as it makes SOOO much more sense, but the guy helping designing the system decided not to, so instead of arguing, and knowing that this method still works, I moved on). Each message also has its event ID. Basically, a giant enum of different events that can occur in our game (player moved, spawned a bullet, etc).

- The type of message is also technically it's source. Physics only knows how to send physics messages, gameplay only sends gameplay messages, etc. On the flip side, every system knows how to interpret other messages, discussed later.

- There is a message handler class which basically does two things: handle incoming messages, and routes them to the appropriate manager (gameplay manager, physics manager, etc). Each manager has its own separate list of events that it will need to handle so the global manager pushes it onto any manager that knows how to interpret the data(this is where it gets inefficient because it's making a copy of data which is not going to change). I don't like this too much, since it is inefficient. Also, it basically just sends all of the messages to the other managers (no need for physics to receive its own physics managers).

- Once per loop, each manager looks at what is in its queue, and sees if it understands the event type (giant switch statement). If it does, it will run the code for that event, then get rid of it from the queue. Rinse and repeat for the code.

One thing that we don't have using the messaging system is input, as there is the potential to have the input feel laggy by being a frame or so behind. We just do direct polling for input via our input manager when we need it in gameplay.

Also, if you are to use this sort of god messaging system, here are two recommendations:

1) Use inheritance, not void*'s! It just makes much more sense IMHO.

2) Don't have each manager take its own copy of the data, especially since the events, in my case, are not going to get changed. All you are probably going to do is take the data out, and put it in a nice way for your various managers to handle it (ie, my physics system converts everything to physics objects which makes things easy so I only have one place in my code which worries about how to convert my items to physics objects, which contains extra data for physics).

Otherwise, good luck!

On another side note, if you design your system carefully, this can easily be multi-threaded. We have each manager (gameplay, physics, graphics, and audio) running in its own thread and it runs great. :)
Well, at the moment i'm using a 'layer' system.
My application starts and sets up the graphical stuff. next my LayerManager is created. once all the background application creation stuff is done the overridden 'setup' member is called. the specific game will then use this routine to load all the layers. layers are drawn from bottom up but input and updating are handled top down. SO if you click your mouse a message is placed in the message system which mentions where the mouse was what it clicked if there was a drag etc.

at the top layer should be the user interface drawing and updating code. at this point the ui grabs the mouse message and says 'ok we pressed this button and that means this message/s need to be placed in the system' this might be a message like 'action #1 to all selected'
next layer might be a game management layer where the instruction 'action #1 to all selected' might be converted to 'actor 1232154 attack actor 232532' and then at this point the final layer might process input (this would be the actual lower level 3d game system).
at THIS layer input like 'move forward' might be handled.

ok then again moving from top layer down each layer handles the 'update' command and the world (and each layer of it) is updated.

finally we draw from the bottom up (the 3d play environment, the ui, etc). the nice thing about this is i could create things like 'cut' scenes simply by creating then adding layers to the top (loading screen from just adding a layer that does background loading on update and ignores input except for any progress message it sends for example)

this has the benefit of being nicely oop, dealing with responsibility where it is at and separating code out nicely into understandable chunks. If i _really_ wanted to later i could split the input/update/draw cycle into three threads and run each in sync while having draw use the old physics update creating the new physics and input sending requests on it's own. see? nicely handled splitting of responsibility for later. creating a new game would mean simply creating new layers, loading and creating routines etc and all the draw and update code would sit by itself in the game engine.
another fun part is i could add in things like 'message processors' where at layer x for example key presses could be converted into instructions by looking at the table of what each key means, then converting that key press into a specific message for an action. (w can be forward or the up arrow could be, all this logic in one location)

the message passing system would be a singleton housed by the application class which would also house the resource manager as well as the layer manager. the message manager would spend most of it's time answering requests from layers such as 'i would like to know any mouse or keyboard input' or 'has anyone attacked this player?' etc. lots of flyweight work and lots of memory recycling.

while i love this level of abstraction and how it separates out responsibility i'm wondering if this could cause issues later on.

any suggestions? thoughts?
Smile, it won't let you live longer, but it is funny when they die with a smile.
The layer sytem works pretty nicely, I use something akin to that, though I refer to it using a stack analogy -- my implimentation doesn't allow states (what you refer to as layers) to be inserted anywhere in the stack, only on the top. As in your system, states are updated top-to-bottom (if their update flag is set to true), but drawn bottom-to-top (if their visibility flag is set, of course). Using the flags allows states which don't have primary focus to continue to run, such as NPCs moving around while the player is conversing with another NPC. The top layers decide whether or not to pass input and some other messages further down the stack.

As you've noticed, this technique lends itself well to a nice OOP design. Different states are encapsulated nicely and don't bleed over to other states, it plays well with resource management because each state has a constructor/destructor, and the stack (or layer-based) approach just lends itself well to many game types, at least on a macro-scopic level. Some people argue that its overkill, and a simple switch statement will do, even in professional development, but I really consider the stack-of-states approach invaluable.

You would do well to avoid singletons though -- They're very bad practice. You can search for any recent singleton thread to read about their shortcomings, but the gist of the problem is that they're something of a trojan horse: They appear to bring simplicity, but what the really bring is an inflexibility to refactor them out easily at a later time. Just do yourself a favor now by creating the message manager as a non-singleton inside the application class (by the way, your previous post alludes that you believe you can give a singleton scope -- you can't, they are by definition global, just another of their numerous problems.) and pass a reference to the message manager to each state's constructor (each state will need to hold a reference to this manager in order to send messages later, so don't forget to copy the reference into the a member variable of the state).

You should only consider a singleton when the constraint of a single instance is imposed upon you by an external source, and in such a way that it's singularity cannot be abstracted away. Singletons are not useful when you want only one; they are useful only when having more than one is an error.

throw table_exception("(? ???)? ? ???");

having more then one layer managers WOULD be an error. same with message manager or resource manager. it all has to funnel through those central components.

I do realize it means they act as global. thats fine. it's not an issue. i just want to insure that if anyone asks for one of these components it's all handled by the same one. if i need a second one i can create a sub handler manager that is handled by the main manager (so that you only get to play with the sub one) this can be useful when you wish to allow specific rights to specific parts of code. another weird layer of interaction but doable.
Smile, it won't let you live longer, but it is funny when they die with a smile.
No, its not an error, nor is it even an unforseen need -- The fact that you already submit to needing the sub-manager displays this clearly. You believe it to be an error because you want to be lazy and not do the small amount of additional work to make your system robust enough to handle multiple managers. Its not inherently an error, it is you choosing to pursue a frail design under the banner of eazyness. This is the same pattern, I daresay trap, that The Singleton Pattern (in its great hidden malevolence) uses to woo newcomers. You're going to end up adding a fair amount of special-case code to make the sub-manager system work when you would be better off just allowing for multiple managers cut from the same cloth to be substituted in the first place -- its the more general solution, and you should always strive for generality over special-cases.

I'm not here to tell you what you have to do, but I will tell you that I have this system implemented for myself, and without a single singleton to be seen. This framework of mine hosts several distinct projects without issue, and there are instances where I can plainly see that choosing a singlton would have locked me into problems. There were two singletons in earlier versions of my code, and they were removed for good reason. Frankly, there is never a justified use for a singleton in a user-land application, especially in a game, except in some rare cases where new functionality must be retrofitted to old code without changing the call signature of the old code.

throw table_exception("(? ???)? ? ???");

Singleton - just create one ;)

I refactored all my Singletons out last year (I had many) and I honestly feel that my code has become clearer and more readable (not to mention more robust). But YMMV.
i really don't see how this is an issue. I can not foresee needing two message managers, or two layer managers or a second application class. the point of each is to insure everything goes through a single point of control. if someone created a second one it would destroy the entire purpose of MAKING it a single point of control. the managers don't do anything but manage the resources that i need to deal with. why would i not want that centralized? if i have a need for a different way of handling the resource then i would use the strategy pattern to add this. a second manager would be like throwing a side car on an airplane, it misses the whole point.
Smile, it won't let you live longer, but it is funny when they die with a smile.

This topic is closed to new replies.

Advertisement