Crystal Space 2.0.0: C.5.1 New Event System

[ < ] | [ > ] |  | [ << ] | [ Up ] | [ >> ] |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

### C.5.1 New Event System

The event system has undergone a major rewrite, and most client applications will need to make changes in order to adapt to the new architecture.

#### Event Names

The old system of 32 static event "types" has been replaced with an extensible tree-structured event naming layer. This system allows event names to be drilled down to arbitrary levels of specificity while simultaneously allowing event subscribers to express interest in general categories or precise event varieties.

For an example: under the old system, any motion of any joystick would produce a `‘csevJoystickMove’` event, and all subscribers to the corresponding `‘CSMASK_JoystickMove’` mask would receive all such events. Under the new system, each joystick is individually named, so event consumers can listen only to particular devices, or to particular flavors of events. For example, the fourth joystick can produce the event `‘crystalspace.input.joystick.3.button.down’`; an event handler may wish to subscribe to `‘crystalspace.input.joystick.3.button.down’` to only receive down events from that joystick, to `‘crystalspace.input.joystick.3.button’` to receive all button events from that joystick, to `‘crystalspace.input.joystick.3’` to receive all events (buttons and moves) from that joystick, to `‘crystalspace.input.joystick’` to receive all events from all joysticks, and so on.

This also means that the concept of Command Codes has been obsoleted. Rather than describing particular command events with a combination of the `‘csevCommand’` event type and a command-event-specific command code property, each of the old command codes now has its own event name. For example, `‘cscmdQuit’` is now `‘crystalspace.application.quit’`, and the `‘cscmdPreProcess’`/`‘cscmdProcess’`/`‘cscmdPostProcess’`/ `‘cscmdFinalProcess’` events have been deprecated in favor of the new single `‘csevFrame’` event and six frame “phases” within which it can be received (see below).

Event names are translated into an efficient internal representation, the `‘csEventID’`, using a singleton event name registry object (`‘csEventNameRegistry’`, implementing the `‘iEventNameRegistry’` interface) which can always be retrieved using the `csEventNameRegistry::GetRegistry(iObjectRegistry*)` method. The actual name translation is performed by the `GetID()` method, which takes string or `‘csString’` arguments and returns `‘csEventID’`s. These `‘csEventID’`s are then used in calls to subscribe to event queues and in comparisons to determine the type of an event. Many common event types' `‘csEventID’`s can be accessed using macros defined in `‘include/csutil/eventnames.h’`, e.g., `‘crystalspace.application.quit’` is `‘csevQuit()’`, which must be called with an argument of either an `‘iObjectRegistry’` pointer or a `‘iEventNameRegistry’` reference. Since referencing an event ID via one of these macros will consume several cycles, performance-sensitive code should cache the results; see, for example, the `‘CS_DECLARE_EVENT_SHORTCUTS’` and `‘CS_INITIALIZE_EVENT_SHORTCUTS()’` macros in `‘include/csutil/eventnames.h’`.

The most intrusive change needed to handle the new event names is in the event demultiplexing you do in event handlers. For example, code that used to look like this:

     static bool DemoEventHandler (iEvent& ev)
     {
       if (ev.Type == csevBroadcast
        && csCommandEventHelper::GetCode(&ev) == cscmdProcess)
       {
           ...
       }
       else if (ev.Type == csevBroadcast
        && csCommandEventHelper::GetCode(&ev) == cscmdFinalProcess)
       {
           ...
       }
       else ...
     }

should be re-written as:

     static bool DemoEventHandler (iEvent& ev)
     {
       if (ev.Name == csevProcess (object_reg))
       {
           ...
       }
       else if (ev.Name == csevFinalProcess (object_reg))
       {
           ...
       }
       else ...
     }

More interestingly, you can also catch entire event hierarchies with a single test:

     if (name_reg->IsKindOf(ev.Name, csevKeyboardEvent (name_reg)))
     {
         HandleKeyboardEvent(ev)
     }

#### Event Scheduling

