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