isaacscript-common
Version:
Helper functions and features for IsaacScript mods.
106 lines (88 loc) • 3.34 kB
text/typescript
import { jsonDecode } from "../../../../functions/jsonHelpers";
import { log, logError } from "../../../../functions/log";
import { merge } from "../../../../functions/merge";
import { iterateTableInOrder } from "../../../../functions/table";
import { isString, isTable } from "../../../../functions/types";
import type { SaveData } from "../../../../interfaces/SaveData";
import type { AnyClass } from "../../../../types/AnyClass";
import { SAVE_DATA_MANAGER_DEBUG } from "./constants";
const DEFAULT_MOD_DATA = "{}";
export function loadFromDisk(
mod: Mod,
oldSaveData: LuaMap<string, SaveData>,
classConstructors: LuaMap<string, AnyClass>,
): void {
if (!mod.HasData()) {
// There is no "save#.dat" file for this save slot.
return;
}
// First, read the "save#.dat" file into a Lua table.
const jsonString = readSaveDatFile(mod);
const newSaveData = jsonDecode(jsonString);
if (newSaveData === undefined) {
// Reading the save data failed.
return;
}
if (SAVE_DATA_MANAGER_DEBUG) {
log('Converted data from the "save#.dat" to a Lua table.');
}
// Second, iterate over all the fields of the new table.)
iterateTableInOrder(
newSaveData,
(subscriberName, saveData) => {
// All elements of loaded save data should have keys that are strings equal to the name of the
// subscriber/feature. Ignore elements with other types of keys.
if (!isString(subscriberName)) {
return;
}
// All elements of loaded save data should be tables that contain fields corresponding to the
// `SaveData` interface. Ignore elements that are not tables.
if (!isTable(saveData)) {
return;
}
// Ignore elements that represent subscriptions that no longer exist in the current save data.
const oldSaveDataForSubscriber = oldSaveData.get(subscriberName);
if (oldSaveDataForSubscriber === undefined) {
return;
}
if (SAVE_DATA_MANAGER_DEBUG) {
log(`Merging in stored data for feature: ${subscriberName}`);
}
// We do not want to blow away the child tables of the existing map, because save data could
// contain out-of-date fields. Instead, merge it one field at a time in a recursive way (and
// convert Lua tables back to TypeScriptToLua Maps, if necessary).
merge(
oldSaveDataForSubscriber as LuaMap<AnyNotNil, unknown>,
saveData,
subscriberName,
classConstructors,
);
},
SAVE_DATA_MANAGER_DEBUG,
);
log(
`The save data manager loaded data from the "save#.dat" file for mod: ${mod.Name}`,
);
}
function readSaveDatFile(mod: Mod) {
const renderFrameCount = Isaac.GetFrameCount();
const [ok, jsonStringOrErrMsg] = pcall(tryLoadModData, mod);
if (!ok) {
logError(
`Failed to read from the "save#.dat" file on render frame ${renderFrameCount}: ${jsonStringOrErrMsg}`,
);
return DEFAULT_MOD_DATA;
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (jsonStringOrErrMsg === undefined) {
return DEFAULT_MOD_DATA;
}
const jsonStringTrimmed = jsonStringOrErrMsg.trim();
if (jsonStringTrimmed === "") {
return DEFAULT_MOD_DATA;
}
return jsonStringTrimmed;
}
function tryLoadModData(this: void, mod: Mod) {
return mod.LoadData();
}