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