throttle renderGame() based on the current backlog (again)

Rendering runs on its own rAF loop and always tries to hit the display framerate, so even though it’s on a different thread it can still chew a lot of CPU while the worker is trying to catch up a huge backlog. To mitigate this, we now emit backlog status from ClientGameRunner and throttle renderGame() based on the current backlog, effectively reintroducing frame skipping so the worker and main-thread update processing get more breathing room when we’re far behind.
This commit is contained in:
scamiv
2025-11-25 18:44:31 +01:00
parent 0b0753d5e1
commit 46d09eb614
3 changed files with 44 additions and 1 deletions
+4
View File
@@ -28,6 +28,7 @@ import { UserSettings } from "../core/game/UserSettings";
import { WorkerClient } from "../core/worker/WorkerClient";
import {
AutoUpgradeEvent,
BacklogStatusEvent,
DoBoatAttackEvent,
DoGroundAttackEvent,
InputHandler,
@@ -600,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