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