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