use std::collections::BTreeMap;
use std::marker::PhantomData;
use std::sync::Arc;
use std::time::SystemTime;
use parking_lot::RwLock;
use tracing::{debug, warn};
use zenoh::Result;
use zenoh::liveliness::LivelinessToken;
use crate::Builder;
use crate::msg::{SerdeCdrSerdes, ZDeserializer, ZMessage};
use crate::pubsub::ZSubBuilder;
pub struct ZenohStamp;
pub struct ExtractorStamp<T, F: Fn(&T) -> SystemTime>(pub(crate) F, pub(crate) PhantomData<T>);
#[doc(hidden)]
pub struct CacheInner<T> {
pub entries: BTreeMap<SystemTime, Arc<T>>,
capacity: usize,
warned_no_ts: bool,
}
impl<T> CacheInner<T> {
pub fn new(capacity: usize) -> Self {
Self {
entries: BTreeMap::new(),
capacity,
warned_no_ts: false,
}
}
pub fn insert(&mut self, stamp: SystemTime, msg: T) {
self.entries.insert(stamp, Arc::new(msg));
while self.entries.len() > self.capacity {
self.entries.pop_first();
}
}
}
pub struct ZCache<T: ZMessage> {
inner: Arc<RwLock<CacheInner<T>>>,
_sub: zenoh::pubsub::Subscriber<()>,
_lv_token: LivelinessToken,
}
impl<T: ZMessage> ZCache<T> {
pub fn get_interval(&self, t_start: SystemTime, t_end: SystemTime) -> Vec<Arc<T>> {
if t_start > t_end {
return Vec::new();
}
let inner = self.inner.read();
inner
.entries
.range(t_start..=t_end)
.map(|(_, v)| Arc::clone(v))
.collect()
}
pub fn get_before(&self, t: SystemTime) -> Option<Arc<T>> {
let inner = self.inner.read();
inner
.entries
.range(..=t)
.next_back()
.map(|(_, v)| Arc::clone(v))
}
pub fn get_after(&self, t: SystemTime) -> Option<Arc<T>> {
let inner = self.inner.read();
inner.entries.range(t..).next().map(|(_, v)| Arc::clone(v))
}
pub fn get_nearest(&self, t: SystemTime) -> Option<Arc<T>> {
let inner = self.inner.read();
if inner.entries.is_empty() {
return None;
}
let before = inner
.entries
.range(..=t)
.next_back()
.map(|(k, v)| (*k, Arc::clone(v)));
let after = inner
.entries
.range(t..)
.next()
.map(|(k, v)| (*k, Arc::clone(v)));
match (before, after) {
(None, Some((_, v))) => Some(v),
(Some((_, v)), None) => Some(v),
(Some((kb, vb)), Some((ka, va))) => {
let dist_before = t.duration_since(kb).unwrap_or_default();
let dist_after = ka.duration_since(t).unwrap_or_default();
if dist_after < dist_before {
Some(va)
} else {
Some(vb)
}
}
(None, None) => None,
}
}
pub fn oldest_stamp(&self) -> Option<SystemTime> {
self.inner.read().entries.keys().next().copied()
}
pub fn newest_stamp(&self) -> Option<SystemTime> {
self.inner.read().entries.keys().next_back().copied()
}
pub fn len(&self) -> usize {
self.inner.read().entries.len()
}
pub fn is_empty(&self) -> bool {
self.inner.read().entries.is_empty()
}
pub fn clear(&self) {
self.inner.write().entries.clear();
}
}
pub struct ZCacheBuilder<T, S = SerdeCdrSerdes<T>, Stamp = ZenohStamp> {
pub(crate) sub_builder: ZSubBuilder<T, S>,
capacity: usize,
stamp: Stamp,
}
impl<T: ZMessage, S> ZCacheBuilder<T, S, ZenohStamp> {
pub(crate) fn new(sub_builder: ZSubBuilder<T, S>, capacity: usize) -> Self {
Self {
sub_builder,
capacity,
stamp: ZenohStamp,
}
}
pub fn with_stamp<F>(self, extractor: F) -> ZCacheBuilder<T, S, ExtractorStamp<T, F>>
where
F: Fn(&T) -> SystemTime + Send + Sync + 'static,
{
ZCacheBuilder {
sub_builder: self.sub_builder,
capacity: self.capacity,
stamp: ExtractorStamp(extractor, PhantomData),
}
}
pub fn with_capacity(mut self, capacity: usize) -> Self {
self.capacity = capacity;
self
}
pub fn with_qos(mut self, qos: crate::qos::QosProfile) -> Self {
self.sub_builder = self.sub_builder.with_qos(qos);
self
}
}
impl<T: ZMessage, S, F> ZCacheBuilder<T, S, ExtractorStamp<T, F>>
where
F: Fn(&T) -> SystemTime + Send + Sync + 'static,
{
pub fn with_capacity(mut self, capacity: usize) -> Self {
self.capacity = capacity;
self
}
pub fn with_qos(mut self, qos: crate::qos::QosProfile) -> Self {
self.sub_builder = self.sub_builder.with_qos(qos);
self
}
}
impl<T, S> Builder for ZCacheBuilder<T, S, ZenohStamp>
where
T: ZMessage + Send + Sync + 'static,
S: for<'a> ZDeserializer<Input<'a> = &'a [u8], Output = T> + 'static,
{
type Output = ZCache<T>;
fn build(self) -> Result<ZCache<T>> {
let ZCacheBuilder {
sub_builder,
capacity,
..
} = self;
let inner = Arc::new(RwLock::new(CacheInner::<T>::new(capacity)));
let inner_cb = inner.clone();
let (sub, lv_token) =
sub_builder.build_raw_subscriber(move |sample: zenoh::sample::Sample| {
let payload = sample.payload().to_bytes();
match S::deserialize(&payload) {
Ok(msg) => {
let stamp = match sample.timestamp() {
Some(ts) => ts.get_time().to_system_time(),
None => {
let mut guard = inner_cb.write();
if !guard.warned_no_ts {
warn!(
"[CACHE] Incoming sample has no Zenoh timestamp; \
falling back to SystemTime::now(). \
Enable timestamping in the Zenoh config to avoid this."
);
guard.warned_no_ts = true;
}
drop(guard);
SystemTime::now()
}
};
inner_cb.write().insert(stamp, msg);
}
Err(e) => tracing::error!("[CACHE] Failed to deserialize message: {}", e),
}
})?;
debug!("[CACHE] ZenohStamp cache ready");
Ok(ZCache {
inner,
_sub: sub,
_lv_token: lv_token,
})
}
}
impl<T, S, F> Builder for ZCacheBuilder<T, S, ExtractorStamp<T, F>>
where
T: ZMessage + Send + Sync + 'static,
S: for<'a> ZDeserializer<Input<'a> = &'a [u8], Output = T> + 'static,
F: Fn(&T) -> SystemTime + Send + Sync + 'static,
{
type Output = ZCache<T>;
fn build(self) -> Result<ZCache<T>> {
let ZCacheBuilder {
sub_builder,
capacity,
stamp: ExtractorStamp(extractor, _),
} = self;
let inner = Arc::new(RwLock::new(CacheInner::<T>::new(capacity)));
let inner_cb = inner.clone();
let (sub, lv_token) =
sub_builder.build_raw_subscriber(move |sample: zenoh::sample::Sample| {
let payload = sample.payload().to_bytes();
match S::deserialize(&payload) {
Ok(msg) => {
let stamp = extractor(&msg);
inner_cb.write().insert(stamp, msg);
}
Err(e) => tracing::error!("[CACHE] Failed to deserialize message: {}", e),
}
})?;
debug!("[CACHE] ExtractorStamp cache ready");
Ok(ZCache {
inner,
_sub: sub,
_lv_token: lv_token,
})
}
}