src/timer/SDL_timer.c
author Philipp Wiesemann <philipp.wiesemann@arcor.de>
Sun, 27 Mar 2016 22:22:13 +0200
changeset 10123 d2686deec646
parent 10003 d91a2c45825e
child 10146 471eb08040ce
permissions -rw-r--r--
Fixed comment in gesture source.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2016 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 "SDL_thread.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         /* !!! FIXME: this is nasty. */
   225 #if defined(__WIN32__) && !defined(HAVE_LIBC)
   226 #undef SDL_CreateThread
   227 #if SDL_DYNAMIC_API
   228         data->thread = SDL_CreateThread_REAL(SDL_TimerThread, name, data, NULL, NULL);
   229 #else
   230         data->thread = SDL_CreateThread(SDL_TimerThread, name, data, NULL, NULL);
   231 #endif
   232 #else
   233         data->thread = SDL_CreateThread(SDL_TimerThread, name, data);
   234 #endif
   235         if (!data->thread) {
   236             SDL_TimerQuit();
   237             return -1;
   238         }
   239 
   240         SDL_AtomicSet(&data->nextID, 1);
   241     }
   242     return 0;
   243 }
   244 
   245 void
   246 SDL_TimerQuit(void)
   247 {
   248     SDL_TimerData *data = &SDL_timer_data;
   249     SDL_Timer *timer;
   250     SDL_TimerMap *entry;
   251 
   252     if (SDL_AtomicCAS(&data->active, 1, 0)) {  /* active? Move to inactive. */
   253         /* Shutdown the timer thread */
   254         if (data->thread) {
   255             SDL_SemPost(data->sem);
   256             SDL_WaitThread(data->thread, NULL);
   257             data->thread = NULL;
   258         }
   259 
   260         SDL_DestroySemaphore(data->sem);
   261         data->sem = NULL;
   262 
   263         /* Clean up the timer entries */
   264         while (data->timers) {
   265             timer = data->timers;
   266             data->timers = timer->next;
   267             SDL_free(timer);
   268         }
   269         while (data->freelist) {
   270             timer = data->freelist;
   271             data->freelist = timer->next;
   272             SDL_free(timer);
   273         }
   274         while (data->timermap) {
   275             entry = data->timermap;
   276             data->timermap = entry->next;
   277             SDL_free(entry);
   278         }
   279 
   280         SDL_DestroyMutex(data->timermap_lock);
   281         data->timermap_lock = NULL;
   282     }
   283 }
   284 
   285 SDL_TimerID
   286 SDL_AddTimer(Uint32 interval, SDL_TimerCallback callback, void *param)
   287 {
   288     SDL_TimerData *data = &SDL_timer_data;
   289     SDL_Timer *timer;
   290     SDL_TimerMap *entry;
   291 
   292     SDL_AtomicLock(&data->lock);
   293     if (!SDL_AtomicGet(&data->active)) {
   294         if (SDL_TimerInit() < 0) {
   295             SDL_AtomicUnlock(&data->lock);
   296             return 0;
   297         }
   298     }
   299 
   300     timer = data->freelist;
   301     if (timer) {
   302         data->freelist = timer->next;
   303     }
   304     SDL_AtomicUnlock(&data->lock);
   305 
   306     if (timer) {
   307         SDL_RemoveTimer(timer->timerID);
   308     } else {
   309         timer = (SDL_Timer *)SDL_malloc(sizeof(*timer));
   310         if (!timer) {
   311             SDL_OutOfMemory();
   312             return 0;
   313         }
   314     }
   315     timer->timerID = SDL_AtomicIncRef(&data->nextID);
   316     timer->callback = callback;
   317     timer->param = param;
   318     timer->interval = interval;
   319     timer->scheduled = SDL_GetTicks() + interval;
   320     SDL_AtomicSet(&timer->canceled, 0);
   321 
   322     entry = (SDL_TimerMap *)SDL_malloc(sizeof(*entry));
   323     if (!entry) {
   324         SDL_free(timer);
   325         SDL_OutOfMemory();
   326         return 0;
   327     }
   328     entry->timer = timer;
   329     entry->timerID = timer->timerID;
   330 
   331     SDL_LockMutex(data->timermap_lock);
   332     entry->next = data->timermap;
   333     data->timermap = entry;
   334     SDL_UnlockMutex(data->timermap_lock);
   335 
   336     /* Add the timer to the pending list for the timer thread */
   337     SDL_AtomicLock(&data->lock);
   338     timer->next = data->pending;
   339     data->pending = timer;
   340     SDL_AtomicUnlock(&data->lock);
   341 
   342     /* Wake up the timer thread if necessary */
   343     SDL_SemPost(data->sem);
   344 
   345     return entry->timerID;
   346 }
   347 
   348 SDL_bool
   349 SDL_RemoveTimer(SDL_TimerID id)
   350 {
   351     SDL_TimerData *data = &SDL_timer_data;
   352     SDL_TimerMap *prev, *entry;
   353     SDL_bool canceled = SDL_FALSE;
   354 
   355     /* Find the timer */
   356     SDL_LockMutex(data->timermap_lock);
   357     prev = NULL;
   358     for (entry = data->timermap; entry; prev = entry, entry = entry->next) {
   359         if (entry->timerID == id) {
   360             if (prev) {
   361                 prev->next = entry->next;
   362             } else {
   363                 data->timermap = entry->next;
   364             }
   365             break;
   366         }
   367     }
   368     SDL_UnlockMutex(data->timermap_lock);
   369 
   370     if (entry) {
   371         if (!SDL_AtomicGet(&entry->timer->canceled)) {
   372             SDL_AtomicSet(&entry->timer->canceled, 1);
   373             canceled = SDL_TRUE;
   374         }
   375         SDL_free(entry);
   376     }
   377     return canceled;
   378 }
   379 
   380 /* vi: set ts=4 sw=4 expandtab: */