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
7#include <stdlib.h>
8#include <signal.h>
9
10#ifdef USE_PTHREADS
11static pthread_key_t _contextKey;
12static pthread_once_t _contextOnce = PTHREAD_ONCE_INIT;
13
14static void _createTLS(void) {
15 pthread_key_create(&_contextKey, 0);
16}
17#else
18static DWORD _contextKey;
19static INIT_ONCE _contextOnce = INIT_ONCE_STATIC_INIT;
20
21static BOOL CALLBACK _createTLS(PINIT_ONCE once, PVOID param, PVOID* context) {
22 (void) (once);
23 (void) (param);
24 (void) (context);
25 _contextKey = TlsAlloc();
26 return TRUE;
27}
28#endif
29
30static void _changeState(struct GBAThread* threadContext, enum ThreadState newState, int broadcast) {
31 MutexLock(&threadContext->stateMutex);
32 threadContext->state = newState;
33 if (broadcast) {
34 ConditionWake(&threadContext->stateCond);
35 }
36 MutexUnlock(&threadContext->stateMutex);
37}
38
39static THREAD_ENTRY _GBAThreadRun(void* context) {
40#ifdef USE_PTHREADS
41 pthread_once(&_contextOnce, _createTLS);
42#else
43 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
44#endif
45
46#ifdef USE_DEBUGGER
47 struct ARMDebugger debugger;
48#endif
49 struct GBA gba;
50 struct GBAThread* threadContext = context;
51 char* savedata = 0;
52
53#if !defined(_WIN32) && defined(USE_PTHREADS)
54 sigset_t signals;
55 sigfillset(&signals);
56 pthread_sigmask(SIG_UNBLOCK, &signals, 0);
57#endif
58
59 GBAInit(&gba);
60 threadContext->gba = &gba;
61 gba.sync = &threadContext->sync;
62#ifdef USE_PTHREADS
63 pthread_setspecific(_contextKey, threadContext);
64#else
65 TlsSetValue(_contextKey, threadContext);
66#endif
67 if (threadContext->renderer) {
68 GBAVideoAssociateRenderer(&gba.video, threadContext->renderer);
69 }
70
71 if (threadContext->fd >= 0) {
72 if (threadContext->fname) {
73 char* dotPoint = strrchr(threadContext->fname, '.');
74 if (dotPoint > strrchr(threadContext->fname, '/') && dotPoint[1] && dotPoint[2] && dotPoint[3]) {
75 savedata = strdup(threadContext->fname);
76 dotPoint = strrchr(savedata, '.');
77 dotPoint[1] = 's';
78 dotPoint[2] = 'a';
79 dotPoint[3] = 'v';
80 dotPoint[4] = '\0';
81 } else if (dotPoint) {
82 savedata = malloc((dotPoint - threadContext->fname + 5) * sizeof(char));
83 strncpy(savedata, threadContext->fname, dotPoint - threadContext->fname + 1);
84 strcat(savedata, "sav");
85 } else {
86 savedata = malloc(strlen(threadContext->fname + 5));
87 strcpy(savedata, threadContext->fname);
88 strcat(savedata, "sav");
89 }
90 }
91 gba.savefile = savedata;
92 GBALoadROM(&gba, threadContext->fd, threadContext->fname);
93 }
94
95#ifdef USE_DEBUGGER
96 if (threadContext->useDebugger) {
97 threadContext->debugger = &debugger;
98 GBAAttachDebugger(&gba, &debugger);
99 } else {
100 threadContext->debugger = 0;
101 }
102#else
103 threadContext->debugger = 0;
104#endif
105
106 gba.keySource = &threadContext->activeKeys;
107
108 if (threadContext->startCallback) {
109 threadContext->startCallback(threadContext);
110 }
111
112 _changeState(threadContext, THREAD_RUNNING, 1);
113
114 while (threadContext->state < THREAD_EXITING) {
115#ifdef USE_DEBUGGER
116 if (threadContext->useDebugger) {
117 ARMDebuggerRun(&debugger);
118 if (debugger.state == DEBUGGER_SHUTDOWN) {
119 _changeState(threadContext, THREAD_EXITING, 0);
120 }
121 } else {
122#endif
123 while (threadContext->state == THREAD_RUNNING) {
124 ARMRun(&gba.cpu);
125 }
126#ifdef USE_DEBUGGER
127 }
128#endif
129 MutexLock(&threadContext->stateMutex);
130 while (threadContext->state == THREAD_PAUSED) {
131 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
132 }
133 MutexUnlock(&threadContext->stateMutex);
134 }
135
136 while (threadContext->state != THREAD_SHUTDOWN) {
137 _changeState(threadContext, THREAD_SHUTDOWN, 0);
138 }
139
140 if (threadContext->cleanCallback) {
141 threadContext->cleanCallback(threadContext);
142 }
143
144 GBADeinit(&gba);
145
146 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
147 ConditionWake(&threadContext->sync.audioRequiredCond);
148 free(savedata);
149
150 return 0;
151}
152
153int GBAThreadStart(struct GBAThread* threadContext) {
154 // TODO: error check
155 threadContext->activeKeys = 0;
156 threadContext->state = THREAD_INITIALIZED;
157 threadContext->sync.videoFrameOn = 1;
158 threadContext->sync.videoFrameSkip = 0;
159
160 MutexInit(&threadContext->stateMutex);
161 ConditionInit(&threadContext->stateCond);
162
163 MutexInit(&threadContext->sync.videoFrameMutex);
164 ConditionInit(&threadContext->sync.videoFrameAvailableCond);
165 ConditionInit(&threadContext->sync.videoFrameRequiredCond);
166 MutexInit(&threadContext->sync.audioBufferMutex);
167 ConditionInit(&threadContext->sync.audioRequiredCond);
168
169 MutexLock(&threadContext->stateMutex);
170 ThreadCreate(&threadContext->thread, _GBAThreadRun, threadContext);
171 while (threadContext->state < THREAD_RUNNING) {
172 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
173 }
174 MutexUnlock(&threadContext->stateMutex);
175
176 return 0;
177}
178
179void GBAThreadJoin(struct GBAThread* threadContext) {
180 MutexLock(&threadContext->sync.videoFrameMutex);
181 threadContext->sync.videoFrameWait = 0;
182 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
183 MutexUnlock(&threadContext->sync.videoFrameMutex);
184
185 ThreadJoin(threadContext->thread);
186
187 MutexDeinit(&threadContext->stateMutex);
188 ConditionDeinit(&threadContext->stateCond);
189
190 MutexDeinit(&threadContext->sync.videoFrameMutex);
191 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
192 ConditionDeinit(&threadContext->sync.videoFrameAvailableCond);
193 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
194 ConditionDeinit(&threadContext->sync.videoFrameRequiredCond);
195
196 ConditionWake(&threadContext->sync.audioRequiredCond);
197 ConditionDeinit(&threadContext->sync.audioRequiredCond);
198 MutexDeinit(&threadContext->sync.audioBufferMutex);
199}
200
201void GBAThreadPause(struct GBAThread* threadContext) {
202 int frameOn = 1;
203 MutexLock(&threadContext->stateMutex);
204 if (threadContext->state == THREAD_RUNNING) {
205 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
206 threadContext->debugger->state = DEBUGGER_EXITING;
207 }
208 threadContext->state = THREAD_PAUSED;
209 frameOn = 0;
210 }
211 MutexUnlock(&threadContext->stateMutex);
212 MutexLock(&threadContext->sync.videoFrameMutex);
213 if (frameOn != threadContext->sync.videoFrameOn) {
214 threadContext->sync.videoFrameOn = frameOn;
215 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
216 }
217 MutexUnlock(&threadContext->sync.videoFrameMutex);
218}
219
220void GBAThreadUnpause(struct GBAThread* threadContext) {
221 int frameOn = 1;
222 MutexLock(&threadContext->stateMutex);
223 if (threadContext->state == THREAD_PAUSED) {
224 threadContext->state = THREAD_RUNNING;
225 ConditionWake(&threadContext->stateCond);
226 }
227 MutexUnlock(&threadContext->stateMutex);
228 MutexLock(&threadContext->sync.videoFrameMutex);
229 if (frameOn != threadContext->sync.videoFrameOn) {
230 threadContext->sync.videoFrameOn = frameOn;
231 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
232 }
233 MutexUnlock(&threadContext->sync.videoFrameMutex);
234}
235
236void GBAThreadTogglePause(struct GBAThread* threadContext) {
237 int frameOn = 1;
238 MutexLock(&threadContext->stateMutex);
239 if (threadContext->state == THREAD_PAUSED) {
240 threadContext->state = THREAD_RUNNING;
241 ConditionWake(&threadContext->stateCond);
242 } else 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
258
259#ifdef USE_PTHREADS
260struct GBAThread* GBAThreadGetContext(void) {
261 pthread_once(&_contextOnce, _createTLS);
262 return pthread_getspecific(_contextKey);
263}
264#else
265struct GBAThread* GBAThreadGetContext(void) {
266 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
267 return TlsGetValue(_contextKey);
268}
269#endif
270
271void GBASyncPostFrame(struct GBASync* sync) {
272 if (!sync) {
273 return;
274 }
275
276 MutexLock(&sync->videoFrameMutex);
277 ++sync->videoFramePending;
278 --sync->videoFrameSkip;
279 if (sync->videoFrameSkip < 0) {
280 ConditionWake(&sync->videoFrameAvailableCond);
281 while (sync->videoFrameWait && sync->videoFramePending) {
282 ConditionWait(&sync->videoFrameRequiredCond, &sync->videoFrameMutex);
283 }
284 }
285 MutexUnlock(&sync->videoFrameMutex);
286}
287
288int GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) {
289 if (!sync) {
290 return 1;
291 }
292
293 MutexLock(&sync->videoFrameMutex);
294 ConditionWake(&sync->videoFrameRequiredCond);
295 if (!sync->videoFrameOn) {
296 return 0;
297 }
298 ConditionWait(&sync->videoFrameAvailableCond, &sync->videoFrameMutex);
299 sync->videoFramePending = 0;
300 sync->videoFrameSkip = frameskip;
301 return 1;
302}
303
304void GBASyncWaitFrameEnd(struct GBASync* sync) {
305 if (!sync) {
306 return;
307 }
308
309 MutexUnlock(&sync->videoFrameMutex);
310}
311
312int GBASyncDrawingFrame(struct GBASync* sync) {
313 return sync->videoFrameSkip <= 0;
314}
315
316void GBASyncProduceAudio(struct GBASync* sync, int wait) {
317 if (sync->audioWait && wait) {
318 // TODO loop properly in event of spurious wakeups
319 ConditionWait(&sync->audioRequiredCond, &sync->audioBufferMutex);
320 }
321 MutexUnlock(&sync->audioBufferMutex);
322}
323
324void GBASyncLockAudio(struct GBASync* sync) {
325 MutexLock(&sync->audioBufferMutex);
326}
327
328void GBASyncConsumeAudio(struct GBASync* sync) {
329 ConditionWake(&sync->audioRequiredCond);
330 MutexUnlock(&sync->audioBufferMutex);
331}