From 813c5020f7c9af18900debde99b8f94228f67b24 Mon Sep 17 00:00:00 2001 From: scamiv <6170744+scamiv@users.noreply.github.com> Date: Thu, 5 Feb 2026 02:37:13 +0100 Subject: [PATCH] extract WorkerProfiler + remove dead helpers - Move profiling/metrics logic out of Worker.worker.ts into WorkerProfiler.ts - Drop unused MAX_TICKS_PER_HEARTBEAT and TransformHandler.viewOffset() - Simplify TerritoryLayer overlay parenting; remove unused markTile() wrapper --- src/client/graphics/TransformHandler.ts | 4 - src/client/graphics/layers/TerritoryLayer.ts | 13 +- src/core/worker/Worker.worker.ts | 364 +------------------ src/core/worker/WorkerProfiler.ts | 362 ++++++++++++++++++ 4 files changed, 368 insertions(+), 375 deletions(-) create mode 100644 src/core/worker/WorkerProfiler.ts diff --git a/src/client/graphics/TransformHandler.ts b/src/client/graphics/TransformHandler.ts index 9a788ab27..a472c691d 100644 --- a/src/client/graphics/TransformHandler.ts +++ b/src/client/graphics/TransformHandler.ts @@ -45,10 +45,6 @@ export class TransformHandler { return this._boundingRect; } - viewOffset(): { x: number; y: number } { - return { x: this.offsetX, y: this.offsetY }; - } - getOffsetX(): number { return this.offsetX; } diff --git a/src/client/graphics/layers/TerritoryLayer.ts b/src/client/graphics/layers/TerritoryLayer.ts index e6aebfa9c..fd8dc7081 100644 --- a/src/client/graphics/layers/TerritoryLayer.ts +++ b/src/client/graphics/layers/TerritoryLayer.ts @@ -242,8 +242,8 @@ export class TerritoryLayer implements Layer { canvas.style.display = "block"; } - const parent = mainCanvas.parentElement; - if (!parent) { + const currentParent = mainCanvas.parentElement; + if (!currentParent) { // Fallback: if the canvas isn't in the DOM yet, append to body. if (!canvas.isConnected) { document.body.appendChild(canvas); @@ -254,8 +254,7 @@ export class TerritoryLayer implements Layer { // Ensure the main canvas is wrapped in a positioned container so the // territory canvas can overlay it without mirroring computed styles. let wrapper: HTMLElement; - const currentParent = mainCanvas.parentElement; - if (currentParent && currentParent.dataset.territoryOverlay === "1") { + if (currentParent.dataset.territoryOverlay === "1") { wrapper = currentParent; } else { wrapper = document.createElement("div"); @@ -265,7 +264,7 @@ export class TerritoryLayer implements Layer { wrapper.style.lineHeight = "0"; // Replace mainCanvas with wrapper, then re-insert mainCanvas inside wrapper. - parent.replaceChild(wrapper, mainCanvas); + currentParent.replaceChild(wrapper, mainCanvas); wrapper.appendChild(mainCanvas); } @@ -303,10 +302,6 @@ export class TerritoryLayer implements Layer { if (h > 0) wrapper.style.height = `${h}px`; } - private markTile(tile: TileRef) { - this.territoryRenderer?.markTile(tile); - } - private updateHoverHighlight() { if (!this.territoryRenderer) { return; diff --git a/src/core/worker/Worker.worker.ts b/src/core/worker/Worker.worker.ts index e958392e8..fad19fa85 100644 --- a/src/core/worker/Worker.worker.ts +++ b/src/core/worker/Worker.worker.ts @@ -30,8 +30,8 @@ import { TileContextResultMessage, TransportShipSpawnResultMessage, WorkerMessage, - WorkerMetricsMessage, } from "./WorkerMessages"; +import { WorkerProfiler } from "./WorkerProfiler"; import { WorkerTerritoryRenderer } from "./WorkerTerritoryRenderer"; const ctx: Worker = self as any; @@ -39,373 +39,13 @@ let gameRunner: Promise | null = null; let gameStartInfo: GameStartInfo | null = null; let myClientID: ClientID | null = null; const mapLoader = new FetchGameMapLoader(`/maps`, version); -const MAX_TICKS_PER_HEARTBEAT = 4; let renderer: WorkerTerritoryRenderer | WorkerCanvas2DRenderer | null = null; let dirtyTiles: DirtyTileQueue | null = null; let dirtyTilesOverflow = false; let renderTileState: Uint16Array | null = null; const pendingTurns: Turn[] = []; -type WorkerDebugConfig = { - enabled: boolean; - intervalMs: number; - includeTrace: boolean; -}; - -class WorkerProfiler { - public config: WorkerDebugConfig = { - enabled: false, - intervalMs: 1000, - includeTrace: false, - }; - - private reportTimer: any = null; - private lastReportWallMs = 0; - - private eventLoopLagSum = 0; - private eventLoopLagCount = 0; - private eventLoopLagMax = 0; - - private simDelaySum = 0; - private simDelayCount = 0; - private simDelayMax = 0; - - private simExecSum = 0; - private simExecCount = 0; - private simExecMax = 0; - - private readonly msgCounts = new Map(); - private readonly msgHandlerSum = new Map(); - private readonly msgQueueSum = new Map(); - private readonly msgHandlerMax = new Map(); - private readonly msgQueueMax = new Map(); - - private traceRing: string[] = []; - private traceHead = 0; - private readonly traceCap = 160; - - private renderSubmittedCount = 0; - private renderNoopCount = 0; - private renderGetTextureSum = 0; - private renderGetTextureMax = 0; - private renderFrameComputeSum = 0; - private renderFrameComputeMax = 0; - private renderTerritoryPassSum = 0; - private renderTerritoryPassMax = 0; - private renderTemporalResolveSum = 0; - private renderTemporalResolveMax = 0; - private renderSubmitSum = 0; - private renderSubmitMax = 0; - private renderCpuTotalSum = 0; - private renderCpuTotalMax = 0; - - start(): void { - if (this.reportTimer) return; - this.lastReportWallMs = Date.now(); - - // Event-loop lag sampler (low overhead). - let expected = Date.now() + 100; - setInterval(() => { - if (!this.config.enabled) return; - const now = Date.now(); - const lag = Math.max(0, now - expected); - expected = now + 100; - this.eventLoopLagSum += lag; - this.eventLoopLagCount++; - this.eventLoopLagMax = Math.max(this.eventLoopLagMax, lag); - }, 100); - - this.reportTimer = setInterval(() => this.report(), this.config.intervalMs); - } - - configure(next: Partial): void { - const prevInterval = this.config.intervalMs; - this.config = { - enabled: next.enabled ?? this.config.enabled, - intervalMs: Math.max( - 100, - (next.intervalMs ?? this.config.intervalMs) | 0, - ), - includeTrace: next.includeTrace ?? this.config.includeTrace, - }; - - if (this.config.enabled && !this.reportTimer) { - this.start(); - } - - if (this.reportTimer && this.config.intervalMs !== prevInterval) { - clearInterval(this.reportTimer); - this.reportTimer = setInterval( - () => this.report(), - this.config.intervalMs, - ); - } - } - - recordMessage(type: string, queueMs: number | null, handlerMs: number): void { - if (!this.config.enabled) return; - this.msgCounts.set(type, (this.msgCounts.get(type) ?? 0) + 1); - this.msgHandlerSum.set( - type, - (this.msgHandlerSum.get(type) ?? 0) + handlerMs, - ); - this.msgHandlerMax.set( - type, - Math.max(this.msgHandlerMax.get(type) ?? 0, handlerMs), - ); - if (queueMs !== null) { - this.msgQueueSum.set(type, (this.msgQueueSum.get(type) ?? 0) + queueMs); - this.msgQueueMax.set( - type, - Math.max(this.msgQueueMax.get(type) ?? 0, queueMs), - ); - } - - if (handlerMs > 25 || (queueMs !== null && queueMs > 250)) { - this.trace( - `${new Date().toISOString()} msg ${type} queue=${queueMs ?? "?"}ms handler=${Math.round(handlerMs)}ms`, - ); - } - } - - recordSimExec(execMs: number): void { - if (!this.config.enabled) return; - this.simExecSum += execMs; - this.simExecCount++; - this.simExecMax = Math.max(this.simExecMax, execMs); - if (execMs > 25) { - this.trace( - `${new Date().toISOString()} sim executeNextTick ${Math.round(execMs)}ms`, - ); - } - } - - recordSimDelay(delayMs: number): void { - if (!this.config.enabled) return; - this.simDelaySum += delayMs; - this.simDelayCount++; - this.simDelayMax = Math.max(this.simDelayMax, delayMs); - if (delayMs > 25) { - this.trace( - `${new Date().toISOString()} sim scheduleDelay ${Math.round(delayMs)}ms`, - ); - } - } - - recordRenderBreakdown(b: { - submitted: boolean; - getTextureMs?: number; - frameComputeMs?: number; - territoryPassMs?: number; - temporalResolveMs?: number; - submitMs?: number; - cpuTotalMs?: number; - }): void { - if (!this.config.enabled) return; - if (!b.submitted) { - this.renderNoopCount++; - return; - } - this.renderSubmittedCount++; - - if (typeof b.getTextureMs === "number") { - this.renderGetTextureSum += b.getTextureMs; - this.renderGetTextureMax = Math.max( - this.renderGetTextureMax, - b.getTextureMs, - ); - } - if (typeof b.frameComputeMs === "number") { - this.renderFrameComputeSum += b.frameComputeMs; - this.renderFrameComputeMax = Math.max( - this.renderFrameComputeMax, - b.frameComputeMs, - ); - } - if (typeof b.territoryPassMs === "number") { - this.renderTerritoryPassSum += b.territoryPassMs; - this.renderTerritoryPassMax = Math.max( - this.renderTerritoryPassMax, - b.territoryPassMs, - ); - } - if (typeof b.temporalResolveMs === "number") { - this.renderTemporalResolveSum += b.temporalResolveMs; - this.renderTemporalResolveMax = Math.max( - this.renderTemporalResolveMax, - b.temporalResolveMs, - ); - } - if (typeof b.submitMs === "number") { - this.renderSubmitSum += b.submitMs; - this.renderSubmitMax = Math.max(this.renderSubmitMax, b.submitMs); - } - if (typeof b.cpuTotalMs === "number") { - this.renderCpuTotalSum += b.cpuTotalMs; - this.renderCpuTotalMax = Math.max(this.renderCpuTotalMax, b.cpuTotalMs); - } - } - - trace(line: string): void { - if (!this.config.enabled || !this.config.includeTrace) return; - if (this.traceRing.length < this.traceCap) { - this.traceRing.push(line); - return; - } - this.traceRing[this.traceHead] = line; - this.traceHead = (this.traceHead + 1) % this.traceCap; - } - - private flushTrace(): string[] { - if (!this.config.includeTrace || this.traceRing.length === 0) { - return []; - } - if (this.traceRing.length < this.traceCap) { - return [...this.traceRing]; - } - return [ - ...this.traceRing.slice(this.traceHead), - ...this.traceRing.slice(0, this.traceHead), - ]; - } - - private report(): void { - if (!this.config.enabled) return; - const now = Date.now(); - const intervalMs = Math.max(1, now - this.lastReportWallMs); - this.lastReportWallMs = now; - - const toAvgRecord = ( - sumMap: Map, - countMap: Map, - ) => { - const out: Record = {}; - for (const [k, sum] of sumMap) { - const c = countMap.get(k) ?? 0; - if (c > 0) { - out[k] = sum / c; - } - } - return out; - }; - - const toMaxRecord = (maxMap: Map) => { - const out: Record = {}; - for (const [k, v] of maxMap) { - out[k] = v; - } - return out; - }; - - const msgCountsObj: Record = {}; - for (const [k, c] of this.msgCounts) { - msgCountsObj[k] = c; - } - - const renderTotal = this.renderSubmittedCount + this.renderNoopCount; - const rAvg = (sum: number): number => - this.renderSubmittedCount > 0 ? sum / this.renderSubmittedCount : 0; - - const metrics: WorkerMetricsMessage = { - type: "worker_metrics", - intervalMs, - eventLoopLagMsAvg: - this.eventLoopLagCount > 0 - ? this.eventLoopLagSum / this.eventLoopLagCount - : 0, - eventLoopLagMsMax: this.eventLoopLagMax, - simPumpDelayMsAvg: - this.simDelayCount > 0 ? this.simDelaySum / this.simDelayCount : 0, - simPumpDelayMsMax: this.simDelayMax, - simPumpExecMsAvg: - this.simExecCount > 0 ? this.simExecSum / this.simExecCount : 0, - simPumpExecMsMax: this.simExecMax, - renderSubmittedCount: - renderTotal > 0 ? this.renderSubmittedCount : undefined, - renderNoopCount: renderTotal > 0 ? this.renderNoopCount : undefined, - renderGetTextureMsAvg: - this.renderSubmittedCount > 0 - ? rAvg(this.renderGetTextureSum) - : undefined, - renderGetTextureMsMax: - this.renderSubmittedCount > 0 ? this.renderGetTextureMax : undefined, - renderFrameComputeMsAvg: - this.renderSubmittedCount > 0 - ? rAvg(this.renderFrameComputeSum) - : undefined, - renderFrameComputeMsMax: - this.renderSubmittedCount > 0 ? this.renderFrameComputeMax : undefined, - renderTerritoryPassMsAvg: - this.renderSubmittedCount > 0 - ? rAvg(this.renderTerritoryPassSum) - : undefined, - renderTerritoryPassMsMax: - this.renderSubmittedCount > 0 ? this.renderTerritoryPassMax : undefined, - renderTemporalResolveMsAvg: - this.renderSubmittedCount > 0 - ? rAvg(this.renderTemporalResolveSum) - : undefined, - renderTemporalResolveMsMax: - this.renderSubmittedCount > 0 - ? this.renderTemporalResolveMax - : undefined, - renderSubmitMsAvg: - this.renderSubmittedCount > 0 ? rAvg(this.renderSubmitSum) : undefined, - renderSubmitMsMax: - this.renderSubmittedCount > 0 ? this.renderSubmitMax : undefined, - renderCpuTotalMsAvg: - this.renderSubmittedCount > 0 - ? rAvg(this.renderCpuTotalSum) - : undefined, - renderCpuTotalMsMax: - this.renderSubmittedCount > 0 ? this.renderCpuTotalMax : undefined, - msgCounts: msgCountsObj, - msgHandlerMsAvg: toAvgRecord(this.msgHandlerSum, this.msgCounts), - msgHandlerMsMax: toMaxRecord(this.msgHandlerMax), - msgQueueMsAvg: toAvgRecord(this.msgQueueSum, this.msgCounts), - msgQueueMsMax: toMaxRecord(this.msgQueueMax), - trace: this.config.includeTrace ? this.flushTrace() : undefined, - }; - - sendMessage(metrics); - - // Reset per-interval counters. - this.eventLoopLagSum = 0; - this.eventLoopLagCount = 0; - this.eventLoopLagMax = 0; - this.simDelaySum = 0; - this.simDelayCount = 0; - this.simDelayMax = 0; - this.simExecSum = 0; - this.simExecCount = 0; - this.simExecMax = 0; - this.renderSubmittedCount = 0; - this.renderNoopCount = 0; - this.renderGetTextureSum = 0; - this.renderGetTextureMax = 0; - this.renderFrameComputeSum = 0; - this.renderFrameComputeMax = 0; - this.renderTerritoryPassSum = 0; - this.renderTerritoryPassMax = 0; - this.renderTemporalResolveSum = 0; - this.renderTemporalResolveMax = 0; - this.renderSubmitSum = 0; - this.renderSubmitMax = 0; - this.renderCpuTotalSum = 0; - this.renderCpuTotalMax = 0; - this.msgCounts.clear(); - this.msgHandlerSum.clear(); - this.msgHandlerMax.clear(); - this.msgQueueSum.clear(); - this.msgQueueMax.clear(); - if (this.config.includeTrace) { - this.traceRing = []; - this.traceHead = 0; - } - } -} - -const profiler = new WorkerProfiler(); +const profiler = new WorkerProfiler(sendMessage); let simPumpScheduled = false; diff --git a/src/core/worker/WorkerProfiler.ts b/src/core/worker/WorkerProfiler.ts new file mode 100644 index 000000000..f812db4af --- /dev/null +++ b/src/core/worker/WorkerProfiler.ts @@ -0,0 +1,362 @@ +import { WorkerMetricsMessage } from "./WorkerMessages"; + +export type WorkerDebugConfig = { + enabled: boolean; + intervalMs: number; + includeTrace: boolean; +}; + +export class WorkerProfiler { + public config: WorkerDebugConfig = { + enabled: false, + intervalMs: 1000, + includeTrace: false, + }; + + private reportTimer: any = null; + private lastReportWallMs = 0; + + private eventLoopLagSum = 0; + private eventLoopLagCount = 0; + private eventLoopLagMax = 0; + + private simDelaySum = 0; + private simDelayCount = 0; + private simDelayMax = 0; + + private simExecSum = 0; + private simExecCount = 0; + private simExecMax = 0; + + private readonly msgCounts = new Map(); + private readonly msgHandlerSum = new Map(); + private readonly msgQueueSum = new Map(); + private readonly msgHandlerMax = new Map(); + private readonly msgQueueMax = new Map(); + + private traceRing: string[] = []; + private traceHead = 0; + private readonly traceCap = 160; + + private renderSubmittedCount = 0; + private renderNoopCount = 0; + private renderGetTextureSum = 0; + private renderGetTextureMax = 0; + private renderFrameComputeSum = 0; + private renderFrameComputeMax = 0; + private renderTerritoryPassSum = 0; + private renderTerritoryPassMax = 0; + private renderTemporalResolveSum = 0; + private renderTemporalResolveMax = 0; + private renderSubmitSum = 0; + private renderSubmitMax = 0; + private renderCpuTotalSum = 0; + private renderCpuTotalMax = 0; + + constructor(private send: (message: WorkerMetricsMessage) => void) {} + + start(): void { + if (this.reportTimer) return; + this.lastReportWallMs = Date.now(); + + // Event-loop lag sampler (low overhead). + let expected = Date.now() + 100; + setInterval(() => { + if (!this.config.enabled) return; + const now = Date.now(); + const lag = Math.max(0, now - expected); + expected = now + 100; + this.eventLoopLagSum += lag; + this.eventLoopLagCount++; + this.eventLoopLagMax = Math.max(this.eventLoopLagMax, lag); + }, 100); + + this.reportTimer = setInterval(() => this.report(), this.config.intervalMs); + } + + configure(next: Partial): void { + const prevInterval = this.config.intervalMs; + this.config = { + enabled: next.enabled ?? this.config.enabled, + intervalMs: Math.max( + 100, + (next.intervalMs ?? this.config.intervalMs) | 0, + ), + includeTrace: next.includeTrace ?? this.config.includeTrace, + }; + + if (this.config.enabled && !this.reportTimer) { + this.start(); + } + + if (this.reportTimer && this.config.intervalMs !== prevInterval) { + clearInterval(this.reportTimer); + this.reportTimer = setInterval( + () => this.report(), + this.config.intervalMs, + ); + } + } + + recordMessage(type: string, queueMs: number | null, handlerMs: number): void { + if (!this.config.enabled) return; + this.msgCounts.set(type, (this.msgCounts.get(type) ?? 0) + 1); + this.msgHandlerSum.set( + type, + (this.msgHandlerSum.get(type) ?? 0) + handlerMs, + ); + this.msgHandlerMax.set( + type, + Math.max(this.msgHandlerMax.get(type) ?? 0, handlerMs), + ); + if (queueMs !== null) { + this.msgQueueSum.set(type, (this.msgQueueSum.get(type) ?? 0) + queueMs); + this.msgQueueMax.set( + type, + Math.max(this.msgQueueMax.get(type) ?? 0, queueMs), + ); + } + + if (handlerMs > 25 || (queueMs !== null && queueMs > 250)) { + this.trace( + `${new Date().toISOString()} msg ${type} queue=${queueMs ?? "?"}ms handler=${Math.round(handlerMs)}ms`, + ); + } + } + + recordSimExec(execMs: number): void { + if (!this.config.enabled) return; + this.simExecSum += execMs; + this.simExecCount++; + this.simExecMax = Math.max(this.simExecMax, execMs); + if (execMs > 25) { + this.trace( + `${new Date().toISOString()} sim executeNextTick ${Math.round(execMs)}ms`, + ); + } + } + + recordSimDelay(delayMs: number): void { + if (!this.config.enabled) return; + this.simDelaySum += delayMs; + this.simDelayCount++; + this.simDelayMax = Math.max(this.simDelayMax, delayMs); + if (delayMs > 25) { + this.trace( + `${new Date().toISOString()} sim scheduleDelay ${Math.round(delayMs)}ms`, + ); + } + } + + recordRenderBreakdown(b: { + submitted: boolean; + getTextureMs?: number; + frameComputeMs?: number; + territoryPassMs?: number; + temporalResolveMs?: number; + submitMs?: number; + cpuTotalMs?: number; + }): void { + if (!this.config.enabled) return; + if (!b.submitted) { + this.renderNoopCount++; + return; + } + this.renderSubmittedCount++; + + if (typeof b.getTextureMs === "number") { + this.renderGetTextureSum += b.getTextureMs; + this.renderGetTextureMax = Math.max( + this.renderGetTextureMax, + b.getTextureMs, + ); + } + if (typeof b.frameComputeMs === "number") { + this.renderFrameComputeSum += b.frameComputeMs; + this.renderFrameComputeMax = Math.max( + this.renderFrameComputeMax, + b.frameComputeMs, + ); + } + if (typeof b.territoryPassMs === "number") { + this.renderTerritoryPassSum += b.territoryPassMs; + this.renderTerritoryPassMax = Math.max( + this.renderTerritoryPassMax, + b.territoryPassMs, + ); + } + if (typeof b.temporalResolveMs === "number") { + this.renderTemporalResolveSum += b.temporalResolveMs; + this.renderTemporalResolveMax = Math.max( + this.renderTemporalResolveMax, + b.temporalResolveMs, + ); + } + if (typeof b.submitMs === "number") { + this.renderSubmitSum += b.submitMs; + this.renderSubmitMax = Math.max(this.renderSubmitMax, b.submitMs); + } + if (typeof b.cpuTotalMs === "number") { + this.renderCpuTotalSum += b.cpuTotalMs; + this.renderCpuTotalMax = Math.max(this.renderCpuTotalMax, b.cpuTotalMs); + } + } + + trace(line: string): void { + if (!this.config.enabled || !this.config.includeTrace) return; + if (this.traceRing.length < this.traceCap) { + this.traceRing.push(line); + return; + } + this.traceRing[this.traceHead] = line; + this.traceHead = (this.traceHead + 1) % this.traceCap; + } + + private flushTrace(): string[] { + if (!this.config.includeTrace || this.traceRing.length === 0) { + return []; + } + if (this.traceRing.length < this.traceCap) { + return [...this.traceRing]; + } + return [ + ...this.traceRing.slice(this.traceHead), + ...this.traceRing.slice(0, this.traceHead), + ]; + } + + private report(): void { + if (!this.config.enabled) return; + const now = Date.now(); + const intervalMs = Math.max(1, now - this.lastReportWallMs); + this.lastReportWallMs = now; + + const toAvgRecord = ( + sumMap: Map, + countMap: Map, + ) => { + const out: Record = {}; + for (const [k, sum] of sumMap) { + const c = countMap.get(k) ?? 0; + if (c > 0) { + out[k] = sum / c; + } + } + return out; + }; + + const toMaxRecord = (maxMap: Map) => { + const out: Record = {}; + for (const [k, v] of maxMap) { + out[k] = v; + } + return out; + }; + + const msgCountsObj: Record = {}; + for (const [k, c] of this.msgCounts) { + msgCountsObj[k] = c; + } + + const renderTotal = this.renderSubmittedCount + this.renderNoopCount; + const rAvg = (sum: number): number => + this.renderSubmittedCount > 0 ? sum / this.renderSubmittedCount : 0; + + const metrics: WorkerMetricsMessage = { + type: "worker_metrics", + intervalMs, + eventLoopLagMsAvg: + this.eventLoopLagCount > 0 + ? this.eventLoopLagSum / this.eventLoopLagCount + : 0, + eventLoopLagMsMax: this.eventLoopLagMax, + simPumpDelayMsAvg: + this.simDelayCount > 0 ? this.simDelaySum / this.simDelayCount : 0, + simPumpDelayMsMax: this.simDelayMax, + simPumpExecMsAvg: + this.simExecCount > 0 ? this.simExecSum / this.simExecCount : 0, + simPumpExecMsMax: this.simExecMax, + renderSubmittedCount: + renderTotal > 0 ? this.renderSubmittedCount : undefined, + renderNoopCount: renderTotal > 0 ? this.renderNoopCount : undefined, + renderGetTextureMsAvg: + this.renderSubmittedCount > 0 + ? rAvg(this.renderGetTextureSum) + : undefined, + renderGetTextureMsMax: + this.renderSubmittedCount > 0 ? this.renderGetTextureMax : undefined, + renderFrameComputeMsAvg: + this.renderSubmittedCount > 0 + ? rAvg(this.renderFrameComputeSum) + : undefined, + renderFrameComputeMsMax: + this.renderSubmittedCount > 0 ? this.renderFrameComputeMax : undefined, + renderTerritoryPassMsAvg: + this.renderSubmittedCount > 0 + ? rAvg(this.renderTerritoryPassSum) + : undefined, + renderTerritoryPassMsMax: + this.renderSubmittedCount > 0 ? this.renderTerritoryPassMax : undefined, + renderTemporalResolveMsAvg: + this.renderSubmittedCount > 0 + ? rAvg(this.renderTemporalResolveSum) + : undefined, + renderTemporalResolveMsMax: + this.renderSubmittedCount > 0 + ? this.renderTemporalResolveMax + : undefined, + renderSubmitMsAvg: + this.renderSubmittedCount > 0 ? rAvg(this.renderSubmitSum) : undefined, + renderSubmitMsMax: + this.renderSubmittedCount > 0 ? this.renderSubmitMax : undefined, + renderCpuTotalMsAvg: + this.renderSubmittedCount > 0 + ? rAvg(this.renderCpuTotalSum) + : undefined, + renderCpuTotalMsMax: + this.renderSubmittedCount > 0 ? this.renderCpuTotalMax : undefined, + msgCounts: msgCountsObj, + msgHandlerMsAvg: toAvgRecord(this.msgHandlerSum, this.msgCounts), + msgHandlerMsMax: toMaxRecord(this.msgHandlerMax), + msgQueueMsAvg: toAvgRecord(this.msgQueueSum, this.msgCounts), + msgQueueMsMax: toMaxRecord(this.msgQueueMax), + trace: this.config.includeTrace ? this.flushTrace() : undefined, + }; + + this.send(metrics); + + // Reset per-interval counters. + this.eventLoopLagSum = 0; + this.eventLoopLagCount = 0; + this.eventLoopLagMax = 0; + this.simDelaySum = 0; + this.simDelayCount = 0; + this.simDelayMax = 0; + this.simExecSum = 0; + this.simExecCount = 0; + this.simExecMax = 0; + this.renderSubmittedCount = 0; + this.renderNoopCount = 0; + this.renderGetTextureSum = 0; + this.renderGetTextureMax = 0; + this.renderFrameComputeSum = 0; + this.renderFrameComputeMax = 0; + this.renderTerritoryPassSum = 0; + this.renderTerritoryPassMax = 0; + this.renderTemporalResolveSum = 0; + this.renderTemporalResolveMax = 0; + this.renderSubmitSum = 0; + this.renderSubmitMax = 0; + this.renderCpuTotalSum = 0; + this.renderCpuTotalMax = 0; + this.msgCounts.clear(); + this.msgHandlerSum.clear(); + this.msgHandlerMax.clear(); + this.msgQueueSum.clear(); + this.msgQueueMax.clear(); + if (this.config.includeTrace) { + this.traceRing = []; + this.traceHead = 0; + } + } +}