Skip to content

Commit 3b399da

Browse files
committed
cherry picking changes from PR #46: instance registry of Action Clients
1 parent 4d355bb commit 3b399da

File tree

3 files changed

+103
-44
lines changed

3 files changed

+103
-44
lines changed

behaviortree_ros2/include/behaviortree_ros2/bt_action_node.hpp

Lines changed: 99 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -169,17 +169,46 @@ class RosActionNode : public BT::ActionNodeBase
169169
NodeStatus tick() override final;
170170

171171
protected:
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

178211
private:
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+
198237
template <class T>
199238
inline 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

283331
template <class T>
284332
inline 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

behaviortree_ros2/include/behaviortree_ros2/bt_service_node.hpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -263,8 +263,8 @@ inline NodeStatus RosServiceNode<T>::tick()
263263
auto CheckStatus = [](NodeStatus status) {
264264
if(!isStatusCompleted(status))
265265
{
266-
throw std::logic_error("RosServiceNode: the callback must return either SUCCESS or "
267-
"FAILURE");
266+
throw LogicError("RosServiceNode: the callback must return either SUCCESS nor "
267+
"FAILURE");
268268
}
269269
return status;
270270
};
@@ -329,7 +329,7 @@ inline NodeStatus RosServiceNode<T>::tick()
329329

330330
if(!response_)
331331
{
332-
throw std::runtime_error("Request was rejected by the service");
332+
throw RuntimeError("Request was rejected by the service");
333333
}
334334
}
335335
}

behaviortree_ros2/include/behaviortree_ros2/bt_topic_sub_node.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class RosTopicSubNode : public BT::ConditionNode
7777
}
7878

7979
std::shared_ptr<rclcpp::Node> node_;
80-
std::shared_ptr<SubscriberInstance> sub_instance_ = nullptr;
80+
std::shared_ptr<SubscriberInstance> sub_instance_;
8181
std::shared_ptr<TopicT> last_msg_;
8282
std::string topic_name_;
8383
boost::signals2::connection signal_connection_;

0 commit comments

Comments
 (0)