Skip to content

Commit

Permalink
Merge pull request #17 from gamultong/feature/cursor-manager-pointing…
Browse files Browse the repository at this point in the history
…-receiver

CursorManager의 pointing 관련 이벤트 리시버 추가
  • Loading branch information
byundojin authored Nov 28, 2024
2 parents 2c9d2c3 + 8778688 commit 1d0dbd4
Show file tree
Hide file tree
Showing 6 changed files with 257 additions and 10 deletions.
2 changes: 2 additions & 0 deletions cursor/internal/cursor.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class Cursor:
conn_id: str
position: Point
pointer: Point | None
new_pointer: Point | None # 새로운 포인터 후보
color: Color
width: int
height: int
Expand All @@ -18,6 +19,7 @@ def create(conn_id: str):
conn_id=conn_id,
position=Point(0, 0),
pointer=None,
new_pointer=None,
color=Color.get_random(),
width=0,
height=0
Expand Down
62 changes: 61 additions & 1 deletion cursor/manager/internal/cursor_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from board import Point
from event import EventBroker
from message import Message
from message.payload import NewConnPayload, NewCursorPayload, NearbyCursorPayload, CursorAppearedPayload, CursorPayload, NewConnEvent
from message.payload import NewConnPayload, NewCursorPayload, NearbyCursorPayload, CursorAppearedPayload, CursorPayload, NewConnEvent, PointingPayload, TryPointingPayload, PointingResultPayload, PointerSetPayload, PointEvent


class CursorManager:
Expand Down Expand Up @@ -113,3 +113,63 @@ async def receive_new_conn(message: Message[NewConnPayload]):
)

await EventBroker.publish(cursor_appeared_message)

@EventBroker.add_receiver(PointEvent.POINTING)
@staticmethod
async def receive_pointing(message: Message[PointingPayload]):
sender = message.header["sender"]

cursor = CursorManager.cursor_dict[sender]
new_pointer = message.payload.position

# 뷰 바운더리 안에서 포인팅하는지 확인
left_up_edge = Point(cursor.position.x - cursor.width, cursor.position.y + cursor.height)
right_down_edge = Point(cursor.position.x + cursor.width, cursor.position.y - cursor.height)

if \
new_pointer.x < left_up_edge.x or \
new_pointer.x > right_down_edge.x or \
new_pointer.y > left_up_edge.y or \
new_pointer.y < right_down_edge.y:
# TODO: 예외 처리?
pass

cursor.new_pointer = new_pointer

message = Message(
event=PointEvent.TRY_POINTING,
header={"sender": sender},
payload=TryPointingPayload(
cursor_position=cursor.position,
new_pointer=new_pointer,
color=cursor.color,
click_type=message.payload.click_type
)
)

await EventBroker.publish(message)

@EventBroker.add_receiver(PointEvent.POINTING_RESULT)
@staticmethod
async def receive_pointing_result(message: Message[PointingResultPayload]):
receiver = message.header["receiver"]

cursor = CursorManager.cursor_dict[receiver]

new_pointer = cursor.new_pointer if message.payload.pointable else None
origin_pointer = cursor.pointer

cursor.pointer = new_pointer
cursor.new_pointer = None

message = Message(
event=PointEvent.POINTER_SET,
header={"target_conns": [cursor.conn_id]},
payload=PointerSetPayload(
origin_position=origin_pointer,
new_position=new_pointer,
color=cursor.color,
)
)

await EventBroker.publish(message)
2 changes: 1 addition & 1 deletion cursor/manager/test/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from .cursor_manager_test import CursorManagerTestCase, CursorManagerNewConnTestCase
from .cursor_manager_test import CursorManagerTestCase, CursorManagerNewConnTestCase, CursorManagerPointingTestCase
193 changes: 189 additions & 4 deletions cursor/manager/test/cursor_manager_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@
from cursor.manager import CursorManager
from event import EventBroker
from message import Message
from message.payload import NewConnEvent, NewConnPayload
from message.payload import NewConnEvent, NewConnPayload, PointEvent, PointingPayload, TryPointingPayload, PointingResultPayload, PointerSetPayload, ClickType
import unittest
from unittest.mock import Mock, AsyncMock
from unittest.mock import AsyncMock
from board import Point
from warnings import warn


