diff --git a/src/Script/RamCalculations.ts b/src/Script/RamCalculations.ts index bbd67a997..4b58a3512 100644 --- a/src/Script/RamCalculations.ts +++ b/src/Script/RamCalculations.ts @@ -16,6 +16,17 @@ import { WorkerScript } from "../Netscript/WorkerScript"; import { areImportsEquals } from "../Terminal/DirectoryHelpers"; import { IPlayer } from "../PersonObjects/IPlayer"; +export interface RamUsageEntry { + type: 'ns' | 'dom' | 'fn' | 'misc'; + name: string; + cost: number; +} + +export interface RamCalculation { + cost: number; + entries?: RamUsageEntry[]; +} + // These special strings are used to reference the presence of a given logical // construct within a user script. const specialReferenceIF = "__SPECIAL_referenceIf"; @@ -38,7 +49,7 @@ async function parseOnlyRamCalculate( otherScripts: Script[], code: string, workerScript: WorkerScript, -): Promise { +): Promise { try { /** * Maps dependent identifiers to their dependencies. @@ -98,12 +109,12 @@ async function parseOnlyRamCalculate( } } catch (e) { console.error(`Error dynamically importing module from ${nextModule} for RAM calculations: ${e}`); - return RamCalculationErrorCode.URLImportError; + return { cost: RamCalculationErrorCode.URLImportError }; } } else { if (!Array.isArray(otherScripts)) { console.warn(`parseOnlyRamCalculate() not called with array of scripts`); - return RamCalculationErrorCode.ImportError; + return { cost: RamCalculationErrorCode.ImportError }; } let script = null; @@ -116,7 +127,7 @@ async function parseOnlyRamCalculate( } if (script == null) { - return RamCalculationErrorCode.ImportError; // No such script on the server + return { cost: RamCalculationErrorCode.ImportError }; // No such script on the server } code = script.code; @@ -128,6 +139,7 @@ async function parseOnlyRamCalculate( // Finally, walk the reference map and generate a ram cost. The initial set of keys to scan // are those that start with __SPECIAL_INITIAL_MODULE__. let ram = RamCostConstants.ScriptBaseRamCost; + const detailedCosts: RamUsageEntry[] = [{ type: 'misc', name: 'baseCost', cost: RamCostConstants.ScriptBaseRamCost}]; const unresolvedRefs = Object.keys(dependencyMap).filter((s) => s.startsWith(initialModule)); const resolvedRefs = new Set(); while (unresolvedRefs.length > 0) { @@ -137,15 +149,19 @@ async function parseOnlyRamCalculate( // Check if this is one of the special keys, and add the appropriate ram cost if so. if (ref === "hacknet" && !resolvedRefs.has("hacknet")) { ram += RamCostConstants.ScriptHacknetNodesRamCost; + detailedCosts.push({ type: 'ns', name: 'hacknet', cost: RamCostConstants.ScriptHacknetNodesRamCost}); } if (ref === "document" && !resolvedRefs.has("document")) { ram += RamCostConstants.ScriptDomRamCost; + detailedCosts.push({ type: 'dom', name: 'document', cost: RamCostConstants.ScriptDomRamCost}); } if (ref === "window" && !resolvedRefs.has("window")) { ram += RamCostConstants.ScriptDomRamCost; + detailedCosts.push({ type: 'dom', name: 'window', cost: RamCostConstants.ScriptDomRamCost}); } if (ref === "corporation" && !resolvedRefs.has("corporation")) { ram += RamCostConstants.ScriptCorporationRamCost; + detailedCosts.push({ type: 'ns', name: 'corporation', cost: RamCostConstants.ScriptCorporationRamCost}); } resolvedRefs.add(ref); @@ -187,34 +203,45 @@ async function parseOnlyRamCalculate( // This accounts for namespaces (Bladeburner, CodingCpntract, etc.) let func; + let refDetail = 'n/a'; if (ref in workerScript.env.vars.bladeburner) { func = workerScript.env.vars.bladeburner[ref]; + refDetail = `bladeburner.${ref}`; } else if (ref in workerScript.env.vars.codingcontract) { func = workerScript.env.vars.codingcontract[ref]; + refDetail = `codingcontract.${ref}`; } else if (ref in workerScript.env.vars.stanek) { func = workerScript.env.vars.stanek[ref]; + refDetail = `stanek.${ref}`; } else if (ref in workerScript.env.vars.gang) { func = workerScript.env.vars.gang[ref]; + refDetail = `gang.${ref}`; } else if (ref in workerScript.env.vars.sleeve) { func = workerScript.env.vars.sleeve[ref]; + refDetail = `sleeve.${ref}`; } else if (ref in workerScript.env.vars.stock) { func = workerScript.env.vars.stock[ref]; + refDetail = `stock.${ref}`; } else if (ref in workerScript.env.vars.ui) { func = workerScript.env.vars.ui[ref]; + refDetail = `ui.${ref}`; } else { func = workerScript.env.vars[ref]; + refDetail = `${ref}`; } - ram += applyFuncRam(func); + const fnRam = applyFuncRam(func); + ram += fnRam; + detailedCosts.push({ type: 'fn', name: refDetail, cost: fnRam}); } catch (error) { continue; } } - return ram; + 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 RamCalculationErrorCode.SyntaxError; + return { cost: RamCalculationErrorCode.SyntaxError }; } } @@ -382,7 +409,7 @@ export async function calculateRamUsage( player: IPlayer, codeCopy: string, otherScripts: Script[], -): Promise { +): Promise { // We don't need a real WorkerScript for this. Just an object that keeps // track of whatever's needed for RAM calculations const workerScript = { @@ -397,8 +424,8 @@ export async function calculateRamUsage( } catch (e) { console.error(`Failed to parse script for RAM calculations:`); console.error(e); - return RamCalculationErrorCode.SyntaxError; + return { cost: RamCalculationErrorCode.SyntaxError }; } - return RamCalculationErrorCode.SyntaxError; + return { cost: RamCalculationErrorCode.SyntaxError }; } diff --git a/src/Script/Script.ts b/src/Script/Script.ts index 5f350ef55..7e8e5a562 100644 --- a/src/Script/Script.ts +++ b/src/Script/Script.ts @@ -4,7 +4,7 @@ * This does NOT represent a script that is actively running and * being evaluated. See RunningScript for that */ -import { calculateRamUsage } from "./RamCalculations"; +import { calculateRamUsage, RamUsageEntry } from "./RamCalculations"; import { ScriptUrl } from "./ScriptUrl"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver"; @@ -38,6 +38,7 @@ export class Script { // Amount of RAM this Script requres to run ramUsage = 0; + ramUsageEntries?: RamUsageEntry[]; // hostname of server that this script is on. server = ""; @@ -131,8 +132,9 @@ export class Script { */ async updateRamUsage(player: IPlayer, otherScripts: Script[]): Promise { const res = await calculateRamUsage(player, this.code, otherScripts); - if (res > 0) { - this.ramUsage = roundToTwo(res); + if (res.cost > 0) { + this.ramUsage = roundToTwo(res.cost); + this.ramUsageEntries = res.entries; } this.markUpdated(); } diff --git a/src/ScriptEditor/ui/ScriptEditorRoot.tsx b/src/ScriptEditor/ui/ScriptEditorRoot.tsx index e5b7078a6..723fc996a 100644 --- a/src/ScriptEditor/ui/ScriptEditorRoot.tsx +++ b/src/ScriptEditor/ui/ScriptEditorRoot.tsx @@ -227,11 +227,11 @@ export function Root(props: IProps): React.ReactElement { setUpdatingRam(true); const codeCopy = newCode + ""; const ramUsage = await calculateRamUsage(props.player, codeCopy, props.player.getCurrentServer().scripts); - if (ramUsage > 0) { - debouncedSetRAM("RAM: " + numeralWrapper.formatRAM(ramUsage)); + if (ramUsage.cost > 0) { + debouncedSetRAM("RAM: " + numeralWrapper.formatRAM(ramUsage.cost)); return; } - switch (ramUsage) { + switch (ramUsage.cost) { case RamCalculationErrorCode.ImportError: { debouncedSetRAM("RAM: Import Error"); break; diff --git a/src/Terminal/commands/mem.ts b/src/Terminal/commands/mem.ts index 42abc25ea..0a12230a9 100644 --- a/src/Terminal/commands/mem.ts +++ b/src/Terminal/commands/mem.ts @@ -38,6 +38,11 @@ export function mem( terminal.print( `This script requires ${numeralWrapper.formatRAM(ramUsage)} of RAM to run for ${numThreads} thread(s)`, ); + + const verboseEntries = script.ramUsageEntries?.sort((a, b) => b.cost - a.cost) ?? []; + for (const entry of verboseEntries) { + terminal.print(`${numeralWrapper.formatRAM(entry.cost * numThreads).padStart(8)} | ${entry.name} (${entry.type})`); + } } catch (e) { terminal.error(e + ""); } diff --git a/test/Netscript/StaticRamCalculation.test.js b/test/Netscript/StaticRamCalculation.test.js index 473e62698..31bff7e1c 100644 --- a/test/Netscript/StaticRamCalculation.test.js +++ b/test/Netscript/StaticRamCalculation.test.js @@ -36,11 +36,11 @@ describe("Netscript Static RAM Calculation/Generation Tests", function () { const code = fnDesc.join(".") + "(); "; - const calculated = await calculateRamUsage(Player, code, []); + const calculated = (await calculateRamUsage(Player, code, [])).cost; testEquality(calculated, expected + ScriptBaseCost); const multipleCallsCode = code.repeat(3); - const multipleCallsCalculated = await calculateRamUsage(Player, multipleCallsCode, []); + const multipleCallsCalculated = (await calculateRamUsage(Player, multipleCallsCode, [])).cost; expect(multipleCallsCalculated).toEqual(calculated); } @@ -60,11 +60,10 @@ describe("Netscript Static RAM Calculation/Generation Tests", function () { expect(expected).toEqual(0); const code = fnDesc.join(".") + "(); "; - - const calculated = await calculateRamUsage(Player, code, []); + const calculated = (await calculateRamUsage(Player, code, [])).cost; testEquality(calculated, ScriptBaseCost); - const multipleCallsCalculated = await calculateRamUsage(Player, code, []); + const multipleCallsCalculated = (await calculateRamUsage(Player, code, [])).cost; expect(multipleCallsCalculated).toEqual(ScriptBaseCost); } @@ -511,7 +510,7 @@ describe("Netscript Static RAM Calculation/Generation Tests", function () { } const calculated = await calculateRamUsage(Player, code, []); - testEquality(calculated, ScriptBaseCost + HacknetNamespaceCost); + testEquality(calculated.cost, ScriptBaseCost + HacknetNamespaceCost); }); });