1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2020 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 SDLCALL
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(&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 
206 int
SDL_TimerInit(void)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
SDL_TimerQuit(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
SDL_AddTimer(Uint32 interval,SDL_TimerCallback callback,void * param)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
SDL_RemoveTimer(SDL_TimerID id)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