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 (void) (once);
26 (void) (param);
27 (void) (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 while (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_INTERRUPTED) {
151 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
152 }
153 MutexUnlock(&threadContext->stateMutex);
154 }
155
156 while (threadContext->state != THREAD_SHUTDOWN) {
157 _changeState(threadContext, THREAD_SHUTDOWN, 0);
158 }
159
160 if (threadContext->cleanCallback) {
161 threadContext->cleanCallback(threadContext);
162 }
163
164 threadContext->gba = 0;
165 ARMDeinit(&cpu);
166 GBADestroy(&gba);
167
168 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
169 ConditionWake(&threadContext->sync.audioRequiredCond);
170 free(savedata);
171
172 return 0;
173}
174
175void GBAMapOptionsToContext(struct StartupOptions* opts, struct GBAThread* threadContext) {
176 threadContext->fd = opts->fd;
177 threadContext->fname = opts->fname;
178 threadContext->biosFd = opts->biosFd;
179 threadContext->patchFd = opts->patchFd;
180 threadContext->frameskip = opts->frameskip;
181 threadContext->logLevel = opts->logLevel;
182 threadContext->rewindBufferCapacity = opts->rewindBufferCapacity;
183 threadContext->rewindBufferInterval = opts->rewindBufferInterval;
184}
185
186int GBAThreadStart(struct GBAThread* threadContext) {
187 // TODO: error check
188 threadContext->activeKeys = 0;
189 threadContext->state = THREAD_INITIALIZED;
190 threadContext->sync.videoFrameOn = 1;
191 threadContext->sync.videoFrameSkip = 0;
192
193 threadContext->rewindBufferNext = threadContext->rewindBufferInterval;
194 threadContext->rewindBufferSize = 0;
195 if (threadContext->rewindBufferCapacity) {
196 threadContext->rewindBuffer = calloc(threadContext->rewindBufferCapacity, sizeof(void*));
197 } else {
198 threadContext->rewindBuffer = 0;
199 }
200
201 MutexInit(&threadContext->stateMutex);
202 ConditionInit(&threadContext->stateCond);
203
204 MutexInit(&threadContext->sync.videoFrameMutex);
205 ConditionInit(&threadContext->sync.videoFrameAvailableCond);
206 ConditionInit(&threadContext->sync.videoFrameRequiredCond);
207 MutexInit(&threadContext->sync.audioBufferMutex);
208 ConditionInit(&threadContext->sync.audioRequiredCond);
209
210#ifndef _WIN32
211 sigset_t signals;
212 sigemptyset(&signals);
213 sigaddset(&signals, SIGINT);
214 sigaddset(&signals, SIGTRAP);
215 pthread_sigmask(SIG_BLOCK, &signals, 0);
216#endif
217
218 MutexLock(&threadContext->stateMutex);
219 ThreadCreate(&threadContext->thread, _GBAThreadRun, threadContext);
220 while (threadContext->state < THREAD_RUNNING) {
221 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
222 }
223 MutexUnlock(&threadContext->stateMutex);
224
225 return 0;
226}
227
228int GBAThreadHasStarted(struct GBAThread* threadContext) {
229 int hasStarted;
230 MutexLock(&threadContext->stateMutex);
231 hasStarted = threadContext->state > THREAD_INITIALIZED;
232 MutexUnlock(&threadContext->stateMutex);
233 return hasStarted;
234}
235
236void GBAThreadEnd(struct GBAThread* threadContext) {
237 MutexLock(&threadContext->stateMutex);
238 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
239 threadContext->debugger->state = DEBUGGER_EXITING;
240 }
241 threadContext->state = THREAD_EXITING;
242 MutexUnlock(&threadContext->stateMutex);
243 MutexLock(&threadContext->sync.audioBufferMutex);
244 threadContext->sync.audioWait = 0;
245 ConditionWake(&threadContext->sync.audioRequiredCond);
246 MutexUnlock(&threadContext->sync.audioBufferMutex);
247}
248
249void GBAThreadJoin(struct GBAThread* threadContext) {
250 MutexLock(&threadContext->sync.videoFrameMutex);
251 threadContext->sync.videoFrameWait = 0;
252 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
253 MutexUnlock(&threadContext->sync.videoFrameMutex);
254
255 ThreadJoin(threadContext->thread);
256
257 MutexDeinit(&threadContext->stateMutex);
258 ConditionDeinit(&threadContext->stateCond);
259
260 MutexDeinit(&threadContext->sync.videoFrameMutex);
261 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
262 ConditionDeinit(&threadContext->sync.videoFrameAvailableCond);
263 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
264 ConditionDeinit(&threadContext->sync.videoFrameRequiredCond);
265
266 ConditionWake(&threadContext->sync.audioRequiredCond);
267 ConditionDeinit(&threadContext->sync.audioRequiredCond);
268 MutexDeinit(&threadContext->sync.audioBufferMutex);
269
270 int i;
271 for (i = 0; i < threadContext->rewindBufferCapacity; ++i) {
272 if (threadContext->rewindBuffer[i]) {
273 GBADeallocateState(threadContext->rewindBuffer[i]);
274 }
275 }
276 free(threadContext->rewindBuffer);
277}
278
279void GBAThreadInterrupt(struct GBAThread* threadContext) {
280 MutexLock(&threadContext->stateMutex);
281 _waitOnInterrupt(threadContext);
282 threadContext->savedState = threadContext->state;
283 threadContext->state = THREAD_INTERRUPTED;
284 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
285 threadContext->debugger->state = DEBUGGER_EXITING;
286 }
287 MutexUnlock(&threadContext->stateMutex);
288}
289
290void GBAThreadContinue(struct GBAThread* threadContext) {
291 _changeState(threadContext, threadContext->savedState, 1);
292}
293
294void GBAThreadPause(struct GBAThread* threadContext) {
295 int frameOn = 1;
296 MutexLock(&threadContext->stateMutex);
297 _waitOnInterrupt(threadContext);
298 if (threadContext->state == THREAD_RUNNING) {
299 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
300 threadContext->debugger->state = DEBUGGER_EXITING;
301 }
302 threadContext->state = THREAD_PAUSED;
303 frameOn = 0;
304 }
305 MutexUnlock(&threadContext->stateMutex);
306 MutexLock(&threadContext->sync.videoFrameMutex);
307 if (frameOn != threadContext->sync.videoFrameOn) {
308 threadContext->sync.videoFrameOn = frameOn;
309 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
310 }
311 MutexUnlock(&threadContext->sync.videoFrameMutex);
312}
313
314void GBAThreadUnpause(struct GBAThread* threadContext) {
315 int frameOn = 1;
316 MutexLock(&threadContext->stateMutex);
317 _waitOnInterrupt(threadContext);
318 if (threadContext->state == THREAD_PAUSED) {
319 threadContext->state = THREAD_RUNNING;
320 ConditionWake(&threadContext->stateCond);
321 }
322 MutexUnlock(&threadContext->stateMutex);
323 MutexLock(&threadContext->sync.videoFrameMutex);
324 if (frameOn != threadContext->sync.videoFrameOn) {
325 threadContext->sync.videoFrameOn = frameOn;
326 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
327 }
328 MutexUnlock(&threadContext->sync.videoFrameMutex);
329}
330
331int GBAThreadIsPaused(struct GBAThread* threadContext) {
332 int isPaused;
333 MutexLock(&threadContext->stateMutex);
334 _waitOnInterrupt(threadContext);
335 isPaused = threadContext->state == THREAD_PAUSED;
336 MutexUnlock(&threadContext->stateMutex);
337 return isPaused;
338}
339
340void GBAThreadTogglePause(struct GBAThread* threadContext) {
341 int frameOn = 1;
342 MutexLock(&threadContext->stateMutex);
343 _waitOnInterrupt(threadContext);
344 if (threadContext->state == THREAD_PAUSED) {
345 threadContext->state = THREAD_RUNNING;
346 ConditionWake(&threadContext->stateCond);
347 } else if (threadContext->state == THREAD_RUNNING) {
348 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
349 threadContext->debugger->state = DEBUGGER_EXITING;
350 }
351 threadContext->state = THREAD_PAUSED;
352 frameOn = 0;
353 }
354 MutexUnlock(&threadContext->stateMutex);
355 MutexLock(&threadContext->sync.videoFrameMutex);
356 if (frameOn != threadContext->sync.videoFrameOn) {
357 threadContext->sync.videoFrameOn = frameOn;
358 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
359 }
360 MutexUnlock(&threadContext->sync.videoFrameMutex);
361}
362
363#ifdef USE_PTHREADS
364struct GBAThread* GBAThreadGetContext(void) {
365 pthread_once(&_contextOnce, _createTLS);
366 return pthread_getspecific(_contextKey);
367}
368#else
369struct GBAThread* GBAThreadGetContext(void) {
370 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
371 return TlsGetValue(_contextKey);
372}
373#endif
374
375void GBASyncPostFrame(struct GBASync* sync) {
376 if (!sync) {
377 return;
378 }
379
380 MutexLock(&sync->videoFrameMutex);
381 ++sync->videoFramePending;
382 --sync->videoFrameSkip;
383 if (sync->videoFrameSkip < 0) {
384 ConditionWake(&sync->videoFrameAvailableCond);
385 while (sync->videoFrameWait && sync->videoFramePending) {
386 ConditionWait(&sync->videoFrameRequiredCond, &sync->videoFrameMutex);
387 }
388 }
389 MutexUnlock(&sync->videoFrameMutex);
390
391 struct GBAThread* thread = GBAThreadGetContext();
392 if (thread->rewindBuffer) {
393 --thread->rewindBufferNext;
394 if (thread->rewindBufferNext <= 0) {
395 thread->rewindBufferNext = thread->rewindBufferInterval;
396 GBARecordFrame(thread);
397 }
398 }
399 if (thread->frameCallback) {
400 thread->frameCallback(thread);
401 }
402}
403
404int GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) {
405 if (!sync) {
406 return 1;
407 }
408
409 MutexLock(&sync->videoFrameMutex);
410 ConditionWake(&sync->videoFrameRequiredCond);
411 if (!sync->videoFrameOn) {
412 return 0;
413 }
414 ConditionWait(&sync->videoFrameAvailableCond, &sync->videoFrameMutex);
415 sync->videoFramePending = 0;
416 sync->videoFrameSkip = frameskip;
417 return 1;
418}
419
420void GBASyncWaitFrameEnd(struct GBASync* sync) {
421 if (!sync) {
422 return;
423 }
424
425 MutexUnlock(&sync->videoFrameMutex);
426}
427
428int GBASyncDrawingFrame(struct GBASync* sync) {
429 return sync->videoFrameSkip <= 0;
430}
431
432void GBASyncProduceAudio(struct GBASync* sync, int wait) {
433 if (sync->audioWait && wait) {
434 // TODO loop properly in event of spurious wakeups
435 ConditionWait(&sync->audioRequiredCond, &sync->audioBufferMutex);
436 }
437 MutexUnlock(&sync->audioBufferMutex);
438}
439
440void GBASyncLockAudio(struct GBASync* sync) {
441 MutexLock(&sync->audioBufferMutex);
442}
443
444void GBASyncConsumeAudio(struct GBASync* sync) {
445 ConditionWake(&sync->audioRequiredCond);
446 MutexUnlock(&sync->audioBufferMutex);
447}