use concinnity_core::render::fullscreen::align_up;
use std::collections::VecDeque;
pub(crate) const STAGING_ALIGN: u64 = 16;
pub(crate) const STAGING_MIN_CAPACITY: u64 = 8 << 20;
pub(crate) const STAGING_RESERVE_CAP: u64 = 64 << 20;
#[derive(Clone, Copy, Debug)]
struct Batch {
end: u64,
retire_at: u64,
}
pub(crate) struct StagingRing {
capacity: u64,
head: u64,
tail: u64,
open: u64,
batches: VecDeque<Batch>,
}
impl StagingRing {
pub(crate) fn new(capacity: u64) -> Self {
debug_assert!(capacity.is_power_of_two());
Self {
capacity,
head: 0,
tail: 0,
open: 0,
batches: VecDeque::new(),
}
}
pub(crate) fn capacity(&self) -> u64 {
self.capacity
}
pub(crate) fn alloc(&mut self, size: u64, align: u64) -> Option<u64> {
if size == 0 || size > self.capacity {
return None;
}
let mut start = align_up(self.head, align.max(1));
if start % self.capacity + size > self.capacity {
start = align_up(start, self.capacity);
}
let end = start + size;
if end - self.tail > self.capacity {
return None;
}
self.head = end;
Some(start % self.capacity)
}
pub(crate) fn seal(&mut self, retire_at: u64) {
if self.head == self.open {
return;
}
self.batches.push_back(Batch {
end: self.head,
retire_at,
});
self.open = self.head;
}
pub(crate) fn reclaim(&mut self, tick: u64) {
while let Some(batch) = self.batches.front() {
if batch.retire_at > tick {
break;
}
self.tail = batch.end;
self.batches.pop_front();
}
}
#[cfg(test)]
fn in_use(&self) -> u64 {
self.head - self.tail
}
}
pub(crate) fn grown_capacity(capacity: u64, size: u64) -> u64 {
capacity
.saturating_mul(2)
.max(size)
.max(STAGING_MIN_CAPACITY)
.next_power_of_two()
}
pub(crate) fn reserved_capacity(expected: u64) -> u64 {
expected
.clamp(STAGING_MIN_CAPACITY, STAGING_RESERVE_CAP)
.next_power_of_two()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn placements_are_aligned_and_sequential() {
let mut ring = StagingRing::new(1024);
assert_eq!(ring.alloc(10, 16), Some(0));
assert_eq!(ring.alloc(10, 16), Some(16));
assert_eq!(ring.alloc(1, 1), Some(26));
assert_eq!(ring.in_use(), 27);
}
#[test]
fn a_full_ring_refuses_until_its_batch_retires() {
let mut ring = StagingRing::new(256);
assert_eq!(ring.alloc(200, 16), Some(0));
ring.seal(5);
assert_eq!(ring.alloc(100, 16), None);
ring.reclaim(4);
assert_eq!(ring.alloc(100, 16), None, "tick 4 precedes the retire tick");
ring.reclaim(5);
assert_eq!(
ring.alloc(100, 16),
Some(0),
"wraps rather than straddling the end"
);
}
#[test]
fn an_unsealed_placement_is_never_reclaimed() {
let mut ring = StagingRing::new(256);
ring.alloc(200, 16);
ring.reclaim(u64::MAX);
assert_eq!(ring.alloc(100, 16), None);
assert_eq!(ring.in_use(), 200);
}
#[test]
fn a_placement_never_straddles_the_end() {
let mut ring = StagingRing::new(256);
assert_eq!(ring.alloc(160, 16), Some(0));
ring.seal(1);
ring.reclaim(1);
assert_eq!(ring.alloc(128, 16), Some(0));
assert_eq!(ring.in_use(), 96 + 128);
ring.seal(2);
ring.reclaim(2);
assert_eq!(ring.in_use(), 0);
}
#[test]
fn batches_retire_in_submission_order() {
let mut ring = StagingRing::new(256);
ring.alloc(64, 16);
ring.seal(10);
ring.alloc(64, 16);
ring.seal(3);
ring.reclaim(5);
assert_eq!(
ring.in_use(),
128,
"the older batch holds the newer one back"
);
ring.reclaim(10);
assert_eq!(ring.in_use(), 0);
}
#[test]
fn sealing_nothing_adds_no_batch() {
let mut ring = StagingRing::new(256);
ring.seal(1);
ring.alloc(64, 16);
ring.reclaim(1);
assert_eq!(
ring.in_use(),
64,
"the empty seal did not capture the later placement"
);
}
#[test]
fn oversized_and_empty_placements_are_refused() {
let mut ring = StagingRing::new(256);
assert_eq!(ring.alloc(257, 1), None);
assert_eq!(ring.alloc(0, 1), None);
assert_eq!(ring.alloc(256, 16), Some(0));
}
#[test]
fn growth_doubles_and_covers_the_request() {
let min = STAGING_MIN_CAPACITY;
assert_eq!(grown_capacity(0, 10), min);
assert_eq!(grown_capacity(min, 10), min * 2);
assert_eq!(grown_capacity(min, min * 3), min * 4);
}
#[test]
fn a_reserve_covers_the_stream_up_to_the_cap() {
let min = STAGING_MIN_CAPACITY;
assert_eq!(reserved_capacity(0), min);
assert_eq!(reserved_capacity(min * 3), min * 4);
assert_eq!(reserved_capacity(u64::MAX), STAGING_RESERVE_CAP);
}
}