As luck would have it I got into the office all bright and shiny eyed to do a massive post here. Then Zeracles beat me too it.
I haven't had a chance to understand it yet but it looks cool.I don't have anything pretty to show but I came across an interesting problem this weekend so I thought I'd share. Or maybe I should say the solution was interesting, the problem has been around for *ages*.
Basically something happens - a user presses a key, resizes the window or even blows up the enemy - and there are a number of other objects that want to know about it. Yep. It's eventing. Now it's been solved time and time again and in probably every language ever invented. So how can I possibly have anything to say about it?
Well, I hadn't yet found a solution that I was entirely happy with - certainly I've implemented any number of eventing systems but I've never liked any of them. Mostly they've felt to complicated and that lead me to believe I didn't really understand what the problem space was. So this weekend I had a long think about what it is I
really wanted from an eventing system. The solution - when I finally found it - proved to be ridiculously simple.
Something happens to an object and that object wants to tell any other interested objects: "Hey! This thing happened to me". I could start using words like queues and stacks and guaranteed delivery at this point but, really, all I want to do is send those objects a message. And a message - to the purists - is just a function call. My problem is reduced to: how do I call a method on several objects at the same time. Normally when sending a message (ie: calling a function) the object receiving the message most be known at compile time. However I couldn't assume that I would know about every object that could ever possibly be interested in my message. Someone else will - very likely - want to know about it after I'm done with my code.
The solution is - obvious to c++ programmers - a bit of virtual function magic. But that's not enough I wanted the usage to be simple as well. When I post my message (or send my event or whater you'd call it) I want to type one line saying: "This happened" - all those who wanted to know about it must magically hear me. But even that's not enough. If I'm listening for any message I want to type only one line to say I'm interested and only one more line to say I hear it.
The solution is in the blue block below (view the image to get a readable size).
The class
CEvent, it's a potential message; it isn't an actual message that is being sent right now. It knows who's interested in it has a collection of listeners. The function
AddListener is all that's required to tell the event you're interested in it. Actually that's not strictly true, the object that's interested has to inherit from
CListener to force the compiler to provide the largest possible function pointers (multiple virtual inheritance) but CListener is an empty class so it's not too painful.
The function
CallListeners actually sends the message. It's the heart of the solution, everything else is just engineering to make it work. And how it works is what makes this all so elegant - it's why I've posted this. For instance let's say there is a missile object and an asteroid object. The missile wants to know if the asteroid is destroyed so it can stop tracking it*. When the asteroid is destroyed it runs this line of code (previously the missile must have added itself to the asteroids Event as a listener Asteroid.Event.AddListener but that's trivial). Line of code:
Asteroid.Event.CallListeners(AsteroidListener::Destroyed, Asteroid);
C++ can deduce the template parameter - in this case AsteroidListener - automatically. One doesn't have to specify the template parameter with <> like: CallListeners<AsteroidListener>(AsteroidListener::Destroyed, Asteroid). But that's still not the real magic, that's just saving a bit of redundant typing. No, if you look at the
CallListeners function you will see a rather strange local variable declared as:
M cTemp
Where M is the template parameter: AsteroidListener. It creates an actual instance of AsteroidListener on the stack. We need this instance so that we have the virtual function table for AsteroidListener**. We need the virtual function table so that we can call
GetClassName on it. Wait! what? It looks like there's no such method but deeper in the system some preprocessor madness means that certain classes (including all CListeners and sub-classes) do have that method. How that works is a tale for another time. For the sake of this post just assume that GetClassName on AsteroidListener has been implemented and returns "AsteroidListener".
Given the name of our Listener we can now look through all the Listeners in the Event that are AsteroidListeners. This guarantees that the function we are calling on the listeners exists:
AsteroidListener::Destroyed. The function is called using the rather strange and nasty
->* operator in
(pcListener->*ListenerFunc)(pcSource, pvContext)
ListenerFunc is the AsteroidListener::Destroyed parameter passed to CallListeners originally. The brackets around "(pcListener->*ListenerFunc)" are necessary because of some retardation in the C++ spec regarding precedence of operators.
And that, is how you call a method on any number of objects in one line.
Neat huh?
*Why? I don't know. It's a contrived example. Deal with it.
**Only instances have virtual function tables, we can't call virtual functions statically. Bugger.@Zeracles: Expect another post shortly 