use crate::{
geometry::Transform,
time::{Stamp, TimePoint, Timestamp},
};
use alloc::{collections::BTreeMap, string::String};
use core::{fmt, time::Duration};
pub(crate) use error::{GetError, InsertError};
mod error;
type NearestTransforms<'a, T> = (
Option<(&'a T, &'a Transform<T>)>,
Option<(&'a T, &'a Transform<T>)>,
);
pub(crate) struct Buffer<T = Timestamp>
where
T: TimePoint,
{
parent: Option<String>,
child: Option<String>,
kind: Kind<T>,
}
impl<T> fmt::Debug for Buffer<T>
where
T: TimePoint,
{
fn fmt(
&self,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
let mut summary = f.debug_struct("Buffer");
summary
.field("parent", &self.parent)
.field("child", &self.child);
match &self.kind {
Kind::Static(slot) => summary.field("kind", &"static").field("transform", slot),
Kind::Dynamic {
data,
latest_timestamp,
max_age,
} => summary
.field("kind", &"dynamic")
.field("samples", &data.len())
.field(
"covered",
&data
.first_key_value()
.zip(data.last_key_value())
.map(|((start, _), (end, _))| (start, end)),
)
.field("latest", latest_timestamp)
.field("max_age", max_age),
};
summary.finish()
}
}
pub(crate) enum Coverage<T>
where
T: TimePoint,
{
AllTime,
Range {
start: T,
end: T,
},
Empty,
}
enum Kind<T>
where
T: TimePoint,
{
Static(Option<Transform<T>>),
Dynamic {
data: BTreeMap<T, Transform<T>>,
latest_timestamp: Option<T>,
max_age: Option<Duration>,
},
}
impl<T> Buffer<T>
where
T: TimePoint,
{
#[must_use]
pub fn dynamic() -> Self {
Self {
parent: None,
child: None,
kind: Kind::Dynamic {
data: BTreeMap::new(),
latest_timestamp: None,
max_age: None,
},
}
}
#[must_use]
pub fn dynamic_with_max_age(max_age: Duration) -> Self {
Self {
parent: None,
child: None,
kind: Kind::Dynamic {
data: BTreeMap::new(),
latest_timestamp: None,
max_age: Some(max_age),
},
}
}
#[must_use]
pub fn static_edge() -> Self {
Self {
parent: None,
child: None,
kind: Kind::Static(None),
}
}
#[must_use]
pub fn parent(&self) -> Option<&str> {
self.parent.as_deref()
}
#[must_use]
pub fn coverage(&self) -> Coverage<T> {
match &self.kind {
Kind::Static(Some(_)) => Coverage::AllTime,
Kind::Static(None) => Coverage::Empty,
Kind::Dynamic { data, .. } => match (data.first_key_value(), data.last_key_value()) {
(Some((&start, _)), Some((&end, _))) => Coverage::Range { start, end },
_ => Coverage::Empty,
},
}
}
pub fn insert(
&mut self,
transform: Transform<T>,
) -> Result<(), InsertError> {
transform.validate().map_err(InsertError::Invalid)?;
if transform.parent() == transform.child() {
return Err(InsertError::SelfReferentialFrame);
}
if let Some(parent) = &self.parent {
if parent != transform.parent() {
return Err(InsertError::ReparentingNotSupported(parent.clone()));
}
}
if let Some(child) = &self.child {
if child != transform.child() {
return Err(InsertError::ChildFrameMismatch {
pinned: child.clone(),
found: transform.child().into(),
});
}
}
let pin = self
.parent
.is_none()
.then(|| (transform.parent().into(), transform.child().into()));
match (&mut self.kind, transform.timestamp()) {
(Kind::Static(slot), Stamp::Static) => {
*slot = Some(transform);
}
(
Kind::Dynamic {
data,
latest_timestamp,
max_age,
},
Stamp::At(timestamp),
) => {
*latest_timestamp = Some(match *latest_timestamp {
Some(current_latest) if current_latest > timestamp => current_latest,
_ => timestamp,
});
data.insert(timestamp, transform);
remove_expired(data, *latest_timestamp, *max_age);
}
_ => return Err(InsertError::StaticDynamicConflict),
}
if let Some((parent, child)) = pin {
self.parent = Some(parent);
self.child = Some(child);
}
Ok(())
}
pub fn get(
&self,
timestamp: T,
) -> Result<Transform<T>, GetError<T>> {
let data = match &self.kind {
Kind::Static(Some(transform)) => return Ok(transform.clone()),
Kind::Static(None) => return Err(GetError::NoTransformAvailable),
Kind::Dynamic { data, .. } => data,
};
let (before, after) = self.get_nearest(×tamp);
match (before, after) {
(Some(before), Some(after)) => Transform::interpolate(before.1, after.1, timestamp)
.map_err(GetError::Interpolation),
_ => match (data.first_key_value(), data.last_key_value()) {
(Some((first, _)), Some((last, _))) => Err(GetError::OutOfRange {
start: *first,
end: *last,
}),
_ => Err(GetError::NoTransformAvailable),
},
}
}
fn get_nearest(
&self,
timestamp: &T,
) -> NearestTransforms<'_, T> {
let Kind::Dynamic { data, .. } = &self.kind else {
return (None, None);
};
let before = data.range(..=timestamp).next_back();
if let Some((t, _)) = before {
if t == timestamp {
return (before, before);
}
}
let after = data.range(timestamp..).next();
(before, after)
}
pub fn remove_before(
&mut self,
timestamp: T,
) {
if let Kind::Dynamic {
data,
latest_timestamp,
..
} = &mut self.kind
{
let kept = data.split_off(×tamp);
*data = kept;
*latest_timestamp = data.last_key_value().map(|(&k, _)| k);
}
}
}
fn remove_expired<T>(
data: &mut BTreeMap<T, Transform<T>>,
latest_timestamp: Option<T>,
max_age: Option<Duration>,
) where
T: TimePoint,
{
if let (Some(max_age), Some(latest_timestamp)) = (max_age, latest_timestamp) {
if let Ok(threshold) = latest_timestamp.checked_sub(max_age) {
while let Some((&oldest, _)) = data.first_key_value() {
if oldest >= threshold {
break;
}
data.pop_first();
}
}
}
}
#[cfg(test)]
mod tests;