cscg22-gearboy

CSCG 2022 Challenge 'Gearboy'
git clone https://git.sinitax.com/sinitax/cscg22-gearboy
Log | Files | Refs | sfeed.txt

SDL_timer.c (10431B)


      1/*
      2  Simple DirectMedia Layer
      3  Copyright (C) 1997-2014 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
     31typedef 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
     42typedef 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 */
     50typedef 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
     71static 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
     79static void
     80SDL_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
    100static int
    101SDL_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
    205int
    206SDL_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
    245void
    246SDL_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
    287SDL_TimerID
    288SDL_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
    357SDL_bool
    358SDL_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: */