GUI: Make autosave portable
@@ -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,18 +35,10 @@ RUNNER_LOAD_STATE,
RUNNER_SCREENSHOT, RUNNER_CONFIG, RUNNER_RESET, - RUNNER_COMMAND_MASK = 0xFFFF, + 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, -}; +#define RUNNER_STATE(X) ((X) << 16) static const struct mInputPlatformInfo _mGUIKeyInfo = { .platformName = "gui",@@ -141,6 +134,21 @@ }
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 @@ if (lastPath) {
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 @@ *GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Unpause", .data = (void*) RUNNER_CONTINUE };
*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 @@ autosave->close(autosave);
} 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); }@@ -414,6 +455,9 @@ CircleBufferWrite32(&runner->fpsBuffer, delta);
runner->totalDelta += delta; 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 @@ mLOG(GUI_RUNNER, DEBUG, "Shutting down...");
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 @@ mCoreConfigSave(&runner->config);
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
@@ -15,6 +15,7 @@ #include "feature/gui/remap.h"
#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 GBALuminanceSource d;
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;@@ -77,6 +93,10 @@ void mGUIInit(struct mGUIRunner*, const char* port);
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
@@ -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) {@@ -199,33 +193,6 @@ break;
default: break; } -} - -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) {@@ -460,10 +427,6 @@ CAMU_Activate(camera.cam);
} } } - - MutexLock(&autosaveMutex); - autosaveCore = runner->core; - MutexUnlock(&autosaveMutex); } static void _gameUnloaded(struct mGUIRunner* runner) {@@ -497,10 +460,6 @@ #endif
default: break; } - - MutexLock(&autosaveMutex); - autosaveCore = NULL; - MutexUnlock(&autosaveMutex); } static void _drawTex(struct mCore* core, bool faded) {@@ -722,16 +681,7 @@ tickCounter = svcGetSystemTick();
} 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 @@ .pollGameInput = _pollGameInput,
.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 @@ _map3DSKey(&runner.params.keyMap, KEY_RIGHT, GUI_INPUT_RIGHT);
_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();