1295.42 상위 우선순위 자식 Success 시 하위 자식 Halt
1. 메커니즘의 핵심 동작
ReactiveFallback에서 상위 우선순위 자식이 SUCCESS를 반환하면, 해당 자식 이후에 RUNNING 상태인 모든 하위 자식에 Halt가 전파된다. 이 메커니즘은 ReactiveFallback의 반응형 우선순위 체계를 실현하는 핵심 동작으로, “더 높은 우선순위의 행동이 성공하면 더 낮은 우선순위의 진행 중인 행동을 즉시 억제한다“는 의미론을 구현한다.
이 Halt 전파는 ReactiveFallback의 Tick 알고리즘 내에서 자동으로 수행되며, 개발자가 별도로 명시적 Halt 로직을 작성할 필요가 없다. Tick 함수가 비실패(non-failure) 자식을 발견한 후, 해당 자식 이후의 모든 RUNNING 자식을 순회하며 Halt를 호출하는 것이 알고리즘의 일부로 내장되어 있다.
2. 동작 과정의 상세 추적
다음 구조에서 상위 자식 Success에 의한 하위 자식 Halt 과정을 상세히 추적하라.
ReactiveFallback
├── C_0: Sequence
│ ├── IsHighPriority
│ └── HighPriorityAction
├── C_1: Sequence
│ ├── IsMediumPriority
│ └── MediumPriorityAction
└── C_2: DefaultAction
2.1 초기 상태: DefaultAction 실행 중
Tick t:
C_0: IsHighPriority → FAILURE
C_0: Sequence → FAILURE
C_1: IsMediumPriority → FAILURE
C_1: Sequence → FAILURE
C_2: DefaultAction.tick() → RUNNING
ReactiveFallback → RUNNING
활성 상태: C_2 = RUNNING
2.2 상위 조건 성립: C_0 SUCCESS
Tick t+1:
C_0: IsHighPriority → SUCCESS ← 조건 변경
C_0: HighPriorityAction.tick() → RUNNING
C_0: Sequence → RUNNING ← 비실패 (RUNNING)
[Tick 전파 중단]
[C_2가 RUNNING 상태 → Halt 전파]
C_2: DefaultAction.halt() ← Halt 호출
C_2 상태: RUNNING → IDLE
ReactiveFallback → RUNNING
활성 상태: C_0 = RUNNING
Tick t+1에서 C_0의 Sequence가 RUNNING을 반환하자, C_1과 C_2에 대한 Tick 전파가 중단된다. C_2가 이전 Tick에서 RUNNING 상태였으므로 Halt가 전파되어 DefaultAction이 중단된다. C_1은 이전 Tick에서 FAILURE 상태였으므로 Halt 대상이 아니다.
2.3 상위 행동 완료: C_0 SUCCESS
Tick t+2:
C_0: IsHighPriority → SUCCESS
C_0: HighPriorityAction.tick() → SUCCESS ← 행동 완료
C_0: Sequence → SUCCESS
[Tick 전파 중단 (SUCCESS 발견)]
[RUNNING 상태 하위 자식 없음 → Halt 전파 불필요]
ReactiveFallback → SUCCESS
C_0의 행동이 완료되어 Sequence가 SUCCESS를 반환하면, ReactiveFallback 전체가 SUCCESS를 반환한다.
3. SUCCESS와 RUNNING의 Halt 전파 차이
상위 자식이 SUCCESS를 반환하는 경우와 RUNNING을 반환하는 경우 모두 하위 RUNNING 자식에 Halt가 전파된다. 두 경우의 차이는 ReactiveFallback 자체의 반환값에 있다.
| 상위 자식 반환값 | 하위 RUNNING 자식 Halt | ReactiveFallback 반환값 |
|---|---|---|
| SUCCESS | 전파됨 | SUCCESS |
| RUNNING | 전파됨 | RUNNING |
| FAILURE | 전파 없음 (다음 자식으로 진행) | 다음 자식에 의존 |
SUCCESS와 RUNNING 모두 하위 자식의 Halt를 유발하지만, SUCCESS는 ReactiveFallback 전체를 완료시키고, RUNNING은 다음 Tick에서 재평가를 계속하게 한다.
4. 다단계 Halt 전파
하위 자식이 단일 액션 노드가 아닌 복합 구조(Sequence, Parallel, SubTree 등)인 경우, Halt는 해당 복합 구조 내부로 재귀적으로 전파된다.
ReactiveFallback
├── C_0: IsEmergency
├── C_1: Sequence
│ ├── NavigateToGoal
│ └── Parallel
│ ├── PickObject
│ └── MonitorForce
└── C_2: IdleAction
C_0이 SUCCESS를 반환하면, C_1이 RUNNING 상태인 경우 C_1에 Halt가 전파된다. C_1(Sequence)은 내부에서 현재 활성 상태인 자식을 찾아 Halt를 재귀적으로 전파한다. 만약 Parallel 노드가 활성 상태였다면, Parallel의 Halt 핸들러가 PickObject와 MonitorForce 모두에 Halt를 전파한다.
Halt 전파 순서:
C_1.halt()
└── C_1.Sequence.halt()
└── C_1.Parallel.halt()
├── PickObject.halt()
└── MonitorForce.halt()
이 재귀적 Halt 전파에 의해, 행동 트리의 깊이와 무관하게 모든 활성 노드가 안전하게 정리된다.
5. Halt 전파 시 자원 정리
하위 자식에 Halt가 전파될 때, 해당 자식이 보유한 외부 자원(ROS2 액션 클라이언트, 타이머, 발행자 등)이 적절히 정리되어야 한다. Halt 처리가 부실하면 다음과 같은 문제가 발생할 수 있다.
-
ROS2 액션 미취소: 행동 트리에서 Halt되었으나 ROS2 액션 서버에서는 목표가 여전히 활성 상태로 남아, 로봇이 의도하지 않은 동작을 계속할 수 있다.
-
타이머 미정지: 타임아웃 타이머가 정지되지 않아 이후 재시작 시 부정확한 시간 측정이 이루어질 수 있다.
-
상태 미초기화: 내부 플래그나 카운터가 초기화되지 않아 재시작 시 비정상 동작이 발생할 수 있다.
따라서 ReactiveFallback의 자식으로 사용되는 모든 액션 노드는, Halt 핸들러 내에서 다음 사항을 반드시 처리하여야 한다.
function ActionNode.halt():
// 1. 외부 자원 정리
cancelActiveGoals()
stopTimers()
// 2. 내부 상태 초기화
resetInternalState()
// 3. 상태를 IDLE로 전환
setStatus(IDLE)
6. XML 정의에서의 구조 예시
<ReactiveFallback>
<Sequence>
<IsEmergencyDetected />
<ExecuteEmergencyProtocol />
</Sequence>
<Sequence>
<IsObstacleNear distance="{min_obstacle_dist}" />
<AvoidObstacle />
</Sequence>
<NavigateToGoal goal="{target_position}" />
</ReactiveFallback>
이 XML 구조에서 IsEmergencyDetected가 SUCCESS를 반환하면, AvoidObstacle이나 NavigateToGoal이 RUNNING 상태인 경우 자동으로 Halt가 전파된다. 개발자는 ReactiveFallback의 자식 배치 순서만으로 우선순위 체계와 Halt 전파 동작을 정의할 수 있다.