Add autosave system with forced no-save-device prompt on failure
autoSaveQueue() is a single callable entry point for requesting an autosave; autoSaveUpdate() pumps the queue each frame and, if the write fails (e.g. no memory card), forces the existing no-card modal back open and pauses world simulation until the player retries or accepts a temporary session. A small overlay shows "SAVING" while a write is in flight. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
#include "system/system.h"
|
||||
#include "console/console.h"
|
||||
#include "save/save.h"
|
||||
#include "save/autosave.h"
|
||||
|
||||
engine_t ENGINE;
|
||||
|
||||
@@ -55,7 +56,6 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
||||
|
||||
sceneSet(SCENE_TYPE_INITIAL);
|
||||
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ errorret_t engineUpdate(void) {
|
||||
// Order here is important.
|
||||
errorChain(networkUpdate());
|
||||
errorChain(saveUpdate());
|
||||
autoSaveUpdate();
|
||||
timeUpdate();
|
||||
inputUpdate();
|
||||
consoleUpdate();
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "util/memory.h"
|
||||
#include "assert/assert.h"
|
||||
#include "save/save.h"
|
||||
#include "save/autosave.h"
|
||||
#include "error/error.h"
|
||||
#include "scene/scene.h"
|
||||
|
||||
@@ -78,6 +79,12 @@ errorret_t rpgUpdate(void) {
|
||||
}
|
||||
#endif
|
||||
|
||||
// A failed autosave forces a no-save-device prompt (see autosave.h) -
|
||||
// freeze the world entirely until the player resolves it, rather than
|
||||
// letting NPCs/cutscenes/camera keep running behind a modal that's
|
||||
// supposed to be blocking.
|
||||
if(autoSaveIsBlocking()) errorOk();
|
||||
|
||||
// TODO: Do not update if the scene is not the map scene?
|
||||
errorChain(mapUpdate());
|
||||
|
||||
|
||||
@@ -8,4 +8,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
save.c
|
||||
savestream.c
|
||||
autosave.c
|
||||
)
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "autosave.h"
|
||||
#include "save.h"
|
||||
#include "error/error.h"
|
||||
#include "ui/frame/initial/uiinitialnocard.h"
|
||||
|
||||
autosave_t AUTO_SAVE;
|
||||
|
||||
static void autoSaveNoCardResult(const bool_t retry, void *user) {
|
||||
AUTO_SAVE.blocking = false;
|
||||
|
||||
// "Retry" just re-queues the write for the next tick - there's no way
|
||||
// to force a re-probe of the hardware mid-session (see saveInit()'s doc
|
||||
// comment), so this only actually helps for a transient failure.
|
||||
if(retry) {
|
||||
AUTO_SAVE.pending = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// The player explicitly acknowledged there's no save device and chose
|
||||
// to proceed anyway - sticks for the rest of the session, so future
|
||||
// autoSaveUpdate() calls silently skip instead of prompting again.
|
||||
saveMarkTemporary();
|
||||
}
|
||||
|
||||
static void autoSaveWriteComplete(errorret_t result, void *user) {
|
||||
AUTO_SAVE.saving = false;
|
||||
if(errorIsOk(result)) return;
|
||||
|
||||
errorCatch(errorPrint(result));
|
||||
|
||||
// The write failed - most likely no save medium is present right now
|
||||
// (a GameCube with no memory card inserted, for example). Pause world
|
||||
// simulation and force the same prompt the initial boot scene uses so
|
||||
// the player can insert a card and retry, or explicitly accept a
|
||||
// temporary, unsaved session.
|
||||
AUTO_SAVE.blocking = true;
|
||||
uiInitialNoCardOpen(autoSaveNoCardResult, NULL);
|
||||
}
|
||||
|
||||
void autoSaveQueue(void) {
|
||||
AUTO_SAVE.pending = true;
|
||||
}
|
||||
|
||||
void autoSaveUpdate(void) {
|
||||
if(AUTO_SAVE.blocking) return;
|
||||
if(!AUTO_SAVE.pending) return;
|
||||
if(saveIsBusy()) return;
|
||||
|
||||
AUTO_SAVE.pending = false;
|
||||
|
||||
// Already opted out of saving for this session - nothing to do, and
|
||||
// definitely don't reprompt every time an autosave is queued.
|
||||
if(saveIsTemporary()) return;
|
||||
|
||||
AUTO_SAVE.saving = true;
|
||||
saveWriteSlot(SAVE_ACTIVE_SLOT, autoSaveWriteComplete, NULL);
|
||||
}
|
||||
|
||||
bool_t autoSaveIsSaving(void) {
|
||||
return AUTO_SAVE.saving;
|
||||
}
|
||||
|
||||
bool_t autoSaveIsBlocking(void) {
|
||||
return AUTO_SAVE.blocking;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
|
||||
typedef struct {
|
||||
/** True from autoSaveQueue() until the queued write starts. */
|
||||
bool_t pending;
|
||||
/** True while the queued write is actually in flight. */
|
||||
bool_t saving;
|
||||
/**
|
||||
* True while the forced no-save-device prompt is up after a queued
|
||||
* write failed - see autoSaveUpdate(). World simulation must pause
|
||||
* while this is true (see rpgUpdate()).
|
||||
*/
|
||||
bool_t blocking;
|
||||
} autosave_t;
|
||||
|
||||
extern autosave_t AUTO_SAVE;
|
||||
|
||||
/**
|
||||
* Queues an autosave to run on a future engine tick. Safe to call as
|
||||
* often as needed (e.g. after every map transition or story event) -
|
||||
* repeated calls before the queued save starts just collapse into one.
|
||||
*/
|
||||
void autoSaveQueue(void);
|
||||
|
||||
/**
|
||||
* Pumps the queued autosave: starts the write once nothing else is
|
||||
* using the save system, and if that write fails (e.g. a GameCube with
|
||||
* no memory card inserted), forces the same no-save-device prompt the
|
||||
* initial boot scene uses and pauses world simulation until the player
|
||||
* resolves it. Must be called every engine frame, after saveUpdate().
|
||||
*/
|
||||
void autoSaveUpdate(void);
|
||||
|
||||
/**
|
||||
* True while a queued autosave's write is actually in progress. Intended
|
||||
* for UI to show a "Saving" indicator.
|
||||
*
|
||||
* @return true if an autosave write is currently in flight.
|
||||
*/
|
||||
bool_t autoSaveIsSaving(void);
|
||||
|
||||
/**
|
||||
* True while a failed autosave is forcing the no-save-device prompt and
|
||||
* waiting on the player's decision. Intended for the world simulation to
|
||||
* pause while this is true.
|
||||
*
|
||||
* @return true if an autosave is currently blocking on player input.
|
||||
*/
|
||||
bool_t autoSaveIsBlocking(void);
|
||||
@@ -8,4 +8,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
uicrop.c
|
||||
uifullbox.c
|
||||
uiloading.c
|
||||
uiautosave.c
|
||||
)
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "uiautosave.h"
|
||||
#include "save/autosave.h"
|
||||
#include "display/text/text.h"
|
||||
#include "display/screen/screen.h"
|
||||
#include "display/spritebatch/spritebatch.h"
|
||||
|
||||
// TODO: Localize once this grows beyond a placeholder indicator.
|
||||
#define UI_AUTOSAVE_TEXT "SAVING"
|
||||
|
||||
errorret_t uiAutoSaveDraw(void) {
|
||||
if(!autoSaveIsSaving()) errorOk();
|
||||
|
||||
int32_t textW, textH;
|
||||
textMeasure(UI_AUTOSAVE_TEXT, &FONT_DEFAULT, &textW, &textH);
|
||||
|
||||
float_t x = (float_t)SCREEN.scanX + UI_AUTOSAVE_MARGIN;
|
||||
float_t y = (float_t)(SCREEN.scanY + SCREEN.scanHeight) -
|
||||
(float_t)textH - UI_AUTOSAVE_MARGIN;
|
||||
|
||||
errorChain(textDraw(x, y, UI_AUTOSAVE_TEXT, COLOR_WHITE, &FONT_DEFAULT));
|
||||
return spriteBatchFlush();
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
|
||||
#define UI_AUTOSAVE_MARGIN 8.0f
|
||||
|
||||
/**
|
||||
* Draws a "Saving" indicator in the bottom-left corner of the screen
|
||||
* while an autosave write is in flight (see save/autosave.h). No-op
|
||||
* otherwise.
|
||||
*
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
errorret_t uiAutoSaveDraw(void);
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "engine/engine.h"
|
||||
#include "ui/overlay/uifullbox.h"
|
||||
#include "ui/overlay/uiloading.h"
|
||||
#include "ui/overlay/uiautosave.h"
|
||||
#include "ui/debug/uiplayerpos.h"
|
||||
#include "ui/overlay/uicrop.h"
|
||||
#include "ui/transition/uitransition.h"
|
||||
@@ -129,6 +130,8 @@ uielement_t UI_ELEMENTS[] = {
|
||||
.draw = uiCropDraw
|
||||
},
|
||||
|
||||
{ .draw = uiAutoSaveDraw },
|
||||
|
||||
// Debug items
|
||||
{ .draw = uiConsoleDraw },
|
||||
{ .draw = uiFPSDraw },
|
||||
|
||||
Reference in New Issue
Block a user