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