all repos — mgba @ 73d32e7cc700ced48b747cbbf3c57eaefd7b4643

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