1296.8 동기 액션 노드의 실행 모델

1. 실행 모델의 개요

동기 액션 노드(SyncActionNode)의 실행 모델은 단일 호출-반환(call-return) 패턴이다. 부모 제어 노드가 executeTick()을 호출하면, 동기 액션 노드는 tick() 메서드를 실행하고 SUCCESS 또는 FAILURE를 즉시 반환한다. 이 과정에서 상태 전이는 IDLE → SUCCESS(또는 FAILURE) → IDLE의 단순한 형태를 따른다.

2. 상태 전이 모델

동기 액션 노드의 상태 전이를 형식적으로 표현하면 다음과 같다.

                   tick() 호출
        IDLE ─────────────────────→ SUCCESS
                                         │
                    부모에 의한 초기화    │
        IDLE ←───────────────────────────┘
        
                   tick() 호출
        IDLE ─────────────────────→ FAILURE
                                         │
                    부모에 의한 초기화    │
        IDLE ←───────────────────────────┘

RUNNING 상태를 거치지 않으므로, 상태 전이 다이어그램이 극도로 단순하다. 이 단순성이 동기 액션 노드의 설계와 테스트를 용이하게 한다.

3. executeTick()의 내부 동작

BehaviorTree.CPP에서 SyncActionNode::executeTick()의 내부 동작 순서를 상세히 기술한다.

NodeStatus SyncActionNode::executeTick()
{
    // 1. 사전 조건 검사 (4.x의 PreCondition 메커니즘)
    auto pre_result = checkPreConditions();
    if (pre_result.has_value())
        return pre_result.value();

    // 2. 상태를 RUNNING으로 임시 설정 (내부 용도)
    setStatus(NodeStatus::RUNNING);

    // 3. tick() 호출
    NodeStatus status = tick();

    // 4. RUNNING 반환 검사
    if (status == NodeStatus::RUNNING)
    {
        throw LogicError(
            "SyncActionNode MUST NOT return RUNNING");
    }

    // 5. 상태 설정 및 반환
    setStatus(status);

    // 6. 사후 조건 검사 (4.x의 PostCondition 메커니즘)
    auto post_result = checkPostConditions(status);
    if (post_result.has_value())
        return post_result.value();

    return status;
}

단계 2에서 상태를 RUNNING으로 임시 설정하는 것은 로거가 상태 변화(IDLE → RUNNING → SUCCESS/FAILURE)를 추적할 수 있도록 하기 위한 내부 메커니즘이다. 외부에서 관찰하면 RUNNING 상태는 순간적이며, executeTick() 반환 시 이미 SUCCESS 또는 FAILURE로 전이된 상태이다.

4. 단일 Tick 실행 시맨틱

동기 액션 노드의 단일 Tick 실행 시맨틱은 다음과 같이 정의한다.

  1. 원자성(Atomicity): tick() 메서드의 실행은 원자적이다. 메서드 진입부터 반환까지 다른 노드가 개입할 수 없다.

  2. 결정론성(Determinism): 동일한 입력(블랙보드 상태, 포트 값)에 대해 동일한 결과를 반환하여야 한다. 비결정론적 동작(난수, 시간 의존 등)은 가능하지만, 가능한 한 입력에 의해 결과가 결정되도록 설계하여야 한다.

  3. 무상태성(Statelessness) 경향: 동기 액션 노드는 이전 Tick의 상태를 유지할 필요가 없는 경우가 많다. 각 tick() 호출이 독립적으로 완료되므로, 내부 상태 없이 입력 포트만으로 동작을 결정하는 순수 함수적 설계가 자연스럽다.

5. Sequence에서의 연속 실행

동기 액션 노드의 실행 모델이 Sequence에서 어떻게 작용하는지를 구체적 예시로 설명한다.

<Sequence>
    <SyncAction_A/>  <!-- SUCCESS -->
    <SyncAction_B/>  <!-- SUCCESS -->
    <SyncAction_C/>  <!-- FAILURE -->
    <SyncAction_D/>  <!-- 실행되지 않음 -->
</Sequence>

단일 Tick에서의 실행 순서이다.

  1. Sequence가 SyncAction_A를 Tick → SUCCESS 반환
  2. Sequence가 SyncAction_B를 Tick → SUCCESS 반환
  3. Sequence가 SyncAction_C를 Tick → FAILURE 반환
  4. Sequence가 FAILURE 반환 (SyncAction_D는 Tick되지 않음)

이 모든 과정이 단일 Tick 내에서 발생한다. 세 개의 SyncActionNode가 하나의 Tick에서 순차적으로 실행되었다.

6. 반복 실행에서의 동작

제어 노드에 의해 동기 액션 노드가 반복적으로 Tick되는 경우, 매 Tick에서 독립적으로 실행된다.

<ReactiveSequence>
    <IsLocalized/>         <!-- 조건: 매 Tick 재평가 -->
    <PublishHeartbeat/>    <!-- SyncAction: 매 Tick 실행 -->
    <NavigateToGoal/>      <!-- StatefulAction: RUNNING 유지 -->
</ReactiveSequence>

ReactiveSequence에서 PublishHeartbeat은 매 Tick마다 재실행된다. 동기 액션은 RUNNING 상태를 유지하지 않으므로, 매 Tick에서 IDLE → SUCCESS의 전이를 반복한다. 이는 심박(heartbeat) 메시지 발행이나 상태 갱신 등의 주기적 작업에 적합한 패턴이다.

7. 실행 모델의 제약

동기 액션 노드의 실행 모델은 다음의 제약을 수반한다.

  1. 차단 호출 금지: tick() 내부에서 네트워크 응답 대기, 파일 I/O 대기 등의 차단 호출을 수행하면 전체 트리의 Tick이 지연된다.

  2. 장시간 연산 회피: CPU 집약적 연산(경로 계획, 이미지 처리 등)을 tick() 내부에서 수행하면 Tick 주기를 위반할 수 있다.

  3. 진행 상태 추적 불가: 행동의 중간 진행 상태를 표현할 수 없다. 진행 상태 추적이 필요한 행동은 StatefulActionNode로 구현하여야 한다.

이러한 제약은 동기 액션 노드의 적용 범위를 경량 작업으로 한정하며, 이 한정이 오히려 설계의 명확성과 안전성을 보장한다.