speed up loading and saving to redis that the move request are getting a answer when switching workers, strip data that neats to get recomuted every turn
Build and Push Docker Container / build-and-push (push) Successful in 4m49s

This commit is contained in:
2026-04-07 12:13:11 +02:00
parent f479541c04
commit f6e19e18e6
6 changed files with 58 additions and 7 deletions
+5
View File
@@ -151,6 +151,11 @@ class GameBoard:
return {"name": self.type, "is_ladder": self.is_ladder}
def __getstate__(self):
state = self.__dict__.copy()
state['turns'] = [] # strip turn history — grows linearly, not needed for move computation
return state
async def save(self, store_class, **kwargs):
store = store_class(**kwargs)
await store.save(self)
+22 -4
View File
@@ -1,5 +1,5 @@
from typing import TYPE_CHECKING, cast
import json, time, os
import asyncio, json, time, os
from quart import Blueprint, request, jsonify
@@ -61,9 +61,27 @@ def create_battlesnake_blueprint(server:'Server') -> Blueprint:
server.metrics_collector.record_http_request('move')
game_state = await request.get_json()
move_started = time.perf_counter()
game_board = cast(GameBoard, await server.game_runtime.get_game_board(game_state))
next_move = game_board.snake_neat_make_a_move()
await server.game_runtime.persist_game_board(game_state['game']['id'], game_board)
game_id = game_state['game']['id']
timeout_ms = int(game_state.get('game', {}).get('timeout', 500))
budget_sec = max(0.05, (timeout_ms - 50) / 1000.0)
next_move = None
move_completed = False
game_board = None
try:
async with asyncio.timeout(budget_sec):
game_board = cast(GameBoard, await server.game_runtime.get_game_board(game_state))
loop = asyncio.get_running_loop()
next_move = await loop.run_in_executor(None, game_board.snake_neat_make_a_move)
move_completed = True
except TimeoutError:
await await_log(server.logger.warning(f'MOVE TIMEOUT: turn={game_state.get("turn")}, game={game_id}, returning fallback {next_move!r}'))
if move_completed:
await server.game_runtime.persist_game_board(game_id, game_board)
await server.gameplay_tracking.record_gameplay_turn(game_state, next_move, game_board)
elapsed_ms = (time.perf_counter() - move_started) * 1000.0
await server.metrics_collector.record_move(next_move, elapsed_ms)
@@ -1,5 +1,5 @@
from typing import TYPE_CHECKING
import inspect, pickle
import inspect, pickle, zlib
if TYPE_CHECKING:
from server.GameBoard import GameBoard
@@ -28,7 +28,7 @@ class RedisGameBoardStore:
async def save(self, game_id:str, game_board:'GameBoard') -> None:
redis = await self._get_redis()
payload = pickle.dumps(game_board, protocol=pickle.HIGHEST_PROTOCOL)
payload = zlib.compress(pickle.dumps(game_board, protocol=pickle.HIGHEST_PROTOCOL), level=1)
await redis.set(self._key(game_id), payload, ex=self.ttl_seconds)
async def load(self, game_id:str):
@@ -36,7 +36,10 @@ class RedisGameBoardStore:
payload = await redis.get(self._key(game_id))
if payload is None:
return None
return pickle.loads(payload)
try:
return pickle.loads(zlib.decompress(payload))
except zlib.error:
return pickle.loads(payload)
async def delete(self, game_id:str) -> None:
redis = await self._get_redis()
+5
View File
@@ -79,6 +79,11 @@ class ApexBattleSnake(TemplateSnake):
# RL bootstrap dataset recorder
self.rl_bootstrap = RLBootstrapDataset()
def __getstate__(self):
state = super().__getstate__()
state['_game_phase'] = 0.0 # A3: per-turn scalar, recomputed in choose_move
return state
# ── Env helpers ──────────────────────────────────────────────────────────────
def _get_timeout_buffer_ms(self) -> int:
+9
View File
@@ -202,3 +202,12 @@ class TemplateSnake:
def set_target_food(self, target_food:dict):
self.target_food = target_food
return True
def __getstate__(self):
state = self.__dict__.copy()
state['history'] = [] # strip history — grows per turn, not needed for moves
state.pop('game_board', None) # re-set at top of every choose_move; circular ref
state.pop('calculations', None) # re-initialised at top of every choose_move
state.pop('eat_the_snake_overwrite', None) # re-initialised at top of every choose_move
state.pop('kill_the_snake', None) # per-call transient
return state
+11
View File
@@ -100,6 +100,17 @@ class UltimateBattleSnake(TemplateSnake):
# RL bootstrap dataset recorder
self.rl_bootstrap = RLBootstrapDataset()
def __getstate__(self):
state = super().__getstate__()
# strip per-turn precomputed state — all re-assigned at the top of choose_move
state['_enemy_dmaps'] = []
state['_enemy_heads'] = []
state['_base_blocked'] = set()
state['_is_snail'] = False
state['_bfs_cache'] = {}
state['_bfs_cache_turn'] = -1
return state
# ── Env helpers ──────────────────────────────────────────────────────────────
def _get_timeout_buffer_ms(self) -> int: