// Lobby preload: warm the per-game cache for the player's ongoing games ahead of time, so // opening one from the lobby renders instantly — no "loading" flash and no rack→board jump // (the saved draft is fetched and cached alongside state and history). It is best-effort and // bounded; finished games are skipped (rarely reopened, no draft), as are games already cached // (the live stream keeps those fresh) or currently being warmed. import { gateway } from './gateway'; import { getCachedGame, setCachedGame } from './gamecache'; import { hasAlphabet } from './alphabet'; import type { GameView } from './model'; // Cap on games warmed at once: enough to cover a typical lobby quickly without a request burst. const MAX_CONCURRENT = 4; // Games being warmed right now, so overlapping calls (the lobby refreshes on every live event) // never fetch the same game twice. const inflight = new Set(); /** * preloadGames warms the cache for the given ongoing games: it fetches each game's state, * history and saved draft in parallel and stores them, skipping finished games and any already * cached or in flight. Concurrency is bounded and failures are swallowed (the game just * cold-loads on open). It resolves once every queued game has settled. */ export async function preloadGames(games: GameView[]): Promise { const queue = games.filter( (g) => g.status !== 'finished' && !getCachedGame(g.id) && !inflight.has(g.id), ); if (queue.length === 0) return; for (const g of queue) inflight.add(g.id); let next = 0; const worker = async (): Promise => { while (next < queue.length) { const g = queue[next++]; try { const [st, hist, draft] = await Promise.all([ gateway.gameState(g.id, !hasAlphabet(g.variant)), gateway.gameHistory(g.id), gateway.draftGet(g.id).catch(() => ''), ]); // A live event may have seeded or advanced this entry while we fetched; don't clobber it. if (!getCachedGame(g.id)) setCachedGame(g.id, st, hist.moves, draft); } catch { /* best-effort: leave the game to cold-load on open */ } finally { inflight.delete(g.id); } } }; await Promise.all(Array.from({ length: Math.min(MAX_CONCURRENT, queue.length) }, worker)); }