From 06d742a7f31fb01903548b60e05ff1a0a1a88329 Mon Sep 17 00:00:00 2001 From: David Walker Date: Fri, 28 Jun 2024 17:58:17 -0700 Subject: [PATCH] BUGFIX: Fix rounding issues due to ramOverride edge cases (#1339) *All* RAM calculations must take place in units of hundredths-of-a-GB in order for there not to be issues. Also adds slightly more verbose logging when the dynamic RAM check fails. --- markdown/bitburner.runoptions.md | 2 +- markdown/bitburner.runoptions.ramoverride.md | 2 +- src/Netscript/NetscriptHelpers.tsx | 6 ++++++ src/ScriptEditor/NetscriptDefinitions.d.ts | 3 ++- src/Terminal/commands/runScript.ts | 3 ++- 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/markdown/bitburner.runoptions.md b/markdown/bitburner.runoptions.md index dbe92e176..274958f0a 100644 --- a/markdown/bitburner.runoptions.md +++ b/markdown/bitburner.runoptions.md @@ -16,7 +16,7 @@ interface RunOptions | Property | Modifiers | Type | Description | | --- | --- | --- | --- | | [preventDuplicates?](./bitburner.runoptions.preventduplicates.md) | | boolean | _(Optional)_ Should we fail to run if another instance is running with the exact same arguments? This used to be the default behavior, now defaults to false. | -| [ramOverride?](./bitburner.runoptions.ramoverride.md) | | number |

_(Optional)_ The RAM allocation to launch each thread of the script with.

Lowering this will not automatically let you get away with using less RAM: the dynamic RAM check enforces that all [NS](./bitburner.ns.md) functions actually called incur their cost. However, if you know that certain functions that are statically present (and thus included in the static RAM cost) will never be called in a particular circumstance, you can use this to avoid paying for them.

You can also use this to increase the RAM if the static RAM checker has missed functions that you need to call.

Must be greater-or-equal to the base RAM cost. Defaults to the statically calculated cost.

| +| [ramOverride?](./bitburner.runoptions.ramoverride.md) | | number |

_(Optional)_ The RAM allocation to launch each thread of the script with.

Lowering this will not automatically let you get away with using less RAM: the dynamic RAM check enforces that all [NS](./bitburner.ns.md) functions actually called incur their cost. However, if you know that certain functions that are statically present (and thus included in the static RAM cost) will never be called in a particular circumstance, you can use this to avoid paying for them.

You can also use this to increase the RAM if the static RAM checker has missed functions that you need to call.

Must be greater-or-equal to the base RAM cost. Will be rounded to the nearest hundredth-of-a-GB, which is the granularity of all RAM calculations. Defaults to the statically calculated cost.

| | [temporary?](./bitburner.runoptions.temporary.md) | | boolean | _(Optional)_ Whether this script is excluded from saves, defaults to false | | [threads?](./bitburner.runoptions.threads.md) | | number | _(Optional)_ Number of threads that the script will run with, defaults to 1 | diff --git a/markdown/bitburner.runoptions.ramoverride.md b/markdown/bitburner.runoptions.ramoverride.md index 10f2747b6..f311a05ea 100644 --- a/markdown/bitburner.runoptions.ramoverride.md +++ b/markdown/bitburner.runoptions.ramoverride.md @@ -10,7 +10,7 @@ Lowering this will not automatically let you get away with using less RAM You can also use this to increase the RAM if the static RAM checker has missed functions that you need to call. -Must be greater-or-equal to the base RAM cost. Defaults to the statically calculated cost. +Must be greater-or-equal to the base RAM cost. Will be rounded to the nearest hundredth-of-a-GB, which is the granularity of all RAM calculations. Defaults to the statically calculated cost. **Signature:** diff --git a/src/Netscript/NetscriptHelpers.tsx b/src/Netscript/NetscriptHelpers.tsx index 045a42e0b..b44352d8a 100644 --- a/src/Netscript/NetscriptHelpers.tsx +++ b/src/Netscript/NetscriptHelpers.tsx @@ -28,6 +28,7 @@ import { toNative } from "../NetscriptFunctions/toNative"; import { ScriptIdentifier } from "./ScriptIdentifier"; import { findRunningScripts, findRunningScriptByPid } from "../Script/ScriptHelpers"; import { arrayToString } from "../utils/helpers/ArrayHelpers"; +import { roundToTwo } from "../utils/helpers/roundToTwo"; import { HacknetServer } from "../Hacknet/HacknetServer"; import { BaseServer } from "../Server/BaseServer"; import { RamCostConstants } from "./RamCostGenerator"; @@ -180,6 +181,9 @@ function runOptions(ctx: NetscriptContext, threadOrOption: unknown): CompleteRun `RunOptions.ramOverride must be >= baseCost (${RamCostConstants.Base}), was ${result.ramOverride}`, ); } + // It is important that all RAM calculations operate in hundredths-of-a-GB, + // otherwise we can get inconsistent rounding results. + result.ramOverride = roundToTwo(result.ramOverride); } return result; } @@ -329,6 +333,7 @@ function updateDynamicRam(ctx: NetscriptContext, ramCost: number): void { // rounding issues without exposing rounding exploits in ramUsage. if (ws.dynamicRamUsage > 1.00000000000001 * ws.scriptRef.ramUsage) { log(ctx, () => "Insufficient static ram available."); + const functionsUsed = Object.keys(ws.dynamicLoadedFns).join(", "); const err = errorMessage( ctx, `Dynamic RAM usage calculated to be greater than RAM allocation. @@ -337,6 +342,7 @@ function updateDynamicRam(ctx: NetscriptContext, ramCost: number): void { Threads: ${ws.scriptRef.threads} Dynamic RAM Usage: ${formatRam(ws.dynamicRamUsage)} per thread RAM Allocation: ${formatRam(ws.scriptRef.ramUsage)} per thread + Functions in-use: [${functionsUsed}] One of these could be the reason: * Using eval() to get a reference to a ns function diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts index 1039c803b..4b5f52e94 100644 --- a/src/ScriptEditor/NetscriptDefinitions.d.ts +++ b/src/ScriptEditor/NetscriptDefinitions.d.ts @@ -270,7 +270,8 @@ interface RunOptions { * You can also use this to increase the RAM if the static RAM checker has missed functions * that you need to call. * - * Must be greater-or-equal to the base RAM cost. Defaults to the statically calculated cost. + * Must be greater-or-equal to the base RAM cost. Will be rounded to the nearest hundredth-of-a-GB, + * which is the granularity of all RAM calculations. Defaults to the statically calculated cost. */ ramOverride?: number; /** diff --git a/src/Terminal/commands/runScript.ts b/src/Terminal/commands/runScript.ts index f2a7be22c..93967923d 100644 --- a/src/Terminal/commands/runScript.ts +++ b/src/Terminal/commands/runScript.ts @@ -9,6 +9,7 @@ import { ScriptArg } from "@nsdefs"; import { isPositiveInteger } from "../../types"; import { ScriptFilePath } from "../../Paths/ScriptFilePath"; import { sendDeprecationNotice } from "./common/deprecation"; +import { roundToTwo } from "../../utils/helpers/roundToTwo"; import { RamCostConstants } from "../../Netscript/RamCostGenerator"; export function runScript(path: ScriptFilePath, commandArgs: (string | number | boolean)[], server: BaseServer): void { @@ -23,7 +24,7 @@ export function runScript(path: ScriptFilePath, commandArgs: (string | number | }); const tailFlag = flags["--tail"] === true; const numThreads = parseFloat(flags["-t"] ?? 1); - const ramOverride = flags["--ram-override"] != null ? parseFloat(flags["--ram-override"]) : null; + const ramOverride = flags["--ram-override"] != null ? roundToTwo(parseFloat(flags["--ram-override"])) : null; if (!isPositiveInteger(numThreads)) { return Terminal.error("Invalid number of threads specified. Number of threads must be an integer greater than 0"); }