79 lines
3.3 KiB
JavaScript
79 lines
3.3 KiB
JavaScript
// Temporary wrappers live only in the profiling page, never in the simulation bundle.
|
|
function perfWrap(names, data) {
|
|
const originals = {};
|
|
for (const name of names) {
|
|
if (typeof window[name] !== 'function') throw Error('Missing profiler target: ' + name);
|
|
const f = originals[name] = window[name];
|
|
data[name] = { ms: 0, calls: 0 };
|
|
window[name] = function (...args) {
|
|
const t = performance.now();
|
|
try { return f.apply(this, args); }
|
|
finally { data[name].ms += performance.now() - t; data[name].calls++; }
|
|
};
|
|
}
|
|
return () => { for (const name of names) window[name] = originals[name]; };
|
|
}
|
|
function perfSim(names, scene, w, h) {
|
|
// Five full periods warm even the 120-tick cold pass without forcing conditional calls.
|
|
for (let k = 0; k < 600; k++) simTick();
|
|
perfScene(scene, w, h);
|
|
const start = perfState(), data = {}, restore = perfWrap(names, data), n = 240;
|
|
const t = performance.now();
|
|
try { for (let k = 0; k < n; k++) simTick(); }
|
|
finally { restore(); }
|
|
const total = (performance.now() - t) / n;
|
|
const steps = {};
|
|
for (const [name, d] of Object.entries(data)) steps[name] = {
|
|
ms_per_tick: d.ms / n, ms_per_call: d.calls ? d.ms / d.calls : null, calls: d.calls
|
|
};
|
|
return { total_ms: total, ticks: n, start, end: perfState(), steps,
|
|
rest_ms: total - Object.values(data).reduce((s, d) => s + d.ms / n, 0) };
|
|
}
|
|
function perfRender(lighting, overlays) {
|
|
SET.lighting = lighting;
|
|
PERF_MARKS = true;
|
|
try {
|
|
for (let k = 0; k < 5; k++) { R.frame++; drawWorld(); }
|
|
const marks = {}, n = 30;
|
|
let draw = 0;
|
|
for (let k = 0; k < n; k++) {
|
|
R.frame++; drawWorld();
|
|
draw += perfLast.drawWorld / n;
|
|
for (const [key, value] of Object.entries(perfMarks)) marks[key] = (marks[key] || 0) + value / n;
|
|
}
|
|
for (let k = 0; k < 5; k++) computeLight();
|
|
const l0 = performance.now();
|
|
for (let k = 0; k < n; k++) computeLight();
|
|
const light = (performance.now() - l0) / n;
|
|
// Exercise the actual overlay calls and their arguments/conditions in frame().
|
|
PERF_MARKS = false;
|
|
for (let k = 0; k < 5; k++) frame(lastT + 1000 / 60);
|
|
const data = {}, restore = perfWrap(overlays, data);
|
|
try { for (let k = 0; k < n; k++) frame(lastT + 1000 / 60); }
|
|
finally { restore(); }
|
|
return { drawWorld_ms: draw, computeLight_ms: light, marks_ms: marks,
|
|
overlays: Object.fromEntries(Object.entries(data).map(([key, d]) => [key, { ms_per_frame: d.ms / n, calls: d.calls }])),
|
|
overlay_ms: Object.values(data).reduce((sum, d) => sum + d.ms / n, 0),
|
|
scale: SM.scale, actual_look: SET.look };
|
|
} finally { PERF_MARKS = false; }
|
|
}
|
|
function perfRun(seconds) {
|
|
return new Promise(resolve => {
|
|
SET.lighting = 'full';
|
|
let warm = 5, previous = null;
|
|
const samples = [];
|
|
GAME.paused = false; acc = 0; lastT = performance.now(); lastRender = lastT;
|
|
PERF_FRAME = s => {
|
|
if (warm-- > 0) { previous = s.at; return; }
|
|
samples.push({ interval: s.at - previous, work: s.work, sim: s.sim, steps: s.steps });
|
|
previous = s.at;
|
|
};
|
|
window.__perfStop = false;
|
|
requestAnimationFrame(frame);
|
|
setTimeout(() => {
|
|
window.__perfHalt(); PERF_FRAME = null; GAME.paused = true;
|
|
resolve({ samples, end: perfState(), scale: SM.scale, actual_look: SET.look });
|
|
}, seconds * 1000);
|
|
});
|
|
}
|