src/timer/SDL_timer.c
author Sam Lantinga <slouken@libsdl.org>
Sun, 26 May 2013 11:34:04 -0700
changeset 7223 76fa20889de8
parent 7191 75360622e65f
child 7649 4f801cd08f3f
permissions -rw-r--r--
Fixed compile errors building with mingw64
slouken@1
     1
/*
slouken@5535
     2
  Simple DirectMedia Layer
slouken@6885
     3
  Copyright (C) 1997-2013 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
*/
slouken@1402
    21
#include "SDL_config.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
urkle@7139
    29
extern void SDL_StartTicks(void);
urkle@7139
    30
slouken@1
    31
/* #define DEBUG_TIMERS */
slouken@1
    32
slouken@5111
    33
typedef struct _SDL_Timer
slouken@5111
    34
{
slouken@5111
    35
    int timerID;
slouken@5111
    36
    SDL_TimerCallback callback;
slouken@5111
    37
    void *param;
slouken@5111
    38
    Uint32 interval;
slouken@5111
    39
    Uint32 scheduled;
slouken@5111
    40
    volatile SDL_bool canceled;
slouken@5111
    41
    struct _SDL_Timer *next;
slouken@5111
    42
} SDL_Timer;
slouken@5111
    43
slouken@5111
    44
typedef struct _SDL_TimerMap
slouken@5111
    45
{
slouken@5111
    46
    int timerID;
slouken@5111
    47
    SDL_Timer *timer;
slouken@5111
    48
    struct _SDL_TimerMap *next;
slouken@5111
    49
} SDL_TimerMap;
slouken@1
    50
slouken@5111
    51
/* The timers are kept in a sorted list */
slouken@5111
    52
typedef struct {
slouken@5111
    53
    /* Data used by the main thread */
slouken@5111
    54
    SDL_Thread *thread;
slouken@5111
    55
    SDL_atomic_t nextID;
slouken@5111
    56
    SDL_TimerMap *timermap;
slouken@5111
    57
    SDL_mutex *timermap_lock;
slouken@5111
    58
slouken@5111
    59
    /* Padding to separate cache lines between threads */
slouken@5115
    60
    char cache_pad[SDL_CACHELINE_SIZE];
slouken@5111
    61
slouken@5111
    62
    /* Data used to communicate with the timer thread */
slouken@5111
    63
    SDL_SpinLock lock;
slouken@5111
    64
    SDL_sem *sem;
slouken@5111
    65
    SDL_Timer * volatile pending;
slouken@5111
    66
    SDL_Timer * volatile freelist;
slouken@5111
    67
    volatile SDL_bool active;
slouken@1
    68
slouken@5111
    69
    /* List of timers - this is only touched by the timer thread */
slouken@5111
    70
    SDL_Timer *timers;
slouken@5111
    71
} SDL_TimerData;
slouken@5111
    72
slouken@5111
    73
static SDL_TimerData SDL_timer_data;
slouken@5111
    74
urkle@7139
    75
static Uint32 ticks_started = 0;
urkle@7139
    76
urkle@7139
    77
void
urkle@7139
    78
SDL_InitTicks(void)
urkle@7139
    79
{
urkle@7139
    80
    if (!ticks_started) {
urkle@7139
    81
        SDL_StartTicks();
urkle@7139
    82
        ticks_started = 1;
urkle@7139
    83
    }
urkle@7139
    84
}
slouken@1
    85
slouken@5111
    86
/* The idea here is that any thread might add a timer, but a single
slouken@5111
    87
 * thread manages the active timer queue, sorted by scheduling time.
slouken@5111
    88
 *
slouken@5111
    89
 * Timers are removed by simply setting a canceled flag
slouken@5111
    90
 */
slouken@5111
    91
slouken@5111
    92
static void
slouken@5111
    93
