mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-22 00:21:55 +00:00
Add client catch-up mode
Increase worker heartbeats per frame when far behind server to fast-forward simulation. Track backlog and expose catch-up status via TickMetricsEvent. Extend performance overlay to display backlog turns and indicate active catch-up mode.
This commit is contained in:
@@ -208,6 +208,16 @@ export class ClientGameRunner {
|
||||
private lastTickReceiveTime: number = 0;
|
||||
private currentTickDelay: number | undefined = undefined;
|
||||
|
||||
// Track how far behind the client simulation is compared to the server.
|
||||
private serverTurnHighWater: number = 0;
|
||||
private lastProcessedTick: number = 0;
|
||||
private backlogTurns: number = 0;
|
||||
|
||||
private catchUpMode: boolean = false;
|
||||
private readonly CATCH_UP_ENTER_BACKLOG = 120; // turns behind to enter catch-up
|
||||
private readonly CATCH_UP_EXIT_BACKLOG = 20; // turns behind to exit catch-up
|
||||
private readonly CATCH_UP_HEARTBEATS_PER_FRAME = 5;
|
||||
|
||||
constructor(
|
||||
private lobby: LobbyConfig,
|
||||
private eventBus: EventBus,
|
||||
@@ -299,9 +309,43 @@ export class ClientGameRunner {
|
||||
this.gameView.update(gu);
|
||||
this.renderer.tick();
|
||||
|
||||
// Update tick / backlog metrics
|
||||
if (!("errMsg" in gu)) {
|
||||
this.lastProcessedTick = gu.tick;
|
||||
this.backlogTurns = Math.max(
|
||||
0,
|
||||
this.serverTurnHighWater - this.lastProcessedTick,
|
||||
);
|
||||
|
||||
const wasCatchUp = this.catchUpMode;
|
||||
if (
|
||||
!this.catchUpMode &&
|
||||
this.backlogTurns >= this.CATCH_UP_ENTER_BACKLOG
|
||||
) {
|
||||
this.catchUpMode = true;
|
||||
} else if (
|
||||
this.catchUpMode &&
|
||||
this.backlogTurns <= this.CATCH_UP_EXIT_BACKLOG
|
||||
) {
|
||||
this.catchUpMode = false;
|
||||
}
|
||||
if (wasCatchUp !== this.catchUpMode) {
|
||||
console.log(
|
||||
`Catch-up mode ${this.catchUpMode ? "enabled" : "disabled"} (backlog: ${
|
||||
this.backlogTurns
|
||||
} turns)`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Emit tick metrics event for performance overlay
|
||||
this.eventBus.emit(
|
||||
new TickMetricsEvent(gu.tickExecutionDuration, this.currentTickDelay),
|
||||
new TickMetricsEvent(
|
||||
gu.tickExecutionDuration,
|
||||
this.currentTickDelay,
|
||||
this.backlogTurns,
|
||||
this.catchUpMode,
|
||||
),
|
||||
);
|
||||
|
||||
// Reset tick delay for next measurement
|
||||
@@ -314,7 +358,12 @@ export class ClientGameRunner {
|
||||
const worker = this.worker;
|
||||
const keepWorkerAlive = () => {
|
||||
if (this.isActive) {
|
||||
worker.sendHeartbeat();
|
||||
const beatsPerFrame = this.catchUpMode
|
||||
? this.CATCH_UP_HEARTBEATS_PER_FRAME
|
||||
: 1;
|
||||
for (let i = 0; i < beatsPerFrame; i++) {
|
||||
worker.sendHeartbeat();
|
||||
}
|
||||
requestAnimationFrame(keepWorkerAlive);
|
||||
}
|
||||
};
|
||||
@@ -363,6 +412,10 @@ export class ClientGameRunner {
|
||||
}
|
||||
|
||||
for (const turn of message.turns) {
|
||||
this.serverTurnHighWater = Math.max(
|
||||
this.serverTurnHighWater,
|
||||
turn.turnNumber,
|
||||
);
|
||||
if (turn.turnNumber < this.turnsSeen) {
|
||||
continue;
|
||||
}
|
||||
@@ -415,6 +468,11 @@ export class ClientGameRunner {
|
||||
}
|
||||
this.lastTickReceiveTime = now;
|
||||
|
||||
this.serverTurnHighWater = Math.max(
|
||||
this.serverTurnHighWater,
|
||||
message.turn.turnNumber,
|
||||
);
|
||||
|
||||
if (this.turnsSeen !== message.turn.turnNumber) {
|
||||
console.error(
|
||||
`got wrong turn have turns ${this.turnsSeen}, received turn ${message.turn.turnNumber}`,
|
||||
|
||||
@@ -129,6 +129,10 @@ export class TickMetricsEvent implements GameEvent {
|
||||
constructor(
|
||||
public readonly tickExecutionDuration?: number,
|
||||
public readonly tickDelay?: number,
|
||||
// Number of turns the client is behind the server (if known)
|
||||
public readonly backlogTurns?: number,
|
||||
// Whether the client is currently in catch-up mode
|
||||
public readonly inCatchUpMode?: boolean,
|
||||
) {}
|
||||
}
|
||||
|
||||
|
||||
@@ -229,7 +229,12 @@ export class PerformanceOverlay extends LitElement implements Layer {
|
||||
this.setVisible(this.userSettings.performanceOverlay());
|
||||
});
|
||||
this.eventBus.on(TickMetricsEvent, (event: TickMetricsEvent) => {
|
||||
this.updateTickMetrics(event.tickExecutionDuration, event.tickDelay);
|
||||
this.updateTickMetrics(
|
||||
event.tickExecutionDuration,
|
||||
event.tickDelay,
|
||||
event.backlogTurns,
|
||||
event.inCatchUpMode,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -418,7 +423,18 @@ export class PerformanceOverlay extends LitElement implements Layer {
|
||||
this.layerBreakdown = breakdown;
|
||||
}
|
||||
|
||||
updateTickMetrics(tickExecutionDuration?: number, tickDelay?: number) {
|
||||
@state()
|
||||
private backlogTurns: number = 0;
|
||||
|
||||
@state()
|
||||
private inCatchUpMode: boolean = false;
|
||||
|
||||
updateTickMetrics(
|
||||
tickExecutionDuration?: number,
|
||||
tickDelay?: number,
|
||||
backlogTurns?: number,
|
||||
inCatchUpMode?: boolean,
|
||||
) {
|
||||
if (!this.isVisible || !this.userSettings.performanceOverlay()) return;
|
||||
|
||||
// Update tick execution duration stats
|
||||
@@ -455,6 +471,13 @@ export class PerformanceOverlay extends LitElement implements Layer {
|
||||
}
|
||||
}
|
||||
|
||||
if (backlogTurns !== undefined) {
|
||||
this.backlogTurns = backlogTurns;
|
||||
}
|
||||
if (inCatchUpMode !== undefined) {
|
||||
this.inCatchUpMode = inCatchUpMode;
|
||||
}
|
||||
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
@@ -600,6 +623,11 @@ export class PerformanceOverlay extends LitElement implements Layer {
|
||||
<span>${this.tickDelayAvg.toFixed(2)}ms</span>
|
||||
(max: <span>${this.tickDelayMax}ms</span>)
|
||||
</div>
|
||||
<div class="performance-line">
|
||||
Backlog turns:
|
||||
<span>${this.backlogTurns}</span>
|
||||
${this.inCatchUpMode ? html`<span> (catch-up)</span>` : html``}
|
||||
</div>
|
||||
${this.layerBreakdown.length
|
||||
? html`<div class="layers-section">
|
||||
<div class="performance-line">
|
||||
|
||||
Reference in New Issue
Block a user