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