From 86cc25110bfe8078d58a0429bd551b25c828fd0f Mon Sep 17 00:00:00 2001 From: scamiv <6170744+scamiv@users.noreply.github.com> Date: Sun, 22 Feb 2026 17:37:28 +0100 Subject: [PATCH] perf: avoid perf overlay overhead when hidden --- src/client/graphics/GameRenderer.ts | 21 ++++++---- .../graphics/layers/PerformanceOverlay.ts | 38 ++++++++++++------- src/core/game/UserSettings.ts | 16 ++++++++ 3 files changed, 54 insertions(+), 21 deletions(-) diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts index 767b442bf..81748576c 100644 --- a/src/client/graphics/GameRenderer.ts +++ b/src/client/graphics/GameRenderer.ts @@ -429,7 +429,10 @@ export class GameRenderer { } renderGame() { - FrameProfiler.clear(); + const shouldProfileFrame = FrameProfiler.isEnabled(); + if (shouldProfileFrame) { + FrameProfiler.clear(); + } const start = performance.now(); // Set background this.context.fillStyle = this.game @@ -463,9 +466,13 @@ export class GameRenderer { isTransformActive, ); - const layerStart = FrameProfiler.start(); - layer.renderLayer?.(this.context); - FrameProfiler.end(getProfileLabel(layer), layerStart); + if (shouldProfileFrame) { + const layerStart = FrameProfiler.start(); + layer.renderLayer?.(this.context); + FrameProfiler.end(getProfileLabel(layer), layerStart); + } else { + layer.renderLayer?.(this.context); + } } handleTransformState(false, isTransformActive); // Ensure context is clean after rendering this.transformHandler.resetChanged(); @@ -473,15 +480,15 @@ export class GameRenderer { requestAnimationFrame(() => this.renderGame()); const duration = performance.now() - start; - const layerDurations = FrameProfiler.consume(); - if (FrameProfiler.isEnabled()) { + if (shouldProfileFrame) { + const layerDurations = FrameProfiler.consume(); this.renderFramesSinceLastTick++; for (const [name, ms] of Object.entries(layerDurations)) { this.renderLayerDurationsSinceLastTick[name] = (this.renderLayerDurationsSinceLastTick[name] ?? 0) + ms; } + this.performanceOverlay.updateFrameMetrics(duration, layerDurations); } - this.performanceOverlay.updateFrameMetrics(duration, layerDurations); if (duration > 50) { console.warn( diff --git a/src/client/graphics/layers/PerformanceOverlay.ts b/src/client/graphics/layers/PerformanceOverlay.ts index e4cea0c55..3846d2dcd 100644 --- a/src/client/graphics/layers/PerformanceOverlay.ts +++ b/src/client/graphics/layers/PerformanceOverlay.ts @@ -265,22 +265,40 @@ export class PerformanceOverlay extends LitElement implements Layer { } init() { + this.setVisible(this.userSettings.performanceOverlay()); + this.eventBus.on(TogglePerformanceOverlayEvent, () => { - this.userSettings.togglePerformanceOverlay(); - this.setVisible(this.userSettings.performanceOverlay()); + const nextVisible = !this.isVisible; + this.setVisible(nextVisible); + this.userSettings.set("settings.performanceOverlay", nextVisible); }); this.eventBus.on(TickMetricsEvent, (event: TickMetricsEvent) => { this.updateTickMetrics(event.tickExecutionDuration, event.tickDelay); }); + + globalThis.addEventListener("user-settings-changed", (event: Event) => { + const customEvent = event as CustomEvent<{ + key?: string; + value?: unknown; + }>; + if (customEvent.detail?.key !== "settings.performanceOverlay") return; + + const nextVisible = customEvent.detail.value === true; + if (this.isVisible === nextVisible) return; + this.setVisible(nextVisible); + }); } setVisible(visible: boolean) { this.isVisible = visible; FrameProfiler.setEnabled(visible); + this.requestUpdate(); } private handleClose() { - this.userSettings.togglePerformanceOverlay(); + const nextVisible = false; + this.setVisible(nextVisible); + this.userSettings.set("settings.performanceOverlay", nextVisible); } private handleMouseDown = (e: MouseEvent) => { @@ -375,14 +393,6 @@ export class PerformanceOverlay extends LitElement implements Layer { frameDuration: number, layerDurations?: Record, ) { - const wasVisible = this.isVisible; - this.isVisible = this.userSettings.performanceOverlay(); - - // Update FrameProfiler enabled state when visibility changes - if (wasVisible !== this.isVisible) { - FrameProfiler.setEnabled(this.isVisible); - } - if (!this.isVisible) return; const now = performance.now(); @@ -478,7 +488,7 @@ export class PerformanceOverlay extends LitElement implements Layer { frameCount: number, layerDurations: Record, ) { - if (!this.isVisible || !this.userSettings.performanceOverlay()) return; + if (!this.isVisible) return; const alpha = 0.2; // smoothing factor for EMA @@ -514,7 +524,7 @@ export class PerformanceOverlay extends LitElement implements Layer { } updateTickLayerMetrics(tickLayerDurations: Record) { - if (!this.isVisible || !this.userSettings.performanceOverlay()) return; + if (!this.isVisible) return; const alpha = 0.2; // smoothing factor for EMA @@ -555,7 +565,7 @@ export class PerformanceOverlay extends LitElement implements Layer { } updateTickMetrics(tickExecutionDuration?: number, tickDelay?: number) { - if (!this.isVisible || !this.userSettings.performanceOverlay()) return; + if (!this.isVisible) return; // Update tick execution duration stats if (tickExecutionDuration !== undefined) { diff --git a/src/core/game/UserSettings.ts b/src/core/game/UserSettings.ts index 83991941a..baa98c908 100644 --- a/src/core/game/UserSettings.ts +++ b/src/core/game/UserSettings.ts @@ -4,6 +4,20 @@ import { PlayerPattern } from "../Schemas"; const PATTERN_KEY = "territoryPattern"; export class UserSettings { + private emitChange(key: string, value: boolean | number): void { + try { + const maybeDispatch = (globalThis as any)?.dispatchEvent; + if (typeof maybeDispatch !== "function") return; + (globalThis as any).dispatchEvent( + new CustomEvent("user-settings-changed", { + detail: { key, value }, + }), + ); + } catch { + // Ignore - settings should still be applied even if event dispatch fails. + } + } + get(key: string, defaultValue: boolean): boolean { const value = localStorage.getItem(key); if (!value) return defaultValue; @@ -17,6 +31,7 @@ export class UserSettings { set(key: string, value: boolean) { localStorage.setItem(key, value ? "true" : "false"); + this.emitChange(key, value); } getFloat(key: string, defaultValue: number): number { @@ -31,6 +46,7 @@ export class UserSettings { setFloat(key: string, value: number) { localStorage.setItem(key, value.toString()); + this.emitChange(key, value); } emojis() {