src/timer/SDL_timer.c
author Ryan C. Gordon
Mon, 01 Jan 2018 19:16:51 -0500
changeset 11803 454f6dc9cb85
parent 11278 37f4d29e155d
child 11811 5d94cb6b24d3
permissions -rw-r--r--
windows: Remove references to GetVersionExA (thanks, Andrew Pilley!).

"GetVersionExA is deprecated in windows 8.1 and above's SDK, causing a warning
when building against the win10 SDK. Attached patch cleans up the usage for a
warning-free build.

GetVersionExA was being used to test to see if SDL was running on win9x or
winnt. A quick chat with Ryan on twitter suggested that SDL doesn't
officially support win9x anymore, so the call to this can be outright removed.

As an aside, replacing the call to GetVersionExA with VerifyVersionInfoA (the
recommended path) would have been pointless, as VerifyVersionInfoA only
supports VER_PLATFORM_WIN32_NT and doesn't officially support any other value
for dwPlatformId currently. (And it's probable that win9x SDKs didn't have
VerifyVersionInfo* in them anyway.)"

Fixes Bugzilla #4019.
slouken@1
     1
/*
slouken@5535
     2
  Simple DirectMedia Layer
slouken@10737
     3
  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
slouken@1
     4
slouken@5535
     5
  This software is provided 'as-is', without any express or implied
slouken@5535
     6
  warranty.  In no event will the authors be held liable for any damages
slouken@5535
     7
  arising from the use of this software.
slouken@1
     8
slouken@5535
     9
  Permission is granted to anyone to use this software for any purpose,
slouken@5535
    10
  including commercial applications, and to alter it and redistribute it
slouken@5535
    11
  freely, subject to the following restrictions:
slouken@1
    12
slouken@5535
    13
  1. The origin of this software must not be misrepresented; you must not
slouken@5535
    14
     claim that you wrote the original software. If you use this software
slouken@5535
    15
     in a product, an acknowledgment in the product documentation would be
slouken@5535
    16
     appreciated but is not required.
slouken@5535
    17
  2. Altered source versions must be plainly marked as such, and must not be
slouken@5535
    18
     misrepresented as being the original software.
slouken@5535
    19
  3. This notice may not be removed or altered from any source distribution.
slouken@1
    20
*/
icculus@8093
    21
#include "../SDL_internal.h"
slouken@1
    22
slouken@1
    23
#include "SDL_timer.h"
slouken@1
    24
#include "SDL_timer_c.h"
slouken@5111
    25
#include "SDL_atomic.h"
slouken@5115
    26
#include "SDL_cpuinfo.h"
icculus@10146
    27
#include "../thread/SDL_systhread.h"
slouken@1
    28
slouken@1
    29
/* #define DEBUG_TIMERS */
slouken@1
    30
slouken@5111
    31
typedef struct _SDL_Timer
slouken@5111
    32
{
slouken@5111
    33
    int timerID;
slouken@5111
    34
    SDL_TimerCallback callback;
slouken@5111
    35
    void *param;
slouken@5111
    36
    Uint32 interval;
slouken@5111
    37
    Uint32 scheduled;
icculus@10003
    38
    SDL_atomic_t canceled;
slouken@5111
    39
    struct _SDL_Timer *next;
slouken@5111
    40
} SDL_Timer;
slouken@5111
    41
slouken@5111
    42
typedef struct _SDL_TimerMap
slouken@5111
    43
{
slouken@5111
    44
    int timerID;
slouken@5111
    45
    SDL_Timer *timer;
slouken@5111
    46
    struct _SDL_TimerMap *next;
slouken@5111
    47
} SDL_TimerMap;
slouken@1
    48
slouken@5111
    49
/* The timers are kept in a sorted list */
slouken@5111
    50