An even more interesting change to the event system is the introduction of a schedulable event subscription system. In the old system, event handlers subscribed to event types on a first-come, first-served basis. This begot an increasing number of event types to represent different points in the frame processing chain that a client might want to hook onto: PreProcess, Process, PostProcess, and FinalProcess. What was really needed was a generic mechanism for representing the order in which handlers of particular events should be invoked, regardless of the order in which they are initialized or in which their respective plugins are loaded.

The new event scheduler takes advantage of properties exposed by a new, enriched `iEventHandler` interface to do exactly this. Under the new system, for each event name that an event handler subscribes to, it may also specify essentially arbitrary happens-before and happens-after relationships with other event handlers, both abstractly ('I want to handle all input events before any instance of the `‘crystalspace.aws’` handler') and specifically ('I want to handle the mouse event only after the `‘crystalspace.cel’` event handler').

Specifically, the new functions that all `iEventHandler` implementations must provide are:

- `const char *GenericName()` Returns a simple text name common to all instances of this event handler.
- `csHandlerID GenericID()` A csHandlerID is a lightweight way to manage generic handler names. Under almost all circumstances, the implementation of this method simply returns `csHandlerRegistry::GetID(this->GenericName())`.
- `const csHandlerID[] GenericPrec(csEventID)` Returns an array (terminated by `CS_EVENTLIST_END`) of `‘csHandlerID’`s of generic event handlers which, for the event name identified by the argument, should always handle events with that name before instances of the current handler.
- `const csHandlerID[] GenericSucc(csEventID)` Returns an array (terminated by `CS_EVENTLIST_END`) of `‘csHandlerID’`s of generic event handlers which, for the event name identified by the argument, should always handle events with that name after instances of the current handler.
- `const csHandlerID[] InstancePrec(csEventID)` Returns an array (terminated by `CS_EVENTLIST_END`) of `‘csHandlerID’`s of both generic event handlers and event handler instances which, for the event name identified by the argument, should always handle event with that name before this particular instance of the event handler.
- `const csHandlerID[] InstanceSucc(csEventID)` Returns an array (terminated by `CS_EVENTLIST_END`) of `‘csHandlerID’`s of both generic event handlers and event handler instances which, for the event name identified by the argument, should always handle event with that name after this particular instance of the event handler.

In many simple cases, you will want to use default implementations of these functions provided by the `CS_EVENTHANDLER_NAMES()` and `CS_EVENTHANDLER_NIL_CONSTRAINTS` macros, like so:

     struct myEventHandler : public iEventHandler
     {
       virtual bool HandleEvent (iEvent &Event)
       {
         foo();
         bar();
         return baz();
       }
       CS_EVENTHANDLER_NAMES("crystalspace.module.name")
       CS_EVENTHANDLER_NIL_CONSTRAINTS
     }

As a convention, most csEventHandlers will implement a static method, `const char *StaticHandlerName()`, which can be retrieved and converted without instantiating the handler. This name string can then be converted into a `‘csHandlerID’` by calling `csHandlerRegistry::GetID(myHandlerClass::StaticHandlerName())`. The `‘csHandlerID’` of an instance of an event handler class can be retrieved by passing a pointer to that class to `csHandlerRegistry::GetID()`, e.g., `csHandlerRegistry::GetID(new myHandlerClass())`.

#### Event Subscription

The event scheduling system is invoked by calling the `csEventQueue::RegisterListener()` or `csEventQueue::Subscribe()` methods (or, if you are inheriting from `csBaseEventHandler`, `csBaseEventHandler::RegisterQueue`). For each of these, the arguments of interest are the `csEventID` and `csEventID[]` parameters. (Note that the `‘RegisterListener’` form with no `‘csEventID’` argument does not subscribe the listener to anything, and must be followed by a call to `Subscribe()` if you ever want the handler to receive any events.) For each `csEventID` (arrays are terminated by `CS_EVENTLIST_END`), these functions will subscribe the event handler to that event name as well as all of its children. Note, however, that it will only retrieve from the event handler the ordering lists (the Prec and Succ methods) for the event name to which it is explicitly subscribed; if you wish to have one set of ordering constraints for both `‘crystalspace.input.keyboard’` and `‘crystalspace.input.mouse’` but a different set for `‘crystalspace.input.joystick’`, you SHOULD NOT simply subscribe to `‘crystalspace.input’` and have the Prec/Succ functions return different values for each of `‘.keyboard’`, `‘.mouse’`, and `‘.joystick’`; rather, you should subscribe to each of those three event names individually. This restriction helps us avoid the introduction of hard-to-detect circular ordering rules.

