openrr_plugin/
lib.rs

1#![doc = include_str!("../README.md")]
2#![warn(missing_debug_implementations, missing_docs, rust_2018_idioms)]
3// buggy: https://github.com/rust-lang/rust-clippy/issues?q=is%3Aissue+derive_partial_eq_without_eq
4#![allow(clippy::derive_partial_eq_without_eq)]
5// https://github.com/rodrimati1992/abi_stable_crates/issues/94
6#![allow(repr_transparent_external_private_fields)]
7
8mod proxy;
9
10#[rustfmt::skip]
11#[path = "gen/api.rs"]
12mod api;
13
14use std::{fmt, path::Path, sync::Arc, time::Duration};
15
16use abi_stable::{erased_types::TD_Opaque, library::lib_header_from_path, StableAbi};
17use arci::{async_trait, WaitFuture};
18
19pub use crate::api::*;
20// This is not a public API. Use export_plugin! macro for plugin exporting.
21#[doc(hidden)]
22pub use crate::proxy::PluginMod_Ref;
23use crate::proxy::{GamepadTraitObject, JointTrajectoryClientTraitObject};
24
25/// Exports the plugin that will instantiated with the specified expression.
26///
27/// # Examples
28///
29/// ```
30/// use openrr_plugin::Plugin;
31///
32/// openrr_plugin::export_plugin!(MyPlugin);
33///
34/// pub struct MyPlugin;
35///
36/// impl Plugin for MyPlugin {
37/// }
38/// ```
39#[macro_export]
40macro_rules! export_plugin {
41    ($plugin_constructor:expr $(,)?) => {
42        /// Exports the root module of this library.
43        ///
44        /// This code isn't run until the layout of the type it returns is checked.
45        #[allow(clippy::drop_non_drop)] // this lint is triggered for code generated by #[export_root_module]
46        #[::abi_stable::export_root_module]
47        pub fn instantiate_root_module() -> $crate::PluginMod_Ref {
48            $crate::PluginMod_Ref::new(plugin_constructor)
49        }
50
51        /// Instantiates the plugin.
52        #[allow(clippy::drop_non_drop)] // this lint is triggered for code generated by #[export_root_module]
53        #[::abi_stable::sabi_extern_fn]
54        pub fn plugin_constructor() -> $crate::PluginProxy {
55            $crate::PluginProxy::new($plugin_constructor)
56        }
57    };
58}
59
60impl PluginProxy {
61    /// Loads a plugin from the specified path.
62    pub fn from_path(path: impl AsRef<Path>) -> Result<Self, arci::Error> {
63        let path = path.as_ref();
64
65        let header = lib_header_from_path(path).map_err(anyhow::Error::from)?;
66        let root_module = header
67            .init_root_module::<PluginMod_Ref>()
68            .map_err(anyhow::Error::from)?;
69
70        let plugin_constructor = root_module.plugin_constructor();
71        let plugin = plugin_constructor();
72
73        Ok(plugin)
74    }
75}
76
77impl fmt::Debug for PluginProxy {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        f.debug_struct("PluginProxy").finish()
80    }
81}
82
83// =============================================================================
84// JointTrajectoryClient
85
86/// FFI-safe equivalent of [`Box<dyn arci::JointTrajectoryClient>`](arci::JointTrajectoryClient).
87#[repr(C)]
88#[derive(StableAbi)]
89pub struct JointTrajectoryClientProxy(JointTrajectoryClientTraitObject);
90
91impl JointTrajectoryClientProxy {
92    /// Creates a new `JointTrajectoryClientProxy`.
93    pub fn new<T>(client: T) -> Self
94    where
95        T: arci::JointTrajectoryClient + 'static,
96    {
97        Self(JointTrajectoryClientTraitObject::from_value(
98            client, TD_Opaque,
99        ))
100    }
101}
102
103impl arci::JointTrajectoryClient for JointTrajectoryClientProxy {
104    fn joint_names(&self) -> Vec<String> {
105        self.0.joint_names().into_iter().map(|s| s.into()).collect()
106    }
107
108    fn current_joint_positions(&self) -> Result<Vec<f64>, arci::Error> {
109        Ok(self
110            .0
111            .current_joint_positions()
112            .into_result()?
113            .into_iter()
114            .map(f64::from)
115            .collect())
116    }
117
118    fn send_joint_positions(
119        &self,
120        positions: Vec<f64>,
121        duration: Duration,
122    ) -> Result<WaitFuture, arci::Error> {
123        Ok(self
124            .0
125            .send_joint_positions(
126                positions.into_iter().map(Into::into).collect(),
127                duration.into(),
128            )
129            .into_result()?
130            .into())
131    }
132
133    fn send_joint_trajectory(
134        &self,
135        trajectory: Vec<arci::TrajectoryPoint>,
136    ) -> Result<WaitFuture, arci::Error> {
137        Ok(self
138            .0
139            .send_joint_trajectory(trajectory.into_iter().map(Into::into).collect())
140            .into_result()?
141            .into())
142    }
143}
144
145impl fmt::Debug for JointTrajectoryClientProxy {
146    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147        f.debug_struct("JointTrajectoryClientProxy").finish()
148    }
149}
150
151// =============================================================================
152// arci::Gamepad
153
154/// FFI-safe equivalent of [`Box<dyn arci::Gamepad>`](arci::Gamepad).
155// Don't implement Clone -- use of Arc is implementation detail.
156#[repr(C)]
157#[derive(StableAbi)]
158pub struct GamepadProxy(GamepadTraitObject);
159
160impl GamepadProxy {
161    /// Creates a new `GamepadProxy`.
162    pub fn new<T>(gamepad: T) -> Self
163    where
164        T: arci::Gamepad + 'static,
165    {
166        Self(GamepadTraitObject::from_value(Arc::new(gamepad), TD_Opaque))
167    }
168}
169
170#[async_trait]
171impl arci::Gamepad for GamepadProxy {
172    async fn next_event(&self) -> arci::gamepad::GamepadEvent {
173        let this = Self(self.0.clone());
174        tokio::task::spawn_blocking(move || this.0.next_event().into())
175            .await
176            .unwrap_or(arci::gamepad::GamepadEvent::Unknown)
177    }
178
179    fn stop(&self) {
180        self.0.stop();
181    }
182}
183
184impl fmt::Debug for GamepadProxy {
185    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186        f.debug_struct("GamepadProxy").finish()
187    }
188}