From 69b5a9cba26bb74b4b5f814bd40fbffc0fc3da8a Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sun, 17 May 2026 13:48:35 -0700 Subject: [PATCH] restore FPS tracking via self-driven RAF in PerformanceOverlay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updateFrameMetrics had zero callers — the canvas2D RAF loop used to invoke it per-frame, and that loop died with canvas2D. Tick metrics were unaffected since GameRenderer.tick() still calls updateTickLayerMetrics directly. The WebGL renderer doesn't expose a per-frame hook for the overlay, so the overlay now drives its own RAF, started/stopped with visibility so it stays off the hot path when hidden. --- .../graphics/layers/PerformanceOverlay.ts | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/client/graphics/layers/PerformanceOverlay.ts b/src/client/graphics/layers/PerformanceOverlay.ts index 2f0e4134c..78871dde5 100644 --- a/src/client/graphics/layers/PerformanceOverlay.ts +++ b/src/client/graphics/layers/PerformanceOverlay.ts @@ -82,6 +82,7 @@ export class PerformanceOverlay extends LitElement implements Controller { private fpsHistorySum: number = 0; private lastSecondTime: number = 0; private framesThisSecond: number = 0; + private fpsRafId: number | null = null; private tickExecutionTimes: number[] = []; private tickExecutionTimesSum: number = 0; private tickDelayTimes: number[] = []; @@ -519,6 +520,8 @@ export class PerformanceOverlay extends LitElement implements Controller { disconnectedCallback(): void { super.disconnectedCallback(); + this.stopFpsLoop(); + if (this.isUserSettingsListenerAttached) { globalThis.removeEventListener( `${USER_SETTINGS_CHANGED_EVENT}:${PERFORMANCE_OVERLAY_KEY}`, @@ -561,6 +564,12 @@ export class PerformanceOverlay extends LitElement implements Controller { this.isVisible = visible; FrameProfiler.setEnabled(visible); + if (visible) { + this.startFpsLoop(); + } else { + this.stopFpsLoop(); + } + if (!visible && this.resizeState) { globalThis.removeEventListener("pointermove", this.onResizePointerMove); globalThis.removeEventListener("pointerup", this.onResizePointerUp); @@ -583,6 +592,27 @@ export class PerformanceOverlay extends LitElement implements Controller { this.userSettings.setPerformanceOverlay(nextVisible); } + // FPS measurement runs on its own RAF — the WebGL renderer doesn't expose a + // per-frame hook for the overlay, and starting/stopping with visibility + // keeps the RAF cost off the hot path when the overlay is hidden. + private startFpsLoop(): void { + if (this.fpsRafId !== null) return; + const tick = () => { + this.updateFrameMetrics(0); + this.fpsRafId = requestAnimationFrame(tick); + }; + this.fpsRafId = requestAnimationFrame(tick); + } + + private stopFpsLoop(): void { + if (this.fpsRafId === null) return; + cancelAnimationFrame(this.fpsRafId); + this.fpsRafId = null; + this.lastTime = 0; + this.lastSecondTime = 0; + this.framesThisSecond = 0; + } + private onDragPointerMove = (e: PointerEvent) => { if (!this.dragState || e.pointerId !== this.dragState.pointerId) return;