Also note that you should use the `Subscribe()` and `Unsubscribe()` methods to add and remove events from the set an event handler wishes to receive. You should only `Unsubscribe()` from the particular events to which you have `Subscribe()`d; the behavior is undefined otherwise (e.g., if you subscribe to `‘crystalspace.input’` and subsequently unsubscribe from `‘crystalspace.input.joystick’`).

#### Other porting Issues

Since the `‘csBaseEventHandler’` object's operation is now linked with having a functional event name registry, any derived class must be sure to call the `‘Initialize(iObjectRegistry*)’` method before calling any of the `‘RegisterQueue(...)’` methods.

#### The `csevFrame` Transition

Under the old static event model, CrystalSpace would render each frame by dispatching four events in sequence: PreProcess, Process, PostProcess, and FinalProcess. The new event handler scheduling mechanisms enable a more flexible and extensible paradigm in which there is only a single event, `‘csevFrame’`, and each handler orders its handling of that event with respect to other handler types and handlers.

However, since it is difficult to plan for (or even imagine) the set of all handler types that may appear in a CrystalSpace application, handlers of `‘csevFrame’` can use simple helper macros to classify themselves as belonging to any of six “phases” of frame creation.

- Logic - game logic to be performed before any rendering is done. Use the `‘CS_EVENTHANDLER_PHASE_LOGIC(x)’` macro.
- 3D - rendering of the 3D view. Use the `‘CS_EVENTHANDLER_PHASE_3D(x)’` macro.
- 2D - rendering of 2D overlays. See the `‘CS_EVENTHANDLER_PHASE_LOGIC(x)’` macro.
- Console - used by CEL. See the `‘CS_EVENTHANDLER_PHASE_LOGIC(x)’` macro.
- Debug - used by bugplug. See the `‘CS_EVENTHANDLER_PHASE_LOGIC(x)’` macro.
- Frame - final rendering to the screen. See the `‘CS_EVENTHANDLER_PHASE_LOGIC(x)’` macro.

These macros, included in the declaration of an event handler class, provide all of the naming and constraint methods to make instances of that handler class schedule their subscriptions to `‘csevFrame’` within the appropriate phase. (You will not be able to use these macros if you need to subscribe the handler to events besides `‘csevFrame’` and apply ordering constraints for those events.)

The old PreProcess event corresponded with the Logic phase, Process with the 3D phase, PostProcess with the 2D, Console, and Debug phases, and FinalProcess with the Frame phase. You should update your event handlers which used to subscribe to the old Process events to instead subscribe to the `‘csevFrame’` event either using the event phase macros listed above or explicitly calling out your own subscription ordering constraints.

#### A Few Common Idioms to Change

If you use `csBaseEventHandler` you should add this line after `RequestPlugins()`:

      csBaseEventHandler::Initialize(GetObjectRegistry());

The call to `RegisterQueue()` should be changed too:

      if (!RegisterQueue(object_reg, csevAllEvents(object_reg)))
        return ReportError("Failed to set up event handler!");

To quit the application you should now use:

      q->GetEventOutlet ()->Broadcast (csevQuit (object_reg));

Handling a broadcast event can be done like this (note that this will be removed in the near future):

      if (event.Name == csevProcess (object_reg))

Handling a keyboard event can be done like this:

      if (event.Name == csevKeyboardDown (object_reg))

[ < ] | [ > ] |  | [ << ] | [ Up ] | [ >> ] |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

This document was generated using texi2html 1.76.