1295.71 BehaviorTree.CPP에서의 Parallel 구현
1. BehaviorTree.CPP 라이브러리 개요
BehaviorTree.CPP는 C++로 구현된 행동 트리 라이브러리로, ROS2 생태계에서 로봇 행동 제어의 사실상 표준(de facto standard)으로 사용되고 있다. 이 라이브러리는 Parallel, ReactiveSequence, ReactiveFallback을 포함한 다양한 제어 노드를 내장 제공하며, XML 기반의 트리 정의와 런타임 동적 로딩을 지원한다. 본 절에서는 BehaviorTree.CPP 버전 4.x에서의 Parallel 노드 구현을 분석한다.
2. ParallelAll 노드
BehaviorTree.CPP 4.x에서는 Parallel 노드가 ParallelAll이라는 이름으로 제공된다. 이 노드는 모든 자식에게 매 Tick에서 Tick을 전파하며, 설정된 성공 및 실패 임계값에 따라 결과를 결정한다.
// BehaviorTree.CPP 4.x의 ParallelAll 노드 사용
class ParallelAll : public ControlNode
{
public:
ParallelAll(const std::string& name, const NodeConfig& config);
static PortsList providedPorts()
{
return {InputPort<int>("max_failures", 0,
"허용되는 최대 실패 자식 수")};
}
private:
NodeStatus tick() override;
void halt() override;
};
max_failures 파라미터가 Parallel의 실패 정책을 결정한다. max_failures = 0이면 하나의 자식이라도 FAILURE를 반환하면 Parallel이 FAILURE를 반환한다(FAILURE_ONE 정책에 해당). max_failures = N이면 최대 N개의 자식 실패가 허용된다.
3. XML에서의 Parallel 노드 정의
BehaviorTree.CPP에서 Parallel 노드를 XML로 정의하는 방식은 다음과 같다.
<BehaviorTree ID="ParallelExample">
<ParallelAll max_failures="0">
<NavigateToGoal goal="{target_position}" />
<MonitorBattery threshold="15.0" />
<RecordSensorData output_file="{log_path}" />
</ParallelAll>
</BehaviorTree>
max_failures="0"은 하나의 자식이라도 실패하면 전체가 실패하는 정책이다. 모든 자식이 SUCCESS를 반환하면 ParallelAll이 SUCCESS를 반환한다.
4. tick() 메서드의 구현 구조
BehaviorTree.CPP의 ParallelAll::tick() 메서드의 동작을 개념적으로 기술한다.
function ParallelAll.tick():
success_count ← 0
failure_count ← 0
for each child in children:
if child.status() == SUCCESS:
success_count ← success_count + 1
continue // 이미 완료된 자식은 재Tick하지 않음
if child.status() == FAILURE:
failure_count ← failure_count + 1
continue
child_status ← child.tick()
if child_status == SUCCESS:
success_count ← success_count + 1
else if child_status == FAILURE:
failure_count ← failure_count + 1
// 정책 평가
if failure_count > max_failures:
haltAllRunningChildren()
return FAILURE
if success_count == childrenCount():
return SUCCESS
return RUNNING
이 구현에서 주목할 점은, 이미 SUCCESS나 FAILURE를 반환한 자식에게는 Tick을 재전파하지 않는다는 것이다. RUNNING 상태인 자식에게만 Tick이 전파된다.
5. halt() 메서드의 구현
function ParallelAll.halt():
for each child in children:
if child.status() == RUNNING:
child.halt()
resetChildren() // 모든 자식의 상태를 IDLE로 초기화
Halt 시 RUNNING 상태인 모든 자식에 Halt를 전파하고, 모든 자식의 상태를 IDLE로 초기화한다.
6. 이전 버전과의 차이
BehaviorTree.CPP 3.x에서는 ParallelNode라는 이름으로 Parallel 노드가 제공되었으며, 성공 임계값(success_count)과 실패 임계값(failure_count)을 별도로 설정할 수 있었다.
<!-- BehaviorTree.CPP 3.x 방식 -->
<Parallel success_count="3" failure_count="1">
<Child1 />
<Child2 />
<Child3 />
</Parallel>
4.x에서는 ParallelAll로 명칭이 변경되고, max_failures 파라미터로 단순화되었다. 성공 정책은 항상 SUCCESS_ALL(모든 자식 성공)이다.
7. ROS2 Nav2에서의 사용
ROS2의 Nav2(Navigation2) 스택에서는 BehaviorTree.CPP를 활용하여 로봇의 항법 행동 트리를 구성한다. Nav2에서 Parallel 노드는 주로 다음의 용도로 사용된다.
<!-- Nav2 행동 트리에서의 Parallel 사용 예시 -->
<ParallelAll max_failures="1">
<FollowPath path="{path}" controller_id="FollowPath" />
<GoalChecker goal="{goal}" />
</ParallelAll>
경로 추종과 목표 도달 검사를 동시에 수행한다. 경로 추종이 실행되는 동안 목표 도달 여부를 지속적으로 검사하여, 목표에 도달하면 SUCCESS를 반환한다.
8. 커스텀 Parallel 노드의 등록
BehaviorTree.CPP에서 커스텀 Parallel 변형 노드를 구현하여 등록할 수 있다.
class CustomParallel : public BT::ControlNode
{
public:
CustomParallel(const std::string& name, const BT::NodeConfig& config)
: ControlNode(name, config) {}
static BT::PortsList providedPorts()
{
return {BT::InputPort<int>("success_threshold", 1,
"성공으로 간주할 최소 성공 자식 수")};
}
BT::NodeStatus tick() override
{
int success_threshold;
getInput("success_threshold", success_threshold);
int success_count = 0;
int failure_count = 0;
for (size_t i = 0; i < childrenCount(); i++)
{
auto child_status = children_nodes_[i]->executeTick();
if (child_status == BT::NodeStatus::SUCCESS)
success_count++;
else if (child_status == BT::NodeStatus::FAILURE)
failure_count++;
}
if (success_count >= success_threshold)
{
haltChildren();
return BT::NodeStatus::SUCCESS;
}
if (failure_count == childrenCount())
return BT::NodeStatus::FAILURE;
return BT::NodeStatus::RUNNING;
}
};
// 팩토리에 등록
factory.registerNodeType<CustomParallel>("CustomParallel");
9. 디버깅과 시각화
BehaviorTree.CPP는 Groot2 시각화 도구와 연동하여, Parallel 노드의 실행 상태를 실시간으로 모니터링할 수 있다. 각 자식의 상태(RUNNING, SUCCESS, FAILURE, IDLE)가 색상으로 구분되어 표시되며, 정책 충족 여부와 Halt 전파를 시각적으로 확인할 수 있다.
// 로깅 활성화
BT::StdCoutLogger logger(tree);
// 또는 파일 로깅
BT::FileLogger file_logger(tree, "bt_trace.fbl");
// Groot2 실시간 모니터링
BT::Groot2Publisher publisher(tree);