all repos — mgba @ 9d351d4a5835da48fd7a4ed24f7cba27adf0ce74

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