SCRIPTS: Script modules are reused when they are imported (#461)

Also corrects some compile race conditions.
This commit is contained in:
Snarling
2023-04-07 00:33:51 -04:00
committed by GitHub
parent f5cddb6984
commit 04d49e3a6d
67 changed files with 428 additions and 561 deletions

16
package-lock.json generated
View File

@@ -6068,9 +6068,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001414", "version": "1.0.30001473",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001414.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001473.tgz",
"integrity": "sha512-t55jfSaWjCdocnFdKQoO+d2ct9C59UZg4dY3OnUlSZ447r8pUtIKdp0hpAzrGFultmTC+Us+KpKi4GZl/LXlFg==", "integrity": "sha512-ewDad7+D2vlyy+E4UJuVfiBsU69IL+8oVmTuZnH5Q6CIUbxNfI50uVpRHbUPDD6SUaN2o0Lh4DhTrvLG/Tn1yg==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@@ -6079,6 +6079,10 @@
{ {
"type": "tidelift", "type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/caniuse-lite" "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
} }
] ]
}, },
@@ -20196,9 +20200,9 @@
"dev": true "dev": true
}, },
"caniuse-lite": { "caniuse-lite": {
"version": "1.0.30001414", "version": "1.0.30001473",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001414.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001473.tgz",
"integrity": "sha512-t55jfSaWjCdocnFdKQoO+d2ct9C59UZg4dY3OnUlSZ447r8pUtIKdp0hpAzrGFultmTC+Us+KpKi4GZl/LXlFg==" "integrity": "sha512-ewDad7+D2vlyy+E4UJuVfiBsU69IL+8oVmTuZnH5Q6CIUbxNfI50uVpRHbUPDD6SUaN2o0Lh4DhTrvLG/Tn1yg=="
}, },
"chalk": { "chalk": {
"version": "2.4.2", "version": "2.4.2",

View File

@@ -6,7 +6,7 @@ import { Factions } from "../Faction/Factions";
import { formatPercent } from "../ui/formatNumber"; import { formatPercent } from "../ui/formatNumber";
import { Money } from "../ui/React/Money"; import { Money } from "../ui/React/Money";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
import { FactionNames } from "../Faction/data/FactionNames"; import { FactionNames } from "../Faction/data/FactionNames";
import { Player } from "@player"; import { Player } from "@player";
import { AugmentationNames } from "./data/AugmentationNames"; import { AugmentationNames } from "./data/AugmentationNames";
@@ -607,4 +607,4 @@ export class Augmentation {
} }
} }
Reviver.constructors.Augmentation = Augmentation; constructorsForReviver.Augmentation = Augmentation;

View File

@@ -1,6 +1,6 @@
import { getRandomInt } from "../utils/helpers/getRandomInt"; import { getRandomInt } from "../utils/helpers/getRandomInt";
import { addOffset } from "../utils/helpers/addOffset"; import { addOffset } from "../utils/helpers/addOffset";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
import { BladeburnerConstants } from "./data/Constants"; import { BladeburnerConstants } from "./data/Constants";
import { Bladeburner } from "./Bladeburner"; import { Bladeburner } from "./Bladeburner";
import { Person } from "../PersonObjects/Person"; import { Person } from "../PersonObjects/Person";
@@ -304,4 +304,4 @@ export class Action {
} }
} }
Reviver.constructors.Action = Action; constructorsForReviver.Action = Action;

View File

@@ -1,4 +1,4 @@
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
interface IParams { interface IParams {
name?: string; name?: string;
@@ -23,4 +23,4 @@ export class ActionIdentifier {
} }
} }
Reviver.constructors.ActionIdentifier = ActionIdentifier; constructorsForReviver.ActionIdentifier = ActionIdentifier;

View File

@@ -1,5 +1,5 @@
import { Operation, IOperationParams } from "./Operation"; import { Operation, IOperationParams } from "./Operation";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
export class BlackOperation extends Operation { export class BlackOperation extends Operation {
constructor(params: IOperationParams | null = null) { constructor(params: IOperationParams | null = null) {
@@ -29,4 +29,4 @@ export class BlackOperation extends Operation {
} }
} }
Reviver.constructors.BlackOperation = BlackOperation; constructorsForReviver.BlackOperation = BlackOperation;

View File

@@ -1,4 +1,4 @@
import { Reviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver"; import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver";
import { ActionIdentifier } from "./ActionIdentifier"; import { ActionIdentifier } from "./ActionIdentifier";
import { ActionTypes } from "./data/ActionTypes"; import { ActionTypes } from "./data/ActionTypes";
import { Growths } from "./data/Growths"; import { Growths } from "./data/Growths";
@@ -2390,4 +2390,4 @@ export class Bladeburner {
} }
} }
Reviver.constructors.Bladeburner = Bladeburner; constructorsForReviver.Bladeburner = Bladeburner;

View File

@@ -1,6 +1,6 @@
import { BladeburnerConstants } from "./data/Constants"; import { BladeburnerConstants } from "./data/Constants";
import { getRandomInt } from "../utils/helpers/getRandomInt"; import { getRandomInt } from "../utils/helpers/getRandomInt";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
import { addOffset } from "../utils/helpers/addOffset"; import { addOffset } from "../utils/helpers/addOffset";
import { CityName } from "../Enums"; import { CityName } from "../Enums";
@@ -169,4 +169,4 @@ export class City {
} }
} }
Reviver.constructors.City = City; constructorsForReviver.City = City;

View File

@@ -1,6 +1,6 @@
import { Bladeburner } from "./Bladeburner"; import { Bladeburner } from "./Bladeburner";
import { Action, IActionParams } from "./Action"; import { Action, IActionParams } from "./Action";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
export class Contract extends Action { export class Contract extends Action {
constructor(params: IActionParams | null = null) { constructor(params: IActionParams | null = null) {
@@ -20,4 +20,4 @@ export class Contract extends Action {
} }
} }
Reviver.constructors.Contract = Contract; constructorsForReviver.Contract = Contract;

View File

@@ -1,7 +1,7 @@
import { Bladeburner } from "./Bladeburner"; import { Bladeburner } from "./Bladeburner";
import { BladeburnerConstants } from "./data/Constants"; import { BladeburnerConstants } from "./data/Constants";
import { Action, IActionParams } from "./Action"; import { Action, IActionParams } from "./Action";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
export interface IOperationParams extends IActionParams { export interface IOperationParams extends IActionParams {
reqdRank?: number; reqdRank?: number;
@@ -53,4 +53,4 @@ export class Operation extends Action {
} }
} }
Reviver.constructors.Operation = Operation; constructorsForReviver.Operation = Operation;

View File

@@ -1,6 +1,6 @@
import { codingContractTypesMetadata, DescriptionFunc, GeneratorFunc, SolverFunc } from "./data/codingcontracttypes"; import { codingContractTypesMetadata, DescriptionFunc, GeneratorFunc, SolverFunc } from "./data/codingcontracttypes";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "./utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "./utils/JSONReviver";
import { CodingContractEvent } from "./ui/React/CodingContractModal"; import { CodingContractEvent } from "./ui/React/CodingContractModal";
/* tslint:disable:no-magic-numbers completed-docs max-classes-per-file no-console */ /* tslint:disable:no-magic-numbers completed-docs max-classes-per-file no-console */
@@ -172,4 +172,4 @@ export class CodingContract {
} }
} }
Reviver.constructors.CodingContract = CodingContract; constructorsForReviver.CodingContract = CodingContract;

View File

@@ -2,7 +2,7 @@ import { CompanyPosition } from "./CompanyPosition";
import * as posNames from "./data/JobTracks"; import * as posNames from "./data/JobTracks";
import { favorToRep, repToFavor } from "../Faction/formulas/favor"; import { favorToRep, repToFavor } from "../Faction/formulas/favor";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
export interface IConstructorParams { export interface IConstructorParams {
name: string; name: string;
@@ -143,4 +143,4 @@ export class Company {
} }
} }
Reviver.constructors.Company = Company; constructorsForReviver.Company = Company;

View File

@@ -10,7 +10,7 @@ import { LiteratureNames } from "../Literature/data/LiteratureNames";
import { Player } from "@player"; import { Player } from "@player";
import { dialogBoxCreate } from "../ui/React/DialogBox"; import { dialogBoxCreate } from "../ui/React/DialogBox";
import { Reviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver"; import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver";
import { isString } from "../utils/helpers/isString"; import { isString } from "../utils/helpers/isString";
import { CityName } from "../Enums"; import { CityName } from "../Enums";
import { CorpStateName } from "@nsdefs"; import { CorpStateName } from "@nsdefs";
@@ -468,4 +468,4 @@ export class Corporation {
} }
} }
Reviver.constructors.Corporation = Corporation; constructorsForReviver.Corporation = Corporation;

View File

@@ -1,5 +1,5 @@
import { CorpStateName } from "@nsdefs"; import { CorpStateName } from "@nsdefs";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
import { stateNames } from "./data/Constants"; import { stateNames } from "./data/Constants";
export class CorporationState { export class CorporationState {
// Number representing what state the Corporation is in. The number // Number representing what state the Corporation is in. The number
@@ -36,4 +36,4 @@ export class CorporationState {
} }
} }
Reviver.constructors.CorporationState = CorporationState; constructorsForReviver.CorporationState = CorporationState;

View File

@@ -1,4 +1,4 @@
import { Reviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver"; import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver";
import { CityName } from "../Enums"; import { CityName } from "../Enums";
import { IndustryResearchTrees, IndustriesData } from "./IndustryData"; import { IndustryResearchTrees, IndustriesData } from "./IndustryData";
import * as corpConstants from "./data/Constants"; import * as corpConstants from "./data/Constants";
@@ -1238,4 +1238,4 @@ export class Industry {
} }
} }
Reviver.constructors.Industry = Industry; constructorsForReviver.Industry = Industry;

View File

@@ -1,5 +1,5 @@
import { CorpMaterialName } from "@nsdefs"; import { CorpMaterialName } from "@nsdefs";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
import { materialNames } from "./data/Constants"; import { materialNames } from "./data/Constants";
import { Export } from "./Export"; import { Export } from "./Export";
import { MaterialInfo } from "./MaterialInfo"; import { MaterialInfo } from "./MaterialInfo";
@@ -140,4 +140,4 @@ export class Material {
} }
} }
Reviver.constructors.Material = Material; constructorsForReviver.Material = Material;

