Skip to content

Commit

Permalink
[#37] feature: apply binary payload format to socket.io server
Browse files Browse the repository at this point in the history
- integrate with FlatBuffers
- binary payload generated on difficulty-config event

Signed-off-by: T.H. <[email protected]>
  • Loading branch information
metalalive committed Feb 6, 2024
1 parent 7a07047 commit bf61893
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 10 deletions.
4 changes: 2 additions & 2 deletions backend/app/adapter/event_emitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import logging
import socketio

from app.dto import Investigator, Difficulty, RtcCharacterMsgData
from app.dto import Investigator, Difficulty, RtcCharacterMsgData, RtcDifficultyMsgData
from app.config import LOG_FILE_PATH, RTC_HOST, RTC_PORT
from app.constant import RealTimeCommConst as RtcConst, GameRtcEvent
from app.domain import Game
Expand Down Expand Up @@ -42,7 +42,7 @@ async def switch_character(
await self.do_emit(data, evt=RtcConst.EVENTS.CHARACTER)

async def update_difficulty(self, game_id: str, level: Difficulty):
data = {"gameID": game_id, "level": level.value}
data = RtcDifficultyMsgData.serialize(game_id, level)
await self.do_emit(data, evt=RtcConst.EVENTS.DIFFICULTY)

async def do_emit(self, data: Union[Dict, bytes], evt: GameRtcEvent):
Expand Down
5 changes: 4 additions & 1 deletion backend/app/adapter/sio_srv.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,10 @@ async def _player_switch_character(sid, data: bytes):
async def _update_game_difficulty(sid, data: Dict):
# TODO, ensure this event is sent by authorized http server
await _generic_forward_msg(
sid, data, evt=RtcConst.EVENTS.DIFFICULTY, validator=RtcDifficultyMsgData
sid,
data,
evt=RtcConst.EVENTS.DIFFICULTY,
validator=RtcDifficultyMsgData.deserialize,
)


Expand Down
47 changes: 46 additions & 1 deletion backend/app/dto/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
from pydantic import BaseModel, RootModel, ConfigDict
import flatbuffers

from .rtc import CharacterSelection, Investigator as InvestigatorFbs
from .rtc import (
CharacterSelection,
Investigator as InvestigatorFbs,
DifficultyConfig,
Difficulty as DifficultyFbs,
)

# data transfer objects (DTO) in the application
# TODO, determine module path
Expand Down Expand Up @@ -100,6 +105,27 @@ class Difficulty(Enum):
# 專家難度
EXPERT = "expert"

@classmethod
def from_fbs(cls, value):
fbs = DifficultyFbs.Difficulty()
match value:
case fbs.INTRODUCTORY:
return cls.INTRODUCTORY
case fbs.STANDARD:
return cls.STANDARD
case fbs.EXPERT:
return cls.EXPERT

def to_fbs(self):
fbs = DifficultyFbs.Difficulty()
match self:
case self.INTRODUCTORY:
return fbs.INTRODUCTORY
case self.STANDARD:
return fbs.STANDARD
case self.EXPERT:
return fbs.EXPERT


class UpdateDifficultyDto(BaseModel):
model_config = ConfigDict(extra="forbid")
Expand Down Expand Up @@ -166,3 +192,22 @@ class RtcDifficultyMsgData(BaseModel):
model_config = ConfigDict(extra="forbid")
gameID: str
level: Difficulty

def serialize(game_id: str, lvl: Difficulty) -> bytes:
builder = flatbuffers.Builder(128)
game_id = builder.CreateString(game_id)
lvl = lvl.to_fbs()
DifficultyConfig.Start(builder)
DifficultyConfig.AddGameId(builder, game_id)
DifficultyConfig.AddLevel(builder, lvl)
selection = DifficultyConfig.End(builder)
builder.Finish(selection)
serial = builder.Output() # byte-array
return bytes(serial)

def deserialize(data: bytes):
buf = bytearray(data)
obj = DifficultyConfig.DifficultyConfig.GetRootAs(buf, offset=0)
game_id = obj.GameId().decode("utf-8")
lvl = Difficulty.from_fbs(obj.Level())
return RtcDifficultyMsgData(gameID=game_id, level=lvl)
8 changes: 8 additions & 0 deletions backend/app/dto/rtc.fbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,18 @@ enum Investigator:byte {
DETECTIVE = 0, DOCTOR, DRIVER, HUNTER,
MAGICIAN, OCCULTIST, REPORTER
}
enum Difficulty:byte {
INTRODUCTORY = 0, STANDARD, EXPERT
}

table CharacterSelection {
game_id: string (required);
player_id: string (required);
investigator: Investigator;
}

table DifficultyConfig {
game_id: string (required);
level: Difficulty;
}

9 changes: 9 additions & 0 deletions backend/app/dto/rtc/Difficulty.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# automatically generated by the FlatBuffers compiler, do not modify

# namespace:


class Difficulty(object):
INTRODUCTORY = 0
STANDARD = 1
EXPERT = 2
76 changes: 76 additions & 0 deletions backend/app/dto/rtc/DifficultyConfig.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# automatically generated by the FlatBuffers compiler, do not modify

# namespace:

import flatbuffers
from flatbuffers.compat import import_numpy

np = import_numpy()


class DifficultyConfig(object):
__slots__ = ["_tab"]

@classmethod
def GetRootAs(cls, buf, offset=0):
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
x = DifficultyConfig()
x.Init(buf, n + offset)
return x

@classmethod
def GetRootAsDifficultyConfig(cls, buf, offset=0):
"""This method is deprecated. Please switch to GetRootAs."""
return cls.GetRootAs(buf, offset)

# DifficultyConfig
def Init(self, buf, pos):
self._tab = flatbuffers.table.Table(buf, pos)

# DifficultyConfig
def GameId(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
if o != 0:
return self._tab.String(o + self._tab.Pos)
return None

# DifficultyConfig
def Level(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
if o != 0:
return self._tab.Get(flatbuffers.number_types.Int8Flags, o + self._tab.Pos)
return 0


def DifficultyConfigStart(builder):
builder.StartObject(2)


def Start(builder):
DifficultyConfigStart(builder)


def DifficultyConfigAddGameId(builder, gameId):
builder.PrependUOffsetTRelativeSlot(
0, flatbuffers.number_types.UOffsetTFlags.py_type(gameId), 0
)


def AddGameId(builder, gameId):
DifficultyConfigAddGameId(builder, gameId)


def DifficultyConfigAddLevel(builder, level):
builder.PrependInt8Slot(1, level, 0)


def AddLevel(builder, level):
DifficultyConfigAddLevel(builder, level)


def DifficultyConfigEnd(builder):
return builder.EndObject()


def End(builder):
return DifficultyConfigEnd(builder)
13 changes: 7 additions & 6 deletions backend/tests/e2e/test_socketio.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from app.config import RTC_HOST, RTC_PORT
from app.constant import RealTimeCommConst as RtcConst
from app.dto import Investigator, RtcCharacterMsgData
from app.dto import Investigator, Difficulty, RtcCharacterMsgData, RtcDifficultyMsgData

SERVER_URL = "http://%s:%s" % (RTC_HOST, RTC_PORT)

Expand Down Expand Up @@ -117,7 +117,8 @@ async def verify_difficulty(self, expect: str):
evts: List = await self._sio_client.receive(timeout=3)
assert len(evts) == 2
assert evts[0] == RtcConst.EVENTS.DIFFICULTY.value
assert evts[1]["level"] == expect
obj = RtcDifficultyMsgData.deserialize(evts[1])
assert obj.level.value == expect


class MockiHttpServer(MockiAbstractClient):
Expand All @@ -136,8 +137,8 @@ async def switch_character(
data = RtcCharacterMsgData.serialize(room_id, player, character)
await self._sio_client.emit(RtcConst.EVENTS.CHARACTER.value, data=data)

async def set_difficulty(self, room_id: str, level: str):
data = {"level": level, "gameID": room_id}
async def set_difficulty(self, room_id: str, level: Difficulty):
data = RtcDifficultyMsgData.serialize(room_id, level)
await self._sio_client.emit(RtcConst.EVENTS.DIFFICULTY.value, data=data)


Expand Down Expand Up @@ -260,10 +261,10 @@ async def test_forward_game_state_msg(self):
await clients[0].verify_character_update(clients[0], "driver")
await clients[1].verify_character_update(clients[0], "driver")

await http_server.set_difficulty(game_room, level="expert")
await http_server.set_difficulty(game_room, level=Difficulty.EXPERT)
await clients[0].verify_difficulty("expert")
await clients[1].verify_difficulty("expert")
await http_server.set_difficulty(game_room, level="standard")
await http_server.set_difficulty(game_room, level=Difficulty.STANDARD)
await clients[0].verify_difficulty("standard")
await clients[1].verify_difficulty("standard")

Expand Down

0 comments on commit bf61893

Please sign in to comment.