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