diff --git a/src/Netscript/WorkerScript.ts b/src/Netscript/WorkerScript.ts index c8c9515d3..3ef23d884 100644 --- a/src/Netscript/WorkerScript.ts +++ b/src/Netscript/WorkerScript.ts @@ -94,11 +94,6 @@ export class WorkerScript { */ ramUsage = 0; - /** - * Whether or not this workerScript is currently running - */ - running = false; - /** * Reference to underlying RunningScript object */ diff --git a/src/NetscriptFunctions.ts b/src/NetscriptFunctions.ts index e1a528e0b..e9ab76745 100644 --- a/src/NetscriptFunctions.ts +++ b/src/NetscriptFunctions.ts @@ -702,7 +702,7 @@ const base: InternalAPI<NS> = { throw helpers.makeRuntimeErrorMsg(ctx, "Could not find server. This is a bug. Report to dev."); } - return runScriptFromScript(Player, "run", scriptServer, scriptname, args, ctx.workerScript, threads); + return runScriptFromScript("run", scriptServer, scriptname, args, ctx.workerScript, threads); }, exec: (ctx: NetscriptContext) => @@ -718,7 +718,7 @@ const base: InternalAPI<NS> = { throw helpers.makeRuntimeErrorMsg(ctx, `Invalid thread count. Must be numeric and > 0, is ${threads}`); } const server = helpers.getServer(ctx, hostname); - return runScriptFromScript(Player, "exec", server, scriptname, args, ctx.workerScript, threads); + return runScriptFromScript("exec", server, scriptname, args, ctx.workerScript, threads); }, spawn: (ctx: NetscriptContext) => @@ -740,12 +740,11 @@ const base: InternalAPI<NS> = { throw helpers.makeRuntimeErrorMsg(ctx, "Could not find server. This is a bug. Report to dev"); } - return runScriptFromScript(Player, "spawn", scriptServer, scriptname, args, ctx.workerScript, threads); + return runScriptFromScript("spawn", scriptServer, scriptname, args, ctx.workerScript, threads); }, spawnDelay * 1e3); helpers.log(ctx, () => `Will execute '${scriptname}' in ${spawnDelay} seconds`); - ctx.workerScript.running = false; // Prevent workerScript from "finishing execution naturally" if (killWorkerScript(ctx.workerScript)) { helpers.log(ctx, () => "Exiting..."); } @@ -820,7 +819,6 @@ const base: InternalAPI<NS> = { return scriptsKilled > 0; }, exit: (ctx: NetscriptContext) => (): void => { - ctx.workerScript.running = false; // Prevent workerScript from "finishing execution naturally" if (killWorkerScript(ctx.workerScript)) { helpers.log(ctx, () => "Exiting..."); } else { diff --git a/src/NetscriptFunctions/CodingContract.ts b/src/NetscriptFunctions/CodingContract.ts index 93cd1b7b2..a95dbe0ec 100644 --- a/src/NetscriptFunctions/CodingContract.ts +++ b/src/NetscriptFunctions/CodingContract.ts @@ -37,7 +37,7 @@ export function NetscriptCodingContract(): InternalAPI<ICodingContract> { throw new Error("The answer provided was not a number, string, or array"); // Convert answer to string. - const answerStr = typeof answer === 'string' ? answer : JSON.stringify(answer); + const answerStr = typeof answer === "string" ? answer : JSON.stringify(answer); const creward = contract.reward; if (creward === null) throw new Error("Somehow solved a contract that didn't have a reward"); diff --git a/src/NetscriptFunctions/Singularity.ts b/src/NetscriptFunctions/Singularity.ts index 7ce3893d0..82ef6e3b2 100644 --- a/src/NetscriptFunctions/Singularity.ts +++ b/src/NetscriptFunctions/Singularity.ts @@ -92,7 +92,7 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> { } const runningScriptObj = new RunningScript(script, []); // No args runningScriptObj.threads = 1; // Only 1 thread - startWorkerScript(player, runningScriptObj, home); + startWorkerScript(runningScriptObj, home); } } }; @@ -235,8 +235,6 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> { runAfterReset(cbScript); }, 0); - // Prevent ctx.workerScript from "finishing execution naturally" - ctx.workerScript.running = false; killWorkerScript(ctx.workerScript); }, installAugmentations: (ctx: NetscriptContext) => @@ -255,7 +253,6 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> { runAfterReset(cbScript); }, 0); - ctx.workerScript.running = false; // Prevent ctx.workerScript from "finishing execution naturally" killWorkerScript(ctx.workerScript); return true; }, diff --git a/src/NetscriptWorker.ts b/src/NetscriptWorker.ts index f7ce6e009..6dc79dd0c 100644 --- a/src/NetscriptWorker.ts +++ b/src/NetscriptWorker.ts @@ -34,10 +34,8 @@ import { simple as walksimple } from "acorn-walk"; import { areFilesEqual } from "./Terminal/DirectoryHelpers"; import { Player } from "./Player"; import { Terminal } from "./Terminal"; -import { IPlayer } from "./PersonObjects/IPlayer"; import { ScriptArg } from "./Netscript/ScriptArg"; import { helpers } from "./Netscript/NetscriptHelpers"; -import { NS } from "./ScriptEditor/NetscriptDefinitions"; // Netscript Ports are instantiated here export const NetscriptPorts: IPort[] = []; @@ -61,10 +59,9 @@ export function prestigeWorkerScripts(): void { // 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 startNetscript2Script(player: IPlayer, workerScript: WorkerScript): Promise<void> { - workerScript.running = true; +function startNetscript2Script(workerScript: WorkerScript): Promise<void> { return new Promise<void>((resolve, reject) => { - executeJSScript(player, workerScript.getServer().scripts, workerScript) + executeJSScript(Player, workerScript.getServer().scripts, workerScript) .then(() => { resolve(); }) @@ -98,7 +95,6 @@ function startNetscript2Script(player: IPlayer, workerScript: WorkerScript): Pro function startNetscript1Script(workerScript: WorkerScript): Promise<void> { const code = workerScript.code; - workerScript.running = true; //Process imports let codeWithImports, codeLineOffset; @@ -109,120 +105,62 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<void> { } catch (e: unknown) { dialogBoxCreate("Error processing Imports in " + workerScript.name + ":<br>" + String(e)); workerScript.env.stopFlag = true; - workerScript.running = false; killWorkerScript(workerScript); return Promise.resolve(); } - const interpreterInitialization = function (int: Interpreter, scope: unknown): void { - interface NS1 extends NS { + function wrapNS1Layer(int: Interpreter, intLayer: unknown, path: string[] = []) { + //TODO: Better typing layers of interpreter scope and ns + interface BasicObject { [key: string]: any; } - //Add the Netscript environment - const ns = NetscriptFunctions(workerScript) as NS1; - for (const name of Object.keys(ns)) { - const entry = ns[name]; + const nsLayer = path.reduce((prev, newPath) => prev[newPath], workerScript.env.vars as BasicObject); + for (const [name, entry] of Object.entries(nsLayer)) { if (typeof entry === "function") { - //Async functions need to be wrapped. See JS-Interpreter documentation - const asyncFuncs = ["hack", "grow", "weaken", "sleep", "prompt", "manualHack", "share", "wget"]; - - if (asyncFuncs.includes(name)) { - const tempWrapper = function (...args: unknown[]): void { - const 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 < args.length - 1; ++i) { - if (typeof args[i] === "object" || Array.isArray(args[i])) { - fnArgs.push(int.pseudoToNative(args[i])); - } else { - fnArgs.push(args[i]); - } + // Async functions need to be wrapped. See JS-Interpreter documentation + const wrapper = async (...args: unknown[]) => { + // This async wrapper is sent a resolver function as an extra arg. + // See JSInterpreter.js:3209 + try { + const callback = args.pop() as (value: unknown) => void; + const result = await entry(...args.map(int.pseudoToNative)); + return callback(int.nativeToPseudo(result)); + } catch (e: unknown) { + // TODO: Unify error handling, this was stolen from previous async handler + if (typeof e === "string") { + console.error(e); + const errorTextArray = e.split("|DELIMITER|"); + const hostname = errorTextArray[1]; + const scriptName = errorTextArray[2]; + const errorMsg = errorTextArray[3]; + let msg = `${scriptName}@${hostname}<br>`; + msg += "<br>"; + msg += errorMsg; + dialogBoxCreate(msg); + workerScript.env.stopFlag = true; + killWorkerScript(workerScript); + return; } - const callb = args[args.length - 1]; - const fnPromise = entry(...fnArgs); - fnPromise - .then(function (res: unknown) { - if (typeof callb === "function") { - callb(res); - } - }) - .catch(function (err: unknown) { - if (typeof err === "string") { - console.error(err); - const errorTextArray = err.split("|DELIMITER|"); - const hostname = errorTextArray[1]; - const scriptName = errorTextArray[2]; - const errorMsg = errorTextArray[3]; - let msg = `${scriptName}@${hostname}<br>`; - msg += "<br>"; - msg += errorMsg; - dialogBoxCreate(msg); - workerScript.env.stopFlag = true; - workerScript.running = false; - killWorkerScript(workerScript); - return Promise.resolve(); - } - }); - }; - int.setProperty(scope, name, int.createAsyncFunction(tempWrapper)); - } else if ( - name === "sprintf" || - name === "vsprintf" || - name === "scp" || - name == "write" || - name === "tryWritePort" || - name === "run" || - name === "exec" - ) { - const tempWrapper = function (...args: unknown[]): void { - const 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 < args.length; ++i) { - if (typeof args[i] === "object" || Array.isArray(args[i])) { - fnArgs.push(int.pseudoToNative(args[i])); - } else { - fnArgs.push(args[i]); - } - } - - return entry(...fnArgs); - }; - int.setProperty(scope, name, int.createNativeFunction(tempWrapper)); - } else { - const tempWrapper = function (...args: unknown[]): unknown { - const res = entry(...args); - - 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)); - } + } + }; + int.setProperty(intLayer, name, int.createAsyncFunction(wrapper)); + } else if (Array.isArray(entry) || typeof entry !== "object") { + // args, strings on enums, etc + int.setProperty(intLayer, name, int.nativeToPseudo(entry)); } else { - //bladeburner, or anything else - int.setProperty(scope, name, int.nativeToPseudo(entry)); + // new object layer, e.g. bladeburner + int.setProperty(intLayer, name, int.nativeToPseudo({})); + wrapNS1Layer(int, (intLayer as BasicObject).properties[name], [...path, name]); } } - - //Add the arguments - int.setProperty(scope, "args", int.nativeToPseudo(workerScript.args)); - }; + } let interpreter: Interpreter; try { - interpreter = new Interpreter(codeWithImports, interpreterInitialization, codeLineOffset); + interpreter = new Interpreter(codeWithImports, wrapNS1Layer, codeLineOffset); } catch (e: unknown) { dialogBoxCreate("Syntax ERROR in " + workerScript.name + ":<br>" + String(e)); workerScript.env.stopFlag = true; - workerScript.running = false; killWorkerScript(workerScript); return Promise.resolve(); } @@ -421,13 +359,8 @@ function processNetscript1Imports(code: string, workerScript: WorkerScript): { c * corresponding WorkerScript), and add the RunningScript to the server on which * it is active */ -export function startWorkerScript( - player: IPlayer, - runningScript: RunningScript, - server: BaseServer, - parent?: WorkerScript, -): number { - if (createAndAddWorkerScript(player, runningScript, server, parent)) { +export function startWorkerScript(runningScript: RunningScript, server: BaseServer, parent?: WorkerScript): number { + if (createAndAddWorkerScript(runningScript, server, parent)) { // Push onto runningScripts. // This has to come after createAndAddWorkerScript() because that fn updates RAM usage server.runScript(runningScript); @@ -447,12 +380,7 @@ export function startWorkerScript( * @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( - player: IPlayer, - runningScriptObj: RunningScript, - server: BaseServer, - parent?: WorkerScript, -): boolean { +function createAndAddWorkerScript(runningScriptObj: RunningScript, server: BaseServer, parent?: WorkerScript): boolean { // Update server's ram usage let threads = 1; if (runningScriptObj.threads && !isNaN(runningScriptObj.threads)) { @@ -496,7 +424,7 @@ function createAndAddWorkerScript( // Start the script's execution let scriptExecution: Promise<void> | null = null; // Script's resulting promise if (workerScript.name.endsWith(".js")) { - scriptExecution = startNetscript2Script(player, workerScript); + scriptExecution = startNetscript2Script(workerScript); } else { scriptExecution = startNetscript1Script(workerScript); if (!(scriptExecution instanceof Promise)) { @@ -508,14 +436,11 @@ function createAndAddWorkerScript( // running status to false scriptExecution .then(function () { - workerScript.running = false; workerScript.env.stopFlag = true; // On natural death, the earnings are transfered to the parent if it still exists. - if (parent !== undefined) { - if (parent.running) { - parent.scriptRef.onlineExpGained += runningScriptObj.onlineExpGained; - parent.scriptRef.onlineMoneyMade += runningScriptObj.onlineMoneyMade; - } + if (parent !== undefined && !parent.env.stopFlag) { + parent.scriptRef.onlineExpGained += runningScriptObj.onlineExpGained; + parent.scriptRef.onlineMoneyMade += runningScriptObj.onlineMoneyMade; } killWorkerScript(workerScript); @@ -583,7 +508,7 @@ export function updateOnlineScriptTimes(numCycles = 1): void { * 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(player: IPlayer): void { +export function loadAllRunningScripts(): void { const skipScriptLoad = window.location.href.toLowerCase().indexOf("?noscripts") !== -1; if (skipScriptLoad) { Terminal.warn("Skipped loading player scripts during startup"); @@ -604,7 +529,7 @@ export function loadAllRunningScripts(player: IPlayer): void { } else { for (let j = 0; j < server.runningScripts.length; ++j) { const fileName = server.runningScripts[j].filename; - createAndAddWorkerScript(player, server.runningScripts[j], server); + createAndAddWorkerScript(server.runningScripts[j], server); if (!server.runningScripts[j]) { // createAndAddWorkerScript can modify the server.runningScripts array if a script is invalid @@ -623,7 +548,6 @@ export function loadAllRunningScripts(player: IPlayer): void { * Run a script from inside another script (run(), exec(), spawn(), etc.) */ export function runScriptFromScript( - player: IPlayer, caller: string, server: BaseServer, scriptname: string, @@ -698,7 +622,7 @@ export function runScriptFromScript( runningScriptObj.threads = threads; runningScriptObj.server = server.hostname; - return startWorkerScript(player, runningScriptObj, server, workerScript); + return startWorkerScript(runningScriptObj, server, workerScript); } workerScript.log(caller, () => `Could not find script '${scriptname}' on '${server.hostname}'`); diff --git a/src/Terminal/commands/runScript.ts b/src/Terminal/commands/runScript.ts index 93e0d2ccb..baa18e589 100644 --- a/src/Terminal/commands/runScript.ts +++ b/src/Terminal/commands/runScript.ts @@ -76,7 +76,7 @@ export function runScript( const runningScript = new RunningScript(script, args); runningScript.threads = numThreads; - const success = startWorkerScript(player, runningScript, server); + const success = startWorkerScript(runningScript, server); if (!success) { terminal.error(`Failed to start script`); return; diff --git a/src/engine.tsx b/src/engine.tsx index a4812d943..db143f9aa 100644 --- a/src/engine.tsx +++ b/src/engine.tsx @@ -278,7 +278,7 @@ const Engine: { Player.gainMoney(offlineHackingIncome, "hacking"); // Process offline progress - loadAllRunningScripts(Player); // This also takes care of offline production for those scripts + loadAllRunningScripts(); // This also takes care of offline production for those scripts if (Player.currentWork !== null) { Player.focus = true; diff --git a/src/ui/React/LogBoxManager.tsx b/src/ui/React/LogBoxManager.tsx index a4c309d76..a9a2e6479 100644 --- a/src/ui/React/LogBoxManager.tsx +++ b/src/ui/React/LogBoxManager.tsx @@ -15,7 +15,6 @@ import { workerScripts } from "../../Netscript/WorkerScripts"; import { startWorkerScript } from "../../NetscriptWorker"; import { GetServer } from "../../Server/AllServers"; import { findRunningScript } from "../../Script/ScriptHelpers"; -import { Player } from "../../Player"; import { debounce } from "lodash"; import { Settings } from "../../Settings/Settings"; import { ANSIITypography } from "./ANSIITypography"; @@ -159,7 +158,7 @@ function LogWindow(props: IProps): React.ReactElement { if (server === null) return; const s = findRunningScript(script.filename, script.args, server); if (s === null) { - startWorkerScript(Player, script, server); + startWorkerScript(script, server); } else { setScript(s); }