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