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