aboutsummaryrefslogtreecommitdiff
path: root/kennel/main.py
blob: 122e40a33f6ee900a30848c19acbf3945ed73ef4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
from fastapi import (
    FastAPI,
    Request,
    Response,
    WebSocket,
    WebSocketException,
    status,
    Cookie,
    Depends,
)
from fastapi.staticfiles import StaticFiles
from kennel.engine.systems.system import SystemType
from kennel.engine.systems.network import Event, Publishable, EventType
from typing import Annotated, Optional
from .kennel import (
    kennel,
    system_manager,
    entity_manager,
    create_session_controllable_entities,
)
from .app import app, templates, logger
from .kennelcats import KennelCatService
from .middleware import logger_middleware
from .config import config
import asyncio
import uuid

app.mount("/static", StaticFiles(directory="static"), 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 templates.TemplateResponse(request=request, name="index.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, event: Event):
        await self.websocket.send_json(event.dict())


@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)
    try:
        network_system = system_manager.get_system(SystemType.NETWORK)
        if network_system is None:
            raise "Network system not found"

        for entity in session_entities:
            logger.info(f"Adding entity {entity.id} for session {session}")
            entity_manager.add_entity(entity)
            network_system.add_event(Event(EventType.ENTITY_BORN, {"id": entity.id}))

        network_system.add_client(session, WebSocketClient(websocket))
        while True:
            message = await websocket.receive_json()
            network_system.client_event(session, Event.from_dict(message))
    except Exception as e:
        logger.error(f"WebSocket exception {e}")
    finally:
        logger.info("Websocket connection closed")
        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}))

        network_system.remove_client(session)
        await websocket.close()


@app.get("/healthcheck")
async def healthcheck():
    return Response("healthy")