use alloc::vec::Vec;
use core::mem::MaybeUninit;
const INLINE_CAP: usize = 16;
pub struct SizeCache {
inline: [MaybeUninit<u32>; INLINE_CAP],
spill: Vec<u32>,
len: u32,
cursor: u32,
}
impl core::fmt::Debug for SizeCache {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let inline_init = (self.len as usize).min(INLINE_CAP);
let inline =
unsafe { core::slice::from_raw_parts(self.inline.as_ptr().cast::<u32>(), inline_init) };
f.debug_struct("SizeCache")
.field("len", &self.len)
.field("cursor", &self.cursor)
.field("inline", &inline)
.field("spill", &self.spill)
.finish()
}
}
impl Default for SizeCache {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl SizeCache {
#[inline]
#[must_use]
pub const fn new() -> Self {
Self {
inline: [MaybeUninit::uninit(); INLINE_CAP],
spill: Vec::new(),
len: 0,
cursor: 0,
}
}
#[inline]
pub fn clear(&mut self) {
self.spill.clear();
self.len = 0;
self.cursor = 0;
}
#[inline]
#[must_use]
pub fn with_spill_buffer(mut spill: Vec<u32>) -> Self {
spill.clear();
Self {
inline: [MaybeUninit::uninit(); INLINE_CAP],
spill,
len: 0,
cursor: 0,
}
}
#[inline]
#[must_use]
pub fn into_spill_buffer(self) -> Vec<u32> {
self.spill
}
#[inline]
pub fn reserve(&mut self) -> usize {
debug_assert!(self.len < u32::MAX, "SizeCache slot count overflow");
let idx = self.len as usize;
if idx < INLINE_CAP {
self.inline[idx] = MaybeUninit::new(0);
} else {
self.spill.push(0);
}
self.len += 1;
idx
}
#[inline]
#[track_caller]
pub fn set(&mut self, idx: usize, size: u32) {
assert!(
idx < self.len as usize,
"SizeCache::set: slot {idx} not reserved (len {})",
self.len
);
if idx < INLINE_CAP {
self.inline[idx] = MaybeUninit::new(size);
} else {
self.spill[idx - INLINE_CAP] = size;
}
}
#[inline]
#[track_caller]
pub fn consume_next(&mut self) -> u32 {
let idx = self.cursor as usize;
if idx >= self.len as usize {
Self::overrun(idx, self.len);
}
self.cursor += 1;
if idx < INLINE_CAP {
unsafe { self.inline[idx].assume_init() }
} else {
self.spill[idx - INLINE_CAP]
}
}
#[cold]
#[inline(never)]
#[track_caller]
fn overrun(idx: usize, len: u32) -> ! {
panic!(
"SizeCache cursor overrun: write_to consumed {} slots but \
compute_size produced {len} (traversal-order mismatch)",
idx + 1,
)
}
}
#[derive(Debug)]
pub struct SizeCachePool {
free: Vec<Vec<u32>>,
max_buffers: usize,
max_capacity: usize,
}
impl SizeCachePool {
#[inline]
#[must_use]
pub const fn new(max_buffers: usize, max_capacity: usize) -> Self {
Self {
free: Vec::new(),
max_buffers,
max_capacity,
}
}
#[inline]
#[must_use]
pub const fn sequential(max_capacity: usize) -> Self {
Self::new(1, max_capacity)
}
#[inline]
#[must_use]
pub fn acquire(&mut self) -> SizeCache {
match self.free.pop() {
Some(buf) => SizeCache::with_spill_buffer(buf),
None => SizeCache::new(),
}
}
#[inline]
pub fn release(&mut self, cache: SizeCache) {
if self.free.len() >= self.max_buffers {
return;
}
let mut buf = cache.into_spill_buffer();
buf.clear();
if buf.capacity() > self.max_capacity {
buf.shrink_to(self.max_capacity);
}
if buf.capacity() == 0 {
return;
}
self.free.push(buf);
}
#[inline]
#[must_use]
pub fn encoded_len<M: crate::Message>(&mut self, msg: &M) -> u32 {
let mut cache = self.acquire();
let len = msg.compute_size(&mut cache);
self.release(cache);
len
}
#[inline]
pub fn encode<M: crate::Message>(&mut self, msg: &M, buf: &mut impl bytes::BufMut) {
let mut cache = self.acquire();
msg.encode_with_cache(&mut cache, buf);
self.release(cache);
}
#[inline]
pub fn encode_view<'a, V: crate::ViewEncode<'a>>(
&mut self,
view: &V,
buf: &mut impl bytes::BufMut,
) {
let mut cache = self.acquire();
view.encode_with_cache(&mut cache, buf);
self.release(cache);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_cache_is_default() {
let c = SizeCache::new();
assert_eq!(c.len, 0);
assert_eq!(c.cursor, 0);
assert!(c.spill.is_empty());
}
#[test]
fn spill_past_inline_cap_preserves_order() {
const N: usize = INLINE_CAP * 2 + 5;
let mut c = SizeCache::new();
let slots: alloc::vec::Vec<usize> = (0..N).map(|_| c.reserve()).collect();
for (i, &s) in slots.iter().enumerate().rev() {
c.set(s, i as u32 * 7);
}
assert_eq!(c.spill.len(), N - INLINE_CAP);
for i in 0..N {
assert_eq!(c.consume_next(), i as u32 * 7);
}
}
#[test]
fn boundary_at_inline_cap() {
let mut c = SizeCache::new();
for i in 0..INLINE_CAP {
let s = c.reserve();
c.set(s, i as u32);
}
assert!(c.spill.is_empty(), "no spill at exactly INLINE_CAP");
let s = c.reserve();
c.set(s, 999);
assert_eq!(c.spill.len(), 1);
for i in 0..INLINE_CAP {
assert_eq!(c.consume_next(), i as u32);
}
assert_eq!(c.consume_next(), 999);
}
#[test]
fn reserve_set_next_roundtrip() {
let mut c = SizeCache::new();
let s0 = c.reserve();
let s1 = c.reserve();
c.set(s0, 10);
c.set(s1, 20);
assert_eq!(c.consume_next(), 10);
assert_eq!(c.consume_next(), 20);
}
#[test]
fn preorder_reservation_with_nested_recursion() {
let mut c = SizeCache::new();
let slot_a = c.reserve();
let slot_x = c.reserve();
c.set(slot_x, 5);
c.set(slot_a, 7);
let slot_b = c.reserve();
c.set(slot_b, 3);
assert_eq!(c.consume_next(), 7); assert_eq!(c.consume_next(), 5); assert_eq!(c.consume_next(), 3); }
#[test]
fn clear_resets_and_retains_capacity() {
let mut c = SizeCache::new();
for _ in 0..(INLINE_CAP + 4) {
c.reserve();
}
let cap = c.spill.capacity();
assert!(cap >= 4);
c.clear();
assert_eq!(c.len, 0);
assert_eq!(c.cursor, 0);
assert!(c.spill.capacity() >= cap);
let s = c.reserve();
c.set(s, 99);
assert_eq!(c.consume_next(), 99);
}
#[test]
fn reserve_without_set_yields_zero() {
let mut c = SizeCache::new();
let _ = c.reserve();
assert_eq!(c.consume_next(), 0);
}
#[test]
fn clear_then_reserve_without_set_yields_zero() {
let mut c = SizeCache::new();
for i in 0..(INLINE_CAP + 3) {
let s = c.reserve();
c.set(s, (i + 100) as u32);
}
c.clear();
let _ = c.reserve();
assert_eq!(c.consume_next(), 0);
}
#[test]
#[should_panic(expected = "SizeCache cursor overrun")]
fn next_past_end_panics() {
let mut c = SizeCache::new();
c.consume_next();
}
#[test]
fn miri_soundness_interleaved_reserve_set_consume() {
let mut c = SizeCache::new();
let n = INLINE_CAP * 2 + 3;
let slots: Vec<usize> = (0..n).map(|_| c.reserve()).collect();
for (i, &s) in slots.iter().enumerate().rev() {
c.set(s, (i as u32).wrapping_mul(3).wrapping_add(1));
}
for i in 0..n {
assert_eq!(c.consume_next(), (i as u32).wrapping_mul(3).wrapping_add(1));
}
c.clear();
let a = c.reserve();
let b = c.reserve();
c.set(b, 20);
assert_eq!(c.consume_next(), 0);
assert_eq!(c.consume_next(), 20);
let _ = a;
}
fn spill_and_return(pool: &mut SizeCachePool, slots: usize) {
let mut c = pool.acquire();
for i in 0..slots {
let s = c.reserve();
c.set(s, i as u32);
}
pool.release(c);
}
#[test]
fn with_spill_buffer_clears_and_retains_capacity() {
let mut donor = Vec::with_capacity(40);
donor.extend_from_slice(&[7, 7, 7]);
let cap = donor.capacity();
let c = SizeCache::with_spill_buffer(donor);
assert_eq!(c.len, 0);
assert!(c.spill.is_empty());
assert!(c.spill.capacity() >= cap, "retains donor capacity");
}
#[test]
fn into_spill_buffer_roundtrips_through_with_spill_buffer() {
let mut c = SizeCache::new();
for _ in 0..(INLINE_CAP + 5) {
c.reserve();
}
let buf = c.into_spill_buffer();
let grown = buf.capacity();
assert!(grown >= 5);
let c2 = SizeCache::with_spill_buffer(buf);
assert!(c2.spill.capacity() >= grown, "allocation reused");
assert_eq!(c2.len, 0);
}
#[test]
fn pool_reuses_spill_allocation() {
let mut pool = SizeCachePool::new(4, 1024);
spill_and_return(&mut pool, INLINE_CAP + 5);
assert_eq!(pool.free.len(), 1, "spilled buffer retained");
let grown = pool.free[0].capacity();
let c = pool.acquire();
assert!(c.spill.capacity() >= grown, "spill capacity reused");
assert_eq!(c.len, 0);
}
#[test]
fn pool_does_not_retain_non_spilling_caches() {
let mut pool = SizeCachePool::new(4, 1024);
let mut c = pool.acquire();
let s = c.reserve(); c.set(s, 1);
pool.release(c);
assert!(pool.free.is_empty(), "empty (cap 0) buffers are not pooled");
}
#[test]
fn pool_respects_max_buffers() {
let mut pool = SizeCachePool::new(1, 1024);
for _ in 0..3 {
spill_and_return(&mut pool, INLINE_CAP + 2);
}
assert!(pool.free.len() <= 1, "free-list bounded by max_buffers");
}
#[test]
fn pool_shrinks_oversized_buffer_on_return() {
let mut pool = SizeCachePool::new(4, 8);
spill_and_return(&mut pool, INLINE_CAP + 100);
assert!(
pool.free[0].capacity() <= 8,
"oversized buffer shrunk to cap"
);
}
#[test]
fn pool_acquire_release_default_is_empty() {
let mut pool = SizeCachePool::new(2, 64);
let c = pool.acquire(); assert_eq!(c.len, 0);
pool.release(c); assert!(pool.free.is_empty());
}
#[test]
fn pool_sequential_caps_buffers_at_one() {
let mut pool = SizeCachePool::sequential(1024);
assert_eq!(pool.max_buffers, 1);
for _ in 0..3 {
spill_and_return(&mut pool, INLINE_CAP + 2);
}
assert_eq!(pool.free.len(), 1, "sequential retains exactly one buffer");
}
#[test]
fn pool_max_capacity_zero_disables_retention() {
let mut pool = SizeCachePool::new(4, 0);
spill_and_return(&mut pool, INLINE_CAP + 50);
assert!(
pool.free.is_empty(),
"max_capacity 0 retains no (zero-capacity) buffers"
);
let mut c = pool.acquire();
let s = c.reserve();
c.set(s, 9);
assert_eq!(c.consume_next(), 9);
}
}