NETSCRIPT: Add ramOverride as a RunOption (#441)

Allows overriding the static ram calculation. Dynamic ram limit still applies.
This commit is contained in:
David Walker 2023-03-22 07:09:12 -07:00 committed by GitHub
parent a03a441906
commit 2b54c6c9b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 75 additions and 37 deletions

@ -15,6 +15,7 @@ interface RunOptions
| Property | Modifiers | Type | Description | | Property | Modifiers | Type | Description |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| [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> |
| [temporary?](./bitburner.runoptions.temporary.md) | | boolean | _(Optional)_ Whether this script is excluded from saves, defaults to false | | [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 | | [threads?](./bitburner.runoptions.threads.md) | | number | _(Optional)_ Number of threads that the script will run with, defaults to 1 |

@ -0,0 +1,19 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [RunOptions](./bitburner.runoptions.md) &gt; [ramOverride](./bitburner.runoptions.ramoverride.md)
## RunOptions.ramOverride property
The RAM allocation to launch each thread of the script with.
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.
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.
**Signature:**
```typescript
ramOverride?: number;
```

@ -6,7 +6,7 @@ import { ScriptDeath } from "./ScriptDeath";
import { formatExp, formatMoney, formatRam, formatThreads } from "../ui/formatNumber"; import { formatExp, formatMoney, formatRam, formatThreads } from "../ui/formatNumber";
import { ScriptArg } from "./ScriptArg"; import { ScriptArg } from "./ScriptArg";
import { CityName } from "../Enums"; import { CityName } from "../Enums";
import { BasicHGWOptions, RunOptions, RunningScript as IRunningScript, Person as IPerson } from "@nsdefs"; import { BasicHGWOptions, RunningScript as IRunningScript, Person as IPerson } from "@nsdefs";
import { Server } from "../Server/Server"; import { Server } from "../Server/Server";
import { import {
calculateHackingChance, calculateHackingChance,
@ -33,7 +33,7 @@ import { BaseServer } from "../Server/BaseServer";
import { dialogBoxCreate } from "../ui/React/DialogBox"; import { dialogBoxCreate } from "../ui/React/DialogBox";
import { checkEnum } from "../utils/helpers/enum"; import { checkEnum } from "../utils/helpers/enum";
import { RamCostConstants } from "./RamCostGenerator"; import { RamCostConstants } from "./RamCostGenerator";
import { isPositiveInteger, PositiveInteger } from "../types"; import { isPositiveInteger, PositiveInteger, Unknownify } from "../types";
import { Engine } from "../engine"; import { Engine } from "../engine";
export const helpers = { export const helpers = {
@ -72,6 +72,7 @@ export const helpers = {
export interface CompleteRunOptions { export interface CompleteRunOptions {
threads: PositiveInteger; threads: PositiveInteger;
temporary: boolean; temporary: boolean;
ramOverride?: number;
} }
export function assertMember<T extends string>( export function assertMember<T extends string>(
@ -181,21 +182,23 @@ function scriptArgs(ctx: NetscriptContext, args: unknown) {
return args; return args;
} }
function runOptions(ctx: NetscriptContext, thread_or_opt: unknown): CompleteRunOptions { function runOptions(ctx: NetscriptContext, threadOrOption: unknown): CompleteRunOptions {
let threads: any = 1; if (typeof threadOrOption !== "object" || threadOrOption === null) {
let temporary: any = false; return { threads: positiveInteger(ctx, "threads", threadOrOption ?? 1), temporary: false };
if (typeof thread_or_opt !== "object" || thread_or_opt === null) {
threads = thread_or_opt ?? 1;
} else {
// Lie and pretend it's a RunOptions. It could be anything, we'll deal with that below.
const options = thread_or_opt as RunOptions;
threads = options.threads ?? 1;
temporary = options.temporary ?? false;
} }
return { // Safe assertion since threadOrOption type has been narrowed to a non-null object
threads: positiveInteger(ctx, "thread", threads), const options = threadOrOption as Unknownify<CompleteRunOptions>;
temporary: !!temporary, const threads = positiveInteger(ctx, "RunOptions.threads", options.threads ?? 1);
}; const temporary = !!options.temporary;
if (options.ramOverride === undefined || options.ramOverride === null) return { threads, temporary };
const ramOverride = number(ctx, "RunOptions.ramOverride", options.ramOverride);
if (ramOverride < RamCostConstants.Base) {
throw makeRuntimeErrorMsg(
ctx,
`RunOptions.ramOverride must be >= baseCost (${RamCostConstants.Base}), was ${ramOverride}`,
);
}
return { threads, temporary, ramOverride };
} }
/** Convert multiple arguments for tprint or print into a single string. */ /** Convert multiple arguments for tprint or print into a single string. */
@ -375,17 +378,17 @@ function updateDynamicRam(ctx: NetscriptContext, ramCost: number): void {
ws.dynamicLoadedFns[fnName] = true; ws.dynamicLoadedFns[fnName] = true;
ws.dynamicRamUsage = Math.min(ws.dynamicRamUsage + ramCost, RamCostConstants.Max); ws.dynamicRamUsage = Math.min(ws.dynamicRamUsage + ramCost, RamCostConstants.Max);
if (ws.dynamicRamUsage > 1.01 * ws.ramUsage) { if (ws.dynamicRamUsage > 1.01 * ws.scriptRef.ramUsage) {
log(ctx, () => "Insufficient static ram available."); log(ctx, () => "Insufficient static ram available.");
ws.env.stopFlag = true; ws.env.stopFlag = true;
throw makeRuntimeErrorMsg( throw makeRuntimeErrorMsg(
ctx, ctx,
`Dynamic RAM usage calculated to be greater than initial RAM usage. `Dynamic RAM usage calculated to be greater than RAM allocation.
This is probably because you somehow circumvented the static RAM calculation. This is probably because you somehow circumvented the static RAM calculation.
Threads: ${ws.scriptRef.threads} Threads: ${ws.scriptRef.threads}
Dynamic RAM Usage: ${formatRam(ws.dynamicRamUsage)} per thread Dynamic RAM Usage: ${formatRam(ws.dynamicRamUsage)} per thread
Static RAM Usage: ${formatRam(ws.ramUsage)} per thread RAM Allocation: ${formatRam(ws.scriptRef.ramUsage)} per thread
One of these could be the reason: One of these could be the reason:
* Using eval() to get a reference to a ns function * Using eval() to get a reference to a ns function
@ -394,6 +397,8 @@ function updateDynamicRam(ctx: NetscriptContext, ramCost: number): void {
* Using map access to do the same * Using map access to do the same
\u00a0\u00a0const myScan = ns['scan']; \u00a0\u00a0const myScan = ns['scan'];
* Using RunOptions.ramOverride to set a smaller allocation than needed
Sorry :(`, Sorry :(`,
"RAM USAGE", "RAM USAGE",
); );
@ -651,13 +656,12 @@ function log(ctx: NetscriptContext, message: () => string) {
/** /**
* Searches for and returns the RunningScript object for the specified script. * Searches for and returns the RunningScript object for the specified script.
* If the 'fn' argument is not specified, this returns the current RunningScript. * If the 'fn' argument is not specified, this returns the current RunningScript.
* @param {string} fn - Filename of script * @param fn - Filename of script
* @param {string} hostname - Hostname/ip of the server on which the script resides * @param hostname - Hostname/ip of the server on which the script resides
* @param {any[]} scriptArgs - Running script's arguments * @param scriptArgs - Running script's arguments
* @returns {RunningScript} * @returns Running script identified by the parameters, or null if no such script
* Running script identified by the parameters, or null if no such script * exists, or the current running script if the first argument 'fn'
* exists, or the current running script if the first argument 'fn' * is not specified.
* is not specified.
*/ */
function getRunningScriptByArgs( function getRunningScriptByArgs(
ctx: NetscriptContext, ctx: NetscriptContext,
@ -706,10 +710,8 @@ function getRunningScript(ctx: NetscriptContext, ident: ScriptIdentifier): Runni
/** /**
* Helper function for getting the error log message when the user specifies * Helper function for getting the error log message when the user specifies
* a nonexistent running script * a nonexistent running script
* @param {string} fn - Filename of script * @param ident - Identifier (pid or identifier object) of script.
* @param {string} hostname - Hostname/ip of the server on which the script resides * @returns Error message to print to logs
* @param {any[]} scriptArgs - Running script's arguments
* @returns {string} Error message to print to logs
*/ */
function getCannotFindRunningScriptErrorMessage(ident: ScriptIdentifier): string { function getCannotFindRunningScriptErrorMessage(ident: ScriptIdentifier): string {
if (typeof ident === "number") return `Cannot find running script with pid: ${ident}`; if (typeof ident === "number") return `Cannot find running script with pid: ${ident}`;

@ -71,9 +71,6 @@ export class WorkerScript {
*/ */
pid: number; pid: number;
/** Script's Static RAM usage. Equivalent to underlying script's RAM usage */
ramUsage = RamCostConstants.Base;
/** Reference to underlying RunningScript object */ /** Reference to underlying RunningScript object */
scriptRef: RunningScript; scriptRef: RunningScript;

@ -37,7 +37,7 @@ export function NetscriptExtra(): InternalAPI<INetscriptExtra> {
real_document.completely_unused_field = undefined; real_document.completely_unused_field = undefined;
// set one to true and check that it affected the other. // set one to true and check that it affected the other.
real_document.completely_unused_field = true; real_document.completely_unused_field = true;
if (d.completely_unused_field && ctx.workerScript.ramUsage === RamCostConstants.Base) { if (d.completely_unused_field && ctx.workerScript.scriptRef.ramUsage === RamCostConstants.Base) {
Player.giveExploit(Exploit.Bypass); Player.giveExploit(Exploit.Bypass);
} }
d.completely_unused_field = undefined; d.completely_unused_field = undefined;

@ -54,7 +54,6 @@ async function startNetscript2Script(workerScript: WorkerScript): Promise<void>
if (script === null) throw "workerScript had no associated script. This is a bug."; if (script === null) throw "workerScript had no associated script. This is a bug.";
if (!script.ramUsage) throw "Attempting to start a script with no calculated ram cost. This is a bug."; if (!script.ramUsage) throw "Attempting to start a script with no calculated ram cost. This is a bug.";
const loadedModule = await compile(script, scripts); const loadedModule = await compile(script, scripts);
workerScript.ramUsage = script.ramUsage;
const ns = workerScript.env.vars; const ns = workerScript.env.vars;
if (!loadedModule) throw `${script.filename} cannot be run because the script module won't load`; if (!loadedModule) throw `${script.filename} cannot be run because the script module won't load`;
@ -319,7 +318,6 @@ function createAndAddWorkerScript(runningScriptObj: RunningScript, server: BaseS
// Create the WorkerScript. NOTE: WorkerScript ctor will set the underlying // Create the WorkerScript. NOTE: WorkerScript ctor will set the underlying
// RunningScript's PID as well // RunningScript's PID as well
const workerScript = new WorkerScript(runningScriptObj, pid, NetscriptFunctions); const workerScript = new WorkerScript(runningScriptObj, pid, NetscriptFunctions);
workerScript.ramUsage = runningScriptObj.ramUsage;
// Add the WorkerScript to the global pool // Add the WorkerScript to the global pool
workerScripts.set(pid, workerScript); workerScripts.set(pid, workerScript);
@ -420,7 +418,7 @@ export function runScriptFromScript(
return 0; return 0;
} }
const singleRamUsage = script.getRamUsage(host.scripts); const singleRamUsage = runOpts.ramOverride ?? script.getRamUsage(host.scripts);
if (!singleRamUsage) { if (!singleRamUsage) {
workerScript.log(caller, () => `Ram usage could not be calculated for ${scriptname}`); workerScript.log(caller, () => `Ram usage could not be calculated for ${scriptname}`);
return 0; return 0;

@ -203,6 +203,21 @@ interface RunOptions {
threads?: number; threads?: number;
/** Whether this script is excluded from saves, defaults to false */ /** Whether this script is excluded from saves, defaults to false */
temporary?: boolean; temporary?: boolean;
/**
* The RAM allocation to launch each thread of the script with.
*
* Lowering this will <i>not</i> automatically let you get away with using less RAM:
* the dynamic RAM check enforces that all {@link NS} 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 <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.
*/
ramOverride?: number;
} }
/** @public */ /** @public */

@ -7,6 +7,12 @@ export type PositiveInteger = Integer & PositiveNumber;
export const isInteger = (n: unknown): n is Integer => Number.isInteger(n); export const isInteger = (n: unknown): n is Integer => Number.isInteger(n);
export const isPositiveInteger = (n: unknown): n is PositiveInteger => isInteger(n) && n > 0; export const isPositiveInteger = (n: unknown): n is PositiveInteger => isInteger(n) && n > 0;
/** Utility type for typechecking objects. Makes all keys optional and sets values to unknown,
* making it safe to assert a shape for the variable once it's known to be a non-null object */
export type Unknownify<T> = {
[key in keyof T]?: unknown;
};
/** Status object for functions that return a boolean indicating success/failure /** Status object for functions that return a boolean indicating success/failure
* and an optional message */ * and an optional message */
export interface IReturnStatus { export interface IReturnStatus {