Skip to content

Commit 3ddd682

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

File tree

2 files changed

+169
-4
lines changed

2 files changed

+169
-4
lines changed

rclrs/src/dynamic_message.rs

Lines changed: 149 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@
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")))]
@@ -54,19 +57,33 @@ struct MessageTypeName {
5457
/// can be used as a factory to create message instances.
5558
#[derive(Clone)]
5659
pub struct DynamicMessageMetadata {
57-
#[allow(dead_code)]
5860
message_type: MessageTypeName,
5961
// The library needs to be kept loaded in order to keep the type_support_ptr valid.
62+
// This is the introspection type support library, not the regular one.
6063
#[allow(dead_code)]
6164
introspection_type_support_library: Arc<libloading::Library>,
62-
#[allow(dead_code)]
6365
type_support_ptr: *const rosidl_message_type_support_t,
64-
#[allow(dead_code)]
6566
structure: MessageStructure,
66-
#[allow(dead_code)]
6767
fini_function: unsafe extern "C" fn(*mut std::os::raw::c_void),
6868
}
6969

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

7289
/// This is an analogue of rclcpp::get_typesupport_library.
@@ -258,12 +275,138 @@ impl DynamicMessageMetadata {
258275
pkg.message_metadata(type_name)
259276
}
260277

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

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

280425
#[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)