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.
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.
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 behaviour by utilizing the FS_EventGenerator class. The FS_Background.C toolkit sample file illustrates the implementation of this concept.
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; } }
1.5.9