from enum import Enum from kennel.engine.entities.entity import Entity, EntityType, EntityManager from .system import System, SystemType from abc import abstractmethod from typing import Optional, List import asyncio 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) -> 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]