MISC: Use structuredClone() for deep cloning (#1077)

This commit is contained in:
LJ 2024-02-10 02:10:19 -07:00 committed by GitHub
parent 6bd50e6f24
commit fd5b0f8241
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 27 additions and 32 deletions

@ -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<typeof JSDOMEnvironment>) {
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.

@ -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;

@ -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<NSFull> = {
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,
};

@ -65,11 +65,7 @@ export function NetscriptCodingContract(): InternalAPI<ICodingContract> {
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);

@ -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<NSCorporation> {
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<NSCorporation> {
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<NSCorporation> {
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);

@ -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<NetscriptInfiltation> {
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),

@ -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<NetscriptSleeve> {
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,

@ -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<TIX> {
@ -44,7 +43,7 @@ export function NetscriptStockMarket(): InternalAPI<TIX> {
};
return {
getConstants: () => () => cloneDeep(StockMarketConstants),
getConstants: () => () => structuredClone(StockMarketConstants),
hasWSEAccount: () => () => Player.hasWseAccount,
hasTIXAPIAccess: () => () => Player.hasTixApiAccess,
has4SData: () => () => Player.has4SData,

@ -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)) {