Skip to content

Commit 30b1931

Browse files
authored
Add methods related to node options to NodeBuilder (#186)
1 parent 1abc390 commit 30b1931

File tree

1 file changed

+137
-8
lines changed

1 file changed

+137
-8
lines changed

rclrs/src/node/builder.rs

Lines changed: 137 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ use std::sync::Arc;
1313
///
1414
/// The default values for optional fields are:
1515
/// - `namespace: "/"`
16+
/// - `use_global_arguments: true`
17+
/// - `arguments: []`
18+
/// - `enable_rosout: true`
1619
///
1720
/// # Example
1821
/// ```
@@ -35,11 +38,13 @@ use std::sync::Arc;
3538
///
3639
/// [1]: crate::Node
3740
/// [2]: crate::Node::builder
38-
///
3941
pub struct NodeBuilder {
4042
context: Arc<Mutex<rcl_context_t>>,
4143
name: String,
4244
namespace: String,
45+
use_global_arguments: bool,
46+
arguments: Vec<String>,
47+
enable_rosout: bool,
4348
}
4449

4550
impl NodeBuilder {
@@ -84,6 +89,9 @@ impl NodeBuilder {
8489
context: context.handle.clone(),
8590
name: name.to_string(),
8691
namespace: "/".to_string(),
92+
use_global_arguments: true,
93+
arguments: vec![],
94+
enable_rosout: true,
8795
}
8896
}
8997

@@ -142,6 +150,81 @@ impl NodeBuilder {
142150
self
143151
}
144152

153+
/// Enables or disables using global arguments.
154+
///
155+
/// The "global" arguments are those used in [creating the context][1].
156+
///
157+
/// # Example
158+
/// ```
159+
/// # use rclrs::{Context, Node, NodeBuilder, RclrsError};
160+
/// let context_args = ["--ros-args", "--remap", "__node:=your_node"]
161+
/// .map(String::from);
162+
/// let context = Context::new(context_args)?;
163+
/// // Ignore the global arguments:
164+
/// let node_without_global_args = context
165+
/// .create_node_builder("my_node")
166+
/// .use_global_arguments(false)
167+
/// .build()?;
168+
/// assert_eq!(node_without_global_args.name(), "my_node");
169+
/// // Do not ignore the global arguments:
170+
/// let node_with_global_args = context
171+
/// .create_node_builder("my_other_node")
172+
/// .use_global_arguments(true)
173+
/// .build()?;
174+
/// assert_eq!(node_with_global_args.name(), "your_node");
175+
/// # Ok::<(), RclrsError>(())
176+
/// ```
177+
///
178+
/// [1]: crate::Context::new
179+
pub fn use_global_arguments(mut self, enable: bool) -> Self {
180+
self.use_global_arguments = enable;
181+
self
182+
}
183+
184+
/// Sets node-specific command line arguments.
185+
///
186+
/// These arguments are parsed the same way as those for [`Context::new()`][1].
187+
/// However, the node-specific command line arguments have higher precedence than the arguments
188+
/// used in creating the context.
189+
///
190+
/// For more details about command line arguments, see [here][2].
191+
///
192+
/// # Example
193+
/// ```
194+
/// # use rclrs::{Context, Node, NodeBuilder, RclrsError};
195+
/// // Usually, this would change the name of "my_node" to "context_args_node":
196+
/// let context_args = ["--ros-args", "--remap", "my_node:__node:=context_args_node"]
197+
/// .map(String::from);
198+
/// let context = Context::new(context_args)?;
199+
/// // But the node arguments will change it to "node_args_node":
200+
/// let node_args = ["--ros-args", "--remap", "my_node:__node:=node_args_node"]
201+
/// .map(String::from);
202+
/// let node = context
203+
/// .create_node_builder("my_node")
204+
/// .arguments(node_args)
205+
/// .build()?;
206+
/// assert_eq!(node.name(), "node_args_node");
207+
/// # Ok::<(), RclrsError>(())
208+
/// ```
209+
///
210+
/// [1]: crate::Context::new
211+
/// [2]: https://design.ros2.org/articles/ros_command_line_arguments.html
212+
pub fn arguments(mut self, arguments: impl IntoIterator<Item = String>) -> Self {
213+
self.arguments = arguments.into_iter().collect();
214+
self
215+
}
216+
217+
/// Enables or disables logging to rosout.
218+
///
219+
/// When enabled, log messages are published to the `/rosout` topic in addition to
220+
/// standard output.
221+
///
222+
/// This option is currently unused in `rclrs`.
223+
pub fn enable_rosout(mut self, enable: bool) -> Self {
224+
self.enable_rosout = enable;
225+
self
226+
}
227+
145228
/// Builds the node instance.
146229
///
147230
/// Node name and namespace validation is performed in this method.
@@ -160,16 +243,12 @@ impl NodeBuilder {
160243
err,
161244
s: self.namespace.clone(),
162245
})?;
246+
let node_options = self.create_node_options()?;
247+
let context_handle = &mut *self.context.lock();
163248

