Modified sectional saving to utilize an integer ID for calling specific sections to cut down on string copying. Utilizes enum values for non-mod sections to allow for cross-project use of those IDs, and sets up `AddSaveFunction` to add mod sections after that using max value enum.

This commit is contained in:
Malkierian 2023-05-15 17:21:14 -07:00
parent 4bc0d7d60d
commit 50fbe5d00c
5 changed files with 114 additions and 87 deletions

View File

@ -61,6 +61,15 @@ typedef struct {
u8 isRoom;
} SceneTimestamp;
typedef enum { // Pre-existing IDs for save sections in base code
SECTION_ID_BASE,
SECTION_ID_RANDOMIZER,
SECTION_ID_STATS,
SECTION_ID_ENTRANCES,
SECTION_ID_SCENES,
SECTION_ID_MAX
} SaveFuncIDs;
typedef struct {
/* */ char buildVersion[50];
/* */ s16 buildVersionMajor;

View File

@ -231,8 +231,8 @@ void LoadStatsVersion1() {
});
}
void SaveStats(SaveContext* saveContext, const std::string& subSection) {
if (subSection == "all") {
void SaveStats(SaveContext* saveContext, int sectionID) {
if (sectionID == SECTION_ID_BASE) {
std::string buildVersion;
SaveManager::Instance->LoadData("buildVersion", buildVersion);
strncpy(gSaveContext.sohStats.buildVersion, buildVersion.c_str(), ARRAY_COUNT(gSaveContext.sohStats.buildVersion) - 1);
@ -268,12 +268,12 @@ void SaveStats(SaveContext* saveContext, const std::string& subSection) {
SaveManager::Instance->LoadData("", gSaveContext.sohStats.locationsSkipped[i]);
});
}
if (subSection == "entrances" || subSection == "all") {
if (sectionID == SECTION_ID_ENTRANCES || sectionID == SECTION_ID_BASE) {
SaveManager::Instance->SaveArray("entrancesDiscovered", ARRAY_COUNT(saveContext->sohStats.entrancesDiscovered), [&](size_t i) {
SaveManager::Instance->SaveData("", saveContext->sohStats.entrancesDiscovered[i]);
});
}
if (subSection == "scenes" || subSection == "all") {
if (sectionID == SECTION_ID_SCENES || sectionID == SECTION_ID_BASE) {
SaveManager::Instance->SaveArray("scenesDiscovered", ARRAY_COUNT(saveContext->sohStats.scenesDiscovered), [&](size_t i) {
SaveManager::Instance->SaveData("", saveContext->sohStats.scenesDiscovered[i]);
});
@ -821,7 +821,10 @@ extern "C" void InitStatTracker() {
SetupDisplayNames();
SetupDisplayColors();
SaveManager::Instance->AddLoadFunction("sohStats", 1, LoadStatsVersion1);
SaveManager::Instance->AddSaveFunction("sohStats", 1, SaveStats);
// Add main section save, no parent
SaveManager::Instance->AddSaveFunction("sohStats", 1, SaveStats, true, SECTION_PARENT_NONE);
// Add subsections, parent of "sohStats". Not sure how to do this without the redundant references to "SaveStats"
SaveManager::Instance->AddSaveFunction("entrances", 1, SaveStats, false, SECTION_ID_STATS);
SaveManager::Instance->AddSaveFunction("scenes", 1, SaveStats, false, SECTION_ID_STATS);
SaveManager::Instance->AddInitFunction(InitStats);
SaveManager::Instance->RegisterGameSaveSection("sohStats");
}

View File

@ -769,7 +769,8 @@ void Entrance_SetSceneDiscovered(u8 sceneNum) {
u32 sceneBit = 1 << (sceneNum - (idx * bitsPerIndex));
gSaveContext.sohStats.scenesDiscovered[idx] |= sceneBit;
}
Save_SaveSection("sohStats.scenes");
// Save scenesDiscovered
Save_SaveSection(SECTION_ID_SCENES);
}
u8 Entrance_GetIsEntranceDiscovered(u16 entranceIndex) {
@ -802,5 +803,6 @@ void Entrance_SetEntranceDiscovered(u16 entranceIndex) {
}
}
}
Save_SaveSection("sohStats.entrances");
// Save entrancesDiscovered
Save_SaveSection(SECTION_ID_ENTRANCES);
}

View File

@ -17,6 +17,7 @@
#include <array>
extern "C" SaveContext gSaveContext;
using namespace std::string_literals;
void SaveManager::WriteSaveFile(const std::filesystem::path& savePath, const uintptr_t addr, void* dramAddr,
const size_t size) {
@ -46,17 +47,20 @@ std::filesystem::path SaveManager::GetFileName(int fileNum) {
}
SaveManager::SaveManager() {
coreSectionIDsByName["base"] = SECTION_ID_BASE;
coreSectionIDsByName["randomizer"] = SECTION_ID_RANDOMIZER;
coreSectionIDsByName["sohStats"] = SECTION_ID_STATS;
coreSectionIDsByName["entrances"] = SECTION_ID_ENTRANCES;
coreSectionIDsByName["scenes"] = SECTION_ID_SCENES;
AddLoadFunction("base", 1, LoadBaseVersion1);
AddLoadFunction("base", 2, LoadBaseVersion2);
AddLoadFunction("base", 3, LoadBaseVersion3);
AddLoadFunction("base", 4, LoadBaseVersion4);
AddSaveFunction("base", 4, SaveBase);
RegisterGameSaveSection("base");
AddSaveFunction("base", 4, SaveBase, true, SECTION_PARENT_NONE);
AddLoadFunction("randomizer", 1, LoadRandomizerVersion1);
AddLoadFunction("randomizer", 2, LoadRandomizerVersion2);
AddSaveFunction("randomizer", 2, SaveRandomizer);
RegisterGameSaveSection("randomizer");
AddSaveFunction("randomizer", 2, SaveRandomizer, true, SECTION_PARENT_NONE);
AddInitFunction(InitFileImpl);
@ -86,20 +90,6 @@ SaveManager::SaveManager() {
}
}
// GameSaveSection functions affect the list of sections that save their data when a game save is triggered
void SaveManager::RegisterGameSaveSection(std::string section) {
if (std::find(gameSaveRegistry.begin(), gameSaveRegistry.end(), section) == gameSaveRegistry.end()) {
gameSaveRegistry.push_back(section);
}
}
void SaveManager::UnregisterAutosaveSection(std::string section) {
auto find = std::find(gameSaveRegistry.begin(), gameSaveRegistry.end(), section);
if (find != gameSaveRegistry.end()) {
gameSaveRegistry.erase(find);
}
}
void SaveManager::LoadRandomizerVersion1() {
for (int i = 0; i < ARRAY_COUNT(gSaveContext.itemLocations); i++) {
SaveManager::Instance->LoadStruct("get" + std::to_string(i), [&]() {
@ -278,7 +268,7 @@ void SaveManager::LoadRandomizerVersion2() {
});
}
void SaveManager::SaveRandomizer(SaveContext* saveContext, const std::string& subString) {
void SaveManager::SaveRandomizer(SaveContext* saveContext, int sectionID) {
if(!saveContext->n64ddFlag) return;
@ -700,40 +690,42 @@ void SaveManager::InitFileDebug() {
gSaveContext.sceneFlags[5].swch = 0x40000000;
}
// Threaded SaveFile takes copy of gSaveContext for local unmodified storage
void SaveManager::SaveFileThreaded(int fileNum, SaveContext* saveContext, const std::string& sectionString) {
void SaveManager::SaveFileThreaded(int fileNum, SaveContext* saveContext, int sectionID) {
// Needed for first time save, hasn't changed in forever anyway
saveBlock["version"] = 1;
size_t period = sectionString.find(".");
std::string section = sectionString;
std::string subsection = "";
if (period != std::string::npos) {
subsection = sectionString.substr(period + 1, std::string::npos);
section = sectionString.substr(0, sectionString.length() - (subsection.length() + 1));
}
if (sectionString == "all") {
for (auto& sectionHandler : sectionSaveHandlers) {
if (std::find(gameSaveRegistry.begin(), gameSaveRegistry.end(), sectionHandler.first) != gameSaveRegistry.end()) {
nlohmann::json& sectionBlock = saveBlock["sections"][sectionHandler.first];
sectionBlock["version"] = sectionHandler.second.first;
// If any save file is loaded for medatata, or a spoiler log is loaded (not sure which at this point), there is still data in the "randomizer" section
// This clears the randomizer data block if and only if the section being called is "randomizer" and n64ddFlag is false.
if (sectionHandler.first == "randomizer" && !gSaveContext.n64ddFlag) {
sectionBlock["data"] = nlohmann::json::object();
}
currentJsonContext = &sectionBlock["data"];
sectionHandler.second.second(saveContext, "all");
if (sectionID == SECTION_ID_BASE) {
for (auto& sectionHandlerPair : sectionSaveHandlers) {
auto& saveFuncInfo = sectionHandlerPair.second;
// Don't call SaveFuncs for sections that aren't tied to game save
if (!saveFuncInfo.saveWithBase) {
continue;
}
nlohmann::json& sectionBlock = saveBlock["sections"][saveFuncInfo.name];
sectionBlock["version"] = sectionHandlerPair.second.version;
// If any save file is loaded for medatata, or a spoiler log is loaded (not sure which at this point), there is still data in the "randomizer" section
// This clears the randomizer data block if and only if the section being called is "randomizer" and n64ddFlag is false.
if (sectionHandlerPair.second.name == "randomizer" && !gSaveContext.n64ddFlag) {
sectionBlock["data"] = nlohmann::json::object();
continue;
}
currentJsonContext = &sectionBlock["data"];
sectionHandlerPair.second.func(saveContext, sectionID);
}
} else if (sectionSaveHandlers.contains(section)) {
SectionSaveHandler handler = sectionSaveHandlers.find(section)->second;
nlohmann::json& sectionBlock = saveBlock["sections"][section];
sectionBlock["version"] = handler.first;
currentJsonContext = &sectionBlock["data"];
handler.second(saveContext, subsection);
} else {
// save function for specified section does not exist. should this error?
return;
SaveFuncInfo svi = sectionSaveHandlers.find(sectionID)->second;
auto& sectionName = svi.name;
auto sectionVersion = svi.version;
// If section has a parentSection, it is a subsection. Load parentSection version and set sectionBlock to parent string
if (svi.parentSection != -1 && svi.parentSection < sectionIndex) {
auto parentSvi = sectionSaveHandlers.find(svi.parentSection)->second;
sectionName = parentSvi.name;
sectionVersion = parentSvi.version;
}
nlohmann::json& sectionBlock = saveBlock["sections"][sectionName];
sectionBlock["version"] = sectionVersion;
currentJsonContext = &sectionBlock["data"];
svi.func(saveContext, sectionID);
}
#if defined(__SWITCH__) || defined(__WIIU__)
@ -751,18 +743,24 @@ void SaveManager::SaveFileThreaded(int fileNum, SaveContext* saveContext, const
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnSaveFile>(fileNum);
}
void SaveManager::SaveSection(int fileNum, const std::string& sectionString) {
// SaveSection creates a copy of gSaveContext to prevent mid-save data modification, and passes its reference to SaveFileThreaded
void SaveManager::SaveSection(int fileNum, int sectionID) {
if (fileNum == 0xFF) {
return;
}
// Can't think of any time the promise would be needed, so use push_task instead of submit
// Don't save a nonexistent section
if (sectionID >= sectionIndex) {
SPDLOG_ERROR("SaveSection: Section ID not registered.");
return;
}
auto saveContext = new SaveContext;
memcpy(saveContext, &gSaveContext, sizeof(gSaveContext));
smThreadPool->push_task_back(&SaveManager::SaveFileThreaded, this, fileNum, saveContext, sectionString);
// Can't think of any time the promise would be needed, so use push_task instead of submit
smThreadPool->push_task_back(&SaveManager::SaveFileThreaded, this, fileNum, saveContext, sectionID);
}
void SaveManager::SaveFile(int fileNum) {
SaveSection(fileNum, "all");
SaveSection(fileNum, SECTION_ID_BASE);
}
void SaveManager::SaveGlobal() {
@ -860,14 +858,21 @@ void SaveManager::AddLoadFunction(const std::string& name, int version, LoadFunc
sectionLoadHandlers[name][version] = func;
}
void SaveManager::AddSaveFunction(const std::string& name, int version, SaveFunc func) {
if (sectionSaveHandlers.contains(name)) {
void SaveManager::AddSaveFunction(const std::string& name, int version, SaveFunc func, bool saveWithBase, int parentSection = -1) {
if (sectionRegistry.contains(name)) {
SPDLOG_ERROR("Adding save function for section that already has one: " + name);
assert(false);
return;
}
sectionSaveHandlers[name] = std::make_pair(version, func);
int index = sectionIndex;
if (coreSectionIDsByName.contains(name)) {
index = coreSectionIDsByName.find(name)->second;
} else {
sectionIndex++;
}
SaveFuncInfo sfi = { name, version, func, saveWithBase, parentSection };
sectionSaveHandlers.emplace(index, sfi);
sectionRegistry.emplace(name);
}
void SaveManager::AddPostFunction(const std::string& name, PostFunc func) {
@ -1623,7 +1628,7 @@ void SaveManager::LoadBaseVersion4() {
SaveManager::Instance->LoadData("dogParams", gSaveContext.dogParams);
}
void SaveManager::SaveBase(SaveContext* saveContext, const std::string& subString) {
void SaveManager::SaveBase(SaveContext* saveContext, int sectionID) {
SaveManager::Instance->SaveData("entranceIndex", saveContext->entranceIndex);
SaveManager::Instance->SaveData("linkAge", saveContext->linkAge);
SaveManager::Instance->SaveData("cutsceneIndex", saveContext->cutsceneIndex);
@ -2290,8 +2295,8 @@ extern "C" void Save_SaveFile(void) {
SaveManager::Instance->SaveFile(gSaveContext.fileNum);
}
extern "C" void Save_SaveSection(char* sectionString) {
SaveManager::Instance->SaveSection(gSaveContext.fileNum, sectionString);
extern "C" void Save_SaveSection(int sectionID) {
SaveManager::Instance->SaveSection(gSaveContext.fileNum, sectionID);
}
extern "C" void Save_SaveGlobal(void) {
@ -2307,8 +2312,8 @@ extern "C" void Save_AddLoadFunction(char* name, int version, SaveManager::LoadF
SaveManager::Instance->AddLoadFunction(name, version, func);
}
extern "C" void Save_AddSaveFunction(char* name, int version, SaveManager::SaveFunc func) {
SaveManager::Instance->AddSaveFunction(name, version, func);
extern "C" void Save_AddSaveFunction(char* name, int version, SaveManager::SaveFunc func, bool saveWithBase, int parentSection = SECTION_PARENT_NONE) {
SaveManager::Instance->AddSaveFunction(name, version, func, saveWithBase, parentSection);
}
extern "C" SaveFileMetaInfo* Save_GetSaveMetaInfo(int fileNum) {

View File

@ -2,6 +2,7 @@
#include <libultraship/libultra/gbi.h>
#define SECTION_PARENT_NONE -1
typedef struct {
u8 valid;
u16 deaths;
@ -27,6 +28,7 @@ typedef struct {
#include <tuple>
#include <functional>
#include <vector>
#include <set>
#include <filesystem>
#include "thread-pool/BS_thread_pool.hpp"
@ -38,6 +40,7 @@ extern "C" {
class SaveManager {
public:
static SaveManager* Instance;
static void WriteSaveFile(const std::filesystem::path& savePath, uintptr_t addr, void* dramAddr, size_t size);
@ -45,15 +48,23 @@ public:
using InitFunc = void(*)(bool isDebug);
using LoadFunc = void(*)();
using SaveFunc = void(*)(SaveContext* saveContext, const std::string& subSection);
using PostFunc = void(*)(int version);
using SaveFunc = void(*)(SaveContext* saveContext, int sectionID);
using PostFunc = void (*)(int version);
typedef struct {
std::string name;
int version;
SaveManager::SaveFunc func;
bool saveWithBase;
int parentSection;
} SaveFuncInfo;
SaveManager();
void Init();
void InitFile(bool isDebug);
void SaveFile(int fileNum);
void SaveSection(int fileNum, const std::string& sectionString);
void SaveSection(int fileNum, int sectionID);
void SaveGlobal();
void LoadFile(int fileNum);
bool SaveFile_Exist(int fileNum);
@ -66,7 +77,7 @@ public:
void AddLoadFunction(const std::string& name, int version, LoadFunc func);
// Adds a function that is called when saving. This should only be called once for each function, the version is filled in automatically.
void AddSaveFunction(const std::string& name, int version, SaveFunc func);
void AddSaveFunction(const std::string& name, int version, SaveFunc func, bool saveWithBase, int parentSection);
// Adds a function to be called after loading is complete. This is to handle any cleanup required from loading old versions.
void AddPostFunction(const std::string& name, PostFunc func);
@ -119,9 +130,6 @@ public:
static const int MaxFiles = 3;
std::array<SaveFileMetaInfo, MaxFiles> fileMetaInfo;
void RegisterGameSaveSection(std::string section);
void UnregisterAutosaveSection(std::string section);
private:
std::filesystem::path GetFileName(int fileNum);
nlohmann::json saveBlock;
@ -129,7 +137,7 @@ public:
void ConvertFromUnversioned();
void CreateDefaultGlobal();
void SaveFileThreaded(int fileNum, SaveContext* saveContext, const std::string& sectionString);
void SaveFileThreaded(int fileNum, SaveContext* saveContext, int sectionID);
void InitMeta(int slotNum);
static void InitFileImpl(bool isDebug);
@ -138,23 +146,23 @@ public:
static void LoadRandomizerVersion1();
static void LoadRandomizerVersion2();
static void SaveRandomizer(SaveContext* saveContext, const std::string& subString);
static void SaveRandomizer(SaveContext* saveContext, int sectionID);
static void LoadBaseVersion1();
static void LoadBaseVersion2();
static void LoadBaseVersion3();
static void LoadBaseVersion4();
static void SaveBase(SaveContext* saveContext, const std::string& subString);
static void SaveBase(SaveContext* saveContext, int sectionID);
std::vector<InitFunc> initFuncs;
using SectionLoadHandler = std::map<int, LoadFunc>;
std::map<std::string, SectionLoadHandler> sectionLoadHandlers;
using SectionSaveHandler = std::pair<int, SaveFunc>;
std::map<std::string, SectionSaveHandler> sectionSaveHandlers;
// tracks sections to save during game saves
std::vector<std::string> gameSaveRegistry;
int sectionIndex = SECTION_ID_MAX;
std::map<std::string, int> coreSectionIDsByName;
std::map<int, SaveFuncInfo> sectionSaveHandlers;
std::set<std::string> sectionRegistry;
std::map<std::string, PostFunc> postHandlers;
@ -168,16 +176,16 @@ public:
// TODO feature parity to the C++ interface. We need Save_AddInitFunction and Save_AddPostFunction at least
typedef void (*Save_LoadFunc)(void);
typedef void (*Save_SaveFunc)(const SaveContext* saveContext);
typedef void (*Save_SaveFunc)(const SaveContext* saveContext, int sectionID);
void Save_Init(void);
void Save_InitFile(int isDebug);
void Save_SaveFile(void);
void Save_SaveSection(char* sectionString);
void Save_SaveSection(int sectionID);
void Save_SaveGlobal(void);
void Save_LoadGlobal(void);
void Save_AddLoadFunction(char* name, int version, Save_LoadFunc func);
void Save_AddSaveFunction(char* name, int version, Save_SaveFunc func);
void Save_AddSaveFunction(char* name, int version, Save_SaveFunc func, bool saveWithBase, int parentSection);
SaveFileMetaInfo* Save_GetSaveMetaInfo(int fileNum);
void Save_CopyFile(int from, int to);
void Save_DeleteFile(int fileNum);