all repos — mgba @ 0b241710f443040201b0f0464f069b43adb4bff6

mGBA Game Boy Advance Emulator

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