1296.17 비동기 액션 노드의 상태 기계적 생명주기

1296.17 비동기 액션 노드의 상태 기계적 생명주기

1. 상태 기계로서의 StatefulActionNode

StatefulActionNode의 생명주기는 유한 상태 기계(Finite State Machine, FSM)로 형식화할 수 있다. 이 상태 기계는 세 가지 상태(IDLE, RUNNING, TERMINAL)와 이들 간의 전이로 구성되며, 각 전이에는 특정 콜백의 호출이 연관된다. “StatefulActionNode“라는 이름 자체가 이 노드가 상태(state)를 유지하며, 상태에 따라 다른 콜백을 호출하는 상태 기반(stateful) 설계임을 명시한다.

2. 상태 공간의 정의

StatefulActionNode의 상태 공간을 형식적으로 정의한다.

S = \{IDLE, RUNNING, SUCCESS, FAILURE\}

실질적으로 SUCCESS와 FAILURE는 최종 상태(terminal state)이며, 부모 노드에 의해 IDLE로 초기화되는 과도적(transient) 성격을 가진다. 따라서 지속적 상태(persistent state)는 IDLE과 RUNNING 두 가지이다.

상태 전이 다이어그램

StatefulActionNode의 완전한 상태 전이 다이어그램을 제시한다.

                    onStart() → RUNNING
              ┌─────────────────────────────┐
              │                             ▼
           ┌──┴──┐                     ┌─────────┐
           │IDLE │                     │ RUNNING │◄──────┐
           └──┬──┘                     └────┬────┘      │
              ▲                             │           │
              │                             │  onRunning() → RUNNING
              │                             │           │
              │    onStart() → SUCCESS      └───────────┘
              │◄────────────────────┐
              │                     │       │
              │    onStart() → FAILURE      │  onRunning() → SUCCESS
              │◄────────────────────┘       │
              │                             ▼
              │                        ┌─────────┐
              │◄───────────────────────┤ SUCCESS │
              │    부모에 의한 초기화   └─────────┘
              │
              │                        ┌─────────┐
              │◄───────────────────────┤ FAILURE │
              │    부모에 의한 초기화   └─────────┘
              │
              │    halt() → onHalted()
              │◄───────────────────────── RUNNING

상태 전이의 형식적 정의

상태 전이를 5-튜플 (S, \Sigma, \delta, s_0, F)로 형식화한다.

  • S = \{IDLE, RUNNING, SUCCESS, FAILURE\}: 상태 집합
  • \Sigma = \{tick, halt, reset\}: 입력 알파벳
  • \delta: S \times \Sigma \rightarrow S: 전이 함수
  • s_0 = IDLE: 초기 상태
  • F = \{SUCCESS, FAILURE\}: 최종 상태 집합

전이 함수 \delta를 표로 정리한다.

현재 상태입력조건다음 상태호출되는 콜백
IDLEtickonStart() → RUNNINGRUNNINGonStart()
IDLEtickonStart() → SUCCESSSUCCESSonStart()
IDLEtickonStart() → FAILUREFAILUREonStart()
RUNNINGtickonRunning() → RUNNINGRUNNINGonRunning()
RUNNINGtickonRunning() → SUCCESSSUCCESSonRunning()
RUNNINGtickonRunning() → FAILUREFAILUREonRunning()
RUNNINGhaltIDLEonHalted()
SUCCESSresetIDLE
FAILUREresetIDLE

reset은 부모 제어 노드가 자식의 상태를 IDLE로 초기화하는 연산이다.

생명주기의 단계별 분석

시작 단계 (IDLE → RUNNING)

생명주기의 시작 단계에서 노드는 IDLE 상태에서 첫 번째 Tick을 받는다.

[IDLE] ──tick──→ onStart() 호출
                    │
                    ├── RUNNING 반환 → [RUNNING] (다중 Tick 실행 시작)
                    ├── SUCCESS 반환 → [SUCCESS] (즉시 완료)
                    └── FAILURE 반환 → [FAILURE] (즉시 실패)

