1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2021 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{
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
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 *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
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 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
207SDL_TimerInit(void)
208{
209 SDL_TimerData *data = &SDL_timer_data;
210
211 if (!SDL_AtomicGet(&data->active)) {
212 const char *name = "SDLTimer";
213 data->timermap_lock = SDL_CreateMutex();
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. */
227 data->thread = SDL_CreateThreadInternal(SDL_TimerThread, name, 0, data);
228 if (!data->thread) {
229 SDL_TimerQuit();
230 return -1;
231 }
232
233 SDL_AtomicSet(&data->nextID, 1);
234 }
235 return 0;
236}
237
238void
239SDL_TimerQuit(void)
240{
241 SDL_TimerData *data = &SDL_timer_data;
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
253 SDL_DestroySemaphore(data->sem);
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
278SDL_TimerID
279SDL_AddTimer(Uint32 interval, SDL_TimerCallback callback, void *param)
280{
281 SDL_TimerData *data = &SDL_timer_data;
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) {
304 SDL_OutOfMemory();
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);
318 SDL_OutOfMemory();
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
341SDL_bool
342SDL_RemoveTimer(SDL_TimerID id)
343{
344 SDL_TimerData *data = &SDL_timer_data;
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: */
374