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" ;
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" ;
2019-05-05 06:03:40 +02:00
2019-04-11 10:37:40 +02:00
import { CONSTANTS } from "./Constants" ;
import { Engine } from "./engine" ;
import { Interpreter } from "./JSInterpreter" ;
import {
isScriptErrorMessage ,
makeRuntimeRejectMsg ,
} from "./NetscriptEvaluator" ;
import { NetscriptFunctions } from "./NetscriptFunctions" ;
import { executeJSScript } from "./NetscriptJSEvaluator" ;
import { NetscriptPort } from "./NetscriptPort" ;
2019-05-07 03:01:06 +02:00
import { Player } from "./Player" ;
import { RunningScript } from "./Script/RunningScript" ;
2019-05-05 06:03:40 +02:00
import { getRamUsageFromRunningScript } from "./Script/RunningScriptHelpers" ;
import {
findRunningScript ,
scriptCalculateOfflineProduction ,
} from "./Script/ScriptHelpers" ;
2019-04-11 10:37:40 +02:00
import { AllServers } from "./Server/AllServers" ;
import { Settings } from "./Settings/Settings" ;
import { setTimeoutRef } from "./utils/SetTimeoutRef" ;
2017-08-30 19:44:29 +02:00
2019-04-11 10:37:40 +02:00
import { generate } from "escodegen" ;
import { parse , Node } from "../utils/acorn" ;
import { dialogBoxCreate } from "../utils/DialogBox" ;
import { compareArrays } from "../utils/helpers/compareArrays" ;
import { arrayToString } from "../utils/helpers/arrayToString" ;
import { roundToTwo } from "../utils/helpers/roundToTwo" ;
import { isString } from "../utils/StringHelperFunctions" ;
2018-07-20 16:28:03 +02:00
const walk = require ( "acorn/dist/walk" ) ;
2019-05-16 08:05:36 +02:00
// Netscript Ports are instantiated here
2019-05-05 06:03:40 +02:00
export const NetscriptPorts = [ ] ;
2018-03-03 22:05:33 +01:00
for ( var i = 0 ; i < CONSTANTS . NumNetscriptPorts ; ++ i ) {
NetscriptPorts . push ( new NetscriptPort ( ) ) ;
2017-07-22 00:54:55 +02:00
}
2019-05-05 06:03:40 +02:00
export function prestigeWorkerScripts ( ) {
2019-06-19 10:51:25 +02:00
for ( const ws of workerScripts . values ( ) ) {
ws . env . stopFlag = true ;
2017-08-30 19:44:29 +02:00
}
2019-06-19 10:51:25 +02:00
WorkerScriptStartStopEventEmitter . emitEvent ( ) ;
workerScripts . clear ( ) ;
2017-08-30 19:44:29 +02:00
}
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.
2018-07-09 01:53:24 +02:00
function startNetscript2Script ( workerScript ) {
2018-05-06 00:13:35 +02:00
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 ) {
2018-05-07 04:16:28 +02:00
// Wrap every netscript function with a check for the stop flag.
// This prevents cases where we never stop because we are only calling
// netscript functions that don't check this.
// This is not a problem for legacy Netscript because it also checks the
// stop flag in the evaluator.
2018-05-09 03:40:07 +02:00
if ( workerScript . env . stopFlag ) { throw workerScript ; }
if ( propName === "sleep" ) return f ( ... args ) ; // OK for multiple simultaneous calls to sleep.
2018-05-07 04:16:28 +02:00
2018-05-06 00:13:35 +02:00
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-10-23 20:55:42 +02:00
// If the function throws an error, clear the runningFn flag first, and then re-throw it
// This allows people to properly catch errors thrown by NS functions without getting
// the concurrent call error above
let result ;
try {
result = f ( ... args ) ;
} catch ( e ) {
runningFn = null ;
throw ( e ) ;
}
2018-05-06 00:13:35 +02:00
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 ;
}
}
} ;
2018-05-09 03:40:07 +02:00
2018-05-06 00:13:35 +02:00
for ( let prop in workerScript . env . vars ) {
if ( typeof workerScript . env . vars [ prop ] !== "function" ) continue ;
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.
2018-05-17 19:10:12 +02:00
return executeJSScript ( workerScript . getServer ( ) . scripts ,
2018-05-12 02:45:40 +02:00
workerScript ) . then ( function ( mainReturnValue ) {
2018-05-06 00:13:35 +02:00
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.
} ) ;
}
2018-07-09 01:53:24 +02:00
function startNetscript1Script ( workerScript ) {
2019-05-17 22:41:16 +02:00
const code = workerScript . code ;
2018-07-09 01:53:24 +02:00
workerScript . running = true ;
2018-07-20 16:28:03 +02:00
//Process imports
2018-07-25 21:53:54 +02:00
var codeWithImports , codeLineOffset ;
2018-07-20 16:28:03 +02:00
try {
2018-07-25 21:53:54 +02:00
let importProcessingRes = processNetscript1Imports ( code , workerScript ) ;
codeWithImports = importProcessingRes . code ;
codeLineOffset = importProcessingRes . lineOffset ;
2018-07-20 16:28:03 +02:00
} catch ( e ) {
dialogBoxCreate ( "Error processing Imports in " + workerScript . name + ":<br>" + e ) ;
workerScript . env . stopFlag = true ;
workerScript . running = false ;
return ;
}
2018-07-09 01:53:24 +02:00
var interpreterInitialization = function ( int , scope ) {
//Add the Netscript environment
var ns = NetscriptFunctions ( workerScript ) ;
2018-07-11 06:41:26 +02:00
for ( let name in ns ) {
2018-07-09 01:53:24 +02:00
let entry = ns [ name ] ;
if ( typeof entry === "function" ) {
//Async functions need to be wrapped. See JS-Interpreter documentation
if ( name === "hack" || name === "grow" || name === "weaken" || name === "sleep" ||
name === "prompt" || name === "run" || name === "exec" ) {
let tempWrapper = function ( ) {
let fnArgs = [ ] ;
2019-05-03 10:01:43 +02:00
//All of the Object/array elements are in JSInterpreter format, so
//we have to convert them back to native format to pass them to these fns
2018-07-09 01:53:24 +02:00
for ( let i = 0 ; i < arguments . length - 1 ; ++ i ) {
2019-05-03 10:01:43 +02:00
if ( typeof arguments [ i ] === 'object' || arguments [ i ] . constructor === Array ) {
fnArgs . push ( int . pseudoToNative ( arguments [ i ] ) ) ;
} else {
fnArgs . push ( arguments [ i ] ) ;
}
2018-07-09 01:53:24 +02:00
}
let cb = arguments [ arguments . length - 1 ] ;
let fnPromise = entry . apply ( null , fnArgs ) ;
fnPromise . then ( function ( res ) {
cb ( res ) ;
2019-02-09 03:46:30 +01:00
} ) . catch ( function ( e ) {
// Do nothing?
2018-07-09 01:53:24 +02:00
} ) ;
}
int . setProperty ( scope , name , int . createAsyncFunction ( tempWrapper ) ) ;
2018-08-06 17:25:22 +02:00
} else if ( name === "sprintf" || name === "vsprintf" || name === "scp" ||
name == "write" || name === "read" || name === "tryWrite" ) {
2018-07-25 21:53:54 +02:00
let tempWrapper = function ( ) {
let fnArgs = [ ] ;
//All of the Object/array elements are in JSInterpreter format, so
//we have to convert them back to native format to pass them to these fns
for ( let i = 0 ; i < arguments . length ; ++ i ) {
if ( typeof arguments [ i ] === 'object' || arguments [ i ] . constructor === Array ) {
fnArgs . push ( int . pseudoToNative ( arguments [ i ] ) ) ;
} else {
fnArgs . push ( arguments [ i ] ) ;
}
}
return entry . apply ( null , fnArgs ) ;
}
int . setProperty ( scope , name , int . createNativeFunction ( tempWrapper ) ) ;
2018-07-09 01:53:24 +02:00
} else {
2018-07-11 06:41:26 +02:00
let tempWrapper = function ( ) {
let res = entry . apply ( null , arguments ) ;
if ( res == null ) {
return res ;
} else if ( res . constructor === Array || ( res === Object ( res ) ) ) {
//Objects and Arrays must be converted to the interpreter's format
return int . nativeToPseudo ( res ) ;
} else {
return res ;
}
}
int . setProperty ( scope , name , int . createNativeFunction ( tempWrapper ) ) ;
2018-07-09 01:53:24 +02:00
}
} else {
2018-07-11 06:41:26 +02:00
//bladeburner, or anything else
2018-07-09 01:53:24 +02:00
int . setProperty ( scope , name , int . nativeToPseudo ( entry ) ) ;
}
}
//Add the arguments
int . setProperty ( scope , "args" , int . nativeToPseudo ( workerScript . args ) ) ;
}
2018-07-11 06:41:26 +02:00
var interpreter ;
try {
2018-07-25 21:53:54 +02:00
interpreter = new Interpreter ( codeWithImports , interpreterInitialization , codeLineOffset ) ;
2018-07-11 06:41:26 +02:00
} catch ( e ) {
dialogBoxCreate ( "Syntax ERROR in " + workerScript . name + ":<br>" + e ) ;
workerScript . env . stopFlag = true ;
workerScript . running = false ;
return ;
}
2018-07-09 01:53:24 +02:00
return new Promise ( function ( resolve , reject ) {
function runInterpreter ( ) {
try {
2019-02-09 03:46:30 +01:00
if ( workerScript . env . stopFlag ) { return reject ( workerScript ) ; }
2018-07-09 01:53:24 +02:00
if ( interpreter . step ( ) ) {
2019-02-20 09:42:27 +01:00
setTimeoutRef ( runInterpreter , Settings . CodeInstructionRunTime ) ;
2018-07-09 01:53:24 +02:00
} else {
resolve ( workerScript ) ;
}
} catch ( e ) {
2018-07-15 20:03:33 +02:00
e = e . toString ( ) ;
if ( ! isScriptErrorMessage ( e ) ) {
e = makeRuntimeRejectMsg ( workerScript , e ) ;
2018-07-09 01:53:24 +02:00
}
2018-07-15 20:03:33 +02:00
workerScript . errorMessage = e ;
return reject ( workerScript ) ;
2018-07-09 01:53:24 +02:00
}
}
2018-07-11 06:41:26 +02:00
try {
runInterpreter ( ) ;
} catch ( e ) {
if ( isString ( e ) ) {
workerScript . errorMessage = e ;
return reject ( workerScript ) ;
} else if ( e instanceof WorkerScript ) {
return reject ( e ) ;
} else {
return reject ( workerScript ) ;
}
}
2018-07-09 01:53:24 +02:00
} ) ;
2018-07-20 16:28:03 +02:00
}
/ * S i n c e t h e J S I n t e r p r e t e r u s e d f o r N e t s c r i p t 1 . 0 o n l y s u p p o r t s E S 5 , t h e k e y w o r d
'import' throws an error . However , since we want to support import funtionality
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
* /
function processNetscript1Imports ( code , workerScript ) {
//allowReserved prevents 'import' from throwing error in ES5
2019-05-12 04:20:20 +02:00
const ast = parse ( code , { ecmaVersion : 6 , allowReserved : true , sourceType : "module" } ) ;
2018-07-20 16:28:03 +02:00
var server = workerScript . getServer ( ) ;
if ( server == null ) {
throw new Error ( "Failed to find underlying Server object for script" ) ;
}
function getScript ( scriptName ) {
for ( let i = 0 ; i < server . scripts . length ; ++ i ) {
if ( server . scripts [ i ] . filename === scriptName ) {
return server . scripts [ i ] ;
}
}
return null ;
}
2019-05-12 04:20:20 +02:00
let generatedCode = "" ; // Generated Javascript Code
let hasImports = false ;
2018-07-20 16:28:03 +02:00
2019-05-12 04:20:20 +02:00
// Walk over the tree and process ImportDeclaration nodes
2018-07-20 16:28:03 +02:00
walk . simple ( ast , {
ImportDeclaration : ( node ) => {
2018-07-25 21:53:54 +02:00
hasImports = true ;
2018-07-20 16:28:03 +02:00
let scriptName = node . source . value ;
2019-01-22 05:39:52 +01:00
if ( scriptName . startsWith ( "./" ) ) {
scriptName = scriptName . slice ( 2 ) ;
}
2018-07-20 16:28:03 +02:00
let script = getScript ( scriptName ) ;
if ( script == null ) {
throw new Error ( "'Import' failed due to invalid script: " + scriptName ) ;
}
let scriptAst = parse ( script . code , { ecmaVersion : 5 , allowReserved : true , sourceType : "module" } ) ;
if ( node . specifiers . length === 1 && node . specifiers [ 0 ] . type === "ImportNamespaceSpecifier" ) {
2019-05-12 04:20:20 +02:00
// import * as namespace from script
2018-07-20 16:28:03 +02:00
let namespace = node . specifiers [ 0 ] . local . name ;
let fnNames = [ ] ; //Names only
let fnDeclarations = [ ] ; //FunctionDeclaration Node objects
walk . simple ( scriptAst , {
FunctionDeclaration : ( node ) => {
fnNames . push ( node . id . name ) ;
fnDeclarations . push ( node ) ;
}
} ) ;
//Now we have to generate the code that would create the namespace
2019-05-12 04:20:20 +02:00
generatedCode +=
2018-07-20 16:28:03 +02:00
"var " + namespace + ";\n" +
"(function (namespace) {\n" ;
//Add the function declarations
fnDeclarations . forEach ( ( fn ) => {
generatedCode += generate ( fn ) ;
generatedCode += "\n" ;
} ) ;
//Add functions to namespace
fnNames . forEach ( ( fnName ) => {
generatedCode += ( "namespace." + fnName + " = " + fnName ) ;
generatedCode += "\n" ;
} ) ;
//Finish
generatedCode += (
2018-07-25 21:53:54 +02:00
"})(" + namespace + " || " + "(" + namespace + " = {}));\n"
2018-07-20 16:28:03 +02:00
)
} else {
//import {...} from script
//Get array of all fns to import
let fnsToImport = [ ] ;
node . specifiers . forEach ( ( e ) => {
fnsToImport . push ( e . local . name ) ;
} ) ;
//Walk through script and get FunctionDeclaration code for all specified fns
let fnDeclarations = [ ] ;
walk . simple ( scriptAst , {
FunctionDeclaration : ( node ) => {
if ( fnsToImport . includes ( node . id . name ) ) {
fnDeclarations . push ( node ) ;
}
}
} ) ;
//Convert FunctionDeclarations into code
fnDeclarations . forEach ( ( fn ) => {
generatedCode += generate ( fn ) ;
generatedCode += "\n" ;
} ) ;
}
}
} ) ;
2018-07-25 21:53:54 +02:00
//If there are no imports, just return the original code
if ( ! hasImports ) { return { code : code , lineOffset : 0 } ; }
2018-07-20 16:28:03 +02:00
//Remove ImportDeclarations from AST. These ImportDeclarations must be in top-level
2018-07-25 21:53:54 +02:00
var linesRemoved = 0 ;
2018-07-20 16:28:03 +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 ) ;
2018-07-25 21:53:54 +02:00
++ linesRemoved ;
2018-07-20 16:28:03 +02:00
}
}
2018-07-09 01:53:24 +02:00
2018-07-25 21:53:54 +02:00
//Calculated line offset
var lineOffset = ( generatedCode . match ( /\n/g ) || [ ] ) . length - linesRemoved ;
2018-07-20 16:28:03 +02:00
//Convert the AST back into code
code = generate ( ast ) ;
2018-07-09 01:53:24 +02:00
2018-07-20 16:28:03 +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
2018-07-25 21:53:54 +02:00
var res = {
code : code ,
lineOffset : lineOffset
}
return res ;
2018-07-09 01:53:24 +02:00
}
2019-06-19 10:03:08 +02:00
/ * *
2019-06-27 09:01:06 +02:00
* Find and return the next availble PID for a script
2019-06-19 10:03:08 +02:00
* /
let pidCounter = 1 ;
function generateNextPid ( ) {
let tempCounter = pidCounter ;
// Cap the number of search iterations at some arbitrary value to avoid
// infinite loops. We'll assume that players wont have 1mil+ running scripts
let found = false ;
for ( let i = 0 ; i < 1e6 ; ) {
if ( ! workerScripts . has ( tempCounter + i ) ) {
found = true ;
tempCounter = tempCounter + i ;
break ;
}
if ( i === Number . MAX _SAFE _INTEGER - 1 ) {
i = 1 ;
} else {
++ i ;
}
}
if ( found ) {
pidCounter = tempCounter + 1 ;
if ( pidCounter >= Number . MAX _SAFE _INTEGER ) {
pidCounter = 1 ;
}
return tempCounter ;
} else {
return - 1 ;
}
}
2019-05-05 06:03:40 +02:00
/ * *
2019-05-17 08:44:59 +02:00
* Start a script
*
* Given a RunningScript object , constructs a 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
2019-05-05 06:03:40 +02:00
* /
export function addWorkerScript ( runningScriptObj , server ) {
2019-05-17 08:44:59 +02:00
const filename = runningScriptObj . filename ;
2017-07-27 04:56:14 +02:00
2019-05-17 08:44:59 +02:00
// Update server's ram usage
let 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
}
2019-05-17 08:44:59 +02:00
const ramUsage = roundToTwo ( getRamUsageFromRunningScript ( runningScriptObj ) * threads ) ;
const ramAvailable = server . maxRam - server . ramUsed ;
2017-09-29 17:02:33 +02:00
if ( ramUsage > ramAvailable ) {
2019-05-17 08:44:59 +02:00
dialogBoxCreate (
` Not enough RAM to run script ${ runningScriptObj . filename } with args ` +
` ${ arrayToString ( 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.) `
) ;
2017-09-29 17:02:33 +02:00
return ;
}
2018-07-04 04:12:46 +02:00
server . ramUsed = roundToTwo ( server . ramUsed + ramUsage ) ;
2017-07-27 04:56:14 +02:00
2019-06-19 10:03:08 +02:00
// Get the pid
2019-06-19 10:51:25 +02:00
const pid = generateNextPid ( ) ;
2019-06-19 10:03:08 +02:00
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. `
) ;
}
2019-06-19 10:51:25 +02:00
// Create the WorkerScript. NOTE: WorkerScript ctor will set the underlying
// RunningScript's PID as well
2019-06-19 10:03:08 +02:00
const s = new WorkerScript ( runningScriptObj , pid , NetscriptFunctions ) ;
2017-06-11 03:46:02 +02:00
s . ramUsage = ramUsage ;
2017-07-27 04:56:14 +02:00
2019-05-17 08:44:59 +02:00
// Start the script's execution
let p = null ; // Script's resulting promise
if ( s . name . endsWith ( ".js" ) || s . name . endsWith ( ".ns" ) ) {
2019-05-17 22:41:16 +02:00
p = startNetscript2Script ( s ) ;
2019-05-17 08:44:59 +02:00
} else {
2019-05-17 22:41:16 +02:00
p = startNetscript1Script ( s ) ;
2019-05-17 08:44:59 +02:00
if ( ! ( p instanceof Promise ) ) { return ; }
}
// Once the code finishes (either resolved or rejected, doesnt matter), set its
// running status to false
p . then ( function ( w ) {
console . log ( "Stopping script " + w . name + " because it finished running naturally" ) ;
killWorkerScript ( s ) ;
w . scriptRef . log ( "Script finished running" ) ;
} ) . catch ( function ( w ) {
if ( w instanceof Error ) {
dialogBoxCreate ( "Script runtime unknown error. This is a bug please contact game developer" ) ;
console . error ( "Evaluating workerscript returns an Error. THIS SHOULDN'T HAPPEN: " + w . toString ( ) ) ;
return ;
} 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 ;
} 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 ) ;
return ;
}
var serverIp = errorTextArray [ 1 ] ;
var scriptName = errorTextArray [ 2 ] ;
var errorMsg = errorTextArray [ 3 ] ;
dialogBoxCreate ( "Script runtime error: <br>Server Ip: " + serverIp +
"<br>Script name: " + scriptName +
"<br>Args:" + arrayToString ( w . args ) + "<br>" + errorMsg ) ;
w . scriptRef . log ( "Script crashed with runtime error" ) ;
} else {
w . scriptRef . log ( "Script killed" ) ;
2019-05-17 22:41:16 +02:00
return ; // Already killed, so stop here
2019-05-17 08:44:59 +02:00
}
w . running = false ;
w . env . stopFlag = true ;
} else if ( isScriptErrorMessage ( w ) ) {
dialogBoxCreate ( "Script runtime unknown error. This is a bug please contact game developer" ) ;
console . log ( "ERROR: Evaluating workerscript returns only error message rather than WorkerScript object. THIS SHOULDN'T HAPPEN: " + w . toString ( ) ) ;
return ;
} else {
dialogBoxCreate ( "An unknown script died for an unknown reason. This is a bug please contact game dev" ) ;
console . log ( w ) ;
}
killWorkerScript ( s ) ;
} ) ;
2017-07-27 04:56:14 +02:00
2019-05-17 08:44:59 +02:00
// Add the WorkerScript to the global pool
2019-06-19 10:51:25 +02:00
workerScripts . set ( pid , s ) ;
2019-05-17 22:41:16 +02:00
WorkerScriptStartStopEventEmitter . emitEvent ( ) ;
2016-12-15 18:51:23 +01:00
return ;
2016-12-14 21:29:40 +01:00
}
2019-05-05 06:03:40 +02:00
/ * *
* Updates the online running time stat of all running scripts
* /
export function updateOnlineScriptTimes ( numCycles = 1 ) {
2016-12-19 19:20:19 +01:00
var time = ( numCycles * Engine . _idleSpeed ) / 1000 ; //seconds
2019-06-19 10:51:25 +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
* /
export function loadAllRunningScripts ( ) {
var total = 0 ;
let skipScriptLoad = ( window . location . href . toLowerCase ( ) . indexOf ( "?noscripts" ) !== - 1 ) ;
if ( skipScriptLoad ) { console . info ( "Skipping the load of any scripts during startup" ) ; }
for ( const property in AllServers ) {
if ( AllServers . hasOwnProperty ( property ) ) {
const server = AllServers [ property ] ;
// Reset each server's RAM usage to 0
server . ramUsed = 0 ;
// Reset modules on all scripts
for ( let i = 0 ; i < server . scripts . length ; ++ i ) {
2019-06-02 21:38:45 +02:00
server . scripts [ i ] . markUpdated ( ) ;
2019-05-05 06:03:40 +02:00
}
if ( skipScriptLoad ) {
// Start game with no scripts
server . runningScripts . length = 0 ;
} else {
for ( let j = 0 ; j < server . runningScripts . length ; ++ j ) {
addWorkerScript ( server . runningScripts [ j ] , server ) ;
// Offline production
total += scriptCalculateOfflineProduction ( server . runningScripts [ j ] ) ;
}
}
}
}
return total ;
}
/ * *
* Run a script from inside another script ( run ( ) , exec ( ) , spawn ( ) , etc . )
* /
export function runScriptFromScript ( server , scriptname , args , workerScript , threads = 1 ) {
//Check if the script is already running
let runningScriptObj = findRunningScript ( scriptname , args , server ) ;
if ( runningScriptObj != null ) {
workerScript . scriptRef . log ( scriptname + " is already running on " + server . hostname ) ;
return Promise . resolve ( false ) ;
}
//'null/undefined' arguments are not allowed
for ( let i = 0 ; i < args . length ; ++ i ) {
if ( args [ i ] == null ) {
workerScript . scriptRef . log ( "ERROR: Cannot execute a script with null/undefined as an argument" ) ;
return Promise . resolve ( false ) ;
}
}
//Check if the script exists and if it does run it
2019-05-11 11:20:09 +02:00
for ( let i = 0 ; i < server . scripts . length ; ++ i ) {
2019-05-05 06:03:40 +02:00
if ( server . scripts [ i ] . filename == scriptname ) {
//Check for admin rights and that there is enough RAM availble to run
var script = server . scripts [ i ] ;
var ramUsage = script . ramUsage ;
threads = Math . round ( Number ( threads ) ) ; //Convert to number and round
if ( threads === 0 ) { return Promise . resolve ( false ) ; }
ramUsage = ramUsage * threads ;
var ramAvailable = server . maxRam - server . ramUsed ;
if ( server . hasAdminRights == false ) {
workerScript . scriptRef . log ( "Cannot run script " + scriptname + " on " + server . hostname + " because you do not have root access!" ) ;
return Promise . resolve ( false ) ;
} else if ( ramUsage > ramAvailable ) {
workerScript . scriptRef . log ( "Cannot run script " + scriptname + "(t=" + threads + ") on " + server . hostname + " because there is not enough available RAM!" ) ;
return Promise . resolve ( false ) ;
} else {
//Able to run script
if ( workerScript . disableLogs . ALL == null && workerScript . disableLogs . exec == null && workerScript . disableLogs . run == null && workerScript . disableLogs . spawn == null ) {
workerScript . scriptRef . log ( ` Running script: ${ scriptname } on ${ server . hostname } with ${ threads } threads and args: ${ arrayToString ( args ) } . May take a few seconds to start up... ` ) ;
}
let runningScriptObj = new RunningScript ( script , args ) ;
runningScriptObj . threads = threads ;
addWorkerScript ( runningScriptObj , server ) ;
// Push onto runningScripts.
// This has to come after addWorkerScript() because that fn updates RAM usage
server . runScript ( runningScriptObj , Player . hacknet _node _money _mult ) ;
return Promise . resolve ( true ) ;
}
}
}
workerScript . scriptRef . log ( "Could not find script " + scriptname + " on " + server . hostname ) ;
return Promise . resolve ( false ) ;
}