View File

@@ -1,6 +1,6 @@
import { EmployeePositions } from "./data/Enums"; import { EmployeePositions } from "./data/Enums";
import * as corpConstants from "./data/Constants"; import * as corpConstants from "./data/Constants";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
import { Industry } from "./Industry"; import { Industry } from "./Industry";
import { Corporation } from "./Corporation"; import { Corporation } from "./Corporation";
import { getRandomInt } from "../utils/helpers/getRandomInt"; import { getRandomInt } from "../utils/helpers/getRandomInt";
@@ -272,4 +272,4 @@ export class OfficeSpace {
} }
} }
Reviver.constructors.OfficeSpace = OfficeSpace; constructorsForReviver.OfficeSpace = OfficeSpace;

View File

@@ -5,7 +5,7 @@ import { IndustriesData } from "./IndustryData";
import { createCityMap } from "../Locations/createCityMap"; import { createCityMap } from "../Locations/createCityMap";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
import { getRandomInt } from "../utils/helpers/getRandomInt"; import { getRandomInt } from "../utils/helpers/getRandomInt";
import { CityName } from "../Enums"; import { CityName } from "../Enums";
import { materialNames } from "./data/Constants"; import { materialNames } from "./data/Constants";
@@ -285,4 +285,4 @@ export class Product {
} }
} }
Reviver.constructors.Product = Product; constructorsForReviver.Product = Product;

View File

@@ -2,7 +2,7 @@ import { Material } from "./Material";
import { Corporation } from "./Corporation"; import { Corporation } from "./Corporation";
import { Industry } from "./Industry"; import { Industry } from "./Industry";
import { MaterialInfo } from "./MaterialInfo"; import { MaterialInfo } from "./MaterialInfo";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
import { exceptionAlert } from "../utils/helpers/exceptionAlert"; import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import { CityName } from "../Enums"; import { CityName } from "../Enums";
import { CorpMaterialName } from "@nsdefs"; import { CorpMaterialName } from "@nsdefs";
@@ -107,4 +107,4 @@ export class Warehouse {
} }
} }
Reviver.constructors.Warehouse = Warehouse; constructorsForReviver.Warehouse = Warehouse;

View File

@@ -1,5 +1,5 @@
import { Fragment, FragmentById } from "./Fragment"; import { Fragment, FragmentById } from "./Fragment";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
export interface IActiveFragmentParams { export interface IActiveFragmentParams {
x: number; x: number;
@@ -82,4 +82,4 @@ export class ActiveFragment {
} }
} }
Reviver.constructors.ActiveFragment = ActiveFragment; constructorsForReviver.ActiveFragment = ActiveFragment;

View File

@@ -6,7 +6,7 @@ import { BaseGift } from "./BaseGift";
import { Factions } from "../Faction/Factions"; import { Factions } from "../Faction/Factions";
import { CalculateEffect } from "./formulas/effect"; import { CalculateEffect } from "./formulas/effect";
import { StaneksGiftEvents } from "./StaneksGiftEvents"; import { StaneksGiftEvents } from "./StaneksGiftEvents";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
import { StanekConstants } from "./data/Constants"; import { StanekConstants } from "./data/Constants";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { Player } from "@player"; import { Player } from "@player";
@@ -255,4 +255,4 @@ export class StaneksGift extends BaseGift {
} }
} }
Reviver.constructors.StaneksGift = StaneksGift; constructorsForReviver.StaneksGift = StaneksGift;

View File

@@ -1,6 +1,6 @@
import { FactionInfo, FactionInfos } from "./FactionInfo"; import { FactionInfo, FactionInfos } from "./FactionInfo";
import { favorToRep, repToFavor } from "./formulas/favor"; import { favorToRep, repToFavor } from "./formulas/favor";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
export class Faction { export class Faction {
/** /**
@@ -71,4 +71,4 @@ export class Faction {
} }
} }
Reviver.constructors.Faction = Faction; constructorsForReviver.Faction = Faction;

View File

@@ -7,7 +7,7 @@
import { Factions } from "../Faction/Factions"; import { Factions } from "../Faction/Factions";
import { dialogBoxCreate } from "../ui/React/DialogBox"; import { dialogBoxCreate } from "../ui/React/DialogBox";
import { Reviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver"; import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver";
import { exceptionAlert } from "../utils/helpers/exceptionAlert"; import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import { getRandomInt } from "../utils/helpers/getRandomInt"; import { getRandomInt } from "../utils/helpers/getRandomInt";
@@ -403,4 +403,4 @@ export class Gang {
} }
} }
Reviver.constructors.Gang = Gang; constructorsForReviver.Gang = Gang;

View File

@@ -5,7 +5,7 @@ import { GangMemberUpgrades } from "./GangMemberUpgrades";
import { IAscensionResult } from "./IAscensionResult"; import { IAscensionResult } from "./IAscensionResult";
import { Player } from "@player"; import { Player } from "@player";
import { Gang } from "./Gang"; import { Gang } from "./Gang";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
import { import {
calculateRespectGain, calculateRespectGain,
calculateMoneyGain, calculateMoneyGain,
@@ -332,4 +332,4 @@ export class GangMember {
} }
} }
Reviver.constructors.GangMember = GangMember; constructorsForReviver.GangMember = GangMember;

View File

@@ -17,7 +17,7 @@ import {
import { HacknetNodeConstants } from "./data/Constants"; import { HacknetNodeConstants } from "./data/Constants";
import { dialogBoxCreate } from "../ui/React/DialogBox"; import { dialogBoxCreate } from "../ui/React/DialogBox";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
import { ObjectValidator, minMax } from "../utils/Validator"; import { ObjectValidator, minMax } from "../utils/Validator";
export class HacknetNode implements IHacknetNode { export class HacknetNode implements IHacknetNode {
@@ -131,4 +131,4 @@ export class HacknetNode implements IHacknetNode {
} }
} }
Reviver.constructors.HacknetNode = HacknetNode; constructorsForReviver.HacknetNode = HacknetNode;

View File

@@ -15,7 +15,7 @@ import {
import { createRandomIp } from "../utils/IPAddress"; import { createRandomIp } from "../utils/IPAddress";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
import { Player } from "@player"; import { Player } from "@player";
interface IConstructorParams { interface IConstructorParams {
@@ -153,4 +153,4 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
} }
} }
Reviver.constructors.HacknetServer = HacknetServer; constructorsForReviver.HacknetServer = HacknetServer;

View File

@@ -9,7 +9,7 @@
import { HashUpgrades } from "./HashUpgrades"; import { HashUpgrades } from "./HashUpgrades";
import { HashUpgrade } from "./HashUpgrade"; import { HashUpgrade } from "./HashUpgrade";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
export class HashManager { export class HashManager {
// Max number of hashes this can hold. Equal to the sum of capacities of // Max number of hashes this can hold. Equal to the sum of capacities of
@@ -156,4 +156,4 @@ export class HashManager {
} }
} }
Reviver.constructors.HashManager = HashManager; constructorsForReviver.HashManager = HashManager;

View File

@@ -234,8 +234,8 @@ function argsToString(args: unknown[]): string {
/** Creates an error message string containing hostname, scriptname, and the error message msg */ /** Creates an error message string containing hostname, scriptname, and the error message msg */
function makeBasicErrorMsg(ws: WorkerScript | ScriptDeath, msg: string, type = "RUNTIME"): string { function makeBasicErrorMsg(ws: WorkerScript | ScriptDeath, msg: string, type = "RUNTIME"): string {
if (ws instanceof WorkerScript) { if (ws instanceof WorkerScript) {
for (const scriptUrl of ws.scriptRef.dependencies) { for (const [scriptUrl, script] of ws.scriptRef.dependencies) {
msg = msg.replace(new RegExp(scriptUrl.url, "g"), scriptUrl.filename); msg = msg.replace(new RegExp(scriptUrl, "g"), script.filename);
} }
} }
return `${type} ERROR\n${ws.name}@${ws.hostname} (PID - ${ws.pid})\n\n${msg}`; return `${type} ERROR\n${ws.name}@${ws.hostname} (PID - ${ws.pid})\n\n${msg}`;
@@ -248,20 +248,19 @@ function makeRuntimeErrorMsg(ctx: NetscriptContext, msg: string, type = "RUNTIME
const stack = errstack.split("\n").slice(1); const stack = errstack.split("\n").slice(1);
const ws = ctx.workerScript; const ws = ctx.workerScript;
const caller = ctx.functionPath; const caller = ctx.functionPath;
const scripts = ws.getServer().scripts;
const userstack = []; const userstack = [];
for (const stackline of stack) { for (const stackline of stack) {
let filename; const filename = (() => {
for (const script of scripts) { // Filename is current file if url found
if (script.filename && stackline.includes(script.filename)) { if (ws.scriptRef.url && stackline.includes(ws.scriptRef.url)) return ws.scriptRef.filename;
filename = script.filename; // Also check urls for dependencies
for (const [url, script] of ws.scriptRef.dependencies) if (stackline.includes(url)) return script.filename;
// Check for filenames directly if no URL found
if (stackline.includes(ws.scriptRef.filename)) return ws.scriptRef.filename;
for (const script of ws.scriptRef.dependencies.values()) {
if (stackline.includes(script.filename)) return script.filename;
} }
for (const dependency of script.dependencies) { })();
if (stackline.includes(dependency.filename)) {
filename = dependency.filename;
}
}
}
if (!filename) continue; if (!filename) continue;
interface ILine { interface ILine {

View File

@@ -893,18 +893,15 @@ export const ns: InternalAPI<NSFull> = {
} }
destScript.code = sourceScript.code; destScript.code = sourceScript.code;
// Set ramUsage to null in order to force a recalculation prior to next run. // Set ramUsage to null in order to force a recalculation prior to next run.
destScript.ramUsage = null; destScript.invalidateModule();
destScript.markUpdated();
helpers.log(ctx, () => `WARNING: File '${file}' overwritten on '${destServer?.hostname}'`); helpers.log(ctx, () => `WARNING: File '${file}' overwritten on '${destServer?.hostname}'`);
continue; continue;
} }
// Create new script if it does not already exist // Create new script if it does not already exist
const newScript = new Script(file); const newScript = new Script(file, sourceScript.code, destServer.hostname);
newScript.code = sourceScript.code; // If the script being copied has no dependencies, reuse the module / URL
// Set ramUsage to null in order to force a recalculation prior to next run. // The new script will not show up in the correct location in the sources tab because it is just reusing the module from a different server
newScript.ramUsage = null;
newScript.server = destServer.hostname;
destServer.scripts.push(newScript); destServer.scripts.push(newScript);
helpers.log(ctx, () => `File '${file}' copied over to '${destServer?.hostname}'.`); helpers.log(ctx, () => `File '${file}' copied over to '${destServer?.hostname}'.`);
} }
@@ -1389,7 +1386,7 @@ export const ns: InternalAPI<NSFull> = {
} }
mode === "w" ? (script.code = String(data)) : (script.code += data); mode === "w" ? (script.code = String(data)) : (script.code += data);
// Set ram to null so a recalc is performed the next time ram usage is needed // Set ram to null so a recalc is performed the next time ram usage is needed
script.ramUsage = null; script.invalidateModule();
return; return;
} else { } else {
// Write to text file // Write to text file
@@ -1831,14 +1828,14 @@ export const ns: InternalAPI<NSFull> = {
dest_file.text = source_file.text; dest_file.text = source_file.text;
} else if (dest_file instanceof Script && source_file instanceof Script) { } else if (dest_file instanceof Script && source_file instanceof Script) {
dest_file.code = source_file.code; dest_file.code = source_file.code;
dest_file.markUpdated(); dest_file.invalidateModule();
} }
destServer.removeFile(source); destServer.removeFile(source);
} else { } else {
source_file.filename = destination; source_file.filename = destination;
if (source_file instanceof Script) { if (source_file instanceof Script) {
source_file.markUpdated(); source_file.invalidateModule();
} }
} }
}, },

