mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-08-07 19:21:30 +00:00
Merge branch 'catchUpMode' into self-clocked-worker-frame-skip-backlog
This commit is contained in:
@@ -213,17 +213,12 @@ export class ClientGameRunner {
|
|||||||
private lastProcessedTick: number = 0;
|
private lastProcessedTick: number = 0;
|
||||||
private backlogTurns: number = 0;
|
private backlogTurns: number = 0;
|
||||||
private backlogGrowing: boolean = false;
|
private backlogGrowing: boolean = false;
|
||||||
|
private lastRenderedTick: number = 0;
|
||||||
|
|
||||||
private pendingUpdates: GameUpdateViewData[] = [];
|
private pendingUpdates: GameUpdateViewData[] = [];
|
||||||
private pendingStart = 0;
|
private pendingStart = 0;
|
||||||
private isProcessingUpdates = false;
|
private isProcessingUpdates = false;
|
||||||
|
|
||||||
// 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;
|
|
||||||
private readonly MAX_RENDER_EVERY_N = 5;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private lobby: LobbyConfig,
|
private lobby: LobbyConfig,
|
||||||
private eventBus: EventBus,
|
private eventBus: EventBus,
|
||||||
@@ -309,39 +304,8 @@ export class ClientGameRunner {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.pendingUpdates.push(gu);
|
this.pendingUpdates.push(gu);
|
||||||
if (this.renderEveryN === 1) {
|
this.processPendingUpdates();
|
||||||
this.processPendingUpdates();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
const keepWorkerAlive = () => {
|
|
||||||
if (this.isActive) {
|
|
||||||
const now = performance.now();
|
|
||||||
let frameDuration = 0;
|
|
||||||
if (this.lastFrameTime !== 0) {
|
|
||||||
frameDuration = now - this.lastFrameTime;
|
|
||||||
}
|
|
||||||
this.lastFrameTime = now;
|
|
||||||
|
|
||||||
// Decide whether to render (and thus process pending updates) this frame.
|
|
||||||
let shouldRender = true;
|
|
||||||
if (
|
|
||||||
this.renderEveryN > 1 &&
|
|
||||||
this.renderSkipCounter < this.renderEveryN - 1
|
|
||||||
) {
|
|
||||||
shouldRender = false;
|
|
||||||
this.renderSkipCounter++;
|
|
||||||
} else if (this.renderEveryN > 1) {
|
|
||||||
this.renderSkipCounter = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldRender) {
|
|
||||||
this.processPendingUpdates();
|
|
||||||
}
|
|
||||||
this.adaptRenderFrequency(frameDuration);
|
|
||||||
requestAnimationFrame(keepWorkerAlive);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
requestAnimationFrame(keepWorkerAlive);
|
|
||||||
|
|
||||||
const onconnect = () => {
|
const onconnect = () => {
|
||||||
console.log("Connected to game server!");
|
console.log("Connected to game server!");
|
||||||
@@ -489,7 +453,7 @@ export class ClientGameRunner {
|
|||||||
const MAX_SLICE_BUDGET_MS = 2000; // allow longer slices when backlog is large
|
const MAX_SLICE_BUDGET_MS = 2000; // allow longer slices when backlog is large
|
||||||
const BACKLOG_FREE_TURNS = 10; // scaling starts at this many turns
|
const BACKLOG_FREE_TURNS = 10; // scaling starts at this many turns
|
||||||
const BACKLOG_MAX_TURNS = 500; // MAX_SLICE_BUDGET_MS is reached at this many turns
|
const BACKLOG_MAX_TURNS = 500; // MAX_SLICE_BUDGET_MS is reached at this many turns
|
||||||
const MAX_PER_SLICE = 1000;
|
const MAX_TICKS_PER_SLICE = 1000;
|
||||||
|
|
||||||
const backlogOverhead = Math.max(
|
const backlogOverhead = Math.max(
|
||||||
0,
|
0,
|
||||||
@@ -532,7 +496,7 @@ export class ClientGameRunner {
|
|||||||
lastTick = gu.tick;
|
lastTick = gu.tick;
|
||||||
|
|
||||||
const elapsed = performance.now() - frameStart;
|
const elapsed = performance.now() - frameStart;
|
||||||
if (processedCount >= MAX_PER_SLICE || elapsed >= sliceBudgetMs) {
|
if (processedCount >= MAX_TICKS_PER_SLICE || elapsed >= sliceBudgetMs) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -553,18 +517,27 @@ export class ClientGameRunner {
|
|||||||
this.gameView.update(combinedGu);
|
this.gameView.update(combinedGu);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.renderer.tick();
|
// Only emit metrics when ALL processing is complete
|
||||||
this.eventBus.emit(
|
if (this.pendingStart >= this.pendingUpdates.length) {
|
||||||
new TickMetricsEvent(
|
const ticksPerRender =
|
||||||
lastTickDuration,
|
this.lastRenderedTick === 0
|
||||||
this.currentTickDelay,
|
? lastTick
|
||||||
this.backlogTurns,
|
: lastTick - this.lastRenderedTick;
|
||||||
this.renderEveryN,
|
this.lastRenderedTick = lastTick;
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Reset tick delay for next measurement
|
this.renderer.tick();
|
||||||
this.currentTickDelay = undefined;
|
this.eventBus.emit(
|
||||||
|
new TickMetricsEvent(
|
||||||
|
lastTickDuration,
|
||||||
|
this.currentTickDelay,
|
||||||
|
this.backlogTurns,
|
||||||
|
ticksPerRender,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Reset tick delay for next measurement
|
||||||
|
this.currentTickDelay = undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.pendingStart < this.pendingUpdates.length) {
|
if (this.pendingStart < this.pendingUpdates.length) {
|
||||||
@@ -577,33 +550,6 @@ export class ClientGameRunner {
|
|||||||
requestAnimationFrame(processFrame);
|
requestAnimationFrame(processFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
private adaptRenderFrequency(frameDuration: number) {
|
|
||||||
// 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_FRAME_MS = 25;
|
|
||||||
const LOW_FRAME_MS = 18;
|
|
||||||
|
|
||||||
// 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.backlogGrowing &&
|
|
||||||
frameDuration > 0 &&
|
|
||||||
frameDuration < LOW_FRAME_MS
|
|
||||||
) {
|
|
||||||
if (this.renderEveryN > 1) {
|
|
||||||
this.renderEveryN--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private mergeGameUpdates(
|
private mergeGameUpdates(
|
||||||
batch: GameUpdateViewData[],
|
batch: GameUpdateViewData[],
|
||||||
): GameUpdateViewData | null {
|
): GameUpdateViewData | null {
|
||||||
|
|||||||
@@ -131,7 +131,8 @@ export class TickMetricsEvent implements GameEvent {
|
|||||||
public readonly tickDelay?: number,
|
public readonly tickDelay?: number,
|
||||||
// Number of turns the client is behind the server (if known)
|
// Number of turns the client is behind the server (if known)
|
||||||
public readonly backlogTurns?: number,
|
public readonly backlogTurns?: number,
|
||||||
public readonly renderEveryN?: number,
|
// Number of ticks applied since last render
|
||||||
|
public readonly ticksPerRender?: number,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ export class PerformanceOverlay extends LitElement implements Layer {
|
|||||||
event.tickExecutionDuration,
|
event.tickExecutionDuration,
|
||||||
event.tickDelay,
|
event.tickDelay,
|
||||||
event.backlogTurns,
|
event.backlogTurns,
|
||||||
event.renderEveryN,
|
event.ticksPerRender,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -424,16 +424,16 @@ export class PerformanceOverlay extends LitElement implements Layer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private renderEveryN: number = 1;
|
private backlogTurns: number = 0;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private backlogTurns: number = 0;
|
private ticksPerRender: number = 0;
|
||||||
|
|
||||||
updateTickMetrics(
|
updateTickMetrics(
|
||||||
tickExecutionDuration?: number,
|
tickExecutionDuration?: number,
|
||||||
tickDelay?: number,
|
tickDelay?: number,
|
||||||
backlogTurns?: number,
|
backlogTurns?: number,
|
||||||
renderEveryN?: number,
|
ticksPerRender?: number,
|
||||||
) {
|
) {
|
||||||
if (!this.isVisible || !this.userSettings.performanceOverlay()) return;
|
if (!this.isVisible || !this.userSettings.performanceOverlay()) return;
|
||||||
|
|
||||||
@@ -474,8 +474,9 @@ export class PerformanceOverlay extends LitElement implements Layer {
|
|||||||
if (backlogTurns !== undefined) {
|
if (backlogTurns !== undefined) {
|
||||||
this.backlogTurns = backlogTurns;
|
this.backlogTurns = backlogTurns;
|
||||||
}
|
}
|
||||||
if (renderEveryN !== undefined) {
|
|
||||||
this.renderEveryN = renderEveryN;
|
if (ticksPerRender !== undefined) {
|
||||||
|
this.ticksPerRender = ticksPerRender;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
@@ -623,15 +624,14 @@ export class PerformanceOverlay extends LitElement implements Layer {
|
|||||||
<span>${this.tickDelayAvg.toFixed(2)}ms</span>
|
<span>${this.tickDelayAvg.toFixed(2)}ms</span>
|
||||||
(max: <span>${this.tickDelayMax}ms</span>)
|
(max: <span>${this.tickDelayMax}ms</span>)
|
||||||
</div>
|
</div>
|
||||||
|
<div class="performance-line">
|
||||||
|
Ticks per render:
|
||||||
|
<span>${this.ticksPerRender}</span>
|
||||||
|
</div>
|
||||||
<div class="performance-line">
|
<div class="performance-line">
|
||||||
Backlog turns:
|
Backlog turns:
|
||||||
<span>${this.backlogTurns}</span>
|
<span>${this.backlogTurns}</span>
|
||||||
</div>
|
</div>
|
||||||
${this.renderEveryN > 1
|
|
||||||
? html`<div class="performance-line">
|
|
||||||
Render every <span>${this.renderEveryN}</span> frame(s)
|
|
||||||
</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