src/timer/SDL_timer.c
author Ryan C. Gordon
Sun, 03 Jan 2016 06:50:50 -0500
changeset 10003 d91a2c45825e
parent 9998 f67cf37e9cd4
child 10146 471eb08040ce
permissions -rw-r--r--
Remove almost all instances of "volatile" keyword.

As Tiffany pointed out in Bugzilla, volatile is not useful for thread safety:

https://software.intel.com/en-us/blogs/2007/11/30/volatile-almost-useless-for-multi-threaded-programming/

Some of these volatiles didn't need to be, some were otherwise protected by
spinlocks or mutexes, and some got moved over to SDL_atomic_t data, etc.

Fixes Bugzilla #3220.
slouken@1
     1
/*
slouken@5535
     2
  Simple DirectMedia Layer
slouken@9998
     3
  Copyright (C) 1997-2016 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"
slouken@5111
    27
#include "SDL_thread.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@5111
   100
static int
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@5111
   171
                current->scheduled = tick + interval;
slouken@5111
   172
                SDL_AddTimerInternal(data, current);
slouken@5111
   173
            } else {
slouken@5111
   174
                if (!freelist_head) {
slouken@5111
   175
                    freelist_head = current;
slouken@5111
   176
                }
slouken@5111
   177
                if (freelist_tail) {
slouken@5111
   178
                    freelist_tail->next = current;
slouken@5111
   179
                }
slouken@5111
   180
                freelist_tail = current;
slouken@5111
   181
icculus@10003
   182
                SDL_AtomicSet(&current->canceled, 1);
slouken@5111
   183
            }
slouken@5111
   184
        }
slouken@5111
   185
slouken@5111
   186
        /* Adjust the delay based on processing time */
slouken@5111
   187
        now = SDL_GetTicks();
slouken@5111
   188
        interval = (now - tick);
slouken@5111
   189
        if (interval > delay) {
slouken@5111
   190
            delay = 0;
slouken@5111
   191
        } else {
slouken@5111
   192
            delay -= interval;
slouken@5111
   193
        }
slouken@5111
   194
slouken@5111
   195
        /* Note that each time a timer is added, this will return
slouken@5111
   196
           immediately, but we process the timers added all at once.
slouken@5111
   197
           That's okay, it just means we run through the loop a few
slouken@5111
   198
           extra times.
slouken@5111
   199
         */
slouken@5111
   200
        SDL_SemWaitTimeout(data->sem, delay);
slouken@1895
   201
    }
slouken@5111
   202
    return 0;
slouken@1
   203
}
slouken@1
   204
slouken@1895
   205
int
slouken@1895
   206
SDL_TimerInit(void)
slouken@1
   207
{
slouken@5111
   208
    SDL_TimerData *data = &SDL_timer_data;
slouken@5111
   209
icculus@10003
   210
    if (!SDL_AtomicGet(&data->active)) {
icculus@5969
   211
        const char *name = "SDLTimer";
slouken@5111
   212
        data->timermap_lock = SDL_CreateMutex();
slouken@5111
   213
        if (!data->timermap_lock) {
slouken@5111
   214
            return -1;
slouken@5111
   215
        }
slouken@1
   216
slouken@5111
   217
        data->sem = SDL_CreateSemaphore(0);
slouken@5111
   218
        if (!data->sem) {
slouken@5111
   219
            SDL_DestroyMutex(data->timermap_lock);
slouken@5111
   220
            return -1;
slouken@5111
   221
        }
slouken@5111
   222
icculus@10003
   223
        SDL_AtomicSet(&data->active, 1);
slouken@5114
   224
        /* !!! FIXME: this is nasty. */
icculus@6430
   225
#if defined(__WIN32__) && !defined(HAVE_LIBC)
slouken@5114
   226
#undef SDL_CreateThread
icculus@8094
   227
#if SDL_DYNAMIC_API
icculus@8094
   228
        data->thread = SDL_CreateThread_REAL(SDL_TimerThread, name, data, NULL, NULL);
icculus@8094
   229
#else
icculus@5969
   230
        data->thread = SDL_CreateThread(SDL_TimerThread, name, data, NULL, NULL);
icculus@8094
   231
#endif
slouken@5114
   232
#else
icculus@5969
   233
        data->thread = SDL_CreateThread(SDL_TimerThread, name, data);
slouken@5114
   234
#endif
slouken@5111
   235
        if (!data->thread) {
slouken@5111
   236
            SDL_TimerQuit();
slouken@5111
   237
            return -1;
slouken@5111
   238
        }
slouken@5111
   239
slouken@5111
   240
        SDL_AtomicSet(&data->nextID, 1);
slouken@1895
   241
    }
slouken@5111
   242
    return 0;
slouken@1
   243
}
slouken@1
   244
