Next: , Previous: Bit Variables, Up: Software Environment


4.5 Event Handling

FreeWPC is mostly an event-driven system, which only acts when there are new inputs to the system. This section describes the basic interface to the application layer software, and then explains how that is implemented internally.

When any module wants to be notified when a particular event occurs, it declares an event catcher function, using the CALLSET_ENTRY macro. Here is an example:

     CALLSET_ENTRY (strobe_multiball, sw_forcefield_target)
     {
     	...
     }

This declares a new catcher for the module named strobe_multiball, which is to be called whenever the event sw_forcefield_target occurs.

The name of the module is arbitrary, but the convention is to use the name of the source code file or something similar. If you need to write two different handlers in the same source file for the same event, they need to have different module names.

The event name must exactly match the name used by the generator of that event. There is a list of predefined system events which are often caught (see System Events). Switch names can also be used as events (e.g. sw_free_kick_target). Normally a switch event is generated on an inactive-to-active transition; you can cause an event on any transition by declaring the switch as ‘edge’ in the machine configuration file. Ball devices also generate events for several reasons (see Ball Tracking), for example, dev_lock_enter.

If there are multiple catchers for the same event, then all of them will be invoked, in some random order. For example, another mode might also make use of the same target shown above:

     CALLSET_ENTRY (mode_start, sw_forcefield_target)
     {
     	...
     }

Then both of these handlers would be called when the switch is closed.

To generate an event, use the callset_invoke() API, passing it the name of the event. You can create your own events for any purpose; event names do not need to be declared. Such events are as full-featured as system-defined events. When defining custom events in a game, it is customary to prefix the event name with the short name of the game (e.g. ‘tz’).

Event catchers are allowed to throw new events. This will result in nested function calls.

4.5.1 Boolean Events

Most event handlers do not return a value. However, in some cases it is desirable to stop calling event handlers once the event has been claimed. You can do this with boolean events.

To throw a boolean event, use callset_invoke_boolean(). This returns TRUE if all of the event handlers return TRUE; else it returns FALSE.

The event handlers are declared using CALLSET_BOOL_ENTRY instead of CALLSET_ENTRY, and they must return either TRUE or FALSE.

The invocation of boolean handlers will immediately stop if one of them returns FALSE (so-called short-circuit evaluation).

4.5.2 Debugging Event Handlers

The runtime code generated by gencallset will call the macro callset_debug before invoking each of the handlers. This macro is passed one argument, which is a 16-bit integer that is unique to every handler. The default implementation is to save this in persistent memory; if a crash occurs, after rebooting the value can be examined to help determine the last function that caused the problem.

Previously, debugging required a compile-time flag set in CALLSET_FLAGS, but this is no longer needed.

For more thorough debugging, you can rewrite the implementation of callset_debug to do something else with those IDs, such as print them or set a breakpoint.

4.5.3 How Event Handlers Are Implemented

When you write a CALLSET_ENTRY, the module name and event name are concatenated to form the name of a function, separated by an underscore.

When you write a callset_invoke, it calls a function named callset_event. gencallset scans all of the source code and creates these functions; you can see the output in build/callset.c. So there is no queueing, memory allocation, or anything complicated — these are just ordinary function calls. The trick is to do all of the work at compile-time.

Because event handlers are just function calls, they can sometimes become deeply nested. For example, a start button press can cause many other events to be thrown. On the 6809 hardware, the stack size is limited and a stack overflow can occur if functions nest too deeply, AND a call to task_sleep is made. As long as you don't sleep, there is no problem; the limitation is in the size of the per-task stack area. start_game and test_start are particularly problematic sometimes. Avoid sleeping in such handlers to be safe; if necessary, use a periodic function instead.