fix: guard shard event emissions on both manuallyStopped and gameFinished

Prevent stale events from shards that ended naturally (not via
stopMonitor). handleMessage now gates on gameFinished in addition to
manuallyStopped. handleEntityUpdate properly cleans up on gameFinished
by emitting room.disconnected, removing from activeShards, and calling
disconnect. handleError also removes from activeShards. Probe message
handler and status broadcast bail out when the shard is stopped or the
game has finished.

Made-with: Cursor
This commit is contained in:
cottongin
2026-03-21 00:07:10 -04:00
parent 171303a6f9
commit c756d45e24

View File

@@ -114,7 +114,9 @@ class EcastShardClient {
this.stopStatusBroadcast(); this.stopStatusBroadcast();
this.statusInterval = setInterval(() => { this.statusInterval = setInterval(() => {
this._refreshPlayerCount().finally(() => { this._refreshPlayerCount().finally(() => {
this.onEvent('game.status', this.getSnapshot()); if (!this.manuallyStopped && !this.gameFinished) {
this.onEvent('game.status', this.getSnapshot());
}
}); });
}, 20000); }, 20000);
} }
@@ -146,7 +148,7 @@ class EcastShardClient {
const timeout = setTimeout(() => done(probe), 10000); const timeout = setTimeout(() => done(probe), 10000);
probe.on('message', (data) => { probe.on('message', (data) => {
if (welcomed) return; if (welcomed || this.manuallyStopped) { clearTimeout(timeout); done(probe); return; }
try { try {
const msg = JSON.parse(data.toString()); const msg = JSON.parse(data.toString());
if (msg.opcode === 'client/welcome') { if (msg.opcode === 'client/welcome') {
@@ -155,15 +157,17 @@ class EcastShardClient {
if (playerCount > this.playerCount || playerNames.length !== this.playerNames.length) { if (playerCount > this.playerCount || playerNames.length !== this.playerNames.length) {
this.playerCount = playerCount; this.playerCount = playerCount;
this.playerNames = playerNames; this.playerNames = playerNames;
this.onEvent('lobby.player-joined', { if (!this.manuallyStopped) {
sessionId: this.sessionId, this.onEvent('lobby.player-joined', {
gameId: this.gameId, sessionId: this.sessionId,
roomCode: this.roomCode, gameId: this.gameId,
playerName: playerNames[playerNames.length - 1] || '', roomCode: this.roomCode,
playerCount, playerName: playerNames[playerNames.length - 1] || '',
players: [...playerNames], playerCount,
maxPlayers: this.maxPlayers, players: [...playerNames],
}); maxPlayers: this.maxPlayers,
});
}
} else if (playerCount !== this.playerCount) { } else if (playerCount !== this.playerCount) {
this.playerCount = playerCount; this.playerCount = playerCount;
this.playerNames = playerNames; this.playerNames = playerNames;
@@ -196,6 +200,7 @@ class EcastShardClient {
} }
handleMessage(message) { handleMessage(message) {
if (this.manuallyStopped || this.gameFinished) return;
switch (message.opcode) { switch (message.opcode) {
case 'client/welcome': case 'client/welcome':
this.handleWelcome(message.result); this.handleWelcome(message.result);
@@ -301,6 +306,15 @@ class EcastShardClient {
playerCount: this.playerCount, playerCount: this.playerCount,
players: [...this.playerNames], players: [...this.playerNames],
}); });
this.onEvent('room.disconnected', {
sessionId: this.sessionId,
gameId: this.gameId,
roomCode: this.roomCode,
reason: 'room_closed',
finalPlayerCount: this.playerCount,
});
activeShards.delete(`${this.sessionId}-${this.gameId}`);
this.disconnect();
} }
} }
} }
@@ -367,6 +381,7 @@ class EcastShardClient {
reason: 'room_closed', reason: 'room_closed',
finalPlayerCount: this.playerCount, finalPlayerCount: this.playerCount,
}); });
activeShards.delete(`${this.sessionId}-${this.gameId}`);
this.disconnect(); this.disconnect();
} }
} }