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
11#include <signal.h>
12
13#ifdef USE_PTHREADS
14static pthread_key_t _contextKey;
15static pthread_once_t _contextOnce = PTHREAD_ONCE_INIT;
16
17static void _createTLS(void) {
18 pthread_key_create(&_contextKey, 0);
19}
20#else
21static DWORD _contextKey;
22static INIT_ONCE _contextOnce = INIT_ONCE_STATIC_INIT;
23
24static BOOL CALLBACK _createTLS(PINIT_ONCE once, PVOID param, PVOID* context) {
25 UNUSED(once);
26 UNUSED(param);
27 UNUSED(context);
28 _contextKey = TlsAlloc();
29 return TRUE;
30}
31#endif
32
33static void _changeState(struct GBAThread* threadContext, enum ThreadState newState, int broadcast) {
34 MutexLock(&threadContext->stateMutex);
35 threadContext->state = newState;
36 if (broadcast) {
37 ConditionWake(&threadContext->stateCond);
38 }
39 MutexUnlock(&threadContext->stateMutex);
40}
41
42static void _waitOnInterrupt(struct GBAThread* threadContext) {
43 while (threadContext->state == THREAD_INTERRUPTED) {
44 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
45 }
46}
47
48static THREAD_ENTRY _GBAThreadRun(void* context) {
49#ifdef USE_PTHREADS
50 pthread_once(&_contextOnce, _createTLS);
51#else
52 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
53#endif
54
55 struct GBA gba;
56 struct ARMCore cpu;
57 struct Patch patch;
58 struct GBAThread* threadContext = context;
59 struct ARMComponent* components[1] = {};
60 int numComponents = 0;
61 char* savedata = 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 >= 0) {
92 if (threadContext->fname) {
93 char* dotPoint = strrchr(threadContext->fname, '.');
94 if (dotPoint > strrchr(threadContext->fname, '/') && dotPoint[1] && dotPoint[2] && dotPoint[3]) {
95 savedata = strdup(threadContext->fname);
96 dotPoint = strrchr(savedata, '.');
97 dotPoint[1] = 's';
98 dotPoint[2] = 'a';
99 dotPoint[3] = 'v';
100 dotPoint[4] = '\0';
101 } else if (dotPoint) {
102 savedata = malloc((dotPoint - threadContext->fname + 5) * sizeof(char));
103 strncpy(savedata, threadContext->fname, dotPoint - threadContext->fname + 1);
104 strcat(savedata, "sav");
105 } else {
106 savedata = malloc(strlen(threadContext->fname + 5));
107 strcpy(savedata, threadContext->fname);
108 strcat(savedata, "sav");
109 }
110 }
111 gba.savefile = savedata;
112 GBALoadROM(&gba, threadContext->fd, threadContext->fname);
113 if (threadContext->biosFd >= 0) {
114 GBALoadBIOS(&gba, threadContext->biosFd);
115 }
116
117 if (threadContext->patchFd >= 0 && loadPatch(threadContext->patchFd, &patch)) {
118 GBAApplyPatch(&gba, &patch);
119 }
120 }
121
122 if (threadContext->debugger) {
123 GBAAttachDebugger(&gba, threadContext->debugger);
124 ARMDebuggerEnter(threadContext->debugger, DEBUGGER_ENTER_ATTACHED);
125 }
126
127 GBASIOSetDriverSet(&gba.sio, &threadContext->sioDrivers);
128
129 gba.keySource = &threadContext->activeKeys;
130
131 if (threadContext->startCallback) {
132 threadContext->startCallback(threadContext);
133 }
134
135 _changeState(threadContext, THREAD_RUNNING, 1);
136
137 while (threadContext->state < THREAD_EXITING) {
138 if (threadContext->debugger) {
139 struct ARMDebugger* debugger = threadContext->debugger;
140 ARMDebuggerRun(debugger);
141 if (debugger->state == DEBUGGER_SHUTDOWN) {
142 _changeState(threadContext, THREAD_EXITING, 0);
143 }
144 } else {
145 while (threadContext->state == THREAD_RUNNING) {
146 ARMRun(&cpu);
147 }
148 }
149 MutexLock(&threadContext->stateMutex);
150 if (threadContext->state == THREAD_PAUSING) {
151 threadContext->state = THREAD_PAUSED;
152 ConditionWake(&threadContext->stateCond);
153 }
154 if (threadContext->state == THREAD_INTERRUPTING) {
155 threadContext->state = THREAD_INTERRUPTED;
156 ConditionWake(&threadContext->stateCond);
157 }
158 while (threadContext->state == THREAD_PAUSED) {
159 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
160 }
161 MutexUnlock(&threadContext->stateMutex);
162 }
163
164 while (threadContext->state != THREAD_SHUTDOWN) {
165 _changeState(threadContext, THREAD_SHUTDOWN, 0);
166 }
167
168 if (threadContext->cleanCallback) {
169 threadContext->cleanCallback(threadContext);
170 }
171
172 threadContext->gba = 0;
173 ARMDeinit(&cpu);
174 GBADestroy(&gba);
175
176 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
177 ConditionWake(&threadContext->sync.audioRequiredCond);
178 free(savedata);
179
180 return 0;
181}
182
183void GBAMapOptionsToContext(struct StartupOptions* opts, struct GBAThread* threadContext) {
184 threadContext->fd = opts->fd;
185 threadContext->fname = opts->fname;
186 threadContext->biosFd = opts->biosFd;
187 threadContext->patchFd = opts->patchFd;
188 threadContext->frameskip = opts->frameskip;
189 threadContext->logLevel = opts->logLevel;
190 threadContext->rewindBufferCapacity = opts->rewindBufferCapacity;
191 threadContext->rewindBufferInterval = opts->rewindBufferInterval;
192}
193
194int GBAThreadStart(struct GBAThread* threadContext) {
195 // TODO: error check
196 threadContext->activeKeys = 0;
197 threadContext->state = THREAD_INITIALIZED;
198 threadContext->sync.videoFrameOn = 1;
199 threadContext->sync.videoFrameSkip = 0;
200
201 threadContext->rewindBufferNext = threadContext->rewindBufferInterval;
202 threadContext->rewindBufferSize = 0;
203 if (threadContext->rewindBufferCapacity) {
204 threadContext->rewindBuffer = calloc(threadContext->rewindBufferCapacity, sizeof(void*));
205 } else {
206 threadContext->rewindBuffer = 0;
207 }
208
209 MutexInit(&threadContext->stateMutex);
210 ConditionInit(&threadContext->stateCond);
211
212 MutexInit(&threadContext->sync.videoFrameMutex);
213 ConditionInit(&threadContext->sync.videoFrameAvailableCond);
214 ConditionInit(&threadContext->sync.videoFrameRequiredCond);
215 MutexInit(&threadContext->sync.audioBufferMutex);
216 ConditionInit(&threadContext->sync.audioRequiredCond);
217
218#ifndef _WIN32
219 sigset_t signals;
220 sigemptyset(&signals);
221 sigaddset(&signals, SIGINT);
222 sigaddset(&signals, SIGTRAP);
223 pthread_sigmask(SIG_BLOCK, &signals, 0);
224#endif
225
226 MutexLock(&threadContext->stateMutex);
227 ThreadCreate(&threadContext->thread, _GBAThreadRun, threadContext);
228 while (threadContext->state < THREAD_RUNNING) {
229 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
230 }
231 MutexUnlock(&threadContext->stateMutex);
232
233 return 0;
234}
235
236int GBAThreadHasStarted(struct GBAThread* threadContext) {
237 int hasStarted;
238 MutexLock(&threadContext->stateMutex);
239 hasStarted = threadContext->state > THREAD_INITIALIZED;
240 MutexUnlock(&threadContext->stateMutex);
241 return hasStarted;
242}
243
244void GBAThreadEnd(struct GBAThread* threadContext) {
245 MutexLock(&threadContext->stateMutex);
246 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
247 threadContext->debugger->state = DEBUGGER_EXITING;
248 }
249 threadContext->state = THREAD_EXITING;
250 MutexUnlock(&threadContext->stateMutex);
251 MutexLock(&threadContext->sync.audioBufferMutex);
252 threadContext->sync.audioWait = 0;
253 ConditionWake(&threadContext->sync.audioRequiredCond);
254 MutexUnlock(&threadContext->sync.audioBufferMutex);
255}
256
257void GBAThreadJoin(struct GBAThread* threadContext) {
258 MutexLock(&threadContext->sync.videoFrameMutex);
259 threadContext->sync.videoFrameWait = 0;
260 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
261 MutexUnlock(&threadContext->sync.videoFrameMutex);
262
263 ThreadJoin(threadContext->thread);
264
265 MutexDeinit(&threadContext->stateMutex);
266 ConditionDeinit(&threadContext->stateCond);
267
268 MutexDeinit(&threadContext->sync.videoFrameMutex);
269 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
270 ConditionDeinit(&threadContext->sync.videoFrameAvailableCond);
271 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
272 ConditionDeinit(&threadContext->sync.videoFrameRequiredCond);
273
274 ConditionWake(&threadContext->sync.audioRequiredCond);
275 ConditionDeinit(&threadContext->sync.audioRequiredCond);
276 MutexDeinit(&threadContext->sync.audioBufferMutex);
277
278 int i;
279 for (i = 0; i < threadContext->rewindBufferCapacity; ++i) {
280 if (threadContext->rewindBuffer[i]) {
281 GBADeallocateState(threadContext->rewindBuffer[i]);
282 }
283 }
284 free(threadContext->rewindBuffer);
285}
286
287void GBAThreadInterrupt(struct GBAThread* threadContext) {
288 MutexLock(&threadContext->stateMutex);
289 threadContext->savedState = threadContext->state;
290 _waitOnInterrupt(threadContext);
291 threadContext->state = THREAD_INTERRUPTING;
292 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
293 threadContext->debugger->state = DEBUGGER_EXITING;
294 }
295 ConditionWake(&threadContext->stateCond);
296 while (threadContext->state == THREAD_INTERRUPTING) {
297 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
298 }
299 MutexUnlock(&threadContext->stateMutex);
300}
301
302void GBAThreadContinue(struct GBAThread* threadContext) {
303 _changeState(threadContext, threadContext->savedState, 1);
304}
305
306void GBAThreadPause(struct GBAThread* threadContext) {
307 int frameOn = 1;
308 MutexLock(&threadContext->stateMutex);
309 _waitOnInterrupt(threadContext);
310 if (threadContext->state == THREAD_RUNNING) {
311 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
312 threadContext->debugger->state = DEBUGGER_EXITING;
313 }
314 threadContext->state = THREAD_PAUSING;
315 frameOn = 0;
316 }
317 MutexUnlock(&threadContext->stateMutex);
318 MutexLock(&threadContext->sync.videoFrameMutex);
319 if (frameOn != threadContext->sync.videoFrameOn) {
320 threadContext->sync.videoFrameOn = frameOn;
321 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
322 }
323 MutexUnlock(&threadContext->sync.videoFrameMutex);
324}
325
326void GBAThreadUnpause(struct GBAThread* threadContext) {
327 int frameOn = 1;
328 MutexLock(&threadContext->stateMutex);
329 _waitOnInterrupt(threadContext);
330 if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
331 threadContext->state = THREAD_RUNNING;
332 ConditionWake(&threadContext->stateCond);
333 }
334 MutexUnlock(&threadContext->stateMutex);
335 MutexLock(&threadContext->sync.videoFrameMutex);
336 if (frameOn != threadContext->sync.videoFrameOn) {
337 threadContext->sync.videoFrameOn = frameOn;
338 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
339 }
340 MutexUnlock(&threadContext->sync.videoFrameMutex);
341}
342
343int GBAThreadIsPaused(struct GBAThread* threadContext) {
344 int isPaused;
345 MutexLock(&threadContext->stateMutex);
346 _waitOnInterrupt(threadContext);
347 isPaused = threadContext->state == THREAD_PAUSED;
348 MutexUnlock(&threadContext->stateMutex);
349 return isPaused;
350}
351
352void GBAThreadTogglePause(struct GBAThread* threadContext) {
353 int frameOn = 1;
354 MutexLock(&threadContext->stateMutex);
355 _waitOnInterrupt(threadContext);
356 if (threadContext->state == THREAD_PAUSED) {
357 threadContext->state = THREAD_RUNNING;
358 ConditionWake(&threadContext->stateCond);
359 } else if (threadContext->state == THREAD_RUNNING) {
360 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
361 threadContext->debugger->state = DEBUGGER_EXITING;
362 }
363 threadContext->state = THREAD_PAUSED;
364 frameOn = 0;
365 }
366 MutexUnlock(&threadContext->stateMutex);
367 MutexLock(&threadContext->sync.videoFrameMutex);
368 if (frameOn != threadContext->sync.videoFrameOn) {
369 threadContext->sync.videoFrameOn = frameOn;
370 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
371 }
372 MutexUnlock(&threadContext->sync.videoFrameMutex);
373}
374
375#ifdef USE_PTHREADS
376struct GBAThread* GBAThreadGetContext(void) {
377 pthread_once(&_contextOnce, _createTLS);
378 return pthread_getspecific(_contextKey);
379}
380#else
381struct GBAThread* GBAThreadGetContext(void) {
382 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
383 return TlsGetValue(_contextKey);
384}
385#endif
386
387void GBASyncPostFrame(struct GBASync* sync) {
388 if (!sync) {
389 return;
390 }
391
392 MutexLock(&sync->videoFrameMutex);
393 ++sync->videoFramePending;
394 --sync->videoFrameSkip;
395 if (sync->videoFrameSkip < 0) {
396 ConditionWake(&sync->videoFrameAvailableCond);
397 while (sync->videoFrameWait && sync->videoFramePending) {
398 ConditionWait(&sync->videoFrameRequiredCond, &sync->videoFrameMutex);
399 }
400 }
401 MutexUnlock(&sync->videoFrameMutex);
402
403 struct GBAThread* thread = GBAThreadGetContext();
404 if (thread->rewindBuffer) {
405 --thread->rewindBufferNext;
406 if (thread->rewindBufferNext <= 0) {
407 thread->rewindBufferNext = thread->rewindBufferInterval;
408 GBARecordFrame(thread);
409 }
410 }
411 if (thread->frameCallback) {
412 thread->frameCallback(thread);
413 }
414}
415
416int GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) {
417 if (!sync) {
418 return 1;
419 }
420
421 MutexLock(&sync->videoFrameMutex);
422 ConditionWake(&sync->videoFrameRequiredCond);
423 if (!sync->videoFrameOn) {
424 return 0;
425 }
426 ConditionWait(&sync->videoFrameAvailableCond, &sync->videoFrameMutex);
427 sync->videoFramePending = 0;
428 sync->videoFrameSkip = frameskip;
429 return 1;
430}
431
432void GBASyncWaitFrameEnd(struct GBASync* sync) {
433 if (!sync) {
434 return;
435 }
436
437 MutexUnlock(&sync->videoFrameMutex);
438}
439
440int GBASyncDrawingFrame(struct GBASync* sync) {
441 return sync->videoFrameSkip <= 0;
442}
443
444void GBASyncProduceAudio(struct GBASync* sync, int wait) {
445 if (sync->audioWait && wait) {
446 // TODO loop properly in event of spurious wakeups
447 ConditionWait(&sync->audioRequiredCond, &sync->audioBufferMutex);
448 }
449 MutexUnlock(&sync->audioBufferMutex);
450}
451
452void GBASyncLockAudio(struct GBASync* sync) {
453 MutexLock(&sync->audioBufferMutex);
454}
455
456void GBASyncConsumeAudio(struct GBASync* sync) {
457 ConditionWake(&sync->audioRequiredCond);
458 MutexUnlock(&sync->audioBufferMutex);
459}