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