src/timer/SDL_timer.c
author Sam Lantinga
Sat, 06 Oct 2012 12:16:32 -0700
changeset 6566 dd7e57847ea9
parent 6430 48d519500f7e
child 6885 700f1b25f77f
permissions -rw-r--r--
Add flags to the vidmode debug output
slouken@1
     1
/*
slouken@5535
     2
  Simple DirectMedia Layer
slouken@6138
     3
  Copyright (C) 1997-2012 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
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;
slouken@5111
    38
    volatile SDL_bool 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;
slouken@5111
    63
    SDL_Timer * volatile pending;
slouken@5111
    64
    SDL_Timer * volatile freelist;
slouken@5111
    65
    volatile SDL_bool 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@1
    73
slouken@5111
    74
/* The idea here is that any thread might add a timer, but a single
slouken@5111
    75
 * thread manages the active timer queue, sorted by scheduling time.
slouken@5111
    76
 *
slouken@5111
    77
 * Timers are removed by simply setting a canceled flag
slouken@5111
    78
 */
slouken@5111
    79
slouken@5111
    80
static void
slouken@5111
    81
SDL_AddTimerInternal(SDL_TimerData *data, SDL_Timer *timer)
slouken@1895
    82
{
slouken@5111
    83
    SDL_Timer *prev, *curr;
slouken@5111
    84
slouken@5111
    85
    prev = NULL;
slouken@5111
    86
    for (curr = data->timers; curr; prev = curr, curr = curr->next) {
slouken@5111
    87
        if ((Sint32)(timer->scheduled-curr->scheduled) < 0) {
slouken@5111
    88
            break;
slouken@5111
    89
        }
slouken@5111
    90
    }
slouken@5111
    91
slouken@5111
    92
    /* Insert the timer here! */
slouken@5111
    93
    if (prev) {
slouken@5111
    94
        prev->next = timer;
slouken@5111
    95
    } else {
slouken@5111
    96
        data->timers = timer;
slouken@5111
    97
    }
slouken@5111
    98
    timer->next = curr;
slouken@5111
    99
}
slouken@5111
   100
slouken@5111
   101
static int
slouken@5111
   102
