all repos — mgba @ 4748ef7d5d24c2396212be649f9cb67f3fe2e106

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