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