mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-08-17 05:34:37 +00:00
frameskip
This commit is contained in:
@@ -216,11 +216,18 @@ export class ClientGameRunner {
|
|||||||
private catchUpMode: boolean = false;
|
private catchUpMode: boolean = false;
|
||||||
private readonly CATCH_UP_ENTER_BACKLOG = 120; // turns behind to enter catch-up
|
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_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 pendingUpdates: GameUpdateViewData[] = [];
|
||||||
private isProcessingUpdates = false;
|
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(
|
constructor(
|
||||||
private lobby: LobbyConfig,
|
private lobby: LobbyConfig,
|
||||||
private eventBus: EventBus,
|
private eventBus: EventBus,
|
||||||
@@ -313,13 +320,36 @@ export class ClientGameRunner {
|
|||||||
const worker = this.worker;
|
const worker = this.worker;
|
||||||
const keepWorkerAlive = () => {
|
const keepWorkerAlive = () => {
|
||||||
if (this.isActive) {
|
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
|
const beatsPerFrame = this.catchUpMode
|
||||||
? this.CATCH_UP_HEARTBEATS_PER_FRAME
|
? this.CATCH_UP_HEARTBEATS_PER_FRAME
|
||||||
: 1;
|
: 1;
|
||||||
|
this.lastBeatsPerFrame = beatsPerFrame;
|
||||||
for (let i = 0; i < beatsPerFrame; i++) {
|
for (let i = 0; i < beatsPerFrame; i++) {
|
||||||
worker.sendHeartbeat();
|
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);
|
requestAnimationFrame(keepWorkerAlive);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -510,13 +540,45 @@ export class ClientGameRunner {
|
|||||||
this.currentTickDelay,
|
this.currentTickDelay,
|
||||||
this.backlogTurns,
|
this.backlogTurns,
|
||||||
this.catchUpMode,
|
this.catchUpMode,
|
||||||
|
this.renderEveryN,
|
||||||
|
this.lastBeatsPerFrame,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Reset tick delay for next measurement
|
// Reset tick delay for next measurement
|
||||||
this.currentTickDelay = undefined;
|
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(
|
private mergeGameUpdates(
|
||||||
batch: GameUpdateViewData[],
|
batch: GameUpdateViewData[],
|
||||||
): GameUpdateViewData | null {
|
): GameUpdateViewData | null {
|
||||||
|
|||||||
@@ -133,6 +133,8 @@ export class TickMetricsEvent implements GameEvent {
|
|||||||
public readonly backlogTurns?: number,
|
public readonly backlogTurns?: number,
|
||||||
// Whether the client is currently in catch-up mode
|
// Whether the client is currently in catch-up mode
|
||||||
public readonly inCatchUpMode?: boolean,
|
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.tickDelay,
|
||||||
event.backlogTurns,
|
event.backlogTurns,
|
||||||
event.inCatchUpMode,
|
event.inCatchUpMode,
|
||||||
|
event.renderEveryN,
|
||||||
|
event.beatsPerFrame,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -429,11 +431,19 @@ export class PerformanceOverlay extends LitElement implements Layer {
|
|||||||
@state()
|
@state()
|
||||||
private inCatchUpMode: boolean = false;
|
private inCatchUpMode: boolean = false;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private renderEveryN: number = 1;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private beatsPerFrame: number = 1;
|
||||||
|
|
||||||
updateTickMetrics(
|
updateTickMetrics(
|
||||||
tickExecutionDuration?: number,
|
tickExecutionDuration?: number,
|
||||||
tickDelay?: number,
|
tickDelay?: number,
|
||||||
backlogTurns?: number,
|
backlogTurns?: number,
|
||||||
inCatchUpMode?: boolean,
|
inCatchUpMode?: boolean,
|
||||||
|
renderEveryN?: number,
|
||||||
|
beatsPerFrame?: number,
|
||||||
) {
|
) {
|
||||||
if (!this.isVisible || !this.userSettings.performanceOverlay()) return;
|
if (!this.isVisible || !this.userSettings.performanceOverlay()) return;
|
||||||
|
|
||||||
@@ -477,6 +487,12 @@ export class PerformanceOverlay extends LitElement implements Layer {
|
|||||||
if (inCatchUpMode !== undefined) {
|
if (inCatchUpMode !== undefined) {
|
||||||
this.inCatchUpMode = inCatchUpMode;
|
this.inCatchUpMode = inCatchUpMode;
|
||||||
}
|
}
|
||||||
|
if (renderEveryN !== undefined) {
|
||||||
|
this.renderEveryN = renderEveryN;
|
||||||
|
}
|
||||||
|
if (beatsPerFrame !== undefined) {
|
||||||
|
this.beatsPerFrame = beatsPerFrame;
|
||||||
|
}
|
||||||
|
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
}
|
}
|
||||||
@@ -628,6 +644,12 @@ export class PerformanceOverlay extends LitElement implements Layer {
|
|||||||
<span>${this.backlogTurns}</span>
|
<span>${this.backlogTurns}</span>
|
||||||
${this.inCatchUpMode ? html`<span> (catch-up)</span>` : html``}
|
${this.inCatchUpMode ? html`<span> (catch-up)</span>` : html``}
|
||||||
</div>
|
</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
|
${this.layerBreakdown.length
|
||||||
? html`<div class="layers-section">
|
? html`<div class="layers-section">
|
||||||
<div class="performance-line">
|
<div class="performance-line">
|
||||||
|
|||||||
Reference in New Issue
Block a user