Skip to content

Commit

Permalink
Merge pull request #221 from RosLibRust/node-generics
Browse files Browse the repository at this point in the history
Node generics
  • Loading branch information
Carter12s authored Jan 9, 2025
2 parents 340a1a0 + dd21245 commit f236af7
Show file tree
Hide file tree
Showing 12 changed files with 115 additions and 15 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- A new `Ros` trait has been added that all backends implement. This trait behaves like a typical ROS1 node handle, and is easier to work with in generics than directly using TopicProvider or ServiceProvider.

### Fixed

### Changed
Expand Down
2 changes: 1 addition & 1 deletion example_package/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use roslibrust::Publish;

// Writing a simple behavior that uses the generic traits from roslibrust
// and the generated types from the macro above.
async fn pub_counter(ros: impl roslibrust::TopicProvider) {
async fn pub_counter(ros: impl roslibrust::Ros) {
let publisher = ros
.advertise::<std_msgs::Int16>("example_counter")
.await
Expand Down
2 changes: 1 addition & 1 deletion example_package_macro/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ roslibrust::find_and_generate_ros_messages!(

// Writing a simple behavior that uses the generic traits from roslibrust
// and the generated types from the macro above.
async fn pub_counter(ros: impl roslibrust::TopicProvider) {
async fn pub_counter(ros: impl roslibrust::Ros) {
let publisher = ros
.advertise::<std_msgs::Int16>("example_counter")
.await
Expand Down
6 changes: 3 additions & 3 deletions roslibrust/examples/generic_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ roslibrust_codegen_macro::find_and_generate_ros_messages!("assets/ros1_common_in
#[cfg(all(feature = "rosbridge", feature = "ros1"))]
#[tokio::main]
async fn main() {
use roslibrust::{Publish, Subscribe, TopicProvider};
use roslibrust::{Publish, Ros, Subscribe};
env_logger::init();

// TopicProvider cannot be an "Object Safe Trait" due to its generic parameters
Expand All @@ -21,13 +21,13 @@ async fn main() {
// single application. The critical part is to make TopicProvider a
// generic type on you Node.

struct MyNode<T: TopicProvider> {
struct MyNode<T: Ros> {
ros: T,
name: String,
}

// Basic example of a node that publishes and subscribes to itself
impl<T: TopicProvider> MyNode<T> {
impl<T: Ros> MyNode<T> {
async fn run(self) {
let publisher = self
.ros
Expand Down
6 changes: 3 additions & 3 deletions roslibrust/examples/generic_client_services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ roslibrust_codegen_macro::find_and_generate_ros_messages!("assets/ros1_common_in
#[tokio::main]
async fn main() {
// Important to bring these traits into scope so we can use them
use roslibrust::{Service, ServiceProvider};
use roslibrust::{Ros, Service};
env_logger::init();

// TopicProvider cannot be an "Object Safe Trait" due to its generic parameters
Expand All @@ -20,13 +20,13 @@ async fn main() {
// single application. The critical part is to make TopicProvider a
// generic type on you Node.

struct MyNode<T: ServiceProvider + 'static> {
struct MyNode<T: Ros> {
ros: T,
}

// Basic example of a node that publishes and subscribes to itself
// This node will work with any backend
impl<T: ServiceProvider> MyNode<T> {
impl<T: Ros> MyNode<T> {
fn handle_service(
_request: std_srvs::SetBoolRequest,
) -> Result<std_srvs::SetBoolResponse, Box<dyn std::error::Error + Send + Sync>> {
Expand Down
1 change: 1 addition & 0 deletions roslibrust/examples/generic_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ impl RosMessageType for GenericHeader {
}

/// Sets up a subscriber that could get either of two versions of a message
/// Note this is currently only supported by the rosbridge backend as this behavior relies on serde_json's fallback capabilities
#[cfg(feature = "rosbridge")]
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
Expand Down
4 changes: 2 additions & 2 deletions roslibrust_common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,5 +131,5 @@ pub mod md5sum;

/// Contains the generic traits represent a pubsub system and service system
/// These traits will be implemented for specific backends to provides access to "ROS Like" functionality
pub mod topic_provider;
pub use topic_provider::*; // Bring topic provider traits into root namespace
pub mod traits;
pub use traits::*; // Bring topic provider traits into root namespace
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,19 @@ pub trait ServiceProvider {
F: ServiceFn<T>;
}

/// Represents all "standard" ROS functionality generically supported by roslibrust
///
/// Implementors of this trait behave like typical ROS1 node handles.
/// Cloning the handle does not create additional underlying connections, but instead simply returns another handle
/// to interact with the underlying node.
///
/// Implementors of this trait are expected to be "self de-registering", when the last node handle for a given
/// node is dropped, the underlying node is expected to be shut down and clean-up after itself
pub trait Ros: 'static + Send + Sync + TopicProvider + ServiceProvider + Clone {}

/// The Ros trait is auto implemented for any type that implements the required traits
impl<T: 'static + Send + Sync + TopicProvider + ServiceProvider + Clone> Ros for T {}

#[cfg(test)]
mod test {
// This test specifically fails because TopicProvider is not object safe
Expand Down
39 changes: 35 additions & 4 deletions roslibrust_mock/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,19 @@ type TypeErasedCallback = Arc<
/// A mock ROS implementation that can be substituted for any roslibrust backend in unit tests.
///
/// Implements [TopicProvider] and [ServiceProvider] to provide basic ros functionality.
#[derive(Clone)]
pub struct MockRos {
// We could probably achieve some fancier type erasure than actually serializing the data
// but this ends up being pretty simple
topics: RwLock<BTreeMap<String, (Channel::Sender<Vec<u8>>, Channel::Receiver<Vec<u8>>)>>,
services: RwLock<BTreeMap<String, TypeErasedCallback>>,
topics: Arc<RwLock<BTreeMap<String, (Channel::Sender<Vec<u8>>, Channel::Receiver<Vec<u8>>)>>>,
services: Arc<RwLock<BTreeMap<String, TypeErasedCallback>>>,
}

impl MockRos {
pub fn new() -> Self {
Self {
topics: RwLock::new(BTreeMap::new()),
services: RwLock::new(BTreeMap::new()),
topics: Arc::new(RwLock::new(BTreeMap::new())),
services: Arc::new(RwLock::new(BTreeMap::new())),
}
}
}
Expand Down Expand Up @@ -289,4 +290,34 @@ mod tests {
assert_eq!(response.success, true);
assert_eq!(response.message, "You set my bool!");
}

#[tokio::test(flavor = "multi_thread")]
async fn test_mock_node() {
// Proves that MockRos impls the Ros trait (via auto impl in roslibrust_common)
// and can be used as such
struct MyNode<T: Ros> {
ros: T,
}

impl<T: Ros> MyNode<T> {
async fn run(self) {
let publisher = self
.ros
.advertise::<std_msgs::String>("/chatter")
.await
.unwrap();

publisher
.publish(&std_msgs::String {
data: "Hello, world!".to_string(),
})
.await
.unwrap();
}
}

let mock_ros = MockRos::new();
let node = MyNode { ros: mock_ros };
node.run().await;
}
}
21 changes: 20 additions & 1 deletion roslibrust_ros1/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ pub(crate) type TypeErasedCallback = dyn Fn(Vec<u8>) -> Result<Vec<u8>, Box<dyn
+ 'static;

// Implement the generic roslibrust trait
impl TopicProvider for NodeHandle {
impl TopicProvider for crate::NodeHandle {
type Publisher<T: RosMessageType> = crate::Publisher<T>;
type Subscriber<T: RosMessageType> = crate::Subscriber<T>;

Expand Down Expand Up @@ -163,6 +163,7 @@ impl<T: RosMessageType> Publish<T> for Publisher<T> {

#[cfg(test)]
mod test {
use roslibrust_common::Ros;
use roslibrust_common::TopicProvider;

// Prove that we've implemented the topic provider trait fully for NodeHandle
Expand All @@ -178,7 +179,25 @@ mod test {
let new_mock: Result<crate::NodeHandle, _> = Err(anyhow::anyhow!("Expected error"));

let _x = MyClient {
// Will panic here which is expect, this test just needs to compile to prove
// NodeHandle implements TopicProvider
_client: new_mock.unwrap(), // panic
};
}

#[test]
#[should_panic]
fn confirm_node_handle_impls_ros() {
struct MyClient<T: Ros> {
_client: T,
}

let new_mock: Result<crate::NodeHandle, _> = Err(anyhow::anyhow!("Expected error"));

let _x = MyClient {
// Will panic here which is expect, this test just needs to compile to prove
// NodeHandle implements Ros
_client: new_mock.unwrap(),
};
}
}
17 changes: 17 additions & 0 deletions roslibrust_rosbridge/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,4 +263,21 @@ mod test {
_client: new_mock.unwrap(), // panic
};
}

#[test]
#[should_panic]
fn confirm_client_handle_impls_ros() {
struct MyClient<T: Ros> {
_client: T,
}

let new_mock: std::result::Result<crate::ClientHandle, _> =
Err(anyhow::anyhow!("Expected error"));

let _x = MyClient {
// Should panic here, but proves that ClientHandle implements Ros
// when this test compiles
_client: new_mock.unwrap(),
};
}
}
17 changes: 17 additions & 0 deletions roslibrust_zenoh/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use zenoh::bytes::ZBytes;

/// A wrapper around a normal zenoh session that adds roslibrust specific functionality.
/// Should be created via [ZenohClient::new], and then used via the [TopicProvider] and [ServiceProvider] traits.
#[derive(Clone)]
pub struct ZenohClient {
session: zenoh::Session,
}
Expand Down Expand Up @@ -386,4 +387,20 @@ mod tests {
),
"7374645f737276732f536574426f6f6c/09fb03525b03e7ea1fd3992bafd87e16/service_server_rs/my_set_bool");
}

#[test]
#[should_panic]
fn confirm_client_handle_impls_ros() {
struct MyClient<T: Ros> {
_client: T,
}

let new_mock: std::result::Result<ZenohClient, _> = Err(anyhow::anyhow!("Expected error"));

let _x = MyClient {
// Should panic here, but proves that ClientHandle implements Ros
// when this test compiles
_client: new_mock.unwrap(),
};
}
}

0 comments on commit f236af7

Please sign in to comment.