tf2_rs 0.1.0

ROS 2 TF2 bindings for Rust using cxx and rclrs
use crate::error::Tf2Error;
use crate::ffi::ffi::{self, BufferCoreWrapper};
use crate::ffi_utils::{call_bool, call_out};
use crate::time::{LookupTime, TimeSpec};
use crate::transform::Transformable;
use crate::transform_stamped::TransformStamped;
use ros_env::tf2_msgs;

unsafe impl Send for BufferCoreWrapper {}
unsafe impl Sync for BufferCoreWrapper {}

#[derive(Clone)]
pub struct BufferCore {
    inner: cxx::SharedPtr<BufferCoreWrapper>,
}

impl BufferCore {
    pub fn new(cache_time_ns: std::time::Duration) -> Self {
        Self {
            inner: ffi::new_buffer_core(cache_time_ns.as_nanos() as u64),
        }
    }

    fn wrapper(&self) -> &BufferCoreWrapper {
        self.inner.as_ref().expect("BufferCoreWrapper is null")
    }

    pub fn clear(&mut self) {
        self.wrapper().clear();
    }

    pub fn set_transform(
        &mut self,
        tf: &TransformStamped,
        authority: &str,
        is_static: bool,
    ) -> Result<(), Tf2Error> {
        let ok = call_bool(|out_ok| {
            self.wrapper()
                .set_transform(&tf.to_ffi(), authority, is_static, out_ok)
        })?;

        if ok {
            Ok(())
        } else {
            Err(Tf2Error::InvalidArgument(
                "buffer_set_transform rejected transform".to_string(),
            ))
        }
    }

    pub fn check_transform(
        &self,
        target_frame: &str,
        source_frame: &str,
        when: LookupTime,
    ) -> Result<TransformAvailability, Tf2Error> {
        let t = ffi::Tf2Time::from(when);
        let out = call_out(|out| {
            self.wrapper()
                .can_transform(target_frame, source_frame, &t, out)
        })?;

        Ok(if out.available {
            TransformAvailability::Available
        } else {
            TransformAvailability::Unavailable {
                diagnostic: (!out.diagnostic.is_empty()).then_some(out.diagnostic),
            }
        })
    }

    pub fn can_transform(
        &self,
        target_frame: &str,
        source_frame: &str,
        when: LookupTime,
    ) -> Result<bool, Tf2Error> {
        Ok(self
            .check_transform(target_frame, source_frame, when)?
            .is_available())
    }

    pub fn lookup_transform(
        &self,
        target_frame: &str,
        source_frame: &str,
        when: LookupTime,
    ) -> Result<TransformStamped, Tf2Error> {
        let t = ffi::Tf2Time::from(when);
        let ffi_tf = call_out(|out| {
            self.wrapper()
                .lookup_transform(target_frame, source_frame, &t, out)
        })?;

        Ok(TransformStamped::from_ffi(ffi_tf))
    }

    pub fn transform<T: Transformable>(
        &self,
        msg: &T,
        target_frame: &str,
        time: TimeSpec,
    ) -> Result<T, Tf2Error> {
        let when = time.resolve(msg);
        let tf = self.lookup_transform(target_frame, msg.frame_id(), when)?;
        msg.apply_transform(&tf)
    }

    pub fn ingest_tf_message(
        &mut self,
        msg: tf2_msgs::msg::TFMessage,
        authority: &str,
        is_static: bool,
        mut on_err: impl FnMut(Tf2Error),
    ) {
        for t in msg.transforms {
            let tf = (&t).into();
            if let Err(e) = self.set_transform(&tf, authority, is_static) {
                on_err(e);
            }
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TransformAvailability {
    Available,
    Unavailable { diagnostic: Option<String> },
}

impl TransformAvailability {
    pub fn is_available(&self) -> bool {
        matches!(self, Self::Available)
    }

    pub fn diagnostic(&self) -> Option<&str> {
        match self {
            Self::Available => None,
            Self::Unavailable { diagnostic } => diagnostic.as_deref(),
        }
    }
}