mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-08-18 09:14:09 +00:00
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:
@@ -28,6 +28,7 @@ import { UserSettings } from "../core/game/UserSettings";
|
|||||||
import { WorkerClient } from "../core/worker/WorkerClient";
|
import { WorkerClient } from "../core/worker/WorkerClient";
|
||||||
import {
|
import {
|
||||||
AutoUpgradeEvent,
|
AutoUpgradeEvent,
|
||||||
|
BacklogStatusEvent,
|
||||||
DoBoatAttackEvent,
|
DoBoatAttackEvent,
|
||||||
DoGroundAttackEvent,
|
DoGroundAttackEvent,
|
||||||
InputHandler,
|
InputHandler,
|
||||||
@@ -527,33 +528,35 @@ export class ClientGameRunner {
|
|||||||
this.pendingStart = 0;
|
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);
|
const combinedGu = this.mergeGameUpdates(batch);
|
||||||
if (combinedGu) {
|
if (combinedGu) {
|
||||||
this.gameView.update(combinedGu);
|
this.gameView.update(combinedGu);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only emit metrics when ALL processing is complete
|
const ticksPerRender =
|
||||||
if (this.pendingStart >= this.pendingUpdates.length) {
|
this.lastRenderedTick === 0
|
||||||
const ticksPerRender =
|
? lastTick
|
||||||
this.lastRenderedTick === 0
|
: lastTick - this.lastRenderedTick;
|
||||||
? lastTick
|
this.lastRenderedTick = lastTick;
|
||||||
: lastTick - this.lastRenderedTick;
|
|
||||||
this.lastRenderedTick = lastTick;
|
|
||||||
|
|
||||||
this.renderer.tick();
|
this.renderer.tick();
|
||||||
this.eventBus.emit(
|
this.eventBus.emit(
|
||||||
new TickMetricsEvent(
|
new TickMetricsEvent(
|
||||||
lastTickDuration,
|
lastTickDuration,
|
||||||
this.currentTickDelay,
|
this.currentTickDelay,
|
||||||
this.backlogTurns,
|
this.backlogTurns,
|
||||||
ticksPerRender,
|
ticksPerRender,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Reset tick delay for next measurement
|
// Reset tick delay for next measurement
|
||||||
this.currentTickDelay = undefined;
|
this.currentTickDelay = undefined;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.pendingStart < this.pendingUpdates.length) {
|
if (this.pendingStart < this.pendingUpdates.length) {
|
||||||
@@ -614,6 +617,9 @@ export class ClientGameRunner {
|
|||||||
this.serverTurnHighWater - this.lastProcessedTick,
|
this.serverTurnHighWater - this.lastProcessedTick,
|
||||||
);
|
);
|
||||||
this.backlogGrowing = this.backlogTurns > previousBacklog;
|
this.backlogGrowing = this.backlogTurns > previousBacklog;
|
||||||
|
this.eventBus.emit(
|
||||||
|
new BacklogStatusEvent(this.backlogTurns, this.backlogGrowing),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private inputEvent(event: MouseUpEvent) {
|
private inputEvent(event: MouseUpEvent) {
|
||||||
|
|||||||
@@ -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 {
|
export class InputHandler {
|
||||||
private lastPointerX: number = 0;
|
private lastPointerX: number = 0;
|
||||||
private lastPointerY: number = 0;
|
private lastPointerY: number = 0;
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ import { EventBus } from "../../core/EventBus";
|
|||||||
import { GameView } from "../../core/game/GameView";
|
import { GameView } from "../../core/game/GameView";
|
||||||
import { UserSettings } from "../../core/game/UserSettings";
|
import { UserSettings } from "../../core/game/UserSettings";
|
||||||
import { GameStartingModal } from "../GameStartingModal";
|
import { GameStartingModal } from "../GameStartingModal";
|
||||||
import { RefreshGraphicsEvent as RedrawGraphicsEvent } from "../InputHandler";
|
import {
|
||||||
|
BacklogStatusEvent,
|
||||||
|
RefreshGraphicsEvent as RedrawGraphicsEvent,
|
||||||
|
} from "../InputHandler";
|
||||||
import { FrameProfiler } from "./FrameProfiler";
|
import { FrameProfiler } from "./FrameProfiler";
|
||||||
import { TransformHandler } from "./TransformHandler";
|
import { TransformHandler } from "./TransformHandler";
|
||||||
import { UIState } from "./UIState";
|
import { UIState } from "./UIState";
|
||||||
@@ -292,6 +295,9 @@ export function createRenderer(
|
|||||||
|
|
||||||
export class GameRenderer {
|
export class GameRenderer {
|
||||||
private context: CanvasRenderingContext2D;
|
private context: CanvasRenderingContext2D;
|
||||||
|
private backlogTurns: number = 0;
|
||||||
|
private backlogGrowing: boolean = false;
|
||||||
|
private lastRenderTime: number = 0;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private game: GameView,
|
private game: GameView,
|
||||||
@@ -309,6 +315,10 @@ export class GameRenderer {
|
|||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
this.eventBus.on(RedrawGraphicsEvent, () => this.redraw());
|
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?.());
|
this.layers.forEach((l) => l.init?.());
|
||||||
|
|
||||||
// only append the canvas if it's not already in the document to avoid reparenting side-effects
|
// only append the canvas if it's not already in the document to avoid reparenting side-effects
|
||||||
@@ -348,6 +358,28 @@ export class GameRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderGame() {
|
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();
|
FrameProfiler.clear();
|
||||||
const start = performance.now();
|
const start = performance.now();
|
||||||
// Set background
|
// Set background
|
||||||
|
|||||||
Reference in New Issue
Block a user