Sign in to follow this  
Gaiiden

Messaging or Callbacks?

Recommended Posts

I've got a dilemma. When I created my GUI system, I started using Boost Signals to create function callbacks so that game objects could receive data from the UI widgets and act upon that data. All well and good. I think I went with callbacks because that's what I read a lot of GUI systems use. Plus I wanted to look into it anyways, see if I could do it. The problem is that I also already have a quite robust messaging system within my engine. It's already the core of the engine, in fact the callbacks rely on it because the objects first receive messages from the kernel (key presses, mouse clicks, etc) and then act upon that data, and then call back objects. The dilemma I face is that I finally realized I could easily rip out all the callbacks and just use messages instead. Since I can pass arbitrary data with my messages in the form of bitstreams, there's really no need for set callback functions. About the only advantage I can figure callbacks have over my messages is that they provide a more linear flow to the code when it comes to stepping through it. So what I'm wondering here is whether I should ditch callbacks and just stick with my messages, or keep the two combined. If you guys have similar structures in your engine, I'd like to hear how you handle it. Personally I think I went off track implementing callbacks and should have thought to just use messages instead, but if there's some advantage to callbacks over messaging I'm missing I'd like to know about it.

Share this post


Link to post
Share on other sites
As a general rule of thumb: Whenever you can simplify a system where it still retains the same amount of functionality, do it. It means greater readability and easier maintenance later on.

Share this post


Link to post
Share on other sites
I used callbacks in my gui engine, because I have no messaging in engine and it looked simpler.
In your case, I would go with messages only, so engine would use the same system everywhere. And as Enokh pointed oout, it will be probably much simpler, if you have massaging powerful enough.

Share this post


Link to post
Share on other sites
Quote:
Original post by C-Junkie
I was under the impression that ALL callback systems had a message loop at their cores, and that callbacks are just a nice way to simplify dispatching messages.

Depends on the implementation of the messaging system. If you have listeners and a message queue that is accessible from every part of the app that wants to broadcast messages you won't necessarily need a message loop.


// pseudo code

void Client::doSomething() {
Message myMessage;
// ...
messageQueue.broadcast(myMessage);
}

// ...
void MessageQueue::broadcast(Message &msg) {
// ...
foreach (listener in listeners) {
if (listener is subscriber_of(msg.Type)) {
listener.onMessage(msg);
}
}
}



...something along that line.

Regards,
Pat.

Share this post


Link to post
Share on other sites
Quote:
Original post by darookie
Depends on the implementation of the messaging system. If you have listeners and a message queue that is accessible from every part of the app that wants to broadcast messages you won't necessarily need a message loop.
You need a message loop somewhere, otherwise it's impossible to poll for input from the user, and the program does nothing.

Share this post


Link to post
Share on other sites
At the abstract level, message systems and callback systems do pretty much the same thing. The major difference is that message dispatching is centralized in a intermediary in a message system, whereas each server dispatches messages directly in a callback system.

The advantages of a message system are flexiblility and convenience. The disadvantages are that such a loose coupling is more prone to bugs and more difficult to debug.

Share this post


Link to post
Share on other sites
Quote:
Original post by C-Junkie
Quote:
Original post by darookie
Depends on the implementation of the messaging system. If you have listeners and a message queue that is accessible from every part of the app that wants to broadcast messages you won't necessarily need a message loop.
You need a message loop somewhere, otherwise it's impossible to poll for input from the user, and the program does nothing.

Yes. I was referring to the engine's internal messaging system, not that of the OS and other external parts (like devices).
Sometimes you don't even need to poll the device (e.g. using the event notification system of DirectInput), though.

Regards,
Pat.

Share this post


