comparison src/timer/SDL_timer.c @ 5113:481dabb098ef

Improved timer implementation The new timer model is formalized as using a separate thread to handle timer callbacks. This was the case on almost every platform before, but it's now a requirement, and simplifies the implementation and makes it perform consistently across platforms. Goals: * Minimize timer thread blocking * Dispatch timers as accurately as possible * SDL_AddTimer() and SDL_RemoveTimer() are completely threadsafe * SDL_RemoveTimer() doesn't crash with a timer that's expired or removed
author Sam Lantinga <slouken@libsdl.org>
date Thu, 27 Jan 2011 14:45:06 -0800
parents 906d7293bb47
children e337f792c6a7
comparison
equal deleted inserted replaced
5112:0846f18eb625 5113:481dabb098ef
21 */ 21 */
22 #include "SDL_config.h" 22 #include "SDL_config.h"
23 23
24 #include "SDL_timer.h" 24 #include "SDL_timer.h"
25 #include "SDL_timer_c.h" 25 #include "SDL_timer_c.h"
26 #include "SDL_mutex.h" 26 #include "SDL_atomic.h"
27 #include "SDL_systimer.h" 27 #include "SDL_thread.h"
28 28
29 /* #define DEBUG_TIMERS */ 29 /* #define DEBUG_TIMERS */
30 30
31 int SDL_timer_started = 0; 31 typedef struct _SDL_Timer
32 int SDL_timer_running = 0; 32 {
33 33 int timerID;
34 /* Data to handle a single periodic alarm */ 34 SDL_TimerCallback callback;
35 Uint32 SDL_alarm_interval = 0; 35 void *param;
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 {
43 Uint32 interval; 36 Uint32 interval;
44 SDL_NewTimerCallback cb; 37 Uint32 scheduled;
45 void *param; 38 volatile SDL_bool canceled;
46 Uint32 last_alarm; 39 struct _SDL_Timer *next;
47 struct _SDL_TimerID *next; 40 } SDL_Timer;
48 }; 41
49 42 typedef struct _SDL_TimerMap
50 static SDL_TimerID SDL_timers = NULL; 43 {
51 static SDL_mutex *SDL_timer_mutex; 44 int timerID;
52 static volatile SDL_bool list_changed = SDL_FALSE; 45 SDL_Timer *timer;
53 46 struct _SDL_TimerMap *next;
54 /* Set whether or not the timer should use a thread. 47 } SDL_TimerMap;
55 This should not be called while the timer subsystem is running. 48
56 */ 49 /* A reasonable guess */
57 int 50 #define CACHELINE_SIZE 128
58 SDL_SetTimerThreaded(int value) 51
59 { 52 /* The timers are kept in a sorted list */
60 int retval; 53 typedef struct {
61 54 /* Data used by the main thread */
62 if (SDL_timer_started) { 55 SDL_Thread *thread;
63 SDL_SetError("Timer already initialized"); 56 SDL_atomic_t nextID;
64 retval = -1; 57 SDL_TimerMap *timermap;
58 SDL_mutex *timermap_lock;
59
60 /* Padding to separate cache lines between threads */
61 char pad[CACHELINE_SIZE];
62
63 /* Data used to communicate with the timer thread */
64 SDL_SpinLock lock;
65 SDL_sem *sem;
66 SDL_Timer * volatile pending;
67 SDL_Timer * volatile freelist;
68 volatile SDL_bool active;
69
70 /* List of timers - this is only touched by the timer thread */
71 SDL_Timer *timers;
72 } SDL_TimerData;
73
74 static SDL_TimerData SDL_timer_data;
75
76
77 /* The idea here is that any thread might add a timer, but a single
78 * thread manages the active timer queue, sorted by scheduling time.
79 *
80 * Timers are removed by simply setting a canceled flag
81 */
82
83 static void
84 SDL_AddTimerInternal(SDL_TimerData *data, SDL_Timer *timer)
85 {
86 SDL_Timer *prev, *curr;
87
88 prev = NULL;
89 for (curr = data->timers; curr; prev = curr, curr = curr->next) {
90 if ((Sint32)(timer->scheduled-curr->scheduled) < 0) {
91 break;
92 }
93 }
94
95 /* Insert the timer here! */
96 if (prev) {
97 prev->next = timer;
65 } else { 98 } else {
66 retval = 0; 99 data->timers = timer;
67 SDL_timer_threaded = value; 100 }
68 } 101 timer->next = curr;
69 return retval; 102 }
103
104 static int
105 SDL_TimerThread(void *_data)
106 {
107 SDL_TimerData *data = (SDL_TimerData *)_data;
108 SDL_Timer *pending;
109 SDL_Timer *current;
110 SDL_Timer *freelist_head = NULL;
111 SDL_Timer *freelist_tail = NULL;
112 Uint32 tick, now, interval, delay;
113
114 /* Threaded timer loop:
115 * 1. Queue timers added by other threads
116 * 2. Handle any timers that should dispatch this cycle
117 * 3. Wait until next dispatch time or new timer arrives
118 */
119 for ( ; ; ) {
120 /* Pending and freelist maintenance */
121 SDL_AtomicLock(&data->lock);
122 {
123 /* Get any timers ready to be queued */
124 pending = data->pending;
125 data->pending = NULL;
126
127 /* Make any unused timer structures available */
128 if (freelist_head) {
129 freelist_tail->next = data->freelist;
130 data->freelist = freelist_head;
131 }
132 }
133 SDL_AtomicUnlock(&data->lock);
134
135 /* Sort the pending timers into our list */
136 while (pending) {
137 current = pending;
138 pending = pending->next;
139 SDL_AddTimerInternal(data, current);
140 }
141 freelist_head = NULL;
142 freelist_tail = NULL;
143
144 /* Check to see if we're still running, after maintenance */
145 if (!data->active) {
146 break;
147 }
148
149 /* Initial delay if there are no timers */
150 delay = SDL_MUTEX_MAXWAIT;
151
152 tick = SDL_GetTicks();
153
154 /* Process all the pending timers for this tick */
155 while (data->timers) {
156 current = data->timers;
157
158 if ((Sint32)(tick-current->scheduled) < 0) {
159 /* Scheduled for the future, wait a bit */
160 delay = (current->scheduled - tick);
161 break;
162 }
163
164 /* We're going to do something with this timer */
165 data->timers = current->next;
166
167 if (current->canceled) {
168 interval = 0;
169 } else {
170 interval = current->callback(current->interval, current->param);
171 }
172
173 if (interval > 0) {
174 /* Reschedule this timer */
175 current->scheduled = tick + interval;
176 SDL_AddTimerInternal(data, current);
177 } else {
178 if (!freelist_head) {
179 freelist_head = current;
180 }
181 if (freelist_tail) {
182 freelist_tail->next = current;
183 }
184 freelist_tail = current;
185
186 current->canceled = SDL_TRUE;
187 }
188 }
189
190 /* Adjust the delay based on processing time */
191 now = SDL_GetTicks();
192 interval = (now - tick);
193 if (interval > delay) {
194 delay = 0;
195 } else {
196 delay -= interval;
197 }
198
199 /* Note that each time a timer is added, this will return
200 immediately, but we process the timers added all at once.
201 That's okay, it just means we run through the loop a few
202 extra times.
203 */
204 SDL_SemWaitTimeout(data->sem, delay);
205 }
206 return 0;
70 } 207 }
71 208
72 int 209 int
73 SDL_TimerInit(void) 210 SDL_TimerInit(void)
74 { 211 {
75 int retval; 212 SDL_TimerData *data = &SDL_timer_data;
76 213
77 retval = 0; 214 if (!data->active) {
78 if (SDL_timer_started) { 215 data->timermap_lock = SDL_CreateMutex();
79 SDL_TimerQuit(); 216 if (!data->timermap_lock) {
80 } 217 return -1;
81 if (!SDL_timer_threaded) { 218 }
82 retval = SDL_SYS_TimerInit(); 219
83 } 220 data->sem = SDL_CreateSemaphore(0);
84 if (SDL_timer_threaded) { 221 if (!data->sem) {
85 SDL_timer_mutex = SDL_CreateMutex(); 222 SDL_DestroyMutex(data->timermap_lock);
86 } 223 return -1;
87 if (retval == 0) { 224 }
88 SDL_timer_started = 1; 225
89 } 226 data->active = SDL_TRUE;
90 return (retval); 227 data->thread = SDL_CreateThread(SDL_TimerThread, data);
228 if (!data->thread) {
229 SDL_TimerQuit();
230 return -1;
231 }
232
233 SDL_AtomicSet(&data->nextID, 1);
234 }
235 return 0;
91 } 236 }
92 237
93 void 238 void
94 SDL_TimerQuit(void) 239 SDL_TimerQuit(void)
95 { 240 {
96 SDL_SetTimer(0, NULL); 241 SDL_TimerData *data = &SDL_timer_data;
97 if (SDL_timer_threaded < 2) { 242 SDL_Timer *timer;
98 SDL_SYS_TimerQuit(); 243 SDL_TimerMap *entry;
99 } 244
100 if (SDL_timer_threaded) { 245 if (data->active) {
101 SDL_DestroyMutex(SDL_timer_mutex); 246 data->active = SDL_FALSE;
102 SDL_timer_mutex = NULL; 247
103 } 248 /* Shutdown the timer thread */
104 SDL_timer_started = 0; 249 if (data->thread) {
105 SDL_timer_threaded = 0; 250 SDL_SemPost(data->sem);
106 } 251 SDL_WaitThread(data->thread, NULL);
107 252 data->thread = NULL;
108 void 253 }
109 SDL_ThreadedTimerCheck(void) 254
110 { 255 SDL_DestroySemaphore(data->sem);
111 Uint32 now, ms; 256 data->sem = NULL;
112 SDL_TimerID t, prev, next; 257
113 SDL_bool removed; 258 /* Clean up the timer entries */
114 259 while (data->timers) {
115 SDL_mutexP(SDL_timer_mutex); 260 timer = data->timers;
116 261 data->timers = timer->next;
117 now = SDL_GetTicks(); 262 SDL_free(timer);
118 do { 263 }
119 list_changed = SDL_FALSE; 264 while (data->freelist) {
120 for (prev = NULL, t = SDL_timers; t; t = next) { 265 timer = data->freelist;
121 removed = SDL_FALSE; 266 data->freelist = timer->next;
122 ms = t->interval - SDL_TIMESLICE; 267 SDL_free(timer);
123 next = t->next; 268 }
124 if ((int) (now - t->last_alarm) > (int) ms) { 269 while (data->timermap) {
125 struct _SDL_TimerID timer; 270 entry = data->timermap;
126 271 data->timermap = entry->next;
127 if ((now - t->last_alarm) < t->interval) { 272 SDL_free(entry);
128 t->last_alarm += t->interval; 273 }
129 } else { 274
130 t->last_alarm = now; 275 SDL_DestroyMutex(data->timermap_lock);
131 } 276 data->timermap_lock = NULL;
132 #ifdef DEBUG_TIMERS 277 }
133 printf("Executing timer %p (thread = %lu)\n",
134 t, SDL_ThreadID());
135 #endif
136 timer = *t;
137 SDL_mutexV(SDL_timer_mutex);
138 ms = timer.cb(timer.interval, timer.param);
139 SDL_mutexP(SDL_timer_mutex);
140 if (list_changed) {
141 next = t->next;
142 for (prev = SDL_timers; prev; prev = prev->next) {
143 if (prev->next == t)
144 break;
145 }
146 }
147 if (ms != t->interval) {
148 if (ms) {
149 t->interval = ROUND_RESOLUTION(ms);
150 } else {
151 /* Remove timer from the list */
152 #ifdef DEBUG_TIMERS
153 printf("SDL: Removing timer %p\n", t);
154 #endif
155 if (prev) {
156 prev->next = next;
157 } else {
158 SDL_timers = next;
159 }
160 SDL_free(t);
161 --SDL_timer_running;
162 removed = SDL_TRUE;
163 }
164 }
165 if (list_changed) {
166 /* Abort, list of timers modified */
167 break;
168 }
169 }
170 /* Don't update prev if the timer has disappeared */
171 if (!removed) {
172 prev = t;
173 }
174 }
175 } while (list_changed);
176
177 SDL_mutexV(SDL_timer_mutex);
178 }
179
180 static SDL_TimerID
181 SDL_AddTimerInternal(Uint32 interval, SDL_NewTimerCallback callback,
182 void *param)
183 {
184 SDL_TimerID t;
185 t = (SDL_TimerID) SDL_malloc(sizeof(struct _SDL_TimerID));
186 if (t) {
187 t->interval = ROUND_RESOLUTION(interval);
188 t->cb = callback;
189 t->param = param;
190 t->last_alarm = SDL_GetTicks();
191 t->next = SDL_timers;
192 SDL_timers = t;
193 ++SDL_timer_running;
194 list_changed = SDL_TRUE;
195 }
196 #ifdef DEBUG_TIMERS
197 printf("SDL_AddTimer(%d) = %08x num_timers = %d\n", interval, (Uint32) t,
198 SDL_timer_running);
199 #endif
200 return t;
201 } 278 }
202 279
203 SDL_TimerID 280 SDL_TimerID
204 SDL_AddTimer(Uint32 interval, SDL_NewTimerCallback callback, void *param) 281 SDL_AddTimer(Uint32 interval, SDL_TimerCallback callback, void *param)
205 { 282 {
206 SDL_TimerID t; 283 SDL_TimerData *data = &SDL_timer_data;
207 if (!SDL_timer_mutex) { 284 SDL_Timer *timer;
208 if (SDL_timer_started) { 285 SDL_TimerMap *entry;
209 SDL_SetError("This platform doesn't support multiple timers"); 286
210 } else { 287 if (!data->active) {
211 SDL_SetError("You must call SDL_Init(SDL_INIT_TIMER) first"); 288 int status = 0;
212 } 289
213 return NULL; 290 SDL_AtomicLock(&data->lock);
214 } 291 if (!data->active) {
215 if (!SDL_timer_threaded) { 292 status = SDL_TimerInit();
216 SDL_SetError("Multiple timers require threaded events!"); 293 }
217 return NULL; 294 SDL_AtomicUnlock(&data->lock);
218 } 295
219 SDL_mutexP(SDL_timer_mutex); 296 if (status < 0) {
220 t = SDL_AddTimerInternal(interval, callback, param); 297 return 0;
221 SDL_mutexV(SDL_timer_mutex); 298 }
222 return t; 299 }
300
301 SDL_AtomicLock(&data->lock);
302 timer = data->freelist;
303 if (timer) {
304 data->freelist = timer->next;
305 }
306 SDL_AtomicUnlock(&data->lock);
307
308 if (timer) {
309 SDL_RemoveTimer(timer->timerID);
310 } else {
311 timer = (SDL_Timer *)SDL_malloc(sizeof(*timer));
312 if (!timer) {
313 SDL_OutOfMemory();
314 return 0;
315 }
316 }
317 timer->timerID = SDL_AtomicIncRef(&data->nextID);
318 timer->callback = callback;
319 timer->param = param;
320 timer->interval = interval;
321 timer->scheduled = SDL_GetTicks() + interval;
322 timer->canceled = SDL_FALSE;
323
324 entry = (SDL_TimerMap *)SDL_malloc(sizeof(*entry));
325 if (!entry) {
326 SDL_free(timer);
327 SDL_OutOfMemory();
328 return 0;
329 }
330 entry->timer = timer;
331 entry->timerID = timer->timerID;
332
333 SDL_mutexP(data->timermap_lock);
334 entry->next = data->timermap;
335 data->timermap = entry;
336 SDL_mutexV(data->timermap_lock);
337
338 /* Add the timer to the pending list for the timer thread */
339 SDL_AtomicLock(&data->lock);
340 timer->next = data->pending;
341 data->pending = timer;
342 SDL_AtomicUnlock(&data->lock);
343
344 /* Wake up the timer thread if necessary */
345 SDL_SemPost(data->sem);
346
347 return entry->timerID;
223 } 348 }
224 349
225 SDL_bool 350 SDL_bool
226 SDL_RemoveTimer(SDL_TimerID id) 351 SDL_RemoveTimer(SDL_TimerID id)
227 { 352 {
228 SDL_TimerID t, prev = NULL; 353 SDL_TimerData *data = &SDL_timer_data;
229 SDL_bool removed; 354 SDL_TimerMap *prev, *entry;
230 355 SDL_bool canceled = SDL_FALSE;
231 removed = SDL_FALSE; 356
232 SDL_mutexP(SDL_timer_mutex); 357 /* Find the timer */
233 /* Look for id in the linked list of timers */ 358 SDL_mutexP(data->timermap_lock);
234 for (t = SDL_timers; t; prev = t, t = t->next) { 359 prev = NULL;
235 if (t == id) { 360 for (entry = data->timermap; entry; prev = entry, entry = entry->next) {
361 if (entry->timerID == id) {
236 if (prev) { 362 if (prev) {
237 prev->next = t->next; 363 prev->next = entry->next;
238 } else { 364 } else {
239 SDL_timers = t->next; 365 data->timermap = entry->next;
240 } 366 }
241 SDL_free(t);
242 --SDL_timer_running;
243 removed = SDL_TRUE;
244 list_changed = SDL_TRUE;
245 break; 367 break;
246 } 368 }
247 } 369 }
248 #ifdef DEBUG_TIMERS 370 SDL_mutexV(data->timermap_lock);
249 printf("SDL_RemoveTimer(%08x) = %d num_timers = %d thread = %lu\n", 371
250 (Uint32) id, removed, SDL_timer_running, SDL_ThreadID()); 372 if (entry) {
251 #endif 373 if (!entry->timer->canceled) {
252 SDL_mutexV(SDL_timer_mutex); 374 entry->timer->canceled = SDL_TRUE;
253 return removed; 375 canceled = SDL_TRUE;
254 } 376 }
255 377 SDL_free(entry);
256 /* Old style callback functions are wrapped through this */ 378 }
257 static Uint32 SDLCALL 379 return canceled;
258 callback_wrapper(Uint32 ms, void *param)
259 {
260 SDL_TimerCallback func = (SDL_TimerCallback) param;
261 return (*func) (ms);
262 }
263
264 int
265 SDL_SetTimer(Uint32 ms, SDL_TimerCallback callback)
266 {
267 int retval;
268
269 #ifdef DEBUG_TIMERS
270 printf("SDL_SetTimer(%d)\n", ms);
271 #endif
272 retval = 0;
273
274 if (SDL_timer_threaded) {
275 SDL_mutexP(SDL_timer_mutex);
276 }
277 if (SDL_timer_running) { /* Stop any currently running timer */
278 if (SDL_timer_threaded) {
279 while (SDL_timers) {
280 SDL_TimerID freeme = SDL_timers;
281 SDL_timers = SDL_timers->next;
282 SDL_free(freeme);
283 }
284 SDL_timer_running = 0;
285 list_changed = SDL_TRUE;
286 } else {
287 SDL_SYS_StopTimer();
288 SDL_timer_running = 0;
289 }
290 }
291 if (ms) {
292 if (SDL_timer_threaded) {
293 if (SDL_AddTimerInternal
294 (ms, callback_wrapper, (void *) callback) == NULL) {
295 retval = -1;
296 }
297 } else {
298 SDL_timer_running = 1;
299 SDL_alarm_interval = ms;
300 SDL_alarm_callback = callback;
301 retval = SDL_SYS_StartTimer();
302 }
303 }
304 if (SDL_timer_threaded) {
305 SDL_mutexV(SDL_timer_mutex);
306 }
307
308 return retval;
309 } 380 }
310 381
311 /* vi: set ts=4 sw=4 expandtab: */ 382 /* vi: set ts=4 sw=4 expandtab: */