src/timer/SDL_timer.c
author Ryan C. Gordon <icculus@icculus.org>
Tue, 23 Oct 2018 01:34:03 -0400
branchSDL-ryan-batching-renderer
changeset 12352 2b1707cc57fc
parent 11811 5d94cb6b24d3
child 12503 806492103856
permissions -rw-r--r--
render: Add floating point versions of various draw APIs.
slouken@1
     1
/*
slouken@5535
     2
  Simple DirectMedia Layer
slouken@11811
     3
  Copyright (C) 1997-2018 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@1
    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@5111
    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@1
    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@1
    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@1
   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@5111
   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@5111
   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@1
   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@5111
   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@5111
   252
slouken@5111
   253
        SDL_DestroySemaphore(data->sem);
slouken@5111
   254
        data->sem = NULL;
slouken@5111
   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@5111
   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@5111
   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@5111
   290
        }
slouken@1895
   291
    }
slouken@5111
   292
slouken@5111
   293
    timer = data->freelist;
slouken@5111
   294
    if (timer) {
slouken@5111
   295
        data->freelist = timer->next;
slouken@1895
   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@5111
   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@5111
   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: */