typedef struct {
slouken@5111
    51
    /* Data used by the main thread */
slouken@5111
    52
    SDL_Thread *thread;
slouken@5111
    53
    SDL_atomic_t nextID;
slouken@5111
    54
    SDL_TimerMap *timermap;
slouken@5111
    55
    SDL_mutex *timermap_lock;
slouken@5111
    56
slouken@5111
    57
    /* Padding to separate cache lines between threads */
slouken@5115
    58
    char cache_pad[SDL_CACHELINE_SIZE];
slouken@5111
    59
slouken@5111
    60
    /* Data used to communicate with the timer thread */
slouken@5111
    61
    SDL_SpinLock lock;
slouken@5111
    62
    SDL_sem *sem;
icculus@10003
    63
    SDL_Timer *pending;
icculus@10003
    64
    SDL_Timer *freelist;
icculus@10003
    65
    SDL_atomic_t active;
slouken@1
    66
slouken@5111
    67
    /* List of timers - this is only touched by the timer thread */
slouken@5111
    68
    SDL_Timer *timers;
slouken@5111
    69
} SDL_TimerData;
slouken@5111
    70
slouken@5111
    71
static SDL_TimerData SDL_timer_data;
slouken@5111
    72
slouken@5111
    73
/* The idea here is that any thread might add a timer, but a single
slouken@5111
    74
 * thread manages the active timer queue, sorted by scheduling time.
slouken@5111
    75
 *
slouken@5111
    76
 * Timers are removed by simply setting a canceled flag
slouken@5111
    77
 */
slouken@5111
    78
slouken@5111
    79
static void
slouken@5111
    80
SDL_AddTimerInternal(SDL_TimerData *data, SDL_Timer *timer)
slouken@1895
    81
{
slouken@5111
    82
    SDL_Timer *prev, *curr;
slouken@5111
    83
slouken@5111
    84
    prev = NULL;
slouken@5111
    85
    for (curr = data->timers; curr; prev = curr, curr = curr->next) {
slouken@5111
    86
        if ((Sint32)(timer->scheduled-curr->scheduled) < 0) {
slouken@5111
    87
            break;
slouken@5111
    88
        }
slouken@5111
    89
    }
slouken@5111
    90
slouken@5111
    91
    /* Insert the timer here! */
slouken@5111
    92
    if (prev) {
slouken@5111
    93
        prev->next = timer;
slouken@5111
    94
    } else {
slouken@5111
    95
        data->timers = timer;
slouken@5111
    96
    }
slouken@5111
    97
    timer->next = curr;
slouken@5111
    98
}
slouken@5111
    99
slouken@11272
   100
static int SDLCALL
slouken@5111
   101
