Designing a "message based" engine...

Started by
17 comments, last by GameDev.net 18 years, 1 month ago
I have been giving this some thought lately, and the more I think about it the less sure I am how to go about implementing it. What I mean by a "message based" engine is that parts of the program submit "messages" into a queue. For example, the player presses a key. That key press generates a message and that message is added to the queue. Each frame the engine (if you can call it that) goes through the queue and looks at each message. Depending on the nature of the message, the engine makes calls to methods in other parts of the program. For example, the engine comes across the message for a keyboard button press. This would call some specific method in the player class to update the player position (or whatever that button ought to do). Now the problem is how to actually go about programming this. I thought about creating a generic base class for a message. It would contain a few simple variables, such as a name, a boolean value to see if it has been taken care of and can be discarded, and perhaps a few other basic things like that. Now I would derive other classes from this, depending on what the message needs to contain. For example, an imput message is going to contain a key code of which indicates which button was pressed, and whether it was pressed or release. That kind of message would be significantly different from a message to say, play a sound, or a message to indicate that the mouse has moved. The problem I am having is, how would the engine be able to distinguish between the different types of messages? How would the engine 'know' that a message contains variables specific to that kind of message? I haven't really done a lot with C++, but is this where RTTI (run time type identification) comes in? If you have any thoughts to add, please do. I am interested in seeing how other people have done this.
Advertisement
RTTI can be used, but is an exceedingly ugly and non-extensible approach. Ordinary polymorphism works quite nicely here: make the events know how to handle themselves by providing a (pure) virtual function in the event base class that takes as a parameter a reference to the engine object. If an event can't do everything it wants to by itself, it can just ask the engine to handle it. This is pretty much how the Visitor pattern works.

struct engine;struct event{  virtual void dispatch(engine &) const = 0;};struct mouse_event : event{  int x, y, buttons;  void dispatch(engine &e) const { e.handle(*this); }};struct keyb_event : event{  int key, mod;  void dispatch(engine &e) const { e.handle(*this); }};/* and so on */struct engine{  void handle(const mouse_event &e) { /* do the deed */ }  void handle(const keyb_event &e)  { /* do the deed */ }};


You should probably also check out boost::variant for an alternative and extremely generic visitor-style approach.
That is some pretty neat looking code. Like I said, I haven't had a lot of experience with C++, so I think I will have to do some playing around when I get home.
If anyone has any further comments to add, please do. I am interested in seeing how other people handle things.
Sounds good.

Kuphryn
I'm currently in the process of designing my game engine which uses an event system to tigger events throughout the engine. I was thinking of posting what I had currently but the design probably wouldn't do you much good...I could however (if I find time this weekend) code up some dummy classes to give you a general idea of what I'm attempting to do but I can't guarantee it would function 100% as I have no system built up around it. Sharlin's post covers off the general idea regarding events and data contained in them so it's definitely a good starting point :). My approach is slightly different and is a multi-threaded system with some of the following classes:

CListener
---------
Provides a means for the event manager to dispatch events to classes which have registered with it (it being the event manager) as a listener of a certain event. This class contains references to function pointers registered with it as "event handlers" for individual events. The event manager calls CListener::Dispatch( CEvent ) to return an event to the listener class. It is then up to the listener class to route each event to the correct "handler" method which was registered with it earlier.

CEventManager
-------------
Maintains control of the event queue as well as a list of listeners and their registered events. Being a threaded environment this class also has a means to allow a subsystem to send a message asyncronously (CThreadedEvent class...not getting into this :)) and forget about it as well as syncronously where it waits for the manager to inform it the event has been added to the queue. From here a sepearte thread is executing that periodically checks the queue, grabs the event and dispatches it to all registered listeners. This has to be quick to avoid deadlock on the message queue otherwise events will begin to lag behind.

CEvent
------
Provides a container for an event including it's name/ID and data which receiving handlers will require to correctly handle the event. When a subsystem calls Send(A)SyncEvent() it passes the built up event to the event manager directly.

There are a number of other classes involved in the system (I think a total of 8 but I'm at work and can't remember) but most of those you don't need to worry about. Like I said...I'll try my best to find some time this weekend to put my design document down and code up a very basic outline of my system NOT including threading related items...it will just complicate things. If not hopefully this explanation gives you a general overview of how things can be handled especially when mixed with Sharlin's example code.

I would highly recommend looking into a book called Game Coding Complete - Second Edition if you're looking into getting into your own engine design. It provides some great insight and interesting approaches to problems to get you on the right track. I borrowed some ideas for the event system from this book but have made a large number of enhancements to it to meet the needs of my system.
I've tried a couple of methods. The first using the command pattern similar to the example by Sharlin. This is ok, but the message handler interface becomes HUGE and would require an entire engine recompile if you add new message types.

The second method I've tried is the method used in the Game Coding Complete 2 book, where the message and the data are separate and are based off a standard interface class. Effectively, you have IMessage, IMessageData and IMessageHandler (and some util functions to register handlers) - It's up to the coder to correctly cast them to the required types by identifying them on a unique 'type' key.

