FIX: show ram calculation error reason to player (#627)

This commit is contained in:
Aleksei Bezrodnov 2023-06-19 09:49:32 +02:00 committed by GitHub
parent 9e75621cd2
commit 08e3afd125
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 140 additions and 128 deletions

@ -22,10 +22,21 @@ export interface RamUsageEntry {
cost: number; cost: number;
} }
export interface RamCalculation { export type RamCalculationSuccess = {
cost: number; cost: number;
entries?: RamUsageEntry[]; entries: RamUsageEntry[];
} errorCode?: never;
errorMessage?: never;
};
export type RamCalculationFailure = {
cost?: never;
entries?: never;
errorCode: RamCalculationErrorCode;
errorMessage?: string;
};
export type RamCalculation = RamCalculationSuccess | RamCalculationFailure;
// These special strings are used to reference the presence of a given logical // These special strings are used to reference the presence of a given logical
// construct within a user script. // construct within a user script.
@ -80,7 +91,6 @@ function parseOnlyRamCalculate(otherScripts: Map<ScriptFilePath, Script>, code:
dependencyMap = Object.assign(dependencyMap, result.dependencyMap); dependencyMap = Object.assign(dependencyMap, result.dependencyMap);
} }
try {
// Parse the initial module, which is the "main" script that is being run // Parse the initial module, which is the "main" script that is being run
const initialModule = "__SPECIAL_INITIAL_MODULE__"; const initialModule = "__SPECIAL_INITIAL_MODULE__";
parseCode(code, initialModule); parseCode(code, initialModule);
@ -93,9 +103,13 @@ function parseOnlyRamCalculate(otherScripts: Map<ScriptFilePath, Script>, code:
// Using root as the path base right now. Difficult to implement // Using root as the path base right now. Difficult to implement
const filename = resolveScriptFilePath(nextModule, root, ns1 ? ".script" : ".js"); const filename = resolveScriptFilePath(nextModule, root, ns1 ? ".script" : ".js");
if (!filename) return { cost: RamCalculationErrorCode.ImportError }; // Invalid import path if (!filename) {
return { errorCode: RamCalculationErrorCode.ImportError, errorMessage: `Invalid import path: "${nextModule}"` };
}
const script = otherScripts.get(filename); const script = otherScripts.get(filename);
if (!script) return { cost: RamCalculationErrorCode.ImportError }; // No such file on server if (!script) {
return { errorCode: RamCalculationErrorCode.ImportError, errorMessage: `No such file on server: "${filename}"` };
}
parseCode(script.code, nextModule); parseCode(script.code, nextModule);
} }
@ -183,12 +197,6 @@ function parseOnlyRamCalculate(otherScripts: Map<ScriptFilePath, Script>, code:
detailedCosts.push({ type: "misc", name: "Max Ram Cap", cost: RamCostConstants.Max }); detailedCosts.push({ type: "misc", name: "Max Ram Cap", cost: RamCostConstants.Max });
} }
return { cost: ram, entries: detailedCosts.filter((e) => e.cost > 0) }; return { cost: ram, entries: detailedCosts.filter((e) => e.cost > 0) };
} catch (error) {
// console.info("parse or eval error: ", error);
// This is not unexpected. The user may be editing a script, and it may be in
// a transitory invalid state.
return { cost: RamCalculationErrorCode.SyntaxError };
}
} }
export function checkInfiniteLoop(code: string): number { export function checkInfiniteLoop(code: string): number {
@ -362,20 +370,21 @@ function parseOnlyCalculateDeps(code: string, currentModule: string): ParseDepsR
/** /**
* Calculate's a scripts RAM Usage * Calculate's a scripts RAM Usage
* @param {string} codeCopy - The script's code * @param {string} code - The script's code
* @param {Script[]} otherScripts - All other scripts on the server. * @param {Script[]} otherScripts - All other scripts on the server.
* Used to account for imported scripts * Used to account for imported scripts
*/ */
export function calculateRamUsage( export function calculateRamUsage(
codeCopy: string, code: string,
otherScripts: Map<ScriptFilePath, Script>, otherScripts: Map<ScriptFilePath, Script>,
ns1?: boolean, ns1?: boolean,
): RamCalculation { ): RamCalculation {
try { try {
return parseOnlyRamCalculate(otherScripts, codeCopy, ns1); return parseOnlyRamCalculate(otherScripts, code, ns1);
} catch (e) { } catch (e) {
console.error(`Failed to parse script for RAM calculations:`); return {
console.error(e); errorCode: RamCalculationErrorCode.SyntaxError,
return { cost: RamCalculationErrorCode.SyntaxError }; errorMessage: e instanceof Error ? e.message : undefined,
};
} }
} }

@ -17,6 +17,7 @@ export class Script implements ContentFile {
// Ram calculation, only exists after first poll of ram cost after updating // Ram calculation, only exists after first poll of ram cost after updating
ramUsage: number | null = null; ramUsage: number | null = null;
ramUsageEntries: RamUsageEntry[] = []; ramUsageEntries: RamUsageEntry[] = [];
ramCalculationError: string | null = null;
// Runtime data that only exists when the script has been initiated. Cleared when script or a dependency script is updated. // Runtime data that only exists when the script has been initiated. Cleared when script or a dependency script is updated.
mod: LoadedModule | null = null; mod: LoadedModule | null = null;
@ -65,6 +66,7 @@ export class Script implements ContentFile {
// Always clear ram usage // Always clear ram usage
this.ramUsage = null; this.ramUsage = null;
this.ramUsageEntries.length = 0; this.ramUsageEntries.length = 0;
this.ramCalculationError = null;
// Early return if there's already no URL // Early return if there's already no URL
if (!this.mod) return; if (!this.mod) return;
this.mod = null; this.mod = null;
@ -88,12 +90,15 @@ export class Script implements ContentFile {
*/ */
updateRamUsage(otherScripts: Map<ScriptFilePath, Script>): void { updateRamUsage(otherScripts: Map<ScriptFilePath, Script>): void {
const ramCalc = calculateRamUsage(this.code, otherScripts, this.filename.endsWith(".script")); const ramCalc = calculateRamUsage(this.code, otherScripts, this.filename.endsWith(".script"));
if (ramCalc.cost >= RamCostConstants.Base) { if (ramCalc.cost && ramCalc.cost >= RamCostConstants.Base) {
this.ramUsage = roundToTwo(ramCalc.cost); this.ramUsage = roundToTwo(ramCalc.cost);
this.ramUsageEntries = ramCalc.entries as RamUsageEntry[]; this.ramUsageEntries = ramCalc.entries as RamUsageEntry[];
} else { this.ramCalculationError = null;
this.ramUsage = null; return;
} }
this.ramUsage = null;
this.ramCalculationError = ramCalc.errorMessage ?? null;
} }
/** Remove script from server. Fails if the provided server isn't the server for this script. */ /** Remove script from server. Fails if the provided server isn't the server for this script. */

@ -34,9 +34,9 @@ export function ScriptEditorContextProvider({ children, vim }: { children: React
setRamEntries([["N/A", ""]]); setRamEntries([["N/A", ""]]);
return; return;
} }
const codeCopy = newCode + "";
const ramUsage = calculateRamUsage(codeCopy, server.scripts); const ramUsage = calculateRamUsage(newCode, server.scripts);
if (ramUsage.cost > 0) { if (ramUsage.cost && ramUsage.cost > 0) {
const entries = ramUsage.entries?.sort((a, b) => b.cost - a.cost) ?? []; const entries = ramUsage.entries?.sort((a, b) => b.cost - a.cost) ?? [];
const entriesDisp = []; const entriesDisp = [];
for (const entry of entries) { for (const entry of entries) {
@ -48,23 +48,20 @@ export function ScriptEditorContextProvider({ children, vim }: { children: React
return; return;
} }
let RAM = ""; if (ramUsage.errorCode !== undefined) {
const entriesDisp = []; setRamEntries([["Syntax Error", ramUsage.errorMessage ?? ""]]);
switch (ramUsage.cost) { switch (ramUsage.errorCode) {
case RamCalculationErrorCode.ImportError: { case RamCalculationErrorCode.ImportError:
RAM = "RAM: Import Error"; setRAM("RAM: Import Error");
entriesDisp.push(["Import Error", ""]);
break; break;
}
case RamCalculationErrorCode.SyntaxError: case RamCalculationErrorCode.SyntaxError:
default: { setRAM("RAM: Syntax Error");
RAM = "RAM: Syntax Error";
entriesDisp.push(["Syntax Error", ""]);
break; break;
} }
} else {
setRAM("RAM: Syntax Error");
setRamEntries([["Syntax Error", ""]]);
} }
setRAM(RAM);
setRamEntries(entriesDisp);
}; };
const [isUpdatingRAM, { on: startUpdatingRAM, off: finishUpdatingRAM }] = useBoolean(false); const [isUpdatingRAM, { on: startUpdatingRAM, off: finishUpdatingRAM }] = useBoolean(false);

@ -24,18 +24,19 @@ export function runScript(path: ScriptFilePath, commandArgs: (string | number |
if (!isPositiveInteger(numThreads)) { if (!isPositiveInteger(numThreads)) {
return Terminal.error("Invalid number of threads specified. Number of threads must be an integer greater than 0"); return Terminal.error("Invalid number of threads specified. Number of threads must be an integer greater than 0");
} }
if (!server.hasAdminRights) return Terminal.error("Need root access to run script");
// Todo: Switch out arg for something with typescript support // Todo: Switch out arg for something with typescript support
const args = flags._ as ScriptArg[]; const args = flags._ as ScriptArg[];
const singleRamUsage = script.getRamUsage(server.scripts); const singleRamUsage = script.getRamUsage(server.scripts);
if (!singleRamUsage) return Terminal.error("Error while calculating ram usage for this script."); if (!singleRamUsage) {
return Terminal.error(`Error while calculating ram usage for this script. ${script.ramCalculationError}`);
}
const ramUsage = singleRamUsage * numThreads; const ramUsage = singleRamUsage * numThreads;
const ramAvailable = server.maxRam - server.ramUsed; const ramAvailable = server.maxRam - server.ramUsed;
if (!server.hasAdminRights) return Terminal.error("Need root access to run script");
if (ramUsage > ramAvailable + 0.001) { if (ramUsage > ramAvailable + 0.001) {
return Terminal.error( return Terminal.error(
"This machine does not have enough RAM to run this script" + "This machine does not have enough RAM to run this script" +