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#include "util/vfs.h"
11
12#include <signal.h>
13
14static const float _defaultFPSTarget = 60.f;
15
16#ifdef USE_PTHREADS
17static pthread_key_t _contextKey;
18static pthread_once_t _contextOnce = PTHREAD_ONCE_INIT;
19
20static void _createTLS(void) {
21 pthread_key_create(&_contextKey, 0);
22}
23#else
24static DWORD _contextKey;
25static INIT_ONCE _contextOnce = INIT_ONCE_STATIC_INIT;
26
27static BOOL CALLBACK _createTLS(PINIT_ONCE once, PVOID param, PVOID* context) {
28 UNUSED(once);
29 UNUSED(param);
30 UNUSED(context);
31 _contextKey = TlsAlloc();
32 return TRUE;
33}
34#endif
35
36static void _changeState(struct GBAThread* threadContext, enum ThreadState newState, bool broadcast) {
37 MutexLock(&threadContext->stateMutex);
38 threadContext->state = newState;
39 if (broadcast) {
40 ConditionWake(&threadContext->stateCond);
41 }
42 MutexUnlock(&threadContext->stateMutex);
43}
44
45static void _waitOnInterrupt(struct GBAThread* threadContext) {
46 while (threadContext->state == THREAD_INTERRUPTED) {
47 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
48 }
49}
50
51static void _waitUntilNotState(struct GBAThread* threadContext, enum ThreadState oldState) {
52 while (threadContext->state == oldState) {
53 MutexUnlock(&threadContext->stateMutex);
54
55 MutexLock(&threadContext->sync.videoFrameMutex);
56 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
57 MutexUnlock(&threadContext->sync.videoFrameMutex);
58
59 MutexLock(&threadContext->sync.audioBufferMutex);
60 ConditionWake(&threadContext->sync.audioRequiredCond);
61 MutexUnlock(&threadContext->sync.audioBufferMutex);
62
63 MutexLock(&threadContext->stateMutex);
64 ConditionWake(&threadContext->stateCond);
65 }
66}
67
68static void _pauseThread(struct GBAThread* threadContext, bool onThread) {
69 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
70 threadContext->debugger->state = DEBUGGER_EXITING;
71 }
72 threadContext->state = THREAD_PAUSING;
73 if (!onThread) {
74 _waitUntilNotState(threadContext, THREAD_PAUSING);
75 }
76}
77
78static void _changeVideoSync(struct GBAThread* threadContext, bool frameOn) {
79 // Make sure the video thread can process events while the GBA thread is paused
80 MutexLock(&threadContext->sync.videoFrameMutex);
81 if (frameOn != threadContext->sync.videoFrameOn) {
82 threadContext->sync.videoFrameOn = frameOn;
83 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
84 }
85 MutexUnlock(&threadContext->sync.videoFrameMutex);
86}
87
88static THREAD_ENTRY _GBAThreadRun(void* context) {
89#ifdef USE_PTHREADS
90 pthread_once(&_contextOnce, _createTLS);
91#else
92 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
93#endif
94
95 struct GBA gba;
96 struct ARMCore cpu;
97 struct Patch patch;
98 struct GBAThread* threadContext = context;
99 struct ARMComponent* components[1] = {};
100 int numComponents = 0;
101
102 if (threadContext->debugger) {
103 components[numComponents] = &threadContext->debugger->d;
104 ++numComponents;
105 }
106
107#if !defined(_WIN32) && defined(USE_PTHREADS)
108 sigset_t signals;
109 sigemptyset(&signals);
110 pthread_sigmask(SIG_SETMASK, &signals, 0);
111#endif
112
113 gba.logHandler = threadContext->logHandler;
114 GBACreate(&gba);
115 ARMSetComponents(&cpu, &gba.d, numComponents, components);
116 ARMInit(&cpu);
117 ARMReset(&cpu);
118 threadContext->gba = &gba;
119 gba.sync = &threadContext->sync;
120 gba.logLevel = threadContext->logLevel;
121#ifdef USE_PTHREADS
122 pthread_setspecific(_contextKey, threadContext);
123#else
124 TlsSetValue(_contextKey, threadContext);
125#endif
126
127 if (threadContext->audioBuffers) {
128 GBAAudioResizeBuffer(&gba.audio, threadContext->audioBuffers);
129 }
130
131 if (threadContext->renderer) {
132 GBAVideoAssociateRenderer(&gba.video, threadContext->renderer);
133 }
134
135 if (threadContext->rom) {
136 GBALoadROM(&gba, threadContext->rom, threadContext->save, threadContext->fname);
137 if (threadContext->bios) {
138 GBALoadBIOS(&gba, threadContext->bios);
139 }
140
141 if (threadContext->patch && loadPatch(threadContext->patch, &patch)) {
142 GBAApplyPatch(&gba, &patch);
143 }
144 }
145
146 if (threadContext->debugger) {
147 GBAAttachDebugger(&gba, threadContext->debugger);
148 ARMDebuggerEnter(threadContext->debugger, DEBUGGER_ENTER_ATTACHED);
149 }
150
151 GBASIOSetDriverSet(&gba.sio, &threadContext->sioDrivers);
152
153 gba.keySource = &threadContext->activeKeys;
154
155 if (threadContext->startCallback) {
156 threadContext->startCallback(threadContext);
157 }
158
159 _changeState(threadContext, THREAD_RUNNING, true);
160
161 while (threadContext->state < THREAD_EXITING) {
162 if (threadContext->debugger) {
163 struct ARMDebugger* debugger = threadContext->debugger;
164 ARMDebuggerRun(debugger);
165 if (debugger->state == DEBUGGER_SHUTDOWN) {
166 _changeState(threadContext, THREAD_EXITING, false);
167 }
168 } else {
169 while (threadContext->state == THREAD_RUNNING) {
170 ARMRun(&cpu);
171 }
172 }
173
174 int resetScheduled = 0;
175 MutexLock(&threadContext->stateMutex);
176 if (threadContext->state == THREAD_PAUSING) {
177 threadContext->state = THREAD_PAUSED;
178 ConditionWake(&threadContext->stateCond);
179 }
180 if (threadContext->state == THREAD_INTERRUPTING) {
181 threadContext->state = THREAD_INTERRUPTED;
182 ConditionWake(&threadContext->stateCond);
183 }
184 if (threadContext->state == THREAD_RESETING) {
185 threadContext->state = THREAD_RUNNING;
186 resetScheduled = 1;
187 }
188 while (threadContext->state == THREAD_PAUSED) {
189 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
190 }
191 MutexUnlock(&threadContext->stateMutex);
192 if (resetScheduled) {
193 ARMReset(&cpu);
194 }
195 }
196
197 while (threadContext->state != THREAD_SHUTDOWN) {
198 _changeState(threadContext, THREAD_SHUTDOWN, false);
199 }
200
201 if (threadContext->cleanCallback) {
202 threadContext->cleanCallback(threadContext);
203 }
204
205 threadContext->gba = 0;
206 ARMDeinit(&cpu);
207 GBADestroy(&gba);
208
209 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
210 ConditionWake(&threadContext->sync.audioRequiredCond);
211
212 return 0;
213}
214
215void GBAMapOptionsToContext(struct StartupOptions* opts, struct GBAThread* threadContext) {
216 if (opts->dirmode) {
217 threadContext->gameDir = VDirOpen(opts->fname);
218 threadContext->stateDir = threadContext->gameDir;
219 } else {
220 threadContext->rom = VFileOpen(opts->fname, O_RDONLY);
221#if ENABLE_LIBZIP
222 threadContext->gameDir = VDirOpenZip(opts->fname, 0);
223#endif
224 }
225 threadContext->fname = opts->fname;
226 threadContext->bios = VFileOpen(opts->bios, O_RDONLY);
227 threadContext->patch = VFileOpen(opts->patch, O_RDONLY);
228 threadContext->frameskip = opts->frameskip;
229 threadContext->logLevel = opts->logLevel;
230 threadContext->rewindBufferCapacity = opts->rewindBufferCapacity;
231 threadContext->rewindBufferInterval = opts->rewindBufferInterval;
232}
233
234bool GBAThreadStart(struct GBAThread* threadContext) {
235 // TODO: error check
236 threadContext->activeKeys = 0;
237 threadContext->state = THREAD_INITIALIZED;
238 threadContext->sync.videoFrameOn = true;
239 threadContext->sync.videoFrameSkip = 0;
240
241 threadContext->rewindBufferNext = threadContext->rewindBufferInterval;
242 threadContext->rewindBufferSize = 0;
243 if (threadContext->rewindBufferCapacity) {
244 threadContext->rewindBuffer = calloc(threadContext->rewindBufferCapacity, sizeof(void*));
245 } else {
246 threadContext->rewindBuffer = 0;
247 }
248
249 if (!threadContext->fpsTarget) {
250 threadContext->fpsTarget = _defaultFPSTarget;
251 }
252
253 if (threadContext->rom && !GBAIsROM(threadContext->rom)) {
254 threadContext->rom->close(threadContext->rom);
255 threadContext->rom = 0;
256 }
257
258 if (threadContext->gameDir) {
259 threadContext->gameDir->rewind(threadContext->gameDir);
260 struct VDirEntry* dirent = threadContext->gameDir->listNext(threadContext->gameDir);
261 while (dirent) {
262 struct Patch patchTemp;
263 struct VFile* vf = threadContext->gameDir->openFile(threadContext->gameDir, dirent->name(dirent), O_RDONLY);
264 if (!vf) {
265 continue;
266 }
267 if (!threadContext->rom && GBAIsROM(vf)) {
268 threadContext->rom = vf;
269 } else if (!threadContext->patch && loadPatch(vf, &patchTemp)) {
270 threadContext->patch = vf;
271 } else {
272 vf->close(vf);
273 }
274 dirent = threadContext->gameDir->listNext(threadContext->gameDir);
275 }
276
277 }
278 if (threadContext->stateDir) {
279 threadContext->save = threadContext->stateDir->openFile(threadContext->stateDir, "sram.sav", O_RDWR | O_CREAT);
280 }
281
282 if (!threadContext->rom) {
283 return false;
284 }
285
286 if (threadContext->fname && !threadContext->save) {
287 char* savedata = 0;
288 char* dotPoint = strrchr(threadContext->fname, '.');
289 if (dotPoint > strrchr(threadContext->fname, '/') && dotPoint[1] && dotPoint[2] && dotPoint[3]) {
290 savedata = strdup(threadContext->fname);
291 dotPoint = strrchr(savedata, '.');
292 dotPoint[1] = 's';
293 dotPoint[2] = 'a';
294 dotPoint[3] = 'v';
295 dotPoint[4] = '\0';
296 } else if (dotPoint) {
297 savedata = malloc((dotPoint - threadContext->fname + 5) * sizeof(char));
298 strncpy(savedata, threadContext->fname, dotPoint - threadContext->fname + 1);
299 strcat(savedata, "sav");
300 } else {
301 savedata = malloc(strlen(threadContext->fname + 5) * sizeof(char));
302 sprintf(savedata, "%s.sav", threadContext->fname);
303 }
304 threadContext->save = VFileOpen(savedata, O_RDWR | O_CREAT);
305 free(savedata);
306 }
307
308 MutexInit(&threadContext->stateMutex);
309 ConditionInit(&threadContext->stateCond);
310
311 MutexInit(&threadContext->sync.videoFrameMutex);
312 ConditionInit(&threadContext->sync.videoFrameAvailableCond);
313 ConditionInit(&threadContext->sync.videoFrameRequiredCond);
314 MutexInit(&threadContext->sync.audioBufferMutex);
315 ConditionInit(&threadContext->sync.audioRequiredCond);
316
317#ifndef _WIN32
318 sigset_t signals;
319 sigemptyset(&signals);
320 sigaddset(&signals, SIGINT);
321 sigaddset(&signals, SIGTRAP);
322 pthread_sigmask(SIG_BLOCK, &signals, 0);
323#endif
324
325 MutexLock(&threadContext->stateMutex);
326 ThreadCreate(&threadContext->thread, _GBAThreadRun, threadContext);
327 while (threadContext->state < THREAD_RUNNING) {
328 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
329 }
330 MutexUnlock(&threadContext->stateMutex);
331
332 return true;
333}
334
335bool GBAThreadHasStarted(struct GBAThread* threadContext) {
336 bool hasStarted;
337 MutexLock(&threadContext->stateMutex);
338 hasStarted = threadContext->state > THREAD_INITIALIZED;
339 MutexUnlock(&threadContext->stateMutex);
340 return hasStarted;
341}
342
343void GBAThreadEnd(struct GBAThread* threadContext) {
344 MutexLock(&threadContext->stateMutex);
345 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
346 threadContext->debugger->state = DEBUGGER_EXITING;
347 }
348 threadContext->state = THREAD_EXITING;
349 ConditionWake(&threadContext->stateCond);
350 MutexUnlock(&threadContext->stateMutex);
351 MutexLock(&threadContext->sync.audioBufferMutex);
352 threadContext->sync.audioWait = 0;
353 ConditionWake(&threadContext->sync.audioRequiredCond);
354 MutexUnlock(&threadContext->sync.audioBufferMutex);
355}
356
357void GBAThreadReset(struct GBAThread* threadContext) {
358 MutexLock(&threadContext->stateMutex);
359 _waitOnInterrupt(threadContext);
360 threadContext->state = THREAD_RESETING;
361 ConditionWake(&threadContext->stateCond);
362 MutexUnlock(&threadContext->stateMutex);
363}
364
365void GBAThreadJoin(struct GBAThread* threadContext) {
366 MutexLock(&threadContext->sync.videoFrameMutex);
367 threadContext->sync.videoFrameWait = 0;
368 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
369 MutexUnlock(&threadContext->sync.videoFrameMutex);
370
371 ThreadJoin(threadContext->thread);
372
373 MutexDeinit(&threadContext->stateMutex);
374 ConditionDeinit(&threadContext->stateCond);
375
376 MutexDeinit(&threadContext->sync.videoFrameMutex);
377 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
378 ConditionDeinit(&threadContext->sync.videoFrameAvailableCond);
379 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
380 ConditionDeinit(&threadContext->sync.videoFrameRequiredCond);
381
382 ConditionWake(&threadContext->sync.audioRequiredCond);
383 ConditionDeinit(&threadContext->sync.audioRequiredCond);
384 MutexDeinit(&threadContext->sync.audioBufferMutex);
385
386 int i;
387 for (i = 0; i < threadContext->rewindBufferCapacity; ++i) {
388 if (threadContext->rewindBuffer[i]) {
389 GBADeallocateState(threadContext->rewindBuffer[i]);
390 }
391 }
392 free(threadContext->rewindBuffer);
393
394 if (threadContext->rom) {
395 threadContext->rom->close(threadContext->rom);
396 threadContext->rom = 0;
397 }
398
399 if (threadContext->save) {
400 threadContext->save->close(threadContext->save);
401 threadContext->save = 0;
402 }
403
404 if (threadContext->bios) {
405 threadContext->bios->close(threadContext->bios);
406 threadContext->bios = 0;
407 }
408
409 if (threadContext->patch) {
410 threadContext->patch->close(threadContext->patch);
411 threadContext->patch = 0;
412 }
413
414 if (threadContext->gameDir) {
415 if (threadContext->stateDir == threadContext->gameDir) {
416 threadContext->stateDir = 0;
417 }
418 threadContext->gameDir->close(threadContext->gameDir);
419 threadContext->gameDir = 0;
420 }
421
422 if (threadContext->stateDir) {
423 threadContext->stateDir->close(threadContext->stateDir);
424 threadContext->stateDir = 0;
425 }
426}
427
428void GBAThreadInterrupt(struct GBAThread* threadContext) {
429 MutexLock(&threadContext->stateMutex);
430 threadContext->savedState = threadContext->state;
431 _waitOnInterrupt(threadContext);
432 threadContext->state = THREAD_INTERRUPTING;
433 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
434 threadContext->debugger->state = DEBUGGER_EXITING;
435 }
436 ConditionWake(&threadContext->stateCond);
437 _waitUntilNotState(threadContext, THREAD_INTERRUPTING);
438 MutexUnlock(&threadContext->stateMutex);
439}
440
441void GBAThreadContinue(struct GBAThread* threadContext) {
442 _changeState(threadContext, threadContext->savedState, 1);
443}
444
445void GBAThreadPause(struct GBAThread* threadContext) {
446 bool frameOn = true;
447 MutexLock(&threadContext->stateMutex);
448 _waitOnInterrupt(threadContext);
449 if (threadContext->state == THREAD_RUNNING) {
450 _pauseThread(threadContext, false);
451 frameOn = false;
452 }
453 MutexUnlock(&threadContext->stateMutex);
454
455 _changeVideoSync(threadContext, frameOn);
456}
457
458void GBAThreadUnpause(struct GBAThread* threadContext) {
459 MutexLock(&threadContext->stateMutex);
460 _waitOnInterrupt(threadContext);
461 if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
462 threadContext->state = THREAD_RUNNING;
463 ConditionWake(&threadContext->stateCond);
464 }
465 MutexUnlock(&threadContext->stateMutex);
466
467 _changeVideoSync(threadContext, true);
468}
469
470bool GBAThreadIsPaused(struct GBAThread* threadContext) {
471 bool isPaused;
472 MutexLock(&threadContext->stateMutex);
473 _waitOnInterrupt(threadContext);
474 isPaused = threadContext->state == THREAD_PAUSED;
475 MutexUnlock(&threadContext->stateMutex);
476 return isPaused;
477}
478
479void GBAThreadTogglePause(struct GBAThread* threadContext) {
480 bool frameOn = true;
481 MutexLock(&threadContext->stateMutex);
482 _waitOnInterrupt(threadContext);
483 if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
484 threadContext->state = THREAD_RUNNING;
485 ConditionWake(&threadContext->stateCond);
486 } else if (threadContext->state == THREAD_RUNNING) {
487 _pauseThread(threadContext, false);
488 frameOn = false;
489 }
490 MutexUnlock(&threadContext->stateMutex);
491
492 _changeVideoSync(threadContext, frameOn);
493}
494
495void GBAThreadPauseFromThread(struct GBAThread* threadContext) {
496 bool frameOn = true;
497 MutexLock(&threadContext->stateMutex);
498 _waitOnInterrupt(threadContext);
499 if (threadContext->state == THREAD_RUNNING) {
500 _pauseThread(threadContext, true);
501 frameOn = false;
502 }
503 MutexUnlock(&threadContext->stateMutex);
504
505 _changeVideoSync(threadContext, frameOn);
506}
507
508#ifdef USE_PTHREADS
509struct GBAThread* GBAThreadGetContext(void) {
510 pthread_once(&_contextOnce, _createTLS);
511 return pthread_getspecific(_contextKey);
512}
513#else
514struct GBAThread* GBAThreadGetContext(void) {
515 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
516 return TlsGetValue(_contextKey);
517}
518#endif
519
520void GBASyncPostFrame(struct GBASync* sync) {
521 if (!sync) {
522 return;
523 }
524
525 MutexLock(&sync->videoFrameMutex);
526 ++sync->videoFramePending;
527 --sync->videoFrameSkip;
528 if (sync->videoFrameSkip < 0) {
529 ConditionWake(&sync->videoFrameAvailableCond);
530 while (sync->videoFrameWait && sync->videoFramePending) {
531 ConditionWait(&sync->videoFrameRequiredCond, &sync->videoFrameMutex);
532 }
533 }
534 MutexUnlock(&sync->videoFrameMutex);
535
536 struct GBAThread* thread = GBAThreadGetContext();
537 if (thread->rewindBuffer) {
538 --thread->rewindBufferNext;
539 if (thread->rewindBufferNext <= 0) {
540 thread->rewindBufferNext = thread->rewindBufferInterval;
541 GBARecordFrame(thread);
542 }
543 }
544 if (thread->frameCallback) {
545 thread->frameCallback(thread);
546 }
547}
548
549bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) {
550 if (!sync) {
551 return true;
552 }
553
554 MutexLock(&sync->videoFrameMutex);
555 ConditionWake(&sync->videoFrameRequiredCond);
556 if (!sync->videoFrameOn) {
557 return false;
558 }
559 ConditionWait(&sync->videoFrameAvailableCond, &sync->videoFrameMutex);
560 sync->videoFramePending = 0;
561 sync->videoFrameSkip = frameskip;
562 return true;
563}
564
565void GBASyncWaitFrameEnd(struct GBASync* sync) {
566 if (!sync) {
567 return;
568 }
569
570 MutexUnlock(&sync->videoFrameMutex);
571}
572
573bool GBASyncDrawingFrame(struct GBASync* sync) {
574 return sync->videoFrameSkip <= 0;
575}
576
577void GBASyncProduceAudio(struct GBASync* sync, int wait) {
578 if (sync->audioWait && wait) {
579 // TODO loop properly in event of spurious wakeups
580 ConditionWait(&sync->audioRequiredCond, &sync->audioBufferMutex);
581 }
582 MutexUnlock(&sync->audioBufferMutex);
583}
584
585void GBASyncLockAudio(struct GBASync* sync) {
586 MutexLock(&sync->audioBufferMutex);
587}
588
589void GBASyncUnlockAudio(struct GBASync* sync) {
590 MutexUnlock(&sync->audioBufferMutex);
591}
592
593void GBASyncConsumeAudio(struct GBASync* sync) {
594 ConditionWake(&sync->audioRequiredCond);
595 MutexUnlock(&sync->audioBufferMutex);
596}