slouken@1895
   245
void
slouken@1895
   246
SDL_TimerQuit(void)
slouken@1
   247
{
slouken@5111
   248
    SDL_TimerData *data = &SDL_timer_data;
slouken@5111
   249
    SDL_Timer *timer;
slouken@5111
   250
    SDL_TimerMap *entry;
slouken@5111
   251
icculus@10003
   252
    if (SDL_AtomicCAS(&data->active, 1, 0)) {  /* active? Move to inactive. */
slouken@5111
   253
        /* Shutdown the timer thread */
slouken@5111
   254
        if (data->thread) {
slouken@5111
   255
            SDL_SemPost(data->sem);
slouken@5111
   256
            SDL_WaitThread(data->thread, NULL);
slouken@5111
   257
            data->thread = NULL;
slouken@5111
   258
        }
slouken@5074
   259
slouken@5111
   260
        SDL_DestroySemaphore(data->sem);
slouken@5111
   261
        data->sem = NULL;
slouken@1028
   262
slouken@5111
   263
        /* Clean up the timer entries */
slouken@5111
   264
        while (data->timers) {
slouken@5111
   265
            timer = data->timers;
slouken@5111
   266
            data->timers = timer->next;
slouken@5111
   267
            SDL_free(timer);
slouken@1895
   268
        }
slouken@5111
   269
        while (data->freelist) {
slouken@5111
   270
            timer = data->freelist;
slouken@5111
   271
            data->freelist = timer->next;
slouken@5111
   272
            SDL_free(timer);
slouken@5111
   273
        }
slouken@5111
   274
        while (data->timermap) {
slouken@5111
   275
            entry = data->timermap;
slouken@5111
   276
            data->timermap = entry->next;
slouken@5111
   277
            SDL_free(entry);
slouken@5111
   278
        }
slouken@1
   279
slouken@5111
   280
        SDL_DestroyMutex(data->timermap_lock);
slouken@5111
   281
        data->timermap_lock = NULL;
slouken@1895
   282
    }
slouken@1028
   283
}
slouken@1028
   284
slouken@1895
   285
SDL_TimerID
slouken@5111
   286
