Added savestatus

This commit is contained in:
2026-08-17 00:17:23 -05:00
parent 560c51cf27
commit 08b4bbfe91
13 changed files with 502 additions and 16 deletions
+2 -1
View File
@@ -17,7 +17,8 @@ console_t CONSOLE;
void consoleInit(void) {
memoryZero(&CONSOLE, sizeof(console_t));
CONSOLE.visible = false;
// CONSOLE.visible = false;
CONSOLE.visible = true;
threadMutexInit(&CONSOLE.printMutex);
}
+1
View File
@@ -10,4 +10,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
savedevice.c
saveslot.c
savesettings.c
savestatus.c
)
+16
View File
@@ -87,6 +87,10 @@ errorret_t saveDeviceSlotWrite(
assertNotNull(device, "device cannot be null");
assertNotNull(slot, "slot cannot be null");
#ifdef saveDeviceSlotWritePlatform
errorChain(saveDeviceSlotWritePlatform(device, slot, slotIndex));
#endif
errorOk();
}
@@ -98,6 +102,10 @@ errorret_t saveDeviceSlotRead(
assertNotNull(device, "device cannot be null");
assertNotNull(slot, "slot cannot be null");
#ifdef saveDeviceSlotReadPlatform
errorChain(saveDeviceSlotReadPlatform(device, slot, slotIndex));
#endif
errorOk();
}
@@ -108,6 +116,10 @@ errorret_t saveDeviceSettingsWrite(
assertNotNull(device, "device cannot be null");
assertNotNull(settings, "settings cannot be null");
#ifdef saveDeviceSettingsWritePlatform
errorChain(saveDeviceSettingsWritePlatform(device, settings));
#endif
errorOk();
}
@@ -118,6 +130,10 @@ errorret_t saveDeviceSettingsRead(
assertNotNull(device, "device cannot be null");
assertNotNull(settings, "settings cannot be null");
#ifdef saveDeviceSettingsReadPlatform
errorChain(saveDeviceSettingsReadPlatform(device, settings));
#endif
errorOk();
}
+58
View File
@@ -23,6 +23,12 @@ errorret_t saveManagerInit() {
// Initialize the default settings.
saveSettingsInit(&SAVE_MANAGER.settings);
// Initialize the default (not in use) status for each save file.
savestatus_t *status = &SAVE_MANAGER.slotStatuses[0];
do {
saveStatusInit(status);
} while(status++ < &SAVE_MANAGER.slotStatuses[SAVE_SLOT_COUNT - 1]);
// Start by initializing each of the save devices.
savedevice_t *device = &SAVE_MANAGER.devices[0];
do {
@@ -151,11 +157,14 @@ void saveManagerOnDeviceAvailabilityChecked(savedevice_t *device, void *user) {
errorret_t saveManagerSaveSettings() {
assertTrue(SAVE_MANAGER.deviceCurrent != 0xFF, "No current device");
if(!SAVE_MANAGER.settingsDirty) errorOk();
errorChain(saveDeviceSettingsWrite(
&SAVE_MANAGER.devices[SAVE_MANAGER.deviceCurrent],
&SAVE_MANAGER.settings
));
SAVE_MANAGER.settingsDirty = false;
errorOk();
}
@@ -167,12 +176,16 @@ errorret_t saveManagerLoadSettings() {
&SAVE_MANAGER.settings
));
SAVE_MANAGER.settingsDirty = false;
errorOk();
}
errorret_t saveManagerSaveSlot() {
assertTrue(SAVE_MANAGER.deviceCurrent != 0xFF, "No current device");
assertTrue(SAVE_MANAGER.slotCurrent != 0xFF, "No current slot");
assertTrue(saveSlotInUse(&SAVE_MANAGER.slot), "Save slot is not valid");
if(!SAVE_MANAGER.slotDirty) errorOk();
errorChain(saveDeviceSlotWrite(
&SAVE_MANAGER.devices[SAVE_MANAGER.deviceCurrent],
@@ -180,6 +193,7 @@ errorret_t saveManagerSaveSlot() {
SAVE_MANAGER.slotCurrent
));
SAVE_MANAGER.slotDirty = false;
errorOk();
}
@@ -193,6 +207,50 @@ errorret_t saveManagerLoadSlot() {
SAVE_MANAGER.slotCurrent
));
SAVE_MANAGER.slotDirty = false;
errorOk();
}
errorret_t saveManagerRefreshSlotStatuses() {
assertTrue(SAVE_MANAGER.deviceCurrent != 0xFF, "No current device");
saveslot_t slot;
for(uint8_t i = 0; i < SAVE_SLOT_COUNT; i++) {
saveSlotInit(&slot);
errorChain(saveDeviceSlotRead(
&SAVE_MANAGER.devices[SAVE_MANAGER.deviceCurrent],
&slot,
i
));
saveStatusFromSlot(&SAVE_MANAGER.slotStatuses[i], &slot);
}
errorOk();
}
errorret_t saveManagerSetSlot(uint8_t slotIndex) {
assertTrue(slotIndex != 0xFF, "Slot index cannot be 0xFF");
SAVE_MANAGER.slotCurrent = slotIndex;
SAVE_MANAGER.slotDirty = true;
errorChain(saveManagerLoadSlot());
errorOk();
}
errorret_t saveManagerSave() {
errorChain(saveManagerSaveSettings());
errorChain(saveManagerSaveSlot());
errorOk();
}
errorret_t saveManagerLoad() {
errorChain(saveManagerLoadSettings());
errorChain(saveManagerLoadSlot());
errorOk();
}
+39
View File
@@ -9,6 +9,7 @@
#include "savedevice.h"
#include "saveslot.h"
#include "savesettings.h"
#include "savestatus.h"
typedef struct {
// File state
@@ -19,6 +20,10 @@ typedef struct {
bool_t settingsDirty;
bool_t slotDirty;
// Basic info about each save file on the current device, refreshed
// whenever the current device changes.
savestatus_t slotStatuses[SAVE_SLOT_COUNT];
// Device state
savedevice_t devices[SAVE_DEVICE_COUNT];
uint8_t deviceCurrent;
@@ -94,6 +99,40 @@ errorret_t saveManagerSaveSlot();
*/
errorret_t saveManagerLoadSlot();
/**
* Walks every slot index on the current device and reads the basic, at-a-
* glance information for each into SAVE_MANAGER.slotStatuses. This does not
* affect the currently loaded slot or the current slot index.
*
* @return Error state if any.
*/
errorret_t saveManagerRefreshSlotStatuses();
/**
* Sets the current slot index, and attempts to load that slot's data from
* the current device.
*
* @param slotIndex The slot index to make current.
* @return Error state if any.
*/
errorret_t saveManagerSetSlot(uint8_t slotIndex);
/**
* Saves both the current settings and the current slot to the current
* device.
*
* @return Error state if any.
*/
errorret_t saveManagerSave();
/**
* Loads both the current settings and the current slot from the current
* device.
*
* @return Error state if any.
*/
errorret_t saveManagerLoad();
/**
* Disposes of the save manager.
*
+16
View File
@@ -0,0 +1,16 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
// Custom error codes for the save system, these start at 2 since ERROR_OK
// and ERROR_NOT_OK occupy 0 and 1 respectively.
#define SAVE_ERROR_NO_CURRENT_DEVICE ((errorcode_t)2)
#define SAVE_ERROR_NO_CURRENT_SLOT ((errorcode_t)3)
#define SAVE_ERROR_DEVICE_NOT_AVAILABLE ((errorcode_t)4)
#define SAVE_ERROR_SLOT_NOT_IN_USE ((errorcode_t)5)
+1
View File
@@ -11,6 +11,7 @@
#include "yyjson.h"
#define SAVE_SLOT_NAME_LENGTH 8
#define SAVE_SLOT_COUNT 4
#pragma pack(push, 1)
typedef struct saveslot_s {
+25
View File
@@ -0,0 +1,25 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "savestatus.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "util/string.h"
void saveStatusInit(savestatus_t *status) {
assertNotNull(status, "Status cannot be null");
memoryZero(status, sizeof(savestatus_t));
}
void saveStatusFromSlot(savestatus_t *status, saveslot_t *slot) {
assertNotNull(status, "Status cannot be null");
assertNotNull(slot, "Slot cannot be null");
status->inUse = saveSlotInUse(slot);
stringCopy(status->name, slot->name, SAVE_SLOT_NAME_LENGTH);
status->time = slot->time;
}
+34
View File
@@ -0,0 +1,34 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "time/timeepoch.h"
#include "saveslot.h"
#pragma pack(push, 1)
typedef struct savestatus_s {
bool_t inUse;
char_t name[SAVE_SLOT_NAME_LENGTH + 1];
dusktimeepoch_t time;
} savestatus_t;
#pragma pack(pop)
/**
* Inits the save status with the default (not in use) state.
*
* @param status The save status to init.
*/
void saveStatusInit(savestatus_t *status);
/**
* Populates the save status with the basic, at-a-glance information taken
* from a fully loaded save slot.
*
* @param status The save status to populate.
* @param slot The save slot to read the basic information from.
*/
void saveStatusFromSlot(savestatus_t *status, saveslot_t *slot);
+4 -1
View File
@@ -19,7 +19,10 @@ void testCallback(savedevice_t *device, void *user) {
if(device == NULL) {
consolePrint("No save device found.");
} else {
consolePrint("Found save device: %s", device->reasonKey);
uint8_t index = (uint8_t)(device - &SAVE_MANAGER.devices[0]);
consolePrint(
"Found save device %u: %s", (uint32_t)index, device->reasonKey
);
}
}
+197 -5
View File
@@ -1,18 +1,23 @@
/**
* Copyright (c) 2026 Dominic Masters
*
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "save/savedevice.h"
#include "save/savedevicelinux.h"
#include "save/saveslot.h"
#include "save/savesettings.h"
#include "save/savejson.h"
#include "assert/assert.h"
#include "util/mkdirp.h"
#include "util/string.h"
#include <stdlib.h>
errorret_t saveDeviceLinuxInit(savedevice_t *device) {
assertNotNull(device, "device cannot be null");
device->state = SAVE_DEVICE_STATE_UNKNOWN;
errorOk();
@@ -31,9 +36,21 @@ void saveDeviceLinuxCheckAvailability(savedevice_t *device) {
device->state == SAVE_DEVICE_STATE_CHECKING_AVAILABILITY,
"Device state incorrect?"
);
// Resolve the save directory.
char_t directory[SAVE_DEVICE_LINUX_PATH_MAX];
errorret_t directoryResult = saveDeviceLinuxGetDirectory(
directory, sizeof(directory)
);
if(errorIsNotOk(directoryResult)) {
errorCatch(errorPrint(directoryResult));
device->state = SAVE_DEVICE_STATE_UNAVAILABLE;
device->reasonKey = "save.linux.no_home";
return saveDeviceFireCallback(device);
}
// Mkdirp the save directory.
int mkdirpResult = mkdirp(SAVE_DEVICE_LINUX_DIRECTORY, 0700);
int mkdirpResult = mkdirp(directory, 0700);
if(mkdirpResult != 0) {
device->state = SAVE_DEVICE_STATE_UNAVAILABLE;
device->reasonKey = "save.linux.mkdirp_failed";
@@ -46,6 +63,181 @@ void saveDeviceLinuxCheckAvailability(savedevice_t *device) {
saveDeviceFireCallback(device);
}
errorret_t saveDeviceLinuxGetDirectory(char_t *dest, const size_t destSize) {
assertNotNull(dest, "dest cannot be null");
const char_t *home = getenv("HOME");
if(home == NULL) errorThrow("HOME environment variable is not set");
stringFormat(dest, destSize, "%s/%s", home, SAVE_DEVICE_LINUX_DIRECTORY_NAME);
errorOk();
}
errorret_t saveDeviceLinuxGetSettingsPath(
char_t *dest,
const size_t destSize
) {
assertNotNull(dest, "dest cannot be null");
char_t directory[SAVE_DEVICE_LINUX_PATH_MAX];
errorChain(saveDeviceLinuxGetDirectory(directory, sizeof(directory)));
stringFormat(
dest, destSize, "%s/%s", directory, SAVE_DEVICE_LINUX_SETTINGS_FILENAME
);
errorOk();
}
errorret_t saveDeviceLinuxGetSlotPath(
char_t *dest,
const size_t destSize,
const uint8_t slotIndex
) {
assertNotNull(dest, "dest cannot be null");
char_t directory[SAVE_DEVICE_LINUX_PATH_MAX];
errorChain(saveDeviceLinuxGetDirectory(directory, sizeof(directory)));
char_t filename[SAVE_SLOT_NAME_LENGTH + 16];
stringFormat(
filename, sizeof(filename) - 1,
SAVE_DEVICE_LINUX_SLOT_FILENAME_FORMAT, (uint32_t)slotIndex
);
stringFormat(dest, destSize, "%s/%s", directory, filename);
errorOk();
}
errorret_t saveDeviceLinuxSlotWrite(
savedevice_t *device,
saveslot_t *slot,
uint8_t slotIndex
) {
assertNotNull(device, "device cannot be null");
assertNotNull(slot, "slot cannot be null");
char_t path[SAVE_DEVICE_LINUX_PATH_MAX];
errorChain(saveDeviceLinuxGetSlotPath(path, sizeof(path), slotIndex));
writeInit();
errorret_t writeResult = saveSlotWriteJSON(slot, doc, object);
if(errorIsNotOk(writeResult)) {
yyjson_mut_doc_free(doc);
errorChain(writeResult);
}
yyjson_write_err writeErr;
bool_t written = yyjson_mut_write_file(path, doc, 0, NULL, &writeErr);
yyjson_mut_doc_free(doc);
if(!written) {
errorThrow(
"Failed to write save slot file: %s (%s)", path, writeErr.msg
);
}
errorOk();
}
errorret_t saveDeviceLinuxSlotRead(
savedevice_t *device,
saveslot_t *slot,
uint8_t slotIndex
) {
assertNotNull(device, "device cannot be null");
assertNotNull(slot, "slot cannot be null");
char_t path[SAVE_DEVICE_LINUX_PATH_MAX];
errorChain(saveDeviceLinuxGetSlotPath(path, sizeof(path), slotIndex));
yyjson_read_err readErr;
yyjson_doc *jsonDoc = yyjson_read_file(path, 0, NULL, &readErr);
if(jsonDoc == NULL) {
// No save file at this slot yet, leave the slot as-is.
if(readErr.code == YYJSON_READ_ERROR_FILE_OPEN) errorOk();
errorThrow(
"Failed to parse save slot file: %s (%s)", path, readErr.msg
);
}
yyjson_val *object = yyjson_doc_get_root(jsonDoc);
if(object == NULL) {
yyjson_doc_free(jsonDoc);
errorThrow("Save slot file missing root object: %s", path);
}
errorret_t readResult = saveSlotReadJSON(slot, object);
yyjson_doc_free(jsonDoc);
errorChain(readResult);
errorOk();
}
errorret_t saveDeviceLinuxSettingsWrite(
savedevice_t *device,
savesettings_t *settings
) {
assertNotNull(device, "device cannot be null");
assertNotNull(settings, "settings cannot be null");
char_t path[SAVE_DEVICE_LINUX_PATH_MAX];
errorChain(saveDeviceLinuxGetSettingsPath(path, sizeof(path)));
writeInit();
errorret_t writeResult = saveSettingsWriteJSON(settings, doc, object);
if(errorIsNotOk(writeResult)) {
yyjson_mut_doc_free(doc);
errorChain(writeResult);
}
yyjson_write_err writeErr;
bool_t written = yyjson_mut_write_file(path, doc, 0, NULL, &writeErr);
yyjson_mut_doc_free(doc);
if(!written) {
errorThrow(
"Failed to write save settings file: %s (%s)", path, writeErr.msg
);
}
errorOk();
}
errorret_t saveDeviceLinuxSettingsRead(
savedevice_t *device,
savesettings_t *settings
) {
assertNotNull(device, "device cannot be null");
assertNotNull(settings, "settings cannot be null");
char_t path[SAVE_DEVICE_LINUX_PATH_MAX];
errorChain(saveDeviceLinuxGetSettingsPath(path, sizeof(path)));
yyjson_read_err readErr;
yyjson_doc *jsonDoc = yyjson_read_file(path, 0, NULL, &readErr);
if(jsonDoc == NULL) {
// No settings file yet, leave settings as-is.
if(readErr.code == YYJSON_READ_ERROR_FILE_OPEN) errorOk();
errorThrow(
"Failed to parse save settings file: %s (%s)", path, readErr.msg
);
}
yyjson_val *object = yyjson_doc_get_root(jsonDoc);
if(object == NULL) {
yyjson_doc_free(jsonDoc);
errorThrow("Save settings file missing root object: %s", path);
}
errorret_t readResult = saveSettingsReadJSON(settings, object);
yyjson_doc_free(jsonDoc);
errorChain(readResult);
errorOk();
}
errorret_t saveDeviceLinuxDispose(savedevice_t *device) {
errorOk();
}
}
+104 -8
View File
@@ -1,6 +1,6 @@
/**
* Copyright (c) 2026 Dominic Masters
*
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
@@ -8,18 +8,24 @@
#pragma once
#include "error/error.h"
#define SAVE_DEVICE_LINUX_DIRECTORY "~/.dusk/saves"
#define SAVE_DEVICE_LINUX_FILE "~/.dusk/saves/save.dat"
// Saves live under $HOME/<SAVE_DEVICE_LINUX_DIRECTORY_NAME>, one JSON file
// per save slot plus a separate settings file.
#define SAVE_DEVICE_LINUX_DIRECTORY_NAME ".dusk/saves"
#define SAVE_DEVICE_LINUX_SETTINGS_FILENAME "settings.json"
#define SAVE_DEVICE_LINUX_SLOT_FILENAME_FORMAT "slot%u.json"
#define SAVE_DEVICE_LINUX_PATH_MAX 512
typedef struct {
void *nothing;
} savedeviceplatform_t;
typedef struct savedevice_s savedevice_t;
typedef struct saveslot_s saveslot_t;
typedef struct savesettings_s savesettings_t;
/**
* Initializes the save device platform.
*
*
* @param device The save device platform to initialize.
* @return Error state if any.
*/
@@ -27,7 +33,7 @@ errorret_t saveDeviceLinuxInit(savedevice_t *device);
/**
* Updates the save device platform.
*
*
* @param device The save device platform to update.
* @return Error state if any.
*/
@@ -36,15 +42,105 @@ errorret_t saveDeviceLinuxUpdate(savedevice_t *device);
/**
* Requests the device to check its availability, this will call the callback
* whence completed.
*
*
* @param device The save device platform to check availability.
*/
void saveDeviceLinuxCheckAvailability(savedevice_t *device);
/**
* Builds the absolute path to the directory saves are stored in, creating
* intermediate directories is not done by this method, see mkdirp.
*
* @param dest The destination buffer to write the path into.
* @param destSize The size of the destination buffer, exc. null terminator.
* @return Error state if any.
*/
errorret_t saveDeviceLinuxGetDirectory(char_t *dest, const size_t destSize);
/**
* Builds the absolute path to the settings file.
*
* @param dest The destination buffer to write the path into.
* @param destSize The size of the destination buffer, exc. null terminator.
* @return Error state if any.
*/
errorret_t saveDeviceLinuxGetSettingsPath(
char_t *dest,
const size_t destSize
);
/**
* Builds the absolute path to the given save slot's file.
*
* @param dest The destination buffer to write the path into.
* @param destSize The size of the destination buffer, exc. null terminator.
* @param slotIndex The slot index to build the path for.
* @return Error state if any.
*/
errorret_t saveDeviceLinuxGetSlotPath(
char_t *dest,
const size_t destSize,
const uint8_t slotIndex
);
/**
* Writes the given save slot out to its file on disk.
*
* @param device The save device to write to.
* @param slot The save slot to write.
* @param slotIndex The slot index to write to.
* @return Error state if any.
*/
errorret_t saveDeviceLinuxSlotWrite(
savedevice_t *device,
saveslot_t *slot,
uint8_t slotIndex
);
/**
* Reads a save slot in from its file on disk. If no file exists yet for the
* given slot index, this is not an error - the slot is simply left as-is.
*
* @param device The save device to read from.
* @param slot The save slot to read into.
* @param slotIndex The slot index to read from.
* @return Error state if any.
*/
errorret_t saveDeviceLinuxSlotRead(
savedevice_t *device,
saveslot_t *slot,
uint8_t slotIndex
);
/**
* Writes the given save settings out to the settings file on disk.
*
* @param device The save device to write to.
* @param settings The save settings to write.
* @return Error state if any.
*/
errorret_t saveDeviceLinuxSettingsWrite(
savedevice_t *device,
savesettings_t *settings
);
/**
* Reads the save settings in from the settings file on disk. If no settings
* file exists yet, this is not an error - settings is simply left as-is.
*
* @param device The save device to read from.
* @param settings The save settings to read into.
* @return Error state if any.
*/
errorret_t saveDeviceLinuxSettingsRead(
savedevice_t *device,
savesettings_t *settings
);
/**
* Disposes of the save device platform.
*
*
* @param device The save device platform to dispose.
* @return Error state if any.
*/
errorret_t saveDeviceLinuxDispose(savedevice_t *device);
errorret_t saveDeviceLinuxDispose(savedevice_t *device);
+5 -1
View File
@@ -13,4 +13,8 @@
#define saveDevicePlatformInit saveDeviceLinuxInit
#define saveDevicePlatformUpdate saveDeviceLinuxUpdate
#define saveDeviceCheckAvailabilityPlatform saveDeviceLinuxCheckAvailability
#define saveDevicePlatformDispose saveDeviceLinuxDispose
#define saveDevicePlatformDispose saveDeviceLinuxDispose
#define saveDeviceSlotWritePlatform saveDeviceLinuxSlotWrite
#define saveDeviceSlotReadPlatform saveDeviceLinuxSlotRead
#define saveDeviceSettingsWritePlatform saveDeviceLinuxSettingsWrite
#define saveDeviceSettingsReadPlatform saveDeviceLinuxSettingsRead