all repos — mgba @ 6d12ef81a50c5ff070d2062ef256a6a4187eda86

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			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}