Karma Home Page
Next: Device-independent data-oriented graphics library Up: Karma Programming Manual Previous: Security

# Event generation and dispatch

A number of event management facilities are available in Karma, each designed for a different class of events. All schemes can co-exist in harmony. Each is described separately.

# Generic Callback Mechanism

This is based on the c package, and is used widely in the library wherever events have to be distributed through the library and/or application. Complex applications can also take advantage of this facility by limiting the flow of information/events between various components of the application.

Any event may be dispatched to an arbitrary number of callbacks (functions which are called to process an event) through the use of a callback list. The code (termed the ``service'') which wishes to dispatch the events need only make a call to c_call_callbacks, without any knowledge of the individual callbacks that are being called. All that is required is that the means is provided to add callbacks to the list, and this is done through the c_register_callback function. No code is needed to unregister callbacks, as this is done through the generic c_unregister_callback function, so clients of the service simply call that function when they are no longer interested in the event in question.

The package provides the flexibility to allow callback functions to ``consume'' events (i.e. the event is not dispatched any further).

# Periodic Events

Periodic events are useful in realtime process and instrumentation systems, where certain operations must be performed on a regular basis.

## Architecture

The event generation is separated from the dispatch and management in order to provide a fairly uniform interface on a variety of platforms. Of course, some platforms provide less functionality than others, but in general this should not prevent applications from working, rather, a small amount of performance may be lost.

### Generic Interface

The e package provides the generic support for manipulating periodic event lists. Callback functions may be added using the e_register_func function and removed with the e_unregister_func function from an event list. An unlimited number of functions may be registered with an event list, and, in theory, an arbitrary number of event lists may be created (not all drivers support this: see below).

Event functions are called periodically, measured in units of the event list timeout (which is determined at creation time for the event list). So, for example, if you wanted three event functions to be called, with 30 ms, 100 ms and 1 s intervals, respectively, you would create a single event list with timeout of 10 ms. Then each function would have an interval of 3, 10 and 100, respectively.

Events are dispatched at a number of levels (priorities), so that event functions registered with a higher class are called quickly. Some drivers have only a single dispatch class, in which case all event functions are called in the order they were registered with the event list.

An event list is created using a specific driver, described below:

### Unix Timer driver

The e package provides a Unix itimer (interval timer) implementation. Since Unix only defines one interval timer, only one event list may be created using the interval timer. This driver supports two levels of dispatch:

- Synchronous: dispatch synchronously with the application main thread. There is the very small possibility of jitter with this dispatch level, although events are not lost. You can reduce jitter by decreasing the event list timeout and increasing the interval for the functions
- Asynchronous: dispatch asynchronously (like a Unix signal handler). Use with caution, as race conditions may result if you manipulate application data structures within the event function. You must ensure that the total time taken by all asynchronous functions is less than the event list timeout, otherwise you will lose events

Note that the operating system will round up the specified interval to the timer resolution (typically 10 ms).

Jitter of synchronous events can occur if the interval timer times out while the process is not polling for file descriptor activity. While the application is outside the dm_native_poll function, any synchronous events will be queued. Provided dm_native_poll is called before the next timeout, there should be no jitter.

It would be possible to modify the implementation to reduce the possible jitter time down to the time taken to execute a system call. If you have an application which absolutely requires minimal jitter and you cannot use asynchronous events or use the minimum timer timeout, then please contact Richard Gooch so the implementation can be changed. However, please bear in mind that the probability of jitter is very small.

### Xt driver

The xtmisc package provides a driver which may be used with the Xt Intrinsics toolkit event management scheme. Note that you cannot use the Unix driver in an Xt application.

This driver allows multiple event lists, but all events are dispatched synchronously.

## Tutorials

A few examples follow which demonstrate how event functions may be set up in a variety of programming environments.

### Example 1

This example shows how event functions are setup and dispatched in a command-line application under a Unix operating system. Note that the panel package is used to provide the command-line user interface, and the module_run function contains the event handler loop. This function only returns when the application should finish.