SDL_TimerThread(void *_data)
slouken@5111
   103
{
slouken@5111
   104
    SDL_TimerData *data = (SDL_TimerData *)_data;
slouken@5111
   105
    SDL_Timer *pending;
slouken@5111
   106
    SDL_Timer *current;
slouken@5111
   107
    SDL_Timer *freelist_head = NULL;
slouken@5111
   108
    SDL_Timer *freelist_tail = NULL;
slouken@5111
   109
    Uint32 tick, now, interval, delay;
slouken@1
   110
slouken@5111
   111
    /* Threaded timer loop:
slouken@5111
   112
     *  1. Queue timers added by other threads
slouken@5111
   113
     *  2. Handle any timers that should dispatch this cycle
slouken@5111
   114
     *  3. Wait until next dispatch time or new timer arrives
slouken@5111
   115
     */
slouken@5111
   116
    for ( ; ; ) {
slouken@5111
   117
        /* Pending and freelist maintenance */
slouken@5111
   118
        SDL_AtomicLock(&data->lock);
slouken@5111
   119
        {
slouken@5111
   120
            /* Get any timers ready to be queued */
slouken@5111
   121
            pending = data->pending;
slouken@5111
   122
            data->pending = NULL;
slouken@5111
   123
slouken@5111
   124
            /* Make any unused timer structures available */
slouken@5111
   125
            if (freelist_head) {
slouken@5111
   126
                freelist_tail->next = data->freelist;
slouken@5111
   127
                data->freelist = freelist_head;
slouken@5111
   128
            }
slouken@5111
   129
        }
slouken@5111
   130
        SDL_AtomicUnlock(&data->lock);
slouken@5111
   131
slouken@5111
   132
        /* Sort the pending timers into our list */
slouken@5111
   133
        while (pending) {
slouken@5111
   134
            current = pending;
slouken@5111
   135
            pending = pending->next;
slouken@5111
   136
            SDL_AddTimerInternal(data, current);
slouken@5111
   137
        }
slouken@5111
   138
        freelist_head = NULL;
slouken@5111
   139
        freelist_tail = NULL;
slouken@5111
   140
slouken@5111
   141
        /* Check to see if we're still running, after maintenance */
slouken@5111
   142
        if (!data->active) {
slouken@5111
   143
            break;
slouken@5111
   144
        }
slouken@5111
   145
slouken@5111
   146
        /* Initial delay if there are no timers */
slouken@5111
   147
        delay = SDL_MUTEX_MAXWAIT;
slouken@5111
   148
slouken@5111
   149
        tick = SDL_GetTicks();
slouken@5111
   150
slouken@5111
   151
        /* Process all the pending timers for this tick */
slouken@5111
   152
        while (data->timers) {
slouken@5111
   153
            current = data->timers;
slouken@1
   154
slouken@5111
   155
            if ((Sint32)(tick-current->scheduled) < 0) {
slouken@5111
   156
                /* Scheduled for the future, wait a bit */
slouken@5111
   157
                delay = (current->scheduled - tick);
slouken@5111
   158
                break;
slouken@5111
   159
            }
slouken@5111
   160
slouken@5111
   161
            /* We're going to do something with this timer */
slouken@5111
   162
            data->timers = current->next;
slouken@5111
   163
slouken@5111
   164
            if (current->canceled) {
slouken@5111
   165
                interval = 0;
slouken@5111
   166
            } else {
slouken@5111
   167
                interval = current->callback(current->interval, current->param);
slouken@5111
   168
            }
slouken@1
   169
slouken@5111
   170
            if (interval > 0) {
slouken@5111
   171
                /* Reschedule this timer */
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
slouken@5111
   183
                current->canceled = SDL_TRUE;
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
slouken@5111
   211
    if (!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
slouken@5111
   224
        data->active = SDL_TRUE;
slouken@5114
   225
        /* !!! FIXME: this is nasty. */
icculus@6430
   226
#if defined(__WIN32__) && !defined(HAVE_LIBC)
slouken@5114
   227
#undef SDL_CreateThread
icculus@5969
   228
        data->thread = SDL_CreateThread(SDL_TimerThread, name, data, NULL, NULL);
slouken@5114
   229
#else
icculus@5969
   230
        data->thread = SDL_CreateThread(SDL_TimerThread, name, data);
slouken@5114
   231
#endif
slouken@5111
   232
        if (!data->thread) {
slouken@5111
   233
            SDL_TimerQuit();
slouken@5111
   234
            return -1;
slouken@5111
   235
        }
slouken@5111
   236
slouken@5111
   237
        SDL_AtomicSet(&data->nextID, 1);
slouken@1895
   238
    }
slouken@5111
   239
    return 0;
slouken@1
   240
}
slouken@1
   241
slouken@1895
   242
void
slouken@1895
   243
SDL_TimerQuit(void)
slouken@1
   244
{
slouken@5111
   245
    SDL_TimerData *data = &SDL_timer_data;
slouken@5111
   246
    SDL_Timer *timer;
slouken@5111
   247
    SDL_TimerMap *entry;
slouken@5111
   248
slouken@5111
   249
    if (data->active) {
slouken@5111
   250
        data->active = SDL_FALSE;
slouken@1
   251
slouken@5111
   252
        /* Shutdown the timer thread */
slouken@5111
   253
        if (data->thread) {
slouken@5111
   254
            SDL_SemPost(data->sem);
slouken@5111
   255
            SDL_WaitThread(data->thread, NULL);
slouken@5111
   256
            data->thread = NULL;
slouken@5111
   257
        }
slouken@5074
   258
slouken@5111
   259
        SDL_DestroySemaphore(data->sem);
slouken@5111
   260
        data->sem = NULL;
slouken@1028
   261
slouken@5111
   262
        /* Clean up the timer entries */
slouken@5111
   263
        while (data->timers) {
slouken@5111
   264
            timer = data->timers;
slouken@5111
   265
            data->timers = timer->next;
slouken@5111
   266
            SDL_free(timer);
slouken@1895
   267
        }
slouken@5111
   268
        while (data->freelist) {
slouken@5111
   269
            timer = data->freelist;
slouken@5111
   270
            data->freelist = timer->next;
slouken@5111
   271
            SDL_free(timer);
slouken@5111
   272
        }
slouken@5111
   273
        while (data->timermap) {
slouken@5111
   274
            entry = data->timermap;
slouken@5111
   275
            data->timermap = entry->next;
slouken@5111
   276
            SDL_free(entry);
slouken@5111
   277
        }
slouken@1
   278
slouken@5111
   279
        SDL_DestroyMutex(data->timermap_lock);
slouken@5111
   280
        data->timermap_lock = NULL;
slouken@1895
   281
    }
slouken@1028
   282
}
slouken@1028
   283
slouken@1895
   284
SDL_TimerID
slouken@5111
   285
SDL_AddTimer(Uint32 interval, SDL_TimerCallback callback, void *param)
slouken@1
   286
{
slouken@5111
   287
    SDL_TimerData *data = &SDL_timer_data;
slouken@5111
   288
    SDL_Timer *timer;
slouken@5111
   289
    SDL_TimerMap *entry;
slouken@5111
   290
slouken@5111
   291
    if (!data->active) {
slouken@5111
   292
        int status = 0;
slouken@5111
   293
slouken@5111
   294
        SDL_AtomicLock(&data->lock);
slouken@5111
   295
        if (!data->active) {
slouken@5111
   296
            status = SDL_TimerInit();
slouken@5111
   297
        }
slouken@5111
   298
        SDL_AtomicUnlock(&data->lock);
slouken@5111
   299
slouken@5111
   300
        if (status < 0) {
slouken@5111
   301
            return 0;
slouken@1895
   302
        }
slouken@5111
   303
    }
slouken@5111
   304
slouken@5111
   305
    SDL_AtomicLock(&data->lock);
slouken@5111
   306
    timer = data->freelist;
slouken@5111
   307
    if (timer) {
slouken@5111
   308
        data->freelist = timer->next;
slouken@5111
   309
    }
slouken@5111
   310
    SDL_AtomicUnlock(&data->lock);
slouken@5111
   311
slouken@5111
   312
    if (timer) {
slouken@5111
   313
        SDL_RemoveTimer(timer->timerID);
slouken@5111
   314
    } else {
slouken@5111
   315
        timer = (SDL_Timer *)SDL_malloc(sizeof(*timer));
slouken@5111
   316
        if (!timer) {
slouken@5111
   317
            SDL_OutOfMemory();
slouken@5111
   318
            return 0;
slouken@5111
   319
        }
slouken@1895
   320
    }
slouken@5111
   321
    timer->timerID = SDL_AtomicIncRef(&data->nextID);
slouken@5111
   322
    timer->callback = callback;
slouken@5111
   323
    timer->param = param;
slouken@5111
   324
    timer->interval = interval;
slouken@5111
   325
    timer->scheduled = SDL_GetTicks() + interval;
slouken@5111
   326
    timer->canceled = SDL_FALSE;
slouken@5111
   327
 
slouken@5111
   328
    entry = (SDL_TimerMap *)SDL_malloc(sizeof(*entry));
slouken@5111
   329
    if (!entry) {
slouken@5111
   330
        SDL_free(timer);
slouken@5111
   331
        SDL_OutOfMemory();
slouken@5111
   332
        return 0;
slouken@1895
   333
    }
slouken@5111
   334
    entry->timer = timer;
slouken@5111
   335
    entry->timerID = timer->timerID;
slouken@5111
   336
slouken@5111
   337
    SDL_mutexP(data->timermap_lock);
slouken@5111
   338
    entry->next = data->timermap;
slouken@5111
   339
    data->timermap = entry;
slouken@5111
   340
    SDL_mutexV(data->timermap_lock);
slouken@5111
   341
slouken@5111
   342
    /* Add the timer to the pending list for the timer thread */
slouken@5111
   343
    SDL_AtomicLock(&data->lock);
slouken@5111
   344
    timer->next = data->pending;
slouken@5111
   345
    data->pending = timer;
slouken@5111
   346
    SDL_AtomicUnlock(&data->lock);
slouken@5111
   347
slouken@5111
   348
    /* Wake up the timer thread if necessary */
slouken@5111
   349
    SDL_SemPost(data->sem);
slouken@5111
   350
slouken@5111
   351
    return entry->timerID;
slouken@1
   352
}
slouken@1
   353
slouken@1895
   354
SDL_bool
slouken@1895
   355
SDL_RemoveTimer(SDL_TimerID id)
slouken@1
   356
{
slouken@5111
   357
    SDL_TimerData *data = &SDL_timer_data;
slouken@5111
   358
    SDL_TimerMap *prev, *entry;
slouken@5111
   359
    SDL_bool canceled = SDL_FALSE;
slouken@1
   360
slouken@5111
   361
    /* Find the timer */
slouken@5111
   362
    SDL_mutexP(data->timermap_lock);
slouken@5111
   363
    prev = NULL;
slouken@5111
   364
    for (entry = data->timermap; entry; prev = entry, entry = entry->next) {
slouken@5111
   365
        if (entry->timerID == id) {
slouken@1895
   366
            if (prev) {
slouken@5111
   367
                prev->next = entry->next;
slouken@1895
   368
            } else {
slouken@5111
   369
                data->timermap = entry->next;
slouken@1895
   370
            }
slouken@1895
   371
            break;
slouken@1895
   372
        }
slouken@1895
   373
    }
slouken@5111
   374
    SDL_mutexV(data->timermap_lock);
slouken@1
   375
slouken@5111
   376
    if (entry) {
slouken@5111
   377
        if (!entry->timer->canceled) {
slouken@5111
   378
            entry->timer->canceled = SDL_TRUE;
slouken@5111
   379
            canceled = SDL_TRUE;
slouken@5111
   380
        }
slouken@5111
   381
        SDL_free(entry);
slouken@1895
   382
    }
slouken@5111
   383
    return canceled;
slouken@1
   384
}
slouken@1895
   385
slouken@1895
   386
/* vi: set ts=4 sw=4 expandtab: */