use crate::merkle::{Family, Location};
use commonware_utils::Widen;
use core::marker::PhantomData;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Limits {
pub entries: usize,
pub skips: u64,
}
pub trait Policy<F: Family, K, V> {
fn evicts(&self) -> bool;
fn limits(&self, made_inactive: usize) -> Limits;
fn decide<'a>(&mut self, entry: Entry<'a, F, K, V>) -> Decision<'a, V>;
}
#[derive(Debug)]
pub struct Entry<'a, F: Family, K, V> {
location: Location<F>,
key: &'a K,
value: V,
}
impl<'a, F: Family, K, V> Entry<'a, F, K, V> {
pub(crate) const fn new(location: Location<F>, key: &'a K, value: V) -> Self {
Self {
location,
key,
value,
}
}
pub const fn location(&self) -> Location<F> {
self.location
}
pub const fn key(&self) -> &K {
self.key
}
pub const fn value(&self) -> &V {
&self.value
}
pub fn keep(self) -> Decision<'a, V> {
Decision::new(Action::Write(self.value))
}
pub fn stop(self) -> Decision<'a, V> {
Decision::new(Action::Stop)
}
pub fn replace(self, value: V) -> Decision<'a, V> {
Decision::new(Action::Write(value))
}
pub fn evict(self) -> (Decision<'a, V>, V) {
(Decision::new(Action::Evict), self.value)
}
}
#[derive(Debug)]
pub struct Decision<'a, V> {
action: Action<V>,
entry: PhantomData<&'a ()>,
}
impl<V> Decision<'_, V> {
const fn new(action: Action<V>) -> Self {
Self {
action,
entry: PhantomData,
}
}
pub(crate) fn into_action(self) -> Action<V> {
self.action
}
}
#[derive(Debug)]
pub(crate) enum Action<V> {
Write(V),
Stop,
Evict,
}
pub(crate) struct Walk<F: Family> {
pub(crate) floor: Location<F>,
pub(crate) end: Location<F>,
pub(crate) entries: usize,
skips: u64,
}
impl<F: Family> Walk<F> {
pub(crate) fn new(floor: Location<F>, tip: Location<F>, entries: usize, skips: u64) -> Self {
let reach = (*floor)
.saturating_add(skips)
.saturating_add(Widen::widen(entries));
Self {
floor,
end: Location::new(reach).min(tip),
entries,
skips,
}
}
pub(crate) fn reach(&mut self, loc: Location<F>) -> bool {
let gap = *loc - *self.floor;
if gap > self.skips {
self.floor += self.skips;
self.skips = 0;
return false;
}
self.skips -= gap;
self.floor = loc;
true
}
pub(crate) fn decide(&mut self) {
self.entries -= 1;
self.floor += 1;
}
pub(crate) fn reachable(&self, candidates: &[u64]) -> usize {
let (mut floor, mut skips) = (*self.floor, self.skips);
candidates
.iter()
.take(self.entries)
.take_while(|&&loc| {
let gap = loc - floor;
if gap > skips {
return false;
}
skips -= gap;
floor = loc + 1;
true
})
.count()
}
pub(crate) fn exhaust(&mut self) {
let gap = (*self.end - *self.floor).min(self.skips);
self.floor += gap;
self.skips -= gap;
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Proportional;
impl<F: Family, K, V> Policy<F, K, V> for Proportional {
fn evicts(&self) -> bool {
false
}
fn limits(&self, made_inactive: usize) -> Limits {
Limits {
entries: made_inactive + 1,
skips: u64::MAX,
}
}
fn decide<'a>(&mut self, entry: Entry<'a, F, K, V>) -> Decision<'a, V> {
entry.keep()
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Hold;
impl<F: Family, K, V> Policy<F, K, V> for Hold {
fn evicts(&self) -> bool {
false
}
fn limits(&self, _: usize) -> Limits {
Limits {
entries: 0,
skips: 0,
}
}
fn decide<'a>(&mut self, entry: Entry<'a, F, K, V>) -> Decision<'a, V> {
entry.stop()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Bounded {
pub entries: usize,
pub skips: u64,
}
impl<F: Family, K, V> Policy<F, K, V> for Bounded {
fn evicts(&self) -> bool {
false
}
fn limits(&self, _: usize) -> Limits {
Limits {
entries: self.entries,
skips: self.skips,
}
}
fn decide<'a>(&mut self, entry: Entry<'a, F, K, V>) -> Decision<'a, V> {
entry.keep()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mmr;
fn at(n: u64) -> Location<mmr::Family> {
Location::new(n)
}
#[test]
fn walk_window_ends_at_tip_or_limits() {
assert_eq!(Walk::new(at(3), at(100), 2, 4).end, at(9));
assert_eq!(Walk::new(at(3), at(5), 2, 4).end, at(5));
assert_eq!(Walk::new(at(3), at(100), usize::MAX, u64::MAX).end, at(100));
assert_eq!(Walk::new(at(3), at(100), 0, 0).end, at(3));
}
#[test]
fn walk_reaches_and_decides() {
let mut walk = Walk::new(at(0), at(100), 2, 3);
assert!(walk.reach(at(2)));
assert_eq!(walk.floor, at(2));
walk.decide();
assert_eq!((walk.floor, walk.entries), (at(3), 1));
assert!(walk.reach(at(3)));
walk.decide();
assert_eq!((walk.floor, walk.entries), (at(4), 0));
let mut walk = Walk::new(at(0), at(100), 2, 3);
assert!(walk.reach(at(1)));
walk.decide();
assert!(!walk.reach(at(5)));
assert_eq!(walk.floor, at(4));
}
#[test]
fn walk_exhausts_to_window_end() {
let mut walk = Walk::new(at(0), at(10), 5, 2);
walk.exhaust();
assert_eq!(walk.floor, at(2));
let mut walk = Walk::new(at(0), at(3), 5, 10);
walk.exhaust();
assert_eq!(walk.floor, at(3));
let mut walk = Walk::new(at(4), at(10), 1, u64::MAX);
assert!(walk.reach(at(6)));
walk.exhaust();
assert_eq!(walk.floor, at(10));
}
#[test]
fn walk_reaches_leading_candidates_within_limits() {
assert_eq!(Walk::new(at(0), at(100), 3, 2).reachable(&[1, 2, 5, 6]), 2);
assert_eq!(Walk::new(at(0), at(100), 1, 10).reachable(&[3, 4]), 1);
assert_eq!(Walk::new(at(0), at(100), 2, 0).reachable(&[0, 2]), 1);
assert_eq!(Walk::new(at(0), at(100), 2, 0).reachable(&[1]), 0);
assert_eq!(Walk::new(at(0), at(100), 0, 10).reachable(&[0]), 0);
assert_eq!(Walk::new(at(0), at(100), 2, 0).reachable(&[]), 0);
}
}