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