pub const DEFAULT_CHUNK_BYTES: usize = 64 * 1024;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct ChunkGeometry {
slot_payload_bytes: usize,
slots_per_lane: usize,
}
impl ChunkGeometry {
pub const fn new(
slot_payload_bytes: usize,
slots_per_lane: usize,
) -> Option<Self> {
if slot_payload_bytes == 0 || slots_per_lane == 0 {
None
} else {
Some(Self { slot_payload_bytes, slots_per_lane })
}
}
pub const fn slot_payload_bytes(self) -> usize {
self.slot_payload_bytes
}
pub const fn slots_per_lane(self) -> usize {
self.slots_per_lane
}
pub const fn chunk_bytes(
self,
slots: usize,
) -> Option<usize> {
if slots == 0 || slots > self.slots_per_lane {
None
} else {
self.slot_payload_bytes.checked_mul(slots)
}
}
pub const fn slots_for_target(
self,
target_bytes: usize,
) -> Option<usize> {
if target_bytes == 0 {
return None;
}
let slots = target_bytes / self.slot_payload_bytes;
let slots = if slots == 0 { 1 } else { slots };
Some(if slots > self.slots_per_lane {
self.slots_per_lane
} else {
slots
})
}
pub const fn chunk_bytes_for_target(
self,
target_bytes: usize,
) -> Option<usize> {
let slots = match self.slots_for_target(target_bytes) {
Some(slots) => slots,
None => return None,
};
self.chunk_bytes(slots)
}
pub const fn plan(
self,
data_bytes: usize,
slots_per_chunk: usize,
) -> Option<ChunkPlan> {
let chunk_bytes = match self.chunk_bytes(slots_per_chunk) {
Some(bytes) => bytes,
None => return None,
};
ChunkPlan::for_limit(data_bytes, self.slot_payload_bytes, chunk_bytes)
}
pub const fn plan_for_target(
self,
data_bytes: usize,
target_bytes: usize,
) -> Option<ChunkPlan> {
let slots = match self.slots_for_target(target_bytes) {
Some(slots) => slots,
None => return None,
};
self.plan(data_bytes, slots)
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct ChunkPlan {
data_bytes: usize,
chunk_bytes: usize,
chunk_count: usize,
slots_per_chunk: usize,
tail_bytes: usize,
tail_slots: usize,
}
impl ChunkPlan {
pub const fn new(
data_bytes: usize,
slot_bytes: usize,
) -> Option<Self> {
Self::for_limit(data_bytes, slot_bytes, DEFAULT_CHUNK_BYTES)
}
pub const fn for_slots(
data_bytes: usize,
slot_payload_bytes: usize,
slots_per_chunk: usize,
) -> Option<Self> {
let geometry =
match ChunkGeometry::new(slot_payload_bytes, slots_per_chunk) {
Some(geometry) => geometry,
None => return None,
};
geometry.plan(data_bytes, slots_per_chunk)
}
pub(crate) const fn for_limit(
data_bytes: usize,
slot_bytes: usize,
limit_bytes: usize,
) -> Option<Self> {
if slot_bytes == 0 || limit_bytes == 0 {
return None;
}
let target =
if limit_bytes < slot_bytes { slot_bytes } else { limit_bytes };
let chunk_bytes = (target / slot_bytes) * slot_bytes;
if data_bytes == 0 {
return Some(Self {
data_bytes,
chunk_bytes,
chunk_count: 0,
slots_per_chunk: chunk_bytes / slot_bytes,
tail_bytes: 0,
tail_slots: 0,
});
}
let chunk_count = data_bytes.div_ceil(chunk_bytes);
let remainder = data_bytes % chunk_bytes;
let tail_bytes = if remainder == 0 { chunk_bytes } else { remainder };
Some(Self {
data_bytes,
chunk_bytes,
chunk_count,
slots_per_chunk: chunk_bytes / slot_bytes,
tail_bytes,
tail_slots: tail_bytes.div_ceil(slot_bytes),
})
}
pub const fn data_bytes(self) -> usize {
self.data_bytes
}
pub const fn chunk_bytes(self) -> usize {
self.chunk_bytes
}
pub const fn chunk_count(self) -> usize {
self.chunk_count
}
pub const fn slots_per_chunk(self) -> usize {
self.slots_per_chunk
}
pub const fn tail_bytes(self) -> usize {
self.tail_bytes
}
pub const fn tail_slots(self) -> usize {
self.tail_slots
}
pub const fn chunk_len(
self,
index: usize,
) -> Option<usize> {
if index >= self.chunk_count {
None
} else if index + 1 == self.chunk_count {
Some(self.tail_bytes)
} else {
Some(self.chunk_bytes)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_plan_uses_benchmark_chunk_and_exact_tail() {
let plan = ChunkPlan::new(150 * 1024, 4 * 1024).expect("plan");
assert_eq!(plan.chunk_bytes(), 64 * 1024);
assert_eq!(plan.slots_per_chunk(), 16);
assert_eq!(plan.chunk_count(), 3);
assert_eq!(plan.tail_bytes(), 22 * 1024);
assert_eq!(plan.tail_slots(), 6);
assert_eq!(plan.chunk_len(0), Some(64 * 1024));
assert_eq!(plan.chunk_len(2), Some(22 * 1024));
assert_eq!(plan.chunk_len(3), None);
}
#[test]
fn a_slot_larger_than_the_default_is_never_split_artificially() {
let plan = ChunkPlan::new(1024 * 1024, 128 * 1024).expect("plan");
assert_eq!(plan.chunk_bytes(), 128 * 1024);
assert_eq!(plan.slots_per_chunk(), 1);
assert_eq!(plan.chunk_count(), 8);
}
#[test]
fn small_and_empty_payloads_are_well_defined() {
let small = ChunkPlan::new(1024, 4 * 1024).expect("plan");
assert_eq!(small.chunk_count(), 1);
assert_eq!(small.tail_bytes(), 1024);
assert_eq!(small.tail_slots(), 1);
let empty = ChunkPlan::new(0, 4 * 1024).expect("plan");
assert_eq!(empty.chunk_count(), 0);
assert_eq!(empty.tail_bytes(), 0);
}
#[test]
fn arena_plan_clamps_full_chunks_to_one_lane() {
let spec = crate::exchange::PayloadArenaSpec::new(200, 4, 4 * 1024);
let plan = spec.plan_chunks(1024 * 1024).expect("plan");
assert_eq!(plan.chunk_bytes(), 16 * 1024);
assert_eq!(plan.slots_per_chunk(), 4);
assert_eq!(plan.chunk_count(), 64);
}
#[test]
fn runtime_slot_count_defines_exact_chunk_capacity() {
let geometry = ChunkGeometry::new(4096, 32).expect("geometry");
assert_eq!(geometry.slot_payload_bytes(), 4096);
assert_eq!(geometry.chunk_bytes(7), Some(7 * 4096));
assert_eq!(geometry.chunk_bytes(0), None);
assert_eq!(geometry.chunk_bytes(33), None);
let plan = geometry.plan(70_000, 7).expect("plan");
assert_eq!(plan.chunk_bytes(), 7 * 4096);
assert_eq!(plan.slots_per_chunk(), 7);
assert_eq!(plan.chunk_count(), 3);
}
#[test]
fn byte_target_is_aligned_and_clamped_to_runtime_geometry() {
let geometry = ChunkGeometry::new(4096, 8).expect("geometry");
assert_eq!(geometry.slots_for_target(1), Some(1));
assert_eq!(geometry.slots_for_target(10_000), Some(2));
assert_eq!(geometry.slots_for_target(1024 * 1024), Some(8));
assert_eq!(geometry.chunk_bytes_for_target(10_000), Some(8192));
assert_eq!(geometry.chunk_bytes_for_target(1024 * 1024), Some(32_768));
assert_eq!(geometry.slots_for_target(0), None);
}
}