use crate::{
core::{Buffer, buffer::GetError},
geometry::{Localized, Quaternion, Transform, Vector3},
time::{Stamp, TimePoint, Timestamp},
};
use alloc::{collections::VecDeque, string::String};
pub use error::RegistryError;
use hashbrown::HashMap;
use core::time::Duration;
mod error;
#[derive(Debug)]
pub struct Registry<T = Timestamp>
where
T: TimePoint,
{
data: HashMap<String, Buffer<T>>,
max_age: Option<Duration>,
}
impl<T> Registry<T>
where
T: TimePoint,
{
#[must_use]
pub fn new() -> Self {
Self {
data: HashMap::new(),
max_age: None,
}
}
#[must_use]
pub fn with_max_age(max_age: Duration) -> Self {
Self {
data: HashMap::new(),
max_age: Some(max_age),
}
}
pub fn add_transform(
&mut self,
t: Transform<T>,
) -> Result<(), RegistryError<T>> {
Self::process_add_transform(t, &mut self.data, self.max_age)
}
pub fn get_transform(
&self,
target: &str,
source: &str,
timestamp: T,
) -> Result<Transform<T>, RegistryError<T>> {
Self::process_get_transform(target, source, timestamp, &self.data)
}
pub fn get_transform_for<U>(
&self,
value: &U,
target_frame: &str,
) -> Result<Transform<T>, RegistryError<T>>
where
U: Localized<T>,
{
self.get_transform(target_frame, value.frame(), value.timestamp())
}
pub fn get_transform_at(
&self,
target_frame: &str,
target_time: T,
source_frame: &str,
source_time: T,
fixed_frame: &str,
) -> Result<Transform<T>, RegistryError<T>> {
Self::process_get_transform_at(
target_frame,
target_time,
source_frame,
source_time,
fixed_frame,
&self.data,
)
}
pub fn remove_transforms_before(
&mut self,
timestamp: T,
) {
for buffer in self.data.values_mut() {
buffer.remove_before(timestamp);
}
}
pub fn remove_frame(
&mut self,
child: &str,
) -> bool {
self.data.remove(child).is_some()
}
fn process_add_transform(
t: Transform<T>,
data: &mut HashMap<String, Buffer<T>>,
max_age: Option<Duration>,
) -> Result<(), RegistryError<T>> {
if !data.contains_key(t.child()) && Self::creates_cycle(t.child(), t.parent(), data) {
return Err(RegistryError::CycleDetected);
}
if let Some(buffer) = data.get_mut(t.child()) {
return buffer.insert(t).map_err(Into::into);
}
let mut buffer = match (t.timestamp(), max_age) {
(Stamp::Static, _) => Buffer::static_edge(),
(Stamp::At(_), Some(max_age)) => Buffer::dynamic_with_max_age(max_age),
(Stamp::At(_), None) => Buffer::dynamic(),
};
let child: String = t.child().into();
buffer.insert(t)?;
data.insert(child, buffer);
Ok(())
}
fn creates_cycle(
child: &str,
parent: &str,
data: &HashMap<String, Buffer<T>>,
) -> bool {
let mut current = parent;
while let Some(buffer) = data.get(current) {
match buffer.parent() {
Some(next) => {
if next == child {
return true;
}
current = next;
}
None => return false,
}
}
false
}
fn frame_exists(
frame: &str,
data: &HashMap<String, Buffer<T>>,
) -> bool {
data.contains_key(frame) || data.values().any(|buffer| buffer.parent() == Some(frame))
}
fn diagnose_not_found(
from: &str,
to: &str,
timestamp: T,
data: &HashMap<String, Buffer<T>>,
walk_failure: &mut Option<(String, GetError<T>)>,
) -> RegistryError<T> {
for frame in [from, to] {
if !Self::frame_exists(frame, data) {
return RegistryError::UnknownFrame(frame.into());
}
}
let (frame, covered) = match walk_failure.take() {
Some((frame, GetError::NoTransformAvailable)) => (frame, None),
Some((frame, GetError::OutOfRange { start, end })) => (frame, Some((start, end))),
Some((_, GetError::Interpolation(cause))) => return cause.into(),
None => {
return RegistryError::Disconnected {
target_frame: from.into(),
source_frame: to.into(),
};
}
};
RegistryError::NotFoundAt {
target_frame: from.into(),
source_frame: to.into(),
frame,
requested: timestamp,
covered,
}
}
fn process_get_transform(
target: &str,
source: &str,
timestamp: T,
data: &HashMap<String, Buffer<T>>,
) -> Result<Transform<T>, RegistryError<T>> {
if target == source {
return Ok(Transform::unvalidated(
target.into(),
source.into(),
Vector3::zero(),
Quaternion::identity(),
Stamp::At(timestamp),
));
}
let reached = |chain: &VecDeque<Transform<T>>, goal: &str| {
chain.back().is_some_and(|tf| tf.parent() == goal)
};
let mut walk_failure = None;
let target_chain =
Self::get_transform_chain(target, source, timestamp, data, &mut walk_failure);
let result = match target_chain {
Some(target_chain) if reached(&target_chain, source) => {
Self::combine_transforms(target_chain, VecDeque::new())
}
target_chain => match (
target_chain,
Self::get_transform_chain(source, target, timestamp, data, &mut walk_failure),
) {
(_, Some(source_chain)) if reached(&source_chain, target) => {
Self::combine_transforms(VecDeque::new(), source_chain)
}
(Some(mut target_chain), Some(mut source_chain)) => {
Self::truncate_at_common_parent(&mut target_chain, &mut source_chain);
let connected = match (target_chain.back(), source_chain.back()) {
(Some(target_top), Some(source_top)) => {
target_top.parent() == source_top.parent()
}
_ => false,
};
if connected {
Self::combine_transforms(target_chain, source_chain)
} else {
Some(Err(Self::diagnose_not_found(
target,
source,
timestamp,
data,
&mut walk_failure,
)))
}
}
(Some(target_chain), None) => {
Self::combine_transforms(target_chain, VecDeque::new())
}
(None, Some(source_chain)) => {
Self::combine_transforms(VecDeque::new(), source_chain)
}
(None, None) => Some(Err(Self::diagnose_not_found(
target,
source,
timestamp,
data,
&mut walk_failure,
))),
},
}
.unwrap_or_else(|| {
Err(Self::diagnose_not_found(
target,
source,
timestamp,
data,
&mut walk_failure,
))
})?;
if result.parent() != target || result.child() != source {
return Err(Self::diagnose_not_found(
target,
source,
timestamp,
data,
&mut walk_failure,
));
}
Ok(result.restamped(Stamp::At(timestamp)))
}
fn process_get_transform_at(
target_frame: &str,
target_time: T,
source_frame: &str,
source_time: T,
fixed_frame: &str,
data: &HashMap<String, Buffer<T>>,
) -> Result<Transform<T>, RegistryError<T>> {
if source_frame == fixed_frame && target_frame == fixed_frame {
return Ok(Transform::unvalidated(
target_frame.into(),
source_frame.into(),
Vector3::zero(),
Quaternion::identity(),
Stamp::At(target_time),
));
}
if source_frame == fixed_frame {
let result = Self::process_get_transform(fixed_frame, target_frame, target_time, data)?
.inverse()?;
return Ok(result.restamped(Stamp::At(target_time)));
}
if target_frame == fixed_frame {
let result = Self::process_get_transform(fixed_frame, source_frame, source_time, data)?;
return Ok(result.restamped(Stamp::At(target_time)));
}
let source_to_fixed =
Self::process_get_transform(fixed_frame, source_frame, source_time, data)?;
let target_to_fixed =
Self::process_get_transform(fixed_frame, target_frame, target_time, data)?;
let result = target_to_fixed
.inverse()?
.compose_ignoring_time(source_to_fixed)?;
Ok(result.restamped(Stamp::At(target_time)))
}
fn get_transform_chain(
from: &str,
to: &str,
timestamp: T,
data: &HashMap<String, Buffer<T>>,
walk_failure: &mut Option<(String, GetError<T>)>,
) -> Option<VecDeque<Transform<T>>> {
let mut transforms = VecDeque::new();
let mut current_frame: String = from.into();
while let Some(frame_buffer) = data.get(¤t_frame) {
match frame_buffer.get(timestamp) {
Ok(tf) => {
current_frame.clear();
current_frame.push_str(tf.parent());
transforms.push_back(tf);
}
Err(source) => {
if walk_failure.is_none() {
*walk_failure = Some((current_frame.clone(), source));
}
break;
}
}
if current_frame == to {
break;
}
}
if transforms.is_empty() {
None
} else {
Some(transforms)
}
}
fn truncate_at_common_parent(
from_chain: &mut VecDeque<Transform<T>>,
to_chain: &mut VecDeque<Transform<T>>,
) {
let mut start_idx = 0;
for (i, j) in from_chain.iter().rev().zip(to_chain.iter().rev()) {
if i == j {
start_idx += 1;
} else {
break;
}
}
from_chain.truncate(from_chain.len() - start_idx);
to_chain.truncate(to_chain.len() - start_idx);
}
fn combine_transforms(
target_chain: VecDeque<Transform<T>>,
source_chain: VecDeque<Transform<T>>,
) -> Option<Result<Transform<T>, RegistryError<T>>> {
let target = match Self::compose_chain(target_chain) {
Ok(composed) => composed,
Err(e) => return Some(Err(e)),
};
let source = match Self::compose_chain(source_chain) {
Ok(composed) => composed,
Err(e) => return Some(Err(e)),
};
match (target, source) {
(None, None) => None,
(Some(target), None) => Some(target.inverse().map_err(Into::into)),
(None, Some(source)) => Some(Ok(source)),
(Some(target), Some(source)) => Some(
target
.inverse()
.and_then(|inverted| inverted * source)
.map_err(Into::into),
),
}
}
fn compose_chain(
chain: VecDeque<Transform<T>>
) -> Result<Option<Transform<T>>, RegistryError<T>> {
let mut iter = chain.into_iter();
let Some(mut composed) = iter.next() else {
return Ok(None);
};
for transform in iter {
composed = (transform * composed)?;
}
Ok(Some(composed))
}
}
impl<T> Default for Registry<T>
where
T: TimePoint,
{
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests;