From 0da975f6150c96209c5643fe6aebcf23f6b09d36 Mon Sep 17 00:00:00 2001 From: scamiv <6170744+scamiv@users.noreply.github.com> Date: Wed, 26 Nov 2025 14:46:05 +0100 Subject: [PATCH] add more stats to perf overlay --- src/client/ClientGameRunner.ts | 65 ++++++++++++++--- src/client/InputHandler.ts | 5 ++ .../graphics/layers/PerformanceOverlay.ts | 73 +++++++++++++++++++ 3 files changed, 131 insertions(+), 12 deletions(-) diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index 3beb2dea3..5f46157b6 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -31,6 +31,7 @@ import { drainTileUpdates, SharedTileRingBuffers, SharedTileRingViews, + TILE_RING_HEADER_OVERFLOW, } from "../core/worker/SharedTileRing"; import { WorkerClient } from "../core/worker/WorkerClient"; import { @@ -577,7 +578,8 @@ export class ClientGameRunner { batch.length > 0 && lastTick !== undefined ) { - const combinedGu = this.mergeGameUpdates(batch); + const { gameUpdate: combinedGu, tileMetrics } = + this.mergeGameUpdates(batch); if (combinedGu) { this.gameView.update(combinedGu); } @@ -615,6 +617,10 @@ export class ClientGameRunner { ticksPerRender, workerTicksPerSecond, renderTicksPerSecond, + tileMetrics.count, + tileMetrics.utilization, + tileMetrics.overflow, + tileMetrics.drainTime, ), ); @@ -632,9 +638,15 @@ export class ClientGameRunner { requestAnimationFrame(processFrame); } - private mergeGameUpdates( - batch: GameUpdateViewData[], - ): GameUpdateViewData | null { + private mergeGameUpdates(batch: GameUpdateViewData[]): { + gameUpdate: GameUpdateViewData | null; + tileMetrics: { + count: number; + utilization: number; + overflow: number; + drainTime: number; + }; + } { if (batch.length === 0) { return null; } @@ -660,31 +672,60 @@ export class ClientGameRunner { } } + let tileMetrics = { + count: 0, + utilization: 0, + overflow: 0, + drainTime: 0, + }; + if (this.tileRingViews) { const MAX_TILE_UPDATES_PER_RENDER = 100000; const tileRefs: TileRef[] = []; + const drainStart = performance.now(); drainTileUpdates( this.tileRingViews, MAX_TILE_UPDATES_PER_RENDER, tileRefs, ); + const drainTime = performance.now() - drainStart; + + // Calculate ring buffer utilization and overflow + const TILE_RING_CAPACITY = 262144; + const utilization = (tileRefs.length / TILE_RING_CAPACITY) * 100; + const overflow = Atomics.load( + this.tileRingViews.header, + TILE_RING_HEADER_OVERFLOW, + ); + + tileMetrics = { + count: tileRefs.length, + utilization, + overflow, + drainTime, + }; + for (const ref of tileRefs) { combinedPackedTileUpdates.push(BigInt(ref)); } } else { + // Non-SAB mode: count tile updates from batch + let totalTileUpdates = 0; for (const gu of batch) { - gu.packedTileUpdates.forEach((tu) => { - combinedPackedTileUpdates.push(tu); - }); + totalTileUpdates += gu.packedTileUpdates.length; } + tileMetrics.count = totalTileUpdates; } return { - tick: last.tick, - updates: combinedUpdates, - packedTileUpdates: new BigUint64Array(combinedPackedTileUpdates), - playerNameViewData: last.playerNameViewData, - tickExecutionDuration: last.tickExecutionDuration, + gameUpdate: { + tick: last.tick, + updates: combinedUpdates, + packedTileUpdates: new BigUint64Array(combinedPackedTileUpdates), + playerNameViewData: last.playerNameViewData, + tickExecutionDuration: last.tickExecutionDuration, + }, + tileMetrics, }; } diff --git a/src/client/InputHandler.ts b/src/client/InputHandler.ts index dbae066ee..bf2510e4d 100644 --- a/src/client/InputHandler.ts +++ b/src/client/InputHandler.ts @@ -137,6 +137,11 @@ export class TickMetricsEvent implements GameEvent { public readonly workerTicksPerSecond?: number, // Approximate render tick() calls per second public readonly renderTicksPerSecond?: number, + // Tile update metrics + public readonly tileUpdatesCount?: number, + public readonly ringBufferUtilization?: number, + public readonly ringBufferOverflows?: number, + public readonly ringDrainTime?: number, ) {} } diff --git a/src/client/graphics/layers/PerformanceOverlay.ts b/src/client/graphics/layers/PerformanceOverlay.ts index 64499024f..6c1de8cd7 100644 --- a/src/client/graphics/layers/PerformanceOverlay.ts +++ b/src/client/graphics/layers/PerformanceOverlay.ts @@ -319,6 +319,14 @@ export class PerformanceOverlay extends LitElement implements Layer { this.layerStats.clear(); this.layerBreakdown = []; + // reset tile metrics + this.tileUpdatesPerRender = 0; + this.tileUpdatesPeak = 0; + this.ringBufferUtilization = 0; + this.ringBufferOverflows = 0; + this.ringDrainTime = 0; + this.totalTilesUpdated = 0; + this.requestUpdate(); }; @@ -437,6 +445,24 @@ export class PerformanceOverlay extends LitElement implements Layer { @state() private renderTicksPerSecond: number = 0; + @state() + private tileUpdatesPerRender: number = 0; + + @state() + private tileUpdatesPeak: number = 0; + + @state() + private ringBufferUtilization: number = 0; + + @state() + private ringBufferOverflows: number = 0; + + @state() + private ringDrainTime: number = 0; + + @state() + private totalTilesUpdated: number = 0; + updateTickMetrics( tickExecutionDuration?: number, tickDelay?: number, @@ -444,6 +470,10 @@ export class PerformanceOverlay extends LitElement implements Layer { ticksPerRender?: number, workerTicksPerSecond?: number, renderTicksPerSecond?: number, + tileUpdatesCount?: number, + ringBufferUtilization?: number, + ringBufferOverflows?: number, + ringDrainTime?: number, ) { if (!this.isVisible || !this.userSettings.performanceOverlay()) return; @@ -497,6 +527,26 @@ export class PerformanceOverlay extends LitElement implements Layer { this.renderTicksPerSecond = renderTicksPerSecond; } + if (tileUpdatesCount !== undefined) { + this.tileUpdatesPerRender = tileUpdatesCount; + this.tileUpdatesPeak = Math.max(this.tileUpdatesPeak, tileUpdatesCount); + this.totalTilesUpdated += tileUpdatesCount; + } + + if (ringBufferUtilization !== undefined) { + this.ringBufferUtilization = + Math.round(ringBufferUtilization * 100) / 100; + } + + if (ringBufferOverflows !== undefined) { + // Accumulate overflows (overflows is a flag, so add 1 if set) + this.ringBufferOverflows += ringBufferOverflows; + } + + if (ringDrainTime !== undefined) { + this.ringDrainTime = Math.round(ringDrainTime * 100) / 100; + } + this.requestUpdate(); } @@ -527,6 +577,14 @@ export class PerformanceOverlay extends LitElement implements Layer { executionSamples: [...this.tickExecutionTimes], delaySamples: [...this.tickDelayTimes], }, + tiles: { + updatesPerRender: this.tileUpdatesPerRender, + peakUpdates: this.tileUpdatesPeak, + ringBufferUtilization: this.ringBufferUtilization, + ringBufferOverflows: this.ringBufferOverflows, + ringDrainTimeMs: this.ringDrainTime, + totalTilesUpdated: this.totalTilesUpdated, + }, layers: this.layerBreakdown.map((layer) => ({ ...layer })), }; } @@ -658,6 +716,21 @@ export class PerformanceOverlay extends LitElement implements Layer { Backlog turns: ${this.backlogTurns} +