Skip to content

Commit 908859f

Browse files
committed
Split execute_goal_request() out into three functions
The logic in `execute_goal_request()` was starting to get unwieldy, so I split it into three functions. The idea is that the first, `take_goal_request()`, should handle everything up until we call the user's callback. Meanwhile, `send_goal_response()` handles everything after the user callback, sending the response and, if accepted, setting everything up for the action server. `execute_goal_request()` is the overall function coordinating all of this. It passes request data from the `take*` function to the user callback and passes the returned response into the `send*` function. In addition to splitting the logic into digestible pieces, this means we could also easily make the goal-request callback asynchronous by delaying the `send*` function until the user has given their asynchronous response.
1 parent 3083cc4 commit 908859f

File tree

2 files changed

+104
-23
lines changed

2 files changed

+104
-23
lines changed

rclrs/src/action.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ impl fmt::Display for GoalUuid {
3737
}
3838

3939
/// The response returned by an [`ActionServer`]'s goal callback when a goal request is received.
40+
#[derive(PartialEq, Eq)]
4041
pub enum GoalResponse {
4142
/// The goal is rejected and will not be executed.
4243
Reject = 1,

rclrs/src/action/server.rs

Lines changed: 103 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::{
55
wait::WaitableNumEntities,
66
Clock, Node, RclrsError, ENTITY_LIFECYCLE_MUTEX,
77
};
8+
use rosidl_runtime_rs::{Action, Message, Service};
89
use std::{
910
ffi::CString,
1011
sync::{atomic::AtomicBool, Arc, Mutex, MutexGuard},
@@ -159,36 +160,115 @@ where
159160
})
160161
}
161162

