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