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)