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 MutexLock(&threadContext->sync.audioBufferMutex);
208 threadContext->sync.audioWait = 0;
209 ConditionWake(&threadContext->sync.audioRequiredCond);
210 MutexUnlock(&threadContext->sync.audioBufferMutex);
211}
212
213void GBAThreadJoin(struct GBAThread* threadContext) {
214 MutexLock(&threadContext->sync.videoFrameMutex);
215 threadContext->sync.videoFrameWait = 0;
216 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
217 MutexUnlock(&threadContext->sync.videoFrameMutex);
218
219 ThreadJoin(threadContext->thread);
220
221 MutexDeinit(&threadContext->stateMutex);
222 ConditionDeinit(&threadContext->stateCond);
223
224 MutexDeinit(&threadContext->sync.videoFrameMutex);
225 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
226 ConditionDeinit(&threadContext->sync.videoFrameAvailableCond);
227 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
228 ConditionDeinit(&threadContext->sync.videoFrameRequiredCond);
229
230 ConditionWake(&threadContext->sync.audioRequiredCond);
231 ConditionDeinit(&threadContext->sync.audioRequiredCond);
232 MutexDeinit(&threadContext->sync.audioBufferMutex);
233
234 int i;
235 for (i = 0; i < threadContext->rewindBufferCapacity; ++i) {
236 if (threadContext->rewindBuffer[i]) {
237 GBADeallocateState(threadContext->rewindBuffer[i]);
238 }
239 }
240 free(threadContext->rewindBuffer);
241}
242
243void GBAThreadPause(struct GBAThread* threadContext) {
244 int frameOn = 1;
245 MutexLock(&threadContext->stateMutex);
246 if (threadContext->state == THREAD_RUNNING) {
247 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
248 threadContext->debugger->state = DEBUGGER_EXITING;
249 }
250 threadContext->state = THREAD_PAUSED;
251 frameOn = 0;
252 }
253 MutexUnlock(&threadContext->stateMutex);
254 MutexLock(&threadContext->sync.videoFrameMutex);
255 if (frameOn != threadContext->sync.videoFrameOn) {
256 threadContext->sync.videoFrameOn = frameOn;
257 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
258 }
259 MutexUnlock(&threadContext->sync.videoFrameMutex);
260}
261
262void GBAThreadUnpause(struct GBAThread* threadContext) {
263 int frameOn = 1;
264 MutexLock(&threadContext->stateMutex);
265 if (threadContext->state == THREAD_PAUSED) {
266 threadContext->state = THREAD_RUNNING;
267 ConditionWake(&threadContext->stateCond);
268 }
269 MutexUnlock(&threadContext->stateMutex);
270 MutexLock(&threadContext->sync.videoFrameMutex);
271 if (frameOn != threadContext->sync.videoFrameOn) {
272 threadContext->sync.videoFrameOn = frameOn;
273 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
274 }
275 MutexUnlock(&threadContext->sync.videoFrameMutex);
276}
277
278int GBAThreadIsPaused(struct GBAThread* threadContext) {
279 int isPaused;
280 MutexLock(&threadContext->stateMutex);
281 isPaused = threadContext->state == THREAD_PAUSED;
282 MutexUnlock(&threadContext->stateMutex);
283 return isPaused;
284}
285
286void GBAThreadTogglePause(struct GBAThread* threadContext) {
287 int frameOn = 1;
288 MutexLock(&threadContext->stateMutex);
289 if (threadContext->state == THREAD_PAUSED) {
290 threadContext->state = THREAD_RUNNING;
291 ConditionWake(&threadContext->stateCond);
292 } else if (threadContext->state == THREAD_RUNNING) {
293 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
294 threadContext->debugger->state = DEBUGGER_EXITING;
295 }
296 threadContext->state = THREAD_PAUSED;
297 frameOn = 0;
298 }
299 MutexUnlock(&threadContext->stateMutex);
300 MutexLock(&threadContext->sync.videoFrameMutex);
301 if (frameOn != threadContext->sync.videoFrameOn) {
302 threadContext->sync.videoFrameOn = frameOn;
303 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
304 }
305 MutexUnlock(&threadContext->sync.videoFrameMutex);
306}
307
308#ifdef USE_PTHREADS
309struct GBAThread* GBAThreadGetContext(void) {
310 pthread_once(&_contextOnce, _createTLS);
311 return pthread_getspecific(_contextKey);
312}
313#else
314struct GBAThread* GBAThreadGetContext(void) {
315 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
316 return TlsGetValue(_contextKey);
317}
318#endif
319
320void GBASyncPostFrame(struct GBASync* sync) {
321 if (!sync) {
322 return;
323 }
324
325 MutexLock(&sync->videoFrameMutex);
326 ++sync->videoFramePending;
327 --sync->videoFrameSkip;
328 if (sync->videoFrameSkip < 0) {
329 ConditionWake(&sync->videoFrameAvailableCond);
330 while (sync->videoFrameWait && sync->videoFramePending) {
331 ConditionWait(&sync->videoFrameRequiredCond, &sync->videoFrameMutex);
332 }
333 }
334 MutexUnlock(&sync->videoFrameMutex);
335
336 struct GBAThread* thread = GBAThreadGetContext();
337 if (thread->rewindBuffer) {
338 --thread->rewindBufferNext;
339 if (thread->rewindBufferNext <= 0) {
340 thread->rewindBufferNext = thread->rewindBufferInterval;
341 GBARecordFrame(thread);
342 }
343 }
344 if (thread->frameCallback) {
345 thread->frameCallback(thread);
346 }
347}
348
349int GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) {
350 if (!sync) {
351 return 1;
352 }
353
354 MutexLock(&sync->videoFrameMutex);
355 ConditionWake(&sync->videoFrameRequiredCond);
356 if (!sync->videoFrameOn) {
357 return 0;
358 }
359 ConditionWait(&sync->videoFrameAvailableCond, &sync->videoFrameMutex);
360 sync->videoFramePending = 0;
361 sync->videoFrameSkip = frameskip;
362 return 1;
363}
364
365void GBASyncWaitFrameEnd(struct GBASync* sync) {
366 if (!sync) {
367 return;
368 }
369
370 MutexUnlock(&sync->videoFrameMutex);
371}
372
373int GBASyncDrawingFrame(struct GBASync* sync) {
374 return sync->videoFrameSkip <= 0;
375}
376
377void GBASyncProduceAudio(struct GBASync* sync, int wait) {
378 if (sync->audioWait && wait) {
379 // TODO loop properly in event of spurious wakeups
380 ConditionWait(&sync->audioRequiredCond, &sync->audioBufferMutex);
381 }
382 MutexUnlock(&sync->audioBufferMutex);
383}
384
385void GBASyncLockAudio(struct GBASync* sync) {
386 MutexLock(&sync->audioBufferMutex);
387}
388
389void GBASyncConsumeAudio(struct GBASync* sync) {
390 ConditionWake(&sync->audioRequiredCond);
391 MutexUnlock(&sync->audioBufferMutex);
392}