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 char* savedata = 0;
63
64 if (threadContext->debugger) {
65 components[numComponents] = &threadContext->debugger->d;
66 ++numComponents;
67 }
68
69#if !defined(_WIN32) && defined(USE_PTHREADS)
70 sigset_t signals;
71 sigemptyset(&signals);
72 pthread_sigmask(SIG_SETMASK, &signals, 0);
73#endif
74
75 gba.logHandler = threadContext->logHandler;
76 GBACreate(&gba);
77 ARMSetComponents(&cpu, &gba.d, numComponents, components);
78 ARMInit(&cpu);
79 ARMReset(&cpu);
80 threadContext->gba = &gba;
81 gba.sync = &threadContext->sync;
82 gba.logLevel = threadContext->logLevel;
83#ifdef USE_PTHREADS
84 pthread_setspecific(_contextKey, threadContext);
85#else
86 TlsSetValue(_contextKey, threadContext);
87#endif
88 if (threadContext->renderer) {
89 GBAVideoAssociateRenderer(&gba.video, threadContext->renderer);
90 }
91
92 if (threadContext->fd) {
93 if (threadContext->fname) {
94 char* dotPoint = strrchr(threadContext->fname, '.');
95 if (dotPoint > strrchr(threadContext->fname, '/') && dotPoint[1] && dotPoint[2] && dotPoint[3]) {
96 savedata = strdup(threadContext->fname);
97 dotPoint = strrchr(savedata, '.');
98 dotPoint[1] = 's';
99 dotPoint[2] = 'a';
100 dotPoint[3] = 'v';
101 dotPoint[4] = '\0';
102 } else if (dotPoint) {
103 savedata = malloc((dotPoint - threadContext->fname + 5) * sizeof(char));
104 strncpy(savedata, threadContext->fname, dotPoint - threadContext->fname + 1);
105 strcat(savedata, "sav");
106 } else {
107 savedata = malloc(strlen(threadContext->fname + 5));
108 strcpy(savedata, threadContext->fname);
109 strcat(savedata, "sav");
110 }
111 }
112 gba.savefile = savedata;
113 GBALoadROM(&gba, threadContext->fd, threadContext->fname);
114 if (threadContext->biosFd) {
115 GBALoadBIOS(&gba, threadContext->biosFd);
116 }
117
118 if (threadContext->patchFd && loadPatch(threadContext->patchFd, &patch)) {
119 GBAApplyPatch(&gba, &patch);
120 }
121 }
122
123 if (threadContext->debugger) {
124 GBAAttachDebugger(&gba, threadContext->debugger);
125 ARMDebuggerEnter(threadContext->debugger, DEBUGGER_ENTER_ATTACHED);
126 }
127
128 GBASIOSetDriverSet(&gba.sio, &threadContext->sioDrivers);
129
130 gba.keySource = &threadContext->activeKeys;
131
132 if (threadContext->startCallback) {
133 threadContext->startCallback(threadContext);
134 }
135
136 _changeState(threadContext, THREAD_RUNNING, 1);
137
138 while (threadContext->state < THREAD_EXITING) {
139 if (threadContext->debugger) {
140 struct ARMDebugger* debugger = threadContext->debugger;
141 ARMDebuggerRun(debugger);
142 if (debugger->state == DEBUGGER_SHUTDOWN) {
143 _changeState(threadContext, THREAD_EXITING, 0);
144 }
145 } else {
146 while (threadContext->state == THREAD_RUNNING) {
147 ARMRun(&cpu);
148 }
149 }
150
151 int resetScheduled = 0;
152 MutexLock(&threadContext->stateMutex);
153 if (threadContext->state == THREAD_PAUSING) {
154 threadContext->state = THREAD_PAUSED;
155 ConditionWake(&threadContext->stateCond);
156 }
157 if (threadContext->state == THREAD_INTERRUPTING) {
158 threadContext->state = THREAD_INTERRUPTED;
159 ConditionWake(&threadContext->stateCond);
160 }
161 if (threadContext->state == THREAD_RESETING) {
162 threadContext->state = THREAD_RUNNING;
163 resetScheduled = 1;
164 }
165 while (threadContext->state == THREAD_PAUSED) {
166 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
167 }
168 MutexUnlock(&threadContext->stateMutex);
169 if (resetScheduled) {
170 ARMReset(&cpu);
171 }
172 }
173
174 while (threadContext->state != THREAD_SHUTDOWN) {
175 _changeState(threadContext, THREAD_SHUTDOWN, 0);
176 }
177
178 if (threadContext->cleanCallback) {
179 threadContext->cleanCallback(threadContext);
180 }
181
182 threadContext->gba = 0;
183 ARMDeinit(&cpu);
184 GBADestroy(&gba);
185
186 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
187 ConditionWake(&threadContext->sync.audioRequiredCond);
188 free(savedata);
189
190 return 0;
191}
192
193void GBAMapOptionsToContext(struct StartupOptions* opts, struct GBAThread* threadContext) {
194 threadContext->fd = VFileFromFD(opts->fd);
195 threadContext->fname = opts->fname;
196 threadContext->biosFd = VFileFromFD(opts->biosFd);
197 threadContext->patchFd = VFileFromFD(opts->patchFd);
198 threadContext->frameskip = opts->frameskip;
199 threadContext->logLevel = opts->logLevel;
200 threadContext->rewindBufferCapacity = opts->rewindBufferCapacity;
201 threadContext->rewindBufferInterval = opts->rewindBufferInterval;
202}
203
204bool GBAThreadStart(struct GBAThread* threadContext) {
205 // TODO: error check
206 threadContext->activeKeys = 0;
207 threadContext->state = THREAD_INITIALIZED;
208 threadContext->sync.videoFrameOn = 1;
209 threadContext->sync.videoFrameSkip = 0;
210
211 threadContext->rewindBufferNext = threadContext->rewindBufferInterval;
212 threadContext->rewindBufferSize = 0;
213 if (threadContext->rewindBufferCapacity) {
214 threadContext->rewindBuffer = calloc(threadContext->rewindBufferCapacity, sizeof(void*));
215 } else {
216 threadContext->rewindBuffer = 0;
217 }
218
219 MutexInit(&threadContext->stateMutex);
220 ConditionInit(&threadContext->stateCond);
221
222 MutexInit(&threadContext->sync.videoFrameMutex);
223 ConditionInit(&threadContext->sync.videoFrameAvailableCond);
224 ConditionInit(&threadContext->sync.videoFrameRequiredCond);
225 MutexInit(&threadContext->sync.audioBufferMutex);
226 ConditionInit(&threadContext->sync.audioRequiredCond);
227
228#ifndef _WIN32
229 sigset_t signals;
230 sigemptyset(&signals);
231 sigaddset(&signals, SIGINT);
232 sigaddset(&signals, SIGTRAP);
233 pthread_sigmask(SIG_BLOCK, &signals, 0);
234#endif
235
236 MutexLock(&threadContext->stateMutex);
237 ThreadCreate(&threadContext->thread, _GBAThreadRun, threadContext);
238 while (threadContext->state < THREAD_RUNNING) {
239 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
240 }
241 MutexUnlock(&threadContext->stateMutex);
242
243 return true;
244}
245
246bool GBAThreadHasStarted(struct GBAThread* threadContext) {
247 bool hasStarted;
248 MutexLock(&threadContext->stateMutex);
249 hasStarted = threadContext->state > THREAD_INITIALIZED;
250 MutexUnlock(&threadContext->stateMutex);
251 return hasStarted;
252}
253
254void GBAThreadEnd(struct GBAThread* threadContext) {
255 MutexLock(&threadContext->stateMutex);
256 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
257 threadContext->debugger->state = DEBUGGER_EXITING;
258 }
259 threadContext->state = THREAD_EXITING;
260 MutexUnlock(&threadContext->stateMutex);
261 MutexLock(&threadContext->sync.audioBufferMutex);
262 threadContext->sync.audioWait = 0;
263 ConditionWake(&threadContext->sync.audioRequiredCond);
264 MutexUnlock(&threadContext->sync.audioBufferMutex);
265}
266
267void GBAThreadReset(struct GBAThread* threadContext) {
268 MutexLock(&threadContext->stateMutex);
269 _waitOnInterrupt(threadContext);
270 threadContext->state = THREAD_RESETING;
271 ConditionWake(&threadContext->stateCond);
272 MutexUnlock(&threadContext->stateMutex);
273}
274
275void GBAThreadJoin(struct GBAThread* threadContext) {
276 MutexLock(&threadContext->sync.videoFrameMutex);
277 threadContext->sync.videoFrameWait = 0;
278 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
279 MutexUnlock(&threadContext->sync.videoFrameMutex);
280
281 ThreadJoin(threadContext->thread);
282
283 MutexDeinit(&threadContext->stateMutex);
284 ConditionDeinit(&threadContext->stateCond);
285
286 MutexDeinit(&threadContext->sync.videoFrameMutex);
287 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
288 ConditionDeinit(&threadContext->sync.videoFrameAvailableCond);
289 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
290 ConditionDeinit(&threadContext->sync.videoFrameRequiredCond);
291
292 ConditionWake(&threadContext->sync.audioRequiredCond);
293 ConditionDeinit(&threadContext->sync.audioRequiredCond);
294 MutexDeinit(&threadContext->sync.audioBufferMutex);
295
296 int i;
297 for (i = 0; i < threadContext->rewindBufferCapacity; ++i) {
298 if (threadContext->rewindBuffer[i]) {
299 GBADeallocateState(threadContext->rewindBuffer[i]);
300 }
301 }
302 free(threadContext->rewindBuffer);
303}
304
305void GBAThreadInterrupt(struct GBAThread* threadContext) {
306 MutexLock(&threadContext->stateMutex);
307 threadContext->savedState = threadContext->state;
308 _waitOnInterrupt(threadContext);
309 threadContext->state = THREAD_INTERRUPTING;
310 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
311 threadContext->debugger->state = DEBUGGER_EXITING;
312 }
313 ConditionWake(&threadContext->stateCond);
314 while (threadContext->state == THREAD_INTERRUPTING) {
315 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
316 }
317 MutexUnlock(&threadContext->stateMutex);
318}
319
320void GBAThreadContinue(struct GBAThread* threadContext) {
321 _changeState(threadContext, threadContext->savedState, 1);
322}
323
324void GBAThreadPause(struct GBAThread* threadContext) {
325 int frameOn = 1;
326 MutexLock(&threadContext->stateMutex);
327 _waitOnInterrupt(threadContext);
328 if (threadContext->state == THREAD_RUNNING) {
329 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
330 threadContext->debugger->state = DEBUGGER_EXITING;
331 }
332 threadContext->state = THREAD_PAUSING;
333 frameOn = 0;
334 }
335 MutexUnlock(&threadContext->stateMutex);
336 MutexLock(&threadContext->sync.videoFrameMutex);
337 if (frameOn != threadContext->sync.videoFrameOn) {
338 threadContext->sync.videoFrameOn = frameOn;
339 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
340 }
341 MutexUnlock(&threadContext->sync.videoFrameMutex);
342}
343
344void GBAThreadUnpause(struct GBAThread* threadContext) {
345 int frameOn = 1;
346 MutexLock(&threadContext->stateMutex);
347 _waitOnInterrupt(threadContext);
348 if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
349 threadContext->state = THREAD_RUNNING;
350 ConditionWake(&threadContext->stateCond);
351 }
352 MutexUnlock(&threadContext->stateMutex);
353 MutexLock(&threadContext->sync.videoFrameMutex);
354 if (frameOn != threadContext->sync.videoFrameOn) {
355 threadContext->sync.videoFrameOn = frameOn;
356 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
357 }
358 MutexUnlock(&threadContext->sync.videoFrameMutex);
359}
360
361bool GBAThreadIsPaused(struct GBAThread* threadContext) {
362 bool isPaused;
363 MutexLock(&threadContext->stateMutex);
364 _waitOnInterrupt(threadContext);
365 isPaused = threadContext->state == THREAD_PAUSED;
366 MutexUnlock(&threadContext->stateMutex);
367 return isPaused;
368}
369
370void GBAThreadTogglePause(struct GBAThread* threadContext) {
371 bool frameOn = true;
372 MutexLock(&threadContext->stateMutex);
373 _waitOnInterrupt(threadContext);
374 if (threadContext->state == THREAD_PAUSED) {
375 threadContext->state = THREAD_RUNNING;
376 ConditionWake(&threadContext->stateCond);
377 } else if (threadContext->state == THREAD_RUNNING) {
378 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
379 threadContext->debugger->state = DEBUGGER_EXITING;
380 }
381 threadContext->state = THREAD_PAUSED;
382 frameOn = false;
383 }
384 MutexUnlock(&threadContext->stateMutex);
385 MutexLock(&threadContext->sync.videoFrameMutex);
386 if (frameOn != threadContext->sync.videoFrameOn) {
387 threadContext->sync.videoFrameOn = frameOn;
388 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
389 }
390 MutexUnlock(&threadContext->sync.videoFrameMutex);
391}
392
393#ifdef USE_PTHREADS
394struct GBAThread* GBAThreadGetContext(void) {
395 pthread_once(&_contextOnce, _createTLS);
396 return pthread_getspecific(_contextKey);
397}
398#else
399struct GBAThread* GBAThreadGetContext(void) {
400 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
401 return TlsGetValue(_contextKey);
402}
403#endif
404
405void GBASyncPostFrame(struct GBASync* sync) {
406 if (!sync) {
407 return;
408 }
409
410 MutexLock(&sync->videoFrameMutex);
411 ++sync->videoFramePending;
412 --sync->videoFrameSkip;
413 if (sync->videoFrameSkip < 0) {
414 ConditionWake(&sync->videoFrameAvailableCond);
415 while (sync->videoFrameWait && sync->videoFramePending) {
416 ConditionWait(&sync->videoFrameRequiredCond, &sync->videoFrameMutex);
417 }
418 }
419 MutexUnlock(&sync->videoFrameMutex);
420
421 struct GBAThread* thread = GBAThreadGetContext();
422 if (thread->rewindBuffer) {
423 --thread->rewindBufferNext;
424 if (thread->rewindBufferNext <= 0) {
425 thread->rewindBufferNext = thread->rewindBufferInterval;
426 GBARecordFrame(thread);
427 }
428 }
429 if (thread->frameCallback) {
430 thread->frameCallback(thread);
431 }
432}
433
434bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) {
435 if (!sync) {
436 return true;
437 }
438
439 MutexLock(&sync->videoFrameMutex);
440 ConditionWake(&sync->videoFrameRequiredCond);
441 if (!sync->videoFrameOn) {
442 return false;
443 }
444 ConditionWait(&sync->videoFrameAvailableCond, &sync->videoFrameMutex);
445 sync->videoFramePending = 0;
446 sync->videoFrameSkip = frameskip;
447 return true;
448}
449
450void GBASyncWaitFrameEnd(struct GBASync* sync) {
451 if (!sync) {
452 return;
453 }
454
455 MutexUnlock(&sync->videoFrameMutex);
456}
457
458bool GBASyncDrawingFrame(struct GBASync* sync) {
459 return sync->videoFrameSkip <= 0;
460}
461
462void GBASyncProduceAudio(struct GBASync* sync, int wait) {
463 if (sync->audioWait && wait) {
464 // TODO loop properly in event of spurious wakeups
465 ConditionWait(&sync->audioRequiredCond, &sync->audioBufferMutex);
466 }
467 MutexUnlock(&sync->audioBufferMutex);
468}
469
470void GBASyncLockAudio(struct GBASync* sync) {
471 MutexLock(&sync->audioBufferMutex);
472}
473
474void GBASyncConsumeAudio(struct GBASync* sync) {
475 ConditionWake(&sync->audioRequiredCond);
476 MutexUnlock(&sync->audioBufferMutex);
477}