Skip to content

Commit c8f61f0

Browse files
committed
Iterating on documentation
Signed-off-by: Michael X. Grey <[email protected]>
1 parent 5f6015a commit c8f61f0

File tree

7 files changed

+384
-35
lines changed

7 files changed

+384
-35
lines changed

rclrs/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ serde-big-array = { version = "0.5.1", optional = true }
4141
tempfile = "3.3.0"
4242
# Needed for publisher and subscriber tests
4343
test_msgs = {version = "*"}
44+
# Used in doctests
45+
example_interfaces = { version = "*" }
4446
# Needed for parameter service tests
4547
tokio = { version = "1", features = ["rt", "time", "macros"] }
4648

rclrs/src/client.rs

Lines changed: 124 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::{
22
any::Any,
33
collections::HashMap,
4-
ffi::CString,
4+
ffi::{CStr, CString},
55
sync::{Arc, Mutex, MutexGuard},
66
};
77

@@ -10,7 +10,7 @@ use rosidl_runtime_rs::Message;
1010
use crate::{
1111
error::ToResult, rcl_bindings::*, IntoPrimitiveOptions, MessageCow, Node, Promise, QoSProfile,
1212
RclPrimitive, RclPrimitiveHandle, RclPrimitiveKind, RclReturnCode, RclrsError, ServiceInfo,
13-
Waitable, WaitableLifecycle, ENTITY_LIFECYCLE_MUTEX,
13+
Waitable, WaitableLifecycle, ENTITY_LIFECYCLE_MUTEX, log_fatal,
1414
};
1515

1616
mod client_async_callback;
@@ -90,10 +90,9 @@ where
9090
}
9191
.ok()?;
9292

93-
// TODO(@mxgrey): Log errors here when logging becomes available.
9493
self.board
9594
.lock()
96-
.unwrap()
95+
.map_err(|_| RclrsError::PoisonedMutex)?
9796
.new_request(sequence_number, sender);
9897

9998
Ok(promise)
@@ -105,8 +104,15 @@ where
105104
/// compiler warns you that you need to. You can use the [`Promise`] to know
106105
/// when the response is finished being processed, but otherwise you can
107106
/// safely discard it.
108-
//
109-
// TODO(@mxgrey): Add documentation to show what callback signatures are supported
107+
///
108+
/// # Usage
109+
///
110+
/// Three callback signatures are supported:
111+
/// - [`FnOnce`] ( `Response` )
112+
/// - [`FnOnce`] ( `Response`, [`RequestId`][1] )
113+
/// - [`FnOnce`] ( `Response`, [`ServiceInfo`] )
114+
///
115+
/// [1]: crate::RequestId
110116
pub fn call_then<'a, Req, Args>(
111117
&self,
112118
request: Req,
@@ -127,8 +133,101 @@ where
127133
/// compiler warns you that you need to. You can use the [`Promise`] to know
128134
/// when the response is finished being processed, but otherwise you can
129135
/// safely discard it.
130-
//
131-
// TODO(@mxgrey): Add documentation to show what callback signatures are supported
136+
///
137+
/// # Usage
138+
///
139+
/// Three callback signatures are supported:
140+
/// - [`FnOnce`] ( `Response` ) -> impl [`Future`][1]<Output=()>
141+
/// - [`FnOnce`] ( `Response`, [`RequestId`][2] ) -> impl [`Future`][1]<Output=()>
142+
/// - [`FnOnce`] ( `Response`, [`ServiceInfo`] ) -> impl [`Future`][1]<Output=()>
143+
///
144+
/// [1]: std::future::Future
145+
/// [2]: crate::RequestId
146+
///
147+
/// Since this method is to help implement async behaviors, the callback that
148+
/// you pass to it must return a [`Future`][1]. There are two ways to create
149+
/// a `Future` in Rust:
150+
///
151+
/// ## 1. `async fn`
152+
///
153+
/// Define an `async fn` whose arguments are compatible with one of the above
154+
/// signatures and which returns a `()` (a.k.a. nothing).
155+
/// ```
156+
/// # use rclrs::*;
157+
/// # let node = Context::default()
158+
/// # .create_basic_executor()
159+
/// # .create_node("test_node")?;
160+
/// use test_msgs::srv::{Empty, Empty_Request, Empty_Response};
161+
///
162+
/// async fn print_hello(_response: Empty_Response) {
163+
/// print!("Hello!");
164+
/// }
165+
///
166+
/// let client = node.create_client::<Empty>("my_service")?;
167+
/// let request = Empty_Request::default();
168+
/// let promise = client.call_then_async(&request, print_hello)?;
169+
/// # Ok::<(), RclrsError>(())
170+
/// ```
171+
///
172+
/// ## 2. Function that returns an `async { ... }`
173+
///
174+
/// You can pass in a callback that returns an `async` block. `async` blocks
175+
/// have an important advantage over `async fn`: You can use `async move { ... }`
176+
/// to capture data into the async block. This allows you to embed some state
177+
/// data into your callback.
178+
///
179+
/// You can do this with either a regular `fn` or with a closure.
180+
///
181+
/// ### `fn`
182+
///
183+
/// ```
184+
/// # use rclrs::*;
185+
/// # use std::future::Future;
186+
/// # let node = Context::default()
187+
/// # .create_basic_executor()
188+
/// # .create_node("test_node")?;
189+
/// use test_msgs::srv::{Empty, Empty_Request, Empty_Response};
190+
///
191+
/// fn print_greeting(_response: Empty_Response) -> impl Future<Output=()> {
192+
/// let greeting = "Hello!";
193+
/// async move {
194+
/// print!("Hello!");
195+
/// }
196+
/// }
197+
///
198+
/// let client = node.create_client::<Empty>("my_service")?;
199+
/// let request = Empty_Request::default();
200+
/// let promise = client.call_then_async(
201+
/// &request,
202+
/// print_greeting)?;
203+
/// # Ok::<(), RclrsError>(())
204+
/// ```
205+
///
206+
/// ### Closure
207+
///
208+
/// A closure will allow you to capture data into the callback from the
209+
/// surrounding context. While the syntax for this is more complicated, it
210+
/// is also the most powerful option.
211+
///
212+
/// ```
213+
/// # use rclrs::*;
214+
/// # let node = Context::default()
215+
/// # .create_basic_executor()
216+
/// # .create_node("test_node")?;
217+
/// use test_msgs::srv::{Empty, Empty_Request, Empty_Response};
218+
///
219+
/// let greeting = "Hello!";
220+
/// let client = node.create_client::<Empty>("my_service")?;
221+
/// let request = Empty_Request::default();
222+
/// let promise = client.call_then_async(
223+
/// &request,
224+
/// move |response: Empty_Response| {
225+
/// async move {
226+
/// print!("{greeting}");
227+
/// }
228+
/// })?;
229+
/// # Ok::<(), RclrsError>(())
230+
/// ```
132231
pub fn call_then_async<'a, Req, Args>(
133232
&self,
134233
request: Req,
@@ -144,7 +243,10 @@ where
144243
callback.run_client_async_callback(response, info).await;
145244
}
146245
Err(_) => {
147-
// TODO(@mxgrey): Log this error when logging becomes available
246+
log_fatal!(
247+
"rclrs.client.call_then_async",
248+
"Request promise has been dropped by the executor",
249+
);
148250
}
149251
}
150252
});
@@ -178,11 +280,19 @@ where
178280
pub fn notify_on_service_ready(self: &Arc<Self>) -> Promise<()> {
179281
let client = Arc::clone(self);
180282
self.handle.node.notify_on_graph_change(
181-
// TODO(@mxgrey): Log any errors here once logging is available
182283
move || client.service_is_ready().is_ok_and(|r| r),
183284
)
184285
}
185286

