1292.47 ReactiveSequence와 일반 Sequence의 차이
1. 개요
ReactiveSequence 노드와 일반 Sequence 노드(SequenceWithMemory)는 동일한 논리곱(AND) 의미론을 공유하지만, Running 상태에서의 후속 tick 처리 방식에서 근본적으로 다르다. 이 차이는 행동 트리의 환경 변화에 대한 반응성(reactivity)과 실행 효율 사이의 설계 균형(trade-off)을 결정하는 핵심 요인이다 (Colledanchise & Ögren, Behavior Trees in Robotics and AI: An Introduction, 2018).
2. 핵심 차이의 요약
| 속성 | 일반 Sequence | ReactiveSequence |
|---|---|---|
| 후속 tick 시작 위치 | 이전 Running 자식부터 | 항상 첫 번째 자식부터 |
| 이전 Success 자식 재평가 | 하지 않음 | 매 tick마다 재평가 |
| 환경 변화 감지 | 지연됨 | 즉각적 |
| tick당 실행 노드 수 | 적음 | 많음 |
| 자식 인덱스 기억 | 기억함 | 기억하지 않음 |
| halt 발생 빈도 | 낮음 | 높음 |
3. 상태 기억의 유무
3.1 일반 Sequence의 상태 기억
일반 Sequence 노드는 자식이 Running을 반환하면 해당 자식의 인덱스를 내부 변수에 저장한다. 후속 tick에서는 이 인덱스부터 실행을 재개하며, 이전에 Success를 반환한 자식은 재실행하지 않는다.
Tick t_k: C1(S) → A1(R) → Seq(R) [인덱스 1 기억]
Tick t_k+1: A1(S) → A2(S) → Seq(S) [C1 건너뜀]
3.2 ReactiveSequence의 상태 비기억
ReactiveSequence 노드는 자식의 인덱스를 기억하지 않으며, 매 tick마다 반드시 인덱스 0부터 실행한다.
Tick t_k: C1(S) → A1(R) → RS(R)
Tick t_k+1: C1(S) → A1(S) → A2(S) → RS(S) [C1 재평가]
4. 환경 변화에 대한 반응
두 노드의 가장 실질적인 차이는 환경 변화에 대한 반응 방식에서 나타난다. 다음 예제를 통해 이를 비교한다.
[Sequence 또는 ReactiveSequence]
├─ Condition [C1: 안전 조건]
└─ Action [A1: 이동]
4.1 일반 Sequence에서 환경 변화 발생 시
| Tick | C1 결과 | A1 결과 | Seq 동작 |
|---|---|---|---|
| t_1 | Success | Running | Running (인덱스 1 기억) |
| t_2 | (미평가) | Running | Running (C1 건너뜀) |
| t_3 | (미평가) | Success | Success |
tick t_2에서 실제로는 안전 조건이 위반되었더라도, C1이 재평가되지 않으므로 A1은 계속 실행된다. 안전 조건의 변화가 감지되지 않는다 (Faconti, BehaviorTree.CPP Documentation, 2024).
4.2 ReactiveSequence에서 환경 변화 발생 시
| Tick | C1 결과 | A1 결과 | RS 동작 |
|---|---|---|---|
| t_1 | Success | Running | Running |
| t_2 | Failure | halt 요청 | Failure |
tick t_2에서 안전 조건이 위반되면, C1이 Failure를 반환하고 RS는 즉시 Failure를 반환하며 A1에 halt를 요청한다. 환경 변화가 즉각적으로 감지되고 대응된다.
5. 실행 효율의 차이
5.1 일반 Sequence의 효율
일반 Sequence는 이전에 성공한 자식을 재실행하지 않으므로, tick당 실행되는 노드의 수가 최소화된다. 자식이 N개이고 i번째 자식이 Running인 경우, 후속 tick에서 실행되는 자식의 수는 N - i개이다.
5.2 ReactiveSequence의 효율
ReactiveSequence는 매 tick마다 모든 선행 자식을 재실행하므로, tick당 실행되는 노드의 수가 증가한다. i번째 자식이 Running인 경우, 후속 tick에서 실행되는 자식의 수는 i + 1개(인덱스 0부터 i까지)이다. 선행 자식이 조건 노드로 구성되어 있으면 이 추가 비용은 미미하지만, 계산 비용이 높은 액션 노드가 선행 자식인 경우 성능에 영향을 줄 수 있다.
6. Halt 발생 패턴의 차이
6.1 일반 Sequence에서의 halt
일반 Sequence에서는 Running 상태의 자식이 스스로 Failure를 반환할 때에만 해당 자식이 비활성화된다. 선행 자식의 상태 변화에 의한 halt는 발생하지 않는다.
6.2 ReactiveSequence에서의 halt
ReactiveSequence에서는 선행 자식이 Failure를 반환할 때마다 Running 상태의 후행 자식에 halt가 요청된다. 이 halt는 매 tick마다 발생할 수 있으므로, 액션 노드의 onHalted() 콜백 구현이 올바르고 효율적이어야 한다 (Colledanchise & Ögren, 2018).
7. 선택 기준
두 노드의 선택은 응용 시나리오의 특성에 따라 결정된다.
일반 Sequence를 사용하는 경우:
- 선행 조건이 한 번 성립하면 이후에 변하지 않는 것이 보장되는 경우
- 선행 자식의 재실행이 부작용을 유발하는 경우
- tick당 계산 비용을 최소화하여야 하는 경우
- 예시: 한 번 초기화된 하드웨어 설정이 이후에 변경되지 않는 경우
ReactiveSequence를 사용하는 경우:
- 선행 조건이 언제든지 무효화될 수 있는 경우
- 환경 변화에 즉각적으로 반응하여야 하는 경우
- 안전 조건의 지속적 감시가 필요한 경우
- 예시: 배터리 잔량, 장애물 감지, 통신 상태 등의 동적 조건 감시
8. 형식적 비교
자식 c_1, c_2, \ldots, c_N에서 c_i가 tick t_k에서 Running을 반환한 경우, tick t_{k+1}에서의 동작을 형식적으로 비교한다.
일반 Sequence:
\text{ticked}(c_j, t_{k+1}) \iff j \geq i
ReactiveSequence:
\text{ticked}(c_j, t_{k+1}) \iff j \leq i \text{ (단, } \forall\, m < j : \text{status}(c_m, t_{k+1}) = Success\text{)}
일반 Sequence에서는 인덱스 i 이전의 자식에 tick이 전달되지 않으며, ReactiveSequence에서는 인덱스 0부터 i까지의 모든 자식에 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/