Sign in to follow this  
adtheice

architecture for messages

Recommended Posts

adtheice    122
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?

Share this post


Link to post
Share on other sites
bzroom    647
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.

Share this post


Link to post
Share on other sites
Ravyne    14300
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).

Share this post


Link to post
Share on other sites
Iced_Eagle    349
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. :)

Share this post


Link to post
Share on other sites
adtheice    122
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?

Share this post


Link to post
Share on other sites
Ravyne    14300
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.

Share this post


Link to post
Share on other sites
adtheice    122
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.

Share this post


Link to post
Share on other sites
Ravyne    14300
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.

Share this post


Link to post
Share on other sites
Stani R    1070
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.

Share this post


Link to post
Share on other sites
adtheice    122
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.

Share this post


Link to post
Share on other sites
Ravyne    14300
The point, really, is that the use of a singleton is an artificial constraint. You could certainly write your message manager in such a way that it is happy to co-exist with another. In fact, it shouldn't even have to know whether or not another message manager exists to perform its duties, agreed? What the singleton patern says, is that the object needs to know that one and only one exists because its going to freak the fuck out otherwise -- the sky will fall, general carnage will ensue. That's certainly not the case, or shouldn't be, with your message manager.

The "single point of control" argument is a logical fallacy -- if you just need one, just make one. There's no need to further saddle it with the singleton baggage, it is, in fact, short-sighted and wrong to do so. If "you just need one" just make the one and be done with it, if you never need to make another somewhere else, great, no harm done -- and as a benefit, you get to put (each) in the correct scope. If you do discover that you need more at some later time, this solution will require no major surgery. If you choose a singleton, you'll then be faced with major changes to function call signatures, visibility of said former singleton, and chain-of-command issues. Singletons are worse than globals -- they are a global in sheep's clothing, all the downsides of globals apply to singletons and then some.

Singleton is an overworked (and once more, incorrect) solution to what you wish to accomplish. Its a heavy-handed attempt at preventing the programmer from sabotaging himself... To that I would share what I have read somewhere in the past, paraphrased, "The design of an interface should strive to thwart Murphy, not Machiavelli."

Share this post


Link to post
Share on other sites
Ravyne    14300
Also, I can give you any number of good reasons to have more than one message manager -- Say you have a high volume of messages bogging down the system but notice that half the messages are inter-enemy communication. The player, or indeed the rest of the system, don't need to hear these messages so you can cut them out entirely and move them to its own message manager. If your app is properly multi-threaded you can put this processing in another thread and remove the bottleneck.

Another example is a communication hierarchy. With a singular system, an entity must receive all messages, decide whether they apply to them, and whether they come from someone with "influence" over them -- either a peer, a subordinate, or a superior. If you have multiple message handlers, influence can be taken for granted on the local manager, rather than adding complex decoding logic. A real-world example is military chain-of-command.

Yet another example is temporal messages, that is, messages which affect entities within some realm of influence -- say a room in a building or quadrant of a map. With the singular system, you must again receive, parse and evaluate the relevance of all messages. If you could subscribe to another message manager upon entering said room or map quadrant you can again assume influence, rather than adding additional complex logic. Real-world examples of this include a buildings PA or CCTV system and your local radio or cell-phone towers.

Share this post


Link to post
Share on other sites
Stani R    1070
Quote:
Original post by adtheice
I do realize it means they act as global. thats fine. it's not an issue.

Allowing your texture loading code to create new AI controllers without even explicitly showing the dependency seems like a pretty big issue to me, though. One reason most people prefer to pass variables around instead of relying on global singletons is that it's easier to understand how the coupling of modules is working out. By dropping singletons, you are forcing yourself to explicitly define and hence think about the interdependencies between your modules, thereby promoting a looser coupling. With singletons, you are essentially linking everything together into one big ball of spaghetti, which is not much fun to untangle later on.

Share this post


Link to post
Share on other sites
Ravyne    14300
Damn you lightbringer, I was saving that little bomb for the next volley... Well said though. You and I, we'll topple this lonely tower yet! :D

Share this post


Link to post
Share on other sites
adtheice    122
see THIS is why i asked on this board. i knew there had to be issues in my architecture. i just never foresaw THIS issue. ok so I'll nix the singleton thing on my managers. Now i'm trying to keep this general enough of a framework that i can simply can inherit a new application fill in the setup and shut down methods inherit the layers and add them appropriately. the idea is to create a 'game' framework for RPG's (or general enough to 'tweak' for other games, i can't see why it would be specific to RPG's but thats the goal for the first game so thats the specific case)

