all repos — mgba @ 70e16613420f3f84149810e9621c4cb65c52bd2c

mGBA Game Boy Advance Emulator

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