Multiple timers patch for SDL 1.1

Here is a patch to add multiple timers support to SDL 1.1. This is done in the
generic threaded code, and has been tested on Linux only, although it is likely
to work with other systems with threaded timers.

The older API (SetTimer()) is still here and compatible, although I
reimplemented it on top of the newer API that allows multiple timers. Here are
the new functions :

/* Function prototype for the new timer callback function */
typedef Uint32 (*SDL_NewTimerCallback)(Uint32 interval, void *param);

/* Definition of the timer ID type */
typedef void *SDL_TimerID;

/* Add a new timer to the pool of timers already running.
Returns a timer ID, or NULL when an error occurs.
*/
extern DECLSPEC SDL_TimerID SDL_AddTimer(Uint32 interval, SDL_NewTimerCallback
callback, void *param);

/* Remove one of the multiple timers knowing its ID.

  • Returns a boolean value indicating success.
    */
    extern DECLSPEC int SDL_RemoveTimer(SDL_TimerID id);

The callback function can now take a parameter; each timer is identified by an
unique ID. There is no limit to the maximum number of timers running at the
same time (other than memory ;)). Note that you can’t use SDL_SetTimer() and
SDL_AddTimer() at the same time: to preserve compatibility, SDL_SetTimer() will
remove all existing timers and will manage only one timer…

The implementation was more straightforward than I expected since I figured
that I didn’t have to bother with checking intervals - the SDL event thread
just calls the check function as often as possible, which is good enough for
multiple timers.

I have also modified testtimer.c to include test of multiple timers. All this
is used successfully in SC3K so far ;-)–
Stephane Peter
Programmer
Loki Entertainment Software

“Microsoft has done to computers what McDonald’s has done to gastronomy”
-------------- next part --------------
Index: include/SDL_timer.h

RCS file: /cvs/SDL/include/SDL_timer.h,v
retrieving revision 1.4.2.3
diff -u -r1.4.2.3 SDL_timer.h
— include/SDL_timer.h 2000/03/16 15:20:37 1.4.2.3
+++ include/SDL_timer.h 2000/03/23 04:38:29
@@ -84,6 +84,24 @@
*/
extern DECLSPEC int SDL_SetTimer(Uint32 interval, SDL_TimerCallback callback);

+/* New timer API, supports multiple timers

  • */

+/* Function prototype for the new timer callback function */
+typedef Uint32 (*SDL_NewTimerCallback)(Uint32 interval, void param);
+
+/
Definition of the timer ID type */
+typedef void SDL_TimerID;
+
+/
Add a new timer to the pool of timers already running.

  • Returns a timer ID, or NULL when an error occurs.
  • */
    +extern DECLSPEC SDL_TimerID SDL_AddTimer(Uint32 interval, SDL_NewTimerCallback
    callback, void *param);

+/* Remove one of the multiple timers knowing its ID.

    • Returns a boolean value indicating success.
  • */
    +extern DECLSPEC int SDL_RemoveTimer(SDL_TimerID id);

/* Ends C function definitions when using C++ */
#ifdef __cplusplus
Index: src/timer/SDL_timer.c

RCS file: /cvs/SDL/src/timer/Attic/SDL_timer.c,v
retrieving revision 1.1.2.3
diff -u -r1.1.2.3 SDL_timer.c
— src/timer/SDL_timer.c 2000/01/17 07:39:17 1.1.2.3
+++ src/timer/SDL_timer.c 2000/03/23 04:38:35
@@ -32,19 +32,26 @@
#include “SDL_error.h”
#include “SDL_timer.h”
#include “SDL_timer_c.h”
+#include “SDL_mutex.h”
#include “SDL_systimer.h”

int SDL_timer_started = 0;
int SDL_timer_running = 0;

-/* Data to handle a single periodic alarm */
-Uint32 SDL_alarm_interval = 0;
-SDL_TimerCallback SDL_alarm_callback;

/* Data used for a thread-based timer */
static int SDL_timer_threaded = 0;
-static Uint32 last_alarm;

