mgba/src/feature/thread-proxy.c

213 lines
7.4 KiB
C

/* Copyright (c) 2013-2017 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <mgba/feature/thread-proxy.h>
#include <mgba/core/tile-cache.h>
#include <mgba/internal/gba/gba.h>
#ifndef DISABLE_THREADING
static void mVideoThreadProxyInit(struct mVideoLogger* logger);
static void mVideoThreadProxyReset(struct mVideoLogger* logger);
static void mVideoThreadProxyDeinit(struct mVideoLogger* logger);
static THREAD_ENTRY _proxyThread(void* renderer);
static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length);
static bool _readData(struct mVideoLogger* logger, void* data, size_t length, bool block);
static void _postEvent(struct mVideoLogger* logger, enum mVideoLoggerEvent);
static void _lock(struct mVideoLogger* logger);
static void _unlock(struct mVideoLogger* logger);
static void _wait(struct mVideoLogger* logger);
static void _wake(struct mVideoLogger* logger, int y);
void mVideoThreadProxyCreate(struct mVideoThreadProxy* renderer) {
mVideoLoggerRendererCreate(&renderer->d, false);
renderer->d.block = true;
renderer->d.init = mVideoThreadProxyInit;
renderer->d.reset = mVideoThreadProxyReset;
renderer->d.deinit = mVideoThreadProxyDeinit;
renderer->d.lock = _lock;
renderer->d.unlock = _unlock;
renderer->d.wait = _wait;
renderer->d.wake = _wake;
renderer->d.writeData = _writeData;
renderer->d.readData = _readData;
renderer->d.postEvent = _postEvent;
}
void mVideoThreadProxyInit(struct mVideoLogger* logger) {
struct mVideoThreadProxy* proxyRenderer = (struct mVideoThreadProxy*) logger;
ConditionInit(&proxyRenderer->fromThreadCond);
ConditionInit(&proxyRenderer->toThreadCond);
MutexInit(&proxyRenderer->mutex);
RingFIFOInit(&proxyRenderer->dirtyQueue, 0x40000);
proxyRenderer->threadState = PROXY_THREAD_IDLE;
ThreadCreate(&proxyRenderer->thread, _proxyThread, proxyRenderer);
}
void mVideoThreadProxyReset(struct mVideoLogger* logger) {
struct mVideoThreadProxy* proxyRenderer = (struct mVideoThreadProxy*) logger;
MutexLock(&proxyRenderer->mutex);
while (proxyRenderer->threadState == PROXY_THREAD_BUSY) {
ConditionWake(&proxyRenderer->toThreadCond);
ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
}
MutexUnlock(&proxyRenderer->mutex);
}
void mVideoThreadProxyDeinit(struct mVideoLogger* logger) {
struct mVideoThreadProxy* proxyRenderer = (struct mVideoThreadProxy*) logger;
bool waiting = false;
MutexLock(&proxyRenderer->mutex);
while (proxyRenderer->threadState == PROXY_THREAD_BUSY) {
ConditionWake(&proxyRenderer->toThreadCond);
ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
}
if (proxyRenderer->threadState == PROXY_THREAD_IDLE) {
proxyRenderer->threadState = PROXY_THREAD_STOPPED;
ConditionWake(&proxyRenderer->toThreadCond);
waiting = true;
}
MutexUnlock(&proxyRenderer->mutex);
if (waiting) {
ThreadJoin(&proxyRenderer->thread);
}
RingFIFODeinit(&proxyRenderer->dirtyQueue);
ConditionDeinit(&proxyRenderer->fromThreadCond);
ConditionDeinit(&proxyRenderer->toThreadCond);
MutexDeinit(&proxyRenderer->mutex);
}
void _proxyThreadRecover(struct mVideoThreadProxy* proxyRenderer) {
MutexLock(&proxyRenderer->mutex);
if (proxyRenderer->threadState != PROXY_THREAD_STOPPED) {
MutexUnlock(&proxyRenderer->mutex);
return;
}
RingFIFOClear(&proxyRenderer->dirtyQueue);
MutexUnlock(&proxyRenderer->mutex);
ThreadJoin(&proxyRenderer->thread);
proxyRenderer->threadState = PROXY_THREAD_IDLE;
ThreadCreate(&proxyRenderer->thread, _proxyThread, proxyRenderer);
}
static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length) {
struct mVideoThreadProxy* proxyRenderer = (struct mVideoThreadProxy*) logger;
while (!RingFIFOWrite(&proxyRenderer->dirtyQueue, data, length)) {
mLOG(GBA_VIDEO, DEBUG, "Can't write %"PRIz"u bytes. Proxy thread asleep?", length);
MutexLock(&proxyRenderer->mutex);
if (proxyRenderer->threadState == PROXY_THREAD_STOPPED) {
mLOG(GBA_VIDEO, ERROR, "Proxy thread stopped prematurely!");
MutexUnlock(&proxyRenderer->mutex);
return false;
}
ConditionWake(&proxyRenderer->toThreadCond);
ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
MutexUnlock(&proxyRenderer->mutex);
}
return true;
}
static bool _readData(struct mVideoLogger* logger, void* data, size_t length, bool block) {
struct mVideoThreadProxy* proxyRenderer = (struct mVideoThreadProxy*) logger;
bool read = false;
while (true) {
read = RingFIFORead(&proxyRenderer->dirtyQueue, data, length);
if (!block || read) {
break;
}
mLOG(GBA_VIDEO, DEBUG, "Can't read %"PRIz"u bytes. CPU thread asleep?", length);
MutexLock(&proxyRenderer->mutex);
ConditionWake(&proxyRenderer->fromThreadCond);
ConditionWait(&proxyRenderer->toThreadCond, &proxyRenderer->mutex);
MutexUnlock(&proxyRenderer->mutex);
}
return read;
}
static void _postEvent(struct mVideoLogger* logger, enum mVideoLoggerEvent event) {
struct mVideoThreadProxy* proxyRenderer = (struct mVideoThreadProxy*) logger;
MutexLock(&proxyRenderer->mutex);
proxyRenderer->event = event;
ConditionWake(&proxyRenderer->toThreadCond);
MutexUnlock(&proxyRenderer->mutex);
}
static void _lock(struct mVideoLogger* logger) {
struct mVideoThreadProxy* proxyRenderer = (struct mVideoThreadProxy*) logger;
MutexLock(&proxyRenderer->mutex);
}
static void _wait(struct mVideoLogger* logger) {
struct mVideoThreadProxy* proxyRenderer = (struct mVideoThreadProxy*) logger;
if (proxyRenderer->threadState == PROXY_THREAD_STOPPED) {
mLOG(GBA_VIDEO, ERROR, "Proxy thread stopped prematurely!");
_proxyThreadRecover(proxyRenderer);
return;
}
MutexLock(&proxyRenderer->mutex);
while (RingFIFOSize(&proxyRenderer->dirtyQueue)) {
ConditionWake(&proxyRenderer->toThreadCond);
ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
}
MutexUnlock(&proxyRenderer->mutex);
}
static void _unlock(struct mVideoLogger* logger) {
struct mVideoThreadProxy* proxyRenderer = (struct mVideoThreadProxy*) logger;
MutexUnlock(&proxyRenderer->mutex);
}
static void _wake(struct mVideoLogger* logger, int y) {
struct mVideoThreadProxy* proxyRenderer = (struct mVideoThreadProxy*) logger;
if ((y & 15) == 15) {
ConditionWake(&proxyRenderer->toThreadCond);
}
}
static THREAD_ENTRY _proxyThread(void* logger) {
struct mVideoThreadProxy* proxyRenderer = logger;
ThreadSetName("Proxy Renderer Thread");
MutexLock(&proxyRenderer->mutex);
while (proxyRenderer->threadState != PROXY_THREAD_STOPPED) {
ConditionWait(&proxyRenderer->toThreadCond, &proxyRenderer->mutex);
if (proxyRenderer->threadState == PROXY_THREAD_STOPPED) {
break;
}
proxyRenderer->threadState = PROXY_THREAD_BUSY;
if (proxyRenderer->event) {
proxyRenderer->d.handleEvent(&proxyRenderer->d, proxyRenderer->event);
proxyRenderer->event = 0;
} else {
MutexUnlock(&proxyRenderer->mutex);
if (!mVideoLoggerRendererRun(&proxyRenderer->d, false)) {
// FIFO was corrupted
proxyRenderer->threadState = PROXY_THREAD_STOPPED;
mLOG(GBA_VIDEO, ERROR, "Proxy thread queue got corrupted!");
}
MutexLock(&proxyRenderer->mutex);
}
ConditionWake(&proxyRenderer->fromThreadCond);
if (proxyRenderer->threadState != PROXY_THREAD_STOPPED) {
proxyRenderer->threadState = PROXY_THREAD_IDLE;
}
}
MutexUnlock(&proxyRenderer->mutex);
#ifdef _3DS
svcExitThread();
#endif
return 0;
}
#endif