Skip to content

Commit 2936a40

Browse files
committed
Add dynamic message functionality
1 parent 27342d5 commit 2936a40

File tree

20 files changed

+3903
-8
lines changed

20 files changed

+3903
-8
lines changed

examples/dynamic_pub_sub/Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[package]
2+
name = "dynamic_pub_sub"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]
9+
rclrs = { version = "0.3", features = ["dyn_msg"] }
10+
anyhow = {version = "1", features = ["backtrace"]}

examples/dynamic_pub_sub/src/main.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
use anyhow::{Error, Result};
2+
use std::env;
3+
4+
fn main() -> Result<(), Error> {
5+
let context = rclrs::Context::new(env::args())?;
6+
7+
let mut node = rclrs::create_node(&context, "dynamic_subscriber")?;
8+
9+
let mut num_messages: usize = 0;
10+
11+
let _subscription = node.create_dynamic_subscription(
12+
"topic",
13+
"rclrs_example_msgs/msg/VariousTypes",
14+
rclrs::QOS_PROFILE_DEFAULT,
15+
move |msg| {
16+
num_messages += 1;
17+
println!("I heard: '{:#?}'", msg.structure());
18+
},
19+
)?;
20+
21+
rclrs::spin(&node).map_err(|err| err.into())
22+
}

rclrs/src/dynamic_message.rs

Lines changed: 239 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,28 @@
66
//! The central type of this module is [`DynamicMessage`].
77
88
use std::fmt::{self, Display};
9+
use std::ops::Deref;
910
use std::path::PathBuf;
1011
use std::sync::Arc;
1112

13+
use rosidl_runtime_rs::RmwMessage;
14+
1215
#[cfg(any(ros_distro = "foxy", ros_distro = "galactic"))]
1316
use crate::rcl_bindings::rosidl_typesupport_introspection_c__MessageMembers as rosidl_message_members_t;
1417
#[cfg(all(not(ros_distro = "foxy"), not(ros_distro = "galactic")))]
1518
use crate::rcl_bindings::rosidl_typesupport_introspection_c__MessageMembers_s as rosidl_message_members_t;
1619
use crate::rcl_bindings::*;
1720

21+
mod dynamic_publisher;
22+
mod dynamic_subscription;
1823
mod error;
24+
mod field_access;
25+
mod message_structure;
26+
pub use dynamic_publisher::*;
27+
pub use dynamic_subscription::*;
1928
pub use error::*;
29+
pub use field_access::*;
30+
pub use message_structure::*;
2031

