From 98f7f473b4cdb466d03bf4b0edf0499dbb735a2d Mon Sep 17 00:00:00 2001 From: David Walker Date: Tue, 21 Mar 2023 15:54:49 -0700 Subject: [PATCH] NETSCRIPT: Add "temporary" as a RunOption to run/exec/spawn (#432) --- markdown/bitburner.md | 1 + markdown/bitburner.ns.exec.md | 13 +- markdown/bitburner.ns.hacknet.md | 2 +- markdown/bitburner.ns.md | 8 +- markdown/bitburner.ns.run.md | 10 +- markdown/bitburner.ns.share.md | 2 +- markdown/bitburner.ns.spawn.md | 6 +- markdown/bitburner.processinfo.md | 1 + markdown/bitburner.processinfo.temporary.md | 13 + markdown/bitburner.runningscript.md | 1 + markdown/bitburner.runningscript.temporary.md | 13 + markdown/bitburner.runoptions.md | 20 ++ markdown/bitburner.runoptions.temporary.md | 13 + markdown/bitburner.runoptions.threads.md | 13 + src/Netscript/NetscriptHelpers.ts | 27 +- src/NetscriptFunctions.ts | 19 +- src/NetscriptWorker.ts | 14 +- src/Script/RunningScript.ts | 3 + src/ScriptEditor/NetscriptDefinitions.d.ts | 42 ++- src/Server/AllServers.ts | 9 + test/jest/Save.test.ts | 168 ++++++++++++ test/jest/__snapshots__/Save.test.ts.snap | 258 ++++++++++++++++++ 22 files changed, 611 insertions(+), 45 deletions(-) create mode 100644 markdown/bitburner.processinfo.temporary.md create mode 100644 markdown/bitburner.runningscript.temporary.md create mode 100644 markdown/bitburner.runoptions.md create mode 100644 markdown/bitburner.runoptions.temporary.md create mode 100644 markdown/bitburner.runoptions.threads.md create mode 100644 test/jest/Save.test.ts create mode 100644 test/jest/__snapshots__/Save.test.ts.snap diff --git a/markdown/bitburner.md b/markdown/bitburner.md index 1a87e4e43..e11c85677 100644 --- a/markdown/bitburner.md +++ b/markdown/bitburner.md @@ -86,6 +86,7 @@ | [RecentScript](./bitburner.recentscript.md) | | | [ReputationFormulas](./bitburner.reputationformulas.md) | Reputation formulas | | [RunningScript](./bitburner.runningscript.md) | | +| [RunOptions](./bitburner.runoptions.md) | | | [Server](./bitburner.server.md) | A single server. | | [Singularity](./bitburner.singularity.md) | Singularity API | | [Skills](./bitburner.skills.md) | | diff --git a/markdown/bitburner.ns.exec.md b/markdown/bitburner.ns.exec.md index 6aef80bb0..3b8582779 100644 --- a/markdown/bitburner.ns.exec.md +++ b/markdown/bitburner.ns.exec.md @@ -9,7 +9,12 @@ Start another script on any server. **Signature:** ```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 @@ -18,7 +23,7 @@ exec(script: string, hostname: string, numThreads?: number, ...args: (string | n | --- | --- | --- | | script | string | Filename of script to execute. | | hostname | string | Hostname of the target server 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. | **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. -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 @@ -50,7 +55,7 @@ ns.exec("generic-hack.js", "foodnstuff"); // The following example will try to run the script generic-hack.js on the // 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 // with 5 threads. It will also pass the number 1 and the string “test” in as diff --git a/markdown/bitburner.ns.hacknet.md b/markdown/bitburner.ns.hacknet.md index 12e54852a..3dc6a310f 100644 --- a/markdown/bitburner.ns.hacknet.md +++ b/markdown/bitburner.ns.hacknet.md @@ -14,5 +14,5 @@ readonly hacknet: Hacknet; ## Remarks -RAM cost: 4 GB +RAM cost: 4 GB. diff --git a/markdown/bitburner.ns.md b/markdown/bitburner.ns.md index e1406ef29..9d8c79bee 100644 --- a/markdown/bitburner.ns.md +++ b/markdown/bitburner.ns.md @@ -72,7 +72,7 @@ export async function main(ns) { | [deleteServer(host)](./bitburner.ns.deleteserver.md) | Delete a purchased server. | | [disableLog(fn)](./bitburner.ns.disablelog.md) | Disables logging for the given 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. | | [fileExists(filename, host)](./bitburner.ns.fileexists.md) | Check if a file exists. | | [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. | | [resizeTail(width, height, pid)](./bitburner.ns.resizetail.md) | Resize a tail window. | | [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. | | [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. | | [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. | -| [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. | -| [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. | | [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. | diff --git a/markdown/bitburner.ns.run.md b/markdown/bitburner.ns.run.md index 30124a946..0aa0a39fb 100644 --- a/markdown/bitburner.ns.run.md +++ b/markdown/bitburner.ns.run.md @@ -9,7 +9,7 @@ Start another script on the current server. **Signature:** ```typescript -run(script: string, numThreads?: number, ...args: (string | number | boolean)[]): number; +run(script: string, threadOrOptions?: number | RunOptions, ...args: (string | number | boolean)[]): number; ``` ## Parameters @@ -17,7 +17,7 @@ run(script: string, numThreads?: number, ...args: (string | number | boolean)[]) | Parameter | Type | Description | | --- | --- | --- | | 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. | **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. +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. 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 @@ -46,7 +48,7 @@ Running this function with a numThreads argument of 0 or less will cause a runti ns.run("foo.js"); //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: ns.run("foo.js", 1, 'foodnstuff'); diff --git a/markdown/bitburner.ns.share.md b/markdown/bitburner.ns.share.md index b308966f5..2fda9c188 100644 --- a/markdown/bitburner.ns.share.md +++ b/markdown/bitburner.ns.share.md @@ -4,7 +4,7 @@ ## NS.share() method -Share your computer with your factions. +Share the server's ram with your factions. **Signature:** diff --git a/markdown/bitburner.ns.spawn.md b/markdown/bitburner.ns.spawn.md index aaac04b3f..4566c2833 100644 --- a/markdown/bitburner.ns.spawn.md +++ b/markdown/bitburner.ns.spawn.md @@ -9,7 +9,7 @@ Terminate current script and start another in 10 seconds. **Signature:** ```typescript -spawn(script: string, numThreads?: number, ...args: (string | number | boolean)[]): void; +spawn(script: string, threadOrOptions?: number | RunOptions, ...args: (string | number | boolean)[]): void; ``` ## Parameters @@ -17,7 +17,7 @@ spawn(script: string, numThreads?: number, ...args: (string | number | boolean)[ | Parameter | Type | Description | | --- | --- | --- | | 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. | **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. -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 diff --git a/markdown/bitburner.processinfo.md b/markdown/bitburner.processinfo.md index cd4460b1c..08721b217 100644 --- a/markdown/bitburner.processinfo.md +++ b/markdown/bitburner.processinfo.md @@ -19,5 +19,6 @@ interface ProcessInfo | [args](./bitburner.processinfo.args.md) | | (string \| number \| boolean)\[\] | Script's arguments | | [filename](./bitburner.processinfo.filename.md) | | string | Script name. | | [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 | diff --git a/markdown/bitburner.processinfo.temporary.md b/markdown/bitburner.processinfo.temporary.md new file mode 100644 index 000000000..8cd36f096 --- /dev/null +++ b/markdown/bitburner.processinfo.temporary.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [ProcessInfo](./bitburner.processinfo.md) > [temporary](./bitburner.processinfo.temporary.md) + +## ProcessInfo.temporary property + +Whether this process is excluded from saves + +**Signature:** + +```typescript +temporary: boolean; +``` diff --git a/markdown/bitburner.runningscript.md b/markdown/bitburner.runningscript.md index 5f4d27dd9..adbaf477a 100644 --- a/markdown/bitburner.runningscript.md +++ b/markdown/bitburner.runningscript.md @@ -27,5 +27,6 @@ interface RunningScript | [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 | | [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 | diff --git a/markdown/bitburner.runningscript.temporary.md b/markdown/bitburner.runningscript.temporary.md new file mode 100644 index 000000000..7d86e69fe --- /dev/null +++ b/markdown/bitburner.runningscript.temporary.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [RunningScript](./bitburner.runningscript.md) > [temporary](./bitburner.runningscript.temporary.md) + +## RunningScript.temporary property + +Whether this RunningScript is excluded from saves + +**Signature:** + +```typescript +temporary: boolean; +``` diff --git a/markdown/bitburner.runoptions.md b/markdown/bitburner.runoptions.md new file mode 100644 index 000000000..3a8e3e9c3 --- /dev/null +++ b/markdown/bitburner.runoptions.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [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 | + diff --git a/markdown/bitburner.runoptions.temporary.md b/markdown/bitburner.runoptions.temporary.md new file mode 100644 index 000000000..61d3bc8ca --- /dev/null +++ b/markdown/bitburner.runoptions.temporary.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [RunOptions](./bitburner.runoptions.md) > [temporary](./bitburner.runoptions.temporary.md) + +## RunOptions.temporary property + +Whether this script is excluded from saves, defaults to false + +**Signature:** + +```typescript +temporary?: boolean; +``` diff --git a/markdown/bitburner.runoptions.threads.md b/markdown/bitburner.runoptions.threads.md new file mode 100644 index 000000000..259511484 --- /dev/null +++ b/markdown/bitburner.runoptions.threads.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [RunOptions](./bitburner.runoptions.md) > [threads](./bitburner.runoptions.threads.md) + +## RunOptions.threads property + +Number of threads that the script will run with, defaults to 1 + +**Signature:** + +```typescript +threads?: number; +``` diff --git a/src/Netscript/NetscriptHelpers.ts b/src/Netscript/NetscriptHelpers.ts index be877c9da..090aa6c98 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, 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 { calculateHackingChance, @@ -41,6 +41,7 @@ export const helpers = { number, positiveInteger, scriptArgs, + runOptions, argsToString, makeBasicErrorMsg, makeRuntimeErrorMsg, @@ -67,6 +68,12 @@ export const helpers = { failOnHacknetServer, }; +// RunOptions with non-optional, type-validated members, for passing between internal functions. +export interface CompleteRunOptions { + threads: PositiveInteger; + temporary: boolean; +} + export function assertMember( ctx: NetscriptContext, obj: Record | T[], @@ -174,6 +181,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; + } + return { + threads: positiveInteger(ctx, "thread", threads), + temporary: !!temporary, + }; +} + /** Convert multiple arguments for tprint or print into a single string. */ function argsToString(args: unknown[]): string { let out = ""; @@ -718,6 +742,7 @@ function createPublicRunningScript(runningScript: RunningScript): IRunningScript ramUsage: runningScript.ramUsage, server: runningScript.server, threads: runningScript.threads, + temporary: runningScript.temporary, }; } diff --git a/src/NetscriptFunctions.ts b/src/NetscriptFunctions.ts index ab92731cd..5eda261ef 100644 --- a/src/NetscriptFunctions.ts +++ b/src/NetscriptFunctions.ts @@ -688,32 +688,32 @@ export const ns: InternalAPI = { }, run: (ctx) => - (_scriptname, _threads = 1, ..._args) => { + (_scriptname, _thread_or_opt = 1, ..._args) => { 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 scriptServer = GetServer(ctx.workerScript.hostname); if (scriptServer == null) { 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: (ctx) => - (_scriptname, _hostname, _threads = 1, ..._args) => { + (_scriptname, _hostname, _thread_or_opt = 1, ..._args) => { const scriptname = helpers.string(ctx, "scriptname", _scriptname); 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 server = helpers.getServer(ctx, hostname); - return runScriptFromScript("exec", server, scriptname, args, ctx.workerScript, threads); + return runScriptFromScript("exec", server, scriptname, args, ctx.workerScript, runOpts); }, spawn: (ctx) => - (_scriptname, _threads = 1, ..._args) => { + (_scriptname, _thread_or_opt = 1, ..._args) => { 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 spawnDelay = 10; setTimeout(() => { @@ -722,7 +722,7 @@ export const ns: InternalAPI = { 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); helpers.log(ctx, () => `Will execute '${scriptname}' in ${spawnDelay} seconds`); @@ -943,6 +943,7 @@ export const ns: InternalAPI = { threads: script.threads, args: script.args.slice(), pid: script.pid, + temporary: script.temporary, }); } return processes; diff --git a/src/NetscriptWorker.ts b/src/NetscriptWorker.ts index 9bba386f1..e93dbda92 100644 --- a/src/NetscriptWorker.ts +++ b/src/NetscriptWorker.ts @@ -32,8 +32,7 @@ import { simple as walksimple } from "acorn-walk"; import { areFilesEqual } from "./Terminal/DirectoryHelpers"; import { Terminal } from "./Terminal"; import { ScriptArg } from "@nsdefs"; -import { handleUnknownError } from "./Netscript/NetscriptHelpers"; -import { PositiveInteger } from "./types"; +import { handleUnknownError, CompleteRunOptions } from "./Netscript/NetscriptHelpers"; export const NetscriptPorts: Map = new Map(); @@ -402,7 +401,7 @@ export function runScriptFromScript( scriptname: string, args: ScriptArg[], workerScript: WorkerScript, - threads = 1 as PositiveInteger, + runOpts: CompleteRunOptions, ): number { /* 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 @@ -434,7 +433,7 @@ export function runScriptFromScript( } // 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. const ramAvailable = host.maxRam - host.ramUsed; @@ -442,17 +441,18 @@ export function runScriptFromScript( workerScript.log( 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; } // Able to run script workerScript.log( 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); - runningScriptObj.threads = threads; + runningScriptObj.threads = runOpts.threads; + runningScriptObj.temporary = runOpts.temporary; return startWorkerScript(runningScriptObj, host, workerScript); } diff --git a/src/Script/RunningScript.ts b/src/Script/RunningScript.ts index ad6e3a672..d7977348b 100644 --- a/src/Script/RunningScript.ts +++ b/src/Script/RunningScript.ts @@ -62,6 +62,9 @@ export class RunningScript { // Number of threads that this script is running with 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 dependencies: ScriptUrl[] = []; diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts index 9700af427..ee9ca1f35 100644 --- a/src/ScriptEditor/NetscriptDefinitions.d.ts +++ b/src/ScriptEditor/NetscriptDefinitions.d.ts @@ -193,6 +193,16 @@ interface RunningScript { server: string; /** Number of threads that this script runs with */ 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 */ @@ -324,6 +334,8 @@ interface ProcessInfo { args: (string | number | boolean)[]; /** Process ID */ 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 * 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. * 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. * - * 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 * ```js @@ -5409,17 +5424,17 @@ export interface NS { * ns.run("foo.js"); * * //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: * ns.run("foo.js", 1, 'foodnstuff'); * ``` * @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. * @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. @@ -5435,7 +5450,7 @@ export interface NS { * 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 * ```js @@ -5446,7 +5461,7 @@ export interface NS { * * // The following example will try to run the script generic-hack.js on the * // 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 * // 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 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. * @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. @@ -5473,7 +5493,7 @@ export interface NS { * * 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 * ```js @@ -5481,10 +5501,10 @@ export interface NS { * ns.spawn('foo.js', 10, 'foodnstuff', 90); * ``` * @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. */ - 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. diff --git a/src/Server/AllServers.ts b/src/Server/AllServers.ts index ae3d27484..9b8029511 100644 --- a/src/Server/AllServers.ts +++ b/src/Server/AllServers.ts @@ -12,6 +12,8 @@ import { isValidIPAddress } from "../utils/helpers/isValidIPAddress"; import { SpecialServers } from "./data/SpecialServers"; import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; +import type { RunningScript } from "../Script/RunningScript"; + /** * Map of all Servers that exist in the game * Key (string) = IP @@ -206,10 +208,17 @@ function excludeReplacer(key: string, value: any): any { return value; } +function scriptFilter(script: RunningScript): boolean { + return !script.temporary; +} + function includeReplacer(key: string, value: any): any { if (key === "logs") { return []; } + if (key === "runningScripts") { + return value.filter(scriptFilter); + } return value; } diff --git a/test/jest/Save.test.ts b/test/jest/Save.test.ts new file mode 100644 index 000000000..f5e401551 --- /dev/null +++ b/test/jest/Save.test.ts @@ -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(); +}); diff --git a/test/jest/__snapshots__/Save.test.ts.snap b/test/jest/__snapshots__/Save.test.ts.snap new file mode 100644 index 000000000..b6627a864 --- /dev/null +++ b/test/jest/__snapshots__/Save.test.ts.snap @@ -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 + } + } +}" +`;