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