use crate::{
geometry::Transform,
time::{TimePoint, Timestamp},
};
use alloc::{collections::BTreeMap, string::String};
use core::time::Duration;
pub use error::BufferError;
mod error;
type NearestTransforms<'a, T> = (
Option<(&'a T, &'a Transform<T>)>,
Option<(&'a T, &'a Transform<T>)>,
);
#[derive(Debug)]
pub struct Buffer<T = Timestamp>
where
T: TimePoint,
{
data: BTreeMap<T, Transform<T>>,
max_age: Option<Duration>,
latest_timestamp: Option<T>,
is_static: bool,
parent: Option<String>,
}
impl<T> Buffer<T>
where
T: TimePoint,
{
#[must_use]
pub fn new() -> Self {
Self {
data: BTreeMap::new(),
max_age: None,
latest_timestamp: None,
is_static: false,
parent: None,
}
}
#[must_use]
pub fn with_max_age(max_age: Duration) -> Self {
Self {
data: BTreeMap::new(),
max_age: Some(max_age),
latest_timestamp: None,
is_static: false,
parent: None,
}
}
#[must_use]
pub fn parent(&self) -> Option<&str> {
self.parent.as_deref()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
pub fn insert(
&mut self,
transform: Transform<T>,
) -> Result<(), BufferError> {
transform.validate()?;
if transform.parent == transform.child {
return Err(BufferError::SelfReferentialFrame);
}
if let Some(parent) = &self.parent {
if *parent != transform.parent {
return Err(BufferError::ReparentingNotSupported(parent.clone()));
}
} else {
self.parent = Some(transform.parent.clone());
}
let timestamp = transform.timestamp;
let is_static = timestamp.is_static();
if self.data.is_empty() {
self.is_static = is_static;
} else if self.is_static != is_static {
return Err(BufferError::StaticDynamicConflict);
}
self.data.insert(timestamp, transform);
if !self.is_static {
self.latest_timestamp = Some(match self.latest_timestamp {
Some(current_latest) if current_latest > timestamp => current_latest,
_ => timestamp,
});
self.delete_expired();
}
Ok(())
}
pub fn get(
&self,
timestamp: &T,
) -> Result<Transform<T>, BufferError> {
if self.is_static {
match self.data.get(&T::static_timestamp()) {
Some(tf) => return Ok(tf.clone()),
None => return Err(BufferError::NoTransformAvailable),
}
}
let (before, after) = self.get_nearest(timestamp);
match (before, after) {
(Some(before), Some(after)) => {
Ok(Transform::interpolate(before.1, after.1, *timestamp)?)
}
_ => Err(BufferError::NoTransformAvailable),
}
}
pub fn delete_before(
&mut self,
timestamp: T,
) {
if self.is_static {
return;
}
self.data.retain(|&k, _| k >= timestamp);
}
fn get_nearest(
&self,
timestamp: &T,
) -> NearestTransforms<'_, T> {
let before = self.data.range(..=timestamp).next_back();
if let Some((t, _)) = before {
if t == timestamp {
return (before, before);
}
}
let after = self.data.range(timestamp..).next();
(before, after)
}
fn delete_expired(&mut self) {
if let (Some(max_age), Some(latest_timestamp)) = (self.max_age, self.latest_timestamp) {
if let Ok(threshold) = latest_timestamp.checked_sub(max_age) {
self.data.retain(|&k, _| k >= threshold);
}
}
}
}
impl<T> Default for Buffer<T>
where
T: TimePoint,
{
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests;