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