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