onStart()에서의 작업은 다음과 같다.

  • 입력 포트 데이터 읽기
  • 멤버 변수 초기화
  • 외부 시스템에 비동기 작업 요청 전송
  • 반환값 결정

실행 단계 (RUNNING ↔ RUNNING)

실행 단계는 노드가 RUNNING 상태를 유지하는 기간이다.

[RUNNING] ──tick──→ onRunning() 호출
                       │
                       ├── RUNNING 반환 → [RUNNING] (계속 실행)
                       ├── SUCCESS 반환 → [SUCCESS] (정상 완료)
                       └── FAILURE 반환 → [FAILURE] (실행 실패)

실행 단계에서의 작업은 다음과 같다.

  • 외부 행동의 완료 여부 비차단 확인
  • 피드백 정보 갱신
  • 타임아웃 검사
  • 오류 상태 검출

이 단계는 RUNNING이 반환되는 한 매 Tick마다 반복된다. Tick 주기가 100 ms이고 행동이 5초간 지속되면, onRunning()은 약 50회 호출된다.

종료 단계 (RUNNING → SUCCESS/FAILURE)

정상 종료 단계에서 onRunning()이 SUCCESS 또는 FAILURE를 반환하면 행동이 완료된다.

[RUNNING] ──tick──→ onRunning()
                       │
                       └── SUCCESS/FAILURE 반환
                            │
                            ▼
                       [SUCCESS/FAILURE]
                            │
                            └── 부모에 의한 reset
                                 │
                                 ▼
                              [IDLE]

종료 후 부모 제어 노드는 자식의 상태를 IDLE로 초기화한다. Sequence에서 자식이 SUCCESS를 반환하면, Sequence는 다음 자식으로 진행하기 전에 현재 자식을 IDLE로 초기화한다.

중단 단계 (RUNNING → IDLE)

외부 Halt에 의한 중단 단계에서 onHalted()가 호출되고 상태가 IDLE로 전이한다.

[RUNNING] ──halt──→ onHalted() 호출
                       │
                       └── 자원 정리, 외부 작업 취소
                            │
                            ▼
                         [IDLE]

중단 후 노드는 IDLE로 돌아가며, 다시 Tick되면 onStart()부터 새로운 생명주기가 시작된다.

생명주기의 반복

StatefulActionNode의 생명주기는 반복될 수 있다. 하나의 생명주기가 종료(정상 완료 또는 Halt)된 후, 노드가 다시 Tick되면 새로운 생명주기가 시작된다.

생명주기 1: IDLE → onStart() → RUNNING → ... → onRunning() → SUCCESS → IDLE
생명주기 2: IDLE → onStart() → RUNNING → ... → halt → onHalted() → IDLE
생명주기 3: IDLE → onStart() → FAILURE → IDLE

각 생명주기는 독립적이며, 이전 생명주기의 상태는 새로운 생명주기에 영향을 미치지 않아야 한다. 이 독립성은 onStart()에서 모든 관련 멤버 변수를 초기화함으로써 보장된다.

ROS2 액션과의 생명주기 대응

StatefulActionNode의 상태 기계적 생명주기는 ROS2 액션의 상태 기계와 구조적으로 대응된다.

StatefulActionNodeROS2 액션 상태
IDLE— (목표 미전송)
onStart() 실행ACCEPTED (목표 수락)
RUNNINGEXECUTING (실행 중)
SUCCESSSUCCEEDED (성공)
FAILUREABORTED (중단)
onHalted() 실행CANCELING → CANCELED (취소)

이 대응은 완전히 일대일은 아니나, 행동 트리의 비동기 액션 노드가 ROS2 액션의 생명주기를 자연스럽게 래핑할 수 있음을 보여준다. ROS2 액션의 ACCEPTED 상태는 onStart()에서 목표를 전송한 직후에 대응하며, EXECUTING은 onRunning()이 반복 호출되는 RUNNING 상태에 대응한다.

상태 전이의 불변 조건

