mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-24 22:04:39 +00:00
Spectate catchup (#3012)
## Description: https://github.com/user-attachments/assets/dc118d5f-3b7f-4ccb-8579-5b0d8c73fe8e Catchup mechanic for live games and changes replays to have a backlog for more "max" speed ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: w.o.n
This commit is contained in:
@@ -20,7 +20,13 @@ import {
|
||||
import { getPersistentID } from "./Auth";
|
||||
import { LobbyConfig } from "./ClientGameRunner";
|
||||
import { ReplaySpeedChangeEvent } from "./InputHandler";
|
||||
import { defaultReplaySpeedMultiplier } from "./utilities/ReplaySpeedMultiplier";
|
||||
import {
|
||||
defaultReplaySpeedMultiplier,
|
||||
ReplaySpeedMultiplier,
|
||||
} from "./utilities/ReplaySpeedMultiplier";
|
||||
|
||||
// build a small backlog so MAX can catch up.
|
||||
const MAX_REPLAY_BACKLOG_TURNS = 60;
|
||||
|
||||
export class LocalServer {
|
||||
// All turns from the game record on replay.
|
||||
@@ -64,9 +70,16 @@ export class LocalServer {
|
||||
const turnIntervalMs =
|
||||
this.lobbyConfig.serverConfig.turnIntervalMs() *
|
||||
this.replaySpeedMultiplier;
|
||||
const backlog = Math.max(0, this.turns.length - this.turnsExecuted);
|
||||
const allowReplayBacklog =
|
||||
this.replaySpeedMultiplier === ReplaySpeedMultiplier.fastest &&
|
||||
this.lobbyConfig.gameRecord !== undefined;
|
||||
const maxBacklog = allowReplayBacklog ? MAX_REPLAY_BACKLOG_TURNS : 0;
|
||||
|
||||
const canQueueNextTurn =
|
||||
backlog === 0 || (maxBacklog > 0 && backlog < maxBacklog);
|
||||
if (
|
||||
this.turnsExecuted === this.turns.length &&
|
||||
canQueueNextTurn &&
|
||||
Date.now() > this.turnStartTime + turnIntervalMs
|
||||
) {
|
||||
this.turnStartTime = Date.now();
|
||||
|
||||
+10
-4
@@ -112,12 +112,12 @@ export class GameRunner {
|
||||
this.turns.push(turn);
|
||||
}
|
||||
|
||||
public executeNextTick() {
|
||||
public executeNextTick(): boolean {
|
||||
if (this.isExecuting) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (this.currTurn >= this.turns.length) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
this.isExecuting = true;
|
||||
|
||||
@@ -144,7 +144,8 @@ export class GameRunner {
|
||||
} else {
|
||||
console.error("Game tick error:", error);
|
||||
}
|
||||
return;
|
||||
this.isExecuting = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.game.inSpawnPhase() && this.game.ticks() % 2 === 0) {
|
||||
@@ -177,6 +178,11 @@ export class GameRunner {
|
||||
tickExecutionDuration: tickExecutionDuration,
|
||||
});
|
||||
this.isExecuting = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public pendingTurns(): number {
|
||||
return Math.max(0, this.turns.length - this.currTurn);
|
||||
}
|
||||
|
||||
public playerActions(
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
const ctx: Worker = self as any;
|
||||
let gameRunner: Promise<GameRunner> | null = null;
|
||||
const mapLoader = new FetchGameMapLoader(`/maps`, version);
|
||||
const MAX_TICKS_PER_HEARTBEAT = 4;
|
||||
|
||||
function gameUpdate(gu: GameUpdateViewData | ErrorUpdate) {
|
||||
// skip if ErrorUpdate
|
||||
@@ -36,9 +37,19 @@ ctx.addEventListener("message", async (e: MessageEvent<MainThreadMessage>) => {
|
||||
const message = e.data;
|
||||
|
||||
switch (message.type) {
|
||||
case "heartbeat":
|
||||
(await gameRunner)?.executeNextTick();
|
||||
case "heartbeat": {
|
||||
const gr = await gameRunner;
|
||||
if (!gr) {
|
||||
break;
|
||||
}
|
||||
const ticksToRun = Math.min(gr.pendingTurns(), MAX_TICKS_PER_HEARTBEAT);
|
||||
for (let i = 0; i < ticksToRun; i++) {
|
||||
if (!gr.executeNextTick()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "init":
|
||||
try {
|
||||
gameRunner = createGameRunner(
|
||||
|
||||
Reference in New Issue
Block a user