Chapter 1298. 데코레이터 노드 활용 (Decorator Node Usage)

Chapter 1298. 데코레이터 노드 활용 (Decorator Node Usage)

1. 개요

데코레이터 노드(decorator node)는 행동 트리에서 단일 자식 노드를 감싸(wrap) 해당 자식의 실행 방식이나 반환 상태를 수정하는 제어 노드이다. 데코레이터는 구조적 디자인 패턴(structural design pattern)에서 유래한 개념으로, 자식 노드의 코드를 수정하지 않고도 실행 횟수 제한, 결과 반전, 타임아웃 적용, 지연 삽입 등의 부가적 동작을 추가할 수 있다. 행동 트리의 제어 흐름을 세밀하게 조정하는 데 필수적인 역할을 수행하며, BehaviorTree.CPP 라이브러리는 다양한 기본 데코레이터와 사용자 정의 데코레이터 구현을 지원한다.

2. 데코레이터 노드의 특성

2.1 구조적 특성

데코레이터 노드는 정확히 하나의 자식 노드만을 가진다. 이 점에서 복수의 자식을 가지는 제어 노드(Sequence, Fallback, Parallel)와 구분되며, 자식이 없는 리프 노드(조건 노드, 액션 노드)와도 구분된다.

노드 유형자식 수역할
제어 노드\geq 2복수 자식의 실행 순서와 흐름 제어
데코레이터 노드= 1단일 자식의 동작 수정
리프 노드= 0실제 조건 평가 또는 행동 실행

2.2 동작 원리

데코레이터는 자식 노드의 tick을 중개(intercept)하여, tick 전 또는 후에 추가 로직을 삽입하거나, 자식의 반환 상태를 변환한다.

[데코레이터의 tick() 호출]
    → [전처리 로직 (선택)]
    → [자식 노드의 tick() 호출]
    → [자식의 반환 상태 수신]
    → [후처리 로직: 반환 상태 변환 (선택)]
    → [최종 반환 상태 반환]

3. BehaviorTree.CPP의 기본 데코레이터

3.1 상태 변환 데코레이터

데코레이터동작용도
InverterSUCCESS↔FAILURE 반전NOT 조건 구현
ForceSuccess항상 SUCCESS 반환자식 실패 무시
ForceFailure항상 FAILURE 반환강제 실패

3.2 실행 제어 데코레이터

데코레이터동작용도
RetryNode자식이 FAILURE 시 재시도오류 복구
RepeatNode자식이 SUCCESS 시 반복반복 실행
RunOnce자식을 한 번만 실행초기화 작업

3.3 시간 제어 데코레이터

데코레이터동작용도
TimeoutNode자식의 실행 시간 제한무한 실행 방지
Delay자식 실행 전 지연실행 간격 조절
RateController자식의 tick 빈도 제한성능 최적화

3.4 조건부 실행 데코레이터

데코레이터동작용도
KeepRunningUntilFailure자식이 FAILURE가 될 때까지 RUNNING 유지지속 실행
SkipUnlessUpdated입력 포트 변경 시에만 자식 실행불필요한 실행 회피

4. 데코레이터의 구현 구조

4.1 DecoratorNode 기반 클래스

BehaviorTree.CPP에서 모든 데코레이터는 BT::DecoratorNode 클래스를 상속한다.

class MyDecorator : public BT::DecoratorNode
{
public:
    MyDecorator(const std::string& name,
                const BT::NodeConfiguration& config)
        : BT::DecoratorNode(name, config)
    {}

    static BT::PortsList providedPorts()
    {
        return {/* 입력 포트 정의 */};
    }

    BT::NodeStatus tick() override
    {
        // 자식 노드 tick
        BT::NodeStatus child_status = child_node_->executeTick();

        // 반환 상태 변환 로직
        return transformStatus(child_status);
    }
};

5. 실용적 활용 패턴

5.1 Inverter를 활용한 조건 반전

<Inverter>
    <Condition ID="IsObstacleDetected"/>
</Inverter>

5.2 RetryNode를 활용한 오류 복구

<RetryNode num_attempts="3">
    <Action ID="ConnectToServer"/>
</RetryNode>