StatefulActionNode의 상태 전이에는 다음의 불변 조건(invariant)이 유지된다.

  1. IDLE에서의 전이 유일성: IDLE 상태에서의 전이는 tick 입력에 의해서만 발생하며, onStart()가 호출된다. haltreset은 IDLE 상태에서 무시된다.

  2. RUNNING에서의 전이 이원성: RUNNING 상태에서의 전이는 tick 입력(→ onRunning()) 또는 halt 입력(→ onHalted())에 의해 발생한다. 두 입력이 동시에 발생하는 것은 단일 스레드 실행 모델에서 불가능하다.

  3. SUCCESS/FAILURE의 과도성: SUCCESS와 FAILURE 상태는 부모 노드에 의해 즉시 또는 다음 Tick에서 IDLE로 초기화된다. 이 상태에서 tick이 호출되면 현재 상태가 그대로 반환된다.

  4. IDLE 반환 금지: onStart()onRunning() 모두 IDLE을 반환할 수 없다. IDLE은 상태 기계의 초기 상태이지 전이의 결과가 아니다.

  5. 콜백 호출 순서: onStart()onRunning() (0회 이상) → (onHalted() 또는 종료)의 순서가 보장된다. onStart() 없이 onRunning()이 호출되거나, 종료 후 onRunning()이 호출되는 것은 불가능하다.

상태 기계와 콜백의 결합 모델

StatefulActionNode의 상태 기계는 콜백의 호출을 통해 구현된다. executeTick() 메서드가 상태 기계의 전이 함수 역할을 수행하며, 현재 상태에 따라 적절한 콜백을 선택한다.

// executeTick()는 상태 기계의 전이 함수
NodeStatus executeTick() {
    if (status() == IDLE)
        return transition_from_idle();   // onStart() 호출
    if (status() == RUNNING)
        return transition_from_running(); // onRunning() 호출
    return status(); // SUCCESS/FAILURE: 현재 상태 유지
}

// halt()는 RUNNING에서의 강제 전이
void halt() {
    if (status() == RUNNING)
        onHalted();  // 중단 콜백
    setStatus(IDLE); // IDLE로 강제 전이
}

이 결합 모델은 상태 기계의 형식적 엄밀성과 콜백 패턴의 구현 용이성을 동시에 제공한다. 개발자는 상태 전이 로직을 직접 구현하지 않고, 각 상태에서 수행할 행동(콜백)만 정의하면 된다.

동기 액션 노드의 생명주기와의 비교

동기 액션 노드의 생명주기는 퇴화된(degenerate) 상태 기계로 볼 수 있다.

특성SyncActionNodeStatefulActionNode
상태 수2 (IDLE, TERMINAL)4 (IDLE, RUNNING, SUCCESS, FAILURE)
전이 수28
최대 Tick/생명주기1무제한
Halt 처리불필요필수
콜백 수1 (tick)3 (onStart, onRunning, onHalted)

동기 액션 노드는 RUNNING 상태가 없으므로 생명주기가 단일 Tick에서 시작과 종료가 동시에 이루어지는 극도로 단순한 형태이다. 비동기 액션 노드의 생명주기는 RUNNING 상태의 도입으로 시간적 확장이 가능하며, 이에 따라 Halt 처리, 다중 Tick 폴링, 상태 보존 등의 추가적 복잡성이 발생한다.

학술적 배경

행동 트리 노드의 상태 전이 모델은 Colledanchise와 Ögren(2018)의 저서 “Behavior Trees in Robotics and AI: An Introduction“에서 형식적으로 정의되었다. 이들은 행동 트리의 각 노드를 반환 상태에 의해 제어되는 상태 기계로 모델링하였으며, RUNNING 상태를 “행동이 아직 결과를 결정하지 못한 상태“로 정의하였다. BehaviorTree.CPP 4.x의 StatefulActionNode는 이 형식적 모델을 콜백 기반 인터페이스로 구체화한 구현이며, Faconti(2022)는 이 설계가 3.x의 스레드 기반 모델보다 상태 전이의 예측 가능성과 디버깅의 용이성에서 우위에 있음을 제시하였다.