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 "platform/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 if (threadContext->startCallback) {
113 threadContext->startCallback(threadContext);
114 }
115
116 _changeState(threadContext, THREAD_RUNNING, true);
117
118 while (threadContext->state < THREAD_EXITING) {
119 core->runLoop(core);
120
121 int resetScheduled = 0;
122 MutexLock(&threadContext->stateMutex);
123 while (threadContext->state > THREAD_RUNNING && threadContext->state < THREAD_EXITING) {
124 if (threadContext->state == THREAD_PAUSING) {
125 threadContext->state = THREAD_PAUSED;
126 ConditionWake(&threadContext->stateCond);
127 }
128 if (threadContext->state == THREAD_INTERRUPTING) {
129 threadContext->state = THREAD_INTERRUPTED;
130 ConditionWake(&threadContext->stateCond);
131 }
132 if (threadContext->state == THREAD_RUN_ON) {
133 if (threadContext->run) {
134 threadContext->run(threadContext);
135 }
136 threadContext->state = threadContext->savedState;
137 ConditionWake(&threadContext->stateCond);
138 }
139 if (threadContext->state == THREAD_RESETING) {
140 threadContext->state = THREAD_RUNNING;
141 resetScheduled = 1;
142 }
143 while (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_INTERRUPTED) {
144 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
145 }
146 }
147 MutexUnlock(&threadContext->stateMutex);
148 if (resetScheduled) {
149 core->reset(core);
150 }
151 }
152
153 while (threadContext->state < THREAD_SHUTDOWN) {
154 _changeState(threadContext, THREAD_SHUTDOWN, false);
155 }
156
157 if (threadContext->cleanCallback) {
158 threadContext->cleanCallback(threadContext);
159 }
160
161 return 0;
162}
163
164bool mCoreThreadStart(struct mCoreThread* threadContext) {
165 threadContext->state = THREAD_INITIALIZED;
166 threadContext->logger.p = threadContext;
167 threadContext->logLevel = threadContext->core->opts.logLevel;
168
169 if (!threadContext->sync.fpsTarget) {
170 threadContext->sync.fpsTarget = _defaultFPSTarget;
171 }
172
173 MutexInit(&threadContext->stateMutex);
174 ConditionInit(&threadContext->stateCond);
175
176 MutexInit(&threadContext->sync.videoFrameMutex);
177 ConditionInit(&threadContext->sync.videoFrameAvailableCond);
178 ConditionInit(&threadContext->sync.videoFrameRequiredCond);
179 MutexInit(&threadContext->sync.audioBufferMutex);
180 ConditionInit(&threadContext->sync.audioRequiredCond);
181
182 threadContext->interruptDepth = 0;
183
184#ifdef USE_PTHREADS
185 sigset_t signals;
186 sigemptyset(&signals);
187 sigaddset(&signals, SIGINT);
188 sigaddset(&signals, SIGTRAP);
189 pthread_sigmask(SIG_BLOCK, &signals, 0);
190#endif
191
192 threadContext->sync.audioWait = threadContext->core->opts.audioSync;
193 threadContext->sync.videoFrameWait = threadContext->core->opts.videoSync;
194 threadContext->sync.fpsTarget = threadContext->core->opts.fpsTarget;
195
196 MutexLock(&threadContext->stateMutex);
197 ThreadCreate(&threadContext->thread, _mCoreThreadRun, threadContext);
198 while (threadContext->state < THREAD_RUNNING) {
199 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
200 }
201 MutexUnlock(&threadContext->stateMutex);
202
203 return true;
204}
205
206bool mCoreThreadHasStarted(struct mCoreThread* threadContext) {
207 bool hasStarted;
208 MutexLock(&threadContext->stateMutex);
209 hasStarted = threadContext->state > THREAD_INITIALIZED;
210 MutexUnlock(&threadContext->stateMutex);
211 return hasStarted;
212}
213
214bool mCoreThreadHasExited(struct mCoreThread* threadContext) {
215 bool hasExited;
216 MutexLock(&threadContext->stateMutex);
217 hasExited = threadContext->state > THREAD_EXITING;
218 MutexUnlock(&threadContext->stateMutex);
219 return hasExited;
220}
221
222bool mCoreThreadHasCrashed(struct mCoreThread* threadContext) {
223 bool hasExited;
224 MutexLock(&threadContext->stateMutex);
225 hasExited = threadContext->state == THREAD_CRASHED;
226 MutexUnlock(&threadContext->stateMutex);
227 return hasExited;
228}
229
230void mCoreThreadEnd(struct mCoreThread* threadContext) {
231 MutexLock(&threadContext->stateMutex);
232 _waitOnInterrupt(threadContext);
233 threadContext->state = THREAD_EXITING;
234 ConditionWake(&threadContext->stateCond);
235 MutexUnlock(&threadContext->stateMutex);
236 MutexLock(&threadContext->sync.audioBufferMutex);
237 threadContext->sync.audioWait = 0;
238 ConditionWake(&threadContext->sync.audioRequiredCond);
239 MutexUnlock(&threadContext->sync.audioBufferMutex);
240
241 MutexLock(&threadContext->sync.videoFrameMutex);
242 threadContext->sync.videoFrameWait = false;
243 threadContext->sync.videoFrameOn = false;
244 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
245 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
246 MutexUnlock(&threadContext->sync.videoFrameMutex);
247}
248
249void mCoreThreadReset(struct mCoreThread* threadContext) {
250 MutexLock(&threadContext->stateMutex);
251 _waitOnInterrupt(threadContext);
252 threadContext->state = THREAD_RESETING;
253 ConditionWake(&threadContext->stateCond);
254 MutexUnlock(&threadContext->stateMutex);
255}
256
257void mCoreThreadJoin(struct mCoreThread* threadContext) {
258 ThreadJoin(threadContext->thread);
259
260 MutexDeinit(&threadContext->stateMutex);
261 ConditionDeinit(&threadContext->stateCond);
262
263 MutexDeinit(&threadContext->sync.videoFrameMutex);
264 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
265 ConditionDeinit(&threadContext->sync.videoFrameAvailableCond);
266 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
267 ConditionDeinit(&threadContext->sync.videoFrameRequiredCond);
268
269 ConditionWake(&threadContext->sync.audioRequiredCond);
270 ConditionDeinit(&threadContext->sync.audioRequiredCond);
271 MutexDeinit(&threadContext->sync.audioBufferMutex);
272}
273
274bool mCoreThreadIsActive(struct mCoreThread* threadContext) {
275 return threadContext->state >= THREAD_RUNNING && threadContext->state < THREAD_EXITING;
276}
277
278void mCoreThreadInterrupt(struct mCoreThread* threadContext) {
279 if (!threadContext) {
280 return;
281 }
282 MutexLock(&threadContext->stateMutex);
283 ++threadContext->interruptDepth;
284 if (threadContext->interruptDepth > 1 || !mCoreThreadIsActive(threadContext)) {
285 MutexUnlock(&threadContext->stateMutex);
286 return;
287 }
288 threadContext->savedState = threadContext->state;
289 _waitOnInterrupt(threadContext);
290 threadContext->state = THREAD_INTERRUPTING;
291 ConditionWake(&threadContext->stateCond);
292 _waitUntilNotState(threadContext, THREAD_INTERRUPTING);
293 MutexUnlock(&threadContext->stateMutex);
294}
295
296void mCoreThreadContinue(struct mCoreThread* threadContext) {
297 if (!threadContext) {
298 return;
299 }
300 MutexLock(&threadContext->stateMutex);
301 --threadContext->interruptDepth;
302 if (threadContext->interruptDepth < 1 && mCoreThreadIsActive(threadContext)) {
303 threadContext->state = threadContext->savedState;
304 ConditionWake(&threadContext->stateCond);
305 }
306 MutexUnlock(&threadContext->stateMutex);
307}
308
309void mCoreThreadRunFunction(struct mCoreThread* threadContext, void (*run)(struct mCoreThread*)) {
310 MutexLock(&threadContext->stateMutex);
311 threadContext->run = run;
312 _waitOnInterrupt(threadContext);
313 threadContext->savedState = threadContext->state;
314 threadContext->state = THREAD_RUN_ON;
315 ConditionWake(&threadContext->stateCond);
316 _waitUntilNotState(threadContext, THREAD_RUN_ON);
317 MutexUnlock(&threadContext->stateMutex);
318}
319
320void mCoreThreadPause(struct mCoreThread* threadContext) {
321 bool frameOn = threadContext->sync.videoFrameOn;
322 MutexLock(&threadContext->stateMutex);
323 _waitOnInterrupt(threadContext);
324 if (threadContext->state == THREAD_RUNNING) {
325 _pauseThread(threadContext, false);
326 threadContext->frameWasOn = frameOn;
327 frameOn = false;
328 }
329 MutexUnlock(&threadContext->stateMutex);
330
331 mCoreSyncSetVideoSync(&threadContext->sync, frameOn);
332}
333
334void mCoreThreadUnpause(struct mCoreThread* threadContext) {
335 bool frameOn = threadContext->sync.videoFrameOn;
336 MutexLock(&threadContext->stateMutex);
337 _waitOnInterrupt(threadContext);
338 if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
339 threadContext->state = THREAD_RUNNING;
340 ConditionWake(&threadContext->stateCond);
341 frameOn = threadContext->frameWasOn;
342 }
343 MutexUnlock(&threadContext->stateMutex);
344
345 mCoreSyncSetVideoSync(&threadContext->sync, frameOn);
346}
347
348bool mCoreThreadIsPaused(struct mCoreThread* threadContext) {
349 bool isPaused;
350 MutexLock(&threadContext->stateMutex);
351 _waitOnInterrupt(threadContext);
352 isPaused = threadContext->state == THREAD_PAUSED;
353 MutexUnlock(&threadContext->stateMutex);
354 return isPaused;
355}
356
357void mCoreThreadTogglePause(struct mCoreThread* threadContext) {
358 bool frameOn = threadContext->sync.videoFrameOn;
359 MutexLock(&threadContext->stateMutex);
360 _waitOnInterrupt(threadContext);
361 if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
362 threadContext->state = THREAD_RUNNING;
363 ConditionWake(&threadContext->stateCond);
364 frameOn = threadContext->frameWasOn;
365 } else if (threadContext->state == THREAD_RUNNING) {
366 _pauseThread(threadContext, false);
367 threadContext->frameWasOn = frameOn;
368 frameOn = false;
369 }
370 MutexUnlock(&threadContext->stateMutex);
371
372 mCoreSyncSetVideoSync(&threadContext->sync, frameOn);
373}
374
375void mCoreThreadPauseFromThread(struct mCoreThread* threadContext) {
376 bool frameOn = true;
377 MutexLock(&threadContext->stateMutex);
378 _waitOnInterrupt(threadContext);
379 if (threadContext->state == THREAD_RUNNING) {
380 _pauseThread(threadContext, true);
381 frameOn = false;
382 }
383 MutexUnlock(&threadContext->stateMutex);
384
385 mCoreSyncSetVideoSync(&threadContext->sync, frameOn);
386}
387
388#ifdef USE_PTHREADS
389struct mCoreThread* mCoreThreadGet(void) {
390 pthread_once(&_contextOnce, _createTLS);
391 return pthread_getspecific(_contextKey);
392}
393#elif _WIN32
394struct mCoreThread* mCoreThreadGet(void) {
395 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
396 return TlsGetValue(_contextKey);
397}
398#else
399struct mCoreThread* mCoreThreadGet(void) {
400 return NULL;
401}
402#endif
403
404#else
405struct mCoreThread* mCoreThreadGet(void) {
406 return NULL;
407}
408#endif
409
410static void _mCoreLog(struct mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args) {
411 UNUSED(logger);
412 struct mCoreThread* thread = mCoreThreadGet();
413 if (thread && !(thread->logLevel & level)) {
414 return;
415 }
416 printf("%s: ", mLogCategoryName(category));
417 vprintf(format, args);
418 printf("\n");
419}
420
421struct mLogger* mCoreThreadLogger(void) {
422 struct mCoreThread* thread = mCoreThreadGet();
423 if (thread) {
424 if (!thread->logger.d.log) {
425 thread->logger.d.log = _mCoreLog;
426 }
427 return &thread->logger.d;
428 }
429 return NULL;
430}
431