From b9e5e533e0486a2cc6ce03bdc84ea7cf1361dec5 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 4 Jan 2024 18:27:37 +0100 Subject: [PATCH] Redesign ROS2 message struct generator to support `cxx_bridge` --- Cargo.lock | 1 + libraries/extensions/ros2-bridge/Cargo.toml | 2 + .../ros2-bridge/msg-gen-macro/src/lib.rs | 45 +++++++++- .../ros2-bridge/msg-gen/src/types/message.rs | 89 +++++++++++++++++-- .../ros2-bridge/msg-gen/src/types/package.rs | 71 ++++++++++++--- .../msg-gen/src/types/primitives.rs | 10 ++- .../msg-gen/src/types/sequences.rs | 4 +- libraries/extensions/ros2-bridge/src/lib.rs | 5 +- 8 files changed, 202 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8e081992..ac8e78be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1696,6 +1696,7 @@ name = "dora-ros2-bridge" version = "0.1.0" dependencies = [ "array-init", + "cxx", "dora-daemon", "dora-ros2-bridge-msg-gen-macro", "eyre", diff --git a/libraries/extensions/ros2-bridge/Cargo.toml b/libraries/extensions/ros2-bridge/Cargo.toml index 7c55a1cb..f0a7ac6e 100644 --- a/libraries/extensions/ros2-bridge/Cargo.toml +++ b/libraries/extensions/ros2-bridge/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [features] default = ["generate-messages"] generate-messages = ["dep:dora-ros2-bridge-msg-gen-macro"] +cxx-bridge = ["dep:cxx"] # enables examples that depend on a sourced ROS2 installation ros2-examples = ["eyre", "tokio", "dora-daemon"] @@ -24,6 +25,7 @@ tokio = { version = "1.29.1", features = ["full"], optional = true } dora-daemon = { path = "../../../binaries/daemon", optional = true } tracing = "0.1.37" tracing-subscriber = "0.3.17" +cxx = { version = "1.0", optional = true } [dev-dependencies] rand = "0.8.5" diff --git a/libraries/extensions/ros2-bridge/msg-gen-macro/src/lib.rs b/libraries/extensions/ros2-bridge/msg-gen-macro/src/lib.rs index 93fb09da..2a6c3b4c 100644 --- a/libraries/extensions/ros2-bridge/msg-gen-macro/src/lib.rs +++ b/libraries/extensions/ros2-bridge/msg-gen-macro/src/lib.rs @@ -63,14 +63,57 @@ pub fn msg_include_all(input: TokenStream) -> TokenStream { .map(Path::new) .collect::>(); + let message_structs = get_packages(&paths) + .unwrap() + .iter() + .map(|v| v.struct_token_stream(config.create_cxx_bridge)) + .collect::>(); + let aliases = get_packages(&paths) + .unwrap() + .iter() + .map(|v| v.aliases_token_stream()) + .collect::>(); let packages = get_packages(&paths) .unwrap() .iter() .map(|v| v.token_stream(config.create_cxx_bridge)) .collect::>(); + let (attributes, imports) = if config.create_cxx_bridge { + (quote! { #[cxx::bridge] }, quote! {}) + } else { + ( + quote! {}, + quote! { + use serde::{Serialize, Deserialize}; + }, + ) + }; + (quote! { - #(#packages)* + #attributes + pub mod ffi { + #imports + + #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] + pub struct U16String { + chars: Vec, + } + + impl crate::_core::InternalDefault for U16String { + fn _default() -> Self { + Default::default() + } + } + + + #(#message_structs)* + } + + #(#aliases)* + + + // #(#packages)* }) .into() } diff --git a/libraries/extensions/ros2-bridge/msg-gen/src/types/message.rs b/libraries/extensions/ros2-bridge/msg-gen/src/types/message.rs index 72dc1337..96693f1a 100644 --- a/libraries/extensions/ros2-bridge/msg-gen/src/types/message.rs +++ b/libraries/extensions/ros2-bridge/msg-gen/src/types/message.rs @@ -1,5 +1,6 @@ use heck::SnakeCase; use quote::{format_ident, quote, ToTokens}; +use syn::Ident; use super::{primitives::*, sequences::Array, ConstantType, MemberType}; @@ -122,9 +123,69 @@ pub struct Message { } impl Message { - pub fn token_stream_with_mod(&self) -> impl ToTokens { + pub fn struct_token_stream(&self, package_name: &Ident, gen_cxx_bridge: bool) -> impl ToTokens { + let cxx_name = format_ident!("{}", self.name); + let struct_raw_name = format_ident!("{package_name}__{}", self.name); + + let rust_type_def_inner = self.members.iter().map(|m| m.rust_type_def(&self.package)); + let constants_def_inner = self.constants.iter().map(|c| c.token_stream()); + let rust_type_default_inner = self.members.iter().map(|m| m.default_value()); + + let attributes = if gen_cxx_bridge { + quote! { + #[namespace = #package_name] + #[cxx_name = #cxx_name] + } + } else { + quote! {} + }; + + if self.members.is_empty() { + quote! {} + } else { + quote! { + #[allow(non_camel_case_types)] + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] + #attributes + pub struct #struct_raw_name { + #(#rust_type_def_inner)* + } + + impl crate::_core::InternalDefault for #struct_raw_name { + fn _default() -> Self { + Self { + #(#rust_type_default_inner)* + } + } + } + + impl std::default::Default for #struct_raw_name { + #[inline] + fn default() -> Self { + crate::_core::InternalDefault::_default() + } + } + } + } + } + + pub fn alias_token_stream(&self, package_name: &Ident) -> impl ToTokens { + let cxx_name = format_ident!("{}", self.name); + let struct_raw_name = format_ident!("{package_name}__{}", self.name); + + if self.members.is_empty() { + quote! {} + } else { + quote! { + pub use super::super::ffi::#struct_raw_name as #cxx_name; + } + } + } + + pub fn token_stream_with_mod(&self, gen_cxx_bridge: bool) -> impl ToTokens { let mod_name = format_ident!("_{}", self.name.to_snake_case()); - let inner = self.token_stream(); + let inner = self.token_stream_args(gen_cxx_bridge); + quote! { pub use #mod_name::*; mod #mod_name { @@ -134,6 +195,10 @@ impl Message { } pub fn token_stream(&self) -> impl ToTokens { + self.token_stream_args(false) + } + + pub fn token_stream_args(&self, gen_cxx_bridge: bool) -> impl ToTokens { let rust_type = format_ident!("{}", self.name); let raw_type = format_ident!("{}_Raw", self.name); let raw_ref_type = format_ident!("{}_RawRef", self.name); @@ -144,6 +209,13 @@ impl Message { self.members.clone() }; + let attributes = if gen_cxx_bridge { + let namespace = &self.name; + quote! { #[cxx::bridge(namespace = #namespace)] } + } else { + quote! {} + }; + let rust_type_def_inner = self.members.iter().map(|m| m.rust_type_def(&self.package)); let constants_def_inner = self.constants.iter().map(|c| c.token_stream()); let rust_type_default_inner = self.members.iter().map(|m| m.default_value()); @@ -178,10 +250,15 @@ impl Message { FFIToRust as _FFIToRust, }; - #[allow(non_camel_case_types)] - #[derive(std::fmt::Debug, std::clone::Clone, std::cmp::PartialEq, serde::Serialize, serde::Deserialize)] - pub struct #rust_type { - #(#rust_type_def_inner)* + pub use self::t::#rust_type; + + #attributes + mod t { + #[allow(non_camel_case_types)] + #[derive(std::fmt::Debug, std::clone::Clone, std::cmp::PartialEq, serde::Serialize, serde::Deserialize)] + pub struct #rust_type { + #(#rust_type_def_inner)* + } } impl #rust_type { diff --git a/libraries/extensions/ros2-bridge/msg-gen/src/types/package.rs b/libraries/extensions/ros2-bridge/msg-gen/src/types/package.rs index bbd16369..50cabf2d 100644 --- a/libraries/extensions/ros2-bridge/msg-gen/src/types/package.rs +++ b/libraries/extensions/ros2-bridge/msg-gen/src/types/package.rs @@ -26,13 +26,50 @@ impl Package { self.messages.is_empty() && self.services.is_empty() && self.actions.is_empty() } - fn messages_block(&self) -> impl ToTokens { + fn message_structs(&self, package_name: Ident, gen_cxx_bridge: bool) -> impl ToTokens { if self.messages.is_empty() { quote! { // empty msg } } else { - let items = self.messages.iter().map(|v| v.token_stream_with_mod()); + let items = self + .messages + .iter() + .map(|v| v.struct_token_stream(&package_name, gen_cxx_bridge)); + quote! { + #(#items)* + } + } + } + + fn message_aliases(&self, package_name: &Ident) -> impl ToTokens { + if self.messages.is_empty() { + quote! { + // empty msg + } + } else { + let items = self + .messages + .iter() + .map(|v| v.alias_token_stream(package_name)); + quote! { + pub mod msg { + #(#items)* + } + } + } + } + + fn messages_block(&self, gen_cxx_bridge: bool) -> impl ToTokens { + if self.messages.is_empty() { + quote! { + // empty msg + } + } else { + let items = self + .messages + .iter() + .map(|v| v.token_stream_with_mod(gen_cxx_bridge)); quote! { pub mod msg { #(#items)* @@ -71,21 +108,33 @@ impl Package { } } + pub fn struct_token_stream(&self, gen_cxx_bridge: bool) -> impl ToTokens { + let package_name = Ident::new(&self.name, Span::call_site()); + let message_structs = self.message_structs(package_name, gen_cxx_bridge); + + quote! { + #message_structs + } + } + + pub fn aliases_token_stream(&self) -> impl ToTokens { + let package_name = Ident::new(&self.name, Span::call_site()); + let aliases = self.message_aliases(&package_name); + + quote! { + pub mod #package_name { + #aliases + } + } + } + pub fn token_stream(&self, gen_cxx_bridge: bool) -> impl ToTokens { let name = Ident::new(&self.name, Span::call_site()); - let messages_block = self.messages_block(); + let messages_block = self.messages_block(gen_cxx_bridge); let services_block = self.services_block(); let actions_block = self.actions_block(); - let attributes = if gen_cxx_bridge { - let namespace = &self.name; - quote! { #[cxx::bridge(namespace = #namespace)] } - } else { - quote! {} - }; - quote! { - #attributes pub mod #name { #messages_block #services_block diff --git a/libraries/extensions/ros2-bridge/msg-gen/src/types/primitives.rs b/libraries/extensions/ros2-bridge/msg-gen/src/types/primitives.rs index abf2747b..98dc8235 100644 --- a/libraries/extensions/ros2-bridge/msg-gen/src/types/primitives.rs +++ b/libraries/extensions/ros2-bridge/msg-gen/src/types/primitives.rs @@ -133,7 +133,8 @@ impl NamedType { let package = Ident::new(package, Span::call_site()); let namespace = Ident::new("msg", Span::call_site()); let name = Ident::new(&self.0, Span::call_site()); - quote! { crate::#package::#namespace::#name } + let ident = format_ident!("{package}__{name}"); + quote! { #ident } } fn raw_type_tokens(&self, package: &str) -> impl ToTokens { @@ -175,7 +176,8 @@ impl NamespacedType { let package = Ident::new(&self.package, Span::call_site()); let namespace = Ident::new(&self.namespace, Span::call_site()); let name = Ident::new(&self.name, Span::call_site()); - quote! { crate::#package::#namespace::#name } + let ident = format_ident!("{package}__{name}"); + quote! { #ident } } fn raw_type_tokens(&self) -> impl ToTokens { @@ -215,9 +217,9 @@ impl GenericString { fn type_tokens(self) -> impl ToTokens { if self.is_wide() { - quote! { crate::_core::string::U16String } + quote! { U16String } } else { - quote! { ::std::string::String } + quote! { String } } } diff --git a/libraries/extensions/ros2-bridge/msg-gen/src/types/sequences.rs b/libraries/extensions/ros2-bridge/msg-gen/src/types/sequences.rs index 13a5c5f7..2c4c1872 100644 --- a/libraries/extensions/ros2-bridge/msg-gen/src/types/sequences.rs +++ b/libraries/extensions/ros2-bridge/msg-gen/src/types/sequences.rs @@ -41,7 +41,7 @@ pub struct Sequence { impl Sequence { pub fn type_tokens(&self, package: &str) -> impl ToTokens { let inner_type = self.value_type.type_tokens(package); - quote! { std::vec::Vec<#inner_type> } + quote! { Vec<#inner_type> } } pub fn raw_type_tokens(&self, package: &str) -> impl ToTokens { @@ -72,7 +72,7 @@ pub struct BoundedSequence { impl BoundedSequence { pub fn type_tokens(&self, package: &str) -> impl ToTokens { let inner_type = self.value_type.type_tokens(package); - quote! { std::vec::Vec<#inner_type> } + quote! { Vec<#inner_type> } } pub fn raw_type_tokens(&self, package: &str) -> impl ToTokens { diff --git a/libraries/extensions/ros2-bridge/src/lib.rs b/libraries/extensions/ros2-bridge/src/lib.rs index e2053e0b..a5f3ecb8 100644 --- a/libraries/extensions/ros2-bridge/src/lib.rs +++ b/libraries/extensions/ros2-bridge/src/lib.rs @@ -1,7 +1,10 @@ pub use ros2_client; pub use rustdds; -#[cfg(feature = "generate-messages")] +#[cfg(all(feature = "generate-messages", feature = "cxx-bridge"))] +dora_ros2_bridge_msg_gen_macro::msg_include_all!(cxx_bridge = true); + +#[cfg(all(feature = "generate-messages", not(feature = "cxx-bridge")))] dora_ros2_bridge_msg_gen_macro::msg_include_all!(); pub mod _core;