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 "system/system.h"
|
||||||
#include "console/console.h"
|
#include "console/console.h"
|
||||||
#include "save/save.h"
|
#include "save/save.h"
|
||||||
|
#include "save/autosave.h"
|
||||||
|
|
||||||
engine_t ENGINE;
|
engine_t ENGINE;
|
||||||
|
|
||||||
@@ -54,7 +55,6 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
sceneSet(SCENE_TYPE_INITIAL);
|
sceneSet(SCENE_TYPE_INITIAL);
|
||||||
|
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
@@ -63,6 +63,7 @@ errorret_t engineUpdate(void) {
|
|||||||
// Order here is important.
|
// Order here is important.
|
||||||
errorChain(networkUpdate());
|
errorChain(networkUpdate());
|
||||||
errorChain(saveUpdate());
|
errorChain(saveUpdate());
|
||||||
|
autoSaveUpdate();
|
||||||
timeUpdate();
|
timeUpdate();
|
||||||
inputUpdate();
|
inputUpdate();
|
||||||
consoleUpdate();
|
consoleUpdate();
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
#include "save/save.h"
|
#include "save/save.h"
|
||||||
|
#include "save/autosave.h"
|
||||||
#include "error/error.h"
|
#include "error/error.h"
|
||||||
#include "scene/scene.h"
|
#include "scene/scene.h"
|
||||||
|
|
||||||
@@ -78,6 +79,12 @@ errorret_t rpgUpdate(void) {
|
|||||||
}
|
}
|
||||||
#endif
|
#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?
|
// TODO: Do not update if the scene is not the map scene?
|
||||||
errorChain(mapUpdate());
|
errorChain(mapUpdate());
|
||||||
|
|
||||||
|
|||||||
@@ -8,4 +8,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
PUBLIC
|
PUBLIC
|
||||||
save.c
|
save.c
|
||||||
savestream.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
|
uicrop.c
|
||||||
uifullbox.c
|
uifullbox.c
|
||||||
uiloading.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 "engine/engine.h"
|
||||||
#include "ui/overlay/uifullbox.h"
|
#include "ui/overlay/uifullbox.h"
|
||||||
#include "ui/overlay/uiloading.h"
|
#include "ui/overlay/uiloading.h"
|
||||||
|
#include "ui/overlay/uiautosave.h"
|
||||||
#include "ui/debug/uiplayerpos.h"
|
#include "ui/debug/uiplayerpos.h"
|
||||||
#include "ui/overlay/uicrop.h"
|
#include "ui/overlay/uicrop.h"
|
||||||
#include "ui/transition/uitransition.h"
|
#include "ui/transition/uitransition.h"
|
||||||
@@ -129,11 +130,13 @@ uielement_t UI_ELEMENTS[] = {
|
|||||||
.draw = uiCropDraw
|
.draw = uiCropDraw
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{ .draw = uiAutoSaveDraw },
|
||||||
|
|
||||||
// Debug items
|
// Debug items
|
||||||
{ .draw = uiConsoleDraw },
|
{ .draw = uiConsoleDraw },
|
||||||
{ .draw = uiFPSDraw },
|
{ .draw = uiFPSDraw },
|
||||||
{ .draw = uiPlayerPosDraw },
|
{ .draw = uiPlayerPosDraw },
|
||||||
|
|
||||||
{ 0 } // Null terminator
|
{ 0 } // Null terminator
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user