def get_cur(conn_id):
return Cursor(
conn_id=conn_id,
position=Point(0, 0),
pointer=None,
new_pointer=None,
height=10,
width=10,
color=Color.BLUE
Expand Down Expand Up @@ -57,7 +57,7 @@ def test_view_includes(self):

class CursorManagerNewConnTestCase(unittest.IsolatedAsyncioTestCase):
def setUp(self):
# 기존 my-cursor 리시버 비우기 및 mock으로 대체
# 기존 multicast 리시버 비우기 및 mock으로 대체
self.multicast_receivers = []
if "multicast" in EventBroker.event_dict:
self.multicast_receivers = EventBroker.event_dict["multicast"].copy()
Expand Down Expand Up @@ -153,5 +153,190 @@ async def test_receive_new_conn_with_cursors(self):
assert set(got.header["target_conns"]) == set([c.conn_id for c in CursorManager.cursor_dict.values()])


class CursorManagerPointingTestCase(unittest.IsolatedAsyncioTestCase):
def setUp(self):
# 기존 try-pointing 리시버 비우기 및 mock으로 대체
self.try_pointing_receivers = []
if PointEvent.TRY_POINTING in EventBroker.event_dict:
self.try_pointing_receivers = EventBroker.event_dict[PointEvent.TRY_POINTING].copy()

EventBroker.event_dict[PointEvent.TRY_POINTING] = []

self.mock_try_pointing_func = AsyncMock()
self.mock_try_pointing_receiver = EventBroker.add_receiver(event=PointEvent.TRY_POINTING)(func=self.mock_try_pointing_func)

# 기존 pointable-result 리시버 비우기 및 mock으로 대체
self.pointer_set_receivers = []
if PointEvent.POINTER_SET in EventBroker.event_dict:
self.pointer_set_receivers = EventBroker.event_dict[PointEvent.POINTER_SET].copy()

EventBroker.event_dict[PointEvent.POINTER_SET] = []

self.mock_pointer_set_func = AsyncMock()
self.mock_pointer_set_receiver = EventBroker.add_receiver(event=PointEvent.POINTER_SET)(func=self.mock_pointer_set_func)

def tearDown(self):
# 리시버 정상화
EventBroker.remove_receiver(self.mock_try_pointing_receiver)
EventBroker.event_dict[PointEvent.TRY_POINTING] = self.try_pointing_receivers

EventBroker.remove_receiver(self.mock_pointer_set_receiver)
EventBroker.event_dict[PointEvent.POINTER_SET] = self.pointer_set_receivers

CursorManager.cursor_dict = {}

async def test_receive_pointing(self):
expected_conn_id = "example"
expected_width = 100
expected_height = 100

cursor = Cursor.create(conn_id=expected_conn_id)
cursor.set_size(expected_width, expected_height)

CursorManager.cursor_dict = {
expected_conn_id: cursor
}

click_type = ClickType.GENERAL_CLICK

message = Message(
event=PointEvent.POINTING,
header={"sender": expected_conn_id},
payload=PointingPayload(
click_type=click_type,
position=Point(0, 0)
)
)

await CursorManager.receive_pointing(message)

self.mock_try_pointing_func.assert_called_once()
got = self.mock_try_pointing_func.mock_calls[0].args[0]

assert type(got) == Message
assert got.event == PointEvent.TRY_POINTING

assert "sender" in got.header
assert type(got.header["sender"]) == str
assert got.header["sender"] == expected_conn_id

assert type(got.payload) == TryPointingPayload
assert got.payload.click_type == click_type
assert got.payload.cursor_position == cursor.position
assert got.payload.color == cursor.color
assert got.payload.new_pointer == Point(0, 0)

assert cursor.new_pointer == message.payload.position

async def test_receive_pointing_result_pointable(self):
expected_conn_id = "example"

cursor = Cursor.create(conn_id=expected_conn_id)
cursor.pointer = Point(0, 0)
cursor.new_pointer = Point(0, 0)

CursorManager.cursor_dict = {
expected_conn_id: cursor
}

message = Message(
event=PointEvent.POINTING_RESULT,
header={"receiver": expected_conn_id},
payload=PointingResultPayload(
pointable=True,
)
)

await CursorManager.receive_pointing_result(message)

self.mock_pointer_set_func.assert_called_once()
got = self.mock_pointer_set_func.mock_calls[0].args[0]

assert type(got) == Message
assert got.event == PointEvent.POINTER_SET

assert "target_conns" in got.header
assert len(got.header["target_conns"]) == 1
assert got.header["target_conns"][0] == expected_conn_id

assert type(got.payload) == PointerSetPayload
assert got.payload.origin_position == Point(0, 0)
assert got.payload.color == cursor.color
assert got.payload.new_position == Point(0, 0)

assert cursor.pointer == got.payload.new_position
assert cursor.new_pointer == None

async def test_receive_pointing_result_not_pointable(self):
expected_conn_id = "example"

cursor = Cursor.create(conn_id=expected_conn_id)
cursor.pointer = Point(0, 0)

CursorManager.cursor_dict = {
expected_conn_id: cursor
}

message = Message(
event=PointEvent.POINTING_RESULT,
header={"receiver": expected_conn_id},
payload=PointingResultPayload(
pointable=False,
)
)

await CursorManager.receive_pointing_result(message)

self.mock_pointer_set_func.assert_called_once()
got = self.mock_pointer_set_func.mock_calls[0].args[0]

assert type(got) == Message
assert got.event == PointEvent.POINTER_SET

assert "target_conns" in got.header
assert len(got.header["target_conns"]) == 1
assert got.header["target_conns"][0] == expected_conn_id

assert type(got.payload) == PointerSetPayload
assert got.payload.origin_position == Point(0, 0)
assert got.payload.color == cursor.color
assert got.payload.new_position == None

async def test_receive_pointing_result_pointable_no_original_pointer(self):
expected_conn_id = "example"

cursor = Cursor.create(conn_id=expected_conn_id)
cursor.new_pointer = Point(0, 0)

CursorManager.cursor_dict = {
expected_conn_id: cursor
}

message = Message(
event=PointEvent.POINTING_RESULT,
header={"receiver": expected_conn_id},
payload=PointingResultPayload(
pointable=True,
)
)

await CursorManager.receive_pointing_result(message)

self.mock_pointer_set_func.assert_called_once()
got = self.mock_pointer_set_func.mock_calls[0].args[0]

assert type(got) == Message
assert got.event == PointEvent.POINTER_SET

assert "target_conns" in got.header
assert len(got.header["target_conns"]) == 1
assert got.header["target_conns"][0] == expected_conn_id

assert type(got.payload) == PointerSetPayload
assert got.payload.origin_position == None
assert got.payload.color == cursor.color
assert got.payload.new_position == Point(0, 0)


if __name__ == "__main__":
unittest.main()
2 changes: 1 addition & 1 deletion message/payload/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
from .internal.exceptions import InvalidFieldException, MissingFieldException
from .internal.new_conn_payload import NewConnPayload, NewConnEvent, NearbyCursorPayload, CursorPayload, CursorAppearedPayload, NewCursorPayload
from .internal.parsable_payload import ParsablePayload
from .internal.pointing_payload import PointerSet, PointingResultPayload, PointingPayload, TryPointingPayload, PointEvent, ClickType
from .internal.pointing_payload import PointerSetPayload, PointingResultPayload, PointingPayload, TryPointingPayload, PointEvent, ClickType
6 changes: 3 additions & 3 deletions message/payload/internal/pointing_payload.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class PointingResultPayload(Payload):


@dataclass
class PointerSet(ParsablePayload):
oringin_position: ParsablePayload[Point]
new_position: ParsablePayload[Point]
class PointerSetPayload(ParsablePayload):
origin_position: ParsablePayload[Point] | None
new_position: ParsablePayload[Point] | None
color: Color

0 comments on commit 1d0dbd4

Please sign in to comment.