all repos — mgba @ f0481d94135b9f671bea3847559431e67a07bf48

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