From 3202aa793efa734c04c2b707b70a184c25961d5e Mon Sep 17 00:00:00 2001 From: Eric Froemling Date: Thu, 23 Jun 2022 12:15:48 -0700 Subject: [PATCH] Latest changes from private. --- .efrocachemap | 32 +++---- .idea/dictionaries/ericf.xml | 7 ++ assets/src/ba_data/python/ba/_player.py | 15 ++- .../.idea/dictionaries/ericf.xml | 7 ++ tests/test_efro/test_rpc.py | 18 ++++ tools/efro/rpc.py | 91 +++++++++++++++---- 6 files changed, 127 insertions(+), 43 deletions(-) diff --git a/.efrocachemap b/.efrocachemap index c44cd7b6..4b54632e 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -4000,18 +4000,18 @@ "build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/cb/e3/7ef1979818e8f39e259177bf429a", "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/24/27/2c3af3d7bced84c2d368d3b12a3e", "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/68/dd/64237dd6203d09443bae46aea724", - "build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/3b/aa/5453dbe6ed56c827bdb95da532d9", + "build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/e8/25/0b74945268df9ff672e6458c51ed", "build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/62/df/3416ab9e48837bf401c16d36f009", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/b1/6e/b47ba2abc30574609473b2569380", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/c7/eb/9742ca33ce3dadf4ecaa99af8f3b", "build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/c0/1d/c66a88772a554cbba3e587cdbc81", - "build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/f5/a9/06bee9980831c4c375c94d86638f", + "build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/da/ea/4919131850d1574fbbbd3c467115", "build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/17/6b/75952b2eb13698a1c94450bbf784", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/3e/83/bcae843016e117b41b5818fe7d0d", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/c9/1a/c33805d339a238e03f2e3fb25610", "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/97/cb/549865ac23bad6e634bf2480afc4", - "build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/a4/de/c1b778ab19ec0a5945646e0c4c1a", - "build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/d7/63/db7b96cf25d7afb52f6bf3efc575", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/1f/58/d05dafad5956a810dbfdd8802c0d", - "build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/86/7c/9a5fc35135a29915416fd300211c", + "build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/93/c3/859c24fb18f2682e00a4dfb3f705", + "build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/af/df/719d1743258636ebbcd5a042636c", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/0e/81/d26a244e16275e91663c66d24295", + "build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/b7/0f/e4fafe596723f513f33bc3fd121d", "build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/22/b3/1dbcde06de6e6107f05bc054c63f", "build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/64/e2/0ed77a64f73a88087b3e59afe068", "build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f6/4d/a6df77b6554a6d2fcce0da8cd6f9", @@ -4028,14 +4028,14 @@ "build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/96/47/a95bf58c8ee9b1a6cf16c0bce38e", "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/65/aa/0b3de0dec8931143e93095030061", "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/fe/e9/59f07ea132235d66772728a3f444", - "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/fe/32/e853d0c6babe75a9cd13c3d1ad74", - "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/9c/c4/051b70035701c59410806b4a88f4", - "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/8a/c7/df085272821c0ecd94285b3b7c8c", - "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/23/f9/e19797b50315d1c1db90aae74252", - "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/ef/5c/d76a648c4113f0cac7f8025d8151", - "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/89/6b/2d5abd4ed36aacc160578ba1346c", - "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/49/a7/1b287c00b6c0797347556a5ce4bf", - "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/43/0d/f0edc7b954a32d06f2b6f8f473d0", + "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/f2/f6/9375e455fa8ea5ebfd745da986eb", + "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/a1/0d/6ec5956f134d63097edb47f6b999", + "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/f1/5f/4a079cab37d3ddeb15e6223d338d", + "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/44/01/179f72c862b7e13bf0f2353100dd", + "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/13/9b/863b7052d8e99d53dd4c54b9e72c", + "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/a9/8d/1d520d9fc7e4284b6f09b5fcc8ff", + "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/92/16/68afcc43eba0f06b3c804a293437", + "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/ec/e7/7d92bd291b606cc32058107314ba", "src/ballistica/generated/python_embedded/binding.inc": "https://files.ballistica.net/cache/ba1/6e/6f/004b696e9a13b083069374e4bb6a", "src/ballistica/generated/python_embedded/bootstrap.inc": "https://files.ballistica.net/cache/ba1/d3/db/e73d4dcf1280d5f677c3cf8b47c3" } \ No newline at end of file diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml index 93b02803..7cd468d9 100644 --- a/.idea/dictionaries/ericf.xml +++ b/.idea/dictionaries/ericf.xml @@ -793,6 +793,7 @@ exportoptions exportoptionspath exporttype + extradata extraflagmat extrahash extrascale @@ -1805,6 +1806,7 @@ playerpts playerrec playerspaz + playerspaztype playerteamdata playertype playerval @@ -2393,6 +2395,7 @@ subvalue subvaluetype successfull + successmsg suiciding sunau suter @@ -2446,6 +2449,8 @@ tcls tcombine tdelay + tdels + tdlds tdval teambasesession teamdata @@ -2578,6 +2583,7 @@ tself tspc tstr + tunmd tupleval turtledemo tval @@ -2766,6 +2772,7 @@ writeclasses writefuncs wslpath + wspath wsroot wtcolor wtflib diff --git a/assets/src/ba_data/python/ba/_player.py b/assets/src/ba_data/python/ba/_player.py index 965213dd..3e56fcfe 100644 --- a/assets/src/ba_data/python/ba/_player.py +++ b/assets/src/ba_data/python/ba/_player.py @@ -13,7 +13,7 @@ from ba._error import (SessionPlayerNotFoundError, print_exception, from ba._messages import DeathType, DieMessage if TYPE_CHECKING: - from typing import Optional, Sequence, Any, Union, Callable + from typing import Sequence, Any, Callable import ba # pylint: disable=invalid-name @@ -39,7 +39,7 @@ class StandLocation: Category: Gameplay Classes """ position: ba.Vec3 - angle: Optional[float] = None + angle: float | None = None class Player(Generic[TeamType]): @@ -56,7 +56,7 @@ class Player(Generic[TeamType]): # their type annotations are introspectable (for docs generation). character: str - actor: Optional[ba.Actor] + actor: ba.Actor | None """The ba.Actor associated with the player.""" color: Sequence[float] @@ -64,7 +64,7 @@ class Player(Generic[TeamType]): _team: TeamType _sessionplayer: ba.SessionPlayer - _nodeactor: Optional[ba.NodeActor] + _nodeactor: ba.NodeActor | None _expired: bool _postinited: bool _customdata: dict @@ -94,7 +94,7 @@ class Player(Generic[TeamType]): self.actor = None self.character = '' - self._nodeactor: Optional[ba.NodeActor] = None + self._nodeactor: ba.NodeActor | None = None self._sessionplayer = sessionplayer self.character = sessionplayer.character self.color = sessionplayer.color @@ -249,8 +249,7 @@ class Player(Generic[TeamType]): assert not self._expired return self._sessionplayer.get_icon() - def assigninput(self, inputtype: Union[ba.InputType, tuple[ba.InputType, - ...]], + def assigninput(self, inputtype: ba.InputType | tuple[ba.InputType, ...], call: Callable) -> None: """ Set the python callable to be run for one or more types of input. @@ -313,7 +312,7 @@ def playercast(totype: type[PlayerType], player: ba.Player) -> PlayerType: # for the optional variety, but that currently seems to not be working. # See: https://github.com/python/mypy/issues/8800 def playercast_o(totype: type[PlayerType], - player: Optional[ba.Player]) -> Optional[PlayerType]: + player: ba.Player | None) -> PlayerType | None: """A variant of ba.playercast() for use with optional ba.Player values. Category: Gameplay Functions diff --git a/ballisticacore-cmake/.idea/dictionaries/ericf.xml b/ballisticacore-cmake/.idea/dictionaries/ericf.xml index 84d93502..c0cb9fc6 100644 --- a/ballisticacore-cmake/.idea/dictionaries/ericf.xml +++ b/ballisticacore-cmake/.idea/dictionaries/ericf.xml @@ -403,6 +403,7 @@ expbool expectedsig expl + extradata extrahash extrascale exts @@ -925,6 +926,7 @@ pipvers pixelformat platstr + playerspaztype playpause playsound plen @@ -1222,6 +1224,7 @@ subscale subscr subtypestr + successmsg sval swiftc symbolification @@ -1233,6 +1236,8 @@ targs tasklabel tcls + tdels + tdlds tegra telefonaktiebolaget teleported @@ -1292,6 +1297,7 @@ trimeshes trynum tself + tunmd tupleval tval tvos @@ -1393,6 +1399,7 @@ woutdir wprjp writeauxiliaryfile + wspath wsroot wunused wvmpth diff --git a/tests/test_efro/test_rpc.py b/tests/test_efro/test_rpc.py index a2dbca5d..ddf50489 100644 --- a/tests/test_efro/test_rpc.py +++ b/tests/test_efro/test_rpc.py @@ -38,12 +38,15 @@ class _MessageType(Enum): RESPONSE2 = 'r2' TEST_SLOW = 'ts' RESPONSE_SLOW = 'rs' + TEST_BIG = 'tb' + RESPONSE_BIG = 'rb' @ioprepped @dataclass class _Message: messagetype: _MessageType + extradata: bytes = b'' class _ServerClientCommon: @@ -88,6 +91,11 @@ class _ServerClientCommon: await asyncio.sleep(SLOW_WAIT) return _Message(_MessageType.RESPONSE_SLOW) + if msg.messagetype is _MessageType.TEST_BIG: + # 5 Mb Response + return _Message(_MessageType.RESPONSE_BIG, + extradata=bytes(bytearray(1024 * 1024 * 5))) + raise RuntimeError(f'Got unexpected message type: {msg.messagetype}') async def _handle_raw_message(self, message: bytes) -> bytes: @@ -301,6 +309,16 @@ def test_simple_messages() -> None: resp = await tester.client.send_message(_Message(_MessageType.TEST2)) assert resp.messagetype is _MessageType.RESPONSE2 + resp = await tester.server.send_message( + _Message(_MessageType.TEST_BIG, + extradata=bytes(bytearray(1024 * 1024 * 5)))) + assert resp.messagetype is _MessageType.RESPONSE_BIG + + resp = await tester.client.send_message( + _Message(_MessageType.TEST_BIG, + extradata=bytes(bytearray(1024 * 1024 * 5)))) + assert resp.messagetype is _MessageType.RESPONSE_BIG + tester.run(_do_it()) diff --git a/tools/efro/rpc.py b/tools/efro/rpc.py index 3484efff..1e72012d 100644 --- a/tools/efro/rpc.py +++ b/tools/efro/rpc.py @@ -33,6 +33,8 @@ class _PacketType(Enum): KEEPALIVE = 1 MESSAGE = 2 RESPONSE = 3 + MESSAGE_BIG = 4 + RESPONSE_BIG = 5 _BYTE_ORDER: Literal['big'] = 'big' @@ -49,7 +51,13 @@ class _PeerInfo: keepalive_interval: Annotated[float, IOAttrs('k')] -OUR_PROTOCOL = 1 +# Note: we are expected to be forward and backward compatible; we can +# increment protocol freely and expect everyone else to still talk to us. +# Likewise we should retain logic to communicate with older protocols. +# Protocol history: +# 1 - initial release +# 2 - gained big (32-bit len val) package/response packets +OUR_PROTOCOL = 2 class _InFlightMessage: @@ -201,21 +209,38 @@ class RPCEndpoint: for any reason. """ self._check_env() - if len(message) > 65535: - raise RuntimeError('Message cannot be larger than 65535 bytes') if self._closing: raise CommunicationError('Endpoint is closed') - # Go with 16 bit looping value for message_id. + # We need to know their protocol, so if we haven't gotten a handshake + # from them yet, just wait. + while self._peer_info is None: + await asyncio.sleep(0.01) + assert self._peer_info is not None + + if self._peer_info.protocol == 1: + if len(message) > 65535: + raise RuntimeError('Message cannot be larger than 65535 bytes') + + # message_id is a 16 bit looping value. message_id = self._next_message_id self._next_message_id = (self._next_message_id + 1) % 65536 - # Payload consists of type (1b), message_id (2b), len (2b), and data. - self._enqueue_outgoing_packet( - _PacketType.MESSAGE.value.to_bytes(1, _BYTE_ORDER) + - message_id.to_bytes(2, _BYTE_ORDER) + - len(message).to_bytes(2, _BYTE_ORDER) + message) + if len(message) > 65535: + # Payload consists of type (1b), message_id (2b), + # len (4b), and data. + self._enqueue_outgoing_packet( + _PacketType.MESSAGE_BIG.value.to_bytes(1, _BYTE_ORDER) + + message_id.to_bytes(2, _BYTE_ORDER) + + len(message).to_bytes(4, _BYTE_ORDER) + message) + else: + # Payload consists of type (1b), message_id (2b), + # len (2b), and data. + self._enqueue_outgoing_packet( + _PacketType.MESSAGE.value.to_bytes(1, _BYTE_ORDER) + + message_id.to_bytes(2, _BYTE_ORDER) + + len(message).to_bytes(2, _BYTE_ORDER) + message) # Make an entry so we know this message is out there. assert message_id not in self._in_flight_messages @@ -381,17 +406,27 @@ class RPCEndpoint: self._last_keepalive_receive_time = time.monotonic() elif mtype is _PacketType.MESSAGE: - await self._handle_message_packet() + await self._handle_message_packet(big=False) + + elif mtype is _PacketType.MESSAGE_BIG: + await self._handle_message_packet(big=True) elif mtype is _PacketType.RESPONSE: - await self._handle_response_packet() + await self._handle_response_packet(big=False) + + elif mtype is _PacketType.RESPONSE_BIG: + await self._handle_response_packet(big=True) else: assert_never(mtype) - async def _handle_message_packet(self) -> None: + async def _handle_message_packet(self, big: bool) -> None: + assert self._peer_info is not None msgid = await self._read_int_16() - msglen = await self._read_int_16() + if big: + msglen = await self._read_int_32() + else: + msglen = await self._read_int_16() msg = await self._reader.readexactly(msglen) if self._debug_print_io: self._debug_print_call(f'{self._label}: received message {msgid}' @@ -408,9 +443,14 @@ class RPCEndpoint: self._debug_print_call( f'{self._label}: done handling message at {self._tm()}.') - async def _handle_response_packet(self) -> None: + async def _handle_response_packet(self, big: bool) -> None: + assert self._peer_info is not None msgid = await self._read_int_16() - rsplen = await self._read_int_16() + # Protocol 2 gained 32 bit data lengths. + if big: + rsplen = await self._read_int_32() + else: + rsplen = await self._read_int_16() if self._debug_print_io: self._debug_print_call(f'{self._label}: received response {msgid}' f' of size {rsplen} at {self._tm()}.') @@ -520,12 +560,25 @@ class RPCEndpoint: logging.exception('Error handling message') return + assert self._peer_info is not None + + if self._peer_info.protocol == 1: + if len(response) > 65535: + raise RuntimeError( + 'Response cannot be larger than 65535 bytes') + # Now send back our response. # Payload consists of type (1b), msgid (2b), len (2b), and data. - self._enqueue_outgoing_packet( - _PacketType.RESPONSE.value.to_bytes(1, _BYTE_ORDER) + - message_id.to_bytes(2, _BYTE_ORDER) + - len(response).to_bytes(2, _BYTE_ORDER) + response) + if len(response) > 65535: + self._enqueue_outgoing_packet( + _PacketType.RESPONSE_BIG.value.to_bytes(1, _BYTE_ORDER) + + message_id.to_bytes(2, _BYTE_ORDER) + + len(response).to_bytes(4, _BYTE_ORDER) + response) + else: + self._enqueue_outgoing_packet( + _PacketType.RESPONSE.value.to_bytes(1, _BYTE_ORDER) + + message_id.to_bytes(2, _BYTE_ORDER) + + len(response).to_bytes(2, _BYTE_ORDER) + response) async def _read_int_8(self) -> int: return int.from_bytes(await self._reader.readexactly(1), _BYTE_ORDER)