SDL_AddTimerInternal(SDL_TimerData *data, SDL_Timer *timer)
slouken@1895
    94
{
slouken@5111
    95
    SDL_Timer *prev, *curr;
slouken@5111
    96
slouken@5111
    97
    prev = NULL;
slouken@5111
    98
    for (curr = data->timers; curr; prev = curr, curr = curr->next) {
slouken@5111
    99
        if ((Sint32)(timer->scheduled-curr->scheduled) < 0) {
slouken@5111
   100
            break;
slouken@5111
   101
        }
slouken@5111
   102
    }
slouken@5111
   103
slouken@5111
   104
    /* Insert the timer here! */
slouken@5111
   105
    if (prev) {
slouken@5111
   106
        prev->next = timer;
slouken@5111
   107
    } else {
slouken@5111
   108
        data->timers = timer;
slouken@5111
   109
    }
slouken@5111
   110
    timer->next = curr;
slouken@5111
   111
}
slouken@5111
   112
slouken@5111
   113
static int
slouken@5111
   114
SDL_TimerThread(void *_data)
slouken@5111
   115
{
slouken@5111
   116
    SDL_TimerData *data = (SDL_TimerData *)_data;
slouken@5111
   117
    SDL_Timer *pending;
slouken@5111
   118
    SDL_Timer *current;
slouken@5111
   119
    SDL_Timer *freelist_head = NULL;
slouken@5111
   120
    SDL_Timer *freelist_tail = NULL;
slouken@5111
   121
    Uint32 tick, now, interval, delay;
slouken@1
   122
slouken@5111
   123
    /* Threaded timer loop:
slouken@5111
   124
     *  1. Queue timers added by other threads
slouken@5111
   125
     *  2. Handle any timers that should dispatch this cycle
slouken@5111
   126
     *  3. Wait until next dispatch time or new timer arrives
slouken@5111
   127
     */
slouken@5111
   128
    for ( ; ; ) {
slouken@5111
   129
        /* Pending and freelist maintenance */
slouken@5111
   130
        SDL_AtomicLock(&data->lock);
slouken@5111
   131
        {
slouken@5111
   132
            /* Get any timers ready to be queued */
slouken@5111
   133
            pending = data->pending;
slouken@5111
   134
            data->pending = NULL;
slouken@5111
   135
slouken@5111
   136
            /* Make any unused timer structures available */
slouken@5111
   137
            if (freelist_head) {
slouken@5111
   138
                freelist_tail->next = data->freelist;
slouken@5111
   139
                data->freelist = freelist_head;
slouken@5111
   140
            }
slouken@5111
   141
        }
slouken@5111
   142
        SDL_AtomicUnlock(&data->lock);
slouken@5111
   143
slouken@5111
   144
        /* Sort the pending timers into our list */
slouken@5111
   145
        while (pending) {
slouken@5111
   146
            current = pending;
slouken@5111
   147
            pending = pending->next;
slouken@5111
   148
            SDL_AddTimerInternal(data, current);
slouken@5111
   149
        }
slouken@5111
   150
        freelist_head = NULL;
slouken@5111
   151
        freelist_tail = NULL;
slouken@5111
   152
slouken@5111
   153
        /* Check to see if we're still running, after maintenance */
slouken@5111
   154
        if (!data->active) {
slouken@5111
   155
            break;
slouken@5111
   156
        }
slouken@5111
   157
slouken@5111
   158
        /* Initial delay if there are no timers */
slouken@5111
   159
        delay = SDL_MUTEX_MAXWAIT;
slouken@5111
   160
slouken@5111
   161
        tick = SDL_GetTicks();
slouken@5111
   162
slouken@5111
   163
        /* Process all the pending timers for this tick */
slouken@5111
   164
        while (data->timers) {
slouken@5111
   165
            current = data->timers;
slouken@1
   166
slouken@5111
   167
            if ((Sint32)(tick-current->scheduled) < 0) {
slouken@5111
   168
                /* Scheduled for the future, wait a bit */
slouken@5111
   169
                delay = (current->scheduled - tick);
slouken@5111
   170
                break;
slouken@5111
   171
            }
slouken@5111
   172
slouken@5111
   173
            /* We're going to do something with this timer */
slouken@5111
   174
            data->timers = current->next;
slouken@5111
   175
slouken@5111
   176
            if (current->canceled) {
slouken@5111
   177
                interval = 0;
slouken@5111
   178
            } else {
slouken@5111
   179
                interval = current->callback(current->interval, current->param);
slouken@5111
   180
            }
slouken@1
   181
slouken@5111
   182
            if (interval > 0) {
slouken@5111
   183
                /* Reschedule this timer */
slouken@5111
   184
                current->scheduled = tick + interval;
slouken@5111
   185
                SDL_AddTimerInternal(data, current);
slouken@5111
   186
            } else {
slouken@5111
   187
                if (!freelist_head) {
slouken@5111
   188
                    freelist_head = current;
slouken@5111
   189
                }
slouken@5111
   190
                if (freelist_tail) {
slouken@5111
   191
                    freelist_tail->next = current;
slouken@5111
   192
                }
slouken@5111
   193
                freelist_tail = current;
slouken@5111
   194
slouken@5111
   195
                current->canceled = SDL_TRUE;
slouken@5111
   196
            }
slouken@5111
   197
        }
slouken@5111
   198
slouken@5111
   199
        /* Adjust the delay based on processing time */
slouken@5111
   200
        now = SDL_GetTicks();
slouken@5111
   201
        interval = (now - tick);
slouken@5111
   202
        if (interval > delay) {
slouken@5111
   203
            delay = 0;
slouken@5111
   204
        } else {
slouken@5111
   205
            delay -= interval;
slouken@5111
   206
        }
slouken@5111
   207
slouken@5111
   208
        /* Note that each time a timer is added, this will return
slouken@5111
   209
           immediately, but we process the timers added all at once.
slouken@5111
   210
           That's okay, it just means we run through the loop a few
slouken@5111
   211
           extra times.
slouken@5111
   212
         */
slouken@5111
   213
        SDL_SemWaitTimeout(data->sem, delay);
slouken@1895
   214
    }
slouken@5111
   215
    return 0;
slouken@1
   216
}
slouken@1
   217
