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