Link to post
Share on other sites
I pray for a day when message queues in GUI systems will become a thing of the past. Callbacks have enormously important advantages (easy to debug: if something goes wrong just look at the call stack, type safe: no bit stream idiocy, you clearly see the parameters in the code, taking advantage of language facilities: you can completely replace function calls with messages passing abstract data streams, that'll work but will completely ignore language facilities like *functions*). Please do yourself a favor and leave callbacks.

Note: I may be a little biased. I've worked with a few GUI systems and every time I encountered a message queue it was ten times harder to work with than callbacks.

Share this post


Link to post
Share on other sites
I have a general rule that I use when I run into decisions like this:

make an imperative system first, build a descriptive system on top of it

Imperative systems are naturally more flexible, and easier to write. Once you have an imperative layer, you can then add whatever descriptive layer on top (or more than one, if needed). Basically, it's easier to extend an imperative system than a descriptive one. So with what's easy to begin with.

How does this map to callbacks vs. events? Well you can look at callbacks as an imperative system: the GUI is tell you to do something, and it maps easily into C functions. An event based system, however, is more descriptive: this happened, so you may want to do something...just letting you know.

If you start with callbacks, you can always add events on top. The same goes the other way around too, I suppose: you could add callback on top of events. But events are a more complicated (abstract) system than callbacks, so you'll always be stuck with that complicated layer.

Just look at what happened to Windows message loops: event MS abandonded them in favour of COM dispatches.

Share this post


Link to post
Share on other sites
It seems as if Longhorn's WinFx framework doesn't use message loops anymore. Might be they're just hidden from the user level, might be I just didn't dig deep enough (there's some heap of information on that site[looksaround]. So if I'm totally on the wrong track with this - just ignore it[smile].

Cheers,
Pat.

Share this post


Link to post
Share on other sites
A quick search for 'idle loop' turned up

Quote:
Use WaitForInputIdle to force the processing of your application to wait until the message loop has returned to the idle state


So WinFX isn't...

Share this post


Link to post
Share on other sites
I haven't build or designed a GUI system, but in my 3D engine, I'll use messages rather than callback functions to communicate between entities. I decided to go this route for several reasons:

1. New message types can easily be added without modifying anything. Only nodes that either send or recieve the messages need to know of their existance.
2. You don't end up with 30 callback functions sitting in your class, even when you only want to handle one message. I find clutter like this in the interface to a class quite offputting.
3. You could always build a callback system ontop of a message system if you desire, but the opposite is not so easy.
4. A message passed as a class is much more powerful than a simple function interface. If you wanted, you could add things such as constructors and destructors to your messages, to handle automatic allocation and deallocation of memory blocks (though I'd probably never do that). You could also add member functions to a message class to help the message handling process. For example, you could add a callback inside any message type that points to a default message handler. That way, if an entity gets passed a message that it doesn't explicitly handle, it can pass it on directly to a default message handler simply by calling a member function inside the message itself. If you don't want to handle it by default, don't register a callback. You can also use the same function to handle several message types.

Share this post


Link to post
Share on other sites
Quote:
Original post by darookie
Ok - next try [smile].
It's there.

Quote:
In the MicroView framework (as in the standard AWT delegation event model), an event source is typically a UI component
In this system, the event source is the UI component. The message loop is underneath this, it's just not exposed.

It's actually impossible to not have a message loop someplace, the very nature of how user input comes in (the interrupt) is unsuitable for going directly into execution of the response code. So the "an interrupt occured on this device" message is queued up until the kernel can deal with it later.

Share this post


Link to post
Share on other sites
That's absolutely right at the lowest end. Still I don't see why we must stick at the lowest level when designing high level systems. The AWT framework hides the actual implementation of its event providers from the user who only sets up his listeners. In my eyes this is a very sound approach as opposed to the archaic win32 message pump (though it's still there of course, you just don't need to see it anymore).
It is of course not practicable (anymore - think DOS) to dispatch hardware-level events directly to user-level code. No argument about that from my side. It's just the high(er) level system that doesn't need to deal with it.

Cheers,
Pat.

Share this post


Link to post
Share on other sites
Right, and high level toolkits don't expose much of the message loops.

GTK (which I use extensively) only exposes it enough to control it enough to do interesting things. (make the main event loop call a callback for when I click a button... and when new data comes in from the network. nifty. then there's dbus events... [smile])

I'm just saying, they're always there, underneath the callbacks.

Share this post


Link to post
Share on other sites
As C-Junkie is intimating, the two systems are not that far apart. In fact, a model exists which is essentially the unification of messaging and callbacks: events.

In messaging systems, you rely on bitstreams which have to be reinterpreted in various fashion to get at the parameters. In callback systems, handlers are registered and invoked in direct response to internal program flows. Events unify these two approaches, discarding the reliance on bitstreams by specifying type-specific handlers.

Since you have both systems operational and in place, Gaiiden, you might as well leave it be. It'll be easier at a later date to decide to use one approach exclusively if necessary.

Share this post


Link to post
Share on other sites
Quote:
In messaging systems, you rely on bitstreams which have to be reinterpreted in various fashion to get at the parameters.

I don't see why you have to. I plan to use a base class of Message, then have child classes for each real message type. These classes can have whatever data members they want, and then you use some basic RTTI to determine what message is of what type, and cast the pointer to the correct type, giving you access to all the data members.

EDIT: Which upon re-reading, still fits the description you gave I guess.

Share this post


Link to post
Share on other sites
Personally I prefer callbacks, especially via signals & slots (though I use my own lib, since every other lib seems to require inheriting from a base class, which I highly dislike). Mostly because they reduce coupling, and require no decoding/RTTI.

Share this post


Link to post
Share on other sites
Quote:
Original post by nuvem
Personally I prefer callbacks, especially via signals & slots (though I use my own lib, since every other lib seems to require inheriting from a base class, which I highly dislike). Mostly because they reduce coupling, and require no decoding/RTTI.


As I was reading through this thread callbacks via signals & slots was the first thing I thought of. This is what Qt uses and, from a users point of view, it is very intuative and easy to use.

Share this post


Link to post
Share on other sites
Thx a lot for all the feedback guys. Makes me feel more comfortable. I'm going to keep the callbacks that I have in place, but use messaging for anything else I need. I think it'll be fine since I have only 3 callbacks, one for objects, one for buttons and one for GUI widgets. Those cover about everything, and so I won't have any scenarios where I have a ton of callbacks in one class or anything, no more than 3.

Share this post


Link to post
Share on other sites
Most of the time, it's not a good idea to provide two ways of doing something, like being able to respond to the same GUI actions through both messages and callbacks/events. Suppose you have a lazy drop-down combo, which fills its list when it is dropped down for the first time: You could accidently handle both the message and the event, resulting in strange behavior. It is always a good thing if the design itself prevents you from doing mistakes. The less precautions you have to keep in your head, the less errors you make.

You can not implement an event-based callback system on top of a message system without loosing most of its advantages (like being able to trace events through the call stack). Plus, you would be forced to update this intermediate layer whenever the messages change, which obviously, as any programmer should know, *will* at some time cause you trouble.

With a message based system (as with any kind of "bit stream" or simply "blob" design for that matter) you will also loose type safety. Change a message's parameters, and the only indication of an incompatibility could be your application crashing at random times. Change an event's arguments instead, and you've got a compile-time error which neatly points out the file and line you have to update. Always make the compiler your friend if you can.

I don't see why anyone should use a message system at all. It's error-prone, harder to debug, more complicated, less intuitive, slower and does not provide the flexibility of an event-based system. Message systems become even worse when you're using multithreading and tend to enforce an everyone-knows-everyone design. No correctly implemented event-based callback system requires a message loop in any way, nor do you need to write callbacks for events you do not wish to handle. So for my part, I would strongly suggest to throw away the message system for good ;)

-Markus-

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