Theory & Practice - Issue 06 - Event Handling Model
by (11 September 2000)
|Return to The Archives|
I've been keeping busy these two or three months, and it's going to get
busier. However, I don't want to stop the acute torture I inflict on my dear
readers just yet. And so, prepare to face another onslaught of my
semi-coherent ramblings on topics I think are new and cool and useful, but
you would probably find less than bleh. In any case, I will soon find out,
as I hope you will all email me after you read this, with your comments ;)
Also, I was planning to continue the landscape tutorials with the aptly named Part II, but that decision was made 2 months ago and currently I didn't get enough semi-coherent rambling together to qualify. So instead, I will talk about something I was recently involved with, and it will hopefully be useful to some of you. And then one day I will write Part II of the Landscape tutorials...
While continuing to work on my 3D engine recently, I was confronted with a problem that I needed to solve. I wanted to have some kind of event system, that would go beyond the simple callback mechanism of C. From experience with tackling several other design problems as well as girls on steroids, I was sure I would solve this problem eventually, and hoped it would be in a cool way.
What I came up with is an object-oriented event handling model. After finishing it, I thought I'd share it with you guys (and girls, on or off steroids), because I think it comes in handy at one time or another when you're writing games.
Basically, you have a client-server model for events. Servers originate events, and clients handle them. In my engine, I have a small module called Events, and it contains classes from which you derive if you want your class to be an event server or client. Here is what Events.h can look like:
Okay, let's analyze this code. Classes that want to be event servers derive from EventServer, and therefore get access to AddEventHandler and PostEvent. PostEvent is protected, since only event servers can signal new events. Classes that want to be event clients will derive from EventClient, thereby inheriting HandleEvent.
Now let's see how the classes work together.
AddEventHandler does what its name implies. The first parameter is the address of the client which is going to handle the event. EventID is the number of the event that has occurred. You'd normally define macros for this, such as WINDOW_MOVE, etc. ClassID I will get to in a moment. What AddEventHandler does is add the event to a linked list of event handlers inside the object. RemoveEventHandler removes the event from that list. Both return a value that indicates success or failure.
HandleEvent takes 5 parameters. The first parameter is the address of the server which signaled the event. EventID is the number of the event that has occurred. Keep holding on for ClassID. Param1 and Param2 are simply a good way of supplying extra event info. The int can describe the data pointed to by the void*.
PostEvent is called by the server when it wants to signal that an event has ocurred. It goes through the linked list of event handlers, finds the ones that match the event's ID and calls their HandleEvent methods. It knows how to call these methods since the HandleEvent method of the EventClient class is virtual. It passes "this" as the SourceObject, the event's ID, the class ID (I will talk about this in 4 seconds) and the extra information about the event in the int and void*.
Okay, now what is up with this mysterious ClassID thing? Well, consider this. Suppose a client "subscribed" to events from both an object of type Window and an object of type Camera. The client subscribes to the Window object's WINDOW_MOVE event, and the Camera object's CAMERA_HIT event, which occurs when the camera hits a wall, for example. Suppose it happens that the macros WINDOW_MOVE and CAMERA_HIT both resolve to the same value. Then the client has a problem. How does it know whether the message has originated from a Window object or a Camera object? This information is important. After all, there's a big difference between your actions when you learn that Sweet Aunt Sally landed in the hospital, and your actions when you learn that your friend won the lottery. Sure, the event handler has the address of the server, but that void* doesn't tell it anything about the type of the server.
It can happen that event IDs will coincide. After all, people might be working on different libraries independently, and will not know about each other. Unless everyone is using a GUID (UUID) generator to generate unique IDs, and everyone is using the same generator, there can be such an ID clash. Without GUIDs, there is another way to resolve this conflict so that everyone is happy. That is with Class IDs.
I developed the concept of Class IDs to solve this problem, so I don't know if such a system actually exists outside of my little event handling design thing. (By the way, if you have any information about this, let me know, please!) I think Class IDs work very nicely. The main idea is this: when the client requests a subscription to an event, it tells the server what it wants to call it. The ID that it gives the server is unique only in the client's list of different server types. The server stores this information along with the client's subscription information (which includes the client's address and the event code). Then, when it comes time to call the client's HandleEvent method, the server spits back the ID it was given.
Why is this system effective? Well, first of all notice that there is no need for everyone to be using the same GUID scheme. Each class can be using its own scheme that gives unique identifiers to different classes, and all it has to do is tell the objects to save that information for later reference. It is as if I told you, "I'd like you to tell me when you will go outside, and by the way you are a Human." This is done through the AddEventHandler method. By returning a success code you say "OK." Then, later, when you tell me you're going outside, you say "Hey, I am a Human, and I'm going outside." Your Class ID is Human, and if I want to access you directly I have your void* address.
When I thought about it, this Class ID approach actually turned out to be very effective in several different applications, not only event handling. It requires minimal memory resources to actually store the IDs (one int per event per client registered for any event on the system). It is similar to capabilities, but a little different and with classes. If you have heard of something like this before, by the way, let me know.
Now that you know what ClassID is about, you might be wondering why there is a ClassID in RemoveEventHandler. Well, this is simply for security reasons. It just means that the only class that can unregister the object from receiving an event is the class itself (guessing at the ClassID could be punished ;-). Aside from that, you can take it out.
I would like to thank George Foot (who you guys probably don't know; he was
involved with the excellent Allegro library -- www.talula.demon.co.uk) and
Paul Nettle, who answers many questions here on flipCode. Both of them were
very helpful in discussing this problem with me, as well as helping me
devise a recipe for the best veggie burger you've ever tasted.|
Again, remember to go out and get some health! It doesn't come in a box... (yet)