frameskip

This commit is contained in:
scamiv
2025-11-23 21:02:22 +01:00
parent f8ce8d71c0
commit ddbd2d7b40
3 changed files with 88 additions and 2 deletions
+64 -2
View File
@@ -216,11 +216,18 @@ export class ClientGameRunner {
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;
private readonly CATCH_UP_HEARTBEATS_PER_FRAME = 5; //upper bound on heartbeats per frame when in catch-up mode
private pendingUpdates: GameUpdateViewData[] = [];
private isProcessingUpdates = false;
// Adaptive rendering during catch-up: render at most once every N frames.
private renderEveryN: number = 1;
private renderSkipCounter: number = 0;
private lastFrameTime: number = 0;
private readonly MAX_RENDER_EVERY_N = 60;
private lastBeatsPerFrame: number = 1;
constructor(
private lobby: LobbyConfig,
private eventBus: EventBus,
@@ -313,13 +320,36 @@ export class ClientGameRunner {
const worker = this.worker;
const keepWorkerAlive = () => {
if (this.isActive) {
const now = performance.now();
let frameDuration = 0;
if (this.lastFrameTime !== 0) {
frameDuration = now - this.lastFrameTime;
}
this.lastFrameTime = now;
const beatsPerFrame = this.catchUpMode
? this.CATCH_UP_HEARTBEATS_PER_FRAME
: 1;
this.lastBeatsPerFrame = beatsPerFrame;
for (let i = 0; i < beatsPerFrame; i++) {
worker.sendHeartbeat();
}
this.processPendingUpdates();
// 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 (shouldRender) {
this.processPendingUpdates();
}
this.adaptRenderFrequency(frameDuration);
requestAnimationFrame(keepWorkerAlive);
}
};
@@ -510,13 +540,45 @@ export class ClientGameRunner {
this.currentTickDelay,
this.backlogTurns,
this.catchUpMode,
this.renderEveryN,
this.lastBeatsPerFrame,
),
);
// Reset tick delay for next measurement
this.currentTickDelay = undefined;
}
}
private adaptRenderFrequency(frameDuration: number) {
if (!this.catchUpMode) {
// Back to normal rendering.
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.
if (this.renderEveryN < this.MAX_RENDER_EVERY_N) {
this.renderEveryN++;
}
} else if (
this.backlogTurns < LOW_BACKLOG ||
(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--;
}
}
}
private mergeGameUpdates(
batch: GameUpdateViewData[],
): GameUpdateViewData | null {
+2
View File
@@ -133,6 +133,8 @@ export class TickMetricsEvent implements GameEvent {
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,
) {}
}
@@ -234,6 +234,8 @@ export class PerformanceOverlay extends LitElement implements Layer {
event.tickDelay,
event.backlogTurns,
event.inCatchUpMode,
event.renderEveryN,
event.beatsPerFrame,
);
});
}
@@ -429,11 +431,19 @@ export class PerformanceOverlay extends LitElement implements Layer {
@state()
private inCatchUpMode: boolean = false;
@state()
private renderEveryN: number = 1;
@state()
private beatsPerFrame: number = 1;
updateTickMetrics(
tickExecutionDuration?: number,
tickDelay?: number,
backlogTurns?: number,
inCatchUpMode?: boolean,
renderEveryN?: number,
beatsPerFrame?: number,
) {
if (!this.isVisible || !this.userSettings.performanceOverlay()) return;
@@ -477,6 +487,12 @@ export class PerformanceOverlay extends LitElement implements Layer {
if (inCatchUpMode !== undefined) {
this.inCatchUpMode = inCatchUpMode;
}
if (renderEveryN !== undefined) {
this.renderEveryN = renderEveryN;
}
if (beatsPerFrame !== undefined) {
this.beatsPerFrame = beatsPerFrame;
}
this.requestUpdate();
}
@@ -628,6 +644,12 @@ export class PerformanceOverlay extends LitElement implements Layer {
<span>${this.backlogTurns}</span>
${this.inCatchUpMode ? html`<span> (catch-up)</span>` : html``}
</div>
${this.inCatchUpMode
? html`<div class="performance-line">
Render every <span>${this.renderEveryN}</span> frame(s),
heartbeats per frame: <span>${this.beatsPerFrame}</span>
</div>`
: html``}
${this.layerBreakdown.length
? html`<div class="layers-section">
<div class="performance-line">