149 lines
4.3 KiB
Python
149 lines
4.3 KiB
Python
import asyncio
|
|
import uuid
|
|
from typing import Annotated, List, Optional
|
|
|
|
from fastapi import (
|
|
Cookie,
|
|
Depends,
|
|
Request,
|
|
Response,
|
|
WebSocket,
|
|
WebSocketDisconnect,
|
|
WebSocketException,
|
|
status,
|
|
)
|
|
from fastapi.responses import FileResponse
|
|
from fastapi.staticfiles import StaticFiles
|
|
|
|
from kennel.app import app, logger
|
|
from kennel.config import config
|
|
from kennel.engine.systems.network import (
|
|
EntityBornEvent,
|
|
Event,
|
|
EventType,
|
|
InitialStateEvent,
|
|
Publishable,
|
|
SetControllableEvent,
|
|
)
|
|
from kennel.engine.systems.system import SystemType
|
|
from kennel.kennel import (
|
|
create_session_controllable_entities,
|
|
entity_manager,
|
|
kennel,
|
|
system_manager,
|
|
)
|
|
|
|
app.mount("/static", StaticFiles(directory="static/dist"), name="static")
|
|
|
|
|
|
loop = asyncio.get_event_loop()
|
|
|
|
|
|
@app.on_event("startup")
|
|
async def startup_event():
|
|
logger.info("Starting Kennel...")
|
|
loop.create_task(kennel.run())
|
|
|
|
|
|
@app.on_event("shutdown")
|
|
async def shutdown_event():
|
|
logger.info("Stopping Kennel...")
|
|
kennel.stop()
|
|
loop.stop()
|
|
logger.info("Kennel stopped")
|
|
|
|
|
|
@app.get("/")
|
|
async def index(request: Request):
|
|
return FileResponse("static/dist/index.html", media_type="text/html")
|
|
|
|
|
|
@app.get("/assign")
|
|
async def assign(
|
|
response: Response,
|
|
session: Annotated[Optional[str], Cookie()] = None,
|
|
):
|
|
if session is None:
|
|
session = str(uuid.uuid4().hex)
|
|
response.set_cookie(key="session", value=session, max_age=config.COOKIE_MAX_AGE)
|
|
return {"session": session}
|
|
|
|
|
|
async def get_cookie_or_token(
|
|
websocket: WebSocket,
|
|
session: Annotated[str | None, Cookie()] = None,
|
|
):
|
|
if session is None:
|
|
raise WebSocketException(code=status.WS_1008_POLICY_VIOLATION)
|
|
return session
|
|
|
|
|
|
class WebSocketClient(Publishable):
|
|
def __init__(self, websocket: WebSocket):
|
|
self.websocket = websocket
|
|
|
|
async def publish(self, events: List[Event]):
|
|
await self.websocket.send_json([event.to_dict() for event in events])
|
|
|
|
|
|
@app.websocket("/ws")
|
|
async def websocket_endpoint(
|
|
websocket: WebSocket,
|
|
session: Annotated[str, Depends(get_cookie_or_token)],
|
|
):
|
|
await websocket.accept()
|
|
logger.info(f"Websocket connection established for session {session}")
|
|
|
|
session_entities = create_session_controllable_entities(session)
|
|
logger.info(f"Creating {len(session_entities)} entities for session {session}")
|
|
try:
|
|
network_system = system_manager.get_system(SystemType.NETWORK)
|
|
if network_system is None:
|
|
raise "Network system not found"
|
|
network_system.add_client(session, WebSocketClient(websocket))
|
|
|
|
network_system.client_downstream_event(
|
|
session,
|
|
InitialStateEvent(
|
|
config.WORLD_WIDTH, config.WORLD_HEIGHT, kennel.entity_manager.to_dict()
|
|
),
|
|
)
|
|
|
|
for entity in session_entities:
|
|
logger.info(f"Adding entity {entity.id} for session {session}")
|
|
entity_manager.add_entity(entity)
|
|
|
|
network_system.server_global_event(EntityBornEvent(entity))
|
|
network_system.client_downstream_event(
|
|
session, SetControllableEvent(entity.id, session)
|
|
)
|
|
|
|
while True:
|
|
message = await websocket.receive_json()
|
|
if not isinstance(message, list):
|
|
message = [message]
|
|
events = [Event.from_dict(event) for event in message]
|
|
if not all([event is not None for event in events]):
|
|
logger.info(f"Invalid events in: {message}"[:100])
|
|
continue
|
|
for event in events:
|
|
network_system.client_upstream_event(session, event)
|
|
except WebSocketDisconnect:
|
|
logger.info(f"Websocket connection closed by client: {session}")
|
|
except Exception as e:
|
|
logger.error("Exception occurred", exc_info=e)
|
|
finally:
|
|
network_system.remove_client(session)
|
|
for entity in session_entities:
|
|
logger.info(f"Removing entity {entity.id} for session {session}")
|
|
entity_manager.remove_entity(entity.id)
|
|
network_system.add_event(Event(EventType.ENTITY_DEATH, {"id": entity.id}))
|
|
|
|
await websocket.close()
|
|
logger.info("Websocket connection closed")
|
|
|
|
|
|
@app.get("/healthcheck")
|
|
async def healthcheck():
|
|
return Response("healthy")
|