Skip to content

Commit f169bff

Browse files
committed
Add the DynamicMessage type itself, and conversion from/to concrete message types
1 parent e1efd85 commit f169bff

File tree

2 files changed

+175
-4
lines changed

2 files changed

+175
-4
lines changed

rclrs/src/dynamic_message.rs

Lines changed: 155 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,27 @@
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;
1925
mod message_structure;
26+
pub use dynamic_publisher::*;
27+
pub use dynamic_subscription::*;
2028
pub use error::*;
29+
pub use field_access::*;
2130
pub use message_structure::*;
2231

2332
/// Factory for constructing messages in a certain package dynamically.
@@ -54,19 +63,33 @@ struct MessageTypeName {
5463
/// can be used as a factory to create message instances.
5564
#[derive(Clone)]
5665
pub struct DynamicMessageMetadata {
57-
#[allow(dead_code)]
5866
message_type: MessageTypeName,
5967
// 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.
6069
#[allow(dead_code)]
6170
introspection_type_support_library: Arc<libloading::Library>,
62-
#[allow(dead_code)]
6371
type_support_ptr: *const rosidl_message_type_support_t,
64-
#[allow(dead_code)]
6572
structure: MessageStructure,
66-
#[allow(dead_code)]
6773
fini_function: unsafe extern "C" fn(*mut std::os::raw::c_void),
6874
}
6975

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+
7093
// ========================= impl for DynamicMessagePackage =========================
7194

7295
/// This is an analogue of rclcpp::get_typesupport_library.
@@ -258,12 +281,138 @@ impl DynamicMessageMetadata {
258281
pkg.message_metadata(type_name)
259282
}
260283

284+
/// Instantiates a new message.
285+
pub fn create(&self) -> Result<DynamicMessage, DynamicMessageError> {
286+
// Get an aligned boxed slice. This is inspired by the maligned crate.
287+
use std::alloc::Layout;
288+
// As mentioned in the struct definition, the maximum alignment required is 8.
289+
let layout = Layout::from_size_align(self.structure.size, 8).unwrap();
290+
let mut storage = unsafe {
291+
assert_ne!(self.structure.size, 0);
292+
// SAFETY: The layout has non-zero size.
293+
let ptr = std::alloc::alloc_zeroed(layout);
294+
// SAFETY: This is valid, memory in ptr has appropriate size and is initialized
295+
let slice = std::slice::from_raw_parts_mut(ptr, self.structure.size);
296+
// The mutable reference decays into a (fat) *mut [u8]
297+
Box::from_raw(slice)
298+
};
299+
// SAFETY: The pointer returned by get_type_support_handle() is always valid.
300+
let type_support = unsafe { &*self.type_support_ptr };
301+
let message_members: &rosidl_message_members_t =
302+
// SAFETY: The data pointer is supposed to be always valid.
303+
unsafe { &*(type_support.data as *const rosidl_message_members_t) };
304+
// SAFETY: The init function is passed zeroed memory of the correct alignment.
305+
unsafe {
306+
(message_members.init_function.unwrap())(
307+
storage.as_mut_ptr() as _,
308+
rosidl_runtime_c__message_initialization::ROSIDL_RUNTIME_C_MSG_INIT_ALL,
309+
);
310+
};
311+
let dyn_msg = DynamicMessage {
312+
metadata: self.clone(),
313+
storage,
314+
needs_fini: true,
315+
};
316+
Ok(dyn_msg)
317+
}
318+
261319
/// Returns a description of the message structure.
262320
pub fn structure(&self) -> &MessageStructure {
263321
&self.structure
264322
}
265323
}
266324

