SDL_timer.c (10431B)
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2014 Sam Lantinga <slouken@libsdl.org> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21#include "../SDL_internal.h" 22 23#include "SDL_timer.h" 24#include "SDL_timer_c.h" 25#include "SDL_atomic.h" 26#include "SDL_cpuinfo.h" 27#include "SDL_thread.h" 28 29/* #define DEBUG_TIMERS */ 30 31typedef struct _SDL_Timer 32{ 33 int timerID; 34 SDL_TimerCallback callback; 35 void *param; 36 Uint32 interval; 37 Uint32 scheduled; 38 volatile SDL_bool canceled; 39 struct _SDL_Timer *next; 40} SDL_Timer; 41 42typedef struct _SDL_TimerMap 43{ 44 int timerID; 45 SDL_Timer *timer; 46 struct _SDL_TimerMap *next; 47} SDL_TimerMap; 48 49/* The timers are kept in a sorted list */ 50typedef struct { 51 /* Data used by the main thread */ 52 SDL_Thread *thread; 53 SDL_atomic_t nextID; 54 SDL_TimerMap *timermap; 55 SDL_mutex *timermap_lock; 56 57 /* Padding to separate cache lines between threads */ 58 char cache_pad[SDL_CACHELINE_SIZE]; 59 60 /* Data used to communicate with the timer thread */ 61 SDL_SpinLock lock; 62 SDL_sem *sem; 63 SDL_Timer * volatile pending; 64 SDL_Timer * volatile freelist; 65 volatile SDL_bool active; 66 67 /* List of timers - this is only touched by the timer thread */ 68 SDL_Timer *timers; 69} SDL_TimerData; 70 71static SDL_TimerData SDL_timer_data; 72 73/* The idea here is that any thread might add a timer, but a single 74 * thread manages the active timer queue, sorted by scheduling time. 75 * 76 * Timers are removed by simply setting a canceled flag 77 */ 78 79static void 80SDL_AddTimerInternal(SDL_TimerData *data, SDL_Timer *timer) 81{ 82 SDL_Timer *prev, *curr; 83 84 prev = NULL; 85 for (curr = data->timers; curr; prev = curr, curr = curr->next) { 86 if ((Sint32)(timer->scheduled-curr->scheduled) < 0) { 87 break; 88 } 89 } 90 91 /* Insert the timer here! */ 92 if (prev) { 93 prev->next = timer; 94 } else { 95 data->timers = timer; 96 } 97 timer->next = curr; 98} 99 100static int 101SDL_TimerThread(void *_data) 102{ 103 SDL_TimerData *data = (SDL_TimerData *)_data; 104 SDL_Timer *pending; 105 SDL_Timer *current; 106 SDL_Timer *freelist_head = NULL; 107 SDL_Timer *freelist_tail = NULL; 108 Uint32 tick, now, interval, delay; 109 110 /* Threaded timer loop: 111 * 1. Queue timers added by other threads 112 * 2. Handle any timers that should dispatch this cycle 113 * 3. Wait until next dispatch time or new timer arrives 114 */ 115 for ( ; ; ) { 116 /* Pending and freelist maintenance */ 117 SDL_AtomicLock(&data->lock); 118 { 119 /* Get any timers ready to be queued */ 120 pending = data->pending; 121 data->pending = NULL; 122 123 /* Make any unused timer structures available */ 124 if (freelist_head) { 125 freelist_tail->next = data->freelist; 126 data->freelist = freelist_head; 127 } 128 } 129 SDL_AtomicUnlock(&data->lock); 130 131 /* Sort the pending timers into our list */ 132 while (pending) { 133 current = pending; 134 pending = pending->next; 135 SDL_AddTimerInternal(data, current); 136 } 137 freelist_head = NULL; 138 freelist_tail = NULL; 139 140 /* Check to see if we're still running, after maintenance */ 141 if (!data->active) { 142 break; 143 } 144 145 /* Initial delay if there are no timers */ 146 delay = SDL_MUTEX_MAXWAIT; 147 148 tick = SDL_GetTicks(); 149 150 /* Process all the pending timers for this tick */ 151 while (data->timers) { 152 current = data->timers; 153 154 if ((Sint32)(tick-current->scheduled) < 0) { 155 /* Scheduled for the future, wait a bit */ 156 delay = (current->scheduled - tick); 157 break; 158 } 159 160 /* We're going to do something with this timer */ 161 data->timers = current->next; 162 163 if (current->canceled) { 164 interval = 0; 165 } else { 166 interval = current->callback(current->interval, current->param); 167 } 168 169 if (interval > 0) { 170 /* Reschedule this timer */ 171 current->scheduled = tick + interval; 172 SDL_AddTimerInternal(data, current); 173 } else { 174 if (!freelist_head) { 175 freelist_head = current; 176 } 177 if (freelist_tail) { 178 freelist_tail->next = current; 179 } 180 freelist_tail = current; 181 182 current->canceled = SDL_TRUE; 183 } 184 } 185 186 /* Adjust the delay based on processing time */ 187 now = SDL_GetTicks(); 188 interval = (now - tick); 189 if (interval > delay) { 190 delay = 0; 191 } else { 192 delay -= interval; 193 } 194 195 /* Note that each time a timer is added, this will return 196 immediately, but we process the timers added all at once. 197 That's okay, it just means we run through the loop a few 198 extra times. 199 */ 200 SDL_SemWaitTimeout(data->sem, delay); 201 } 202 return 0; 203} 204 205int 206SDL_TimerInit(void) 207{ 208 SDL_TimerData *data = &SDL_timer_data; 209 210 if (!data->active) { 211 const char *name = "SDLTimer"; 212 data->timermap_lock = SDL_CreateMutex(); 213 if (!data->timermap_lock) { 214 return -1; 215 } 216 217 data->sem = SDL_CreateSemaphore(0); 218 if (!data->sem) { 219 SDL_DestroyMutex(data->timermap_lock); 220 return -1; 221 } 222 223 data->active = SDL_TRUE; 224 /* !!! FIXME: this is nasty. */ 225#if defined(__WIN32__) && !defined(HAVE_LIBC) 226#undef SDL_CreateThread 227#if SDL_DYNAMIC_API 228 data->thread = SDL_CreateThread_REAL(SDL_TimerThread, name, data, NULL, NULL); 229#else 230 data->thread = SDL_CreateThread(SDL_TimerThread, name, data, NULL, NULL); 231#endif 232#else 233 data->thread = SDL_CreateThread(SDL_TimerThread, name, data); 234#endif 235 if (!data->thread) { 236 SDL_TimerQuit(); 237 return -1; 238 } 239 240 SDL_AtomicSet(&data->nextID, 1); 241 } 242 return 0; 243} 244 245void 246SDL_TimerQuit(void) 247{ 248 SDL_TimerData *data = &SDL_timer_data; 249 SDL_Timer *timer; 250 SDL_TimerMap *entry; 251 252 if (data->active) { 253 data->active = SDL_FALSE; 254 255 /* Shutdown the timer thread */ 256 if (data->thread) { 257 SDL_SemPost(data->sem); 258 SDL_WaitThread(data->thread, NULL); 259 data->thread = NULL; 260 } 261 262 SDL_DestroySemaphore(data->sem); 263 data->sem = NULL; 264 265 /* Clean up the timer entries */ 266 while (data->timers) { 267 timer = data->timers; 268 data->timers = timer->next; 269 SDL_free(timer); 270 } 271 while (data->freelist) { 272 timer = data->freelist; 273 data->freelist = timer->next; 274 SDL_free(timer); 275 } 276 while (data->timermap) { 277 entry = data->timermap; 278 data->timermap = entry->next; 279 SDL_free(entry); 280 } 281 282 SDL_DestroyMutex(data->timermap_lock); 283 data->timermap_lock = NULL; 284 } 285} 286 287SDL_TimerID 288SDL_AddTimer(Uint32 interval, SDL_TimerCallback callback, void *param) 289{ 290 SDL_TimerData *data = &SDL_timer_data; 291 SDL_Timer *timer; 292 SDL_TimerMap *entry; 293 294 if (!data->active) { 295 int status = 0; 296 297 SDL_AtomicLock(&data->lock); 298 if (!data->active) { 299 status = SDL_TimerInit(); 300 } 301 SDL_AtomicUnlock(&data->lock); 302 303 if (status < 0) { 304 return 0; 305 } 306 } 307 308 SDL_AtomicLock(&data->lock); 309 timer = data->freelist; 310 if (timer) { 311 data->freelist = timer->next; 312 } 313 SDL_AtomicUnlock(&data->lock); 314 315 if (timer) { 316 SDL_RemoveTimer(timer->timerID); 317 } else { 318 timer = (SDL_Timer *)SDL_malloc(sizeof(*timer)); 319 if (!timer) { 320 SDL_OutOfMemory(); 321 return 0; 322 } 323 } 324 timer->timerID = SDL_AtomicIncRef(&data->nextID); 325 timer->callback = callback; 326 timer->param = param; 327 timer->interval = interval; 328 timer->scheduled = SDL_GetTicks() + interval; 329 timer->canceled = SDL_FALSE; 330 331 entry = (SDL_TimerMap *)SDL_malloc(sizeof(*entry)); 332 if (!entry) { 333 SDL_free(timer); 334 SDL_OutOfMemory(); 335 return 0; 336 } 337 entry->timer = timer; 338 entry->timerID = timer->timerID; 339 340 SDL_LockMutex(data->timermap_lock); 341 entry->next = data->timermap; 342 data->timermap = entry; 343 SDL_UnlockMutex(data->timermap_lock); 344 345 /* Add the timer to the pending list for the timer thread */ 346 SDL_AtomicLock(&data->lock); 347 timer->next = data->pending; 348 data->pending = timer; 349 SDL_AtomicUnlock(&data->lock); 350 351 /* Wake up the timer thread if necessary */ 352 SDL_SemPost(data->sem); 353 354 return entry->timerID; 355} 356 357SDL_bool 358SDL_RemoveTimer(SDL_TimerID id) 359{ 360 SDL_TimerData *data = &SDL_timer_data; 361 SDL_TimerMap *prev, *entry; 362 SDL_bool canceled = SDL_FALSE; 363 364 /* Find the timer */ 365 SDL_LockMutex(data->timermap_lock); 366 prev = NULL; 367 for (entry = data->timermap; entry; prev = entry, entry = entry->next) { 368 if (entry->timerID == id) { 369 if (prev) { 370 prev->next = entry->next; 371 } else { 372 data->timermap = entry->next; 373 } 374 break; 375 } 376 } 377 SDL_UnlockMutex(data->timermap_lock); 378 379 if (entry) { 380 if (!entry->timer->canceled) { 381 entry->timer->canceled = SDL_TRUE; 382 canceled = SDL_TRUE; 383 } 384 SDL_free(entry); 385 } 386 return canceled; 387} 388 389/* vi: set ts=4 sw=4 expandtab: */