SDL 2.0
SDL_timer.c
Go to the documentation of this file.
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2019 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
31typedef struct _SDL_Timer
32{
35 void *param;
39 struct _SDL_Timer *next;
40} SDL_Timer;
41
42typedef struct _SDL_TimerMap
43{
46 struct _SDL_TimerMap *next;
48
49/* The timers are kept in a sorted list */
50typedef struct {
51 /* Data used by the main thread */
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 */
62 SDL_sem *sem;
66
67 /* List of timers - this is only touched by the timer thread */
70
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
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 SDLCALL
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 (!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(&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->interval = interval;
172 current->scheduled = tick + interval;
173 SDL_AddTimerInternal(data, current);
174 } else {
175 if (!freelist_head) {
176 freelist_head = current;
177 }
178 if (freelist_tail) {
179 freelist_tail->next = current;
180 }
181 freelist_tail = current;
182
183 SDL_AtomicSet(&current->canceled, 1);
184 }
185 }
186
187 /* Adjust the delay based on processing time */
188 now = SDL_GetTicks();
189 interval = (now - tick);
190 if (interval > delay) {
191 delay = 0;
192 } else {
193 delay -= interval;
194 }
195
196 /* Note that each time a timer is added, this will return
197 immediately, but we process the timers added all at once.
198 That's okay, it just means we run through the loop a few
199 extra times.
200 */
201 SDL_SemWaitTimeout(data->sem, delay);
202 }
203 return 0;
204}
205
206int
208{
210
211 if (!SDL_AtomicGet(&data->active)) {
212 const char *name = "SDLTimer";
214 if (!data->timermap_lock) {
215 return -1;
216 }
217
218 data->sem = SDL_CreateSemaphore(0);
219 if (!data->sem) {
220 SDL_DestroyMutex(data->timermap_lock);
221 return -1;
222 }
223
224 SDL_AtomicSet(&data->active, 1);
225
226 /* Timer threads use a callback into the app, so we can't set a limited stack size here. */
228 if (!data->thread) {
230 return -1;
231 }
232
233 SDL_AtomicSet(&data->nextID, 1);
234 }
235 return 0;
236}
237
238void
240{
242 SDL_Timer *timer;
243 SDL_TimerMap *entry;
244
245 if (SDL_AtomicCAS(&data->active, 1, 0)) { /* active? Move to inactive. */
246 /* Shutdown the timer thread */
247 if (data->thread) {
248 SDL_SemPost(data->sem);
249 SDL_WaitThread(data->thread, NULL);
250 data->thread = NULL;
251 }
252
254 data->sem = NULL;
255
256 /* Clean up the timer entries */
257 while (data->timers) {
258 timer = data->timers;
259 data->timers = timer->next;
260 SDL_free(timer);
261 }
262 while (data->freelist) {
263 timer = data->freelist;
264 data->freelist = timer->next;
265 SDL_free(timer);
266 }
267 while (data->timermap) {
268 entry = data->timermap;
269 data->timermap = entry->next;
270 SDL_free(entry);
271 }
272
273 SDL_DestroyMutex(data->timermap_lock);
274 data->timermap_lock = NULL;
275 }
276}
277
280{
282 SDL_Timer *timer;
283 SDL_TimerMap *entry;
284
285 SDL_AtomicLock(&data->lock);
286 if (!SDL_AtomicGet(&data->active)) {
287 if (SDL_TimerInit() < 0) {
288 SDL_AtomicUnlock(&data->lock);
289 return 0;
290 }
291 }
292
293 timer = data->freelist;
294 if (timer) {
295 data->freelist = timer->next;
296 }
297 SDL_AtomicUnlock(&data->lock);
298
299 if (timer) {
300 SDL_RemoveTimer(timer->timerID);
301 } else {
302 timer = (SDL_Timer *)SDL_malloc(sizeof(*timer));
303 if (!timer) {
305 return 0;
306 }
307 }
308 timer->timerID = SDL_AtomicIncRef(&data->nextID);
309 timer->callback = callback;
310 timer->param = param;
311 timer->interval = interval;
312 timer->scheduled = SDL_GetTicks() + interval;
313 SDL_AtomicSet(&timer->canceled, 0);
314
315 entry = (SDL_TimerMap *)SDL_malloc(sizeof(*entry));
316 if (!entry) {
317 SDL_free(timer);
319 return 0;
320 }
321 entry->timer = timer;
322 entry->timerID = timer->timerID;
323
324 SDL_LockMutex(data->timermap_lock);
325 entry->next = data->timermap;
326 data->timermap = entry;
327 SDL_UnlockMutex(data->timermap_lock);
328
329 /* Add the timer to the pending list for the timer thread */
330 SDL_AtomicLock(&data->lock);
331 timer->next = data->pending;
332 data->pending = timer;
333 SDL_AtomicUnlock(&data->lock);
334
335 /* Wake up the timer thread if necessary */
336 SDL_SemPost(data->sem);
337
338 return entry->timerID;
339}
340
343{
345 SDL_TimerMap *prev, *entry;
346 SDL_bool canceled = SDL_FALSE;
347
348 /* Find the timer */
349 SDL_LockMutex(data->timermap_lock);
350 prev = NULL;
351 for (entry = data->timermap; entry; prev = entry, entry = entry->next) {
352 if (entry->timerID == id) {
353 if (prev) {
354 prev->next = entry->next;
355 } else {
356 data->timermap = entry->next;
357 }
358 break;
359 }
360 }
361 SDL_UnlockMutex(data->timermap_lock);
362
363 if (entry) {
364 if (!SDL_AtomicGet(&entry->timer->canceled)) {
365 SDL_AtomicSet(&entry->timer->canceled, 1);
366 canceled = SDL_TRUE;
367 }
368 SDL_free(entry);
369 }
370 return canceled;
371}
372
373/* vi: set ts=4 sw=4 expandtab: */
int SDL_SpinLock
Definition: SDL_atomic.h:89
#define SDL_AtomicIncRef(a)
Increment an atomic variable used as a reference count.
Definition: SDL_atomic.h:252
#define SDL_CACHELINE_SIZE
Definition: SDL_cpuinfo.h:114
#define SDL_AtomicSet
#define SDL_AtomicLock
#define SDL_AtomicCAS
#define SDL_SemPost
#define SDL_LockMutex
#define SDL_malloc
#define SDL_SemWaitTimeout
#define SDL_DestroySemaphore
#define SDL_CreateMutex
#define SDL_AtomicUnlock
#define SDL_free
#define SDL_CreateSemaphore
#define SDL_WaitThread
#define SDL_AtomicGet
#define SDL_DestroyMutex
#define SDL_UnlockMutex
#define SDL_OutOfMemory()
Definition: SDL_error.h:52
#define SDLCALL
Definition: SDL_internal.h:49
#define SDL_MUTEX_MAXWAIT
Definition: SDL_mutex.h:49
GLint GLenum GLsizei GLsizei GLsizei GLint GLsizei const GLvoid * data
Definition: SDL_opengl.h:1974
GLfloat param
GLuint const GLchar * name
SDL_bool
Definition: SDL_stdinc.h:162
@ SDL_TRUE
Definition: SDL_stdinc.h:164
@ SDL_FALSE
Definition: SDL_stdinc.h:163
uint32_t Uint32
Definition: SDL_stdinc.h:203
int32_t Sint32
Definition: SDL_stdinc.h:197
SDL_Thread * SDL_CreateThreadInternal(int(*fn)(void *), const char *name, const size_t stacksize, void *data)
Definition: SDL_thread.c:429
static void SDL_AddTimerInternal(SDL_TimerData *data, SDL_Timer *timer)
Definition: SDL_timer.c:80
static SDL_TimerData SDL_timer_data
Definition: SDL_timer.c:71
SDL_bool SDL_RemoveTimer(SDL_TimerID id)
Remove a timer knowing its ID.
Definition: SDL_timer.c:342
void SDL_TimerQuit(void)
Definition: SDL_timer.c:239
SDL_TimerID SDL_AddTimer(Uint32 interval, SDL_TimerCallback callback, void *param)
Add a new timer to the pool of timers already running.
Definition: SDL_timer.c:279
static int SDL_TimerThread(void *_data)
Definition: SDL_timer.c:101
int SDL_TimerInit(void)
Definition: SDL_timer.c:207
Uint32 SDL_GetTicks(void)
Get the number of milliseconds since the SDL library initialization.
Uint32(* SDL_TimerCallback)(Uint32 interval, void *param)
Definition: SDL_timer.h:81
int SDL_TimerID
Definition: SDL_timer.h:86
#define NULL
Definition: begin_code.h:167
SDL_sem * sem
Definition: SDL_timer.c:62
SDL_SpinLock lock
Definition: SDL_timer.c:61
SDL_Timer * timers
Definition: SDL_timer.c:68
SDL_mutex * timermap_lock
Definition: SDL_timer.c:55
SDL_atomic_t nextID
Definition: SDL_timer.c:53
SDL_Thread * thread
Definition: SDL_timer.c:52
SDL_Timer * freelist
Definition: SDL_timer.c:64
SDL_atomic_t active
Definition: SDL_timer.c:65
SDL_TimerMap * timermap
Definition: SDL_timer.c:54
SDL_Timer * pending
Definition: SDL_timer.c:63
Uint32 scheduled
Definition: SDL_timer.c:37
SDL_atomic_t canceled
Definition: SDL_timer.c:38
struct _SDL_Timer * next
Definition: SDL_timer.c:39
void * param
Definition: SDL_timer.c:35
SDL_TimerCallback callback
Definition: SDL_timer.c:34
Uint32 interval
Definition: SDL_timer.c:36
int timerID
Definition: SDL_timer.c:33
struct _SDL_TimerMap * next
Definition: SDL_timer.c:46
SDL_Timer * timer
Definition: SDL_timer.c:45
A type representing an atomic integer value. It is a struct so people don't accidentally use numeric ...
Definition: SDL_atomic.h:216
static Uint32 callback(Uint32 interval, void *param)
Definition: testtimer.c:34