A single event list is created with a 10 ms timeout. A ``fast'' (asynchronous) function is registered which is called every 10 timeouts (i.e. every 100 ms), and a ``slow'' (synchronous) function is registered which is called every 100 timeouts (i.e. every 1 s).

    /*----------------------------------------------------------*/
    /*  Event dispatch sample programme: Unix/command-line      */
    /*----------------------------------------------------------*/

    #include <stdio.h>
    #include <math.h>
    #include <karma.h>
    #include <karma_module.h>
    #include <karma_panel.h>
    #include <karma_m.h>
    #include <karma_e.h>

    #define VERSION "1.0"

    /*  Private functions  */
    STATIC_FUNCTION (flag fast_func, (KPeriodicEventFunc func, void *info) );
    STATIC_FUNCTION (flag slow_func, (KPeriodicEventFunc func, void *info) );

    /*  Public functions follow  */

    int main (int argc, char **argv)
    {
        KPeriodicEventList event_list;
        KControlPanel panel;
        static char function_name[] = "main";

        if ( ( panel = panel_create (FALSE) ) == NULL )
        {
            m_abort (function_name, "control panel");
        }
        panel_push_onto_stack (panel);
        event_list = e_unix_create_list (10000, 0, NULL);
        e_register_func (event_list, fast_func, NULL, 10, DISPATCH_ASYNCHRONOUS);
        e_register_func (event_list, slow_func, NULL, 100, DISPATCH_SYNCHRONOUS);
        module_run (argc, argv, "event-test", VERSION, NULL, -1, 0, FALSE);
        return (RV_OK);
    }   /*  End Function main   */

    /*  Private functions follow  */

    static flag fast_func (KPeriodicEventFunc func, void *info)
    /*  [SUMMARY] Event callback.
        [PURPOSE] This routine is called when a periodic event occurs.
        <func> The KPeriodicEventFunc object.
        <info> A pointer to arbitrary information.
        [RETURNS] TRUE if the event function should be called again, else FALSE.
    */
    {
        fprintf (stderr, "fast_func...\n");
        return (TRUE);
    }   /*  End Function fast_func  */

    static flag slow_func (KPeriodicEventFunc func, void *info)
    /*  [SUMMARY] Event callback.
        [PURPOSE] This routine is called when a periodic event occurs.
        <func> The KPeriodicEventFunc object.
        <info> A pointer to arbitrary information.
        [RETURNS] TRUE if the event function should be called again, else FALSE.
    */
    {
        fprintf (stderr, "slow_func...\n");
        return (TRUE);
    }   /*  End Function fast_func  */

### Example 2

This example is similar to the previous example, except that no command-line user interface is implemented, rather, the event handler loop is implemented directly. This example is more appropriate for a process/instrumentation control daemon which does not require any user input. The dm_native_poll function is used in an event handler loop. If the application did not require any communications support, the Unix ``pause()'' system call would suffice.

A single event list is created with a 10 ms timeout. A ``fast'' (asynchronous) function is registered which is called every 10 timeouts (i.e. every 100 ms), and a ``slow'' (synchronous) function is registered which is called every 100 timeouts (i.e. every 1 s).

    /*----------------------------------------------------------*/
    /*  Event dispatch sample programme: Unix/daemon            */
    /*----------------------------------------------------------*/

    #include <stdio.h>
    #include <math.h>
    #include <karma.h>
    #include <karma_dm.h>
    #include <karma_e.h>

    /*  Private functions  */
    STATIC_FUNCTION (flag fast_func, (KPeriodicEventFunc func, void *info) );
    STATIC_FUNCTION (flag slow_func, (KPeriodicEventFunc func, void *info) );

    /*  Public functions follow  */

    int main (int argc, char **argv)
    {
        KPeriodicEventList event_list;

        dm_native_setup ();
        event_list = e_unix_create_list (10000, 0, NULL);
        e_register_func (event_list, fast_func, NULL, 10, DISPATCH_ASYNCHRONOUS);
        e_register_func (event_list, slow_func, NULL, 100, DISPATCH_SYNCHRONOUS);
        while (TRUE) dm_native_poll (-1);
        return (RV_OK);
    }   /*  End Function main   */

    /*  Private functions follow  */

    static flag fast_func (KPeriodicEventFunc func, void *info)
    /*  [SUMMARY] Event callback.
        [PURPOSE] This routine is called when a periodic event occurs.
        <func> The KPeriodicEventFunc object.
        <info> A pointer to arbitrary information.
        [RETURNS] TRUE if the event function should be called again, else FALSE.
    */
    {
        fprintf (stderr, "fast_func...\n");
        return (TRUE);
    }   /*  End Function fast_func  */

    static flag slow_func (KPeriodicEventFunc func, void *info)
    /*  [SUMMARY] Event callback.
        [PURPOSE] This routine is called when a periodic event occurs.
        <func> The KPeriodicEventFunc object.
        <info> A pointer to arbitrary information.
        [RETURNS] TRUE if the event function should be called again, else FALSE.
    */
    {
        fprintf (stderr, "slow_func...\n");
        return (TRUE);
    }   /*  End Function fast_func  */

