Skip to content

Commit 7daf8cc

Browse files
Soya-Onishinnmm
andauthored
Add NodeBuilder for node instantiation (#158)
* Add NodeBuilder * Replace constructor method procedure For backward compatibility, it is better to retain constructor method but deperecated. As for now, constructors only use NodeBuilder. * Fix doc to compile example successfully * Fix doc to work test successfully * Fix field types for more simplicity * Remove unused code That will re-appear when supporting node options * Fix doc comments for cleanliness Co-authored-by: Nikolai Morin <[email protected]> * Fix doc example for more easier to understand how to use Co-authored-by: Nikolai Morin <[email protected]> * Fix doc for more cleanliness Co-authored-by: Nikolai Morin <[email protected]> * Fix docs, Replace err types and Delete some methods * Fix docs reference * Make documentation more consistent * Move NodeBuilder to a dedicated file * Fix doc examples and use Node::builder instead NodeBuilder * Fix doc reference and re-add pub to NodeBuilder::new * Swap arguments to match other related methods * Fix doc to appropriate text Co-authored-by: Nikolai Morin <[email protected]> * Add description about taking name Co-authored-by: Nikolai Morin <[email protected]> * Fix reference Co-authored-by: Nikolai Morin <[email protected]> * Fix entity for reference Co-authored-by: Nikolai Morin <[email protected]> Co-authored-by: Nikolai Morin <[email protected]>
1 parent c8be4f3 commit 7daf8cc

File tree

3 files changed

+270
-118
lines changed

3 files changed

+270
-118
lines changed

rclrs/src/context.rs

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::rcl_bindings::*;
2-
use crate::{Node, RclrsError, ToResult};
2+
use crate::{Node, NodeBuilder, RclrsError, ToResult};
33

44
use std::ffi::CString;
55
use std::os::raw::c_char;
@@ -112,37 +112,36 @@ impl Context {
112112
///
113113
/// # Example
114114
/// ```
115-
/// # use rclrs::{Context, RclReturnCode};
115+
/// # use rclrs::{Context, RclrsError};
116116
/// let ctx = Context::new([])?;
117117
/// let node = ctx.create_node("my_node");
118118
/// assert!(node.is_ok());
119-
/// # Ok::<(), RclReturnCode>(())
119+
/// # Ok::<(), RclrsError>(())
120120
/// ```
121121
pub fn create_node(&self, node_name: &str) -> Result<Node, RclrsError> {
122-
Node::new(node_name, self)
122+
Node::builder(self, node_name).build()
123123
}
124124

125-
/// Creates a new node in a namespace.
125+
/// Creates a [`NodeBuilder`][1].
126126
///
127-
/// Convenience function equivalent to [`Node::new_with_namespace`][1].
127+
/// Convenience function equivalent to [`NodeBuilder::new()`][2] and [`Node::builder()`][3].
128128
/// Please see that function's documentation.
129129
///
130-
/// [1]: crate::Node::new_with_namespace
130+
/// [1]: crate::NodeBuilder
131+
/// [2]: crate::NodeBuilder::new
132+
/// [3]: crate::Node::builder
131133
///
132134
/// # Example
133135
/// ```
134-
/// # use rclrs::{Context, RclReturnCode};
135-
/// let ctx = Context::new([])?;
136-
/// let node = ctx.create_node_with_namespace("/my/nested/namespace", "my_node");
137-
/// assert!(node.is_ok());
138-
/// # Ok::<(), RclReturnCode>(())
136+
/// # use rclrs::{Context, RclrsError};
137+
/// let context = Context::new([])?;
138+
/// let node_builder = context.create_node_builder("my_node");
139+
/// let node = node_builder.build()?;
140+
/// assert_eq!(node.name(), "my_node");
141+
/// # Ok::<(), RclrsError>(())
139142
/// ```
140-
pub fn create_node_with_namespace(
141-
&self,
142-
node_namespace: &str,
143-
node_name: &str,
144-
) -> Result<Node, RclrsError> {
145-
Node::new_with_namespace(node_namespace, node_name, self)
143+
pub fn create_node_builder(&self, node_name: &str) -> NodeBuilder {
144+
Node::builder(self, node_name)
146145
}
147146

148147
/// Checks if the context is still valid.

rclrs/src/node/builder.rs

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
use crate::rcl_bindings::*;
2+
use crate::{Context, Node, RclrsError, ToResult};
3+
4+
use std::ffi::CString;
5+
6+
use parking_lot::Mutex;
7+
use std::sync::Arc;
8+
9+
/// A builder for creating a [`Node`][1].
10+
///
11+
/// The builder pattern allows selectively setting some fields, and leaving all others at their default values.
12+
/// This struct instance can be created via [`Node::builder()`][2].
13+
///
14+
/// The default values for optional fields are:
15+
/// - `namespace: "/"`
16+
///
17+
/// # Example
18+
/// ```
19+
/// # use rclrs::{Context, NodeBuilder, Node, RclrsError};
20+
/// let context = Context::new([])?;
21+
/// // Building a node in a single expression
22+
/// let node = NodeBuilder::new(&context, "foo_node").namespace("/bar").build()?;
23+
/// assert_eq!(node.name(), "foo_node");
24+
/// assert_eq!(node.namespace(), "/bar");
25+
/// // Building a node via Node::builder()
26+
/// let node = Node::builder(&context, "bar_node").build()?;
27+
/// assert_eq!(node.name(), "bar_node");
28+
/// // Building a node step-by-step
29+
/// let mut builder = Node::builder(&context, "goose");
30+
/// builder = builder.namespace("/duck/duck");
31+
/// let node = builder.build()?;
32+
/// assert_eq!(node.fully_qualified_name(), "/duck/duck/goose");
33+
/// # Ok::<(), RclrsError>(())
34+
/// ```
35+
///
36+
/// [1]: crate::Node
37+
/// [2]: crate::Node::builder
38+
///
39+
pub struct NodeBuilder {
40+
context: Arc<Mutex<rcl_context_t>>,
41+
name: String,
42+
namespace: String,
43+
}
44+
45+
impl NodeBuilder {
46+
/// Creates a builder for a node with the given name.
47+
///
48+
/// See the [`Node` docs][1] for general information on node names.
49+
///
50+
/// # Rules for valid node names
51+
///
52+
/// The rules for a valid node name are checked by the [`rmw_validate_node_name()`][2]
53+
/// function. They are:
54+
/// - Must contain only the `a-z`, `A-Z`, `0-9`, and `_` characters
55+
/// - Must not be empty and not be longer than `RMW_NODE_NAME_MAX_NAME_LENGTH`
56+
/// - Must not start with a number
57+
///
58+
/// Note that node name validation is delayed until [`NodeBuilder::build()`][3].
59+
///
60+
/// # Example
61+
/// ```
62+
/// # use rclrs::{Context, NodeBuilder, RclrsError, RclReturnCode, NodeErrorCode};
63+
/// let context = Context::new([])?;
64+
/// // This is a valid node name
65+
/// assert!(NodeBuilder::new(&context, "my_node").build().is_ok());
66+
/// // This is another valid node name (although not a good one)
67+
/// assert!(NodeBuilder::new(&context, "_______").build().is_ok());
68+
/// // This is an invalid node name
69+
/// assert_eq!(
70+
/// NodeBuilder::new(&context, "röböt")
71+
/// .build()
72+
/// .unwrap_err()
73+
/// .code,
74+
/// RclReturnCode::NodeError(NodeErrorCode::NodeInvalidName)
75+
/// );
76+
/// # Ok::<(), RclrsError>(())
77+
/// ```
78+
///
79+
/// [1]: crate::Node#naming
80+
/// [2]: https://docs.ros2.org/latest/api/rmw/validate__node__name_8h.html#a5690a285aed9735f89ef11950b6e39e3
81+
/// [3]: NodeBuilder::build
82+
pub fn new(context: &Context, name: &str) -> NodeBuilder {
83+
NodeBuilder {
84+
context: context.handle.clone(),
85+
name: name.to_string(),
86+
namespace: "/".to_string(),
87+
}
88+
}
89+
90+
/// Sets the node namespace.
91+
///
92+
/// See the [`Node` docs][1] for general information on namespaces.
93+
///
94+
/// # Rules for valid namespaces
95+
///
96+
/// The rules for a valid node namespace are based on the [rules for a valid topic][2]
97+
/// and are checked by the [`rmw_validate_namespace()`][3] function. However, a namespace
98+
/// without a leading forward slash is automatically changed to have a leading forward slash
99+
/// before it is checked with this function.
100+
///
101+
/// Thus, the effective rules are:
102+
/// - Must contain only the `a-z`, `A-Z`, `0-9`, `_`, and `/` characters
103+
/// - Must not have a number at the beginning, or after a `/`
104+
/// - Must not contain two or more `/` characters in a row
105+
/// - Must not have a `/` character at the end, except if `/` is the full namespace
106+
///
107+
/// Note that namespace validation is delayed until [`NodeBuilder::build()`][4].
108+
///
109+
/// # Example
110+
/// ```
111+
/// # use rclrs::{Context, Node, RclrsError, RclReturnCode, NodeErrorCode};
112+
/// let context = Context::new([])?;
113+
/// // This is a valid namespace
114+
/// let builder_ok_ns = Node::builder(&context, "my_node").namespace("/some/nested/namespace");
115+
/// assert!(builder_ok_ns.build().is_ok());
116+
/// // This is an invalid namespace
117+
/// assert_eq!(
118+
/// Node::builder(&context, "my_node")
119+
/// .namespace("/10_percent_luck/20_percent_skill")
120+
/// .build()
121+
/// .unwrap_err()
122+
/// .code,
123+
/// RclReturnCode::NodeError(NodeErrorCode::NodeInvalidNamespace)
124+
/// );
125+
/// // A missing forward slash at the beginning is automatically added
126+
/// assert_eq!(
127+
/// Node::builder(&context, "my_node")
128+
/// .namespace("foo")
129+
/// .build()?
130+
/// .namespace(),
131+
/// "/foo"
132+
/// );
133+
/// # Ok::<(), RclrsError>(())
134+
/// ```
135+
///
136+
/// [1]: crate::Node#naming
137+
/// [2]: http://design.ros2.org/articles/topic_and_service_names.html
138+
/// [3]: https://docs.ros2.org/latest/api/rmw/validate__namespace_8h.html#a043f17d240cf13df01321b19a469ee49
139+
/// [4]: NodeBuilder::build
140+
pub fn namespace(mut self, namespace: &str) -> Self {
141+
self.namespace = namespace.to_string();
142+
self
143+
}
144+
145+
/// Builds the node instance.
146+
///
147+
/// Node name and namespace validation is performed in this method.
148+
///
149+
/// For example usage, see the [`NodeBuilder`][1] docs.
150+
///
151+
/// # Panics
152+
/// When the node name or namespace contain null bytes.
153+
///
154+
/// [1]: crate::NodeBuilder
155+
pub fn build(&self) -> Result<Node, RclrsError> {
156+
let node_name = CString::new(self.name.as_str()).unwrap();
157+
let node_namespace = CString::new(self.namespace.as_str()).unwrap();
158+
159+
// SAFETY: No preconditions for this function.
160+
let mut node_handle = unsafe { rcl_get_zero_initialized_node() };
161+
162+
unsafe {
163+
// SAFETY: No preconditions for this function.
164+
let context_handle = &mut *self.context.lock();
165+
// SAFETY: No preconditions for this function.
166+
let node_options = rcl_node_get_default_options();
167+
168+
// SAFETY: The node handle is zero-initialized as expected by this function.
169+
// The strings and node options are copied by this function, so we don't need
170+
// to keep them alive.
171+
// The context handle has to be kept alive because it is co-owned by the node.
172+
rcl_node_init(
173+
&mut node_handle,
174+
node_name.as_ptr(),
175+
node_namespace.as_ptr(),
176+
context_handle,
177+
&node_options,
178+
)
179+
.ok()?;
180+
};
181+
182+
let handle = Arc::new(Mutex::new(node_handle));
183+
184+
Ok(Node {
185+
handle,
186+
context: self.context.clone(),
187+
subscriptions: std::vec![],
188+
})
189+
}
190+
}

0 commit comments

Comments
 (0)