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 } else {
175 threadContext->rom = VFileOpen(opts->fname, O_RDONLY);
176#if ENABLE_LIBZIP
177 threadContext->gamedir = VDirOpenZip(opts->fname, 0);
178#endif
179 }
180 threadContext->fname = opts->fname;
181 threadContext->bios = VFileOpen(opts->bios, O_RDONLY);
182 threadContext->patch = VFileOpen(opts->patch, O_RDONLY);
183 threadContext->frameskip = opts->frameskip;
184 threadContext->logLevel = opts->logLevel;
185 threadContext->rewindBufferCapacity = opts->rewindBufferCapacity;
186 threadContext->rewindBufferInterval = opts->rewindBufferInterval;
187}
188
189bool GBAThreadStart(struct GBAThread* threadContext) {
190 // TODO: error check
191 threadContext->activeKeys = 0;
192 threadContext->state = THREAD_INITIALIZED;
193 threadContext->sync.videoFrameOn = 1;
194 threadContext->sync.videoFrameSkip = 0;
195
196 threadContext->rewindBufferNext = threadContext->rewindBufferInterval;
197 threadContext->rewindBufferSize = 0;
198 if (threadContext->rewindBufferCapacity) {
199 threadContext->rewindBuffer = calloc(threadContext->rewindBufferCapacity, sizeof(void*));
200 } else {
201 threadContext->rewindBuffer = 0;
202 }
203
204 if (threadContext->rom && !GBAIsROM(threadContext->rom)) {
205 threadContext->rom->close(threadContext->rom);
206 threadContext->rom = 0;
207 }
208
209 if (threadContext->gamedir) {
210 threadContext->gamedir->rewind(threadContext->gamedir);
211 struct VDirEntry* dirent = threadContext->gamedir->listNext(threadContext->gamedir);
212 while (dirent) {
213 struct Patch patchTemp;
214 struct VFile* vf = threadContext->gamedir->openFile(threadContext->gamedir, dirent->name(dirent), O_RDONLY);
215 if (!vf) {
216 continue;
217 }
218 if (!threadContext->rom && GBAIsROM(vf)) {
219 threadContext->rom = vf;
220 } else if (!threadContext->patch && loadPatch(vf, &patchTemp)) {
221 threadContext->patch = vf;
222 } else {
223 vf->close(vf);
224 }
225 dirent = threadContext->gamedir->listNext(threadContext->gamedir);
226 }
227
228 // TODO: Differentiate ZIPs and filesystem directories
229 threadContext->save = threadContext->gamedir->openFile(threadContext->gamedir, "sram.sav", O_RDWR | O_CREAT);
230 }
231
232 if (!threadContext->rom) {
233 return false;
234 }
235
236 if (threadContext->fname && !threadContext->save) {
237 char* savedata = 0;
238 char* dotPoint = strrchr(threadContext->fname, '.');
239 if (dotPoint > strrchr(threadContext->fname, '/') && dotPoint[1] && dotPoint[2] && dotPoint[3]) {
240 savedata = strdup(threadContext->fname);
241 dotPoint = strrchr(savedata, '.');
242 dotPoint[1] = 's';
243 dotPoint[2] = 'a';
244 dotPoint[3] = 'v';
245 dotPoint[4] = '\0';
246 } else if (dotPoint) {
247 savedata = malloc((dotPoint - threadContext->fname + 5) * sizeof(char));
248 strncpy(savedata, threadContext->fname, dotPoint - threadContext->fname + 1);
249 strcat(savedata, "sav");
250 } else {
251 savedata = malloc(strlen(threadContext->fname + 5) * sizeof(char));
252 sprintf(savedata, "%s.sav", threadContext->fname);
253 }
254 threadContext->save = VFileOpen(savedata, O_RDWR | O_CREAT);
255 free(savedata);
256 }
257
258 MutexInit(&threadContext->stateMutex);
259 ConditionInit(&threadContext->stateCond);
260
261 MutexInit(&threadContext->sync.videoFrameMutex);
262 ConditionInit(&threadContext->sync.videoFrameAvailableCond);
263 ConditionInit(&threadContext->sync.videoFrameRequiredCond);
264 MutexInit(&threadContext->sync.audioBufferMutex);
265 ConditionInit(&threadContext->sync.audioRequiredCond);
266
267#ifndef _WIN32
268 sigset_t signals;
269 sigemptyset(&signals);
270 sigaddset(&signals, SIGINT);
271 sigaddset(&signals, SIGTRAP);
272 pthread_sigmask(SIG_BLOCK, &signals, 0);
273#endif
274
275 MutexLock(&threadContext->stateMutex);
276 ThreadCreate(&threadContext->thread, _GBAThreadRun, threadContext);
277 while (threadContext->state < THREAD_RUNNING) {
278 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
279 }
280 MutexUnlock(&threadContext->stateMutex);
281
282 return true;
283}
284
285bool GBAThreadHasStarted(struct GBAThread* threadContext) {
286 bool hasStarted;
287 MutexLock(&threadContext->stateMutex);
288 hasStarted = threadContext->state > THREAD_INITIALIZED;
289 MutexUnlock(&threadContext->stateMutex);
290 return hasStarted;
291}
292
293void GBAThreadEnd(struct GBAThread* threadContext) {
294 MutexLock(&threadContext->stateMutex);
295 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
296 threadContext->debugger->state = DEBUGGER_EXITING;
297 }
298 threadContext->state = THREAD_EXITING;
299 MutexUnlock(&threadContext->stateMutex);
300 MutexLock(&threadContext->sync.audioBufferMutex);
301 threadContext->sync.audioWait = 0;
302 ConditionWake(&threadContext->sync.audioRequiredCond);
303 MutexUnlock(&threadContext->sync.audioBufferMutex);
304}
305
306void GBAThreadReset(struct GBAThread* threadContext) {
307 MutexLock(&threadContext->stateMutex);
308 _waitOnInterrupt(threadContext);
309 threadContext->state = THREAD_RESETING;
310 ConditionWake(&threadContext->stateCond);
311 MutexUnlock(&threadContext->stateMutex);
312}
313
314void GBAThreadJoin(struct GBAThread* threadContext) {
315 MutexLock(&threadContext->sync.videoFrameMutex);
316 threadContext->sync.videoFrameWait = 0;
317 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
318 MutexUnlock(&threadContext->sync.videoFrameMutex);
319
320 ThreadJoin(threadContext->thread);
321
322 MutexDeinit(&threadContext->stateMutex);
323 ConditionDeinit(&threadContext->stateCond);
324
325 MutexDeinit(&threadContext->sync.videoFrameMutex);
326 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
327 ConditionDeinit(&threadContext->sync.videoFrameAvailableCond);
328 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
329 ConditionDeinit(&threadContext->sync.videoFrameRequiredCond);
330
331 ConditionWake(&threadContext->sync.audioRequiredCond);
332 ConditionDeinit(&threadContext->sync.audioRequiredCond);
333 MutexDeinit(&threadContext->sync.audioBufferMutex);
334
335 int i;
336 for (i = 0; i < threadContext->rewindBufferCapacity; ++i) {
337 if (threadContext->rewindBuffer[i]) {
338 GBADeallocateState(threadContext->rewindBuffer[i]);
339 }
340 }
341 free(threadContext->rewindBuffer);
342
343 if (threadContext->rom) {
344 threadContext->rom->close(threadContext->rom);
345 threadContext->rom = 0;
346 }
347
348 if (threadContext->save) {
349 threadContext->save->close(threadContext->save);
350 threadContext->save = 0;
351 }
352
353 if (threadContext->bios) {
354 threadContext->bios->close(threadContext->bios);
355 threadContext->bios = 0;
356 }
357
358 if (threadContext->patch) {
359 threadContext->patch->close(threadContext->patch);
360 threadContext->patch = 0;
361 }
362
363 if (threadContext->gamedir) {
364 threadContext->gamedir->close(threadContext->gamedir);
365 threadContext->gamedir = 0;
366 }
367}
368
369void GBAThreadInterrupt(struct GBAThread* threadContext) {
370 MutexLock(&threadContext->stateMutex);
371 threadContext->savedState = threadContext->state;
372 _waitOnInterrupt(threadContext);
373 threadContext->state = THREAD_INTERRUPTING;
374 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
375 threadContext->debugger->state = DEBUGGER_EXITING;
376 }
377 ConditionWake(&threadContext->stateCond);
378 while (threadContext->state == THREAD_INTERRUPTING) {
379 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
380 }
381 MutexUnlock(&threadContext->stateMutex);
382}
383
384void GBAThreadContinue(struct GBAThread* threadContext) {
385 _changeState(threadContext, threadContext->savedState, 1);
386}
387
388void GBAThreadPause(struct GBAThread* threadContext) {
389 int frameOn = 1;
390 MutexLock(&threadContext->stateMutex);
391 _waitOnInterrupt(threadContext);
392 if (threadContext->state == THREAD_RUNNING) {
393 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
394 threadContext->debugger->state = DEBUGGER_EXITING;
395 }
396 threadContext->state = THREAD_PAUSING;
397 frameOn = 0;
398 }
399 MutexUnlock(&threadContext->stateMutex);
400 MutexLock(&threadContext->sync.videoFrameMutex);
401 if (frameOn != threadContext->sync.videoFrameOn) {
402 threadContext->sync.videoFrameOn = frameOn;
403 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
404 }
405 MutexUnlock(&threadContext->sync.videoFrameMutex);
406}
407
408void GBAThreadUnpause(struct GBAThread* threadContext) {
409 int frameOn = 1;
410 MutexLock(&threadContext->stateMutex);
411 _waitOnInterrupt(threadContext);
412 if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
413 threadContext->state = THREAD_RUNNING;
414 ConditionWake(&threadContext->stateCond);
415 }
416 MutexUnlock(&threadContext->stateMutex);
417 MutexLock(&threadContext->sync.videoFrameMutex);
418 if (frameOn != threadContext->sync.videoFrameOn) {
419 threadContext->sync.videoFrameOn = frameOn;
420 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
421 }
422 MutexUnlock(&threadContext->sync.videoFrameMutex);
423}
424
425bool GBAThreadIsPaused(struct GBAThread* threadContext) {
426 bool isPaused;
427 MutexLock(&threadContext->stateMutex);
428 _waitOnInterrupt(threadContext);
429 isPaused = threadContext->state == THREAD_PAUSED;
430 MutexUnlock(&threadContext->stateMutex);
431 return isPaused;
432}
433
434void GBAThreadTogglePause(struct GBAThread* threadContext) {
435 bool frameOn = true;
436 MutexLock(&threadContext->stateMutex);
437 _waitOnInterrupt(threadContext);
438 if (threadContext->state == THREAD_PAUSED) {
439 threadContext->state = THREAD_RUNNING;
440 ConditionWake(&threadContext->stateCond);
441 } else if (threadContext->state == THREAD_RUNNING) {
442 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
443 threadContext->debugger->state = DEBUGGER_EXITING;
444 }
445 threadContext->state = THREAD_PAUSED;
446 frameOn = false;
447 }
448 MutexUnlock(&threadContext->stateMutex);
449 MutexLock(&threadContext->sync.videoFrameMutex);
450 if (frameOn != threadContext->sync.videoFrameOn) {
451 threadContext->sync.videoFrameOn = frameOn;
452 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
453 }
454 MutexUnlock(&threadContext->sync.videoFrameMutex);
455}
456
457#ifdef USE_PTHREADS
458struct GBAThread* GBAThreadGetContext(void) {
459 pthread_once(&_contextOnce, _createTLS);
460 return pthread_getspecific(_contextKey);
461}
462#else
463struct GBAThread* GBAThreadGetContext(void) {
464 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
465 return TlsGetValue(_contextKey);
466}
467#endif
468
469void GBASyncPostFrame(struct GBASync* sync) {
470 if (!sync) {
471 return;
472 }
473
474 MutexLock(&sync->videoFrameMutex);
475 ++sync->videoFramePending;
476 --sync->videoFrameSkip;
477 if (sync->videoFrameSkip < 0) {
478 ConditionWake(&sync->videoFrameAvailableCond);
479 while (sync->videoFrameWait && sync->videoFramePending) {
480 ConditionWait(&sync->videoFrameRequiredCond, &sync->videoFrameMutex);
481 }
482 }
483 MutexUnlock(&sync->videoFrameMutex);
484
485 struct GBAThread* thread = GBAThreadGetContext();
486 if (thread->rewindBuffer) {
487 --thread->rewindBufferNext;
488 if (thread->rewindBufferNext <= 0) {
489 thread->rewindBufferNext = thread->rewindBufferInterval;
490 GBARecordFrame(thread);
491 }
492 }
493 if (thread->frameCallback) {
494 thread->frameCallback(thread);
495 }
496}
497
498bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) {
499 if (!sync) {
500 return true;
501 }
502
503 MutexLock(&sync->videoFrameMutex);
504 ConditionWake(&sync->videoFrameRequiredCond);
505 if (!sync->videoFrameOn) {
506 return false;
507 }
508 ConditionWait(&sync->videoFrameAvailableCond, &sync->videoFrameMutex);
509 sync->videoFramePending = 0;
510 sync->videoFrameSkip = frameskip;
511 return true;
512}
513
514void GBASyncWaitFrameEnd(struct GBASync* sync) {
515 if (!sync) {
516 return;
517 }
518
519 MutexUnlock(&sync->videoFrameMutex);
520}
521
522bool GBASyncDrawingFrame(struct GBASync* sync) {
523 return sync->videoFrameSkip <= 0;
524}
525
526void GBASyncProduceAudio(struct GBASync* sync, int wait) {
527 if (sync->audioWait && wait) {
528 // TODO loop properly in event of spurious wakeups
529 ConditionWait(&sync->audioRequiredCond, &sync->audioBufferMutex);
530 }
531 MutexUnlock(&sync->audioBufferMutex);
532}
533
534void GBASyncLockAudio(struct GBASync* sync) {
535 MutexLock(&sync->audioBufferMutex);
536}
537
538void GBASyncConsumeAudio(struct GBASync* sync) {
539 ConditionWake(&sync->audioRequiredCond);
540 MutexUnlock(&sync->audioBufferMutex);
541}