### Example 3

This example is similar to the previous examples, except that the Xt event dispatching mechanism is used.

A single event list is created with a 100 ms timeout. A ``fast'' (synchronous) function is registered which is called every timeout (i.e. every 100 ms), and a ``slow'' (synchronous) function is registered which is called every 10 timeouts (i.e. every 1 s).

    /*----------------------------------------------------------*/
    /*  Event dispatch sample programme: Xt                     */
    /*----------------------------------------------------------*/

    #include <stdio.h>
    #include <X11/Xatom.h>
    #include <X11/Intrinsic.h>
    #include <X11/StringDefs.h>
    #include <X11/Xaw/Command.h>
    #include <karma.h>
    #include <karma_xtmisc.h>
    #include <karma_e.h>

    /*  Private functions  */
    STATIC_FUNCTION (void quit_cbk,
                     (Widget w, XtPointer client_data, XtPointer call_data) );
    STATIC_FUNCTION (flag fast_func, (KPeriodicEventFunc func, void *info) );
    STATIC_FUNCTION (flag slow_func, (KPeriodicEventFunc func, void *info) );

    int main (int argc, char **argv)
    {
        KPeriodicEventList event_list;
        XtAppContext app_context;
        Widget main_shell, btn;

        /*  Start up Xt  */
        main_shell = XtVaAppInitialize (&app_context, "EventTest", NULL, 0,
                                        &argc, argv, NULL,
                                        NULL);
        btn = XtVaCreateManagedWidget ("quit", commandWidgetClass, main_shell,
                                       NULL);
        XtAddCallback (btn, XtNcallback, quit_cbk, NULL);
        XtRealizeWidget (main_shell);
        event_list = xtmisc_event_create_list (app_context, 100000, 0, NULL);
        e_register_func (event_list, fast_func, NULL, 1, DISPATCH_SYNCHRONOUS);
        e_register_func (event_list, slow_func, NULL, 10, DISPATCH_SYNCHRONOUS);
        XtAppMainLoop (app_context);
        return (RV_OK);
    }   /*  End Function main   */

    /*  Private functions follow  */

    static void quit_cbk (Widget w, XtPointer client_data, XtPointer call_data)
    {
        exit (RV_OK);
    }   /*  End Function quit_cbk  */

    static flag fast_func (KPeriodicEventFunc func, void *info)
    /*  [SUMMARY] Event callback.
        [PURPOSE] This routine is called when a periodic event occurs.
        <func> The KPeriodicEventFunc object.
        <info> A pointer to arbitrary information.
        [RETURNS] TRUE if the event function should be called again, else FALSE.
    */
    {
        fprintf (stderr, "fast_func...\n");
        return (TRUE);
    }   /*  End Function fast_func  */

    static flag slow_func (KPeriodicEventFunc func, void *info)
    /*  [SUMMARY] Event callback.
        [PURPOSE] This routine is called when a periodic event occurs.
        <func> The KPeriodicEventFunc object.
        <info> A pointer to arbitrary information.
        [RETURNS] TRUE if the event function should be called again, else FALSE.
    */
    {
        fprintf (stderr, "slow_func...\n");
        return (TRUE);
    }   /*  End Function fast_func  */

# Work Functions

