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