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