bitburner-src/src/NetscriptWorker.ts

492 lines
18 KiB
TypeScript
Raw Normal View History

/**
* Functions for handling WorkerScripts, which are the underlying mechanism
* that allows for scripts to run
*/
import { killWorkerScript } from "./Netscript/killWorkerScript";
import { ScriptDeath } from "./Netscript/ScriptDeath";
import { WorkerScript } from "./Netscript/WorkerScript";
import { workerScripts } from "./Netscript/WorkerScripts";
import { generateNextPid } from "./Netscript/Pid";
import { CONSTANTS } from "./Constants";
2021-09-25 00:42:13 +02:00
import { Interpreter } from "./ThirdParty/JSInterpreter";
import { NetscriptFunctions } from "./NetscriptFunctions";
2022-08-29 08:41:17 +02:00
import { compile, Node } from "./NetscriptJSEvaluator";
import { Port, PortNumber } from "./NetscriptPort";
import { RunningScript } from "./Script/RunningScript";
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";
import { Settings } from "./Settings/Settings";
import { generate } from "escodegen";
2022-09-05 15:55:57 +02:00
import { dialogBoxCreate } from "./ui/React/DialogBox";
import { formatRam } from "./ui/formatNumber";
import { arrayToString } from "./utils/helpers/ArrayHelpers";
2021-09-25 20:42:57 +02:00
import { roundToTwo } from "./utils/helpers/roundToTwo";
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";
import { parseCommand } from "./Terminal/Parser";
import { Terminal } from "./Terminal";
import { ScriptArg } from "@nsdefs";
import { CompleteRunOptions, getRunningScriptsByArgs } from "./Netscript/NetscriptHelpers";
import { handleUnknownError } from "./Netscript/ErrorMessages";
import { isLegacyScript, legacyScriptExtension, resolveScriptFilePath, ScriptFilePath } from "./Paths/ScriptFilePath";
FILES: Path rework & typesafety (#479) * Added new types for various file paths, all in the Paths folder. * TypeSafety and other helper functions related to these types * Added basic globbing support with * and ?. Currently only implemented for Script/Text, on nano and download terminal commands * Enforcing the new types throughout the codebase, plus whatever rewrites happened along the way * Server.textFiles is now a map * TextFile no longer uses a fn property, now it is filename * Added a shared ContentFile interface for shared functionality between TextFile and Script. * related to ContentFile change above, the player is now allowed to move a text file to a script file and vice versa. * File paths no longer conditionally start with slashes, and all directory names other than root have ending slashes. The player is still able to provide paths starting with / but this now indicates that the player is specifying an absolute path instead of one relative to root. * Singularized the MessageFilename and LiteratureName enums * Because they now only accept correct types, server.writeToXFile functions now always succeed (the only reasons they could fail before were invalid filepath). * Fix several issues with tab completion, which included pretty much a complete rewrite * Changed the autocomplete display options so there's less chance it clips outside the display area. * Turned CompletedProgramName into an enum. * Got rid of programsMetadata, and programs and DarkWebItems are now initialized immediately instead of relying on initializers called from the engine. * For any executable (program, cct, or script file) pathing can be used directly to execute without using the run command (previously the command had to start with ./ and it wasn't actually using pathing).
2023-04-24 16:26:57 +02:00
import { root } from "./Paths/Directory";
export const NetscriptPorts = new Map<PortNumber, Port>();
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()) {
killWorkerScript(ws);
}
2022-09-01 16:27:31 +02:00
NetscriptPorts.clear();
}
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) throw "workerScript had no associated script. This is a bug.";
2022-08-29 08:41:17 +02:00
const ns = workerScript.env.vars;
if (!ns) throw `${script.filename} cannot be run because the NS object hasn't been constructed properly.`;
const loadedModule = await compile(script, scripts);
2022-08-29 08:41:17 +02:00
if (!loadedModule) throw `${script.filename} cannot be run because the script module won't load`;
const mainFunc = loadedModule.main;
// TODO unplanned: Better error for "unexpected reserved word" when using await in non-async function?
if (typeof mainFunc !== "function")
2022-08-29 08:41:17 +02:00
throw `${script.filename} cannot be run because it does not have a main function.`;
// Explicitly called from a variable so that we don't bind "this".
await mainFunc(ns);
2022-08-29 08:41:17 +02:00
}
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
}
//TODO unplanned: Make NS1 wrapping type safe instead of using BasicObject.
type BasicObject = Record<string, any>;
const wrappedNS = NetscriptFunctions(workerScript);
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;
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));
} 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
}
}
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));
}
}
/* 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
we'll implement it ourselves by parsing the Nodes in the AST out.
@param code - The script's code
@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
}
*/
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");
}
FILES: Path rework & typesafety (#479) * Added new types for various file paths, all in the Paths folder. * TypeSafety and other helper functions related to these types * Added basic globbing support with * and ?. Currently only implemented for Script/Text, on nano and download terminal commands * Enforcing the new types throughout the codebase, plus whatever rewrites happened along the way * Server.textFiles is now a map * TextFile no longer uses a fn property, now it is filename * Added a shared ContentFile interface for shared functionality between TextFile and Script. * related to ContentFile change above, the player is now allowed to move a text file to a script file and vice versa. * File paths no longer conditionally start with slashes, and all directory names other than root have ending slashes. The player is still able to provide paths starting with / but this now indicates that the player is specifying an absolute path instead of one relative to root. * Singularized the MessageFilename and LiteratureName enums * Because they now only accept correct types, server.writeToXFile functions now always succeed (the only reasons they could fail before were invalid filepath). * Fix several issues with tab completion, which included pretty much a complete rewrite * Changed the autocomplete display options so there's less chance it clips outside the display area. * Turned CompletedProgramName into an enum. * Got rid of programsMetadata, and programs and DarkWebItems are now initialized immediately instead of relying on initializers called from the engine. * For any executable (program, cct, or script file) pathing can be used directly to execute without using the run command (previously the command had to start with ./ and it wasn't actually using pathing).
2023-04-24 16:26:57 +02:00
function getScript(scriptName: ScriptFilePath): Script | null {
return server.scripts.get(scriptName) ?? null;
2021-09-05 01:09:30 +02:00
}
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;
const scriptName = resolveScriptFilePath(node.source.value, root, legacyScriptExtension);
FILES: Path rework & typesafety (#479) * Added new types for various file paths, all in the Paths folder. * TypeSafety and other helper functions related to these types * Added basic globbing support with * and ?. Currently only implemented for Script/Text, on nano and download terminal commands * Enforcing the new types throughout the codebase, plus whatever rewrites happened along the way * Server.textFiles is now a map * TextFile no longer uses a fn property, now it is filename * Added a shared ContentFile interface for shared functionality between TextFile and Script. * related to ContentFile change above, the player is now allowed to move a text file to a script file and vice versa. * File paths no longer conditionally start with slashes, and all directory names other than root have ending slashes. The player is still able to provide paths starting with / but this now indicates that the player is specifying an absolute path instead of one relative to root. * Singularized the MessageFilename and LiteratureName enums * Because they now only accept correct types, server.writeToXFile functions now always succeed (the only reasons they could fail before were invalid filepath). * Fix several issues with tab completion, which included pretty much a complete rewrite * Changed the autocomplete display options so there's less chance it clips outside the display area. * Turned CompletedProgramName into an enum. * Got rid of programsMetadata, and programs and DarkWebItems are now initialized immediately instead of relying on initializers called from the engine. * For any executable (program, cct, or script file) pathing can be used directly to execute without using the run command (previously the command had to start with ./ and it wasn't actually using pathing).
2023-04-24 16:26:57 +02:00
if (!scriptName) throw new Error("'Import' failed due to invalid path: " + scriptName);
2021-09-25 07:26:03 +02:00
const script = getScript(scriptName);
FILES: Path rework & typesafety (#479) * Added new types for various file paths, all in the Paths folder. * TypeSafety and other helper functions related to these types * Added basic globbing support with * and ?. Currently only implemented for Script/Text, on nano and download terminal commands * Enforcing the new types throughout the codebase, plus whatever rewrites happened along the way * Server.textFiles is now a map * TextFile no longer uses a fn property, now it is filename * Added a shared ContentFile interface for shared functionality between TextFile and Script. * related to ContentFile change above, the player is now allowed to move a text file to a script file and vice versa. * File paths no longer conditionally start with slashes, and all directory names other than root have ending slashes. The player is still able to provide paths starting with / but this now indicates that the player is specifying an absolute path instead of one relative to root. * Singularized the MessageFilename and LiteratureName enums * Because they now only accept correct types, server.writeToXFile functions now always succeed (the only reasons they could fail before were invalid filepath). * Fix several issues with tab completion, which included pretty much a complete rewrite * Changed the autocomplete display options so there's less chance it clips outside the display area. * Turned CompletedProgramName into an enum. * Got rid of programsMetadata, and programs and DarkWebItems are now initialized immediately instead of relying on initializers called from the engine. * For any executable (program, cct, or script file) pathing can be used directly to execute without using the run command (previously the command had to start with ./ and it wasn't actually using pathing).
2023-04-24 16:26:57 +02:00
if (!script) throw new Error("'Import' failed due to script not found: " + 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
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
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);
}
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;
}
2021-09-05 01:09:30 +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;
2021-09-05 01:09:30 +02:00
//Convert the AST back into code
code = generate(ast);
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;
2021-09-25 07:26:03 +02:00
const res = {
2021-09-05 01:09:30 +02:00
code: code,
lineOffset: lineOffset,
};
return res;
}
/**
* Used to start a RunningScript (by creating and starting its
* corresponding WorkerScript), and add the RunningScript to the server on which
* it is active
*/
export function startWorkerScript(runningScript: RunningScript, server: BaseServer, parent?: WorkerScript): number {
if (server.hostname !== runningScript.server) {
// Temporarily adding a check here to see if this ever triggers
console.error(
`Tried to launch a worker script on a different server ${server.hostname} than the runningScript's server ${runningScript.server}`,
);
return 0;
}
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);
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;
}
/**
* Given a RunningScript object, constructs its corresponding WorkerScript,
* 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
* returns {boolean} indicating whether or not the workerScript was successfully added
*/
function createAndAddWorkerScript(runningScriptObj: RunningScript, server: BaseServer, parent?: WorkerScript): boolean {
const ramUsage = roundToTwo(runningScriptObj.ramUsage * runningScriptObj.threads);
2021-09-05 01:09:30 +02:00
const ramAvailable = server.maxRam - server.ramUsed;
// Check failure conditions before generating the workersScript and return false
if (ramUsage > ramAvailable + 0.001) {
deferredError(
`Not enough RAM to run script ${runningScriptObj.filename} with args ${arrayToString(
runningScriptObj.args,
)}, needed ${formatRam(ramUsage)} but only have ${formatRam(ramAvailable)} free
If you are seeing this on startup, likely causes are that the autoexec script is too big to fit in RAM, or it took up too much space and other previously running scripts couldn't fit on home.
Otherwise, this 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
2021-09-05 01:09:30 +02:00
// Get the pid
const pid = generateNextPid();
if (pid === -1) {
deferredError(
2021-09-05 01:09:30 +02:00
`Failed to start script because could not find available PID. This is most ` +
`because you have too many scripts running.`,
);
return false;
2021-09-05 01:09:30 +02:00
}
server.updateRamUsed(roundToTwo(server.ramUsed + ramUsage));
2021-09-05 01:09:30 +02:00
// Create the WorkerScript. NOTE: WorkerScript ctor will set the underlying
// RunningScript's PID as well
const workerScript = new WorkerScript(runningScriptObj, pid, NetscriptFunctions);
2021-09-05 01:09:30 +02:00
// Add the WorkerScript to the global pool
workerScripts.set(pid, workerScript);
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
(isLegacyScript(workerScript.name) ? startNetscript1Script : startNetscript2Script)(workerScript)
2022-08-29 08:41:17 +02:00
// 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) {
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;
}
/** 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;
}
}
// Needed for popping dialog boxes in functions that run *before* the UI is
// created, and thus before AlertManager exists to listen to the alerts we
// create.
function deferredError(msg: string) {
setTimeout(() => dialogBoxCreate(msg), 0);
}
function createAutoexec(server: BaseServer): RunningScript | null {
const args = parseCommand(Settings.AutoexecScript);
if (args.length === 0) return null;
const cmd = String(args[0]);
const scriptPath = resolveScriptFilePath(cmd);
if (!scriptPath) {
deferredError(`While running autoexec script:
"${cmd}" is invalid for a script name (maybe missing suffix?)`);
return null;
}
const script = server.scripts.get(scriptPath);
if (!script) {
deferredError(`While running autoexec script:
"${cmd}" does not exist!`);
return null;
}
const ramUsage = script.getRamUsage(server.scripts);
if (ramUsage === null) {
deferredError(`While running autoexec script:
"${cmd}" has errors!`);
return null;
}
args.shift();
const rs = new RunningScript(script, ramUsage, args);
rs.temporary = true;
return rs;
}
/**
* 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 {
const skipScriptLoad = window.location.href.toLowerCase().includes("?noscripts");
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
const rsList = server.savedScripts;
server.savedScripts = undefined;
if (skipScriptLoad || !rsList) {
2021-10-07 22:04:04 +02:00
// Start game with no scripts
continue;
}
if (server.hostname === "home") {
// Push autoexec script onto the front of the list
const runningScript = createAutoexec(server);
if (runningScript) {
rsList.unshift(runningScript);
}
}
for (const runningScript of rsList) {
startWorkerScript(runningScript, server);
scriptCalculateOfflineProduction(runningScript);
2021-09-05 01:09:30 +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,
host: BaseServer,
FILES: Path rework & typesafety (#479) * Added new types for various file paths, all in the Paths folder. * TypeSafety and other helper functions related to these types * Added basic globbing support with * and ?. Currently only implemented for Script/Text, on nano and download terminal commands * Enforcing the new types throughout the codebase, plus whatever rewrites happened along the way * Server.textFiles is now a map * TextFile no longer uses a fn property, now it is filename * Added a shared ContentFile interface for shared functionality between TextFile and Script. * related to ContentFile change above, the player is now allowed to move a text file to a script file and vice versa. * File paths no longer conditionally start with slashes, and all directory names other than root have ending slashes. The player is still able to provide paths starting with / but this now indicates that the player is specifying an absolute path instead of one relative to root. * Singularized the MessageFilename and LiteratureName enums * Because they now only accept correct types, server.writeToXFile functions now always succeed (the only reasons they could fail before were invalid filepath). * Fix several issues with tab completion, which included pretty much a complete rewrite * Changed the autocomplete display options so there's less chance it clips outside the display area. * Turned CompletedProgramName into an enum. * Got rid of programsMetadata, and programs and DarkWebItems are now initialized immediately instead of relying on initializers called from the engine. * For any executable (program, cct, or script file) pathing can be used directly to execute without using the run command (previously the command had to start with ./ and it wasn't actually using pathing).
2023-04-24 16:26:57 +02:00
scriptname: ScriptFilePath,
2022-07-18 08:28:21 +02:00
args: ScriptArg[],
2021-09-24 23:56:30 +02:00
workerScript: WorkerScript,
runOpts: CompleteRunOptions,
2021-09-24 23:56:30 +02:00
): number {
const script = host.scripts.get(scriptname);
if (!script) {
workerScript.log(caller, () => `Could not find script '${scriptname}' on '${host.hostname}'`);
2021-09-05 01:09:30 +02:00
return 0;
}
// Check if script is already running on server and fail if it is.
if (
runOpts.preventDuplicates &&
getRunningScriptsByArgs(
{ workerScript, function: "runScriptFromScript", functionPath: "internal.runScriptFromScript" },
scriptname,
host.hostname,
args,
) !== null
) {
workerScript.log(caller, () => `'${scriptname}' is already running on '${host.hostname}'`);
2021-09-05 01:09:30 +02:00
return 0;
}
const singleRamUsage = runOpts.ramOverride ?? script.getRamUsage(host.scripts);
if (!singleRamUsage) {
workerScript.log(caller, () => `Ram usage could not be calculated for ${scriptname}`);
2021-09-05 01:09:30 +02:00
return 0;
}
// Check if admin rights on host, fail if not.
if (!host.hasAdminRights) {
workerScript.log(caller, () => `You do not have root access on '${host.hostname}'`);
return 0;
2021-09-05 01:09:30 +02:00
}
// Calculate ram usage including thread count
const ramUsage = singleRamUsage * runOpts.threads;
// Check if there is enough ram to run the script, fail if not.
const ramAvailable = host.maxRam - host.ramUsed;
if (ramUsage > ramAvailable + 0.001) {
2022-03-11 16:46:43 +01:00
workerScript.log(
caller,
() =>
`Cannot run script '${scriptname}' (t=${runOpts.threads}) on '${host.hostname}' because there is not enough available RAM!`,
2022-03-11 16:46:43 +01:00
);
return 0;
2021-09-05 01:09:30 +02:00
}
// Able to run script
workerScript.log(
caller,
() => `'${scriptname}' on '${host.hostname}' with ${runOpts.threads} threads and args: ${arrayToString(args)}.`,
);
const runningScriptObj = new RunningScript(script, singleRamUsage, args);
runningScriptObj.threads = runOpts.threads;
runningScriptObj.temporary = runOpts.temporary;
return startWorkerScript(runningScriptObj, host, workerScript);
}