SDL_TimerThread(void *_data)
slouken@5111
   102
{
slouken@5111
   103
    SDL_TimerData *data = (SDL_TimerData *)_data;
slouken@5111
   104
    SDL_Timer *pending;
slouken@5111
   105
    SDL_Timer *current;
slouken@5111
   106
    SDL_Timer *freelist_head = NULL;
slouken@5111
   107
    SDL_Timer *freelist_tail = NULL;
slouken@5111
   108
    Uint32 tick, now, interval, delay;
slouken@1
   109
slouken@5111
   110
    /* Threaded timer loop:
slouken@5111
   111
     *  1. Queue timers added by other threads
slouken@5111
   112
     *  2. Handle any timers that should dispatch this cycle
slouken@5111
   113
     *  3. Wait until next dispatch time or new timer arrives
slouken@5111
   114
     */
slouken@5111
   115
    for ( ; ; ) {
slouken@5111
   116
        /* Pending and freelist maintenance */
slouken@5111
   117
        SDL_AtomicLock(&data->lock);
slouken@5111
   118
        {
slouken@5111
   119
            /* Get any timers ready to be queued */
slouken@5111
   120
            pending = data->pending;
slouken@5111
   121
            data->pending = NULL;
slouken@5111
   122
slouken@5111
   123
            /* Make any unused timer structures available */
slouken@5111
   124
            if (freelist_head) {
slouken@5111
   125
                freelist_tail->next = data->freelist;
slouken@5111
   126
                data->freelist = freelist_head;
slouken@5111
   127
            }
slouken@5111
   128
        }
slouken@5111
   129
        SDL_AtomicUnlock(&data->lock);
slouken@5111
   130
slouken@5111
   131
        /* Sort the pending timers into our list */
slouken@5111
   132
        while (pending) {
slouken@5111
   133
            current = pending;
slouken@5111
   134
            pending = pending->next;
slouken@5111
   135
            SDL_AddTimerInternal(data, current);
slouken@5111
   136
        }
slouken@5111
   137
        freelist_head = NULL;
slouken@5111
   138
        freelist_tail = NULL;
slouken@5111
   139
slouken@5111
   140
        /* Check to see if we're still running, after maintenance */
icculus@10003
   141
        if (!SDL_AtomicGet(&data->active)) {
slouken@5111
   142
            break;
slouken@5111
   143
        }
slouken@5111
   144
slouken@5111
   145
        /* Initial delay if there are no timers */
slouken@5111
   146
        delay = SDL_MUTEX_MAXWAIT;
slouken@5111
   147
slouken@5111
   148
        tick = SDL_GetTicks();
slouken@5111
   149
slouken@5111
   150
        /* Process all the pending timers for this tick */
slouken@5111
   151
        while (data->timers) {
slouken@5111
   152
            current = data->timers;
slouken@1
   153
slouken@5111
   154
            if ((Sint32)(tick-current->scheduled) < 0) {
slouken@5111
   155
                /* Scheduled for the future, wait a bit */
slouken@5111
   156
                delay = (current->scheduled - tick);
slouken@5111
   157
                break;
slouken@5111
   158
            }
slouken@5111
   159
slouken@5111
   160
            /* We're going to do something with this timer */
slouken@5111
   161
            data->timers = current->next;
slouken@5111
   162
icculus@10003
   163
            if (SDL_AtomicGet(&current->canceled)) {
slouken@5111
   164
                interval = 0;
slouken@5111
   165
            } else {
slouken@5111
   166
                interval = current->callback(current->interval, current->param);
slouken@5111
   167
            }
slouken@1
   168
slouken@5111
   169
            if (interval > 0) {
slouken@5111
   170
                /* Reschedule this timer */
slouken@11278
   171
                current->interval = interval;
slouken@5111
   172
                current->scheduled = tick + interval;
slouken@5111
   173
                SDL_AddTimerInternal(data, current);
slouken@5111
   174
            } else {
slouken@5111
   175
                if (!freelist_head) {
slouken@5111
   176
                    freelist_head = current;
slouken@5111
   177
                }
slouken@5111
   178
                if (freelist_tail) {
slouken@5111
   179
                    freelist_tail->next = current;
slouken@5111
   180
                }
slouken@5111
   181
                freelist_tail = current;
slouken@5111
   182
icculus@10003
   183
                SDL_AtomicSet(&current->canceled, 1);
slouken@5111
   184
            }
slouken@5111
   185
        }
slouken@5111
   186
slouken@5111
   187
        /* Adjust the delay based on processing time */
slouken@5111
   188
        now = SDL_GetTicks();
slouken@5111
   189
        interval = (now - tick);
slouken@5111
   190
        if (interval > delay) {
slouken@5111
   191
            delay = 0;
slouken@5111
   192
        } else {
slouken@5111
   193
            delay -= interval;
slouken@5111
   194
        }
slouken@5111
   195
slouken@5111
   196
        /* Note that each time a timer is added, this will return
slouken@5111
   197
           immediately, but we process the timers added all at once.
slouken@5111
   198
           That's okay, it just means we run through the loop a few
slouken@5111
   199
           extra times.
slouken@5111
   200
         */
slouken@5111
   201
        SDL_SemWaitTimeout(data->sem, delay);
slouken@1895
   202
    }
slouken@5111
   203
    return 0;
slouken@1
   204
}
slouken@1
   205
slouken@1895
   206
int
slouken@1895
   207
