|
| 1 | +use crate::{ |
| 2 | + error::ToResult, rcl_bindings::*, wait::WaitableNumEntities, Node, NodeHandle, QoSProfile, |
| 3 | + RclrsError, ENTITY_LIFECYCLE_MUTEX, |
| 4 | +}; |
| 5 | +use std::{ |
| 6 | + borrow::Borrow, |
| 7 | + ffi::CString, |
| 8 | + marker::PhantomData, |
| 9 | + sync::{atomic::AtomicBool, Arc, Mutex, MutexGuard}, |
| 10 | +}; |
| 11 | + |
| 12 | +// SAFETY: The functions accessing this type, including drop(), shouldn't care about the thread |
| 13 | +// they are running in. Therefore, this type can be safely sent to another thread. |
| 14 | +unsafe impl Send for rcl_action_client_t {} |
| 15 | + |
| 16 | +/// Manage the lifecycle of an `rcl_action_client_t`, including managing its dependencies |
| 17 | +/// on `rcl_node_t` and `rcl_context_t` by ensuring that these dependencies are |
| 18 | +/// [dropped after][1] the `rcl_action_client_t`. |
| 19 | +/// |
| 20 | +/// [1]: <https://doc.rust-lang.org/reference/destructors.html> |
| 21 | +pub struct ActionClientHandle { |
| 22 | + rcl_action_client: Mutex<rcl_action_client_t>, |
| 23 | + node_handle: Arc<NodeHandle>, |
| 24 | + pub(crate) in_use_by_wait_set: Arc<AtomicBool>, |
| 25 | +} |
| 26 | + |
| 27 | +impl ActionClientHandle { |
| 28 | + pub(crate) fn lock(&self) -> MutexGuard<rcl_action_client_t> { |
| 29 | + self.rcl_action_client.lock().unwrap() |
| 30 | + } |
| 31 | +} |
| 32 | + |
| 33 | +impl Drop for ActionClientHandle { |
| 34 | + fn drop(&mut self) { |
| 35 | + let rcl_action_client = self.rcl_action_client.get_mut().unwrap(); |
| 36 | + let mut rcl_node = self.node_handle.rcl_node.lock().unwrap(); |
| 37 | + let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap(); |
| 38 | + // SAFETY: The entity lifecycle mutex is locked to protect against the risk of |
| 39 | + // global variables in the rmw implementation being unsafely modified during cleanup. |
| 40 | + unsafe { |
| 41 | + rcl_action_client_fini(rcl_action_client, &mut *rcl_node); |
| 42 | + } |
| 43 | + } |
| 44 | +} |
| 45 | + |
| 46 | +/// Trait to be implemented by concrete ActionClient structs. |
| 47 | +/// |
| 48 | +/// See [`ActionClient<T>`] for an example |
| 49 | +pub trait ActionClientBase: Send + Sync { |
| 50 | + /// Internal function to get a reference to the `rcl` handle. |
| 51 | + fn handle(&self) -> &ActionClientHandle; |
| 52 | + /// Returns the number of underlying entities for the action client. |
| 53 | + fn num_entities(&self) -> &WaitableNumEntities; |
| 54 | + /// Tries to run the callback for the given readiness mode. |
| 55 | + fn execute(&self, mode: ReadyMode) -> Result<(), RclrsError>; |
| 56 | +} |
| 57 | + |
| 58 | +pub(crate) enum ReadyMode { |
| 59 | + Feedback, |
| 60 | + Status, |
| 61 | + GoalResponse, |
| 62 | + CancelResponse, |
| 63 | + ResultResponse, |
| 64 | +} |
| 65 | + |
| 66 | +/// |
| 67 | +/// Main class responsible for sending goals to a ROS action server. |
| 68 | +/// |
| 69 | +/// Create a client using [`Node::create_action_client`][1]. |
| 70 | +/// |
| 71 | +/// Receiving feedback and results requires the node's executor to [spin][2]. |
| 72 | +/// |
| 73 | +/// [1]: crate::NodeState::create_action_client |
| 74 | +/// [2]: crate::spin |
| 75 | +pub type ActionClient<ActionT> = Arc<ActionClientState<ActionT>>; |
| 76 | + |
| 77 | +/// The inner state of an [`ActionClient`]. |
| 78 | +/// |
| 79 | +/// This is public so that you can choose to create a [`Weak`][1] reference to it |
| 80 | +/// if you want to be able to refer to an [`ActionClient`] in a non-owning way. It is |
| 81 | +/// generally recommended to manage the `ActionClientState` inside of an [`Arc`], |
| 82 | +/// and [`ActionClient`] is provided as a convenience alias for that. |
| 83 | +/// |
| 84 | +/// The public API of the [`ActionClient`] type is implemented via `ActionClientState`. |
| 85 | +/// |
| 86 | +/// [1]: std::sync::Weak |
| 87 | +pub struct ActionClientState<ActionT> |
| 88 | +where |
| 89 | + ActionT: rosidl_runtime_rs::Action, |
| 90 | +{ |
| 91 | + _marker: PhantomData<fn() -> ActionT>, |
| 92 | + pub(crate) handle: Arc<ActionClientHandle>, |
| 93 | + num_entities: WaitableNumEntities, |
| 94 | + /// Ensure the parent node remains alive as long as the subscription is held. |
| 95 | + /// This implementation will change in the future. |
| 96 | + #[allow(unused)] |
| 97 | + node: Node, |
| 98 | +} |
| 99 | + |
| 100 | +impl<T> ActionClientState<T> |
| 101 | +where |
| 102 | + T: rosidl_runtime_rs::Action, |
| 103 | +{ |
| 104 | + /// Creates a new action client. |
| 105 | + pub(crate) fn new<'a>( |
| 106 | + node: &Node, |
| 107 | + options: impl Into<ActionClientOptions<'a>>, |
| 108 | + ) -> Result<Self, RclrsError> |
| 109 | + where |
| 110 | + T: rosidl_runtime_rs::Action, |
| 111 | + { |
| 112 | + let options = options.into(); |
| 113 | + // SAFETY: Getting a zero-initialized value is always safe. |
| 114 | + let mut rcl_action_client = unsafe { rcl_action_get_zero_initialized_client() }; |
| 115 | + let type_support = T::get_type_support() as *const rosidl_action_type_support_t; |
| 116 | + let action_name_c_string = |
| 117 | + CString::new(options.action_name).map_err(|err| RclrsError::StringContainsNul { |
| 118 | + err, |
| 119 | + s: options.action_name.into(), |
| 120 | + })?; |
| 121 | + |
| 122 | + // SAFETY: No preconditions for this function. |
| 123 | + let action_client_options = unsafe { rcl_action_client_get_default_options() }; |
| 124 | + |
| 125 | + { |
| 126 | + let mut rcl_node = node.handle.rcl_node.lock().unwrap(); |
| 127 | + let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap(); |
| 128 | + |
| 129 | + // SAFETY: |
| 130 | + // * The rcl_action_client was zero-initialized as expected by this function. |
| 131 | + // * The rcl_node is kept alive by the NodeHandle because it is a dependency of the action client. |
| 132 | + // * The action name and the options are copied by this function, so they can be dropped |
| 133 | + // afterwards. |
| 134 | + // * The entity lifecycle mutex is locked to protect against the risk of global |
| 135 | + // variables in the rmw implementation being unsafely modified during initialization. |
| 136 | + unsafe { |
| 137 | + rcl_action_client_init( |
| 138 | + &mut rcl_action_client, |
| 139 | + &mut *rcl_node, |
| 140 | + type_support, |
| 141 | + action_name_c_string.as_ptr(), |
| 142 | + &action_client_options, |
| 143 | + ) |
| 144 | + .ok()?; |
| 145 | + } |
| 146 | + } |
| 147 | + |
| 148 | + let handle = Arc::new(ActionClientHandle { |
| 149 | + rcl_action_client: Mutex::new(rcl_action_client), |
| 150 | + node_handle: Arc::clone(&node.handle), |
| 151 | + in_use_by_wait_set: Arc::new(AtomicBool::new(false)), |
| 152 | + }); |
| 153 | + |
| 154 | + let mut num_entities = WaitableNumEntities::default(); |
| 155 | + unsafe { |
| 156 | + rcl_action_client_wait_set_get_num_entities( |
| 157 | + &*handle.lock(), |
| 158 | + &mut num_entities.num_subscriptions, |
| 159 | + &mut num_entities.num_guard_conditions, |
| 160 | + &mut num_entities.num_timers, |
| 161 | + &mut num_entities.num_clients, |
| 162 | + &mut num_entities.num_services, |
| 163 | + ) |
| 164 | + .ok()?; |
| 165 | + } |
| 166 | + |
| 167 | + Ok(Self { |
| 168 | + _marker: Default::default(), |
| 169 | + handle, |
| 170 | + num_entities, |
| 171 | + node: Arc::clone(node), |
| 172 | + }) |
| 173 | + } |
| 174 | + |
| 175 | + fn execute_feedback(&self) -> Result<(), RclrsError> { |
| 176 | + todo!() |
| 177 | + } |
| 178 | + |
| 179 | + fn execute_status(&self) -> Result<(), RclrsError> { |
| 180 | + todo!() |
| 181 | + } |
| 182 | + |
| 183 | + fn execute_goal_response(&self) -> Result<(), RclrsError> { |
| 184 | + todo!() |
| 185 | + } |
| 186 | + |
| 187 | + fn execute_cancel_response(&self) -> Result<(), RclrsError> { |
| 188 | + todo!() |
| 189 | + } |
| 190 | + |
| 191 | + fn execute_result_response(&self) -> Result<(), RclrsError> { |
| 192 | + todo!() |
| 193 | + } |
| 194 | +} |
| 195 | + |
| 196 | +impl<T> ActionClientBase for ActionClientState<T> |
| 197 | +where |
| 198 | + T: rosidl_runtime_rs::Action, |
| 199 | +{ |
| 200 | + fn handle(&self) -> &ActionClientHandle { |
| 201 | + &self.handle |
| 202 | + } |
| 203 | + |
| 204 | + fn num_entities(&self) -> &WaitableNumEntities { |
| 205 | + &self.num_entities |
| 206 | + } |
| 207 | + |
| 208 | + fn execute(&self, mode: ReadyMode) -> Result<(), RclrsError> { |
| 209 | + match mode { |
| 210 | + ReadyMode::Feedback => self.execute_feedback(), |
| 211 | + ReadyMode::Status => self.execute_status(), |
| 212 | + ReadyMode::GoalResponse => self.execute_goal_response(), |
| 213 | + ReadyMode::CancelResponse => self.execute_cancel_response(), |
| 214 | + ReadyMode::ResultResponse => self.execute_result_response(), |
| 215 | + } |
| 216 | + } |
| 217 | +} |
| 218 | + |
| 219 | +/// `ActionClientOptions` are used by [`Node::create_action_client`][1] to initialize an |
| 220 | +/// [`ActionClient`]. |
| 221 | +/// |
| 222 | +/// [1]: crate::Node::create_action_client |
| 223 | +#[derive(Debug, Clone)] |
| 224 | +#[non_exhaustive] |
| 225 | +pub struct ActionClientOptions<'a> { |
| 226 | + /// The name of the action that this client will send requests to |
| 227 | + pub action_name: &'a str, |
| 228 | + /// The quality of service profile for the goal service |
| 229 | + pub goal_service_qos: QoSProfile, |
| 230 | + /// The quality of service profile for the result service |
| 231 | + pub result_service_qos: QoSProfile, |
| 232 | + /// The quality of service profile for the cancel service |
| 233 | + pub cancel_service_qos: QoSProfile, |
| 234 | + /// The quality of service profile for the feedback topic |
| 235 | + pub feedback_topic_qos: QoSProfile, |
| 236 | + /// The quality of service profile for the status topic |
| 237 | + pub status_topic_qos: QoSProfile, |
| 238 | +} |
| 239 | + |
| 240 | +impl<'a> ActionClientOptions<'a> { |
| 241 | + /// Initialize a new [`ActionClientOptions`] with default settings. |
| 242 | + pub fn new(action_name: &'a str) -> Self { |
| 243 | + Self { |
| 244 | + action_name, |
| 245 | + goal_service_qos: QoSProfile::services_default(), |
| 246 | + result_service_qos: QoSProfile::services_default(), |
| 247 | + cancel_service_qos: QoSProfile::services_default(), |
| 248 | + feedback_topic_qos: QoSProfile::topics_default(), |
| 249 | + status_topic_qos: QoSProfile::action_status_default(), |
| 250 | + } |
| 251 | + } |
| 252 | +} |
| 253 | + |
| 254 | +impl<'a, T: Borrow<str> + ?Sized + 'a> From<&'a T> for ActionClientOptions<'a> { |
| 255 | + fn from(value: &'a T) -> Self { |
| 256 | + Self::new(value.borrow()) |
| 257 | + } |
| 258 | +} |
0 commit comments