src/timer/SDL_timer.c
author Ryan C. Gordon
Tue, 24 Jan 2017 16:18:25 -0500
changeset 10850 c9dc0068b0e7
parent 10737 3406a0f8b041
child 11272 d60ede5a0c2e
permissions -rw-r--r--
configure: report libsamplerate support status.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2017 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
   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->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(&current->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
   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
   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
   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
   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: */