This commit is contained in:
Marc Emmanuel 2026-03-26 23:41:45 +01:00 committed by GitHub
commit bfd1a20183
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 54 additions and 5 deletions

View file

@ -260,7 +260,14 @@ function getName(id) {
}
function getGraph(currentGraph) {
const newGraph = shouldRegenerateGrid(currentGraph, seed) ? generateGrid() : structuredClone(currentGraph);
if (shouldRegenerateGrid(currentGraph, seed)) return generateGrid();
// Deep clone avoiding non-cloneable properties (like D3 quadtrees with functions)
const newGraph = JSON.parse(JSON.stringify(currentGraph));
// Recreate voronoi cells since JSON doesn't preserve typed arrays or circular refs
const {cells, vertices} = calculateVoronoi(newGraph.points, newGraph.boundary);
newGraph.cells = cells;
newGraph.vertices = vertices;
delete newGraph.cells.h;
return newGraph;
}

View file

@ -13,6 +13,48 @@ import {
import type { River } from "./river-generator";
import type { Point } from "./voronoi";
/**
* Deeply clones an object, omitting properties that cannot be cloned with structuredClone.
* D3 quadtrees store accessor functions that fail structuredClone, so they're excluded.
*/
const safeDeepClone = <T>(obj: T): T => {
if (obj === null || typeof obj !== "object") {
return obj;
}
// Handle typed arrays - slice() creates a copy with a new underlying buffer
if (ArrayBuffer.isView(obj) && !(obj instanceof DataView)) {
return (obj as any).slice() as T;
}
// Handle arrays
if (Array.isArray(obj)) {
return obj.map((item) => safeDeepClone(item)) as T;
}
// Handle objects - skip quadtree (has functions) and other non-clonable properties
const cloned: Record<string, unknown> = {};
for (const key in obj) {
if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;
const value = (obj as Record<string, unknown>)[key];
// Skip quadtree properties (D3 quadtrees have _x and _y functions)
if (value && typeof value === "object" && "_x" in (value as object)) {
continue;
}
// Skip functions
if (typeof value === "function") {
continue;
}
cloned[key] = safeDeepClone(value);
}
return cloned as T;
};
declare global {
var Resample: Resampler;
}
@ -495,9 +537,9 @@ class Resampler {
process(options: ResamplerProcessOptions): void {
const { projection, inverse, scale } = options;
const parentMap = {
grid: structuredClone(grid),
pack: structuredClone(pack),
notes: structuredClone(notes),
grid: safeDeepClone(grid),
pack: safeDeepClone(pack),
notes: safeDeepClone(notes),
};
const riversData = this.saveRiversData(pack.rivers);

View file

@ -25,7 +25,7 @@ export interface PackedGraph {
p: [number, number][]; // cell polygon points
b: boolean[]; // cell is on border
h: TypedArray; // cell heights
q: Quadtree<[number, number, number]>; // cell quadtree index
q?: Quadtree<[number, number, number]>; // cell quadtree index (optional, not cloned)
/** Terrain type */
t: TypedArray; // cell terrain types
r: TypedArray; // river id passing through cell