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.
88 lines
3.7 KiB
Markdown
88 lines
3.7 KiB
Markdown
# 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:
|
|
|
|
```text
|
|
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.
|