src/timer/SDL_timer.c
author Sam Lantinga <slouken@libsdl.org>
Sun, 26 May 2013 11:34:04 -0700
changeset 7223 76fa20889de8
parent 7191 75360622e65f
child 7649 4f801cd08f3f
permissions -rw-r--r--
Fixed compile errors building with mingw64
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2013 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_config.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 extern void SDL_StartTicks(void);
    30 
    31 /* #define DEBUG_TIMERS */
    32 
    33 typedef struct _SDL_Timer
    34 {
    35     int timerID;
    36     SDL_TimerCallback callback;
    37     void *param;
    38     Uint32 interval;
    39     Uint32 scheduled;
    40     volatile SDL_bool canceled;
    41     struct _SDL_Timer *next;
    42 } SDL_Timer;
    43 
    44 typedef struct _SDL_TimerMap
    45 {
    46     int timerID;
    47     SDL_Timer *timer;
    48     struct _SDL_TimerMap *next;
    49 } SDL_TimerMap;
    50 
    51 /* The timers are kept in a sorted list */
    52 typedef struct {
    53     /* Data used by the main thread */
    54     SDL_Thread *thread;
    55     SDL_atomic_t nextID;
    56     SDL_TimerMap *timermap;
    57     SDL_mutex *timermap_lock;
    58 
    59     /* Padding to separate cache lines between threads */
    60     char cache_pad[SDL_CACHELINE_SIZE];
    61 
    62     /* Data used to communicate with the timer thread */
    63     SDL_SpinLock lock;
    64     SDL_sem *sem;
    65     SDL_Timer * volatile pending;
    66     SDL_Timer * volatile freelist;
    67     volatile SDL_bool active;
    68 
    69     /* List of timers - this is only touched by the timer thread */
    70     SDL_Timer *timers;
    71 } SDL_TimerData;
    72 
    73 static SDL_TimerData SDL_timer_data;
    74 
    75 static Uint32 ticks_started = 0;
    76 
    77 void
    78 SDL_InitTicks(void)
    79 {
    80     if (!ticks_started) {
    81         SDL_StartTicks();
    82         ticks_started = 1;
    83     }
    84 }
    85 
    86 /* The idea here is that any thread might add a timer, but a single
    87  * thread manages the active timer queue, sorted by scheduling time.
    88  *
    89  * Timers are removed by simply setting a canceled flag
    90  */
    91 
    92 static void
    93 SDL_AddTimerInternal(SDL_TimerData *data, SDL_Timer *timer)
    94 {
    95     SDL_Timer *prev, *curr;
    96 
    97     prev = NULL;
    98     for (curr = data->timers; curr; prev = curr, curr = curr->next) {
    99         if ((Sint32)(timer->scheduled-curr->scheduled) < 0) {
   100             break;
   101         }
   102     }
   103 
   104     /* Insert the timer here! */
   105     if (prev) {
   106         prev->next = timer;
   107     } else {
   108         data->timers = timer;
   109     }
   110     timer->next = curr;
   111 }
   112 
   113 static int
   114 SDL_TimerThread(void *_data)
   115 {
   116     SDL_TimerData *data = (SDL_TimerData *)_data;
   117     SDL_Timer *pending;
   118     SDL_Timer *current;
   119     SDL_Timer *freelist_head = NULL;
   120     SDL_Timer *freelist_tail = NULL;
   121     Uint32 tick, now, interval, delay;
   122 
   123     /* Threaded timer loop:
   124      *  1. Queue timers added by other threads
   125      *  2. Handle any timers that should dispatch this cycle
   126      *  3. Wait until next dispatch time or new timer arrives
   127      */
   128     for ( ; ; ) {
   129         /* Pending and freelist maintenance */
   130         SDL_AtomicLock(&data->lock);
   131         {
   132             /* Get any timers ready to be queued */
   133             pending = data->pending;
   134             data->pending = NULL;
   135 
   136             /* Make any unused timer structures available */
   137             if (freelist_head) {
   138                 freelist_tail->next = data->freelist;
   139                 data->freelist = freelist_head;
   140             }
   141         }
   142         SDL_AtomicUnlock(&data->lock);
   143 
   144         /* Sort the pending timers into our list */
   145         while (pending) {
   146             current = pending;
   147             pending = pending->next;
   148             SDL_AddTimerInternal(data, current);
   149         }
   150         freelist_head = NULL;
   151         freelist_tail = NULL;
   152 
   153         /* Check to see if we're still running, after maintenance */
   154         if (!data->active) {
   155             break;
   156         }
   157 
   158         /* Initial delay if there are no timers */
   159         delay = SDL_MUTEX_MAXWAIT;
   160 
   161         tick = SDL_GetTicks();
   162 
   163         /* Process all the pending timers for this tick */
   164         while (data->timers) {
   165             current = data->timers;
   166 
   167             if ((Sint32)(tick-current->scheduled) < 0) {
   168                 /* Scheduled for the future, wait a bit */
   169                 delay = (current->scheduled - tick);
   170                 break;
   171             }
   172 
   173             /* We're going to do something with this timer */
   174             data->timers = current->next;
   175 
   176             if (current->canceled) {
   177                 interval = 0;
   178             } else {
   179                 interval = current->callback(current->interval, current->param);
   180             }
   181 
   182             if (interval > 0) {
   183                 /* Reschedule this timer */
   184                 current->scheduled = tick + interval;
   185                 SDL_AddTimerInternal(data, current);
   186             } else {
   187                 if (!freelist_head) {
   188                     freelist_head = current;
   189                 }
   190                 if (freelist_tail) {
   191                     freelist_tail->next = current;
   192                 }
   193                 freelist_tail = current;
   194 
   195                 current->canceled = SDL_TRUE;
   196             }
   197         }
   198 
   199         /* Adjust the delay based on processing time */
   200         now = SDL_GetTicks();
   201         interval = (now - tick);
   202         if (interval > delay) {
   203             delay = 0;
   204         } else {
   205             delay -= interval;
   206         }
   207 
   208         /* Note that each time a timer is added, this will return
   209            immediately, but we process the timers added all at once.
   210            That's okay, it just means we run through the loop a few
   211            extra times.
   212          */
   213         SDL_SemWaitTimeout(data->sem, delay);
   214     }
   215     return 0;
   216 }
   217 
   218 int
   219 SDL_TimerInit(void)
   220 {
   221     SDL_TimerData *data = &SDL_timer_data;
   222 
   223     if (!data->active) {
   224         const char *name = "SDLTimer";
   225         data->timermap_lock = SDL_CreateMutex();
   226         if (!data->timermap_lock) {
   227             return -1;
   228         }
   229 
   230         data->sem = SDL_CreateSemaphore(0);
   231         if (!data->sem) {
   232             SDL_DestroyMutex(data->timermap_lock);
   233             return -1;
   234         }
   235 
   236         data->active = SDL_TRUE;
   237         /* !!! FIXME: this is nasty. */
   238 #if defined(__WIN32__) && !defined(HAVE_LIBC)
   239 #undef SDL_CreateThread
   240         data->thread = SDL_CreateThread(SDL_TimerThread, name, data, NULL, NULL);
   241 #else
   242         data->thread = SDL_CreateThread(SDL_TimerThread, name, data);
   243 #endif
   244         if (!data->thread) {
   245             SDL_TimerQuit();
   246             return -1;
   247         }
   248 
   249         SDL_AtomicSet(&data->nextID, 1);
   250     }
   251     return 0;
   252 }
   253 
   254 void
   255 SDL_TimerQuit(void)
   256 {
   257     SDL_TimerData *data = &SDL_timer_data;
   258     SDL_Timer *timer;
   259     SDL_TimerMap *entry;
   260 
   261     if (data->active) {
   262         data->active = SDL_FALSE;
   263 
   264         /* Shutdown the timer thread */
   265         if (data->thread) {
   266             SDL_SemPost(data->sem);
   267             SDL_WaitThread(data->thread, NULL);
   268             data->thread = NULL;
   269         }
   270 
   271         SDL_DestroySemaphore(data->sem);
   272         data->sem = NULL;
   273 
   274         /* Clean up the timer entries */
   275         while (data->timers) {
   276             timer = data->timers;
   277             data->timers = timer->next;
   278             SDL_free(timer);
   279         }
   280         while (data->freelist) {
   281             timer = data->freelist;
   282             data->freelist = timer->next;
   283             SDL_free(timer);
   284         }
   285         while (data->timermap) {
   286             entry = data->timermap;
   287             data->timermap = entry->next;
   288             SDL_free(entry);
   289         }
   290 
   291         SDL_DestroyMutex(data->timermap_lock);
   292         data->timermap_lock = NULL;
   293     }
   294 }
   295 
   296 SDL_TimerID
   297 SDL_AddTimer(Uint32 interval, SDL_TimerCallback callback, void *param)
   298 {
   299     SDL_TimerData *data = &SDL_timer_data;
   300     SDL_Timer *timer;
   301     SDL_TimerMap *entry;
   302 
   303     if (!data->active) {
   304         int status = 0;
   305 
   306         SDL_AtomicLock(&data->lock);
   307         if (!data->active) {
   308             status = SDL_TimerInit();
   309         }
   310         SDL_AtomicUnlock(&data->lock);
   311 
   312         if (status < 0) {
   313             return 0;
   314         }
   315     }
   316 
   317     SDL_AtomicLock(&data->lock);
   318     timer = data->freelist;
   319     if (timer) {
   320         data->freelist = timer->next;
   321     }
   322     SDL_AtomicUnlock(&data->lock);
   323 
   324     if (timer) {
   325         SDL_RemoveTimer(timer->timerID);
   326     } else {
   327         timer = (SDL_Timer *)SDL_malloc(sizeof(*timer));
   328         if (!timer) {
   329             SDL_OutOfMemory();
   330             return 0;
   331         }
   332     }
   333     timer->timerID = SDL_AtomicIncRef(&data->nextID);
   334     timer->callback = callback;
   335     timer->param = param;
   336     timer->interval = interval;
   337     timer->scheduled = SDL_GetTicks() + interval;
   338     timer->canceled = SDL_FALSE;
   339 
   340     entry = (SDL_TimerMap *)SDL_malloc(sizeof(*entry));
   341     if (!entry) {
   342         SDL_free(timer);
   343         SDL_OutOfMemory();
   344         return 0;
   345     }
   346     entry->timer = timer;
   347     entry->timerID = timer->timerID;
   348 
   349     SDL_LockMutex(data->timermap_lock);
   350     entry->next = data->timermap;
   351     data->timermap = entry;
   352     SDL_UnlockMutex(data->timermap_lock);
   353 
   354     /* Add the timer to the pending list for the timer thread */
   355     SDL_AtomicLock(&data->lock);
   356     timer->next = data->pending;
   357     data->pending = timer;
   358     SDL_AtomicUnlock(&data->lock);
   359 
   360     /* Wake up the timer thread if necessary */
   361     SDL_SemPost(data->sem);
   362 
   363     return entry->timerID;
   364 }
   365 
   366 SDL_bool
   367 SDL_RemoveTimer(SDL_TimerID id)
   368 {
   369     SDL_TimerData *data = &SDL_timer_data;
   370     SDL_TimerMap *prev, *entry;
   371     SDL_bool canceled = SDL_FALSE;
   372 
   373     /* Find the timer */
   374     SDL_LockMutex(data->timermap_lock);
   375     prev = NULL;
   376     for (entry = data->timermap; entry; prev = entry, entry = entry->next) {
   377         if (entry->timerID == id) {
   378             if (prev) {
   379                 prev->next = entry->next;
   380             } else {
   381                 data->timermap = entry->next;
   382             }
   383             break;
   384         }
   385     }
   386     SDL_UnlockMutex(data->timermap_lock);
   387 
   388     if (entry) {
   389         if (!entry->timer->canceled) {
   390             entry->timer->canceled = SDL_TRUE;
   391             canceled = SDL_TRUE;
   392         }
   393         SDL_free(entry);
   394     }
   395     return canceled;
   396 }
   397 
   398 /* vi: set ts=4 sw=4 expandtab: */