2017-08-30 19:44:29 +02:00
|
|
|
import {addActiveScriptsItem,
|
|
|
|
deleteActiveScriptsItem,
|
|
|
|
updateActiveScriptsItems} from "./ActiveScriptsUI.js";
|
|
|
|
import {CONSTANTS} from "./Constants.js";
|
|
|
|
import {Engine} from "./engine.js";
|
|
|
|
import {Environment} from "./NetscriptEnvironment.js";
|
2017-11-01 23:56:30 +01:00
|
|
|
import {evaluate, isScriptErrorMessage,
|
2018-05-06 00:13:35 +02:00
|
|
|
makeRuntimeRejectMsg,
|
2017-11-01 23:56:30 +01:00
|
|
|
killNetscriptDelay} from "./NetscriptEvaluator.js";
|
2018-05-06 00:13:35 +02:00
|
|
|
import {executeJSScript} from "./NetscriptJSEvaluator.js";
|
2018-03-03 22:05:33 +01:00
|
|
|
import {NetscriptPort} from "./NetscriptPort.js";
|
2017-08-30 19:44:29 +02:00
|
|
|
import {AllServers} from "./Server.js";
|
|
|
|
import {Settings} from "./Settings.js";
|
|
|
|
|
|
|
|
import {parse} from "../utils/acorn.js";
|
|
|
|
import {dialogBoxCreate} from "../utils/DialogBox.js";
|
|
|
|
import {compareArrays, printArray} from "../utils/HelperFunctions.js";
|
2017-07-27 04:56:14 +02:00
|
|
|
|
2017-06-17 04:53:57 +02:00
|
|
|
function WorkerScript(runningScriptObj) {
|
|
|
|
this.name = runningScriptObj.filename;
|
2016-11-30 00:38:50 +01:00
|
|
|
this.running = false;
|
2016-12-02 22:57:20 +01:00
|
|
|
this.serverIp = null;
|
2017-06-17 04:53:57 +02:00
|
|
|
this.code = runningScriptObj.scriptRef.code;
|
2017-06-28 11:47:42 +02:00
|
|
|
this.env = new Environment(this);
|
2018-02-24 23:55:06 +01:00
|
|
|
this.env.set("args", runningScriptObj.args.slice());
|
2016-12-02 22:57:20 +01:00
|
|
|
this.output = "";
|
2016-12-19 19:20:19 +01:00
|
|
|
this.ramUsage = 0;
|
2017-06-17 04:53:57 +02:00
|
|
|
this.scriptRef = runningScriptObj;
|
2017-05-01 07:39:48 +02:00
|
|
|
this.errorMessage = "";
|
2018-02-24 23:55:06 +01:00
|
|
|
this.args = runningScriptObj.args.slice();
|
2017-11-01 23:56:30 +01:00
|
|
|
this.delay = null;
|
2017-10-12 04:00:22 +02:00
|
|
|
this.fnWorker = null; //Workerscript for a function call
|
2018-01-27 07:52:39 +01:00
|
|
|
this.checkingRam = false;
|
|
|
|
this.loadedFns = {}; //Stores names of fns that are "loaded" by this script, thus using RAM
|
2018-02-24 23:55:06 +01:00
|
|
|
this.disableLogs = {}; //Stores names of fns that should have logs disabled
|
2016-12-19 19:20:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//Returns the server on which the workerScript is running
|
|
|
|
WorkerScript.prototype.getServer = function() {
|
|
|
|
return AllServers[this.serverIp];
|
2016-11-28 23:02:06 +01:00
|
|
|
}
|
|
|
|
|
2016-11-30 00:07:24 +01:00
|
|
|
//Array containing all scripts that are running across all servers, to easily run them all
|
2017-08-30 19:44:29 +02:00
|
|
|
let workerScripts = [];
|
2016-11-28 23:02:06 +01:00
|
|
|
|
2018-03-03 22:05:33 +01:00
|
|
|
var NetscriptPorts = [];
|
|
|
|
for (var i = 0; i < CONSTANTS.NumNetscriptPorts; ++i) {
|
|
|
|
NetscriptPorts.push(new NetscriptPort());
|
2017-07-22 00:54:55 +02:00
|
|
|
}
|
|
|
|
|
2017-08-30 19:44:29 +02:00
|
|
|
function prestigeWorkerScripts() {
|
|
|
|
for (var i = 0; i < workerScripts.length; ++i) {
|
|
|
|
deleteActiveScriptsItem(workerScripts[i]);
|
|
|
|
workerScripts[i].env.stopFlag = true;
|
|
|
|
}
|
|
|
|
workerScripts.length = 0;
|
|
|
|
}
|
|
|
|
|
2018-05-06 00:13:35 +02:00
|
|
|
// JS script promises need a little massaging to have the same guarantees as netscript
|
|
|
|
// promises. This does said massaging and kicks the script off. It returns a promise
|
|
|
|
// that resolves or rejects when the corresponding worker script is done.
|
|
|
|
function startJsScript(workerScript) {
|
|
|
|
workerScript.running = true;
|
|
|
|
|
2018-05-06 02:39:34 +02:00
|
|
|
// The name of the currently running netscript function, to prevent concurrent
|
|
|
|
// calls to hack, grow, etc.
|
|
|
|
let runningFn = null;
|
|
|
|
|
2018-05-06 00:13:35 +02:00
|
|
|
// We need to go through the environment and wrap each function in such a way that it
|
|
|
|
// can be called at most once at a time. This will prevent situations where multiple
|
|
|
|
// hack promises are outstanding, for example.
|
|
|
|
function wrap(propName, f) {
|
|
|
|
// This function unfortunately cannot be an async function, because we don't
|
|
|
|
// know if the original one was, and there's no way to tell.
|
|
|
|
return function (...args) {
|
|
|
|
const msg = "Concurrent calls to Netscript functions not allowed! " +
|
|
|
|
"Did you forget to await hack(), grow(), or some other " +
|
|
|
|
"promise-returning function? (Currently running: %s tried to run: %s)"
|
2018-05-06 02:39:34 +02:00
|
|
|
if (runningFn) {
|
|
|
|
workerScript.errorMessage = makeRuntimeRejectMsg(workerScript, sprintf(msg, runningFn, propName), null)
|
2018-05-06 00:13:35 +02:00
|
|
|
throw workerScript;
|
|
|
|
}
|
2018-05-06 02:39:34 +02:00
|
|
|
runningFn = propName;
|
2018-05-06 00:13:35 +02:00
|
|
|
let result = f(...args);
|
|
|
|
if (result && result.finally !== undefined) {
|
|
|
|
return result.finally(function () {
|
2018-05-06 02:39:34 +02:00
|
|
|
runningFn = null;
|
2018-05-06 00:13:35 +02:00
|
|
|
});
|
|
|
|
} else {
|
2018-05-06 02:39:34 +02:00
|
|
|
runningFn = null;
|
2018-05-06 00:13:35 +02:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
for (let prop in workerScript.env.vars) {
|
|
|
|
if (typeof workerScript.env.vars[prop] !== "function") continue;
|
|
|
|
if (prop === "sleep") continue; // OK for multiple simultaneous calls to sleep.
|
|
|
|
workerScript.env.vars[prop] = wrap(prop, workerScript.env.vars[prop]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Note: the environment that we pass to the JS script only needs to contain the functions visible
|
|
|
|
// to that script, which env.vars does at this point.
|
|
|
|
return executeJSScript(workerScript.scriptRef.scriptRef,
|
|
|
|
workerScript.getServer().scripts,
|
|
|
|
workerScript.env.vars).then(function (mainReturnValue) {
|
|
|
|
if (mainReturnValue === undefined) return workerScript;
|
|
|
|
return [mainReturnValue, workerScript];
|
|
|
|
}).catch(e => {
|
|
|
|
if (e instanceof Error) {
|
2018-05-06 02:39:34 +02:00
|
|
|
workerScript.errorMessage = makeRuntimeRejectMsg(
|
|
|
|
workerScript, e.message + (e.stack && ("\nstack:\n" + e.stack.toString()) || ""));
|
2018-05-06 00:13:35 +02:00
|
|
|
throw workerScript;
|
|
|
|
} else if (isScriptErrorMessage(e)) {
|
|
|
|
workerScript.errorMessage = e;
|
|
|
|
throw workerScript;
|
|
|
|
}
|
|
|
|
throw e; // Don't know what to do with it, let's rethrow.
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-11-28 23:02:06 +01:00
|
|
|
//Loop through workerScripts and run every script that is not currently running
|
|
|
|
function runScriptsLoop() {
|
2017-09-08 04:56:27 +02:00
|
|
|
//Delete any scripts that finished or have been killed. Loop backwards bc removing
|
|
|
|
//items fucks up the indexing
|
|
|
|
for (var i = workerScripts.length - 1; i >= 0; i--) {
|
|
|
|
if (workerScripts[i].running == false && workerScripts[i].env.stopFlag == true) {
|
|
|
|
//Delete script from the runningScripts array on its host serverIp
|
|
|
|
var ip = workerScripts[i].serverIp;
|
|
|
|
var name = workerScripts[i].name;
|
|
|
|
|
2018-01-09 21:48:06 +01:00
|
|
|
//Free RAM
|
|
|
|
AllServers[ip].ramUsed -= workerScripts[i].ramUsage;
|
|
|
|
|
|
|
|
//Delete script from Active Scripts
|
|
|
|
deleteActiveScriptsItem(workerScripts[i]);
|
|
|
|
|
2017-09-08 04:56:27 +02:00
|
|
|
for (var j = 0; j < AllServers[ip].runningScripts.length; j++) {
|
|
|
|
if (AllServers[ip].runningScripts[j].filename == name &&
|
|
|
|
compareArrays(AllServers[ip].runningScripts[j].args, workerScripts[i].args)) {
|
|
|
|
AllServers[ip].runningScripts.splice(j, 1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//Delete script from workerScripts
|
|
|
|
workerScripts.splice(i, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-05 23:31:46 +01:00
|
|
|
//Run any scripts that haven't been started
|
2016-11-28 23:02:06 +01:00
|
|
|
for (var i = 0; i < workerScripts.length; i++) {
|
2016-12-05 23:31:46 +01:00
|
|
|
//If it isn't running, start the script
|
2016-12-06 17:59:20 +01:00
|
|
|
if (workerScripts[i].running == false && workerScripts[i].env.stopFlag == false) {
|
2018-05-06 00:13:35 +02:00
|
|
|
let p = null; // p is the script's result promise.
|
|
|
|
if (workerScripts[i].name.endsWith(".js")) {
|
|
|
|
p = startJsScript(workerScripts[i]);
|
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
var ast = parse(workerScripts[i].code, {sourceType:"module"});
|
|
|
|
//console.log(ast);
|
|
|
|
} catch (e) {
|
|
|
|
console.log("Error parsing script: " + workerScripts[i].name);
|
|
|
|
dialogBoxCreate("Syntax ERROR in " + workerScripts[i].name + ":<br>" + e);
|
|
|
|
workerScripts[i].env.stopFlag = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
workerScripts[i].running = true;
|
|
|
|
p = evaluate(ast, workerScripts[i]);
|
|
|
|
}
|
|
|
|
|
2016-12-05 23:31:46 +01:00
|
|
|
//Once the code finishes (either resolved or rejected, doesnt matter), set its
|
|
|
|
//running status to false
|
2016-12-06 17:59:20 +01:00
|
|
|
p.then(function(w) {
|
2016-12-14 21:29:40 +01:00
|
|
|
console.log("Stopping script " + w.name + " because it finished running naturally");
|
2016-12-06 17:59:20 +01:00
|
|
|
w.running = false;
|
|
|
|
w.env.stopFlag = true;
|
2017-06-17 04:53:57 +02:00
|
|
|
w.scriptRef.log("Script finished running");
|
2017-11-01 23:56:30 +01:00
|
|
|
}).catch(function(w) {
|
2016-12-15 23:22:42 +01:00
|
|
|
if (w instanceof Error) {
|
2017-06-17 04:53:57 +02:00
|
|
|
dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer");
|
2017-08-30 19:44:29 +02:00
|
|
|
console.log("ERROR: Evaluating workerscript returns an Error. THIS SHOULDN'T HAPPEN: " + w.toString());
|
2017-06-17 04:53:57 +02:00
|
|
|
return;
|
2017-10-10 06:56:48 +02:00
|
|
|
} else if (w.constructor === Array && w.length === 2 && w[0] === "RETURNSTATEMENT") {
|
|
|
|
//Script ends with a return statement
|
|
|
|
console.log("Script returning with value: " + w[1]);
|
|
|
|
//TODO maybe do something with this in the future
|
|
|
|
return;
|
2017-05-01 07:39:48 +02:00
|
|
|
} else if (w instanceof WorkerScript) {
|
|
|
|
if (isScriptErrorMessage(w.errorMessage)) {
|
|
|
|
var errorTextArray = w.errorMessage.split("|");
|
|
|
|
if (errorTextArray.length != 4) {
|
|
|
|
console.log("ERROR: Something wrong with Error text in evaluator...");
|
|
|
|
console.log("Error text: " + errorText);
|
2017-05-31 02:00:24 +02:00
|
|
|
return;
|
2017-05-01 07:39:48 +02:00
|
|
|
}
|
|
|
|
var serverIp = errorTextArray[1];
|
|
|
|
var scriptName = errorTextArray[2];
|
|
|
|
var errorMsg = errorTextArray[3];
|
2017-07-27 04:56:14 +02:00
|
|
|
|
|
|
|
dialogBoxCreate("Script runtime error: <br>Server Ip: " + serverIp +
|
|
|
|
"<br>Script name: " + scriptName +
|
2017-06-17 04:53:57 +02:00
|
|
|
"<br>Args:" + printArray(w.args) + "<br>" + errorMsg);
|
|
|
|
w.scriptRef.log("Script crashed with runtime error");
|
|
|
|
} else {
|
|
|
|
w.scriptRef.log("Script killed");
|
2017-05-01 07:39:48 +02:00
|
|
|
}
|
2016-12-15 23:22:42 +01:00
|
|
|
w.running = false;
|
|
|
|
w.env.stopFlag = true;
|
2017-07-27 04:56:14 +02:00
|
|
|
|
2017-05-31 02:00:24 +02:00
|
|
|
} else if (isScriptErrorMessage(w)) {
|
2017-06-17 04:53:57 +02:00
|
|
|
dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer");
|
2017-08-30 19:44:29 +02:00
|
|
|
console.log("ERROR: Evaluating workerscript returns only error message rather than WorkerScript object. THIS SHOULDN'T HAPPEN: " + w.toString());
|
2017-06-17 04:53:57 +02:00
|
|
|
return;
|
2017-06-07 06:09:53 +02:00
|
|
|
} else {
|
|
|
|
dialogBoxCreate("An unknown script died for an unknown reason. This is a bug please contact game dev");
|
2017-05-31 02:00:24 +02:00
|
|
|
}
|
2016-12-05 23:31:46 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2017-07-27 04:56:14 +02:00
|
|
|
|
2017-09-15 16:06:59 +02:00
|
|
|
setTimeout(runScriptsLoop, 6000);
|
2016-11-28 23:02:06 +01:00
|
|
|
}
|
|
|
|
|
2016-12-06 19:09:23 +01:00
|
|
|
//Queues a script to be killed by settings its stop flag to true. Then, the code will reject
|
|
|
|
//all of its promises recursively, and when it does so it will no longer be running.
|
|
|
|
//The runScriptsLoop() will then delete the script from worker scripts
|
2017-06-17 04:53:57 +02:00
|
|
|
function killWorkerScript(runningScriptObj, serverIp) {
|
2016-12-06 19:09:23 +01:00
|
|
|
for (var i = 0; i < workerScripts.length; i++) {
|
2017-06-17 04:53:57 +02:00
|
|
|
if (workerScripts[i].name == runningScriptObj.filename && workerScripts[i].serverIp == serverIp &&
|
|
|
|
compareArrays(workerScripts[i].args, runningScriptObj.args)) {
|
2016-12-06 19:09:23 +01:00
|
|
|
workerScripts[i].env.stopFlag = true;
|
2017-11-01 23:56:30 +01:00
|
|
|
killNetscriptDelay(workerScripts[i]);
|
2018-03-03 22:05:33 +01:00
|
|
|
//Recursively kill all functions
|
|
|
|
var curr = workerScripts[i];
|
|
|
|
while (curr.fnWorker) {
|
|
|
|
curr.fnWorker.env.stopFlag = true;
|
|
|
|
killNetscriptDelay(curr.fnWorker);
|
|
|
|
curr = curr.fnWorker;
|
2017-10-12 04:00:22 +02:00
|
|
|
}
|
2017-06-07 06:09:53 +02:00
|
|
|
return true;
|
2016-12-06 19:09:23 +01:00
|
|
|
}
|
|
|
|
}
|
2017-06-07 06:09:53 +02:00
|
|
|
return false;
|
2016-12-06 19:09:23 +01:00
|
|
|
}
|
|
|
|
|
2017-07-27 04:56:14 +02:00
|
|
|
//Queues a script to be run
|
2017-06-17 04:53:57 +02:00
|
|
|
function addWorkerScript(runningScriptObj, server) {
|
|
|
|
var filename = runningScriptObj.filename;
|
2017-07-27 04:56:14 +02:00
|
|
|
|
2016-12-14 21:29:40 +01:00
|
|
|
//Update server's ram usage
|
2017-06-11 23:07:38 +02:00
|
|
|
var threads = 1;
|
2017-06-17 04:53:57 +02:00
|
|
|
if (runningScriptObj.threads && !isNaN(runningScriptObj.threads)) {
|
|
|
|
threads = runningScriptObj.threads;
|
2017-06-11 23:07:38 +02:00
|
|
|
} else {
|
2017-06-17 05:32:58 +02:00
|
|
|
runningScriptObj.threads = 1;
|
2017-06-11 23:07:38 +02:00
|
|
|
}
|
2017-07-27 04:56:14 +02:00
|
|
|
var ramUsage = runningScriptObj.scriptRef.ramUsage * threads
|
2017-06-20 18:11:33 +02:00
|
|
|
* Math.pow(CONSTANTS.MultithreadingRAMCost, threads-1);
|
2017-09-29 17:02:33 +02:00
|
|
|
var ramAvailable = server.maxRam - server.ramUsed;
|
|
|
|
if (ramUsage > ramAvailable) {
|
|
|
|
dialogBoxCreate("Not enough RAM to run script " + runningScriptObj.filename + " with args " +
|
|
|
|
printArray(runningScriptObj.args) + ". This likely occurred because you re-loaded " +
|
|
|
|
"the game and the script's RAM usage increased (either because of an update to the game or " +
|
|
|
|
"your changes to the script.)");
|
|
|
|
return;
|
|
|
|
}
|
2017-06-11 03:46:02 +02:00
|
|
|
server.ramUsed += ramUsage;
|
2017-07-27 04:56:14 +02:00
|
|
|
|
2016-12-19 19:20:19 +01:00
|
|
|
//Create the WorkerScript
|
2017-06-17 04:53:57 +02:00
|
|
|
var s = new WorkerScript(runningScriptObj);
|
2016-12-14 21:29:40 +01:00
|
|
|
s.serverIp = server.ip;
|
2017-06-11 03:46:02 +02:00
|
|
|
s.ramUsage = ramUsage;
|
2017-07-27 04:56:14 +02:00
|
|
|
|
2016-12-19 19:20:19 +01:00
|
|
|
//Add the WorkerScript to the Active Scripts list
|
2017-06-06 23:22:57 +02:00
|
|
|
addActiveScriptsItem(s);
|
2017-07-27 04:56:14 +02:00
|
|
|
|
2016-12-19 19:20:19 +01:00
|
|
|
//Add the WorkerScript
|
2016-12-14 21:29:40 +01:00
|
|
|
workerScripts.push(s);
|
2016-12-15 18:51:23 +01:00
|
|
|
return;
|
2016-12-14 21:29:40 +01:00
|
|
|
}
|
|
|
|
|
2016-12-19 19:20:19 +01:00
|
|
|
//Updates the online running time stat of all running scripts
|
|
|
|
function updateOnlineScriptTimes(numCycles = 1) {
|
|
|
|
var time = (numCycles * Engine._idleSpeed) / 1000; //seconds
|
|
|
|
for (var i = 0; i < workerScripts.length; ++i) {
|
|
|
|
workerScripts[i].scriptRef.onlineRunningTime += time;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-30 19:44:29 +02:00
|
|
|
export {WorkerScript, workerScripts, NetscriptPorts, runScriptsLoop,
|
|
|
|
killWorkerScript, addWorkerScript, updateOnlineScriptTimes,
|
|
|
|
prestigeWorkerScripts};
|