Animate individual PvB and PvP moves in lobby live viewer#731
Conversation
|
@copilot I don't quite understand why we need pvbMoveIndexes if we keep track of the latest index per game already in the update. |
Done in the latest commit. The same individual-move animation logic now applies to PvP:
|
Good point — there was no reason to split by type. Replaced |
There was a problem hiding this comment.
@copilot not your change but rename gameID into gameId
There was a problem hiding this comment.
Done — renamed gameID to gameId in the groupBy lambda (commit Rename gameID to gameId in GameDataService).
| let liveGames = 0; | ||
|
|
||
| this.#client.fetchLatestGamesUpdate(gameIdsToUpdate, (updates) => { | ||
| const moveIndexes = {}; |
There was a problem hiding this comment.
@copilot Line 136-139: can it be done in a single assign? can it be inlined with var creation?
There was a problem hiding this comment.
Done — collapsed to a single Object.fromEntries assignment inline with the const declaration.
|
Looks fairly correct overall. I'm wondering if we should move this to WS though. |
Done — moved the lobby live games update to a WebSocket session (commit Server:
Client:
|
| action = { refreshLiveGamesSessions() } | ||
| ) | ||
|
|
||
| fun cancel() { |
There was a problem hiding this comment.
@copilot Update the ShutdownHandler (or something) to handle this then
There was a problem hiding this comment.
Done — ShutdownHandler now injects LobbyService and calls lobbyService.cancel() during the shutdown sequence (commit Cancel LobbyService refresher job during shutdown).
| if (this.#totalRefresh % 60 === 0 && !mustSkipCompleteRefresh) { | ||
| this.#loadLatestPvpGames(); | ||
| this.#loadLatestPvbGames(); | ||
| this.#wsSession.subscribe(gameIds); |
There was a problem hiding this comment.
No. subscribe() reuses the single WebSocket opened once in the LiveGamesWebSocketSession constructor — it just stores the new game ids and sends an updated subscription message over the existing socket (#sendSubscription → socket.send(...)). The viewer creates only one LiveGamesWebSocketSession, so re-subscribing never opens a new connection.
| // information is fetched once per session instead of being batched across sessions | ||
| liveGamesSessions.forEach { session -> | ||
| val request = session.currentRequest() | ||
| if (request.gameIds.isNotEmpty()) { |
There was a problem hiding this comment.
@copilot collect all gameIds you need, min index and fetching all the missing moves, send the missing moves to sessions that should get them
There was a problem hiding this comment.
Done (commit Batch live games fetch across lobby sessions). refreshLiveGamesSessions now collects the union of all watched game ids and, per game, the lowest tracked move index across sessions, then does a single fetchLatestGamesUpdate for that batch. Each session receives the same batched response and slices out only the moves it still needs (drop(trackedIndex - batchMinIndex)), so the DB work is no longer duplicated per session.
| } | ||
|
|
||
| // remove the sessions that are not active anymore | ||
| liveGamesSessions.removeIf { session -> |
There was a problem hiding this comment.
Done (commit Remove closed live games sessions at start of refresh). The closed-session cleanup now runs at the very start of refreshLiveGamesSessions, before collecting game ids and fetching, so no work is done for sessions that are already gone.
|
@copilot I see timestamp getting updated in the lobby but I don't see the moves getting animated and also a lot of errors: |
... The error came from passing raw UCI strings to |
PvB and PvP thumbnails in the lobby live viewer always jumped 2 positions at once — a player move and bot/opponent response both arrive between updates, so
loadFenanimated both piece changes simultaneously.Instead of diffing FENs, the server now returns the individual new moves and the client animates them sequentially via
registerOpponentMove. The updates are delivered over a WebSocket session (managed inLobbyService) rather than HTTP polling.Server
LatestGamesUpdateRequest: addedmoveIndexes: Map<String, Int>(built server-side per session; shared across PvP and PvB)LatestGamesUpdateResponse.Entry: addedmoveIndex: Int?andnewMoves: List<String>PlayerVsPlayerGameDaoService.fetchCurrentStatusAndFen: now also selectsCURRENT_HALF_MOVE_INDEXPlayerVsPlayerGameDaoService: addedfetchNewMovesForGamesAndIndexesmirroring the PvB equivalentPlayerVsBotGameDaoService.fetchCurrentStatusAndFen: now also selectsCURRENT_HALF_MOVE_INDEXGameDataService.fetchLatestGamesUpdate: callsfetchNewMovesForGamesAndIndexesfor both PvP and PvB whenmoveIndexesis non-empty; populatesnewMoves/moveIndexin all entriesLiveGamesSubscription: new WebSocket input DTO — the client only declares which game ids it watches; the last move index per game is tracked server-sideLiveGamesBatchUpdate: batched update handed to every session, carrying the sharedLatestGamesUpdateResponseplus the per-game minimum move index used for the fetchLiveGamesWebSocketSession: tracks per-game move index and last status, and from the batched update slices out only the games it watches and the moves it still needs (drop(trackedIndex - batchMinIndex)); pushes only entries that changed (new moves or status change)LobbyService: now manages the live-games sessions with a 1s refresher job (mirroringPlayerVsBotGameService/PlayerVsPlayerGameService); each refresh collects the union of all watched game ids and the lowest tracked move index per game across sessions, then performs a singleGameDataService.fetchLatestGamesUpdatefor that batch instead of one fetch per sessionShutdownHandler: now injectsLobbyServiceand callslobbyService.cancel()during the shutdown sequence so the live-games refresher job is stopped gracefullyws/lobby/live-gamesWebSocket route (registered inApiServiceRoutingModule); removed the HTTPPOST /api/lobby/latest-games-updateendpointClient
GameThumb: trackscurrentMoveIndex; on refresh, enqueuesnewMovesand drains via#processMoveQueue()— each move animated withregisterOpponentMove(move, false, cb), 300 ms between moves; falls back toloadFenon the first update (before move index is known)LiveGamesWebSocketSession(js/ws/live-games.js): new client built onopenReconnectingWebSocket; connects for everyone (including guests), sends the watched game ids on open/change, and dispatches pushed updatesLiveGamesViewer: subscribes over the WebSocket and applies pushed updates; reloads the latest games every 60 s to discover new ones and re-subscribes (skipping when mostly-live to avoid interrupting animations); lobby mini boards created withplaySounds: falseto suppress click sounds during background animationsLobbyClient: removedfetchLatestGamesUpdate(replaced by the WebSocket session)