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());
}
}