as it is i use a nice 'getLayerManager' and 'getMessageManager' static functions that point to my singletons then i continue on. if i am throwing out the singletons then should i create a default one with the constructor asking for a pass in of the message handler or should i be creating some kind of more decoupling here?

I'm still thinking about how to handle the messaging now that i'm throwing out the basic one place runs it all idea. when i had decided on the singleton i knew what was going on and i could visualize the flow. now that you have convinced me to chuck it i realize that i had decided on the singleton before knowing that i needed it and that some other way of handling it might not have been better. I'm thinking some main message handler system with 'plug on' strategy systems for handling specific special cases. this would allow me to do more specific messaging. i'm not sure yet exactly how i want to handle that, i'm going to have to consider it for a while till i can visualize how i want it to work. UGG! more thinking before i can code hehe. thanks guys (or gals) keep the ideas flowing.

Share this post


Link to post
Share on other sites
Wyrframe    2426
I think the message-bin idea (broadcast messages are sorted into like bins and delivered later) could work, and give good support for concurrency, under one simple assumption; many listeners can update independently, in separate threads, but a new update cycle doesn't start until everyone has finished the last update cycle.

Listeners call a shared postmaster with a list of key->value tuples and a callback object or function.

The postmaster sorts keys according to a pre-optimized pattern. By default, sort by whatever is natural for your key type. Record key frequency-of-use on a per-key basis (don't keep data about what keys are used with what other keys) during testing runs. During later runs, you can use the data to create a more optimal sorting order for the next step...

Postmaster then creates nested bins to be delivered next update cycle. Each bin holds other bins, and listeners and messages whose key sets match this bin's key set exactly. For multi-threaded, each bin needs its own semaphore, preferably two; one used only when adding or removing a listener, and one used only when adding a message. No need to block both lists.

Once the current update cycle completes, the postmaster wipes the current cycle's set of message bins for re-use, and swap bin sets; the current cycle's bins will be the new cycles' queue bins, and the next-cycle bins will be iterated over as this new cycle's dispatch bins.

Bins are only emptied, not destroyed; a bin that has been unused for several cycles may have its vector of message storage shrunk down. Bins unused for a long period are more likely to be re-used soon; messages are frequently passed, and rarely unique. Bins that are nearly or completely unique (those whose key sets contain keys which don't appear in the "known keys" aka "sorting" list) can be discarded each time they are emptied.

It is not an error that for a given key K, multiple bins will include messages which include that key. For instance, for keys K1, K2, K3, sorted in that order, some listeners will want messages with values for (K1, K2), and some will want messages with values for just (K2). Thus, the root bin will contain a K1 bin and a K2 bin, but the K1 bin will contain a child bin for K2 which represents the set (K1, K2).

For each listener, in the bin (K1...Kn) it is listening on, each message's actual values (K1=V1, ... Kn=Vn) is compared to the listener's registered values. Only if the values match is the listener notified of that particular message. These maps can be kept as simple sorted lists of the values, as listeners will only be placed in a bin if its key set matches the bin's key set exactly, and messages will only be placed in a bin if its key set is an exact match to the bin's key set. However...

The postmaster iterates over bins depth-first, dispatching each message in a given bin to each listener for that bin, and then forwarding messages up to the parent bin. A bin B with children bi will tell its children to perform their dispatches, and then, since their messages' key sets are supersets of B's key set, B will union their message sets with its own before performing dispatch to its own listeners.

There is exactly one correct bin for all messages and all listeners, because we sorted the key sets, guaranteeing one tree for a given key-value map, and that key supersets will be made children of their subsets. The one requirement is that keys are totally sortable (that is, for keys K, K'; K <= K' and K' <= K implies K = K').

This can be made multithreaded easily by keeping a fixed-size set of worker threads; each one is handed a leaf bin, or a bin which all of its children have been completely notified (after which, with the union of its childrens' message sets and its own message set, it can be considered an effective-leaf bin, and processed). If you have a 4-core machine, set up 4 worker threads, and try to set affinity to prevent cross-core thrashes. Each thread can operate completely independent of eachother, and when two threads dispatch new messages to the same bin, that bin's mutex will keep them from trampling eachother. Really, that should result in very few collisions.

I think that covers it. PM me if I missed anything. I'll toss together C++ and Java implementations this weekend.

Share this post


Link to post
Share on other sites
Stani R    1070
Quote:
Original post by adtheice
as it is i use a nice 'getLayerManager' and 'getMessageManager' static functions that point to my singletons then i continue on. if i am throwing out the singletons then should i create a default one with the constructor asking for a pass in of the message handler or should i be creating some kind of more decoupling here?


The way I see it, there is ultimately some place in your program (at the very top) where everything comes together. This is where you create and link up all the needed systems, and run the game loop / manage the game states. If a system needs another system to operate, such as needing access to that central message handler, this would be the place to pass it down, usually as argument in a constructor. At least that's how I do it.

Share this post


Link to post
Share on other sites
ToohrVyk    1596
Ah, the good old procedural-versus-objective contest of wills [smile]

An object-oriented approach to programming reduces coupling between high-level systems by delaying dependencies. That is, a given system does not know what systems it interacts with, it only knows the interfaces of those systems, and the actual dependency graph of systems only appears at runtime.

A procedural approach makes dependencies explicit by embedding them in the systems, and making systems global. That is, the dependency graph of your systems is written down in code and perfectly reflects what happens at runtime.

There are two consequences for this:
  • An object-oriented approach is superior when you need an extensible project. Low coupling implies that you can remove a system and replace it with another (simply respect the same interface) and you can even hotplug systems at runtime.

  • A procedural approach is superior when you need a clear outline of your systems and the dependencies between them. Since the dependency graph in a procedural system is written down in your code, you can extract it by hand or using statical analysis tools, and you can restrict interactions you don't want to happen.


Now, there are two grey areas around this. On the one hand, unimpeded global variables accessed by everyone do not a system make. Even in procedural programming, global variables are not left around naked (unless, of course, the semantic of a global variable that can be modified by everyone is precisely what is required, but it seldom is) and are instead wrapped in systems which work around it. So, regardless of the approach, pervasive global variables are a bad idea.

On the other hand, the Singleton design pattern holds some sort of middle ground between procedural and objective design. It's both a static system, in the procedural sense, which tends to make dependency handling easier, and a dynamic object in the objective sense, which allows it to be replaced by another object at will.

Share this post


Link to post
Share on other sites
adtheice    122
wyrframe the problem with that is i don't want to use callbacks. i all ready decided i liked and wanted to use the fire and forget methodology with fall through on unhandled messages.

Now i need to figure out how to sort and store messages so that i can do easy queries of them and collect all the messages of specific types. (like i want all attack messages or all messages relating to this id or etc etc). currently trying to figure out a fast and flexible way of doing this generally. some of what you have seems to be what i want. i need to dig into this some more before i sketch anything in. my architecture has all ready gone through some major re-envisioning. thanks again for that, i needed to fix those problems.

Share this post


Link to post
Share on other sites
ToohrVyk    1596
Quote:
Original post by adtheice
Now i need to figure out how to sort and store messages so that i can do easy queries of them and collect all the messages of specific types. (like i want all attack messages or all messages relating to this id or etc etc). currently trying to figure out a fast and flexible way of doing this generally.
This sounds like a database. If you need the entire feature set of a database, consider a lightweight in-memory database, such as SQLite. Otherwise, you'll have to remove features.

Share this post


Link to post
Share on other sites
Antheus    2409
Quote:
Original post by Wyrframe
I think the message-bin idea (broadcast messages are sorted into like bins and delivered later) could work, and give good support for concurrency, under one simple assumption; many listeners can update independently, in separate threads, but a new update cycle doesn't start until everyone has finished the last update cycle.


How will you manage proper ordering of messages?

Let's say you have movement system which updates locations of moving entities, and physics system which manages collisions.

Each of these runs in parallel, yet they need to operate on same data with circular dependency on each other.

Granted, in this particular case you can merge both systems into one, but that doesn't solve the general problem.

This isn't even remotely a trivial problem.

Share this post


Link to post
Share on other sites
Antheus    2409
Quote:
Original post by adtheice
I realize this is not a trivial problem, hence one of the reasons i was asking for help =P


Not messaging, multi-threading. The biggest challenge, one to which there is no final answer as of yet, is how to distribute the state and how to schedule work in such a way to utilize multiple cores as effectively as possible.

Adding generic multi-threading system just for the sake of it may not only degrade performance, but will definitely complicate things a lot.

The Smoke engine I linked is an example of one such attempt.

IMHO - there's no substitute for experience, and what worked for one might not have for others. Code both approaches, then see how they turn out. Prototyping shouldn't take much time, but stick to single-threaded approach. Messages are just different function calls, but basic idea is to change the way logic is handle (for decoupling, concurrency, load balancing, networking, ...).

You can even look into stackless python tutorial for example of how such logic can work.

Share this post


Link to post
Share on other sites
adtheice    122
Quote:

Not messaging, multi-threading. The biggest challenge, one to which there is no final answer as of yet, is how to distribute the state and how to schedule work in such a way to utilize multiple cores as effectively as possible.


last time i checked Erlang did multi-threading right.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this