diff --git a/src/NetscriptJSEvaluator.ts b/src/NetscriptJSEvaluator.ts index d706d3066..ddd41b2f4 100644 --- a/src/NetscriptJSEvaluator.ts +++ b/src/NetscriptJSEvaluator.ts @@ -82,6 +82,16 @@ export async function executeJSScript( return loadedModule.main(ns); } +function isDependencyOutOfDate(filename: string, scripts: Script[], scriptModuleSequenceNumber: number): boolean { + const depScript = scripts.find((s) => s.filename == filename); + + // 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.moduleSequenceNumber > scriptModuleSequenceNumber; + return depIsMoreRecent; +} /** Returns whether we should compile the script parameter. * * @param {Script} script @@ -89,16 +99,7 @@ export async function executeJSScript( */ function shouldCompile(script: Script, scripts: Script[]): boolean { if (script.module === "") return true; - return script.dependencies.some((dep) => { - const depScript = scripts.find((s) => s.filename == dep.filename); - - // 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.moduleSequenceNumber > script.moduleSequenceNumber; - return depIsMoreRecent; - }); + return script.dependencies.some((dep) => isDependencyOutOfDate(dep.filename, scripts, script.moduleSequenceNumber)); } // Gets a stack of blob urls, the top/right-most element being @@ -123,8 +124,13 @@ function shouldCompile(script: Script, scripts: Script[]): boolean { // BUG: apparently seen is never consulted. Oops. function _getScriptUrls(script: Script, scripts: Script[], seen: Script[]): ScriptUrl[] { // Inspired by: https://stackoverflow.com/a/43834063/91401 - /** @type {ScriptUrl[]} */ - const urlStack = []; + const urlStack: ScriptUrl[] = []; + // Seen contains the dependents of the current script. Make sure we include that in the script dependents. + for (const dependent of seen) { + if (!script.dependents.some(s => s.server === dependent.server && s.filename == dependent.filename)) { + script.dependents.push({ server: dependent.server, filename: dependent.filename }); + } + } seen.push(script); try { // Replace every import statement with an import to a blob url containing @@ -187,6 +193,19 @@ function _getScriptUrls(script: Script, scripts: Script[], seen: Script[]): Scri // Check to see if the urls for this script are stored in the cache by the hash value. let urls = ImportCache.get(importedScript.hash()); // If we don't have it in the cache, then we need to generate the urls for it. + if (urls) { + // Verify that these urls are valid and have not been updated. + for(const url of urls) { + if (isDependencyOutOfDate(url.filename, scripts, url.moduleSequenceNumber)) { + // Revoke these URLs from the browser. We will be unable to use them again. + for (const url of urls) URL.revokeObjectURL(url.url); + // Clear the cache and prepare for new blobs. + urls = null; + ImportCache.remove(importedScript.hash()); + break; + } + } + } if (!urls) { // Try to get a URL for the requested script and its dependencies. urls = _getScriptUrls(importedScript, scripts, seen); @@ -217,7 +236,7 @@ function _getScriptUrls(script: Script, scripts: Script[], seen: Script[]): Scri // (e.g. same scripts on server, same hash value, etc) can use this blob url. BlobCache.store(transformedHash, blob); // Push the blob URL onto the top of the stack. - urlStack.push(new ScriptUrl(script.filename, blob)); + urlStack.push(new ScriptUrl(script.filename, blob, script.moduleSequenceNumber)); 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 d6b13dbff..393e08091 100644 --- a/src/Script/Script.ts +++ b/src/Script/Script.ts @@ -10,10 +10,13 @@ import { ScriptUrl } from "./ScriptUrl"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver"; import { roundToTwo } from "../utils/helpers/roundToTwo"; import { computeHash } from "../utils/helpers/computeHash"; +import { ImportCache } from "../utils/ImportCache"; import { IPlayer } from "../PersonObjects/IPlayer"; let globalModuleSequenceNumber = 0; +interface ScriptReference { filename: string; server: string } + export class Script { // Code for this script code = ""; @@ -35,6 +38,7 @@ export class Script { // 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: ScriptUrl[] = []; + dependents: ScriptReference[] = []; // Amount of RAM this Script requres to run ramUsage = 0; @@ -99,7 +103,11 @@ export class Script { * Force update of the computed hash based on the source code. */ rehash(): void { + const oldHash = this._hash; this._hash = computeHash(this.code); + if (oldHash !== this._hash) { + ImportCache.remove(oldHash); + } } /** @@ -124,6 +132,10 @@ export class Script { this.server = hostname; this.updateRamUsage(player, otherScripts); this.markUpdated(); + for (const dependent of this.dependents) { + const [dependentScript] = otherScripts.filter(s => s.filename === dependent.filename && s.server == dependent.server); + if (dependentScript !== null) dependentScript.markUpdated(); + } } /** @@ -156,6 +168,7 @@ export class Script { s.url = ""; // Rehash the code to ensure that hash is set properly. s.rehash(); + s.dependents = []; return s; } diff --git a/src/Script/ScriptUrl.ts b/src/Script/ScriptUrl.ts index 037189147..1dedf89b5 100644 --- a/src/Script/ScriptUrl.ts +++ b/src/Script/ScriptUrl.ts @@ -1,9 +1,11 @@ export class ScriptUrl { filename: string; url: string; + moduleSequenceNumber: number; - constructor(filename: string, url: string) { + constructor(filename: string, url: string, moduleSequenceNumber: number) { this.filename = filename; this.url = url; + this.moduleSequenceNumber = moduleSequenceNumber; } } diff --git a/src/utils/ImportCache.ts b/src/utils/ImportCache.ts index 9a8383e21..ffeb845e8 100644 --- a/src/utils/ImportCache.ts +++ b/src/utils/ImportCache.ts @@ -3,12 +3,16 @@ import { ScriptUrl } from "../Script/ScriptUrl"; const importCache: { [hash: string]: ScriptUrl[] } = {}; export class ImportCache { - static get(hash: string): ScriptUrl[] { - return importCache[hash]; + static get(hash: string): ScriptUrl[] | null { + return importCache[hash] || null; } static store(hash: string, value: ScriptUrl[]): void { if (importCache[hash]) return; importCache[hash] = value; } + + static remove(hash: string): void { + delete importCache[hash]; + } }