104 lines
3.8 KiB
Python
104 lines
3.8 KiB
Python
from typing import cast
|
|
import time
|
|
|
|
from server.metrics import MetricsCollector
|
|
from server.GameBoard import GameBoard
|
|
|
|
from server.storage import StorageLoader
|
|
from snakes import SnakeBuilder
|
|
|
|
class GameRuntimeService:
|
|
def __init__(self, game_state_store:StorageLoader, snake_type:str, game_state_local_cache:bool, stale_game_timeout_sec:int):
|
|
self.game_state_store = game_state_store
|
|
self.snake_type = snake_type
|
|
self.game_state_local_cache = game_state_local_cache
|
|
self.stale_game_timeout_sec = stale_game_timeout_sec
|
|
self.metrics_collector = None
|
|
|
|
self.running_games: dict[str, GameBoard] = {}
|
|
self.game_move_counts: dict[str, int] = {}
|
|
self.game_last_seen_unix: dict[str, int] = {}
|
|
|
|
def attach_metrics_collector(self, metrics_collector:MetricsCollector) -> None:
|
|
self.metrics_collector = metrics_collector
|
|
|
|
async def create_game_board(self, game_state:dict, snake_builder:SnakeBuilder) -> GameBoard:
|
|
game_id = game_state['game']['id']
|
|
new_game_board = GameBoard(
|
|
game_id=game_id,
|
|
width=game_state['board']['width'],
|
|
height=game_state['board']['height'],
|
|
ruleset=game_state['game']['ruleset'],
|
|
source=game_state['game']['source'],
|
|
map=game_state['game']['map'],
|
|
snake_class=snake_builder.build(self.snake_type),
|
|
)
|
|
await new_game_board.start_game(game_state)
|
|
|
|
if self.game_state_local_cache:
|
|
self.running_games[game_id] = new_game_board
|
|
|
|
await self.game_state_store.save(game_id, new_game_board)
|
|
self.game_move_counts[game_id] = 0
|
|
self.game_last_seen_unix[game_id] = int(time.time())
|
|
if self.metrics_collector is not None:
|
|
await self.metrics_collector.record_game_started(len(self.game_last_seen_unix))
|
|
return new_game_board
|
|
|
|
async def persist_game_board(self, game_id:str, game_board:GameBoard) -> None:
|
|
if self.game_state_local_cache:
|
|
self.running_games[game_id] = game_board
|
|
await self.game_state_store.save(game_id, game_board)
|
|
|
|
async def delete_game_board(self, game_state:dict) -> None:
|
|
game_id = game_state['game']['id']
|
|
self.running_games.pop(game_id, None)
|
|
self.game_move_counts.pop(game_id, None)
|
|
self.game_last_seen_unix.pop(game_id, None)
|
|
await self.game_state_store.delete(game_id)
|
|
|
|
async def get_game_board(self, game_state:dict, snake_builder:SnakeBuilder, end:bool=False) -> GameBoard:
|
|
game_id = game_state['game']['id']
|
|
game_board: GameBoard
|
|
if self.game_state_local_cache and game_id in self.running_games:
|
|
game_board = self.running_games[game_id]
|
|
else:
|
|
persisted_board = await self.game_state_store.load(game_id)
|
|
if persisted_board is not None:
|
|
game_board = cast(GameBoard, persisted_board)
|
|
if self.game_state_local_cache:
|
|
self.running_games[game_id] = game_board
|
|
else:
|
|
game_board = await self.create_game_board(game_state, snake_builder)
|
|
if self.metrics_collector is not None:
|
|
await self.metrics_collector.record_game_autocreated()
|
|
|
|
if not end:
|
|
self.game_move_counts[game_id] = self.game_move_counts.get(game_id, 0) + 1
|
|
|
|
self.game_last_seen_unix[game_id] = int(time.time())
|
|
|
|
game_board.read_game_data(game_state)
|
|
if end:
|
|
game_board.end_game(game_state)
|
|
await self.persist_game_board(game_id, game_board)
|
|
|
|
return game_board
|
|
|
|
async def prune_stale_games(self) -> None:
|
|
if not self.game_last_seen_unix:
|
|
return
|
|
|
|
now = int(time.time())
|
|
stale_ids = [
|
|
game_id
|
|
for game_id, last_seen in self.game_last_seen_unix.items()
|
|
if now - last_seen >= self.stale_game_timeout_sec
|
|
]
|
|
for game_id in stale_ids:
|
|
self.running_games.pop(game_id, None)
|
|
self.game_move_counts.pop(game_id, None)
|
|
self.game_last_seen_unix.pop(game_id, None)
|
|
if self.metrics_collector is not None:
|
|
await self.metrics_collector.record_stuck_removed()
|