perf(net): frame recurring sends in place
Write normal game-message and ACK packet framing directly into bounded stack spans, hash fragments without materialization, and send the populated slice to the socket. Preserve exact wire bytes and ISAAC failure ordering with differential and zero-allocation tests.
This commit is contained in:
parent
e928c5dd02
commit
41c1a59392
7 changed files with 485 additions and 31 deletions
|
|
@ -146,8 +146,10 @@ H-c is executed as three independently reversible units:
|
|||
fields, and fragments as borrowed views. Copy only multi-fragment state
|
||||
that must survive the current datagram. Evidence:
|
||||
[`../research/2026-07-25-slice-h-c2-borrowed-packet-decode.md`](../research/2026-07-25-slice-h-c2-borrowed-packet-decode.md).
|
||||
3. **H-c3 — direct outbound framing — PENDING.** Write packet and fragment
|
||||
3. **H-c3 — direct outbound framing — COMPLETE.** Write packet and fragment
|
||||
framing into caller storage and remove intermediate payload arrays.
|
||||
Evidence:
|
||||
[`../research/2026-07-25-slice-h-c3-direct-outbound-framing.md`](../research/2026-07-25-slice-h-c3-direct-outbound-framing.md).
|
||||
|
||||
Retail/transport invariants:
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
# Slice H-c3 — direct outbound framing
|
||||
|
||||
## Result
|
||||
|
||||
Recurring game messages and ACKs now go from caller bytes to the UDP socket
|
||||
without a managed framing allocation.
|
||||
|
||||
For a normal game message, `WorldSession` reserves one 484-byte stack span:
|
||||
|
||||
```text
|
||||
20-byte PacketHeader
|
||||
16-byte MessageFragmentHeader
|
||||
up to 448 bytes of existing GameMessage payload
|
||||
```
|
||||
|
||||
`GameMessageFragment.WriteSingleFragment` writes the fragment header and
|
||||
payload directly after the packet-header reservation.
|
||||
`PacketCodec.FinalizeInPlace` validates and hashes that body, consumes the
|
||||
outbound ISAAC word, and writes the fixed header into the reserved prefix.
|
||||
`NetClient.Send` then passes only the populated slice to `Socket.SendTo`.
|
||||
|
||||
ACKs use the same mechanism with one 24-byte stack span: 20 header bytes plus
|
||||
the four-byte acknowledged server sequence.
|
||||
|
||||
The retained public `BuildSingleFragment`, `Serialize`, and `PacketCodec.Encode`
|
||||
APIs remain available for fixtures and infrequent callers. `Encode` now shares
|
||||
the span-based fragment hashing primitive, so it no longer materializes a
|
||||
fragment payload merely to calculate its checksum.
|
||||
|
||||
## Behavior and failure ordering
|
||||
|
||||
- packet, fragment, and game-action sequence behavior is unchanged;
|
||||
- fragment `Id`, `Count`, `Index`, `TotalSize`, and queue bytes are unchanged;
|
||||
- ACKs still reuse the most recently issued client packet sequence;
|
||||
- encrypted packets consume exactly one ISAAC word after complete structural
|
||||
validation;
|
||||
- a malformed fragment throws before consuming the ISAAC stream;
|
||||
- the synchronous socket call completes before its stack storage expires;
|
||||
- messages above retail's 448-byte single-fragment payload limit retain the
|
||||
existing explicit failure rather than silently truncating or inventing a
|
||||
split policy.
|
||||
|
||||
This is storage/mechanical work around the established retail/ACE wire
|
||||
contract. It does not change AC gameplay or packet-order behavior.
|
||||
|
||||
## Deterministic evidence
|
||||
|
||||
Tests prove:
|
||||
|
||||
- the direct fragment writer is byte-identical to the existing owned
|
||||
build/serialize path;
|
||||
- direct encrypted game-message framing is byte-identical to the owned path
|
||||
with independently seeded ISAAC instances;
|
||||
- direct ACK framing is byte-identical to the owned path;
|
||||
- malformed input leaves the ISAAC stream untouched;
|
||||
- short destinations fail before writing past their boundary;
|
||||
- the warmed direct framing path allocates zero bytes over 1,000 iterations.
|
||||
|
||||
A Release microbenchmark framed 500,000 representative 44-byte game messages:
|
||||
|
||||
| Framing path | Time | Throughput cost | Allocated |
|
||||
|---|---:|---:|---:|
|
||||
| owned intermediates | 137.206 ms | 274.4 ns/message | 176,000,040 bytes |
|
||||
| direct stack span | 12.551 ms | 25.1 ns/message | 0 bytes |
|
||||
|
||||
That is a 10.932x framing microbenchmark speedup and removes 352 managed bytes
|
||||
per representative outbound message.
|
||||
|
||||
## Slice H-c remaining gate
|
||||
|
||||
The code units are complete. H-c closeout requires connected login, world
|
||||
entry, ACK continuity, reconnect, portal, interaction, and graceful-disconnect
|
||||
coverage on the available RDP session. RDP is valid for network correctness
|
||||
but not for final GPU/frame-time performance acceptance.
|
||||
Loading…
Add table
Add a link
Reference in a new issue