mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-08-16 13:14:56 +00:00
Worker now self-clocks; no heartbeats needed
GameRunner exposes pending work via a new hasPendingTurns() so the worker can check whether more ticks need to be processed. Worker auto-runs ticks: as soon as it initializes or receives a new turn, it calls processPendingTurns() and loops executeNextTick() while hasPendingTurns() is true. No more "heartbeat" message type; the worker no longer depends on the main thread’s RAF loop to advance the simulation. Client main thread simplified: Removed CATCH_UP_HEARTBEATS_PER_FRAME, the heartbeat loop, and the lastBeatsPerFrame tracking. keepWorkerAlive now just manages frame skipping + draining. When it decides to render (based on renderEveryN), it drains pendingUpdates, merges them, updates GameView, and runs renderer.tick(). Because rendering a batch always implies draining, we restored the invariant that every GameView.update is paired with a layer tick() (no more lost incremental updates). MAX_RENDER_EVERY_N is now 5 to keep the queue from growing too large while the worker sprints.
This commit is contained in:
@@ -272,4 +272,8 @@ export class GameRunner {
|
||||
}
|
||||
return player.bestTransportShipSpawn(targetTile);
|
||||
}
|
||||
|
||||
public hasPendingTurns(): boolean {
|
||||
return this.currTurn < this.turns.length;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
const ctx: Worker = self as any;
|
||||
let gameRunner: Promise<GameRunner> | null = null;
|
||||
const mapLoader = new FetchGameMapLoader(`/maps`, version);
|
||||
let isProcessingTurns = false;
|
||||
|
||||
function gameUpdate(gu: GameUpdateViewData | ErrorUpdate) {
|
||||
// skip if ErrorUpdate
|
||||
@@ -32,13 +33,33 @@ function sendMessage(message: WorkerMessage) {
|
||||
ctx.postMessage(message);
|
||||
}
|
||||
|
||||
async function processPendingTurns() {
|
||||
if (isProcessingTurns) {
|
||||
return;
|
||||
}
|
||||
if (!gameRunner) {
|
||||
return;
|
||||
}
|
||||
|
||||
const gr = await gameRunner;
|
||||
if (!gr || !gr.hasPendingTurns()) {
|
||||
return;
|
||||
}
|
||||
|
||||
isProcessingTurns = true;
|
||||
try {
|
||||
while (gr.hasPendingTurns()) {
|
||||
gr.executeNextTick();
|
||||
}
|
||||
} finally {
|
||||
isProcessingTurns = false;
|
||||
}
|
||||
}
|
||||
|
||||
ctx.addEventListener("message", async (e: MessageEvent<MainThreadMessage>) => {
|
||||
const message = e.data;
|
||||
|
||||
switch (message.type) {
|
||||
case "heartbeat":
|
||||
(await gameRunner)?.executeNextTick();
|
||||
break;
|
||||
case "init":
|
||||
try {
|
||||
gameRunner = createGameRunner(
|
||||
@@ -51,6 +72,7 @@ ctx.addEventListener("message", async (e: MessageEvent<MainThreadMessage>) => {
|
||||
type: "initialized",
|
||||
id: message.id,
|
||||
} as InitializedMessage);
|
||||
processPendingTurns();
|
||||
return gr;
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -67,6 +89,7 @@ ctx.addEventListener("message", async (e: MessageEvent<MainThreadMessage>) => {
|
||||
try {
|
||||
const gr = await gameRunner;
|
||||
await gr.addTurn(message.turn);
|
||||
processPendingTurns();
|
||||
} catch (error) {
|
||||
console.error("Failed to process turn:", error);
|
||||
throw error;
|
||||
|
||||
Reference in New Issue
Block a user