From 9c9a69f2e26789f13945f2bd3d0797b03e2ff2d7 Mon Sep 17 00:00:00 2001 From: David Walker Date: Fri, 28 Jun 2024 18:42:20 -0700 Subject: [PATCH] NETSCRIPT: Add ramOverride() function (#1346) This adds a way to dynamically change the static RAM limit of a script, which is also its current RAM usage. This makes it possible for scripts to dynamically change their memory footprint, opening up new strategies beyond current ram-dodging. Calling functions still permanently increases the *dynamic* memory limit; RAM-dodging is still the optimal strategy for avoiding RAM costs, in that sense. This also adds dynamicRamUsage to the info returned by `getRunningScript`, to allow introspection on the currently needed ram. --- markdown/bitburner.ns.md | 1 + markdown/bitburner.ns.ramoverride.md | 34 ++++++++++++++++++ ...bitburner.runningscript.dynamicramusage.md | 15 ++++++++ markdown/bitburner.runningscript.md | 3 +- markdown/bitburner.runningscript.ramusage.md | 2 +- src/Netscript/NetscriptHelpers.tsx | 7 +++- src/Netscript/RamCostGenerator.ts | 1 + src/NetscriptFunctions.ts | 26 +++++++++++++- src/ScriptEditor/NetscriptDefinitions.d.ts | 35 ++++++++++++++++++- 9 files changed, 119 insertions(+), 5 deletions(-) create mode 100644 markdown/bitburner.ns.ramoverride.md create mode 100644 markdown/bitburner.runningscript.dynamicramusage.md diff --git a/markdown/bitburner.ns.md b/markdown/bitburner.ns.md index 0e8534d92..751a2fdfd 100644 --- a/markdown/bitburner.ns.md +++ b/markdown/bitburner.ns.md @@ -143,6 +143,7 @@ export async function main(ns) { | [prompt(txt, options)](./bitburner.ns.prompt.md) | Prompt the player with an input modal. | | [ps(host)](./bitburner.ns.ps.md) | List running scripts on a server. | | [purchaseServer(hostname, ram)](./bitburner.ns.purchaseserver.md) | Purchase a server. | +| [ramOverride(ram)](./bitburner.ns.ramoverride.md) | Change the current static RAM allocation of the script. | | [read(filename)](./bitburner.ns.read.md) | Read content of a file. | | [readPort(portNumber)](./bitburner.ns.readport.md) | Read data from a port. | | [relaysmtp(host)](./bitburner.ns.relaysmtp.md) | Runs relaySMTP.exe on a server. | diff --git a/markdown/bitburner.ns.ramoverride.md b/markdown/bitburner.ns.ramoverride.md new file mode 100644 index 000000000..59087e71c --- /dev/null +++ b/markdown/bitburner.ns.ramoverride.md @@ -0,0 +1,34 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [NS](./bitburner.ns.md) > [ramOverride](./bitburner.ns.ramoverride.md) + +## NS.ramOverride() method + +Change the current static RAM allocation of the script. + +**Signature:** + +```typescript +ramOverride(ram?: number): number; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| ram | number | _(Optional)_ The new RAM limit to set. | + +**Returns:** + +number + +The new static RAM limit, which will be the old one if it wasn't changed. This means you can use no parameters to check the current ram limit. + +## Remarks + +RAM cost: 0 GB + +This acts analagously to the ramOverride parameter in runOptions, but for changing RAM in the current running script. The static RAM allocation (the amount of RAM used by ONE thread) will be adjusted to the given value, if possible. This can fail if the number is less than the current dynamic RAM limit, or if adjusting upward would require more RAM than is available on the server. + +RAM usage will be rounded to the nearest hundredth of a GB, which is the granularity of all RAM calculations. + diff --git a/markdown/bitburner.runningscript.dynamicramusage.md b/markdown/bitburner.runningscript.dynamicramusage.md new file mode 100644 index 000000000..69ac5c750 --- /dev/null +++ b/markdown/bitburner.runningscript.dynamicramusage.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [RunningScript](./bitburner.runningscript.md) > [dynamicRamUsage](./bitburner.runningscript.dynamicramusage.md) + +## RunningScript.dynamicRamUsage property + +The dynamic RAM usage of (one thread of) this script instance. Does not affect overall RAM consumption (ramUsage is for that), but rather shows how much of the reserved RAM is currently in use via all the ns functions the script has called. Initially 1.6GB, this increases as new functions are called. + +Only set for scripts that are still running. + +**Signature:** + +```typescript +dynamicRamUsage: number | undefined; +``` diff --git a/markdown/bitburner.runningscript.md b/markdown/bitburner.runningscript.md index 8d758e91f..b69ab1cc9 100644 --- a/markdown/bitburner.runningscript.md +++ b/markdown/bitburner.runningscript.md @@ -16,6 +16,7 @@ interface RunningScript | Property | Modifiers | Type | Description | | --- | --- | --- | --- | | [args](./bitburner.runningscript.args.md) | | [ScriptArg](./bitburner.scriptarg.md)\[\] | Arguments the script was called with | +| [dynamicRamUsage](./bitburner.runningscript.dynamicramusage.md) | | number \| undefined |

The dynamic RAM usage of (one thread of) this script instance. Does not affect overall RAM consumption (ramUsage is for that), but rather shows how much of the reserved RAM is currently in use via all the ns functions the script has called. Initially 1.6GB, this increases as new functions are called.

Only set for scripts that are still running.

| | [filename](./bitburner.runningscript.filename.md) | | string | Filename of the script | | [logs](./bitburner.runningscript.logs.md) | | string\[\] | Script logs as an array. The newest log entries are at the bottom. Timestamps, if enabled, are placed inside [brackets] at the start of each line. | | [offlineExpGained](./bitburner.runningscript.offlineexpgained.md) | | number | Total amount of hacking experience earned from this script when offline | @@ -25,7 +26,7 @@ interface RunningScript | [onlineMoneyMade](./bitburner.runningscript.onlinemoneymade.md) | | number | Total amount of money made by this script when online | | [onlineRunningTime](./bitburner.runningscript.onlinerunningtime.md) | | number | Number of seconds that this script has been running online | | [pid](./bitburner.runningscript.pid.md) | | number | Process ID. Must be an integer | -| [ramUsage](./bitburner.runningscript.ramusage.md) | | number | How much RAM this script uses for ONE thread | +| [ramUsage](./bitburner.runningscript.ramusage.md) | | number | How much RAM this script uses for ONE thread. Also known as "static RAM usage," this value does not change once the script is started, unless you call ns.ramOverride(). | | [server](./bitburner.runningscript.server.md) | | string | Hostname of the server on which this script runs | | [tailProperties](./bitburner.runningscript.tailproperties.md) | | [TailProperties](./bitburner.tailproperties.md) \| null | Properties of the tail window, or null if it is not shown | | [temporary](./bitburner.runningscript.temporary.md) | | boolean | Whether this RunningScript is excluded from saves | diff --git a/markdown/bitburner.runningscript.ramusage.md b/markdown/bitburner.runningscript.ramusage.md index 2ddbb3a14..661dc9c5f 100644 --- a/markdown/bitburner.runningscript.ramusage.md +++ b/markdown/bitburner.runningscript.ramusage.md @@ -4,7 +4,7 @@ ## RunningScript.ramUsage property -How much RAM this script uses for ONE thread +How much RAM this script uses for ONE thread. Also known as "static RAM usage," this value does not change once the script is started, unless you call ns.ramOverride(). **Signature:** diff --git a/src/Netscript/NetscriptHelpers.tsx b/src/Netscript/NetscriptHelpers.tsx index a985e3757..8bae9e14e 100644 --- a/src/Netscript/NetscriptHelpers.tsx +++ b/src/Netscript/NetscriptHelpers.tsx @@ -1,5 +1,6 @@ import type { NetscriptContext } from "./APIWrapper"; import type { RunningScript as IRunningScript, Person as IPerson, Server as IServer, ScriptArg } from "@nsdefs"; +import type { WorkerScript } from "./WorkerScript"; import React from "react"; import { killWorkerScript } from "./killWorkerScript"; @@ -336,6 +337,9 @@ function updateDynamicRam(ctx: NetscriptContext, ramCost: number): void { ws.dynamicRamUsage = Math.min(ws.dynamicRamUsage + ramCost, RamCostConstants.Max); // This constant is just a handful of ULPs, and gives protection against // rounding issues without exposing rounding exploits in ramUsage. + // Most RAM calculations are guarded with roundToTwo(), but we use direct + // addition and this multiplication here for speed, since dynamic RAM + // checking is a speed-critical component. if (ws.dynamicRamUsage > 1.00000000000001 * ws.scriptRef.ramUsage) { log(ctx, () => "Insufficient static ram available."); const functionsUsed = Object.keys(ws.dynamicLoadedFns).join(", "); @@ -671,10 +675,11 @@ function getCannotFindRunningScriptErrorMessage(ident: ScriptIdentifier): string * @param runningScript Existing, internal RunningScript * @returns A sanitized, NS-facing copy of the RunningScript */ -function createPublicRunningScript(runningScript: RunningScript): IRunningScript { +function createPublicRunningScript(runningScript: RunningScript, workerScript?: WorkerScript): IRunningScript { const logProps = runningScript.tailProps; return { args: runningScript.args.slice(), + dynamicRamUsage: workerScript && roundToTwo(workerScript.dynamicRamUsage), filename: runningScript.filename, logs: runningScript.logs.map((x) => "" + x), offlineExpGained: runningScript.offlineExpGained, diff --git a/src/Netscript/RamCostGenerator.ts b/src/Netscript/RamCostGenerator.ts index 426d2471b..e0b16e3f9 100644 --- a/src/Netscript/RamCostGenerator.ts +++ b/src/Netscript/RamCostGenerator.ts @@ -569,6 +569,7 @@ export const RamCosts: RamCostTree = { getTotalScriptExpGain: RamCostConstants.GetScript, getScriptExpGain: RamCostConstants.GetScript, getRunningScript: RamCostConstants.GetRunningScript, + ramOverride: 0, formatNumber: 0, formatRam: 0, formatPercent: 0, diff --git a/src/NetscriptFunctions.ts b/src/NetscriptFunctions.ts index faaa3ba63..6d258d91d 100644 --- a/src/NetscriptFunctions.ts +++ b/src/NetscriptFunctions.ts @@ -63,6 +63,7 @@ import { formatNumber, } from "./ui/formatNumber"; import { convertTimeMsToTimeElapsedString } from "./utils/StringHelperFunctions"; +import { roundToTwo } from "./utils/helpers/roundToTwo"; import { LogBoxEvents, LogBoxCloserEvents } from "./ui/React/LogBoxManager"; import { arrayToString } from "./utils/helpers/ArrayHelpers"; import { NetscriptGang } from "./NetscriptFunctions/Gang"; @@ -1478,8 +1479,31 @@ export const ns: InternalAPI = { const ident = helpers.scriptIdentifier(ctx, fn, hostname, args); const runningScript = helpers.getRunningScript(ctx, ident); if (runningScript === null) return null; - return helpers.createPublicRunningScript(runningScript); + return helpers.createPublicRunningScript(runningScript, ctx.workerScript); }, + ramOverride: (ctx) => (_ram) => { + const newRam = roundToTwo(helpers.number(ctx, "ram", _ram || 0)); + const rs = ctx.workerScript.scriptRef; + const server = ctx.workerScript.getServer(); + if (newRam < roundToTwo(ctx.workerScript.dynamicRamUsage)) { + // Impossibly small, return immediately. + return rs.ramUsage; + } + const newServerRamUsed = roundToTwo(server.ramUsed + (newRam - rs.ramUsage) * rs.threads); + if (newServerRamUsed >= server.maxRam) { + // Can't allocate more RAM. + return rs.ramUsage; + } + if (newServerRamUsed <= 0) { + throw helpers.errorMessage( + ctx, + `Game error: Calculated impossible new server ramUsed ${newServerRamUsed} from new limit of ${_ram}`, + ); + } + server.updateRamUsed(newServerRamUsed); + rs.ramUsage = newRam; + return rs.ramUsage; + }, getHackTime: (ctx) => (_hostname = ctx.workerScript.hostname) => { diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts index 4e0e5960e..682965916 100644 --- a/src/ScriptEditor/NetscriptDefinitions.d.ts +++ b/src/ScriptEditor/NetscriptDefinitions.d.ts @@ -212,6 +212,16 @@ interface ReactElement { interface RunningScript { /** Arguments the script was called with */ args: ScriptArg[]; + /** + * The dynamic RAM usage of (one thread of) this script instance. + * Does not affect overall RAM consumption (ramUsage is for that), but + * rather shows how much of the reserved RAM is currently in use via all the + * ns functions the script has called. Initially 1.6GB, this increases as + * new functions are called. + * + * Only set for scripts that are still running. + */ + dynamicRamUsage: number | undefined; /** Filename of the script */ filename: string; /** @@ -233,7 +243,11 @@ interface RunningScript { onlineRunningTime: number; /** Process ID. Must be an integer */ pid: number; - /** How much RAM this script uses for ONE thread */ + /** + * How much RAM this script uses for ONE thread. + * Also known as "static RAM usage," this value does not change once the + * script is started, unless you call ns.ramOverride(). + */ ramUsage: number; /** Hostname of the server on which this script runs */ server: string; @@ -6698,6 +6712,25 @@ export interface NS { */ getRunningScript(filename?: FilenameOrPID, hostname?: string, ...args: ScriptArg[]): RunningScript | null; + /** + * Change the current static RAM allocation of the script. + * @remarks + * RAM cost: 0 GB + * + * This acts analagously to the ramOverride parameter in runOptions, but for changing RAM in + * the current running script. The static RAM allocation (the amount of RAM used by ONE thread) + * will be adjusted to the given value, if possible. This can fail if the number is less than the + * current dynamic RAM limit, or if adjusting upward would require more RAM than is available on + * the server. + * + * RAM usage will be rounded to the nearest hundredth of a GB, which is the granularity of all RAM calculations. + * + * @param ram - The new RAM limit to set. + * @returns The new static RAM limit, which will be the old one if it wasn't changed. + * This means you can use no parameters to check the current ram limit. + */ + ramOverride(ram?: number): number; + /** * Get cost of purchasing a server. * @remarks