all repos — mgba @ 11e3bdc58577540dfe888b68c8b4249ed16e025c

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