2132
/// Factory for constructing messages in a certain package dynamically.
2233
///
@@ -52,17 +63,33 @@ struct MessageTypeName {
5263
/// can be used as a factory to create message instances.
5364
#[derive(Clone)]
5465
pub struct DynamicMessageMetadata {
55-
#[allow(dead_code)]
5666
message_type: MessageTypeName,
5767
// The library needs to be kept loaded in order to keep the type_support_ptr valid.
68+
// This is the introspection type support library, not the regular one.
5869
#[allow(dead_code)]
5970
introspection_type_support_library: Arc<libloading::Library>,
60-
#[allow(dead_code)]
6171
type_support_ptr: *const rosidl_message_type_support_t,
62-
#[allow(dead_code)]
72+
structure: MessageStructure,
6373
fini_function: unsafe extern "C" fn(*mut std::os::raw::c_void),
6474
}
6575

76+
/// A message whose type is not known at compile-time.
77+
///
78+
/// This type allows inspecting the structure of the message as well as the
79+
/// values contained in it.
80+
/// It also allows _modifying_ the values, but not the structure, because
81+
/// even a dynamic message must always correspond to a specific message type.
82+
// There is no clone function yet, we need to add that in rosidl.
83+
pub struct DynamicMessage {
84+
metadata: DynamicMessageMetadata,
85+
// This is aligned to the maximum possible alignment of a message (8)
86+
// by the use of a special allocation function.
87+
storage: Box<[u8]>,
88+
// This type allows moving the message contents out into another message,
89+
// in which case the drop impl is not responsible for calling fini anymore
90+
needs_fini: bool,
91+
}
92+
6693
// ========================= impl for DynamicMessagePackage =========================
6794

6895
/// This is an analogue of rclcpp::get_typesupport_library.
@@ -172,6 +199,8 @@ impl DynamicMessagePackage {
172199
let message_members: &rosidl_message_members_t =
173200
// SAFETY: The data pointer is supposed to be always valid.
174201
unsafe { &*(type_support.data as *const rosidl_message_members_t) };
202+
// SAFETY: The message members coming from a type support library will always be valid.
203+
let structure = unsafe { MessageStructure::from_rosidl_message_members(message_members) };
175204
// The fini function will always exist.
176205
let fini_function = message_members.fini_function.unwrap();
177206
let metadata = DynamicMessageMetadata {
@@ -180,6 +209,7 @@ impl DynamicMessagePackage {
180209
&self.introspection_type_support_library,
181210
),
182211
type_support_ptr,
212+
structure,
183213
fini_function,
184214
};
185215
Ok(metadata)
@@ -230,6 +260,13 @@ impl Display for MessageTypeName {
230260

231261
// ========================= impl for DynamicMessageMetadata =========================
232262

263+
impl Deref for DynamicMessageMetadata {
264+
type Target = MessageStructure;
265+
fn deref(&self) -> &Self::Target {
266+
&self.structure
267+
}
268+
}
269+
233270
// SAFETY: The functions accessing this type, including drop(), shouldn't care about the thread
234271
// they are running in. Therefore, this type can be safely sent to another thread.
235272
unsafe impl Send for DynamicMessageMetadata {}
@@ -250,6 +287,203 @@ impl DynamicMessageMetadata {
250287
let pkg = DynamicMessagePackage::new(package_name)?;
251288
pkg.message_metadata(type_name)
252289
}
290+
291+
/// Instantiates a new message.
292+
pub fn create(&self) -> Result<DynamicMessage, DynamicMessageError> {
293+
// Get an aligned boxed slice. This is inspired by the maligned crate.
294+
use std::alloc::Layout;
295+
// As mentioned in the struct definition, the maximum alignment required is 8.
296+
let layout = Layout::from_size_align(self.structure.size, 8).unwrap();
297+
let mut storage = unsafe {
298+
assert_ne!(self.structure.size, 0);
299+
// SAFETY: The layout has non-zero size.
300+
let ptr = std::alloc::alloc_zeroed(layout);
301+
// SAFETY: This is valid, memory in ptr has appropriate size and is initialized
302+
let slice = std::slice::from_raw_parts_mut(ptr, self.structure.size);
303+
// The mutable reference decays into a (fat) *mut [u8]
304+
Box::from_raw(slice)
305+
};
306+
// SAFETY: The pointer returned by get_type_support_handle() is always valid.
307+
let type_support = unsafe { &*self.type_support_ptr };
308+
let message_members: &rosidl_message_members_t =
309+
// SAFETY: The data pointer is supposed to be always valid.
310+
unsafe { &*(type_support.data as *const rosidl_message_members_t) };
311+
// SAFETY: The init function is passed zeroed memory of the correct alignment.
312+
unsafe {
313+
(message_members.init_function.unwrap())(
314+
storage.as_mut_ptr() as _,
315+
rosidl_runtime_c__message_initialization::ROSIDL_RUNTIME_C_MSG_INIT_ALL,
316+
);
317+
};
318+
let dyn_msg = DynamicMessage {
319+
metadata: self.clone(),
320+
storage,
321+
needs_fini: true,
322+
};
323+
Ok(dyn_msg)
324+
}
325+
326+
/// Returns a description of the message structure.
327+
pub fn structure(&self) -> &MessageStructure {
328+
&self.structure
329+
}
330+
}
331+
332+
// ========================= impl for DynamicMessage =========================
333+
334+
impl Deref for DynamicMessage {
335+
type Target = MessageStructure;
336+
fn deref(&self) -> &Self::Target {
337+
&self.metadata.structure
338+
}
339+
}
340+
341+
impl Drop for DynamicMessage {
342+
fn drop(&mut self) {
343+
if self.needs_fini {
344+
// SAFETY: The fini_function expects to be passed a pointer to the message
345+
unsafe { (self.metadata.fini_function)(self.storage.as_mut_ptr() as _) }
346+
}
347+
}
348+
}
349+
350+
impl PartialEq for DynamicMessage {
351+
fn eq(&self, other: &Self) -> bool {
352+
self.metadata.type_support_ptr == other.metadata.type_support_ptr
353+
&& self.storage == other.storage
354+
}
355+
}
356+
357+
impl Eq for DynamicMessage {}
358+
359+
impl DynamicMessage {
360+
/// Dynamically loads a type support library for the specified type and creates a message instance.
361+
///
362+
/// The full message type is of the form `<package>/msg/<type_name>`, e.g.
363+
/// `std_msgs/msg/String`.
364+
///
365+
/// The message instance will contain the default values of the message type.
366+
pub fn new(full_message_type: &str) -> Result<Self, DynamicMessageError> {
367+
DynamicMessageMetadata::new(full_message_type)?.create()
368+
}
369+
370+
/// See [`DynamicMessageView::get()`][1].
371+
///
372+
/// [1]: crate::dynamic_message::DynamicMessageView::get
373+
pub fn get(&self, field_name: &str) -> Option<Value<'_>> {
374+
let field_info = self.metadata.structure.get_field_info(field_name)?;
375+
// For the unwrap_or, see DynamicMessageViewMut::get_mut
376+
let size = field_info.size().unwrap_or(1);
377+
let bytes = &self.storage[field_info.offset..field_info.offset + size];
378+
// SAFETY: The bytes contain a valid field of the type recorded in field_info.
379+
unsafe { Value::new(bytes, field_info) }
380+
}
381+
382+
/// See [`DynamicMessageViewMut::get_mut()`][1].
383+
///
384+
/// [1]: crate::dynamic_message::DynamicMessageViewMut::get_mut
385+
pub fn get_mut(&mut self, field_name: &str) -> Option<ValueMut<'_>> {
386+
let field_info = self.metadata.structure.get_field_info(field_name)?;
387+
// For the unwrap_or, see DynamicMessageViewMut::get_mut
388+
let size = field_info.size().unwrap_or(1);
389+
let bytes = &mut self.storage[field_info.offset..field_info.offset + size];
390+
// SAFETY: The bytes contain a valid field of the type recorded in field_info.
391+
Some(unsafe { ValueMut::new(bytes, field_info) })
392+
}
393+
394+
/// Returns a description of the message structure.
395+
pub fn structure(&self) -> &MessageStructure {
396+
&self.metadata.structure
397+
}
398+
399+
/// Iterate over all fields in declaration order.
400+
pub fn iter(&self) -> impl Iterator<Item = (&str, Value<'_>)> + '_ {
401+
self.metadata.structure.fields.iter().map(|field_info| {
402+
let value = self.get(&field_info.name).unwrap();
403+
(field_info.name.as_str(), value)
404+
})
405+
}
406+
407+
/// Iterate over all fields in declaration order (mutable version).
408+
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&str, ValueMut<'_>)> + '_ {
409+
self.view_mut().iter_mut()
410+
}
411+
412+
/// Returns a view object of this message.
413+
///
414+
/// The purpose for this conversion is to allow uniform handling of this top-level message
415+
/// and nested messages contained in it through a [`DynamicMessageView`].
416+
pub fn view(&self) -> DynamicMessageView<'_> {
417+
DynamicMessageView {
418+
structure: &self.metadata.structure,
419+
storage: &self.storage,
420+
}
421+
}
422+
423+
/// Returns a mutable view object of this message.
424+
///
425+
/// The purpose for this conversion is to allow uniform handling of this top-level message
426+
/// and nested messages contained in it through a [`DynamicMessageViewMut`].
427+
pub fn view_mut(&mut self) -> DynamicMessageViewMut<'_> {
428+
DynamicMessageViewMut {
429+
structure: &self.metadata.structure,
430+
storage: &mut self.storage,
431+
}
432+
}
433+
434+
/// Converts a statically typed RMW-native message into a `DynamicMessage`.
435+
pub fn convert_from_rmw_message<T>(mut msg: T) -> Result<Self, DynamicMessageError>
436+
where
437+
T: RmwMessage,
438+
{
439+
let mut dyn_msg = Self::new(<T as RmwMessage>::TYPE_NAME)?;
440+
let align = std::mem::align_of::<T>();
441+
assert_eq!(dyn_msg.storage.as_ptr().align_offset(align), 0);
442+
{
443+
// SAFETY: This transmutes the slice of bytes into a &mut T. This is fine, since
444+
// under the hood it *is* a T.
445+
// However, the resulting value is not seen as borrowing from dyn_msg by the borrow checker,
446+
// so we are careful to not create a second mutable reference before dropping this one,
447+
// since that would be UB.
448+
let dyn_msg_transmuted = unsafe { &mut *(dyn_msg.storage.as_mut_ptr() as *mut T) };
449+
// We cannot simply overwrite one message with the other, or we will get a memory leak/double-free.
450+
// Swapping is the solution.
451+
std::mem::swap(&mut msg, dyn_msg_transmuted);
452+
}
453+
Ok(dyn_msg)
454+
}
455+
456+
/// Converts a `DynamicMessage` into a statically typed RMW-native message.
457+
///
458+
/// If the RMW-native message type does not match the underlying message type of this `DynamicMessage`,
459+
/// it is not converted but instead returned unchanged.
460+
pub fn convert_into_rmw_message<T>(mut self) -> Result<T, Self>
461+
where
462+
T: RmwMessage,
463+
{
464+
if <T as RmwMessage>::TYPE_NAME == self.metadata.message_type.to_string() {
465+
// SAFETY: Even though a zero-initialized message might not match RMW expectations for
466+
// what a message should look like, it is safe to temporarily have a zero-initialized
467+
// value, i.e. it is not undefined behavior to do this since it's a C struct, and an
468+
// all-zeroes bit pattern is always a valid instance of any C struct.
469+
let mut dest = unsafe { std::mem::zeroed::<T>() };
470+
let dest_ptr = &mut dest as *mut T as *mut u8;
471+
// This reinterprets the struct as a slice of bytes.
472+
// The bytes copied into the dest slice are a valid value of T, as ensured by comparison
473+
// of the type support pointers.
474+
let dest_slice =
475+
unsafe { std::slice::from_raw_parts_mut(dest_ptr, std::mem::size_of::<T>()) };
476+
// This creates a shallow copy, with ownership of the "deep" (or inner) parts moving
477+
// into the destination.
478+
dest_slice.copy_from_slice(&*self.storage);
479+
// Don't run the fini function on the src data anymore, because the inner parts would be
480+
// double-freed by dst and src.
481+
self.needs_fini = false;
482+
Ok(dest)
483+
} else {
484+
Err(self)
485+
}
486+
}
253487
}
254488

255489
#[cfg(test)]
@@ -263,6 +497,8 @@ mod tests {
263497
fn all_types_are_sync_and_send() {
264498
assert_send::<DynamicMessageMetadata>();
265499
assert_sync::<DynamicMessageMetadata>();
500+
assert_send::<DynamicMessage>();
501+
assert_sync::<DynamicMessage>();
266502
}
267503

268504
#[test]

0 commit comments

Comments
 (0)