1293.42 WithoutMemory (Reactive) 노드의 Tick 재진입 규칙
1. 재진입 규칙의 정의
WithoutMemory 노드(Reactive 노드)의 Tick 재진입 규칙이란, 이전 Tick에서의 자식 반환 상태와 무관하게 매 Tick마다 첫 번째 자식부터 모든 자식을 재평가하는 동작 규칙이다. WithMemory 노드가 메모리 인덱스를 통해 이전에 완료된 자식을 건너뛰는 것과 달리, WithoutMemory 노드는 메모리 인덱스를 유지하지 않으며 매 Tick의 시작 시 항상 인덱스 0에서 Tick 전파를 개시한다(Colledanchise & Ogren, 2018).
2. ReactiveSequence의 재진입 규칙
ReactiveSequence에서 매 Tick마다 첫 번째 자식부터 재평가하는 규칙은 다음과 같이 구현된다.
function ReactiveSequence.tick():
for i = 0 to children.size() - 1: // 항상 0부터 시작
child_status = children[i].tick()
if child_status == FAILURE:
haltRunningChildren()
return FAILURE
if child_status == RUNNING:
haltChildrenAfter(i + 1)
return RUNNING
return SUCCESS
이전 Tick에서 SUCCESS를 반환한 자식도 다시 Tick된다. 이를 통해 조건의 지속적 충족을 검증한다.
2.1 실행 흐름 추적
Tick 1: ReactiveSequence
→ Child_0.tick() → SUCCESS (조건 확인)
→ Child_1.tick() → SUCCESS (조건 확인)
→ Child_2.tick() → RUNNING (액션 시작)
반환: RUNNING
Tick 2: ReactiveSequence
→ Child_0.tick() → SUCCESS (재평가: 여전히 유효)
→ Child_1.tick() → SUCCESS (재평가: 여전히 유효)
→ Child_2.tick() → RUNNING (재진입: 계속 실행)
반환: RUNNING
Tick 3: ReactiveSequence
→ Child_0.tick() → FAILURE (재평가: 조건 위반!)
→ Child_2에 Halt 호출 (RUNNING 자식 중단)
반환: FAILURE
3. ReactiveFallback의 재진입 규칙
ReactiveFallback에서도 매 Tick마다 첫 번째 자식부터 재평가한다.
function ReactiveFallback.tick():
for i = 0 to children.size() - 1: // 항상 0부터 시작
child_status = children[i].tick()
if child_status == SUCCESS:
haltRunningChildren()
return SUCCESS
if child_status == RUNNING:
haltChildrenAfter(i + 1)
return RUNNING
return FAILURE
이전 Tick에서 FAILURE를 반환한 자식도 다시 Tick된다. 이를 통해 상위 우선순위 대안의 복구를 감지한다.
3.1 실행 흐름 추적
Tick 1: ReactiveFallback
→ Child_0.tick() → FAILURE (우선 대안 불가)
→ Child_1.tick() → RUNNING (차선 대안 시도)
반환: RUNNING
Tick 2: ReactiveFallback
→ Child_0.tick() → FAILURE (재평가: 여전히 불가)
→ Child_1.tick() → RUNNING (재진입: 계속 시도)
반환: RUNNING
Tick 3: ReactiveFallback
→ Child_0.tick() → SUCCESS (재평가: 우선 대안 복구!)
→ Child_1에 Halt 호출 (차선 대안 중단)
반환: SUCCESS
4. RUNNING 자식의 재진입과 Halt
WithoutMemory 노드에서 이전 Tick에서 RUNNING 상태였던 자식은 두 가지 경우에 처해진다.
4.1 재진입 (계속 실행)
이전 자식들의 평가 결과가 이전 Tick과 동일하여, RUNNING 자식에 Tick이 도달하는 경우이다. 이 경우 RUNNING 자식의 onRunning() 메서드가 호출되어 작업의 진행 상태가 확인된다.
Tick N-1: Child_0(SUCCESS), Child_1(SUCCESS), Child_2(RUNNING)
Tick N: Child_0(SUCCESS), Child_1(SUCCESS), Child_2(RUNNING) ← 재진입
4.2 Halt (중단)
이전 자식의 평가 결과가 변경되어, RUNNING 자식에 Tick이 도달하지 않는 경우이다. 이 경우 RUNNING 자식에 Halt가 호출되어 작업이 중단되고 IDLE 상태로 리셋된다.
ReactiveSequence:
Tick N-1: Child_0(SUCCESS), Child_1(SUCCESS), Child_2(RUNNING)
Tick N: Child_0(FAILURE) → Child_2에 Halt 호출
ReactiveFallback:
Tick N-1: Child_0(FAILURE), Child_1(RUNNING)
Tick N: Child_0(SUCCESS) → Child_1에 Halt 호출
5. Halt 후 재시작 동작
Halt에 의해 IDLE 상태로 리셋된 자식이 후속 Tick에서 다시 Tick을 수신하면, 해당 자식은 onStart()부터 새로 시작된다. 이전 실행의 진행 상태는 소실된다.
Tick 3: ReactiveSequence
Child_0(FAILURE) → Halt Child_2 (RUNNING → IDLE)
반환: FAILURE
Tick 4: ReactiveSequence
Child_0(SUCCESS), Child_1(SUCCESS)
Child_2.tick() → Child_2.onStart() 호출 (처음부터 재시작)
Child_2 반환: RUNNING
반환: RUNNING
이 동작은 안전성 측면에서 중요하다. 조건이 위반되어 중단된 작업은 조건이 복구된 후 처음부터 재시작되어야 하기 때문이다.
6. WithMemory와 WithoutMemory의 재진입 비교
| 특성 | WithMemory | WithoutMemory (Reactive) |
|---|---|---|
| Tick 시작 인덱스 | 메모리 인덱스 (가변) | 항상 0 |
| 이전 완료 자식 재평가 | 하지 않음 | 매 Tick마다 수행 |
| RUNNING 자식 Halt 빈도 | 낮음 | 높음 (조건 변화 시) |
| Tick당 실행 비용 | T_{child_{current}} | \sum_{i=0}^{j} T_{child_i} |
| 환경 변화 감지 | 불가 | 즉시 가능 |
| 작업 진행 보존 | 보장 | 조건 변화 시 소실 |
7. Tick 비용의 형식적 비교
WithMemory 노드와 WithoutMemory 노드의 Tick당 실행 비용을 비교한다. RUNNING 자식의 인덱스를 j라 하면:
T_{WithMemory} = T_{child_j}
T_{WithoutMemory} = \sum_{i=0}^{j} T_{child_i}
WithoutMemory 노드는 인덱스 0부터 j까지의 모든 자식을 Tick하므로, 앞선 자식들의 Tick 비용이 추가된다. 앞선 자식들이 경량 조건 노드(T_{cond} \ll T_{action})인 경우 추가 비용은 미미하지만, 비동기 액션 노드인 경우 추가 비용이 유의미할 수 있다.
8. 설계 시 자식 배치 규칙
WithoutMemory 노드에서 자식의 배치 순서는 성능과 정확성에 직접적인 영향을 미친다.
8.1 ReactiveSequence의 배치 규칙
<ReactiveSequence>
<!-- 앞: 경량 조건 노드 (매 Tick 재평가) -->
<ConditionA/>
<ConditionB/>
<!-- 뒤: 비동기 액션 노드 (하나만 배치 권장) -->
<AsyncAction/>
</ReactiveSequence>
8.2 ReactiveFallback의 배치 규칙
<ReactiveFallback>
<!-- 앞: 우선순위 높은 동기 조건/액션 -->
<HighPriorityCondition/>
<!-- 뒤: 우선순위 낮은 대안 -->
<LowPriorityAction/>
</ReactiveFallback>
9. 적용 시나리오 가이드
| 시나리오 | 적합한 모드 | 근거 |
|---|---|---|
| 안전 조건 감시 + 작업 수행 | ReactiveSequence | 조건 위반 즉시 감지 필요 |
| 센서 우선순위 선택 | ReactiveFallback | 상위 센서 복구 시 즉시 전환 |
| 순차적 조립 작업 | WithMemory Sequence | 완료 단계 반복 불필요 |
| 네트워크 재연결 시도 | WithMemory Fallback | 실패 채널 즉시 재시도 무의미 |
| 비상 정지 감시 + 임무 수행 | ReactiveSequence | 안전 최우선 |
| 다중 경로 탐색 | ReactiveFallback | 최적 경로 복구 시 전환 |
참고 문헌
- 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/