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