refactor: replace deepCopy with structuredClone

This commit is contained in:
Azgaar 2026-03-09 23:05:27 +01:00
parent 7a49098425
commit 1116cc5e0f
9 changed files with 123 additions and 142 deletions

4
.gitignore vendored
View file

@ -6,3 +6,7 @@
/coverage
/playwright-report
/test-results
/_bmad
/_bmad-output
.github/agents/bmad-*
.github/prompts/bmad-*

View file

@ -1141,7 +1141,6 @@ function reGraph() {
pack.cells = packCells;
pack.cells.p = newCells.p;
pack.cells.g = createTypedArray({maxValue: grid.points.length, from: newCells.g});
pack.cells.q = d3.quadtree(newCells.p.map(([x, y], i) => [x, y, i]));
pack.cells.h = createTypedArray({maxValue: 100, from: newCells.h});
pack.cells.area = createTypedArray({maxValue: UINT16_MAX, length: packCells.i.length}).map((_, cellId) => {
const area = Math.abs(d3.polygonArea(getPackPolygon(cellId)));
@ -1234,7 +1233,7 @@ function showStatistics() {
INFO && console.info(stats);
// Dispatch event for test automation and external integrations
window.dispatchEvent(new CustomEvent('map:generated', { detail: { seed, mapId } }));
window.dispatchEvent(new CustomEvent("map:generated", {detail: {seed, mapId}}));
}
const regenerateMap = debounce(async function (options) {

View file

@ -260,7 +260,7 @@ function getName(id) {
}
function getGraph(currentGraph) {
const newGraph = shouldRegenerateGrid(currentGraph, seed) ? generateGrid() : deepCopy(currentGraph);
const newGraph = shouldRegenerateGrid(currentGraph, seed) ? generateGrid() : structuredClone(currentGraph);
delete newGraph.cells.h;
return newGraph;
}

View file

@ -9,7 +9,7 @@ window.Resample = (function () {
scale: Number
*/
function process({projection, inverse, scale}) {
const parentMap = {grid: deepCopy(grid), pack: deepCopy(pack), notes: deepCopy(notes)};
const parentMap = {grid: structuredClone(grid), pack: structuredClone(pack), notes: structuredClone(notes)};
const riversData = saveRiversData(pack.rivers);
grid = generateGrid();
@ -28,7 +28,7 @@ window.Resample = (function () {
reGraph();
Features.markupPack();
Ice.generate()
Ice.generate();
createDefaultRuler();
restoreCellData(parentMap, inverse, scale);
@ -51,9 +51,10 @@ window.Resample = (function () {
grid.cells.temp = new Int8Array(grid.points.length);
grid.cells.prec = new Uint8Array(grid.points.length);
const parentPackQ = d3.quadtree(parentMap.pack.cells.p.map(([x, y], i) => [x, y, i]));
grid.points.forEach(([x, y], newGridCell) => {
const [parentX, parentY] = inverse(x, y);
const parentPackCell = parentMap.pack.cells.q.find(parentX, parentY, Infinity)[2];
const parentPackCell = parentPackQ.find(parentX, parentY, Infinity)[2];
const parentGridCell = parentMap.pack.cells.g[parentPackCell];
grid.cells.h[newGridCell] = parentMap.grid.cells.h[parentGridCell];
@ -347,11 +348,12 @@ window.Resample = (function () {
}
function restoreFeatureDetails(parentMap, inverse) {
const parentPackQ = d3.quadtree(parentMap.pack.cells.p.map(([x, y], i) => [x, y, i]));
pack.features.forEach(feature => {
if (!feature) return;
const [x, y] = pack.cells.p[feature.firstCell];
const [parentX, parentY] = inverse(x, y);
const parentCell = parentMap.pack.cells.q.find(parentX, parentY, Infinity)[2];
const parentCell = parentPackQ.find(parentX, parentY, Infinity)[2];
if (parentCell === undefined) return;
const parentFeature = parentMap.pack.features[parentMap.pack.cells.f[parentCell]];

View file

@ -57,7 +57,8 @@ window.Submap = (function () {
const oldGrid = parentMap.grid;
// build cache old -> [newcelllist]
const forwardGridMap = parentMap.grid.points.map(_ => []);
resampler(grid.points, parentMap.pack.cells.q, (id, oldid) => {
const parentPackQ = d3.quadtree(parentMap.pack.cells.p.map(([x, y], i) => [x, y, i]));
resampler(grid.points, parentPackQ, (id, oldid) => {
const cid = parentMap.pack.cells.g[oldid];
grid.cells.h[id] = oldGrid.cells.h[cid];
grid.cells.temp[id] = oldGrid.cells.temp[cid];
@ -154,7 +155,7 @@ window.Submap = (function () {
// find replacement: closest water cell
const [ox, oy] = cells.p[id];
const [tx, ty] = inverse(x, y);
oldid = oldCells.q.find(tx, ty, Infinity)[2];
oldid = d3.quadtree(oldCells.p.map(([px, py], i) => [px, py, i])).find(tx, ty, Infinity)[2];
if (!oldid) {
console.warn("Warning, no id found in quad", id, "parent", gridCellId);
continue;

View file

@ -16,46 +16,6 @@ export const unique = <T>(array: T[]): T[] => {
return [...new Set(array)];
};
/**
* Deep copy an object or array
* @param {Object|Array} obj - The object or array to deep copy
* @returns A deep copy of the object or array
*/
export const deepCopy = <T>(obj: T): T => {
const id = (x: T): T => x;
const dcTArray = (a: T[]): T[] => a.map(id);
const dcObject = (x: object): object =>
Object.fromEntries(Object.entries(x).map(([k, d]) => [k, dcAny(d)]));
const dcAny = (x: any): any =>
x instanceof Object ? (cf.get(x.constructor) || id)(x) : x;
// don't map keys, probably this is what we would expect
const dcMapCore = (m: Map<any, any>): [any, any][] =>
[...m.entries()].map(([k, v]) => [k, dcAny(v)]);
const cf: Map<any, (x: any) => any> = new Map<any, (x: any) => any>([
[Int8Array, dcTArray],
[Uint8Array, dcTArray],
[Uint8ClampedArray, dcTArray],
[Int16Array, dcTArray],
[Uint16Array, dcTArray],
[Int32Array, dcTArray],
[Uint32Array, dcTArray],
[Float32Array, dcTArray],
[Float64Array, dcTArray],
[BigInt64Array, dcTArray],
[BigUint64Array, dcTArray],
[Map, (m) => new Map(dcMapCore(m))],
[WeakMap, (m) => new WeakMap(dcMapCore(m))],
[Array, (a) => a.map(dcAny)],
[Set, (s) => [...s.values()].map(dcAny)],
[Date, (d) => new Date(d.getTime())],
[Object, dcObject],
// ... extend here to implement their custom deep copy
]);
return dcAny(obj);
};
/**
* Get the appropriate typed array constructor based on the maximum value
* @param {number} maxValue - The maximum value that will be stored in the array
@ -109,7 +69,6 @@ declare global {
interface Window {
last: typeof last;
unique: typeof unique;
deepCopy: typeof deepCopy;
getTypedArray: typeof getTypedArray;
createTypedArray: typeof createTypedArray;
INT8_MAX: number;

View file

@ -67,7 +67,7 @@ export const getSegmentId = (
};
/**
* Creates a debounced function that delays invoking func until after ms milliseconds have elapsed
* Creates a debounced function that delays next func call until after ms milliseconds
* @param func - The function to debounce
* @param ms - The number of milliseconds to delay
* @returns The debounced function
@ -212,11 +212,14 @@ export const isCtrlClick = (event: MouseEvent | KeyboardEvent): boolean => {
* @returns Formatted date string
*/
export const generateDate = (from: number = 100, to: number = 1000): string => {
return new Date(rand(from, to), rand(12), rand(31)).toLocaleDateString("en", {
year: "numeric",
month: "long",
day: "numeric",
});
return new Date(rand(from, to), rand(11), rand(1, 28)).toLocaleDateString(
"en",
{
year: "numeric",
month: "long",
day: "numeric",
},
);
};
/**

View file

@ -1,5 +1,5 @@
import Alea from "alea";
import { color } from "d3";
import { color, quadtree } from "d3";
import Delaunator from "delaunator";
import {
type Cells,
@ -266,6 +266,11 @@ export const findGridAll = (
return found;
};
const quadtreeCache = new WeakMap<
object,
ReturnType<typeof quadtree<[number, number, number]>>
>();
/**
* Returns the index of the packed cell containing the given x and y coordinates
* @param {number} x - The x coordinate
@ -277,10 +282,16 @@ export const findClosestCell = (
x: number,
y: number,
radius = Infinity,
packedGraph: any,
pack: { cells: { p: [number, number][] } },
): number | undefined => {
if (!packedGraph.cells?.q) return;
const found = packedGraph.cells.q.find(x, y, radius);
if (!pack.cells?.p) throw new Error("Pack cells not found");
let qTree = quadtreeCache.get(pack.cells.p);
if (!qTree) {
qTree = quadtree(pack.cells.p.map(([px, py], i) => [px, py, i]));
if (!qTree) throw new Error("Failed to create quadtree");
quadtreeCache.set(pack.cells.p, qTree);
}
const found = qTree.find(x, y, radius);
return found ? found[2] : undefined;
};
@ -414,8 +425,13 @@ export const findAllCellsInRadius = (
radius: number,
packedGraph: any,
): number[] => {
// Use findAllInQuadtree directly instead of relying on prototype extension
const found = findAllInQuadtree(x, y, radius, packedGraph.cells.q);
const q = quadtree<[number, number, number]>(
packedGraph.cells.p.map(
([px, py]: [number, number], i: number) =>
[px, py, i] as [number, number, number],
),
);
const found = findAllInQuadtree(x, y, radius, q);
return found.map((r: any) => r[2]);
};

View file

@ -26,7 +26,6 @@ window.list = list;
import {
createTypedArray,
deepCopy,
getTypedArray,
last,
TYPED_ARRAY_MAX_VALUES,
@ -35,7 +34,6 @@ import {
window.last = last;
window.unique = unique;
window.deepCopy = deepCopy;
window.getTypedArray = getTypedArray;
window.createTypedArray = createTypedArray;
window.INT8_MAX = TYPED_ARRAY_MAX_VALUES.INT8_MAX;
@ -274,90 +272,89 @@ window.drawPoint = drawPoint;
window.drawPath = drawPath;
export {
rn,
lim,
minmax,
normalize,
lerp,
isVowel,
trimVowels,
getAdjective,
nth,
abbreviate,
list,
last,
unique,
deepCopy,
getTypedArray,
createTypedArray,
TYPED_ARRAY_MAX_VALUES,
rand,
P,
each,
gauss,
Pint,
biased,
generateSeed,
getNumberInRange,
ra,
rw,
convertTemperature,
si,
getIntegerFromSI,
toHEX,
getColors,
getRandomColor,
getMixedColor,
C_12,
getComposedPath,
getNextId,
rollups,
distanceSquared,
getIsolines,
getPolesOfInaccessibility,
connectVertices,
findPath,
getVertexPath,
round,
capitalize,
splitInTwo,
parseTransform,
isValidJSON,
safeParseJSON,
sanitizeId,
byId,
shouldRegenerateGrid,
generateGrid,
findGridAll,
findGridCell,
findClosestCell,
C_12,
calculateVoronoi,
findAllCellsInRadius,
getPackPolygon,
getGridPolygon,
poissonDiscSampler,
isLand,
isWater,
findAllInQuadtree,
drawHeights,
capitalize,
clipPoly,
getSegmentId,
connectVertices,
convertTemperature,
createTypedArray,
debounce,
throttle,
parseError,
getBase64,
openURL,
wiki,
link,
isCtrlClick,
generateDate,
getLongitude,
getLatitude,
getCoordinates,
initializePrompt,
distanceSquared,
drawCellsValue,
drawHeights,
drawPath,
drawPoint,
drawPolygons,
drawRouteConnections,
drawPoint,
drawPath,
each,
findAllCellsInRadius,
findAllInQuadtree,
findClosestCell,
findGridAll,
findGridCell,
findPath,
gauss,
generateDate,
generateGrid,
generateSeed,
getAdjective,
getBase64,
getColors,
getComposedPath,
getCoordinates,
getGridPolygon,
getIntegerFromSI,
getIsolines,
getLatitude,
getLongitude,
getMixedColor,
getNextId,
getNumberInRange,
getPackPolygon,
getPolesOfInaccessibility,
getRandomColor,
getSegmentId,
getTypedArray,
getVertexPath,
initializePrompt,
isCtrlClick,
isLand,
isValidJSON,
isVowel,
isWater,
last,
lerp,
lim,
link,
list,
minmax,
normalize,
nth,
openURL,
P,
parseError,
parseTransform,
Pint,
poissonDiscSampler,
ra,
rand,
rn,
rollups,
round,
rw,
safeParseJSON,
sanitizeId,
shouldRegenerateGrid,
si,
splitInTwo,
throttle,
toHEX,
trimVowels,
TYPED_ARRAY_MAX_VALUES,
unique,
wiki,
};