Strip legacy save/settings systems for a unified savemanager rewrite; split uielement list out of uielement.c

Removes the per-platform save/savestream/autosave implementations, the settings UI frames, and the autosave overlay, replacing them with a savemanager.c/h + savefile.h/savedevice.h scaffold to build the new save system on top of. Also separates the concrete UI_ELEMENTS registration into uielementlist.h/.c so uielement.c only holds the generic per-element lifecycle logic.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
2026-08-14 12:56:02 -05:00
co-authored by Claude Sonnet 5
parent 6c8e4d5cbd
commit 33f50a2c69
91 changed files with 402 additions and 5074 deletions
-1
View File
@@ -16,6 +16,5 @@ add_subdirectory(input)
if(DUSK_NETWORK)
add_subdirectory(network)
endif()
add_subdirectory(save)
add_subdirectory(system)
add_subdirectory(time)
+1 -2
View File
@@ -6,7 +6,6 @@
*/
#include "input/input.h"
#include "save/save.h"
inputbuttondata_t INPUT_BUTTON_DATA[] = {
#ifdef DUSK_INPUT_GAMEPAD
@@ -548,5 +547,5 @@ errorret_t inputInitLinux(void) {
}
float_t inputGetDeadzoneSDL2(const inputbutton_t button) {
return saveGetMeta()->deadzone;
return 0.2f;
}
-11
View File
@@ -1,11 +0,0 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
savelinux.c
savejsonlinux.c
)
-175
View File
@@ -1,175 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "save/savejsonlinux.h"
#include "util/string.h"
#include <sys/stat.h>
errorret_t saveJsonWriterInitLinux(savejsonwriterlinux_t *writer) {
writer->doc = yyjson_mut_doc_new(NULL);
if(!writer->doc) {
errorThrow("Failed to allocate JSON document");
}
writer->root = yyjson_mut_obj(writer->doc);
yyjson_mut_doc_set_root(writer->doc, writer->root);
errorOk();
}
void saveJsonWriterAddUInt32Linux(
savejsonwriterlinux_t *writer, const char_t *key, const uint32_t value
) {
yyjson_mut_obj_add_uint(writer->doc, writer->root, key, (uint64_t)value);
}
void saveJsonWriterAddFloatLinux(
savejsonwriterlinux_t *writer, const char_t *key, const float_t value
) {
yyjson_mut_obj_add_real(writer->doc, writer->root, key, (double)value);
}
void saveJsonWriterAddUInt8Linux(
savejsonwriterlinux_t *writer, const char_t *key, const uint8_t value
) {
yyjson_mut_obj_add_uint(writer->doc, writer->root, key, (uint64_t)value);
}
void saveJsonWriterAddStringLinux(
savejsonwriterlinux_t *writer, const char_t *key, const char_t *value
) {
yyjson_mut_obj_add_strcpy(writer->doc, writer->root, key, value);
}
void saveJsonWriterAddBoolArrayLinux(
savejsonwriterlinux_t *writer, const char_t *key, const bool_t *values,
const size_t count
) {
yyjson_mut_val *arr = yyjson_mut_arr(writer->doc);
for(size_t i = 0; i < count; i++) {
yyjson_mut_arr_add_bool(writer->doc, arr, values[i]);
}
yyjson_mut_obj_add_val(writer->doc, writer->root, key, arr);
}
void saveJsonWriterAddUInt8ArrayLinux(
savejsonwriterlinux_t *writer, const char_t *key, const uint8_t *values,
const size_t count
) {
yyjson_mut_val *arr = yyjson_mut_arr(writer->doc);
for(size_t i = 0; i < count; i++) {
yyjson_mut_arr_add_uint(writer->doc, arr, (uint64_t)values[i]);
}
yyjson_mut_obj_add_val(writer->doc, writer->root, key, arr);
}
errorret_t saveJsonWriterSaveLinux(
savejsonwriterlinux_t *writer, const char_t *path
) {
yyjson_write_err err;
if(!yyjson_mut_write_file(
path, writer->doc, YYJSON_WRITE_PRETTY, NULL, &err
)) {
errorThrow("Failed to write %s: %s", path, err.msg);
}
errorOk();
}
void saveJsonWriterDisposeLinux(savejsonwriterlinux_t *writer) {
if(writer->doc) {
yyjson_mut_doc_free(writer->doc);
writer->doc = NULL;
}
}
errorret_t saveJsonReaderOpenLinux(
const char_t *path, yyjson_doc **outDoc, yyjson_val **outRoot,
bool_t *found
) {
*outDoc = NULL;
*outRoot = NULL;
struct stat st;
if(stat(path, &st) != 0) {
*found = false;
errorOk();
}
yyjson_read_err err;
*outDoc = yyjson_read_file(
path, YYJSON_READ_ALLOW_COMMENTS | YYJSON_READ_ALLOW_TRAILING_COMMAS,
NULL, &err
);
if(!*outDoc) {
*found = false;
errorThrow("Failed to parse %s: %s", path, err.msg);
}
*outRoot = yyjson_doc_get_root(*outDoc);
*found = true;
errorOk();
}
uint32_t saveJsonReadUInt32Linux(
yyjson_val *root, const char_t *key, const uint32_t defaultValue
) {
yyjson_val *val = yyjson_obj_get(root, key);
if(!val || !yyjson_is_num(val)) return defaultValue;
return (uint32_t)yyjson_get_uint(val);
}
float_t saveJsonReadFloatLinux(
yyjson_val *root, const char_t *key, const float_t defaultValue
) {
yyjson_val *val = yyjson_obj_get(root, key);
if(!val || !yyjson_is_num(val)) return defaultValue;
return (float_t)yyjson_get_num(val);
}
uint8_t saveJsonReadUInt8Linux(
yyjson_val *root, const char_t *key, const uint8_t defaultValue
) {
yyjson_val *val = yyjson_obj_get(root, key);
if(!val || !yyjson_is_num(val)) return defaultValue;
return (uint8_t)yyjson_get_uint(val);
}
void saveJsonReadStringLinux(
yyjson_val *root, const char_t *key, char_t *out, const size_t maxLen,
const char_t *defaultValue
) {
yyjson_val *val = yyjson_obj_get(root, key);
const char_t *src = defaultValue;
if(val && yyjson_is_str(val)) src = yyjson_get_str(val);
stringCopy(out, src, maxLen);
}
void saveJsonReadBoolArrayLinux(
yyjson_val *root, const char_t *key, bool_t *out, const size_t count
) {
yyjson_val *arr = yyjson_obj_get(root, key);
if(!arr || !yyjson_is_arr(arr)) return;
size_t idx, len;
yyjson_val *elem;
yyjson_arr_foreach(arr, idx, len, elem) {
if(idx >= count) break;
if(yyjson_is_bool(elem)) out[idx] = yyjson_get_bool(elem);
}
}
void saveJsonReadUInt8ArrayLinux(
yyjson_val *root, const char_t *key, uint8_t *out, const size_t count
) {
yyjson_val *arr = yyjson_obj_get(root, key);
if(!arr || !yyjson_is_arr(arr)) return;
size_t idx, len;
yyjson_val *elem;
yyjson_arr_foreach(arr, idx, len, elem) {
if(idx >= count) break;
if(yyjson_is_int(elem)) out[idx] = (uint8_t)yyjson_get_int(elem);
}
}
-162
View File
@@ -1,162 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
#include "yyjson.h"
/**
* Small helper around a yyjson mutable document, used to build up a save
* file's fields one at a time before writing it out. Schema-agnostic -
* knows nothing about saveslot_t/savemeta_t; the caller supplies field
* names and values.
*/
typedef struct {
yyjson_mut_doc *doc;
yyjson_mut_val *root;
} savejsonwriterlinux_t;
/**
* Creates a new mutable JSON document with an empty root object.
*
* @param writer Writer to initialize.
* @return An error if the document can't be allocated.
*/
errorret_t saveJsonWriterInitLinux(savejsonwriterlinux_t *writer);
/**
* Adds a "version": <uint> field to the root object.
*/
void saveJsonWriterAddUInt32Linux(
savejsonwriterlinux_t *writer, const char_t *key, const uint32_t value
);
/**
* Adds a float field to the root object (written as a JSON number).
*/
void saveJsonWriterAddFloatLinux(
savejsonwriterlinux_t *writer, const char_t *key, const float_t value
);
/**
* Adds a "key": <uint> field to the root object.
*/
void saveJsonWriterAddUInt8Linux(
savejsonwriterlinux_t *writer, const char_t *key, const uint8_t value
);
/**
* Adds a string field to the root object. The value is copied into the
* document, so the caller's buffer doesn't need to outlive the call.
*/
void saveJsonWriterAddStringLinux(
savejsonwriterlinux_t *writer, const char_t *key, const char_t *value
);
/**
* Adds a JSON array of booleans as a field on the root object.
*/
void saveJsonWriterAddBoolArrayLinux(
savejsonwriterlinux_t *writer, const char_t *key, const bool_t *values,
const size_t count
);
/**
* Adds a JSON array of unsigned 8-bit integers as a field on the root
* object.
*/
void saveJsonWriterAddUInt8ArrayLinux(
savejsonwriterlinux_t *writer, const char_t *key, const uint8_t *values,
const size_t count
);
/**
* Pretty-prints the document to the given file path, creating or
* truncating it.
*
* @param writer Writer holding the document to write.
* @param path Destination file path.
* @return An error if the write fails.
*/
errorret_t saveJsonWriterSaveLinux(
savejsonwriterlinux_t *writer, const char_t *path
);
/**
* Frees the document. Safe to call even if saveJsonWriterInitLinux()
* failed partway.
*
* @param writer Writer to dispose.
*/
void saveJsonWriterDisposeLinux(savejsonwriterlinux_t *writer);
/**
* Reads and parses a JSON file, returning its root object.
*
* @param path File path to read.
* @param outDoc Receives the parsed document (must be freed via
* yyjson_doc_free() once done, regardless of found/error outcome).
* @param outRoot Receives the root object, or NULL if not found.
* @param found Set to true if the file exists, false if it does not
* (not finding the file is not an error).
* @return An error if the file exists but fails to parse.
*/
errorret_t saveJsonReaderOpenLinux(
const char_t *path, yyjson_doc **outDoc, yyjson_val **outRoot,
bool_t *found
);
/**
* Reads a uint32 field, falling back to defaultValue if the key is
* missing or not a number - a hand-edited file shouldn't hard-fail the
* whole load over one bad/missing field.
*/
uint32_t saveJsonReadUInt32Linux(
yyjson_val *root, const char_t *key, const uint32_t defaultValue
);
/**
* Reads a float field, falling back to defaultValue if the key is
* missing or not a number.
*/
float_t saveJsonReadFloatLinux(
yyjson_val *root, const char_t *key, const float_t defaultValue
);
/**
* Reads a uint8 field, falling back to defaultValue if the key is
* missing or not a number.
*/
uint8_t saveJsonReadUInt8Linux(
yyjson_val *root, const char_t *key, const uint8_t defaultValue
);
/**
* Reads a string field into out, falling back to defaultValue if the key
* is missing or not a string. Always null-terminates.
*/
void saveJsonReadStringLinux(
yyjson_val *root, const char_t *key, char_t *out, const size_t maxLen,
const char_t *defaultValue
);
/**
* Reads a JSON array of booleans into out, up to count entries. Missing
* key, non-array value, or a shorter array all leave the remaining/all
* entries untouched (caller should zero the buffer first).
*/
void saveJsonReadBoolArrayLinux(
yyjson_val *root, const char_t *key, bool_t *out, const size_t count
);
/**
* Reads a JSON array of unsigned 8-bit integers into out, up to count
* entries. Same forgiving semantics as saveJsonReadBoolArrayLinux().
*/
void saveJsonReadUInt8ArrayLinux(
yyjson_val *root, const char_t *key, uint8_t *out, const size_t count
);
-150
View File
@@ -1,150 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "save/save.h"
#include "save/savejsonlinux.h"
#include "util/string.h"
#include "util/memory.h"
#include <sys/stat.h>
#include <errno.h>
errorret_t saveInitLinux(void) {
stringCopy(SAVE.platform.savePath, SAVE_LINUX_PATH, SAVE_LINUX_PATH_MAX);
if(mkdir(SAVE.platform.savePath, 0755) != 0 && errno != EEXIST) {
errorThrow("Failed to create save directory: %s", SAVE.platform.savePath);
}
errorOk();
}
errorret_t saveDisposeLinux(void) {
errorOk();
}
static void _saveSlotPathLinux(
char_t *out, const size_t max, const uint8_t slot
) {
snprintf(
out, max, SAVE_LINUX_SLOT_FILE_FORMAT, SAVE.platform.savePath,
(uint32_t)slot
);
}
static void _saveMetaPathLinux(char_t *out, const size_t max) {
snprintf(out, max, SAVE_LINUX_META_FILE_FORMAT, SAVE.platform.savePath);
}
errorret_t saveSlotLoadLinux(const uint8_t slot, saveslot_t *out) {
char_t path[SAVE_LINUX_PATH_MAX];
_saveSlotPathLinux(path, SAVE_LINUX_PATH_MAX, slot);
yyjson_doc *doc;
yyjson_val *root;
bool_t found;
errorret_t ret = saveJsonReaderOpenLinux(path, &doc, &root, &found);
if(errorIsNotOk(ret)) { yyjson_doc_free(doc); errorChain(ret); }
if(!found) errorOk();
memoryZero(out, sizeof(saveslot_t));
out->version = saveJsonReadUInt32Linux(root, "version", SAVE_SLOT_VERSION);
saveJsonReadStringLinux(
root, "playerName", out->playerName, SAVE_PLAYER_NAME_MAX, ""
);
saveJsonReadBoolArrayLinux(
root, "globalItemCollected", out->globalItemCollected,
SAVE_GLOBAL_ITEM_COUNT_MAX
);
saveJsonReadUInt8ArrayLinux(
root, "storyFlags", out->storyFlags, SAVE_STORY_FLAG_COUNT_MAX
);
out->exists = true;
yyjson_doc_free(doc);
errorOk();
}
errorret_t saveSlotWriteLinux(const uint8_t slot, saveslot_t *slotData) {
char_t path[SAVE_LINUX_PATH_MAX];
_saveSlotPathLinux(path, SAVE_LINUX_PATH_MAX, slot);
slotData->version = SAVE_SLOT_VERSION;
savejsonwriterlinux_t writer;
errorChain(saveJsonWriterInitLinux(&writer));
saveJsonWriterAddUInt32Linux(&writer, "version", slotData->version);
saveJsonWriterAddStringLinux(&writer, "playerName", slotData->playerName);
saveJsonWriterAddBoolArrayLinux(
&writer, "globalItemCollected", slotData->globalItemCollected,
SAVE_GLOBAL_ITEM_COUNT_MAX
);
saveJsonWriterAddUInt8ArrayLinux(
&writer, "storyFlags", slotData->storyFlags, SAVE_STORY_FLAG_COUNT_MAX
);
errorret_t ret = saveJsonWriterSaveLinux(&writer, path);
saveJsonWriterDisposeLinux(&writer);
errorChain(ret);
slotData->exists = true;
errorOk();
}
errorret_t saveDeleteSlotLinux(const uint8_t slot) {
char_t path[SAVE_LINUX_PATH_MAX];
_saveSlotPathLinux(path, SAVE_LINUX_PATH_MAX, slot);
if(remove(path) != 0 && errno != ENOENT) {
errorThrow("Failed to delete save slot %u: %s", (uint32_t)slot, path);
}
errorOk();
}
errorret_t saveMetaLoadLinux(savemeta_t *out) {
char_t path[SAVE_LINUX_PATH_MAX];
_saveMetaPathLinux(path, SAVE_LINUX_PATH_MAX);
yyjson_doc *doc;
yyjson_val *root;
bool_t found;
errorret_t ret = saveJsonReaderOpenLinux(path, &doc, &root, &found);
if(errorIsNotOk(ret)) { yyjson_doc_free(doc); errorChain(ret); }
if(!found) errorOk();
out->version = saveJsonReadUInt32Linux(root, "version", SAVE_META_VERSION);
out->deadzone = saveJsonReadFloatLinux(
root, "deadzone", SAVE_META_DEADZONE_DEFAULT
);
out->language = saveJsonReadUInt8Linux(
root, "language", SAVE_META_LANGUAGE_DEFAULT
);
out->exists = true;
yyjson_doc_free(doc);
errorOk();
}
errorret_t saveMetaWriteLinux(savemeta_t *meta) {
char_t path[SAVE_LINUX_PATH_MAX];
_saveMetaPathLinux(path, SAVE_LINUX_PATH_MAX);
meta->version = SAVE_META_VERSION;
savejsonwriterlinux_t writer;
errorChain(saveJsonWriterInitLinux(&writer));
saveJsonWriterAddUInt32Linux(&writer, "version", meta->version);
saveJsonWriterAddFloatLinux(&writer, "deadzone", meta->deadzone);
saveJsonWriterAddUInt8Linux(&writer, "language", meta->language);
errorret_t ret = saveJsonWriterSaveLinux(&writer, path);
saveJsonWriterDisposeLinux(&writer);
errorChain(ret);
meta->exists = true;
errorOk();
}
-80
View File
@@ -1,80 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
#include "save/saveslot.h"
#include "save/savemeta.h"
#define SAVE_LINUX_PATH_MAX FILENAME_MAX
#define SAVE_LINUX_SLOT_FILE_FORMAT "%s/slot%u.json"
#define SAVE_LINUX_META_FILE_FORMAT "%s/settings.json"
#ifndef SAVE_LINUX_PATH
#define SAVE_LINUX_PATH "./saves"
#endif
typedef struct {
char_t savePath[SAVE_LINUX_PATH_MAX];
} savelinux_t;
/**
* Initializes the save system on Linux - ensures the save directory
* exists (shared by both save slots and meta).
*
* @return An error code if initialization fails.
*/
errorret_t saveInitLinux(void);
/**
* Disposes of the save system on Linux.
*
* @return An error code if disposal fails.
*/
errorret_t saveDisposeLinux(void);
/**
* Loads a save slot as JSON (slotN.json) from disk, if it exists.
*
* @param slot The save slot index.
* @param out Output slot data.
* @return An error code if the slot exists but fails to parse.
*/
errorret_t saveSlotLoadLinux(const uint8_t slot, saveslot_t *out);
/**
* Writes a save slot as JSON (slotN.json) to disk.
*
* @param slot The save slot index.
* @param slotData Slot data to write.
* @return An error code if the write fails.
*/
errorret_t saveSlotWriteLinux(const uint8_t slot, saveslot_t *slotData);
/**
* Deletes the save slot JSON file for the given index.
*
* @param slot The save slot index.
* @return An error code if the delete fails.
*/
errorret_t saveDeleteSlotLinux(const uint8_t slot);
/**
* Loads save meta as JSON (settings.json) from disk, if it exists.
*
* @param out Output meta data.
* @return An error code if the file exists but fails to parse.
*/
errorret_t saveMetaLoadLinux(savemeta_t *out);
/**
* Writes save meta as JSON (settings.json) to disk.
*
* @param meta Meta data to write.
* @return An error code if the write fails.
*/
errorret_t saveMetaWriteLinux(savemeta_t *meta);
-27
View File
@@ -1,27 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "save/savelinux.h"
typedef savelinux_t saveplatform_t;
// Linux fully overrides every save operation with JSON I/O (see
// savelinux.c/savejsonlinux.c) - nothing generic ever opens a
// savestream_t here, so this only needs to exist for that shared type to
// compile.
typedef struct {
uint8_t reserved;
} saveplatformstream_t;
#define saveInitPlatform saveInitLinux
#define saveDisposePlatform saveDisposeLinux
#define saveSlotDeletePlatform saveDeleteSlotLinux
#define saveSlotLoadPlatform saveSlotLoadLinux
#define saveSlotWritePlatform saveSlotWriteLinux
#define saveMetaLoadPlatform saveMetaLoadLinux
#define saveMetaWritePlatform saveMetaWriteLinux
+4
View File
@@ -13,4 +13,8 @@ errorret_t systemInitLinux() {
systemdialogtype_t systemGetActiveDialogTypeLinux() {
return SYSTEM_DIALOG_TYPE_NONE;
}
const localeinfo_t * systemGetLocaleLinux() {
return &LOCALE_DEFAULT;
}
+9 -1
View File
@@ -18,4 +18,12 @@ errorret_t systemInitLinux(void);
*
* @return Currently open system dialog type.
*/
systemdialogtype_t systemGetActiveDialogTypeLinux();
systemdialogtype_t systemGetActiveDialogTypeLinux();
/**
* Returns the current locale of the system. Linux has no system language
* query wired up yet, so this always returns LOCALE_DEFAULT.
*
* @return The current locale.
*/
const localeinfo_t * systemGetLocaleLinux();
+2 -1
View File
@@ -9,4 +9,5 @@
#include "system/systemlinux.h"
#define systemInitPlatform systemInitLinux
#define systemGetActiveDialogTypePlatform systemGetActiveDialogTypeLinux
#define systemGetActiveDialogTypePlatform systemGetActiveDialogTypeLinux
#define systemGetLocalePlatform systemGetLocaleLinux