I found this worked pretty well in the system that I used. The way Mike McShaffry defines his types is to take a hash of the string id for it; this is better than having a single include file with all the numeric IDs for the message types as again, it lets you add messages without a full recompile.
I am not too concerned about having to recompile often. Considering the size of the project at this point, it only take a few seconds anyway.

I have been trying to get ahold of cow_in_the_well to talk to him about this. Cow_in_the_well did something similar in the Mango Game Engine (used in his 4 Elements contest entry). I thought the idea was quite intriguing and like the design, so I figured I would do my own version of it.
Quote:Original post by evolutional
I've tried a couple of methods. The first using the command pattern similar to the example by Sharlin. This is ok, but the message handler interface becomes HUGE and would require an entire engine recompile if you add new message types.

The second method I've tried is the method used in the Game Coding Complete 2 book, where the message and the data are separate and are based off a standard interface class. Effectively, you have IMessage, IMessageData and IMessageHandler (and some util functions to register handlers) - It's up to the coder to correctly cast them to the required types by identifying them on a unique 'type' key.

I found this worked pretty well in the system that I used. The way Mike McShaffry defines his types is to take a hash of the string id for it; this is better than having a single include file with all the numeric IDs for the message types as again, it lets you add messages without a full recompile.

I found the system worked well for myself to a certain extent but my engine design is modularised so I'm having a hard time figuring out where to define events that inherit from CEvent so that the entire system (or only sub-systems that require them) have access to them without exposing too much of a sub-system to others. I've taken the basic outline and re-worked it in a number of ways including "callback" functions (not sure how to word this in my engine being developed as a single codebase for linux, mac and windows) to handle events rather than the if-elseif format used in the book. As well adding threading makes it much more robust but at the same time creates additional headaches in syncronization and performance. The example definitely proivdes an excellent starting point though and should work exceptionally well as-is for those looking to quickly implement an event based system in a single-threaded, unmodularised environment. While the entire engine is intended to be released as an open-source project I've considered releasing individual portions of the codebase (mainly my foundation classes) as I go which others may wish to just plug into their games/engines rather than using my solution.

EDIT:

Moe...while you may not be worried about it right now always keep in mind that you may wish to re-use this code at one point or another so it shouldn't be overly dependent on the design of your current project. In a system the size of mine if changing one interface means re-compiling the entire system I may as well go out for dinner while my build machine chugs away...I'm trying to reduce dependencies as much as possible to make the code truly flexible for almost any project :).
True enough! Mind you, I think my largest project to date was only 2500 lines (my 4 Elements 2 entry).

Here is what I was thinking:

I would have a basic class called "Event" or "Message", something like this:
class Event{private:     boolean m_processed;     int m_eventDuration;public:     String m_description;     int m_eventType;     //get and set variables here, like usual}

The description is a brief textual description of the event. I figure it could come in handy when I have to debug things later on. The m_processed is set to true if that event has been processed in that frame. The m_eventDuration is the duration of how long the event should stay in the queue. If it is something that should persist more than one frame (for example, a force in the physics engine that is persistant in the game world for several seconds), it would be set to some value. I am thinking this will most likely be a duration of milliseconds. If it is set to 0, the event is handled then discarded immediately after. The m_eventType determines where the event should get passed off to - the sound system, physics, AI, etc. It will probably be a const int (like an enum). For each type of event, I would have a const int (or enum).

Now each kind of event would be derived from that basic class. For example, to play a sound, I would need a sound event (please forgive my syntax, it has been a while):
class PlaySoundEvent : Event{private:    String m_filename;    int m_duration;    bool m_loopSound;public:    };

A mouse input event might look something like this:
class MouseMoveEvent : Event{private:    int x; //amount moved on x axis    int y; //amount moved on y axispublic:};


Now for each kind of event, I would have an event manager class that would receive those types of events and handle them. For example, the SoundEventManager class could be something like this:
class SoundEventManager{private:      PlaySound(String filename, bool loop, int duration);     StopSound(String filename);public:     HandleEvent(Event &event);};

Now the main super-big event handler would simply go through the queue and pass of the events to each EventManager class, as needed:
Engine::EventHandler(){     //I am sure there is lots of other code to go here...     for(int i = 0; i < eventQueue.count; i++)     {          if(eventQueue.event.getEventType() == EVENT_TYPE_SOUND)               soundEventManager.HandleEvent(&eventQueue.event);          if(eventQueue.event.getEventType() == EVENT_TYPE_MOUSEMOVE)               inputEventManager.HandleEvent(&eventQueue.event);          if(eventQueue.event.getEventType() == whatever other type there is)               that event type handler.HandleEvent(&eventQueue.event);     }}

How is this looking so far? I know it isn't as modular as it could be, but because I haven't done any real programming in the last 3 years I am trying to go for something simple that will work.

This topic is closed to new replies.

Advertisement