서버 연결에 실패하면 최대 3회 재시도한다.

5.3 TimeoutNode를 활용한 실행 시간 제한

<Timeout msec="30000">
    <Action ID="NavigateToGoal"/>
</Timeout>

내비게이션이 30초 이내에 완료되지 않으면 FAILURE를 반환한다.

5.4 RateController를 활용한 평가 빈도 제한

<RateController hz="1.0">
    <Condition ID="IsPathValid"/>
</RateController>

경로 유효성 확인을 초당 1회로 제한하여 서비스 호출 빈도를 줄인다.

5.5 RepeatNode를 활용한 반복 작업

<Repeat num_cycles="5">
    <Action ID="PatrolWaypoint"/>
</Repeat>

순찰 웨이포인트를 5회 반복 방문한다.

6. 데코레이터의 조합

데코레이터는 중첩하여 복합적인 동작을 구현할 수 있다.

<Timeout msec="60000">
    <RetryNode num_attempts="5">
        <Sequence>
            <Action ID="PlanPath"/>
            <Action ID="FollowPath"/>
        </Sequence>
    </RetryNode>
</Timeout>

전체 60초 이내에서, 경로 계획과 추종을 최대 5회 재시도한다.

7. 사용자 정의 데코레이터

7.1 로깅 데코레이터

자식 노드의 실행 결과를 로깅하는 데코레이터이다.

class LoggingDecorator : public BT::DecoratorNode
{
public:
    LoggingDecorator(const std::string& name,
                     const BT::NodeConfiguration& config)
        : BT::DecoratorNode(name, config)
    {}

    BT::NodeStatus tick() override
    {
        auto status = child_node_->executeTick();
        std::cout << "[" << name() << "] child returned: "
                  << BT::toStr(status) << std::endl;
        return status;
    }
};

7.2 쿨다운 데코레이터

자식이 실행된 후 일정 시간 동안 재실행을 방지하는 데코레이터이다.

class CooldownDecorator : public BT::DecoratorNode
{
public:
    CooldownDecorator(const std::string& name,
                      const BT::NodeConfiguration& config)
        : BT::DecoratorNode(name, config),
          last_success_time_(std::chrono::steady_clock::time_point::min())
    {}

    static BT::PortsList providedPorts()
    {
        return {BT::InputPort<double>("cooldown_sec", 5.0,
                "쿨다운 시간 (초)")};
    }

    BT::NodeStatus tick() override
    {
        double cooldown;
        getInput("cooldown_sec", cooldown);

        auto now = std::chrono::steady_clock::now();
        auto elapsed = std::chrono::duration<double>(
            now - last_success_time_).count();

        if (elapsed < cooldown)
        {
            return BT::NodeStatus::FAILURE;
        }

        auto status = child_node_->executeTick();
        if (status == BT::NodeStatus::SUCCESS)
        {
            last_success_time_ = now;
        }
        return status;
    }

private:
    std::chrono::steady_clock::time_point last_success_time_;
};

8. 설계 시 고려 사항

8.1 데코레이터와 제어 노드의 구분

데코레이터는 단일 자식에 대한 동작 수정에 사용하고, 복수 자식 간의 흐름 제어에는 제어 노드를 사용한다. 데코레이터를 사용하여 복수 자식 간의 관계를 표현하려는 시도는 안티패턴이다.

8.2 데코레이터 중첩의 가독성

과도한 데코레이터 중첩은 행동 트리의 가독성을 저하시킨다. 3단계 이상의 중첩이 필요한 경우, 서브트리로 분리하여 가독성을 유지한다.

8.3 halt()의 전파

데코레이터가 halt될 때, 자식 노드에도 halt를 전파하여야 한다. BehaviorTree.CPP의 DecoratorNode 기반 클래스는 이를 자동으로 처리한다.

9. 참고 문헌

  • Colledanchise, M., & Ogren, P. (2018). Behavior Trees in Robotics and AI: An Introduction. CRC Press.
  • Gamma, E., et al. (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley.
  • BehaviorTree.CPP 공식 문서. https://www.behaviortree.dev/

버전날짜변경 사항
v0.12026-04-04초안 작성