Build and Push Docker Container / build-and-push (push) Successful in 3m52s
- Add compact storage package: codec, records, hydrate, replay blobs, cache. - Running games write game_turns and snake_states keyed by integer ids. - Encode moves as 1-byte codes and bodies as head plus step codes. - Store food and hazards only when they changed since the previous turn. - Strip reasoning fields that duplicate stored values and restore on read. - Pack finished games into one zstd blob per game in game_replays. - Merge late turns into packed blobs during the stale-game sweep. - Extract SQLite and PostgreSQL schemas into dedicated modules. - Add read/import game bundle and list_game_ids to the backend template. - Add legacy readers and converter for the old turns/snake_turns layout. - Add migrate_gameplay_to_compact.py with in-place or cross-DB migration. - Verify every migrated game and keep legacy rows on failure. - Support --since, --minimum-quality and guarded --drop-legacy. - Add just recipe migrate-gameplay-postgres. - Rewrite merge script to copy game by game via backend bundles. - Move benchmark state loading into benchmark_states for both backends. - Update PostgreSQL cleanup script to the new replay tables. - Drop sqlite-zstd build stages from the Dockerfile. - Remove old migration scripts, normalized_turn and their tests. - Add tests for compact storage, schema, legacy migration and PostgreSQL. - Document the compact layout and migration workflow in the README.
58 lines
2.1 KiB
Python
58 lines
2.1 KiB
Python
"""Load sampled gameplay positions from a SQLite or PostgreSQL gameplay DB."""
|
|
import asyncio
|
|
from urllib.parse import urlparse
|
|
|
|
def is_postgresql_source(source:str) -> bool:
|
|
return urlparse(source).scheme.lower() in {"postgres", "postgresql"}
|
|
|
|
def replay_turn_to_state(game:dict, turn:dict) -> tuple[dict, dict]|None:
|
|
board = turn.get("board") or {}
|
|
you = turn.get("you") or {}
|
|
if not you or not board.get("snakes"):
|
|
return None
|
|
return board, {
|
|
"you": you,
|
|
"game_id": game["game_id"],
|
|
"source": game.get("source") or "custom",
|
|
"map": game.get("map") or "standard",
|
|
"ruleset": {
|
|
"name": game.get("ruleset_name") or "standard",
|
|
"version": game.get("ruleset_version") or "v1.0.0",
|
|
"settings": {},
|
|
},
|
|
"turn": int(turn["turn"]),
|
|
}
|
|
|
|
async def sample_states(backend, samples:int, stride:int) -> list[tuple[dict, dict]]:
|
|
"""Every ``stride``-th stored turn, newest games first."""
|
|
states = []
|
|
seen = 0
|
|
for game_id in reversed(await backend.list_game_ids()):
|
|
replay = await backend.get_game_replay(game_id)
|
|
if replay is None:
|
|
continue
|
|
for turn in reversed(replay["turns"]):
|
|
if seen % stride == 0:
|
|
state = replay_turn_to_state(replay["game"], turn)
|
|
if state is not None:
|
|
states.append(state)
|
|
if len(states) >= samples:
|
|
return states
|
|
seen += 1
|
|
return states
|
|
|
|
async def _load_states(source:str, samples:int, stride:int) -> list[tuple[dict, dict]]:
|
|
if is_postgresql_source(source):
|
|
from server.database.backend.PostgresqlGameplayBackend import PostgresqlGameplayBackend # noqa: PLC0415
|
|
backend = PostgresqlGameplayBackend(source)
|
|
else:
|
|
from server.database.backend.SqliteGameplayBackend import SqliteGameplayBackend # noqa: PLC0415
|
|
backend = SqliteGameplayBackend(source, read_only=True)
|
|
try:
|
|
return await sample_states(backend, samples, stride)
|
|
finally:
|
|
await backend.close()
|
|
|
|
def load_states(source:str, samples:int, stride:int) -> list[tuple[dict, dict]]:
|
|
return asyncio.run(_load_states(source, max(1, samples), max(1, stride)))
|