1
1
use std:: {
2
2
any:: Any ,
3
3
collections:: HashMap ,
4
- ffi:: CString ,
4
+ ffi:: { CStr , CString } ,
5
5
sync:: { Arc , Mutex , MutexGuard } ,
6
6
} ;
7
7
@@ -10,7 +10,7 @@ use rosidl_runtime_rs::Message;
10
10
use crate :: {
11
11
error:: ToResult , rcl_bindings:: * , IntoPrimitiveOptions , MessageCow , Node , Promise , QoSProfile ,
12
12
RclPrimitive , RclPrimitiveHandle , RclPrimitiveKind , RclReturnCode , RclrsError , ServiceInfo ,
13
- Waitable , WaitableLifecycle , ENTITY_LIFECYCLE_MUTEX ,
13
+ Waitable , WaitableLifecycle , ENTITY_LIFECYCLE_MUTEX , log_fatal ,
14
14
} ;
15
15
16
16
mod client_async_callback;
90
90
}
91
91
. ok ( ) ?;
92
92
93
- // TODO(@mxgrey): Log errors here when logging becomes available.
94
93
self . board
95
94
. lock ( )
96
- . unwrap ( )
95
+ . map_err ( |_| RclrsError :: PoisonedMutex ) ?
97
96
. new_request ( sequence_number, sender) ;
98
97
99
98
Ok ( promise)
@@ -105,8 +104,15 @@ where
105
104
/// compiler warns you that you need to. You can use the [`Promise`] to know
106
105
/// when the response is finished being processed, but otherwise you can
107
106
/// 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
110
116
pub fn call_then < ' a , Req , Args > (
111
117
& self ,
112
118
request : Req ,
@@ -127,8 +133,101 @@ where
127
133
/// compiler warns you that you need to. You can use the [`Promise`] to know
128
134
/// when the response is finished being processed, but otherwise you can
129
135
/// 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
+ /// ```
132
231
pub fn call_then_async < ' a , Req , Args > (
133
232
& self ,
134
233
request : Req ,
@@ -144,7 +243,10 @@ where
144
243
callback. run_client_async_callback ( response, info) . await ;
145
244
}
146
245
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
+ ) ;
148
250
}
149
251
}
150
252
} ) ;
@@ -178,11 +280,19 @@ where
178
280
pub fn notify_on_service_ready ( self : & Arc < Self > ) -> Promise < ( ) > {
179
281
let client = Arc :: clone ( self ) ;
180
282
self . handle . node . notify_on_graph_change (
181
- // TODO(@mxgrey): Log any errors here once logging is available
182
283
move || client. service_is_ready ( ) . is_ok_and ( |r| r) ,
183
284
)
184
285
}
185
286
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
+
186
296
/// Creates a new client.
187
297
pub ( crate ) fn create < ' a > (
188
298
options : impl Into < ClientOptions < ' a > > ,
@@ -378,8 +488,10 @@ where
378
488
// This is okay, it means a spurious wakeup happened
379
489
}
380
490
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
+ ) ;
383
495
}
384
496
}
385
497
}
0 commit comments