plckit 0.1.2

Kit for PLCs and real-time micro-services
Documentation
use bma_ts::Monotonic;
use std::{ops::Deref, time::Duration};

pub struct TtlCell<T> {
    value: Option<T>,
    ttl: Duration,
    set_at: Monotonic,
}

impl<T> TtlCell<T> {
    #[inline]
    pub fn new(ttl: Duration) -> Self {
        Self {
            value: None,
            ttl,
            set_at: Monotonic::now(),
        }
    }
    #[inline]
    pub fn new_with_value(ttl: Duration, value: T) -> Self {
        Self {
            value: Some(value),
            ttl,
            set_at: Monotonic::now(),
        }
    }
    #[inline]
    pub fn replace(&mut self, value: T) -> Option<T> {
        let prev = self.value.replace(value);
        let result = if self.expired() { None } else { prev };
        self.touch();
        result
    }
    #[inline]
    pub fn set(&mut self, value: T) {
        self.value = Some(value);
        self.touch();
    }
    #[inline]
    pub fn clear(&mut self) {
        self.value = None;
    }
    #[inline]
    pub fn as_ref(&self) -> Option<&T> {
        if self.expired() {
            None
        } else {
            self.value.as_ref()
        }
    }
    #[inline]
    pub fn as_ref_with<'a, O>(
        &'a self,
        other: &'a TtlCell<O>,
        max_time_delta: Duration,
    ) -> Option<(&T, &O)> {
        let maybe_first = self.as_ref();
        let maybe_second = other.as_ref();
        if let Some(first) = maybe_first {
            if let Some(second) = maybe_second {
                if self.set_at.abs_diff(other.set_at) <= max_time_delta {
                    return Some((first, second));
                }
            }
        }
        None
    }
    #[inline]
    pub fn take(&mut self) -> Option<T> {
        if self.expired() {
            None
        } else {
            self.value.take()
        }
    }
    #[inline]
    pub fn take_with<O>(
        &mut self,
        other: &mut TtlCell<O>,
        max_time_delta: Duration,
    ) -> Option<(T, O)> {
        let maybe_first = self.take();
        let maybe_second = other.take();
        if let Some(first) = maybe_first {
            if let Some(second) = maybe_second {
                if self.set_at.abs_diff(other.set_at) <= max_time_delta {
                    return Some((first, second));
                }
            }
        }
        None
    }
    #[inline]
    pub fn as_deref(&self) -> Option<&T::Target>
    where
        T: Deref,
    {
        match self.as_ref() {
            Some(t) => Some(&**t),
            None => None,
        }
    }
    #[inline]
    pub fn expired(&self) -> bool {
        self.set_at.elapsed() > self.ttl
    }
    #[inline]
    pub fn touch(&mut self) {
        self.set_at = Monotonic::now();
    }
    #[inline]
    pub fn set_at(&self) -> Monotonic {
        self.set_at
    }
}

#[cfg(test)]
mod test {
    use std::{thread, time::Duration};

    use super::TtlCell;

    #[test]
    fn test_get_set() {
        let ttl = Duration::from_millis(10);
        let mut opt = TtlCell::new_with_value(ttl, 25);
        thread::sleep(ttl / 2);
        assert_eq!(opt.as_ref().copied(), Some(25));
        thread::sleep(ttl);
        assert_eq!(opt.as_ref().copied(), None);
        opt.set(30);
        thread::sleep(ttl / 2);
        assert_eq!(opt.as_ref().copied(), Some(30));
        thread::sleep(ttl);
        assert_eq!(opt.as_ref().copied(), None);
    }
    #[test]
    fn test_take_replace() {
        let ttl = Duration::from_millis(10);
        let mut opt = TtlCell::new_with_value(ttl, 25);
        thread::sleep(ttl / 2);
        assert_eq!(opt.take(), Some(25));
        assert_eq!(opt.as_ref().copied(), None);
        opt.set(30);
        thread::sleep(ttl / 2);
        assert_eq!(opt.replace(29), Some(30));
        assert_eq!(opt.as_ref().copied(), Some(29));
        thread::sleep(ttl);
        assert_eq!(opt.as_ref().copied(), None);
    }
    #[test]
    fn test_take_with() {
        let mut first = TtlCell::new_with_value(Duration::from_secs(1), 25);
        thread::sleep(Duration::from_millis(10));
        let mut second = TtlCell::new_with_value(Duration::from_secs(1), 25);
        assert!(first
            .take_with(&mut second, Duration::from_millis(100))
            .is_some());
        let mut first = TtlCell::new_with_value(Duration::from_secs(1), 25);
        thread::sleep(Duration::from_millis(100));
        let mut second = TtlCell::new_with_value(Duration::from_secs(1), 25);
        assert!(first
            .take_with(&mut second, Duration::from_millis(50))
            .is_none());
    }
}