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.
This commit is contained in:
David Walker 2024-06-28 18:42:20 -07:00 committed by GitHub
parent 1c20a24079
commit 9c9a69f2e2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 119 additions and 5 deletions

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

@ -0,0 +1,34 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [NS](./bitburner.ns.md) &gt; [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.

@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [RunningScript](./bitburner.runningscript.md) &gt; [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;
```

@ -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 | <p>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.</p><p>Only set for scripts that are still running.</p> |
| [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 <code>[brackets]</code> 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 |

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

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

@ -569,6 +569,7 @@ export const RamCosts: RamCostTree<NSFull> = {
getTotalScriptExpGain: RamCostConstants.GetScript,
getScriptExpGain: RamCostConstants.GetScript,
getRunningScript: RamCostConstants.GetRunningScript,
ramOverride: 0,
formatNumber: 0,
formatRam: 0,
formatPercent: 0,

@ -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<NSFull> = {
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) => {

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