diff --git a/src/render/mod.rs b/src/render/mod.rs index 34fac83..d615b2c 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -507,20 +507,12 @@ impl Renderer { // immutable &World into build_chunk_mesh while still owning // &mut self below. let chunk_clone = chunk.clone(); - let t0 = browser_now(); + // Mesh build no longer logs per chunk — the per-frame spam + // (~289 entries during init) was filling the browser console + // and contributing to perceived browser lag, especially with + // DevTools open. Use the FPS HUD / bench toggles for runtime + // perf measurement instead. let mesh = build_chunk_mesh(world, &chunk_clone); - let mesh_ms = browser_now() - t0; - // Lowered threshold so the heightmap-fast path also surfaces - // in telemetry. Tune up later if it gets noisy. - if mesh_ms > 0.5 { - log::info!( - "rebuild_chunk {:?}: {:.1}ms ({} verts, {} idx)", - coord, - mesh_ms, - mesh.vertices.len(), - mesh.indices.len() - ); - } if mesh.indices.is_empty() { self.chunk_buffers.remove(&coord); return; diff --git a/web/main.js b/web/main.js index 2a466a0..2b1e9ed 100644 --- a/web/main.js +++ b/web/main.js @@ -202,12 +202,24 @@ function setupMenu() { function setupStatusLoop() { const hpFill = document.getElementById("hp-fill"); const hpLabel = document.getElementById("hp-label"); + let lastHp = -1; + let lastAlive = null; setInterval(() => { const hp = wasm.get_hp(); const alive = wasm.is_alive(); - hpFill.style.width = (hp / 20 * 100) + "%"; - hpLabel.textContent = `${hp} / 20`; - document.body.classList.toggle("dead", !alive); + // Dedupe: only touch the DOM when something actually changed. + // Pre-change the loop ran 10× per second and fired three style + // writes each time — every frame paying a recalc cost even when + // HP held steady. Worth-only-noticeable when the value changes. + if (hp !== lastHp) { + hpFill.style.width = (hp / 20 * 100) + "%"; + hpLabel.textContent = `${hp} / 20`; + lastHp = hp; + } + if (alive !== lastAlive) { + document.body.classList.toggle("dead", !alive); + lastAlive = alive; + } }, 100); } @@ -678,22 +690,31 @@ function cycleHotbar(delta) { wasm.select_block(b); } -// FPS HUD — reads the EMA-smoothed dt published by App::publish_telemetry -// each frame. Polled every 200ms (5Hz) which is plenty for a readable -// number without adding measurable overhead. Coloring: green ≤ 18ms, -// amber 18-33ms, red beyond. +// FPS HUD — reads the EMA-smoothed dt from the game and writes it +// once a second. The previous version polled at 5 Hz with DOM writes +// every poll, which on weak hardware contributed to perceived browser +// lag. 1 Hz is more than enough to read the number; the EMA already +// smooths anything faster anyway. Also skips DOM writes when the +// reported value hasn't changed enough to display differently. function setupFpsHud() { const el = document.getElementById("fps"); if (!el) return; + let lastText = ""; + let lastTier = ""; setInterval(() => { const dt = wasm.get_frame_dt_ms ? wasm.get_frame_dt_ms() : 0; if (!dt || dt <= 0) { - el.textContent = "— fps"; + if (lastText !== "— fps") { el.textContent = "— fps"; lastText = "— fps"; } return; } const fps = 1000 / dt; - el.textContent = `${fps.toFixed(0)} fps (${dt.toFixed(1)}ms)`; - el.classList.toggle("warn", dt > 18 && dt <= 33); - el.classList.toggle("bad", dt > 33); - }, 200); + const next = `${fps.toFixed(0)} fps (${dt.toFixed(1)}ms)`; + if (next !== lastText) { el.textContent = next; lastText = next; } + const tier = dt > 33 ? "bad" : dt > 18 ? "warn" : ""; + if (tier !== lastTier) { + el.classList.toggle("warn", tier === "warn"); + el.classList.toggle("bad", tier === "bad"); + lastTier = tier; + } + }, 1000); }