src/timer/SDL_timer.c
author Sam Lantinga <slouken@libsdl.org>
Thu, 21 Nov 2019 14:04:48 -0800
changeset 13274 b948d820ae75
parent 12503 806492103856
permissions -rw-r--r--
Added support for the PDP Battlefield One controller
     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 
    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(&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
   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: */