325+
// ========================= impl for DynamicMessage =========================
326+
327+
impl Drop for DynamicMessage {
328+
fn drop(&mut self) {
329+
if self.needs_fini {
330+
// SAFETY: The fini_function expects to be passed a pointer to the message
331+
unsafe { (self.metadata.fini_function)(self.storage.as_mut_ptr() as _) }
332+
}
333+
}
334+
}
335+
336+
impl PartialEq for DynamicMessage {
337+
fn eq(&self, other: &Self) -> bool {
338+
self.metadata.type_support_ptr == other.metadata.type_support_ptr
339+
&& self.storage == other.storage
340+
}
341+
}
342+
343+
impl Eq for DynamicMessage {}
344+
345+
impl DynamicMessage {
346+
/// Dynamically loads a type support library for the specified type and creates a message instance.
347+
///
348+
/// The full message type is of the form `<package>/msg/<type_name>`, e.g.
349+
/// `std_msgs/msg/String`.
350+
///
351+
/// The message instance will contain the default values of the message type.
352+
pub fn new(full_message_type: &str) -> Result<Self, DynamicMessageError> {
353+
DynamicMessageMetadata::new(full_message_type)?.create()
354+
}
355+
356+
/// Returns a description of the message structure.
357+
pub fn structure(&self) -> &MessageStructure {
358+
&self.metadata.structure
359+
}
360+
361+
/// Converts a statically typed RMW-native message into a `DynamicMessage`.
362+
pub fn convert_from_rmw_message<T>(mut msg: T) -> Result<Self, DynamicMessageError>
363+
where
364+
T: RmwMessage,
365+
{
366+
let mut dyn_msg = Self::new(<T as RmwMessage>::TYPE_NAME)?;
367+
let align = std::mem::align_of::<T>();
368+
assert_eq!(dyn_msg.storage.as_ptr().align_offset(align), 0);
369+
{
370+
// SAFETY: This transmutes the slice of bytes into a &mut T. This is fine, since
371+
// under the hood it *is* a T.
372+
// However, the resulting value is not seen as borrowing from dyn_msg by the borrow checker,
373+
// so we are careful to not create a second mutable reference before dropping this one,
374+
// since that would be UB.
375+
let dyn_msg_transmuted = unsafe { &mut *(dyn_msg.storage.as_mut_ptr() as *mut T) };
376+
// We cannot simply overwrite one message with the other, or we will get a memory leak/double-free.
377+
// Swapping is the solution.
378+
std::mem::swap(&mut msg, dyn_msg_transmuted);
379+
}
380+
Ok(dyn_msg)
381+
}
382+
383+
/// Converts a `DynamicMessage` into a statically typed RMW-native message.
384+
///
385+
/// If the RMW-native message type does not match the underlying message type of this `DynamicMessage`,
386+
/// it is not converted but instead returned unchanged.
387+
pub fn convert_into_rmw_message<T>(mut self) -> Result<T, Self>
388+
where
389+
T: RmwMessage,
390+
{
391+
if <T as RmwMessage>::TYPE_NAME == self.metadata.message_type.to_string() {
392+
// SAFETY: Even though a zero-initialized message might not match RMW expectations for
393+
// what a message should look like, it is safe to temporarily have a zero-initialized
394+
// value, i.e. it is not undefined behavior to do this since it's a C struct, and an
395+
// all-zeroes bit pattern is always a valid instance of any C struct.
396+
let mut dest = unsafe { std::mem::zeroed::<T>() };
397+
let dest_ptr = &mut dest as *mut T as *mut u8;
398+
// This reinterprets the struct as a slice of bytes.
399+
// The bytes copied into the dest slice are a valid value of T, as ensured by comparison
400+
// of the type support pointers.
401+
let dest_slice =
402+
unsafe { std::slice::from_raw_parts_mut(dest_ptr, std::mem::size_of::<T>()) };
403+
// This creates a shallow copy, with ownership of the "deep" (or inner) parts moving
404+
// into the destination.
405+
dest_slice.copy_from_slice(&*self.storage);
406+
// Don't run the fini function on the src data anymore, because the inner parts would be
407+
// double-freed by dst and src.
408+
self.needs_fini = false;
409+
Ok(dest)
410+
} else {
411+
Err(self)
412+
}
413+
}
414+
}
415+
267416
#[cfg(test)]
268417
mod tests {
269418
use super::*;
@@ -275,6 +424,8 @@ mod tests {
275424
fn all_types_are_sync_and_send() {
276425
assert_send::<DynamicMessageMetadata>();
277426
assert_sync::<DynamicMessageMetadata>();
427+
assert_send::<DynamicMessage>();
428+
assert_sync::<DynamicMessage>();
278429
}
279430

280431
#[test]

rclrs_tests/src/dynamic_message_tests.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,26 @@ use std::num::NonZeroUsize;
22

33
use rclrs::dynamic_message::*;
44

5+
#[test]
6+
fn max_alignment_is_8() {
7+
// The DynamicMessage type makes sure that its storage is aligned to 8
8+
let alignments = [
9+
std::mem::align_of::<msg::Builtins>(),
10+
std::mem::align_of::<msg::Arrays>(),
11+
std::mem::align_of::<msg::Empty>(),
12+
std::mem::align_of::<msg::Strings>(),
13+
std::mem::align_of::<msg::BoundedSequences>(),
14+
std::mem::align_of::<msg::Nested>(),
15+
std::mem::align_of::<msg::MultiNested>(),
16+
std::mem::align_of::<msg::UnboundedSequences>(),
17+
std::mem::align_of::<msg::WStrings>(),
18+
std::mem::align_of::<msg::Constants>(),
19+
std::mem::align_of::<msg::BasicTypes>(),
20+
std::mem::align_of::<msg::Defaults>(),
21+
];
22+
assert_eq!(alignments.into_iter().max().unwrap(), 8);
23+
}
24+
525
#[test]
626
fn message_structure_is_accurate() {
727
let arrays_metadata = DynamicMessageMetadata::new("test_msgs/msg/Arrays").unwrap();

0 commit comments

Comments
 (0)