mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-25 09:03:47 +01:00
REFACTOR: Mitigate cyclic dependency between Jsonable classes (#1792)
This commit is contained in:
parent
45a6ca6b8e
commit
05da0efc81
@ -1,7 +1,7 @@
|
||||
// Constructs all CompanyPosition objects using the metadata in data/companypositions.ts
|
||||
import { getCompaniesMetadata } from "./data/CompaniesMetadata";
|
||||
import { Company } from "./Company";
|
||||
import { Reviver } from "../utils/JSONReviver";
|
||||
import { Reviver } from "../utils/GenericReviver";
|
||||
import { assertLoadingType } from "../utils/TypeAssertion";
|
||||
import { CompanyName } from "./Enums";
|
||||
import { PartialRecord, createEnumKeyedRecord } from "../Types/Record";
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { dialogBoxCreate } from "../ui/React/DialogBox";
|
||||
import { Reviver } from "../utils/JSONReviver";
|
||||
import { Reviver } from "../utils/GenericReviver";
|
||||
import { BaseGift } from "./BaseGift";
|
||||
|
||||
import { StaneksGift } from "./StaneksGift";
|
||||
|
@ -12,7 +12,7 @@ import { CONSTANTS } from "./Constants";
|
||||
import { commitHash } from "./utils/helpers/commitHash";
|
||||
import { resolveFilePath } from "./Paths/FilePath";
|
||||
import { hasScriptExtension } from "./Paths/ScriptFilePath";
|
||||
import { handleGetSaveDataInfoError } from "./Netscript/ErrorMessages";
|
||||
import { handleGetSaveDataInfoError } from "./utils/ErrorHandler";
|
||||
|
||||
interface IReturnWebStatus extends IReturnStatus {
|
||||
data?: Record<string, unknown>;
|
||||
|
@ -3,7 +3,7 @@ import type { PlayerObject } from "../PersonObjects/Player/PlayerObject";
|
||||
import { FactionName, FactionDiscovery } from "@enums";
|
||||
import { Faction } from "./Faction";
|
||||
|
||||
import { Reviver } from "../utils/JSONReviver";
|
||||
import { Reviver } from "../utils/GenericReviver";
|
||||
import { assertLoadingType } from "../utils/TypeAssertion";
|
||||
import { PartialRecord, createEnumKeyedRecord, getRecordValues } from "../Types/Record";
|
||||
import { Augmentations } from "../Augmentation/Augmentations";
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { FactionName } from "@enums";
|
||||
import { Reviver } from "../utils/JSONReviver";
|
||||
import { Reviver } from "../utils/GenericReviver";
|
||||
|
||||
interface GangTerritory {
|
||||
power: number;
|
||||
|
@ -1,7 +1,6 @@
|
||||
import type { WorkerScript } from "./WorkerScript";
|
||||
import { ScriptDeath } from "./ScriptDeath";
|
||||
import type { NetscriptContext } from "./APIWrapper";
|
||||
import { dialogBoxCreate } from "../ui/React/DialogBox";
|
||||
|
||||
/** Log a message to a script's logs */
|
||||
export function log(ctx: NetscriptContext, message: () => string) {
|
||||
@ -74,43 +73,3 @@ export function errorMessage(ctx: NetscriptContext, msg: string, type = "RUNTIME
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Generate an error dialog when workerscript is known */
|
||||
export function handleUnknownError(e: unknown, ws: WorkerScript | null = null, initialText = "") {
|
||||
if (e instanceof ScriptDeath) {
|
||||
// No dialog for ScriptDeath
|
||||
return;
|
||||
}
|
||||
if (ws && typeof e === "string") {
|
||||
const headerText = basicErrorMessage(ws, "", "");
|
||||
if (!e.includes(headerText)) e = basicErrorMessage(ws, e);
|
||||
} else if (e instanceof SyntaxError) {
|
||||
const msg = `${e.message} (sorry we can't be more helpful)`;
|
||||
e = ws ? basicErrorMessage(ws, msg, "SYNTAX") : `SYNTAX ERROR:\n\n${msg}`;
|
||||
} else if (e instanceof Error) {
|
||||
// Ignore any cancellation errors from Monaco that get here
|
||||
if (e.name === "Canceled" && e.message === "Canceled") return;
|
||||
const msg = `${e.message}${e.stack ? `\nstack:\n${e.stack.toString()}` : ""}`;
|
||||
e = ws ? basicErrorMessage(ws, msg) : `RUNTIME ERROR:\n\n${msg}`;
|
||||
}
|
||||
if (typeof e !== "string") {
|
||||
console.error("Unexpected error:", e);
|
||||
const msg = `Unexpected type of error thrown. This error was likely thrown manually within a script.
|
||||
Error has been logged to the console.\n\nType of error: ${typeof e}\nValue of error: ${e}`;
|
||||
e = ws ? basicErrorMessage(ws, msg, "UNKNOWN") : msg;
|
||||
}
|
||||
dialogBoxCreate(initialText + String(e));
|
||||
}
|
||||
|
||||
/** Use this handler to handle the error when we call getSaveData function or getSaveInfo function */
|
||||
export function handleGetSaveDataInfoError(error: unknown, fromGetSaveInfo = false) {
|
||||
console.error(error);
|
||||
let errorMessage = `Cannot get save ${fromGetSaveInfo ? "info" : "data"}. Error: ${error}.`;
|
||||
if (error instanceof RangeError) {
|
||||
errorMessage += " This may be because the save data is too large.";
|
||||
}
|
||||
if (error instanceof Error && error.stack) {
|
||||
errorMessage += `\nStack:\n${error.stack}`;
|
||||
}
|
||||
dialogBoxCreate(errorMessage);
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import { GetServer } from "../Server/AllServers";
|
||||
import { AddRecentScript } from "./RecentScripts";
|
||||
import { ITutorial } from "../InteractiveTutorial";
|
||||
import { AlertEvents } from "../ui/React/AlertManager";
|
||||
import { handleUnknownError } from "./ErrorMessages";
|
||||
import { handleUnknownError } from "../utils/ErrorHandler";
|
||||
import { roundToTwo } from "../utils/helpers/roundToTwo";
|
||||
|
||||
export function killWorkerScript(ws: WorkerScript): boolean {
|
||||
|
@ -34,7 +34,7 @@ import { parseCommand } from "./Terminal/Parser";
|
||||
import { Terminal } from "./Terminal";
|
||||
import { ScriptArg } from "@nsdefs";
|
||||
import { CompleteRunOptions, getRunningScriptsByArgs } from "./Netscript/NetscriptHelpers";
|
||||
import { handleUnknownError } from "./Netscript/ErrorMessages";
|
||||
import { handleUnknownError } from "./utils/ErrorHandler";
|
||||
import { isLegacyScript, legacyScriptExtension, resolveScriptFilePath, ScriptFilePath } from "./Paths/ScriptFilePath";
|
||||
import { root } from "./Paths/Directory";
|
||||
|
||||
|
@ -22,7 +22,7 @@ import { HashManager } from "../../Hacknet/HashManager";
|
||||
import { type MoneySource, MoneySourceTracker } from "../../utils/MoneySourceTracker";
|
||||
import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../../utils/JSONReviver";
|
||||
import { JSONMap, JSONSet } from "../../Types/Jsonable";
|
||||
import { cyrb53 } from "../../utils/StringHelperFunctions";
|
||||
import { cyrb53 } from "../../utils/HashUtils";
|
||||
import { getRandomIntInclusive } from "../../utils/helpers/getRandomIntInclusive";
|
||||
import { CONSTANTS } from "../../Constants";
|
||||
import { Person } from "../Person";
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { sanitizeExploits } from "./Exploits/Exploit";
|
||||
|
||||
import { Reviver } from "./utils/JSONReviver";
|
||||
import { Reviver } from "./utils/GenericReviver";
|
||||
|
||||
import type { PlayerObject } from "./PersonObjects/Player/PlayerObject";
|
||||
|
||||
|
@ -24,7 +24,7 @@ import { SnackbarEvents } from "./ui/React/Snackbar";
|
||||
import * as ExportBonus from "./ExportBonus";
|
||||
|
||||
import { dialogBoxCreate } from "./ui/React/DialogBox";
|
||||
import { Reviver, constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "./utils/JSONReviver";
|
||||
import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, type IReviverValue } from "./utils/JSONReviver";
|
||||
import { save } from "./db";
|
||||
import { AwardNFG, v1APIBreak } from "./utils/v1APIBreak";
|
||||
import { AugmentationName, LocationName, ToastVariant } from "@enums";
|
||||
@ -45,8 +45,9 @@ import { isBinaryFormat } from "../electron/saveDataBinaryFormat";
|
||||
import { downloadContentAsFile } from "./utils/FileUtils";
|
||||
import { showAPIBreaks } from "./utils/APIBreaks/APIBreak";
|
||||
import { breakInfos261 } from "./utils/APIBreaks/2.6.1";
|
||||
import { handleGetSaveDataInfoError } from "./Netscript/ErrorMessages";
|
||||
import { handleGetSaveDataInfoError } from "./utils/ErrorHandler";
|
||||
import { isObject } from "./utils/helpers/typeAssertion";
|
||||
import { Reviver } from "./utils/GenericReviver";
|
||||
|
||||
/* SaveObject.js
|
||||
* Defines the object used to save/load games
|
||||
|
@ -7,7 +7,7 @@ import { HacknetServer } from "../Hacknet/HacknetServer";
|
||||
import { IMinMaxRange } from "../types";
|
||||
import { createRandomIp } from "../utils/IPAddress";
|
||||
import { getRandomIntInclusive } from "../utils/helpers/getRandomIntInclusive";
|
||||
import { Reviver } from "../utils/JSONReviver";
|
||||
import { Reviver } from "../utils/GenericReviver";
|
||||
import { SpecialServers } from "./data/SpecialServers";
|
||||
import { currentNodeMults } from "../BitNode/BitNodeMultipliers";
|
||||
import { IPAddress, isIPAddress } from "../Types/strings";
|
||||
|
@ -12,7 +12,7 @@ import { CONSTANTS } from "../Constants";
|
||||
import { formatMoney } from "../ui/formatNumber";
|
||||
|
||||
import { dialogBoxCreate } from "../ui/React/DialogBox";
|
||||
import { Reviver } from "../utils/JSONReviver";
|
||||
import { Reviver } from "../utils/GenericReviver";
|
||||
import { NetscriptContext } from "../Netscript/APIWrapper";
|
||||
import { helpers } from "../Netscript/NetscriptHelpers";
|
||||
import { getRandomIntInclusive } from "../utils/helpers/getRandomIntInclusive";
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { handleUnknownError } from "./Netscript/ErrorMessages";
|
||||
import { handleUnknownError } from "./utils/ErrorHandler";
|
||||
|
||||
export function setupUncaughtPromiseHandler(): void {
|
||||
window.addEventListener("unhandledrejection", (e) => {
|
||||
|
@ -3,7 +3,7 @@ import { EventEmitter } from "../../utils/EventEmitter";
|
||||
import { Modal } from "./Modal";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Box from "@mui/material/Box";
|
||||
import { cyrb53 } from "../../utils/StringHelperFunctions";
|
||||
import { cyrb53 } from "../../utils/HashUtils";
|
||||
|
||||
export const AlertEvents = new EventEmitter<[string | JSX.Element]>();
|
||||
|
||||
|
@ -38,7 +38,7 @@ import { useBoolean } from "../hooks";
|
||||
|
||||
import { ComparisonIcon } from "./ComparisonIcon";
|
||||
import { SaveData } from "../../../types";
|
||||
import { handleGetSaveDataInfoError } from "../../../Netscript/ErrorMessages";
|
||||
import { handleGetSaveDataInfoError } from "../../../utils/ErrorHandler";
|
||||
|
||||
const useStyles = makeStyles()((theme: Theme) => ({
|
||||
root: {
|
||||
|
44
src/utils/ErrorHandler.ts
Normal file
44
src/utils/ErrorHandler.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { basicErrorMessage } from "../Netscript/ErrorMessages";
|
||||
import { ScriptDeath } from "../Netscript/ScriptDeath";
|
||||
import type { WorkerScript } from "../Netscript/WorkerScript";
|
||||
import { dialogBoxCreate } from "../ui/React/DialogBox";
|
||||
|
||||
/** Generate an error dialog when workerscript is known */
|
||||
export function handleUnknownError(e: unknown, ws: WorkerScript | null = null, initialText = "") {
|
||||
if (e instanceof ScriptDeath) {
|
||||
// No dialog for ScriptDeath
|
||||
return;
|
||||
}
|
||||
if (ws && typeof e === "string") {
|
||||
const headerText = basicErrorMessage(ws, "", "");
|
||||
if (!e.includes(headerText)) e = basicErrorMessage(ws, e);
|
||||
} else if (e instanceof SyntaxError) {
|
||||
const msg = `${e.message} (sorry we can't be more helpful)`;
|
||||
e = ws ? basicErrorMessage(ws, msg, "SYNTAX") : `SYNTAX ERROR:\n\n${msg}`;
|
||||
} else if (e instanceof Error) {
|
||||
// Ignore any cancellation errors from Monaco that get here
|
||||
if (e.name === "Canceled" && e.message === "Canceled") return;
|
||||
const msg = `${e.message}${e.stack ? `\nstack:\n${e.stack.toString()}` : ""}`;
|
||||
e = ws ? basicErrorMessage(ws, msg) : `RUNTIME ERROR:\n\n${msg}`;
|
||||
}
|
||||
if (typeof e !== "string") {
|
||||
console.error("Unexpected error:", e);
|
||||
const msg = `Unexpected type of error thrown. This error was likely thrown manually within a script.
|
||||
Error has been logged to the console.\n\nType of error: ${typeof e}\nValue of error: ${e}`;
|
||||
e = ws ? basicErrorMessage(ws, msg, "UNKNOWN") : msg;
|
||||
}
|
||||
dialogBoxCreate(initialText + String(e));
|
||||
}
|
||||
|
||||
/** Use this handler to handle the error when we call getSaveData function or getSaveInfo function */
|
||||
export function handleGetSaveDataInfoError(error: unknown, fromGetSaveInfo = false) {
|
||||
console.error(error);
|
||||
let errorMessage = `Cannot get save ${fromGetSaveInfo ? "info" : "data"}. Error: ${error}.`;
|
||||
if (error instanceof RangeError) {
|
||||
errorMessage += " This may be because the save data is too large.";
|
||||
}
|
||||
if (error instanceof Error && error.stack) {
|
||||
errorMessage += `\nStack:\n${error.stack}`;
|
||||
}
|
||||
dialogBoxCreate(errorMessage);
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import type React from "react";
|
||||
|
||||
import type { Page } from "../ui/Router";
|
||||
import { commitHash } from "./helpers/commitHash";
|
||||
|
37
src/utils/GenericReviver.ts
Normal file
37
src/utils/GenericReviver.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { loadActionIdentifier } from "../Bladeburner/utils/loadActionIdentifier";
|
||||
import { constructorsForReviver, isReviverValue } from "./JSONReviver";
|
||||
import { validateObject } from "./Validator";
|
||||
|
||||
/**
|
||||
* A generic "smart reviver" function.
|
||||
* Looks for object values with a `ctor` property and a `data` property.
|
||||
* If it finds them, and finds a matching constructor, it hands
|
||||
* off to that `fromJSON` function, passing in the value. */
|
||||
export function Reviver(_key: string, value: unknown): any {
|
||||
if (!isReviverValue(value)) {
|
||||
return value;
|
||||
}
|
||||
const ctor = constructorsForReviver[value.ctor];
|
||||
if (!ctor) {
|
||||
// Known missing constructors with special handling.
|
||||
switch (value.ctor) {
|
||||
case "AllServersMap": // Reviver removed in v0.43.1
|
||||
case "Industry": // No longer part of save data since v2.3.0
|
||||
case "Employee": // Entire object removed from game in v2.2.0 (employees abstracted)
|
||||
case "Company": // Reviver removed in v2.6.1
|
||||
case "Faction": // Reviver removed in v2.6.1
|
||||
console.warn(`Legacy load type ${value.ctor} converted to expected format while loading.`);
|
||||
return value.data;
|
||||
case "ActionIdentifier": // No longer a class as of v2.6.1
|
||||
return loadActionIdentifier(value.data);
|
||||
}
|
||||
// Missing constructor with no special handling. Throw error.
|
||||
throw new Error(`Could not locate constructor named ${value.ctor}. If the save data is valid, this is a bug.`);
|
||||
}
|
||||
|
||||
const obj = ctor.fromJSON(value);
|
||||
if (ctor.validationData !== undefined) {
|
||||
validateObject(obj, ctor.validationData);
|
||||
}
|
||||
return obj;
|
||||
}
|
19
src/utils/HashUtils.ts
Normal file
19
src/utils/HashUtils.ts
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Hashes the input string. This is a fast hash, so NOT good for cryptography.
|
||||
* This has been ripped off here: https://stackoverflow.com/a/52171480
|
||||
* @param str The string that is to be hashed
|
||||
* @param seed A seed to randomize the result
|
||||
* @returns An hexadecimal string representation of the hashed input
|
||||
*/
|
||||
export function cyrb53(str: string, seed = 0): string {
|
||||
let h1 = 0xdeadbeef ^ seed;
|
||||
let h2 = 0x41c6ce57 ^ seed;
|
||||
for (let i = 0, ch; i < str.length; i++) {
|
||||
ch = str.charCodeAt(i);
|
||||
h1 = Math.imul(h1 ^ ch, 2654435761);
|
||||
h2 = Math.imul(h2 ^ ch, 1597334677);
|
||||
}
|
||||
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
||||
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
||||
return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(16);
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
/* Generic Reviver, toJSON, and fromJSON functions used for saving and loading objects */
|
||||
import { ObjectValidator, validateObject } from "./Validator";
|
||||
import { ObjectValidator } from "./Validator";
|
||||
import { JSONMap, JSONSet } from "../Types/Jsonable";
|
||||
import { loadActionIdentifier } from "../Bladeburner/utils/loadActionIdentifier";
|
||||
import { objectAssert } from "./helpers/typeAssertion";
|
||||
|
||||
type JsonableClass = (new () => { toJSON: () => IReviverValue }) & {
|
||||
@ -14,44 +13,12 @@ export interface IReviverValue<T = unknown> {
|
||||
data: T;
|
||||
}
|
||||
|
||||
function isReviverValue(value: unknown): value is IReviverValue {
|
||||
export 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 `data` property.
|
||||
* If it finds them, and finds a matching constructor, it hands
|
||||
* off to that `fromJSON` function, passing in the value. */
|
||||
export function Reviver(_key: string, value: unknown): any {
|
||||
if (!isReviverValue(value)) return value;
|
||||
const ctor = constructorsForReviver[value.ctor];
|
||||
if (!ctor) {
|
||||
// Known missing constructors with special handling.
|
||||
switch (value.ctor) {
|
||||
case "AllServersMap": // Reviver removed in v0.43.1
|
||||
case "Industry": // No longer part of save data since v2.3.0
|
||||
case "Employee": // Entire object removed from game in v2.2.0 (employees abstracted)
|
||||
case "Company": // Reviver removed in v2.6.1
|
||||
case "Faction": // Reviver removed in v2.6.1
|
||||
console.warn(`Legacy load type ${value.ctor} converted to expected format while loading.`);
|
||||
return value.data;
|
||||
case "ActionIdentifier": // No longer a class as of v2.6.1
|
||||
return loadActionIdentifier(value.data);
|
||||
}
|
||||
// Missing constructor with no special handling. Throw error.
|
||||
throw new Error(`Could not locate constructor named ${value.ctor}. If the save data is valid, this is a bug.`);
|
||||
}
|
||||
|
||||
const obj = ctor.fromJSON(value);
|
||||
if (ctor.validationData !== undefined) {
|
||||
validateObject(obj, ctor.validationData);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
export const constructorsForReviver: Partial<Record<string, JsonableClass>> = { JSONSet, JSONMap };
|
||||
|
||||
/**
|
||||
|
@ -89,26 +89,6 @@ export function generateRandomString(n: number): string {
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hashes the input string. This is a fast hash, so NOT good for cryptography.
|
||||
* This has been ripped off here: https://stackoverflow.com/a/52171480
|
||||
* @param str The string that is to be hashed
|
||||
* @param seed A seed to randomize the result
|
||||
* @returns An hexadecimal string representation of the hashed input
|
||||
*/
|
||||
export function cyrb53(str: string, seed = 0): string {
|
||||
let h1 = 0xdeadbeef ^ seed;
|
||||
let h2 = 0x41c6ce57 ^ seed;
|
||||
for (let i = 0, ch; i < str.length; i++) {
|
||||
ch = str.charCodeAt(i);
|
||||
h1 = Math.imul(h1 ^ ch, 2654435761);
|
||||
h2 = Math.imul(h2 ^ ch, 1597334677);
|
||||
}
|
||||
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
||||
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
||||
return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(16);
|
||||
}
|
||||
|
||||
export function capitalizeFirstLetter(s: string): string {
|
||||
return s.charAt(0).toUpperCase() + s.slice(1);
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import React from "react";
|
||||
import { dialogBoxCreate } from "../../ui/React/DialogBox";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { parseUnknownError } from "../ErrorHelper";
|
||||
import { cyrb53 } from "../StringHelperFunctions";
|
||||
import { cyrb53 } from "../HashUtils";
|
||||
import { commitHash } from "./commitHash";
|
||||
|
||||
const errorSet = new Set<string>();
|
||||
|
Loading…
Reference in New Issue
Block a user