1294.101 Sequence와 Fallback 설계의 모범 사례
1. 변형 선택의 원칙
1.1 Sequence 변형 선택 기준
Sequence의 세 가지 변형(SequenceNode, ReactiveSequence, SequenceStar)은 각각 서로 다른 사용 사례에 적합하다. 올바른 변형 선택은 행동 트리 설계의 기초이다(Colledanchise & Ogren, 2018).
| 요구 사항 | 권장 변형 | 근거 |
|---|---|---|
| 순차 작업 수행, 완료된 단계 건너뛰기 | SequenceNode (WithMemory) | 메모리 기반 재진입으로 중복 실행 방지 |
| 안전 조건의 지속적 감시 하에 작업 수행 | ReactiveSequence | 매 Tick 조건 재평가로 위험 즉시 감지 |
| 실패해도 다음 단계를 계속 진행 | SequenceStar | 실패를 무시하고 다음 자식으로 진행 |
기본 원칙으로, 안전 조건의 실시간 감시가 필요하지 않은 순차 작업에는 SequenceNode를 사용하고, 안전 감시가 필요한 경우에만 ReactiveSequence를 사용한다.
1.2 Fallback 변형 선택 기준
| 요구 사항 | 권장 변형 | 근거 |
|---|---|---|
| 순차적 대안 시도, 비동기 대안 지원 | FallbackNode (WithMemory) | 메모리로 실패 대안 건너뛰기 |
| 상위 대안 가용 시 즉시 전환 | ReactiveFallback | 매 Tick 우선순위 재평가 |
2. 조건-행동 분리 패턴
2.1 조건은 앞, 행동은 뒤
Sequence에서 조건 노드를 행동 노드보다 앞에 배치한다. 조건이 FAILURE를 반환하면 비용이 큰 행동의 실행을 방지할 수 있다:
<!-- 모범 사례: 조건 선행 -->
<Sequence>
<Condition ID="IsTargetReachable"/> <!-- 경량 검사 -->
<Action ID="NavigateToTarget"/> <!-- 고비용 행동 -->
<Action ID="PickUpTarget"/>
</Sequence>
<!-- 비권장: 조건 없이 행동 직접 시작 -->
<Sequence>
<Action ID="NavigateToTarget"/> <!-- 도달 불가 시 무한 시도 -->
<Action ID="PickUpTarget"/>
</Sequence>
조건 선행 패턴은 불필요한 행동 실행을 방지하고, 실패 원인을 명확히 한다.
2.2 ReactiveSequence에서의 조건-행동 구조
<ReactiveSequence>
<!-- 안전 조건 (경량, 매 Tick 재평가) -->
<Condition ID="IsBatteryOK"/>
<Condition ID="IsCommsActive"/>
<!-- 행동 (WithMemory Sequence로 래핑) -->
<Sequence>
<Action ID="Step1"/>
<Action ID="Step2"/>
<Action ID="Step3"/>
</Sequence>
</ReactiveSequence>
외부 ReactiveSequence가 안전 조건을 매 Tick 감시하고, 내부 SequenceNode가 작업을 순차 수행한다. 안전 조건이 FAILURE를 반환하면 내부 Sequence가 Halt된다.
3. 최후 수단의 명시적 포함
3.1 Fallback의 마지막 자식에 안전 행동 배치
<!-- 모범 사례: 최후 수단 포함 -->
<Fallback>
<Action ID="PrimaryNavigation"/>
<Action ID="RecoveryNavigation"/>
<Action ID="SafeStop"/> <!-- 항상 SUCCESS 반환 -->
</Fallback>
<!-- 비권장: 최후 수단 미포함 -->
<Fallback>
<Action ID="PrimaryNavigation"/>
<Action ID="RecoveryNavigation"/>
<!-- 모두 실패 시 FAILURE 전파 → 상위에서 처리 불가 가능 -->
</Fallback>
안전이 중요한 시스템에서는 Fallback의 마지막 자식에 반드시 성공하는 안전 행동(예: 정지, 홈 복귀)을 배치한다. 이는 모든 대안이 소진되어도 안전한 상태를 보장한다(Faconti, 2022).
4. 자식 수의 적정 관리
4.1 권장 자식 수
Sequence: 3-7개
이유: 과도한 자식은 가독성 저하와 디버깅 어려움 유발
Fallback: 2-5개
이유: 대안이 많으면 최악 Tick 시간 증가
ReactiveSequence: 조건 2-3개 + 행동 1개(또는 내부 Sequence 1개)
이유: 매 Tick 재평가되므로 조건 수 최소화
4.2 서브트리 분해
자식 수가 권장 범위를 초과하면 논리적 단위로 서브트리를 분해한다:
<!-- 분해 전 -->
<Sequence>
<Action ID="Init1"/><Action ID="Init2"/><Action ID="Init3"/>
<Action ID="Nav1"/><Action ID="Nav2"/>
<Action ID="Task1"/><Action ID="Task2"/><Action ID="Task3"/>
<Action ID="Report"/>
</Sequence>
<!-- 분해 후 -->
<Sequence>
<SubTree ID="Initialization"/> <!-- Init1-3 -->
<SubTree ID="Navigation"/> <!-- Nav1-2 -->
<SubTree ID="TaskExecution"/> <!-- Task1-3 -->
<Action ID="Report"/>
</Sequence>
5. 목표 확인 패턴의 활용
5.1 Fallback으로 중복 실행 방지
<Fallback>
<Condition ID="IsAtDestination"/> <!-- 이미 목적지에 있는가? -->
<Action ID="NavigateToDestination"/> <!-- 아니면 이동 -->
</Fallback>
이 패턴은 목표가 이미 달성되었으면 행동을 건너뛴다. 반복적으로 Tick되는 행동 트리에서 불필요한 재실행을 방지하는 기본적이면서 중요한 패턴이다.
6. 데코레이터의 적절한 활용
6.1 재시도 횟수 제한
<Fallback>
<Retry num_attempts="3">
<Action ID="PrimaryMethod"/>
</Retry>
<Action ID="AlternativeMethod"/>
</Fallback>
PrimaryMethod를 최대 3회 재시도하고, 여전히 실패하면 AlternativeMethod로 전환한다. Retry 데코레이터는 동일한 Fallback 대안을 반복하는 것보다 간결하다.
6.2 타임아웃 적용
<Sequence>
<Timeout msec="10000">
<Action ID="NavigateToPose"/>
</Timeout>
<Action ID="PerformTask"/>
</Sequence>
비동기 행동에 타임아웃을 적용하여, 무한 대기를 방지한다. 타임아웃 초과 시 FAILURE를 반환하고 Sequence가 중단된다.
6.3 ForceSuccess를 통한 선택적 단계 처리
<Sequence>
<Action ID="CriticalStep"/>
<ForceSuccess>
<Action ID="OptionalLogging"/> <!-- 실패해도 무시 -->
</ForceSuccess>
<Action ID="NextCriticalStep"/>
</Sequence>
필수가 아닌 단계에 ForceSuccess를 적용하면, 해당 단계의 실패가 Sequence 전체를 중단시키지 않는다.
7. 블랙보드 데이터 흐름 설계
7.1 명시적 포트 연결
<Sequence>
<Action ID="DetectObject"
detected_pose="{object_pose}"/> <!-- 출력 -->
<Action ID="NavigateToObject"
target_pose="{object_pose}"/> <!-- 입력 -->
<Action ID="GraspObject"
grasp_pose="{object_pose}"/> <!-- 입력 -->
</Sequence>
Sequence 내 노드 간의 데이터 전달은 블랙보드 포트를 통해 명시적으로 연결한다. 포트 이름을 통해 데이터 흐름이 XML에서 직접 파악 가능하다.
8. 네이밍 규칙
8.1 제어 노드의 이름 부여
<Sequence name="patrol_sequence">
<Action ID="NavigateToWaypoint"/>
<Action ID="InspectArea"/>
<Action ID="ReportStatus"/>
</Sequence>
<Fallback name="navigation_with_recovery">
<Action ID="NavigateDirectly"/>
<Action ID="NavigateWithDetour"/>
<Action ID="SafeStop"/>
</Fallback>
제어 노드의 name 속성에 의미 있는 이름을 부여한다. 이 이름은 로그와 디버깅 도구에서 표시되므로, 노드의 의도를 한눈에 파악할 수 있도록 명명한다.
9. 테스트 가능성을 고려한 설계
9.1 모의 노드 교체 가능한 구조
<Sequence>
<SubTree ID="PreConditions"/> <!-- 테스트 시 모의 서브트리 교체 -->
<SubTree ID="MainAction"/> <!-- 테스트 시 모의 서브트리 교체 -->
<SubTree ID="PostProcessing"/>
</Sequence>
서브트리 단위로 분해된 구조는 단위 테스트에서 특정 서브트리를 모의(Mock) 서브트리로 교체하여 격리된 테스트를 수행할 수 있다.
9.2 단일 Tick 테스트 가능한 조건 분리
조건 노드를 행동 노드와 분리하면, 조건의 반환 상태만 변경하여 다양한 제어 흐름을 테스트할 수 있다.
10. 설계 검토 항목
Sequence와 Fallback을 설계한 후 다음 항목을 검토한다:
- 변형 적합성: 선택한 변형(Memory vs Reactive)이 사용 사례에 적합한가.
- 조기 종료 경로: FAILURE 또는 SUCCESS의 조기 종료가 발생하는 모든 경로를 식별하고, 각 경로에서 시스템이 안전한 상태를 유지하는가.
- Halt 안전성: 모든 자식 노드의
onHalted()구현이 안전하고 경량인가. - 최후 수단: 안전 관련 Fallback에 항상 성공하는 최후 수단이 포함되어 있는가.
- 자식 순서: Sequence의 자식 순서가 의존 관계를 올바르게 반영하고, Fallback의 자식 순서가 우선순위를 올바르게 반영하는가(Colledanchise & Ogren, 2018).
참고 문헌
- 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/