Event Passing

CornerStone C++ includes a an event-passing framework that is used to deliver information on specific activities on widgets, such as taps, the beginning of interaction, and the end of interaction. The purpose of the event passing mechanism is to allow easy triggering of action X based on event Y. Typical use cases for events are buttons, where one wants to get information about touch-down, and touch-up, and trigger action based on that information.

The event framework has been present in experimental form in the SDK. Based on the experience it is being changed into stable status.

Event Contents

The transport mechanism of the events consists of a message id (a character string), and message data (binary blob). The use of character strings enables easy naming/identification of messages, as the "address space" for strings is practically infinite. The message data is carried in a binary buffer, that has straightforward read/write functions for storing and parsing basic data types.

One might notice that the structure looks very much like the OSC data transfer protocol. This is no conincidence, as the event approach has been influenced by the OSC approach. In many cases it should be possible to convert automatically between OSC messages and CornerStone messages. The differences between the two systems is mainly that CornerStone messages have been intended to be easier to parse. Further CornerStone does not include a pattern-matching feature in the event id (i.e. messages cannot use wildcards like "scene-a/*/reset".

Receiving Events

All event receivers are inherited from class ValueObject, that is inside namespace Valuable. The messages are processed in virtual function processMessage:

virtual void processMessage (const char * id, Radiant::BinaryData & data)

Within this function call one should first try to process the message, and after that call the parent classes processMessage, if the message is not handled locally. To process a message, cone would typically check the id string it against expected event ids, and once the match is found proceed to parsing data in the binary data holder. In many cases the message data can be ignored, as simply knowing that an event occurred can be the relevant information.

Often one can also embed information into the identifier, so that it already contains some textual data, for example: "open:http://www.google.com". In this case it would be practical to check that the first 5 letters match the "open:" string (using C function strncmp), and then use the rest of the string as the actual URL to open.

The binary data argument contains possible event content. The use of binary data might look a bit raw, but it allows conversions between data types within the blob parser, so that the receiver can for example process integer- and floating point parameters with the same code.

Sending Events

In principle any object can send events, without restrictions. In practice the code Widget class implements a set of features that simplify event sending. For example it keeps track of event listeners, and removes the listeners when they are deleted. To send an event one could use the following code:

Radiant::BinaryData data;
data.writeFloat32(0.2f);
data.writeInt32(1234);
eventSend("thunderstruck", data);

The system will then send out an event "thunderstruck" to all registered receivers.

Binding an Event Receiver to an Event Sender

To use the event framework with a given widget, one needs to add the receiver as a listener to the event sender. This is done with the function call Widget::eventAddListener.

To connect the sent events to a receiver one can then write:

Widget * sender = mySender();
Widget * listener = myListener();
// Close listener, when sender emits a "thunderstruck" event
sender->eventAddListener("thunderstruck", "close", listener);

As can be seen above the Widget event mechanism include a message renaming service, so that sent event ids can be bound to any listener ids. This enables connecting messages in run-time without modifying the connected classes in any way.

Roadmap

The event processing system has been deviced to enable flexible events passing, with some planned future improvements.

  • Hierarchical event passing. The id of the message is in practice an address, that could be interpreted as a hierarhical path to some value, so the id might be: "scene-a/browser-1/open:http://www.multitouch.fi". One could implement hierarchical event ids already now, but there is no active support for this feature yet.

  • Tread safety. At the moment the event passing is not guaranteed to be thread safe. As both the message id (string) and data (binary blob) can be copied easily it should be easy to pass them between objects as necessary.

  • Networking. The message contents can be easily streamed over a socket connection, as necessary.

Design Comparison

The roadmap above illustrates some of the design goals that the event system is set to implement. Below are comparisons to some other event frameworks.

wxWindows (and many other systems) uses integers as the event identifiers. For successful event passing the event sender and receiver need to share the identifiers, which shared globally. This approach has the advantage of being extremely fast (due to the speed of integer comparisons), but it makes programming quite difficult as one needs to maintain and share the integer values. Avoiding collisions between identifiers can be difficult if the code for the application is coming from multiple sources. Likewise maintaining shared identifiers is difficult

Java Swing uses interface classes to implement listeners. Thus a class listening to various events, needs to implement a series of interfaces to match the event types. The system has the advantage of allowing one to pass customized events to each listener. How-ever, the creation of new event types requires meticulous work in writing the interfaces, and then implementing the receiver functions. Originally CornerStone used the interface approach with keyboard event handling. The old feature is still present, but in addition to it, the keyboard events are also available with the "standard" event processing system.

Qt uses signal/slot mechanism to connect events (aka signals) to slots (aka callback functions). The system has also been implemented in the Boost C++ libraries. The system has been successful in overcoming practically all the problems of the previous event passing methods, while offering an intuitive programming approach. A C++ purist might argue that Qt extends C++ so heavily that it is not proper C++ any more. A practically oriented person would accept the signal/slot mechanism as a bug fix, that implements the "missing" event passing mechanism in C++.

We have tried to include the good features of the signal/slot model into our event framework. Using strings are event idenfitiers (which is the basic structure behind Qt's signal/slot implementation) offers a lot of freedom. The CornerStone event mechanism does not rely on a QApplication (or any other marshalling object) to run for the event processing to work. The event renaming system also has an advantage over the signal/slot mechanism by allowing parameters to be embedded in the event identifier. This has been a small nuisance in Qt, since connecting several senders (for example a row of push-buttons) required either implementation of several slots in the receiver or the use of intermediate mapping objects that disambiguate the events coming from the senders. In the signal/slot approach the event sender and and listener must have exactly the same arguments (identical function signature), which may lead to situations where one needs to implement multiple slots, to receive practically identical signals from multiple sources. In practice this is seldom an issue if the data types in the signals have been selected in a clever way.

As a (some-what partial) conclusion it can be said that signal/slot mechanism offers a slightly easier message processing environment, while the CornerStone event system has higher flexibility in regard to processing events in more complex situations, or using the event system without the need for relying on the QAppllication or simialar event marshaller.