Skip to content

Commit

Permalink
[#37] feature: http server emits event to socketio server on creating…
Browse files Browse the repository at this point in the history
… game

- socketio server collects the events, to know all the ongoing games
- http server maintains a shared socketio client sending all relavant
  events
- rename `AbstractRepository` to `AbstractGameRepository`
- TODO, implement room repository for real-time communication

Signed-off-by: T.H. <[email protected]>
  • Loading branch information
metalalive committed Jan 23, 2024
1 parent 7bba139 commit 5b17c85
Show file tree
Hide file tree
Showing 11 changed files with 392 additions and 314 deletions.
15 changes: 10 additions & 5 deletions backend/app/adapter/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from fastapi.responses import JSONResponse
from hypercorn.config import Config
from hypercorn.asyncio import serve
from app.config import LOG_FILE_PATH

from app.dto import (
CreateGameReqDto,
Expand All @@ -22,9 +21,10 @@
SwitchInvestigatorUseCase,
UpdateGameDifficultyUseCase,
)
from app.config import REST_HOST, REST_PORT
from app.config import LOG_FILE_PATH, REST_HOST, REST_PORT
from app.domain import GameError
from app.adapter.repository import get_repository
from app.adapter.event_emitter import SocketIoEventEmitter
from app.adapter.repository import get_game_repository
from app.adapter.presenter import read_investigator_presenter, create_game_presenter

_logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -54,7 +54,9 @@ def __init__(self, e: GameError):
@_router.post("/games", response_model=CreateGameRespDto)
async def create_game(req: CreateGameReqDto):
uc = CreateGameUseCase(
repository=shared_context["repository"], settings=shared_context["settings"]
repository=shared_context["repository"],
evt_emitter=shared_context["evt_emit"],
settings=shared_context["settings"],
)
response = await uc.execute(req.players, create_game_presenter)
return response
Expand Down Expand Up @@ -96,11 +98,14 @@ async def update_game_difficulty(game_id: str, req: UpdateDifficultyDto):
async def lifetime_server_context(app: FastAPI):
# TODO, parameters should be in separate python module or `json` , `toml` file
settings = {"host": "localhost:8081"}
shared_context["repository"] = get_repository()
shared_context["repository"] = get_game_repository()
shared_context["settings"] = settings
shared_context["evt_emit"] = SocketIoEventEmitter()
yield
## TODO, de-initialize the context if necessary,
# e.g. close database connections
evt_emit = shared_context.pop("evt_emit")
await evt_emit.deinit()
shared_context.clear()


Expand Down
38 changes: 38 additions & 0 deletions backend/app/adapter/event_emitter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import logging
import socketio

from app.config import LOG_FILE_PATH, RTC_HOST, RTC_PORT
from app.constant import RealTimeCommConst as RtcConst
from app.domain import Game

_logger = logging.getLogger(__name__)
_logger.setLevel(logging.WARNING)
_logger.addHandler(logging.FileHandler(LOG_FILE_PATH["REST"], mode="a"))


class AbsEventEmitter:
async def create_game(self, game: Game):
raise NotImplementedError("AbsEventEmitter.create_game")


class SocketIoEventEmitter(AbsEventEmitter):
def __init__(self):
self._url = "http://%s:%s" % (RTC_HOST, RTC_PORT)
self._namespace = RtcConst.NAMESPACE
self._client = socketio.AsyncSimpleClient(logger=True)

async def create_game(self, game: Game):
data = {"gameID": game.id, "players": [p.id for p in game.players]}
try:
if not self._client.connected:
await self._client.connect(self._url, namespace=self._namespace)
await self._client.emit(RtcConst.EVENTS.NEW_ROOM.value, data=data)
except Exception as e:
_logger.error("send new-room event: %s", e)
# TODO, reconnect if connection inactive

async def deinit(self):
try:
await self._client.disconnect()
except Exception as e:
_logger.error("error on deinit: %s", e)
8 changes: 4 additions & 4 deletions backend/app/adapter/repository/__init__.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
from app.domain import Game


class AbstractRepository:
class AbstractGameRepository:
async def save(self, game: Game):
raise NotImplementedError("AbstractRepository.save")

async def get_game(self, game_id: str) -> Game:
raise NotImplementedError("AbstractRepository.get_game")


def get_repository():
def get_game_repository():
# TODO
# - make it configurable at runtime
# - set max limit of concurrent and ongoing games to save
from .in_mem import InMemoryRepository
from .in_mem import InMemoryGameRepository

return InMemoryRepository()
return InMemoryGameRepository()
4 changes: 2 additions & 2 deletions backend/app/adapter/repository/in_mem.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
from typing import Optional

from app.domain import Game
from app.adapter.repository import AbstractRepository
from app.adapter.repository import AbstractGameRepository


class InMemoryRepository(AbstractRepository):
class InMemoryGameRepository(AbstractGameRepository):
def __init__(self):
self._lock = asyncio.Lock()
self._games = {}
Expand Down
16 changes: 15 additions & 1 deletion backend/app/adapter/sio_srv.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
import asyncio
import os
from typing import Dict
from typing import Dict, List

import socketio
from hypercorn.config import Config
Expand All @@ -20,6 +20,12 @@
_logger.addHandler(logging.FileHandler(LOG_FILE_PATH["RTC"], mode="a"))


class NewRoomMsgData(BaseModel):
model_config = ConfigDict(extra="forbid")
gameID: str
players: List[str]


class ChatMsgData(BaseModel):
model_config = ConfigDict(extra="forbid")
msg: str
Expand Down Expand Up @@ -101,6 +107,14 @@ async def deinit_communication(sid, data: Dict):
)


@srv.on(RtcConst.EVENTS.NEW_ROOM.value, namespace=RtcConst.NAMESPACE)
async def _new_game_room(sid, data: Dict):
try: # TODO, ensure this event is sent by authorized http server
data = NewRoomMsgData(**data)
except ValidationError as e:
_logger.error("%s", e)


def gen_srv_task(host: str):
cfg = Config()
cfg.bind = [host]
Expand Down
1 change: 1 addition & 0 deletions backend/app/constant.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class GameRtcEvent(Enum):
INIT = "init"
DEINIT = "deinit"
CHAT = "chat"
NEW_ROOM = "new_room"


class RealTimeCommConst:
Expand Down
5 changes: 4 additions & 1 deletion backend/app/usecase/config_game.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@


class AbstractUseCase:
def __init__(self, repository, settings: Dict = None):
def __init__(self, repository, evt_emitter=None, settings: Dict = None):
self.repository = repository
self.evt_emitter = evt_emitter
self.settings = settings


Expand All @@ -29,6 +30,8 @@ async def execute(
game.add_players(data)
self.rand_select_investigator(game)
await self.repository.save(game)
if self.evt_emitter:
await self.evt_emitter.create_game(game)
return presenter(self.settings, game.id)

def rand_select_investigator(self, game: Game):
Expand Down
Loading

0 comments on commit 5b17c85

Please sign in to comment.