GUI: Make autosave portable

This commit is contained in:
Vicki Pfau 2018-01-20 15:00:45 -08:00
parent 0e9ba00dbf
commit 45c2fdf7ed
3 changed files with 124 additions and 102 deletions

View File

@ -23,6 +23,7 @@
mLOG_DECLARE_CATEGORY(GUI_RUNNER);
mLOG_DEFINE_CATEGORY(GUI_RUNNER, "GUI Runner", "gui.runner");
#define AUTOSAVE_GRANULARITY 600
#define FPS_GRANULARITY 120
#define FPS_BUFFER_SIZE 3
@ -34,19 +35,11 @@ enum {
RUNNER_SCREENSHOT,
RUNNER_CONFIG,
RUNNER_RESET,
RUNNER_COMMAND_MASK = 0xFFFF,
RUNNER_STATE_1 = 0x10000,
RUNNER_STATE_2 = 0x20000,
RUNNER_STATE_3 = 0x30000,
RUNNER_STATE_4 = 0x40000,
RUNNER_STATE_5 = 0x50000,
RUNNER_STATE_6 = 0x60000,
RUNNER_STATE_7 = 0x70000,
RUNNER_STATE_8 = 0x80000,
RUNNER_STATE_9 = 0x90000,
RUNNER_COMMAND_MASK = 0xFFFF
};
#define RUNNER_STATE(X) ((X) << 16)
static const struct mInputPlatformInfo _mGUIKeyInfo = {
.platformName = "gui",
.keyId = (const char*[GUI_INPUT_MAX]) {
@ -141,6 +134,21 @@ static uint8_t _readLux(struct GBALuminanceSource* lux) {
return 0xFF - value;
}
static void _tryAutosave(struct mGUIRunner* runner) {
#ifdef DISABLE_THREADING
mCoreSaveState(runner->core, 0, SAVESTATE_SAVEDATA | SAVESTATE_RTC | SAVESTATE_METADATA);
#else
if (!runner->autosave.buffer) {
runner->autosave.buffer = VFileMemChunk(NULL, 0);
}
MutexLock(&runner->autosave.mutex);
runner->autosave.core = runner->core;
mCoreSaveStateNamed(runner->core, runner->autosave.buffer, SAVESTATE_SAVEDATA | SAVESTATE_RTC | SAVESTATE_METADATA);
ConditionWake(&runner->autosave.cond);
MutexUnlock(&runner->autosave.mutex);
#endif
}
void mGUIInit(struct mGUIRunner* runner, const char* port) {
GUIInit(&runner->params);
runner->port = port;
@ -174,9 +182,34 @@ void mGUIInit(struct mGUIRunner* runner, const char* port) {
strncpy(runner->params.currentPath, lastPath, PATH_MAX - 1);
runner->params.currentPath[PATH_MAX - 1] = '\0';
}
#ifndef DISABLE_THREADING
if (!runner->autosave.running) {
runner->autosave.running = true;
MutexInit(&runner->autosave.mutex);
ConditionInit(&runner->autosave.cond);
ThreadCreate(&runner->autosave.thread, mGUIAutosaveThread, &runner->autosave);
}
#endif
}
void mGUIDeinit(struct mGUIRunner* runner) {
#ifndef DISABLE_THREADING
MutexLock(&runner->autosave.mutex);
runner->autosave.running = false;
ConditionWake(&runner->autosave.cond);
MutexUnlock(&runner->autosave.mutex);
ThreadJoin(runner->autosave.thread);
ConditionDeinit(&runner->autosave.cond);
MutexDeinit(&runner->autosave.mutex);
if (runner->autosave.buffer) {
runner->autosave.buffer->close(runner->autosave.buffer);
}
#endif
if (runner->teardown) {
runner->teardown(runner);
}
@ -239,25 +272,26 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) {
*GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Save state", .submenu = &stateSaveMenu };
*GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Load state", .submenu = &stateLoadMenu };
*GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 1", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_1) };
*GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 2", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_2) };
*GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 3", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_3) };
*GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 4", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_4) };
*GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 5", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_5) };
*GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 6", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_6) };
*GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 7", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_7) };
*GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 8", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_8) };
*GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 9", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_9) };
*GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 1", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE(1)) };
*GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 2", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE(2)) };
*GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 3", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE(3)) };
*GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 4", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE(4)) };
*GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 5", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE(5)) };
*GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 6", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE(6)) };
*GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 7", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE(7)) };
*GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 8", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE(8)) };
*GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 9", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE(9)) };
*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 1", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_1) };
*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 2", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_2) };
*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 3", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_3) };
*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 4", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_4) };
*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 5", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_5) };
*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 6", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_6) };
*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 7", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_7) };
*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 8", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_8) };
*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 9", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_9) };
*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "Autosave", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE(0)) };
*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 1", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE(1)) };
*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 2", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE(2)) };
*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 3", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE(3)) };
*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 4", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE(4)) };
*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 5", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE(5)) };
*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 6", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE(6)) };
*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 7", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE(7)) };
*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 8", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE(8)) };
*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 9", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE(9)) };
*GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Take screenshot", .data = (void*) RUNNER_SCREENSHOT };
*GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Configure", .data = (void*) RUNNER_CONFIG };
@ -325,6 +359,13 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) {
}
bool running = true;
#ifndef DISABLE_THREADING
MutexLock(&runner->autosave.mutex);
runner->autosave.core = runner->core;
MutexUnlock(&runner->autosave.mutex);
#endif
if (runner->gameLoaded) {
runner->gameLoaded(runner);
}
@ -415,6 +456,9 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) {
runner->fps = (CircleBufferSize(&runner->fpsBuffer) * FPS_GRANULARITY * 1000000.0f) / (runner->totalDelta * sizeof(uint32_t));
}
}
if (runner->core->frameCounter(runner->core) % AUTOSAVE_GRANULARITY == 0) {
_tryAutosave(runner);
}
}
}
@ -467,6 +511,11 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) {
if (runner->gameUnloaded) {
runner->gameUnloaded(runner);
}
#ifndef DISABLE_THREADING
MutexLock(&runner->autosave.mutex);
runner->autosave.core = NULL;
MutexUnlock(&runner->autosave.mutex);
#endif
mLOG(GUI_RUNNER, DEBUG, "Unloading game...");
runner->core->unloadROM(runner->core);
drawState.screenshotId = 0;
@ -524,3 +573,21 @@ void mGUIRunloop(struct mGUIRunner* runner) {
mGUIRun(runner, path);
}
}
#ifndef DISABLE_THREADING
THREAD_ENTRY mGUIAutosaveThread(void* context) {
struct mGUIAutosaveContext* autosave = context;
MutexLock(&autosave->mutex);
while (autosave->running) {
ConditionWait(&autosave->cond, &autosave->mutex);
if (autosave->running && autosave->core) {
struct VFile* vf = mCoreGetState(autosave->core, 0, true);
void* mem = autosave->buffer->map(autosave->buffer, autosave->buffer->size(autosave->buffer), MAP_READ);
vf->write(vf, mem, autosave->buffer->size(autosave->buffer));
autosave->buffer->unmap(autosave->buffer, mem, autosave->buffer->size(autosave->buffer));
vf->close(vf);
}
}
MutexUnlock(&autosave->mutex);
}
#endif

