Mercurial > sdl-ios-xcode
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: */ |