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