+typedef struct _sdl_timer {

  • Uint32 interval;
  • SDL_NewTimerCallback cb;
  • void *param;
  • Uint32 last_alarm;
  • struct _sdl_timer *next;
    +} sdl_timer;

+static sdl_timer *timers = NULL;
+static Uint32 num_timers = 0;
+static SDL_mutex *timer_mutex;

/* Set whether or not the timer should use a thread.
This should be called while the timer subsystem is running.
@@ -71,6 +78,7 @@
SDL_SetTimer(0, NULL);
if ( SDL_timer_threaded ) {
retval = 0;

  •   timer_mutex = SDL_CreateMutex();
    
    } else {
    retval = SDL_SYS_TimerInit();
    }
    @@ -84,55 +92,123 @@
    if ( SDL_timer_threaded < 2 ) {
    SDL_SYS_TimerQuit();
    }
  • if ( SDL_timer_threaded ) {
  •   SDL_DestroyMutex(timer_mutex);
    
  • }
    SDL_timer_started = 0;
    }

-/* This function is called from the SDL event thread if it is available */
void SDL_ThreadedTimerCheck(void)
{
Uint32 now, ms;

  • sdl_timer *t;

    now = SDL_GetTicks();

  • /* If we are within SDL_TIMESLICE ms of target time, execute! */
  • ms = (SDL_alarm_interval-SDL_TIMESLICE);
  • if ( (last_alarm < now) && ((now - last_alarm) > ms) ) {
  •   if ( (now - last_alarm) < SDL_alarm_interval ) {
    
  •   	last_alarm = last_alarm+SDL_alarm_interval;
    
  •   } else {
    
  •   	last_alarm = now;
    
  • SDL_mutexP(timer_mutex);
  • for ( t = timers; t ; t = t->next ) {
  •   ms = t->interval - SDL_TIMESLICE;
    
  •   if ( (t->last_alarm < now) && ((now - t->last_alarm) > ms) ) {
    
  •   	if ( (now - t->last_alarm) < t->interval ) {
    
  •   		t->last_alarm += t->interval;
    
  •   	} else {
    
  •   		t->last_alarm = now;
    
  •   	}
    
  •   	ms = t->cb(t->interval, t->param);
    
  •   	if ( ms != t->interval ) {
    
  •   		if ( ms ) {
    
  •   			t->interval = ROUND_RESOLUTION(ms);
    
  •   		} else {
    
  •   			SDL_mutexV(timer_mutex);
    
  •   			SDL_RemoveTimer(t);
    
  •   			SDL_mutexP(timer_mutex);
    
  •   		}
    
  •   	}
      }
    
  •   ms = (*SDL_alarm_callback)(SDL_alarm_interval);
    
  •   if ( ms != SDL_alarm_interval ) {
    
  •   	if ( ms ) {
    
  •   		SDL_alarm_interval = ROUND_RESOLUTION(ms);
    
  • }
  • SDL_mutexV(timer_mutex);
    +}

+SDL_TimerID SDL_AddTimer(Uint32 interval, SDL_NewTimerCallback callback, void *
param)
+{

  • sdl_timer *t;
  • if ( ! SDL_timer_threaded ) {
  •   SDL_SetError("Multiple timers require threaded events!");
    
  •   return NULL;
    
  • }
  • SDL_mutexP(timer_mutex);
  • t = (sdl_timer *) malloc(sizeof(sdl_timer));
  • if ( t ) {
  •   t->interval = ROUND_RESOLUTION(interval);
    
  •   t->cb = callback;
    
  •   t->param = param;
    
  •   t->last_alarm = 0;
    
  •   t->next = timers;
    
  •   timers = t;
    
  •   SDL_timer_running = 1;
    
  • }
  • SDL_mutexV(timer_mutex);
  • return t;
    +}

+int SDL_RemoveTimer(SDL_TimerID id)
+{

  • sdl_timer *t, *prev = NULL;
  • SDL_mutexP(timer_mutex);
  • /* Look for id in the linked list of timers */
  • for (t = timers; t; prev=t, t = t->next ) {
  •   if ( t == (sdl_timer *) id ) {
    
  •   	if(prev) {
    
  •   		prev->next = t->next;
      	} else {
    
  •   		SDL_SetTimer(0, NULL);
    
  •   		timers = t->next;
      	}
    
  •   	free(t);
    
  •   	SDL_mutexV(timer_mutex);
    
  •   	return 1;
      }
    
    }
  • SDL_mutexV(timer_mutex);
  • return 0;
    }

-int SDL_SetTimer (Uint32 ms, SDL_TimerCallback callback)
+static void SDL_RemoveAllTimers(sdl_timer *t)
{

  • if (t) {
  •   SDL_RemoveAllTimers(t->next);
    
  •   free(t);
    
  • }
    +}

+/* Old style callback functions are wrapped through this */
+static Uint32 callback_wrapper(Uint32 ms, void *param)
+{

  • SDL_TimerCallback func = (SDL_TimerCallback) param;
  • return (*func)(ms);
    +}

+int SDL_SetTimer(Uint32 ms, SDL_TimerCallback callback)
+{
int retval;

retval = 0;
if ( SDL_timer_running ) {	/* Stop any currently running timer */
	SDL_timer_running = 0;
	if ( SDL_timer_threaded ) {
  •   	last_alarm = 0;
    
  •   	SDL_mutexP(timer_mutex);
    
  •   	SDL_RemoveAllTimers(timers);
    
  •   	timers = NULL;
    
  •   	SDL_mutexV(timer_mutex);
      } else {
      	SDL_SYS_StopTimer();
      }
    
    }
  • ms = ROUND_RESOLUTION(ms);
  • SDL_alarm_interval = ms;
  • SDL_alarm_callback = callback;
  • if ( SDL_alarm_interval ) { /* Start a new timer, if desired */
  •   SDL_timer_running = 1;
    
  • if ( ms ) {
    if ( SDL_timer_threaded ) {
  •   	last_alarm = SDL_GetTicks();
    
  •   	retval = (SDL_AddTimer(ms, callback_wrapper, callback) != NULL);
      } else {
    
  •   	SDL_timer_running = 1;
      	retval = SDL_SYS_StartTimer();
      }
    
    }
    Index: test/testtimer.c
    ===================================================================
    RCS file: /cvs/SDL/test/testtimer.c,v
    retrieving revision 1.2
    diff -u -r1.2 testtimer.c
    — test/testtimer.c 1999/12/09 22:31:53 1.2
    +++ test/testtimer.c 2000/03/23 04:38:37
    @@ -17,9 +17,16 @@
    return(interval);
    }

+static Uint32 callback(Uint32 interval, void *param)
+{

  • printf(“Timer %d : param = %d\n”, interval, (int) param);
  • return interval;
    +}

main(int argc, char *argv[])
{
int desired;

  • SDL_TimerID t1, t2, t3;

    if ( SDL_Init(SDL_INIT_TIMER) < 0 ) {
    fprintf(stderr, “Couldn’t load SDL: %s\n”, SDL_GetError());
    @@ -50,5 +57,30 @@
    “Timer resolution: desired = %d ms, actual = %f ms\n”,
    desired, (double)(10*1000)/ticks);
    }

  • /* Test multiple timers */

  • printf(“Testing multiple timers…\n”);

  • t1 = SDL_AddTimer(100, callback, (void*)1);

  • if(!t1)

  • fprintf(stderr,"Could not create timer 1\n");
    
  • t2 = SDL_AddTimer(50, callback, (void*)2);

  • if(!t1)

  • fprintf(stderr,"Could not create timer 2\n");
    
  • t3 = SDL_AddTimer(233, callback, (void*)3);

  • if(!t1)

  • fprintf(stderr,"Could not create timer 3\n");
    
  • /* Wait 10 seconds */

  • printf(“Waiting 10 seconds\n”);

  • SDL_Delay(10*1000);

  • printf(“Removing timer 1 and waiting 5 more seconds\n”);

  • SDL_RemoveTimer(t1);

  • SDL_Delay(5*1000);

  • SDL_RemoveTimer(t2);

  • SDL_RemoveTimer(t3);

  • exit(0);
    }