Protocol/Server: hybrid body TLV (2-bit vs RLE), state_full body builder, and partitioned snapshot on join
- Protocol: RLE packing, TLV chooser, body_2bit_chunk helper, state_full_body builder - Server: on join, build snapshot body and partition across PART packets if over MTU; include apples only in first part; chunk single oversized snake with 2-bit chunking
This commit is contained in:
@@ -192,6 +192,37 @@ def bitpack_2bit_directions(directions: Iterable[Direction]) -> bytes:
|
||||
return bytes(out)
|
||||
|
||||
|
||||
def rle_pack_directions(directions: Sequence[Direction]) -> bytes:
|
||||
"""Pack directions as runs: dir (u8) + count (QUIC varint >=1)."""
|
||||
if not directions:
|
||||
return b""
|
||||
out = bytearray()
|
||||
run_dir = directions[0]
|
||||
run_len = 1
|
||||
for d in directions[1:]:
|
||||
if d == run_dir:
|
||||
run_len += 1
|
||||
else:
|
||||
out.append(int(run_dir) & 0xFF)
|
||||
out.extend(quic_varint_encode(run_len))
|
||||
run_dir = d
|
||||
run_len = 1
|
||||
out.append(int(run_dir) & 0xFF)
|
||||
out.extend(quic_varint_encode(run_len))
|
||||
return bytes(out)
|
||||
|
||||
|
||||
def build_body_tlv_for_dirs(directions: Sequence[Direction]) -> bytes:
|
||||
"""Choose the more compact of 2-bit stream vs RLE for given directions and return its TLV."""
|
||||
# 2-bit body size
|
||||
two_bit_payload = bitpack_2bit_directions(directions)
|
||||
two_bit = pack_body_tlv(BodyTLV.BODY_2BIT, two_bit_payload)
|
||||
# RLE
|
||||
rle_payload = rle_pack_directions(directions)
|
||||
rle = pack_body_tlv(BodyTLV.BODY_RLE, rle_payload)
|
||||
return two_bit if len(two_bit) <= len(rle) else rle
|
||||
|
||||
|
||||
# Join / Ack / Deny
|
||||
|
||||
def build_join(name_utf8: bytes, preferred_color_id: int | None = None) -> bytes:
|
||||
@@ -280,6 +311,13 @@ def build_state_full(
|
||||
apples: sequence of (x, y)
|
||||
"""
|
||||
header = pack_header(version, PacketType.STATE_FULL, 0, seq, tick & 0xFFFF)
|
||||
body = build_state_full_body(snakes=snakes, apples=apples)
|
||||
return header + body
|
||||
|
||||
|
||||
def build_state_full_body(
|
||||
*, snakes: Sequence[Tuple[int, int, int, int, Sequence[Direction]]], apples: Sequence[Tuple[int, int]]
|
||||
) -> bytes:
|
||||
body = bytearray()
|
||||
# snakes count
|
||||
body.extend(quic_varint_encode(len(snakes)))
|
||||
@@ -288,15 +326,29 @@ def build_state_full(
|
||||
body.extend(int(slen & 0xFFFF).to_bytes(2, "big"))
|
||||
body.append(hx & 0xFF)
|
||||
body.append(hy & 0xFF)
|
||||
payload = bitpack_2bit_directions(dirs)
|
||||
tlv = pack_body_tlv(BodyTLV.BODY_2BIT, payload)
|
||||
tlv = build_body_tlv_for_dirs(dirs)
|
||||
body.extend(tlv)
|
||||
# apples
|
||||
body.extend(quic_varint_encode(len(apples)))
|
||||
for ax, ay in apples:
|
||||
body.append(ax & 0xFF)
|
||||
body.append(ay & 0xFF)
|
||||
return header + bytes(body)
|
||||
return bytes(body)
|
||||
|
||||
|
||||
def build_body_2bit_chunk(directions: Sequence[Direction], start_index: int, dirs_in_chunk: int) -> bytes:
|
||||
"""Build chunk TLV for a sub-range of directions (2-bit)."""
|
||||
sub = directions[start_index : start_index + dirs_in_chunk]
|
||||
payload = bytearray()
|
||||
payload.extend(int(start_index & 0xFFFF).to_bytes(2, "big"))
|
||||
payload.extend(int(dirs_in_chunk & 0xFFFF).to_bytes(2, "big"))
|
||||
payload.extend(bitpack_2bit_directions(sub))
|
||||
return pack_body_tlv(BodyTLV.BODY_2BIT_CHUNK, bytes(payload))
|
||||
|
||||
|
||||
def estimate_body_2bit_bytes(snake_len: int) -> int:
|
||||
used_bits = max(0, (snake_len - 1) * 2)
|
||||
return (used_bits + 7) // 8
|
||||
|
||||
|
||||
# --- State Delta ---
|
||||
|
||||
Reference in New Issue
Block a user