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 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
489 threadContext->debugger->state = DEBUGGER_EXITING;
490 }
491 ConditionWake(&threadContext->stateCond);
492 _waitUntilNotState(threadContext, THREAD_INTERRUPTING);
493 MutexUnlock(&threadContext->stateMutex);
494}
495
496void GBAThreadContinue(struct GBAThread* threadContext) {
497 MutexLock(&threadContext->stateMutex);
498 --threadContext->interruptDepth;
499 if (threadContext->interruptDepth < 1 && GBAThreadIsActive(threadContext)) {
500 threadContext->state = threadContext->savedState;
501 ConditionWake(&threadContext->stateCond);
502 }
503 MutexUnlock(&threadContext->stateMutex);
504}
505
506void GBAThreadPause(struct GBAThread* threadContext) {
507 bool frameOn = true;
508 MutexLock(&threadContext->stateMutex);
509 _waitOnInterrupt(threadContext);
510 if (threadContext->state == THREAD_RUNNING) {
511 _pauseThread(threadContext, false);
512 frameOn = false;
513 }
514 MutexUnlock(&threadContext->stateMutex);
515
516 _changeVideoSync(&threadContext->sync, frameOn);
517}
518
519void GBAThreadUnpause(struct GBAThread* threadContext) {
520 MutexLock(&threadContext->stateMutex);
521 _waitOnInterrupt(threadContext);
522 if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
523 threadContext->state = THREAD_RUNNING;
524 ConditionWake(&threadContext->stateCond);
525 }
526 MutexUnlock(&threadContext->stateMutex);
527
528 _changeVideoSync(&threadContext->sync, true);
529}
530
531bool GBAThreadIsPaused(struct GBAThread* threadContext) {
532 bool isPaused;
533 MutexLock(&threadContext->stateMutex);
534 _waitOnInterrupt(threadContext);
535 isPaused = threadContext->state == THREAD_PAUSED;
536 MutexUnlock(&threadContext->stateMutex);
537 return isPaused;
538}
539
540void GBAThreadTogglePause(struct GBAThread* threadContext) {
541 bool frameOn = true;
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 } else if (threadContext->state == THREAD_RUNNING) {
548 _pauseThread(threadContext, false);
549 frameOn = false;
550 }
551 MutexUnlock(&threadContext->stateMutex);
552
553 _changeVideoSync(&threadContext->sync, frameOn);
554}
555
556void GBAThreadPauseFromThread(struct GBAThread* threadContext) {
557 bool frameOn = true;
558 MutexLock(&threadContext->stateMutex);
559 _waitOnInterrupt(threadContext);
560 if (threadContext->state == THREAD_RUNNING) {
561 _pauseThread(threadContext, true);
562 frameOn = false;
563 }
564 MutexUnlock(&threadContext->stateMutex);
565
566 _changeVideoSync(&threadContext->sync, frameOn);
567}
568
569#ifdef USE_PTHREADS
570struct GBAThread* GBAThreadGetContext(void) {
571 pthread_once(&_contextOnce, _createTLS);
572 return pthread_getspecific(_contextKey);
573}
574#elif _WIN32
575struct GBAThread* GBAThreadGetContext(void) {
576 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
577 return TlsGetValue(_contextKey);
578}
579#endif
580
581#ifdef USE_PNG
582void GBAThreadTakeScreenshot(struct GBAThread* threadContext) {
583 unsigned stride;
584 void* pixels = 0;
585 struct VFile* vf = VDirOptionalOpenIncrementFile(threadContext->stateDir, threadContext->gba->activeFile, "screenshot", "-", ".png", O_CREAT | O_TRUNC | O_WRONLY);
586 threadContext->gba->video.renderer->getPixels(threadContext->gba->video.renderer, &stride, &pixels);
587 png_structp png = PNGWriteOpen(vf);
588 png_infop info = PNGWriteHeader(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
589 PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels);
590 PNGWriteClose(png, info);
591 vf->close(vf);
592}
593#endif
594
595#else
596struct GBAThread* GBAThreadGetContext(void) {
597 return 0;
598}
599#endif
600
601void GBASyncPostFrame(struct GBASync* sync) {
602 if (!sync) {
603 return;
604 }
605
606 MutexLock(&sync->videoFrameMutex);
607 ++sync->videoFramePending;
608 --sync->videoFrameSkip;
609 if (sync->videoFrameSkip < 0) {
610 do {
611 ConditionWake(&sync->videoFrameAvailableCond);
612 if (sync->videoFrameWait) {
613 ConditionWait(&sync->videoFrameRequiredCond, &sync->videoFrameMutex);
614 }
615 } while (sync->videoFrameWait && sync->videoFramePending);
616 }
617 MutexUnlock(&sync->videoFrameMutex);
618
619 struct GBAThread* thread = GBAThreadGetContext();
620 if (!thread) {
621 return;
622 }
623
624 if (thread->rewindBuffer) {
625 --thread->rewindBufferNext;
626 if (thread->rewindBufferNext <= 0) {
627 thread->rewindBufferNext = thread->rewindBufferInterval;
628 GBARecordFrame(thread);
629 }
630 }
631 if (thread->stream) {
632 thread->stream->postVideoFrame(thread->stream, thread->renderer);
633 }
634 if (thread->frameCallback) {
635 thread->frameCallback(thread);
636 }
637}
638
639bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) {
640 if (!sync) {
641 return true;
642 }
643
644 MutexLock(&sync->videoFrameMutex);
645 ConditionWake(&sync->videoFrameRequiredCond);
646 if (!sync->videoFrameOn && !sync->videoFramePending) {
647 return false;
648 }
649 if (sync->videoFrameOn) {
650 ConditionWait(&sync->videoFrameAvailableCond, &sync->videoFrameMutex);
651 }
652 sync->videoFramePending = 0;
653 sync->videoFrameSkip = frameskip;
654 return true;
655}
656
657void GBASyncWaitFrameEnd(struct GBASync* sync) {
658 if (!sync) {
659 return;
660 }
661
662 MutexUnlock(&sync->videoFrameMutex);
663}
664
665bool GBASyncDrawingFrame(struct GBASync* sync) {
666 if (!sync) {
667 return true;
668 }
669
670 return sync->videoFrameSkip <= 0;
671}
672
673void GBASyncSuspendDrawing(struct GBASync* sync) {
674 if (!sync) {
675 return;
676 }
677
678 _changeVideoSync(sync, false);
679}
680
681void GBASyncResumeDrawing(struct GBASync* sync) {
682 if (!sync) {
683 return;
684 }
685
686 _changeVideoSync(sync, true);
687}
688
689void GBASyncProduceAudio(struct GBASync* sync, bool wait) {
690 if (!sync) {
691 return;
692 }
693
694 if (sync->audioWait && wait) {
695 // TODO loop properly in event of spurious wakeups
696 ConditionWait(&sync->audioRequiredCond, &sync->audioBufferMutex);
697 }
698 MutexUnlock(&sync->audioBufferMutex);
699}
700
701void GBASyncLockAudio(struct GBASync* sync) {
702 if (!sync) {
703 return;
704 }
705
706 MutexLock(&sync->audioBufferMutex);
707}
708
709void GBASyncUnlockAudio(struct GBASync* sync) {
710 if (!sync) {
711 return;
712 }
713
714 MutexUnlock(&sync->audioBufferMutex);
715}
716
717void GBASyncConsumeAudio(struct GBASync* sync) {
718 if (!sync) {
719 return;
720 }
721
722 ConditionWake(&sync->audioRequiredCond);
723 MutexUnlock(&sync->audioBufferMutex);
724}