all repos — mgba @ b1a648e46e7878c4eebf457699bbf444fbeecf48

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
  7#include <stdlib.h>
  8#include <signal.h>
  9
 10#ifdef USE_PTHREADS
 11static pthread_key_t _contextKey;
 12static pthread_once_t _contextOnce = PTHREAD_ONCE_INIT;
 13
 14static void _createTLS(void) {
 15	pthread_key_create(&_contextKey, 0);
 16}
 17#else
 18static DWORD _contextKey;
 19static INIT_ONCE _contextOnce = INIT_ONCE_STATIC_INIT;
 20
 21static BOOL CALLBACK _createTLS(PINIT_ONCE once, PVOID param, PVOID* context) {
 22	(void) (once);
 23	(void) (param);
 24	(void) (context);
 25	_contextKey = TlsAlloc();
 26	return TRUE;
 27}
 28#endif
 29
 30static void _changeState(struct GBAThread* threadContext, enum ThreadState newState, int broadcast) {
 31	MutexLock(&threadContext->stateMutex);
 32	threadContext->state = newState;
 33	if (broadcast) {
 34		ConditionWake(&threadContext->stateCond);
 35	}
 36	MutexUnlock(&threadContext->stateMutex);
 37}
 38
 39static THREAD_ENTRY _GBAThreadRun(void* context) {
 40#ifdef USE_PTHREADS
 41	pthread_once(&_contextOnce, _createTLS);
 42#else
 43	InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
 44#endif
 45
 46#ifdef USE_DEBUGGER
 47	struct ARMDebugger debugger;
 48#endif
 49	struct GBA gba;
 50	struct GBAThread* threadContext = context;
 51	char* savedata = 0;
 52
 53#if !defined(_WIN32) && defined(USE_PTHREADS)
 54	sigset_t signals;
 55	sigemptyset(&signals);
 56	pthread_sigmask(SIG_SETMASK, &signals, 0);
 57#endif
 58
 59	GBAInit(&gba);
 60	threadContext->gba = &gba;
 61	gba.sync = &threadContext->sync;
 62#ifdef USE_PTHREADS
 63	pthread_setspecific(_contextKey, threadContext);
 64#else
 65	TlsSetValue(_contextKey, threadContext);
 66#endif
 67	if (threadContext->renderer) {
 68		GBAVideoAssociateRenderer(&gba.video, threadContext->renderer);
 69	}
 70
 71	if (threadContext->fd >= 0) {
 72		if (threadContext->fname) {
 73			char* dotPoint = strrchr(threadContext->fname, '.');
 74			if (dotPoint > strrchr(threadContext->fname, '/') && dotPoint[1] && dotPoint[2] && dotPoint[3]) {
 75				savedata = strdup(threadContext->fname);
 76				dotPoint = strrchr(savedata, '.');
 77				dotPoint[1] = 's';
 78				dotPoint[2] = 'a';
 79				dotPoint[3] = 'v';
 80				dotPoint[4] = '\0';
 81			} else if (dotPoint) {
 82				savedata = malloc((dotPoint - threadContext->fname + 5) * sizeof(char));
 83				strncpy(savedata, threadContext->fname, dotPoint - threadContext->fname + 1);
 84				strcat(savedata, "sav");
 85			} else {
 86				savedata = malloc(strlen(threadContext->fname + 5));
 87				strcpy(savedata, threadContext->fname);
 88				strcat(savedata, "sav");
 89			}
 90		}
 91		gba.savefile = savedata;
 92		GBALoadROM(&gba, threadContext->fd, threadContext->fname);
 93	}
 94
 95#ifdef USE_DEBUGGER
 96	if (threadContext->useDebugger) {
 97		threadContext->debugger = &debugger;
 98		GBAAttachDebugger(&gba, &debugger);
 99	} else {
100		threadContext->debugger = 0;
101	}
102#else
103	threadContext->debugger = 0;
104#endif
105
106	gba.keySource = &threadContext->activeKeys;
107
108	if (threadContext->startCallback) {
109		threadContext->startCallback(threadContext);
110	}
111
112	_changeState(threadContext, THREAD_RUNNING, 1);
113
114	while (threadContext->state < THREAD_EXITING) {
115#ifdef USE_DEBUGGER
116		if (threadContext->useDebugger) {
117			ARMDebuggerRun(&debugger);
118			if (debugger.state == DEBUGGER_SHUTDOWN) {
119				_changeState(threadContext, THREAD_EXITING, 0);
120			}
121		} else {
122#endif
123			while (threadContext->state == THREAD_RUNNING) {
124				ARMRun(&gba.cpu);
125			}
126#ifdef USE_DEBUGGER
127		}
128#endif
129		MutexLock(&threadContext->stateMutex);
130		while (threadContext->state == THREAD_PAUSED) {
131			ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
132		}
133		MutexUnlock(&threadContext->stateMutex);
134	}
135
136	while (threadContext->state != THREAD_SHUTDOWN) {
137		_changeState(threadContext, THREAD_SHUTDOWN, 0);
138	}
139
140	if (threadContext->cleanCallback) {
141		threadContext->cleanCallback(threadContext);
142	}
143
144	GBADeinit(&gba);
145
146	ConditionWake(&threadContext->sync.videoFrameAvailableCond);
147	ConditionWake(&threadContext->sync.audioRequiredCond);
148	free(savedata);
149
150	return 0;
151}
152
153int GBAThreadStart(struct GBAThread* threadContext) {
154	// TODO: error check
155	threadContext->activeKeys = 0;
156	threadContext->state = THREAD_INITIALIZED;
157	threadContext->sync.videoFrameOn = 1;
158	threadContext->sync.videoFrameSkip = 0;
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 GBAThreadJoin(struct GBAThread* threadContext) {
188	MutexLock(&threadContext->sync.videoFrameMutex);
189	threadContext->sync.videoFrameWait = 0;
190	ConditionWake(&threadContext->sync.videoFrameRequiredCond);
191	MutexUnlock(&threadContext->sync.videoFrameMutex);
192
193	ThreadJoin(threadContext->thread);
194
195	MutexDeinit(&threadContext->stateMutex);
196	ConditionDeinit(&threadContext->stateCond);
197
198	MutexDeinit(&threadContext->sync.videoFrameMutex);
199	ConditionWake(&threadContext->sync.videoFrameAvailableCond);
200	ConditionDeinit(&threadContext->sync.videoFrameAvailableCond);
201	ConditionWake(&threadContext->sync.videoFrameRequiredCond);
202	ConditionDeinit(&threadContext->sync.videoFrameRequiredCond);
203
204	ConditionWake(&threadContext->sync.audioRequiredCond);
205	ConditionDeinit(&threadContext->sync.audioRequiredCond);
206	MutexDeinit(&threadContext->sync.audioBufferMutex);
207}
208
209void GBAThreadPause(struct GBAThread* threadContext) {
210	int frameOn = 1;
211	MutexLock(&threadContext->stateMutex);
212	if (threadContext->state == THREAD_RUNNING) {
213		if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
214			threadContext->debugger->state = DEBUGGER_EXITING;
215		}
216		threadContext->state = THREAD_PAUSED;
217		frameOn = 0;
218	}
219	MutexUnlock(&threadContext->stateMutex);
220	MutexLock(&threadContext->sync.videoFrameMutex);
221	if (frameOn != threadContext->sync.videoFrameOn) {
222		threadContext->sync.videoFrameOn = frameOn;
223		ConditionWake(&threadContext->sync.videoFrameAvailableCond);
224	}
225	MutexUnlock(&threadContext->sync.videoFrameMutex);
226}
227
228void GBAThreadUnpause(struct GBAThread* threadContext) {
229	int frameOn = 1;
230	MutexLock(&threadContext->stateMutex);
231	if (threadContext->state == THREAD_PAUSED) {
232		threadContext->state = THREAD_RUNNING;
233		ConditionWake(&threadContext->stateCond);
234	}
235	MutexUnlock(&threadContext->stateMutex);
236	MutexLock(&threadContext->sync.videoFrameMutex);
237	if (frameOn != threadContext->sync.videoFrameOn) {
238		threadContext->sync.videoFrameOn = frameOn;
239		ConditionWake(&threadContext->sync.videoFrameAvailableCond);
240	}
241	MutexUnlock(&threadContext->sync.videoFrameMutex);
242}
243
244void GBAThreadTogglePause(struct GBAThread* threadContext) {
245	int frameOn = 1;
246	MutexLock(&threadContext->stateMutex);
247	if (threadContext->state == THREAD_PAUSED) {
248		threadContext->state = THREAD_RUNNING;
249		ConditionWake(&threadContext->stateCond);
250	} else if (threadContext->state == THREAD_RUNNING) {
251		if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
252			threadContext->debugger->state = DEBUGGER_EXITING;
253		}
254		threadContext->state = THREAD_PAUSED;
255		frameOn = 0;
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
266
267#ifdef USE_PTHREADS
268struct GBAThread* GBAThreadGetContext(void) {
269	pthread_once(&_contextOnce, _createTLS);
270	return pthread_getspecific(_contextKey);
271}
272#else
273struct GBAThread* GBAThreadGetContext(void) {
274	InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
275	return TlsGetValue(_contextKey);
276}
277#endif
278
279void GBASyncPostFrame(struct GBASync* sync) {
280	if (!sync) {
281		return;
282	}
283
284	MutexLock(&sync->videoFrameMutex);
285	++sync->videoFramePending;
286	--sync->videoFrameSkip;
287	if (sync->videoFrameSkip < 0) {
288		ConditionWake(&sync->videoFrameAvailableCond);
289		while (sync->videoFrameWait && sync->videoFramePending) {
290			ConditionWait(&sync->videoFrameRequiredCond, &sync->videoFrameMutex);
291		}
292	}
293	MutexUnlock(&sync->videoFrameMutex);
294
295	struct GBAThread* thread = GBAThreadGetContext();
296	if (thread->frameCallback) {
297		thread->frameCallback(thread);
298	}
299}
300
301int GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) {
302	if (!sync) {
303		return 1;
304	}
305
306	MutexLock(&sync->videoFrameMutex);
307	ConditionWake(&sync->videoFrameRequiredCond);
308	if (!sync->videoFrameOn) {
309		return 0;
310	}
311	ConditionWait(&sync->videoFrameAvailableCond, &sync->videoFrameMutex);
312	sync->videoFramePending = 0;
313	sync->videoFrameSkip = frameskip;
314	return 1;
315}
316
317void GBASyncWaitFrameEnd(struct GBASync* sync) {
318	if (!sync) {
319		return;
320	}
321
322	MutexUnlock(&sync->videoFrameMutex);
323}
324
325int GBASyncDrawingFrame(struct GBASync* sync) {
326	return sync->videoFrameSkip <= 0;
327}
328
329void GBASyncProduceAudio(struct GBASync* sync, int wait) {
330	if (sync->audioWait && wait) {
331		// TODO loop properly in event of spurious wakeups
332		ConditionWait(&sync->audioRequiredCond, &sync->audioBufferMutex);
333	}
334	MutexUnlock(&sync->audioBufferMutex);
335}
336
337void GBASyncLockAudio(struct GBASync* sync) {
338	MutexLock(&sync->audioBufferMutex);
339}
340
341void GBASyncConsumeAudio(struct GBASync* sync) {
342	ConditionWake(&sync->audioRequiredCond);
343	MutexUnlock(&sync->audioBufferMutex);
344}