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-overrides.h"
12#include "gba-serialize.h"
13
14#include "debugger/debugger.h"
15
16#include "util/patch.h"
17#include "util/png-io.h"
18#include "util/vfs.h"
19
20#include "platform/commandline.h"
21
22#include <signal.h>
23
24static const float _defaultFPSTarget = 60.f;
25
26#ifdef USE_PTHREADS
27static pthread_key_t _contextKey;
28static pthread_once_t _contextOnce = PTHREAD_ONCE_INIT;
29
30static void _createTLS(void) {
31 pthread_key_create(&_contextKey, 0);
32}
33#elif _WIN32
34static DWORD _contextKey;
35static INIT_ONCE _contextOnce = INIT_ONCE_STATIC_INIT;
36
37static BOOL CALLBACK _createTLS(PINIT_ONCE once, PVOID param, PVOID* context) {
38 UNUSED(once);
39 UNUSED(param);
40 UNUSED(context);
41 _contextKey = TlsAlloc();
42 return TRUE;
43}
44#endif
45
46#ifndef DISABLE_THREADING
47static void _changeState(struct GBAThread* threadContext, enum ThreadState newState, bool broadcast) {
48 MutexLock(&threadContext->stateMutex);
49 threadContext->state = newState;
50 if (broadcast) {
51 ConditionWake(&threadContext->stateCond);
52 }
53 MutexUnlock(&threadContext->stateMutex);
54}
55
56static void _waitOnInterrupt(struct GBAThread* threadContext) {
57 while (threadContext->state == THREAD_INTERRUPTED) {
58 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
59 }
60}
61
62static void _waitUntilNotState(struct GBAThread* threadContext, enum ThreadState oldState) {
63 while (threadContext->state == oldState) {
64 MutexUnlock(&threadContext->stateMutex);
65
66 MutexLock(&threadContext->sync.videoFrameMutex);
67 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
68 MutexUnlock(&threadContext->sync.videoFrameMutex);
69
70 MutexLock(&threadContext->sync.audioBufferMutex);
71 ConditionWake(&threadContext->sync.audioRequiredCond);
72 MutexUnlock(&threadContext->sync.audioBufferMutex);
73
74 MutexLock(&threadContext->stateMutex);
75 ConditionWake(&threadContext->stateCond);
76 }
77}
78
79static void _pauseThread(struct GBAThread* threadContext, bool onThread) {
80 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
81 threadContext->debugger->state = DEBUGGER_EXITING;
82 }
83 threadContext->state = THREAD_PAUSING;
84 if (!onThread) {
85 _waitUntilNotState(threadContext, THREAD_PAUSING);
86 }
87}
88#endif
89
90static void _changeVideoSync(struct GBASync* sync, bool frameOn) {
91 // Make sure the video thread can process events while the GBA thread is paused
92 MutexLock(&sync->videoFrameMutex);
93 if (frameOn != sync->videoFrameOn) {
94 sync->videoFrameOn = frameOn;
95 ConditionWake(&sync->videoFrameAvailableCond);
96 }
97 MutexUnlock(&sync->videoFrameMutex);
98}
99
100#ifndef DISABLE_THREADING
101static THREAD_ENTRY _GBAThreadRun(void* context) {
102#ifdef USE_PTHREADS
103 pthread_once(&_contextOnce, _createTLS);
104#else
105 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
106#endif
107
108 struct GBA gba;
109 struct ARMCore cpu;
110 struct Patch patch;
111 struct GBAThread* threadContext = context;
112 struct ARMComponent* components[GBA_COMPONENT_MAX] = {};
113 int numComponents = GBA_COMPONENT_MAX;
114
115#if !defined(_WIN32) && defined(USE_PTHREADS)
116 sigset_t signals;
117 sigemptyset(&signals);
118 pthread_sigmask(SIG_SETMASK, &signals, 0);
119#endif
120
121 GBACreate(&gba);
122 ARMSetComponents(&cpu, &gba.d, numComponents, components);
123 ARMInit(&cpu);
124 gba.sync = &threadContext->sync;
125 threadContext->gba = &gba;
126 gba.logLevel = threadContext->logLevel;
127#ifdef USE_PTHREADS
128 pthread_setspecific(_contextKey, threadContext);
129#else
130 TlsSetValue(_contextKey, threadContext);
131#endif
132
133 if (threadContext->audioBuffers) {
134 GBAAudioResizeBuffer(&gba.audio, threadContext->audioBuffers);
135 } else {
136 threadContext->audioBuffers = GBA_AUDIO_SAMPLES;
137 }
138
139 if (threadContext->renderer) {
140 GBAVideoAssociateRenderer(&gba.video, threadContext->renderer);
141 }
142
143 if (threadContext->rom) {
144 GBALoadROM(&gba, threadContext->rom, threadContext->save, threadContext->fname);
145
146 struct GBACartridgeOverride override;
147 const struct GBACartridge* cart = (const struct GBACartridge*) gba.memory.rom;
148 memcpy(override.id, &cart->id, sizeof(override.id));
149 if (GBAOverrideFind(threadContext->overrides, &override)) {
150 GBAOverrideApply(&gba, &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);
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
266void GBAMapArgumentsToContext(const struct GBAArguments* args, struct GBAThread* threadContext) {
267 if (args->dirmode) {
268 threadContext->gameDir = VDirOpen(args->fname);
269 threadContext->stateDir = threadContext->gameDir;
270 } else {
271 threadContext->rom = VFileOpen(args->fname, O_RDONLY);
272#if ENABLE_LIBZIP
273 threadContext->gameDir = VDirOpenZip(args->fname, 0);
274#endif
275 }
276 threadContext->fname = args->fname;
277 threadContext->patch = VFileOpen(args->patch, O_RDONLY);
278}
279
280bool GBAThreadStart(struct GBAThread* threadContext) {
281 // TODO: error check
282 threadContext->activeKeys = 0;
283 threadContext->state = THREAD_INITIALIZED;
284 threadContext->sync.videoFrameOn = true;
285 threadContext->sync.videoFrameSkip = 0;
286
287 threadContext->rewindBuffer = 0;
288 int newCapacity = threadContext->rewindBufferCapacity;
289 int newInterval = threadContext->rewindBufferInterval;
290 threadContext->rewindBufferCapacity = 0;
291 threadContext->rewindBufferInterval = 0;
292 GBARewindSettingsChanged(threadContext, newCapacity, newInterval);
293
294 if (!threadContext->fpsTarget) {
295 threadContext->fpsTarget = _defaultFPSTarget;
296 }
297
298 if (threadContext->rom && !GBAIsROM(threadContext->rom)) {
299 threadContext->rom->close(threadContext->rom);
300 threadContext->rom = 0;
301 }
302
303 if (threadContext->gameDir) {
304 threadContext->gameDir->rewind(threadContext->gameDir);
305 struct VDirEntry* dirent = threadContext->gameDir->listNext(threadContext->gameDir);
306 while (dirent) {
307 struct Patch patchTemp;
308 struct VFile* vf = threadContext->gameDir->openFile(threadContext->gameDir, dirent->name(dirent), O_RDONLY);
309 if (!vf) {
310 continue;
311 }
312 if (!threadContext->rom && GBAIsROM(vf)) {
313 threadContext->rom = vf;
314 } else if (!threadContext->patch && loadPatch(vf, &patchTemp)) {
315 threadContext->patch = vf;
316 } else {
317 vf->close(vf);
318 }
319 dirent = threadContext->gameDir->listNext(threadContext->gameDir);
320 }
321
322 }
323
324 if (!threadContext->rom) {
325 threadContext->state = THREAD_SHUTDOWN;
326 return false;
327 }
328
329 threadContext->save = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "sram", ".sav", O_CREAT | O_RDWR);
330
331 MutexInit(&threadContext->stateMutex);
332 ConditionInit(&threadContext->stateCond);
333
334 MutexInit(&threadContext->sync.videoFrameMutex);
335 ConditionInit(&threadContext->sync.videoFrameAvailableCond);
336 ConditionInit(&threadContext->sync.videoFrameRequiredCond);
337 MutexInit(&threadContext->sync.audioBufferMutex);
338 ConditionInit(&threadContext->sync.audioRequiredCond);
339
340 threadContext->interruptDepth = 0;
341
342#ifndef _WIN32
343 sigset_t signals;
344 sigemptyset(&signals);
345 sigaddset(&signals, SIGINT);
346 sigaddset(&signals, SIGTRAP);
347 pthread_sigmask(SIG_BLOCK, &signals, 0);
348#endif
349
350 MutexLock(&threadContext->stateMutex);
351 ThreadCreate(&threadContext->thread, _GBAThreadRun, threadContext);
352 while (threadContext->state < THREAD_RUNNING) {
353 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
354 }
355 MutexUnlock(&threadContext->stateMutex);
356
357 return true;
358}
359
360bool GBAThreadHasStarted(struct GBAThread* threadContext) {
361 bool hasStarted;
362 MutexLock(&threadContext->stateMutex);
363 hasStarted = threadContext->state > THREAD_INITIALIZED;
364 MutexUnlock(&threadContext->stateMutex);
365 return hasStarted;
366}
367
368bool GBAThreadHasExited(struct GBAThread* threadContext) {
369 bool hasExited;
370 MutexLock(&threadContext->stateMutex);
371 hasExited = threadContext->state > THREAD_EXITING;
372 MutexUnlock(&threadContext->stateMutex);
373 return hasExited;
374}
375
376bool GBAThreadHasCrashed(struct GBAThread* threadContext) {
377 bool hasExited;
378 MutexLock(&threadContext->stateMutex);
379 hasExited = threadContext->state == THREAD_CRASHED;
380 MutexUnlock(&threadContext->stateMutex);
381 return hasExited;
382}
383
384void GBAThreadEnd(struct GBAThread* threadContext) {
385 MutexLock(&threadContext->stateMutex);
386 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
387 threadContext->debugger->state = DEBUGGER_EXITING;
388 }
389 threadContext->state = THREAD_EXITING;
390 if (threadContext->gba) {
391 threadContext->gba->cpu->halted = false;
392 }
393 ConditionWake(&threadContext->stateCond);
394 MutexUnlock(&threadContext->stateMutex);
395 MutexLock(&threadContext->sync.audioBufferMutex);
396 threadContext->sync.audioWait = 0;
397 ConditionWake(&threadContext->sync.audioRequiredCond);
398 MutexUnlock(&threadContext->sync.audioBufferMutex);
399
400 MutexLock(&threadContext->sync.videoFrameMutex);
401 threadContext->sync.videoFrameWait = false;
402 threadContext->sync.videoFrameOn = false;
403 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
404 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
405 MutexUnlock(&threadContext->sync.videoFrameMutex);
406}
407
408void GBAThreadReset(struct GBAThread* threadContext) {
409 MutexLock(&threadContext->stateMutex);
410 _waitOnInterrupt(threadContext);
411 threadContext->state = THREAD_RESETING;
412 ConditionWake(&threadContext->stateCond);
413 MutexUnlock(&threadContext->stateMutex);
414}
415
416void GBAThreadJoin(struct GBAThread* threadContext) {
417 ThreadJoin(threadContext->thread);
418
419 MutexDeinit(&threadContext->stateMutex);
420 ConditionDeinit(&threadContext->stateCond);
421
422 MutexDeinit(&threadContext->sync.videoFrameMutex);
423 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
424 ConditionDeinit(&threadContext->sync.videoFrameAvailableCond);
425 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
426 ConditionDeinit(&threadContext->sync.videoFrameRequiredCond);
427
428 ConditionWake(&threadContext->sync.audioRequiredCond);
429 ConditionDeinit(&threadContext->sync.audioRequiredCond);
430 MutexDeinit(&threadContext->sync.audioBufferMutex);
431
432 int i;
433 for (i = 0; i < threadContext->rewindBufferCapacity; ++i) {
434 if (threadContext->rewindBuffer[i]) {
435 GBADeallocateState(threadContext->rewindBuffer[i]);
436 }
437 }
438 free(threadContext->rewindBuffer);
439
440 if (threadContext->rom) {
441 threadContext->rom->close(threadContext->rom);
442 threadContext->rom = 0;
443 }
444
445 if (threadContext->save) {
446 threadContext->save->close(threadContext->save);
447 threadContext->save = 0;
448 }
449
450 if (threadContext->bios) {
451 threadContext->bios->close(threadContext->bios);
452 threadContext->bios = 0;
453 }
454
455 if (threadContext->patch) {
456 threadContext->patch->close(threadContext->patch);
457 threadContext->patch = 0;
458 }
459
460 if (threadContext->gameDir) {
461 if (threadContext->stateDir == threadContext->gameDir) {
462 threadContext->stateDir = 0;
463 }
464 threadContext->gameDir->close(threadContext->gameDir);
465 threadContext->gameDir = 0;
466 }
467
468 if (threadContext->stateDir) {
469 threadContext->stateDir->close(threadContext->stateDir);
470 threadContext->stateDir = 0;
471 }
472}
473
474bool GBAThreadIsActive(struct GBAThread* threadContext) {
475 return threadContext->state >= THREAD_RUNNING && threadContext->state < THREAD_EXITING;
476}
477
478void GBAThreadInterrupt(struct GBAThread* threadContext) {
479 MutexLock(&threadContext->stateMutex);
480 ++threadContext->interruptDepth;
481 if (threadContext->interruptDepth > 1 || !GBAThreadIsActive(threadContext)) {
482 MutexUnlock(&threadContext->stateMutex);
483 return;
484 }
485 threadContext->savedState = threadContext->state;
486 _waitOnInterrupt(threadContext);
487 threadContext->state = THREAD_INTERRUPTING;
488 threadContext->gba->cpu->nextEvent = 0;
489 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
490 threadContext->debugger->state = DEBUGGER_EXITING;
491 }
492 ConditionWake(&threadContext->stateCond);
493 _waitUntilNotState(threadContext, THREAD_INTERRUPTING);
494 MutexUnlock(&threadContext->stateMutex);
495}
496
497void GBAThreadContinue(struct GBAThread* threadContext) {
498 MutexLock(&threadContext->stateMutex);
499 --threadContext->interruptDepth;
500 if (threadContext->interruptDepth < 1 && GBAThreadIsActive(threadContext)) {
501 threadContext->state = threadContext->savedState;
502 ConditionWake(&threadContext->stateCond);
503 }
504 MutexUnlock(&threadContext->stateMutex);
505}
506
507void GBAThreadPause(struct GBAThread* threadContext) {
508 bool frameOn = true;
509 MutexLock(&threadContext->stateMutex);
510 _waitOnInterrupt(threadContext);
511 if (threadContext->state == THREAD_RUNNING) {
512 _pauseThread(threadContext, false);
513 frameOn = false;
514 }
515 MutexUnlock(&threadContext->stateMutex);
516
517 _changeVideoSync(&threadContext->sync, frameOn);
518}
519
520void GBAThreadUnpause(struct GBAThread* threadContext) {
521 MutexLock(&threadContext->stateMutex);
522 _waitOnInterrupt(threadContext);
523 if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
524 threadContext->state = THREAD_RUNNING;
525 ConditionWake(&threadContext->stateCond);
526 }
527 MutexUnlock(&threadContext->stateMutex);
528
529 _changeVideoSync(&threadContext->sync, true);
530}
531
532bool GBAThreadIsPaused(struct GBAThread* threadContext) {
533 bool isPaused;
534 MutexLock(&threadContext->stateMutex);
535 _waitOnInterrupt(threadContext);
536 isPaused = threadContext->state == THREAD_PAUSED;
537 MutexUnlock(&threadContext->stateMutex);
538 return isPaused;
539}
540
541void GBAThreadTogglePause(struct GBAThread* threadContext) {
542 bool frameOn = true;
543 MutexLock(&threadContext->stateMutex);
544 _waitOnInterrupt(threadContext);
545 if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
546 threadContext->state = THREAD_RUNNING;
547 ConditionWake(&threadContext->stateCond);
548 } else if (threadContext->state == THREAD_RUNNING) {
549 _pauseThread(threadContext, false);
550 frameOn = false;
551 }
552 MutexUnlock(&threadContext->stateMutex);
553
554 _changeVideoSync(&threadContext->sync, frameOn);
555}
556
557void GBAThreadPauseFromThread(struct GBAThread* threadContext) {
558 bool frameOn = true;
559 MutexLock(&threadContext->stateMutex);
560 _waitOnInterrupt(threadContext);
561 if (threadContext->state == THREAD_RUNNING) {
562 _pauseThread(threadContext, true);
563 frameOn = false;
564 }
565 MutexUnlock(&threadContext->stateMutex);
566
567 _changeVideoSync(&threadContext->sync, frameOn);
568}
569
570#ifdef USE_PTHREADS
571struct GBAThread* GBAThreadGetContext(void) {
572 pthread_once(&_contextOnce, _createTLS);
573 return pthread_getspecific(_contextKey);
574}
575#elif _WIN32
576struct GBAThread* GBAThreadGetContext(void) {
577 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
578 return TlsGetValue(_contextKey);
579}
580#endif
581
582#ifdef USE_PNG
583void GBAThreadTakeScreenshot(struct GBAThread* threadContext) {
584 unsigned stride;
585 void* pixels = 0;
586 struct VFile* vf = VDirOptionalOpenIncrementFile(threadContext->stateDir, threadContext->gba->activeFile, "screenshot", "-", ".png", O_CREAT | O_TRUNC | O_WRONLY);
587 threadContext->gba->video.renderer->getPixels(threadContext->gba->video.renderer, &stride, &pixels);
588 png_structp png = PNGWriteOpen(vf);
589 png_infop info = PNGWriteHeader(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
590 PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels);
591 PNGWriteClose(png, info);
592 vf->close(vf);
593}
594#endif
595
596#else
597struct GBAThread* GBAThreadGetContext(void) {
598 return 0;
599}
600#endif
601
602void GBASyncPostFrame(struct GBASync* sync) {
603 if (!sync) {
604 return;
605 }
606
607 MutexLock(&sync->videoFrameMutex);
608 ++sync->videoFramePending;
609 --sync->videoFrameSkip;
610 if (sync->videoFrameSkip < 0) {
611 do {
612 ConditionWake(&sync->videoFrameAvailableCond);
613 if (sync->videoFrameWait) {
614 ConditionWait(&sync->videoFrameRequiredCond, &sync->videoFrameMutex);
615 }
616 } while (sync->videoFrameWait && sync->videoFramePending);
617 }
618 MutexUnlock(&sync->videoFrameMutex);
619}
620
621bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) {
622 if (!sync) {
623 return true;
624 }
625
626 MutexLock(&sync->videoFrameMutex);
627 ConditionWake(&sync->videoFrameRequiredCond);
628 if (!sync->videoFrameOn && !sync->videoFramePending) {
629 return false;
630 }
631 if (sync->videoFrameOn) {
632 ConditionWait(&sync->videoFrameAvailableCond, &sync->videoFrameMutex);
633 }
634 sync->videoFramePending = 0;
635 sync->videoFrameSkip = frameskip;
636 return true;
637}
638
639void GBASyncWaitFrameEnd(struct GBASync* sync) {
640 if (!sync) {
641 return;
642 }
643
644 MutexUnlock(&sync->videoFrameMutex);
645}
646
647bool GBASyncDrawingFrame(struct GBASync* sync) {
648 if (!sync) {
649 return true;
650 }
651
652 return sync->videoFrameSkip <= 0;
653}
654
655void GBASyncSuspendDrawing(struct GBASync* sync) {
656 if (!sync) {
657 return;
658 }
659
660 _changeVideoSync(sync, false);
661}
662
663void GBASyncResumeDrawing(struct GBASync* sync) {
664 if (!sync) {
665 return;
666 }
667
668 _changeVideoSync(sync, true);
669}
670
671void GBASyncProduceAudio(struct GBASync* sync, bool wait) {
672 if (!sync) {
673 return;
674 }
675
676 if (sync->audioWait && wait) {
677 // TODO loop properly in event of spurious wakeups
678 ConditionWait(&sync->audioRequiredCond, &sync->audioBufferMutex);
679 }
680 MutexUnlock(&sync->audioBufferMutex);
681}
682
683void GBASyncLockAudio(struct GBASync* sync) {
684 if (!sync) {
685 return;
686 }
687
688 MutexLock(&sync->audioBufferMutex);
689}
690
691void GBASyncUnlockAudio(struct GBASync* sync) {
692 if (!sync) {
693 return;
694 }
695
696 MutexUnlock(&sync->audioBufferMutex);
697}
698
699void GBASyncConsumeAudio(struct GBASync* sync) {
700 if (!sync) {
701 return;
702 }
703
704 ConditionWake(&sync->audioRequiredCond);
705 MutexUnlock(&sync->audioBufferMutex);
706}