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