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