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 | |
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 |
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 SDLCALL |
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->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(¤t->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 | |
206 | int |
207 | SDL_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 | |
238 | void |
239 | SDL_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 | |
278 | SDL_TimerID |
279 | SDL_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 | |
341 | SDL_bool |
342 | SDL_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 | |