sbuffkin_hexmaker/dev/coord-label-thrash-bench.mjs
isaprettycoolguy@protonmail.com e74bc0d6f6 fix: eliminate O(n²) layout thrash in renderCoordLabelsLayer (open-time stall)
renderCoordLabelsLayer read each hex's offsetWidth/offsetLeft/offsetTop and
created the label DOM inside the SAME loop. Every offset read after a DOM write
forces a synchronous layout flush, so the loop was O(n²): on the chult map
(3843 hexes) opening stalled the main thread ~21s (regression from commit
10475fd, the coord-label layer).

Split into a read phase (collect all hex geometry into an array) then a write
phase (create all labels) — matches the read-then-write pattern already used by
renderPathOverlay/renderFactionOverlay. ~21s → ~0.1s (dev/coord-label-thrash-
bench measures ~198× batched vs interleaved).

Bakes in a regression guard: CLAUDE.md convention forbidding interleaved
layout-read / DOM-write in per-hex loops, plus the committed benchmark.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 06:57:16 -04:00

36 lines
1.9 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/** Reproduces the renderCoordLabelsLayer layout-thrash regression (commit
* 10475fd) and validates the read-then-write fix, at the real chult map's
* hex count, with and without a bg image. Headless Chromium — absolute ms
* differ from the user's machine but the thrash→batch ratio is the signal.
*/
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { withBrowser } from "/mnt/c/Users/markr/work/tools/chromium-driver/lib/browser.mjs";
const here = path.dirname(fileURLToPath(import.meta.url));
const sandbox = path.join(here, "coord-label-thrash-bench.html");
const ITERS = 3;
const r = await withBrowser(async ({ page }) => {
await page.goto(pathToFileURL(sandbox).href, { waitUntil: "networkidle0" });
await page.waitForFunction(() => document.title === "ready", { timeout: 10000 });
const hexCount = await page.evaluate(() => window.__hexCount);
const m = (which, bg) => page.evaluate((w, b, n) => window.__measure(w, b, n), which, bg, ITERS);
return {
hexCount,
iters: ITERS,
thrash_noBg: await m("thrash", false),
batch_noBg: await m("batch", false),
thrash_bg: await m("thrash", true),
batch_bg: await m("batch", true),
};
});
const f = (n) => n.toFixed(1).padStart(9);
console.log(`\nrenderCoordLabelsLayer cost — hexes=${r.hexCount}, median of ${r.iters} (ms)\n`);
console.log(` no bg image with bg image`);
console.log(`thrash (current) ${f(r.thrash_noBg)} ${f(r.thrash_bg)} ← regression`);
console.log(`batch (fixed) ${f(r.batch_noBg)} ${f(r.batch_bg)}`);
console.log(`\nspeedup no-bg: ${(r.thrash_noBg / r.batch_noBg).toFixed(1)}× with-bg: ${(r.thrash_bg / r.batch_bg).toFixed(1)}×`);
console.log(`bg amplifies thrash by ${(r.thrash_bg / r.thrash_noBg).toFixed(1)}× (vs ${(r.batch_bg / r.batch_noBg).toFixed(1)}× when batched)\n`);