src/timer/SDL_timer.c
author Sam Lantinga
Sat, 06 Oct 2012 11:23:47 -0700
changeset 6565 1f3c0df426dc
parent 6430 48d519500f7e
child 6885 700f1b25f77f
permissions -rw-r--r--
When using Xinerama, XVidMode always works on screen 0. Otherwise use the real X11 screen.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2012 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 /* #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 
    74 /* The idea here is that any thread might add a timer, but a single
    75  * thread manages the active timer queue, sorted by scheduling time.
    76  *
    77  * Timers are removed by simply setting a canceled flag
    78  */
    79 
    80 static void
    81 SDL_AddTimerInternal(SDL_TimerData *data, SDL_Timer *timer)
    82 {
    83     SDL_Timer *prev, *curr;
    84 
    85     prev = NULL;
    86     for (curr = data->timers; curr; prev = curr, curr = curr->next) {
    87         if ((Sint32)(timer->scheduled-curr->scheduled) < 0) {
    88             break;
    89         }
    90     }
    91 
    92     /* Insert the timer here! */
    93     if (prev) {
    94         prev->next = timer;
    95     } else {
    96         data->timers = timer;
    97     }
    98     timer->next = curr;
    99 }
   100 
   101 static int
   102 SDL_TimerThread(void *_data)
   103 {
   104     SDL_TimerData *data = (SDL_TimerData *)_data;
   105     SDL_Timer *pending;
   106     SDL_Timer *current;
   107     SDL_Timer *freelist_head = NULL;
   108     SDL_Timer *freelist_tail = NULL;
   109     Uint32 tick, now, interval, delay;
   110 
   111     /* Threaded timer loop:
   112      *  1. Queue timers added by other threads
   113      *  2. Handle any timers that should dispatch this cycle
   114      *  3. Wait until next dispatch time or new timer arrives
   115      */
   116     for ( ; ; ) {
   117         /* Pending and freelist maintenance */
   118         SDL_AtomicLock(&data->lock);
   119         {
   120             /* Get any timers ready to be queued */
   121             pending = data->pending;
   122             data->pending = NULL;
   123 
   124             /* Make any unused timer structures available */
   125             if (freelist_head) {
   126                 freelist_tail->next = data->freelist;
   127                 data->freelist = freelist_head;
   128             }
   129         }
   130         SDL_AtomicUnlock(&data->lock);
   131 
   132         /* Sort the pending timers into our list */
   133         while (pending) {
   134             current = pending;
   135             pending = pending->next;
   136             SDL_AddTimerInternal(data, current);
   137         }
   138         freelist_head = NULL;
   139         freelist_tail = NULL;
   140 
   141         /* Check to see if we're still running, after maintenance */
   142         if (!data->active) {
   143             break;
   144         }
   145 
   146         /* Initial delay if there are no timers */
   147         delay = SDL_MUTEX_MAXWAIT;
   148 
   149         tick = SDL_GetTicks();
   150 
   151         /* Process all the pending timers for this tick */
   152         while (data->timers) {
   153             current = data->timers;
   154 
   155             if ((Sint32)(tick-current->scheduled) < 0) {
   156                 /* Scheduled for the future, wait a bit */
   157                 delay = (current->scheduled - tick);
   158                 break;
   159             }
   160 
   161             /* We're going to do something with this timer */
   162             data->timers = current->next;
   163 
   164             if (current->canceled) {
   165                 interval = 0;
   166             } else {
   167                 interval = current->callback(current->interval, current->param);
   168             }
   169 
   170             if (interval > 0) {
   171                 /* Reschedule this timer */
   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                 current->canceled = SDL_TRUE;
   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 (!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         data->active = SDL_TRUE;
   225         /* !!! FIXME: this is nasty. */
   226 #if defined(__WIN32__) && !defined(HAVE_LIBC)
   227 #undef SDL_CreateThread
   228         data->thread = SDL_CreateThread(SDL_TimerThread, name, data, NULL, NULL);
   229 #else
   230         data->thread = SDL_CreateThread(SDL_TimerThread, name, data);
   231 #endif
   232         if (!data->thread) {
   233             SDL_TimerQuit();
   234             return -1;
   235         }
   236 
   237         SDL_AtomicSet(&data->nextID, 1);
   238     }
   239     return 0;
   240 }
   241 
   242 void
   243 SDL_TimerQuit(void)
   244 {
   245     SDL_TimerData *data = &SDL_timer_data;
   246     SDL_Timer *timer;
   247     SDL_TimerMap *entry;
   248 
   249     if (data->active) {
   250         data->active = SDL_FALSE;
   251 
   252         /* Shutdown the timer thread */
   253         if (data->thread) {
   254             SDL_SemPost(data->sem);
   255             SDL_WaitThread(data->thread, NULL);
   256             data->thread = NULL;
   257         }
   258 
   259         SDL_DestroySemaphore(data->sem);
   260         data->sem = NULL;
   261 
   262         /* Clean up the timer entries */
   263         while (data->timers) {
   264             timer = data->timers;
   265             data->timers = timer->next;
   266             SDL_free(timer);
   267         }
   268         while (data->freelist) {
   269             timer = data->freelist;
   270             data->freelist = timer->next;
   271             SDL_free(timer);
   272         }
   273         while (data->timermap) {
   274             entry = data->timermap;
   275             data->timermap = entry->next;
   276             SDL_free(entry);
   277         }
   278 
   279         SDL_DestroyMutex(data->timermap_lock);
   280         data->timermap_lock = NULL;
   281     }
   282 }
   283 
   284 SDL_TimerID
   285 SDL_AddTimer(Uint32 interval, SDL_TimerCallback callback, void *param)
   286 {
   287     SDL_TimerData *data = &SDL_timer_data;
   288     SDL_Timer *timer;
   289     SDL_TimerMap *entry;
   290 
   291     if (!data->active) {
   292         int status = 0;
   293 
   294         SDL_AtomicLock(&data->lock);
   295         if (!data->active) {
   296             status = SDL_TimerInit();
   297         }
   298         SDL_AtomicUnlock(&data->lock);
   299 
   300         if (status < 0) {
   301             return 0;
   302         }
   303     }
   304 
   305     SDL_AtomicLock(&data->lock);
   306     timer = data->freelist;
   307     if (timer) {
   308         data->freelist = timer->next;
   309     }
   310     SDL_AtomicUnlock(&data->lock);
   311 
   312     if (timer) {
   313         SDL_RemoveTimer(timer->timerID);
   314     } else {
   315         timer = (SDL_Timer *)SDL_malloc(sizeof(*timer));
   316         if (!timer) {
   317             SDL_OutOfMemory();
   318             return 0;
   319         }
   320     }
   321     timer->timerID = SDL_AtomicIncRef(&data->nextID);
   322     timer->callback = callback;
   323     timer->param = param;
   324     timer->interval = interval;
   325     timer->scheduled = SDL_GetTicks() + interval;
   326     timer->canceled = SDL_FALSE;
   327  
   328     entry = (SDL_TimerMap *)SDL_malloc(sizeof(*entry));
   329     if (!entry) {
   330         SDL_free(timer);
   331         SDL_OutOfMemory();
   332         return 0;
   333     }
   334     entry->timer = timer;
   335     entry->timerID = timer->timerID;
   336 
   337     SDL_mutexP(data->timermap_lock);
   338     entry->next = data->timermap;
   339     data->timermap = entry;
   340     SDL_mutexV(data->timermap_lock);
   341 
   342     /* Add the timer to the pending list for the timer thread */
   343     SDL_AtomicLock(&data->lock);
   344     timer->next = data->pending;
   345     data->pending = timer;
   346     SDL_AtomicUnlock(&data->lock);
   347 
   348     /* Wake up the timer thread if necessary */
   349     SDL_SemPost(data->sem);
   350 
   351     return entry->timerID;
   352 }
   353 
   354 SDL_bool
   355 SDL_RemoveTimer(SDL_TimerID id)
   356 {
   357     SDL_TimerData *data = &SDL_timer_data;
   358     SDL_TimerMap *prev, *entry;
   359     SDL_bool canceled = SDL_FALSE;
   360 
   361     /* Find the timer */
   362     SDL_mutexP(data->timermap_lock);
   363     prev = NULL;
   364     for (entry = data->timermap; entry; prev = entry, entry = entry->next) {
   365         if (entry->timerID == id) {
   366             if (prev) {
   367                 prev->next = entry->next;
   368             } else {
   369                 data->timermap = entry->next;
   370             }
   371             break;
   372         }
   373     }
   374     SDL_mutexV(data->timermap_lock);
   375 
   376     if (entry) {
   377         if (!entry->timer->canceled) {
   378             entry->timer->canceled = SDL_TRUE;
   379             canceled = SDL_TRUE;
   380         }
   381         SDL_free(entry);
   382     }
   383     return canceled;
   384 }
   385 
   386 /* vi: set ts=4 sw=4 expandtab: */