2019-05-17 22:41:16 +02:00
|
|
|
/**
|
|
|
|
* Class representing a script file.
|
|
|
|
*
|
|
|
|
* This does NOT represent a script that is actively running and
|
|
|
|
* being evaluated. See RunningScript for that
|
|
|
|
*/
|
2022-01-05 22:41:48 +01:00
|
|
|
import { calculateRamUsage, RamUsageEntry } from "./RamCalculations";
|
2019-03-05 02:40:28 +01:00
|
|
|
|
2023-04-07 06:33:51 +02:00
|
|
|
import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
|
2021-09-25 20:42:57 +02:00
|
|
|
import { roundToTwo } from "../utils/helpers/roundToTwo";
|
2022-07-20 04:44:45 +02:00
|
|
|
import { ScriptModule } from "./ScriptModule";
|
2023-03-06 04:39:42 +01:00
|
|
|
import { RamCostConstants } from "../Netscript/RamCostGenerator";
|
2023-04-07 06:33:51 +02:00
|
|
|
import { queueUrlRevoke } from "../NetscriptJSEvaluator";
|
2019-03-03 04:15:10 +01:00
|
|
|
|
2023-04-07 06:33:51 +02:00
|
|
|
// The object portion of this type is not runtime information, it's only to ensure type validation
|
|
|
|
// And make it harder to overwrite a url with a random non-url string.
|
|
|
|
export type ScriptURL = string & { __type: "ScriptURL" };
|
2022-01-09 09:50:36 +01:00
|
|
|
|
2019-03-03 04:15:10 +01:00
|
|
|
export class Script {
|
2021-09-05 01:09:30 +02:00
|
|
|
code = "";
|
2023-04-07 06:33:51 +02:00
|
|
|
filename = "default.js";
|
|
|
|
server = "home";
|
2021-09-05 01:09:30 +02:00
|
|
|
|
2023-04-07 06:33:51 +02:00
|
|
|
// Ram calculation, only exists after first poll of ram cost after updating
|
|
|
|
ramUsage?: number;
|
2022-01-05 22:41:48 +01:00
|
|
|
ramUsageEntries?: RamUsageEntry[];
|
2021-09-05 01:09:30 +02:00
|
|
|
|
2023-04-07 06:33:51 +02:00
|
|
|
// Runtime data that only exists when the script has been initiated. Cleared when script or a dependency script is updated.
|
|
|
|
module?: Promise<ScriptModule>;
|
|
|
|
url?: ScriptURL;
|
|
|
|
/** Scripts that directly import this one. Stored so we can invalidate these dependent scripts when this one is invalidated. */
|
|
|
|
dependents: Set<Script> = new Set();
|
|
|
|
/** Scripts that are imported by this one, either directly or through an import chain */
|
|
|
|
dependencies: Map<ScriptURL, Script> = new Map();
|
2021-09-05 01:09:30 +02:00
|
|
|
|
2023-03-06 04:39:42 +01:00
|
|
|
constructor(fn = "", code = "", server = "") {
|
2021-09-05 01:09:30 +02:00
|
|
|
this.filename = fn;
|
|
|
|
this.code = code;
|
2021-10-07 23:55:49 +02:00
|
|
|
this.server = server; // hostname of server this script is on
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
|
|
|
|
2022-10-04 12:40:10 +02:00
|
|
|
/** Download the script as a file */
|
2021-09-05 01:09:30 +02:00
|
|
|
download(): void {
|
2021-11-26 04:06:13 +01:00
|
|
|
const filename = this.filename;
|
2021-09-05 01:09:30 +02:00
|
|
|
const file = new Blob([this.code], { type: "text/plain" });
|
2022-07-20 08:36:21 +02:00
|
|
|
const a = document.createElement("a"),
|
|
|
|
url = URL.createObjectURL(file);
|
|
|
|
a.href = url;
|
|
|
|
a.download = filename;
|
|
|
|
document.body.appendChild(a);
|
|
|
|
a.click();
|
|
|
|
setTimeout(function () {
|
|
|
|
document.body.removeChild(a);
|
|
|
|
window.URL.revokeObjectURL(url);
|
|
|
|
}, 0);
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
|
|
|
|
2023-04-07 06:33:51 +02:00
|
|
|
/** Invalidates the current script module and related data, e.g. when modifying the file. */
|
|
|
|
invalidateModule(): void {
|
|
|
|
// Always clear ram usage
|
|
|
|
this.ramUsage = undefined;
|
|
|
|
this.ramUsageEntries = undefined;
|
|
|
|
// Early return if there's already no URL
|
|
|
|
if (!this.url) return;
|
|
|
|
this.module = undefined;
|
|
|
|
queueUrlRevoke(this.url);
|
|
|
|
this.url = undefined;
|
|
|
|
for (const dependency of this.dependencies.values()) dependency.dependents.delete(this);
|
|
|
|
this.dependencies.clear();
|
|
|
|
for (const dependent of this.dependents) dependent.invalidateModule();
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Save a script from the script editor
|
|
|
|
* @param {string} code - The new contents of the script
|
|
|
|
* @param {Script[]} otherScripts - Other scripts on the server. Used to process imports
|
|
|
|
*/
|
2023-04-07 06:33:51 +02:00
|
|
|
saveScript(filename: string, code: string, hostname: string): void {
|
|
|
|
this.invalidateModule();
|
2022-01-15 04:01:11 +01:00
|
|
|
this.code = Script.formatCode(code);
|
2021-09-25 07:06:17 +02:00
|
|
|
this.filename = filename;
|
2021-10-07 23:55:49 +02:00
|
|
|
this.server = hostname;
|
2023-03-06 04:39:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Gets the ram usage, while also attempting to update it if it's currently null */
|
|
|
|
getRamUsage(otherScripts: Script[]): number | null {
|
|
|
|
if (this.ramUsage) return this.ramUsage;
|
|
|
|
this.updateRamUsage(otherScripts);
|
2023-04-07 06:33:51 +02:00
|
|
|
return this.ramUsage ?? null;
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calculates and updates the script's RAM usage based on its code
|
|
|
|
* @param {Script[]} otherScripts - Other scripts on the server. Used to process imports
|
|
|
|
*/
|
2022-08-29 08:41:17 +02:00
|
|
|
updateRamUsage(otherScripts: Script[]): void {
|
2023-03-06 04:39:42 +01:00
|
|
|
const ramCalc = calculateRamUsage(this.code, otherScripts);
|
|
|
|
if (ramCalc.cost >= RamCostConstants.Base) {
|
|
|
|
this.ramUsage = roundToTwo(ramCalc.cost);
|
|
|
|
this.ramUsageEntries = ramCalc.entries;
|
2023-04-07 06:33:51 +02:00
|
|
|
} else delete this.ramUsage;
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
|
|
|
|
2021-10-13 01:23:36 +02:00
|
|
|
imports(): string[] {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
2023-04-07 06:33:51 +02:00
|
|
|
/** The keys that are relevant in a save file */
|
|
|
|
static savedKeys = ["code", "filename", "server"] as const;
|
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
// Serialize the current object to a JSON save state
|
2022-07-15 01:00:10 +02:00
|
|
|
toJSON(): IReviverValue {
|
2023-04-07 06:33:51 +02:00
|
|
|
return Generic_toJSON("Script", this, Script.savedKeys);
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Initializes a Script Object from a JSON save state
|
2022-07-15 01:00:10 +02:00
|
|
|
static fromJSON(value: IReviverValue): Script {
|
2023-04-07 06:33:51 +02:00
|
|
|
return Generic_fromJSON(Script, value.data, Script.savedKeys);
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
2022-01-14 11:14:56 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Formats code: Removes the starting & trailing whitespace
|
|
|
|
* @param {string} code - The code to format
|
|
|
|
* @returns The formatted code
|
|
|
|
*/
|
2022-01-20 22:11:48 +01:00
|
|
|
static formatCode(code: string): string {
|
2022-01-14 11:14:56 +01:00
|
|
|
return code.replace(/^\s+|\s+$/g, "");
|
|
|
|
}
|
2019-03-03 04:15:10 +01:00
|
|
|
}
|
|
|
|
|
2023-04-07 06:33:51 +02:00
|
|
|
constructorsForReviver.Script = Script;
|