SDL_TimerInit(void)
slouken@1
   208
{
slouken@5111
   209
    SDL_TimerData *data = &SDL_timer_data;
slouken@5111
   210
icculus@10003
   211
    if (!SDL_AtomicGet(&data->active)) {
icculus@5969
   212
        const char *name = "SDLTimer";
slouken@5111
   213
        data->timermap_lock = SDL_CreateMutex();
slouken@5111
   214
        if (!data->timermap_lock) {
slouken@5111
   215
            return -1;
slouken@5111
   216
        }
slouken@1
   217
slouken@5111
   218
        data->sem = SDL_CreateSemaphore(0);
slouken@5111
   219
        if (!data->sem) {
slouken@5111
   220
            SDL_DestroyMutex(data->timermap_lock);
slouken@5111
   221
            return -1;
slouken@5111
   222
        }
slouken@5111
   223
icculus@10003
   224
        SDL_AtomicSet(&data->active, 1);
icculus@10146
   225
icculus@10146
   226
        /* Timer threads use a callback into the app, so we can't set a limited stack size here. */
icculus@10146
   227
        data->thread = SDL_CreateThreadInternal(SDL_TimerThread, name, 0, data);
slouken@5111
   228
        if (!data->thread) {
slouken@5111
   229
            SDL_TimerQuit();
slouken@5111
   230
            return -1;
slouken@5111
   231
        }
slouken@5111
   232
slouken@5111
   233
        SDL_AtomicSet(&data->nextID, 1);
slouken@1895
   234
    }
slouken@5111
   235
    return 0;
slouken@1
   236
}
slouken@1
   237
slouken@1895
   238
void
slouken@1895
   239
SDL_TimerQuit(void)
slouken@1
   240
{
slouken@5111
   241
    SDL_TimerData *data = &SDL_timer_data;
slouken@5111
   242
    SDL_Timer *timer;
slouken@5111
   243
    SDL_TimerMap *entry;
slouken@5111
   244
icculus@10003
   245
    if (SDL_AtomicCAS(&data->active, 1, 0)) {  /* active? Move to inactive. */
slouken@5111
   246
        /* Shutdown the timer thread */
slouken@5111
   247
        if (data->thread) {
slouken@5111
   248
            SDL_SemPost(data->sem);
slouken@5111
   249
            SDL_WaitThread(data->thread, NULL);
slouken@5111
   250
            data->thread = NULL;
slouken@5111
   251
        }
slouken@5074
   252
slouken@5111
   253
        SDL_DestroySemaphore(data->sem);
slouken@5111
   254
        data->sem = NULL;
slouken@1028
   255
slouken@5111
   256
        /* Clean up the timer entries */
slouken@5111
   257
        while (data->timers) {
slouken@5111
   258
            timer = data->timers;
slouken@5111
   259
            data->timers = timer->next;
slouken@5111
   260
            SDL_free(timer);
slouken@1895
   261
        }
slouken@5111
   262
        while (data->freelist) {
slouken@5111
   263
            timer = data->freelist;
slouken@5111
   264
            data->freelist = timer->next;
slouken@5111
   265
            SDL_free(timer);
slouken@5111
   266
        }
slouken@5111
   267
        while (data->timermap) {
slouken@5111
   268
            entry = data->timermap;
slouken@5111
   269
            data->timermap = entry->next;
slouken@5111
   270
            SDL_free(entry);
slouken@5111
   271
        }
slouken@1
   272
slouken@5111
   273
        SDL_DestroyMutex(data->timermap_lock);
slouken@5111
   274
        data->timermap_lock = NULL;
slouken@1895
   275
    }
slouken@1028
   276
}
slouken@1028
   277
slouken@1895
   278
SDL_TimerID
slouken@5111
   279