slouken@1895
   218
int
slouken@1895
   219
SDL_TimerInit(void)
slouken@1
   220
{
slouken@5111
   221
    SDL_TimerData *data = &SDL_timer_data;
slouken@5111
   222
slouken@5111
   223
    if (!data->active) {
icculus@5969
   224
        const char *name = "SDLTimer";
slouken@5111
   225
        data->timermap_lock = SDL_CreateMutex();
slouken@5111
   226
        if (!data->timermap_lock) {
slouken@5111
   227
            return -1;
slouken@5111
   228
        }
slouken@1
   229
slouken@5111
   230
        data->sem = SDL_CreateSemaphore(0);
slouken@5111
   231
        if (!data->sem) {
slouken@5111
   232
            SDL_DestroyMutex(data->timermap_lock);
slouken@5111
   233
            return -1;
slouken@5111
   234
        }
slouken@5111
   235
slouken@5111
   236
        data->active = SDL_TRUE;
slouken@5114
   237
        /* !!! FIXME: this is nasty. */
icculus@6430
   238
#if defined(__WIN32__) && !defined(HAVE_LIBC)
slouken@5114
   239
#undef SDL_CreateThread
icculus@5969
   240
        data->thread = SDL_CreateThread(SDL_TimerThread, name, data, NULL, NULL);
slouken@5114
   241
#else
icculus@5969
   242
        data->thread = SDL_CreateThread(SDL_TimerThread, name, data);
slouken@5114
   243
#endif
slouken@5111
   244
        if (!data->thread) {
slouken@5111
   245
            SDL_TimerQuit();
slouken@5111
   246
            return -1;
slouken@5111
   247
        }
slouken@5111
   248
slouken@5111
   249
        SDL_AtomicSet(&data->nextID, 1);
slouken@1895
   250
    }
slouken@5111
   251
    return 0;
slouken@1
   252
}
slouken@1
   253
