From fd5b0f824199940f99d82858264ec8a9854670d3 Mon Sep 17 00:00:00 2001 From: LJ <23249107+LJNeon@users.noreply.github.com> Date: Sat, 10 Feb 2024 02:10:19 -0700 Subject: [PATCH] MISC: Use structuredClone() for deep cloning (#1077) --- FixJSDOMEnvironment.ts | 3 +++ src/Go/boardState/boardState.ts | 3 +-- src/NetscriptFunctions.ts | 12 ++++++------ src/NetscriptFunctions/CodingContract.ts | 6 +----- src/NetscriptFunctions/Corporation.ts | 16 ++++++++-------- src/NetscriptFunctions/Infiltration.ts | 4 ++-- src/NetscriptFunctions/Sleeve.ts | 9 ++++----- src/NetscriptFunctions/StockMarket.ts | 3 +-- src/ScriptEditor/ui/themes.ts | 3 +-- 9 files changed, 27 insertions(+), 32 deletions(-) diff --git a/FixJSDOMEnvironment.ts b/FixJSDOMEnvironment.ts index 61c3c5e68..e68ccdea3 100644 --- a/FixJSDOMEnvironment.ts +++ b/FixJSDOMEnvironment.ts @@ -1,10 +1,13 @@ import JSDOMEnvironment from "jest-environment-jsdom"; +import { cloneDeep } from "lodash"; // https://github.com/facebook/jest/blob/v29.4.3/website/versioned_docs/version-29.4/Configuration.md#testenvironment-string export default class FixJSDOMEnvironment extends JSDOMEnvironment { constructor(...args: ConstructorParameters) { super(...args); + // TODO Tests aren't polyfilled. + this.global.structuredClone = cloneDeep; // FIXME https://github.com/nodejs/node/issues/35889 // Add missing importActual() function to mirror requireActual(), // which lets us work around the ESM bug. diff --git a/src/Go/boardState/boardState.ts b/src/Go/boardState/boardState.ts index 25cb6e728..059c07365 100644 --- a/src/Go/boardState/boardState.ts +++ b/src/Go/boardState/boardState.ts @@ -19,7 +19,6 @@ import { getBoardFromSimplifiedBoardState, } from "../boardAnalysis/boardAnalysis"; import { endGoGame } from "../boardAnalysis/scoring"; -import { cloneDeep } from "lodash"; import { addObstacles, resetCoordinates, rotate90Degrees } from "./offlineNodes"; /** @@ -276,7 +275,7 @@ export function getEmptySpaces(boardState: BoardState): PointState[] { * Makes a deep copy of the given board state */ export function getStateCopy(initialState: BoardState) { - const boardState = cloneDeep(initialState); + const boardState = structuredClone(initialState); boardState.history = [...initialState.history]; boardState.previousPlayer = initialState.previousPlayer; diff --git a/src/NetscriptFunctions.ts b/src/NetscriptFunctions.ts index 53b62ecf7..f09ad84cf 100644 --- a/src/NetscriptFunctions.ts +++ b/src/NetscriptFunctions.ts @@ -95,7 +95,7 @@ import { INetscriptExtra } from "./NetscriptFunctions/Extra"; import { ScriptDeath } from "./Netscript/ScriptDeath"; import { getBitNodeMultipliers } from "./BitNode/BitNode"; import { assert, arrayAssert, stringAssert, objectAssert } from "./utils/helpers/typeAssertion"; -import { cloneDeep, escapeRegExp } from "lodash"; +import { escapeRegExp } from "lodash"; import numeral from "numeral"; import { clearPort, peekPort, portHandle, readPort, tryWritePort, writePort, nextPortWrite } from "./NetscriptPort"; import { FilePath, resolveFilePath } from "./Paths/FilePath"; @@ -1679,17 +1679,17 @@ export const ns: InternalAPI = { getPlayer: () => () => { const data = { // Person - hp: cloneDeep(Player.hp), - skills: cloneDeep(Player.skills), - exp: cloneDeep(Player.exp), - mults: cloneDeep(Player.mults), + hp: structuredClone(Player.hp), + skills: structuredClone(Player.skills), + exp: structuredClone(Player.exp), + mults: structuredClone(Player.mults), city: Player.city, // Player-specific numPeopleKilled: Player.numPeopleKilled, money: Player.money, location: Player.location, totalPlaytime: Player.totalPlaytime, - jobs: cloneDeep(Player.jobs), + jobs: structuredClone(Player.jobs), factions: Player.factions.slice(), entropy: Player.entropy, }; diff --git a/src/NetscriptFunctions/CodingContract.ts b/src/NetscriptFunctions/CodingContract.ts index 301340415..8b3d254d6 100644 --- a/src/NetscriptFunctions/CodingContract.ts +++ b/src/NetscriptFunctions/CodingContract.ts @@ -65,11 +65,7 @@ export function NetscriptCodingContract(): InternalAPI { const filename = helpers.string(ctx, "filename", _filename); const hostname = _hostname ? helpers.string(ctx, "hostname", _hostname) : ctx.workerScript.hostname; const contract = getCodingContract(ctx, hostname, filename); - const data = contract.getData(); - if (Array.isArray(data)) { - // For multi-dimensional arrays, we have to copy the internal arrays as well - return JSON.parse(JSON.stringify(data)); - } else return data; + return structuredClone(contract.getData()); }, getDescription: (ctx) => (_filename, _hostname?) => { const filename = helpers.string(ctx, "filename", _filename); diff --git a/src/NetscriptFunctions/Corporation.ts b/src/NetscriptFunctions/Corporation.ts index 0f85d88c6..8731d6a2f 100644 --- a/src/NetscriptFunctions/Corporation.ts +++ b/src/NetscriptFunctions/Corporation.ts @@ -6,7 +6,7 @@ import { Material } from "../Corporation/Material"; import { Warehouse } from "../Corporation/Warehouse"; import { Division } from "../Corporation/Division"; import { Corporation, CorporationResolvers } from "../Corporation/Corporation"; -import { cloneDeep, omit } from "lodash"; +import { omit } from "lodash"; import { setDeprecatedProperties } from "../utils/DeprecationHelper"; import { Corporation as NSCorporation, @@ -248,7 +248,7 @@ export function NetscriptCorporation(): InternalAPI { const materialName = getEnumHelper("CorpMaterialName").nsGetMember(ctx, _materialName, "materialName"); const material = getMaterial(divisionName, cityName, materialName); const corporation = getCorporation(); - const exports = cloneDeep(material.exports); + const exports = structuredClone(material.exports); return { marketPrice: material.marketPrice, desiredSellPrice: material.desiredSellPrice, @@ -277,7 +277,7 @@ export function NetscriptCorporation(): InternalAPI { competition: corporation.unlocks.has(CorpUnlockName.MarketDataCompetition) ? product.competition : undefined, rating: product.rating, effectiveRating: cityData.effectiveRating, - stats: cloneDeep(product.stats), + stats: structuredClone(product.stats), productionCost: cityData.productionCost, desiredSellPrice: cityData.desiredSellPrice, desiredSellAmount: cityData.desiredSellAmount, @@ -626,21 +626,21 @@ export function NetscriptCorporation(): InternalAPI { hasCorporation: () => () => !!Player.corporation, getConstants: (ctx) => () => { checkAccess(ctx); - /* TODO 2.2: possibly just rework the whole corp constants structure to be more readable, and just use cloneDeep - * to provide it directly to player. + /* TODO 2.2: possibly just rework the whole corp constants structure to be more readable, and just use + * structuredClone to provide it directly to player. * TODO 2.2: Roll product information into industriesData, there's no reason to look up a product separately */ // TODO: add functions for getting materialInfo and research info - return cloneDeep(omit(corpConstants, "fundingRoundShares", "fundingRoundMultiplier", "valuationLength")); + return structuredClone(omit(corpConstants, "fundingRoundShares", "fundingRoundMultiplier", "valuationLength")); }, getIndustryData: (ctx) => (_industryName) => { checkAccess(ctx); const industryName = getEnumHelper("IndustryType").nsGetMember(ctx, _industryName, "industryName"); - return cloneDeep(IndustriesData[industryName]); + return structuredClone(IndustriesData[industryName]); }, getMaterialData: (ctx) => (_materialName) => { checkAccess(ctx); const materialName = getEnumHelper("CorpMaterialName").nsGetMember(ctx, _materialName, "materialName"); - return cloneDeep(MaterialInfo[materialName]); + return structuredClone(MaterialInfo[materialName]); }, expandIndustry: (ctx) => (_industryName, _divisionName) => { checkAccess(ctx); diff --git a/src/NetscriptFunctions/Infiltration.ts b/src/NetscriptFunctions/Infiltration.ts index 8cda15e91..3752ebb73 100644 --- a/src/NetscriptFunctions/Infiltration.ts +++ b/src/NetscriptFunctions/Infiltration.ts @@ -1,5 +1,5 @@ import type { InternalAPI, NetscriptContext } from "../Netscript/APIWrapper"; -import { Infiltration as NetscriptInfiltation, InfiltrationLocation } from "@nsdefs"; +import { Infiltration as NetscriptInfiltation, InfiltrationLocation, ILocation } from "@nsdefs"; import { FactionName, LocationName } from "@enums"; import { Location } from "../Locations/Location"; import { Locations } from "../Locations/Locations"; @@ -29,7 +29,7 @@ export function NetscriptInfiltration(): InternalAPI { const reward = calculateReward(startingSecurityLevel); const maxLevel = location.infiltrationData.maxClearanceLevel; return { - location: JSON.parse(JSON.stringify(location)), + location: structuredClone(location) as ILocation, reward: { tradeRep: calculateTradeInformationRepReward(reward, maxLevel, startingSecurityLevel), sellCash: calculateSellInformationCashReward(reward, maxLevel, startingSecurityLevel), diff --git a/src/NetscriptFunctions/Sleeve.ts b/src/NetscriptFunctions/Sleeve.ts index 493ac9c86..4044f7d03 100644 --- a/src/NetscriptFunctions/Sleeve.ts +++ b/src/NetscriptFunctions/Sleeve.ts @@ -10,7 +10,6 @@ import { isSleeveBladeburnerWork } from "../PersonObjects/Sleeve/Work/SleeveBlad import { isSleeveFactionWork } from "../PersonObjects/Sleeve/Work/SleeveFactionWork"; import { isSleeveCompanyWork } from "../PersonObjects/Sleeve/Work/SleeveCompanyWork"; import { helpers } from "../Netscript/NetscriptHelpers"; -import { cloneDeep } from "lodash"; import { getAugCost } from "../Augmentation/AugmentationHelpers"; import { Factions } from "../Faction/Factions"; @@ -161,10 +160,10 @@ export function NetscriptSleeve(): InternalAPI { const sl = Player.sleeves[sleeveNumber]; const data = { - hp: cloneDeep(sl.hp), - skills: cloneDeep(sl.skills), - exp: cloneDeep(sl.exp), - mults: cloneDeep(sl.mults), + hp: structuredClone(sl.hp), + skills: structuredClone(sl.skills), + exp: structuredClone(sl.exp), + mults: structuredClone(sl.mults), city: sl.city, shock: sl.shock, sync: sl.sync, diff --git a/src/NetscriptFunctions/StockMarket.ts b/src/NetscriptFunctions/StockMarket.ts index 058154ff3..55a0d02c3 100644 --- a/src/NetscriptFunctions/StockMarket.ts +++ b/src/NetscriptFunctions/StockMarket.ts @@ -20,7 +20,6 @@ import { Stock } from "../StockMarket/Stock"; import { StockOrder, TIX } from "@nsdefs"; import { InternalAPI, NetscriptContext } from "../Netscript/APIWrapper"; import { helpers } from "../Netscript/NetscriptHelpers"; -import { cloneDeep } from "lodash"; import { StockMarketConstants } from "../StockMarket/data/Constants"; export function NetscriptStockMarket(): InternalAPI { @@ -44,7 +43,7 @@ export function NetscriptStockMarket(): InternalAPI { }; return { - getConstants: () => () => cloneDeep(StockMarketConstants), + getConstants: () => () => structuredClone(StockMarketConstants), hasWSEAccount: () => () => Player.hasWseAccount, hasTIXAPIAccess: () => () => Player.hasTixApiAccess, has4SData: () => () => Player.has4SData, diff --git a/src/ScriptEditor/ui/themes.ts b/src/ScriptEditor/ui/themes.ts index f96b281c1..40cf38f64 100644 --- a/src/ScriptEditor/ui/themes.ts +++ b/src/ScriptEditor/ui/themes.ts @@ -1,7 +1,6 @@ import type { editor } from "monaco-editor"; import { getRecordKeys } from "../../Types/Record"; import { Settings } from "../../Settings/Settings"; -import { cloneDeep } from "lodash"; type DefineThemeFn = typeof editor.defineTheme; export interface IScriptEditorTheme { @@ -76,7 +75,7 @@ const colorRegExp = /^#?([0-9A-Fa-f]{6})([0-9A-Fa-f]{2})?$/; // Invalid data will be replaced with FF0000 (bright red) export const sanitizeTheme = (theme: IScriptEditorTheme): void => { if (typeof theme !== "object") { - Settings.EditorTheme = cloneDeep(defaultMonacoTheme); + Settings.EditorTheme = structuredClone(defaultMonacoTheme); return; } for (const themeKey of getRecordKeys(theme)) {