Skip to content

Commit 29fe10e

Browse files
committed
Migrate Context, Executor, and Node creation to new API
Signed-off-by: Michael X. Grey <[email protected]>
1 parent 066dd7c commit 29fe10e

File tree

14 files changed

+509
-395
lines changed

14 files changed

+509
-395
lines changed

rclrs/src/context.rs

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::{
66
vec::Vec,
77
};
88

9-
use crate::{rcl_bindings::*, RclrsError, ToResult};
9+
use crate::{rcl_bindings::*, RclrsError, ToResult, Executor};
1010

1111
/// This is locked whenever initializing or dropping any middleware entity
1212
/// because we have found issues in RCL and some RMW implementations that
@@ -70,34 +70,41 @@ pub(crate) struct ContextHandle {
7070
pub(crate) rcl_context: Mutex<rcl_context_t>,
7171
}
7272

73+
impl Default for Context {
74+
fn default() -> Self {
75+
// SAFETY: It should always be valid to instantiate a context with no
76+
// arguments, no parameters, no options, etc.
77+
Self::new([], InitOptions::default())
78+
.expect("Failed to instantiate a default context")
79+
}
80+
}
81+
7382
impl Context {
7483
/// Creates a new context.
7584
///
76-
/// Usually this would be called with `std::env::args()`, analogously to `rclcpp::init()`.
77-
/// See also the official "Passing ROS arguments to nodes via the command-line" tutorial.
85+
/// * `args` - A sequence of strings that resembles command line arguments
86+
/// that users can pass into a ROS executable. See [the official tutorial][1]
87+
/// to know what these arguments may look like. To simply pass in the arguments
88+
/// that the user has provided from the command line, call [`Self::from_env`]
89+
/// or [`Self::default_from_env`] instead.
7890
///
79-
/// Creating a context will fail if the args contain invalid ROS arguments.
91+
/// * `options` - Additional options that your application can use to override
92+
/// settings that would otherwise be determined by the environment.
8093
///
81-
/// # Example
82-
/// ```
83-
/// # use rclrs::Context;
84-
/// assert!(Context::new([]).is_ok());
85-
/// let invalid_remapping = ["--ros-args", "-r", ":=:*/]"].map(String::from);
86-
/// assert!(Context::new(invalid_remapping).is_err());
87-
/// ```
88-
pub fn new(args: impl IntoIterator<Item = String>) -> Result<Self, RclrsError> {
89-
Self::new_with_options(args, InitOptions::new())
90-
}
91-
92-
/// Same as [`Context::new`] except you can additionally provide initialization options.
94+
/// Creating a context will fail if `args` contains invalid ROS arguments.
9395
///
9496
/// # Example
9597
/// ```
9698
/// use rclrs::{Context, InitOptions};
97-
/// let context = Context::new_with_options([], InitOptions::new().with_domain_id(Some(5))).unwrap();
99+
/// let context = Context::new(
100+
/// std::env::args(),
101+
/// InitOptions::new().with_domain_id(Some(5)),
102+
/// ).unwrap();
98103
/// assert_eq!(context.domain_id(), 5);
99-
/// ````
100-
pub fn new_with_options(
104+
/// ```
105+
///
106+
/// [1]: https://docs.ros.org/en/rolling/How-To-Guides/Node-arguments.html
107+
pub fn new(
101108
args: impl IntoIterator<Item = String>,
102109
options: InitOptions,
103110
) -> Result<Self, RclrsError> {
@@ -150,6 +157,23 @@ impl Context {
150157
})
151158
}
152159

160+
/// Same as [`Self::new`] but [`std::env::args`] is automatically passed in
161+
/// for `args`.
162+
pub fn from_env(options: InitOptions) -> Result<Self, RclrsError> {
163+
Self::new(std::env::args(), options)
164+
}
165+
166+
/// Same as [`Self::from_env`] but the default [`InitOptions`] is passed in
167+
/// for `options`.
168+
pub fn default_from_env() -> Result<Self, RclrsError> {
169+
Self::new(std::env::args(), InitOptions::default())
170+
}
171+
172+
/// Create a basic executor that comes built into rclrs.
173+
pub fn create_basic_executor(&self) -> Executor {
174+
Executor::new(Arc::clone(&self.handle))
175+
}
176+
153177
/// Returns the ROS domain ID that the context is using.
154178
///
155179
/// The domain ID controls which nodes can send messages to each other, see the [ROS 2 concept article][1].
@@ -250,14 +274,14 @@ mod tests {
250274
#[test]
251275
fn test_create_context() -> Result<(), RclrsError> {
252276
// If the context fails to be created, this will cause a panic
253-
let _ = Context::new(vec![])?;
277+
let _ = Context::new(vec![], InitOptions::default())?;
254278
Ok(())
255279
}
256280

257281
#[test]
258282
fn test_context_ok() -> Result<(), RclrsError> {
259283
// If the context fails to be created, this will cause a panic
260-
let created_context = Context::new(vec![]).unwrap();
284+
let created_context = Context::new(vec![], InitOptions::default()).unwrap();
261285
assert!(created_context.ok());
262286

263287
Ok(())

rclrs/src/error.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,3 +352,24 @@ impl ToResult for rcl_ret_t {
352352
to_rclrs_result(*self)
353353
}
354354
}
355+
356+
/// A helper trait to disregard timeouts as not an error.
357+
pub trait RclrsErrorFilter {
358+
/// If the result was a timeout error, change it to `Ok(())`.
359+
fn timeout_ok(self) -> Result<(), RclrsError>;
360+
}
361+
362+
impl RclrsErrorFilter for Result<(), RclrsError> {
363+
fn timeout_ok(self) -> Result<(), RclrsError> {
364+
match self {
365+
Ok(()) => Ok(()),
366+
Err(err) => {
367+
if matches!(err, RclrsError::RclError { code: RclReturnCode::Timeout, .. }) {
368+
return Ok(());
369+
}
370+
371+
Err(err)
372+
}
373+
}
374+
}
375+
}

rclrs/src/executor.rs

Lines changed: 84 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,61 @@
1-
use crate::{rcl_bindings::rcl_context_is_valid, Node, RclReturnCode, RclrsError, WaitSet};
1+
use crate::{
2+
rcl_bindings::rcl_context_is_valid,
3+
Node, RclrsError, WaitSet, ContextHandle, NodeOptions, WeakNode,
4+
};
25
use std::{
3-
sync::{Arc, Mutex, Weak},
6+
sync::{Arc, Mutex},
47
time::Duration,
58
};
69

710
/// Single-threaded executor implementation.
8-
pub struct SingleThreadedExecutor {
9-
nodes_mtx: Mutex<Vec<Weak<Node>>>,
11+
pub struct Executor {
12+
context: Arc<ContextHandle>,
13+
nodes_mtx: Mutex<Vec<WeakNode>>,
1014
}
1115

12-
impl Default for SingleThreadedExecutor {
13-
fn default() -> Self {
14-
Self::new()
16+
impl Executor {
17+
/// Create a [`Node`] that will run on this Executor.
18+
pub fn create_node(
19+
&self,
20+
options: impl Into<NodeOptions>,
21+
) -> Result<Node, RclrsError> {
22+
let options: NodeOptions = options.into();
23+
let node = options.build(&self.context)?;
24+
self.nodes_mtx.lock().unwrap().push(node.downgrade());
25+
Ok(node)
1526
}
16-
}
1727

18-
impl SingleThreadedExecutor {
19-
/// Creates a new executor.
20-
pub fn new() -> Self {
21-
SingleThreadedExecutor {
22-
nodes_mtx: Mutex::new(Vec::new()),
23-
}
24-
}
28+
/// Spin the Executor. The current thread will be blocked until the Executor
29+
/// stops spinning.
30+
///
31+
/// [`SpinOptions`] can be used to automatically stop the spinning when
32+
/// certain conditions are met. Use `SpinOptions::default()` to allow the
33+
/// Executor to keep spinning indefinitely.
34+
pub fn spin(&mut self, options: SpinOptions) -> Result<(), RclrsError> {
35+
loop {
36+
if self.nodes_mtx.lock().unwrap().is_empty() {
37+
// Nothing to spin for, so just quit here
38+
return Ok(());
39+
}
2540

26-
/// Add a node to the executor.
27-
pub fn add_node(&self, node: &Arc<Node>) -> Result<(), RclrsError> {
28-
{ self.nodes_mtx.lock().unwrap() }.push(Arc::downgrade(node));
29-
Ok(())
30-
}
41+
self.spin_once(options.timeout)?;
3142

32-
/// Remove a node from the executor.
33-
pub fn remove_node(&self, node: Arc<Node>) -> Result<(), RclrsError> {
34-
{ self.nodes_mtx.lock().unwrap() }
35-
.retain(|n| !n.upgrade().map(|n| Arc::ptr_eq(&n, &node)).unwrap_or(false));
36-
Ok(())
43+
if options.only_next_available_work {
44+
// We were only suppposed to spin once, so quit here
45+
return Ok(());
46+
}
47+
48+
std::thread::yield_now();
49+
}
3750
}
3851

3952
/// Polls the nodes for new messages and executes the corresponding callbacks.
4053
///
4154
/// This function additionally checks that the context is still valid.
42-
pub fn spin_once(&self, timeout: Option<Duration>) -> Result<(), RclrsError> {
55+
fn spin_once(&self, timeout: Option<Duration>) -> Result<(), RclrsError> {
4356
for node in { self.nodes_mtx.lock().unwrap() }
4457
.iter()
45-
.filter_map(Weak::upgrade)
58+
.filter_map(WeakNode::upgrade)
4659
.filter(|node| unsafe {
4760
rcl_context_is_valid(&*node.handle.context_handle.rcl_context.lock().unwrap())
4861
})
@@ -66,19 +79,51 @@ impl SingleThreadedExecutor {
6679
Ok(())
6780
}
6881

69-
/// Convenience function for calling [`SingleThreadedExecutor::spin_once`] in a loop.
70-
pub fn spin(&self) -> Result<(), RclrsError> {
71-
while !{ self.nodes_mtx.lock().unwrap() }.is_empty() {
72-
match self.spin_once(None) {
73-
Ok(_)
74-
| Err(RclrsError::RclError {
75-
code: RclReturnCode::Timeout,
76-
..
77-
}) => std::thread::yield_now(),
78-
error => return error,
79-
}
82+
/// Used by [`Context`] to create the `Executor`. Users cannot call this
83+
/// function.
84+
pub(crate) fn new(context: Arc<ContextHandle>) -> Self {
85+
Self {
86+
context,
87+
nodes_mtx: Mutex::new(Vec::new()),
8088
}
89+
}
90+
}
8191

82-
Ok(())
92+
/// A bundle of optional conditions that a user may want to impose on how long
93+
/// an executor spins for.
94+
///
95+
/// By default the executor will be allowed to spin indefinitely.
96+
#[non_exhaustive]
97+
#[derive(Default)]
98+
pub struct SpinOptions {
99+
/// Only perform the next available work. This is similar to spin_once in
100+
/// rclcpp and rclpy.
101+
///
102+
/// To only process work that is immediately available without waiting at all,
103+
/// set a timeout of zero.
104+
pub only_next_available_work: bool,
105+
/// Stop waiting after this duration of time has passed. Use `Some(0)` to not
106+
/// wait any amount of time. Use `None` to wait an infinite amount of time.
107+
pub timeout: Option<Duration>,
108+
}
109+
110+
impl SpinOptions {
111+
/// Use default spin options.
112+
pub fn new() -> Self {
113+
Self::default()
114+
}
115+
116+
/// Behave like spin_once in rclcpp and rclpy.
117+
pub fn spin_once() -> Self {
118+
Self {
119+
only_next_available_work: true,
120+
..Default::default()
121+
}
122+
}
123+
124+
/// Stop spinning once this durtion of time is reached.
125+
pub fn timeout(mut self, timeout: Duration) -> Self {
126+
self.timeout = Some(timeout);
127+
self
83128
}
84129
}

rclrs/src/lib.rs

Lines changed: 0 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@ mod rcl_bindings;
3030
#[cfg(feature = "dyn_msg")]
3131
pub mod dynamic_message;
3232

33-
use std::{sync::Arc, time::Duration};
34-
3533
pub use arguments::*;
3634
pub use client::*;
3735
pub use clock::*;
@@ -48,66 +46,3 @@ pub use subscription::*;
4846
pub use time::*;
4947
use time_source::*;
5048
pub use wait::*;
51-
52-
/// Polls the node for new messages and executes the corresponding callbacks.
53-
///
54-
/// See [`WaitSet::wait`] for the meaning of the `timeout` parameter.
55-
///
56-
/// This may under some circumstances return
57-
/// [`SubscriptionTakeFailed`][1], [`ClientTakeFailed`][1], [`ServiceTakeFailed`][1] when the wait
58-
/// set spuriously wakes up.
59-
/// This can usually be ignored.
60-
///
61-
/// [1]: crate::RclReturnCode
62-
pub fn spin_once(node: Arc<Node>, timeout: Option<Duration>) -> Result<(), RclrsError> {
63-
let executor = SingleThreadedExecutor::new();
64-
executor.add_node(&node)?;
65-
executor.spin_once(timeout)
66-
}
67-
68-
/// Convenience function for calling [`spin_once`] in a loop.
69-
pub fn spin(node: Arc<Node>) -> Result<(), RclrsError> {
70-
let executor = SingleThreadedExecutor::new();
71-
executor.add_node(&node)?;
72-
executor.spin()
73-
}
74-
75-
/// Creates a new node in the empty namespace.
76-
///
77-
/// Convenience function equivalent to [`Node::new`][1].
78-
/// Please see that function's documentation.
79-
///
80-
/// [1]: crate::Node::new
81-
///
82-
/// # Example
83-
/// ```
84-
/// # use rclrs::{Context, RclrsError};
85-
/// let ctx = Context::new([])?;
86-
/// let node = rclrs::create_node(&ctx, "my_node");
87-
/// assert!(node.is_ok());
88-
/// # Ok::<(), RclrsError>(())
89-
/// ```
90-
pub fn create_node(context: &Context, node_name: &str) -> Result<Arc<Node>, RclrsError> {
91-
Node::new(context, node_name)
92-
}
93-
94-
/// Creates a [`NodeBuilder`].
95-
///
96-
/// Convenience function equivalent to [`NodeBuilder::new()`][1] and [`Node::builder()`][2].
97-
/// Please see that function's documentation.
98-
///
99-
/// [1]: crate::NodeBuilder::new
100-
/// [2]: crate::Node::builder
101-
///
102-
/// # Example
103-
/// ```
104-
/// # use rclrs::{Context, RclrsError};
105-
/// let context = Context::new([])?;
106-
/// let node_builder = rclrs::create_node_builder(&context, "my_node");
107-
/// let node = node_builder.build()?;
108-
/// assert_eq!(node.name(), "my_node");
109-
/// # Ok::<(), RclrsError>(())
110-
/// ```
111-
pub fn create_node_builder(context: &Context, node_name: &str) -> NodeBuilder {
112-
Node::builder(context, node_name)
113-
}

0 commit comments

Comments
 (0)