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