mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 19:55:35 +00:00
Clean up previous implementations
removed: - catchUpMode and its CATCH_UP_ENTER/EXIT thresholds in ClientGameRunner - tick metrics fields and overlay UI for inCatchUpMode and beatsPerFrame - leftover worker heartbeat plumbing (message type + WorkerClient.sendHeartbeat) that was no longer used after self-clocking changed: - backlog tracking: keep serverTurnHighWater / lastProcessedTick / backlogTurns, but simplify it to just compute backlog and a backlogGrowing flag instead of driving a dedicated catch-up mode - frame skip: adaptRenderFrequency now only increases renderEveryN when backlog > 0 and still growing; when backlog is stable/shrinking or zero, it decays renderEveryN back toward 1 - render loop: uses the backlog-aware renderEveryN unconditionally (no catch-up flag), and resets skipping completely when backlog reaches 0 - metrics/overlay: TickMetricsEvent now carries backlogTurns and renderEveryN; the performance overlay displays backlog and current “render every N frames” but no longer mentions catch-up or heartbeats Learnings during branch development leading to this Once the worker self-clocks, a separate “catch-up mode” and beats-per-frame knob don’t add real control; they just complicate the model. Backlog is still a valuable signal, but it’s more effective as a quantitative input (backlog size and whether it’s growing) than as a boolean mode toggle. Frame skipping should be driven by actual backlog pressure plus frame cost: throttle only while backlog is growing and frames are heavy, and automatically relax back to full-rate rendering once the simulation catches up.
This commit is contained in:
@@ -230,15 +230,12 @@ export class ClientGameRunner {
|
||||
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 backlogGrowing: boolean = false;
|
||||
|
||||
private pendingUpdates: GameUpdateViewData[] = [];
|
||||
private isProcessingUpdates = false;
|
||||
|
||||
// Adaptive rendering during catch-up: render at most once every N frames.
|
||||
// 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;
|
||||
@@ -329,7 +326,7 @@ export class ClientGameRunner {
|
||||
return;
|
||||
}
|
||||
this.pendingUpdates.push(gu);
|
||||
if (!this.catchUpMode) {
|
||||
if (this.renderEveryN === 1) {
|
||||
this.processPendingUpdates();
|
||||
}
|
||||
});
|
||||
@@ -344,13 +341,14 @@ export class ClientGameRunner {
|
||||
|
||||
// Decide whether to render (and thus process pending updates) this frame.
|
||||
let shouldRender = true;
|
||||
if (this.catchUpMode && this.renderEveryN > 1) {
|
||||
if (this.renderSkipCounter < this.renderEveryN - 1) {
|
||||
shouldRender = false;
|
||||
this.renderSkipCounter++;
|
||||
} else {
|
||||
this.renderSkipCounter = 0;
|
||||
}
|
||||
if (
|
||||
this.renderEveryN > 1 &&
|
||||
this.renderSkipCounter < this.renderEveryN - 1
|
||||
) {
|
||||
shouldRender = false;
|
||||
this.renderSkipCounter++;
|
||||
} else if (this.renderEveryN > 1) {
|
||||
this.renderSkipCounter = 0;
|
||||
}
|
||||
|
||||
if (shouldRender) {
|
||||
@@ -544,7 +542,6 @@ export class ClientGameRunner {
|
||||
lastTickDuration,
|
||||
this.currentTickDelay,
|
||||
this.backlogTurns,
|
||||
this.catchUpMode,
|
||||
this.renderEveryN,
|
||||
),
|
||||
);
|
||||
@@ -555,28 +552,26 @@ export class ClientGameRunner {
|
||||
}
|
||||
|
||||
private adaptRenderFrequency(frameDuration: number) {
|
||||
if (!this.catchUpMode) {
|
||||
// Back to normal rendering.
|
||||
// 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_BACKLOG = 200;
|
||||
const LOW_BACKLOG = 50;
|
||||
const HIGH_FRAME_MS = 25;
|
||||
const LOW_FRAME_MS = 18;
|
||||
|
||||
if (this.backlogTurns > HIGH_BACKLOG && frameDuration > HIGH_FRAME_MS) {
|
||||
// We are far behind and frames are heavy → render less often.
|
||||
// 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.backlogTurns < LOW_BACKLOG ||
|
||||
(frameDuration > 0 && frameDuration < LOW_FRAME_MS)
|
||||
!this.backlogGrowing &&
|
||||
frameDuration > 0 &&
|
||||
frameDuration < LOW_FRAME_MS
|
||||
) {
|
||||
// Either mostly caught up or frames are cheap again → move back toward normal.
|
||||
if (this.renderEveryN > 1) {
|
||||
this.renderEveryN--;
|
||||
}
|
||||
@@ -625,27 +620,12 @@ export class ClientGameRunner {
|
||||
|
||||
private updateBacklogMetrics(processedTick: number) {
|
||||
this.lastProcessedTick = processedTick;
|
||||
const previousBacklog = this.backlogTurns;
|
||||
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)`,
|
||||
);
|
||||
}
|
||||
this.backlogGrowing = this.backlogTurns > previousBacklog;
|
||||
}
|
||||
|
||||
private inputEvent(event: MouseUpEvent) {
|
||||
|
||||
@@ -131,10 +131,7 @@ export class TickMetricsEvent implements GameEvent {
|
||||
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,
|
||||
public readonly renderEveryN?: number,
|
||||
public readonly beatsPerFrame?: number,
|
||||
) {}
|
||||
}
|
||||
|
||||
|
||||
@@ -233,9 +233,7 @@ export class PerformanceOverlay extends LitElement implements Layer {
|
||||
event.tickExecutionDuration,
|
||||
event.tickDelay,
|
||||
event.backlogTurns,
|
||||
event.inCatchUpMode,
|
||||
event.renderEveryN,
|
||||
event.beatsPerFrame,
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -425,25 +423,17 @@ export class PerformanceOverlay extends LitElement implements Layer {
|
||||
this.layerBreakdown = breakdown;
|
||||
}
|
||||
|
||||
@state()
|
||||
private backlogTurns: number = 0;
|
||||
|
||||
@state()
|
||||
private inCatchUpMode: boolean = false;
|
||||
|
||||
@state()
|
||||
private renderEveryN: number = 1;
|
||||
|
||||
@state()
|
||||
private beatsPerFrame: number | null = null;
|
||||
private backlogTurns: number = 0;
|
||||
|
||||
updateTickMetrics(
|
||||
tickExecutionDuration?: number,
|
||||
tickDelay?: number,
|
||||
backlogTurns?: number,
|
||||
inCatchUpMode?: boolean,
|
||||
renderEveryN?: number,
|
||||
beatsPerFrame?: number,
|
||||
) {
|
||||
if (!this.isVisible || !this.userSettings.performanceOverlay()) return;
|
||||
|
||||
@@ -484,15 +474,9 @@ export class PerformanceOverlay extends LitElement implements Layer {
|
||||
if (backlogTurns !== undefined) {
|
||||
this.backlogTurns = backlogTurns;
|
||||
}
|
||||
if (inCatchUpMode !== undefined) {
|
||||
this.inCatchUpMode = inCatchUpMode;
|
||||
}
|
||||
if (renderEveryN !== undefined) {
|
||||
this.renderEveryN = renderEveryN;
|
||||
}
|
||||
if (beatsPerFrame !== undefined) {
|
||||
this.beatsPerFrame = beatsPerFrame ?? null;
|
||||
}
|
||||
|
||||
this.requestUpdate();
|
||||
}
|
||||
@@ -642,13 +626,10 @@ export class PerformanceOverlay extends LitElement implements Layer {
|
||||
<div class="performance-line">
|
||||
Backlog turns:
|
||||
<span>${this.backlogTurns}</span>
|
||||
${this.inCatchUpMode ? html`<span> (catch-up)</span>` : html``}
|
||||
</div>
|
||||
${this.inCatchUpMode
|
||||
${this.renderEveryN > 1
|
||||
? html`<div class="performance-line">
|
||||
Render every <span>${this.renderEveryN}</span> frame(s),
|
||||
heartbeats per frame:
|
||||
<span>${this.beatsPerFrame ?? "auto"}</span>
|
||||
Render every <span>${this.renderEveryN}</span> frame(s)
|
||||
</div>`
|
||||
: html``}
|
||||
${this.layerBreakdown.length
|
||||
|
||||
@@ -100,12 +100,6 @@ export class WorkerClient {
|
||||
});
|
||||
}
|
||||
|
||||
sendHeartbeat() {
|
||||
this.worker.postMessage({
|
||||
type: "heartbeat",
|
||||
});
|
||||
}
|
||||
|
||||
playerProfile(playerID: number): Promise<PlayerProfile> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.isInitialized) {
|
||||
|
||||
@@ -9,7 +9,6 @@ import { GameUpdateViewData } from "../game/GameUpdates";
|
||||
import { ClientID, GameStartInfo, Turn } from "../Schemas";
|
||||
|
||||
export type WorkerMessageType =
|
||||
| "heartbeat"
|
||||
| "init"
|
||||
| "initialized"
|
||||
| "turn"
|
||||
@@ -31,10 +30,6 @@ interface BaseWorkerMessage {
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export interface HeartbeatMessage extends BaseWorkerMessage {
|
||||
type: "heartbeat";
|
||||
}
|
||||
|
||||
// Messages from main thread to worker
|
||||
export interface InitMessage extends BaseWorkerMessage {
|
||||
type: "init";
|
||||
@@ -114,7 +109,6 @@ export interface TransportShipSpawnResultMessage extends BaseWorkerMessage {
|
||||
|
||||
// Union types for type safety
|
||||
export type MainThreadMessage =
|
||||
| HeartbeatMessage
|
||||
| InitMessage
|
||||
| TurnMessage
|
||||
| PlayerActionsMessage
|
||||
|
||||
Reference in New Issue
Block a user