all repos — mgba @ 9f3cf19c2d11fe139a8fd9e65264c54b4116e1d2

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