src/gba/gba-thread.c (view raw)
1#include "gba-thread.h"
2
3#include "arm.h"
4#include "debugger.h"
5#include "gba.h"
6#include "gba-serialize.h"
7
8#include <stdlib.h>
9#include <signal.h>
10
11#ifdef USE_PTHREADS
12static pthread_key_t _contextKey;
13static pthread_once_t _contextOnce = PTHREAD_ONCE_INIT;
14
15static void _createTLS(void) {
16 pthread_key_create(&_contextKey, 0);
17}
18#else
19static DWORD _contextKey;
20static INIT_ONCE _contextOnce = INIT_ONCE_STATIC_INIT;
21
22static BOOL CALLBACK _createTLS(PINIT_ONCE once, PVOID param, PVOID* context) {
23 (void) (once);
24 (void) (param);
25 (void) (context);
26 _contextKey = TlsAlloc();
27 return TRUE;
28}
29#endif
30
31static void _changeState(struct GBAThread* threadContext, enum ThreadState newState, int broadcast) {
32 MutexLock(&threadContext->stateMutex);
33 threadContext->state = newState;
34 if (broadcast) {
35 ConditionWake(&threadContext->stateCond);
36 }
37 MutexUnlock(&threadContext->stateMutex);
38}
39
40static THREAD_ENTRY _GBAThreadRun(void* context) {
41#ifdef USE_PTHREADS
42 pthread_once(&_contextOnce, _createTLS);
43#else
44 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
45#endif
46
47#ifdef USE_DEBUGGER
48 struct ARMDebugger debugger;
49#endif
50 struct GBA gba;
51 struct GBAThread* threadContext = context;
52 char* savedata = 0;
53
54#if !defined(_WIN32) && defined(USE_PTHREADS)
55 sigset_t signals;
56 sigemptyset(&signals);
57 pthread_sigmask(SIG_SETMASK, &signals, 0);
58#endif
59
60 GBAInit(&gba);
61 threadContext->gba = &gba;
62 gba.sync = &threadContext->sync;
63#ifdef USE_PTHREADS
64 pthread_setspecific(_contextKey, threadContext);
65#else
66 TlsSetValue(_contextKey, threadContext);
67#endif
68 if (threadContext->renderer) {
69 GBAVideoAssociateRenderer(&gba.video, threadContext->renderer);
70 }
71
72 if (threadContext->fd >= 0) {
73 if (threadContext->fname) {
74 char* dotPoint = strrchr(threadContext->fname, '.');
75 if (dotPoint > strrchr(threadContext->fname, '/') && dotPoint[1] && dotPoint[2] && dotPoint[3]) {
76 savedata = strdup(threadContext->fname);
77 dotPoint = strrchr(savedata, '.');
78 dotPoint[1] = 's';
79 dotPoint[2] = 'a';
80 dotPoint[3] = 'v';
81 dotPoint[4] = '\0';
82 } else if (dotPoint) {
83 savedata = malloc((dotPoint - threadContext->fname + 5) * sizeof(char));
84 strncpy(savedata, threadContext->fname, dotPoint - threadContext->fname + 1);
85 strcat(savedata, "sav");
86 } else {
87 savedata = malloc(strlen(threadContext->fname + 5));
88 strcpy(savedata, threadContext->fname);
89 strcat(savedata, "sav");
90 }
91 }
92 gba.savefile = savedata;
93 GBALoadROM(&gba, threadContext->fd, threadContext->fname);
94 }
95
96#ifdef USE_DEBUGGER
97 if (threadContext->useDebugger) {
98 threadContext->debugger = &debugger;
99 GBAAttachDebugger(&gba, &debugger);
100 } else {
101 threadContext->debugger = 0;
102 }
103#else
104 threadContext->debugger = 0;
105#endif
106
107 gba.keySource = &threadContext->activeKeys;
108
109 if (threadContext->startCallback) {
110 threadContext->startCallback(threadContext);
111 }
112
113 _changeState(threadContext, THREAD_RUNNING, 1);
114
115 while (threadContext->state < THREAD_EXITING) {
116#ifdef USE_DEBUGGER
117 if (threadContext->useDebugger) {
118 ARMDebuggerRun(&debugger);
119 if (debugger.state == DEBUGGER_SHUTDOWN) {
120 _changeState(threadContext, THREAD_EXITING, 0);
121 }
122 } else {
123#endif
124 while (threadContext->state == THREAD_RUNNING) {
125 ARMRun(&gba.cpu);
126 }
127#ifdef USE_DEBUGGER
128 }
129#endif
130 MutexLock(&threadContext->stateMutex);
131 while (threadContext->state == THREAD_PAUSED) {
132 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
133 }
134 MutexUnlock(&threadContext->stateMutex);
135 }
136
137 while (threadContext->state != THREAD_SHUTDOWN) {
138 _changeState(threadContext, THREAD_SHUTDOWN, 0);
139 }
140
141 if (threadContext->cleanCallback) {
142 threadContext->cleanCallback(threadContext);
143 }
144
145 GBADeinit(&gba);
146
147 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
148 ConditionWake(&threadContext->sync.audioRequiredCond);
149 free(savedata);
150
151 return 0;
152}
153
154int GBAThreadStart(struct GBAThread* threadContext) {
155 // TODO: error check
156 threadContext->activeKeys = 0;
157 threadContext->state = THREAD_INITIALIZED;
158 threadContext->sync.videoFrameOn = 1;
159 threadContext->sync.videoFrameSkip = 0;
160
161 threadContext->rewindBufferNext = threadContext->rewindBufferInterval;
162 threadContext->rewindBufferSize = 0;
163 if (threadContext->rewindBufferCapacity) {
164 threadContext->rewindBuffer = calloc(threadContext->rewindBufferCapacity, sizeof(void*));
165 } else {
166 threadContext->rewindBuffer = 0;
167 }
168
169 MutexInit(&threadContext->stateMutex);
170 ConditionInit(&threadContext->stateCond);
171
172 MutexInit(&threadContext->sync.videoFrameMutex);
173 ConditionInit(&threadContext->sync.videoFrameAvailableCond);
174 ConditionInit(&threadContext->sync.videoFrameRequiredCond);
175 MutexInit(&threadContext->sync.audioBufferMutex);
176 ConditionInit(&threadContext->sync.audioRequiredCond);
177
178#ifndef _WIN32
179 sigset_t signals;
180 sigemptyset(&signals);
181 sigaddset(&signals, SIGINT);
182 sigaddset(&signals, SIGTRAP);
183 pthread_sigmask(SIG_BLOCK, &signals, 0);
184#endif
185
186 MutexLock(&threadContext->stateMutex);
187 ThreadCreate(&threadContext->thread, _GBAThreadRun, threadContext);
188 while (threadContext->state < THREAD_RUNNING) {
189 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
190 }
191 MutexUnlock(&threadContext->stateMutex);
192
193 return 0;
194}
195
196void GBAThreadEnd(struct GBAThread* threadContext) {
197 MutexLock(&threadContext->stateMutex);
198 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
199 threadContext->debugger->state = DEBUGGER_EXITING;
200 }
201 threadContext->state = THREAD_EXITING;
202 MutexUnlock(&threadContext->stateMutex);
203}
204
205void GBAThreadJoin(struct GBAThread* threadContext) {
206 MutexLock(&threadContext->sync.videoFrameMutex);
207 threadContext->sync.videoFrameWait = 0;
208 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
209 MutexUnlock(&threadContext->sync.videoFrameMutex);
210
211 ThreadJoin(threadContext->thread);
212
213 MutexDeinit(&threadContext->stateMutex);
214 ConditionDeinit(&threadContext->stateCond);
215
216 MutexDeinit(&threadContext->sync.videoFrameMutex);
217 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
218 ConditionDeinit(&threadContext->sync.videoFrameAvailableCond);
219 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
220 ConditionDeinit(&threadContext->sync.videoFrameRequiredCond);
221
222 ConditionWake(&threadContext->sync.audioRequiredCond);
223 ConditionDeinit(&threadContext->sync.audioRequiredCond);
224 MutexDeinit(&threadContext->sync.audioBufferMutex);
225
226 int i;
227 for (i = 0; i < threadContext->rewindBufferCapacity; ++i) {
228 if (threadContext->rewindBuffer[i]) {
229 GBADeallocateState(threadContext->rewindBuffer[i]);
230 }
231 }
232 free(threadContext->rewindBuffer);
233}
234
235void GBAThreadPause(struct GBAThread* threadContext) {
236 int frameOn = 1;
237 MutexLock(&threadContext->stateMutex);
238 if (threadContext->state == THREAD_RUNNING) {
239 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
240 threadContext->debugger->state = DEBUGGER_EXITING;
241 }
242 threadContext->state = THREAD_PAUSED;
243 frameOn = 0;
244 }
245 MutexUnlock(&threadContext->stateMutex);
246 MutexLock(&threadContext->sync.videoFrameMutex);
247 if (frameOn != threadContext->sync.videoFrameOn) {
248 threadContext->sync.videoFrameOn = frameOn;
249 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
250 }
251 MutexUnlock(&threadContext->sync.videoFrameMutex);
252}
253
254void GBAThreadUnpause(struct GBAThread* threadContext) {
255 int frameOn = 1;
256 MutexLock(&threadContext->stateMutex);
257 if (threadContext->state == THREAD_PAUSED) {
258 threadContext->state = THREAD_RUNNING;
259 ConditionWake(&threadContext->stateCond);
260 }
261 MutexUnlock(&threadContext->stateMutex);
262 MutexLock(&threadContext->sync.videoFrameMutex);
263 if (frameOn != threadContext->sync.videoFrameOn) {
264 threadContext->sync.videoFrameOn = frameOn;
265 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
266 }
267 MutexUnlock(&threadContext->sync.videoFrameMutex);
268}
269
270int GBAThreadIsPaused(struct GBAThread* threadContext) {
271 int isPaused;
272 MutexLock(&threadContext->stateMutex);
273 isPaused = threadContext->state == THREAD_PAUSED;
274 MutexUnlock(&threadContext->stateMutex);
275 return isPaused;
276}
277
278void GBAThreadTogglePause(struct GBAThread* threadContext) {
279 int frameOn = 1;
280 MutexLock(&threadContext->stateMutex);
281 if (threadContext->state == THREAD_PAUSED) {
282 threadContext->state = THREAD_RUNNING;
283 ConditionWake(&threadContext->stateCond);
284 } else if (threadContext->state == THREAD_RUNNING) {
285 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
286 threadContext->debugger->state = DEBUGGER_EXITING;
287 }
288 threadContext->state = THREAD_PAUSED;
289 frameOn = 0;
290 }
291 MutexUnlock(&threadContext->stateMutex);
292 MutexLock(&threadContext->sync.videoFrameMutex);
293 if (frameOn != threadContext->sync.videoFrameOn) {
294 threadContext->sync.videoFrameOn = frameOn;
295 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
296 }
297 MutexUnlock(&threadContext->sync.videoFrameMutex);
298}
299
300#ifdef USE_PTHREADS
301struct GBAThread* GBAThreadGetContext(void) {
302 pthread_once(&_contextOnce, _createTLS);
303 return pthread_getspecific(_contextKey);
304}
305#else
306struct GBAThread* GBAThreadGetContext(void) {
307 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
308 return TlsGetValue(_contextKey);
309}
310#endif
311
312void GBASyncPostFrame(struct GBASync* sync) {
313 if (!sync) {
314 return;
315 }
316
317 MutexLock(&sync->videoFrameMutex);
318 ++sync->videoFramePending;
319 --sync->videoFrameSkip;
320 if (sync->videoFrameSkip < 0) {
321 ConditionWake(&sync->videoFrameAvailableCond);
322 while (sync->videoFrameWait && sync->videoFramePending) {
323 ConditionWait(&sync->videoFrameRequiredCond, &sync->videoFrameMutex);
324 }
325 }
326 MutexUnlock(&sync->videoFrameMutex);
327
328 struct GBAThread* thread = GBAThreadGetContext();
329 if (thread->rewindBuffer) {
330 --thread->rewindBufferNext;
331 if (thread->rewindBufferNext <= 0) {
332 thread->rewindBufferNext = thread->rewindBufferInterval;
333 GBARecordFrame(thread);
334 }
335 }
336 if (thread->frameCallback) {
337 thread->frameCallback(thread);
338 }
339}
340
341int GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) {
342 if (!sync) {
343 return 1;
344 }
345
346 MutexLock(&sync->videoFrameMutex);
347 ConditionWake(&sync->videoFrameRequiredCond);
348 if (!sync->videoFrameOn) {
349 return 0;
350 }
351 ConditionWait(&sync->videoFrameAvailableCond, &sync->videoFrameMutex);
352 sync->videoFramePending = 0;
353 sync->videoFrameSkip = frameskip;
354 return 1;
355}
356
357void GBASyncWaitFrameEnd(struct GBASync* sync) {
358 if (!sync) {
359 return;
360 }
361
362 MutexUnlock(&sync->videoFrameMutex);
363}
364
365int GBASyncDrawingFrame(struct GBASync* sync) {
366 return sync->videoFrameSkip <= 0;
367}
368
369void GBASyncProduceAudio(struct GBASync* sync, int wait) {
370 if (sync->audioWait && wait) {
371 // TODO loop properly in event of spurious wakeups
372 ConditionWait(&sync->audioRequiredCond, &sync->audioBufferMutex);
373 }
374 MutexUnlock(&sync->audioBufferMutex);
375}
376
377void GBASyncLockAudio(struct GBASync* sync) {
378 MutexLock(&sync->audioBufferMutex);
379}
380
381void GBASyncConsumeAudio(struct GBASync* sync) {
382 ConditionWake(&sync->audioRequiredCond);
383 MutexUnlock(&sync->audioBufferMutex);
384}