@@ -169,17 +169,46 @@ class RosActionNode : public BT::ActionNodeBase
169169 NodeStatus tick () override final ;
170170
171171protected:
172+ struct ActionClientInstance
173+ {
174+ ActionClientInstance (std::shared_ptr<rclcpp::Node> node,
175+ const std::string& action_name);
176+
177+ ActionClientPtr action_client;
178+ rclcpp::CallbackGroup::SharedPtr callback_group;
179+ rclcpp::executors::SingleThreadedExecutor callback_executor;
180+ typename ActionClient::SendGoalOptions goal_options;
181+ };
182+
183+ static std::mutex& getMutex ()
184+ {
185+ static std::mutex action_client_mutex;
186+ return action_client_mutex;
187+ }
188+
189+ rclcpp::Logger logger ()
190+ {
191+ return node_->get_logger ();
192+ }
193+
194+ using ClientsRegistry =
195+ std::unordered_map<std::string, std::weak_ptr<ActionClientInstance>>;
196+ // contains the fully-qualified name of the node and the name of the client
197+ static ClientsRegistry& getRegistry ()
198+ {
199+ static ClientsRegistry action_clients_registry;
200+ return action_clients_registry;
201+ }
202+
172203 std::shared_ptr<rclcpp::Node> node_;
173- std::string prev_action_name_;
204+ std::shared_ptr<ActionClientInstance> client_instance_;
205+ std::string action_name_;
174206 bool action_name_may_change_ = false ;
175207 const std::chrono::milliseconds server_timeout_;
176208 const std::chrono::milliseconds wait_for_server_timeout_;
209+ std::string action_client_key_;
177210
178211private:
179- ActionClientPtr action_client_;
180- rclcpp::CallbackGroup::SharedPtr callback_group_;
181- rclcpp::executors::SingleThreadedExecutor callback_group_executor_;
182-
183212 std::shared_future<typename GoalHandle::SharedPtr> future_goal_handle_;
184213 typename GoalHandle::SharedPtr goal_handle_;
185214
@@ -195,6 +224,16 @@ class RosActionNode : public BT::ActionNodeBase
195224// ---------------------- DEFINITIONS -----------------------------
196225// ----------------------------------------------------------------
197226
227+ template <class T >
228+ RosActionNode<T>::ActionClientInstance::ActionClientInstance(
229+ std::shared_ptr<rclcpp::Node> node, const std::string& action_name)
230+ {
231+ callback_group =
232+ node->create_callback_group (rclcpp::CallbackGroupType::MutuallyExclusive);
233+ callback_executor.add_callback_group (callback_group, node->get_node_base_interface ());
234+ action_client = rclcpp_action::create_client<T>(node, action_name, callback_group);
235+ }
236+
198237template <class T >
199238inline RosActionNode<T>::RosActionNode(const std::string& instance_name,
200239 const NodeConfig& conf,
@@ -262,46 +301,62 @@ inline bool RosActionNode<T>::createClient(const std::string& action_name)
262301 throw RuntimeError (" action_name is empty" );
263302 }
264303
265- callback_group_ =
266- node_->create_callback_group (rclcpp::CallbackGroupType::MutuallyExclusive);
267- callback_group_executor_.add_callback_group (callback_group_,
268- node_->get_node_base_interface ());
269- action_client_ = rclcpp_action::create_client<T>(node_, action_name, callback_group_);
304+ std::unique_lock lk (getMutex ());
305+ action_client_key_ = std::string (node_->get_fully_qualified_name ()) + " /" + action_name;
270306
271- prev_action_name_ = action_name;
307+ auto & registry = getRegistry ();
308+ auto it = registry.find (action_client_key_);
309+ if (it == registry.end ())
310+ {
311+ client_instance_ = std::make_shared<ActionClientInstance>(node_, action_name);
312+ registry.insert ({ action_client_key_, client_instance_ });
313+ }
314+ else
315+ {
316+ client_instance_ = it->second .lock ();
317+ }
272318
273- bool found = action_client_->wait_for_action_server (wait_for_server_timeout_);
319+ action_name_ = action_name;
320+
321+ bool found =
322+ client_instance_->action_client ->wait_for_action_server (wait_for_server_timeout_);
274323 if (!found)
275324 {
276- RCLCPP_ERROR (node_->get_logger (),
277- " %s: Action server with name '%s' is not reachable." , name ().c_str (),
278- prev_action_name_.c_str ());
325+ RCLCPP_ERROR (logger (), " %s: Action server with name '%s' is not reachable." ,
326+ name ().c_str (), action_name_.c_str ());
279327 }
280328 return found;
281329}
282330
283331template <class T >
284332inline NodeStatus RosActionNode<T>::tick()
285333{
334+ if (!rclcpp::ok ())
335+ {
336+ halt ();
337+ return NodeStatus::FAILURE;
338+ }
339+
286340 // First, check if the action_client_ is valid and that the name of the
287341 // action_name in the port didn't change.
288342 // otherwise, create a new client
289- if (!action_client_ || (status () == NodeStatus::IDLE && action_name_may_change_))
343+ if (!client_instance_ || (status () == NodeStatus::IDLE && action_name_may_change_))
290344 {
291345 std::string action_name;
292346 getInput (" action_name" , action_name);
293- if (prev_action_name_ != action_name)
347+ if (action_name_ != action_name)
294348 {
295349 createClient (action_name);
296350 }
297351 }
352+ auto & action_client = client_instance_->action_client ;
298353
299354 // ------------------------------------------
300355 auto CheckStatus = [](NodeStatus status) {
301356 if (!isStatusCompleted (status))
302357 {
303- throw std::logic_error (" RosActionNode: the callback must return either SUCCESS of "
304- " FAILURE" );
358+ throw LogicError (" RosActionNode: the callback must return either SUCCESS nor "
359+ " FAILURE" );
305360 }
306361 return status;
307362 };
@@ -340,7 +395,7 @@ inline NodeStatus RosActionNode<T>::tick()
340395 goal_options.result_callback = [this ](const WrappedResult& result) {
341396 if (goal_handle_->get_goal_id () == result.goal_id )
342397 {
343- RCLCPP_DEBUG (node_-> get_logger (), " result_callback" );
398+ RCLCPP_DEBUG (logger (), " result_callback" );
344399 result_ = result;
345400 emitWakeUpSignal ();
346401 }
@@ -351,29 +406,30 @@ inline NodeStatus RosActionNode<T>::tick()
351406 auto goal_handle_ = future_handle.get ();
352407 if (!goal_handle_)
353408 {
354- RCLCPP_ERROR (node_-> get_logger (), " Goal was rejected by server" );
409+ RCLCPP_ERROR (logger (), " Goal was rejected by server" );
355410 }
356411 else
357412 {
358- RCLCPP_DEBUG (node_->get_logger (), " Goal accepted by server, waiting for "
359- " result" );
413+ RCLCPP_DEBUG (logger (), " Goal accepted by server, waiting for result" );
360414 }
361415 };
362416 // --------------------
363-
364417 // Check if server is ready
365- if (!action_client_->action_server_is_ready ())
418+ if (!action_client->action_server_is_ready ())
419+ {
366420 return onFailure (SERVER_UNREACHABLE);
421+ }
367422
368- future_goal_handle_ = action_client_ ->async_send_goal (goal, goal_options);
423+ future_goal_handle_ = action_client ->async_send_goal (goal, goal_options);
369424 time_goal_sent_ = node_->now ();
370425
371426 return NodeStatus::RUNNING;
372427 }
373428
374429 if (status () == NodeStatus::RUNNING)
375430 {
376- callback_group_executor_.spin_some ();
431+ std::unique_lock<std::mutex> lock (getMutex ());
432+ client_instance_->callback_executor .spin_some ();
377433
378434 // FIRST case: check if the goal request has a timeout
379435 if (!goal_received_)
@@ -382,8 +438,8 @@ inline NodeStatus RosActionNode<T>::tick()
382438 auto timeout =
383439 rclcpp::Duration::from_seconds (double (server_timeout_.count ()) / 1000 );
384440
385- auto ret = callback_group_executor_ .spin_until_future_complete (future_goal_handle_,
386- nodelay);
441+ auto ret = client_instance_-> callback_executor .spin_until_future_complete (
442+ future_goal_handle_, nodelay);
387443 if (ret != rclcpp::FutureReturnCode::SUCCESS)
388444 {
389445 if ((node_->now () - time_goal_sent_) > timeout)
@@ -449,25 +505,28 @@ inline void RosActionNode<T>::cancelGoal()
449505{
450506 if (!goal_handle_)
451507 {
452- RCLCPP_WARN (node_-> get_logger (), " cancelGoal called on an empty goal_handle" );
508+ RCLCPP_WARN (logger (), " cancelGoal called on an empty goal_handle" );
453509 return ;
454510 }
455511
456- auto future_result = action_client_->async_get_result (goal_handle_);
457- auto future_cancel = action_client_->async_cancel_goal (goal_handle_);
512+ auto & executor = client_instance_->callback_executor ;
513+ auto & action_client = client_instance_->action_client ;
514+
515+ auto future_result = action_client->async_get_result (goal_handle_);
516+ auto future_cancel = action_client->async_cancel_goal (goal_handle_);
517+
518+ constexpr auto SUCCESS = rclcpp::FutureReturnCode::SUCCESS;
458519
459- if (callback_group_executor_.spin_until_future_complete (
460- future_cancel, server_timeout_) != rclcpp::FutureReturnCode::SUCCESS)
520+ if (executor.spin_until_future_complete (future_cancel, server_timeout_) != SUCCESS)
461521 {
462- RCLCPP_ERROR (node_-> get_logger (), " Failed to cancel action server for [%s]" ,
463- prev_action_name_ .c_str ());
522+ RCLCPP_ERROR (logger (), " Failed to cancel action server for [%s]" ,
523+ action_name_ .c_str ());
464524 }
465525
466- if (callback_group_executor_.spin_until_future_complete (
467- future_result, server_timeout_) != rclcpp::FutureReturnCode::SUCCESS)
526+ if (executor.spin_until_future_complete (future_result, server_timeout_) != SUCCESS)
468527 {
469- RCLCPP_ERROR (node_-> get_logger (), " Failed to get result call failed :( for [%s]" ,
470- prev_action_name_ .c_str ());
528+ RCLCPP_ERROR (logger (), " Failed to get result call failed :( for [%s]" ,
529+ action_name_ .c_str ());
471530 }
472531}
473532
0 commit comments