1294.47 ReactiveFallback의 처음부터 재실행 동작
1. 처음부터 재실행의 의미
ReactiveFallback의 “처음부터 재실행(restart from the beginning)” 동작이란, 매 Tick에서 자식 배열의 인덱스 0부터 순차적으로 executeTick()을 호출하는 것을 의미한다. 이전 Tick에서 특정 자식이 RUNNING을 반환했더라도, 다음 Tick에서는 그 자식보다 앞에 위치한 모든 자식을 다시 평가한다. 이는 FallbackWithMemory의 재진입(resume) 동작과 대비되는 핵심 차이점이다(Colledanchise & Ogren, 2018).
2. 재실행 동작의 상세 과정
2.1 감시-대안 패턴
ReactiveFallback에서 앞쪽 자식은 “우선순위 감시자(priority monitor)” 역할을, 뒤쪽 자식은 “현재 대안(current alternative)” 역할을 수행한다.
<ReactiveFallback>
<!-- 감시자: 매 Tick 재검사 -->
<Condition ID="IsGoalAchieved"/>
<Condition ID="IsFastMethodAvailable"/>
<!-- 현재 대안: 비동기 작업 수행 -->
<Action ID="UseSlowMethod"/>
</ReactiveFallback>
매 Tick에서의 동작:
IsGoalAchieved평가 → SUCCESS이면 즉시 종료IsFastMethodAvailable평가 → SUCCESS이면 즉시 종료UseSlowMethod평가 → RUNNING 유지 또는 완료
감시자 중 하나라도 SUCCESS를 반환하면 현재 대안이 Halt된다.
2.2 Tick별 실행 흐름
<ReactiveFallback>
<Condition ID="IsAtGoal"/> <!-- 자식 0 -->
<Condition ID="HasShortcut"/> <!-- 자식 1 -->
<Action ID="LongRoute"/> <!-- 자식 2 -->
</ReactiveFallback>
Tick 1:
children[0]: IsAtGoal → FAILURE (목표 미도달)
children[1]: HasShortcut → FAILURE (지름길 없음)
children[2]: LongRoute → RUNNING (긴 경로 진행 중)
ReactiveFallback → RUNNING
Tick 2: (처음부터 재실행)
children[0]: IsAtGoal → FAILURE (다시 평가됨)
children[1]: HasShortcut → FAILURE (다시 평가됨)
children[2]: LongRoute → RUNNING (다시 평가됨)
ReactiveFallback → RUNNING
Tick 3: (처음부터 재실행)
children[0]: IsAtGoal → FAILURE
children[1]: HasShortcut → SUCCESS (지름길 발견!)
children[2]: LongRoute → Halt (긴 경로 중단)
ReactiveFallback → SUCCESS
2.3 FallbackWithMemory와의 비교
동일한 트리에서 FallbackWithMemory의 동작:
Tick 1:
children[0]: IsAtGoal → FAILURE
children[1]: HasShortcut → FAILURE
children[2]: LongRoute → RUNNING (current_child_idx = 2)
Fallback → RUNNING
Tick 2: (인덱스 2부터 재개)
children[0]: (건너뜀)
children[1]: (건너뜀 — 지름길이 생겨도 감지 못함)
children[2]: LongRoute → RUNNING
Fallback → RUNNING
Tick 3: (인덱스 2부터 재개)
children[0]: (건너뜀)
children[1]: (건너뜀)
children[2]: LongRoute → RUNNING
Fallback → RUNNING ← 지름길을 감지하지 못하고 긴 경로 계속
3. RUNNING 자식에 대한 재실행의 영향
ReactiveFallback에서 RUNNING 상태인 자식은 매 Tick마다 다시 executeTick()이 호출된다. StatefulActionNode의 경우, 노드가 이미 RUNNING 상태이면 executeTick()은 onRunning()을 호출하여 작업 완료 여부를 확인한다. 즉, RUNNING 자식에 대한 “재실행“은 “상태 확인“에 해당하며, 작업이 처음부터 다시 시작되는 것은 아니다.
그러나 앞쪽 자식의 SUCCESS로 인해 RUNNING 자식이 Halt된 후, 다음 Tick에서 다시 해당 자식에 도달하면 IDLE 상태에서 onStart()가 호출되어 작업이 처음부터 다시 시작된다.
4. Halt 후 재시작의 동작
ReactiveFallback에서 앞쪽 조건의 SUCCESS로 인해 RUNNING 자식이 Halt된 후, 다음 Tick에서 조건이 다시 FAILURE를 반환하면 해당 자식이 IDLE 상태에서 다시 시작된다.
Tick N: IsAtGoal→F, LongRoute→R → RUNNING
Tick N+1: IsAtGoal→S → SUCCESS (LongRoute Halt → IDLE)
Tick N+2: IsAtGoal→F, LongRoute→R → RUNNING (LongRoute가 onStart()부터 재시작)
이 동작은 일시적으로 목표에 도달했다가 다시 벗어나면 경로 탐색을 처음부터 재시작하는 행동 패턴을 구현한다. 이는 ReactiveSequence에서 조건이 일시적으로 실패했다가 복구되면 작업을 재시작하는 동작의 대칭적 쌍대이다.
5. 우선순위 전환 시나리오
5.1 시나리오: 더 나은 대안으로의 전환
<ReactiveFallback>
<Sequence>
<Condition ID="IsPrimaryServiceOnline"/>
<Action ID="UsePrimaryService"/>
</Sequence>
<Action ID="UseBackupService"/>
</ReactiveFallback>
Tick 1: PrimaryOnline→F → UseBackup→R → RUNNING
Tick 2: PrimaryOnline→F → UseBackup→R → RUNNING
Tick 3: PrimaryOnline→S → UsePrimary→R → RUNNING (UseBackup Halt)
Tick 4: PrimaryOnline→S → UsePrimary→S → SUCCESS
Tick 3에서 주 서비스가 복구되면 백업 서비스 사용이 즉시 중단되고 주 서비스로 전환된다. 이 동작은 FallbackWithMemory에서는 불가능하다.
6. 처음부터 재실행 동작의 설계 의도
ReactiveFallback의 처음부터 재실행 동작은 대안의 우선순위를 동적으로 반영하기 위한 설계이다. 로봇이 차선의 대안을 수행하는 동안에도 더 나은 대안의 가용성을 지속적으로 감시하여, 가용해지는 즉시 전환하는 능력을 제공한다. 이는 ReactiveSequence의 조건 감시와 대칭적으로, ReactiveFallback은 대안 감시를 구현한다(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/