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