UNPKG

isaacscript

Version:

A command line tool for managing Isaac mods written in TypeScript.

114 lines 4.07 kB
import { assertDefined } from "complete-common"; import { getArgs, isFile, makeDirectory, readFile } from "complete-node"; import * as JSONC from "jsonc-parser"; import fs from "node:fs"; import path from "node:path"; import { PROJECT_NAME } from "../../../constants.js"; const SUBPROCESS_NAME = "save#.dat writer"; const MAX_MESSAGES = 100; let saveDatPath; let saveDatFileName; await init(); async function init() { const args = getArgs(); const firstArg = args[0]; assertDefined(firstArg, `The ${SUBPROCESS_NAME} process did not get a valid first argument.`); saveDatPath = firstArg; saveDatFileName = path.basename(saveDatPath); // Check to see if the data directory exists. const watcherModDataPath = path.dirname(saveDatPath); const file = await isFile(watcherModDataPath); if (!file) { await makeDirectory(watcherModDataPath); } // Listen for messages from the parent process. process.on("message", (msg) => { // eslint-disable-next-line @typescript-eslint/no-floating-promises onMessage(msg.type, msg.data); }); } async function onMessage(type, data, numRetries = 0) { const saveDat = await readSaveDatFromDisk(); if (saveDat.length > MAX_MESSAGES) { // If IsaacScript is running and: // - the game is not open // - or the game is open but the IsaacScript Watcher mod is disabled // Then this process will continue to write to the "save#.dat" file, which can cause it to grow // arbitrarily large (since there is no-one on the other side removing the messages). If there // is N messages already in the queue, assume that no-one is listening and stop adding any more // messages. return; } addMessageToSaveDat(type, saveDat, data); // Mutates saveDat writeSaveDatToDisk(type, data, saveDat, numRetries); } // eslint-disable-next-line complete/no-mutable-return async function readSaveDatFromDisk() { let saveDat; const file = await isFile(saveDatPath); if (file) { const saveDatRaw = await readFile(saveDatPath); try { saveDat = JSONC.parse(saveDatRaw); } catch (error) { send(`Failed to parse "${saveDatPath}": ${error}`); process.exit(1); } } else { saveDat = []; } if (!Array.isArray(saveDat)) { saveDat = []; } return saveDat; } function addMessageToSaveDat(type, // eslint-disable-next-line complete/prefer-readonly-parameter-types saveDat, data) { switch (type) { case "command": case "ping": { saveDat.push({ type, data, }); break; } case "msg": { const lines = data.split("\n"); for (const line of lines) { saveDat.push({ type, data: line, }); } break; } } } function writeSaveDatToDisk(type, data, saveDat, numRetries) { const saveDatRaw = `${JSON.stringify(saveDat, undefined, 2)}\n`; // Prettify it for easier debugging. try { // We don't use the "writeFile" helper function here, since we want to allow for failure. fs.writeFileSync(saveDatPath, saveDatRaw); } catch (error) { if (numRetries > 4) { send(`Failed to write to the ${saveDatFileName} for 5 times in a row. Maybe the file got locked somehow. ${PROJECT_NAME} will now exit.\nThe writing error is as follows: ${error}`); process.exit(1); } send(`Writing to "${saveDatFileName}" failed. (The number of retries so far is ${numRetries}.) Trying again in 0.1 seconds...`); setTimeout(() => { // eslint-disable-next-line @typescript-eslint/no-floating-promises onMessage(type, data, numRetries + 1); }, 100); } } function send(msg) { if (process.send !== undefined) { process.send(msg); } } //# sourceMappingURL=saveDatWriter.js.map