feat(core): zero-copy pooled reassembly — shards land at their final AU offset
Rewrite the client Reassembler around one whole-frame buffer per frame: frame_bytes rides in every header and packetize geometry is deterministic (every non-final block is exactly max_data_per_block data shards), so a data shard's final AU offset is computable on arrival — copy it there once, straight from the decrypt ring. New ErasureCoder::reconstruct_into decodes ONLY the missing shards directly into the frame buffer's holes (gf16 native; gf8 legacy shim); received recovery shards ride pooled shard-sized buffers. The completed buffer IS Frame::data. Deletes the per-shard to_vec + per-block concat + final AU concat (~178k allocs and a double copy of every byte per second at 2 Gbps — the pump wall the 2026-07-14 sweeps measured at 98.9% of an M3 Ultra core). Reassembly now costs ~0.4 µs/packet in-stream. The eager buffer changes the hostile-header exposure, so two new firewalls: derived-geometry validation (a header lying about its data_shards/block_count vs its own frame_bytes is dropped before it can scribble across another shard's range) and an in-flight allocation budget (IN_FLIGHT_BUF_FACTOR × max_frame_bytes) so a window of tiny first-shards can't commit gigabytes. Behavior parity pinned by the existing suite (all green unchanged) plus new end-to-end roundtrips through the real Packetizer (multi-block + partial tail, loss within budget, reversed delivery, duplicates, empty frame, unrecoverable block ages out, budget enforcement). loss-harness recovery curve identical; pipeline bench: gf8/1MB +42%, gf16 neutral (host-encode dominated). Known pre-existing quirk kept as-is: reversed delivery reconstructs early (data+recovery ≥ k) and counts late-not-lost shards into fec_recovered_shards. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,9 @@
|
||||
//! shards/block — this is what removes the GameStream 255-shard / ~1 Gbps wall.
|
||||
//! Shard length must be even.
|
||||
|
||||
use super::{validate_block_shape, validate_encode_shape, ErasureCoder, FecError};
|
||||
use super::{
|
||||
validate_block_shape, validate_encode_shape, validate_into_shape, ErasureCoder, FecError,
|
||||
};
|
||||
use crate::config::FecScheme;
|
||||
|
||||
pub struct Gf16Coder;
|
||||
@@ -81,4 +83,46 @@ impl ErasureCoder for Gf16Coder {
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn reconstruct_into(
|
||||
&self,
|
||||
recovery_count: usize,
|
||||
data: &mut [&mut [u8]],
|
||||
have: &[bool],
|
||||
recovery: &[(usize, &[u8])],
|
||||
) -> Result<(), FecError> {
|
||||
validate_into_shape(data, have, recovery, recovery_count)?;
|
||||
if have.iter().all(|h| *h) {
|
||||
return Ok(()); // nothing missing — no codec work, no copies
|
||||
}
|
||||
if data[0].len() % 2 != 0 {
|
||||
return Err(FecError::Config("GF(2^16) shard length must be even"));
|
||||
}
|
||||
let data_count = data.len();
|
||||
// Present originals as indexed refs (shared reborrows of the caller's slots); the decoder
|
||||
// returns the restored shards owned, so the borrows end before the write-back below.
|
||||
let original_in: Vec<(usize, &[u8])> = data
|
||||
.iter()
|
||||
.zip(have)
|
||||
.enumerate()
|
||||
.filter(|(_, (_, &h))| h)
|
||||
.map(|(i, (s, _))| (i, &**s))
|
||||
.collect();
|
||||
let restored = reed_solomon_simd::decode(
|
||||
data_count,
|
||||
recovery_count,
|
||||
original_in,
|
||||
recovery.iter().copied(),
|
||||
)
|
||||
.map_err(|_| FecError::Backend("gf16 decode"))?;
|
||||
for (i, h) in have.iter().enumerate() {
|
||||
if !*h {
|
||||
let shard = restored
|
||||
.get(&i)
|
||||
.ok_or(FecError::Backend("gf16 decode left an original missing"))?;
|
||||
data[i].copy_from_slice(shard);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user