all repos — mgba @ e9c441281988d973b8e9fc7956e983249f7c8acd

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