66//! The central type of this module is [`DynamicMessage`].
77
88use std:: {
9- collections:: HashMap ,
109 fmt:: { self , Display } ,
1110 ops:: Deref ,
1211 path:: PathBuf ,
13- sync:: { Arc , LazyLock , Mutex } ,
12+ sync:: Arc ,
1413} ;
1514
1615use rosidl_runtime_rs:: RmwMessage ;
@@ -30,49 +29,24 @@ pub use error::*;
3029pub use field_access:: * ;
3130pub use message_structure:: * ;
3231
33- /// A struct to cache loaded shared libraries for dynamic messages, indexing them by name.
34- #[ derive( Default ) ]
35- pub struct DynamicMessageLibraryCache ( HashMap < String , Arc < libloading:: Library > > ) ;
36-
37- impl DynamicMessageLibraryCache {
38- /// Get a reference to the library for the specific `package_name`. Attempt to load and store
39- /// it in the cache if it is not currently loaded.
40- pub fn get_or_load (
41- & mut self ,
42- package_name : & str ,
43- ) -> Result < Arc < libloading:: Library > , DynamicMessageError > {
44- use std:: collections:: hash_map:: Entry ;
45- let lib = match self . 0 . entry ( package_name. into ( ) ) {
46- Entry :: Occupied ( entry) => entry. get ( ) . clone ( ) ,
47- Entry :: Vacant ( entry) => entry
48- . insert ( get_type_support_library (
49- package_name,
50- INTROSPECTION_TYPE_SUPPORT_IDENTIFIER ,
51- ) ?)
52- . clone ( ) ,
53- } ;
54- Ok ( lib)
55- }
56-
57- /// Remove a package_name from the cache. Return `true` if it was removed, `false` otherwise
58- ///
59- /// This function can be used to reduce memory footprint if the message library is not used
60- /// anymore.
61- /// Note that since shared libraries are wrapped by an `Arc` this does _not_ unload the library
62- /// until all other structures that reference it ([`DynamicMessage`] or
63- /// [`DynamicMessageMetadata`]) are also dropped.
64- pub fn unload ( & mut self , package_name : & str ) -> bool {
65- self . 0 . remove ( package_name) . is_some ( )
66- }
67- }
68-
69- /// A global cache for loaded message packages.
32+ /// Factory for constructing messages in a certain package dynamically.
7033///
71- /// Since creating a new dynamic message requires loading a shared library from the file system, by
72- /// caching loaded libraries we can reduce the overhead for preloaded libraries to
73- /// just a [`Arc::clone`].
74- pub static DYNAMIC_MESSAGE_PACKAGE_CACHE : LazyLock < Mutex < DynamicMessageLibraryCache > > =
75- LazyLock :: new ( || Default :: default ( ) ) ;
34+ /// This is the result of loading the introspection type support library (which is a per-package
35+ /// operation), whereas [`DynamicMessageMetadata`] is the result of loading the data related to
36+ /// the message from the library.
37+ //
38+ // Theoretically it could be beneficial to make this struct public so users can "cache"
39+ // the library loading, but unless a compelling use case comes up, I don't think it's
40+ // worth the complexity.
41+ //
42+ // Under the hood, this is an `Arc<libloading::Library>`, so if this struct and the
43+ // [`DynamicMessageMetadata`] and [`DynamicMessage`] structs created from it are dropped,
44+ // the library will be unloaded. This shared ownership ensures that the type_support_ptr
45+ // is always valid.
46+ struct DynamicMessagePackage {
47+ introspection_type_support_library : Arc < libloading:: Library > ,
48+ package : String ,
49+ }
7650
7751/// A parsed/validated message type name of the form `<package_name>/msg/<type_name>`.
7852#[ derive( Clone , Debug , PartialEq , Eq ) ]
@@ -116,6 +90,8 @@ pub struct DynamicMessage {
11690 needs_fini : bool ,
11791}
11892
93+ // ========================= impl for DynamicMessagePackage =========================
94+
11995/// This is an analogue of rclcpp::get_typesupport_library.
12096fn get_type_support_library (
12197 package_name : & str ,
@@ -184,6 +160,62 @@ unsafe fn get_type_support_handle(
184160
185161const INTROSPECTION_TYPE_SUPPORT_IDENTIFIER : & str = "rosidl_typesupport_introspection_c" ;
186162
163+ impl DynamicMessagePackage {
164+ /// Creates a new `DynamicMessagePackage`.
165+ ///
166+ /// This dynamically loads a type support library for the specified package.
167+ pub fn new ( package_name : impl Into < String > ) -> Result < Self , DynamicMessageError > {
168+ let package_name = package_name. into ( ) ;
169+ Ok ( Self {
170+ introspection_type_support_library : get_type_support_library (
171+ & package_name,
172+ INTROSPECTION_TYPE_SUPPORT_IDENTIFIER ,
173+ ) ?,
174+ package : package_name,
175+ } )
176+ }
177+
178+ pub ( crate ) fn message_metadata (
179+ & self ,
180+ type_name : impl Into < String > ,
181+ ) -> Result < DynamicMessageMetadata , DynamicMessageError > {
182+ let message_type = MessageTypeName {
183+ package_name : self . package . clone ( ) ,
184+ type_name : type_name. into ( ) ,
185+ } ;
186+ // SAFETY: The symbol type of the type support getter function can be trusted
187+ // assuming the install dir hasn't been tampered with.
188+ // The pointer returned by this function is kept valid by keeping the library loaded.
189+ let type_support_ptr = unsafe {
190+ get_type_support_handle (
191+ self . introspection_type_support_library . as_ref ( ) ,
192+ INTROSPECTION_TYPE_SUPPORT_IDENTIFIER ,
193+ & message_type,
194+ ) ?
195+ } ;
196+ // SAFETY: The pointer returned by get_type_support_handle() is always valid.
197+ let type_support = unsafe { & * type_support_ptr } ;
198+ debug_assert ! ( !type_support. data. is_null( ) ) ;
199+ let message_members: & rosidl_message_members_t =
200+ // SAFETY: The data pointer is supposed to be always valid.
201+ 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) } ;
204+ // The fini function will always exist.
205+ let fini_function = message_members. fini_function . unwrap ( ) ;
206+ let metadata = DynamicMessageMetadata {
207+ message_type,
208+ introspection_type_support_library : Arc :: clone (
209+ & self . introspection_type_support_library ,
210+ ) ,
211+ type_support_ptr,
212+ structure,
213+ fini_function,
214+ } ;
215+ Ok ( metadata)
216+ }
217+ }
218+
187219// ========================= impl for MessageTypeName =========================
188220
189221impl TryFrom < & str > for MessageTypeName {
@@ -248,38 +280,8 @@ impl DynamicMessageMetadata {
248280 ///
249281 /// See [`DynamicMessage::new()`] for the expected format of the `full_message_type`.
250282 pub fn new ( message_type : MessageTypeName ) -> Result < Self , DynamicMessageError > {
251- // SAFETY: The symbol type of the type support getter function can be trusted
252- // assuming the install dir hasn't been tampered with.
253- // The pointer returned by this function is kept valid by keeping the library loaded.
254- let library = DYNAMIC_MESSAGE_PACKAGE_CACHE
255- . lock ( )
256- . unwrap ( )
257- . get_or_load ( & message_type. package_name ) ?;
258- let type_support_ptr = unsafe {
259- get_type_support_handle (
260- & * library,
261- INTROSPECTION_TYPE_SUPPORT_IDENTIFIER ,
262- & message_type,
263- ) ?
264- } ;
265- // SAFETY: The pointer returned by get_type_support_handle() is always valid.
266- let type_support = unsafe { & * type_support_ptr } ;
267- debug_assert ! ( !type_support. data. is_null( ) ) ;
268- let message_members: & rosidl_message_members_t =
269- // SAFETY: The data pointer is supposed to be always valid.
270- unsafe { & * ( type_support. data as * const rosidl_message_members_t ) } ;
271- // SAFETY: The message members coming from a type support library will always be valid.
272- let structure = unsafe { MessageStructure :: from_rosidl_message_members ( message_members) } ;
273- // The fini function will always exist.
274- let fini_function = message_members. fini_function . unwrap ( ) ;
275- let metadata = DynamicMessageMetadata {
276- message_type,
277- introspection_type_support_library : library,
278- type_support_ptr,
279- structure,
280- fini_function,
281- } ;
282- Ok ( metadata)
283+ let pkg = DynamicMessagePackage :: new ( & message_type. package_name ) ?;
284+ pkg. message_metadata ( & message_type. type_name )
283285 }
284286
285287 /// Instantiates a new message.
@@ -563,23 +565,4 @@ mod tests {
563565 assert_eq ! ( * value, 42 ) ;
564566 }
565567 }
566-
567- #[ test]
568- fn message_package_cache ( ) {
569- let package_name = "test_msgs" ;
570-
571- // Create a weak reference to avoid increasing reference count
572- let mut cache = DynamicMessageLibraryCache :: default ( ) ;
573- let lib = Arc :: downgrade ( & cache. get_or_load ( package_name) . unwrap ( ) ) ;
574- {
575- // Mock a user of the library (i.e. message)
576- let _mock = lib. upgrade ( ) . unwrap ( ) ;
577- assert ! ( cache. unload( package_name) ) ;
578- assert ! ( !cache. unload( "non_existing_package" ) ) ;
579- // The library should _still_ be loaded since the message holds a reference
580- assert ! ( lib. upgrade( ) . is_some( ) ) ;
581- }
582- // Now the library should be unloaded and the reference should not be upgradeable
583- assert ! ( lib. upgrade( ) . is_none( ) ) ;
584- }
585568}
0 commit comments