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