WIP: ECS / Network System #1
			
				
			
		
		
		
	|  | @ -5,8 +5,8 @@ load_dotenv() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Config: | class Config: | ||||||
|     SIMULATION_WIDTH = int(os.getenv("SIMULATION_WIDTH", 1000)) |     WORLD_WIDTH = int(os.getenv("WORLD_WIDTH", 1000)) | ||||||
|     SIMULATION_HEIGHT = int(os.getenv("SIMULATION_HEIGHT", 1000)) |     WORLD_HEIGHT = int(os.getenv("WORLD_HEIGHT", 1000)) | ||||||
| 
 | 
 | ||||||
|     HATECOMPUTERS_ENDPOINT = os.getenv( |     HATECOMPUTERS_ENDPOINT = os.getenv( | ||||||
|         "HATECOMPUTERS_ENDPOINT", "https://hatecomputers.club" |         "HATECOMPUTERS_ENDPOINT", "https://hatecomputers.club" | ||||||
|  |  | ||||||
|  | @ -10,4 +10,5 @@ class Controllable(Component): | ||||||
|         return f"Controllable(by={self.by})" |         return f"Controllable(by={self.by})" | ||||||
| 
 | 
 | ||||||
|     def dict(self) -> dict: |     def dict(self) -> dict: | ||||||
|         return {"by": self.by} |         # don't serialize who owns this | ||||||
|  |         return {} | ||||||
|  |  | ||||||
|  | @ -23,6 +23,13 @@ class Entity: | ||||||
|     def add_component(self, component: Component) -> None: |     def add_component(self, component: Component) -> None: | ||||||
|         self.components[component.component_type] = component |         self.components[component.component_type] = component | ||||||
| 
 | 
 | ||||||
|  |     def to_dict(self) -> dict: | ||||||
|  |         return { | ||||||
|  |             "entity_type": self.entity_type, | ||||||
|  |             "id": self.id, | ||||||
|  |             "components": {k: v.dict() for k, v in self.components.items()}, | ||||||
|  |         } | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| class EntityManager: | class EntityManager: | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|  | @ -58,3 +65,6 @@ class EntityManager: | ||||||
| 
 | 
 | ||||||
|     def get_entity(self, entity_id: str) -> Optional[Entity]: |     def get_entity(self, entity_id: str) -> Optional[Entity]: | ||||||
|         return self.entities.get(entity_id) |         return self.entities.get(entity_id) | ||||||
|  | 
 | ||||||
|  |     def to_dict(self) -> dict: | ||||||
|  |         return {k: v.to_dict() for k, v in self.entities.items()} | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ import asyncio | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class EventType(str, Enum): | class EventType(str, Enum): | ||||||
|  |     INITIAL_STATE = "INITIAL_STATE" | ||||||
|     ENTITY_BORN = "ENTITY_BORN" |     ENTITY_BORN = "ENTITY_BORN" | ||||||
|     ENTITY_POSITION_UPDATE = "ENTITY_POSITION_UPDATE" |     ENTITY_POSITION_UPDATE = "ENTITY_POSITION_UPDATE" | ||||||
|     ENTITY_DEATH = "ENTITY_DEATH" |     ENTITY_DEATH = "ENTITY_DEATH" | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ from abc import abstractmethod | ||||||
| 
 | 
 | ||||||
| class SystemType(str, Enum): | class SystemType(str, Enum): | ||||||
|     NETWORK = "NETWORK" |     NETWORK = "NETWORK" | ||||||
|  |     WORLD = "WORLD" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class System: | class System: | ||||||
|  |  | ||||||
|  | @ -0,0 +1,24 @@ | ||||||
|  | from kennel.engine.systems.system import System, SystemType | ||||||
|  | from kennel.engine.entities.entity import EntityManager | ||||||
|  | from kennel.engine.components.component import ComponentType | ||||||
|  | from kennel.app import logger | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class WorldSystem(System): | ||||||
|  |     def __init__(self, width: int, height: int): | ||||||
|  |         super().__init__(SystemType.WORLD) | ||||||
|  |         self.width = width | ||||||
|  |         self.height = height | ||||||
|  | 
 | ||||||
|  |     async def update(self, entity_manager: EntityManager, delta_time: float): | ||||||
|  |         entities = entity_manager.get_entities_with_component(ComponentType.POSITION) | ||||||
|  |         for entity in entities: | ||||||
|  |             position = entity.get_component(ComponentType.POSITION) | ||||||
|  |             if position is None: | ||||||
|  |                 logger.error(f"Entity {entity} has no position component") | ||||||
|  |                 continue | ||||||
|  | 
 | ||||||
|  |             position.x = max(0, min(self.width, position.x)) | ||||||
|  |             position.y = max(0, min(self.height, position.y)) | ||||||
|  | 
 | ||||||
|  |             entity.add_component(position) | ||||||
|  | @ -10,6 +10,7 @@ from kennel.engine.systems.network import ( | ||||||
|     Event, |     Event, | ||||||
|     EventType, |     EventType, | ||||||
| ) | ) | ||||||
|  | from kennel.engine.systems.world import WorldSystem | ||||||
| from kennel.config import config | from kennel.config import config | ||||||
| from .app import logger | from .app import logger | ||||||
| from typing import List | from typing import List | ||||||
|  | @ -59,9 +60,6 @@ class KennelClientEventTransformer(ClientEventTransformer): | ||||||
|                 logger.error(f"Entity {id} does not exist") |                 logger.error(f"Entity {id} does not exist") | ||||||
|                 return event |                 return event | ||||||
|             for component_type in entity.components: |             for component_type in entity.components: | ||||||
|                 if component_type == ComponentType.CONTROLLABLE: |  | ||||||
|                     # Do not send controllable components to clients |  | ||||||
|                     continue |  | ||||||
|                 component = entity.get_component(component_type) |                 component = entity.get_component(component_type) | ||||||
|                 if component is not None: |                 if component is not None: | ||||||
|                     event.data[component_type] = component.dict() |                     event.data[component_type] = component.dict() | ||||||
|  | @ -69,8 +67,9 @@ class KennelClientEventTransformer(ClientEventTransformer): | ||||||
|         return event |         return event | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | system_manager.add_system(WorldSystem(config.WORLD_WIDTH, config.WORLD_HEIGHT)) | ||||||
| system_manager.add_system( | system_manager.add_system( | ||||||
|     NetworkSystem(EventProcessor(), KennelClientEventTransformer()) |     NetworkSystem(EventProcessor(), KennelClientEventTransformer()), | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| kennel = Game(entity_manager, system_manager, config.MIN_TIME_STEP) | kennel = Game(entity_manager, system_manager, config.MIN_TIME_STEP) | ||||||
|  |  | ||||||
|  | @ -12,16 +12,16 @@ from fastapi.staticfiles import StaticFiles | ||||||
| from kennel.engine.systems.system import SystemType | from kennel.engine.systems.system import SystemType | ||||||
| from kennel.engine.systems.network import Event, Publishable, EventType | from kennel.engine.systems.network import Event, Publishable, EventType | ||||||
| from typing import Annotated, Optional | from typing import Annotated, Optional | ||||||
| from .kennel import ( | from kennel.kennel import ( | ||||||
|     kennel, |     kennel, | ||||||
|     system_manager, |     system_manager, | ||||||
|     entity_manager, |     entity_manager, | ||||||
|     create_session_controllable_entities, |     create_session_controllable_entities, | ||||||
| ) | ) | ||||||
| from .app import app, templates, logger | from kennel.app import app, templates, logger | ||||||
| from .kennelcats import KennelCatService | from kennel.kennelcats import KennelCatService | ||||||
| from .middleware import logger_middleware | from kennel.middleware import logger_middleware | ||||||
| from .config import config | from kennel.config import config | ||||||
| import asyncio | import asyncio | ||||||
| import uuid | import uuid | ||||||
| 
 | 
 | ||||||
|  | @ -85,6 +85,16 @@ async def websocket_endpoint( | ||||||
|     await websocket.accept() |     await websocket.accept() | ||||||
|     logger.info(f"Websocket connection established for session {session}") |     logger.info(f"Websocket connection established for session {session}") | ||||||
| 
 | 
 | ||||||
|  |     await websocket.send_json( | ||||||
|  |         { | ||||||
|  |             "event_type": EventType.INITIAL_STATE, | ||||||
|  |             "data": { | ||||||
|  |                 "world": {"width": config.WORLD_WIDTH, "height": config.WORLD_HEIGHT}, | ||||||
|  |                 "entities": kennel.entity_manager.to_dict(), | ||||||
|  |             }, | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|     session_entities = create_session_controllable_entities(session) |     session_entities = create_session_controllable_entities(session) | ||||||
|     try: |     try: | ||||||
|         network_system = system_manager.get_system(SystemType.NETWORK) |         network_system = system_manager.get_system(SystemType.NETWORK) | ||||||
|  |  | ||||||
|  | @ -5,10 +5,10 @@ | ||||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"> |     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||||
|     <meta http-equiv="X-UA-Compatible" content="ie=edge"> |     <meta http-equiv="X-UA-Compatible" content="ie=edge"> | ||||||
|     <title>Kennel Club</title> |     <title>Kennel Club</title> | ||||||
|  |     <script src='https://unpkg.com/panzoom@9.4.0/dist/panzoom.min.js'></script> | ||||||
|   </head> |   </head> | ||||||
|   <body> |   <body> | ||||||
|     <div id="kennel-window"></div> |     <div id="kennel-window"></div> | ||||||
|     <script src="{{ url_for('static', path='/index.js') }}"></script> |     <script src="{{ url_for('static', path='/index.js') }}"></script> | ||||||
|   </body> |   </body> | ||||||
| </html> | </html> | ||||||
| 
 |  | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue