use alloc::{boxed::Box, string::String, vec::Vec};
use core::{borrow::Borrow, fmt, ops::Deref};
use buggy::{Bug, BugExt as _};
use rend::u64_le;
use crate::{Address, CmdId, Command, PolicyId, Prior};
pub mod linear;
#[cfg(any(feature = "libc", feature = "testing"))]
mod spill;
#[cfg(feature = "libc")]
pub use spill::LibcSpill;
#[cfg(feature = "testing")]
pub use spill::MemSpill;
pub trait Spill {
fn write_at(&mut self, offset: usize, data: &[u8]) -> Result<(), StorageError>;
fn read_at(&mut self, offset: usize, data: &mut [u8]) -> Result<(), StorageError>;
}
pub const QUEUE_CAPACITY: usize = 512;
#[derive(Debug, Default)]
pub struct TraversalQueue {
entries: heapless::Vec<Location, QUEUE_CAPACITY>,
partition: usize,
}
impl TraversalQueue {
pub const fn new() -> Self {
Self {
entries: heapless::Vec::new(),
partition: 0,
}
}
pub fn clear(&mut self) {
self.entries.clear();
self.partition = 0;
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn push(&mut self, loc: Location) -> Result<(), StorageError> {
self.push_covered(loc, false)
}
pub fn push_covered(&mut self, loc: Location, covered: bool) -> Result<(), StorageError> {
if let Some(i) = self.entries.iter().position(|x| x.same_segment(loc)) {
let was_covered = i >= self.partition;
let new_covered = if loc.max_cut > self.entries[i].max_cut {
self.entries[i].max_cut = loc.max_cut;
covered
} else if loc.max_cut == self.entries[i].max_cut {
was_covered || covered
} else {
return Ok(());
};
if !was_covered && new_covered {
self.partition = self
.partition
.checked_sub(1)
.assume("partition must be >= 1 when uncovered entry exists")?;
self.entries.swap(i, self.partition);
} else if was_covered && !new_covered {
self.entries.swap(i, self.partition);
self.partition = self
.partition
.checked_add(1)
.assume("partition must not overflow")?;
}
return Ok(());
}
self.entries
.push(loc)
.map_err(|_| StorageError::TraversalQueueOverflow(QUEUE_CAPACITY))?;
if !covered {
let last = self
.entries
.len()
.checked_sub(1)
.assume("just pushed, len must be >= 1")?;
self.entries.swap(self.partition, last);
self.partition = self
.partition
.checked_add(1)
.assume("partition must not overflow")?;
}
Ok(())
}
pub fn push_duplicate(&mut self, loc: Location) -> Result<(), StorageError> {
self.entries
.push(loc)
.map_err(|_| StorageError::TraversalQueueOverflow(QUEUE_CAPACITY))?;
let last = self
.entries
.len()
.checked_sub(1)
.assume("just pushed, len must be >= 1")?;
self.entries.swap(self.partition, last);
self.partition = self
.partition
.checked_add(1)
.assume("partition must not overflow")?;
Ok(())
}
pub fn pop(&mut self) -> Result<Option<Location>, StorageError> {
Ok(self.pop_covered()?.map(|(loc, _)| loc))
}
pub fn pop_covered(&mut self) -> Result<Option<(Location, bool)>, StorageError> {
let Some((i, _)) = self.entries.iter().enumerate().max_by_key(|&(_, loc)| *loc) else {
return Ok(None);
};
if i < self.partition {
Ok(Some((self.remove_uncovered(i)?, false)))
} else {
let loc = self.entries.swap_remove(i);
Ok(Some((loc, true)))
}
}
fn remove_uncovered(&mut self, i: usize) -> Result<Location, StorageError> {
self.partition = self
.partition
.checked_sub(1)
.assume("partition must be >= 1 when uncovered entry exists")?;
self.entries.swap(i, self.partition);
Ok(self.entries.swap_remove(self.partition))
}
pub fn peek(&self) -> Option<&Location> {
self.entries.iter().max_by_key(|loc| *loc)
}
pub fn pop_duplicates(&mut self) -> Result<Option<(Location, usize)>, StorageError> {
let Some(location) = self.entries.iter().max_by_key(|loc| *loc).copied() else {
return Ok(None);
};
let mut count: usize = 0;
let mut j = self.entries.len();
while j > 0 {
j = j.checked_sub(1).assume("j > 0 checked in loop condition")?;
if self.entries[j] == location {
count = count
.checked_add(1)
.assume("count bounded by QUEUE_CAPACITY")?;
if j < self.partition {
self.partition = self
.partition
.checked_sub(1)
.assume("partition >= 1 when uncovered entry at j < partition")?;
self.entries.swap(j, self.partition);
self.entries.swap_remove(self.partition);
} else {
self.entries.swap_remove(j);
}
}
}
Ok(Some((location, count)))
}
pub fn all_covered(&self) -> bool {
self.partition == 0
}
pub fn drain_above(
&mut self,
threshold: MaxCut,
mut f: impl FnMut(Location),
) -> Result<(), StorageError> {
let mut i = 0;
while i < self.partition {
if self.entries[i].max_cut > threshold {
f(self.remove_uncovered(i)?);
} else {
i = i.checked_add(1).assume("index must not overflow")?;
}
}
let mut i = self.partition;
while i < self.entries.len() {
if self.entries[i].max_cut > threshold {
self.entries.swap_remove(i);
} else {
i = i.checked_add(1).assume("index must not overflow")?;
}
}
Ok(())
}
pub fn cover_up_to(
&mut self,
segment: SegmentIndex,
coverage_mc: MaxCut,
longest_mc: MaxCut,
) -> Result<(), StorageError> {
let Some(i) = self.entries.iter().position(|x| x.segment == segment) else {
return Ok(());
};
let was_covered = i >= self.partition;
if was_covered {
return Ok(());
}
if coverage_mc >= longest_mc {
self.partition = self
.partition
.checked_sub(1)
.assume("partition must be >= 1 when uncovered entry exists")?;
self.entries.swap(i, self.partition);
} else if coverage_mc >= self.entries[i].max_cut {
self.entries[i].max_cut = coverage_mc
.checked_add(1)
.assume("coverage_mc + 1 must not overflow")?;
}
Ok(())
}
pub fn drain_all(&mut self, mut f: impl FnMut(Location)) {
for i in 0..self.partition {
f(self.entries[i]);
}
self.entries.clear();
self.partition = 0;
}
}
pub struct TraversalBuffer {
queue: TraversalQueue,
}
impl TraversalBuffer {
pub const fn new() -> Self {
Self {
queue: TraversalQueue::new(),
}
}
pub fn get(&mut self) -> &mut TraversalQueue {
self.queue.clear();
&mut self.queue
}
}
impl Default for TraversalBuffer {
fn default() -> Self {
Self::new()
}
}
pub struct TraversalBuffers {
pub primary: TraversalBuffer,
pub secondary: TraversalBuffer,
}
impl TraversalBuffers {
pub const fn new() -> Self {
Self {
primary: TraversalBuffer::new(),
secondary: TraversalBuffer::new(),
}
}
}
impl Default for TraversalBuffers {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "low-mem-usage")]
pub const MAX_COMMAND_LENGTH: usize = 400;
#[cfg(not(feature = "low-mem-usage"))]
pub const MAX_COMMAND_LENGTH: usize = 2048;
aranya_crypto::custom_id! {
pub struct GraphId;
}
#[derive(
Copy,
Clone,
Debug,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
serde::Serialize,
serde::Deserialize,
rkyv::Archive,
rkyv::Serialize,
rkyv::Deserialize,
rkyv::Portable,
rkyv::bytecheck::CheckBytes,
zerocopy::IntoBytes,
zerocopy::FromBytes,
zerocopy::Immutable,
zerocopy::KnownLayout,
)]
#[rkyv(as = Self)]
#[bytecheck(crate = rkyv::bytecheck)]
#[serde(transparent)]
#[repr(transparent)]
pub struct SegmentIndex(#[serde(with = "crate::util::u64_le_serde")] u64_le);
impl fmt::Display for SegmentIndex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl SegmentIndex {
pub const fn new(val: u64) -> Self {
Self(u64_le::from_native(val))
}
pub const fn get(self) -> u64 {
self.0.to_native()
}
}
#[derive(
Copy,
Clone,
Debug,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
serde::Serialize,
serde::Deserialize,
rkyv::Archive,
rkyv::Serialize,
rkyv::Deserialize,
rkyv::Portable,
rkyv::bytecheck::CheckBytes,
zerocopy::IntoBytes,
zerocopy::FromBytes,
zerocopy::Immutable,
zerocopy::KnownLayout,
)]
#[rkyv(as = Self)]
#[bytecheck(crate = rkyv::bytecheck)]
#[serde(transparent)]
#[repr(transparent)]
pub struct MaxCut(#[serde(with = "crate::util::u64_le_serde")] u64_le);
impl fmt::Display for MaxCut {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl MaxCut {
pub const fn new(val: u64) -> Self {
Self(u64_le::from_native(val))
}
pub const fn get(self) -> u64 {
self.0.to_native()
}
#[must_use]
pub fn checked_add(self, other: u64) -> Option<Self> {
self.get().checked_add(other).map(Self::new)
}
#[must_use]
pub fn decremented(self) -> Option<Self> {
self.get().checked_sub(1).map(Self::new)
}
#[must_use]
pub fn distance_from(self, other: Self) -> Option<u64> {
self.get().checked_sub(other.get())
}
}
#[derive(
Copy,
Clone,
Debug,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
serde::Serialize,
serde::Deserialize,
rkyv::Archive,
rkyv::Serialize,
rkyv::Deserialize,
rkyv::Portable,
rkyv::bytecheck::CheckBytes,
zerocopy::IntoBytes,
zerocopy::FromBytes,
zerocopy::Immutable,
zerocopy::KnownLayout,
)]
#[rkyv(as = Self)]
#[bytecheck(crate = rkyv::bytecheck)]
#[repr(C)]
pub struct Location {
pub max_cut: MaxCut,
pub segment: SegmentIndex,
}
impl From<(SegmentIndex, MaxCut)> for Location {
fn from((segment, max_cut): (SegmentIndex, MaxCut)) -> Self {
Self::new(segment, max_cut)
}
}
impl AsRef<Self> for Location {
fn as_ref(&self) -> &Self {
self
}
}
impl Location {
pub fn new(segment: SegmentIndex, max_cut: MaxCut) -> Self {
Self { max_cut, segment }
}
pub fn same_segment(self, other: Self) -> bool {
self.segment == other.segment
}
}
impl fmt::Display for Location {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.segment, self.max_cut)
}
}
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct LocatedAddress {
pub id: CmdId,
pub segment: SegmentIndex,
pub max_cut: MaxCut,
}
impl LocatedAddress {
pub fn address(self) -> Address {
Address {
id: self.id,
max_cut: self.max_cut,
}
}
pub fn location(self) -> Location {
Location {
segment: self.segment,
max_cut: self.max_cut,
}
}
}
#[derive(Debug, thiserror::Error)]
#[cfg_attr(test, derive(PartialEq, Eq))]
#[non_exhaustive]
pub enum StorageError {
#[error("storage already exists")]
StorageExists,
#[error("no such storage")]
NoSuchStorage,
#[error("segment index {} is out of bounds", .0.segment)]
SegmentOutOfBounds(Location),
#[error("max cut {} is out of bounds in segment {}", .0.max_cut, .0.segment)]
CommandOutOfBounds(Location),
#[error("IO error")]
IoError,
#[error("policy mismatch")]
PolicyMismatch,
#[error("cannot write an empty perspective")]
EmptyPerspective,
#[error("traversal queue overflow (capacity {0})")]
TraversalQueueOverflow(usize),
#[error("strand heap overflow (capacity {0})")]
StrandHeapOverflow(usize),
#[error("convergence root index overflow (capacity {0})")]
ConvergenceRootOverflow(usize),
#[error("command's parents do not match the perspective head")]
PerspectiveHeadMismatch,
#[error(transparent)]
Bug(#[from] Bug),
}
pub trait StorageProvider {
type Perspective: Perspective + Revertable;
type Segment: Segment;
type Storage: Storage<
Segment = Self::Segment,
Perspective = Self::Perspective,
FactIndex = <Self::Segment as Segment>::FactIndex,
>;
fn new_perspective(&mut self, policy_id: PolicyId) -> Self::Perspective;
fn new_storage(
&mut self,
init: Self::Perspective,
) -> Result<(GraphId, &mut Self::Storage), StorageError>;
fn get_storage(&mut self, graph: GraphId) -> Result<&mut Self::Storage, StorageError>;
fn remove_storage(&mut self, graph: GraphId) -> Result<(), StorageError>;
fn list_graph_ids(
&mut self,
) -> Result<impl Iterator<Item = Result<GraphId, StorageError>>, StorageError>;
}
pub trait Storage {
type Perspective: Perspective + Revertable;
type FactPerspective: FactPerspective;
type Segment: Segment<FactIndex = Self::FactIndex>;
type FactIndex: FactIndex;
fn get_location(
&self,
address: Address,
buffer: &mut TraversalBuffer,
) -> Result<Option<Location>, StorageError> {
self.get_location_from(self.get_head()?, address, buffer)
}
fn get_location_from(
&self,
start: Location,
address: Address,
buffer: &mut TraversalBuffer,
) -> Result<Option<Location>, StorageError> {
if start.max_cut < address.max_cut {
return Ok(None);
}
let queue = buffer.get();
queue.push(start)?;
while let Some(loc) = queue.pop()? {
debug_assert!(
loc.max_cut >= address.max_cut,
"Invariant: we only enqueue locations with at least the target max cut"
);
let segment = self.get_segment(loc)?;
if let Some(found) = segment.get_by_address(address) {
return Ok(Some(found));
}
if let Some(&skip) = segment
.skip_list()
.iter()
.find(|skip| skip.max_cut >= address.max_cut)
{
queue.push(skip)?;
} else {
for prior in segment.prior() {
if prior.max_cut >= address.max_cut {
queue.push(prior)?;
}
}
}
}
Ok(None)
}
fn get_command_address(&self, location: Location) -> Result<Address, StorageError> {
let segment = self.get_segment(location)?;
let command = segment
.get_command(location)
.ok_or(StorageError::CommandOutOfBounds(location))?;
let address = command.address()?;
Ok(address)
}
fn get_linear_perspective(&self, parent: Location) -> Result<Self::Perspective, StorageError>;
fn get_fact_perspective(&self, first: Location) -> Result<Self::FactPerspective, StorageError>;
fn new_merge_perspective(
&self,
left: Location,
right: Location,
last_common_ancestor: Location,
policy_id: PolicyId,
braid: Self::FactIndex,
) -> Result<Self::Perspective, StorageError>;
fn get_segment(&self, location: Location) -> Result<Self::Segment, StorageError>;
fn get_head(&self) -> Result<Location, StorageError>;
fn get_head_address(&self) -> Result<Address, StorageError> {
self.get_command_address(self.get_head()?)
}
fn commit(&mut self, segment: Self::Segment) -> Result<(), StorageError>;
fn write(&mut self, perspective: Self::Perspective) -> Result<Self::Segment, StorageError>;
fn write_facts(
&mut self,
fact_perspective: Self::FactPerspective,
) -> Result<Self::FactIndex, StorageError>;
fn is_ancestor(
&self,
search_location: Location,
start_location: Location,
buffer: &mut TraversalBuffer,
) -> Result<bool, StorageError> {
if search_location.max_cut > start_location.max_cut || search_location == start_location {
return Ok(false);
}
let queue = buffer.get();
queue.push(start_location)?;
while let Some(loc) = queue.pop()? {
debug_assert!(
loc.max_cut >= search_location.max_cut,
"Invariant: we only enqueue locations with at least the target max cut"
);
let segment = self.get_segment(loc)?;
if segment.get_command(search_location).is_some() {
return Ok(true);
}
if let Some(&skip) = segment
.skip_list()
.iter()
.find(|skip| skip.max_cut >= search_location.max_cut)
{
queue.push(skip)?;
} else {
for prior in segment.prior() {
if prior.max_cut >= search_location.max_cut {
queue.push(prior)?;
}
}
}
}
Ok(false)
}
}
pub trait Segment {
type FactIndex: FactIndex;
type Command<'a>: Command
where
Self: 'a;
fn index(&self) -> SegmentIndex;
fn head_id(&self) -> CmdId;
fn policy(&self) -> PolicyId;
fn prior(&self) -> Prior<Location>;
fn get_command(&self, location: Location) -> Option<Self::Command<'_>>;
fn facts(&self) -> Result<Self::FactIndex, StorageError>;
fn shortest_max_cut(&self) -> MaxCut;
fn longest_max_cut(&self) -> Result<MaxCut, StorageError>;
fn skip_list(&self) -> &[Location];
fn get_from(&self, location: Location) -> Vec<Self::Command<'_>> {
let segment = location.segment;
core::iter::successors(Some(location.max_cut), |max_cut| max_cut.checked_add(1))
.map_while(|max_cut| self.get_command(Location { max_cut, segment }))
.collect()
}
fn get_by_address(&self, address: Address) -> Option<Location> {
let loc = Location::new(self.index(), address.max_cut);
let cmd = self.get_command(loc)?;
if cmd.id() != address.id {
return None;
}
Some(loc)
}
fn first_location(&self) -> Location {
Location {
max_cut: self.shortest_max_cut(),
segment: self.index(),
}
}
fn head_location(&self) -> Result<Location, StorageError> {
Ok(Location {
max_cut: self.longest_max_cut()?,
segment: self.index(),
})
}
fn head_address(&self) -> Result<Address, StorageError> {
Ok(Address {
id: self.head_id(),
max_cut: self.longest_max_cut()?,
})
}
#[must_use]
fn previous(&self, mut location: Location) -> Option<Location> {
debug_assert_eq!(location.segment, self.index());
if location.max_cut <= self.shortest_max_cut() {
return None;
}
location.max_cut = location.max_cut.decremented()?;
Some(location)
}
}
pub trait FactIndex: Query {}
pub trait Perspective: FactPerspective {
fn policy(&self) -> PolicyId;
fn add_command(&mut self, command: &impl Command) -> Result<usize, StorageError>;
fn includes(&self, id: CmdId) -> bool;
fn head_address(&self) -> Result<Prior<Address>, Bug>;
}
pub trait FactPerspective: QueryMut {}
pub trait Revertable {
fn checkpoint(&self) -> Checkpoint;
fn revert(&mut self, checkpoint: Checkpoint) -> Result<(), StorageError>;
}
pub struct Checkpoint {
pub index: usize,
}
pub trait Query {
fn query(&self, name: &str, keys: &[Bytes]) -> Result<Option<Bytes>, StorageError>;
type QueryIterator: Iterator<Item = Result<Fact, StorageError>>;
fn query_prefix(
&self,
name: &str,
prefix: &[Bytes],
) -> Result<Self::QueryIterator, StorageError>;
}
#[derive(Debug, PartialEq, Eq)]
pub struct Fact {
pub key: Keys,
pub value: Bytes,
}
pub trait QueryMut: Query {
fn insert(&mut self, name: String, keys: Keys, value: Bytes) -> Result<(), StorageError>;
fn delete(&mut self, name: String, keys: Keys) -> Result<(), StorageError>;
}
#[cfg(all(test, feature = "graphviz"))]
pub(crate) trait FactIndexExtra {
fn name(&self) -> String;
fn prior(&self) -> Result<Option<Self>, StorageError>
where
Self: Sized;
}
#[derive(
Clone,
Debug,
Default,
PartialEq,
Eq,
PartialOrd,
Ord,
serde::Serialize,
serde::Deserialize,
rkyv::Archive,
rkyv::Serialize,
rkyv::Deserialize,
)]
pub struct Keys(Box<[Bytes]>);
impl Deref for Keys {
type Target = [Bytes];
fn deref(&self) -> &[Bytes] {
self.0.as_ref()
}
}
impl AsRef<[Bytes]> for Keys {
fn as_ref(&self) -> &[Bytes] {
self.0.as_ref()
}
}
impl Borrow<[Bytes]> for Keys {
fn borrow(&self) -> &[Bytes] {
self.0.as_ref()
}
}
impl From<Vec<Bytes>> for Keys {
fn from(value: Vec<Bytes>) -> Self {
Self(value.into_boxed_slice())
}
}
impl From<&[&[u8]]> for Keys {
fn from(value: &[&[u8]]) -> Self {
value.iter().copied().collect()
}
}
impl<B: Into<Bytes>> FromIterator<B> for Keys {
fn from_iter<T: IntoIterator<Item = B>>(iter: T) -> Self {
Self(iter.into_iter().map(Into::into).collect())
}
}
impl<'a> IntoIterator for &'a Keys {
type Item = &'a Bytes;
type IntoIter = core::slice::Iter<'a, Bytes>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
impl ArchivedKeys {
pub fn iter(&self) -> impl Iterator<Item = &[u8]> {
self.0.iter().map(AsRef::as_ref)
}
}
pub type Bytes = Box<[u8]>;
mod impls {
use alloc::boxed::Box;
use super::{GraphId, PolicyId, StorageError, StorageProvider};
impl<SP: StorageProvider> StorageProvider for &mut SP {
type Perspective = SP::Perspective;
type Segment = SP::Segment;
type Storage = SP::Storage;
fn new_perspective(&mut self, policy_id: PolicyId) -> Self::Perspective {
SP::new_perspective(self, policy_id)
}
fn new_storage(
&mut self,
init: Self::Perspective,
) -> Result<(GraphId, &mut Self::Storage), StorageError> {
SP::new_storage(self, init)
}
fn get_storage(&mut self, graph: GraphId) -> Result<&mut Self::Storage, StorageError> {
SP::get_storage(self, graph)
}
fn remove_storage(&mut self, graph: GraphId) -> Result<(), StorageError> {
SP::remove_storage(self, graph)
}
fn list_graph_ids(
&mut self,
) -> Result<impl Iterator<Item = Result<GraphId, StorageError>>, StorageError> {
SP::list_graph_ids(self)
}
}
impl<SP: StorageProvider> StorageProvider for Box<SP> {
type Perspective = SP::Perspective;
type Segment = SP::Segment;
type Storage = SP::Storage;
fn new_perspective(&mut self, policy_id: PolicyId) -> Self::Perspective {
SP::new_perspective(self, policy_id)
}
fn new_storage(
&mut self,
init: Self::Perspective,
) -> Result<(GraphId, &mut Self::Storage), StorageError> {
SP::new_storage(self, init)
}
fn get_storage(&mut self, graph: GraphId) -> Result<&mut Self::Storage, StorageError> {
SP::get_storage(self, graph)
}
fn remove_storage(&mut self, graph: GraphId) -> Result<(), StorageError> {
SP::remove_storage(self, graph)
}
fn list_graph_ids(
&mut self,
) -> Result<impl Iterator<Item = Result<GraphId, StorageError>>, StorageError> {
SP::list_graph_ids(self)
}
}
}
#[cfg(test)]
mod queue_tests {
use super::*;
fn loc(seg: usize, mc: usize) -> Location {
Location::new(SegmentIndex::new(seg as u64), MaxCut::new(mc as u64))
}
#[test]
fn test_queue_overflow_returns_error() {
let mut queue = TraversalQueue::new();
for i in 0..QUEUE_CAPACITY {
queue.push(loc(i, i)).unwrap();
}
let result = queue
.push(loc(999, 999))
.expect_err("expected push_queue to fail");
assert_eq!(result, StorageError::TraversalQueueOverflow(QUEUE_CAPACITY));
}
#[test]
fn test_push_defaults_covered_false() {
let mut queue = TraversalQueue::new();
queue.push(loc(0, 5)).unwrap();
let (_, covered) = queue.pop_covered().unwrap().unwrap();
assert!(!covered);
}
#[test]
fn test_push_covered_preserves_flag() {
let mut queue = TraversalQueue::new();
queue.push_covered(loc(0, 5), true).unwrap();
let (_, covered) = queue.pop_covered().unwrap().unwrap();
assert!(covered);
}
#[test]
fn test_push_covered_same_max_cut_ors_flags() {
let mut queue = TraversalQueue::new();
queue.push_covered(loc(0, 5), false).unwrap();
queue.push_covered(loc(0, 5), true).unwrap();
let (_, covered) = queue.pop_covered().unwrap().unwrap();
assert!(covered);
}
#[test]
fn test_push_covered_same_max_cut_cannot_uncover() {
let mut queue = TraversalQueue::new();
queue.push_covered(loc(0, 5), true).unwrap();
queue.push_covered(loc(0, 5), false).unwrap();
let (_, covered) = queue.pop_covered().unwrap().unwrap();
assert!(covered);
}
#[test]
fn test_push_same_segment_updates_max_cut() {
let mut queue = TraversalQueue::new();
queue.push(loc(0, 5)).unwrap();
queue.push(loc(0, 8)).unwrap();
let l = queue.pop().unwrap().unwrap();
assert_eq!(l.max_cut, MaxCut::new(8));
assert!(queue.is_empty());
}
#[test]
fn test_push_covered_higher_max_cut_adopts_new_flag() {
let mut queue = TraversalQueue::new();
queue.push_covered(loc(0, 5), true).unwrap();
queue.push_covered(loc(0, 8), false).unwrap();
let (l, covered) = queue.pop_covered().unwrap().unwrap();
assert_eq!(l.max_cut, MaxCut::new(8));
assert!(!covered);
}
#[test]
fn test_push_covered_lower_max_cut_no_change() {
let mut queue = TraversalQueue::new();
queue.push_covered(loc(0, 8), false).unwrap();
queue.push_covered(loc(0, 3), true).unwrap();
let (l, covered) = queue.pop_covered().unwrap().unwrap();
assert_eq!(l.max_cut, MaxCut::new(8));
assert!(!covered);
}
#[test]
fn test_pop_discards_covered_flag() {
let mut queue = TraversalQueue::new();
queue.push_covered(loc(0, 5), true).unwrap();
let l = queue.pop().unwrap().unwrap();
assert_eq!(l.max_cut, MaxCut::new(5));
assert!(queue.is_empty());
}
#[test]
fn test_all_covered() {
let mut queue = TraversalQueue::new();
queue.push_covered(loc(0, 1), true).unwrap();
queue.push_covered(loc(1, 2), true).unwrap();
assert!(queue.all_covered());
queue.push_covered(loc(2, 3), false).unwrap();
assert!(!queue.all_covered());
}
#[test]
fn test_drain_above() {
let mut queue = TraversalQueue::new();
queue.push(loc(0, 3)).unwrap();
queue.push(loc(1, 7)).unwrap();
queue.push(loc(2, 5)).unwrap();
let mut result: heapless::Vec<Location, 8> = heapless::Vec::new();
queue
.drain_above(MaxCut::new(4), |loc| {
let _ = result.push(loc);
})
.unwrap();
assert_eq!(result.len(), 2);
assert!(result.iter().any(|l| l.max_cut == MaxCut::new(7)));
assert!(result.iter().any(|l| l.max_cut == MaxCut::new(5)));
let remaining = queue.pop().unwrap().unwrap();
assert_eq!(remaining.max_cut, MaxCut::new(3));
assert!(queue.is_empty());
}
#[test]
fn test_drain_above_with_covered_entries() {
let mut queue = TraversalQueue::new();
queue.push(loc(0, 3)).unwrap(); queue.push(loc(1, 7)).unwrap(); queue.push_covered(loc(2, 6), true).unwrap(); queue.push_covered(loc(3, 2), true).unwrap(); queue.push(loc(4, 5)).unwrap();
let mut drained: heapless::Vec<Location, 8> = heapless::Vec::new();
queue
.drain_above(MaxCut::new(4), |loc| {
let _ = drained.push(loc);
})
.unwrap();
assert_eq!(drained.len(), 2);
assert!(drained.iter().any(|l| l.segment.get() == 1));
assert!(drained.iter().any(|l| l.segment.get() == 4));
let mut remaining = Vec::new();
while let Some((l, covered)) = queue.pop_covered().unwrap() {
remaining.push((l.segment, covered));
}
assert_eq!(remaining.len(), 2);
assert!(remaining.contains(&(SegmentIndex::new(0), false)));
assert!(remaining.contains(&(SegmentIndex::new(3), true)));
}
#[test]
fn test_push_duplicate_keeps_separate_entries() {
let mut queue = TraversalQueue::new();
queue.push_duplicate(loc(0, 5)).unwrap();
queue.push_duplicate(loc(0, 5)).unwrap();
let first = queue.pop().unwrap();
assert!(first.is_some());
let second = queue.pop().unwrap();
assert!(second.is_some());
assert!(queue.is_empty());
}
#[test]
fn test_push_duplicate_overflow() {
let mut queue = TraversalQueue::new();
for i in 0..QUEUE_CAPACITY {
queue.push_duplicate(loc(0, i)).unwrap();
}
let result = queue.push_duplicate(loc(0, 999));
assert_eq!(
result.unwrap_err(),
StorageError::TraversalQueueOverflow(QUEUE_CAPACITY)
);
}
#[test]
fn test_pop_duplicates_returns_count() {
let mut queue = TraversalQueue::new();
queue.push_duplicate(loc(0, 5)).unwrap();
queue.push_duplicate(loc(0, 5)).unwrap();
queue.push_duplicate(loc(1, 3)).unwrap();
let (location, count) = queue.pop_duplicates().unwrap().unwrap();
assert_eq!(location, loc(0, 5));
assert_eq!(count, 2);
let (location, count) = queue.pop_duplicates().unwrap().unwrap();
assert_eq!(location, loc(1, 3));
assert_eq!(count, 1);
assert!(queue.pop_duplicates().unwrap().is_none());
}
#[test]
fn test_pop_duplicates_different_segments_same_max_cut() {
let mut queue = TraversalQueue::new();
queue.push_duplicate(loc(0, 5)).unwrap();
queue.push_duplicate(loc(1, 5)).unwrap();
let (location, count) = queue.pop_duplicates().unwrap().unwrap();
assert_eq!(count, 1);
assert_eq!(location.max_cut, MaxCut::new(5));
let (_, count) = queue.pop_duplicates().unwrap().unwrap();
assert_eq!(count, 1);
assert!(queue.pop_duplicates().unwrap().is_none());
}
#[test]
fn test_pop_duplicates_empty() {
let mut queue = TraversalQueue::new();
assert!(queue.pop_duplicates().unwrap().is_none());
}
}