Protocol/Server: implement STATE_DELTA + PART partitioning and per-tick minimal changes

- Protocol: SnakeDelta structure; build_state_delta(_body); build_part
- Server: compute per-snake changes (move/grow/blocked-shrink), apples diffs
- Partition large deltas by snake changes with apples in first part; use update_id
- Send STATE_DELTA when under MTU; else PART packets referencing STATE_DELTA
This commit is contained in:
Vladyslav Doloman
2025-10-07 20:30:48 +03:00
parent 991b8f3660
commit 967784542d
2 changed files with 183 additions and 28 deletions

View File

@@ -297,3 +297,80 @@ def build_state_full(
body.append(ax & 0xFF)
body.append(ay & 0xFF)
return header + bytes(body)
# --- State Delta ---
@dataclass
class SnakeDelta:
snake_id: int
head_moved: bool
tail_removed: bool
grew: bool
blocked: bool
new_head_x: int = 0
new_head_y: int = 0
direction: Direction = Direction.RIGHT
def build_state_delta_body(
*, update_id: int, changes: Sequence[SnakeDelta], apples_added: Sequence[Tuple[int, int]], apples_removed: Sequence[Tuple[int, int]]
) -> bytes:
body = bytearray()
body.extend(int(update_id & 0xFFFF).to_bytes(2, "big"))
# snakes
body.extend(quic_varint_encode(len(changes)))
for ch in changes:
body.append(ch.snake_id & 0xFF)
flags = (
(1 if ch.head_moved else 0)
| ((1 if ch.tail_removed else 0) << 1)
| ((1 if ch.grew else 0) << 2)
| ((1 if ch.blocked else 0) << 3)
)
body.append(flags & 0xFF)
body.append(int(ch.direction) & 0x03)
if ch.head_moved:
body.append(ch.new_head_x & 0xFF)
body.append(ch.new_head_y & 0xFF)
# apples added
body.extend(quic_varint_encode(len(apples_added)))
for ax, ay in apples_added:
body.append(ax & 0xFF)
body.append(ay & 0xFF)
# apples removed
body.extend(quic_varint_encode(len(apples_removed)))
for rx, ry in apples_removed:
body.append(rx & 0xFF)
body.append(ry & 0xFF)
return bytes(body)
def build_state_delta(
*, version: int, seq: int, tick: int, update_id: int, changes: Sequence[SnakeDelta], apples_added: Sequence[Tuple[int, int]], apples_removed: Sequence[Tuple[int, int]]
) -> bytes:
header = pack_header(version, PacketType.STATE_DELTA, 0, seq, tick & 0xFFFF)
body = build_state_delta_body(update_id=update_id, changes=changes, apples_added=apples_added, apples_removed=apples_removed)
return header + body
def build_part(
*,
version: int,
seq: int,
tick: int,
update_id: int,
part_index: int,
parts_total: int,
inner_type: PacketType,
chunk_payload: bytes,
) -> bytes:
header = pack_header(version, PacketType.PART, 0, seq, tick & 0xFFFF)
body = bytearray()
body.extend(int(update_id & 0xFFFF).to_bytes(2, "big"))
body.append(part_index & 0xFF)
body.append(parts_total & 0xFF)
body.append(int(inner_type) & 0xFF)
# include the chunk payload bytes
body.extend(chunk_payload)
return header + bytes(body)