use std::{ops::Range, task::Poll, time::Duration};
use crate::runtime::Deadline;
pub(crate) const GRACE: Duration = Duration::from_secs(1);
#[derive(Default, Debug)]
pub(crate) struct Tail {
accounted: Vec<Range<u64>>,
streams: u64,
}
impl Tail {
pub fn open(&mut self, sequence: u64) {
self.stream();
self.account(sequence..sequence.saturating_add(1));
}
pub fn stream(&mut self) {
self.streams += 1;
}
pub fn account(&mut self, groups: Range<u64>) {
if groups.is_empty() {
return;
}
let first = self.accounted.partition_point(|range| range.end < groups.start);
let last = self.accounted.partition_point(|range| range.start <= groups.end);
let merged = match &self.accounted[first..last] {
[] => groups,
[head, .., tail] => head.start.min(groups.start)..tail.end.max(groups.end),
[only] => only.start.min(groups.start)..only.end.max(groups.end),
};
self.accounted.splice(first..last, [merged]);
}
pub fn covers(&self, groups: Range<u64>) -> bool {
groups.is_empty()
|| self
.accounted
.iter()
.any(|range| range.start <= groups.start && groups.end <= range.end)
}
pub fn streams(&self) -> u64 {
self.streams
}
}
pub(crate) struct Settle {
tail: kio::Consumer<Tail>,
grace: Deadline<crate::time::Clock>,
}
impl Settle {
pub fn new(runtime: &crate::time::Clock, tail: kio::Consumer<Tail>, grace: Duration) -> Self {
Self {
tail,
grace: Deadline::after(runtime, grace),
}
}
pub fn poll(&mut self, waiter: &kio::Waiter, mut complete: impl FnMut(&Tail) -> bool) -> Poll<()> {
if self.grace.poll(waiter).is_ready() {
return Poll::Ready(());
}
self.tail
.poll(waiter, |tail| match complete(tail) {
true => Poll::Ready(()),
false => Poll::Pending,
})
.map(|_| ())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ranges_merge_across_gaps() {
let mut tail = Tail::default();
tail.open(0);
tail.open(2);
assert!(!tail.covers(0..3), "group 1 is missing");
assert_eq!(tail.accounted, vec![0..1, 2..3]);
tail.account(1..2);
assert!(tail.covers(0..3) && tail.accounted.len() == 1, "adjacent ranges merge");
tail.account(5..7);
tail.account(9..10);
tail.account(4..9);
assert_eq!(tail.accounted, vec![0..3, 4..10], "one insert swallows several ranges");
assert!(tail.covers(4..10));
assert!(!tail.covers(2..5));
assert!(tail.covers(7..7), "an empty range is always covered");
assert_eq!(tail.streams(), 2, "only streams count, not drops");
}
}