|
| 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