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