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 threadContext->gameDir = 0;
273#if ENABLE_LIBZIP
274 if (!threadContext->gameDir) {
275 threadContext->gameDir = VDirOpenZip(args->fname, 0);
276 }
277#endif
278#if ENABLE_LZMA
279 if (!threadContext->gameDir) {
280 threadContext->gameDir = VDirOpen7z(args->fname, 0);
281 }
282#endif
283 }
284 threadContext->fname = args->fname;
285 threadContext->patch = VFileOpen(args->patch, O_RDONLY);
286}
287
288bool GBAThreadStart(struct GBAThread* threadContext) {
289 // TODO: error check
290 threadContext->activeKeys = 0;
291 threadContext->state = THREAD_INITIALIZED;
292 threadContext->sync.videoFrameOn = true;
293 threadContext->sync.videoFrameSkip = 0;
294
295 threadContext->rewindBuffer = 0;
296 int newCapacity = threadContext->rewindBufferCapacity;
297 int newInterval = threadContext->rewindBufferInterval;
298 threadContext->rewindBufferCapacity = 0;
299 threadContext->rewindBufferInterval = 0;
300 GBARewindSettingsChanged(threadContext, newCapacity, newInterval);
301
302 if (!threadContext->fpsTarget) {
303 threadContext->fpsTarget = _defaultFPSTarget;
304 }
305
306 if (threadContext->rom && !GBAIsROM(threadContext->rom)) {
307 threadContext->rom->close(threadContext->rom);
308 threadContext->rom = 0;
309 }
310
311 if (threadContext->gameDir) {
312 threadContext->gameDir->rewind(threadContext->gameDir);
313 struct VDirEntry* dirent = threadContext->gameDir->listNext(threadContext->gameDir);
314 while (dirent) {
315 struct Patch patchTemp;
316 struct VFile* vf = threadContext->gameDir->openFile(threadContext->gameDir, dirent->name(dirent), O_RDONLY);
317 if (!vf) {
318 dirent = threadContext->gameDir->listNext(threadContext->gameDir);
319 continue;
320 }
321 if (!threadContext->rom && GBAIsROM(vf)) {
322 threadContext->rom = vf;
323 } else if (!threadContext->patch && loadPatch(vf, &patchTemp)) {
324 threadContext->patch = vf;
325 } else {
326 vf->close(vf);
327 }
328 dirent = threadContext->gameDir->listNext(threadContext->gameDir);
329 }
330
331 }
332
333 if (!threadContext->rom) {
334 threadContext->state = THREAD_SHUTDOWN;
335 return false;
336 }
337
338 threadContext->save = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "sram", ".sav", O_CREAT | O_RDWR);
339
340 MutexInit(&threadContext->stateMutex);
341 ConditionInit(&threadContext->stateCond);
342
343 MutexInit(&threadContext->sync.videoFrameMutex);
344 ConditionInit(&threadContext->sync.videoFrameAvailableCond);
345 ConditionInit(&threadContext->sync.videoFrameRequiredCond);
346 MutexInit(&threadContext->sync.audioBufferMutex);
347 ConditionInit(&threadContext->sync.audioRequiredCond);
348
349 threadContext->interruptDepth = 0;
350
351#ifndef _WIN32
352 sigset_t signals;
353 sigemptyset(&signals);
354 sigaddset(&signals, SIGINT);
355 sigaddset(&signals, SIGTRAP);
356 pthread_sigmask(SIG_BLOCK, &signals, 0);
357#endif
358
359 MutexLock(&threadContext->stateMutex);
360 ThreadCreate(&threadContext->thread, _GBAThreadRun, threadContext);
361 while (threadContext->state < THREAD_RUNNING) {
362 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
363 }
364 MutexUnlock(&threadContext->stateMutex);
365
366 return true;
367}
368
369bool GBAThreadHasStarted(struct GBAThread* threadContext) {
370 bool hasStarted;
371 MutexLock(&threadContext->stateMutex);
372 hasStarted = threadContext->state > THREAD_INITIALIZED;
373 MutexUnlock(&threadContext->stateMutex);
374 return hasStarted;
375}
376
377bool GBAThreadHasExited(struct GBAThread* threadContext) {
378 bool hasExited;
379 MutexLock(&threadContext->stateMutex);
380 hasExited = threadContext->state > THREAD_EXITING;
381 MutexUnlock(&threadContext->stateMutex);
382 return hasExited;
383}
384
385bool GBAThreadHasCrashed(struct GBAThread* threadContext) {
386 bool hasExited;
387 MutexLock(&threadContext->stateMutex);
388 hasExited = threadContext->state == THREAD_CRASHED;
389 MutexUnlock(&threadContext->stateMutex);
390 return hasExited;
391}
392
393void GBAThreadEnd(struct GBAThread* threadContext) {
394 MutexLock(&threadContext->stateMutex);
395 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
396 threadContext->debugger->state = DEBUGGER_EXITING;
397 }
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 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
499 threadContext->debugger->state = DEBUGGER_EXITING;
500 }
501 ConditionWake(&threadContext->stateCond);
502 _waitUntilNotState(threadContext, THREAD_INTERRUPTING);
503 MutexUnlock(&threadContext->stateMutex);
504}
505
506void GBAThreadContinue(struct GBAThread* threadContext) {
507 MutexLock(&threadContext->stateMutex);
508 --threadContext->interruptDepth;
509 if (threadContext->interruptDepth < 1 && GBAThreadIsActive(threadContext)) {
510 threadContext->state = threadContext->savedState;
511 ConditionWake(&threadContext->stateCond);
512 }
513 MutexUnlock(&threadContext->stateMutex);
514}
515
516void GBAThreadPause(struct GBAThread* threadContext) {
517 bool frameOn = true;
518 MutexLock(&threadContext->stateMutex);
519 _waitOnInterrupt(threadContext);
520 if (threadContext->state == THREAD_RUNNING) {
521 _pauseThread(threadContext, false);
522 frameOn = false;
523 }
524 MutexUnlock(&threadContext->stateMutex);
525
526 _changeVideoSync(&threadContext->sync, frameOn);
527}
528
529void GBAThreadUnpause(struct GBAThread* threadContext) {
530 MutexLock(&threadContext->stateMutex);
531 _waitOnInterrupt(threadContext);
532 if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
533 threadContext->state = THREAD_RUNNING;
534 ConditionWake(&threadContext->stateCond);
535 }
536 MutexUnlock(&threadContext->stateMutex);
537
538 _changeVideoSync(&threadContext->sync, true);
539}
540
541bool GBAThreadIsPaused(struct GBAThread* threadContext) {
542 bool isPaused;
543 MutexLock(&threadContext->stateMutex);
544 _waitOnInterrupt(threadContext);
545 isPaused = threadContext->state == THREAD_PAUSED;
546 MutexUnlock(&threadContext->stateMutex);
547 return isPaused;
548}
549
550void GBAThreadTogglePause(struct GBAThread* threadContext) {
551 bool frameOn = true;
552 MutexLock(&threadContext->stateMutex);
553 _waitOnInterrupt(threadContext);
554 if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
555 threadContext->state = THREAD_RUNNING;
556 ConditionWake(&threadContext->stateCond);
557 } else if (threadContext->state == THREAD_RUNNING) {
558 _pauseThread(threadContext, false);
559 frameOn = false;
560 }
561 MutexUnlock(&threadContext->stateMutex);
562
563 _changeVideoSync(&threadContext->sync, frameOn);
564}
565
566void GBAThreadPauseFromThread(struct GBAThread* threadContext) {
567 bool frameOn = true;
568 MutexLock(&threadContext->stateMutex);
569 _waitOnInterrupt(threadContext);
570 if (threadContext->state == THREAD_RUNNING) {
571 _pauseThread(threadContext, true);
572 frameOn = false;
573 }
574 MutexUnlock(&threadContext->stateMutex);
575
576 _changeVideoSync(&threadContext->sync, frameOn);
577}
578
579#ifdef USE_PTHREADS
580struct GBAThread* GBAThreadGetContext(void) {
581 pthread_once(&_contextOnce, _createTLS);
582 return pthread_getspecific(_contextKey);
583}
584#elif _WIN32
585struct GBAThread* GBAThreadGetContext(void) {
586 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
587 return TlsGetValue(_contextKey);
588}
589#endif
590
591#ifdef USE_PNG
592void GBAThreadTakeScreenshot(struct GBAThread* threadContext) {
593 unsigned stride;
594 void* pixels = 0;
595 struct VFile* vf = VDirOptionalOpenIncrementFile(threadContext->stateDir, threadContext->gba->activeFile, "screenshot", "-", ".png", O_CREAT | O_TRUNC | O_WRONLY);
596 threadContext->gba->video.renderer->getPixels(threadContext->gba->video.renderer, &stride, &pixels);
597 png_structp png = PNGWriteOpen(vf);
598 png_infop info = PNGWriteHeader(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
599 PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels);
600 PNGWriteClose(png, info);
601 vf->close(vf);
602}
603#endif
604
605#else
606struct GBAThread* GBAThreadGetContext(void) {
607 return 0;
608}
609#endif
610
611void GBASyncPostFrame(struct GBASync* sync) {
612 if (!sync) {
613 return;
614 }
615
616 MutexLock(&sync->videoFrameMutex);
617 ++sync->videoFramePending;
618 --sync->videoFrameSkip;
619 if (sync->videoFrameSkip < 0) {
620 do {
621 ConditionWake(&sync->videoFrameAvailableCond);
622 if (sync->videoFrameWait) {
623 ConditionWait(&sync->videoFrameRequiredCond, &sync->videoFrameMutex);
624 }
625 } while (sync->videoFrameWait && sync->videoFramePending);
626 }
627 MutexUnlock(&sync->videoFrameMutex);
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}