1294.17 SequenceWithMemory의 실행 알고리즘

1. 알고리즘 개요

SequenceWithMemory의 실행 알고리즘은 기본 Sequence 알고리즘에 재진입 메커니즘을 추가한 것이다. 핵심은 내부 상태 변수 current_child_idx를 유지하여, 이전 Tick에서 RUNNING을 반환한 자식의 위치를 기억하고 다음 Tick에서 해당 위치부터 실행을 재개하는 것이다(Colledanchise & Ogren, 2018).

2. 알고리즘의 형식적 기술

2.1 입력과 출력

  • 입력: 자식 노드 배열 C_0, C_1, \ldots, C_{N-1}
  • 내부 상태: current_child_idx (초기값 0)
  • 출력: SUCCESS, FAILURE, 또는 RUNNING

2.2 단계별 절차

Algorithm: SequenceWithMemory.tick()

Input: children[0..N-1], current_child_idx
Output: NodeStatus

1.  i ← current_child_idx
2.  while i < N do
3.      status ← children[i].executeTick()
4.      
5.      if status = SUCCESS then
6.          i ← i + 1
7.      else if status = RUNNING then
8.          current_child_idx ← i
9.          return RUNNING
10.     else if status = FAILURE then
11.         haltChildren(0, N-1)
12.         current_child_idx ← 0
13.         return FAILURE
14.     end if
15. end while
16. 
17. haltChildren(0, N-1)
18. current_child_idx ← 0
19. return SUCCESS

3. 알고리즘의 상세 분석

3.1 초기 진입 (current_child_idx = 0)

Sequence가 처음 Tick될 때(또는 이전 실행이 완료된 후 다시 Tick될 때), current_child_idx는 0이므로 첫 번째 자식부터 실행한다.

최초 Tick:
  i = 0 → children[0].tick()
  결과에 따라 진행 또는 반환

3.2 재진입 (current_child_idx > 0)

이전 Tick에서 자식 C_k가 RUNNING을 반환한 경우, current_child_idxk로 설정되어 있다. 다음 Tick에서 C_k부터 실행을 재개한다. C_0부터 C_{k-1}까지는 평가되지 않는다.

재진입 (current_child_idx = 2):
  i = 2 → children[2].tick()
  children[0], children[1]은 건너뜀

3.3 SUCCESS 처리 (행 5-6)

자식이 SUCCESS를 반환하면 인덱스를 증가시켜 다음 자식으로 이동한다. 이 과정은 동일한 Tick 내에서 연속적으로 수행된다. 즉, 한 자식이 SUCCESS를 반환하면 즉시 다음 자식에게 Tick이 전달된다.

3.4 RUNNING 처리 (행 7-9)

자식이 RUNNING을 반환하면 해당 인덱스를 current_child_idx에 저장하고 RUNNING을 반환한다. 이 시점에서 Tick이 종료되며, 이후의 자식에게는 Tick이 전달되지 않는다.

3.5 FAILURE 처리 (행 10-13)

자식이 FAILURE를 반환하면, 모든 자식을 Halt하고 current_child_idx를 0으로 초기화한 후 FAILURE를 반환한다. Halt는 RUNNING 상태인 자식의 비동기 작업을 중단하고 상태를 IDLE로 초기화한다.

3.6 전체 완료 처리 (행 17-19)

모든 자식이 SUCCESS를 반환하여 루프가 완료되면, 모든 자식을 Halt(IDLE로 초기화)하고 current_child_idx를 0으로 초기화한 후 SUCCESS를 반환한다.

4. 실행 추적 예시

4.1 개 자식, 자식 1과 3이 비동기

<Sequence>
    <Condition ID="CondA"/>        <!-- 자식 0: 동기 -->
    <Action ID="AsyncActB"/>       <!-- 자식 1: 비동기 -->
    <Condition ID="CondC"/>        <!-- 자식 2: 동기 -->
    <Action ID="AsyncActD"/>       <!-- 자식 3: 비동기 -->
</Sequence>
Tick 1 (current_child_idx = 0):
  i=0: CondA → SUCCESS, i=1
  i=1: AsyncActB → RUNNING
  current_child_idx = 1, return RUNNING

Tick 2 (current_child_idx = 1):
  i=1: AsyncActB → RUNNING
  return RUNNING

Tick 3 (current_child_idx = 1):
  i=1: AsyncActB → SUCCESS, i=2
  i=2: CondC → SUCCESS, i=3
  i=3: AsyncActD → RUNNING
  current_child_idx = 3, return RUNNING

Tick 4 (current_child_idx = 3):
  i=3: AsyncActD → SUCCESS, i=4
  루프 종료, current_child_idx = 0, return SUCCESS

4.2 중간 FAILURE 발생

Tick 1 (current_child_idx = 0):
  i=0: CondA → SUCCESS, i=1
  i=1: AsyncActB → RUNNING
  current_child_idx = 1, return RUNNING

Tick 2 (current_child_idx = 1):
  i=1: AsyncActB → FAILURE
  haltChildren(), current_child_idx = 0, return FAILURE

5. 시간 복잡도

상황단일 Tick 복잡도
재진입 후 RUNNING 유지O(1)
재진입 후 SUCCESS → 다음도 즉시 완료O(N - k) (k는 재진입 인덱스)
최초 진입, 첫 자식 FAILUREO(1)
모든 자식 즉시 SUCCESSO(N)

WithMemory의 재진입에 의해, RUNNING 상태가 지속되는 동안의 Tick 비용은 O(1)로 최소화된다. 이는 ReactiveSequence의 O(k+1) (처음부터 재평가)와 대비되는 효율성이다(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/