NETSCRIPT: Add "temporary" as a RunOption to run/exec/spawn (#432)

This commit is contained in:
David Walker 2023-03-21 15:54:49 -07:00 committed by GitHub
parent 042a476f78
commit 98f7f473b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 611 additions and 45 deletions

@ -86,6 +86,7 @@
| [RecentScript](./bitburner.recentscript.md) | | | [RecentScript](./bitburner.recentscript.md) | |
| [ReputationFormulas](./bitburner.reputationformulas.md) | Reputation formulas | | [ReputationFormulas](./bitburner.reputationformulas.md) | Reputation formulas |
| [RunningScript](./bitburner.runningscript.md) | | | [RunningScript](./bitburner.runningscript.md) | |
| [RunOptions](./bitburner.runoptions.md) | |
| [Server](./bitburner.server.md) | A single server. | | [Server](./bitburner.server.md) | A single server. |
| [Singularity](./bitburner.singularity.md) | Singularity API | | [Singularity](./bitburner.singularity.md) | Singularity API |
| [Skills](./bitburner.skills.md) | | | [Skills](./bitburner.skills.md) | |

@ -9,7 +9,12 @@ Start another script on any server.
**Signature:** **Signature:**
```typescript ```typescript
exec(script: string, hostname: string, numThreads?: number, ...args: (string | number | boolean)[]): number; exec(
script: string,
hostname: string,
threadOrOptions?: number | RunOptions,
...args: (string | number | boolean)[]
): number;
``` ```
## Parameters ## Parameters
@ -18,7 +23,7 @@ exec(script: string, hostname: string, numThreads?: number, ...args: (string | n
| --- | --- | --- | | --- | --- | --- |
| script | string | Filename of script to execute. | | script | string | Filename of script to execute. |
| hostname | string | Hostname of the <code>target server</code> on which to execute the script. | | hostname | string | Hostname of the <code>target server</code> on which to execute the script. |
| numThreads | number | _(Optional)_ Integer number of threads for new script. Defaults to 1. | | threadOrOptions | number \| [RunOptions](./bitburner.runoptions.md) | _(Optional)_ Either an integer number of threads for new script, or a [RunOptions](./bitburner.runoptions.md) object. Threads defaults to 1. |
| args | (string \| number \| boolean)\[\] | Additional arguments to pass into the new script that is being run. Note that if any arguments are being passed into the new script, then the third argument numThreads must be filled in with a value. | | args | (string \| number \| boolean)\[\] | Additional arguments to pass into the new script that is being run. Note that if any arguments are being passed into the new script, then the third argument numThreads must be filled in with a value. |
**Returns:** **Returns:**
@ -37,7 +42,7 @@ If the script was successfully started, then this function returns the PID of th
PID stands for Process ID. The PID is a unique identifier for each script. The PID will always be a positive integer. PID stands for Process ID. The PID is a unique identifier for each script. The PID will always be a positive integer.
Running this function with a numThreads argument of 0 or less will cause a runtime error. Running this function with 0 or fewer threads will cause a runtime error.
## Example ## Example
@ -50,7 +55,7 @@ ns.exec("generic-hack.js", "foodnstuff");
// The following example will try to run the script generic-hack.js on the // The following example will try to run the script generic-hack.js on the
// joesguns server with 10 threads. // joesguns server with 10 threads.
ns.exec("generic-hack.js", "joesguns", 10); ns.exec("generic-hack.js", "joesguns", {threads: 10});
// This last example will try to run the script foo.js on the foodnstuff server // This last example will try to run the script foo.js on the foodnstuff server
// with 5 threads. It will also pass the number 1 and the string “test” in as // with 5 threads. It will also pass the number 1 and the string “test” in as

@ -14,5 +14,5 @@ readonly hacknet: Hacknet;
## Remarks ## Remarks
RAM cost: 4 GB RAM cost: 4 GB.

@ -72,7 +72,7 @@ export async function main(ns) {
| [deleteServer(host)](./bitburner.ns.deleteserver.md) | Delete a purchased server. | | [deleteServer(host)](./bitburner.ns.deleteserver.md) | Delete a purchased server. |
| [disableLog(fn)](./bitburner.ns.disablelog.md) | Disables logging for the given function. | | [disableLog(fn)](./bitburner.ns.disablelog.md) | Disables logging for the given function. |
| [enableLog(fn)](./bitburner.ns.enablelog.md) | Enable logging for a certain function. | | [enableLog(fn)](./bitburner.ns.enablelog.md) | Enable logging for a certain function. |
| [exec(script, hostname, numThreads, args)](./bitburner.ns.exec.md) | Start another script on any server. | | [exec(script, hostname, threadOrOptions, args)](./bitburner.ns.exec.md) | Start another script on any server. |
| [exit()](./bitburner.ns.exit.md) | Terminates the current script immediately. | | [exit()](./bitburner.ns.exit.md) | Terminates the current script immediately. |
| [fileExists(filename, host)](./bitburner.ns.fileexists.md) | Check if a file exists. | | [fileExists(filename, host)](./bitburner.ns.fileexists.md) | Check if a file exists. |
| [flags(schema)](./bitburner.ns.flags.md) | Parse command line flags. | | [flags(schema)](./bitburner.ns.flags.md) | Parse command line flags. |
@ -152,15 +152,15 @@ export async function main(ns) {
| [renamePurchasedServer(hostname, newName)](./bitburner.ns.renamepurchasedserver.md) | Rename a purchased server. | | [renamePurchasedServer(hostname, newName)](./bitburner.ns.renamepurchasedserver.md) | Rename a purchased server. |
| [resizeTail(width, height, pid)](./bitburner.ns.resizetail.md) | Resize a tail window. | | [resizeTail(width, height, pid)](./bitburner.ns.resizetail.md) | Resize a tail window. |
| [rm(name, host)](./bitburner.ns.rm.md) | Delete a file. | | [rm(name, host)](./bitburner.ns.rm.md) | Delete a file. |
| [run(script, numThreads, args)](./bitburner.ns.run.md) | Start another script on the current server. | | [run(script, threadOrOptions, args)](./bitburner.ns.run.md) | Start another script on the current server. |
| [scan(host)](./bitburner.ns.scan.md) | Get the list of servers connected to a server. | | [scan(host)](./bitburner.ns.scan.md) | Get the list of servers connected to a server. |
| [scp(files, destination, source)](./bitburner.ns.scp.md) | Copy file between servers. | | [scp(files, destination, source)](./bitburner.ns.scp.md) | Copy file between servers. |
| [scriptKill(script, host)](./bitburner.ns.scriptkill.md) | Kill all scripts with a filename. | | [scriptKill(script, host)](./bitburner.ns.scriptkill.md) | Kill all scripts with a filename. |
| [scriptRunning(script, host)](./bitburner.ns.scriptrunning.md) | Check if any script with a filename is running. | | [scriptRunning(script, host)](./bitburner.ns.scriptrunning.md) | Check if any script with a filename is running. |
| [serverExists(host)](./bitburner.ns.serverexists.md) | Returns a boolean denoting whether or not the specified server exists. | | [serverExists(host)](./bitburner.ns.serverexists.md) | Returns a boolean denoting whether or not the specified server exists. |
| [share()](./bitburner.ns.share.md) | Share your computer with your factions. | | [share()](./bitburner.ns.share.md) | Share the server's ram with your factions. |
| [sleep(millis)](./bitburner.ns.sleep.md) | Suspends the script for n milliseconds. | | [sleep(millis)](./bitburner.ns.sleep.md) | Suspends the script for n milliseconds. |
| [spawn(script, numThreads, args)](./bitburner.ns.spawn.md) | Terminate current script and start another in 10 seconds. | | [spawn(script, threadOrOptions, args)](./bitburner.ns.spawn.md) | Terminate current script and start another in 10 seconds. |
| [sprintf(format, args)](./bitburner.ns.sprintf.md) | Format a string. | | [sprintf(format, args)](./bitburner.ns.sprintf.md) | Format a string. |
| [sqlinject(host)](./bitburner.ns.sqlinject.md) | Runs SQLInject.exe on a server. | | [sqlinject(host)](./bitburner.ns.sqlinject.md) | Runs SQLInject.exe on a server. |
| [tail(fn, host, args)](./bitburner.ns.tail.md) | Open the tail window of a script. | | [tail(fn, host, args)](./bitburner.ns.tail.md) | Open the tail window of a script. |

@ -9,7 +9,7 @@ Start another script on the current server.
**Signature:** **Signature:**
```typescript ```typescript
run(script: string, numThreads?: number, ...args: (string | number | boolean)[]): number; run(script: string, threadOrOptions?: number | RunOptions, ...args: (string | number | boolean)[]): number;
``` ```
## Parameters ## Parameters
@ -17,7 +17,7 @@ run(script: string, numThreads?: number, ...args: (string | number | boolean)[])
| Parameter | Type | Description | | Parameter | Type | Description |
| --- | --- | --- | | --- | --- | --- |
| script | string | Filename of script to run. | | script | string | Filename of script to run. |
| numThreads | number | _(Optional)_ Integer number of threads for new script. Defaults to 1. | | threadOrOptions | number \| [RunOptions](./bitburner.runoptions.md) | _(Optional)_ Either an integer number of threads for new script, or a [RunOptions](./bitburner.runoptions.md) object. Threads defaults to 1. |
| args | (string \| number \| boolean)\[\] | Additional arguments to pass into the new script that is being run. Note that if any arguments are being passed into the new script, then the second argument numThreads must be filled in with a value. | | args | (string \| number \| boolean)\[\] | Additional arguments to pass into the new script that is being run. Note that if any arguments are being passed into the new script, then the second argument numThreads must be filled in with a value. |
**Returns:** **Returns:**
@ -32,11 +32,13 @@ RAM cost: 1 GB
Run a script as a separate process. This function can only be used to run scripts located on the current server (the server running the script that calls this function). Requires a significant amount of RAM to run this command. Run a script as a separate process. This function can only be used to run scripts located on the current server (the server running the script that calls this function). Requires a significant amount of RAM to run this command.
The second argument is either a thread count, or a [RunOptions](./bitburner.runoptions.md) object that can also specify the number of threads (among other things).
If the script was successfully started, then this functions returns the PID of that script. Otherwise, it returns 0. If the script was successfully started, then this functions returns the PID of that script. Otherwise, it returns 0.
PID stands for Process ID. The PID is a unique identifier for each script. The PID will always be a positive integer. PID stands for Process ID. The PID is a unique identifier for each script. The PID will always be a positive integer.
Running this function with a numThreads argument of 0 or less will cause a runtime error. Running this function with 0 or fewer threads will cause a runtime error.
## Example ## Example
@ -46,7 +48,7 @@ Running this function with a numThreads argument of 0 or less will cause a runti
ns.run("foo.js"); ns.run("foo.js");
//The following example will run foo.js but with 5 threads instead of single-threaded: //The following example will run foo.js but with 5 threads instead of single-threaded:
ns.run("foo.js", 5); ns.run("foo.js", {threads: 5});
//This next example will run foo.js single-threaded, and will pass the string foodnstuff into the script as an argument: //This next example will run foo.js single-threaded, and will pass the string foodnstuff into the script as an argument:
ns.run("foo.js", 1, 'foodnstuff'); ns.run("foo.js", 1, 'foodnstuff');

@ -4,7 +4,7 @@
## NS.share() method ## NS.share() method
Share your computer with your factions. Share the server's ram with your factions.
**Signature:** **Signature:**

@ -9,7 +9,7 @@ Terminate current script and start another in 10 seconds.
**Signature:** **Signature:**
```typescript ```typescript
spawn(script: string, numThreads?: number, ...args: (string | number | boolean)[]): void; spawn(script: string, threadOrOptions?: number | RunOptions, ...args: (string | number | boolean)[]): void;
``` ```
## Parameters ## Parameters
@ -17,7 +17,7 @@ spawn(script: string, numThreads?: number, ...args: (string | number | boolean)[
| Parameter | Type | Description | | Parameter | Type | Description |
| --- | --- | --- | | --- | --- | --- |
| script | string | Filename of script to execute. | | script | string | Filename of script to execute. |
| numThreads | number | _(Optional)_ Integer number of threads for new script. Defaults to 1. | | threadOrOptions | number \| [RunOptions](./bitburner.runoptions.md) | _(Optional)_ Either an integer number of threads for new script, or a [RunOptions](./bitburner.runoptions.md) object. Threads defaults to 1. |
| args | (string \| number \| boolean)\[\] | Additional arguments to pass into the new script that is being run. | | args | (string \| number \| boolean)\[\] | Additional arguments to pass into the new script that is being run. |
**Returns:** **Returns:**
@ -32,7 +32,7 @@ Terminates the current script, and then after a delay of about 10 seconds it wil
Because this function immediately terminates the script, it does not have a return value. Because this function immediately terminates the script, it does not have a return value.
Running this function with a numThreads argument of 0 or less will cause a runtime error. Running this function with 0 or fewer threads will cause a runtime error.
## Example ## Example

@ -19,5 +19,6 @@ interface ProcessInfo
| [args](./bitburner.processinfo.args.md) | | (string \| number \| boolean)\[\] | Script's arguments | | [args](./bitburner.processinfo.args.md) | | (string \| number \| boolean)\[\] | Script's arguments |
| [filename](./bitburner.processinfo.filename.md) | | string | Script name. | | [filename](./bitburner.processinfo.filename.md) | | string | Script name. |
| [pid](./bitburner.processinfo.pid.md) | | number | Process ID | | [pid](./bitburner.processinfo.pid.md) | | number | Process ID |
| [temporary](./bitburner.processinfo.temporary.md) | | boolean | Whether this process is excluded from saves |
| [threads](./bitburner.processinfo.threads.md) | | number | Number of threads script is running with | | [threads](./bitburner.processinfo.threads.md) | | number | Number of threads script is running with |

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [ProcessInfo](./bitburner.processinfo.md) &gt; [temporary](./bitburner.processinfo.temporary.md)
## ProcessInfo.temporary property
Whether this process is excluded from saves
**Signature:**
```typescript
temporary: boolean;
```

@ -27,5 +27,6 @@ interface RunningScript
| [pid](./bitburner.runningscript.pid.md) | | number | Process ID. Must be an integer | | [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 |
| [server](./bitburner.runningscript.server.md) | | string | Hostname of the server on which this script runs | | [server](./bitburner.runningscript.server.md) | | string | Hostname of the server on which this script runs |
| [temporary](./bitburner.runningscript.temporary.md) | | boolean | Whether this RunningScript is excluded from saves |
| [threads](./bitburner.runningscript.threads.md) | | number | Number of threads that this script runs with | | [threads](./bitburner.runningscript.threads.md) | | number | Number of threads that this script runs with |

@ -0,0 +1,13 @@
<!-- 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; [temporary](./bitburner.runningscript.temporary.md)
## RunningScript.temporary property
Whether this RunningScript is excluded from saves
**Signature:**
```typescript
temporary: boolean;
```

@ -0,0 +1,20 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [RunOptions](./bitburner.runoptions.md)
## RunOptions interface
**Signature:**
```typescript
interface RunOptions
```
## Properties
| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [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,13 @@
<!-- 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; [temporary](./bitburner.runoptions.temporary.md)
## RunOptions.temporary property
Whether this script is excluded from saves, defaults to false
**Signature:**
```typescript
temporary?: boolean;
```

@ -0,0 +1,13 @@
<!-- 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; [threads](./bitburner.runoptions.threads.md)
## RunOptions.threads property
Number of threads that the script will run with, defaults to 1
**Signature:**
```typescript
threads?: 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, RunningScript as IRunningScript, Person as IPerson } from "@nsdefs"; import { BasicHGWOptions, RunOptions, RunningScript as IRunningScript, Person as IPerson } from "@nsdefs";
import { Server } from "../Server/Server"; import { Server } from "../Server/Server";
import { import {
calculateHackingChance, calculateHackingChance,
@ -41,6 +41,7 @@ export const helpers = {
number, number,
positiveInteger, positiveInteger,
scriptArgs, scriptArgs,
runOptions,
argsToString, argsToString,
makeBasicErrorMsg, makeBasicErrorMsg,
makeRuntimeErrorMsg, makeRuntimeErrorMsg,
@ -67,6 +68,12 @@ export const helpers = {
failOnHacknetServer, failOnHacknetServer,
}; };
// RunOptions with non-optional, type-validated members, for passing between internal functions.
export interface CompleteRunOptions {
threads: PositiveInteger;
temporary: boolean;
}
export function assertMember<T extends string>( export function assertMember<T extends string>(
ctx: NetscriptContext, ctx: NetscriptContext,
obj: Record<string, T> | T[], obj: Record<string, T> | T[],
@ -174,6 +181,23 @@ function scriptArgs(ctx: NetscriptContext, args: unknown) {
return args; 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;
}
return {
threads: positiveInteger(ctx, "thread", threads),
temporary: !!temporary,
};
}
/** Convert multiple arguments for tprint or print into a single string. */ /** Convert multiple arguments for tprint or print into a single string. */
function argsToString(args: unknown[]): string { function argsToString(args: unknown[]): string {
let out = ""; let out = "";
@ -718,6 +742,7 @@ function createPublicRunningScript(runningScript: RunningScript): IRunningScript
ramUsage: runningScript.ramUsage, ramUsage: runningScript.ramUsage,
server: runningScript.server, server: runningScript.server,
threads: runningScript.threads, threads: runningScript.threads,
temporary: runningScript.temporary,
}; };
} }

@ -688,32 +688,32 @@ export const ns: InternalAPI<NSFull> = {
}, },
run: run:
(ctx) => (ctx) =>
(_scriptname, _threads = 1, ..._args) => { (_scriptname, _thread_or_opt = 1, ..._args) => {
const scriptname = helpers.string(ctx, "scriptname", _scriptname); const scriptname = helpers.string(ctx, "scriptname", _scriptname);
const threads = helpers.positiveInteger(ctx, "threads", _threads); const runOpts = helpers.runOptions(ctx, _thread_or_opt);
const args = helpers.scriptArgs(ctx, _args); const args = helpers.scriptArgs(ctx, _args);
const scriptServer = GetServer(ctx.workerScript.hostname); const scriptServer = GetServer(ctx.workerScript.hostname);
if (scriptServer == null) { if (scriptServer == null) {
throw helpers.makeRuntimeErrorMsg(ctx, "Could not find server. This is a bug. Report to dev."); throw helpers.makeRuntimeErrorMsg(ctx, "Could not find server. This is a bug. Report to dev.");
} }
return runScriptFromScript("run", scriptServer, scriptname, args, ctx.workerScript, threads); return runScriptFromScript("run", scriptServer, scriptname, args, ctx.workerScript, runOpts);
}, },
exec: exec:
(ctx) => (ctx) =>
(_scriptname, _hostname, _threads = 1, ..._args) => { (_scriptname, _hostname, _thread_or_opt = 1, ..._args) => {
const scriptname = helpers.string(ctx, "scriptname", _scriptname); const scriptname = helpers.string(ctx, "scriptname", _scriptname);
const hostname = helpers.string(ctx, "hostname", _hostname); const hostname = helpers.string(ctx, "hostname", _hostname);
const threads = helpers.positiveInteger(ctx, "threads", _threads); const runOpts = helpers.runOptions(ctx, _thread_or_opt);
const args = helpers.scriptArgs(ctx, _args); const args = helpers.scriptArgs(ctx, _args);
const server = helpers.getServer(ctx, hostname); const server = helpers.getServer(ctx, hostname);
return runScriptFromScript("exec", server, scriptname, args, ctx.workerScript, threads); return runScriptFromScript("exec", server, scriptname, args, ctx.workerScript, runOpts);
}, },
spawn: spawn:
(ctx) => (ctx) =>
(_scriptname, _threads = 1, ..._args) => { (_scriptname, _thread_or_opt = 1, ..._args) => {
const scriptname = helpers.string(ctx, "scriptname", _scriptname); const scriptname = helpers.string(ctx, "scriptname", _scriptname);
const threads = helpers.positiveInteger(ctx, "threads", _threads); const runOpts = helpers.runOptions(ctx, _thread_or_opt);
const args = helpers.scriptArgs(ctx, _args); const args = helpers.scriptArgs(ctx, _args);
const spawnDelay = 10; const spawnDelay = 10;
setTimeout(() => { setTimeout(() => {
@ -722,7 +722,7 @@ export const ns: InternalAPI<NSFull> = {
throw helpers.makeRuntimeErrorMsg(ctx, "Could not find server. This is a bug. Report to dev"); throw helpers.makeRuntimeErrorMsg(ctx, "Could not find server. This is a bug. Report to dev");
} }
return runScriptFromScript("spawn", scriptServer, scriptname, args, ctx.workerScript, threads); return runScriptFromScript("spawn", scriptServer, scriptname, args, ctx.workerScript, runOpts);
}, spawnDelay * 1e3); }, spawnDelay * 1e3);
helpers.log(ctx, () => `Will execute '${scriptname}' in ${spawnDelay} seconds`); helpers.log(ctx, () => `Will execute '${scriptname}' in ${spawnDelay} seconds`);
@ -943,6 +943,7 @@ export const ns: InternalAPI<NSFull> = {
threads: script.threads, threads: script.threads,
args: script.args.slice(), args: script.args.slice(),
pid: script.pid, pid: script.pid,
temporary: script.temporary,
}); });
} }
return processes; return processes;

@ -32,8 +32,7 @@ import { simple as walksimple } from "acorn-walk";
import { areFilesEqual } from "./Terminal/DirectoryHelpers"; import { areFilesEqual } from "./Terminal/DirectoryHelpers";
import { Terminal } from "./Terminal"; import { Terminal } from "./Terminal";
import { ScriptArg } from "@nsdefs"; import { ScriptArg } from "@nsdefs";
import { handleUnknownError } from "./Netscript/NetscriptHelpers"; import { handleUnknownError, CompleteRunOptions } from "./Netscript/NetscriptHelpers";
import { PositiveInteger } from "./types";
export const NetscriptPorts: Map<PortNumber, Port> = new Map(); export const NetscriptPorts: Map<PortNumber, Port> = new Map();
@ -402,7 +401,7 @@ export function runScriptFromScript(
scriptname: string, scriptname: string,
args: ScriptArg[], args: ScriptArg[],
workerScript: WorkerScript, workerScript: WorkerScript,
threads = 1 as PositiveInteger, runOpts: CompleteRunOptions,
): number { ): number {
/* Very inefficient, TODO change data structures so that finding script & checking if it's already running does /* Very inefficient, TODO change data structures so that finding script & checking if it's already running does
* not require iterating through all scripts and comparing names/args. This is a big part of slowdown when * not require iterating through all scripts and comparing names/args. This is a big part of slowdown when
@ -434,7 +433,7 @@ export function runScriptFromScript(
} }
// Calculate ram usage including thread count // Calculate ram usage including thread count
const ramUsage = singleRamUsage * threads; const ramUsage = singleRamUsage * runOpts.threads;
// Check if there is enough ram to run the script, fail if not. // Check if there is enough ram to run the script, fail if not.
const ramAvailable = host.maxRam - host.ramUsed; const ramAvailable = host.maxRam - host.ramUsed;
@ -442,17 +441,18 @@ export function runScriptFromScript(
workerScript.log( workerScript.log(
caller, caller,
() => () =>
`Cannot run script '${scriptname}' (t=${threads}) on '${host.hostname}' because there is not enough available RAM!`, `Cannot run script '${scriptname}' (t=${runOpts.threads}) on '${host.hostname}' because there is not enough available RAM!`,
); );
return 0; return 0;
} }
// Able to run script // Able to run script
workerScript.log( workerScript.log(
caller, caller,
() => `'${scriptname}' on '${host.hostname}' with ${threads} threads and args: ${arrayToString(args)}.`, () => `'${scriptname}' on '${host.hostname}' with ${runOpts.threads} threads and args: ${arrayToString(args)}.`,
); );
const runningScriptObj = new RunningScript(script, singleRamUsage, args); const runningScriptObj = new RunningScript(script, singleRamUsage, args);
runningScriptObj.threads = threads; runningScriptObj.threads = runOpts.threads;
runningScriptObj.temporary = runOpts.temporary;
return startWorkerScript(runningScriptObj, host, workerScript); return startWorkerScript(runningScriptObj, host, workerScript);
} }

@ -62,6 +62,9 @@ export class RunningScript {
// Number of threads that this script is running with // Number of threads that this script is running with
threads = 1 as PositiveInteger; threads = 1 as PositiveInteger;
// Whether this RunningScript is excluded from saves
temporary = false;
// Script urls for the current running script for translating urls back to file names in errors // Script urls for the current running script for translating urls back to file names in errors
dependencies: ScriptUrl[] = []; dependencies: ScriptUrl[] = [];

@ -193,6 +193,16 @@ interface RunningScript {
server: string; server: string;
/** Number of threads that this script runs with */ /** Number of threads that this script runs with */
threads: number; threads: number;
/** Whether this RunningScript is excluded from saves */
temporary: boolean;
}
/** @public */
interface RunOptions {
/** Number of threads that the script will run with, defaults to 1 */
threads?: number;
/** Whether this script is excluded from saves, defaults to false */
temporary?: boolean;
} }
/** @public */ /** @public */
@ -324,6 +334,8 @@ interface ProcessInfo {
args: (string | number | boolean)[]; args: (string | number | boolean)[];
/** Process ID */ /** Process ID */
pid: number; pid: number;
/** Whether this process is excluded from saves */
temporary: boolean;
} }
/** /**
@ -5395,13 +5407,16 @@ export interface NS {
* current server (the server running the script that calls this function). Requires a significant * current server (the server running the script that calls this function). Requires a significant
* amount of RAM to run this command. * amount of RAM to run this command.
* *
* The second argument is either a thread count, or a {@link RunOptions} object that can also
* specify the number of threads (among other things).
*
* If the script was successfully started, then this functions returns the PID of that script. * If the script was successfully started, then this functions returns the PID of that script.
* Otherwise, it returns 0. * Otherwise, it returns 0.
* *
* PID stands for Process ID. The PID is a unique identifier for each script. * PID stands for Process ID. The PID is a unique identifier for each script.
* The PID will always be a positive integer. * The PID will always be a positive integer.
* *
* Running this function with a numThreads argument of 0 or less will cause a runtime error. * Running this function with 0 or fewer threads will cause a runtime error.
* *
* @example * @example
* ```js * ```js
@ -5409,17 +5424,17 @@ export interface NS {
* ns.run("foo.js"); * ns.run("foo.js");
* *
* //The following example will run foo.js but with 5 threads instead of single-threaded: * //The following example will run foo.js but with 5 threads instead of single-threaded:
* ns.run("foo.js", 5); * ns.run("foo.js", {threads: 5});
* *
* //This next example will run foo.js single-threaded, and will pass the string foodnstuff into the script as an argument: * //This next example will run foo.js single-threaded, and will pass the string foodnstuff into the script as an argument:
* ns.run("foo.js", 1, 'foodnstuff'); * ns.run("foo.js", 1, 'foodnstuff');
* ``` * ```
* @param script - Filename of script to run. * @param script - Filename of script to run.
* @param numThreads - Integer number of threads for new script. Defaults to 1. * @param threadOrOptions - Either an integer number of threads for new script, or a {@link RunOptions} object. Threads defaults to 1.
* @param args - Additional arguments to pass into the new script that is being run. Note that if any arguments are being passed into the new script, then the second argument numThreads must be filled in with a value. * @param args - Additional arguments to pass into the new script that is being run. Note that if any arguments are being passed into the new script, then the second argument numThreads must be filled in with a value.
* @returns Returns the PID of a successfully started script, and 0 otherwise. * @returns Returns the PID of a successfully started script, and 0 otherwise.
*/ */
run(script: string, numThreads?: number, ...args: (string | number | boolean)[]): number; run(script: string, threadOrOptions?: number | RunOptions, ...args: (string | number | boolean)[]): number;
/** /**
* Start another script on any server. * Start another script on any server.
@ -5435,7 +5450,7 @@ export interface NS {
* PID stands for Process ID. The PID is a unique identifier for each script. * PID stands for Process ID. The PID is a unique identifier for each script.
* The PID will always be a positive integer. * The PID will always be a positive integer.
* *
* Running this function with a numThreads argument of 0 or less will cause a runtime error. * Running this function with 0 or fewer threads will cause a runtime error.
* *
* @example * @example
* ```js * ```js
@ -5446,7 +5461,7 @@ export interface NS {
* *
* // The following example will try to run the script generic-hack.js on the * // The following example will try to run the script generic-hack.js on the
* // joesguns server with 10 threads. * // joesguns server with 10 threads.
* ns.exec("generic-hack.js", "joesguns", 10); * ns.exec("generic-hack.js", "joesguns", {threads: 10});
* *
* // This last example will try to run the script foo.js on the foodnstuff server * // This last example will try to run the script foo.js on the foodnstuff server
* // with 5 threads. It will also pass the number 1 and the string “test” in as * // with 5 threads. It will also pass the number 1 and the string “test” in as
@ -5455,11 +5470,16 @@ export interface NS {
* ``` * ```
* @param script - Filename of script to execute. * @param script - Filename of script to execute.
* @param hostname - Hostname of the `target server` on which to execute the script. * @param hostname - Hostname of the `target server` on which to execute the script.
* @param numThreads - Integer number of threads for new script. Defaults to 1. * @param threadOrOptions - Either an integer number of threads for new script, or a {@link RunOptions} object. Threads defaults to 1.
* @param args - Additional arguments to pass into the new script that is being run. Note that if any arguments are being passed into the new script, then the third argument numThreads must be filled in with a value. * @param args - Additional arguments to pass into the new script that is being run. Note that if any arguments are being passed into the new script, then the third argument numThreads must be filled in with a value.
* @returns Returns the PID of a successfully started script, and 0 otherwise. * @returns Returns the PID of a successfully started script, and 0 otherwise.
*/ */
exec(script: string, hostname: string, numThreads?: number, ...args: (string | number | boolean)[]): number; exec(
script: string,
hostname: string,
threadOrOptions?: number | RunOptions,
...args: (string | number | boolean)[]
): number;
/** /**
* Terminate current script and start another in 10 seconds. * Terminate current script and start another in 10 seconds.
@ -5473,7 +5493,7 @@ export interface NS {
* *
* Because this function immediately terminates the script, it does not have a return value. * Because this function immediately terminates the script, it does not have a return value.
* *
* Running this function with a numThreads argument of 0 or less will cause a runtime error. * Running this function with 0 or fewer threads will cause a runtime error.
* *
* @example * @example
* ```js * ```js
@ -5481,10 +5501,10 @@ export interface NS {
* ns.spawn('foo.js', 10, 'foodnstuff', 90); * ns.spawn('foo.js', 10, 'foodnstuff', 90);
* ``` * ```
* @param script - Filename of script to execute. * @param script - Filename of script to execute.
* @param numThreads - Integer number of threads for new script. Defaults to 1. * @param threadOrOptions - Either an integer number of threads for new script, or a {@link RunOptions} object. Threads defaults to 1.
* @param args - Additional arguments to pass into the new script that is being run. * @param args - Additional arguments to pass into the new script that is being run.
*/ */
spawn(script: string, numThreads?: number, ...args: (string | number | boolean)[]): void; spawn(script: string, threadOrOptions?: number | RunOptions, ...args: (string | number | boolean)[]): void;
/** /**
* Terminate the script with the provided PID. * Terminate the script with the provided PID.

@ -12,6 +12,8 @@ import { isValidIPAddress } from "../utils/helpers/isValidIPAddress";
import { SpecialServers } from "./data/SpecialServers"; import { SpecialServers } from "./data/SpecialServers";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import type { RunningScript } from "../Script/RunningScript";
/** /**
* Map of all Servers that exist in the game * Map of all Servers that exist in the game
* Key (string) = IP * Key (string) = IP
@ -206,10 +208,17 @@ function excludeReplacer(key: string, value: any): any {
return value; return value;
} }
function scriptFilter(script: RunningScript): boolean {
return !script.temporary;
}
function includeReplacer(key: string, value: any): any { function includeReplacer(key: string, value: any): any {
if (key === "logs") { if (key === "logs") {
return []; return [];
} }
if (key === "runningScripts") {
return value.filter(scriptFilter);
}
return value; return value;
} }

168
test/jest/Save.test.ts Normal file

@ -0,0 +1,168 @@
import "../../src/Player";
import { loadAllServers, saveAllServers } from "../../src/Server/AllServers";
// Direct tests of loading and saving.
// Tests here should try to be comprehensive (cover as much stuff as possible)
// without requiring burdensome levels of maintenance when legitimate changes
// are made.
// Get a stable clock so we don't have time-based diffs.
function freezeTime() {
// You're about to hack time, are you sure? YES/NO
const RealDate = Date;
Date = function () {
return new RealDate(1678834800000);
};
Date.now = () => 1678834800000;
}
// Savegame generated from dev on 2023-03-12, mostly empty game with a few
// tweaks. A RunningScript was added in-game to test the one bit of
// non-trivial machinery involved in save/load.
//
// Most of the Servers have been removed to reduce space. Default values have
// been removed both for space, and to test that they are added correctly.
function loadStandardServers() {
loadAllServers(String.raw`{
"home": {
"ctor": "Server",
"data": {
"hasAdminRights": true,
"hostname": "home",
"ip": "67.4.8.1",
"isConnectedTo": true,
"maxRam": 8,
"messages": [
"hackers-starting-handbook.lit"
],
"organizationName": "Home PC",
"programs": [
"NUKE.exe"
],
"ramUsed": 1.6,
"runningScripts": [
{
"ctor": "RunningScript",
"data": {
"args": [],
"filename": "script.js",
"logs": [
"I shouldn't even be saved, since I'm temporary"
],
"logUpd": true,
"offlineRunningTime": 0.01,
"onlineRunningTime": 7.210000000000004,
"pid": 3,
"ramUsage": 1.6,
"server": "home",
"temporary": true,
"dependencies": [
{
"filename": "script.js",
"url": "blob:http://localhost/302fe9e5-2ec3-4ed7-bb5a-4f8f4a85f46d",
"moduleSequenceNumber": 2
}
]
}
},
{
"ctor": "RunningScript",
"data": {
"args": [],
"filename": "script.js",
"logs": [
"I'm a log line that should be pruned",
"Another log line"
],
"logUpd": true,
"offlineRunningTime": 0.01,
"onlineRunningTime": 7.210000000000004,
"pid": 2,
"ramUsage": 1.6,
"server": "home",
"dependencies": [
{
"filename": "script.js",
"url": "blob:http://localhost/302fe9e5-2ec3-4ed7-bb5a-4f8f4a85f46d",
"moduleSequenceNumber": 2
}
]
}
}
],
"scripts": [
{
"ctor": "Script",
"data": {
"code": "/** @param {NS} ns */\\nexport async function main(ns) {\\n return ns.asleep(1000000);\\n}",
"filename": "script.js",
"module": {},
"dependencies": [
{
"filename": "script.js",
"url": "blob:http://localhost/e0abfafd-2c73-42fc-9eea-288c03820c47",
"moduleSequenceNumber": 5
}
],
"ramUsage": 1.6,
"server": "home",
"moduleSequenceNumber": 5,
"ramUsageEntries": [
{
"type": "misc",
"name": "baseCost",
"cost": 1.6
}
]
}
}
],
"serversOnNetwork": [
"n00dles"
],
"purchasedByPlayer": true
}
},
"n00dles": {
"ctor": "Server",
"data": {
"hostname": "n00dles",
"ip": "61.6.6.2",
"maxRam": 4,
"organizationName": "Noodle Bar",
"serversOnNetwork": [
"home"
],
"moneyAvailable": 70000,
"moneyMax": 1750000,
"numOpenPortsRequired": 0,
"serverGrowth": 3000
}
}
}`); // Fix confused highlighting `
}
test("load/saveAllServers", () => {
// Feed a JSON object through loadAllServers/saveAllServers.
// The object is a pruned set of servers that was extracted from a real (dev) game.
freezeTime();
loadStandardServers();
// Re-stringify with indenting for nicer diffs
const result = saveAllServers(/*excludeRunningScripts=*/ false);
expect(JSON.stringify(JSON.parse(result), null, 2)).toMatchSnapshot();
});
test("load/saveAllServers pruning RunningScripts", () => {
// Feed a JSON object through loadAllServers/saveAllServers.
// The object is a pruned set of servers that was extracted from a real (dev) game.
freezeTime();
loadStandardServers();
// Re-stringify with indenting for nicer diffs
const result = saveAllServers(/*excludeRunningScripts=*/ true);
expect(JSON.stringify(JSON.parse(result), null, 2)).toMatchSnapshot();
});

@ -0,0 +1,258 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`load/saveAllServers 1`] = `
"{
"home": {
"ctor": "Server",
"data": {
"contracts": [],
"cpuCores": 1,
"ftpPortOpen": false,
"hasAdminRights": true,
"hostname": "home",
"httpPortOpen": false,
"ip": "67.4.8.1",
"isConnectedTo": true,
"maxRam": 8,
"messages": [
"hackers-starting-handbook.lit"
],
"organizationName": "Home PC",
"programs": [
"NUKE.exe"
],
"ramUsed": 1.6,
"runningScripts": [
{
"ctor": "RunningScript",
"data": {
"args": [],
"dataMap": {},
"filename": "script.js",
"logs": [],
"logUpd": true,
"offlineExpGained": 0,
"offlineMoneyMade": 0,
"offlineRunningTime": 0.01,
"onlineExpGained": 0,
"onlineMoneyMade": 0,
"onlineRunningTime": 7.210000000000004,
"pid": 2,
"ramUsage": 1.6,
"server": "home",
"threads": 1,
"temporary": false,
"dependencies": [
{
"filename": "script.js",
"url": "blob:http://localhost/302fe9e5-2ec3-4ed7-bb5a-4f8f4a85f46d",
"moduleSequenceNumber": 2
}
]
}
}
],
"scripts": [
{
"ctor": "Script",
"data": {
"code": "/** @param {NS} ns */\\\\nexport async function main(ns) {\\\\n return ns.asleep(1000000);\\\\n}",
"filename": "script.js",
"url": "",
"module": {},
"dependencies": [
{
"filename": "script.js",
"url": "blob:http://localhost/e0abfafd-2c73-42fc-9eea-288c03820c47",
"moduleSequenceNumber": 5
}
],
"dependents": [],
"ramUsage": 1.6,
"queueCompile": false,
"server": "home",
"moduleSequenceNumber": 5,
"ramUsageEntries": [
{
"type": "misc",
"name": "baseCost",
"cost": 1.6
}
]
}
}
],
"serversOnNetwork": [
"n00dles"
],
"smtpPortOpen": false,
"sqlPortOpen": false,
"sshPortOpen": false,
"textFiles": [],
"purchasedByPlayer": true,
"backdoorInstalled": false,
"baseDifficulty": 1,
"hackDifficulty": 1,
"minDifficulty": 1,
"moneyAvailable": 0,
"moneyMax": 0,
"numOpenPortsRequired": 5,
"openPortCount": 0,
"requiredHackingSkill": 1,
"serverGrowth": 1
}
},
"n00dles": {
"ctor": "Server",
"data": {
"contracts": [],
"cpuCores": 1,
"ftpPortOpen": false,
"hasAdminRights": false,
"hostname": "n00dles",
"httpPortOpen": false,
"ip": "61.6.6.2",
"isConnectedTo": false,
"maxRam": 4,
"messages": [],
"organizationName": "Noodle Bar",
"programs": [],
"ramUsed": 0,
"runningScripts": [],
"scripts": [],
"serversOnNetwork": [
"home"
],
"smtpPortOpen": false,
"sqlPortOpen": false,
"sshPortOpen": false,
"textFiles": [],
"purchasedByPlayer": false,
"backdoorInstalled": false,
"baseDifficulty": 1,
"hackDifficulty": 1,
"minDifficulty": 1,
"moneyAvailable": 70000,
"moneyMax": 1750000,
"numOpenPortsRequired": 0,
"openPortCount": 0,
"requiredHackingSkill": 1,
"serverGrowth": 3000
}
}
}"
`;
exports[`load/saveAllServers pruning RunningScripts 1`] = `
"{
"home": {
"ctor": "Server",
"data": {
"contracts": [],
"cpuCores": 1,
"ftpPortOpen": false,
"hasAdminRights": true,
"hostname": "home",
"httpPortOpen": false,
"ip": "67.4.8.1",
"isConnectedTo": true,
"maxRam": 8,
"messages": [
"hackers-starting-handbook.lit"
],
"organizationName": "Home PC",
"programs": [
"NUKE.exe"
],
"ramUsed": 1.6,
"runningScripts": [],
"scripts": [
{
"ctor": "Script",
"data": {
"code": "/** @param {NS} ns */\\\\nexport async function main(ns) {\\\\n return ns.asleep(1000000);\\\\n}",
"filename": "script.js",
"url": "",
"module": {},
"dependencies": [
{
"filename": "script.js",
"url": "blob:http://localhost/e0abfafd-2c73-42fc-9eea-288c03820c47",
"moduleSequenceNumber": 5
}
],
"dependents": [],
"ramUsage": 1.6,
"queueCompile": false,
"server": "home",
"moduleSequenceNumber": 5,
"ramUsageEntries": [
{
"type": "misc",
"name": "baseCost",
"cost": 1.6
}
]
}
}
],
"serversOnNetwork": [
"n00dles"
],
"smtpPortOpen": false,
"sqlPortOpen": false,
"sshPortOpen": false,
"textFiles": [],
"purchasedByPlayer": true,
"backdoorInstalled": false,
"baseDifficulty": 1,
"hackDifficulty": 1,
"minDifficulty": 1,
"moneyAvailable": 0,
"moneyMax": 0,
"numOpenPortsRequired": 5,
"openPortCount": 0,
"requiredHackingSkill": 1,
"serverGrowth": 1
}
},
"n00dles": {
"ctor": "Server",
"data": {
"contracts": [],
"cpuCores": 1,
"ftpPortOpen": false,
"hasAdminRights": false,
"hostname": "n00dles",
"httpPortOpen": false,
"ip": "61.6.6.2",
"isConnectedTo": false,
"maxRam": 4,
"messages": [],
"organizationName": "Noodle Bar",
"programs": [],
"ramUsed": 0,
"runningScripts": [],
"scripts": [],
"serversOnNetwork": [
"home"
],
"smtpPortOpen": false,
"sqlPortOpen": false,
"sshPortOpen": false,
"textFiles": [],
"purchasedByPlayer": false,
"backdoorInstalled": false,
"baseDifficulty": 1,
"hackDifficulty": 1,
"minDifficulty": 1,
"moneyAvailable": 70000,
"moneyMax": 1750000,
"numOpenPortsRequired": 0,
"openPortCount": 0,
"requiredHackingSkill": 1,
"serverGrowth": 3000
}
}
}"
`;