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