diff --git a/markdown/bitburner.runoptions.md b/markdown/bitburner.runoptions.md index 3a8e3e9c3..da8ce57c9 100644 --- a/markdown/bitburner.runoptions.md +++ b/markdown/bitburner.runoptions.md @@ -15,6 +15,7 @@ interface RunOptions | Property | Modifiers | Type | Description | | --- | --- | --- | --- | +| [ramOverride?](./bitburner.runoptions.ramoverride.md) | | number |

_(Optional)_ The RAM allocation to launch each thread of the script with.

Lowering this will not 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 increase 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.

| | [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 | diff --git a/markdown/bitburner.runoptions.ramoverride.md b/markdown/bitburner.runoptions.ramoverride.md new file mode 100644 index 000000000..10f2747b6 --- /dev/null +++ b/markdown/bitburner.runoptions.ramoverride.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [RunOptions](./bitburner.runoptions.md) > [ramOverride](./bitburner.runoptions.ramoverride.md) + +## RunOptions.ramOverride property + +The RAM allocation to launch each thread of the script with. + +Lowering this will not 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 increase 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; +``` diff --git a/src/Netscript/NetscriptHelpers.ts b/src/Netscript/NetscriptHelpers.ts index 090aa6c98..ce7d0b5ec 100644 --- a/src/Netscript/NetscriptHelpers.ts +++ b/src/Netscript/NetscriptHelpers.ts @@ -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( @@ -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; + 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}`; diff --git a/src/Netscript/WorkerScript.ts b/src/Netscript/WorkerScript.ts index 0b2047fb2..24cf51eb3 100644 --- a/src/Netscript/WorkerScript.ts +++ b/src/Netscript/WorkerScript.ts @@ -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; diff --git a/src/NetscriptFunctions/Extra.ts b/src/NetscriptFunctions/Extra.ts index fe44f03f3..4d0712a65 100644 --- a/src/NetscriptFunctions/Extra.ts +++ b/src/NetscriptFunctions/Extra.ts @@ -37,7 +37,7 @@ export function NetscriptExtra(): InternalAPI { 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; diff --git a/src/NetscriptWorker.ts b/src/NetscriptWorker.ts index e93dbda92..374a63380 100644 --- a/src/NetscriptWorker.ts +++ b/src/NetscriptWorker.ts @@ -54,7 +54,6 @@ async function startNetscript2Script(workerScript: WorkerScript): Promise 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; diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts index ee9ca1f35..38069b406 100644 --- a/src/ScriptEditor/NetscriptDefinitions.d.ts +++ b/src/ScriptEditor/NetscriptDefinitions.d.ts @@ -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 not 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 increase 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 */ diff --git a/src/types.ts b/src/types.ts index 549a1affa..ba6ca0840 100644 --- a/src/types.ts +++ b/src/types.ts @@ -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 = { + [key in keyof T]?: unknown; +}; + /** Status object for functions that return a boolean indicating success/failure * and an optional message */ export interface IReturnStatus {