Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
111 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

Simple Event Handling


Introduction

I recently wrote a simple event handling module for a group project. I found it to be quite a useful tool, and I'm writing this article so that hopefully others may find it useful as well. Our group project was implemented by five programmers, each writing a different component of the game. The need for event handling arose when different components each needed to know about certain types of input (mainly keyboard and mouse events), but didn't need to know about the underlying lower-level implementation. For example, components needed to know when a key corresponding to a certain action was pressed (such as the “jump” key), but didn't want to know which actual key was pressed.

From that arose this simple event dispatching and handling system. The goals for this system where three-fold:

  • A simple system to use and setup
  • Relatively quick (we don't want to send events about everything)
  • Object-oriented (given that it had to fit in with the rest of the project)

The rest of the article will step through each part of the system, explaining the implementation, as well as making suggestions for improvements. The final section will illustrate the use of this event handler/dispatcher, which will make its usage clear.

Events

The first thing we will need is an Event structure to hold information about events to be sent around:

struct Event {
  int Type;
  int arg1, arg2;
};

The Event structure is fairly straightforward. The Type variable will indicate what type of event the structure refers to. The two arg variables are simply arguments to be passed along with the structure. When this system was first being designed, the only events to be passed around were mouse movement events (needing an x and y coordinates as arguments), and key press events (needing the key being pressed as an argument).

A possible improvement here is to pass around a pointer to an Event class instead of a structure. The Event class could then contain any amount of information, and derived classes could be created for different types of events. I choose a simpler option, mainly because the scope of our project didn't require such a complex solution.

The Event.Type variable is used by event handlers to identify the event. I choose to define the type of events in an enumeration structure. Here is a simplified version of the structure used in our project to illustrate its function:

enum EventType
{
   // Mouse button events. Each of the next six events gets passed
  // the absolute position of the mouse
  E_MOUSELEFTBUTTONPRESS, 
  E_MOUSELEFTBUTTONRELEASE,

  // Start new games
  E_NEWGAMEEASY,
  E_NEWGAMENORMAL,
  E_NEWGAMEHARD,

  // Game play related
  E_INCREMENTSCORE,
  E_PAUSEGAME,
  E_CONTINUEGAME,
  E_GAMEOVER,

  // Close the App
  E_APPCLOSE
};

IEventHandler

The next thing we need is an interface for event handlers. I defined a simple IEventHandler interface:

class IEventHandler {

public:
  virtual void EventHandler(const Event &e) = 0;
};

Every class that wants to listen to events needs to inherent from IEventHandler, and needs to implement the EventHandler virtual function. A typical implementation of this method will have a switch statement (switching on the Event.Type variable), and will have a case statement for any events the class wants to handle. Here's a short example:

void Foo::EventHandler(const Event &e) {
  switch (e.Type) {
    case E_NEWGAMEEASY:
	// handle creating a new easy game.
      break;
    case E_MOUSELEFTBUTTONPRESS:
	// handle mouse button being pressed
      break;
    default:
      break;
  }
}

Event Dispatcher

We now need an EventDispatcher class. I implemented it as a singleton class. Explaining how to implement a singleton class and how it works is beyond the scope of this article. It suffices to say that there will only ever exist one instance of the class, and to obtain a pointer to that instance, one has to call EventDispatcher::Get(). For reference, the code implementing the singleton class is included along with the rest of the code at the end of the article. For our EventDispatcher class, we only need to declare two public functions (ignoring the singleton implementation part):

class EventDispatcher {

public:
  void RegisterHandler(IEventHandler *device);

  // Sends the event to all the devices registered to listen
  void SendEvent(int eventType, int arg1 = 0, int arg2 = 0);

private:
  IEventHandler *_deviceList;
};

These two functions are again fairly straightforward. RegisterHandler is called to add a new object (device) as a listener. SendEvent is the method used to dispatch events. The two arguments to the event are zero by default.

The list of registered devices will be stored as a simple singly-linked list, so we need a IEventHandler pointer. This pointer will be initialized to null by EventDispatcher's constructor (not shown here).

Now we'll go over the definitions of the two functions specified above:

void EventDispatcher::RegisterHandler(IEventHandler *device) {
  device->SetNextHandler(_deviceList);
  _deviceList = device;
}

This method just adds the device to the device list (the device is added to the front of the list, but we are not concerned with the order of the devices in the list, since every device is sent every event. The user has to make sure that _deviceList is originally set to null in EventDispatcher's constructor.

void EventDispatcher::SendEvent(int eventType, int arg1, int arg2) {
  Event e;
  e.Type = eventType;
  e.arg1 = arg1;
  e.arg2 = arg2;
  IEventHandler * curDevice = _deviceList;
  for (; curDevice; curDevice = curDevice->GetNextHandler()) {
    assert(curDevice != curDevice->GetNextHandler());
    curDevice->EventHandler(e);
  }
}

This method creates a new event and dispatches to every device registered with the dispatcher. At this point, the reader should have noticed the use of two methods (GetNextHandler and SetNextHandler) belonging to the IEventHandler class, which I haven't discussed. Well, it's time to further develop the IEventHandler interface.





Revising the IEventHandler interface


Contents
  Introduction
  Revising the IEventHandler interface

  Source code
  Printable version
  Discuss this article