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의 재진입 비교

특성WithMemoryWithoutMemory (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/