src/gba/gba-thread.c (view raw)
1#include "gba-thread.h"
2
3#include "arm.h"
4#include "gba.h"
5#include "gba-serialize.h"
6
7#include "debugger/debugger.h"
8
9#include "util/patch.h"
10#include "util/png-io.h"
11#include "util/vfs.h"
12
13#include <signal.h>
14
15static const float _defaultFPSTarget = 60.f;
16
17#ifdef USE_PTHREADS
18static pthread_key_t _contextKey;
19static pthread_once_t _contextOnce = PTHREAD_ONCE_INIT;
20
21static void _createTLS(void) {
22 pthread_key_create(&_contextKey, 0);
23}
24#else
25static DWORD _contextKey;
26static INIT_ONCE _contextOnce = INIT_ONCE_STATIC_INIT;
27
28static BOOL CALLBACK _createTLS(PINIT_ONCE once, PVOID param, PVOID* context) {
29 UNUSED(once);
30 UNUSED(param);
31 UNUSED(context);
32 _contextKey = TlsAlloc();
33 return TRUE;
34}
35#endif
36
37static void _changeState(struct GBAThread* threadContext, enum ThreadState newState, bool broadcast) {
38 MutexLock(&threadContext->stateMutex);
39 threadContext->state = newState;
40 if (broadcast) {
41 ConditionWake(&threadContext->stateCond);
42 }
43 MutexUnlock(&threadContext->stateMutex);
44}
45
46static void _waitOnInterrupt(struct GBAThread* threadContext) {
47 while (threadContext->state == THREAD_INTERRUPTED) {
48 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
49 }
50}
51
52static void _waitUntilNotState(struct GBAThread* threadContext, enum ThreadState oldState) {
53 while (threadContext->state == oldState) {
54 MutexUnlock(&threadContext->stateMutex);
55
56 MutexLock(&threadContext->sync.videoFrameMutex);
57 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
58 MutexUnlock(&threadContext->sync.videoFrameMutex);
59
60 MutexLock(&threadContext->sync.audioBufferMutex);
61 ConditionWake(&threadContext->sync.audioRequiredCond);
62 MutexUnlock(&threadContext->sync.audioBufferMutex);
63
64 MutexLock(&threadContext->stateMutex);
65 ConditionWake(&threadContext->stateCond);
66 }
67}
68
69static void _pauseThread(struct GBAThread* threadContext, bool onThread) {
70 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
71 threadContext->debugger->state = DEBUGGER_EXITING;
72 }
73 threadContext->state = THREAD_PAUSING;
74 if (!onThread) {
75 _waitUntilNotState(threadContext, THREAD_PAUSING);
76 }
77}
78
79static void _changeVideoSync(struct GBASync* sync, bool frameOn) {
80 // Make sure the video thread can process events while the GBA thread is paused
81 MutexLock(&sync->videoFrameMutex);
82 if (frameOn != sync->videoFrameOn) {
83 sync->videoFrameOn = frameOn;
84 ConditionWake(&sync->videoFrameAvailableCond);
85 }
86 MutexUnlock(&sync->videoFrameMutex);
87}
88
89static THREAD_ENTRY _GBAThreadRun(void* context) {
90#ifdef USE_PTHREADS
91 pthread_once(&_contextOnce, _createTLS);
92#else
93 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
94#endif
95
96 struct GBA gba;
97 struct ARMCore cpu;
98 struct Patch patch;
99 struct GBAThread* threadContext = context;
100 struct ARMComponent* components[1] = {};
101 int numComponents = 0;
102
103 if (threadContext->debugger) {
104 components[numComponents] = &threadContext->debugger->d;
105 ++numComponents;
106 }
107
108#if !defined(_WIN32) && defined(USE_PTHREADS)
109 sigset_t signals;
110 sigemptyset(&signals);
111 pthread_sigmask(SIG_SETMASK, &signals, 0);
112#endif
113
114 GBACreate(&gba);
115 ARMSetComponents(&cpu, &gba.d, numComponents, components);
116 ARMInit(&cpu);
117 threadContext->gba = &gba;
118 gba.sync = &threadContext->sync;
119 gba.logLevel = threadContext->logLevel;
120#ifdef USE_PTHREADS
121 pthread_setspecific(_contextKey, threadContext);
122#else
123 TlsSetValue(_contextKey, threadContext);
124#endif
125
126 if (threadContext->audioBuffers) {
127 GBAAudioResizeBuffer(&gba.audio, threadContext->audioBuffers);
128 } else {
129 threadContext->audioBuffers = GBA_AUDIO_SAMPLES;
130 }
131
132 if (threadContext->renderer) {
133 GBAVideoAssociateRenderer(&gba.video, threadContext->renderer);
134 }
135
136 if (threadContext->rom) {
137 GBALoadROM(&gba, threadContext->rom, threadContext->save, threadContext->fname);
138 if (threadContext->bios) {
139 GBALoadBIOS(&gba, threadContext->bios);
140 }
141
142 if (threadContext->patch && loadPatch(threadContext->patch, &patch)) {
143 GBAApplyPatch(&gba, &patch);
144 }
145 }
146
147 ARMReset(&cpu);
148
149 if (threadContext->debugger) {
150 threadContext->debugger->log = GBADebuggerLogShim;
151 GBAAttachDebugger(&gba, threadContext->debugger);
152 ARMDebuggerEnter(threadContext->debugger, DEBUGGER_ENTER_ATTACHED);
153 }
154
155 GBASIOSetDriverSet(&gba.sio, &threadContext->sioDrivers);
156
157 gba.keySource = &threadContext->activeKeys;
158
159 if (threadContext->startCallback) {
160 threadContext->startCallback(threadContext);
161 }
162
163 _changeState(threadContext, THREAD_RUNNING, true);
164
165 while (threadContext->state < THREAD_EXITING) {
166 if (threadContext->debugger) {
167 struct ARMDebugger* debugger = threadContext->debugger;
168 ARMDebuggerRun(debugger);
169 if (debugger->state == DEBUGGER_SHUTDOWN) {
170 _changeState(threadContext, THREAD_EXITING, false);
171 }
172 } else {
173 while (threadContext->state == THREAD_RUNNING) {
174 ARMRunLoop(&cpu);
175 }
176 }
177
178 int resetScheduled = 0;
179 MutexLock(&threadContext->stateMutex);
180 if (threadContext->state == THREAD_PAUSING) {
181 threadContext->state = THREAD_PAUSED;
182 ConditionWake(&threadContext->stateCond);
183 }
184 if (threadContext->state == THREAD_INTERRUPTING) {
185 threadContext->state = THREAD_INTERRUPTED;
186 ConditionWake(&threadContext->stateCond);
187 }
188 if (threadContext->state == THREAD_RESETING) {
189 threadContext->state = THREAD_RUNNING;
190 resetScheduled = 1;
191 }
192 while (threadContext->state == THREAD_PAUSED) {
193 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
194 }
195 MutexUnlock(&threadContext->stateMutex);
196 if (resetScheduled) {
197 ARMReset(&cpu);
198 }
199 }
200
201 while (threadContext->state != THREAD_SHUTDOWN) {
202 _changeState(threadContext, THREAD_SHUTDOWN, false);
203 }
204
205 if (threadContext->cleanCallback) {
206 threadContext->cleanCallback(threadContext);
207 }
208
209 threadContext->gba = 0;
210 ARMDeinit(&cpu);
211 GBADestroy(&gba);
212
213 threadContext->sync.videoFrameOn = false;
214 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
215 ConditionWake(&threadContext->sync.audioRequiredCond);
216
217 return 0;
218}
219
220void GBAMapOptionsToContext(struct StartupOptions* opts, struct GBAThread* threadContext) {
221 if (opts->dirmode) {
222 threadContext->gameDir = VDirOpen(opts->fname);
223 threadContext->stateDir = threadContext->gameDir;
224 } else {
225 threadContext->rom = VFileOpen(opts->fname, O_RDONLY);
226#if ENABLE_LIBZIP
227 threadContext->gameDir = VDirOpenZip(opts->fname, 0);
228#endif
229 }
230 threadContext->fname = opts->fname;
231 threadContext->bios = VFileOpen(opts->bios, O_RDONLY);
232 threadContext->patch = VFileOpen(opts->patch, O_RDONLY);
233 threadContext->frameskip = opts->frameskip;
234 threadContext->logLevel = opts->logLevel;
235 threadContext->rewindBufferCapacity = opts->rewindBufferCapacity;
236 threadContext->rewindBufferInterval = opts->rewindBufferInterval;
237}
238
239bool GBAThreadStart(struct GBAThread* threadContext) {
240 // TODO: error check
241 threadContext->activeKeys = 0;
242 threadContext->state = THREAD_INITIALIZED;
243 threadContext->sync.videoFrameOn = true;
244 threadContext->sync.videoFrameSkip = 0;
245
246 threadContext->rewindBufferNext = threadContext->rewindBufferInterval;
247 threadContext->rewindBufferSize = 0;
248 if (threadContext->rewindBufferCapacity) {
249 threadContext->rewindBuffer = calloc(threadContext->rewindBufferCapacity, sizeof(void*));
250 } else {
251 threadContext->rewindBuffer = 0;
252 }
253
254 if (!threadContext->fpsTarget) {
255 threadContext->fpsTarget = _defaultFPSTarget;
256 }
257
258 if (threadContext->rom && !GBAIsROM(threadContext->rom)) {
259 threadContext->rom->close(threadContext->rom);
260 threadContext->rom = 0;
261 }
262
263 if (threadContext->gameDir) {
264 threadContext->gameDir->rewind(threadContext->gameDir);
265 struct VDirEntry* dirent = threadContext->gameDir->listNext(threadContext->gameDir);
266 while (dirent) {
267 struct Patch patchTemp;
268 struct VFile* vf = threadContext->gameDir->openFile(threadContext->gameDir, dirent->name(dirent), O_RDONLY);
269 if (!vf) {
270 continue;
271 }
272 if (!threadContext->rom && GBAIsROM(vf)) {
273 threadContext->rom = vf;
274 } else if (!threadContext->patch && loadPatch(vf, &patchTemp)) {
275 threadContext->patch = vf;
276 } else {
277 vf->close(vf);
278 }
279 dirent = threadContext->gameDir->listNext(threadContext->gameDir);
280 }
281
282 }
283
284 if (!threadContext->rom) {
285 threadContext->state = THREAD_SHUTDOWN;
286 return false;
287 }
288
289 threadContext->save = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "sram", ".sav", O_CREAT | O_RDWR);
290
291 MutexInit(&threadContext->stateMutex);
292 ConditionInit(&threadContext->stateCond);
293
294 MutexInit(&threadContext->sync.videoFrameMutex);
295 ConditionInit(&threadContext->sync.videoFrameAvailableCond);
296 ConditionInit(&threadContext->sync.videoFrameRequiredCond);
297 MutexInit(&threadContext->sync.audioBufferMutex);
298 ConditionInit(&threadContext->sync.audioRequiredCond);
299
300#ifndef _WIN32
301 sigset_t signals;
302 sigemptyset(&signals);
303 sigaddset(&signals, SIGINT);
304 sigaddset(&signals, SIGTRAP);
305 pthread_sigmask(SIG_BLOCK, &signals, 0);
306#endif
307
308 MutexLock(&threadContext->stateMutex);
309 ThreadCreate(&threadContext->thread, _GBAThreadRun, threadContext);
310 while (threadContext->state < THREAD_RUNNING) {
311 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
312 }
313 MutexUnlock(&threadContext->stateMutex);
314
315 return true;
316}
317
318bool GBAThreadHasStarted(struct GBAThread* threadContext) {
319 bool hasStarted;
320 MutexLock(&threadContext->stateMutex);
321 hasStarted = threadContext->state > THREAD_INITIALIZED;
322 MutexUnlock(&threadContext->stateMutex);
323 return hasStarted;
324}
325
326void GBAThreadEnd(struct GBAThread* threadContext) {
327 MutexLock(&threadContext->stateMutex);
328 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
329 threadContext->debugger->state = DEBUGGER_EXITING;
330 }
331 threadContext->state = THREAD_EXITING;
332 ConditionWake(&threadContext->stateCond);
333 MutexUnlock(&threadContext->stateMutex);
334 MutexLock(&threadContext->sync.audioBufferMutex);
335 threadContext->sync.audioWait = 0;
336 ConditionWake(&threadContext->sync.audioRequiredCond);
337 MutexUnlock(&threadContext->sync.audioBufferMutex);
338}
339
340void GBAThreadReset(struct GBAThread* threadContext) {
341 MutexLock(&threadContext->stateMutex);
342 _waitOnInterrupt(threadContext);
343 threadContext->state = THREAD_RESETING;
344 ConditionWake(&threadContext->stateCond);
345 MutexUnlock(&threadContext->stateMutex);
346}
347
348void GBAThreadJoin(struct GBAThread* threadContext) {
349 MutexLock(&threadContext->sync.videoFrameMutex);
350 threadContext->sync.videoFrameWait = 0;
351 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
352 MutexUnlock(&threadContext->sync.videoFrameMutex);
353
354 ThreadJoin(threadContext->thread);
355
356 MutexDeinit(&threadContext->stateMutex);
357 ConditionDeinit(&threadContext->stateCond);
358
359 MutexDeinit(&threadContext->sync.videoFrameMutex);
360 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
361 ConditionDeinit(&threadContext->sync.videoFrameAvailableCond);
362 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
363 ConditionDeinit(&threadContext->sync.videoFrameRequiredCond);
364
365 ConditionWake(&threadContext->sync.audioRequiredCond);
366 ConditionDeinit(&threadContext->sync.audioRequiredCond);
367 MutexDeinit(&threadContext->sync.audioBufferMutex);
368
369 int i;
370 for (i = 0; i < threadContext->rewindBufferCapacity; ++i) {
371 if (threadContext->rewindBuffer[i]) {
372 GBADeallocateState(threadContext->rewindBuffer[i]);
373 }
374 }
375 free(threadContext->rewindBuffer);
376
377 GBAInputMapDeinit(&threadContext->inputMap);
378
379 if (threadContext->rom) {
380 threadContext->rom->close(threadContext->rom);
381 threadContext->rom = 0;
382 }
383
384 if (threadContext->save) {
385 threadContext->save->close(threadContext->save);
386 threadContext->save = 0;
387 }
388
389 if (threadContext->bios) {
390 threadContext->bios->close(threadContext->bios);
391 threadContext->bios = 0;
392 }
393
394 if (threadContext->patch) {
395 threadContext->patch->close(threadContext->patch);
396 threadContext->patch = 0;
397 }
398
399 if (threadContext->gameDir) {
400 if (threadContext->stateDir == threadContext->gameDir) {
401 threadContext->stateDir = 0;
402 }
403 threadContext->gameDir->close(threadContext->gameDir);
404 threadContext->gameDir = 0;
405 }
406
407 if (threadContext->stateDir) {
408 threadContext->stateDir->close(threadContext->stateDir);
409 threadContext->stateDir = 0;
410 }
411}
412
413void GBAThreadInterrupt(struct GBAThread* threadContext) {
414 MutexLock(&threadContext->stateMutex);
415 threadContext->savedState = threadContext->state;
416 _waitOnInterrupt(threadContext);
417 threadContext->state = THREAD_INTERRUPTING;
418 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
419 threadContext->debugger->state = DEBUGGER_EXITING;
420 }
421 ConditionWake(&threadContext->stateCond);
422 _waitUntilNotState(threadContext, THREAD_INTERRUPTING);
423 MutexUnlock(&threadContext->stateMutex);
424}
425
426void GBAThreadContinue(struct GBAThread* threadContext) {
427 _changeState(threadContext, threadContext->savedState, 1);
428}
429
430void GBAThreadPause(struct GBAThread* threadContext) {
431 bool frameOn = true;
432 MutexLock(&threadContext->stateMutex);
433 _waitOnInterrupt(threadContext);
434 if (threadContext->state == THREAD_RUNNING) {
435 _pauseThread(threadContext, false);
436 frameOn = false;
437 }
438 MutexUnlock(&threadContext->stateMutex);
439
440 _changeVideoSync(&threadContext->sync, frameOn);
441}
442
443void GBAThreadUnpause(struct GBAThread* threadContext) {
444 MutexLock(&threadContext->stateMutex);
445 _waitOnInterrupt(threadContext);
446 if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
447 threadContext->state = THREAD_RUNNING;
448 ConditionWake(&threadContext->stateCond);
449 }
450 MutexUnlock(&threadContext->stateMutex);
451
452 _changeVideoSync(&threadContext->sync, true);
453}
454
455bool GBAThreadIsPaused(struct GBAThread* threadContext) {
456 bool isPaused;
457 MutexLock(&threadContext->stateMutex);
458 _waitOnInterrupt(threadContext);
459 isPaused = threadContext->state == THREAD_PAUSED;
460 MutexUnlock(&threadContext->stateMutex);
461 return isPaused;
462}
463
464void GBAThreadTogglePause(struct GBAThread* threadContext) {
465 bool frameOn = true;
466 MutexLock(&threadContext->stateMutex);
467 _waitOnInterrupt(threadContext);
468 if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
469 threadContext->state = THREAD_RUNNING;
470 ConditionWake(&threadContext->stateCond);
471 } else if (threadContext->state == THREAD_RUNNING) {
472 _pauseThread(threadContext, false);
473 frameOn = false;
474 }
475 MutexUnlock(&threadContext->stateMutex);
476
477 _changeVideoSync(&threadContext->sync, frameOn);
478}
479
480void GBAThreadPauseFromThread(struct GBAThread* threadContext) {
481 bool frameOn = true;
482 MutexLock(&threadContext->stateMutex);
483 _waitOnInterrupt(threadContext);
484 if (threadContext->state == THREAD_RUNNING) {
485 _pauseThread(threadContext, true);
486 frameOn = false;
487 }
488 MutexUnlock(&threadContext->stateMutex);
489
490 _changeVideoSync(&threadContext->sync, frameOn);
491}
492
493#ifdef USE_PTHREADS
494struct GBAThread* GBAThreadGetContext(void) {
495 pthread_once(&_contextOnce, _createTLS);
496 return pthread_getspecific(_contextKey);
497}
498#else
499struct GBAThread* GBAThreadGetContext(void) {
500 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
501 return TlsGetValue(_contextKey);
502}
503#endif
504
505#ifdef USE_PNG
506void GBAThreadTakeScreenshot(struct GBAThread* threadContext) {
507 unsigned stride;
508 void* pixels = 0;
509 struct VFile* vf = VDirOptionalOpenIncrementFile(threadContext->stateDir, threadContext->gba->activeFile, "screenshot", "-", ".png", O_CREAT | O_TRUNC | O_WRONLY);
510 threadContext->gba->video.renderer->getPixels(threadContext->gba->video.renderer, &stride, &pixels);
511 png_structp png = PNGWriteOpen(vf);
512 png_infop info = PNGWriteHeader(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
513 PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels);
514 PNGWriteClose(png, info);
515 vf->close(vf);
516}
517#endif
518
519void GBASyncPostFrame(struct GBASync* sync) {
520 if (!sync) {
521 return;
522 }
523
524 MutexLock(&sync->videoFrameMutex);
525 ++sync->videoFramePending;
526 --sync->videoFrameSkip;
527 if (sync->videoFrameSkip < 0) {
528 do {
529 ConditionWake(&sync->videoFrameAvailableCond);
530 if (sync->videoFrameWait) {
531 ConditionWait(&sync->videoFrameRequiredCond, &sync->videoFrameMutex);
532 }
533 } while (sync->videoFrameWait && sync->videoFramePending);
534 }
535 MutexUnlock(&sync->videoFrameMutex);
536
537 struct GBAThread* thread = GBAThreadGetContext();
538 if (!thread) {
539 return;
540 }
541
542 if (thread->rewindBuffer) {
543 --thread->rewindBufferNext;
544 if (thread->rewindBufferNext <= 0) {
545 thread->rewindBufferNext = thread->rewindBufferInterval;
546 GBARecordFrame(thread);
547 }
548 }
549 if (thread->stream) {
550 thread->stream->postVideoFrame(thread->stream, thread->renderer);
551 }
552 if (thread->frameCallback) {
553 thread->frameCallback(thread);
554 }
555}
556
557bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) {
558 if (!sync) {
559 return true;
560 }
561
562 MutexLock(&sync->videoFrameMutex);
563 ConditionWake(&sync->videoFrameRequiredCond);
564 if (!sync->videoFrameOn && !sync->videoFramePending) {
565 return false;
566 }
567 if (sync->videoFrameOn && !sync->videoFramePending) {
568 ConditionWait(&sync->videoFrameAvailableCond, &sync->videoFrameMutex);
569 }
570 sync->videoFramePending = 0;
571 sync->videoFrameSkip = frameskip;
572 return true;
573}
574
575void GBASyncWaitFrameEnd(struct GBASync* sync) {
576 if (!sync) {
577 return;
578 }
579
580 MutexUnlock(&sync->videoFrameMutex);
581}
582
583bool GBASyncDrawingFrame(struct GBASync* sync) {
584 return sync->videoFrameSkip <= 0;
585}
586
587void GBASyncSuspendDrawing(struct GBASync* sync) {
588 _changeVideoSync(sync, false);
589}
590
591void GBASyncResumeDrawing(struct GBASync* sync) {
592 _changeVideoSync(sync, true);
593}
594
595void GBASyncProduceAudio(struct GBASync* sync, bool wait) {
596 if (sync->audioWait && wait) {
597 // TODO loop properly in event of spurious wakeups
598 ConditionWait(&sync->audioRequiredCond, &sync->audioBufferMutex);
599 }
600 MutexUnlock(&sync->audioBufferMutex);
601}
602
603void GBASyncLockAudio(struct GBASync* sync) {
604 MutexLock(&sync->audioBufferMutex);
605}
606
607void GBASyncUnlockAudio(struct GBASync* sync) {
608 MutexUnlock(&sync->audioBufferMutex);
609}
610
611void GBASyncConsumeAudio(struct GBASync* sync) {
612 ConditionWake(&sync->audioRequiredCond);
613 MutexUnlock(&sync->audioBufferMutex);
614}