Documentation: Timers, Signals and Input Messages [Remove Frame]
|
Most messages your application will receive are generated from FOX Widgets, such as buttons, sliders, and other controls. However, FOX also provides some messages which are generated from other sources.
There are four types of such messages: Timers, Chores, Signals, and Inputs.
Timer Messages
|
Timer messages are used so your program can receive a message after some specified interval has elapsed. This can be very useful, for example for performing an animation of some kind. Like all messages, timer messages are handled by specifying a target object which is to handle the message. When the specified time has elapsed, the object will receive a message of the type SEL_TIMEOUT, with the message ID being the one which was registered at the beginning of the time interval. The length of the time interval is expressed in nanoseconds, and the interval starts at the time the callback message was registered. The message callback to the target object will be when the interval has expired.
Here's how you would program a message map entry to catch a timer message in a target object of type MyObject:
// Message map entry of "object" FXDEFMAP(MyObject) MyObjectMap[]={ FXMAPFUNC(SEL_TIMEOUT,MyObject::ID_ANIMATIONSTEP,MyObject::onAnimationStep), ... };
A timer may be registered with a specific target object and message-id identifying the object and method to be invoked on that object when the timer expires. An optional user-data pointer may be passed which is made available to the message handler when the timer message is invoked. This can be used by the handler to e.g. determine what to do. To register a timer message, you would call the function FXApp::addTimeout(), as follows:
// Register Timer callback message for 1 second MyObject* object; void *userdata; app->addTimeout(object,ID_ANIMATIONSTEP,1000000000,userdata);If a timer with this particular target and message combination already exists, the interval is reset to the new time.
Timers can be removed or unregistered at any time, by calling FXApp::removeTimeout() with same target and message-id with which it was registered previously:
// Unregister Timer callback message app->removeTimeout(object,ID_ANIMATIONSTEP);
It is OK to call removeTimeout() on a timer that has already fired. Timers in FOX are fired only once, not repeatedly; thus, to do animations the timer must be reset each time it expires, as is done below:
// Receive a Timer callback long MyObject::onAnimationStep(FXObject*,FXSelector,void* ptr){ if(continueToAnimate){ // Do something ... // Restart timer for another interval, passing the user data for next time app->addTimeout(object,ID_ANIMATIONSTEP,1000,ptr); } return 1; }
It is OK to call FXApp::removeTimeout() even when the timer has
already fired.
A typical scenario used in FOX programs is to remove any timers that may
be outstanding in the destructor of the target.
Since a timer is uniquely identified by the target and message-id,
a class using timers does not need to bother with bookkeeping issues
like keeping track of which timers are still in use.
This is very convenient in destructors:
// Destructor: just remove the timer, regarless of whether it was set MyObject::~MyObject(){ app->removeTimeout(this,ID_ANIMATIONSTEP); }
Which makes for much cleaner code.
Timers are fired when the application returns to the event loop.
This may cause timers to be invoked a bit later than specified by the timer interval.
For a single "one-shot" timeout, this jitter is typically small.
But for periodic timeouts, the amount of jitter may build up, causing the errors between
desired fire-time and actual fire-time to get larger and larger.
Furtunately, there is an alternate timeout method:
// Register Timer callback message at one second from now MyObject* object; void *userdata; FXTime later=FXThread::time()+1000000000L; app->addDeadline(object,ID_ANIMATIONSTEP,later,userdata);
This sets a timer to fire at time later, where later is
specified as absolute time in nanoseconds since Jan. 1st, 1970. Each time
the timer fires, we can add 1000000000 to get to the next timeout. This
way, the errors do not build up but stay within bounds.
Note, however, that this approach might be a slightly dangerous: we're assuming
that processing the timeout-handler takes less time than the amount of time
added to get to the next due-time. If processing the timeout-handler takes
more time, then the system might choke on handling nothing but timeouts!
Therefore, the addDeadline() method is recommended for long duraration
timeouts, such as e.g. updating a clock or something like that.
To check if a timer is in effect, the FXApp::hasTimeout() API can be used:
FXbool wasset=app->hasTimeout(object,ID_ANIMATIONSTEP);
It is also possible to determine the amount of time remaining before a timer becomes due, using the FXApp::remainingTimeout() API:
FXTime nanosecs=app->remainingTimeout(object,ID_ANIMATIONSTEP);This returns the amount of time remaining till the given timer expires; if no timer is set, the special value forever is returned. If the timer has already expired but has not yet been handled, 0 (zero) is returned.
Chore Messages
|
Chore messages are messages which are delivered to their target object when the application is about to block for events. They are used for background tasks which are to be performed when no other, more urgent tasks need to be performed. You can use chores for housekeeping tasks in your application, or perhaps for animations. A chore will fire as soon as the event stream is exhausted and there is nothing else for the application to do, this is why it is also sometimes referred to as idle processing.
When the chore message is fired, your object will receive a message of the type SEL_CHORE, with the message ID being the one which was registered. To intercept this message, here's how you would program your message map:
FXDEFMAP(MyObject) MyObjectMap[]={ FXMAPFUNC(SEL_CHORE,MyObject::ID_IDLETASK,MyObject::onIdleTask), ... };
As you see, it is very similar to timer callback processing. Setting or registering a chore callback message is similar as well, and is done by calling FXApp::addChore() as shown below:
// Register Chore callback message MyObject* object; void *userdata; app->addChore(object,ID_ANIMATIONSTEP,userdata);
Chores can be unregistered at any time prior to being fired, by calling FXApp::removeChore() with the same target and message-id as was used to register it in FXApp::addChore():
// Unregister Chore callback message app->removeChore(object,ID_ANIMATIONSTEP);
Like timers, it is OK to remove a chore that has already fired. Thus, classes which are receiving a chore message can remove it in the destructor, regardless of whether it has already fired or not. This can substantially simplify book keeping.
To check if a chore has been set, use the FXApp::hasChore() API:
FXbool wasset=app->hasChore(object,ID_ANIMATIONSTEP);
Some notes:
Signal Messages
|
Signal messages are generated when certain asynchronous events happen. On most systems, these events are generated in the form of POSIX signals. The POSIX signal facility is available on most systems to which FOX has been ported, although non-POSIX [e.g. BSD) signals should work also.
You can use Signal messages to allow FOX objects to receive signals and process them. For example, you could register a signal handler for SIGINT, so that an application may be closed down properly when the user hits Ctl-C on the controlling terminal. Another use might be to register a handler to catch the SIGFPE during a computation, so a warning panel can be popped for a divide by zero, and perhaps gracefully save the user's data rather than core dumping.
When a Signal message is sent, your target object will receive a message of the type SEL_SIGNAL with the ID being the one specified when the callback message was registered:
// Message map entry of "object" FXDEFMAP(MyObject) MyObjectMap[]={ FXMAPFUNC(SEL_SIGNAL,MyObject::ID_INTERRUPT,MyObject::onCleanUpAndQuit), ... };
A signal handler can be added by calling FXApp::addSignal(). There are two methods to deliver a signal to the application: synchronously, and asynchronously (immediately).
Synchronous or non-immediate signals are held until the application returns to the event loop, and then dispatched to the application. Thus, in most cases, the normal flow of computation in the application will not be interrupted, and your signal callback message handler can assume that all data structures are in a consistent state. Relatively harmless signals such as SIGINT are best handled synchronously.
Asynchronous or immediate signals are dispatched to the target object immediately. Since the regular processing of your application may have been interrupted by the signal, you will have to exercise extreme caution in the handler, as data structures may be partically complete. The immediate signal handlers are best reserved for last-ditch efforts, such as cleaning up after a SIGSEGV or SIGBUS, when a grave error has occured but there may be a chance to perhaps recover some of the user's data.
// Register Signal callback message app->addSignal(SIGINT,myobject,ID_INTERRUPT,FALSE,flags);
The flags are set as for POSIX signal handling facilities, pleace confer
your man pages for sigaction(2).
To remove the signal handler callback message and restore the default
signal handling action, you can call FXApp::removeSignal() as follows:
// Unregister Signal callback message app->removeSignal(SIGINT);
Input Messages
|
Input messages allow your programs to receive inputs from other sources than the GUI. Input messages can for example be used to watch sockets, pipes, and a host of other synchronization objects [if available on your machine].
Writing networked applications, such as e.g. a chat program, involves watching inputs from a number of different sources. You could have your program continuously check all these inputs for activity in a timer callback, but it is far more efficient to register an input source and yield the CPU until there is something going on.
Fortunately, most operating systems provide such a facility, and FOX can take advantage of this:
To register a callback message for an input source, you can call FXApp::addInput(). The callback message will remain registered even even after it has fired, unlike for Timers and Chores which are automatically removed after being fired once.
When a synchronization object becomes signaled, a message of the type SEL_IO_READ, SEL_IO_WRITE, or SEL_IO_EXCEPT will be sent to the target object, with the ID being the one specified in addInput(). You can intercept these messages as follows:
// Message map entry of "object" FXDEFMAP(MyObject) MyObjectMap[]={ FXMAPFUNC(SEL_IO_READ,MyObject::ID_ACCEPT,MyObject::onAcceptConnectionFromTheNet), FXMAPFUNC(SEL_IO_READ,MyObject::ID_SOCKET,MyObject::onReceivedInputFromTheNet), FXMAPFUNC(SEL_IO_WRITE,MyObject::ID_SOCKET,MyObject::onSendOutputToTheNet), FXMAPFUNC(SEL_IO_EXCEPT,MyObject::ID_SOCKET,MyObject::onDealWithExcept), ... };
In this example, a server type application may be creating a socket (socket(2)), and listen for incoming connections. When an incoming connection is received the callback handler onAcceptConnectionFromTheNet() presumably verifies the request and calls accept (accept(2)) and registers another handler to deal with incoming or outgoing data, and exceptional conditions.
You can register a input handler by calling FXApp::addInput().
// Accept the connection socket=accept(...); void* userdata; // Register input callback message app->addInput(myobject,ID_SOCKET,socket,INPUT_READ|INPUT_WRITE|INPUT_EXCEPT,userdata);
Passing INPUT_READ|INPUT_WRITE|INPUT_EXCEPT will register the same callback message handler ID for all three types of I/O activities. The optional user-data pointer is passed along to the message handler, to help it determine what to do. For example, you may want to pass along the socket file descriptor, cast to a void-pointer.
To remove a callback message handler, you can call FXApp::removeInput() as follows:
// Unregister input callback message app->removeInput(socket,INPUT_WRITE);
This will remove the callback message ID for I/O output.
It is usually a good idea for output, because the file descriptor will
remain signaled as long as there is buffering to accept more outgoing data.
You would add the INPUT_WRITE back only when buffers get full
[when the other party is tardy processing the data you're sending, lets
say].
Copyright © 1997-2022 Jeroen van der Zijp |