all repos — mgba @ faa27b075484f97bc6a233f7476be06565efe2b9

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