diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index 3f1425d1e..077b3d2a7 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -213,17 +213,12 @@ export class ClientGameRunner { private lastProcessedTick: number = 0; private backlogTurns: number = 0; private backlogGrowing: boolean = false; + private lastRenderedTick: number = 0; private pendingUpdates: GameUpdateViewData[] = []; private pendingStart = 0; private isProcessingUpdates = false; - // Adaptive rendering when frames are heavy: render at most once every N frames. - private renderEveryN: number = 1; - private renderSkipCounter: number = 0; - private lastFrameTime: number = 0; - private readonly MAX_RENDER_EVERY_N = 5; - constructor( private lobby: LobbyConfig, private eventBus: EventBus, @@ -309,39 +304,8 @@ export class ClientGameRunner { return; } this.pendingUpdates.push(gu); - if (this.renderEveryN === 1) { - this.processPendingUpdates(); - } + this.processPendingUpdates(); }); - const keepWorkerAlive = () => { - if (this.isActive) { - const now = performance.now(); - let frameDuration = 0; - if (this.lastFrameTime !== 0) { - frameDuration = now - this.lastFrameTime; - } - this.lastFrameTime = now; - - // Decide whether to render (and thus process pending updates) this frame. - let shouldRender = true; - if ( - this.renderEveryN > 1 && - this.renderSkipCounter < this.renderEveryN - 1 - ) { - shouldRender = false; - this.renderSkipCounter++; - } else if (this.renderEveryN > 1) { - this.renderSkipCounter = 0; - } - - if (shouldRender) { - this.processPendingUpdates(); - } - this.adaptRenderFrequency(frameDuration); - requestAnimationFrame(keepWorkerAlive); - } - }; - requestAnimationFrame(keepWorkerAlive); const onconnect = () => { console.log("Connected to game server!"); @@ -489,7 +453,7 @@ export class ClientGameRunner { const MAX_SLICE_BUDGET_MS = 2000; // allow longer slices when backlog is large const BACKLOG_FREE_TURNS = 10; // scaling starts at this many turns const BACKLOG_MAX_TURNS = 500; // MAX_SLICE_BUDGET_MS is reached at this many turns - const MAX_PER_SLICE = 1000; + const MAX_TICKS_PER_SLICE = 1000; const backlogOverhead = Math.max( 0, @@ -532,7 +496,7 @@ export class ClientGameRunner { lastTick = gu.tick; const elapsed = performance.now() - frameStart; - if (processedCount >= MAX_PER_SLICE || elapsed >= sliceBudgetMs) { + if (processedCount >= MAX_TICKS_PER_SLICE || elapsed >= sliceBudgetMs) { break; } } @@ -553,18 +517,27 @@ export class ClientGameRunner { this.gameView.update(combinedGu); } - this.renderer.tick(); - this.eventBus.emit( - new TickMetricsEvent( - lastTickDuration, - this.currentTickDelay, - this.backlogTurns, - this.renderEveryN, - ), - ); + // Only emit metrics when ALL processing is complete + if (this.pendingStart >= this.pendingUpdates.length) { + const ticksPerRender = + this.lastRenderedTick === 0 + ? lastTick + : lastTick - this.lastRenderedTick; + this.lastRenderedTick = lastTick; - // Reset tick delay for next measurement - this.currentTickDelay = undefined; + this.renderer.tick(); + this.eventBus.emit( + new TickMetricsEvent( + lastTickDuration, + this.currentTickDelay, + this.backlogTurns, + ticksPerRender, + ), + ); + + // Reset tick delay for next measurement + this.currentTickDelay = undefined; + } } if (this.pendingStart < this.pendingUpdates.length) { @@ -577,33 +550,6 @@ export class ClientGameRunner { requestAnimationFrame(processFrame); } - private adaptRenderFrequency(frameDuration: number) { - // Frameskip only matters while we have a backlog; otherwise stay at 1. - if (this.backlogTurns === 0) { - this.renderEveryN = 1; - this.renderSkipCounter = 0; - return; - } - - const HIGH_FRAME_MS = 25; - const LOW_FRAME_MS = 18; - - // Only throttle rendering if backlog is still growing; otherwise drift back toward 1. - if (this.backlogGrowing && frameDuration > HIGH_FRAME_MS) { - if (this.renderEveryN < this.MAX_RENDER_EVERY_N) { - this.renderEveryN++; - } - } else if ( - !this.backlogGrowing && - frameDuration > 0 && - frameDuration < LOW_FRAME_MS - ) { - if (this.renderEveryN > 1) { - this.renderEveryN--; - } - } - } - private mergeGameUpdates( batch: GameUpdateViewData[], ): GameUpdateViewData | null { diff --git a/src/client/InputHandler.ts b/src/client/InputHandler.ts index abe01cac3..bd95e7201 100644 --- a/src/client/InputHandler.ts +++ b/src/client/InputHandler.ts @@ -131,7 +131,8 @@ export class TickMetricsEvent implements GameEvent { public readonly tickDelay?: number, // Number of turns the client is behind the server (if known) public readonly backlogTurns?: number, - public readonly renderEveryN?: number, + // Number of ticks applied since last render + public readonly ticksPerRender?: number, ) {} } diff --git a/src/client/graphics/layers/PerformanceOverlay.ts b/src/client/graphics/layers/PerformanceOverlay.ts index 183c4fa44..531f19f0b 100644 --- a/src/client/graphics/layers/PerformanceOverlay.ts +++ b/src/client/graphics/layers/PerformanceOverlay.ts @@ -233,7 +233,7 @@ export class PerformanceOverlay extends LitElement implements Layer { event.tickExecutionDuration, event.tickDelay, event.backlogTurns, - event.renderEveryN, + event.ticksPerRender, ); }); } @@ -424,16 +424,16 @@ export class PerformanceOverlay extends LitElement implements Layer { } @state() - private renderEveryN: number = 1; + private backlogTurns: number = 0; @state() - private backlogTurns: number = 0; + private ticksPerRender: number = 0; updateTickMetrics( tickExecutionDuration?: number, tickDelay?: number, backlogTurns?: number, - renderEveryN?: number, + ticksPerRender?: number, ) { if (!this.isVisible || !this.userSettings.performanceOverlay()) return; @@ -474,8 +474,9 @@ export class PerformanceOverlay extends LitElement implements Layer { if (backlogTurns !== undefined) { this.backlogTurns = backlogTurns; } - if (renderEveryN !== undefined) { - this.renderEveryN = renderEveryN; + + if (ticksPerRender !== undefined) { + this.ticksPerRender = ticksPerRender; } this.requestUpdate(); @@ -623,15 +624,14 @@ export class PerformanceOverlay extends LitElement implements Layer { ${this.tickDelayAvg.toFixed(2)}ms (max: ${this.tickDelayMax}ms) +
+ Ticks per render: + ${this.ticksPerRender} +
Backlog turns: ${this.backlogTurns}
- ${this.renderEveryN > 1 - ? html`
- Render every ${this.renderEveryN} frame(s) -
` - : html``} ${this.layerBreakdown.length ? html`