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

6
.gitignore vendored
View file

@ -5,4 +5,8 @@
/dist /dist
/coverage /coverage
/playwright-report /playwright-report
/test-results /test-results
/_bmad
/_bmad-output
.github/agents/bmad-*
.github/prompts/bmad-*

View file

@ -1141,7 +1141,6 @@ function reGraph() {
pack.cells = packCells; pack.cells = packCells;
pack.cells.p = newCells.p; pack.cells.p = newCells.p;
pack.cells.g = createTypedArray({maxValue: grid.points.length, from: newCells.g}); 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.h = createTypedArray({maxValue: 100, from: newCells.h});
pack.cells.area = createTypedArray({maxValue: UINT16_MAX, length: packCells.i.length}).map((_, cellId) => { pack.cells.area = createTypedArray({maxValue: UINT16_MAX, length: packCells.i.length}).map((_, cellId) => {
const area = Math.abs(d3.polygonArea(getPackPolygon(cellId))); const area = Math.abs(d3.polygonArea(getPackPolygon(cellId)));
@ -1234,7 +1233,7 @@ function showStatistics() {
INFO && console.info(stats); INFO && console.info(stats);
// Dispatch event for test automation and external integrations // 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) { const regenerateMap = debounce(async function (options) {

View file

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

View file

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

View file

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

View file

@ -16,46 +16,6 @@ export const unique = <T>(array: T[]): T[] => {
return [...new Set(array)]; 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 * Get the appropriate typed array constructor based on the maximum value
* @param {number} maxValue - The maximum value that will be stored in the array * @param {number} maxValue - The maximum value that will be stored in the array
@ -109,7 +69,6 @@ declare global {
interface Window { interface Window {
last: typeof last; last: typeof last;
unique: typeof unique; unique: typeof unique;
deepCopy: typeof deepCopy;
getTypedArray: typeof getTypedArray; getTypedArray: typeof getTypedArray;
createTypedArray: typeof createTypedArray; createTypedArray: typeof createTypedArray;
INT8_MAX: number; 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 func - The function to debounce
* @param ms - The number of milliseconds to delay * @param ms - The number of milliseconds to delay
* @returns The debounced function * @returns The debounced function
@ -212,11 +212,14 @@ export const isCtrlClick = (event: MouseEvent | KeyboardEvent): boolean => {
* @returns Formatted date string * @returns Formatted date string
*/ */
export const generateDate = (from: number = 100, to: number = 1000): string => { export const generateDate = (from: number = 100, to: number = 1000): string => {
return new Date(rand(from, to), rand(12), rand(31)).toLocaleDateString("en", { return new Date(rand(from, to), rand(11), rand(1, 28)).toLocaleDateString(
year: "numeric", "en",
month: "long", {
day: "numeric", year: "numeric",
}); month: "long",
day: "numeric",
},
);
}; };
/** /**

View file

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

View file

@ -26,7 +26,6 @@ window.list = list;
import { import {
createTypedArray, createTypedArray,
deepCopy,
getTypedArray, getTypedArray,
last, last,
TYPED_ARRAY_MAX_VALUES, TYPED_ARRAY_MAX_VALUES,
@ -35,7 +34,6 @@ import {
window.last = last; window.last = last;
window.unique = unique; window.unique = unique;
window.deepCopy = deepCopy;
window.getTypedArray = getTypedArray; window.getTypedArray = getTypedArray;
window.createTypedArray = createTypedArray; window.createTypedArray = createTypedArray;
window.INT8_MAX = TYPED_ARRAY_MAX_VALUES.INT8_MAX; window.INT8_MAX = TYPED_ARRAY_MAX_VALUES.INT8_MAX;
@ -274,90 +272,89 @@ window.drawPoint = drawPoint;
window.drawPath = drawPath; window.drawPath = drawPath;
export { export {
rn,
lim,
minmax,
normalize,
lerp,
isVowel,
trimVowels,
getAdjective,
nth,
abbreviate, abbreviate,
list,
last,
unique,
deepCopy,
getTypedArray,
createTypedArray,
TYPED_ARRAY_MAX_VALUES,
rand,
P,
each,
gauss,
Pint,
biased, 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, byId,
shouldRegenerateGrid, C_12,
generateGrid,
findGridAll,
findGridCell,
findClosestCell,
calculateVoronoi, calculateVoronoi,
findAllCellsInRadius, capitalize,
getPackPolygon,
getGridPolygon,
poissonDiscSampler,
isLand,
isWater,
findAllInQuadtree,
drawHeights,
clipPoly, clipPoly,
getSegmentId, connectVertices,
convertTemperature,
createTypedArray,
debounce, debounce,
throttle, distanceSquared,
parseError,
getBase64,
openURL,
wiki,
link,
isCtrlClick,
generateDate,
getLongitude,
getLatitude,
getCoordinates,
initializePrompt,
drawCellsValue, drawCellsValue,
drawHeights,
drawPath,
drawPoint,
drawPolygons, drawPolygons,
drawRouteConnections, drawRouteConnections,
drawPoint, each,
drawPath, 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,
}; };