1294.86 BehaviorTree.CPP의 ReactiveFallback 클래스

1. ReactiveFallback 클래스의 개요

BehaviorTree.CPP v4에서 ReactiveFallback 클래스는 매 Tick마다 첫 번째 자식부터 재평가하는 비상태적 Fallback을 구현한다. ReactiveSequence와 대칭적 구조를 가지며, SUCCESS와 FAILURE의 역할이 교환되어 있다. 이 클래스는 주로 우선순위 기반 대안 전환 패턴에서 사용된다(Faconti, 2022).

2. 클래스 선언

namespace BT {

class ReactiveFallback : public ControlNode {
public:
    ReactiveFallback(const std::string& name,
                     const NodeConfig& config);
    
    virtual ~ReactiveFallback() override = default;
    
    static PortsList providedPorts() { return {}; }

private:
    virtual BT::NodeStatus tick() override;
};

}  // namespace BT

ReactiveSequence와 동일하게 current_child_idx_ 멤버 변수가 없으며, halt() 메서드를 별도로 오버라이드하지 않는다.

3. tick() 메서드의 상세 분석

3.1 전체 구현

NodeStatus ReactiveFallback::tick() {
    bool all_skipped = true;
    setStatus(NodeStatus::RUNNING);
    
    for (size_t index = 0; index < childrenCount(); index++) {
        TreeNode* current_child = children_nodes_[index];
        const NodeStatus child_status = current_child->executeTick();
        
        switch (child_status) {
            case NodeStatus::RUNNING: {
                for (size_t j = index + 1; j < childrenCount(); j++) {
                    haltChild(j);
                }
                return NodeStatus::RUNNING;
            }
            case NodeStatus::SUCCESS: {
                resetChildren();
                return NodeStatus::SUCCESS;
            }
            case NodeStatus::FAILURE: {
                all_skipped = false;
            } break;
            
            case NodeStatus::SKIPPED: {
            } break;
        }
    }
    
    resetChildren();
    return all_skipped ? NodeStatus::SKIPPED : NodeStatus::FAILURE;
}

3.2 ReactiveSequence와의 대칭적 관계

자식 반환 상태ReactiveSequenceReactiveFallback
SUCCESS다음 자식으로 진행즉시 SUCCESS 반환
FAILURE즉시 FAILURE 반환다음 자식으로 진행
RUNNING후속 자식 Halt, RUNNING 반환후속 자식 Halt, RUNNING 반환

3.3 동작 분석

  1. 항상 인덱스 0부터 시작: 매 Tick마다 첫 번째 자식부터 재평가한다. 앞쪽 대안이 SUCCESS 또는 RUNNING을 반환하면, 뒤쪽에서 RUNNING이었던 대안이 Halt된다.

  2. SUCCESS의 조기 종료: 앞쪽 자식이 SUCCESS를 반환하면 모든 자식을 초기화하고 즉시 SUCCESS를 반환한다. 뒤쪽에 RUNNING이었던 자식이 있으면 resetChildren()에 의해 Halt된다.

  3. RUNNING 이후 자식 Halt: RUNNING을 반환한 자식 이후의 모든 자식을 Halt한다. 이전 Tick에서 더 뒤쪽 자식이 RUNNING이었을 수 있기 때문이다.

  4. 전체 FAILURE: 모든 자식이 FAILURE를 반환하면 FAILURE를 반환한다.

4. 우선순위 전환 메커니즘

4.1 상위 우선순위 대안으로의 자동 전환

<ReactiveFallback>
    <Sequence>
        <Condition ID="IsPrimaryOK"/>        <!-- 1순위 -->
        <Action ID="PrimaryAction"/>
    </Sequence>
    <Action ID="FallbackAction"/>            <!-- 2순위 -->
</ReactiveFallback>
Tick 1: IsPrimaryOK→F → FallbackAction→R     (2순위 수행)
Tick 2: IsPrimaryOK→F → FallbackAction→R     (2순위 계속)
Tick 3: IsPrimaryOK→S, PrimaryAction→R       (1순위 복귀)
        FallbackAction→Halt                   (2순위 중단)

Tick 3에서 1순위 조건이 충족되면, FallbackAction이 Halt되고 PrimaryAction으로 전환된다. 이 전환은 ReactiveFallback의 매 Tick 재평가에 의해 자동으로 이루어진다.

4.2 FallbackNode(WithMemory)와의 차이

[FallbackNode]
Tick 1: IsPrimaryOK→F → FallbackAction→R     (idx=1)
Tick 2: FallbackAction→R                      (IsPrimaryOK 건너뜀!)
Tick 3: FallbackAction→R                      (1순위 변화 미감지)

[ReactiveFallback]
Tick 1: IsPrimaryOK→F → FallbackAction→R     (매 Tick 재평가)
Tick 2: IsPrimaryOK→F → FallbackAction→R     (1순위 재확인)
Tick 3: IsPrimaryOK→S → PrimaryAction→R      (1순위 복귀!)
        FallbackAction→Halt

FallbackNode는 메모리에 의해 IsPrimaryOK를 건너뛰므로 1순위 복귀를 감지하지 못하지만, ReactiveFallback은 매 Tick 재평가하므로 즉시 감지한다.

5. XML에서의 사용

<BehaviorTree ID="PriorityBasedBehavior">
    <ReactiveFallback name="priority_selector">
        <Sequence>
            <Condition ID="IsHighPriority"/>
            <Action ID="HighPriorityTask"/>
        </Sequence>
        <Sequence>
            <Condition ID="IsMediumPriority"/>
            <Action ID="MediumPriorityTask"/>
        </Sequence>
        <Action ID="DefaultTask"/>
    </ReactiveFallback>
</BehaviorTree>

6. 사용 시 주의 사항

  1. Halt 빈도: ReactiveFallback에서는 상위 대안의 가용 상태가 변할 때마다 현재 대안이 Halt된다. 빈번한 Halt-재시작이 발생할 수 있으므로, 모든 액션 노드의 onHalted() 구현이 안전하고 경량이어야 한다.

  2. 조건 노드의 경량성: 매 Tick 앞쪽 자식이 재평가되므로, 조건 노드의 평가 비용이 전체 성능에 직접 영향을 미친다.

  3. 무한 전환 방지: 조건이 매 Tick 변동하면 대안 간 무한 전환이 발생할 수 있다. 히스테리시스(hysteresis)를 적용하여 안정적인 전환을 보장해야 한다(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/