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
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
|
//
// Copyright(C) 2005-2014 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.
//
// 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 uint64_t current_time;
// If non-zero, callbacks are currently paused.
static int opl_timer_paused;
// Offset in microseconds to adjust time due to the fact that playback
// was paused.
static uint64_t pause_offset = 0;
// 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(uint64_t *next_time)
{
// If paused, just wait in 50ms increments until unpaused.
// Update pause_offset so after we unpause, the callback
// times will be right.
if (opl_timer_paused)
{
*next_time = current_time + 50 * OPL_MS;
pause_offset += 50 * OPL_MS;
return 0;
}
// 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 * OPL_MS;
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) + pause_offset;
return *next_time <= current_time;
}
static uint64_t GetNextTime(void)
{
opl_callback_t callback;
void *callback_data;
uint64_t 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)
{
uint64_t next_time;
uint64_t 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() * OPL_MS;
if (next_time > now)
{
SDL_Delay((next_time - now) / OPL_MS);
}
// 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();
opl_timer_paused = 0;
pause_offset = 0;
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(uint64_t us, opl_callback_t callback, void *data)
{
SDL_LockMutex(callback_queue_mutex);
OPL_Queue_Push(callback_queue, callback, data,
current_time + us - pause_offset);
SDL_UnlockMutex(callback_queue_mutex);
}
void OPL_Timer_ClearCallbacks(void)
{
SDL_LockMutex(callback_queue_mutex);
OPL_Queue_Clear(callback_queue);
SDL_UnlockMutex(callback_queue_mutex);
}
void OPL_Timer_AdjustCallbacks(float factor)
{
SDL_LockMutex(callback_queue_mutex);
OPL_Queue_AdjustCallbacks(callback_queue, current_time, factor);
SDL_UnlockMutex(callback_queue_mutex);
}
void OPL_Timer_Lock(void)
{
SDL_LockMutex(timer_mutex);
}
void OPL_Timer_Unlock(void)
{
SDL_UnlockMutex(timer_mutex);
}
void OPL_Timer_SetPaused(int paused)
{
SDL_LockMutex(callback_queue_mutex);
opl_timer_paused = paused;
SDL_UnlockMutex(callback_queue_mutex);
}
|