SDL_AddTimer(Uint32 interval, SDL_TimerCallback callback, void *param)
slouken@1
   280
{
slouken@5111
   281
    SDL_TimerData *data = &SDL_timer_data;
slouken@5111
   282
    SDL_Timer *timer;
slouken@5111
   283
    SDL_TimerMap *entry;
slouken@5111
   284
icculus@10003
   285
    SDL_AtomicLock(&data->lock);
icculus@10003
   286
    if (!SDL_AtomicGet(&data->active)) {
icculus@10003
   287
        if (SDL_TimerInit() < 0) {
icculus@10003
   288
            SDL_AtomicUnlock(&data->lock);
slouken@5111
   289
            return 0;
slouken@1895
   290
        }
slouken@5111
   291
    }
slouken@5111
   292
slouken@5111
   293
    timer = data->freelist;
slouken@5111
   294
    if (timer) {
slouken@5111
   295
        data->freelist = timer->next;
slouken@5111
   296
    }
slouken@5111
   297
    SDL_AtomicUnlock(&data->lock);
slouken@5111
   298
slouken@5111
   299
    if (timer) {
slouken@5111
   300
        SDL_RemoveTimer(timer->timerID);
slouken@5111
   301
    } else {
slouken@5111
   302
        timer = (SDL_Timer *)SDL_malloc(sizeof(*timer));
slouken@5111
   303
        if (!timer) {
slouken@5111
   304
            SDL_OutOfMemory();
slouken@5111
   305
            return 0;
slouken@5111
   306
        }
slouken@1895
   307
    }
slouken@5111
   308
    timer->timerID = SDL_AtomicIncRef(&data->nextID);
slouken@5111
   309
    timer->callback = callback;
slouken@5111
   310
    timer->param = param;
slouken@5111
   311
    timer->interval = interval;
slouken@5111
   312
    timer->scheduled = SDL_GetTicks() + interval;
icculus@10003
   313
    SDL_AtomicSet(&timer->canceled, 0);
slouken@7191
   314
slouken@5111
   315
    entry = (SDL_TimerMap *)SDL_malloc(sizeof(*entry));
slouken@5111
   316
    if (!entry) {
slouken@5111
   317
        SDL_free(timer);
slouken@5111
   318
        SDL_OutOfMemory();
slouken@5111
   319
        return 0;
slouken@1895
   320
    }
slouken@5111
   321
    entry->timer = timer;
slouken@5111
   322
    entry->timerID = timer->timerID;
slouken@5111
   323
slouken@6977
   324
    SDL_LockMutex(data->timermap_lock);
slouken@5111
   325
    entry->next = data->timermap;
slouken@5111
   326
    data->timermap = entry;
slouken@6977
   327
    SDL_UnlockMutex(data->timermap_lock);
slouken@5111
   328
slouken@5111
   329
    /* Add the timer to the pending list for the timer thread */
slouken@5111
   330
    SDL_AtomicLock(&data->lock);
slouken@5111
   331
    timer->next = data->pending;
slouken@5111
   332
    data->pending = timer;
slouken@5111
   333
    SDL_AtomicUnlock(&data->lock);
slouken@5111
   334
slouken@5111
   335
    /* Wake up the timer thread if necessary */
slouken@5111
   336
    SDL_SemPost(data->sem);
slouken@5111
   337
slouken@5111
   338
    return entry->timerID;
slouken@1
   339
}
slouken@1
   340
slouken@1895
   341
SDL_bool
slouken@1895
   342
SDL_RemoveTimer(SDL_TimerID id)
slouken@1
   343
{
slouken@5111
   344
    SDL_TimerData *data = &SDL_timer_data;
slouken@5111
   345
    SDL_TimerMap *prev, *entry;
slouken@5111
   346
    SDL_bool canceled = SDL_FALSE;
slouken@1
   347
slouken@5111
   348
    /* Find the timer */
slouken@6977
   349
    SDL_LockMutex(data->timermap_lock);
slouken@5111
   350
    prev = NULL;
slouken@5111
   351
    for (entry = data->timermap; entry; prev = entry, entry = entry->next) {
slouken@5111
   352
        if (entry->timerID == id) {
slouken@1895
   353
            if (prev) {
slouken@5111
   354
                prev->next = entry->next;
slouken@1895
   355
            } else {
slouken@5111
   356
                data->timermap = entry->next;
slouken@1895
   357
            }
slouken@1895
   358
            break;
slouken@1895
   359
        }
slouken@1895
   360
    }
slouken@6977
   361
    SDL_UnlockMutex(data->timermap_lock);
slouken@1
   362
slouken@5111
   363
    if (entry) {
icculus@10003
   364
        if (!SDL_AtomicGet(&entry->timer->canceled)) {
icculus@10003
   365
            SDL_AtomicSet(&entry->timer->canceled, 1);
slouken@5111
   366
            canceled = SDL_TRUE;
slouken@5111
   367
        }
slouken@5111
   368
        SDL_free(entry);
slouken@1895
   369
    }
slouken@5111
   370
    return canceled;
slouken@1
   371
}
slouken@1895
   372
slouken@1895
   373
/* vi: set ts=4 sw=4 expandtab: */