add more stats to perf overlay

This commit is contained in:
scamiv
2025-11-26 14:46:05 +01:00
parent 8159222612
commit ff0533fd1b
3 changed files with 131 additions and 12 deletions
+53 -12
View File
@@ -31,6 +31,7 @@ import {
drainTileUpdates, drainTileUpdates,
SharedTileRingBuffers, SharedTileRingBuffers,
SharedTileRingViews, SharedTileRingViews,
TILE_RING_HEADER_OVERFLOW,
} from "../core/worker/SharedTileRing"; } from "../core/worker/SharedTileRing";
import { WorkerClient } from "../core/worker/WorkerClient"; import { WorkerClient } from "../core/worker/WorkerClient";
import { import {
@@ -561,7 +562,8 @@ export class ClientGameRunner {
batch.length > 0 && batch.length > 0 &&
lastTick !== undefined lastTick !== undefined
) { ) {
const combinedGu = this.mergeGameUpdates(batch); const { gameUpdate: combinedGu, tileMetrics } =
this.mergeGameUpdates(batch);
if (combinedGu) { if (combinedGu) {
this.gameView.update(combinedGu); this.gameView.update(combinedGu);
} }
@@ -599,6 +601,10 @@ export class ClientGameRunner {
ticksPerRender, ticksPerRender,
workerTicksPerSecond, workerTicksPerSecond,
renderTicksPerSecond, renderTicksPerSecond,
tileMetrics.count,
tileMetrics.utilization,
tileMetrics.overflow,
tileMetrics.drainTime,
), ),
); );
@@ -616,9 +622,15 @@ export class ClientGameRunner {
requestAnimationFrame(processFrame); requestAnimationFrame(processFrame);
} }
private mergeGameUpdates( private mergeGameUpdates(batch: GameUpdateViewData[]): {
batch: GameUpdateViewData[], gameUpdate: GameUpdateViewData | null;
): GameUpdateViewData | null { tileMetrics: {
count: number;
utilization: number;
overflow: number;
drainTime: number;
};
} {
if (batch.length === 0) { if (batch.length === 0) {
return null; return null;
} }
@@ -644,31 +656,60 @@ export class ClientGameRunner {
} }
} }
let tileMetrics = {
count: 0,
utilization: 0,
overflow: 0,
drainTime: 0,
};
if (this.tileRingViews) { if (this.tileRingViews) {
const MAX_TILE_UPDATES_PER_RENDER = 100000; const MAX_TILE_UPDATES_PER_RENDER = 100000;
const tileRefs: TileRef[] = []; const tileRefs: TileRef[] = [];
const drainStart = performance.now();
drainTileUpdates( drainTileUpdates(
this.tileRingViews, this.tileRingViews,
MAX_TILE_UPDATES_PER_RENDER, MAX_TILE_UPDATES_PER_RENDER,
tileRefs, 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) { for (const ref of tileRefs) {
combinedPackedTileUpdates.push(BigInt(ref)); combinedPackedTileUpdates.push(BigInt(ref));
} }
} else { } else {
// Non-SAB mode: count tile updates from batch
let totalTileUpdates = 0;
for (const gu of batch) { for (const gu of batch) {
gu.packedTileUpdates.forEach((tu) => { totalTileUpdates += gu.packedTileUpdates.length;
combinedPackedTileUpdates.push(tu);
});
} }
tileMetrics.count = totalTileUpdates;
} }
return { return {
tick: last.tick, gameUpdate: {
updates: combinedUpdates, tick: last.tick,
packedTileUpdates: new BigUint64Array(combinedPackedTileUpdates), updates: combinedUpdates,
playerNameViewData: last.playerNameViewData, packedTileUpdates: new BigUint64Array(combinedPackedTileUpdates),
tickExecutionDuration: last.tickExecutionDuration, playerNameViewData: last.playerNameViewData,
tickExecutionDuration: last.tickExecutionDuration,
},
tileMetrics,
}; };
} }
+5
View File
@@ -137,6 +137,11 @@ export class TickMetricsEvent implements GameEvent {
public readonly workerTicksPerSecond?: number, public readonly workerTicksPerSecond?: number,
// Approximate render tick() calls per second // Approximate render tick() calls per second
public readonly renderTicksPerSecond?: number, public readonly renderTicksPerSecond?: number,
// Tile update metrics
public readonly tileUpdatesCount?: number,
public readonly ringBufferUtilization?: number,
public readonly ringBufferOverflows?: number,
public readonly ringDrainTime?: number,
) {} ) {}
} }
@@ -319,6 +319,14 @@ export class PerformanceOverlay extends LitElement implements Layer {
this.layerStats.clear(); this.layerStats.clear();
this.layerBreakdown = []; 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(); this.requestUpdate();
}; };
@@ -437,6 +445,24 @@ export class PerformanceOverlay extends LitElement implements Layer {
@state() @state()
private renderTicksPerSecond: number = 0; 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( updateTickMetrics(
tickExecutionDuration?: number, tickExecutionDuration?: number,
tickDelay?: number, tickDelay?: number,
@@ -444,6 +470,10 @@ export class PerformanceOverlay extends LitElement implements Layer {
ticksPerRender?: number, ticksPerRender?: number,
workerTicksPerSecond?: number, workerTicksPerSecond?: number,
renderTicksPerSecond?: number, renderTicksPerSecond?: number,
tileUpdatesCount?: number,
ringBufferUtilization?: number,
ringBufferOverflows?: number,
ringDrainTime?: number,
) { ) {
if (!this.isVisible || !this.userSettings.performanceOverlay()) return; if (!this.isVisible || !this.userSettings.performanceOverlay()) return;
@@ -497,6 +527,26 @@ export class PerformanceOverlay extends LitElement implements Layer {
this.renderTicksPerSecond = renderTicksPerSecond; 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(); this.requestUpdate();
} }
@@ -527,6 +577,14 @@ export class PerformanceOverlay extends LitElement implements Layer {
executionSamples: [...this.tickExecutionTimes], executionSamples: [...this.tickExecutionTimes],
delaySamples: [...this.tickDelayTimes], 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 })), layers: this.layerBreakdown.map((layer) => ({ ...layer })),
}; };
} }
@@ -658,6 +716,21 @@ export class PerformanceOverlay extends LitElement implements Layer {
Backlog turns: Backlog turns:
<span>${this.backlogTurns}</span> <span>${this.backlogTurns}</span>
</div> </div>
<div class="performance-line">
Tile updates/render:
<span>${this.tileUpdatesPerRender}</span>
(peak: <span>${this.tileUpdatesPeak}</span>)
</div>
<div class="performance-line">
Ring buffer:
<span>${this.ringBufferUtilization}%</span>
(${this.totalTilesUpdated} total, ${this.ringBufferOverflows}
overflows)
</div>
<div class="performance-line">
Ring drain time:
<span>${this.ringDrainTime.toFixed(2)}ms</span>
</div>
${this.layerBreakdown.length ${this.layerBreakdown.length
? html`<div class="layers-section"> ? html`<div class="layers-section">
<div class="performance-line"> <div class="performance-line">