Home | History | Annotate | Download | only in timer
      1 /*
      2     SDL - Simple DirectMedia Layer
      3     Copyright (C) 1997-2012 Sam Lantinga
      4 
      5     This library is free software; you can redistribute it and/or
      6     modify it under the terms of the GNU Lesser General Public
      7     License as published by the Free Software Foundation; either
      8     version 2.1 of the License, or (at your option) any later version.
      9 
     10     This library is distributed in the hope that it will be useful,
     11     but WITHOUT ANY WARRANTY; without even the implied warranty of
     12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13     Lesser General Public License for more details.
     14 
     15     You should have received a copy of the GNU Lesser General Public
     16     License along with this library; if not, write to the Free Software
     17     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
     18 
     19     Sam Lantinga
     20     slouken (at) libsdl.org
     21 */
     22 #include "SDL_config.h"
     23 
     24 #include "SDL_timer.h"
     25 #include "SDL_timer_c.h"
     26 #include "SDL_mutex.h"
     27 #include "SDL_systimer.h"
     28 
     29 /* #define DEBUG_TIMERS */
     30 
     31 int SDL_timer_started = 0;
     32 int SDL_timer_running = 0;
     33 
     34 /* Data to handle a single periodic alarm */
     35 Uint32 SDL_alarm_interval = 0;
     36 SDL_TimerCallback SDL_alarm_callback;
     37 
     38 /* Data used for a thread-based timer */
     39 static int SDL_timer_threaded = 0;
     40 
     41 struct _SDL_TimerID {
     42 	Uint32 interval;
     43 	SDL_NewTimerCallback cb;
     44 	void *param;
     45 	Uint32 last_alarm;
     46 	struct _SDL_TimerID *next;
     47 };
     48 
     49 static SDL_TimerID SDL_timers = NULL;
     50 static SDL_mutex *SDL_timer_mutex;
     51 static volatile SDL_bool list_changed = SDL_FALSE;
     52 
     53 /* Set whether or not the timer should use a thread.
     54    This should not be called while the timer subsystem is running.
     55 */
     56 int SDL_SetTimerThreaded(int value)
     57 {
     58 	int retval;
     59 
     60 	if ( SDL_timer_started ) {
     61 		SDL_SetError("Timer already initialized");
     62 		retval = -1;
     63 	} else {
     64 		retval = 0;
     65 		SDL_timer_threaded = value;
     66 	}
     67 	return retval;
     68 }
     69 
     70 int SDL_TimerInit(void)
     71 {
     72 	int retval;
     73 
     74 	retval = 0;
     75 	if ( SDL_timer_started ) {
     76 		SDL_TimerQuit();
     77 	}
     78 	if ( ! SDL_timer_threaded ) {
     79 		retval = SDL_SYS_TimerInit();
     80 	}
     81 	if ( SDL_timer_threaded ) {
     82 		SDL_timer_mutex = SDL_CreateMutex();
     83 	}
     84 	if ( retval == 0 ) {
     85 		SDL_timer_started = 1;
     86 	}
     87 	return(retval);
     88 }
     89 
     90 void SDL_TimerQuit(void)
     91 {
     92 	SDL_SetTimer(0, NULL);
     93 	if ( SDL_timer_threaded < 2 ) {
     94 		SDL_SYS_TimerQuit();
     95 	}
     96 	if ( SDL_timer_threaded ) {
     97 		SDL_DestroyMutex(SDL_timer_mutex);
     98 		SDL_timer_mutex = NULL;
     99 	}
    100 	SDL_timer_started = 0;
    101 	SDL_timer_threaded = 0;
    102 }
    103 
    104 void SDL_ThreadedTimerCheck(void)
    105 {
    106 	Uint32 now, ms;
    107 	SDL_TimerID t, prev, next;
    108 	SDL_bool removed;
    109 
    110 	SDL_mutexP(SDL_timer_mutex);
    111 	list_changed = SDL_FALSE;
    112 	now = SDL_GetTicks();
    113 	for ( prev = NULL, t = SDL_timers; t; t = next ) {
    114 		removed = SDL_FALSE;
    115 		ms = t->interval - SDL_TIMESLICE;
    116 		next = t->next;
    117 		if ( (int)(now - t->last_alarm) > (int)ms ) {
    118 			struct _SDL_TimerID timer;
    119 
    120 			if ( (now - t->last_alarm) < t->interval ) {
    121 				t->last_alarm += t->interval;
    122 			} else {
    123 				t->last_alarm = now;
    124 			}
    125 #ifdef DEBUG_TIMERS
    126 			printf("Executing timer %p (thread = %d)\n",
    127 				t, SDL_ThreadID());
    128 #endif
    129 			timer = *t;
    130 			SDL_mutexV(SDL_timer_mutex);
    131 			ms = timer.cb(timer.interval, timer.param);
    132 			SDL_mutexP(SDL_timer_mutex);
    133 			if ( list_changed ) {
    134 				/* Abort, list of timers modified */
    135 				/* FIXME: what if ms was changed? */
    136 				break;
    137 			}
    138 			if ( ms != t->interval ) {
    139 				if ( ms ) {
    140 					t->interval = ROUND_RESOLUTION(ms);
    141 				} else {
    142 					/* Remove timer from the list */
    143 #ifdef DEBUG_TIMERS
    144 					printf("SDL: Removing timer %p\n", t);
    145 #endif
    146 					if ( prev ) {
    147 						prev->next = next;
    148 					} else {
    149 						SDL_timers = next;
    150 					}
    151 					SDL_free(t);
    152 					--SDL_timer_running;
    153 					removed = SDL_TRUE;
    154 				}
    155 			}
    156 		}
    157 		/* Don't update prev if the timer has disappeared */
    158 		if ( ! removed ) {
    159 			prev = t;
    160 		}
    161 	}
    162 	SDL_mutexV(SDL_timer_mutex);
    163 }
    164 
    165 static SDL_TimerID SDL_AddTimerInternal(Uint32 interval, SDL_NewTimerCallback callback, void *param)
    166 {
    167 	SDL_TimerID t;
    168 	t = (SDL_TimerID) SDL_malloc(sizeof(struct _SDL_TimerID));
    169 	if ( t ) {
    170 		t->interval = ROUND_RESOLUTION(interval);
    171 		t->cb = callback;
    172 		t->param = param;
    173 		t->last_alarm = SDL_GetTicks();
    174 		t->next = SDL_timers;
    175 		SDL_timers = t;
    176 		++SDL_timer_running;
    177 		list_changed = SDL_TRUE;
    178 	}
    179 #ifdef DEBUG_TIMERS
    180 	printf("SDL_AddTimer(%d) = %08x num_timers = %d\n", interval, (Uint32)t, SDL_timer_running);
    181 #endif
    182 	return t;
    183 }
    184 
    185 SDL_TimerID SDL_AddTimer(Uint32 interval, SDL_NewTimerCallback callback, void *param)
    186 {
    187 	SDL_TimerID t;
    188 	if ( ! SDL_timer_mutex ) {
    189 		if ( SDL_timer_started ) {
    190 			SDL_SetError("This platform doesn't support multiple timers");
    191 		} else {
    192 			SDL_SetError("You must call SDL_Init(SDL_INIT_TIMER) first");
    193 		}
    194 		return NULL;
    195 	}
    196 	if ( ! SDL_timer_threaded ) {
    197 		SDL_SetError("Multiple timers require threaded events!");
    198 		return NULL;
    199 	}
    200 	SDL_mutexP(SDL_timer_mutex);
    201 	t = SDL_AddTimerInternal(interval, callback, param);
    202 	SDL_mutexV(SDL_timer_mutex);
    203 	return t;
    204 }
    205 
    206 SDL_bool SDL_RemoveTimer(SDL_TimerID id)
    207 {
    208 	SDL_TimerID t, prev = NULL;
    209 	SDL_bool removed;
    210 
    211 	removed = SDL_FALSE;
    212 	SDL_mutexP(SDL_timer_mutex);
    213 	/* Look for id in the linked list of timers */
    214 	for (t = SDL_timers; t; prev=t, t = t->next ) {
    215 		if ( t == id ) {
    216 			if(prev) {
    217 				prev->next = t->next;
    218 			} else {
    219 				SDL_timers = t->next;
    220 			}
    221 			SDL_free(t);
    222 			--SDL_timer_running;
    223 			removed = SDL_TRUE;
    224 			list_changed = SDL_TRUE;
    225 			break;
    226 		}
    227 	}
    228 #ifdef DEBUG_TIMERS
    229 	printf("SDL_RemoveTimer(%08x) = %d num_timers = %d thread = %d\n", (Uint32)id, removed, SDL_timer_running, SDL_ThreadID());
    230 #endif
    231 	SDL_mutexV(SDL_timer_mutex);
    232 	return removed;
    233 }
    234 
    235 /* Old style callback functions are wrapped through this */
    236 static Uint32 SDLCALL callback_wrapper(Uint32 ms, void *param)
    237 {
    238 	SDL_TimerCallback func = (SDL_TimerCallback) param;
    239 	return (*func)(ms);
    240 }
    241 
    242 int SDL_SetTimer(Uint32 ms, SDL_TimerCallback callback)
    243 {
    244 	int retval;
    245 
    246 #ifdef DEBUG_TIMERS
    247 	printf("SDL_SetTimer(%d)\n", ms);
    248 #endif
    249 	retval = 0;
    250 
    251 	if ( SDL_timer_threaded ) {
    252 		SDL_mutexP(SDL_timer_mutex);
    253 	}
    254 	if ( SDL_timer_running ) {	/* Stop any currently running timer */
    255 		if ( SDL_timer_threaded ) {
    256 			while ( SDL_timers ) {
    257 				SDL_TimerID freeme = SDL_timers;
    258 				SDL_timers = SDL_timers->next;
    259 				SDL_free(freeme);
    260 			}
    261 			SDL_timer_running = 0;
    262 			list_changed = SDL_TRUE;
    263 		} else {
    264 			SDL_SYS_StopTimer();
    265 			SDL_timer_running = 0;
    266 		}
    267 	}
    268 	if ( ms ) {
    269 		if ( SDL_timer_threaded ) {
    270 			if ( SDL_AddTimerInternal(ms, callback_wrapper, (void *)callback) == NULL ) {
    271 				retval = -1;
    272 			}
    273 		} else {
    274 			SDL_timer_running = 1;
    275 			SDL_alarm_interval = ms;
    276 			SDL_alarm_callback = callback;
    277 			retval = SDL_SYS_StartTimer();
    278 		}
    279 	}
    280 	if ( SDL_timer_threaded ) {
    281 		SDL_mutexV(SDL_timer_mutex);
    282 	}
    283 
    284 	return retval;
    285 }
    286