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 minuteIf 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;and in your initialisation code you should lock the memory:void my_timer_handler() { counter++; }
END_OF_FUNCTION(my_timer_handler);
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.