diff --git a/apps/server/src/application/coordinators/disconnectCoordinator.ts b/apps/server/src/application/coordinators/disconnectCoordinator.ts index 71d0884..47df6fe 100644 --- a/apps/server/src/application/coordinators/disconnectCoordinator.ts +++ b/apps/server/src/application/coordinators/disconnectCoordinator.ts @@ -6,6 +6,7 @@ type GameOutputPort, } from "@server/domains/game/application/ports/gameUseCasePorts"; import type { + CleanupGameRuntimePort, DisconnectRoomPort, FindGameByPlayerPort, FindRoomByIdPort, @@ -18,7 +19,8 @@ /** 切断調停で利用する入力ポートと出力ポートの契約 */ export type DisconnectCoordinatorParams = { socketId: string; - roomManager: DisconnectRoomPort & FindRoomByPlayerPort & FindRoomByIdPort & FindGameByPlayerPort; + roomManager: DisconnectRoomPort & FindRoomByPlayerPort & FindRoomByIdPort; + runtimeRegistry: FindGameByPlayerPort & CleanupGameRuntimePort; gameOutput: Pick; roomOutput: Pick; }; @@ -27,11 +29,12 @@ export const disconnectCoordinator = ({ socketId, roomManager, + runtimeRegistry, gameOutput, roomOutput, }: DisconnectCoordinatorParams) => { const roomId = roomManager.getRoomByPlayerId(socketId)?.roomId; - const gameManager = roomManager.getGameManagerByPlayerId(socketId); + const gameManager = runtimeRegistry.getGameManagerByPlayerId(socketId); if (gameManager) { disconnectUseCase({ @@ -47,4 +50,6 @@ socketId, output: roomOutput, }); + + runtimeRegistry.cleanupDisposedRoomRuntimes(); }; diff --git a/apps/server/src/application/coordinators/readyForGameCoordinator.ts b/apps/server/src/application/coordinators/readyForGameCoordinator.ts index 8c1acc8..0f64720 100644 --- a/apps/server/src/application/coordinators/readyForGameCoordinator.ts +++ b/apps/server/src/application/coordinators/readyForGameCoordinator.ts @@ -10,7 +10,8 @@ type ReadyForGameCoordinatorParams = { socketId: string; - roomManager: FindRoomByPlayerPort & FindGameByPlayerPort; + roomManager: FindRoomByPlayerPort; + runtimeRegistry: FindGameByPlayerPort; output: Pick; }; @@ -18,10 +19,11 @@ export const readyForGameCoordinator = ({ socketId, roomManager, + runtimeRegistry, output, }: ReadyForGameCoordinatorParams) => { const room = roomManager.getRoomByPlayerId(socketId); - const gameManager = roomManager.getGameManagerByPlayerId(socketId); + const gameManager = runtimeRegistry.getGameManagerByPlayerId(socketId); readyForGameUseCase({ socketId, diff --git a/apps/server/src/application/coordinators/startGameCoordinator.ts b/apps/server/src/application/coordinators/startGameCoordinator.ts index 2bc6080..e5143d9 100644 --- a/apps/server/src/application/coordinators/startGameCoordinator.ts +++ b/apps/server/src/application/coordinators/startGameCoordinator.ts @@ -17,7 +17,8 @@ type StartGameCoordinatorParams = { ownerId: string; - roomManager: FindRoomByOwnerPort & RoomPhaseTransitionPort & FindGameByRoomPort; + roomManager: FindRoomByOwnerPort & RoomPhaseTransitionPort; + runtimeRegistry: FindGameByRoomPort; output: Pick< GameOutputPort, | "publishUpdatePlayersToSocket" @@ -32,6 +33,7 @@ export const startGameCoordinator = ({ ownerId, roomManager, + runtimeRegistry, output, }: StartGameCoordinatorParams) => { const room = roomManager.getRoomByOwnerId(ownerId); @@ -74,7 +76,7 @@ }); const playerIds = updatedRoom.players.map((player) => player.id); - const gameManager = roomManager.getGameManagerByRoomId(updatedRoom.roomId); + const gameManager = runtimeRegistry.getGameManagerByRoomId(updatedRoom.roomId); if (!gameManager) { return; } diff --git a/apps/server/src/domains/room/RoomManager.ts b/apps/server/src/domains/room/RoomManager.ts index ed8c4a6..47a86ed 100644 --- a/apps/server/src/domains/room/RoomManager.ts +++ b/apps/server/src/domains/room/RoomManager.ts @@ -4,7 +4,6 @@ */ import type { roomTypes } from "@repo/shared"; import { roomConsts } from "@repo/shared"; -import { GameManager } from "@server/domains/game/GameManager"; import { RoomJoinService } from "./application/services/RoomJoinService"; import { RoomExitService } from "./application/services/RoomExitService"; import { RoomQueryService } from "./application/services/RoomQueryService"; @@ -13,7 +12,6 @@ /** ルーム操作の公開インターフェースを提供するマネージャ */ export class RoomManager { private rooms: Map = new Map(); - private gameManagers: Map = new Map(); private roomJoinService: RoomJoinService; private roomExitService: RoomExitService; private roomQueryService: RoomQueryService; @@ -26,20 +24,12 @@ // ルームにプレイヤーを追加する,ルームが未作成なら新規作成する public addPlayerToRoom(roomId: string, socketId: string, playerName: string): JoinRoomResult { - const joinResult = this.roomJoinService.addPlayerToRoom(roomId, socketId, playerName); - - if (joinResult.status === "joined" && !this.gameManagers.has(roomId)) { - this.gameManagers.set(roomId, new GameManager(roomId)); - } - - return joinResult; + return this.roomJoinService.addPlayerToRoom(roomId, socketId, playerName); } // プレイヤーをルームから削除し,更新が発生したルーム配列を返す public removePlayer(socketId: string): roomTypes.Room[] { - const updatedRooms = this.roomExitService.removePlayer(socketId); - this.cleanupDisposedRooms(); - return updatedRooms; + return this.roomExitService.removePlayer(socketId); } // オーナーIDからルームを取得する @@ -57,19 +47,6 @@ return this.roomQueryService.getRoomByPlayerId(playerId); } - public getGameManagerByRoomId(roomId: string): GameManager | undefined { - return this.gameManagers.get(roomId); - } - - public getGameManagerByPlayerId(playerId: string): GameManager | undefined { - const roomId = this.roomQueryService.getRoomByPlayerId(playerId)?.roomId; - if (!roomId) { - return undefined; - } - - return this.gameManagers.get(roomId); - } - // ルーム状態をPLAYINGへ更新する public markRoomPlaying(roomId: string): roomTypes.Room | undefined { const room = this.roomQueryService.getRoomById(roomId); @@ -91,15 +68,4 @@ room.status = roomConsts.RoomPhase.WAITING; return room; } - - private cleanupDisposedRooms(): void { - for (const [roomId, gameManager] of this.gameManagers.entries()) { - if (this.rooms.has(roomId)) { - continue; - } - - gameManager.dispose(); - this.gameManagers.delete(roomId); - } - } } \ No newline at end of file diff --git a/apps/server/src/domains/room/application/ports/roomUseCasePorts.ts b/apps/server/src/domains/room/application/ports/roomUseCasePorts.ts index 7702349..e87f5b0 100644 --- a/apps/server/src/domains/room/application/ports/roomUseCasePorts.ts +++ b/apps/server/src/domains/room/application/ports/roomUseCasePorts.ts @@ -61,6 +61,16 @@ getRoomById(roomId: string): roomTypes.Room | undefined; } +/** ルーム参加後にゲームランタイムを確保する操作ポート */ +export interface EnsureGameRuntimePort { + ensureGameManagerForRoom(roomId: string): void; +} + +/** ルーム解散後に不要ランタイムを破棄する操作ポート */ +export interface CleanupGameRuntimePort { + cleanupDisposedRoomRuntimes(): void; +} + /** ルームIDでゲーム管理を解決する参照ポート */ export interface FindGameByRoomPort { getGameManagerByRoomId(roomId: string): RoomScopedGamePort | undefined; diff --git a/apps/server/src/domains/room/application/services/RoomGameRuntimeRegistry.ts b/apps/server/src/domains/room/application/services/RoomGameRuntimeRegistry.ts new file mode 100644 index 0000000..390887f --- /dev/null +++ b/apps/server/src/domains/room/application/services/RoomGameRuntimeRegistry.ts @@ -0,0 +1,53 @@ +/** + * RoomGameRuntimeRegistry + * ルーム単位のゲームランタイム生成,解決,破棄を管理する + */ +import { GameManager } from "@server/domains/game/GameManager"; +import type { + CleanupGameRuntimePort, + EnsureGameRuntimePort, + FindGameByPlayerPort, + FindGameByRoomPort, + FindRoomByIdPort, + FindRoomByPlayerPort, +} from "../ports/roomUseCasePorts"; + +/** ルームIDごとのゲームランタイムを管理するレジストリ */ +export class RoomGameRuntimeRegistry + implements EnsureGameRuntimePort, CleanupGameRuntimePort, FindGameByRoomPort, FindGameByPlayerPort { + private gameManagers: Map = new Map(); + + constructor(private roomResolver: FindRoomByPlayerPort & FindRoomByIdPort) {} + + public ensureGameManagerForRoom(roomId: string): void { + if (this.gameManagers.has(roomId)) { + return; + } + + this.gameManagers.set(roomId, new GameManager(roomId)); + } + + public getGameManagerByRoomId(roomId: string): GameManager | undefined { + return this.gameManagers.get(roomId); + } + + public getGameManagerByPlayerId(playerId: string): GameManager | undefined { + const roomId = this.roomResolver.getRoomByPlayerId(playerId)?.roomId; + if (!roomId) { + return undefined; + } + + return this.gameManagers.get(roomId); + } + + public cleanupDisposedRoomRuntimes(): void { + for (const [roomId, gameManager] of this.gameManagers.entries()) { + if (this.roomResolver.getRoomById(roomId)) { + continue; + } + + gameManager.dispose(); + this.gameManagers.delete(roomId); + } + } +} diff --git a/apps/server/src/network/SocketManager.ts b/apps/server/src/network/SocketManager.ts index a0c4ee0..2498bdf 100644 --- a/apps/server/src/network/SocketManager.ts +++ b/apps/server/src/network/SocketManager.ts @@ -6,6 +6,7 @@ import type { SocketConnectionManagerBundle, SocketConnectionRoomPort, + SocketConnectionRuntimePort, } from "./types/connectionPorts"; import { registerConnectionHandlers } from "./handlers/registerConnectionHandlers"; @@ -16,11 +17,13 @@ constructor( io: Server, - roomManager: SocketConnectionRoomPort + roomManager: SocketConnectionRoomPort, + runtimeRegistry: SocketConnectionRuntimePort ) { this.io = io; this.managers = { roomManager, + runtimeRegistry, }; } diff --git a/apps/server/src/network/bootstrap/boot.ts b/apps/server/src/network/bootstrap/boot.ts index 130d54d..1da296a 100644 --- a/apps/server/src/network/bootstrap/boot.ts +++ b/apps/server/src/network/bootstrap/boot.ts @@ -4,6 +4,7 @@ */ import type { Server as HttpServer } from "http"; import { RoomManager } from "@server/domains/room/RoomManager"; +import { RoomGameRuntimeRegistry } from "@server/domains/room/application/services/RoomGameRuntimeRegistry"; import { SocketManager } from "../SocketManager"; import { createIo } from "./createIo"; @@ -12,7 +13,8 @@ // ネットワーク層とドメイン層の依存を構築する const io = createIo(httpServer); const roomManager = new RoomManager(); - const socketManager = new SocketManager(io, roomManager); + const runtimeRegistry = new RoomGameRuntimeRegistry(roomManager); + const socketManager = new SocketManager(io, roomManager, runtimeRegistry); socketManager.initialize(); }; diff --git a/apps/server/src/network/handlers/game/registerGameHandlers.ts b/apps/server/src/network/handlers/game/registerGameHandlers.ts index 6d3db79..90f5996 100644 --- a/apps/server/src/network/handlers/game/registerGameHandlers.ts +++ b/apps/server/src/network/handlers/game/registerGameHandlers.ts @@ -33,7 +33,8 @@ export const registerGameHandlers = ( io: Server, socket: Socket, - roomManager: FindRoomByOwnerPort & FindRoomByPlayerPort & RoomPhaseTransitionPort & FindGameByRoomPort & FindGameByPlayerPort + roomManager: FindRoomByOwnerPort & FindRoomByPlayerPort & RoomPhaseTransitionPort, + runtimeRegistry: FindGameByRoomPort & FindGameByPlayerPort ) => { const common = createCommonHandlerContext(io, socket); const gameOutputAdapter = createGameOutputAdapter(common); @@ -68,6 +69,7 @@ startGameCoordinator({ ownerId: socket.id, roomManager, + runtimeRegistry, output: gameOutputAdapter, }); }); @@ -77,6 +79,7 @@ readyForGameCoordinator({ socketId: socket.id, roomManager, + runtimeRegistry, output: gameOutputAdapter, }); }); @@ -87,7 +90,7 @@ return; } - const gameManager = roomManager.getGameManagerByPlayerId(socket.id); + const gameManager = runtimeRegistry.getGameManagerByPlayerId(socket.id); if (!gameManager) { return; } @@ -106,7 +109,7 @@ } const roomId = roomManager.getRoomByPlayerId(socket.id)?.roomId; - const gameManager = roomManager.getGameManagerByPlayerId(socket.id); + const gameManager = runtimeRegistry.getGameManagerByPlayerId(socket.id); if (!roomId || !gameManager) { return; } diff --git a/apps/server/src/network/handlers/registerConnectionHandlers.ts b/apps/server/src/network/handlers/registerConnectionHandlers.ts index 35e602a..e4fccca 100644 --- a/apps/server/src/network/handlers/registerConnectionHandlers.ts +++ b/apps/server/src/network/handlers/registerConnectionHandlers.ts @@ -19,6 +19,7 @@ export const registerConnectionHandlers = ({ io, roomManager, + runtimeRegistry, }: RegisterConnectionHandlersParams) => { const gameDisconnectOutputAdapter = createGameDisconnectOutputAdapter(io); const roomDisconnectOutputAdapter = createRoomDisconnectOutputAdapter(io); @@ -31,8 +32,8 @@ socketId: socket.id, }); - registerRoomHandlers(io, socket, roomManager); - registerGameHandlers(io, socket, roomManager); + registerRoomHandlers(io, socket, roomManager, runtimeRegistry); + registerGameHandlers(io, socket, roomManager, runtimeRegistry); socket.on(protocol.SocketEvents.DISCONNECT, () => { // 切断ログ記録後にドメイン別の後処理を実行する @@ -45,6 +46,7 @@ disconnectCoordinator({ socketId: socket.id, roomManager, + runtimeRegistry, gameOutput: gameDisconnectOutputAdapter, roomOutput: roomDisconnectOutputAdapter, }); diff --git a/apps/server/src/network/handlers/room/registerRoomHandlers.ts b/apps/server/src/network/handlers/room/registerRoomHandlers.ts index 3f1e989..db1b83c 100644 --- a/apps/server/src/network/handlers/room/registerRoomHandlers.ts +++ b/apps/server/src/network/handlers/room/registerRoomHandlers.ts @@ -4,7 +4,10 @@ */ import { Server, Socket } from "socket.io"; import { protocol } from "@repo/shared"; -import type { JoinRoomPort } from "@server/domains/room/application/ports/roomUseCasePorts"; +import type { + EnsureGameRuntimePort, + JoinRoomPort, +} from "@server/domains/room/application/ports/roomUseCasePorts"; import { joinRoomUseCase } from "@server/domains/room/application/useCases/joinRoomUseCase"; import { logEvent } from "@server/logging/logger"; import { logResults, logScopes } from "@server/logging/index"; @@ -23,7 +26,8 @@ export const registerRoomHandlers = ( io: Server, socket: Socket, - roomManager: JoinRoomPort + roomManager: JoinRoomPort, + runtimeRegistry: EnsureGameRuntimePort ) => { const common = createCommonHandlerContext(io, socket); const roomOutputAdapter = createRoomOutputAdapter(common); @@ -70,6 +74,7 @@ return; case "joined": + runtimeRegistry.ensureGameManagerForRoom(roomId); await socket.join(roomId); roomOutputAdapter.publishRoomUpdateToRoom(roomId, joinResult.room); logEvent(logScopes.ROOM_USE_CASE, { diff --git a/apps/server/src/network/types/connectionPorts.ts b/apps/server/src/network/types/connectionPorts.ts index f5dce4c..2b1f4a2 100644 --- a/apps/server/src/network/types/connectionPorts.ts +++ b/apps/server/src/network/types/connectionPorts.ts @@ -4,7 +4,9 @@ */ import type { Server } from "socket.io"; import type { + CleanupGameRuntimePort, DisconnectRoomPort, + EnsureGameRuntimePort, FindGameByRoomPort, FindGameByPlayerPort, FindRoomByOwnerPort, @@ -19,7 +21,11 @@ & JoinRoomPort & FindRoomByOwnerPort & FindRoomByPlayerPort - & RoomPhaseTransitionPort + & RoomPhaseTransitionPort; + +/** 接続時のゲームランタイム解決で利用する入力ポート集合 */ +export type ConnectionRuntimePort = + & EnsureGameRuntimePort & FindGameByRoomPort & FindGameByPlayerPort; @@ -29,9 +35,15 @@ & DisconnectRoomPort & FindRoomByIdPort; +/** ソケット接続全体で利用するランタイム管理ポート集合 */ +export type SocketConnectionRuntimePort = + & ConnectionRuntimePort + & CleanupGameRuntimePort; + /** ソケット接続ハンドラで受け取るマネージャ依存の束 */ export type SocketConnectionManagerBundle = { roomManager: SocketConnectionRoomPort; + runtimeRegistry: SocketConnectionRuntimePort; }; /** 接続ハンドラ登録関数が受け取る入力パラメータ */