slouken@1895
   254
void
slouken@1895
   255
SDL_TimerQuit(void)
slouken@1
   256
{
slouken@5111
   257
    SDL_TimerData *data = &SDL_timer_data;
slouken@5111
   258
    SDL_Timer *timer;
slouken@5111
   259
    SDL_TimerMap *entry;
slouken@5111
   260
slouken@5111
   261
    if (data->active) {
slouken@5111
   262
        data->active = SDL_FALSE;
slouken@1
   263
slouken@5111
   264
        /* Shutdown the timer thread */
slouken@5111
   265
        if (data->thread) {
slouken@5111
   266
            SDL_SemPost(data->sem);
slouken@5111
   267
            SDL_WaitThread(data->thread, NULL);
slouken@5111
   268
            data->thread = NULL;
slouken@5111
   269
        }
slouken@5074
   270
slouken@5111
   271
        SDL_DestroySemaphore(data->sem);
slouken@5111
   272
        data->sem = NULL;
slouken@1028
   273
slouken@5111
   274
        /* Clean up the timer entries */
slouken@5111
   275
        while (data->timers) {
slouken@5111
   276
            timer = data->timers;
slouken@5111
   277
            data->timers = timer->next;
slouken@5111
   278
            SDL_free(timer);
slouken@1895
   279
        }
slouken@5111
   280
        while (data->freelist) {
slouken@5111
   281
            timer = data->freelist;
slouken@5111
   282
            data->freelist = timer->next;
slouken@5111
   283
            SDL_free(timer);
slouken@5111
   284
        }
slouken@5111
   285
        while (data->timermap) {
slouken@5111
   286
            entry = data->timermap;
slouken@5111
   287
            data->timermap = entry->next;
slouken@5111
   288
            SDL_free(entry);
slouken@5111
   289
        }
slouken@1
   290
slouken@5111
   291
        SDL_DestroyMutex(data->timermap_lock);
slouken@5111
   292
        data->timermap_lock = NULL;
slouken@1895
   293
    }
slouken@1028
   294
}
slouken@1028
   295
slouken@1895
   296
SDL_TimerID
slouken@5111
   297
