1 /*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2016 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 "../thread/SDL_systhread.h"
28
29 /* #define DEBUG_TIMERS */
30
31 typedef struct _SDL_Timer
32 {
33 int timerID;
34 SDL_TimerCallback callback;
35 void *param;
36 Uint32 interval;
37 Uint32 scheduled;
38 SDL_atomic_t canceled;
39 struct _SDL_Timer *next;
40 } SDL_Timer;
41
42 typedef 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 */
50 typedef 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 *pending;
64 SDL_Timer *freelist;
65 SDL_atomic_t active;
66
67 /* List of timers - this is only touched by the timer thread */
68 SDL_Timer *timers;
69 } SDL_TimerData;
70
71 static 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
79 static void
SDL_AddTimerInternal(SDL_TimerData * data,SDL_Timer * timer)80 SDL_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
100 static int
SDL_TimerThread(void * _data)101 SDL_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 (!SDL_AtomicGet(&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 (SDL_AtomicGet(¤t->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 SDL_AtomicSet(¤t->canceled, 1);
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
205 int
SDL_TimerInit(void)206 SDL_TimerInit(void)
207 {
208 SDL_TimerData *data = &SDL_timer_data;
209
210 if (!SDL_AtomicGet(&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 SDL_AtomicSet(&data->active, 1);
224
225 /* Timer threads use a callback into the app, so we can't set a limited stack size here. */
226 data->thread = SDL_CreateThreadInternal(SDL_TimerThread, name, 0, data);
227 if (!data->thread) {
228 SDL_TimerQuit();
229 return -1;
230 }
231
232 SDL_AtomicSet(&data->nextID, 1);
233 }
234 return 0;
235 }
236
237 void
SDL_TimerQuit(void)238 SDL_TimerQuit(void)
239 {
240 SDL_TimerData *data = &SDL_timer_data;
241 SDL_Timer *timer;
242 SDL_TimerMap *entry;
243
244 if (SDL_AtomicCAS(&data->active, 1, 0)) { /* active? Move to inactive. */
245 /* Shutdown the timer thread */
246 if (data->thread) {
247 SDL_SemPost(data->sem);
248 SDL_WaitThread(data->thread, NULL);
249 data->thread = NULL;
250 }
251
252 SDL_DestroySemaphore(data->sem);
253 data->sem = NULL;
254
255 /* Clean up the timer entries */
256 while (data->timers) {
257 timer = data->timers;
258 data->timers = timer->next;
259 SDL_free(timer);
260 }
261 while (data->freelist) {
262 timer = data->freelist;
263 data->freelist = timer->next;
264 SDL_free(timer);
265 }
266 while (data->timermap) {
267 entry = data->timermap;
268 data->timermap = entry->next;
269 SDL_free(entry);
270 }
271
272 SDL_DestroyMutex(data->timermap_lock);
273 data->timermap_lock = NULL;
274 }
275 }
276
277 SDL_TimerID
SDL_AddTimer(Uint32 interval,SDL_TimerCallback callback,void * param)278 SDL_AddTimer(Uint32 interval, SDL_TimerCallback callback, void *param)
279 {
280 SDL_TimerData *data = &SDL_timer_data;
281 SDL_Timer *timer;
282 SDL_TimerMap *entry;
283
284 SDL_AtomicLock(&data->lock);
285 if (!SDL_AtomicGet(&data->active)) {
286 if (SDL_TimerInit() < 0) {
287 SDL_AtomicUnlock(&data->lock);
288 return 0;
289 }
290 }
291
292 timer = data->freelist;
293 if (timer) {
294 data->freelist = timer->next;
295 }
296 SDL_AtomicUnlock(&data->lock);
297
298 if (timer) {
299 SDL_RemoveTimer(timer->timerID);
300 } else {
301 timer = (SDL_Timer *)SDL_malloc(sizeof(*timer));
302 if (!timer) {
303 SDL_OutOfMemory();
304 return 0;
305 }
306 }
307 timer->timerID = SDL_AtomicIncRef(&data->nextID);
308 timer->callback = callback;
309 timer->param = param;
310 timer->interval = interval;
311 timer->scheduled = SDL_GetTicks() + interval;
312 SDL_AtomicSet(&timer->canceled, 0);
313
314 entry = (SDL_TimerMap *)SDL_malloc(sizeof(*entry));
315 if (!entry) {
316 SDL_free(timer);
317 SDL_OutOfMemory();
318 return 0;
319 }
320 entry->timer = timer;
321 entry->timerID = timer->timerID;
322
323 SDL_LockMutex(data->timermap_lock);
324 entry->next = data->timermap;
325 data->timermap = entry;
326 SDL_UnlockMutex(data->timermap_lock);
327
328 /* Add the timer to the pending list for the timer thread */
329 SDL_AtomicLock(&data->lock);
330 timer->next = data->pending;
331 data->pending = timer;
332 SDL_AtomicUnlock(&data->lock);
333
334 /* Wake up the timer thread if necessary */
335 SDL_SemPost(data->sem);
336
337 return entry->timerID;
338 }
339
340 SDL_bool
SDL_RemoveTimer(SDL_TimerID id)341 SDL_RemoveTimer(SDL_TimerID id)
342 {
343 SDL_TimerData *data = &SDL_timer_data;
344 SDL_TimerMap *prev, *entry;
345 SDL_bool canceled = SDL_FALSE;
346
347 /* Find the timer */
348 SDL_LockMutex(data->timermap_lock);
349 prev = NULL;
350 for (entry = data->timermap; entry; prev = entry, entry = entry->next) {
351 if (entry->timerID == id) {
352 if (prev) {
353 prev->next = entry->next;
354 } else {
355 data->timermap = entry->next;
356 }
357 break;
358 }
359 }
360 SDL_UnlockMutex(data->timermap_lock);
361
362 if (entry) {
363 if (!SDL_AtomicGet(&entry->timer->canceled)) {
364 SDL_AtomicSet(&entry->timer->canceled, 1);
365 canceled = SDL_TRUE;
366 }
367 SDL_free(entry);
368 }
369 return canceled;
370 }
371
372 /* vi: set ts=4 sw=4 expandtab: */
373