all repos — mgba @ 2def7289f3cb02c3a3edc51e26ce3f634033e264

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/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