162-
fn execute_goal_request(&self) -> Result<(), RclrsError> {
163-
// Take pending goal request
164-
let (request_id, request) = {
165-
let mut request_id = rmw_request_id_t {
166-
writer_guid: [0; 16],
167-
sequence_number: 0,
163+
fn take_goal_request(&self) -> Result<(<<T::SendGoalService as Service>::Request as Message>::RmwMsg, rmw_request_id_t), RclrsError> {
164+
let mut request_id = rmw_request_id_t {
165+
writer_guid: [0; 16],
166+
sequence_number: 0,
167+
};
168+
type RmwRequest<T> = <<<T as rosidl_runtime_rs::ActionImpl>::SendGoalService as Service>::Request as Message>::RmwMsg;
169+
let mut request_rmw = RmwRequest::<T>::default();
170+
let handle = &*self.handle.lock();
171+
unsafe {
172+
// SAFETY: The three pointers are valid/initialized
173+
rcl_action_take_goal_request(
174+
handle,
175+
&mut request_id,
176+
&mut request_rmw as *mut RmwRequest<T> as *mut _,
177+
)
178+
}.ok()?;
179+
180+
Ok((request_rmw, request_id))
181+
}
182+
183+
fn send_goal_response(&self, response: GoalResponse, request: &<<T::SendGoalService as Service>::Request as Message>::RmwMsg, mut request_id: rmw_request_id_t) -> Result<(), RclrsError> {
184+
let accepted = response != GoalResponse::Reject;
185+
186+
// SAFETY: No preconditions
187+
let mut goal_info = unsafe { rcl_action_get_zero_initialized_goal_info() };
188+
// Populate the goal UUID; the other fields will be populated by rcl_action later on.
189+
// TODO(nwn): Check this claim.
190+
rosidl_runtime_rs::ExtractUuid::extract_uuid(request, &mut goal_info.goal_id.uuid);
191+
192+
let goal_handle = if accepted {
193+
let server_handle = &mut *self.handle.lock();
194+
let goal_handle_ptr = unsafe {
195+
// SAFETY: The action server handle is locked and so synchronized with other
196+
// functions. The request_id and response message are uniquely owned, and so will
197+
// not mutate during this function call. The returned goal handle pointer should be
198+
// valid unless it is null.
199+
rcl_action_accept_new_goal(
200+
server_handle,
201+
&goal_info,
202+
)
168203
};
169-
type RmwMsg<T> =
170-
<<T as rosidl_runtime_rs::Action>::Goal as rosidl_runtime_rs::Message>::RmwMsg;
171-
let mut request_rmw = RmwMsg::<T>::default();
204+
if goal_handle_ptr.is_null() {
205+
// Other than rcl_get_error_string(), there's no indication what happened.
206+
panic!("Failed to accept goal");
207+
} else {
208+
Some(ServerGoalHandle::<T>::new(goal_handle_ptr, todo!(""), GoalUuid(goal_info.goal_id.uuid)))
209+
}
210+
} else {
211+
None
212+
};
213+
214+
{
215+
type RmwResponse<T> = <<<T as rosidl_runtime_rs::ActionImpl>::SendGoalService as Service>::Response as Message>::RmwMsg;
216+
let mut response_rmw = RmwResponse::<T>::default();
217+
// TODO(nwn): Set the `accepted` field through a trait, similarly to how we extracted the UUID.
218+
// response_rmw.accepted = accepted;
172219
let handle = &*self.handle.lock();
173-
let take_result = unsafe {
174-
// SAFETY: The three pointers are valid/initialized
175-
rcl_action_take_goal_request(
220+
unsafe {
221+
// SAFETY: The action server handle is locked and so synchronized with other
222+
// functions. The request_id and response message are uniquely owned, and so will
223+
// not mutate during this function call.
224+
// Also, `rcl_action_accept_new_goal()` has been called beforehand, as specified in
225+
// the `rcl_action` docs.
226+
rcl_action_send_goal_response(
176227
handle,
177228
&mut request_id,
178-
&mut request_rmw as *mut RmwMsg<T> as *mut _,
229+
&mut response_rmw as *mut RmwResponse<T> as *mut _,
179230
)
180-
};
181-
match take_result.try_into().unwrap() {
182-
RclReturnCode::Ok => (),
231+
}.ok()?; // TODO(nwn): Suppress RclReturnCode::Timeout?
232+
}
233+
234+
if let Some(goal_handle) = goal_handle {
235+
// Goal was accepted
236+
237+
// TODO: Add a UUID->goal_handle entry to a server goal map.
238+
239+
// TODO: If accept_and_execute, update goal state
240+
241+
// TODO: Call publish_status()
242+
243+
// TODO: Call the goal_accepted callback
244+
}
245+
246+
Ok(())
247+
}
248+
249+
fn execute_goal_request(&self) -> Result<(), RclrsError> {
250+
let (request, request_id) = match self.take_goal_request() {
251+
Ok(res) => res,
252+
Err(RclrsError::RclError { code: RclReturnCode::ServiceTakeFailed, .. }) => {
183253
// Spurious wakeup – this may happen even when a waitset indicated that this
184-
// service was ready, so it shouldn't be an error.
185-
RclReturnCode::ServiceTakeFailed => return Ok(()),
186-
_ => return take_result.ok(),
187-
}
188-
let request = todo!("Convert request_rmw to expected type");
189-
(request_id, request)
254+
// action was ready, so it shouldn't be an error.
255+
return Ok(());
256+
},
257+
Err(err) => return Err(err),
190258
};
191-
todo!()
259+
260+
let response: GoalResponse =
261+
{
262+
let mut uuid = GoalUuid::default();
263+
rosidl_runtime_rs::ExtractUuid::extract_uuid(&request, &mut uuid.0);
264+
265+
todo!("Optionally convert request to an idiomatic type for the user's callback.");
266+
todo!("Call self.goal_callback(uuid, request)");
267+
};
268+
269+
self.send_goal_response(response, &request, request_id)?;
270+
271+
Ok(())
192272
}
193273

194274
fn execute_cancel_request(&self) -> Result<(), RclrsError> {

0 commit comments

Comments
 (0)