all repos — mgba @ 5d0ab484573f1c5bb057129374cb1e7b56139393

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	if (core->opts.rewindEnable && core->opts.rewindBufferCapacity > 0) {
171		 mCoreRewindContextInit(&threadContext->impl->rewind, core->opts.rewindBufferCapacity, true);
172		 threadContext->impl->rewind.stateFlags = core->opts.rewindSave ? SAVESTATE_SAVEDATA : 0;
173	}
174
175	_changeState(threadContext->impl, THREAD_RUNNING, true);
176
177	if (threadContext->startCallback) {
178		threadContext->startCallback(threadContext);
179	}
180	if (threadContext->resetCallback) {
181		threadContext->resetCallback(threadContext);
182	}
183
184	struct mCoreThreadInternal* impl = threadContext->impl;
185	while (impl->state < THREAD_EXITING) {
186#ifdef USE_DEBUGGERS
187		struct mDebugger* debugger = core->debugger;
188		if (debugger) {
189			mDebuggerRun(debugger);
190			if (debugger->state == DEBUGGER_SHUTDOWN) {
191				_changeState(impl, THREAD_EXITING, false);
192			}
193		} else
194#endif
195		{
196			while (impl->state <= THREAD_MAX_RUNNING) {
197				core->runLoop(core);
198			}
199		}
200
201		int resetScheduled = 0;
202		MutexLock(&impl->stateMutex);
203		while (impl->state > THREAD_MAX_RUNNING && impl->state < THREAD_EXITING) {
204			if (impl->state == THREAD_PAUSING) {
205				impl->state = THREAD_PAUSED;
206				ConditionWake(&impl->stateCond);
207			}
208			if (impl->state == THREAD_INTERRUPTING) {
209				impl->state = THREAD_INTERRUPTED;
210				ConditionWake(&impl->stateCond);
211			}
212			if (impl->state == THREAD_RUN_ON) {
213				if (threadContext->run) {
214					threadContext->run(threadContext);
215				}
216				impl->state = impl->savedState;
217				ConditionWake(&impl->stateCond);
218			}
219			if (impl->state == THREAD_RESETING) {
220				impl->state = THREAD_RUNNING;
221				resetScheduled = 1;
222			}
223			while (impl->state == THREAD_PAUSED || impl->state == THREAD_INTERRUPTED || impl->state == THREAD_WAITING) {
224				ConditionWait(&impl->stateCond, &impl->stateMutex);
225			}
226		}
227		MutexUnlock(&impl->stateMutex);
228		if (resetScheduled) {
229			core->reset(core);
230			if (threadContext->resetCallback) {
231				threadContext->resetCallback(threadContext);
232			}
233		}
234	}
235
236	while (impl->state < THREAD_SHUTDOWN) {
237		_changeState(impl, THREAD_SHUTDOWN, false);
238	}
239
240	if (core->opts.rewindEnable) {
241		 mCoreRewindContextDeinit(&impl->rewind);
242	}
243
244	if (threadContext->cleanCallback) {
245		threadContext->cleanCallback(threadContext);
246	}
247	core->clearCoreCallbacks(core);
248
249	threadContext->logger.d.filter = NULL;
250
251	return 0;
252}
253
254bool mCoreThreadStart(struct mCoreThread* threadContext) {
255	threadContext->impl = malloc(sizeof(*threadContext->impl));
256	threadContext->impl->state = THREAD_INITIALIZED;
257	threadContext->logger.p = threadContext;
258	if (!threadContext->logger.d.log) {
259		threadContext->logger.d.log = _mCoreLog;
260		threadContext->logger.d.filter = NULL;
261	}
262
263	if (!threadContext->impl->sync.fpsTarget) {
264		threadContext->impl->sync.fpsTarget = _defaultFPSTarget;
265	}
266
267	MutexInit(&threadContext->impl->stateMutex);
268	ConditionInit(&threadContext->impl->stateCond);
269
270	MutexInit(&threadContext->impl->sync.videoFrameMutex);
271	ConditionInit(&threadContext->impl->sync.videoFrameAvailableCond);
272	ConditionInit(&threadContext->impl->sync.videoFrameRequiredCond);
273	MutexInit(&threadContext->impl->sync.audioBufferMutex);
274	ConditionInit(&threadContext->impl->sync.audioRequiredCond);
275
276	threadContext->impl->interruptDepth = 0;
277
278#ifdef USE_PTHREADS
279	sigset_t signals;
280	sigemptyset(&signals);
281	sigaddset(&signals, SIGINT);
282	sigaddset(&signals, SIGTRAP);
283	pthread_sigmask(SIG_BLOCK, &signals, 0);
284#endif
285
286	threadContext->impl->sync.audioWait = threadContext->core->opts.audioSync;
287	threadContext->impl->sync.videoFrameWait = threadContext->core->opts.videoSync;
288	threadContext->impl->sync.fpsTarget = threadContext->core->opts.fpsTarget;
289
290	MutexLock(&threadContext->impl->stateMutex);
291	ThreadCreate(&threadContext->impl->thread, _mCoreThreadRun, threadContext);
292	while (threadContext->impl->state < THREAD_RUNNING) {
293		ConditionWait(&threadContext->impl->stateCond, &threadContext->impl->stateMutex);
294	}
295	MutexUnlock(&threadContext->impl->stateMutex);
296
297	return true;
298}
299
300bool mCoreThreadHasStarted(struct mCoreThread* threadContext) {
301	if (!threadContext->impl) {
302		return false;
303	}
304	bool hasStarted;
305	MutexLock(&threadContext->impl->stateMutex);
306	hasStarted = threadContext->impl->state > THREAD_INITIALIZED;
307	MutexUnlock(&threadContext->impl->stateMutex);
308	return hasStarted;
309}
310
311bool mCoreThreadHasExited(struct mCoreThread* threadContext) {
312	if (!threadContext->impl) {
313		return false;
314	}
315	bool hasExited;
316	MutexLock(&threadContext->impl->stateMutex);
317	hasExited = threadContext->impl->state > THREAD_EXITING;
318	MutexUnlock(&threadContext->impl->stateMutex);
319	return hasExited;
320}
321
322bool mCoreThreadHasCrashed(struct mCoreThread* threadContext) {
323	if (!threadContext->impl) {
324		return false;
325	}
326	bool hasExited;
327	MutexLock(&threadContext->impl->stateMutex);
328	hasExited = threadContext->impl->state == THREAD_CRASHED;
329	MutexUnlock(&threadContext->impl->stateMutex);
330	return hasExited;
331}
332
333void mCoreThreadMarkCrashed(struct mCoreThread* threadContext) {
334	MutexLock(&threadContext->impl->stateMutex);
335	threadContext->impl->state = THREAD_CRASHED;
336	MutexUnlock(&threadContext->impl->stateMutex);
337}
338
339void mCoreThreadEnd(struct mCoreThread* threadContext) {
340	MutexLock(&threadContext->impl->stateMutex);
341	_waitOnInterrupt(threadContext->impl);
342	threadContext->impl->state = THREAD_EXITING;
343	ConditionWake(&threadContext->impl->stateCond);
344	MutexUnlock(&threadContext->impl->stateMutex);
345	MutexLock(&threadContext->impl->sync.audioBufferMutex);
346	threadContext->impl->sync.audioWait = 0;
347	ConditionWake(&threadContext->impl->sync.audioRequiredCond);
348	MutexUnlock(&threadContext->impl->sync.audioBufferMutex);
349
350	MutexLock(&threadContext->impl->sync.videoFrameMutex);
351	threadContext->impl->sync.videoFrameWait = false;
352	threadContext->impl->sync.videoFrameOn = false;
353	ConditionWake(&threadContext->impl->sync.videoFrameRequiredCond);
354	ConditionWake(&threadContext->impl->sync.videoFrameAvailableCond);
355	MutexUnlock(&threadContext->impl->sync.videoFrameMutex);
356}
357
358void mCoreThreadReset(struct mCoreThread* threadContext) {
359	MutexLock(&threadContext->impl->stateMutex);
360	if (threadContext->impl->state == THREAD_INTERRUPTED || threadContext->impl->state == THREAD_INTERRUPTING) {
361		threadContext->impl->savedState = THREAD_RESETING;
362	} else {
363		threadContext->impl->state = THREAD_RESETING;
364	}
365	ConditionWake(&threadContext->impl->stateCond);
366	MutexUnlock(&threadContext->impl->stateMutex);
367}
368
369void mCoreThreadJoin(struct mCoreThread* threadContext) {
370	if (!threadContext->impl) {
371		return;
372	}
373	ThreadJoin(threadContext->impl->thread);
374
375	MutexDeinit(&threadContext->impl->stateMutex);
376	ConditionDeinit(&threadContext->impl->stateCond);
377
378	MutexDeinit(&threadContext->impl->sync.videoFrameMutex);
379	ConditionWake(&threadContext->impl->sync.videoFrameAvailableCond);
380	ConditionDeinit(&threadContext->impl->sync.videoFrameAvailableCond);
381	ConditionWake(&threadContext->impl->sync.videoFrameRequiredCond);
382	ConditionDeinit(&threadContext->impl->sync.videoFrameRequiredCond);
383
384	ConditionWake(&threadContext->impl->sync.audioRequiredCond);
385	ConditionDeinit(&threadContext->impl->sync.audioRequiredCond);
386	MutexDeinit(&threadContext->impl->sync.audioBufferMutex);
387
388	free(threadContext->impl);
389	threadContext->impl = NULL;
390}
391
392bool mCoreThreadIsActive(struct mCoreThread* threadContext) {
393	if (!threadContext->impl) {
394		return false;
395	}
396	return threadContext->impl->state >= THREAD_RUNNING && threadContext->impl->state < THREAD_EXITING;
397}
398
399void mCoreThreadInterrupt(struct mCoreThread* threadContext) {
400	if (!threadContext) {
401		return;
402	}
403	MutexLock(&threadContext->impl->stateMutex);
404	++threadContext->impl->interruptDepth;
405	if (threadContext->impl->interruptDepth > 1 || !mCoreThreadIsActive(threadContext)) {
406		MutexUnlock(&threadContext->impl->stateMutex);
407		return;
408	}
409	threadContext->impl->savedState = threadContext->impl->state;
410	_waitOnInterrupt(threadContext->impl);
411	threadContext->impl->state = THREAD_INTERRUPTING;
412	ConditionWake(&threadContext->impl->stateCond);
413	_waitUntilNotState(threadContext->impl, THREAD_INTERRUPTING);
414	MutexUnlock(&threadContext->impl->stateMutex);
415}
416
417void mCoreThreadInterruptFromThread(struct mCoreThread* threadContext) {
418	if (!threadContext) {
419		return;
420	}
421	MutexLock(&threadContext->impl->stateMutex);
422	++threadContext->impl->interruptDepth;
423	if (threadContext->impl->interruptDepth > 1 || !mCoreThreadIsActive(threadContext)) {
424		if (threadContext->impl->state == THREAD_INTERRUPTING) {
425			threadContext->impl->state = THREAD_INTERRUPTED;
426		}
427		MutexUnlock(&threadContext->impl->stateMutex);
428		return;
429	}
430	threadContext->impl->savedState = threadContext->impl->state;
431	threadContext->impl->state = THREAD_INTERRUPTING;
432	ConditionWake(&threadContext->impl->stateCond);
433	MutexUnlock(&threadContext->impl->stateMutex);
434}
435
436void mCoreThreadContinue(struct mCoreThread* threadContext) {
437	if (!threadContext) {
438		return;
439	}
440	MutexLock(&threadContext->impl->stateMutex);
441	--threadContext->impl->interruptDepth;
442	if (threadContext->impl->interruptDepth < 1 && mCoreThreadIsActive(threadContext)) {
443		threadContext->impl->state = threadContext->impl->savedState;
444		ConditionWake(&threadContext->impl->stateCond);
445	}
446	MutexUnlock(&threadContext->impl->stateMutex);
447}
448
449void mCoreThreadRunFunction(struct mCoreThread* threadContext, void (*run)(struct mCoreThread*)) {
450	MutexLock(&threadContext->impl->stateMutex);
451	threadContext->run = run;
452	_waitOnInterrupt(threadContext->impl);
453	threadContext->impl->savedState = threadContext->impl->state;
454	threadContext->impl->state = THREAD_RUN_ON;
455	ConditionWake(&threadContext->impl->stateCond);
456	_waitUntilNotState(threadContext->impl, THREAD_RUN_ON);
457	MutexUnlock(&threadContext->impl->stateMutex);
458}
459
460void mCoreThreadPause(struct mCoreThread* threadContext) {
461	bool frameOn = threadContext->impl->sync.videoFrameOn;
462	MutexLock(&threadContext->impl->stateMutex);
463	_waitOnInterrupt(threadContext->impl);
464	if (threadContext->impl->state == THREAD_RUNNING) {
465		_pauseThread(threadContext->impl);
466		threadContext->impl->frameWasOn = frameOn;
467		frameOn = false;
468	}
469	MutexUnlock(&threadContext->impl->stateMutex);
470
471	mCoreSyncSetVideoSync(&threadContext->impl->sync, frameOn);
472}
473
474void mCoreThreadUnpause(struct mCoreThread* threadContext) {
475	bool frameOn = threadContext->impl->sync.videoFrameOn;
476	MutexLock(&threadContext->impl->stateMutex);
477	_waitOnInterrupt(threadContext->impl);
478	if (threadContext->impl->state == THREAD_PAUSED || threadContext->impl->state == THREAD_PAUSING) {
479		threadContext->impl->state = THREAD_RUNNING;
480		ConditionWake(&threadContext->impl->stateCond);
481		frameOn = threadContext->impl->frameWasOn;
482	}
483	MutexUnlock(&threadContext->impl->stateMutex);
484
485	mCoreSyncSetVideoSync(&threadContext->impl->sync, frameOn);
486}
487
488bool mCoreThreadIsPaused(struct mCoreThread* threadContext) {
489	bool isPaused;
490	MutexLock(&threadContext->impl->stateMutex);
491	if (threadContext->impl->interruptDepth) {
492		isPaused = threadContext->impl->savedState == THREAD_PAUSED;
493	} else {
494		isPaused = threadContext->impl->state == THREAD_PAUSED;
495	}
496	MutexUnlock(&threadContext->impl->stateMutex);
497	return isPaused;
498}
499
500void mCoreThreadTogglePause(struct mCoreThread* threadContext) {
501	bool frameOn = threadContext->impl->sync.videoFrameOn;
502	MutexLock(&threadContext->impl->stateMutex);
503	_waitOnInterrupt(threadContext->impl);
504	if (threadContext->impl->state == THREAD_PAUSED || threadContext->impl->state == THREAD_PAUSING) {
505		threadContext->impl->state = THREAD_RUNNING;
506		ConditionWake(&threadContext->impl->stateCond);
507		frameOn = threadContext->impl->frameWasOn;
508	} else if (threadContext->impl->state == THREAD_RUNNING) {
509		_pauseThread(threadContext->impl);
510		threadContext->impl->frameWasOn = frameOn;
511		frameOn = false;
512	}
513	MutexUnlock(&threadContext->impl->stateMutex);
514
515	mCoreSyncSetVideoSync(&threadContext->impl->sync, frameOn);
516}
517
518void mCoreThreadPauseFromThread(struct mCoreThread* threadContext) {
519	bool frameOn = true;
520	MutexLock(&threadContext->impl->stateMutex);
521	if (threadContext->impl->state == THREAD_RUNNING || (threadContext->impl->interruptDepth && threadContext->impl->savedState == THREAD_RUNNING)) {
522		threadContext->impl->state = THREAD_PAUSING;
523		frameOn = false;
524	}
525	MutexUnlock(&threadContext->impl->stateMutex);
526
527	mCoreSyncSetVideoSync(&threadContext->impl->sync, frameOn);
528}
529
530void mCoreThreadSetRewinding(struct mCoreThread* threadContext, bool rewinding) {
531	MutexLock(&threadContext->impl->stateMutex);
532	if (rewinding && (threadContext->impl->state == THREAD_REWINDING || (threadContext->impl->interruptDepth && threadContext->impl->savedState == THREAD_REWINDING))) {
533		MutexUnlock(&threadContext->impl->stateMutex);
534		return;
535	}
536	if (!rewinding && ((!threadContext->impl->interruptDepth && threadContext->impl->state != THREAD_REWINDING) || (threadContext->impl->interruptDepth && threadContext->impl->savedState != THREAD_REWINDING))) {
537		MutexUnlock(&threadContext->impl->stateMutex);
538		return;
539	}
540	_waitOnInterrupt(threadContext->impl);
541	if (rewinding && threadContext->impl->state == THREAD_RUNNING) {
542		threadContext->impl->state = THREAD_REWINDING;
543	}
544	if (!rewinding && threadContext->impl->state == THREAD_REWINDING) {
545		threadContext->impl->state = THREAD_RUNNING;
546	}
547	MutexUnlock(&threadContext->impl->stateMutex);
548}
549
550void mCoreThreadWaitFromThread(struct mCoreThread* threadContext) {
551	MutexLock(&threadContext->impl->stateMutex);
552	if (threadContext->impl->interruptDepth && threadContext->impl->savedState == THREAD_RUNNING) {
553		threadContext->impl->savedState = THREAD_WAITING;
554	} else if (threadContext->impl->state == THREAD_RUNNING) {
555		threadContext->impl->state = THREAD_WAITING;
556	}
557	MutexUnlock(&threadContext->impl->stateMutex);
558}
559
560void mCoreThreadStopWaiting(struct mCoreThread* threadContext) {
561	MutexLock(&threadContext->impl->stateMutex);
562	if (threadContext->impl->interruptDepth && threadContext->impl->savedState == THREAD_WAITING) {
563		threadContext->impl->savedState = THREAD_RUNNING;
564	} else if (threadContext->impl->state == THREAD_WAITING) {
565		threadContext->impl->state = THREAD_RUNNING;
566		ConditionWake(&threadContext->impl->stateCond);
567	}
568	MutexUnlock(&threadContext->impl->stateMutex);
569}
570
571#ifdef USE_PTHREADS
572struct mCoreThread* mCoreThreadGet(void) {
573	pthread_once(&_contextOnce, _createTLS);
574	return pthread_getspecific(_contextKey);
575}
576#elif _WIN32
577struct mCoreThread* mCoreThreadGet(void) {
578	InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
579	return TlsGetValue(_contextKey);
580}
581#else
582struct mCoreThread* mCoreThreadGet(void) {
583	return NULL;
584}
585#endif
586
587#else
588struct mCoreThread* mCoreThreadGet(void) {
589	return NULL;
590}
591#endif
592
593static void _mCoreLog(struct mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args) {
594	UNUSED(logger);
595	UNUSED(level);
596	printf("%s: ", mLogCategoryName(category));
597	vprintf(format, args);
598	printf("\n");
599}
600
601struct mLogger* mCoreThreadLogger(void) {
602	struct mCoreThread* thread = mCoreThreadGet();
603	if (thread) {
604		return &thread->logger.d;
605	}
606	return NULL;
607}
608