use super::rollback::ArenaSnapshot;
pub struct SnapshotRing {
entries: std::collections::VecDeque<ArenaSnapshot>,
depth: usize,
}
impl SnapshotRing {
pub fn new(depth: usize) -> Self {
SnapshotRing {
entries: std::collections::VecDeque::new(),
depth,
}
}
pub fn depth(&self) -> usize {
self.depth
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn push(&mut self, snap: ArenaSnapshot) {
self.entries.push_back(snap);
while self.entries.len() > self.depth + 1 {
self.entries.pop_front();
}
}
pub fn get(&self, ticks_back: usize) -> Option<&ArenaSnapshot> {
if ticks_back > self.depth {
return None;
}
let len = self.entries.len();
let idx = len.checked_sub(1)?.checked_sub(ticks_back)?;
self.entries.get(idx)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::state::{
FourStateValue, SignalAccess, SlotClass, SlotLocation, StateCellId, StateEngine,
TemplateRegistry,
};
fn fresh_engine() -> StateEngine {
StateEngine::new(TemplateRegistry::new())
}
fn inline_cell(engine: &mut StateEngine) -> StateCellId {
let word_offset = unsafe { engine.arena.alloc_inline_word() };
let loc = SlotLocation {
class: SlotClass::Inline,
word_offset,
bit_width: 8,
elem_word_count: 1,
row_stride_words: 0,
num_rows: 0,
};
engine.alloc_cell(loc)
}
fn narrow(val: u64) -> FourStateValue {
FourStateValue::Narrow(val as u128)
}
fn read_narrow(engine: &StateEngine, cell: StateCellId) -> u128 {
match engine.read(cell) {
FourStateValue::Narrow(w) => w,
FourStateValue::Wide { .. } => panic!("expected Narrow"),
}
}
#[test]
fn snapshot_ring_depth_1_returns_previous_ticks_value() {
let mut engine = fresh_engine();
let cell = inline_cell(&mut engine);
let mut ring = SnapshotRing::new(1);
engine.commit_write(cell, narrow(10), &mut ());
ring.push(engine.snapshot(None));
engine.commit_write(cell, narrow(20), &mut ());
ring.push(engine.snapshot(None));
engine.commit_write(cell, narrow(99), &mut ());
engine
.restore(ring.get(1).expect("depth 1 retains one prior tick"))
.expect("same engine, same layout");
assert_eq!(
read_narrow(&engine, cell),
10,
"ticks_back=1 must give back the OLDER (tick 0) value"
);
engine
.restore(ring.get(0).expect("current is always retained"))
.expect("same engine, same layout");
assert_eq!(
read_narrow(&engine, cell),
20,
"ticks_back=0 (current) must give back the newest (tick 1) value"
);
assert!(
ring.get(2).is_none(),
"depth 1 retains only ticks_back in {{0,1}} -- 2 is out of range"
);
}
#[test]
fn snapshot_ring_depth_0_design_allocates_no_ring() {
let mut engine = fresh_engine();
let cell = inline_cell(&mut engine);
let mut ring = SnapshotRing::new(0);
assert_eq!(ring.depth(), 0);
for i in 0..5u64 {
engine.commit_write(cell, narrow(i), &mut ());
ring.push(engine.snapshot(None));
assert_eq!(
ring.len(),
1,
"depth 0 must never retain more than entry 0, even after {} pushes",
i + 1
);
}
engine
.restore(ring.get(0).expect("current is always retained"))
.expect("same engine, same layout");
assert_eq!(
read_narrow(&engine, cell),
4,
"the lone retained entry must be the MOST RECENT push"
);
assert!(
ring.get(1).is_none(),
"depth 0 retains no prior ticks at all"
);
}
#[test]
fn fresh_ring_has_no_entries_yet() {
let ring = SnapshotRing::new(3);
assert_eq!(ring.len(), 0);
assert!(ring.get(0).is_none());
}
}