View File

@@ -5,8 +5,7 @@
import * as walk from "acorn-walk"; import * as walk from "acorn-walk";
import { parse } from "acorn"; import { parse } from "acorn";
import { ScriptUrl } from "./Script/ScriptUrl"; import { Script, ScriptURL } from "./Script/Script";
import { Script } from "./Script/Script";
import { areImportsEquals } from "./Terminal/DirectoryHelpers"; import { areImportsEquals } from "./Terminal/DirectoryHelpers";
import { ScriptModule } from "./Script/ScriptModule"; import { ScriptModule } from "./Script/ScriptModule";
@@ -18,6 +17,24 @@ function makeScriptBlob(code: string): Blob {
return new Blob([code], { type: "text/javascript" }); return new Blob([code], { type: "text/javascript" });
} }
const urlsToRevoke: ScriptURL[] = [];
let activeCompilations = 0;
/** Function to queue up revoking of script URLs. If there's no active compilation, just revoke it now. */
export const queueUrlRevoke = (url: ScriptURL) => {
if (!activeCompilations) return URL.revokeObjectURL(url);
urlsToRevoke.push(url);
};
/** Function to revoke any expired urls */
function triggerURLRevokes() {
if (activeCompilations === 0) {
// Revoke all pending revoke URLS
urlsToRevoke.forEach((url) => URL.revokeObjectURL(url));
// Remove all url strings from array
urlsToRevoke.length = 0;
}
}
// Webpack likes to turn the import into a require, which sort of // Webpack likes to turn the import into a require, which sort of
// but not really behaves like import. So we use a "magic comment" // but not really behaves like import. So we use a "magic comment"
// to disable that and leave it as a dynamic import. // to disable that and leave it as a dynamic import.
@@ -29,171 +46,113 @@ function makeScriptBlob(code: string): Blob {
// import() is not a function, so it can't be replaced. We need this separate // import() is not a function, so it can't be replaced. We need this separate
// config object to provide a hook point. // config object to provide a hook point.
export const config = { export const config = {
doImport(url: string): Promise<ScriptModule> { doImport(url: ScriptURL): Promise<ScriptModule> {
return import(/*webpackIgnore:true*/ url); return import(/*webpackIgnore:true*/ url);
}, },
}; };
export async function compile(script: Script, scripts: Script[]): Promise<ScriptModule> { export function compile(script: Script, scripts: Script[]): Promise<ScriptModule> {
//!shouldCompile ensures that script.module is non-null, hence the "as". // Return the module if it already exists
if (!shouldCompile(script, scripts)) return script.module as Promise<ScriptModule>; if (script.module) return script.module;
script.queueCompile = true; // While importing, use an existing url or generate a new one.
//If we're already in the middle of compiling (script.module has not resolved yet), wait for the previous compilation to finish if (!script.url) script.url = generateScriptUrl(script, scripts, []);
//If script.module is null, this does nothing. activeCompilations++;
await script.module; script.module = config
//If multiple compiles were called on the same script before a compilation could be completed this ensures only one compilation is actually performed. .doImport(script.url)
if (!script.queueCompile) return script.module as Promise<ScriptModule>; .catch((e) => {
script.queueCompile = false; script.invalidateModule();
const uurls = _getScriptUrls(script, scripts, []); console.error(`Error occurred while attempting to compile ${script.filename} on ${script.server}:`);
const url = uurls[uurls.length - 1].url; console.error(e);
if (script.url && script.url !== url) URL.revokeObjectURL(script.url); throw e;
})
if (script.dependencies.length > 0) script.dependencies.forEach((dep) => URL.revokeObjectURL(dep.url)); .finally(() => {
script.url = uurls[uurls.length - 1].url; activeCompilations--;
// The URL at the top is the one we want to import. It will triggerURLRevokes();
// recursively import all the other modules in the urlStack. });
script.module = config.doImport(uurls[uurls.length - 1].url);
script.dependencies = uurls;
return script.module; return script.module;
} }
function isDependencyOutOfDate(filename: string, scripts: Script[], scriptModuleSequenceNumber: number): boolean { /** Add the necessary dependency relationships for a script.
const depScript = scripts.find((s) => s.filename == filename); * Dependents are used only for passing invalidation up an import tree, so only direct dependents need to be stored.
* Direct and indirect dependents need to have the current url/script added to their dependency map for error text.
// 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 * This should only be called once the script has an assigned URL. */
* @param {Script[]} scripts function addDependencyInfo(script: Script, dependents: Script[]) {
*/ if (!script.url) throw new Error(`addDependencyInfo called without an assigned script URL (${script.filename})`);
function shouldCompile(script: Script, scripts: Script[]): boolean { if (dependents.length) {
if (!script.module) return true; script.dependents.add(dependents[dependents.length - 1]);
if (script.dependencies.some((dep) => isDependencyOutOfDate(dep.filename, scripts, script.moduleSequenceNumber))) { for (const dependent of dependents) dependent.dependencies.set(script.url, script);
script.module = null;
return true;
} }
return false;
} }
// Gets a stack of blob urls, the top/right-most element being
// the blob url for the named script on the named server.
//
// - script -- the script for whom we are getting a URL.
// - scripts -- all the scripts available on this server
// - seen -- The modules above this one -- to prevent mutual dependency.
//
// TODO 2.3 consider: Possibly reusing modules when imported in different locations. Per previous notes, this may
// require a topo-sort then url-izing from leaf-most to root-most.
/** /**
* @param {Script} script * @param script the script that needs a URL assigned
* @param {Script[]} scripts * @param scripts array of other scripts on the server
* @param {Script[]} seen * @param dependents All scripts that were higher up in the import tree in a recursive call.
* @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. function generateScriptUrl(script: Script, scripts: Script[], dependents: Script[]): ScriptURL {
function _getScriptUrls(script: Script, scripts: Script[], seen: Script[]): ScriptUrl[] { // Early return for recursive calls where the script already has a URL
if (script.url) {
addDependencyInfo(script, dependents);
return script.url;
}
// Inspired by: https://stackoverflow.com/a/43834063/91401 // Inspired by: https://stackoverflow.com/a/43834063/91401
const urlStack: ScriptUrl[] = []; const ast = parse(script.code, { sourceType: "module", ecmaVersion: "latest", ranges: true });
// Seen contains the dependents of the current script. Make sure we include that in the script dependents. interface importNode {
for (const dependent of seen) { filename: string;
if (!script.dependents.some((s) => s.server === dependent.server && s.filename == dependent.filename)) { start: number;
script.dependents.push({ server: dependent.server, filename: dependent.filename }); end: number;
}
} }
seen.push(script); const importNodes: importNode[] = [];
try { // Walk the nodes of this tree and find any import declaration statements.
// Replace every import statement with an import to a blob url containing walk.simple(ast, {
// the corresponding script. E.g. ImportDeclaration(node: Node) {
// // Push this import onto the stack to replace
// import {foo} from "bar.js"; if (!node.source) return;
// importNodes.push({
// becomes filename: node.source.value,
// start: node.source.range[0] + 1,
// import {foo} from "blob://<uuid>" end: node.source.range[1] - 1,
// });
// Where the blob URL contains the script content. },
ExportNamedDeclaration(node: Node) {
if (!node.source) return;
importNodes.push({
filename: node.source.value,
start: node.source.range[0] + 1,
end: node.source.range[1] - 1,
});
},
ExportAllDeclaration(node: Node) {
if (!node.source) return;
importNodes.push({
filename: node.source.value,
start: node.source.range[0] + 1,
end: node.source.range[1] - 1,
});
},
});
// Sort the nodes from last start index to first. This replaces the last import with a blob first,
// preventing the ranges for other imports from being shifted.
importNodes.sort((a, b) => b.start - a.start);
let newCode = script.code;
// Loop through each node and replace the script name with a blob url.
for (const node of importNodes) {
const filename = node.filename.startsWith("./") ? node.filename.substring(2) : node.filename;
// Parse the code into an ast tree // Find the corresponding script.
const ast = parse(script.code, { sourceType: "module", ecmaVersion: "latest", ranges: true }); const importedScript = scripts.find((s) => areImportsEquals(s.filename, filename));
interface importNode { if (!importedScript) continue;
filename: string;
start: number;
end: number;
}
const importNodes: importNode[] = [];
// Walk the nodes of this tree and find any import declaration statements.
walk.simple(ast, {
ImportDeclaration(node: Node) {
// Push this import onto the stack to replace
if (!node.source) return;
importNodes.push({
filename: node.source.value,
start: node.source.range[0] + 1,
end: node.source.range[1] - 1,
});
},
ExportNamedDeclaration(node: Node) {
if (!node.source) return;
importNodes.push({
filename: node.source.value,
start: node.source.range[0] + 1,
end: node.source.range[1] - 1,
});
},
ExportAllDeclaration(node: Node) {
if (!node.source) return;
importNodes.push({
filename: node.source.value,
start: node.source.range[0] + 1,
end: node.source.range[1] - 1,
});
},
});
// Sort the nodes from last start index to first. This replaces the last import with a blob first,
// preventing the ranges for other imports from being shifted.
importNodes.sort((a, b) => b.start - a.start);
let transformedCode = script.code;
// Loop through each node and replace the script name with a blob url.
for (const node of importNodes) {
const filename = node.filename.startsWith("./") ? node.filename.substring(2) : node.filename;
// Find the corresponding script. importedScript.url = generateScriptUrl(importedScript, scripts, [...dependents, script]);
const matchingScripts = scripts.filter((s) => areImportsEquals(s.filename, filename)); newCode = newCode.substring(0, node.start) + importedScript.url + newCode.substring(node.end);
if (matchingScripts.length === 0) continue;
const [importedScript] = matchingScripts;
const urls = _getScriptUrls(importedScript, scripts, seen);
// The top url in the stack is the replacement import file for this script.
urlStack.push(...urls);
const blob = urls[urls.length - 1].url;
// Replace the blob inside the import statement.
transformedCode = transformedCode.substring(0, node.start) + blob + transformedCode.substring(node.end);
}
// We automatically define a print function() in the NetscriptJS module so that
// accidental calls to window.print() do not bring up the "print screen" dialog
transformedCode += `\n//# sourceURL=${script.server}/${script.filename}`;
const blob = URL.createObjectURL(makeScriptBlob(transformedCode));
// Push the blob URL onto the top of the stack.
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.
for (const url of urlStack) URL.revokeObjectURL(url.url);
throw err;
} finally {
seen.pop();
} }
newCode += `\n//# sourceURL=${script.server}/${script.filename}`;
// At this point we have the full code and can construct a new blob / assign the URL.
script.url = URL.createObjectURL(makeScriptBlob(newCode)) as ScriptURL;
addDependencyInfo(script, dependents);
return script.url;
} }

View File

@@ -51,16 +51,16 @@ export function prestigeWorkerScripts(): void {
async function startNetscript2Script(workerScript: WorkerScript): Promise<void> { async function startNetscript2Script(workerScript: WorkerScript): Promise<void> {
const scripts = workerScript.getServer().scripts; const scripts = workerScript.getServer().scripts;
const script = workerScript.getScript(); const script = workerScript.getScript();
if (script === null) throw "workerScript had no associated script. This is a bug."; if (!script) throw "workerScript had no associated script. This is a bug.";
if (!script.ramUsage) throw "Attempting to start a script with no calculated ram cost. This is a bug.";
const loadedModule = await compile(script, scripts);
const ns = workerScript.env.vars; const ns = workerScript.env.vars;
if (!ns) throw `${script.filename} cannot be run because the NS object hasn't been constructed properly.`;
const loadedModule = await compile(script, scripts);
if (!loadedModule) throw `${script.filename} cannot be run because the script module won't load`; if (!loadedModule) throw `${script.filename} cannot be run because the script module won't load`;
// TODO unplanned: Better error for "unexpected reserved word" when using await in non-async function? // TODO unplanned: Better error for "unexpected reserved word" when using await in non-async function?
if (typeof loadedModule.main !== "function") if (typeof loadedModule.main !== "function")
throw `${script.filename} cannot be run because it does not have a main function.`; throw `${script.filename} cannot be run because it does not have a main function.`;
if (!ns) throw `${script.filename} cannot be run because the NS object hasn't been constructed properly.`;
await loadedModule.main(ns); await loadedModule.main(ns);
} }
@@ -295,6 +295,7 @@ export function startWorkerScript(runningScript: RunningScript, server: BaseServ
function createAndAddWorkerScript(runningScriptObj: RunningScript, server: BaseServer, parent?: WorkerScript): boolean { function createAndAddWorkerScript(runningScriptObj: RunningScript, server: BaseServer, parent?: WorkerScript): boolean {
const ramUsage = roundToTwo(runningScriptObj.ramUsage * runningScriptObj.threads); const ramUsage = roundToTwo(runningScriptObj.ramUsage * runningScriptObj.threads);
const ramAvailable = server.maxRam - server.ramUsed; const ramAvailable = server.maxRam - server.ramUsed;
// Check failure conditions before generating the workersScript and return false
if (ramUsage > ramAvailable + 0.001) { if (ramUsage > ramAvailable + 0.001) {
dialogBoxCreate( dialogBoxCreate(
`Not enough RAM to run script ${runningScriptObj.filename} with args ${arrayToString(runningScriptObj.args)}.\n` + `Not enough RAM to run script ${runningScriptObj.filename} with args ${arrayToString(runningScriptObj.args)}.\n` +
@@ -304,17 +305,18 @@ function createAndAddWorkerScript(runningScriptObj: RunningScript, server: BaseS
return false; return false;
} }
server.updateRamUsed(roundToTwo(server.ramUsed + ramUsage));
// Get the pid // Get the pid
const pid = generateNextPid(); const pid = generateNextPid();
if (pid === -1) { if (pid === -1) {
throw new Error( dialogBoxCreate(
`Failed to start script because could not find available PID. This is most ` + `Failed to start script because could not find available PID. This is most ` +
`because you have too many scripts running.`, `because you have too many scripts running.`,
); );
return false;
} }
server.updateRamUsed(roundToTwo(server.ramUsed + ramUsage));
// Create the WorkerScript. NOTE: WorkerScript ctor will set the underlying // Create the WorkerScript. NOTE: WorkerScript ctor will set the underlying
// RunningScript's PID as well // RunningScript's PID as well
const workerScript = new WorkerScript(runningScriptObj, pid, NetscriptFunctions); const workerScript = new WorkerScript(runningScriptObj, pid, NetscriptFunctions);
@@ -366,28 +368,18 @@ export function loadAllRunningScripts(): void {
// Reset each server's RAM usage to 0 // Reset each server's RAM usage to 0
server.ramUsed = 0; server.ramUsed = 0;
// Reset modules on all scripts
for (let i = 0; i < server.scripts.length; ++i) {
server.scripts[i].markUpdated();
}
if (skipScriptLoad) { if (skipScriptLoad) {
// Start game with no scripts // Start game with no scripts
server.runningScripts.length = 0; server.runningScripts.length = 0;
} else { continue;
for (let j = 0; j < server.runningScripts.length; ++j) { }
const fileName = server.runningScripts[j].filename; // Using backwards index iteration to avoid complications when removing elements during iteration.
createAndAddWorkerScript(server.runningScripts[j], server); for (let i = server.runningScripts.length - 1; i >= 0; i--) {
const runningScript = server.runningScripts[i];
if (!server.runningScripts[j]) { const success = createAndAddWorkerScript(runningScript, server);
// createAndAddWorkerScript can modify the server.runningScripts array if a script is invalid scriptCalculateOfflineProduction(runningScript);
console.error(`createAndAddWorkerScript removed ${fileName} from ${server}`); // Remove the RunningScript if the WorkerScript failed to start.
continue; if (!success) server.runningScripts.splice(i, 1);
}
// Offline production
scriptCalculateOfflineProduction(server.runningScripts[j]);
}
} }
} }
} }

View File

@@ -20,7 +20,7 @@ import { HacknetNode } from "../../Hacknet/HacknetNode";
import { HashManager } from "../../Hacknet/HashManager"; import { HashManager } from "../../Hacknet/HashManager";
import { MoneySourceTracker } from "../../utils/MoneySourceTracker"; import { MoneySourceTracker } from "../../utils/MoneySourceTracker";
import { Reviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../../utils/JSONReviver"; import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../../utils/JSONReviver";
import { PlayerAchievement } from "../../Achievements/Achievements"; import { PlayerAchievement } from "../../Achievements/Achievements";
import { cyrb53 } from "../../utils/StringHelperFunctions"; import { cyrb53 } from "../../utils/StringHelperFunctions";
import { getRandomInt } from "../../utils/helpers/getRandomInt"; import { getRandomInt } from "../../utils/helpers/getRandomInt";
@@ -175,4 +175,4 @@ export class PlayerObject extends Person implements IPlayer {
setPlayer(new PlayerObject()); setPlayer(new PlayerObject());
Reviver.constructors.PlayerObject = PlayerObject; constructorsForReviver.PlayerObject = PlayerObject;

View File

@@ -22,7 +22,7 @@ import { CityName, CrimeType, GymType, LocationName, UniversityClassType } from
import { Factions } from "../../Faction/Factions"; import { Factions } from "../../Faction/Factions";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../../utils/JSONReviver";
import { formatPercent } from "../../ui/formatNumber"; import { formatPercent } from "../../ui/formatNumber";
import { FactionWorkType } from "../../Enums"; import { FactionWorkType } from "../../Enums";
import { SleeveWork } from "./Work/Work"; import { SleeveWork } from "./Work/Work";
@@ -480,4 +480,4 @@ export class Sleeve extends Person implements SleevePerson {
} }
} }
Reviver.constructors.Sleeve = Sleeve; constructorsForReviver.Sleeve = Sleeve;

View File

@@ -1,5 +1,5 @@
import { Player } from "@player"; import { Player } from "@player";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../../../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../../../utils/JSONReviver";
import { Sleeve } from "../Sleeve"; import { Sleeve } from "../Sleeve";
import { applySleeveGains, Work, WorkType } from "./Work"; import { applySleeveGains, Work, WorkType } from "./Work";
import { CONSTANTS } from "../../../Constants"; import { CONSTANTS } from "../../../Constants";
@@ -84,4 +84,4 @@ export class SleeveBladeburnerWork extends Work {
} }
} }
Reviver.constructors.SleeveBladeburnerWork = SleeveBladeburnerWork; constructorsForReviver.SleeveBladeburnerWork = SleeveBladeburnerWork;

View File

@@ -1,4 +1,4 @@
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../../../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../../../utils/JSONReviver";
import { applySleeveGains, Work, WorkType } from "./Work"; import { applySleeveGains, Work, WorkType } from "./Work";
import { Classes, ClassType } from "../../../Work/ClassWork"; import { Classes, ClassType } from "../../../Work/ClassWork";
import { LocationName } from "../../../Enums"; import { LocationName } from "../../../Enums";
@@ -60,4 +60,4 @@ export class SleeveClassWork extends Work {
} }
} }
Reviver.constructors.SleeveClassWork = SleeveClassWork; constructorsForReviver.SleeveClassWork = SleeveClassWork;

View File

@@ -1,4 +1,4 @@
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../../../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../../../utils/JSONReviver";
import { Sleeve } from "../Sleeve"; import { Sleeve } from "../Sleeve";
import { applySleeveGains, Work, WorkType } from "./Work"; import { applySleeveGains, Work, WorkType } from "./Work";
import { LocationName } from "../../../Enums"; import { LocationName } from "../../../Enums";
@@ -63,4 +63,4 @@ export class SleeveCompanyWork extends Work {
} }
} }
Reviver.constructors.SleeveCompanyWork = SleeveCompanyWork; constructorsForReviver.SleeveCompanyWork = SleeveCompanyWork;

View File

@@ -1,5 +1,5 @@
import { Player } from "@player"; import { Player } from "@player";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../../../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../../../utils/JSONReviver";
import { Sleeve } from "../Sleeve"; import { Sleeve } from "../Sleeve";
import { applySleeveGains, Work, WorkType } from "./Work"; import { applySleeveGains, Work, WorkType } from "./Work";
import { CrimeType } from "../../../Enums"; import { CrimeType } from "../../../Enums";
@@ -72,4 +72,4 @@ export class SleeveCrimeWork extends Work {
} }
} }
Reviver.constructors.SleeveCrimeWork = SleeveCrimeWork; constructorsForReviver.SleeveCrimeWork = SleeveCrimeWork;

View File

@@ -1,5 +1,5 @@
import { Player } from "@player"; import { Player } from "@player";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../../../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../../../utils/JSONReviver";
import { Sleeve } from "../Sleeve"; import { Sleeve } from "../Sleeve";
import { applySleeveGains, Work, WorkType } from "./Work"; import { applySleeveGains, Work, WorkType } from "./Work";
import { FactionWorkType } from "../../../Enums"; import { FactionWorkType } from "../../../Enums";
@@ -74,4 +74,4 @@ export class SleeveFactionWork extends Work {
} }
} }
Reviver.constructors.SleeveFactionWork = SleeveFactionWork; constructorsForReviver.SleeveFactionWork = SleeveFactionWork;

View File

@@ -1,5 +1,5 @@
import { Player } from "@player"; import { Player } from "@player";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../../../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../../../utils/JSONReviver";
import { Sleeve } from "../Sleeve"; import { Sleeve } from "../Sleeve";
import { Work, WorkType } from "./Work"; import { Work, WorkType } from "./Work";
import { CONSTANTS } from "../../../Constants"; import { CONSTANTS } from "../../../Constants";
@@ -45,4 +45,4 @@ export class SleeveInfiltrateWork extends Work {
} }
} }
Reviver.constructors.SleeveInfiltrateWork = SleeveInfiltrateWork; constructorsForReviver.SleeveInfiltrateWork = SleeveInfiltrateWork;

View File

@@ -1,4 +1,4 @@
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../../../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../../../utils/JSONReviver";
import { Sleeve } from "../Sleeve"; import { Sleeve } from "../Sleeve";
import { Work, WorkType } from "./Work"; import { Work, WorkType } from "./Work";
import { calculateIntelligenceBonus } from "../../formulas/intelligence"; import { calculateIntelligenceBonus } from "../../formulas/intelligence";
@@ -32,4 +32,4 @@ export class SleeveRecoveryWork extends Work {
} }
} }
Reviver.constructors.SleeveRecoveryWork = SleeveRecoveryWork; constructorsForReviver.SleeveRecoveryWork = SleeveRecoveryWork;

View File

@@ -1,5 +1,5 @@
import { Player } from "@player"; import { Player } from "@player";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../../../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../../../utils/JSONReviver";
import { Work, WorkType } from "./Work"; import { Work, WorkType } from "./Work";
export const isSleeveSupportWork = (w: Work | null): w is SleeveSupportWork => export const isSleeveSupportWork = (w: Work | null): w is SleeveSupportWork =>
@@ -35,4 +35,4 @@ export class SleeveSupportWork extends Work {
} }
} }
Reviver.constructors.SleeveSupportWork = SleeveSupportWork; constructorsForReviver.SleeveSupportWork = SleeveSupportWork;

View File

@@ -1,5 +1,5 @@
import { Player } from "@player"; import { Player } from "@player";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../../../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../../../utils/JSONReviver";
import { Sleeve } from "../Sleeve"; import { Sleeve } from "../Sleeve";
import { Work, WorkType } from "./Work"; import { Work, WorkType } from "./Work";
import { calculateIntelligenceBonus } from "../../formulas/intelligence"; import { calculateIntelligenceBonus } from "../../formulas/intelligence";
@@ -33,4 +33,4 @@ export class SleeveSynchroWork extends Work {
} }
} }
Reviver.constructors.SleeveSynchroWork = SleeveSynchroWork; constructorsForReviver.SleeveSynchroWork = SleeveSynchroWork;

View File

@@ -191,6 +191,8 @@ export function prestigeSourceFile(flume: boolean): void {
// Reset home computer (only the programs) and add to AllServers // Reset home computer (only the programs) and add to AllServers
AddToAllServers(homeComp); AddToAllServers(homeComp);
prestigeHomeComputer(homeComp); prestigeHomeComputer(homeComp);
// Ram usage needs to be cleared for bitnode-level resets, due to possible change in singularity cost.
for (const script of homeComp.scripts) script.ramUsage = undefined;
// Re-create foreign servers // Re-create foreign servers
initForeignServers(Player.getHomeComputer()); initForeignServers(Player.getHomeComputer());

View File

@@ -21,7 +21,7 @@ import { SnackbarEvents, ToastVariant } from "./ui/React/Snackbar";
import * as ExportBonus from "./ExportBonus"; import * as ExportBonus from "./ExportBonus";
import { dialogBoxCreate } from "./ui/React/DialogBox"; import { dialogBoxCreate } from "./ui/React/DialogBox";
import { Reviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "./utils/JSONReviver"; import { Reviver, constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "./utils/JSONReviver";
import { save } from "./db"; import { save } from "./db";
import { AwardNFG, v1APIBreak } from "./utils/v1APIBreak"; import { AwardNFG, v1APIBreak } from "./utils/v1APIBreak";
import { AugmentationNames } from "./Augmentation/data/AugmentationNames"; import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
@@ -798,7 +798,7 @@ function download(filename: string, content: string): void {
}, 0); }, 0);
} }
Reviver.constructors.BitburnerSaveObject = BitburnerSaveObject; constructorsForReviver.BitburnerSaveObject = BitburnerSaveObject;
export { saveObject, loadGame, download }; export { saveObject, loadGame, download };

View File

@@ -3,16 +3,16 @@
* A Script can have multiple active instances * A Script can have multiple active instances
*/ */
import type React from "react"; import type React from "react";
import { Script } from "./Script"; import { Script, ScriptURL } from "./Script";
import { ScriptUrl } from "./ScriptUrl";
import { Settings } from "../Settings/Settings"; import { Settings } from "../Settings/Settings";
import { Terminal } from "../Terminal"; import { Terminal } from "../Terminal";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
import { formatTime } from "../utils/helpers/formatTime"; import { formatTime } from "../utils/helpers/formatTime";
import { ScriptArg } from "@nsdefs"; import { ScriptArg } from "@nsdefs";
import { RamCostConstants } from "../Netscript/RamCostGenerator"; import { RamCostConstants } from "../Netscript/RamCostGenerator";
import { PositiveInteger } from "../types"; import { PositiveInteger } from "../types";
import { getKeyList } from "../utils/helpers/getKeyList";
export class RunningScript { export class RunningScript {
// Script arguments // Script arguments
@@ -23,7 +23,7 @@ export class RunningScript {
dataMap: Record<string, number[]> = {}; dataMap: Record<string, number[]> = {};
// Script filename // Script filename
filename = ""; filename = "default.js";
// This script's logs. An array of log entries // This script's logs. An array of log entries
logs: React.ReactNode[] = []; logs: React.ReactNode[] = [];
@@ -66,7 +66,8 @@ export class RunningScript {
temporary = false; temporary = false;
// Script urls for the current running script for translating urls back to file names in errors // Script urls for the current running script for translating urls back to file names in errors
dependencies: ScriptUrl[] = []; dependencies: Map<ScriptURL, Script> = new Map();
url?: ScriptURL;
constructor(script?: Script, ramUsage?: number, args: ScriptArg[] = []) { constructor(script?: Script, ramUsage?: number, args: ScriptArg[] = []) {
if (!script) return; if (!script) return;
@@ -75,7 +76,7 @@ export class RunningScript {
this.args = args; this.args = args;
this.server = script.server; this.server = script.server;
this.ramUsage = ramUsage; this.ramUsage = ramUsage;
this.dependencies = script.dependencies; this.dependencies = new Map(script.dependencies);
} }
log(txt: React.ReactNode): void { log(txt: React.ReactNode): void {
@@ -133,13 +134,14 @@ export class RunningScript {
// Serialize the current object to a JSON save state // Serialize the current object to a JSON save state
toJSON(): IReviverValue { toJSON(): IReviverValue {
return Generic_toJSON("RunningScript", this); return Generic_toJSON("RunningScript", this, includedProperties);
} }
// Initializes a RunningScript Object from a JSON save state // Initializes a RunningScript Object from a JSON save state
static fromJSON(value: IReviverValue): RunningScript { static fromJSON(value: IReviverValue): RunningScript {
return Generic_fromJSON(RunningScript, value.data); return Generic_fromJSON(RunningScript, value.data, includedProperties);
} }
} }
const includedProperties = getKeyList(RunningScript, { removedKeys: ["logs", "dependencies", "logUpd", "pid"] });
Reviver.constructors.RunningScript = RunningScript; constructorsForReviver.RunningScript = RunningScript;

View File

@@ -5,58 +5,38 @@
* being evaluated. See RunningScript for that * being evaluated. See RunningScript for that
*/ */
import { calculateRamUsage, RamUsageEntry } from "./RamCalculations"; import { calculateRamUsage, RamUsageEntry } from "./RamCalculations";
import { ScriptUrl } from "./ScriptUrl";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
import { roundToTwo } from "../utils/helpers/roundToTwo"; import { roundToTwo } from "../utils/helpers/roundToTwo";
import { ScriptModule } from "./ScriptModule"; import { ScriptModule } from "./ScriptModule";
import { RamCostConstants } from "../Netscript/RamCostGenerator"; import { RamCostConstants } from "../Netscript/RamCostGenerator";
import { queueUrlRevoke } from "../NetscriptJSEvaluator";
let globalModuleSequenceNumber = 0; // 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.
interface ScriptReference { export type ScriptURL = string & { __type: "ScriptURL" };
filename: string;
server: string;
}
export class Script { export class Script {
// Code for this script
code = ""; code = "";
filename = "default.js";
server = "home";
// Filename for the script file // Ram calculation, only exists after first poll of ram cost after updating
filename = ""; ramUsage?: number;
// url of the script if any, only for NS2.
url = "";
// The dynamic module generated for this script when it is run.
// This is only applicable for NetscriptJS
module: Promise<ScriptModule> | null = null;
// The timestamp when when the script was last updated.
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
// has been updated since it was last run.
dependencies: ScriptUrl[] = [];
dependents: ScriptReference[] = [];
// Amount of RAM this Script requires to run. null indicates an error in calculating ram.
ramUsage: number | null = null;
ramUsageEntries?: RamUsageEntry[]; ramUsageEntries?: RamUsageEntry[];
// Used to deconflict multiple simultaneous compilations. // Runtime data that only exists when the script has been initiated. Cleared when script or a dependency script is updated.
queueCompile = false; module?: Promise<ScriptModule>;
url?: ScriptURL;
// hostname of server that this script is on. /** Scripts that directly import this one. Stored so we can invalidate these dependent scripts when this one is invalidated. */
server = ""; 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();
constructor(fn = "", code = "", server = "") { constructor(fn = "", code = "", server = "") {
this.filename = fn; this.filename = fn;
this.code = code; this.code = code;
this.server = server; // hostname of server this script is on this.server = server; // hostname of server this script is on
this.moduleSequenceNumber = ++globalModuleSequenceNumber;
} }
/** Download the script as a file */ /** Download the script as a file */
@@ -75,13 +55,19 @@ export class Script {
}, 0); }, 0);
} }
/** /** Invalidates the current script module and related data, e.g. when modifying the file. */
* Marks this script as having been updated. It will be recompiled next time something tries invalidateModule(): void {
* to exec it. // Always clear ram usage
*/ this.ramUsage = undefined;
markUpdated(): void { this.ramUsageEntries = undefined;
this.module = null; // Early return if there's already no URL
this.moduleSequenceNumber = ++globalModuleSequenceNumber; 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();
} }
/** /**
@@ -89,30 +75,18 @@ export class Script {
* @param {string} code - The new contents of the script * @param {string} code - The new contents of the script
* @param {Script[]} otherScripts - Other scripts on the server. Used to process imports * @param {Script[]} otherScripts - Other scripts on the server. Used to process imports
*/ */
saveScript(filename: string, code: string, hostname: string, otherScripts: Script[]): void { saveScript(filename: string, code: string, hostname: string): void {
// Update code and filename this.invalidateModule();
this.code = Script.formatCode(code); this.code = Script.formatCode(code);
this.filename = filename; this.filename = filename;
this.server = hostname; this.server = hostname;
// Null ramUsage forces a recalc next time ramUsage is needed
this.ramUsage = null;
this.markUpdated();
this.dependents.forEach((dependent) => {
const scriptToUpdate = otherScripts.find(
(otherScript) => otherScript.filename === dependent.filename && otherScript.server === dependent.server,
);
if (!scriptToUpdate) return;
scriptToUpdate.ramUsage = null;
scriptToUpdate.markUpdated();
});
} }
/** Gets the ram usage, while also attempting to update it if it's currently null */ /** Gets the ram usage, while also attempting to update it if it's currently null */
getRamUsage(otherScripts: Script[]): number | null { getRamUsage(otherScripts: Script[]): number | null {
if (this.ramUsage) return this.ramUsage; if (this.ramUsage) return this.ramUsage;
this.updateRamUsage(otherScripts); this.updateRamUsage(otherScripts);
return this.ramUsage; return this.ramUsage ?? null;
} }
/** /**
@@ -124,26 +98,24 @@ export class Script {
if (ramCalc.cost >= RamCostConstants.Base) { if (ramCalc.cost >= RamCostConstants.Base) {
this.ramUsage = roundToTwo(ramCalc.cost); this.ramUsage = roundToTwo(ramCalc.cost);
this.ramUsageEntries = ramCalc.entries; this.ramUsageEntries = ramCalc.entries;
} else this.ramUsage = null; } else delete this.ramUsage;
this.markUpdated();
} }
imports(): string[] { imports(): string[] {
return []; return [];
} }
/** The keys that are relevant in a save file */
static savedKeys = ["code", "filename", "server"] as const;
// Serialize the current object to a JSON save state // Serialize the current object to a JSON save state
toJSON(): IReviverValue { toJSON(): IReviverValue {
return Generic_toJSON("Script", this); return Generic_toJSON("Script", this, Script.savedKeys);
} }
// Initializes a Script Object from a JSON save state // Initializes a Script Object from a JSON save state
static fromJSON(value: IReviverValue): Script { static fromJSON(value: IReviverValue): Script {
const s = Generic_fromJSON(Script, value.data); return Generic_fromJSON(Script, value.data, Script.savedKeys);
// Force the url to blank from the save data. Urls are not valid outside the current browser page load.
s.url = "";
s.dependents = [];
return s;
} }
/** /**
@@ -156,4 +128,4 @@ export class Script {
} }
} }
Reviver.constructors.Script = Script; constructorsForReviver.Script = Script;

View File

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

View File

@@ -434,7 +434,7 @@ export function Root(props: IProps): React.ReactElement {
//If the current script already exists on the server, overwrite it //If the current script already exists on the server, overwrite it
for (let i = 0; i < server.scripts.length; i++) { for (let i = 0; i < server.scripts.length; i++) {
if (scriptToSave.fileName == server.scripts[i].filename) { if (scriptToSave.fileName == server.scripts[i].filename) {
server.scripts[i].saveScript(scriptToSave.fileName, scriptToSave.code, Player.currentServer, server.scripts); server.scripts[i].saveScript(scriptToSave.fileName, scriptToSave.code, Player.currentServer);
if (Settings.SaveGameOnFileSave) saveObject.saveGame(); if (Settings.SaveGameOnFileSave) saveObject.saveGame();
Router.toPage(Page.Terminal); Router.toPage(Page.Terminal);
return; return;
@@ -443,7 +443,7 @@ export function Root(props: IProps): React.ReactElement {
//If the current script does NOT exist, create a new one //If the current script does NOT exist, create a new one
const script = new Script(); const script = new Script();
script.saveScript(scriptToSave.fileName, scriptToSave.code, Player.currentServer, server.scripts); script.saveScript(scriptToSave.fileName, scriptToSave.code, Player.currentServer);
server.scripts.push(script); server.scripts.push(script);
} else if (scriptToSave.isTxt) { } else if (scriptToSave.isTxt) {
for (let i = 0; i < server.textFiles.length; ++i) { for (let i = 0; i < server.textFiles.length; ++i) {
@@ -511,12 +511,7 @@ export function Root(props: IProps): React.ReactElement {
//If the current script already exists on the server, overwrite it //If the current script already exists on the server, overwrite it
for (let i = 0; i < server.scripts.length; i++) { for (let i = 0; i < server.scripts.length; i++) {
if (currentScript.fileName == server.scripts[i].filename) { if (currentScript.fileName == server.scripts[i].filename) {
server.scripts[i].saveScript( server.scripts[i].saveScript(currentScript.fileName, currentScript.code, Player.currentServer);
currentScript.fileName,
currentScript.code,
Player.currentServer,
server.scripts,
);
if (Settings.SaveGameOnFileSave) saveObject.saveGame(); if (Settings.SaveGameOnFileSave) saveObject.saveGame();
rerender(); rerender();
return; return;
@@ -525,7 +520,7 @@ export function Root(props: IProps): React.ReactElement {
//If the current script does NOT exist, create a new one //If the current script does NOT exist, create a new one
const script = new Script(); const script = new Script();
script.saveScript(currentScript.fileName, currentScript.code, Player.currentServer, server.scripts); script.saveScript(currentScript.fileName, currentScript.code, Player.currentServer);
server.scripts.push(script); server.scripts.push(script);
} else if (currentScript.isTxt) { } else if (currentScript.isTxt) {
for (let i = 0; i < server.textFiles.length; ++i) { for (let i = 0; i < server.textFiles.length; ++i) {

View File

@@ -214,9 +214,6 @@ function scriptFilter(script: RunningScript): boolean {
} }
function includeReplacer(key: string, value: any): any { function includeReplacer(key: string, value: any): any {
if (key === "logs") {
return [];
}
if (key === "runningScripts") { if (key === "runningScripts") {
return value.filter(scriptFilter); return value.filter(scriptFilter);
} }

View File

@@ -186,19 +186,14 @@ export abstract class BaseServer {
} }
} }
} else if (isScriptFilename(fn)) { } else if (isScriptFilename(fn)) {
for (let i = 0; i < this.scripts.length; ++i) { const scriptIndex = this.scripts.findIndex((script) => script.filename === fn);
if (this.scripts[i].filename === fn) { if (scriptIndex === -1) return { res: false, msg: `script ${fn} not found.` };
if (this.isRunning(fn)) { if (this.isRunning(fn)) {
return { return { res: false, msg: "Cannot delete a script that is currently running!" };
res: false,
msg: "Cannot delete a script that is currently running!",
};
}
this.scripts.splice(i, 1);
return { res: true };
}
} }
this.scripts[i].invalidateModule();
this.scripts.splice(i, 1);
return { res: true };
} else if (fn.endsWith(".lit")) { } else if (fn.endsWith(".lit")) {
for (let i = 0; i < this.messages.length; ++i) { for (let i = 0; i < this.messages.length; ++i) {
const f = this.messages[i]; const f = this.messages[i];
@@ -273,8 +268,7 @@ export abstract class BaseServer {
const script = this.scripts[i]; const script = this.scripts[i];
script.code = code; script.code = code;
// Set ramUsage to null in order to force recalculation on next run // Set ramUsage to null in order to force recalculation on next run
script.ramUsage = null; script.invalidateModule();
script.markUpdated();
ret.overwritten = true; ret.overwritten = true;
ret.success = true; ret.success = true;
return ret; return ret;

View File

@@ -5,7 +5,7 @@ import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { createRandomString } from "../utils/helpers/createRandomString"; import { createRandomString } from "../utils/helpers/createRandomString";
import { createRandomIp } from "../utils/IPAddress"; import { createRandomIp } from "../utils/IPAddress";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
export interface IConstructorParams { export interface IConstructorParams {
adminRights?: boolean; adminRights?: boolean;
@@ -155,4 +155,4 @@ export class Server extends BaseServer {
} }
} }
Reviver.constructors.Server = Server; constructorsForReviver.Server = Server;

View File

@@ -254,11 +254,6 @@ export function prestigeHomeComputer(homeComp: Server): void {
homeComp.programs.push(Programs.BitFlume.name); homeComp.programs.push(Programs.BitFlume.name);
} }
//Reset RAM usage calculation for all scripts
homeComp.scripts.forEach(function (script) {
script.ramUsage = null;
});
homeComp.messages.length = 0; //Remove .lit and .msg files homeComp.messages.length = 0; //Remove .lit and .msg files
homeComp.messages.push(LiteratureNames.HackersStartingHandbook); homeComp.messages.push(LiteratureNames.HackersStartingHandbook);
} }

View File

@@ -5,7 +5,7 @@
import { OrderTypes } from "./data/OrderTypes"; import { OrderTypes } from "./data/OrderTypes";
import { PositionTypes } from "./data/PositionTypes"; import { PositionTypes } from "./data/PositionTypes";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
export class Order { export class Order {
readonly pos: PositionTypes; readonly pos: PositionTypes;
@@ -54,4 +54,4 @@ export class Order {
} }
} }
Reviver.constructors.Order = Order; constructorsForReviver.Order = Order;

View File

@@ -1,5 +1,5 @@
import { IMinMaxRange } from "../types"; import { IMinMaxRange } from "../types";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
import { getRandomInt } from "../utils/helpers/getRandomInt"; import { getRandomInt } from "../utils/helpers/getRandomInt";
export const StockForecastInfluenceLimit = 5; export const StockForecastInfluenceLimit = 5;
@@ -275,4 +275,4 @@ export class Stock {
} }
} }
Reviver.constructors.Stock = Stock; constructorsForReviver.Stock = Stock;

View File

@@ -4,9 +4,7 @@ import { killWorkerScript } from "../../Netscript/killWorkerScript";
import { WorkerScriptStartStopEventEmitter } from "../../Netscript/WorkerScriptStartStopEventEmitter"; import { WorkerScriptStartStopEventEmitter } from "../../Netscript/WorkerScriptStartStopEventEmitter";
export function killall(_args: (string | number | boolean)[], server: BaseServer): void { export function killall(_args: (string | number | boolean)[], server: BaseServer): void {
for (let i = server.runningScripts.length - 1; i >= 0; --i) {
killWorkerScript({ runningScript: server.runningScripts[i], hostname: server.hostname });
}
WorkerScriptStartStopEventEmitter.emit();
Terminal.print("Killing all running scripts"); Terminal.print("Killing all running scripts");
for (const runningScript of server.runningScripts) killWorkerScript(runningScript.pid);
WorkerScriptStartStopEventEmitter.emit();
} }

View File

@@ -1,6 +1,6 @@
import { dialogBoxCreate } from "./ui/React/DialogBox"; import { dialogBoxCreate } from "./ui/React/DialogBox";
import { BaseServer } from "./Server/BaseServer"; import { BaseServer } from "./Server/BaseServer";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "./utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "./utils/JSONReviver";
import { removeLeadingSlash, isInRootDirectory } from "./Terminal/DirectoryHelpers"; import { removeLeadingSlash, isInRootDirectory } from "./Terminal/DirectoryHelpers";
/** Represents a plain text file that is typically stored on a server. */ /** Represents a plain text file that is typically stored on a server. */
@@ -73,7 +73,7 @@ export class TextFile {
} }
} }
Reviver.constructors.TextFile = TextFile; constructorsForReviver.TextFile = TextFile;
/** /**
* Retrieve the file object for the filename on the specified server. * Retrieve the file object for the filename on the specified server.

View File

@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { Reviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver"; import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver";
import { CONSTANTS } from "../Constants"; import { CONSTANTS } from "../Constants";
import { LocationName } from "../Enums"; import { LocationName } from "../Enums";
import { formatExp } from "../ui/formatNumber"; import { formatExp } from "../ui/formatNumber";
@@ -159,4 +159,4 @@ export class ClassWork extends Work {
} }
} }
Reviver.constructors.ClassWork = ClassWork; constructorsForReviver.ClassWork = ClassWork;

View File

@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { Reviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver"; import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver";
import { Player } from "@player"; import { Player } from "@player";
import { Work, WorkType } from "./Work"; import { Work, WorkType } from "./Work";
import { influenceStockThroughCompanyWork } from "../StockMarket/PlayerInfluencing"; import { influenceStockThroughCompanyWork } from "../StockMarket/PlayerInfluencing";
@@ -86,4 +86,4 @@ export class CompanyWork extends Work {
} }
} }
Reviver.constructors.CompanyWork = CompanyWork; constructorsForReviver.CompanyWork = CompanyWork;

View File

@@ -1,5 +1,5 @@
import { dialogBoxCreate } from "../ui/React/DialogBox"; import { dialogBoxCreate } from "../ui/React/DialogBox";
import { Reviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver"; import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames"; import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { CONSTANTS } from "../Constants"; import { CONSTANTS } from "../Constants";
import { Player } from "@player"; import { Player } from "@player";
@@ -119,4 +119,4 @@ export class CreateProgramWork extends Work {
} }
} }
Reviver.constructors.CreateProgramWork = CreateProgramWork; constructorsForReviver.CreateProgramWork = CreateProgramWork;

View File

@@ -1,4 +1,4 @@
import { Reviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver"; import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver";
import { Crime } from "../Crime/Crime"; import { Crime } from "../Crime/Crime";
import { CONSTANTS } from "../Constants"; import { CONSTANTS } from "../Constants";
import { determineCrimeSuccess, findCrime } from "../Crime/CrimeHelpers"; import { determineCrimeSuccess, findCrime } from "../Crime/CrimeHelpers";
@@ -106,4 +106,4 @@ export class CrimeWork extends Work {
} }
} }
Reviver.constructors.CrimeWork = CrimeWork; constructorsForReviver.CrimeWork = CrimeWork;

View File

@@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { Work, WorkType } from "./Work"; import { Work, WorkType } from "./Work";
import { Reviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver"; import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver";
import { Player } from "@player"; import { Player } from "@player";
import { FactionNames } from "../Faction/data/FactionNames"; import { FactionNames } from "../Faction/data/FactionNames";
import { Factions } from "../Faction/Factions"; import { Factions } from "../Faction/Factions";
@@ -100,4 +100,4 @@ export class FactionWork extends Work {
} }
} }
Reviver.constructors.FactionWork = FactionWork; constructorsForReviver.FactionWork = FactionWork;

View File

@@ -7,7 +7,7 @@ import { Work, WorkType } from "./Work";
import { graftingIntBonus } from "../PersonObjects/Grafting/GraftingHelpers"; import { graftingIntBonus } from "../PersonObjects/Grafting/GraftingHelpers";
import { applyAugmentation } from "../Augmentation/AugmentationHelpers"; import { applyAugmentation } from "../Augmentation/AugmentationHelpers";
import { dialogBoxCreate } from "../ui/React/DialogBox"; import { dialogBoxCreate } from "../ui/React/DialogBox";
import { Reviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver"; import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver";
import { GraftableAugmentation } from "../PersonObjects/Grafting/GraftableAugmentation"; import { GraftableAugmentation } from "../PersonObjects/Grafting/GraftableAugmentation";
import { StaticAugmentations } from "../Augmentation/StaticAugmentations"; import { StaticAugmentations } from "../Augmentation/StaticAugmentations";
@@ -102,4 +102,4 @@ export class GraftingWork extends Work {
} }
} }
Reviver.constructors.GraftingWork = GraftingWork; constructorsForReviver.GraftingWork = GraftingWork;

View File

@@ -1,85 +1,101 @@
/* Generic Reviver, toJSON, and fromJSON functions used for saving and loading objects */ /* Generic Reviver, toJSON, and fromJSON functions used for saving and loading objects */
import { validateObject } from "./Validator"; import { ObjectValidator, validateObject } from "./Validator";
export interface IReviverValue { export interface IReviverValue {
ctor: string; ctor: string;
data: any; data: any;
} }
function isReviverValue(value: unknown): value is IReviverValue {
return (
typeof value === "object" && value !== null && "ctor" in value && typeof value.ctor === "string" && "data" in value
);
}
// A generic "smart reviver" function. /**
// Looks for object values with a `ctor` property and * A generic "smart reviver" function.
// a `data` property. If it finds them, and finds a matching * Looks for object values with a `ctor` property and a `data` property.
// constructor that has a `fromJSON` property on it, it hands * If it finds them, and finds a matching constructor, it hands
// off to that `fromJSON` function, passing in the value. * off to that `fromJSON` function, passing in the value. */
export function Reviver(key: string, value: IReviverValue | null): any { export function Reviver(_key: string, value: unknown): any {
if (value == null) { if (!isReviverValue(value)) return value;
return null; const ctor = constructorsForReviver[value.ctor];
} if (!ctor) {
// Known missing constructors with special handling.
if (typeof value === "object" && typeof value.ctor === "string" && typeof value.data !== "undefined") { switch (value.ctor) {
// Compatibility for version v0.43.1 case "AllServersMap":
// TODO Remove this eventually console.warn("Converting AllServersMap for v0.43.1");
if (value.ctor === "AllServersMap") { return value.data;
console.warn("Converting AllServersMap for v0.43.1");
return value.data;
} }
// Missing constructor with no special handling. Throw error.
const ctor = Reviver.constructors[value.ctor]; throw new Error(`Could not locate constructor named ${value.ctor}. If the save data is valid, this is a bug.`);
if (typeof ctor === "function" && typeof ctor.fromJSON === "function") {
const obj = ctor.fromJSON(value);
if (ctor.validationData !== undefined) {
validateObject(obj, ctor.validationData);
}
return obj;
}
}
return value;
}
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace Reviver {
export const constructors: { [key: string]: any } = {};
}
// A generic "toJSON" function that creates the data expected
// by Reviver.
// `ctorName` The name of the constructor to use to revive it
// `obj` The object being serialized
// `keys` (Optional) Array of the properties to serialize,
// if not given then all of the objects "own" properties
// that don't have function values will be serialized.
// (Note: If you list a property in `keys`, it will be serialized
// regardless of whether it's an "own" property.)
// Returns: The structure (which will then be turned into a string
// as part of the JSON.stringify algorithm)
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function Generic_toJSON(ctorName: string, obj: Record<string, any>, keys?: string[]): IReviverValue {
if (!keys) {
keys = Object.keys(obj); // Only "own" properties are included
} }
const data: Record<string, unknown> = {}; const obj = ctor.fromJSON(value);
for (let index = 0; index < keys.length; ++index) { if (ctor.validationData !== undefined) {
const key = keys[index]; validateObject(obj, ctor.validationData);
data[key] = obj[key];
}
return { ctor: ctorName, data: data };
}
// A generic "fromJSON" function for use with Reviver: Just calls the
// constructor function with no arguments, then applies all of the
// key/value pairs from the raw data to the instance. Only useful for
// constructors that can be reasonably called without arguments!
// `ctor` The constructor to call
// `data` The data to apply
// Returns: The object
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function Generic_fromJSON<T>(ctor: new () => T, data: any): T {
const obj: any = new ctor();
for (const name in data) {
obj[name] = data[name];
} }
return obj; return obj;
} }
export const constructorsForReviver: Partial<
Record<
string,
(new () => object) & {
fromJSON: (value: IReviverValue) => any;
validationData?: ObjectValidator<any>;
}
>
> = {};
/**
* A generic "toJSON" function that creates the data expected by Reviver.
*
* @param ctorName String name of the constructor, part of the reviver JSON.
* @param obj The object to convert to stringified data in the reviver JSON.
* @param keys If provided, only these keys will be saved to the reviver JSON data. */
export function Generic_toJSON<T extends Record<string, any>>(
ctorName: string,
obj: T,
keys?: readonly (keyof T)[],
): IReviverValue {
const data = {} as T;
// keys provided: only save data for the provided keys
if (keys) {
for (const key of keys) data[key] = obj[key];
return { ctor: ctorName, data: data };
}
// no keys provided: save all own keys of the object
for (const [key, val] of Object.entries(obj) as [keyof T, T[keyof T]][]) data[key] = val;
return { ctor: ctorName, data: data };
}
/**
* A generic "fromJSON" function for use with Reviver: Just calls the
* constructor function with no arguments, then applies all of the
* key/value pairs from the raw data to the instance. Only useful for
* constructors that can be reasonably called without arguments!
*
* @param ctor The constructor to call
* @param data The saved data to restore to the constructed object
* @param keys If provided, only these keys will be restored from data.
* @returns The object */
export function Generic_fromJSON<T extends Record<string, any>>(
ctor: new () => T,
// data can actually be anything. We're just pretending it has the right keys for T. Save data is not type validated.
data: Record<keyof T, any>,
keys?: readonly (keyof T)[],
): T {
const obj = new ctor();
// If keys were provided, just load the provided keys (if they are in the data)
if (keys) {
for (const key of keys) {
const val = data[key];
if (val) obj[key] = val;
}
return obj;
}
// No keys provided: load every key in data
for (const [key, val] of Object.entries(data) as [keyof T, T[keyof T]][]) obj[key] = val;
return obj;
}

View File

@@ -2,7 +2,7 @@
* This is an object that is used to keep track of where all of the player's * This is an object that is used to keep track of where all of the player's
* money is coming from (or going to) * money is coming from (or going to)
*/ */
import { Generic_fromJSON, Generic_toJSON, Reviver, IReviverValue } from "./JSONReviver"; import { Generic_fromJSON, Generic_toJSON, constructorsForReviver, IReviverValue } from "./JSONReviver";
export class MoneySourceTracker { export class MoneySourceTracker {
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/ban-types
@@ -60,4 +60,4 @@ export class MoneySourceTracker {
} }
} }
Reviver.constructors.MoneySourceTracker = MoneySourceTracker; constructorsForReviver.MoneySourceTracker = MoneySourceTracker;

View File

@@ -0,0 +1,18 @@
/** Function for getting a list of keys to use for saving an object
* @param ctor the class constructor
*
* @param removedKeys Keys that exist on a default constructed member, but should not be saved.
* These keys will just revert to default values on load.
*
* @param addedKeys Optional keys that do not exist on a default constructed member, but should be saved when present.
*/
export function getKeyList<T extends object>(
ctor: new () => T,
modifications?: { removedKeys?: readonly (keyof T)[]; addedKeys?: readonly (keyof T)[] },
): readonly (keyof T)[] {
const newObj = new ctor();
const keySet = new Set(Object.getOwnPropertyNames(newObj)) as Set<keyof T>;
modifications?.removedKeys?.forEach((key) => keySet.delete(key));
modifications?.addedKeys?.forEach((key) => keySet.add(key));
return [...keySet];
}

View File

@@ -7,16 +7,6 @@ import { loadAllServers, saveAllServers } from "../../src/Server/AllServers";
// without requiring burdensome levels of maintenance when legitimate changes // without requiring burdensome levels of maintenance when legitimate changes
// are made. // are made.
// Get a stable clock so we don't have time-based diffs.
function freezeTime() {
// You're about to hack time, are you sure? YES/NO
const RealDate = Date;
Date = function () {
return new RealDate(1678834800000);
};
Date.now = () => 1678834800000;
}
// Savegame generated from dev on 2023-03-12, mostly empty game with a few // Savegame generated from dev on 2023-03-12, mostly empty game with a few
// tweaks. A RunningScript was added in-game to test the one bit of // tweaks. A RunningScript was added in-game to test the one bit of
// non-trivial machinery involved in save/load. // non-trivial machinery involved in save/load.
@@ -147,7 +137,6 @@ test("load/saveAllServers", () => {
// Feed a JSON object through loadAllServers/saveAllServers. // Feed a JSON object through loadAllServers/saveAllServers.
// The object is a pruned set of servers that was extracted from a real (dev) game. // The object is a pruned set of servers that was extracted from a real (dev) game.
freezeTime();
loadStandardServers(); loadStandardServers();
// Re-stringify with indenting for nicer diffs // Re-stringify with indenting for nicer diffs
@@ -159,7 +148,6 @@ test("load/saveAllServers pruning RunningScripts", () => {
// Feed a JSON object through loadAllServers/saveAllServers. // Feed a JSON object through loadAllServers/saveAllServers.
// The object is a pruned set of servers that was extracted from a real (dev) game. // The object is a pruned set of servers that was extracted from a real (dev) game.
freezeTime();
loadStandardServers(); loadStandardServers();
// Re-stringify with indenting for nicer diffs // Re-stringify with indenting for nicer diffs

View File

@@ -10,7 +10,7 @@ describe("Validate Save Script Works", function () {
const server = "home"; const server = "home";
const filename = "test.js"; const filename = "test.js";
const script = new Script(); const script = new Script();
script.saveScript(filename, code, server, []); script.saveScript(filename, code, server);
expect(script.filename).toEqual(filename); expect(script.filename).toEqual(filename);
expect(script.code).toEqual(code); expect(script.code).toEqual(code);

View File

@@ -29,26 +29,16 @@ exports[`load/saveAllServers 1`] = `
"args": [], "args": [],
"dataMap": {}, "dataMap": {},
"filename": "script.js", "filename": "script.js",
"logs": [],
"logUpd": true,
"offlineExpGained": 0, "offlineExpGained": 0,
"offlineMoneyMade": 0, "offlineMoneyMade": 0,
"offlineRunningTime": 0.01, "offlineRunningTime": 0.01,
"onlineExpGained": 0, "onlineExpGained": 0,
"onlineMoneyMade": 0, "onlineMoneyMade": 0,
"onlineRunningTime": 7.210000000000004, "onlineRunningTime": 7.210000000000004,
"pid": 2,
"ramUsage": 1.6, "ramUsage": 1.6,
"server": "home", "server": "home",
"threads": 1, "threads": 1,
"temporary": false, "temporary": false
"dependencies": [
{
"filename": "script.js",
"url": "blob:http://localhost/302fe9e5-2ec3-4ed7-bb5a-4f8f4a85f46d",
"moduleSequenceNumber": 2
}
]
} }
} }
], ],
@@ -58,27 +48,7 @@ exports[`load/saveAllServers 1`] = `
"data": { "data": {
"code": "/** @param {NS} ns */\\\\nexport async function main(ns) {\\\\n return ns.asleep(1000000);\\\\n}", "code": "/** @param {NS} ns */\\\\nexport async function main(ns) {\\\\n return ns.asleep(1000000);\\\\n}",
"filename": "script.js", "filename": "script.js",
"url": "", "server": "home"
"module": {},
"dependencies": [
{
"filename": "script.js",
"url": "blob:http://localhost/e0abfafd-2c73-42fc-9eea-288c03820c47",
"moduleSequenceNumber": 5
}
],
"dependents": [],
"ramUsage": 1.6,
"queueCompile": false,
"server": "home",
"moduleSequenceNumber": 5,
"ramUsageEntries": [
{
"type": "misc",
"name": "baseCost",
"cost": 1.6
}
]
} }
} }
], ],
@@ -172,27 +142,7 @@ exports[`load/saveAllServers pruning RunningScripts 1`] = `
"data": { "data": {
"code": "/** @param {NS} ns */\\\\nexport async function main(ns) {\\\\n return ns.asleep(1000000);\\\\n}", "code": "/** @param {NS} ns */\\\\nexport async function main(ns) {\\\\n return ns.asleep(1000000);\\\\n}",
"filename": "script.js", "filename": "script.js",
"url": "", "server": "home"
"module": {},
"dependencies": [
{
"filename": "script.js",
"url": "blob:http://localhost/e0abfafd-2c73-42fc-9eea-288c03820c47",
"moduleSequenceNumber": 5
}
],
"dependents": [],
"ramUsage": 1.6,
"queueCompile": false,
"server": "home",
"moduleSequenceNumber": 5,
"ramUsageEntries": [
{
"type": "misc",
"name": "baseCost",
"cost": 1.6
}
]
} }
} }
], ],