From 65331ab22e485d4fcb4174e2172a1da342b07aab Mon Sep 17 00:00:00 2001 From: James Aguilar <799564+jaguilar@users.noreply.github.com> Date: Sun, 2 Jun 2019 15:21:08 -0400 Subject: [PATCH 1/6] recompile ns2 scripts if dependencies change --- src/NetscriptJSEvaluator.js | 58 ++++++++++++++++++++++++++++++++----- src/Script/Script.ts | 18 ++++++++++-- src/Server/BaseServer.ts | 2 +- 3 files changed, 67 insertions(+), 11 deletions(-) diff --git a/src/NetscriptJSEvaluator.js b/src/NetscriptJSEvaluator.js index 0f8032f90..65b714c06 100644 --- a/src/NetscriptJSEvaluator.js +++ b/src/NetscriptJSEvaluator.js @@ -1,10 +1,22 @@ import { makeRuntimeRejectMsg } from "./NetscriptEvaluator"; +import { Script } from "./Script/Script"; // Makes a blob that contains the code of a given script. export function makeScriptBlob(code) { return new Blob([code], {type: "text/javascript"}); } +class ScriptUrl { + /** + * @param {string} filename + * @param {string} url + */ + constructor(filename, url) { + this.filename = filename; + this.url = url; + } +} + // Begin executing a user JS script, and return a promise that resolves // or rejects when the script finishes. // - script is a script to execute (see Script.js). We depend only on .filename and .code. @@ -15,9 +27,9 @@ export function makeScriptBlob(code) { // running the main function of the script. export async function executeJSScript(scripts = [], workerScript) { let loadedModule; - let urlStack = null; + let urls = null; let script = workerScript.getScript(); - if (script.module === "") { + if (shouldCompile(script, scripts)) { // The URL at the top is the one we want to import. It will // recursively import all the other modules in the urlStack. // @@ -25,8 +37,9 @@ export async function executeJSScript(scripts = [], workerScript) { // but not really behaves like import. Particularly, it cannot // load fully dynamic content. So we hide the import from webpack // by placing it inside an eval call. - urlStack = _getScriptUrls(script, scripts, []); - script.module = await eval('import(urlStack[urlStack.length - 1])'); + urls = _getScriptUrls(script, scripts, []); + script.module = await eval('import(urls[urls.length - 1].url)'); + script.dependencies = urls.map(u => u.filename); } loadedModule = script.module; @@ -41,12 +54,31 @@ export async function executeJSScript(scripts = [], workerScript) { return loadedModule.main(ns); } finally { // Revoke the generated URLs - if (urlStack != null) { - for (const url in urlStack) URL.revokeObjectURL(url); + if (urls != null) { + for (const b in urls) URL.revokeObjectURL(b.url); } }; } +/** Returns whether we should compile the script parameter. + * + * @param {Script} script + * @param {Script[]} scripts + */ +function shouldCompile(script, scripts) { + if (script.module === "") return true; + return script.dependencies.some(dep => { + const depScript = scripts.find(s => s.filename == dep); + + // If the script is not present on the server, we should recompile, if only to get any necessary + // compilation errors. + if (!depScript) return true; + + const depIsMoreRecent = depScript.updateTimestamp > script.updateTimestamp + return depIsMoreRecent; + }); +} + // Gets a stack of blob urls, the top/right-most element being // the blob url for the named script on the named server. // @@ -58,8 +90,18 @@ export async function executeJSScript(scripts = [], workerScript) { // different parts of the tree. That hasn't presented any problem with during // testing, but it might be an idea for the future. Would require a topo-sort // then url-izing from leaf-most to root-most. +/** + * @param {Script} script + * @param {Script[]} scripts + * @param {Script[]} seen + * @returns {ScriptUrl[]} All of the compiled scripts, with the final one + * in the list containing the blob corresponding to + * the script parameter. + */ +// BUG: apparently seen is never consulted. Oops. export function _getScriptUrls(script, scripts, seen) { // Inspired by: https://stackoverflow.com/a/43834063/91401 + /** @type {ScriptUrl[]} */ const urlStack = []; seen.push(script); try { @@ -86,7 +128,7 @@ export function _getScriptUrls(script, scripts, seen) { // The top url in the stack is the replacement import file for this script. urlStack.push(...urls); - return [prefix, urls[urls.length - 1], suffix].join(''); + return [prefix, urls[urls.length - 1].url, suffix].join(''); } ); @@ -96,7 +138,7 @@ export function _getScriptUrls(script, scripts, seen) { // If we successfully transformed the code, create a blob url for it and // push that URL onto the top of the stack. - urlStack.push(URL.createObjectURL(makeScriptBlob(transformedCode))); + urlStack.push(new ScriptUrl(script.filename, URL.createObjectURL(makeScriptBlob(transformedCode)))); return urlStack; } catch (err) { // If there is an error, we need to clean up the URLs. diff --git a/src/Script/Script.ts b/src/Script/Script.ts index cb3200a6c..dc8a69367 100644 --- a/src/Script/Script.ts +++ b/src/Script/Script.ts @@ -31,6 +31,14 @@ export class Script { // This is only applicable for NetscriptJS module: any = ""; + // The timestamp when when the script was last updated. + updateTimestamp: number = 0; + + // Only used with NS2 scripts; the list of dependency script filenames. This is constructed + // whenever the script is first evaluated, and therefore may be out of date if the script + // has been updated since it was last run. + dependencies: string[] = []; + // Amount of RAM this Script requres to run ramUsage: number = 0; @@ -85,11 +93,17 @@ export class Script { } this.filename = filenameElem!.value; this.server = serverIp; - this.updateRamUsage(otherScripts); - this.module = ""; + this.updateRamUsage(otherScripts); + this.markUpdated(); } } + // Marks that this script has been updated. Causes recompilation of NS2 modules. + markUpdated() { + this.module = ""; + this.updateTimestamp = Date.now(); + } + /** * Calculates and updates the script's RAM usage based on its code * @param {Script[]} otherScripts - Other scripts on the server. Used to process imports diff --git a/src/Server/BaseServer.ts b/src/Server/BaseServer.ts index 74f297189..143570abf 100644 --- a/src/Server/BaseServer.ts +++ b/src/Server/BaseServer.ts @@ -233,7 +233,7 @@ export class BaseServer { let script = this.scripts[i]; script.code = code; script.updateRamUsage(this.scripts); - script.module = ""; + script.markUpdated(); ret.overwritten = true; ret.success = true; return ret; From 1236ad252b8d916561abd2f2c4565f025f69e333 Mon Sep 17 00:00:00 2001 From: James Aguilar <799564+jaguilar@users.noreply.github.com> Date: Sun, 2 Jun 2019 15:32:35 -0400 Subject: [PATCH 2/6] markUpdated() for Script --- src/NetscriptFunctions.js | 3 ++- src/NetscriptWorker.js | 2 +- src/Script/Script.ts | 10 +++++++++- src/Server/BaseServer.ts | 2 +- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index aa4c877dc..92e64bf9c 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -1139,7 +1139,7 @@ function NetscriptFunctions(workerScript) { var oldScript = destServer.scripts[i]; oldScript.code = sourceScript.code; oldScript.ramUsage = sourceScript.ramUsage; - oldScript.module = ""; + oldScript.markUpdated();; return true; } } @@ -1901,6 +1901,7 @@ function NetscriptFunctions(workerScript) { } mode === "w" ? script.code = data : script.code += data; script.updateRamUsage(server.scripts); + script.markUpdated(); } else { // Write to text file let txtFile = getTextFile(fn, server); diff --git a/src/NetscriptWorker.js b/src/NetscriptWorker.js index 319693358..095ba401d 100644 --- a/src/NetscriptWorker.js +++ b/src/NetscriptWorker.js @@ -534,7 +534,7 @@ export function loadAllRunningScripts() { // Reset modules on all scripts for (let i = 0; i < server.scripts.length; ++i) { - server.scripts[i].module = ""; + server.scripts[i].markUpdated(); } if (skipScriptLoad) { diff --git a/src/Script/Script.ts b/src/Script/Script.ts index cb3200a6c..9cc395d56 100644 --- a/src/Script/Script.ts +++ b/src/Script/Script.ts @@ -68,6 +68,14 @@ export class Script { } } + /** + * Marks this script as having been updated. It will be recompiled next time something tries + * to exec it. + */ + markUpdated() { + this.module = ""; + } + /** * Save a script from the script editor * @param {string} code - The new contents of the script @@ -86,7 +94,7 @@ export class Script { this.filename = filenameElem!.value; this.server = serverIp; this.updateRamUsage(otherScripts); - this.module = ""; + this.markUpdated(); } } diff --git a/src/Server/BaseServer.ts b/src/Server/BaseServer.ts index 74f297189..143570abf 100644 --- a/src/Server/BaseServer.ts +++ b/src/Server/BaseServer.ts @@ -233,7 +233,7 @@ export class BaseServer { let script = this.scripts[i]; script.code = code; script.updateRamUsage(this.scripts); - script.module = ""; + script.markUpdated(); ret.overwritten = true; ret.success = true; return ret; From 3ef9042051acb2db53d4b850cf04c030b4cbb5e4 Mon Sep 17 00:00:00 2001 From: James Aguilar <799564+jaguilar@users.noreply.github.com> Date: Sun, 2 Jun 2019 15:38:45 -0400 Subject: [PATCH 3/6] fix two cases where markUpdated was not properly called --- src/NetscriptFunctions.js | 2 +- src/NetscriptWorker.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index aa4c877dc..cbc0c6162 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -1139,7 +1139,7 @@ function NetscriptFunctions(workerScript) { var oldScript = destServer.scripts[i]; oldScript.code = sourceScript.code; oldScript.ramUsage = sourceScript.ramUsage; - oldScript.module = ""; + oldScript.module.markUpdated(); return true; } } diff --git a/src/NetscriptWorker.js b/src/NetscriptWorker.js index 319693358..095ba401d 100644 --- a/src/NetscriptWorker.js +++ b/src/NetscriptWorker.js @@ -534,7 +534,7 @@ export function loadAllRunningScripts() { // Reset modules on all scripts for (let i = 0; i < server.scripts.length; ++i) { - server.scripts[i].module = ""; + server.scripts[i].markUpdated(); } if (skipScriptLoad) { From 2201dfc371e06199dd55aba2cbd1d465421f5d05 Mon Sep 17 00:00:00 2001 From: James Aguilar <799564+jaguilar@users.noreply.github.com> Date: Sun, 2 Jun 2019 15:40:13 -0400 Subject: [PATCH 4/6] fix typo --- src/NetscriptFunctions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index cbc0c6162..5fef44888 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -1139,7 +1139,7 @@ function NetscriptFunctions(workerScript) { var oldScript = destServer.scripts[i]; oldScript.code = sourceScript.code; oldScript.ramUsage = sourceScript.ramUsage; - oldScript.module.markUpdated(); + oldScript.markUpdated(); return true; } } From d45689c7dff0d3e5852459273bb9fa44228b739e Mon Sep 17 00:00:00 2001 From: James Aguilar <799564+jaguilar@users.noreply.github.com> Date: Sun, 2 Jun 2019 20:17:27 -0400 Subject: [PATCH 5/6] use sequence numbers rather than timestamps for script expiration --- src/NetscriptJSEvaluator.js | 2 +- src/Script/Script.ts | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/NetscriptJSEvaluator.js b/src/NetscriptJSEvaluator.js index 65b714c06..d3241f557 100644 --- a/src/NetscriptJSEvaluator.js +++ b/src/NetscriptJSEvaluator.js @@ -74,7 +74,7 @@ function shouldCompile(script, scripts) { // compilation errors. if (!depScript) return true; - const depIsMoreRecent = depScript.updateTimestamp > script.updateTimestamp + const depIsMoreRecent = depScript.moduleSequenceNumber > script.moduleSequenceNumber return depIsMoreRecent; }); } diff --git a/src/Script/Script.ts b/src/Script/Script.ts index dc8a69367..3b9df8135 100644 --- a/src/Script/Script.ts +++ b/src/Script/Script.ts @@ -15,6 +15,8 @@ import { } from "../../utils/JSONReviver"; import { roundToTwo } from "../../utils/helpers/roundToTwo"; +let globalModuleSequenceNumber = 0; + export class Script { // Initializes a Script Object from a JSON save state static fromJSON(value: any): Script { @@ -32,7 +34,7 @@ export class Script { module: any = ""; // The timestamp when when the script was last updated. - updateTimestamp: number = 0; + moduleSequenceNumber: number; // Only used with NS2 scripts; the list of dependency script filenames. This is constructed // whenever the script is first evaluated, and therefore may be out of date if the script @@ -51,6 +53,7 @@ export class Script { this.ramUsage = 0; this.server = server; // IP of server this script is on this.module = ""; + this.moduleSequenceNumber = ++globalModuleSequenceNumber; if (this.code !== "") { this.updateRamUsage(otherScripts); } }; @@ -101,7 +104,7 @@ export class Script { // Marks that this script has been updated. Causes recompilation of NS2 modules. markUpdated() { this.module = ""; - this.updateTimestamp = Date.now(); + this.moduleSequenceNumber = ++globalModuleSequenceNumber; } /** From 2f8eac07ee5f95fdf3185c7c220bbcadf1db81d8 Mon Sep 17 00:00:00 2001 From: J <799564+jaguilar@users.noreply.github.com> Date: Sun, 2 Jun 2019 23:53:07 -0400 Subject: [PATCH 6/6] fix indentation --- src/Script/Script.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Script/Script.ts b/src/Script/Script.ts index f1a8f3bf0..6e11fd08b 100644 --- a/src/Script/Script.ts +++ b/src/Script/Script.ts @@ -105,7 +105,7 @@ export class Script { this.filename = filenameElem!.value; this.server = serverIp; this.updateRamUsage(otherScripts); - this.markUpdated(); + this.markUpdated(); } }