src/timer/SDL_timer.c
author Sam Lantinga <slouken@libsdl.org>
Sat, 02 Jan 2016 10:10:34 -0800
changeset 9998 f67cf37e9cd4
parent 9619 b94b6d0bff0f
child 10003 d91a2c45825e
permissions -rw-r--r--
Updated copyright to 2016
     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     volatile SDL_bool 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 * volatile pending;
    64     SDL_Timer * volatile freelist;
    65     volatile SDL_bool 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 (!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 (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                 current->canceled = SDL_TRUE;
   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 (!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         data->active = SDL_TRUE;
   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 (data->active) {
   253         data->active = SDL_FALSE;
   254 
   255         /* Shutdown the timer thread */
   256         if (data->thread) {
   257             SDL_SemPost(data->sem);
   258             SDL_WaitThread(data->thread, NULL);
   259             data->thread = NULL;
   260         }
   261 
   262         SDL_DestroySemaphore(data->sem);
   263         data->sem = NULL;
   264 
   265         /* Clean up the timer entries */
   266         while (data->timers) {
   267             timer = data->timers;
   268             data->timers = timer->next;
   269             SDL_free(timer);
   270         }
   271         while (data->freelist) {
   272             timer = data->freelist;
   273             data->freelist = timer->next;
   274             SDL_free(timer);
   275         }
   276         while (data->timermap) {
   277             entry = data->timermap;
   278             data->timermap = entry->next;
   279             SDL_free(entry);
   280         }
   281 
   282         SDL_DestroyMutex(data->timermap_lock);
   283         data->timermap_lock = NULL;
   284     }
   285 }
   286 
   287 SDL_TimerID
   288 SDL_AddTimer(Uint32 interval, SDL_TimerCallback callback, void *param)
   289 {
   290     SDL_TimerData *data = &SDL_timer_data;
   291     SDL_Timer *timer;
   292     SDL_TimerMap *entry;
   293 
   294     if (!data->active) {
   295         int status = 0;
   296 
   297         SDL_AtomicLock(&data->lock);
   298         if (!data->active) {
   299             status = SDL_TimerInit();
   300         }
   301         SDL_AtomicUnlock(&data->lock);
   302 
   303         if (status < 0) {
   304             return 0;
   305         }
   306     }
   307 
   308     SDL_AtomicLock(&data->lock);
   309     timer = data->freelist;
   310     if (timer) {
   311         data->freelist = timer->next;
   312     }
   313     SDL_AtomicUnlock(&data->lock);
   314 
   315     if (timer) {
   316         SDL_RemoveTimer(timer->timerID);
   317     } else {
   318         timer = (SDL_Timer *)SDL_malloc(sizeof(*timer));
   319         if (!timer) {
   320             SDL_OutOfMemory();
   321             return 0;
   322         }
   323     }
   324     timer->timerID = SDL_AtomicIncRef(&data->nextID);
   325     timer->callback = callback;
   326     timer->param = param;
   327     timer->interval = interval;
   328     timer->scheduled = SDL_GetTicks() + interval;
   329     timer->canceled = SDL_FALSE;
   330 
   331     entry = (SDL_TimerMap *)SDL_malloc(sizeof(*entry));
   332     if (!entry) {
   333         SDL_free(timer);
   334         SDL_OutOfMemory();
   335         return 0;
   336     }
   337     entry->timer = timer;
   338     entry->timerID = timer->timerID;
   339 
   340     SDL_LockMutex(data->timermap_lock);
   341     entry->next = data->timermap;
   342     data->timermap = entry;
   343     SDL_UnlockMutex(data->timermap_lock);
   344 
   345     /* Add the timer to the pending list for the timer thread */
   346     SDL_AtomicLock(&data->lock);
   347     timer->next = data->pending;
   348     data->pending = timer;
   349     SDL_AtomicUnlock(&data->lock);
   350 
   351     /* Wake up the timer thread if necessary */
   352     SDL_SemPost(data->sem);
   353 
   354     return entry->timerID;
   355 }
   356 
   357 SDL_bool
   358 SDL_RemoveTimer(SDL_TimerID id)
   359 {
   360     SDL_TimerData *data = &SDL_timer_data;
   361     SDL_TimerMap *prev, *entry;
   362     SDL_bool canceled = SDL_FALSE;
   363 
   364     /* Find the timer */
   365     SDL_LockMutex(data->timermap_lock);
   366     prev = NULL;
   367     for (entry = data->timermap; entry; prev = entry, entry = entry->next) {
   368         if (entry->timerID == id) {
   369             if (prev) {
   370                 prev->next = entry->next;
   371             } else {
   372                 data->timermap = entry->next;
   373             }
   374             break;
   375         }
   376     }
   377     SDL_UnlockMutex(data->timermap_lock);
   378 
   379     if (entry) {
   380         if (!entry->timer->canceled) {
   381             entry->timer->canceled = SDL_TRUE;
   382             canceled = SDL_TRUE;
   383         }
   384         SDL_free(entry);
   385     }
   386     return canceled;
   387 }
   388 
   389 /* vi: set ts=4 sw=4 expandtab: */