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