164-
// SAFETY: No preconditions for this function.
249+
// SAFETY: Getting a zero-initialized value is always safe.
165250
let mut node_handle = unsafe { rcl_get_zero_initialized_node() };
166-
167251
unsafe {
168-
// SAFETY: No preconditions for this function.
169-
let context_handle = &mut *self.context.lock();
170-
// SAFETY: No preconditions for this function.
171-
let node_options = rcl_node_get_default_options();
172-
173252
// SAFETY: The node handle is zero-initialized as expected by this function.
174253
// The strings and node options are copied by this function, so we don't need
175254
// to keep them alive.
@@ -192,4 +271,54 @@ impl NodeBuilder {
192271
subscriptions: std::vec![],
193272
})
194273
}
274+
275+
/// Creates node options.
276+
///
277+
/// Any fields not present in the builder will have their default value.
278+
/// For detail about default values, see [`NodeBuilder`][1] docs.
279+
///
280+
/// [1]: crate::NodeBuilder
281+
fn create_node_options(&self) -> Result<rcl_node_options_t, RclrsError> {
282+
// SAFETY: No preconditions for this function.
283+
let mut node_options = unsafe { rcl_node_get_default_options() };
284+
285+
let cstring_args = self
286+
.arguments
287+
.iter()
288+
.map(|s| match CString::new(s.as_str()) {
289+
Ok(cstr) => Ok(cstr),
290+
Err(err) => Err(RclrsError::StringContainsNul { s: s.clone(), err }),
291+
})
292+
.collect::<Result<Vec<_>, _>>()?;
293+
294+
let cstring_arg_ptrs = cstring_args.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
295+
unsafe {
296+
// SAFETY: This function does not store the ephemeral cstring_args_ptrs
297+
// pointers. We are passing in a zero-initialized arguments struct as expected.
298+
rcl_parse_arguments(
299+
cstring_arg_ptrs.len() as i32,
300+
cstring_arg_ptrs.as_ptr(),
301+
rcutils_get_default_allocator(),
302+
&mut node_options.arguments,
303+
)
304+
}
305+
.ok()?;
306+
307+
node_options.use_global_arguments = self.use_global_arguments;
308+
node_options.enable_rosout = self.enable_rosout;
309+
// SAFETY: No preconditions for this function.
310+
node_options.allocator = unsafe { rcutils_get_default_allocator() };
311+
312+
Ok(node_options)
313+
}
314+
}
315+
316+
impl Drop for rcl_node_options_t {
317+
fn drop(&mut self) {
318+
// SAFETY: Do not finish this struct except here.
319+
unsafe {
320+
// This also finalizes the `rcl_arguments_t` contained in `rcl_node_options_t`.
321+
rcl_node_options_fini(self).ok().unwrap();
322+
}
323+
}
195324
}

0 commit comments

Comments
 (0)