This provides a means to implement backgound processing. This differs from the technique of creating a separate kernel scheduling context (often termed a ``thread'') in which the background processing is performed in that thread. The threaded approach suffers from the disadvantage that the background thread and the main thread compete for the system CPU resources. This is particularly noticable in applications with graphical user interfaces (GUIs), where the main thread controls the GUI, and reponse slows down while background processing is underway. The other disadvantage of the threaded approach is that more care is needed to ensure that global data is not modified/accessed at the same time. Threaded applications open up whole new areas of bugs.

The approach taken in Karma requires the application main context to periodically pass control to the work function while the application is idle. The work function must run for a sufficiently short period (100 ms is usually short enough) so that the user of the application does not notice any delays. While the application main context is busy, the work functions are not called, and hence the main context does not have to compete, and runs at maximum speed. The disadvantage of this technique is that the background processing must be organised so that it can divide up the work to be done in short segments and save it's state before it returns control back to the main context. In other words: it's more effort for the programmer. If you really prefer the threaded approach, you are free to do so (you may find the Karma mt package of use here).

## Architecture

The management and dispatch of work functions is separate from the passing of control from the main application context. Note that there is only one context for dispatching work functions.

### Generic Interface

The generic interface provides the means to register functions to be called when the main context is idle (see w_register_func) and unregister (see w_unregister_func). Other calls allow the application to test whether work functions are supported (in other words, if a driver is installed). A few priority levels are defined, which enhances the flexibility of the package.

### Standard driver

The w package provides the w_do_work function which dispatches the next work function. This may be used in conjunction with whatever polling loop the application employs.

### Xt driver

The xtmisc package provides a driver which may be used with the Xt Intrinsics toolkit event management scheme. All that is required is to call the xtmisc_support_work_funcs function.

Those familiar with Xt may wonder why not use Xt's native work procedure scheme, using the ``XtAppAddWorkProc'' function. The advantage of using the Karma scheme is firstly that it is more portable (i.e. the same interface may be used when switching to the standard driver), and secondly that it is more flexible, having a number of defined priorities.

## Tutorials

A few examples follow which demonstrate how work functions may be set up in a variety of programming environments.

### Example 1

This example shows how work functions are setup and dispatched in a command-line application under a Unix operating system. Note that the panel package is used to provide the command-line user interface, and the module_run function contains the event handler loop. This function only returns when the application should finish.

A single work function is registered, which does a small amount of work before returning control the the main application context. This work function can consume the full CPU resources, without slowing down the main context.

    /*----------------------------------------------------------*/
    /*  Work function sample programme: Unix/command-line       */
    /*----------------------------------------------------------*/

    #include <stdio.h>
    #include <math.h>
    #include <karma.h>
    #include <karma_module.h>
    #include <karma_panel.h>
    #include <karma_w.h>
    #include <karma_m.h>

    #define VERSION "1.0"

    /*  Private functions  */
    STATIC_FUNCTION (flag work_func, (void **info) );

    /*  Public functions follow  */

    int main (int argc, char **argv)
    {
        KControlPanel panel;
        static char function_name[] = "main";

        if ( ( panel = panel_create (FALSE) ) == NULL )
        {
            m_abort (function_name, "control panel");
        }
        panel_push_onto_stack (panel);
        w_register_support ();
        w_register_func (work_func, NULL, KWF_PRIORITY_HIGH);
        module_run (argc, argv, "work-test", VERSION, NULL, -1, 0, FALSE);
        return (RV_OK);
    }   /*  End Function main   */

    /*  Private functions follow  */

    static flag work_func (void **info)
    /*  [SUMMARY] Work function.
        [PURPOSE] This routine is called to perform some work.
        <info> A pointer to the arbitrary work function information pointer.
        [RETURNS] TRUE if the work function should be called again, else FALSE
        indicating that the work function is to be unregistered.
    */
    {
        int count;
        static int cycle = 0;

        /*  A simple "computation"  */
        for (count = 0; count < 10000000; ++count);
        if (++cycle > 10)
        {
            fprintf (stderr, "10 iterations passed...\n");
            cycle = 0;
        }
        return (TRUE);
    }   /*  End Function work_func  */

### Example 2

