import asyncio import uuid import time from typing import List, Optional from kennel.config import config from kennel.engine.components.component import ComponentType from kennel.engine.entities.entity import Entity, EntityManager from kennel.engine.entities.laser import Laser from kennel.engine.entities.cat import Cat from kennel.engine.game import Game from kennel.engine.systems.markov_transition_state_system import ( MarkovTransitionStateSystem, ) from kennel.engine.systems.network import ( EntityPositionUpdateEvent, EntityBornEvent, EntityDeathEvent, Event, EventType, NetworkSystem, UpstreamEventProcessor, ) from kennel.kennelcats import KennelCatService, KennelCat from kennel.engine.systems.system import SystemManager from kennel.engine.systems.world import WorldSystem from .app import logger entity_manager = EntityManager() system_manager = SystemManager() class KennelEventProcessor(UpstreamEventProcessor): def accept( self, entity_manager: EntityManager, event: tuple[Event, str] ) -> Optional[Event]: client_event, client_id = event if client_event.event_type == EventType.ENTITY_POSITION_UPDATE: return self._process_entity_position_update( entity_manager, client_event, client_id ) def _process_entity_position_update( self, entity_manager: EntityManager, event: EntityPositionUpdateEvent, client_id: str, ) -> Optional[Event]: entity = entity_manager.get_entity(event.data["id"]) if entity is None: logger.error(f"Entity(id={event.data['id']}) does not exist") return controllable = entity.get_component(ComponentType.CONTROLLABLE) if controllable is None or controllable.by != client_id: logger.error(f"Entity {entity} is not controllable by client {client_id}") return position = entity.get_component(ComponentType.POSITION) if position is None: logger.error(f"Entity {entity} has no position") return position.x = event.data["position"]["x"] position.y = event.data["position"]["y"] entity.add_component(position) entity_manager.add_entity(entity) return event class KennelCatsManager: kennel_cat_service: KennelCatService entity_manager: EntityManager network_system: NetworkSystem last_seen: set[str] poll_interval_sec: int running: bool def __init__( self, kennel_cat_service: KennelCatService, entity_manager: EntityManager, network_system: NetworkSystem, poll_interval_sec: int, ): self.kennel_cat_service = kennel_cat_service self.entity_manager = entity_manager self.network_system = network_system self.poll_interval_sec = poll_interval_sec self.last_seen = set() self.running = False async def start(self) -> None: logger.info("starting kennel cats manager") if self.running: return self.running = True while self.running: logger.info("polling kennel cats service") cats = self.kennel_cat_service.get_kennel() cats_table = {cat.id: cat for cat in cats} cat_ids = set([cat.id for cat in cats]) removed_cats = [cats_table[id] for id in self.last_seen.difference(cat_ids)] added_cats = [cats_table[id] for id in cat_ids.difference(self.last_seen)] logger.info(f"removing {removed_cats}") logger.info(f"adding {added_cats}") for removed in removed_cats: self.entity_manager.remove_entity(removed) entity_death = EntityDeathEvent(removed.id) self.network_system.server_global_event(entity_death) for added in added_cats: new_cat = Cat(added.id) self.entity_manager.add_entity(new_cat) entity_born = EntityBornEvent(new_cat) self.network_system.server_global_event(entity_born) self.last_seen = cat_ids await asyncio.sleep(self.poll_interval_sec) def stop(self) -> None: logger.info("stopping kennel cats manager") self.running = False network_system = NetworkSystem(KennelEventProcessor()) world_system = WorldSystem(config.WORLD_WIDTH, config.WORLD_HEIGHT) markov_transition_state_system = MarkovTransitionStateSystem(network_system) system_manager.add_system(network_system) system_manager.add_system(world_system) kennel = Game(entity_manager, system_manager, config.MIN_TIME_STEP) kennel_cat_service = KennelCatService(config.HATECOMPUTERS_ENDPOINT) kennel_cats_manager = KennelCatsManager( kennel_cat_service, entity_manager, network_system, config.KENNEL_CATS_POLL_SEC ) def create_session_controllable_entities(session: str) -> List[Entity]: laser = Laser(uuid.uuid4().hex[:10], session) return [laser]