acdream/docs/research/2026-07-25-slice-h-c2-borrowed-packet-decode.md
Erik e928c5dd02 perf(net): borrow inbound packet storage
Decode headers, optional fields, fragments, and single-fragment messages directly over pooled datagrams. Copy only fragment state that crosses a datagram lifetime, preserve synchronous dispatch and ACK ordering, and lock the path to the owned decoder with differential and zero-allocation tests.
2026-07-25 05:58:55 +02:00

3.7 KiB

Slice H-c2 — borrowed packet decode

Result

The production receive path no longer materializes an owned Packet, PacketHeaderOptional, BodyBytes, fragment list, or single-fragment payload array for every accepted UDP datagram. It now decodes one borrowed view over the right-sized pooled datagram:

pooled datagram
    -> borrowed packet/header/optional views
    -> allocation-free fragment enumeration
    -> single-fragment message borrows the same storage
    -> synchronous message/event parse and dispatch
    -> pooled datagram returned

Only a multi-fragment logical message copies its fragment payloads. That copy is required because those bytes must survive beyond the current datagram while the remaining fragments arrive.

Lifetime contract

BorrowedPacket, BorrowedOptionalHeader, and BorrowedMessageFragment are internal types. Their memory is valid only while the caller retains the datagram owner. WorldSession.ProcessDatagram performs parsing and event dispatch synchronously before returning that owner.

GameEventEnvelope.TryParseBorrowed is likewise internal. Registered event handlers must parse or copy durable state during Dispatch; they must not retain the envelope or payload. The existing public byte-array parser remains available for independently owned fixtures and callers.

Multi-fragment assembly copies on first acceptance, not on completion, so returning either source datagram cannot invalidate the partial message. Conflicting Count or Queue values for a reused sequence cannot corrupt an accepted partial. Duplicate fragments remain idempotent.

Behavior preserved

  • optional-header field order exactly matches PacketHeaderOptional.Parse;
  • unencrypted and ISAAC checksum verification consume the same bytes and key;
  • packet ACK and last-heard placement in WorldSession are unchanged;
  • fragment grouping remains keyed by message Sequence;
  • complete messages are delivered in the same fragment-index order;
  • game-event handlers remain synchronous on the update/render thread;
  • malformed zero-count and out-of-range-index fragments are rejected before assembly rather than indexing invalid storage.

Named-retail CNetLayerPacket remains the packet-size reference. ACE's packet parser and Holtburger's inbound fragment path are the wire/order cross-references; this slice changes .NET storage ownership only.

Deterministic evidence

The differential suite feeds the owned and borrowed decoders identical datagrams and compares:

  • all optional fields in their retail wire order;
  • retransmit sequence order;
  • login payload coverage;
  • one and multiple fragments;
  • body, optional, fragment-header, and fragment-payload bytes;
  • matching seeded ISAAC checksum consumption;
  • every malformed-packet error category touched by this slice.

Fragment tests prove that a single-fragment result aliases its source storage, while a multi-fragment result survives mutations of both source datagrams. The warmed single-fragment decode/enumeration unit test measures zero current-thread allocation over 1,000 iterations.

A Release microbenchmark over 500,000 valid 48-byte, single-fragment packets measured:

Decoder Time Throughput cost Allocated
owned 246.372 ms 492.7 ns/packet 200,000,040 bytes
borrowed 28.431 ms 56.9 ns/packet 40 bytes

That is an 8.666x decoder microbenchmark speedup and removes the warmed per-packet allocation (the reported 40 bytes are the benchmark timer).

Remaining H-c work

H-c3 removes outbound packet/fragment intermediate arrays by writing the exact wire framing directly into caller-provided storage. H-c closeout then runs the connected lifecycle, reconnect, portal, interaction, and soak gates.