SDL_AddTimer(Uint32 interval, SDL_TimerCallback callback, void *param)
slouken@1
   287
{
slouken@5111
   288
    SDL_TimerData *data = &SDL_timer_data;
slouken@5111
   289
    SDL_Timer *timer;
slouken@5111
   290
    SDL_TimerMap *entry;
slouken@5111
   291
icculus@10003
   292
    SDL_AtomicLock(&data->lock);
icculus@10003
   293
    if (!SDL_AtomicGet(&data->active)) {
icculus@10003
   294
        if (SDL_TimerInit() < 0) {
icculus@10003
   295
            SDL_AtomicUnlock(&data->lock);
slouken@5111
   296
            return 0;
slouken@1895
   297
        }
slouken@5111
   298
    }
slouken@5111
   299
slouken@5111
   300
    timer = data->freelist;
slouken@5111
   301
    if (timer) {
slouken@5111
   302
        data->freelist = timer->next;
slouken@5111
   303
    }
slouken@5111
   304
    SDL_AtomicUnlock(&data->lock);
slouken@5111
   305
slouken@5111
   306
    if (timer) {
slouken@5111
   307
        SDL_RemoveTimer(timer->timerID);
slouken@5111
   308
    } else {
slouken@5111
   309
        timer = (SDL_Timer *)SDL_malloc(sizeof(*timer));
slouken@5111
   310
        if (!timer) {
slouken@5111
   311
            SDL_OutOfMemory();
slouken@5111
   312
            return 0;
slouken@5111
   313
        }
slouken@1895
   314
    }
slouken@5111
   315
    timer->timerID = SDL_AtomicIncRef(&data->nextID);
slouken@5111
   316
    timer->callback = callback;
slouken@5111
   317
    timer->param = param;
slouken@5111
   318
    timer->interval = interval;
slouken@5111
   319
    timer->scheduled = SDL_GetTicks() + interval;
icculus@10003
   320
    SDL_AtomicSet(&timer->canceled, 0);
slouken@7191
   321
slouken@5111
   322
    entry = (SDL_TimerMap *)SDL_malloc(sizeof(*entry));
slouken@5111
   323
    if (!entry) {
slouken@5111
   324
        SDL_free(timer);
slouken@5111
   325
        SDL_OutOfMemory();
slouken@5111
   326
        return 0;
slouken@1895
   327
    }
slouken@5111
   328
    entry->timer = timer;
slouken@5111
   329
    entry->timerID = timer->timerID;
slouken@5111
   330
slouken@6977
   331
    SDL_LockMutex(data->timermap_lock);
slouken@5111
   332
    entry->next = data->timermap;
slouken@5111
   333
    data->timermap = entry;
slouken@6977
   334
    SDL_UnlockMutex(data->timermap_lock);
slouken@5111
   335
slouken@5111
   336
    /* Add the timer to the pending list for the timer thread */
slouken@5111
   337
    SDL_AtomicLock(&data->lock);
slouken@5111
   338
    timer->next = data->pending;
slouken@5111
   339
    data->pending = timer;
slouken@5111
   340
    SDL_AtomicUnlock(&data->lock);
slouken@5111
   341
slouken@5111
   342
    /* Wake up the timer thread if necessary */
slouken@5111
   343
    SDL_SemPost(data->sem);
slouken@5111
   344
slouken@5111
   345
    return entry->timerID;
slouken@1
   346
}
slouken@1
   347
slouken@1895
   348
SDL_bool
slouken@1895
   349
SDL_RemoveTimer(SDL_TimerID id)
slouken@1
   350
{
slouken@5111
   351
    SDL_TimerData *data = &SDL_timer_data;
slouken@5111
   352
    SDL_TimerMap *prev, *entry;
slouken@5111
   353
    SDL_bool canceled = SDL_FALSE;
slouken@1
   354
slouken@5111
   355
    /* Find the timer */
slouken@6977
   356
    SDL_LockMutex(data->timermap_lock);
slouken@5111
   357
    prev = NULL;
slouken@5111
   358
    for (entry = data->timermap; entry; prev = entry, entry = entry->next) {
slouken@5111
   359
        if (entry->timerID == id) {
slouken@1895
   360
            if (prev) {
slouken@5111
   361
                prev->next = entry->next;
slouken@1895
   362
            } else {
slouken@5111
   363
                data->timermap = entry->next;
slouken@1895
   364
            }
slouken@1895
   365
            break;
slouken@1895
   366
        }
slouken@1895
   367
    }
slouken@6977
   368
    SDL_UnlockMutex(data->timermap_lock);
slouken@1
   369
slouken@5111
   370
    if (entry) {
icculus@10003
   371
        if (!SDL_AtomicGet(&entry->timer->canceled)) {
icculus@10003
   372
            SDL_AtomicSet(&entry->timer->canceled, 1);
slouken@5111
   373
            canceled = SDL_TRUE;
slouken@5111
   374
        }
slouken@5111
   375
        SDL_free(entry);
slouken@1895
   376
    }
slouken@5111
   377
    return canceled;
slouken@1
   378
}
slouken@1895
   379
slouken@1895
   380
/* vi: set ts=4 sw=4 expandtab: */