[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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.
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) } |
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())
.
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').
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.
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.
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.
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 ] | [ >> ] |
This document was generated using texi2html 1.76.