Refactor rendering and throttle based on backlog

- Refactor rendering and metrics emission in ClientGameRunner to ensure updates occur only after all processing is complete
- Throttle renderGame() based on the current backlog
This commit is contained in:
scamiv
2025-11-25 18:46:25 +01:00
parent 6ab7136c3c
commit 7cf024fb6e
3 changed files with 66 additions and 21 deletions
+26 -20
View File
@@ -28,6 +28,7 @@ import { UserSettings } from "../core/game/UserSettings";
import { WorkerClient } from "../core/worker/WorkerClient";
import {
AutoUpgradeEvent,
BacklogStatusEvent,
DoBoatAttackEvent,
DoGroundAttackEvent,
InputHandler,
@@ -511,33 +512,35 @@ export class ClientGameRunner {
this.pendingStart = 0;
}
if (batch.length > 0 && lastTick !== undefined) {
// Only update view and render when ALL processing is complete
if (
this.pendingStart >= this.pendingUpdates.length &&
batch.length > 0 &&
lastTick !== undefined
) {
const combinedGu = this.mergeGameUpdates(batch);
if (combinedGu) {
this.gameView.update(combinedGu);
}
// 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;
const ticksPerRender =
this.lastRenderedTick === 0
? lastTick
: lastTick - this.lastRenderedTick;
this.lastRenderedTick = lastTick;
this.renderer.tick();
this.eventBus.emit(
new TickMetricsEvent(
lastTickDuration,
this.currentTickDelay,
this.backlogTurns,
ticksPerRender,
),
);
this.renderer.tick();
this.eventBus.emit(
new TickMetricsEvent(
lastTickDuration,
this.currentTickDelay,
this.backlogTurns,
ticksPerRender,
),
);
// Reset tick delay for next measurement
this.currentTickDelay = undefined;
}
// Reset tick delay for next measurement
this.currentTickDelay = undefined;
}
if (this.pendingStart < this.pendingUpdates.length) {
@@ -598,6 +601,9 @@ export class ClientGameRunner {
this.serverTurnHighWater - this.lastProcessedTick,
);
this.backlogGrowing = this.backlogTurns > previousBacklog;
this.eventBus.emit(
new BacklogStatusEvent(this.backlogTurns, this.backlogGrowing),
);
}
private inputEvent(event: MouseUpEvent) {
+7
View File
@@ -136,6 +136,13 @@ export class TickMetricsEvent implements GameEvent {
) {}
}
export class BacklogStatusEvent implements GameEvent {
constructor(
public readonly backlogTurns: number,
public readonly backlogGrowing: boolean,
) {}
}
export class InputHandler {
private lastPointerX: number = 0;
private lastPointerY: number = 0;
+33 -1
View File
@@ -2,7 +2,10 @@ import { EventBus } from "../../core/EventBus";
import { GameView } from "../../core/game/GameView";
import { UserSettings } from "../../core/game/UserSettings";
import { GameStartingModal } from "../GameStartingModal";
import { RefreshGraphicsEvent as RedrawGraphicsEvent } from "../InputHandler";
import {
BacklogStatusEvent,
RefreshGraphicsEvent as RedrawGraphicsEvent,
} from "../InputHandler";
import { FrameProfiler } from "./FrameProfiler";
import { TransformHandler } from "./TransformHandler";
import { UIState } from "./UIState";
@@ -292,6 +295,9 @@ export function createRenderer(
export class GameRenderer {
private context: CanvasRenderingContext2D;
private backlogTurns: number = 0;
private backlogGrowing: boolean = false;
private lastRenderTime: number = 0;
constructor(
private game: GameView,
@@ -309,6 +315,10 @@ export class GameRenderer {
initialize() {
this.eventBus.on(RedrawGraphicsEvent, () => this.redraw());
this.eventBus.on(BacklogStatusEvent, (event: BacklogStatusEvent) => {
this.backlogTurns = event.backlogTurns;
this.backlogGrowing = event.backlogGrowing;
});
this.layers.forEach((l) => l.init?.());
document.body.appendChild(this.canvas);
@@ -344,6 +354,28 @@ export class GameRenderer {
}
renderGame() {
const now = performance.now();
if (this.backlogTurns > 0) {
const BASE_FPS = 60;
const MIN_FPS = 20;
const BACKLOG_MAX_TURNS = 50;
const scale = Math.min(1, this.backlogTurns / BACKLOG_MAX_TURNS);
const targetFps = BASE_FPS - scale * (BASE_FPS - MIN_FPS);
const minFrameInterval = 1000 / targetFps;
if (this.lastRenderTime !== 0) {
const sinceLast = now - this.lastRenderTime;
if (sinceLast < minFrameInterval) {
requestAnimationFrame(() => this.renderGame());
return;
}
}
}
this.lastRenderTime = now;
FrameProfiler.clear();
const start = performance.now();
// Set background