2019-05-05 06:03:40 +02:00
|
|
|
/**
|
|
|
|
* Functions for handling WorkerScripts, which are the underlying mechanism
|
|
|
|
* that allows for scripts to run
|
|
|
|
*/
|
2019-05-17 08:44:59 +02:00
|
|
|
import { killWorkerScript } from "./Netscript/killWorkerScript";
|
2022-02-06 01:15:42 +01:00
|
|
|
import { ScriptDeath } from "./Netscript/ScriptDeath";
|
2019-05-05 06:03:40 +02:00
|
|
|
import { WorkerScript } from "./Netscript/WorkerScript";
|
2019-05-16 08:05:36 +02:00
|
|
|
import { workerScripts } from "./Netscript/WorkerScripts";
|
2019-05-17 22:41:16 +02:00
|
|
|
import { WorkerScriptStartStopEventEmitter } from "./Netscript/WorkerScriptStartStopEventEmitter";
|
2021-03-07 19:08:12 +01:00
|
|
|
import { generateNextPid } from "./Netscript/Pid";
|
2019-05-05 06:03:40 +02:00
|
|
|
|
2019-04-11 10:37:40 +02:00
|
|
|
import { CONSTANTS } from "./Constants";
|
2021-09-25 00:42:13 +02:00
|
|
|
import { Interpreter } from "./ThirdParty/JSInterpreter";
|
2023-01-04 14:56:29 +01:00
|
|
|
import { NetscriptFunctions } from "./NetscriptFunctions";
|
2022-08-29 08:41:17 +02:00
|
|
|
import { compile, Node } from "./NetscriptJSEvaluator";
|
2023-02-28 01:54:04 +01:00
|
|
|
import { Port, PortNumber } from "./NetscriptPort";
|
2019-05-07 03:01:06 +02:00
|
|
|
import { RunningScript } from "./Script/RunningScript";
|
2019-05-05 06:03:40 +02:00
|
|
|
import { getRamUsageFromRunningScript } from "./Script/RunningScriptHelpers";
|
2021-05-01 09:17:31 +02:00
|
|
|
import { scriptCalculateOfflineProduction } from "./Script/ScriptHelpers";
|
2021-09-24 23:56:30 +02:00
|
|
|
import { Script } from "./Script/Script";
|
2021-10-07 22:04:04 +02:00
|
|
|
import { GetAllServers } from "./Server/AllServers";
|
2021-09-24 23:56:30 +02:00
|
|
|
import { BaseServer } from "./Server/BaseServer";
|
2019-04-11 10:37:40 +02:00
|
|
|
import { Settings } from "./Settings/Settings";
|
2017-08-30 19:44:29 +02:00
|
|
|
|
2019-04-11 10:37:40 +02:00
|
|
|
import { generate } from "escodegen";
|
|
|
|
|
2022-09-05 15:55:57 +02:00
|
|
|
import { dialogBoxCreate } from "./ui/React/DialogBox";
|
2021-09-25 20:42:57 +02:00
|
|
|
import { arrayToString } from "./utils/helpers/arrayToString";
|
|
|
|
import { roundToTwo } from "./utils/helpers/roundToTwo";
|
2018-07-20 16:28:03 +02:00
|
|
|
|
2021-05-01 09:17:31 +02:00
|
|
|
import { parse } from "acorn";
|
2021-05-29 20:48:56 +02:00
|
|
|
import { simple as walksimple } from "acorn-walk";
|
2021-11-06 02:01:23 +01:00
|
|
|
import { areFilesEqual } from "./Terminal/DirectoryHelpers";
|
2021-12-22 20:32:36 +01:00
|
|
|
import { Terminal } from "./Terminal";
|
2022-07-18 08:28:21 +02:00
|
|
|
import { ScriptArg } from "./Netscript/ScriptArg";
|
2022-09-05 15:55:57 +02:00
|
|
|
import { handleUnknownError } from "./Netscript/NetscriptHelpers";
|
2018-07-20 16:28:03 +02:00
|
|
|
|
2023-02-28 01:54:04 +01:00
|
|
|
export const NetscriptPorts: Map<PortNumber, Port> = new Map();
|
2017-07-22 00:54:55 +02:00
|
|
|
|
2021-09-25 08:36:49 +02:00
|
|
|
export function prestigeWorkerScripts(): void {
|
2021-09-05 01:09:30 +02:00
|
|
|
for (const ws of workerScripts.values()) {
|
|
|
|
ws.env.stopFlag = true;
|
|
|
|
killWorkerScript(ws);
|
|
|
|
}
|
2022-09-01 16:27:31 +02:00
|
|
|
|
2022-09-23 05:55:16 +02:00
|
|
|
NetscriptPorts.clear();
|
2019-06-19 10:51:25 +02:00
|
|
|
|
2021-09-18 21:44:39 +02:00
|
|
|
WorkerScriptStartStopEventEmitter.emit();
|
2021-09-05 01:09:30 +02:00
|
|
|
workerScripts.clear();
|
2017-08-30 19:44:29 +02:00
|
|
|
}
|
|
|
|
|
2022-08-29 08:41:17 +02:00
|
|
|
async function startNetscript2Script(workerScript: WorkerScript): Promise<void> {
|
|
|
|
const scripts = workerScript.getServer().scripts;
|
|
|
|
const script = workerScript.getScript();
|
|
|
|
if (script === null) throw "workerScript had no associated script. This is a bug.";
|
|
|
|
const loadedModule = await compile(script, scripts);
|
|
|
|
workerScript.ramUsage = script.ramUsage;
|
|
|
|
const ns = workerScript.env.vars;
|
|
|
|
|
|
|
|
if (!loadedModule) throw `${script.filename} cannot be run because the script module won't load`;
|
2022-12-30 02:28:53 +01:00
|
|
|
// TODO unplanned: Better error for "unexpected reserved word" when using await in non-async function?
|
2022-08-29 08:41:17 +02:00
|
|
|
if (typeof loadedModule.main !== "function")
|
|
|
|
throw `${script.filename} cannot be run because it does not have a main function.`;
|
|
|
|
if (!ns) throw `${script.filename} cannot be run because the NS object hasn't been constructed properly.`;
|
|
|
|
await loadedModule.main(ns);
|
|
|
|
}
|
2021-09-05 01:09:30 +02:00
|
|
|
|
2022-08-29 08:41:17 +02:00
|
|
|
async function startNetscript1Script(workerScript: WorkerScript): Promise<void> {
|
2021-09-05 01:09:30 +02:00
|
|
|
const code = workerScript.code;
|
2022-08-29 08:41:17 +02:00
|
|
|
let errorToThrow: unknown;
|
2021-09-05 01:09:30 +02:00
|
|
|
|
|
|
|
//Process imports
|
2021-09-25 07:26:03 +02:00
|
|
|
let codeWithImports, codeLineOffset;
|
2021-09-05 01:09:30 +02:00
|
|
|
try {
|
2021-09-25 07:26:03 +02:00
|
|
|
const importProcessingRes = processNetscript1Imports(code, workerScript);
|
2021-09-05 01:09:30 +02:00
|
|
|
codeWithImports = importProcessingRes.code;
|
|
|
|
codeLineOffset = importProcessingRes.lineOffset;
|
2022-07-15 07:51:30 +02:00
|
|
|
} catch (e: unknown) {
|
2022-08-29 08:41:17 +02:00
|
|
|
throw `Error processing Imports in ${workerScript.name}@${workerScript.hostname}:\n\n${e}`;
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
|
|
|
|
2022-12-30 02:28:53 +01:00
|
|
|
//TODO unplanned: Make NS1 wrapping type safe instead of using BasicObject.
|
2022-11-09 19:46:21 +01:00
|
|
|
type BasicObject = Record<string, any>;
|
2023-01-04 14:56:29 +01:00
|
|
|
const wrappedNS = NetscriptFunctions(workerScript);
|
2022-11-28 15:11:55 +01:00
|
|
|
function wrapNS1Layer(int: Interpreter, intLayer: unknown, nsLayer = wrappedNS as BasicObject) {
|
2022-08-28 02:56:12 +02:00
|
|
|
for (const [name, entry] of Object.entries(nsLayer)) {
|
2021-09-05 01:09:30 +02:00
|
|
|
if (typeof entry === "function") {
|
2022-08-28 02:56:12 +02:00
|
|
|
const wrapper = async (...args: unknown[]) => {
|
|
|
|
try {
|
2022-08-29 08:41:17 +02:00
|
|
|
// Sent a resolver function as an extra arg. See createAsyncFunction JSInterpreter.js:3209
|
2022-08-28 02:56:12 +02:00
|
|
|
const callback = args.pop() as (value: unknown) => void;
|
2023-01-04 14:56:29 +01:00
|
|
|
const result = await entry(...args.map((arg) => int.pseudoToNative(arg)));
|
2022-08-28 02:56:12 +02:00
|
|
|
return callback(int.nativeToPseudo(result));
|
|
|
|
} catch (e: unknown) {
|
2022-08-29 08:41:17 +02:00
|
|
|
errorToThrow = e;
|
2022-08-28 02:56:12 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
int.setProperty(intLayer, name, int.createAsyncFunction(wrapper));
|
2022-08-28 04:32:48 +02:00
|
|
|
} else if (Array.isArray(entry) || typeof entry !== "object") {
|
2022-08-28 02:56:12 +02:00
|
|
|
// args, strings on enums, etc
|
|
|
|
int.setProperty(intLayer, name, int.nativeToPseudo(entry));
|
2021-09-05 01:09:30 +02:00
|
|
|
} else {
|
2022-08-28 02:56:12 +02:00
|
|
|
// new object layer, e.g. bladeburner
|
|
|
|
int.setProperty(intLayer, name, int.nativeToPseudo({}));
|
2022-08-28 11:33:38 +02:00
|
|
|
wrapNS1Layer(int, (intLayer as BasicObject).properties[name], nsLayer[name]);
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
2021-04-30 05:52:56 +02:00
|
|
|
}
|
2022-08-28 04:32:48 +02:00
|
|
|
}
|
2021-09-05 01:09:30 +02:00
|
|
|
|
2022-07-15 07:51:30 +02:00
|
|
|
let interpreter: Interpreter;
|
2021-09-05 01:09:30 +02:00
|
|
|
try {
|
2022-08-28 02:56:12 +02:00
|
|
|
interpreter = new Interpreter(codeWithImports, wrapNS1Layer, codeLineOffset);
|
2022-07-15 07:51:30 +02:00
|
|
|
} catch (e: unknown) {
|
2022-08-29 08:41:17 +02:00
|
|
|
throw `Syntax ERROR in ${workerScript.name}@${workerScript.hostname}:\n\n${String(e)}`;
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
|
|
|
|
2022-08-29 08:41:17 +02:00
|
|
|
let more = true;
|
|
|
|
while (more) {
|
|
|
|
if (errorToThrow) throw errorToThrow;
|
|
|
|
if (workerScript.env.stopFlag) return;
|
|
|
|
for (let i = 0; more && i < 3; i++) more = interpreter.step();
|
|
|
|
if (more) await new Promise((r) => setTimeout(r, Settings.CodeInstructionRunTime));
|
|
|
|
}
|
2018-07-20 16:28:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Since the JS Interpreter used for Netscript 1.0 only supports ES5, the keyword
|
2022-10-09 07:25:31 +02:00
|
|
|
'import' throws an error. However, since we want to support import functionality
|
2018-07-20 16:28:03 +02:00
|
|
|
we'll implement it ourselves by parsing the Nodes in the AST out.
|
|
|
|
|
|
|
|
@param code - The script's code
|
2018-07-25 21:53:54 +02:00
|
|
|
@returns {Object} {
|
|
|
|
code: Newly-generated code with imported functions
|
|
|
|
lineOffset: Net number of lines of code added/removed due to imported functions
|
|
|
|
Should typically be positive
|
|
|
|
}
|
2018-07-20 16:28:03 +02:00
|
|
|
*/
|
2022-07-18 08:28:21 +02:00
|
|
|
function processNetscript1Imports(code: string, workerScript: WorkerScript): { code: string; lineOffset: number } {
|
2021-09-05 01:09:30 +02:00
|
|
|
//allowReserved prevents 'import' from throwing error in ES5
|
2022-07-18 08:28:21 +02:00
|
|
|
const ast: Node = parse(code, {
|
2021-09-05 01:09:30 +02:00
|
|
|
ecmaVersion: 9,
|
|
|
|
allowReserved: true,
|
|
|
|
sourceType: "module",
|
|
|
|
});
|
|
|
|
|
2021-09-25 07:26:03 +02:00
|
|
|
const server = workerScript.getServer();
|
2021-09-05 01:09:30 +02:00
|
|
|
if (server == null) {
|
|
|
|
throw new Error("Failed to find underlying Server object for script");
|
|
|
|
}
|
|
|
|
|
2021-09-24 23:56:30 +02:00
|
|
|
function getScript(scriptName: string): Script | null {
|
2021-09-05 01:09:30 +02:00
|
|
|
for (let i = 0; i < server.scripts.length; ++i) {
|
|
|
|
if (server.scripts[i].filename === scriptName) {
|
|
|
|
return server.scripts[i];
|
|
|
|
}
|
2018-07-20 16:28:03 +02:00
|
|
|
}
|
2021-09-05 01:09:30 +02:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
let generatedCode = ""; // Generated Javascript Code
|
|
|
|
let hasImports = false;
|
|
|
|
|
|
|
|
// Walk over the tree and process ImportDeclaration nodes
|
|
|
|
walksimple(ast, {
|
2022-07-18 08:28:21 +02:00
|
|
|
ImportDeclaration: (node: Node) => {
|
2021-09-05 01:09:30 +02:00
|
|
|
hasImports = true;
|
|
|
|
let scriptName = node.source.value;
|
|
|
|
if (scriptName.startsWith("./")) {
|
|
|
|
scriptName = scriptName.slice(2);
|
|
|
|
}
|
2021-09-25 07:26:03 +02:00
|
|
|
const script = getScript(scriptName);
|
2021-09-05 01:09:30 +02:00
|
|
|
if (script == null) {
|
|
|
|
throw new Error("'Import' failed due to invalid script: " + scriptName);
|
|
|
|
}
|
2021-09-25 07:26:03 +02:00
|
|
|
const scriptAst = parse(script.code, {
|
2021-09-05 01:09:30 +02:00
|
|
|
ecmaVersion: 9,
|
|
|
|
allowReserved: true,
|
|
|
|
sourceType: "module",
|
|
|
|
});
|
|
|
|
|
2021-09-09 05:47:34 +02:00
|
|
|
if (node.specifiers.length === 1 && node.specifiers[0].type === "ImportNamespaceSpecifier") {
|
2021-09-05 01:09:30 +02:00
|
|
|
// import * as namespace from script
|
2021-09-25 07:26:03 +02:00
|
|
|
const namespace = node.specifiers[0].local.name;
|
|
|
|
const fnNames: string[] = []; //Names only
|
2022-07-18 08:28:21 +02:00
|
|
|
const fnDeclarations: Node[] = []; //FunctionDeclaration Node objects
|
2021-09-05 01:09:30 +02:00
|
|
|
walksimple(scriptAst, {
|
2022-07-18 08:28:21 +02:00
|
|
|
FunctionDeclaration: (node: Node) => {
|
2021-09-05 01:09:30 +02:00
|
|
|
fnNames.push(node.id.name);
|
|
|
|
fnDeclarations.push(node);
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
//Now we have to generate the code that would create the namespace
|
2022-03-16 20:43:04 +01:00
|
|
|
generatedCode += `var ${namespace};\n(function (namespace) {\n`;
|
2021-09-05 01:09:30 +02:00
|
|
|
|
|
|
|
//Add the function declarations
|
2022-07-18 08:28:21 +02:00
|
|
|
fnDeclarations.forEach((fn: Node) => {
|
2021-09-05 01:09:30 +02:00
|
|
|
generatedCode += generate(fn);
|
|
|
|
generatedCode += "\n";
|
|
|
|
});
|
|
|
|
|
|
|
|
//Add functions to namespace
|
|
|
|
fnNames.forEach((fnName) => {
|
|
|
|
generatedCode += "namespace." + fnName + " = " + fnName;
|
|
|
|
generatedCode += "\n";
|
|
|
|
});
|
|
|
|
|
|
|
|
//Finish
|
2022-03-16 20:43:04 +01:00
|
|
|
generatedCode += `})(${namespace} || (" + namespace + " = {}));\n`;
|
2021-09-05 01:09:30 +02:00
|
|
|
} else {
|
|
|
|
//import {...} from script
|
|
|
|
|
|
|
|
//Get array of all fns to import
|
2021-09-25 07:26:03 +02:00
|
|
|
const fnsToImport: string[] = [];
|
2022-07-18 08:28:21 +02:00
|
|
|
node.specifiers.forEach((e: Node) => {
|
2021-09-05 01:09:30 +02:00
|
|
|
fnsToImport.push(e.local.name);
|
|
|
|
});
|
|
|
|
|
|
|
|
//Walk through script and get FunctionDeclaration code for all specified fns
|
2022-07-18 08:28:21 +02:00
|
|
|
const fnDeclarations: Node[] = [];
|
2021-09-05 01:09:30 +02:00
|
|
|
walksimple(scriptAst, {
|
2022-07-18 08:28:21 +02:00
|
|
|
FunctionDeclaration: (node: Node) => {
|
2021-09-05 01:09:30 +02:00
|
|
|
if (fnsToImport.includes(node.id.name)) {
|
|
|
|
fnDeclarations.push(node);
|
2018-07-20 16:28:03 +02:00
|
|
|
}
|
2021-09-05 01:09:30 +02:00
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
//Convert FunctionDeclarations into code
|
2022-07-18 08:28:21 +02:00
|
|
|
fnDeclarations.forEach((fn: Node) => {
|
2021-09-05 01:09:30 +02:00
|
|
|
generatedCode += generate(fn);
|
|
|
|
generatedCode += "\n";
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
//If there are no imports, just return the original code
|
|
|
|
if (!hasImports) {
|
|
|
|
return { code: code, lineOffset: 0 };
|
|
|
|
}
|
|
|
|
|
|
|
|
//Remove ImportDeclarations from AST. These ImportDeclarations must be in top-level
|
2021-09-25 07:26:03 +02:00
|
|
|
let linesRemoved = 0;
|
2021-09-05 01:09:30 +02:00
|
|
|
if (ast.type !== "Program" || ast.body == null) {
|
|
|
|
throw new Error("Code could not be properly parsed");
|
|
|
|
}
|
|
|
|
for (let i = ast.body.length - 1; i >= 0; --i) {
|
|
|
|
if (ast.body[i].type === "ImportDeclaration") {
|
|
|
|
ast.body.splice(i, 1);
|
|
|
|
++linesRemoved;
|
2018-07-20 16:28:03 +02:00
|
|
|
}
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
2018-07-09 01:53:24 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
//Calculated line offset
|
2021-09-25 07:26:03 +02:00
|
|
|
const lineOffset = (generatedCode.match(/\n/g) || []).length - linesRemoved;
|
2018-07-25 21:53:54 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
//Convert the AST back into code
|
|
|
|
code = generate(ast);
|
2018-07-09 01:53:24 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
//Add the imported code and re-generate in ES5 (JS Interpreter for NS1 only supports ES5);
|
|
|
|
code = generatedCode + code;
|
2019-05-12 04:20:20 +02:00
|
|
|
|
2021-09-25 07:26:03 +02:00
|
|
|
const res = {
|
2021-09-05 01:09:30 +02:00
|
|
|
code: code,
|
|
|
|
lineOffset: lineOffset,
|
|
|
|
};
|
|
|
|
return res;
|
2018-07-09 01:53:24 +02:00
|
|
|
}
|
|
|
|
|
2019-05-05 06:03:40 +02:00
|
|
|
/**
|
2019-07-14 05:55:58 +02:00
|
|
|
* Used to start a RunningScript (by creating and starting its
|
|
|
|
* corresponding WorkerScript), and add the RunningScript to the server on which
|
|
|
|
* it is active
|
|
|
|
*/
|
2022-08-28 04:32:48 +02:00
|
|
|
export function startWorkerScript(runningScript: RunningScript, server: BaseServer, parent?: WorkerScript): number {
|
2022-08-28 02:56:12 +02:00
|
|
|
if (createAndAddWorkerScript(runningScript, server, parent)) {
|
2021-09-05 01:09:30 +02:00
|
|
|
// Push onto runningScripts.
|
|
|
|
// This has to come after createAndAddWorkerScript() because that fn updates RAM usage
|
2021-09-24 23:56:30 +02:00
|
|
|
server.runScript(runningScript);
|
2019-07-14 05:55:58 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
// Once the WorkerScript is constructed in createAndAddWorkerScript(), the RunningScript
|
|
|
|
// object should have a PID assigned to it, so we return that
|
|
|
|
return runningScript.pid;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2019-07-14 05:55:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Given a RunningScript object, constructs its corresponding WorkerScript,
|
2019-05-17 08:44:59 +02:00
|
|
|
* adds it to the global 'workerScripts' pool, and begins executing it.
|
|
|
|
* @param {RunningScript} runningScriptObj - Script that's being run
|
|
|
|
* @param {Server} server - Server on which the script is to be run
|
2019-07-14 05:55:58 +02:00
|
|
|
* returns {boolean} indicating whether or not the workerScript was successfully added
|
2019-05-05 06:03:40 +02:00
|
|
|
*/
|
2022-08-28 04:32:48 +02:00
|
|
|
function createAndAddWorkerScript(runningScriptObj: RunningScript, server: BaseServer, parent?: WorkerScript): boolean {
|
2021-09-05 01:09:30 +02:00
|
|
|
// Update server's ram usage
|
|
|
|
let threads = 1;
|
|
|
|
if (runningScriptObj.threads && !isNaN(runningScriptObj.threads)) {
|
|
|
|
threads = runningScriptObj.threads;
|
|
|
|
} else {
|
|
|
|
runningScriptObj.threads = 1;
|
|
|
|
}
|
2021-10-28 00:55:06 +02:00
|
|
|
const oneRamUsage = getRamUsageFromRunningScript(runningScriptObj);
|
|
|
|
const ramUsage = roundToTwo(oneRamUsage * threads);
|
2021-09-05 01:09:30 +02:00
|
|
|
const ramAvailable = server.maxRam - server.ramUsed;
|
2022-05-06 14:56:19 +02:00
|
|
|
if (ramUsage > ramAvailable + 0.001) {
|
2021-09-05 01:09:30 +02:00
|
|
|
dialogBoxCreate(
|
2022-09-05 14:19:24 +02:00
|
|
|
`Not enough RAM to run script ${runningScriptObj.filename} with args ${arrayToString(runningScriptObj.args)}.\n` +
|
|
|
|
`This can occur when you reload the game and the script's RAM usage has increased (either because of an update to the game or ` +
|
2022-09-05 14:29:03 +02:00
|
|
|
`your changes to the script).\nThis can also occur if you have attempted to launch a script from a tail window with insufficient RAM. `,
|
2021-09-05 01:09:30 +02:00
|
|
|
);
|
|
|
|
return false;
|
|
|
|
}
|
2021-11-16 05:49:33 +01:00
|
|
|
|
2022-08-29 08:41:17 +02:00
|
|
|
server.updateRamUsed(roundToTwo(server.ramUsed + ramUsage));
|
2021-09-05 01:09:30 +02:00
|
|
|
|
|
|
|
// Get the pid
|
|
|
|
const pid = generateNextPid();
|
|
|
|
if (pid === -1) {
|
|
|
|
throw new Error(
|
|
|
|
`Failed to start script because could not find available PID. This is most ` +
|
|
|
|
`because you have too many scripts running.`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create the WorkerScript. NOTE: WorkerScript ctor will set the underlying
|
|
|
|
// RunningScript's PID as well
|
2022-02-06 01:15:42 +01:00
|
|
|
const workerScript = new WorkerScript(runningScriptObj, pid, NetscriptFunctions);
|
|
|
|
workerScript.ramUsage = oneRamUsage;
|
2021-09-05 01:09:30 +02:00
|
|
|
|
|
|
|
// Add the WorkerScript to the global pool
|
2022-02-06 01:15:42 +01:00
|
|
|
workerScripts.set(pid, workerScript);
|
2021-09-18 21:44:39 +02:00
|
|
|
WorkerScriptStartStopEventEmitter.emit();
|
2021-09-05 01:09:30 +02:00
|
|
|
|
2022-08-29 08:41:17 +02:00
|
|
|
// Start the script's execution using the correct function for file type
|
|
|
|
(workerScript.name.endsWith(".js") ? startNetscript2Script : startNetscript1Script)(workerScript)
|
|
|
|
// Once the code finishes (either resolved or rejected, doesnt matter), set its
|
|
|
|
// running status to false
|
2022-04-12 03:45:55 +02:00
|
|
|
.then(function () {
|
2022-10-09 07:25:31 +02:00
|
|
|
// On natural death, the earnings are transferred to the parent if it still exists.
|
2022-08-28 11:33:38 +02:00
|
|
|
if (parent && !parent.env.stopFlag) {
|
2022-08-28 04:32:48 +02:00
|
|
|
parent.scriptRef.onlineExpGained += runningScriptObj.onlineExpGained;
|
|
|
|
parent.scriptRef.onlineMoneyMade += runningScriptObj.onlineMoneyMade;
|
2022-04-12 03:45:55 +02:00
|
|
|
}
|
|
|
|
killWorkerScript(workerScript);
|
|
|
|
workerScript.log("", () => "Script finished running");
|
|
|
|
})
|
|
|
|
.catch(function (e) {
|
2022-09-05 15:55:57 +02:00
|
|
|
handleUnknownError(e, workerScript);
|
2022-08-29 08:41:17 +02:00
|
|
|
workerScript.log("", () => (e instanceof ScriptDeath ? "Script killed." : "Script crashed due to an error."));
|
2022-04-12 03:45:55 +02:00
|
|
|
killWorkerScript(workerScript);
|
|
|
|
});
|
2021-09-05 01:09:30 +02:00
|
|
|
return true;
|
2016-12-14 21:29:40 +01:00
|
|
|
}
|
|
|
|
|
2022-10-04 12:40:10 +02:00
|
|
|
/** Updates the online running time stat of all running scripts */
|
2021-09-25 08:36:49 +02:00
|
|
|
export function updateOnlineScriptTimes(numCycles = 1): void {
|
2023-02-21 15:44:18 +01:00
|
|
|
const time = (numCycles * CONSTANTS.MilliPerCycle) / 1000; //seconds
|
2021-09-05 01:09:30 +02:00
|
|
|
for (const ws of workerScripts.values()) {
|
|
|
|
ws.scriptRef.onlineRunningTime += time;
|
|
|
|
}
|
2016-12-19 19:20:19 +01:00
|
|
|
}
|
|
|
|
|
2019-05-05 06:03:40 +02:00
|
|
|
/**
|
|
|
|
* Called when the game is loaded. Loads all running scripts (from all servers)
|
|
|
|
* into worker scripts so that they will start running
|
|
|
|
*/
|
2022-08-28 02:56:12 +02:00
|
|
|
export function loadAllRunningScripts(): void {
|
2021-09-25 07:26:03 +02:00
|
|
|
const skipScriptLoad = window.location.href.toLowerCase().indexOf("?noscripts") !== -1;
|
2021-09-05 01:09:30 +02:00
|
|
|
if (skipScriptLoad) {
|
2021-12-29 08:51:41 +01:00
|
|
|
Terminal.warn("Skipped loading player scripts during startup");
|
2021-09-05 01:09:30 +02:00
|
|
|
console.info("Skipping the load of any scripts during startup");
|
|
|
|
}
|
2021-10-07 22:04:04 +02:00
|
|
|
for (const server of GetAllServers()) {
|
|
|
|
// Reset each server's RAM usage to 0
|
|
|
|
server.ramUsed = 0;
|
2021-09-05 01:09:30 +02:00
|
|
|
|
2021-10-07 22:04:04 +02:00
|
|
|
// Reset modules on all scripts
|
|
|
|
for (let i = 0; i < server.scripts.length; ++i) {
|
|
|
|
server.scripts[i].markUpdated();
|
|
|
|
}
|
2021-09-05 01:09:30 +02:00
|
|
|
|
2021-10-07 22:04:04 +02:00
|
|
|
if (skipScriptLoad) {
|
|
|
|
// Start game with no scripts
|
|
|
|
server.runningScripts.length = 0;
|
|
|
|
} else {
|
|
|
|
for (let j = 0; j < server.runningScripts.length; ++j) {
|
2022-01-08 15:13:25 +01:00
|
|
|
const fileName = server.runningScripts[j].filename;
|
2022-08-28 02:56:12 +02:00
|
|
|
createAndAddWorkerScript(server.runningScripts[j], server);
|
2021-09-05 01:09:30 +02:00
|
|
|
|
2022-01-08 15:13:25 +01:00
|
|
|
if (!server.runningScripts[j]) {
|
|
|
|
// createAndAddWorkerScript can modify the server.runningScripts array if a script is invalid
|
|
|
|
console.error(`createAndAddWorkerScript removed ${fileName} from ${server}`);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-10-07 22:04:04 +02:00
|
|
|
// Offline production
|
|
|
|
scriptCalculateOfflineProduction(server.runningScripts[j]);
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-05-05 06:03:40 +02:00
|
|
|
}
|
|
|
|
|
2022-10-04 12:40:10 +02:00
|
|
|
/** Run a script from inside another script (run(), exec(), spawn(), etc.) */
|
2021-09-24 23:56:30 +02:00
|
|
|
export function runScriptFromScript(
|
|
|
|
caller: string,
|
|
|
|
server: BaseServer,
|
|
|
|
scriptname: string,
|
2022-07-18 08:28:21 +02:00
|
|
|
args: ScriptArg[],
|
2021-09-24 23:56:30 +02:00
|
|
|
workerScript: WorkerScript,
|
|
|
|
threads = 1,
|
|
|
|
): number {
|
2021-09-05 01:09:30 +02:00
|
|
|
// Sanitize arguments
|
|
|
|
if (!(workerScript instanceof WorkerScript)) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof scriptname !== "string" || !Array.isArray(args)) {
|
2021-11-27 00:30:58 +01:00
|
|
|
workerScript.log(caller, () => `Invalid arguments: scriptname='${scriptname} args='${args}'`);
|
2021-09-05 01:09:30 +02:00
|
|
|
console.error(`runScriptFromScript() failed due to invalid arguments`);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-12-12 19:30:28 +01:00
|
|
|
args.forEach((arg, i) => {
|
2021-11-25 17:47:31 +01:00
|
|
|
if (typeof arg !== "string" && typeof arg !== "number" && typeof arg !== "boolean")
|
2021-12-12 19:30:28 +01:00
|
|
|
throw new Error(
|
|
|
|
"Only strings, numbers, and booleans can be passed as arguments to other scripts.\n" +
|
|
|
|
`${scriptname} argument index ${i} is of type ${typeof arg} and value ${JSON.stringify(arg)}`,
|
|
|
|
);
|
2021-10-16 01:13:05 +02:00
|
|
|
});
|
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
// Check if the script is already running
|
2021-09-25 07:26:03 +02:00
|
|
|
const runningScriptObj = server.getRunningScript(scriptname, args);
|
2021-09-05 01:09:30 +02:00
|
|
|
if (runningScriptObj != null) {
|
2021-11-27 00:30:58 +01:00
|
|
|
workerScript.log(caller, () => `'${scriptname}' is already running on '${server.hostname}'`);
|
2021-09-05 01:09:30 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 'null/undefined' arguments are not allowed
|
|
|
|
for (let i = 0; i < args.length; ++i) {
|
|
|
|
if (args[i] == null) {
|
2021-11-27 00:30:58 +01:00
|
|
|
workerScript.log(caller, () => "Cannot execute a script with null/undefined as an argument");
|
2021-09-05 01:09:30 +02:00
|
|
|
return 0;
|
2019-07-12 04:37:17 +02:00
|
|
|
}
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the script exists and if it does run it
|
|
|
|
for (let i = 0; i < server.scripts.length; ++i) {
|
2021-11-06 02:01:23 +01:00
|
|
|
if (!areFilesEqual(server.scripts[i].filename, scriptname)) continue;
|
2022-10-09 07:25:31 +02:00
|
|
|
// Check for admin rights and that there is enough RAM available to run
|
2021-11-06 02:01:23 +01:00
|
|
|
const script = server.scripts[i];
|
|
|
|
let ramUsage = script.ramUsage;
|
2021-12-29 08:51:41 +01:00
|
|
|
threads = Math.floor(Number(threads));
|
2021-11-06 02:01:23 +01:00
|
|
|
if (threads === 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
ramUsage = ramUsage * threads;
|
|
|
|
const ramAvailable = server.maxRam - server.ramUsed;
|
2019-05-05 06:03:40 +02:00
|
|
|
|
2021-11-06 02:01:23 +01:00
|
|
|
if (server.hasAdminRights == false) {
|
2021-11-27 00:30:58 +01:00
|
|
|
workerScript.log(caller, () => `You do not have root access on '${server.hostname}'`);
|
2021-11-06 02:01:23 +01:00
|
|
|
return 0;
|
2022-05-06 14:56:19 +02:00
|
|
|
} else if (ramUsage > ramAvailable + 0.001) {
|
2021-11-06 02:01:23 +01:00
|
|
|
workerScript.log(
|
|
|
|
caller,
|
2021-11-27 00:30:58 +01:00
|
|
|
() =>
|
|
|
|
`Cannot run script '${scriptname}' (t=${threads}) on '${server.hostname}' because there is not enough available RAM!`,
|
2021-11-06 02:01:23 +01:00
|
|
|
);
|
|
|
|
return 0;
|
2019-05-05 06:03:40 +02:00
|
|
|
}
|
2022-03-11 16:46:43 +01:00
|
|
|
// Able to run script
|
|
|
|
workerScript.log(
|
|
|
|
caller,
|
|
|
|
() => `'${scriptname}' on '${server.hostname}' with ${threads} threads and args: ${arrayToString(args)}.`,
|
|
|
|
);
|
|
|
|
const runningScriptObj = new RunningScript(script, args);
|
|
|
|
runningScriptObj.threads = threads;
|
|
|
|
runningScriptObj.server = server.hostname;
|
|
|
|
|
2022-08-28 02:56:12 +02:00
|
|
|
return startWorkerScript(runningScriptObj, server, workerScript);
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
2019-07-14 05:55:58 +02:00
|
|
|
|
2021-11-27 00:30:58 +01:00
|
|
|
workerScript.log(caller, () => `Could not find script '${scriptname}' on '${server.hostname}'`);
|
2021-09-05 01:09:30 +02:00
|
|
|
return 0;
|
2019-05-05 06:03:40 +02:00
|
|
|
}
|