mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-20 16:22:56 +02:00
NETSCRIPT: Greatly speed up script launching, and remove the limitation unique args per script (#440)
* Remove the limitation unique args per script * Internal changes to how runningScripts are stored on the server, to make common usage faster.
This commit is contained in:
@@ -5,11 +5,10 @@ import { NSFull } from "../NetscriptFunctions";
|
||||
* Netscript functions and arguments for that script.
|
||||
*/
|
||||
export class Environment {
|
||||
/** Whether or not the script that uses this Environment should stop running */
|
||||
/** Whether or not the script that uses this Environment is stopped */
|
||||
stopFlag = false;
|
||||
|
||||
/** The currently running function */
|
||||
|
||||
runningFn = "";
|
||||
|
||||
/** Environment variables (currently only Netscript functions) */
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { NetscriptContext } from "./APIWrapper";
|
||||
import { WorkerScript } from "./WorkerScript";
|
||||
import { killWorkerScript } from "./killWorkerScript";
|
||||
import { GetAllServers, GetServer } from "../Server/AllServers";
|
||||
import { Player } from "@player";
|
||||
import { ScriptDeath } from "./ScriptDeath";
|
||||
@@ -26,7 +27,7 @@ import { GangMemberTask } from "../Gang/GangMemberTask";
|
||||
import { RunningScript } from "../Script/RunningScript";
|
||||
import { toNative } from "../NetscriptFunctions/toNative";
|
||||
import { ScriptIdentifier } from "./ScriptIdentifier";
|
||||
import { findRunningScript, findRunningScriptByPid } from "../Script/ScriptHelpers";
|
||||
import { findRunningScripts, findRunningScriptByPid } from "../Script/ScriptHelpers";
|
||||
import { arrayToString } from "../utils/helpers/arrayToString";
|
||||
import { HacknetServer } from "../Hacknet/HacknetServer";
|
||||
import { BaseServer } from "../Server/BaseServer";
|
||||
@@ -35,6 +36,8 @@ import { checkEnum } from "../utils/helpers/enum";
|
||||
import { RamCostConstants } from "./RamCostGenerator";
|
||||
import { isPositiveInteger, PositiveInteger, Unknownify } from "../types";
|
||||
import { Engine } from "../engine";
|
||||
import { resolveFilePath, FilePath } from "../Paths/FilePath";
|
||||
import { hasScriptExtension, ScriptFilePath } from "../Paths/ScriptFilePath";
|
||||
|
||||
export const helpers = {
|
||||
string,
|
||||
@@ -61,8 +64,10 @@ export const helpers = {
|
||||
gangMember,
|
||||
gangTask,
|
||||
log,
|
||||
filePath,
|
||||
scriptPath,
|
||||
getRunningScript,
|
||||
getRunningScriptByArgs,
|
||||
getRunningScriptsByArgs,
|
||||
getCannotFindRunningScriptErrorMessage,
|
||||
createPublicRunningScript,
|
||||
failOnHacknetServer,
|
||||
@@ -73,6 +78,7 @@ export interface CompleteRunOptions {
|
||||
threads: PositiveInteger;
|
||||
temporary: boolean;
|
||||
ramOverride?: number;
|
||||
preventDuplicates: boolean;
|
||||
}
|
||||
|
||||
export function assertMember<T extends string>(
|
||||
@@ -186,6 +192,7 @@ function runOptions(ctx: NetscriptContext, threadOrOption: unknown): CompleteRun
|
||||
const result: CompleteRunOptions = {
|
||||
threads: 1 as PositiveInteger,
|
||||
temporary: false,
|
||||
preventDuplicates: false,
|
||||
};
|
||||
function checkThreads(threads: unknown, argName: string) {
|
||||
if (threads !== null && threads !== undefined) {
|
||||
@@ -200,6 +207,7 @@ function runOptions(ctx: NetscriptContext, threadOrOption: unknown): CompleteRun
|
||||
const options = threadOrOption as Unknownify<CompleteRunOptions>;
|
||||
checkThreads(options.threads, "RunOptions.threads");
|
||||
result.temporary = !!options.temporary;
|
||||
result.preventDuplicates = !!options.preventDuplicates;
|
||||
if (options.ramOverride !== undefined && options.ramOverride !== null) {
|
||||
result.ramOverride = number(ctx, "RunOptions.ramOverride", options.ramOverride);
|
||||
if (result.ramOverride < RamCostConstants.Base) {
|
||||
@@ -348,10 +356,8 @@ function checkEnvFlags(ctx: NetscriptContext): void {
|
||||
throw new ScriptDeath(ws);
|
||||
}
|
||||
if (ws.env.runningFn && ctx.function !== "asleep") {
|
||||
ws.delayReject?.(new ScriptDeath(ws));
|
||||
ws.env.stopFlag = true;
|
||||
log(ctx, () => "Failed to run due to failed concurrency check.");
|
||||
throw makeRuntimeErrorMsg(
|
||||
const err = makeRuntimeErrorMsg(
|
||||
ctx,
|
||||
`Concurrent calls to Netscript functions are not allowed!
|
||||
Did you forget to await hack(), grow(), or some other
|
||||
@@ -359,6 +365,8 @@ function checkEnvFlags(ctx: NetscriptContext): void {
|
||||
Currently running: ${ws.env.runningFn} tried to run: ${ctx.function}`,
|
||||
"CONCURRENCY",
|
||||
);
|
||||
killWorkerScript(ws);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,8 +396,7 @@ function updateDynamicRam(ctx: NetscriptContext, ramCost: number): void {
|
||||
ws.dynamicRamUsage = Math.min(ws.dynamicRamUsage + ramCost, RamCostConstants.Max);
|
||||
if (ws.dynamicRamUsage > 1.01 * ws.scriptRef.ramUsage) {
|
||||
log(ctx, () => "Insufficient static ram available.");
|
||||
ws.env.stopFlag = true;
|
||||
throw makeRuntimeErrorMsg(
|
||||
const err = makeRuntimeErrorMsg(
|
||||
ctx,
|
||||
`Dynamic RAM usage calculated to be greater than RAM allocation.
|
||||
This is probably because you somehow circumvented the static RAM calculation.
|
||||
@@ -410,6 +417,8 @@ function updateDynamicRam(ctx: NetscriptContext, ramCost: number): void {
|
||||
Sorry :(`,
|
||||
"RAM USAGE",
|
||||
);
|
||||
killWorkerScript(ws);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -651,22 +660,35 @@ function log(ctx: NetscriptContext, message: () => string) {
|
||||
ctx.workerScript.log(ctx.functionPath, message);
|
||||
}
|
||||
|
||||
export function filePath(ctx: NetscriptContext, argName: string, filename: unknown): FilePath {
|
||||
assertString(ctx, argName, filename);
|
||||
const path = resolveFilePath(filename, ctx.workerScript.name);
|
||||
if (path) return path;
|
||||
throw makeRuntimeErrorMsg(ctx, `Invalid ${argName}, was not a valid path: ${filename}`);
|
||||
}
|
||||
|
||||
export function scriptPath(ctx: NetscriptContext, argName: string, filename: unknown): ScriptFilePath {
|
||||
const path = filePath(ctx, argName, filename);
|
||||
if (hasScriptExtension(path)) return path;
|
||||
throw makeRuntimeErrorMsg(ctx, `Invalid ${argName}, must be a script: ${filename}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for and returns the RunningScript object for the specified script.
|
||||
* Searches for and returns the RunningScript objects for the specified script.
|
||||
* If the 'fn' argument is not specified, this returns the current RunningScript.
|
||||
* @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'
|
||||
* @returns Running scripts identified by the parameters, or empty if no such script
|
||||
* exists, or only the current running script if the first argument 'fn'
|
||||
* is not specified.
|
||||
*/
|
||||
function getRunningScriptByArgs(
|
||||
export function getRunningScriptsByArgs(
|
||||
ctx: NetscriptContext,
|
||||
fn: string,
|
||||
hostname: string,
|
||||
scriptArgs: ScriptArg[],
|
||||
): RunningScript | null {
|
||||
): Map<number, RunningScript> | null {
|
||||
if (!Array.isArray(scriptArgs)) {
|
||||
throw helpers.makeRuntimeErrorMsg(
|
||||
ctx,
|
||||
@@ -675,18 +697,14 @@ function getRunningScriptByArgs(
|
||||
);
|
||||
}
|
||||
|
||||
if (fn != null && typeof fn === "string") {
|
||||
// Get Logs of another script
|
||||
if (hostname == null) {
|
||||
hostname = ctx.workerScript.hostname;
|
||||
}
|
||||
const server = helpers.getServer(ctx, hostname);
|
||||
|
||||
return findRunningScript(fn, scriptArgs, server);
|
||||
const path = scriptPath(ctx, "filename", fn);
|
||||
// Lookup server to scope search
|
||||
if (hostname == null) {
|
||||
hostname = ctx.workerScript.hostname;
|
||||
}
|
||||
const server = helpers.getServer(ctx, hostname);
|
||||
|
||||
// If no arguments are specified, return the current RunningScript
|
||||
return ctx.workerScript.scriptRef;
|
||||
return findRunningScripts(path, scriptArgs, server);
|
||||
}
|
||||
|
||||
function getRunningScriptByPid(pid: number): RunningScript | null {
|
||||
@@ -701,7 +719,9 @@ function getRunningScript(ctx: NetscriptContext, ident: ScriptIdentifier): Runni
|
||||
if (typeof ident === "number") {
|
||||
return getRunningScriptByPid(ident);
|
||||
} else {
|
||||
return getRunningScriptByArgs(ctx, ident.scriptname, ident.hostname, ident.args);
|
||||
const scripts = getRunningScriptsByArgs(ctx, ident.scriptname, ident.hostname, ident.args);
|
||||
if (scripts === null) return null;
|
||||
return scripts.values().next().value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ export class WorkerScript {
|
||||
delay: number | null = null;
|
||||
|
||||
/** Holds the Promise reject() function while the script is "blocked" by an async op */
|
||||
delayReject?: (reason?: ScriptDeath) => void;
|
||||
delayReject: ((reason?: ScriptDeath) => void) | undefined = undefined;
|
||||
|
||||
/** Stores names of all functions that have logging disabled */
|
||||
disableLogs: Record<string, boolean> = {};
|
||||
@@ -79,7 +79,7 @@ export class WorkerScript {
|
||||
hostname: string;
|
||||
|
||||
/** Function called when the script ends. */
|
||||
atExit?: () => void;
|
||||
atExit: (() => void) | undefined = undefined;
|
||||
|
||||
constructor(runningScriptObj: RunningScript, pid: number, nsFuncsGenerator?: (ws: WorkerScript) => NSFull) {
|
||||
this.name = runningScriptObj.filename;
|
||||
|
||||
@@ -7,47 +7,24 @@ import { WorkerScript } from "./WorkerScript";
|
||||
import { workerScripts } from "./WorkerScripts";
|
||||
import { WorkerScriptStartStopEventEmitter } from "./WorkerScriptStartStopEventEmitter";
|
||||
|
||||
import { RunningScript } from "../Script/RunningScript";
|
||||
import { GetServer } from "../Server/AllServers";
|
||||
|
||||
import { AddRecentScript } from "./RecentScripts";
|
||||
import { ITutorial } from "../InteractiveTutorial";
|
||||
import { AlertEvents } from "../ui/React/AlertManager";
|
||||
import { handleUnknownError } from "./NetscriptHelpers";
|
||||
import { roundToTwo } from "../utils/helpers/roundToTwo";
|
||||
|
||||
export type killScriptParams = WorkerScript | number | { runningScript: RunningScript; hostname: string };
|
||||
|
||||
export function killWorkerScript(params: killScriptParams): boolean {
|
||||
export function killWorkerScript(ws: WorkerScript): boolean {
|
||||
if (ITutorial.isRunning) {
|
||||
AlertEvents.emit("Processes cannot be killed during the tutorial.");
|
||||
return false;
|
||||
}
|
||||
if (params instanceof WorkerScript) {
|
||||
stopAndCleanUpWorkerScript(params);
|
||||
stopAndCleanUpWorkerScript(ws);
|
||||
|
||||
return true;
|
||||
} else if (typeof params === "number") {
|
||||
return killWorkerScriptByPid(params);
|
||||
} else {
|
||||
// Try to kill by PID
|
||||
const res = killWorkerScriptByPid(params.runningScript.pid);
|
||||
if (res) {
|
||||
return res;
|
||||
}
|
||||
|
||||
// If for some reason that doesn't work, we'll try the old way
|
||||
for (const ws of workerScripts.values()) {
|
||||
if (ws.scriptRef === params.runningScript) {
|
||||
stopAndCleanUpWorkerScript(ws);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function killWorkerScriptByPid(pid: number): boolean {
|
||||
export function killWorkerScriptByPid(pid: number): boolean {
|
||||
const ws = workerScripts.get(pid);
|
||||
if (ws instanceof WorkerScript) {
|
||||
stopAndCleanUpWorkerScript(ws);
|
||||
@@ -58,6 +35,10 @@ function killWorkerScriptByPid(pid: number): boolean {
|
||||
}
|
||||
|
||||
function stopAndCleanUpWorkerScript(ws: WorkerScript): void {
|
||||
// Only clean up once.
|
||||
// Important: Only this function can set stopFlag!
|
||||
if (ws.env.stopFlag) return;
|
||||
|
||||
//Clean up any ongoing netscriptDelay
|
||||
if (ws.delay) clearTimeout(ws.delay);
|
||||
ws.delayReject?.(new ScriptDeath(ws));
|
||||
@@ -65,7 +46,6 @@ function stopAndCleanUpWorkerScript(ws: WorkerScript): void {
|
||||
|
||||
if (typeof ws.atExit === "function") {
|
||||
try {
|
||||
ws.env.stopFlag = false;
|
||||
const atExit = ws.atExit;
|
||||
ws.atExit = undefined;
|
||||
atExit();
|
||||
@@ -95,21 +75,21 @@ function removeWorkerScript(workerScript: WorkerScript): void {
|
||||
}
|
||||
|
||||
// Delete the RunningScript object from that server
|
||||
for (let i = 0; i < server.runningScripts.length; ++i) {
|
||||
const runningScript = server.runningScripts[i];
|
||||
if (runningScript === workerScript.scriptRef) {
|
||||
server.runningScripts.splice(i, 1);
|
||||
break;
|
||||
const rs = workerScript.scriptRef;
|
||||
const byPid = server.runningScriptMap.get(rs.scriptKey);
|
||||
if (!byPid) {
|
||||
console.error(`Couldn't find runningScriptMap for key ${rs.scriptKey}`);
|
||||
} else {
|
||||
byPid.delete(workerScript.pid);
|
||||
if (byPid.size === 0) {
|
||||
server.runningScriptMap.delete(rs.scriptKey);
|
||||
}
|
||||
}
|
||||
|
||||
// Recalculate ram used on that server
|
||||
// Update ram used. Reround to prevent accumulation of error.
|
||||
server.updateRamUsed(roundToTwo(server.ramUsed - rs.ramUsage * rs.threads));
|
||||
|
||||
server.updateRamUsed(0);
|
||||
for (const rs of server.runningScripts) server.updateRamUsed(server.ramUsed + rs.ramUsage * rs.threads);
|
||||
|
||||
// Delete script from global pool (workerScripts) after verifying it's the right script (PIDs reset on aug install)
|
||||
if (workerScripts.get(workerScript.pid) === workerScript) workerScripts.delete(workerScript.pid);
|
||||
workerScripts.delete(workerScript.pid);
|
||||
AddRecentScript(workerScript);
|
||||
WorkerScriptStartStopEventEmitter.emit();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user