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을 설계한 후 다음 항목을 검토한다:

  1. 변형 적합성: 선택한 변형(Memory vs Reactive)이 사용 사례에 적합한가.
  2. 조기 종료 경로: FAILURE 또는 SUCCESS의 조기 종료가 발생하는 모든 경로를 식별하고, 각 경로에서 시스템이 안전한 상태를 유지하는가.
  3. Halt 안전성: 모든 자식 노드의 onHalted() 구현이 안전하고 경량인가.
  4. 최후 수단: 안전 관련 Fallback에 항상 성공하는 최후 수단이 포함되어 있는가.
  5. 자식 순서: 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/