SDL_AddTimer(Uint32 interval, SDL_TimerCallback callback, void *param)
slouken@1
   298
{
slouken@5111
   299
    SDL_TimerData *data = &SDL_timer_data;
slouken@5111
   300
    SDL_Timer *timer;
slouken@5111
   301
    SDL_TimerMap *entry;
slouken@5111
   302
slouken@5111
   303
    if (!data->active) {
slouken@5111
   304
        int status = 0;
slouken@5111
   305
slouken@5111
   306
        SDL_AtomicLock(&data->lock);
slouken@5111
   307
        if (!data->active) {
slouken@5111
   308
            status = SDL_TimerInit();
slouken@5111
   309
        }
slouken@5111
   310
        SDL_AtomicUnlock(&data->lock);
slouken@5111
   311
slouken@5111
   312
        if (status < 0) {
slouken@5111
   313
            return 0;
slouken@1895
   314
        }
slouken@5111
   315
    }
slouken@5111
   316
slouken@5111
   317
    SDL_AtomicLock(&data->lock);
slouken@5111
   318
    timer = data->freelist;
slouken@5111
   319
    if (timer) {
slouken@5111
   320
        data->freelist = timer->next;
slouken@5111
   321
    }
slouken@5111
   322
    SDL_AtomicUnlock(&data->lock);
slouken@5111
   323
slouken@5111
   324
    if (timer) {
slouken@5111
   325
        SDL_RemoveTimer(timer->timerID);
slouken@5111
   326
    } else {
slouken@5111
   327
        timer = (SDL_Timer *)SDL_malloc(sizeof(*timer));
slouken@5111
   328
        if (!timer) {
slouken@5111
   329
            SDL_OutOfMemory();
slouken@5111
   330
            return 0;
slouken@5111
   331
        }
slouken@1895
   332
    }
slouken@5111
   333
    timer->timerID = SDL_AtomicIncRef(&data->nextID);
slouken@5111
   334
    timer->callback = callback;
slouken@5111
   335
    timer->param = param;
slouken@5111
   336
    timer->interval = interval;
slouken@5111
   337
    timer->scheduled = SDL_GetTicks() + interval;
slouken@5111
   338
    timer->canceled = SDL_FALSE;
slouken@7191
   339
slouken@5111
   340
    entry = (SDL_TimerMap *)SDL_malloc(sizeof(*entry));
slouken@5111
   341
    if (!entry) {
slouken@5111
   342
        SDL_free(timer);
slouken@5111
   343
        SDL_OutOfMemory();
slouken@5111
   344
        return 0;
slouken@1895
   345
    }
slouken@5111
   346
    entry->timer = timer;
slouken@5111
   347
    entry->timerID = timer->timerID;
slouken@5111
   348
slouken@6977
   349
    SDL_LockMutex(data->timermap_lock);
slouken@5111
   350
    entry->next = data->timermap;
slouken@5111
   351
    data->timermap = entry;
slouken@6977
   352
    SDL_UnlockMutex(data->timermap_lock);
slouken@5111
   353
slouken@5111
   354
    /* Add the timer to the pending list for the timer thread */
slouken@5111
   355
    SDL_AtomicLock(&data->lock);
slouken@5111
   356
    timer->next = data->pending;
slouken@5111
   357
    data->pending = timer;
slouken@5111
   358
    SDL_AtomicUnlock(&data->lock);
slouken@5111
   359
slouken@5111
   360
    /* Wake up the timer thread if necessary */
slouken@5111
   361
    SDL_SemPost(data->sem);
slouken@5111
   362
slouken@5111
   363
    return entry->timerID;
slouken@1
   364
}
slouken@1
   365
slouken@1895
   366
SDL_bool
slouken@1895
   367
SDL_RemoveTimer(SDL_TimerID id)
slouken@1
   368
{
slouken@5111
   369
    SDL_TimerData *data = &SDL_timer_data;
slouken@5111
   370
    SDL_TimerMap *prev, *entry;
slouken@5111
   371
    SDL_bool canceled = SDL_FALSE;
slouken@1
   372
slouken@5111
   373
    /* Find the timer */
slouken@6977
   374
    SDL_LockMutex(data->timermap_lock);
slouken@5111
   375
    prev = NULL;
slouken@5111
   376
    for (entry = data->timermap; entry; prev = entry, entry = entry->next) {
slouken@5111
   377
        if (entry->timerID == id) {
slouken@1895
   378
            if (prev) {
slouken@5111
   379
                prev->next = entry->next;
slouken@1895
   380
            } else {
slouken@5111
   381
                data->timermap = entry->next;
slouken@1895
   382
            }
slouken@1895
   383
            break;
slouken@1895
   384
        }
slouken@1895
   385
    }
slouken@6977
   386
    SDL_UnlockMutex(data->timermap_lock);
slouken@1
   387
slouken@5111
   388
    if (entry) {
slouken@5111
   389
        if (!entry->timer->canceled) {
slouken@5111
   390
            entry->timer->canceled = SDL_TRUE;
slouken@5111
   391
            canceled = SDL_TRUE;
slouken@5111
   392
        }
slouken@5111
   393
        SDL_free(entry);
slouken@1895
   394
    }
slouken@5111
   395
    return canceled;
slouken@1
   396
}
slouken@1895
   397
slouken@1895
   398
/* vi: set ts=4 sw=4 expandtab: */