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