timevec 0.2.0

Time limited vector deque
Documentation
use std::collections::vec_deque::Drain;
use std::collections::VecDeque;
use core::time::Duration;
use std::marker::PhantomData;
use std::ops::RangeBounds;

type Item<T> = (Duration, T);

pub type TimeVecItem<T> = Item<T>;

#[derive(Clone, Debug)]
pub struct TimeVec<T> {
    pub limit: Duration,
    buffer: VecDeque<Item<T>>,
}

impl<T> TimeVec<T> {
    pub fn new(limit: Duration, capacity: usize) -> Self {
        let buffer = VecDeque::with_capacity(capacity);
        Self { limit, buffer }
    }

    pub fn builder() -> TimeVecBuilder<T> {
        TimeVecBuilder::<T>::default()
    }

    #[inline]
    pub fn checked_duration(&self) -> Option<Duration> {
        self.buffer
            .front()
            .map(|oldest| self.buffer.back().unwrap().0 - oldest.0)
    }

    #[inline]
    pub fn duration(&self) -> Duration {
        self.checked_duration().unwrap_or(Duration::ZERO)
    }

    #[inline]
    fn timestamp_is_ok(&self, value: Duration) -> bool {
        self.buffer
            .back()
            .map(|i| value > i.0)
            .unwrap_or(true)
    }

    #[inline]
    pub fn push_back_checked(&mut self, timestamp: Duration, item: T) -> Option<Drain<Item<T>>> {
        self.timestamp_is_ok(timestamp).then(|| {
            self.push_back_unchecked(timestamp, item)
        })
    }

    #[inline]
    pub fn push_back(&mut self, timestamp: Duration, item: T) -> Drain<Item<T>> {
        assert!(self.timestamp_is_ok(timestamp), "Timestamp is older then previous.");
        self.push_back_unchecked(timestamp, item)
    }

    #[inline]
    pub fn push_back_unchecked(&mut self, timestamp: Duration, item: T) -> Drain<Item<T>> {
        self.buffer.push_back((timestamp, item));

        let partition_timestamp = timestamp.saturating_sub(self.limit);
        let partition_point = self.buffer.partition_point(|i| i.0 < partition_timestamp);

        self.buffer.drain(0..partition_point)
    }

    #[inline]
    pub fn pop_front(&mut self) -> Option<Item<T>> {
        self.buffer.pop_front()
    }

    #[inline]
    pub fn pop_back(&mut self) -> Option<Item<T>> {
        self.buffer.pop_back()
    }

    #[inline]
    pub fn duration_from_back(&self, duration: &Duration) -> Option<Duration> {
        self.buffer.back().map(|item| duration.checked_sub(item.0)).flatten()
    }

    #[inline]
    pub fn clear(&mut self) {
        self.buffer.clear()
    }

    #[inline]
    pub fn len(&self) -> usize {
        self.buffer.len()
    }

    #[inline]
    pub fn iter<'a>(&'a self) -> impl ExactSizeIterator<Item = &Item<T>> + 'a {
        self.buffer.iter()
    }

    #[inline]
    pub fn iter_data<'a>(&'a self) -> impl ExactSizeIterator<Item = &T> + 'a {
        self.buffer.iter().map(|snap| &snap.1)
    }

    #[inline]
    pub fn iter_time<'a>(&'a self) -> impl ExactSizeIterator<Item = &Duration> + 'a {
        self.buffer.iter().map(|snap| &snap.0)
    }

    #[inline]
    pub fn is_empty(&self) -> bool {
        self.buffer.is_empty()
    }

    #[inline]
    pub fn drain<R: RangeBounds<usize>>(&mut self, range: R) -> Drain<Item<T>> {
        self.buffer.drain(range)
    }
}

#[derive(Copy, Clone, Debug)]
pub struct TimeVecBuilder<T> {
    pub limit: Option<Duration>,
    pub capacity: Option<usize>,
    _item: PhantomData<T>,
}

impl<T> Default for TimeVecBuilder<T> {
    fn default() -> Self {
        Self { limit: None, capacity: None, _item: PhantomData }
    }
}

impl<T> TimeVecBuilder<T> {
    pub fn with_limit(mut self, value: Duration) -> Self {
        self.limit = Some(value);
        self
    }

    pub fn with_limit_secs(self, value: u64) -> Self {
        self.with_limit(Duration::from_secs(value))
    }

    pub fn with_limit_micros(self, value: u64) -> Self {
        self.with_limit(Duration::from_micros(value))
    }

    pub fn with_limit_millis(self, value: u64) -> Self {
        self.with_limit(Duration::from_millis(value))
    }

    pub fn with_limit_nanos(self, value: u64) -> Self {
        self.with_limit(Duration::from_nanos(value))
    }

    pub fn with_capacity(mut self, value: usize) -> Self {
        self.capacity = Some(value);
        self
    }

    pub fn build(self) -> TimeVec<T> {
        TimeVec {
            limit: self.limit.unwrap_or_default(),
            buffer: self.capacity
                .map(|value| VecDeque::<Item<T>>::with_capacity(value))
                .unwrap_or_default()
                
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::time::Duration;

    #[test]
    fn with_zero_limit() {
        let mut tv = TimeVec::<()>::builder()
            .with_limit(Duration::ZERO)
            .build();
        assert!(tv.is_empty());

        assert_eq!(tv.checked_duration(), None);
        assert_eq!(tv.duration(), Duration::ZERO);
        assert_eq!(tv.len(), 0);

        tv.push_back(Duration::from_secs(1), ());
        assert_eq!(tv.checked_duration(), Some(Duration::ZERO));
        assert_eq!(tv.duration(), Duration::ZERO);
        assert_eq!(tv.len(), 1);
    }

    #[test]
    fn push_two_items_with_same_timestamp() {
        let mut tv = TimeVec::<()>::builder()
            .with_limit_nanos(1)
            .build();
        
        tv.push_back_checked(Duration::from_secs(1), ());
        assert_eq!(tv.len(), 1);

        tv.push_back_checked(Duration::from_secs(1), ());
        assert_eq!(tv.len(), 1);
    }

    #[test]
    fn min_limit() {
        let mut tv = TimeVec::<()>::builder()
            .with_limit_nanos(1)
            .build();
        
        tv.push_back(Duration::ZERO, ());
        assert_eq!(tv.len(), 1);

        tv.push_back(Duration::from_nanos(1), ());
        assert_eq!(tv.len(), 2);

        tv.push_back(Duration::from_nanos(2), ());
        assert_eq!(tv.checked_duration(), Some(Duration::from_nanos(1)));
        assert_eq!(tv.len(), 2);
    }

    #[test]
    fn push_above_the_non_min_limit() {
        let mut tv = TimeVec::<()>::builder()
            .with_limit_nanos(3)
            .build();
        
        tv.push_back(Duration::ZERO, ());
        tv.push_back(Duration::from_nanos(1), ());
        tv.push_back(Duration::from_nanos(2), ());
        tv.push_back(Duration::from_nanos(3), ());
        assert_eq!(tv.len(), 4);

        tv.push_back(Duration::from_nanos(4), ());
        assert_eq!(tv.len(), 4);
    }
}