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/blip_buf.h>
9#include <mgba/core/core.h>
10#include <mgba/core/serialize.h>
11#include <mgba-util/patch.h>
12#include <mgba-util/vfs.h>
13
14#include <signal.h>
15
16#ifndef DISABLE_THREADING
17
18static const float _defaultFPSTarget = 60.f;
19static ThreadLocal _contextKey;
20
21#ifdef USE_PTHREADS
22static pthread_once_t _contextOnce = PTHREAD_ONCE_INIT;
23
24static void _createTLS(void) {
25 ThreadLocalInitKey(&_contextKey);
26}
27#elif _WIN32
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 ThreadLocalInitKey(&_contextKey);
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 mCoreThreadInternal* 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 mCoreThreadInternal* threadContext) {
51 while (threadContext->state == mTHREAD_INTERRUPTED || threadContext->state == mTHREAD_INTERRUPTING) {
52 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
53 }
54}
55
56static void _pokeRequest(struct mCoreThreadInternal* threadContext) {
57 if (threadContext->state == mTHREAD_RUNNING || threadContext->state == mTHREAD_PAUSED) {
58 threadContext->state = mTHREAD_REQUEST;
59 }
60}
61
62static void _waitPrologue(struct mCoreThreadInternal* threadContext, bool* videoFrameWait, bool* audioWait) {
63 MutexLock(&threadContext->sync.videoFrameMutex);
64 *videoFrameWait = threadContext->sync.videoFrameWait;
65 threadContext->sync.videoFrameWait = false;
66 MutexUnlock(&threadContext->sync.videoFrameMutex);
67 MutexLock(&threadContext->sync.audioBufferMutex);
68 *audioWait = threadContext->sync.audioWait;
69 threadContext->sync.audioWait = false;
70 MutexUnlock(&threadContext->sync.audioBufferMutex);
71}
72
73static void _waitEpilogue(struct mCoreThreadInternal* threadContext, bool videoFrameWait, bool audioWait) {
74 MutexLock(&threadContext->sync.audioBufferMutex);
75 threadContext->sync.audioWait = audioWait;
76 MutexUnlock(&threadContext->sync.audioBufferMutex);
77 MutexLock(&threadContext->sync.videoFrameMutex);
78 threadContext->sync.videoFrameWait = videoFrameWait;
79 MutexUnlock(&threadContext->sync.videoFrameMutex);
80}
81
82static void _wait(struct mCoreThreadInternal* threadContext) {
83 MutexUnlock(&threadContext->stateMutex);
84
85 if (!MutexTryLock(&threadContext->sync.videoFrameMutex)) {
86 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
87 MutexUnlock(&threadContext->sync.videoFrameMutex);
88 }
89
90 if (!MutexTryLock(&threadContext->sync.audioBufferMutex)) {
91 ConditionWake(&threadContext->sync.audioRequiredCond);
92 MutexUnlock(&threadContext->sync.audioBufferMutex);
93 }
94
95 MutexLock(&threadContext->stateMutex);
96 ConditionWake(&threadContext->stateCond);
97}
98
99static void _waitOnRequest(struct mCoreThreadInternal* threadContext, enum mCoreThreadRequest request) {
100 bool videoFrameWait, audioWait;
101 _waitPrologue(threadContext, &videoFrameWait, &audioWait);
102 while (threadContext->requested & request) {
103 _pokeRequest(threadContext);
104 _wait(threadContext);
105 }
106 _waitEpilogue(threadContext, videoFrameWait, audioWait);
107}
108
109static void _waitUntilNotState(struct mCoreThreadInternal* threadContext, enum mCoreThreadState state) {
110 bool videoFrameWait, audioWait;
111 _waitPrologue(threadContext, &videoFrameWait, &audioWait);
112 while (threadContext->state == state) {
113 _wait(threadContext);
114 }
115 _waitEpilogue(threadContext, videoFrameWait, audioWait);
116}
117
118static void _sendRequest(struct mCoreThreadInternal* threadContext, enum mCoreThreadRequest request) {
119 threadContext->requested |= request;
120 _pokeRequest(threadContext);
121}
122
123static void _cancelRequest(struct mCoreThreadInternal* threadContext, enum mCoreThreadRequest request) {
124 threadContext->requested &= ~request;
125 _pokeRequest(threadContext);
126 ConditionWake(&threadContext->stateCond);
127}
128
129void _frameStarted(void* context) {
130 struct mCoreThread* thread = context;
131 if (!thread) {
132 return;
133 }
134 if (thread->core->opts.rewindEnable && thread->core->opts.rewindBufferCapacity > 0) {
135 if (!thread->impl->rewinding || !mCoreRewindRestore(&thread->impl->rewind, thread->core)) {
136 mCoreRewindAppend(&thread->impl->rewind, thread->core);
137 }
138 }
139}
140
141void _frameEnded(void* context) {
142 struct mCoreThread* thread = context;
143 if (!thread) {
144 return;
145 }
146 if (thread->frameCallback) {
147 thread->frameCallback(thread);
148 }
149}
150
151void _crashed(void* context) {
152 struct mCoreThread* thread = context;
153 if (!thread) {
154 return;
155 }
156 _changeState(thread->impl, mTHREAD_CRASHED, true);
157}
158
159void _coreSleep(void* context) {
160 struct mCoreThread* thread = context;
161 if (!thread) {
162 return;
163 }
164 if (thread->sleepCallback) {
165 thread->sleepCallback(thread);
166 }
167}
168
169void _coreShutdown(void* context) {
170 struct mCoreThread* thread = context;
171 if (!thread) {
172 return;
173 }
174 _changeState(thread->impl, mTHREAD_EXITING, true);
175}
176
177static THREAD_ENTRY _mCoreThreadRun(void* context) {
178 struct mCoreThread* threadContext = context;
179#ifdef USE_PTHREADS
180 pthread_once(&_contextOnce, _createTLS);
181#elif _WIN32
182 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
183#endif
184
185 ThreadLocalSetKey(_contextKey, threadContext);
186 ThreadSetName("CPU Thread");
187
188#if !defined(_WIN32) && defined(USE_PTHREADS)
189 sigset_t signals;
190 sigemptyset(&signals);
191 pthread_sigmask(SIG_SETMASK, &signals, 0);
192#endif
193
194 struct mCore* core = threadContext->core;
195 struct mCoreCallbacks callbacks = {
196 .videoFrameStarted = _frameStarted,
197 .videoFrameEnded = _frameEnded,
198 .coreCrashed = _crashed,
199 .sleep = _coreSleep,
200 .shutdown = _coreShutdown,
201 .context = threadContext
202 };
203 core->addCoreCallbacks(core, &callbacks);
204 core->setSync(core, &threadContext->impl->sync);
205
206 struct mLogFilter filter;
207 if (!threadContext->logger.d.filter) {
208 threadContext->logger.d.filter = &filter;
209 mLogFilterInit(threadContext->logger.d.filter);
210 mLogFilterLoad(threadContext->logger.d.filter, &core->config);
211 }
212
213 mCoreThreadRewindParamsChanged(threadContext);
214 if (threadContext->startCallback) {
215 threadContext->startCallback(threadContext);
216 }
217
218 core->reset(core);
219 _changeState(threadContext->impl, mTHREAD_RUNNING, true);
220
221 if (threadContext->resetCallback) {
222 threadContext->resetCallback(threadContext);
223 }
224
225 struct mCoreThreadInternal* impl = threadContext->impl;
226 bool wasPaused = false;
227 int pendingRequests = 0;
228
229 while (impl->state < mTHREAD_EXITING) {
230#ifdef USE_DEBUGGERS
231 struct mDebugger* debugger = core->debugger;
232 if (debugger) {
233 mDebuggerRun(debugger);
234 if (debugger->state == DEBUGGER_SHUTDOWN) {
235 _changeState(impl, mTHREAD_EXITING, false);
236 }
237 } else
238#endif
239 {
240 while (impl->state == mTHREAD_RUNNING) {
241 core->runLoop(core);
242 }
243 }
244
245 MutexLock(&impl->stateMutex);
246 while (impl->state >= mTHREAD_MIN_WAITING && impl->state < mTHREAD_EXITING) {
247 if (impl->state == mTHREAD_INTERRUPTING) {
248 impl->state = mTHREAD_INTERRUPTED;
249 ConditionWake(&impl->stateCond);
250 }
251
252 while (impl->state >= mTHREAD_MIN_WAITING && impl->state <= mTHREAD_MAX_WAITING) {
253 ConditionWait(&impl->stateCond, &impl->stateMutex);
254
255 if (impl->sync.audioWait) {
256 MutexUnlock(&impl->stateMutex);
257 mCoreSyncLockAudio(&impl->sync);
258 mCoreSyncProduceAudio(&impl->sync, core->getAudioChannel(core, 0), core->getAudioBufferSize(core));
259 MutexLock(&impl->stateMutex);
260 }
261 }
262 if (wasPaused && !(impl->requested & mTHREAD_REQ_PAUSE)) {
263 break;
264 }
265 }
266
267 impl->requested &= ~pendingRequests | mTHREAD_REQ_PAUSE | mTHREAD_REQ_WAIT;
268 pendingRequests = impl->requested;
269
270 if (impl->state == mTHREAD_REQUEST) {
271 if (pendingRequests) {
272 if (pendingRequests & mTHREAD_REQ_PAUSE) {
273 impl->state = mTHREAD_PAUSED;
274 }
275 if (pendingRequests & mTHREAD_REQ_WAIT) {
276 impl->state = mTHREAD_PAUSED;
277 }
278 } else {
279 impl->state = mTHREAD_RUNNING;
280 ConditionWake(&threadContext->impl->stateCond);
281 }
282 }
283 MutexUnlock(&impl->stateMutex);
284
285 // Deferred callbacks can't be run inside of the critical section
286 if (!wasPaused && (pendingRequests & mTHREAD_REQ_PAUSE)) {
287 wasPaused = true;
288 if (threadContext->pauseCallback) {
289 threadContext->pauseCallback(threadContext);
290 }
291 }
292 if (wasPaused && !(pendingRequests & mTHREAD_REQ_PAUSE)) {
293 wasPaused = false;
294 if (threadContext->unpauseCallback) {
295 threadContext->unpauseCallback(threadContext);
296 }
297 }
298 if (pendingRequests & mTHREAD_REQ_RESET) {
299 core->reset(core);
300 if (threadContext->resetCallback) {
301 threadContext->resetCallback(threadContext);
302 }
303 }
304 if (pendingRequests & mTHREAD_REQ_RUN_ON) {
305 if (threadContext->run) {
306 threadContext->run(threadContext);
307 }
308 }
309 }
310
311 while (impl->state < mTHREAD_SHUTDOWN) {
312 _changeState(impl, mTHREAD_SHUTDOWN, false);
313 }
314
315 if (core->opts.rewindEnable) {
316 mCoreRewindContextDeinit(&impl->rewind);
317 }
318
319 if (threadContext->cleanCallback) {
320 threadContext->cleanCallback(threadContext);
321 }
322 core->clearCoreCallbacks(core);
323
324 if (threadContext->logger.d.filter == &filter) {
325 mLogFilterDeinit(&filter);
326 }
327 threadContext->logger.d.filter = NULL;
328
329 return 0;
330}
331
332bool mCoreThreadStart(struct mCoreThread* threadContext) {
333 threadContext->impl = calloc(sizeof(*threadContext->impl), 1);
334 threadContext->impl->state = mTHREAD_INITIALIZED;
335 threadContext->impl->requested = 0;
336 threadContext->logger.p = threadContext;
337 if (!threadContext->logger.d.log) {
338 threadContext->logger.d.log = _mCoreLog;
339 threadContext->logger.d.filter = NULL;
340 }
341
342 if (!threadContext->impl->sync.fpsTarget) {
343 threadContext->impl->sync.fpsTarget = _defaultFPSTarget;
344 }
345
346 MutexInit(&threadContext->impl->stateMutex);
347 ConditionInit(&threadContext->impl->stateCond);
348
349 MutexInit(&threadContext->impl->sync.videoFrameMutex);
350 ConditionInit(&threadContext->impl->sync.videoFrameAvailableCond);
351 ConditionInit(&threadContext->impl->sync.videoFrameRequiredCond);
352 MutexInit(&threadContext->impl->sync.audioBufferMutex);
353 ConditionInit(&threadContext->impl->sync.audioRequiredCond);
354
355 threadContext->impl->interruptDepth = 0;
356
357#ifdef USE_PTHREADS
358 sigset_t signals;
359 sigemptyset(&signals);
360 sigaddset(&signals, SIGINT);
361 sigaddset(&signals, SIGTRAP);
362 pthread_sigmask(SIG_BLOCK, &signals, 0);
363#endif
364
365 threadContext->impl->sync.audioWait = threadContext->core->opts.audioSync;
366 threadContext->impl->sync.videoFrameWait = threadContext->core->opts.videoSync;
367 threadContext->impl->sync.fpsTarget = threadContext->core->opts.fpsTarget;
368
369 MutexLock(&threadContext->impl->stateMutex);
370 ThreadCreate(&threadContext->impl->thread, _mCoreThreadRun, threadContext);
371 while (threadContext->impl->state < mTHREAD_RUNNING) {
372 ConditionWait(&threadContext->impl->stateCond, &threadContext->impl->stateMutex);
373 }
374 MutexUnlock(&threadContext->impl->stateMutex);
375
376 return true;
377}
378
379bool mCoreThreadHasStarted(struct mCoreThread* threadContext) {
380 if (!threadContext->impl) {
381 return false;
382 }
383 bool hasStarted;
384 MutexLock(&threadContext->impl->stateMutex);
385 hasStarted = threadContext->impl->state > mTHREAD_INITIALIZED;
386 MutexUnlock(&threadContext->impl->stateMutex);
387 return hasStarted;
388}
389
390bool mCoreThreadHasExited(struct mCoreThread* threadContext) {
391 if (!threadContext->impl) {
392 return false;
393 }
394 bool hasExited;
395 MutexLock(&threadContext->impl->stateMutex);
396 hasExited = threadContext->impl->state > mTHREAD_EXITING;
397 MutexUnlock(&threadContext->impl->stateMutex);
398 return hasExited;
399}
400
401bool mCoreThreadHasCrashed(struct mCoreThread* threadContext) {
402 if (!threadContext->impl) {
403 return false;
404 }
405 bool hasExited;
406 MutexLock(&threadContext->impl->stateMutex);
407 hasExited = threadContext->impl->state == mTHREAD_CRASHED;
408 MutexUnlock(&threadContext->impl->stateMutex);
409 return hasExited;
410}
411
412void mCoreThreadMarkCrashed(struct mCoreThread* threadContext) {
413 MutexLock(&threadContext->impl->stateMutex);
414 threadContext->impl->state = mTHREAD_CRASHED;
415 MutexUnlock(&threadContext->impl->stateMutex);
416}
417
418void mCoreThreadEnd(struct mCoreThread* threadContext) {
419 MutexLock(&threadContext->impl->stateMutex);
420 _waitOnInterrupt(threadContext->impl);
421 threadContext->impl->state = mTHREAD_EXITING;
422 ConditionWake(&threadContext->impl->stateCond);
423 MutexUnlock(&threadContext->impl->stateMutex);
424 MutexLock(&threadContext->impl->sync.audioBufferMutex);
425 threadContext->impl->sync.audioWait = 0;
426 ConditionWake(&threadContext->impl->sync.audioRequiredCond);
427 MutexUnlock(&threadContext->impl->sync.audioBufferMutex);
428
429 MutexLock(&threadContext->impl->sync.videoFrameMutex);
430 threadContext->impl->sync.videoFrameWait = false;
431 ConditionWake(&threadContext->impl->sync.videoFrameRequiredCond);
432 ConditionWake(&threadContext->impl->sync.videoFrameAvailableCond);
433 MutexUnlock(&threadContext->impl->sync.videoFrameMutex);
434}
435
436void mCoreThreadReset(struct mCoreThread* threadContext) {
437 MutexLock(&threadContext->impl->stateMutex);
438 _waitOnInterrupt(threadContext->impl);
439 _sendRequest(threadContext->impl, mTHREAD_REQ_RESET);
440 _waitOnRequest(threadContext->impl, mTHREAD_REQ_RESET);
441 MutexUnlock(&threadContext->impl->stateMutex);
442}
443
444void mCoreThreadJoin(struct mCoreThread* threadContext) {
445 if (!threadContext->impl) {
446 return;
447 }
448 ThreadJoin(&threadContext->impl->thread);
449
450 MutexDeinit(&threadContext->impl->stateMutex);
451 ConditionDeinit(&threadContext->impl->stateCond);
452
453 MutexDeinit(&threadContext->impl->sync.videoFrameMutex);
454 ConditionWake(&threadContext->impl->sync.videoFrameAvailableCond);
455 ConditionDeinit(&threadContext->impl->sync.videoFrameAvailableCond);
456 ConditionWake(&threadContext->impl->sync.videoFrameRequiredCond);
457 ConditionDeinit(&threadContext->impl->sync.videoFrameRequiredCond);
458
459 ConditionWake(&threadContext->impl->sync.audioRequiredCond);
460 ConditionDeinit(&threadContext->impl->sync.audioRequiredCond);
461 MutexDeinit(&threadContext->impl->sync.audioBufferMutex);
462
463 free(threadContext->impl);
464 threadContext->impl = NULL;
465}
466
467bool mCoreThreadIsActive(struct mCoreThread* threadContext) {
468 if (!threadContext->impl) {
469 return false;
470 }
471 return threadContext->impl->state >= mTHREAD_RUNNING && threadContext->impl->state < mTHREAD_EXITING;
472}
473
474void mCoreThreadInterrupt(struct mCoreThread* threadContext) {
475 if (!threadContext) {
476 return;
477 }
478 MutexLock(&threadContext->impl->stateMutex);
479 ++threadContext->impl->interruptDepth;
480 if (threadContext->impl->interruptDepth > 1 || !mCoreThreadIsActive(threadContext)) {
481 MutexUnlock(&threadContext->impl->stateMutex);
482 return;
483 }
484 threadContext->impl->state = mTHREAD_INTERRUPTING;
485 _waitUntilNotState(threadContext->impl, mTHREAD_INTERRUPTING);
486 MutexUnlock(&threadContext->impl->stateMutex);
487}
488
489void mCoreThreadInterruptFromThread(struct mCoreThread* threadContext) {
490 if (!threadContext) {
491 return;
492 }
493 MutexLock(&threadContext->impl->stateMutex);
494 ++threadContext->impl->interruptDepth;
495 if (threadContext->impl->interruptDepth > 1 || !mCoreThreadIsActive(threadContext)) {
496 if (threadContext->impl->state == mTHREAD_INTERRUPTING) {
497 threadContext->impl->state = mTHREAD_INTERRUPTED;
498 }
499 MutexUnlock(&threadContext->impl->stateMutex);
500 return;
501 }
502 threadContext->impl->state = mTHREAD_INTERRUPTING;
503 ConditionWake(&threadContext->impl->stateCond);
504 MutexUnlock(&threadContext->impl->stateMutex);
505}
506
507void mCoreThreadContinue(struct mCoreThread* threadContext) {
508 if (!threadContext) {
509 return;
510 }
511 MutexLock(&threadContext->impl->stateMutex);
512 --threadContext->impl->interruptDepth;
513 if (threadContext->impl->interruptDepth < 1 && mCoreThreadIsActive(threadContext)) {
514 threadContext->impl->state = mTHREAD_REQUEST;
515 ConditionWake(&threadContext->impl->stateCond);
516 }
517 MutexUnlock(&threadContext->impl->stateMutex);
518}
519
520void mCoreThreadRunFunction(struct mCoreThread* threadContext, void (*run)(struct mCoreThread*)) {
521 MutexLock(&threadContext->impl->stateMutex);
522 _waitOnInterrupt(threadContext->impl);
523 threadContext->run = run;
524 _sendRequest(threadContext->impl, mTHREAD_REQ_RUN_ON);
525 _waitOnRequest(threadContext->impl, mTHREAD_REQ_RUN_ON);
526 MutexUnlock(&threadContext->impl->stateMutex);
527}
528
529void mCoreThreadPause(struct mCoreThread* threadContext) {
530 MutexLock(&threadContext->impl->stateMutex);
531 _waitOnInterrupt(threadContext->impl);
532 _sendRequest(threadContext->impl, mTHREAD_REQ_PAUSE);
533 _waitUntilNotState(threadContext->impl, mTHREAD_REQUEST);
534 MutexUnlock(&threadContext->impl->stateMutex);
535}
536
537void mCoreThreadUnpause(struct mCoreThread* threadContext) {
538 MutexLock(&threadContext->impl->stateMutex);
539 _cancelRequest(threadContext->impl, mTHREAD_REQ_PAUSE);
540 _waitUntilNotState(threadContext->impl, mTHREAD_REQUEST);
541 MutexUnlock(&threadContext->impl->stateMutex);
542}
543
544bool mCoreThreadIsPaused(struct mCoreThread* threadContext) {
545 bool isPaused;
546 MutexLock(&threadContext->impl->stateMutex);
547 isPaused = !!(threadContext->impl->requested & mTHREAD_REQ_PAUSE);
548 MutexUnlock(&threadContext->impl->stateMutex);
549 return isPaused;
550}
551
552void mCoreThreadTogglePause(struct mCoreThread* threadContext) {
553 MutexLock(&threadContext->impl->stateMutex);
554 _waitOnInterrupt(threadContext->impl);
555 if (threadContext->impl->requested & mTHREAD_REQ_PAUSE) {
556 _cancelRequest(threadContext->impl, mTHREAD_REQ_PAUSE);
557 } else {
558 _sendRequest(threadContext->impl, mTHREAD_REQ_PAUSE);
559 }
560 _waitUntilNotState(threadContext->impl, mTHREAD_REQUEST);
561 MutexUnlock(&threadContext->impl->stateMutex);
562}
563
564void mCoreThreadPauseFromThread(struct mCoreThread* threadContext) {
565 MutexLock(&threadContext->impl->stateMutex);
566 _sendRequest(threadContext->impl, mTHREAD_REQ_PAUSE);
567 MutexUnlock(&threadContext->impl->stateMutex);
568}
569
570void mCoreThreadSetRewinding(struct mCoreThread* threadContext, bool rewinding) {
571 MutexLock(&threadContext->impl->stateMutex);
572 threadContext->impl->rewinding = rewinding;
573 MutexUnlock(&threadContext->impl->stateMutex);
574}
575
576void mCoreThreadRewindParamsChanged(struct mCoreThread* threadContext) {
577 struct mCore* core = threadContext->core;
578 if (core->opts.rewindEnable && core->opts.rewindBufferCapacity > 0) {
579 mCoreRewindContextInit(&threadContext->impl->rewind, core->opts.rewindBufferCapacity, true);
580 } else {
581 mCoreRewindContextDeinit(&threadContext->impl->rewind);
582 }
583}
584
585void mCoreThreadWaitFromThread(struct mCoreThread* threadContext) {
586 MutexLock(&threadContext->impl->stateMutex);
587 _sendRequest(threadContext->impl, mTHREAD_REQ_WAIT);
588 MutexUnlock(&threadContext->impl->stateMutex);
589}
590
591void mCoreThreadStopWaiting(struct mCoreThread* threadContext) {
592 MutexLock(&threadContext->impl->stateMutex);
593 _cancelRequest(threadContext->impl, mTHREAD_REQ_WAIT);
594 MutexUnlock(&threadContext->impl->stateMutex);
595}
596
597struct mCoreThread* mCoreThreadGet(void) {
598#ifdef USE_PTHREADS
599 pthread_once(&_contextOnce, _createTLS);
600#elif _WIN32
601 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
602#endif
603 return ThreadLocalGetValue(_contextKey);
604}
605
606static void _mCoreLog(struct mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args) {
607 UNUSED(logger);
608 UNUSED(level);
609 printf("%s: ", mLogCategoryName(category));
610 vprintf(format, args);
611 printf("\n");
612 struct mCoreThread* thread = mCoreThreadGet();
613 if (thread && level == mLOG_FATAL) {
614 mCoreThreadMarkCrashed(thread);
615 }
616}
617#else
618struct mCoreThread* mCoreThreadGet(void) {
619 return NULL;
620}
621#endif
622
623struct mLogger* mCoreThreadLogger(void) {
624 struct mCoreThread* thread = mCoreThreadGet();
625 if (thread) {
626 return &thread->logger.d;
627 }
628 return NULL;
629}
630