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