This example is similar to the previous example, except that no command-line user interface is implemented, rather, the event handler loop is implemented directly. This example is more appropriate for a process/instrumentation control daemon which does not require any user input. The dm_native_poll function is used in an event handler loop. If the application did not require any communications support, the w_do_work function would suffice.

A single work function is registered, which does a small amount of work before returning control the the main application context. This work function can consume the full CPU resources, without slowing down the main context.

    /*----------------------------------------------------------*/
    /*  Work function sample programme: Unix/daemon             */
    /*----------------------------------------------------------*/

    #include <stdio.h>
    #include <math.h>
    #include <karma.h>
    #include <karma_dm.h>
    #include <karma_w.h>

    /*  Private functions  */
    STATIC_FUNCTION (flag work_func, (void **info) );

    /*  Public functions follow  */

    int main (int argc, char **argv)
    {
        dm_native_setup ();
        w_register_support ();
        w_register_func (work_func, NULL, KWF_PRIORITY_HIGH);
        while (TRUE) dm_native_poll (-1);
        return (RV_OK);
    }   /*  End Function main   */

    /*  Private functions follow  */

    static flag work_func (void **info)
    /*  [SUMMARY] Work function.
        [PURPOSE] This routine is called to perform some work.
        <info> A pointer to the arbitrary work function information pointer.
        [RETURNS] TRUE if the work function should be called again, else FALSE
        indicating that the work function is to be unregistered.
    */
    {
        int count;
        static int cycle = 0;

        /*  A simple "computation"  */
        for (count = 0; count < 10000000; ++count);
        if (++cycle > 10)
        {
            fprintf (stderr, "10 iterations passed...\n");
            cycle = 0;
        }
        return (TRUE);
    }   /*  End Function work_func  */

### Example 3

This example is similar to the previous examples, except that the Xt event dispatching mechanism is used.

A single work function is registered, which does a small amount of work before returning control the the main application context. This work function can consume the full CPU resources, without slowing down the main context.

    /*----------------------------------------------------------*/
    /*  Event dispatch sample programme: Xt                     */
    /*----------------------------------------------------------*/

    #include <stdio.h>
    #include <X11/Xatom.h>
    #include <X11/Intrinsic.h>
    #include <X11/StringDefs.h>
    #include <X11/Xaw/Command.h>
    #include <karma.h>
    #include <karma_xtmisc.h>
    #include <karma_w.h>

    /*  Private functions  */
    STATIC_FUNCTION (void quit_cbk,
                     (Widget w, XtPointer client_data, XtPointer call_data) );
    STATIC_FUNCTION (flag work_func, (void **info) );

    int main (int argc, char **argv)
    {
        XtAppContext app_context;
        Widget main_shell, btn;

        /*  Start up Xt  */
        main_shell = XtVaAppInitialize (&app_context, "WorkTest", NULL, 0,
                                        &argc, argv, NULL,
                                        NULL);
        btn = XtVaCreateManagedWidget ("quit", commandWidgetClass, main_shell,
                                       NULL);
        XtAddCallback (btn, XtNcallback, quit_cbk, NULL);
        XtRealizeWidget (main_shell);
        xtmisc_support_work_funcs (app_context);
        w_register_func (work_func, NULL, KWF_PRIORITY_HIGH);
        XtAppMainLoop (app_context);
        return (RV_OK);
    }   /*  End Function main   */

    /*  Private functions follow  */

    static void quit_cbk (Widget w, XtPointer client_data, XtPointer call_data)
    {
        exit (RV_OK);
    }   /*  End Function quit_cbk  */

    static flag work_func (void **info)
    /*  [SUMMARY] Work function.
        [PURPOSE] This routine is called to perform some work.
        <info> A pointer to the arbitrary work function information pointer.
        [RETURNS] TRUE if the work function should be called again, else FALSE
        indicating that the work function is to be unregistered.
    */
    {
        int count;
        static int cycle = 0;

        /*  A simple "computation"  */
        for (count = 0; count < 10000000; ++count);
        if (++cycle > 10)
        {
            fprintf (stderr, "10 iterations passed...\n");
            cycle = 0;
        }
        return (TRUE);
    }   /*  End Function work_func  */

Karma Home Page
Next: Device-independent data-oriented graphics library Up: Karma Programming Manual Previous: Security