1294.14 SequenceWithMemory의 재진입 규칙

1. 재진입의 정의

재진입(re-entry)이란, Sequence 노드가 이전 Tick에서 RUNNING을 반환한 후, 다음 Tick에서 다시 Tick을 수신했을 때 실행을 어디서부터 시작하는지를 결정하는 규칙이다. SequenceWithMemory의 재진입 규칙은 “마지막으로 RUNNING을 반환한 자식의 인덱스부터 재개한다“는 것이다(Colledanchise & Ogren, 2018).

2. 재진입 규칙의 상세

2.1 규칙 1: RUNNING 자식부터 재개

이전 Tick에서 자식 C_i가 RUNNING을 반환하면, 다음 Tick에서 C_i부터 실행을 재개한다. C_0, C_1, \ldots, C_{i-1}은 건너뛴다.

Tick N:   C₀→S  C₁→S  C₂→R          → current_index = 2
Tick N+1:              C₂→?          → C₂부터 재개

2.2 규칙 2: SUCCESS 자식은 재평가하지 않음

RUNNING 자식보다 앞에 위치한 자식들은 이전 Tick에서 SUCCESS를 반환한 상태이므로, 다시 평가하지 않는다. 이 자식들의 SUCCESS 결과가 유지된다고 가정한다.

2.3 규칙 3: RUNNING 자식이 SUCCESS를 반환하면 다음으로 진행

재진입 후 RUNNING이었던 자식이 SUCCESS를 반환하면, 그 다음 자식으로 진행하여 평가를 계속한다.

Tick N:   C₀→S  C₁→R               → current_index = 1
Tick N+1:       C₁→S  C₂→R         → current_index = 2
Tick N+2:             C₂→S         → Sequence SUCCESS

2.4 규칙 4: FAILURE 또는 SUCCESS 시 인덱스 초기화

Sequence가 최종적으로 FAILURE 또는 SUCCESS를 반환하면, current_index는 0으로 초기화된다. 다음에 Sequence가 다시 Tick되면 첫 번째 자식부터 실행한다.

2.5 규칙 5: Halt 시 인덱스 초기화

외부에서 Sequence에 Halt가 호출되면, current_index는 0으로 초기화되고 모든 자식이 IDLE 상태로 복귀한다.

3. 재진입 규칙의 실행 예시

3.1 단계 순차 임무

<Sequence>
    <Action ID="MoveTo" goal="A"/>      <!-- 자식 0 -->
    <Action ID="PickObject"/>           <!-- 자식 1 -->
    <Action ID="MoveTo" goal="B"/>      <!-- 자식 2 -->
</Sequence>
Tick 1: current_index=0
  MoveTo(A) → RUNNING         → current_index = 0, Seq → RUNNING

Tick 2: current_index=0
  MoveTo(A) → RUNNING         → Seq → RUNNING

Tick 3: current_index=0
  MoveTo(A) → SUCCESS         → current_index 진행
  PickObject → RUNNING        → current_index = 1, Seq → RUNNING

Tick 4: current_index=1
  PickObject → SUCCESS        → current_index 진행
  MoveTo(B) → RUNNING         → current_index = 2, Seq → RUNNING

Tick 5: current_index=2
  MoveTo(B) → SUCCESS         → 모든 자식 완료
  current_index = 0            → Seq → SUCCESS

3.2 중간 단계에서의 FAILURE

Tick 1: current_index=0
  MoveTo(A) → SUCCESS
  PickObject → RUNNING        → current_index = 1

Tick 2: current_index=1
  PickObject → FAILURE         ← 파지 실패
  current_index = 0            → Seq → FAILURE

FAILURE 발생 시 인덱스가 0으로 초기화되므로, Sequence가 다시 Tick되면 MoveTo(A)부터 다시 실행한다.

4. 재진입과 조건 노드의 관계

SequenceWithMemory에서 조건 노드가 SUCCESS를 반환한 후 건너뛰어지는 것은, 해당 조건이 다음 Tick에서도 여전히 충족된다는 가정을 내포한다. 이 가정이 성립하지 않는 환경에서는 ReactiveSequence를 사용하여 매 Tick마다 조건을 재검사해야 한다.

SequenceWithMemory에서의 위험:
  Tick 1: [IsBatteryOK → SUCCESS] [Navigate → RUNNING]
  Tick 2: [건너뜀 — 배터리 소진됨] [Navigate → RUNNING]
          ← 배터리 부족 상태에서 네비게이션 지속 — 위험

5. 재진입 규칙의 내부 구현

class SequenceWithMemory : public ControlNode {
public:
    NodeStatus tick() override {
        // current_child_idx_부터 시작
        while (current_child_idx_ < childrenCount()) {
            auto status = children_nodes_[current_child_idx_]->executeTick();
            
            switch (status) {
                case NodeStatus::RUNNING:
                    return NodeStatus::RUNNING;
                    
                case NodeStatus::FAILURE:
                    haltChildren();
                    current_child_idx_ = 0;
                    return NodeStatus::FAILURE;
                    
                case NodeStatus::SUCCESS:
                    current_child_idx_++;
                    break;
            }
        }
        
        // 모든 자식 완료
        haltChildren();
        current_child_idx_ = 0;
        return NodeStatus::SUCCESS;
    }
    
    void halt() override {
        current_child_idx_ = 0;
        ControlNode::halt();
    }

private:
    size_t current_child_idx_{0};
};

current_child_idx_가 재진입 규칙의 핵심 상태 변수이다. 이 변수에 의해 다음 Tick의 시작 위치가 결정되며, Sequence의 최종 반환 또는 Halt 시 초기화된다(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/