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