HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
Working With Event Loop and Event Generators

Introduction

All Houdini applications (Master, Escape, and Batch) have an internal loop that is being continuously run until the application exits. In case of the UI applications (Master and Escape), it is an event loop, and in case of Batch it is a command reader loop. The event loops removes the next event from the event queue or the next command line from the command input source and processes it. This continues until the application terminates, at which point it jumps out of the loop and exits.

Houdini Event Loop

In the UI application, the UI action such as a key press or a mouse move are detected by an event generator, encoded into a UI_Event, and queued in the event queue. However, the event queue can also contain non-hardware events. For example, when some command modifies a text in an input field, this input field may send an event to notify any parties that registered an interest in its UI_Value that its contents has changed. During each iteration of the event loop, Houdini removes the next event from the event queue and processes it. The processing is performed by some object, derived from UI_Object, that is interested in this type of events issued by a given type of event generator. For example, the mouse clicks and keyboard strokes are handled by a UI_Window class and are passed to its gadgets, such as buttons, for further handling. Continuing with this example, gadgets owned by the window (eg. buttons), may decide that the mouse button release should trigger a new UI_Event that is going to be processed by some specialized object. For instance, the render button action is handled by some render operator. Once the event has been handled, the event loop deletes it and moves to the next event in the queue.

In the non-UI application, like Houdini Batch, there are no UI_Event objects and no event queue. However, instead of the event object there is a command line and there is still a loop that continuously processes these command lines. During each iteration of the command processing loop, the input source reads a line of script and runs it as command (or a continuation of a command, for multi-line commands). Once the command execution has finished, the next line of code is read in. If there is no data in the input stream, the loop blocks until there is some more text available.

Event Generator

Event generators interact with the handlers via the event loop, event queue, and the events themselves. And, while the UI_Event, UI_Value, and UI_Object are available in the HDK, and the interaction mechanisms, in particular the loop and the queue themselves, are abstracted and inaccessible. Thus, to allow the integration of custom events into the internal event processing framework, the HDK offers FS_EventGenerator class.

FS_EventGenerator class does not post own events on the event queue, but rather, it has a chance to generate events that are processed in their own ways. This abstract and generic design allows the FS_EventGenerator to be used not only with the UI_Event based event loop of Houdini Master, but also with the command processing loop of the Houdini Batch.

To implement a new custom event generator, you will need to derive it from the FS_EventGenerator class. This class provides the basic mechanisms to integrate own events with the main loop. For example, to register your event generator, you would call FS_EventGenerator::installGenerator(). After that, the event processing loop will periodically invoke FS_EventGenerator::processEvents(), which you must overload to process your own events. The FS_EventGenerator::processEvents method should return 1 if there are further possible events, and 0 if there aren't. For example, if you delete the event generator, this should return 0.

The decision when to call FS_EventGenerator::processEvents() depends on the return value of the FS_EventGenerator::getFileDescriptor() and the FS_EventGenerator::getPollTime() methods. This allows two ways of notifying the event loop that there are some events to be generated and processed. If FS_EventGenerator::getFileDescriptor() returns a valid (non-negative) descriptor number, then the processing loop will use it for select() call, and will call FS_EventGenerator::processEvents() if there is any data to be read on this file descriptor. This is the most efficient way of implementing the event generator. The second option is to return a non-negative number of milliseconds by the FS_EventGenerator::getPollTime(). Then the event loop will repeatedly call FS_EventGenerator::processEvents() once every poll time period. This mechanism is less efficient than the file descriptor approach because it the FS_EventGenerator::processEvents() may be unnecessarily called many times, when there are no real events to generate and process.

When the generator is no longer needed, you can call FS_EventGenerator::uninstallGenerator() to unregister it from the event loop.

Asynchronous Processing

The event processing loop runs in a single thread. Some event handlers may spawn own threads on the side, but there is only one main execution thread for event handling. This means that the handlers run a synchronous code, and that the next handler will not be invoked until the previous handler has finished own processing.

Most of the time, this type of synchronous event processing is sufficient. However, sometimes it is more convenient and more efficient to process the events in parallel. For example, it could be desirable to start a command and let it run in a secondary thread while the main thread returns to the event loop to process the remaining events, and then at some point to have a second event that collects the results computed by the command and process them further in the main thread.

It is possible to implement this asynchronous behavior by utilizing the FS_EventGenerator class. The FS_Background.C toolkit sample file illustrates the implementation of this concept.

Event Generator Example

This section describes some fragments of the code taken from the FS_Background.C example in the toolkit code sample. These snippets illustrate the implementation of the HDK_Sample::BackgroundTimer generator class derived from FS_EventGenerator.

The following static method creates a new generator instance and registers it with the main processing loop using FS_EventGenerator::installGenerator()

// the static instance of the timer event generator
BackgroundTimer *BackgroundTimer::theTimer = NULL;
void
BackgroundTimer::startPolling()
{
// Only create a timer if there isn't one already
if (!theTimer)
{
theTimer = new BackgroundTimer();
// Start the polling process
if (!theTimer->installGenerator())
{
delete theTimer;
theTimer = NULL;
}
}
}

This example uses the less-efficient polling mechanism to invoke the FS_EventGenerator::processEvents() method, so it provides the polling period of half second:

int
BackgroundTimer::getPollTime()
{
return 500; // in milliseconds
}

In this example, the generator's purpose is to check which tasks have completed their execution, to report their status, and to delete and clean up after any finished tasks. All of this is encapsulated in the static function poolTasks(). If there are no active tasks left, the FS_EventGenerator::processEvents() calls its static member stopPolling() which unregisters the generator from the processing loop. It also returns 0, which will indicate to the processing loop not to call it again.

int
BackgroundTimer::processEvents()
{
// process the tasks that used to run in parallel but are now finished
pollTasks();
// When no more tasks, stop polling
if (!theTasks.entries())
stopPolling();
// Return true if we want to be called again
return theTasks.entries() > 0;
}

Finally, the following code unregisters the generator from the processing loop.

void
BackgroundTimer::stopPolling()
{
if (theTimer)
{
// Stop events from being sent to a deleted object
theTimer->uninstallGenerator();
delete theTimer;
theTimer = NULL;
}
}