mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2026-03-23 07:37:24 +01:00
refactor: replace webgl-layer-framework with webgl-layer module
- Removed the webgl-layer-framework module and its associated tests. - Introduced a new webgl-layer module to handle WebGL2 layer management. - Updated references throughout the codebase to use the new webgl-layer module. - Adjusted layer registration and rendering logic to align with the new structure. - Ensured compatibility with existing functionality while improving modularity.
This commit is contained in:
parent
d1d31da864
commit
9e00d69843
37 changed files with 380 additions and 7187 deletions
|
|
@ -1,59 +0,0 @@
|
|||
// AC7 detailed icon count comparison
|
||||
import {chromium} from "playwright";
|
||||
|
||||
async function measure() {
|
||||
const browser = await chromium.launch({headless: true});
|
||||
const page = await browser.newPage();
|
||||
|
||||
await page.goto("http://localhost:5173/Fantasy-Map-Generator/?seed=test-seed&width=1280&height=720");
|
||||
await page.waitForFunction(() => window.mapId !== undefined, {timeout: 90000});
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.evaluate(() => {
|
||||
window.WebGL2LayerFramework.init();
|
||||
if (window.generateReliefIcons) window.generateReliefIcons();
|
||||
});
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const counts = [1000, 2000, 3000, 5000, 7000];
|
||||
for (const n of counts) {
|
||||
const result = await page.evaluate(
|
||||
count =>
|
||||
new Promise(res => {
|
||||
const full = window.pack.relief;
|
||||
const c = Math.min(count, full.length);
|
||||
if (c < count * 0.5) {
|
||||
res({skip: true, available: c});
|
||||
return;
|
||||
}
|
||||
window.pack.relief = full.slice(0, c);
|
||||
const el = document.getElementById("terrain");
|
||||
|
||||
const tSvg = performance.now();
|
||||
window.drawRelief("svg", el);
|
||||
const svgMs = performance.now() - tSvg;
|
||||
|
||||
el.innerHTML = "";
|
||||
if (window.undrawRelief) window.undrawRelief();
|
||||
|
||||
const tW = performance.now();
|
||||
window.drawRelief("webGL", el);
|
||||
requestAnimationFrame(() => {
|
||||
const wMs = performance.now() - tW;
|
||||
window.pack.relief = full;
|
||||
const pct = svgMs > 0 ? (((svgMs - wMs) / svgMs) * 100).toFixed(1) : "N/A";
|
||||
res({icons: c, svgMs: svgMs.toFixed(2), webglMs: wMs.toFixed(2), reductionPct: pct});
|
||||
});
|
||||
}),
|
||||
n
|
||||
);
|
||||
if (!result.skip) console.log(`n=${n}: ${JSON.stringify(result)}`);
|
||||
}
|
||||
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
measure().catch(e => {
|
||||
console.error(e.message);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
// NFR-P5: Measure init() time precisely by intercepting the call
|
||||
import {chromium} from "playwright";
|
||||
|
||||
async function measureInit() {
|
||||
const browser = await chromium.launch({headless: true});
|
||||
const page = await browser.newPage();
|
||||
|
||||
// Inject timing hook BEFORE page load to capture init() call
|
||||
await page.addInitScript(() => {
|
||||
window.__webglInitMs = null;
|
||||
Object.defineProperty(window, "WebGL2LayerFramework", {
|
||||
configurable: true,
|
||||
set(fw) {
|
||||
const origInit = fw.init.bind(fw);
|
||||
fw.init = function () {
|
||||
const t0 = performance.now();
|
||||
const result = origInit();
|
||||
window.__webglInitMs = performance.now() - t0;
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(window, "WebGL2LayerFramework", {configurable: true, writable: true, value: fw});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto("http://localhost:5173/Fantasy-Map-Generator/?seed=test-seed&width=1280&height=720");
|
||||
await page.waitForFunction(() => window.mapId !== undefined, {timeout: 90000});
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const initTiming = await page.evaluate(() => {
|
||||
return {
|
||||
initMs: window.__webglInitMs !== null ? window.__webglInitMs.toFixed(2) : "not captured",
|
||||
captured: window.__webglInitMs !== null
|
||||
};
|
||||
});
|
||||
|
||||
console.log("\nNFR-P5 init() timing (5 runs):");
|
||||
console.log(JSON.stringify(initTiming, null, 2));
|
||||
|
||||
// Also get SVG vs WebGL comparison
|
||||
const svgVsWebgl = await page.evaluate(
|
||||
() =>
|
||||
new Promise(resolve => {
|
||||
const terrain = document.getElementById("terrain");
|
||||
const fullRelief = window.pack.relief;
|
||||
const count5k = Math.min(5000, fullRelief.length);
|
||||
window.pack.relief = fullRelief.slice(0, count5k);
|
||||
|
||||
// SVG baseline
|
||||
const tSvg = performance.now();
|
||||
window.drawRelief("svg", terrain);
|
||||
const svgMs = performance.now() - tSvg;
|
||||
|
||||
// WebGL measurement
|
||||
window.undrawRelief();
|
||||
const tWebgl = performance.now();
|
||||
window.drawRelief("webGL", terrain);
|
||||
requestAnimationFrame(() => {
|
||||
const webglMs = performance.now() - tWebgl;
|
||||
window.pack.relief = fullRelief;
|
||||
const reduction = (((svgMs - webglMs) / svgMs) * 100).toFixed(1);
|
||||
resolve({
|
||||
icons: count5k,
|
||||
svgMs: svgMs.toFixed(2),
|
||||
webglMs: webglMs.toFixed(2),
|
||||
reductionPercent: reduction,
|
||||
target: ">80% reduction",
|
||||
pass: Number(reduction) > 80
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
console.log("\nAC7 SVG vs WebGL comparison:");
|
||||
console.log(JSON.stringify(svgVsWebgl, null, 2));
|
||||
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
measureInit().catch(e => {
|
||||
console.error("Error:", e.message);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -1,150 +0,0 @@
|
|||
// Story 3.1 - Performance Measurement v2
|
||||
// Calls WebGL2LayerFramework.init() explicitly before measuring.
|
||||
import {chromium} from "playwright";
|
||||
|
||||
async function measure() {
|
||||
const browser = await chromium.launch({headless: true});
|
||||
const page = await browser.newPage();
|
||||
|
||||
await page.goto("http://localhost:5173/Fantasy-Map-Generator/?seed=test-seed&width=1280&height=720");
|
||||
await page.waitForFunction(() => window.mapId !== undefined, {timeout: 90000});
|
||||
await page.waitForTimeout(2000);
|
||||
console.log("Map ready.");
|
||||
|
||||
// NFR-P5: Call init() cold and time it
|
||||
const nfrP5 = await page.evaluate(() => {
|
||||
const t0 = performance.now();
|
||||
const ok = window.WebGL2LayerFramework.init();
|
||||
const ms = performance.now() - t0;
|
||||
return {initMs: ms.toFixed(2), initSucceeded: ok, hasFallback: window.WebGL2LayerFramework.hasFallback};
|
||||
});
|
||||
console.log("NFR-P5 init():", JSON.stringify(nfrP5));
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Generate icons
|
||||
await page.evaluate(() => {
|
||||
if (window.generateReliefIcons) window.generateReliefIcons();
|
||||
});
|
||||
const reliefCount = await page.evaluate(() => window.pack?.relief?.length ?? 0);
|
||||
const terrainEl = await page.evaluate(() => !!document.getElementById("terrain"));
|
||||
console.log("icons=" + reliefCount + " terrain=" + terrainEl + " initOk=" + nfrP5.initSucceeded);
|
||||
|
||||
if (terrainEl && reliefCount > 0 && nfrP5.initSucceeded) {
|
||||
// NFR-P1: drawRelief 1k icons
|
||||
const p1 = await page.evaluate(
|
||||
() =>
|
||||
new Promise(res => {
|
||||
const full = window.pack.relief;
|
||||
window.pack.relief = full.slice(0, 1000);
|
||||
const el = document.getElementById("terrain");
|
||||
const t0 = performance.now();
|
||||
window.drawRelief("webGL", el);
|
||||
requestAnimationFrame(() => {
|
||||
res({icons: 1000, ms: (performance.now() - t0).toFixed(2)});
|
||||
window.pack.relief = full;
|
||||
});
|
||||
})
|
||||
);
|
||||
console.log("NFR-P1:", JSON.stringify(p1));
|
||||
|
||||
// NFR-P2: drawRelief up to 10k icons
|
||||
const p2 = await page.evaluate(
|
||||
() =>
|
||||
new Promise(res => {
|
||||
const full = window.pack.relief;
|
||||
const c = Math.min(10000, full.length);
|
||||
window.pack.relief = full.slice(0, c);
|
||||
const el = document.getElementById("terrain");
|
||||
const t0 = performance.now();
|
||||
window.drawRelief("webGL", el);
|
||||
requestAnimationFrame(() => {
|
||||
res({icons: c, ms: (performance.now() - t0).toFixed(2)});
|
||||
window.pack.relief = full;
|
||||
});
|
||||
})
|
||||
);
|
||||
console.log("NFR-P2:", JSON.stringify(p2));
|
||||
}
|
||||
|
||||
// NFR-P3: setVisible toggle (O(1) group.visible flip)
|
||||
const p3 = await page.evaluate(() => {
|
||||
const t = [];
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const t0 = performance.now();
|
||||
window.WebGL2LayerFramework.setVisible("terrain", i % 2 === 0);
|
||||
t.push(performance.now() - t0);
|
||||
}
|
||||
t.sort((a, b) => a - b);
|
||||
return {p50: t[10].toFixed(4), max: t[t.length - 1].toFixed(4), samples: t.length};
|
||||
});
|
||||
console.log("NFR-P3 setVisible:", JSON.stringify(p3));
|
||||
|
||||
// NFR-P4: requestRender scheduling latency (zoom path proxy)
|
||||
const p4 = await page.evaluate(() => {
|
||||
const t = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const t0 = performance.now();
|
||||
window.WebGL2LayerFramework.requestRender();
|
||||
t.push(performance.now() - t0);
|
||||
}
|
||||
const avg = t.reduce((a, b) => a + b, 0) / t.length;
|
||||
return {avgMs: avg.toFixed(4), maxMs: Math.max(...t).toFixed(4)};
|
||||
});
|
||||
console.log("NFR-P4 zoom proxy:", JSON.stringify(p4));
|
||||
|
||||
// NFR-P6: structural check — setVisible must NOT call clearLayer/dispose
|
||||
const p6 = await page.evaluate(() => {
|
||||
const src = window.WebGL2LayerFramework.setVisible.toString();
|
||||
const ok = !src.includes("clearLayer") && !src.includes("dispose");
|
||||
return {
|
||||
verdict: ok ? "PASS" : "FAIL",
|
||||
callsClearLayer: src.includes("clearLayer"),
|
||||
callsDispose: src.includes("dispose")
|
||||
};
|
||||
});
|
||||
console.log("NFR-P6 GPU state:", JSON.stringify(p6));
|
||||
|
||||
// AC7: SVG vs WebGL comparison (5k icons)
|
||||
if (terrainEl && reliefCount >= 100 && nfrP5.initSucceeded) {
|
||||
const ac7 = await page.evaluate(
|
||||
() =>
|
||||
new Promise(res => {
|
||||
const full = window.pack.relief;
|
||||
const c = Math.min(5000, full.length);
|
||||
window.pack.relief = full.slice(0, c);
|
||||
const el = document.getElementById("terrain");
|
||||
|
||||
const tSvg = performance.now();
|
||||
window.drawRelief("svg", el);
|
||||
const svgMs = performance.now() - tSvg;
|
||||
|
||||
el.innerHTML = "";
|
||||
if (window.undrawRelief) window.undrawRelief();
|
||||
|
||||
const tW = performance.now();
|
||||
window.drawRelief("webGL", el);
|
||||
requestAnimationFrame(() => {
|
||||
const wMs = performance.now() - tW;
|
||||
window.pack.relief = full;
|
||||
const pct = svgMs > 0 ? (((svgMs - wMs) / svgMs) * 100).toFixed(1) : "N/A";
|
||||
res({
|
||||
icons: c,
|
||||
svgMs: svgMs.toFixed(2),
|
||||
webglMs: wMs.toFixed(2),
|
||||
reductionPct: pct,
|
||||
pass: Number(pct) > 80
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
console.log("AC7 SVG vs WebGL:", JSON.stringify(ac7));
|
||||
}
|
||||
|
||||
await browser.close();
|
||||
console.log("Done.");
|
||||
}
|
||||
|
||||
measure().catch(e => {
|
||||
console.error("Error:", e.message);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -1,155 +0,0 @@
|
|||
// Performance measurement script for Story 3.1 — v2 (calls init() explicitly)
|
||||
// Measures NFR-P1 through NFR-P6 using Playwright in a real Chromium browser.
|
||||
import {chromium} from "playwright";
|
||||
|
||||
async function measure() {
|
||||
const browser = await chromium.launch({headless: true});
|
||||
const page = await browser.newPage();
|
||||
|
||||
console.log("Loading app...");
|
||||
await page.goto("http://localhost:5173/Fantasy-Map-Generator/?seed=test-seed&width=1280&height=720");
|
||||
|
||||
console.log("Waiting for map generation...");
|
||||
await page.waitForFunction(() => window.mapId !== undefined, {timeout: 90000});
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
console.log("Running measurements...");
|
||||
|
||||
// Check framework availability
|
||||
const frameworkState = await page.evaluate(() => ({
|
||||
available: typeof window.WebGL2LayerFramework !== "undefined",
|
||||
hasFallback: window.WebGL2LayerFramework?.hasFallback,
|
||||
reliefCount: window.pack?.relief?.length ?? 0
|
||||
}));
|
||||
|
||||
console.log("Framework state:", frameworkState);
|
||||
|
||||
// Generate relief icons if not present
|
||||
await page.evaluate(() => {
|
||||
if (!window.pack?.relief?.length && typeof window.generateReliefIcons === "function") {
|
||||
window.generateReliefIcons();
|
||||
}
|
||||
});
|
||||
|
||||
const reliefCount = await page.evaluate(() => window.pack?.relief?.length ?? 0);
|
||||
console.log("Relief icons:", reliefCount);
|
||||
|
||||
// --- NFR-P3: setVisible toggle time (pure JS, O(1) operation) ---
|
||||
const nfrP3 = await page.evaluate(() => {
|
||||
const timings = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const t0 = performance.now();
|
||||
window.WebGL2LayerFramework.setVisible("terrain", false);
|
||||
timings.push(performance.now() - t0);
|
||||
window.WebGL2LayerFramework.setVisible("terrain", true);
|
||||
}
|
||||
const avg = timings.reduce((a, b) => a + b, 0) / timings.length;
|
||||
const min = Math.min(...timings);
|
||||
const max = Math.max(...timings);
|
||||
return {avg: avg.toFixed(4), min: min.toFixed(4), max: max.toFixed(4), timings};
|
||||
});
|
||||
console.log("NFR-P3 setVisible toggle (10 samples):", nfrP3);
|
||||
|
||||
// --- NFR-P5: init() timing (re-init after cleanup) ---
|
||||
// The framework singleton was already init'd at startup. We time it via Navigation Timing.
|
||||
const nfrP5 = await page.evaluate(() => {
|
||||
// Use Navigation Timing to estimate total startup including WebGL init
|
||||
const navEntry = performance.getEntriesByType("navigation")[0];
|
||||
// Also time a requestRender cycle (RAF-based)
|
||||
const t0 = performance.now();
|
||||
window.WebGL2LayerFramework.requestRender();
|
||||
const scheduleTime = performance.now() - t0;
|
||||
return {
|
||||
pageLoadMs: navEntry ? navEntry.loadEventEnd.toFixed(1) : "N/A",
|
||||
requestRenderScheduleMs: scheduleTime.toFixed(4),
|
||||
note: "init() called synchronously at module load; timing via page load metrics"
|
||||
};
|
||||
});
|
||||
console.log("NFR-P5 (init proxy):", nfrP5);
|
||||
|
||||
// --- NFR-P1/P2: drawRelief timing ---
|
||||
// Requires terrain element and relief icons to be available
|
||||
const terrainExists = await page.evaluate(() => !!document.getElementById("terrain"));
|
||||
console.log("Terrain element exists:", terrainExists);
|
||||
|
||||
if (terrainExists && reliefCount > 0) {
|
||||
// NFR-P1: 1k icons — slice pack.relief to 1000
|
||||
const nfrP1 = await page.evaluate(
|
||||
() =>
|
||||
new Promise(resolve => {
|
||||
const fullRelief = window.pack.relief;
|
||||
window.pack.relief = fullRelief.slice(0, 1000);
|
||||
const terrain = document.getElementById("terrain");
|
||||
const t0 = performance.now();
|
||||
window.drawRelief("webGL", terrain);
|
||||
requestAnimationFrame(() => {
|
||||
const elapsed = performance.now() - t0;
|
||||
window.pack.relief = fullRelief; // restore
|
||||
resolve({icons: 1000, elapsedMs: elapsed.toFixed(2)});
|
||||
});
|
||||
})
|
||||
);
|
||||
console.log("NFR-P1 drawRelief 1k:", nfrP1);
|
||||
|
||||
// NFR-P2: 10k icons — slice to 10000 (or use all if fewer)
|
||||
const nfrP2 = await page.evaluate(
|
||||
() =>
|
||||
new Promise(resolve => {
|
||||
const fullRelief = window.pack.relief;
|
||||
const count = Math.min(10000, fullRelief.length);
|
||||
window.pack.relief = fullRelief.slice(0, count);
|
||||
const terrain = document.getElementById("terrain");
|
||||
const t0 = performance.now();
|
||||
window.drawRelief("webGL", terrain);
|
||||
requestAnimationFrame(() => {
|
||||
const elapsed = performance.now() - t0;
|
||||
window.pack.relief = fullRelief;
|
||||
resolve({icons: count, elapsedMs: elapsed.toFixed(2)});
|
||||
});
|
||||
})
|
||||
);
|
||||
console.log("NFR-P2 drawRelief 10k:", nfrP2);
|
||||
} else {
|
||||
console.log("NFR-P1/P2: terrain or relief icons not available — skipping");
|
||||
}
|
||||
|
||||
// --- NFR-P4: Zoom latency proxy ---
|
||||
// Measure time from synthetic zoom event dispatch to requestRender scheduling
|
||||
const nfrP4 = await page.evaluate(() => {
|
||||
const timings = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const t0 = performance.now();
|
||||
// Simulate the zoom handler: requestRender() is what the zoom path calls
|
||||
window.WebGL2LayerFramework.requestRender();
|
||||
timings.push(performance.now() - t0);
|
||||
}
|
||||
const avg = timings.reduce((a, b) => a + b, 0) / timings.length;
|
||||
return {
|
||||
avgMs: avg.toFixed(4),
|
||||
note: "JS scheduling proxy — actual GPU draw happens in RAF callback, measured separately"
|
||||
};
|
||||
});
|
||||
console.log("NFR-P4 zoom requestRender proxy:", nfrP4);
|
||||
|
||||
// --- NFR-P6: GPU state preservation structural check ---
|
||||
const nfrP6 = await page.evaluate(() => {
|
||||
// Inspect setVisible source to confirm clearLayer is NOT called
|
||||
const setVisibleSrc = window.WebGL2LayerFramework.setVisible.toString();
|
||||
const callsClearLayer = setVisibleSrc.includes("clearLayer");
|
||||
const callsDispose = setVisibleSrc.includes("dispose");
|
||||
return {
|
||||
setVisibleCallsClearLayer: callsClearLayer,
|
||||
setVisibleCallsDispose: callsDispose,
|
||||
verdict: !callsClearLayer && !callsDispose ? "PASS — GPU resources preserved on hide" : "FAIL"
|
||||
};
|
||||
});
|
||||
console.log("NFR-P6 structural check:", nfrP6);
|
||||
|
||||
await browser.close();
|
||||
console.log("\nMeasurement complete.");
|
||||
}
|
||||
|
||||
measure().catch(e => {
|
||||
console.error("Error:", e.message);
|
||||
process.exit(1);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue