feat: add live replays and PostgreSQL maintenance tools
Build and Push Docker Container / build-and-push (push) Successful in 7m53s
Build and Push Docker Container / build-and-push (push) Successful in 7m53s
- Stream compact live replay updates across local and clustered dashboards. - Render responsive snake bodies as SVG paths with aligned custom icons. - Add cache-busted assets, replay fallback routes, and live-follow playback. - Support PostgreSQL benchmark sampling and idempotent SQLite migration. - Add dry-run cleanup for old low-quality PostgreSQL replay payloads. - Reward safe perimeter lanes and bump Prism to version 1.5.0. - Add backend, migration, dashboard, and perimeter regression coverage.
This commit is contained in:
@@ -12,12 +12,22 @@ class DashboardQueryService:
|
||||
self.ws_hub = ws_hub
|
||||
self.logger = logger
|
||||
self.dashboard_running_game_stale_sec = dashboard_running_game_stale_sec
|
||||
self.publish_notice:Callable[[str], Awaitable[None]] | None = None
|
||||
self.publish_notice:Callable[[str, str|None], Awaitable[None]] | None = None
|
||||
|
||||
def set_publish_notice(self, publish_notice:Callable[[str], Awaitable[None]]) -> None:
|
||||
def set_publish_notice(
|
||||
self, publish_notice:Callable[[str, str|None], Awaitable[None]],
|
||||
) -> None:
|
||||
self.publish_notice = publish_notice
|
||||
|
||||
async def on_dashboard_games_update_notice(self, trigger:str) -> None:
|
||||
async def on_dashboard_games_update_notice(
|
||||
self, trigger:str, game_id:str|None=None,
|
||||
) -> None:
|
||||
if trigger == 'game_turn' and game_id:
|
||||
await self.push_dashboard_game_replay_update(
|
||||
game_id,
|
||||
publish_cluster=False,
|
||||
)
|
||||
return
|
||||
await self.push_dashboard_games_update(
|
||||
game_state=None,
|
||||
publish_cluster=False,
|
||||
@@ -56,6 +66,22 @@ class DashboardQueryService:
|
||||
'replay': replay_payload,
|
||||
}
|
||||
|
||||
async def build_dashboard_game_replay_update_event(self, game_id:str) -> dict:
|
||||
replay_payload = await self.get_dashboard_game_replay(game_id)
|
||||
if replay_payload is None:
|
||||
return {
|
||||
'type': 'dashboard_game_replay_update',
|
||||
'game_id': game_id,
|
||||
'error': 'game_not_found',
|
||||
}
|
||||
turns = replay_payload.get('turns', [])
|
||||
return {
|
||||
'type': 'dashboard_game_replay_update',
|
||||
'game_id': game_id,
|
||||
'game': replay_payload.get('game', {}),
|
||||
'turn': turns[-1] if turns else None,
|
||||
}
|
||||
|
||||
async def handle_dashboard_ws_request(self, payload_raw:object) -> dict|None:
|
||||
if not isinstance(payload_raw, str):
|
||||
return None
|
||||
@@ -95,7 +121,24 @@ class DashboardQueryService:
|
||||
)
|
||||
await self.ws_hub.broadcast_payload(event_payload)
|
||||
if publish_cluster and self.publish_notice is not None:
|
||||
await self.publish_notice(str(event_payload.get('trigger') or ''))
|
||||
game_id = None
|
||||
if game_state is not None:
|
||||
game_id = game_state.get('game', {}).get('id')
|
||||
await self.publish_notice(
|
||||
str(event_payload.get('trigger') or ''), game_id,
|
||||
)
|
||||
|
||||
async def push_dashboard_game_replay_update(
|
||||
self, game_id:str, publish_cluster:bool=True,
|
||||
) -> None:
|
||||
if self.gameplay_database is None:
|
||||
return
|
||||
event_payload = await self.build_dashboard_game_replay_update_event(game_id)
|
||||
if event_payload.get('error'):
|
||||
return
|
||||
await self.ws_hub.broadcast_payload(event_payload)
|
||||
if publish_cluster and self.publish_notice is not None:
|
||||
await self.publish_notice('game_turn', game_id)
|
||||
|
||||
async def get_dashboard_summary(self) -> dict:
|
||||
if self.gameplay_database is None:
|
||||
|
||||
Reference in New Issue
Block a user