View File

@ -15,6 +15,7 @@ CXX_GUARD_START
#include <mgba/internal/gba/hardware.h>
#include <mgba-util/circle-buffer.h>
#include <mgba-util/gui.h>
#include <mgba-util/threading.h>
enum mGUIInput {
mGUI_INPUT_INCREASE_BRIGHTNESS = GUI_INPUT_USER_START,
@ -38,12 +39,27 @@ struct mGUIRunnerLux {
int luxLevel;
};
#ifndef DISABLE_THREADING
struct VFile;
struct mGUIAutosaveContext {
struct VFile* buffer;
struct mCore* core;
Thread thread;
Mutex mutex;
Condition cond;
bool running;
};
#endif
struct mGUIRunner {
struct mCore* core;
struct GUIParams params;
struct mGUIBackground background;
struct mGUIRunnerLux luminanceSource;
#ifndef DISABLE_THREADING
struct mGUIAutosaveContext autosave;
#endif
struct mInputMap guiKeys;
struct mCoreConfig config;
@ -78,6 +94,10 @@ void mGUIDeinit(struct mGUIRunner*);
void mGUIRun(struct mGUIRunner*, const char* path);
void mGUIRunloop(struct mGUIRunner*);
#ifndef DISABLE_THREADING
THREAD_ENTRY mGUIAutosaveThread(void* context);
#endif
CXX_GUARD_END
#endif

View File

@ -106,12 +106,6 @@ static C3D_Tex upscaleBufferTex;
static aptHookCookie cookie;
static Thread autosave;
static struct VFile* autosaveBuffer = NULL;
static Mutex autosaveMutex;
static Condition autosaveCond;
static struct mCore* autosaveCore = NULL;
extern bool allocateRomBuffer(void);
static bool _initGpu(void) {
@ -201,33 +195,6 @@ static void _aptHook(APT_HookType hook, void* user) {
}
}
static void _autosaveThread(void* context) {
bool* running = context;
MutexLock(&autosaveMutex);
while (*running) {
ConditionWait(&autosaveCond, &autosaveMutex);
if (*running && autosaveCore) {
struct VFile* vf = mCoreGetState(autosaveCore, 0, true);
void* mem = autosaveBuffer->map(autosaveBuffer, autosaveBuffer->size(autosaveBuffer), MAP_READ);
vf->write(vf, mem, autosaveBuffer->size(autosaveBuffer));
autosaveBuffer->unmap(autosaveBuffer, mem, autosaveBuffer->size(autosaveBuffer));
vf->close(vf);
}
}
MutexUnlock(&autosaveMutex);
}
static void _tryAutosave(struct mCore* core) {
if (!autosaveBuffer) {
autosaveBuffer = VFileMemChunk(NULL, 0);
}
MutexLock(&autosaveMutex);
autosaveCore = core;
mCoreSaveStateNamed(core, autosaveBuffer, SAVESTATE_SAVEDATA | SAVESTATE_RTC | SAVESTATE_METADATA);
ConditionWake(&autosaveCond);
MutexUnlock(&autosaveMutex);
}
static void _map3DSKey(struct mInputMap* map, int ctrKey, enum GBAKey key) {
mInputBindKey(map, _3DS_INPUT, __builtin_ctz(ctrKey), key);
}
@ -460,10 +427,6 @@ static void _gameLoaded(struct mGUIRunner* runner) {
}
}
}
MutexLock(&autosaveMutex);
autosaveCore = runner->core;
MutexUnlock(&autosaveMutex);
}
static void _gameUnloaded(struct mGUIRunner* runner) {
@ -497,10 +460,6 @@ static void _gameUnloaded(struct mGUIRunner* runner) {
default:
break;
}
MutexLock(&autosaveMutex);
autosaveCore = NULL;
MutexUnlock(&autosaveMutex);
}
static void _drawTex(struct mCore* core, bool faded) {
@ -722,16 +681,7 @@ static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) {
}
static bool _running(struct mGUIRunner* runner) {
static int frame = 0;
if (autosaveCore) {
++frame;
if (frame == 300) {
_tryAutosave(autosaveCore);
frame = 0;
}
} else {
frame = 0;
}
UNUSED(runner);
return aptMainLoop();
}
@ -1081,6 +1031,13 @@ int main() {
.running = _running
};
runner.autosave.running = true;
MutexInit(&runner.autosave.mutex);
ConditionInit(&runner.autosave.cond);
APT_SetAppCpuTimeLimit(20);
runner.autosave.thread = threadCreate(mGUIAutosaveThread, &runner.autosave, 0x4000, 0x1F, 1, true);
mGUIInit(&runner, "3ds");
_map3DSKey(&runner.params.keyMap, KEY_X, GUI_INPUT_CANCEL);
@ -1094,29 +1051,7 @@ int main() {
_map3DSKey(&runner.params.keyMap, KEY_CSTICK_UP, mGUI_INPUT_INCREASE_BRIGHTNESS);
_map3DSKey(&runner.params.keyMap, KEY_CSTICK_DOWN, mGUI_INPUT_DECREASE_BRIGHTNESS);
bool autosaveActive = true;
MutexInit(&autosaveMutex);
ConditionInit(&autosaveCond);
APT_SetAppCpuTimeLimit(20);
autosave = threadCreate(_autosaveThread, &autosaveActive, 0x4000, 0x1F, 1, true);
mGUIRunloop(&runner);
MutexLock(&autosaveMutex);
autosaveActive = false;
ConditionWake(&autosaveCond);
MutexUnlock(&autosaveMutex);
threadJoin(autosave, U64_MAX);
if (autosaveBuffer) {
autosaveBuffer->close(autosaveBuffer);
}
ConditionDeinit(&autosaveCond);
MutexDeinit(&autosaveMutex);
mGUIDeinit(&runner);
_cleanup();