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.
This commit is contained in:
David Walker 2024-06-28 17:58:17 -07:00 committed by GitHub
parent 357cc568e9
commit 06d742a7f3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 12 additions and 4 deletions

@ -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 | <p>_(Optional)_ The RAM allocation to launch each thread of the script with.</p><p>Lowering this will <i>not</i> 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.</p><p>You can also use this to <i>increase</i> the RAM if the static RAM checker has missed functions that you need to call.</p><p>Must be greater-or-equal to the base RAM cost. Defaults to the statically calculated cost.</p> |
| [ramOverride?](./bitburner.runoptions.ramoverride.md) | | number | <p>_(Optional)_ The RAM allocation to launch each thread of the script with.</p><p>Lowering this will <i>not</i> 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.</p><p>You can also use this to <i>increase</i> the RAM if the static RAM checker has missed functions that you need to call.</p><p>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.</p> |
| [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 |

@ -10,7 +10,7 @@ Lowering this will <i>not</i> automatically let you get away with using less RAM
You can also use this to <i>increase</i> 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:**

@ -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

@ -270,7 +270,8 @@ interface RunOptions {
* You can also use this to <i>increase</i> 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;
/**

@ -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");
}