src/core/thread.c (view raw)
1/* Copyright (c) 2013-2016 Jeffrey Pfau
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6#include <mgba/core/thread.h>
7
8#include <mgba/core/core.h>
9#include <mgba/core/serialize.h>
10#include <mgba-util/patch.h>
11#include <mgba-util/vfs.h>
12
13#include <signal.h>
14
15#ifndef DISABLE_THREADING
16
17static const float _defaultFPSTarget = 60.f;
18
19#ifdef USE_PTHREADS
20static pthread_key_t _contextKey;
21static pthread_once_t _contextOnce = PTHREAD_ONCE_INIT;
22
23static void _createTLS(void) {
24 pthread_key_create(&_contextKey, 0);
25}
26#elif _WIN32
27static DWORD _contextKey;
28static INIT_ONCE _contextOnce = INIT_ONCE_STATIC_INIT;
29
30static BOOL CALLBACK _createTLS(PINIT_ONCE once, PVOID param, PVOID* context) {
31 UNUSED(once);
32 UNUSED(param);
33 UNUSED(context);
34 _contextKey = TlsAlloc();
35 return TRUE;
36}
37#endif
38
39static void _mCoreLog(struct mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args);
40
41static void _changeState(struct mCoreThread* threadContext, enum mCoreThreadState newState, bool broadcast) {
42 MutexLock(&threadContext->stateMutex);
43 threadContext->state = newState;
44 if (broadcast) {
45 ConditionWake(&threadContext->stateCond);
46 }
47 MutexUnlock(&threadContext->stateMutex);
48}
49
50static void _waitOnInterrupt(struct mCoreThread* threadContext) {
51 while (threadContext->state == THREAD_INTERRUPTED || threadContext->state == THREAD_INTERRUPTING) {
52 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
53 }
54}
55
56static void _waitUntilNotState(struct mCoreThread* threadContext, enum mCoreThreadState oldState) {
57 MutexLock(&threadContext->sync.videoFrameMutex);
58 bool videoFrameWait = threadContext->sync.videoFrameWait;
59 threadContext->sync.videoFrameWait = false;
60 MutexUnlock(&threadContext->sync.videoFrameMutex);
61
62 while (threadContext->state == oldState) {
63 MutexUnlock(&threadContext->stateMutex);
64
65 if (!MutexTryLock(&threadContext->sync.videoFrameMutex)) {
66 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
67 MutexUnlock(&threadContext->sync.videoFrameMutex);
68 }
69
70 if (!MutexTryLock(&threadContext->sync.audioBufferMutex)) {
71 ConditionWake(&threadContext->sync.audioRequiredCond);
72 MutexUnlock(&threadContext->sync.audioBufferMutex);
73 }
74
75 MutexLock(&threadContext->stateMutex);
76 ConditionWake(&threadContext->stateCond);
77 }
78
79 MutexLock(&threadContext->sync.videoFrameMutex);
80 threadContext->sync.videoFrameWait = videoFrameWait;
81 MutexUnlock(&threadContext->sync.videoFrameMutex);
82}
83
84static void _pauseThread(struct mCoreThread* threadContext) {
85 threadContext->state = THREAD_PAUSING;
86 _waitUntilNotState(threadContext, THREAD_PAUSING);
87}
88
89void _frameStarted(void* context) {
90 struct mCoreThread* thread = context;
91 if (!thread) {
92 return;
93 }
94 if (thread->core->opts.rewindEnable && thread->core->opts.rewindBufferCapacity > 0) {
95 if (thread->state != THREAD_REWINDING) {
96 mCoreRewindAppend(&thread->rewind, thread->core);
97 } else if (thread->state == THREAD_REWINDING) {
98 if (!mCoreRewindRestore(&thread->rewind, thread->core)) {
99 mCoreRewindAppend(&thread->rewind, thread->core);
100 }
101 }
102 }
103}
104
105void _frameEnded(void* context) {
106 struct mCoreThread* thread = context;
107 if (!thread) {
108 return;
109 }
110 if (thread->frameCallback) {
111 thread->frameCallback(thread);
112 }
113}
114
115void _crashed(void* context) {
116 struct mCoreThread* thread = context;
117 if (!thread) {
118 return;
119 }
120 _changeState(thread, THREAD_CRASHED, true);
121}
122
123void _coreSleep(void* context) {
124 struct mCoreThread* thread = context;
125 if (!thread) {
126 return;
127 }
128 if (thread->sleepCallback) {
129 thread->sleepCallback(thread);
130 }
131}
132
133static THREAD_ENTRY _mCoreThreadRun(void* context) {
134 struct mCoreThread* threadContext = context;
135#ifdef USE_PTHREADS
136 pthread_once(&_contextOnce, _createTLS);
137 pthread_setspecific(_contextKey, threadContext);
138#elif _WIN32
139 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
140 TlsSetValue(_contextKey, threadContext);
141#endif
142
143 ThreadSetName("CPU Thread");
144
145#if !defined(_WIN32) && defined(USE_PTHREADS)
146 sigset_t signals;
147 sigemptyset(&signals);
148 pthread_sigmask(SIG_SETMASK, &signals, 0);
149#endif
150
151 struct mCore* core = threadContext->core;
152 struct mCoreCallbacks callbacks = {
153 .videoFrameStarted = _frameStarted,
154 .videoFrameEnded = _frameEnded,
155 .coreCrashed = _crashed,
156 .sleep = _coreSleep,
157 .context = threadContext
158 };
159 core->addCoreCallbacks(core, &callbacks);
160 core->setSync(core, &threadContext->sync);
161 core->reset(core);
162
163 struct mLogFilter filter;
164 if (!threadContext->logger.d.filter) {
165 threadContext->logger.d.filter = &filter;
166 mLogFilterInit(threadContext->logger.d.filter);
167 mLogFilterLoad(threadContext->logger.d.filter, &core->config);
168 }
169
170 if (core->opts.rewindEnable && core->opts.rewindBufferCapacity > 0) {
171 mCoreRewindContextInit(&threadContext->rewind, core->opts.rewindBufferCapacity);
172 threadContext->rewind.stateFlags = core->opts.rewindSave ? SAVESTATE_SAVEDATA : 0;
173 }
174
175 _changeState(threadContext, THREAD_RUNNING, true);
176
177 if (threadContext->startCallback) {
178 threadContext->startCallback(threadContext);
179 }
180 if (threadContext->resetCallback) {
181 threadContext->resetCallback(threadContext);
182 }
183
184 while (threadContext->state < THREAD_EXITING) {
185#ifdef USE_DEBUGGERS
186 struct mDebugger* debugger = core->debugger;
187 if (debugger) {
188 mDebuggerRun(debugger);
189 if (debugger->state == DEBUGGER_SHUTDOWN) {
190 _changeState(threadContext, THREAD_EXITING, false);
191 }
192 } else
193#endif
194 {
195 while (threadContext->state <= THREAD_MAX_RUNNING) {
196 core->runLoop(core);
197 }
198 }
199
200 int resetScheduled = 0;
201 MutexLock(&threadContext->stateMutex);
202 while (threadContext->state > THREAD_MAX_RUNNING && threadContext->state < THREAD_EXITING) {
203 if (threadContext->state == THREAD_PAUSING) {
204 threadContext->state = THREAD_PAUSED;
205 ConditionWake(&threadContext->stateCond);
206 }
207 if (threadContext->state == THREAD_INTERRUPTING) {
208 threadContext->state = THREAD_INTERRUPTED;
209 ConditionWake(&threadContext->stateCond);
210 }
211 if (threadContext->state == THREAD_RUN_ON) {
212 if (threadContext->run) {
213 threadContext->run(threadContext);
214 }
215 threadContext->state = threadContext->savedState;
216 ConditionWake(&threadContext->stateCond);
217 }
218 if (threadContext->state == THREAD_RESETING) {
219 threadContext->state = THREAD_RUNNING;
220 resetScheduled = 1;
221 }
222 while (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_INTERRUPTED || threadContext->state == THREAD_WAITING) {
223 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
224 }
225 }
226 MutexUnlock(&threadContext->stateMutex);
227 if (resetScheduled) {
228 core->reset(core);
229 if (threadContext->resetCallback) {
230 threadContext->resetCallback(threadContext);
231 }
232 }
233 }
234
235 while (threadContext->state < THREAD_SHUTDOWN) {
236 _changeState(threadContext, THREAD_SHUTDOWN, false);
237 }
238
239 if (core->opts.rewindEnable) {
240 mCoreRewindContextDeinit(&threadContext->rewind);
241 }
242
243 if (threadContext->cleanCallback) {
244 threadContext->cleanCallback(threadContext);
245 }
246 core->clearCoreCallbacks(core);
247
248 threadContext->logger.d.filter = NULL;
249
250 return 0;
251}
252
253bool mCoreThreadStart(struct mCoreThread* threadContext) {
254 threadContext->state = THREAD_INITIALIZED;
255 threadContext->logger.p = threadContext;
256 if (!threadContext->logger.d.log) {
257 threadContext->logger.d.log = _mCoreLog;
258 threadContext->logger.d.filter = NULL;
259 }
260
261 if (!threadContext->sync.fpsTarget) {
262 threadContext->sync.fpsTarget = _defaultFPSTarget;
263 }
264
265 MutexInit(&threadContext->stateMutex);
266 ConditionInit(&threadContext->stateCond);
267
268 MutexInit(&threadContext->sync.videoFrameMutex);
269 ConditionInit(&threadContext->sync.videoFrameAvailableCond);
270 ConditionInit(&threadContext->sync.videoFrameRequiredCond);
271 MutexInit(&threadContext->sync.audioBufferMutex);
272 ConditionInit(&threadContext->sync.audioRequiredCond);
273
274 threadContext->interruptDepth = 0;
275
276#ifdef USE_PTHREADS
277 sigset_t signals;
278 sigemptyset(&signals);
279 sigaddset(&signals, SIGINT);
280 sigaddset(&signals, SIGTRAP);
281 pthread_sigmask(SIG_BLOCK, &signals, 0);
282#endif
283
284 threadContext->sync.audioWait = threadContext->core->opts.audioSync;
285 threadContext->sync.videoFrameWait = threadContext->core->opts.videoSync;
286 threadContext->sync.fpsTarget = threadContext->core->opts.fpsTarget;
287
288 MutexLock(&threadContext->stateMutex);
289 ThreadCreate(&threadContext->thread, _mCoreThreadRun, threadContext);
290 while (threadContext->state < THREAD_RUNNING) {
291 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
292 }
293 MutexUnlock(&threadContext->stateMutex);
294
295 return true;
296}
297
298bool mCoreThreadHasStarted(struct mCoreThread* threadContext) {
299 bool hasStarted;
300 MutexLock(&threadContext->stateMutex);
301 hasStarted = threadContext->state > THREAD_INITIALIZED;
302 MutexUnlock(&threadContext->stateMutex);
303 return hasStarted;
304}
305
306bool mCoreThreadHasExited(struct mCoreThread* threadContext) {
307 bool hasExited;
308 MutexLock(&threadContext->stateMutex);
309 hasExited = threadContext->state > THREAD_EXITING;
310 MutexUnlock(&threadContext->stateMutex);
311 return hasExited;
312}
313
314bool mCoreThreadHasCrashed(struct mCoreThread* threadContext) {
315 bool hasExited;
316 MutexLock(&threadContext->stateMutex);
317 hasExited = threadContext->state == THREAD_CRASHED;
318 MutexUnlock(&threadContext->stateMutex);
319 return hasExited;
320}
321
322void mCoreThreadMarkCrashed(struct mCoreThread* threadContext) {
323 MutexLock(&threadContext->stateMutex);
324 threadContext->state = THREAD_CRASHED;
325 MutexUnlock(&threadContext->stateMutex);
326}
327
328void mCoreThreadEnd(struct mCoreThread* threadContext) {
329 MutexLock(&threadContext->stateMutex);
330 _waitOnInterrupt(threadContext);
331 threadContext->state = THREAD_EXITING;
332 ConditionWake(&threadContext->stateCond);
333 MutexUnlock(&threadContext->stateMutex);
334 MutexLock(&threadContext->sync.audioBufferMutex);
335 threadContext->sync.audioWait = 0;
336 ConditionWake(&threadContext->sync.audioRequiredCond);
337 MutexUnlock(&threadContext->sync.audioBufferMutex);
338
339 MutexLock(&threadContext->sync.videoFrameMutex);
340 threadContext->sync.videoFrameWait = false;
341 threadContext->sync.videoFrameOn = false;
342 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
343 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
344 MutexUnlock(&threadContext->sync.videoFrameMutex);
345}
346
347void mCoreThreadReset(struct mCoreThread* threadContext) {
348 MutexLock(&threadContext->stateMutex);
349 if (threadContext->state == THREAD_INTERRUPTED || threadContext->state == THREAD_INTERRUPTING) {
350 threadContext->savedState = THREAD_RESETING;
351 } else {
352 threadContext->state = THREAD_RESETING;
353 }
354 ConditionWake(&threadContext->stateCond);
355 MutexUnlock(&threadContext->stateMutex);
356}
357
358void mCoreThreadJoin(struct mCoreThread* threadContext) {
359 ThreadJoin(threadContext->thread);
360
361 MutexDeinit(&threadContext->stateMutex);
362 ConditionDeinit(&threadContext->stateCond);
363
364 MutexDeinit(&threadContext->sync.videoFrameMutex);
365 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
366 ConditionDeinit(&threadContext->sync.videoFrameAvailableCond);
367 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
368 ConditionDeinit(&threadContext->sync.videoFrameRequiredCond);
369
370 ConditionWake(&threadContext->sync.audioRequiredCond);
371 ConditionDeinit(&threadContext->sync.audioRequiredCond);
372 MutexDeinit(&threadContext->sync.audioBufferMutex);
373}
374
375bool mCoreThreadIsActive(struct mCoreThread* threadContext) {
376 return threadContext->state >= THREAD_RUNNING && threadContext->state < THREAD_EXITING;
377}
378
379void mCoreThreadInterrupt(struct mCoreThread* threadContext) {
380 if (!threadContext) {
381 return;
382 }
383 MutexLock(&threadContext->stateMutex);
384 ++threadContext->interruptDepth;
385 if (threadContext->interruptDepth > 1 || !mCoreThreadIsActive(threadContext)) {
386 MutexUnlock(&threadContext->stateMutex);
387 return;
388 }
389 threadContext->savedState = threadContext->state;
390 _waitOnInterrupt(threadContext);
391 threadContext->state = THREAD_INTERRUPTING;
392 ConditionWake(&threadContext->stateCond);
393 _waitUntilNotState(threadContext, THREAD_INTERRUPTING);
394 MutexUnlock(&threadContext->stateMutex);
395}
396
397void mCoreThreadInterruptFromThread(struct mCoreThread* threadContext) {
398 if (!threadContext) {
399 return;
400 }
401 MutexLock(&threadContext->stateMutex);
402 ++threadContext->interruptDepth;
403 if (threadContext->interruptDepth > 1 || !mCoreThreadIsActive(threadContext)) {
404 MutexUnlock(&threadContext->stateMutex);
405 return;
406 }
407 threadContext->savedState = threadContext->state;
408 threadContext->state = THREAD_INTERRUPTING;
409 ConditionWake(&threadContext->stateCond);
410 MutexUnlock(&threadContext->stateMutex);
411}
412
413void mCoreThreadContinue(struct mCoreThread* threadContext) {
414 if (!threadContext) {
415 return;
416 }
417 MutexLock(&threadContext->stateMutex);
418 --threadContext->interruptDepth;
419 if (threadContext->interruptDepth < 1 && mCoreThreadIsActive(threadContext)) {
420 threadContext->state = threadContext->savedState;
421 ConditionWake(&threadContext->stateCond);
422 }
423 MutexUnlock(&threadContext->stateMutex);
424}
425
426void mCoreThreadRunFunction(struct mCoreThread* threadContext, void (*run)(struct mCoreThread*)) {
427 MutexLock(&threadContext->stateMutex);
428 threadContext->run = run;
429 _waitOnInterrupt(threadContext);
430 threadContext->savedState = threadContext->state;
431 threadContext->state = THREAD_RUN_ON;
432 ConditionWake(&threadContext->stateCond);
433 _waitUntilNotState(threadContext, THREAD_RUN_ON);
434 MutexUnlock(&threadContext->stateMutex);
435}
436
437void mCoreThreadPause(struct mCoreThread* threadContext) {
438 bool frameOn = threadContext->sync.videoFrameOn;
439 MutexLock(&threadContext->stateMutex);
440 _waitOnInterrupt(threadContext);
441 if (threadContext->state == THREAD_RUNNING) {
442 _pauseThread(threadContext);
443 threadContext->frameWasOn = frameOn;
444 frameOn = false;
445 }
446 MutexUnlock(&threadContext->stateMutex);
447
448 mCoreSyncSetVideoSync(&threadContext->sync, frameOn);
449}
450
451void mCoreThreadUnpause(struct mCoreThread* threadContext) {
452 bool frameOn = threadContext->sync.videoFrameOn;
453 MutexLock(&threadContext->stateMutex);
454 _waitOnInterrupt(threadContext);
455 if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
456 threadContext->state = THREAD_RUNNING;
457 ConditionWake(&threadContext->stateCond);
458 frameOn = threadContext->frameWasOn;
459 }
460 MutexUnlock(&threadContext->stateMutex);
461
462 mCoreSyncSetVideoSync(&threadContext->sync, frameOn);
463}
464
465bool mCoreThreadIsPaused(struct mCoreThread* threadContext) {
466 bool isPaused;
467 MutexLock(&threadContext->stateMutex);
468 if (threadContext->state == THREAD_INTERRUPTED || threadContext->state == THREAD_INTERRUPTING) {
469 isPaused = threadContext->savedState == THREAD_PAUSED;
470 } else {
471 isPaused = threadContext->state == THREAD_PAUSED;
472 }
473 MutexUnlock(&threadContext->stateMutex);
474 return isPaused;
475}
476
477void mCoreThreadTogglePause(struct mCoreThread* threadContext) {
478 bool frameOn = threadContext->sync.videoFrameOn;
479 MutexLock(&threadContext->stateMutex);
480 _waitOnInterrupt(threadContext);
481 if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
482 threadContext->state = THREAD_RUNNING;
483 ConditionWake(&threadContext->stateCond);
484 frameOn = threadContext->frameWasOn;
485 } else if (threadContext->state == THREAD_RUNNING) {
486 _pauseThread(threadContext);
487 threadContext->frameWasOn = frameOn;
488 frameOn = false;
489 }
490 MutexUnlock(&threadContext->stateMutex);
491
492 mCoreSyncSetVideoSync(&threadContext->sync, frameOn);
493}
494
495void mCoreThreadPauseFromThread(struct mCoreThread* threadContext) {
496 bool frameOn = true;
497 MutexLock(&threadContext->stateMutex);
498 if (threadContext->state == THREAD_RUNNING || (threadContext->state == THREAD_INTERRUPTING && threadContext->savedState == THREAD_RUNNING)) {
499 threadContext->state = THREAD_PAUSING;
500 frameOn = false;
501 }
502 MutexUnlock(&threadContext->stateMutex);
503
504 mCoreSyncSetVideoSync(&threadContext->sync, frameOn);
505}
506
507void mCoreThreadSetRewinding(struct mCoreThread* threadContext, bool rewinding) {
508 MutexLock(&threadContext->stateMutex);
509 if (rewinding && (threadContext->state == THREAD_REWINDING || (threadContext->state == THREAD_INTERRUPTING && threadContext->savedState == THREAD_REWINDING))) {
510 MutexUnlock(&threadContext->stateMutex);
511 return;
512 }
513 if (!rewinding && (threadContext->state == THREAD_RUNNING || (threadContext->state == THREAD_INTERRUPTING && threadContext->savedState == THREAD_RUNNING))) {
514 MutexUnlock(&threadContext->stateMutex);
515 return;
516 }
517 _waitOnInterrupt(threadContext);
518 if (rewinding && threadContext->state == THREAD_RUNNING) {
519 threadContext->state = THREAD_REWINDING;
520 }
521 if (!rewinding && threadContext->state == THREAD_REWINDING) {
522 threadContext->state = THREAD_RUNNING;
523 }
524 MutexUnlock(&threadContext->stateMutex);
525}
526
527void mCoreThreadWaitFromThread(struct mCoreThread* threadContext) {
528 MutexLock(&threadContext->stateMutex);
529 if ((threadContext->state == THREAD_INTERRUPTED || threadContext->state == THREAD_INTERRUPTING) && threadContext->savedState == THREAD_RUNNING) {
530 threadContext->savedState = THREAD_WAITING;
531 } else if (threadContext->state == THREAD_RUNNING) {
532 threadContext->state = THREAD_WAITING;
533 }
534 MutexUnlock(&threadContext->stateMutex);
535}
536
537void mCoreThreadStopWaiting(struct mCoreThread* threadContext) {
538 MutexLock(&threadContext->stateMutex);
539 if ((threadContext->state == THREAD_INTERRUPTED || threadContext->state == THREAD_INTERRUPTING) && threadContext->savedState == THREAD_WAITING) {
540 threadContext->savedState = THREAD_RUNNING;
541 } else if (threadContext->state == THREAD_WAITING) {
542 threadContext->state = THREAD_RUNNING;
543 ConditionWake(&threadContext->stateCond);
544 }
545 MutexUnlock(&threadContext->stateMutex);
546}
547
548#ifdef USE_PTHREADS
549struct mCoreThread* mCoreThreadGet(void) {
550 pthread_once(&_contextOnce, _createTLS);
551 return pthread_getspecific(_contextKey);
552}
553#elif _WIN32
554struct mCoreThread* mCoreThreadGet(void) {
555 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
556 return TlsGetValue(_contextKey);
557}
558#else
559struct mCoreThread* mCoreThreadGet(void) {
560 return NULL;
561}
562#endif
563
564#else
565struct mCoreThread* mCoreThreadGet(void) {
566 return NULL;
567}
568#endif
569
570static void _mCoreLog(struct mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args) {
571 UNUSED(logger);
572 UNUSED(level);
573 printf("%s: ", mLogCategoryName(category));
574 vprintf(format, args);
575 printf("\n");
576}
577
578struct mLogger* mCoreThreadLogger(void) {
579 struct mCoreThread* thread = mCoreThreadGet();
580 if (thread) {
581 return &thread->logger.d;
582 }
583 return NULL;
584}
585