1294.83 BehaviorTree.CPP의 SequenceNode 클래스
1. SequenceNode 클래스의 개요
BehaviorTree.CPP v4에서 SequenceNode 클래스는 SequenceWithMemory(StatefulSequence) 동작을 구현하는 제어 노드이다. 이 클래스는 ControlNode를 상속하며, current_child_idx_ 멤버 변수를 통해 마지막 RUNNING 자식의 위치를 기억한다. XML에서 <Sequence> 태그로 선언되며, BehaviorTree.CPP v4에서 가장 기본적인 Sequence 구현체이다(Faconti, 2022).
2. 클래스 선언
namespace BT {
class SequenceNode : public ControlNode {
public:
SequenceNode(const std::string& name,
const NodeConfig& config);
virtual ~SequenceNode() override = default;
static PortsList providedPorts() { return {}; }
virtual void halt() override;
private:
size_t current_child_idx_;
bool all_skipped_;
virtual BT::NodeStatus tick() override;
};
} // namespace BT
SequenceNode는 입출력 포트를 가지지 않으므로 providedPorts()가 빈 목록을 반환한다. 노드의 핵심 로직은 tick() 메서드에 구현되어 있다.
3. tick() 메서드의 상세 분석
3.1 전체 구현
NodeStatus SequenceNode::tick() {
const size_t children_count = children_nodes_.size();
// IDLE에서 RUNNING으로의 최초 전이 시 초기화
if (status() == NodeStatus::IDLE) {
all_skipped_ = true;
}
setStatus(NodeStatus::RUNNING);
// current_child_idx_부터 순회
while (current_child_idx_ < children_count) {
TreeNode* current_child = children_nodes_[current_child_idx_];
const NodeStatus child_status = current_child->executeTick();
switch (child_status) {
case NodeStatus::RUNNING:
// 현재 인덱스를 유지하고 RUNNING 반환
return NodeStatus::RUNNING;
case NodeStatus::FAILURE:
// 인덱스 초기화 후 FAILURE 반환
resetChildren();
current_child_idx_ = 0;
return NodeStatus::FAILURE;
case NodeStatus::SUCCESS:
all_skipped_ = false;
current_child_idx_++;
break;
case NodeStatus::SKIPPED:
current_child_idx_++;
break;
}
}
// 모든 자식 순회 완료
current_child_idx_ = 0;
return all_skipped_ ? NodeStatus::SKIPPED : NodeStatus::SUCCESS;
}
3.2 동작 분석
-
IDLE 상태 감지: 노드가 IDLE에서 처음 Tick되면
all_skipped_플래그를 초기화한다. -
인덱스 기반 순회:
current_child_idx_부터 자식을 순회한다. 이전 Tick에서 RUNNING을 반환한 자식의 인덱스가 유지되므로, 이전에 SUCCESS를 반환한 자식은 건너뛴다. -
RUNNING 처리: 자식이 RUNNING을 반환하면,
current_child_idx_를 유지한 채 RUNNING을 반환한다. 다음 Tick에서 동일 자식부터 재시작한다. -
FAILURE 처리: 자식이 FAILURE를 반환하면,
resetChildren()으로 모든 자식을 IDLE로 초기화하고,current_child_idx_를 0으로 재설정한 후 FAILURE를 반환한다. -
SUCCESS 처리: 자식이 SUCCESS를 반환하면,
current_child_idx_를 증가시키고 다음 자식으로 진행한다. -
전체 완료: 모든 자식이 SUCCESS(또는 SKIPPED)를 반환하면,
current_child_idx_를 0으로 초기화하고 SUCCESS를 반환한다.
4. halt() 메서드
void SequenceNode::halt() {
current_child_idx_ = 0;
ControlNode::halt();
}
halt()가 호출되면 current_child_idx_를 0으로 초기화하고, 부모 클래스의 halt()를 호출하여 모든 자식을 Halt한다. 이에 의해 메모리가 완전히 소멸되며, 다음 실행 시 첫 번째 자식부터 시작한다.
5. 메모리 메커니즘의 구현 세부
5.1 current_child_idx_의 생명주기
IDLE → tick() 호출 → current_child_idx_ = 0 (초기값)
↓
child[0]→S → current_child_idx_ = 1
↓
child[1]→R → RUNNING 반환 (current_child_idx_ = 1 유지)
↓
다음 tick() → child[1]부터 재시작 (child[0] 건너뜀)
↓
child[1]→S → current_child_idx_ = 2
↓
...
↓
child[N-1]→S → SUCCESS 반환, current_child_idx_ = 0
5.2 인덱스 초기화 조건
| 조건 | current_child_idx_ 값 |
|---|---|
| 자식 RUNNING | 현재 인덱스 유지 |
| 자식 SUCCESS (마지막 아님) | 인덱스 + 1 |
| 모든 자식 SUCCESS | 0으로 초기화 |
| 자식 FAILURE | 0으로 초기화 |
| halt() 호출 | 0으로 초기화 |
6. XML 등록과 사용
6.1 자동 등록
BehaviorTree.CPP에서 SequenceNode는 기본 제공 노드로 자동 등록된다. XML에서 다음과 같이 사용한다:
<BehaviorTree ID="MainTree">
<Sequence name="mission_sequence">
<Action ID="Step1"/>
<Action ID="Step2"/>
<Action ID="Step3"/>
</Sequence>
</BehaviorTree>
6.2 name 속성
name 속성은 디버깅과 로깅을 위한 식별자이다. 동일 트리 내에서 여러 Sequence를 구분할 때 유용하다.
7. 사용 시 주의 사항
-
v3에서 v4로의 명칭 변경: BehaviorTree.CPP v3에서
SequenceStar(Sequence*)로 불리던 것이 v4에서SequenceNode로 변경되었다. v3의Sequence는 v4에서 별도의 구현이 없으며,SequenceNode가 기본 Sequence이다. -
동기 자식에서의 동작: 모든 자식이 동기적으로 즉시 완료되면, 메모리 기능이 실질적으로 비활성화된다. 모든 자식이 단일 Tick 내에서 SUCCESS를 반환하므로,
current_child_idx_가 0에서 N까지 진행하고 다시 0으로 돌아온다. -
FAILURE 시 전체 초기화: 어떤 자식이 FAILURE를 반환하면, 이전에 SUCCESS를 반환한 자식의 기억이 모두 소멸된다. 이는 “작업 체인이 실패했으므로 처음부터 다시 시작해야 한다“는 의미론을 구현한 것이다(Faconti, 2022).
참고 문헌
- Colledanchise, M., & Ogren, P. (2018). Behavior Trees in Robotics and AI: An Introduction. CRC Press.
- Faconti, D. (2022). BehaviorTree.CPP documentation and API reference. https://www.behaviortree.dev/