refactor(worker): remove heartbeat mechanism and implement pending turn processing

- Removed the heartbeat functionality from the ClientGameRunner and WorkerClient.
- Introduced a new processPendingTurns function in Worker.worker.ts to handle turn execution more efficiently.
- Added a hasPendingTurns method in GameRunner to check for unprocessed turns.
- Updated WorkerMessages to remove heartbeat message type.
This commit is contained in:
scamiv
2026-01-25 17:20:30 +01:00
parent 986d509a1c
commit 4112f97ca5
5 changed files with 31 additions and 36 deletions
-9
View File
@@ -381,15 +381,6 @@ export class ClientGameRunner {
}
});
const worker = this.worker;
const keepWorkerAlive = () => {
if (this.isActive) {
worker.sendHeartbeat();
requestAnimationFrame(keepWorkerAlive);
}
};
requestAnimationFrame(keepWorkerAlive);
const onconnect = () => {
console.log("Connected to game server!");
this.transport.rejoinGame(this.turnsSeen);
+5 -1
View File
@@ -145,7 +145,7 @@ export class GameRunner {
console.error("Game tick error:", error);
}
this.isExecuting = false;
return false;
return;
}
if (this.game.inSpawnPhase() && this.game.ticks() % 2 === 0) {
@@ -268,4 +268,8 @@ export class GameRunner {
}
return player.bestTransportShipSpawn(targetTile);
}
public hasPendingTurns(): boolean {
return this.currTurn < this.turns.length;
}
}
+26 -14
View File
@@ -16,7 +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;
let isProcessingTurns = false;
function gameUpdate(gu: GameUpdateViewData | ErrorUpdate) {
// skip if ErrorUpdate
@@ -33,23 +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": {
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(
@@ -62,6 +72,7 @@ ctx.addEventListener("message", async (e: MessageEvent<MainThreadMessage>) => {
type: "initialized",
id: message.id,
} as InitializedMessage);
processPendingTurns();
return gr;
});
} catch (error) {
@@ -78,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;
-6
View File
@@ -102,12 +102,6 @@ export class WorkerClient {
});
}
sendHeartbeat() {
this.worker.postMessage({
type: "heartbeat",
});
}
playerProfile(playerID: number): Promise<PlayerProfile> {
return new Promise((resolve, reject) => {
if (!this.isInitialized) {
-6
View File
@@ -9,7 +9,6 @@ import { GameUpdateViewData } from "../game/GameUpdates";
import { ClientID, GameStartInfo, Turn } from "../Schemas";
export type WorkerMessageType =
| "heartbeat"
| "init"
| "initialized"
| "turn"
@@ -31,10 +30,6 @@ interface BaseWorkerMessage {
id?: string;
}
export interface HeartbeatMessage extends BaseWorkerMessage {
type: "heartbeat";
}
// Messages from main thread to worker
export interface InitMessage extends BaseWorkerMessage {
type: "init";
@@ -114,7 +109,6 @@ export interface TransportShipSpawnResultMessage extends BaseWorkerMessage {
// Union types for type safety
export type MainThreadMessage =
| HeartbeatMessage
| InitMessage
| TurnMessage
| PlayerActionsMessage