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