Timer routines



Allegro can set up several virtual timer functions, all going at different speeds.

Under DOS it will constantly reprogram the clock to make sure they are all called at the correct times. Because they alter the low level timer chip settings, these routines should not be used together with other DOS timer functions like the djgpp uclock() routine.

Under other platforms, they are usually implemented using threads, which run parallel to the main thread. Therefore timer callbacks on such platforms will not block the main thread when called, so you may need to use appropriate synchronisation devices (eg. mutexes, semaphores, etc.) when accessing data that is shared by a callback and the main thread. (Currently Allegro does not provide such devices.)

int install_timer();
Installs the Allegro timer interrupt handler. You must do this before installing any user timer routines, and also before displaying a mouse pointer, playing FLI animations or MIDI music, and using any of the GUI routines. Returns zero on success, or a negative number on failure (but you may decide not to check the return value as this function is very unlikely to fail).

void remove_timer();
Removes the Allegro timer handler (and, under DOS, passes control of the clock back to the operating system). You don't normally need to bother calling this, because allegro_exit() will do it for you.

int install_int(void (*proc)(), int speed);
Installs a user timer handler, with the speed given as the number of milliseconds between ticks. This is the same thing as install_int_ex(proc, MSEC_TO_TIMER(speed)). If you call this routine without having first installed the timer module, install_timer() will be called automatically. If there is no room to add a new user timer, install_int() will return a negative number, otherwise it returns zero.

int install_int_ex(void (*proc)(), int speed);
Adds a function to the list of user timer handlers or, if it is already installed, retroactively adjusts its speed (i.e makes as though the speed change occured precisely at the last tick). The speed is given in hardware clock ticks, of which there are 1193181 a second. You can convert from other time formats to hardware clock ticks with the macros:

      SECS_TO_TIMER(secs)  - give the number of seconds between
                             each tick
      MSEC_TO_TIMER(msec)  - give the number of milliseconds
                             between ticks
      BPS_TO_TIMER(bps)    - give the number of ticks each second
      BPM_TO_TIMER(bpm)    - give the number of ticks per minute

If there is no room to add a new user timer, install_int_ex() will return a negative number, otherwise it returns zero. There can only be sixteen timers in use at a time, and some other parts of Allegro (the GUI code, the mouse pointer display routines, rest(), the FLI player, and the MIDI player) need to install handlers of their own, so you should avoid using too many at the same time. If you call this routine without having first installed the timer module, install_timer() will be called automatically.

Your function will be called by the Allegro interrupt handler and not directly by the processor, so it can be a normal C function and does not need a special wrapper. You should be aware, however, that it will be called in an interrupt context, which imposes a lot of restrictions on what you can do in it. It should not use large amounts of stack, it must not make any calls to the operating system, use C library functions, or contain any floating point code, and it must execute very quickly. Don't try to do lots of complicated code in a timer handler: as a general rule you should just set some flags and respond to these later in your main control loop.

In a DOS protected mode environment like djgpp, memory is virtualised and can be swapped to disk. Due to the non-reentrancy of DOS, if a disk swap occurs inside an interrupt handler the system will die a painful death, so you need to make sure you lock all the memory (both code and data) that is touched inside timer routines. Allegro will lock everything it uses, but you are responsible for locking your handler functions. The macros LOCK_VARIABLE (variable), END_OF_FUNCTION (function_name), END_OF_STATIC_FUNCTION (function_name), and LOCK_FUNCTION (function_name) can be used to simplify this task. For example, if you want an interrupt handler that increments a counter variable, you should write:

      volatile int counter;

void my_timer_handler() { counter++; }

END_OF_FUNCTION(my_timer_handler);

and in your initialisation code you should lock the memory:

      LOCK_VARIABLE(counter);
      LOCK_FUNCTION(my_timer_handler);

Obviously this can get awkward if you use complicated data structures and call other functions from within your handler, so you should try to keep your interrupt routines as simple as possible.

void remove_int(void (*proc)());
Removes a function from the list of user interrupt routines. At program termination, allegro_exit() does this automatically.

int install_param_int(void (*proc)(void *), void *param, int speed);
Like install_int(), but the callback routine will be passed a copy of the specified void pointer parameter. To disable the handler, use remove_param_int() instead of remove_int().

