1292.45 ReactiveSequence 노드의 동작 원리

1. ReactiveSequence 노드의 정의

ReactiveSequence 노드는 행동 트리(Behavior Tree)의 제어 흐름 노드 중 하나로, 매 tick마다 항상 첫 번째 자식부터 재평가(re-evaluation)하는 변형된 Sequence 노드이다. 일반 Sequence 노드가 이전 tick에서 Running을 반환한 자식의 인덱스를 기억하고 해당 자식부터 실행을 재개하는 것과 달리, ReactiveSequence 노드는 매 tick마다 예외 없이 첫 번째 자식부터 순차적으로 실행한다. 이 재평가 특성에 의해 선행 조건의 변화를 즉각적으로 감지하고 대응할 수 있다 (Colledanchise & Ögren, Behavior Trees in Robotics and AI: An Introduction, 2018).

2. ReactiveSequence 노드의 알고리즘

ReactiveSequence 노드의 동작을 의사 코드로 표현하면 다음과 같다.

function ReactiveSequence.tick():
    for i = 0 to N:
        status = child[i].tick()
        if status == Running:
            halt_children_after(i)
            return Running
        if status == Failure:
            halt_children_after(i)
            return Failure
    return Success

이 알고리즘의 핵심적 특성은 반복문의 시작 인덱스가 항상 0이라는 것이다. 일반 Sequence 노드에서 remembered_index로부터 시작하는 것과 달리, ReactiveSequence는 매 tick마다 인덱스 0부터 시작한다. 또한, Running 또는 Failure를 반환하기 전에 halt_children_after(i) 함수를 호출하여, 인덱스 i 이후의 자식 중 Running 상태에 있는 노드에 halt를 요청한다 (Faconti, BehaviorTree.CPP Documentation, 2024).

3. 재평가의 의미론

ReactiveSequence의 재평가 메커니즘은 다음의 의미론적 해석을 가진다: “모든 선행 조건이 매 tick마다 유효한 경우에만 후행 행동을 계속 수행하라.”

이 의미론은 로봇 공학에서 안전 조건의 지속적 감시에 직접적으로 대응된다. 예를 들어, 배터리 잔량이 충분한 경우에만 경로 추종을 계속하는 행동은 ReactiveSequence로 자연스럽게 모델링된다.

4. 실행 흐름 예제

다음의 ReactiveSequence 노드를 고려한다.

ReactiveSequence [RS1]
 ├─ Condition [C1: 배터리 잔량 > 20%]
 ├─ Condition [C2: GPS 신호 유효]
 └─ Action [A1: 경로 추종]

4.1 정상 실행 흐름

Tick t_1: C1(Success) \rightarrow C2(Success) \rightarrow A1(Running) \rightarrow RS1(Running)

Tick t_2: C1(Success) \rightarrow C2(Success) \rightarrow A1(Running) \rightarrow RS1(Running)

매 tick마다 C1과 C2가 재평가된다. 두 조건이 모두 Success를 반환하는 동안 A1은 계속 실행된다.

4.2 선행 조건 실패에 의한 행동 중단

Tick t_3: C1(Failure) \rightarrow A1에 halt 요청 \rightarrow RS1(Failure)

배터리 잔량이 20% 이하로 감소하여 C1이 Failure를 반환한다. RS1은 즉시 Failure를 반환하고, Running 상태의 A1에 halt를 요청한다. C2에는 tick이 전달되지 않는다.

4.3 조건 복원에 의한 행동 재개

Tick t_4: C1(Success) \rightarrow C2(Success) \rightarrow A1(Running) \rightarrow RS1(Running)

배터리가 충전되어 C1이 다시 Success를 반환하면, A1은 onStart() 콜백을 통해 새로 활성화된다.

5. Halt의 자동 전파

ReactiveSequence에서 선행 자식이 Failure를 반환하면, 해당 인덱스 이후의 Running 상태 자식에 halt가 자동으로 전파된다. 이 자동 halt 전파는 ReactiveSequence의 핵심 메커니즘이며, 선행 조건이 무효화되었을 때 진행 중인 행동을 즉시 중단하는 역할을 한다.

ReactiveSequence [RS1]
 ├─ Condition [C1]
 ├─ Action [A1]   ← tick t_k에서 Running
 └─ Action [A2]

tick t_{k+1}에서 C1이 Failure를 반환하면:

  1. A1에 halt가 요청된다 (A1의 onHalted() 콜백 호출).
  2. A2에는 tick이 전달되지 않는다 (Idle 상태 유지).
  3. RS1은 Failure를 반환한다.

6. 조건 노드와의 조합 패턴

ReactiveSequence의 가장 전형적인 사용 패턴은, 앞쪽 자식으로 조건 노드를 배치하고 뒤쪽 자식으로 액션 노드를 배치하는 것이다. 조건 노드는 즉시 Success 또는 Failure를 반환하므로, 매 tick마다의 재평가 비용이 최소화된다.

ReactiveSequence
 ├─ Condition: [가드 조건]
 └─ Action: [보호되는 행동]

이 패턴은 가드 조건이 유지되는 동안에만 행동이 수행되도록 보장한다. 가드 조건이 실패하면 행동이 즉시 중단된다 (Colledanchise & Ögren, 2018).

7. 중첩된 ReactiveSequence

ReactiveSequence의 자식으로 다른 ReactiveSequence를 배치할 수 있다. 이 중첩 구조에서는 외부 ReactiveSequence의 조건이 실패하면 내부 ReactiveSequence 전체에 halt가 전파된다.

ReactiveSequence [RS1]
 ├─ Condition [C1: 전역 안전 조건]
 └─ ReactiveSequence [RS2]
     ├─ Condition [C2: 지역 조건]
     └─ Action [A1: 행동 수행]

C1이 Failure를 반환하면 RS2 전체에 halt가 전파되어, C2와 A1 모두 비활성화된다. 이 계층적 조건 감시 구조는 전역 안전 조건과 지역 작업 조건을 분리하여 관리하는 데 활용된다.

8. 실행 비용과 설계 고려사항

ReactiveSequence는 매 tick마다 첫 번째 자식부터 재평가하므로, 선행 자식의 수와 실행 비용이 tick당 총 실행 시간에 직접적으로 영향을 미친다. 따라서 선행 자식으로는 즉시 반환되는 조건 노드를 배치하고, 계산 비용이 높은 액션 노드는 후방에 배치하는 것이 권장된다. 선행 자식이 장시간 소요되는 액션 노드인 경우, 매 tick마다의 재실행이 과도한 계산 부하를 유발할 수 있다 (Faconti, 2024).


참고 문헌

  • Colledanchise, M. & Ögren, P. (2018). Behavior Trees in Robotics and AI: An Introduction. CRC Press.
  • Faconti, D. (2024). BehaviorTree.CPP Documentation. https://www.behaviortree.dev/