287+
/// Get the name of the service that this client intends to call.
288+
pub fn service_name(&self) -> String {
289+
unsafe {
290+
let char_ptr = rcl_client_get_service_name(&*self.handle.lock() as *const _);
291+
debug_assert!(!char_ptr.is_null());
292+
CStr::from_ptr(char_ptr).to_string_lossy().into_owned()
293+
}
294+
}
295+
186296
/// Creates a new client.
187297
pub(crate) fn create<'a>(
188298
options: impl Into<ClientOptions<'a>>,
@@ -378,8 +488,10 @@ where
378488
// This is okay, it means a spurious wakeup happened
379489
}
380490
err => {
381-
// TODO(@mxgrey): Log the error here once logging is available
382-
eprintln!("Error while taking a response for a client: {err}");
491+
log_fatal!(
492+
"rclrs.client.execute",
493+
"Error while taking a response for a client: {err}",
494+
);
383495
}
384496
}
385497
}

rclrs/src/error.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ pub enum RclrsError {
4646
expected: std::any::TypeId,
4747
/// The payload type given by the worker
4848
received: std::any::TypeId,
49-
}
49+
},
50+
/// A mutex used internally has been [poisoned][std::sync::PoisonError].
51+
PoisonedMutex,
5052
}
5153

5254
impl RclrsError {
@@ -108,6 +110,12 @@ impl Display for RclrsError {
108110
"Received invalid payload: expected {expected:?}, received {received:?}",
109111
)
110112
}
113+
RclrsError::PoisonedMutex => {
114+
write!(
115+
f,
116+
"A mutex used internally has been poisoned"
117+
)
118+
}
111119
}
112120
}
113121
}
@@ -145,6 +153,7 @@ impl Error for RclrsError {
145153
RclrsError::NegativeDuration(_) => None,
146154
RclrsError::UnownedGuardCondition => None,
147155
RclrsError::InvalidPayload { .. } => None,
156+
RclrsError::PoisonedMutex => None,
148157
}
149158
}
150159
}

rclrs/src/executor/basic_executor.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use std::{
1717

1818
use crate::{
1919
WeakActivityListener, ExecutorChannel, ExecutorRuntime, SpinConditions, WorkerChannel,
20-
RclrsError, WaitSetRunner, WaitSetRunConditions, Waitable, log_warn, log_fatal, ToLogParams,
20+
RclrsError, WaitSetRunner, WaitSetRunConditions, Waitable, log_warn, log_fatal,
2121
GuardCondition, ExecutorWorkerOptions, PayloadTask,
2222
};
2323

0 commit comments

Comments
 (0)