int install_param_int_ex(void (*proc)(void *), void *param, int speed);
Like install_int_ex(), but the callback routine will be passed a copy of the specified void pointer parameter. To disable the handler, use remove_param_int() instead of remove_int().

void remove_param_int(void (*proc)(void *), void *param);
Like remove_int(), but for use with timer callbacks that have parameter values. If there is more than one copy of the same callback active at a time, it identifies which one to remove by checking the parameter value (so you can't have more than one copy of a handler using an identical parameter).

int timer_can_simulate_retrace()
Checks whether it is possible to sync the timer module with the monitor retrace, given the current platform and environment (at the moment this is only possible when running in clean DOS mode in a VGA or mode-X resolution). Returns non-zero if simulation is possible.

void timer_simulate_retrace(int enable);
The DOS timer handler can be used to simulate vertical retrace interrupts. A retrace interrupt can be extremely useful for implementing smooth animation, but unfortunately the VGA hardware doesn't support it. The EGA did, and some SVGA chipsets do, but not enough, and not in a sufficiently standardised way, for it to be useful. Allegro works around this by programming the timer to generate an interrupt when it thinks a retrace is next likely to occur, and polling the VGA inside the interrupt handler to make sure it stays in sync with the monitor refresh. This works quite well in some situations, but there are a lot of caveats:

- You can't use the retrace simulator in SVGA modes. It will work with some chipsets, but not others, and it conflicts with most VESA implementations. Retrace simulation is only reliable in VGA mode 13h and mode-X.

- Retrace simulation doesn't work under win95, because win95 returns garbage when I try to read the elapsed time from the PIT. If anyone knows how I can make this work, please tell me!

- Retrace simulation involves a lot of waiting around in the timer handler with interrupts disabled. This will significantly slow down your entire system, and may also cause static when playing samples on SB 1.0 cards (because they don't support auto-initialised DMA: SB 2.0 and above will be fine).

Bearing all those problems in mind, I'd strongly advise against relying on the retrace simulator. If you are coding in mode-X, and don't care about your program working under win95, it is great, but it would be a good idea to give the user an option to disable it.

Retrace simulation must be enabled before you use the triple buffering functions in a mode-X resolution. It can also be useful for simple retrace detection, because the polling vsync() function can occasionally miss retraces if a soundcard or timer interrupt occurs at exactly the same time as the retrace. When retrace interrupt simulation is enabled, vsync() will check the retrace_count variable rather than polling the VGA, so it won't miss retraces even if they are masked by other interrupts.

int timer_is_using_retrace()
Checks whether the timer module is currently synced with the monitor retrace or not. Returns non-zero if it is.

extern volatile int retrace_count;
If the retrace simulator is installed, this is incremented on each vertical retrace, otherwise it is incremented 70 times a second (ignoring retraces). This provides a useful way of controlling the speed of your program without the hassle of installing user timer functions.

The speed of retraces varies depending on the graphics mode. In mode 13h and 200/400 line mode-X resolutions there are 70 retraces a second, and in 240/480 line modes there are 60. It can be as low as 50 (in 376x282 mode) or as high as 92 (in 400x300 mode).

extern void (*retrace_proc)();
If the retrace simulator is installed, this function is called during every vertical retrace, otherwise it is called 70 times a second (ignoring retraces). Set it to NULL to disable the callback. The function must obey the same rules as regular timer handlers (ie. it must be locked, and mustn't call OS or libc functions) but even more so: it must execute _very_ quickly, or it will mess up the timer synchronisation. The only use I can see for this function is for doing palette manipulations, because triple buffering can be done with the request_scroll() function, and the retrace_count variable can be used for timing your code. If you want to alter the palette in the retrace_proc you should use the inline _set_color() function rather than the regular set_color() or set_palette(), and you shouldn't try to alter more than two or three palette entries in a single retrace.

void rest(long time);
Once Allegro has taken over the timer the standard delay() function will no longer work, so you should use this routine instead. The time is given in milliseconds.

void rest_callback(long time, void (*callback)())
Like rest(), but continually calls the specified function while it is waiting for the required time to elapse.




Back to Contents