use bma_ts::Monotonic;
use core::fmt;
use std::{ops::Deref, time::Duration};
pub struct TtlCell<T> {
value: Option<T>,
ttl: Duration,
set_at: Monotonic,
}
impl<T> fmt::Debug for TtlCell<T>
where
T: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.as_ref() {
Some(v) => write!(f, "Some({:?})", v),
None => write!(f, "None"),
}
}
}
impl<T> PartialEq for TtlCell<T>
where
T: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.as_ref() == other.as_ref()
}
}
impl<T> Eq for TtlCell<T> where T: Eq {}
impl<T> Clone for TtlCell<T>
where
T: Clone,
{
fn clone(&self) -> Self {
Self {
value: self.value.clone(),
ttl: self.ttl,
set_at: self.set_at,
}
}
}
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.is_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.is_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<(&'a T, &'a 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.is_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 is_expired(&self) -> bool {
self.set_at.elapsed() > self.ttl || self.value.is_none()
}
#[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);
insta::assert_debug_snapshot!(opt.as_ref().copied(), @r###"
Some(
25,
)
"###);
thread::sleep(ttl);
insta::assert_debug_snapshot!(opt.as_ref().copied(), @"None");
opt.set(30);
thread::sleep(ttl / 2);
insta::assert_debug_snapshot!(opt.as_ref().copied(), @r###"
Some(
30,
)
"###);
thread::sleep(ttl);
insta::assert_debug_snapshot!(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);
insta::assert_debug_snapshot!(opt.take(), @r###"
Some(
25,
)
"###);
insta::assert_debug_snapshot!(opt.as_ref().copied(), @"None");
opt.set(30);
thread::sleep(ttl / 2);
insta::assert_debug_snapshot!(opt.replace(29), @r###"
Some(
30,
)
"###);
thread::sleep(ttl);
insta::assert_debug_snapshot!(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);
insta::assert_debug_snapshot!(first
.take_with(&mut second, Duration::from_millis(100)), @r###"
Some(
(
25,
25,
),
)
"###);
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);
insta::assert_debug_snapshot!(
first.take_with(&mut second, Duration::from_millis(50)), @"None");
}
}