Merge pull request #2657 from theit8514/grandparent-script-cache

Remove dependents from cache when dependency updated
This commit is contained in:
hydroflame 2022-01-17 15:56:06 -05:00 committed by GitHub
commit 8df950721c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 54 additions and 16 deletions

@ -82,6 +82,16 @@ export async function executeJSScript(
return loadedModule.main(ns); 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. /** Returns whether we should compile the script parameter.
* *
* @param {Script} script * @param {Script} script
@ -89,16 +99,7 @@ export async function executeJSScript(
*/ */
function shouldCompile(script: Script, scripts: Script[]): boolean { function shouldCompile(script: Script, scripts: Script[]): boolean {
if (script.module === "") return true; if (script.module === "") return true;
return script.dependencies.some((dep) => { return script.dependencies.some((dep) => isDependencyOutOfDate(dep.filename, scripts, script.moduleSequenceNumber));
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;
});
} }
// Gets a stack of blob urls, the top/right-most element being // 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. // BUG: apparently seen is never consulted. Oops.
function _getScriptUrls(script: Script, scripts: Script[], seen: Script[]): ScriptUrl[] { function _getScriptUrls(script: Script, scripts: Script[], seen: Script[]): ScriptUrl[] {
// Inspired by: https://stackoverflow.com/a/43834063/91401 // Inspired by: https://stackoverflow.com/a/43834063/91401
/** @type {ScriptUrl[]} */ const urlStack: ScriptUrl[] = [];
const urlStack = []; // 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); seen.push(script);
try { try {
// Replace every import statement with an import to a blob url containing // 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. // Check to see if the urls for this script are stored in the cache by the hash value.
let urls = ImportCache.get(importedScript.hash()); 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 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) { if (!urls) {
// Try to get a URL for the requested script and its dependencies. // Try to get a URL for the requested script and its dependencies.
urls = _getScriptUrls(importedScript, scripts, seen); 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. // (e.g. same scripts on server, same hash value, etc) can use this blob url.
BlobCache.store(transformedHash, blob); BlobCache.store(transformedHash, blob);
// Push the blob URL onto the top of the stack. // 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; return urlStack;
} catch (err) { } catch (err) {
// If there is an error, we need to clean up the URLs. // If there is an error, we need to clean up the URLs.

@ -10,10 +10,13 @@ import { ScriptUrl } from "./ScriptUrl";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
import { roundToTwo } from "../utils/helpers/roundToTwo"; import { roundToTwo } from "../utils/helpers/roundToTwo";
import { computeHash } from "../utils/helpers/computeHash"; import { computeHash } from "../utils/helpers/computeHash";
import { ImportCache } from "../utils/ImportCache";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
let globalModuleSequenceNumber = 0; let globalModuleSequenceNumber = 0;
interface ScriptReference { filename: string; server: string }
export class Script { export class Script {
// Code for this script // Code for this script
code = ""; code = "";
@ -35,6 +38,7 @@ export class Script {
// whenever the script is first evaluated, and therefore may be out of date if the 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. // has been updated since it was last run.
dependencies: ScriptUrl[] = []; dependencies: ScriptUrl[] = [];
dependents: ScriptReference[] = [];
// Amount of RAM this Script requres to run // Amount of RAM this Script requres to run
ramUsage = 0; ramUsage = 0;
@ -99,7 +103,11 @@ export class Script {
* Force update of the computed hash based on the source code. * Force update of the computed hash based on the source code.
*/ */
rehash(): void { rehash(): void {
const oldHash = this._hash;
this._hash = computeHash(this.code); this._hash = computeHash(this.code);
if (oldHash !== this._hash) {
ImportCache.remove(oldHash);
}
} }
/** /**
@ -124,6 +132,10 @@ export class Script {
this.server = hostname; this.server = hostname;
this.updateRamUsage(player, otherScripts); this.updateRamUsage(player, otherScripts);
this.markUpdated(); 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 = ""; s.url = "";
// Rehash the code to ensure that hash is set properly. // Rehash the code to ensure that hash is set properly.
s.rehash(); s.rehash();
s.dependents = [];
return s; return s;
} }

@ -1,9 +1,11 @@
export class ScriptUrl { export class ScriptUrl {
filename: string; filename: string;
url: string; url: string;
moduleSequenceNumber: number;
constructor(filename: string, url: string) { constructor(filename: string, url: string, moduleSequenceNumber: number) {
this.filename = filename; this.filename = filename;
this.url = url; this.url = url;
this.moduleSequenceNumber = moduleSequenceNumber;
} }
} }

@ -3,12 +3,16 @@ import { ScriptUrl } from "../Script/ScriptUrl";
const importCache: { [hash: string]: ScriptUrl[] } = {}; const importCache: { [hash: string]: ScriptUrl[] } = {};
export class ImportCache { export class ImportCache {
static get(hash: string): ScriptUrl[] { static get(hash: string): ScriptUrl[] | null {
return importCache[hash]; return importCache[hash] || null;
} }
static store(hash: string, value: ScriptUrl[]): void { static store(hash: string, value: ScriptUrl[]): void {
if (importCache[hash]) return; if (importCache[hash]) return;
importCache[hash] = value; importCache[hash] = value;
} }
static remove(hash: string): void {
delete importCache[hash];
}
} }