mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 12:20:46 +00:00
lobby counts
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
|
||||
import { PublicGames, PublicGamesSchema } from "../core/Schemas";
|
||||
import {
|
||||
LobbyCountsSchema,
|
||||
PublicGames,
|
||||
PublicGamesSchema,
|
||||
} from "../core/Schemas";
|
||||
|
||||
interface LobbySocketOptions {
|
||||
reconnectDelay?: number;
|
||||
@@ -19,6 +23,7 @@ export class PublicLobbySocket {
|
||||
private wsAttemptCounted = false;
|
||||
private workerPath: string = "";
|
||||
private stopped = true;
|
||||
private lastGames: PublicGames | null = null;
|
||||
|
||||
private readonly reconnectDelay: number;
|
||||
private readonly maxWsAttempts: number;
|
||||
@@ -79,10 +84,30 @@ export class PublicLobbySocket {
|
||||
|
||||
private handleMessage(event: MessageEvent) {
|
||||
try {
|
||||
const publicGames = PublicGamesSchema.parse(
|
||||
JSON.parse(event.data as string),
|
||||
);
|
||||
this.onLobbiesUpdate(publicGames);
|
||||
const raw = JSON.parse(event.data as string);
|
||||
|
||||
// Slim update — just counts/timers, merge into cached full state.
|
||||
const counts = LobbyCountsSchema.safeParse(raw);
|
||||
if (counts.success && this.lastGames) {
|
||||
const games = {} as PublicGames["games"];
|
||||
for (const [type, lobbies] of Object.entries(this.lastGames.games) as [
|
||||
keyof PublicGames["games"],
|
||||
PublicGames["games"][keyof PublicGames["games"]],
|
||||
][]) {
|
||||
games[type] = lobbies.map((lobby) => {
|
||||
const update = counts.data.counts[lobby.gameID];
|
||||
return update ? { ...lobby, ...update } : lobby;
|
||||
}) as PublicGames["games"][typeof type];
|
||||
}
|
||||
this.onLobbiesUpdate({ serverTime: counts.data.serverTime, games });
|
||||
this.lastGames = { serverTime: counts.data.serverTime, games };
|
||||
return;
|
||||
}
|
||||
|
||||
// Full update — store and forward.
|
||||
const incoming = PublicGamesSchema.parse(raw);
|
||||
this.lastGames = incoming;
|
||||
this.onLobbiesUpdate(incoming);
|
||||
} catch (error) {
|
||||
console.error("Error parsing WebSocket message:", error);
|
||||
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||
|
||||
@@ -169,6 +169,16 @@ export const PublicGamesSchema = z.object({
|
||||
games: z.record(PublicGameTypeSchema, z.array(PublicGameInfoSchema)),
|
||||
});
|
||||
|
||||
// Slim update sent when game configs haven't changed — just player counts + timers.
|
||||
export const LobbyCountsSchema = z.object({
|
||||
serverTime: z.number(),
|
||||
counts: z.record(
|
||||
z.string(), // gameID
|
||||
z.object({ numClients: z.number(), startsAt: z.number().optional() }),
|
||||
),
|
||||
});
|
||||
export type LobbyCounts = z.infer<typeof LobbyCountsSchema>;
|
||||
|
||||
export class LobbyInfoEvent implements GameEvent {
|
||||
constructor(
|
||||
public lobby: GameInfo,
|
||||
|
||||
@@ -75,7 +75,7 @@ export class MasterLobbyService {
|
||||
if (this.readyWorkers.size === this.config.numWorkers() && !this.started) {
|
||||
this.started = true;
|
||||
this.log.info("All workers ready, starting game scheduling");
|
||||
startPolling(async () => this.broadcastLobbies(), 500);
|
||||
startPolling(async () => this.broadcastLobbies(), 250);
|
||||
startPolling(async () => await this.maybeScheduleLobby(), 1000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import http from "http";
|
||||
import { WebSocket, WebSocketServer } from "ws";
|
||||
import { PublicGameInfo, PublicGames } from "../core/Schemas";
|
||||
import { LobbyCounts, PublicGameInfo, PublicGames } from "../core/Schemas";
|
||||
import { GameManager } from "./GameManager";
|
||||
import {
|
||||
MasterMessageSchema,
|
||||
@@ -12,6 +12,8 @@ import { logger } from "./Logger";
|
||||
export class WorkerLobbyService {
|
||||
private readonly lobbiesWss: WebSocketServer;
|
||||
private readonly lobbyClients: Set<WebSocket> = new Set();
|
||||
private lastPublicGames: PublicGames | null = null;
|
||||
private lastGameSetKey: string = "";
|
||||
|
||||
constructor(
|
||||
private readonly server: http.Server,
|
||||
@@ -112,6 +114,10 @@ export class WorkerLobbyService {
|
||||
private setupLobbiesWebSocket() {
|
||||
this.lobbiesWss.on("connection", (ws: WebSocket) => {
|
||||
this.lobbyClients.add(ws);
|
||||
// Send the last known full state immediately on connect.
|
||||
if (this.lastPublicGames && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify(this.lastPublicGames));
|
||||
}
|
||||
ws.on("message", () => {
|
||||
ws.terminate();
|
||||
});
|
||||
@@ -136,8 +142,39 @@ export class WorkerLobbyService {
|
||||
});
|
||||
}
|
||||
|
||||
private buildCountsMessage(publicGames: PublicGames): string {
|
||||
const counts: LobbyCounts["counts"] = {};
|
||||
for (const lobbies of Object.values(publicGames.games)) {
|
||||
for (const lobby of lobbies) {
|
||||
counts[lobby.gameID] = {
|
||||
numClients: lobby.numClients,
|
||||
startsAt: lobby.startsAt,
|
||||
};
|
||||
}
|
||||
}
|
||||
return JSON.stringify({
|
||||
serverTime: publicGames.serverTime,
|
||||
counts,
|
||||
} satisfies LobbyCounts);
|
||||
}
|
||||
|
||||
private broadcastLobbiesToClients(publicGames: PublicGames) {
|
||||
const message = JSON.stringify(publicGames);
|
||||
const allLobbies = Object.values(publicGames.games).flat();
|
||||
const gameSetKey = allLobbies
|
||||
.map((g) => g.gameID)
|
||||
.sort()
|
||||
.join(",");
|
||||
|
||||
const configsChanged = gameSetKey !== this.lastGameSetKey;
|
||||
this.lastGameSetKey = gameSetKey;
|
||||
|
||||
if (configsChanged) {
|
||||
this.lastPublicGames = publicGames;
|
||||
}
|
||||
|
||||
const message = configsChanged
|
||||
? JSON.stringify(publicGames)
|
||||
: this.buildCountsMessage(publicGames);
|
||||
|
||||
const clientsToRemove: WebSocket[] = [];
|
||||
this.lobbyClients.forEach((client) => {
|
||||
|
||||
Reference in New Issue
Block a user