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