import asyncio from abc import abstractmethod from enum import Enum from typing import List, Optional from kennel.app import logger from kennel.engine.entities.entity import Entity, EntityManager from .system import System, SystemType class EventType(str, Enum): INITIAL_STATE = "INITIAL_STATE" SET_CONTROLLABLE = "SET_CONTROLLABLE" ENTITY_BORN = "ENTITY_BORN" ENTITY_DEATH = "ENTITY_DEATH" ENTITY_POSITION_UPDATE = "ENTITY_POSITION_UPDATE" class Event: def __init__(self, event_type: EventType, data: dict): self.event_type = event_type self.data = data def __str__(self) -> str: return f"Event({self.event_type}, {self.data})" def to_dict(self) -> dict: return {"event_type": self.event_type, "data": self.data} @staticmethod def from_dict(data: dict) -> Optional["Event"]: if "event_type" not in data or "data" not in data: return event_type = data["event_type"] if event_type == EventType.INITIAL_STATE: return InitialStateEvent( data["data"]["world"]["width"], data["data"]["world"]["height"], data["data"]["entities"], ) if event_type == EventType.SET_CONTROLLABLE: return SetControllableEvent(data["data"]["id"], data["data"]["client_id"]) if event_type == EventType.ENTITY_BORN: return EntityBornEvent(data["data"]) if event_type == EventType.ENTITY_POSITION_UPDATE: return EntityPositionUpdateEvent( data["data"]["id"], data["data"]["position"] ) if event_type == EventType.ENTITY_DEATH: return EntityDeathEvent(data["data"]["id"]) logger.warn(f"Unknown event type: {data['event_type']}") return Event(EventType(data["event_type"]), data["data"]) class InitialStateEvent(Event): def __init__(self, world_width: int, world_height: int, entities: List[Entity]): self.world_width = world_width self.world_height = world_height self.entities = entities super().__init__( EventType.INITIAL_STATE, { "world": {"width": world_width, "height": world_height}, "entities": [entity.to_dict() for entity in entities], }, ) class SetControllableEvent(Event): def __init__(self, entity_id: str, client_id: str): self.entity_id = entity_id self.client_id = client_id super().__init__( EventType.SET_CONTROLLABLE, {"id": entity_id, "client_id": client_id} ) class EntityBornEvent(Event): def __init__(self, entity: Entity): self.entity = entity super().__init__(EventType.ENTITY_BORN, {"entity": entity.to_dict()}) class EntityPositionUpdateEvent(Event): def __init__(self, entity_id: str, position: dict): super().__init__( EventType.ENTITY_POSITION_UPDATE, {"id": entity_id, "position": position}, ) class EntityDeathEvent(Event): def __init__(self, entity_id: str): super().__init__(EventType.ENTITY_DEATH, {"id": entity_id}) class Publishable: @abstractmethod async def publish(self, event: Event): pass class EventProcessor: @abstractmethod def accept( self, entity_manager: EntityManager, event: Event | tuple[Event, str] ) -> None: pass class NetworkSystem(System): def __init__(self, event_processor: EventProcessor): super().__init__(SystemType.NETWORK) self.event_processor = event_processor self.events = [] self.client_events = [] self.clients = {} async def update(self, entity_manager: EntityManager, delta_time: float) -> None: if len(self.events) + len(self.client_events) == 0: return for event in self.events + self.client_events: self.event_processor.accept(entity_manager, event) client_sendable = self.events + [event for event, _ in self.client_events] await asyncio.gather( *[ client.publish(event) for client in self.clients.values() for event in client_sendable ] ) self.events = [] self.client_events = [] def client_event(self, client_id: str, event: Event) -> None: self.client_events.append((event, client_id)) def add_event(self, event: Event) -> None: self.events.append(event) def add_client(self, client_id: str, client: Publishable) -> None: self.clients[client_id] = client def remove_client(self, client_id: str) -> None: del self.clients[client_id]