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 동작 분석

  1. IDLE 상태 감지: 노드가 IDLE에서 처음 Tick되면 all_skipped_ 플래그를 초기화한다.

  2. 인덱스 기반 순회: current_child_idx_부터 자식을 순회한다. 이전 Tick에서 RUNNING을 반환한 자식의 인덱스가 유지되므로, 이전에 SUCCESS를 반환한 자식은 건너뛴다.

  3. RUNNING 처리: 자식이 RUNNING을 반환하면, current_child_idx_를 유지한 채 RUNNING을 반환한다. 다음 Tick에서 동일 자식부터 재시작한다.

  4. FAILURE 처리: 자식이 FAILURE를 반환하면, resetChildren()으로 모든 자식을 IDLE로 초기화하고, current_child_idx_를 0으로 재설정한 후 FAILURE를 반환한다.

  5. SUCCESS 처리: 자식이 SUCCESS를 반환하면, current_child_idx_를 증가시키고 다음 자식으로 진행한다.

  6. 전체 완료: 모든 자식이 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
모든 자식 SUCCESS0으로 초기화
자식 FAILURE0으로 초기화
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. 사용 시 주의 사항

  1. v3에서 v4로의 명칭 변경: BehaviorTree.CPP v3에서 SequenceStar(Sequence*)로 불리던 것이 v4에서 SequenceNode로 변경되었다. v3의 Sequence는 v4에서 별도의 구현이 없으며, SequenceNode가 기본 Sequence이다.

  2. 동기 자식에서의 동작: 모든 자식이 동기적으로 즉시 완료되면, 메모리 기능이 실질적으로 비활성화된다. 모든 자식이 단일 Tick 내에서 SUCCESS를 반환하므로, current_child_idx_가 0에서 N까지 진행하고 다시 0으로 돌아온다.

  3. 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/