summaryrefslogtreecommitdiff
path: root/opl/opl_timer.c
blob: 62ffbd37dfb801a79bf003e663e7270d4d531f45 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
// Emacs style mode select   -*- C++ -*- 
//-----------------------------------------------------------------------------
//
// Copyright(C) 2009 Simon Howard
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
// 02111-1307, USA.
//
// DESCRIPTION:
//     OPL timer thread.
//     Once started using OPL_Timer_StartThread, the thread sleeps,
//     waking up to invoke callbacks set using OPL_Timer_SetCallback.
//
//-----------------------------------------------------------------------------

#include "SDL.h"

#include "opl_timer.h"
#include "opl_queue.h"

typedef enum
{
    THREAD_STATE_STOPPED,
    THREAD_STATE_RUNNING,
    THREAD_STATE_STOPPING,
} thread_state_t;

static SDL_Thread *timer_thread = NULL;
static thread_state_t timer_thread_state;
static int current_time;

// Queue of callbacks waiting to be invoked.
// The callback queue mutex is held while the callback queue structure
// or current_time is being accessed.

static opl_callback_queue_t *callback_queue;
static SDL_mutex *callback_queue_mutex;

// The timer mutex is held while timer callback functions are being
// invoked, so that the calling code can prevent clashes.

static SDL_mutex *timer_mutex;

// Returns true if there is a callback at the head of the queue ready
// to be invoked.  Otherwise, next_time is set to the time when the
// timer thread must wake up again to check.

static int CallbackWaiting(unsigned int *next_time)
{
    // If there are no queued callbacks, sleep for 50ms at a time
    // until a callback is added.

    if (OPL_Queue_IsEmpty(callback_queue))
    {
        *next_time = current_time + 50;
        return 0;
    }

    // Read the time of the first callback in the queue.
    // If the time for the callback has not yet arrived,
    // we must sleep until the callback time.

    *next_time = OPL_Queue_Peek(callback_queue);

    return *next_time <= current_time;
}

static unsigned int GetNextTime(void)
{
    opl_callback_t callback;
    void *callback_data;
    unsigned int next_time;
    int have_callback;

    // Keep running through callbacks until there are none ready to
    // run. When we run out of callbacks, next_time will be set.

    do
    {
        SDL_LockMutex(callback_queue_mutex);

        // Check if the callback at the head of the list is ready to
        // be invoked.  If so, pop from the head of the queue.

        have_callback = CallbackWaiting(&next_time);

        if (have_callback)
        {
            OPL_Queue_Pop(callback_queue, &callback, &callback_data);
        }

        SDL_UnlockMutex(callback_queue_mutex);

        // Now invoke the callback, if we have one.
        // The timer mutex is held while the callback is invoked.

        if (have_callback)
        {
            SDL_LockMutex(timer_mutex);
            callback(callback_data);
            SDL_UnlockMutex(timer_mutex);
        }
    } while (have_callback);

    return next_time;
}

static int ThreadFunction(void *unused)
{
    unsigned int next_time;
    unsigned int now;

    // Keep running until OPL_Timer_StopThread is called.

    while (timer_thread_state == THREAD_STATE_RUNNING)
    {
        // Get the next time that we must sleep until, and
        // wait until that time.

        next_time = GetNextTime();
        now = SDL_GetTicks();

        if (next_time > now)
        {
            SDL_Delay(next_time - now);
        }

        // Update the current time.

        SDL_LockMutex(callback_queue_mutex);
        current_time = next_time;
        SDL_UnlockMutex(callback_queue_mutex);
    }

    timer_thread_state = THREAD_STATE_STOPPED;

    return 0;
}

static void InitResources(void)
{
    callback_queue = OPL_Queue_Create();
    timer_mutex = SDL_CreateMutex();
    callback_queue_mutex = SDL_CreateMutex();
}

static void FreeResources(void)
{
    OPL_Queue_Destroy(callback_queue);
    SDL_DestroyMutex(callback_queue_mutex);
    SDL_DestroyMutex(timer_mutex);
}

int OPL_Timer_StartThread(void)
{
    InitResources();

    timer_thread_state = THREAD_STATE_RUNNING;
    current_time = SDL_GetTicks();

    timer_thread = SDL_CreateThread(ThreadFunction, NULL);

    if (timer_thread == NULL)
    {
        timer_thread_state = THREAD_STATE_STOPPED;
        FreeResources();

        return 0;
    }

    return 1;
}

void OPL_Timer_StopThread(void)
{
    timer_thread_state = THREAD_STATE_STOPPING;

    while (timer_thread_state != THREAD_STATE_STOPPED)
    {
        SDL_Delay(1);
    }

    FreeResources();
}

void OPL_Timer_SetCallback(unsigned int ms, opl_callback_t callback, void *data)
{
    SDL_LockMutex(callback_queue_mutex);
    OPL_Queue_Push(callback_queue, callback, data, current_time + ms);
    SDL_UnlockMutex(callback_queue_mutex);
}

void OPL_Timer_Lock(void)
{
    SDL_LockMutex(timer_mutex);
}

void OPL_Timer_Unlock(void)
{
    SDL_UnlockMutex(timer_mutex);
}