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 |
| --- | --- | --- | --- |
| [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 |
| [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 { ScriptArg } from "./ScriptArg";
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 {
calculateHackingChance,
@ -33,7 +33,7 @@ import { BaseServer } from "../Server/BaseServer";
import { dialogBoxCreate } from "../ui/React/DialogBox";
import { checkEnum } from "../utils/helpers/enum";
import { RamCostConstants } from "./RamCostGenerator";
import { isPositiveInteger, PositiveInteger } from "../types";
import { isPositiveInteger, PositiveInteger, Unknownify } from "../types";
import { Engine } from "../engine";
export const helpers = {
@ -72,6 +72,7 @@ export const helpers = {
export interface CompleteRunOptions {
threads: PositiveInteger;
temporary: boolean;
ramOverride?: number;
}
export function assertMember<T extends string>(
@ -181,21 +182,23 @@ function scriptArgs(ctx: NetscriptContext, args: unknown) {
return args;
}
function runOptions(ctx: NetscriptContext, thread_or_opt: unknown): CompleteRunOptions {
let threads: any = 1;
let temporary: any = 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;
function runOptions(ctx: NetscriptContext, threadOrOption: unknown): CompleteRunOptions {
if (typeof threadOrOption !== "object" || threadOrOption === null) {
return { threads: positiveInteger(ctx, "threads", threadOrOption ?? 1), temporary: false };
}
return {
threads: positiveInteger(ctx, "thread", threads),
temporary: !!temporary,
};
// Safe assertion since threadOrOption type has been narrowed to a non-null object
const options = threadOrOption as Unknownify<CompleteRunOptions>;
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. */
@ -375,17 +378,17 @@ function updateDynamicRam(ctx: NetscriptContext, ramCost: number): void {
ws.dynamicLoadedFns[fnName] = true;
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.");
ws.env.stopFlag = true;
throw makeRuntimeErrorMsg(
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.
Threads: ${ws.scriptRef.threads}
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:
* 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
\u00a0\u00a0const myScan = ns['scan'];
* Using RunOptions.ramOverride to set a smaller allocation than needed
Sorry :(`,
"RAM USAGE",
);
@ -651,13 +656,12 @@ function log(ctx: NetscriptContext, message: () => string) {
/**
* Searches for and returns the RunningScript object for the specified script.
* If the 'fn' argument is not specified, this returns the current RunningScript.
* @param {string} fn - Filename of script
* @param {string} hostname - Hostname/ip of the server on which the script resides
* @param {any[]} scriptArgs - Running script's arguments
* @returns {RunningScript}
* Running script identified by the parameters, or null if no such script
* exists, or the current running script if the first argument 'fn'
* is not specified.
* @param fn - Filename of script
* @param hostname - Hostname/ip of the server on which the script resides
* @param scriptArgs - Running script's arguments
* @returns Running script identified by the parameters, or null if no such script
* exists, or the current running script if the first argument 'fn'
* is not specified.
*/
function getRunningScriptByArgs(
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
* a nonexistent running script
* @param {string} fn - Filename of script
* @param {string} hostname - Hostname/ip of the server on which the script resides
* @param {any[]} scriptArgs - Running script's arguments
* @returns {string} Error message to print to logs
* @param ident - Identifier (pid or identifier object) of script.
* @returns Error message to print to logs
*/
function getCannotFindRunningScriptErrorMessage(ident: ScriptIdentifier): string {
if (typeof ident === "number") return `Cannot find running script with pid: ${ident}`;

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

@ -37,7 +37,7 @@ export function NetscriptExtra(): InternalAPI<INetscriptExtra> {
real_document.completely_unused_field = undefined;
// set one to true and check that it affected the other.
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);
}
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.ramUsage) throw "Attempting to start a script with no calculated ram cost. This is a bug.";
const loadedModule = await compile(script, scripts);
workerScript.ramUsage = script.ramUsage;
const ns = workerScript.env.vars;
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
// RunningScript's PID as well
const workerScript = new WorkerScript(runningScriptObj, pid, NetscriptFunctions);
workerScript.ramUsage = runningScriptObj.ramUsage;
// Add the WorkerScript to the global pool
workerScripts.set(pid, workerScript);
@ -420,7 +418,7 @@ export function runScriptFromScript(
return 0;
}
const singleRamUsage = script.getRamUsage(host.scripts);
const singleRamUsage = runOpts.ramOverride ?? script.getRamUsage(host.scripts);
if (!singleRamUsage) {
workerScript.log(caller, () => `Ram usage could not be calculated for ${scriptname}`);
return 0;

@ -203,6 +203,21 @@ interface RunOptions {
threads?: number;
/** Whether this script is excluded from saves, defaults to false */
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 */

@ -7,6 +7,12 @@ export type PositiveInteger = Integer & PositiveNumber;
export const isInteger = (n: unknown): n is Integer => Number.isInteger(n);
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
* and an optional message */
export interface IReturnStatus {