all repos — mgba @ 9de2189b2ef52ddb51dfde14befda9e4dff4e436

mGBA Game Boy Advance Emulator

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