#[cfg(not(feature = "std"))]
use alloc::{
boxed::Box,
vec,
};
#[cfg(feature = "std")]
use std::{
boxed::Box,
vec,
};
use core::cmp::Ordering;
use crate::position::BlockRef;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct BlockRing {
numbers: Box<[u64]>,
hashes: Box<[[u8; 32]]>,
head: usize,
len: usize,
}
impl BlockRing {
pub(crate) fn with_capacity(capacity: usize) -> Self {
debug_assert!(
capacity.is_power_of_two(),
"BlockRing capacity must be a power of two"
);
Self {
numbers: vec![0u64; capacity].into_boxed_slice(),
hashes: vec![[0u8; 32]; capacity].into_boxed_slice(),
head: 0,
len: 0,
}
}
#[inline]
pub(crate) fn capacity(&self) -> usize {
self.numbers.len()
}
#[inline]
pub(crate) fn is_empty(&self) -> bool {
self.len == 0
}
#[inline]
fn physical(&self, logical: usize) -> usize {
(self.head + logical) & (self.capacity() - 1)
}
pub(crate) fn push(&mut self, block: BlockRef) {
let capacity = self.capacity();
if self.len < capacity {
let index = self.physical(self.len);
self.numbers[index] = block.number;
self.hashes[index] = block.hash;
self.len += 1;
} else {
let index = self.head;
self.numbers[index] = block.number;
self.hashes[index] = block.hash;
self.head = (self.head + 1) & (capacity - 1);
}
}
#[inline]
pub(crate) fn newest(&self) -> Option<BlockRef> {
if self.is_empty() {
None
} else {
Some(self.get(self.len - 1))
}
}
#[inline]
pub(crate) fn get(&self, index: usize) -> BlockRef {
let physical = self.physical(index);
BlockRef {
number: self.numbers[physical],
hash: self.hashes[physical],
}
}
pub(crate) fn hash_at(&self, number: u64) -> Option<[u8; 32]> {
let mut low = 0usize;
let mut high = self.len;
while low < high {
let mid = low + (high - low) / 2;
let physical = self.physical(mid);
match self.numbers[physical].cmp(&number) {
Ordering::Less => low = mid + 1,
Ordering::Equal => return Some(self.hashes[physical]),
Ordering::Greater => high = mid,
}
}
None
}
pub(crate) fn clear(&mut self) {
self.head = 0;
self.len = 0;
}
pub(crate) fn iter(&self) -> Observed<'_> {
Observed {
ring: self,
next: 0,
remaining: self.len,
}
}
}
pub struct Observed<'a> {
ring: &'a BlockRing,
next: usize,
remaining: usize,
}
impl<'a> Iterator for Observed<'a> {
type Item = BlockRef;
fn next(&mut self) -> Option<Self::Item> {
if self.remaining == 0 {
return None;
}
let block = self.ring.get(self.next);
self.next += 1;
self.remaining -= 1;
Some(block)
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining, Some(self.remaining))
}
}
impl<'a> ExactSizeIterator for Observed<'a> {
fn len(&self) -> usize {
self.remaining
}
}
#[cfg(test)]
mod tests {
use super::BlockRing;
use crate::position::BlockRef;
#[cfg(not(feature = "std"))]
use alloc::{
vec,
vec::Vec,
};
#[cfg(feature = "std")]
use std::{
vec,
vec::Vec,
};
fn block(number: u64) -> BlockRef {
let mut hash = [0u8; 32];
hash[..8].copy_from_slice(&number.to_le_bytes());
BlockRef { number, hash }
}
#[test]
fn push_and_newest_round_trip() {
let mut ring = BlockRing::with_capacity(4);
ring.push(block(1));
ring.push(block(2));
ring.push(block(3));
let newest = ring.newest();
assert_eq!(newest, Some(block(3)));
}
#[test]
fn wraparound_overwrites_oldest() {
let mut ring = BlockRing::with_capacity(4);
for number in 1..=6 {
ring.push(block(number));
}
let observed: Vec<BlockRef> = ring.iter().collect();
assert_eq!(observed, vec![block(3), block(4), block(5), block(6)]);
}
#[test]
fn hash_at_finds_only_retained_numbers() {
let mut ring = BlockRing::with_capacity(4);
for number in 1..=6 {
ring.push(block(number));
}
let evicted = ring.hash_at(2);
assert_eq!(evicted, None);
assert_eq!(ring.hash_at(5), Some(block(5).hash));
}
#[test]
fn clone_is_independent() {
let mut ring = BlockRing::with_capacity(4);
ring.push(block(1));
let clone = ring.clone();
ring.push(block(2));
assert_eq!(clone.newest(), Some(block(1)));
assert_eq!(ring.newest(), Some(block(2)));
}
}