all repos — mgba @ db408920ca40417bb1826b82738aefeed06b4814

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