데코레이터 노드의 동작 원리 (Operating Principles of Decorator Nodes)
1. 개요
데코레이터 노드의 동작 원리는 자식 노드의 tick을 중개(intercept)하여, 자식의 실행 전·후에 추가 로직을 삽입하거나 반환 상태를 변환하는 메커니즘에 기반한다. 본 절에서는 데코레이터의 tick 전파 과정, 상태 변환 메커니즘, halt 전파, 내부 상태 관리 등의 동작 원리를 상세히 다룬다.
2. tick 전파 과정
2.1 실행 흐름
데코레이터 노드의 tick() 메서드가 호출되면, 다음 과정이 순차적으로 실행된다.
1. 부모 노드가 데코레이터의 tick()을 호출
2. 데코레이터의 전처리 로직 실행 (선택)
3. 데코레이터가 자식 노드의 executeTick()을 호출
4. 자식 노드가 상태(SUCCESS/FAILURE/RUNNING)를 반환
5. 데코레이터의 후처리 로직 실행
6. 변환된 상태를 부모에게 반환
2.2 executeTick()과 tick()의 차이
BehaviorTree.CPP에서 executeTick()은 tick()을 호출하기 전에 노드의 상태 관리를 수행하는 래퍼 메서드이다. 데코레이터는 자식을 호출할 때 tick()이 아닌 executeTick()을 호출하여야 한다.
BT::NodeStatus tick() override
{
// 자식의 executeTick() 호출 (tick()이 아님)
BT::NodeStatus child_status = child_node_->executeTick();
// 상태 변환
return transformStatus(child_status);
}
3. 전처리와 후처리 패턴
3.1 전처리 패턴
자식 노드를 tick하기 전에 실행되는 로직이다.
BT::NodeStatus tick() override
{
// 전처리: 조건 확인
if (!shouldExecuteChild())
{
return BT::NodeStatus::FAILURE; // 자식을 tick하지 않음
}
// 자식 tick
return child_node_->executeTick();
}
Delay 데코레이터는 지정된 시간이 경과할 때까지 자식을 tick하지 않는 전처리 패턴의 예이다.
3.2 후처리 패턴
자식 노드의 반환 상태를 받은 후 실행되는 로직이다.
BT::NodeStatus tick() override
{
BT::NodeStatus child_status = child_node_->executeTick();
// 후처리: 상태 변환
if (child_status == BT::NodeStatus::SUCCESS)
{
return BT::NodeStatus::FAILURE; // 반전
}
if (child_status == BT::NodeStatus::FAILURE)
{
return BT::NodeStatus::SUCCESS; // 반전
}
return child_status; // RUNNING은 그대로 전달
}
Inverter 데코레이터는 순수한 후처리 패턴의 예이다.
3.3 복합 패턴
전처리와 후처리를 모두 포함하는 패턴이다.
BT::NodeStatus tick() override
{
// 전처리: 타임아웃 확인
if (hasTimedOut())
{
haltChild();
return BT::NodeStatus::FAILURE;
}
// 자식 tick
BT::NodeStatus child_status = child_node_->executeTick();
// 후처리: 재시도 로직
if (child_status == BT::NodeStatus::FAILURE)
{
if (retry_count_ < max_retries_)
{
++retry_count_;
return BT::NodeStatus::RUNNING;
}
}
return child_status;
}
4. halt 전파 메커니즘
4.1 halt()의 호출 시점
데코레이터의 halt()는 다음 상황에서 호출된다.
- 부모 노드(제어 노드 또는 상위 데코레이터)가 데코레이터를 halt하는 경우
ReactiveSequence/ReactiveFallback에서 조건 실패에 의해 halt되는 경우
4.2 halt()의 구현
void halt() override
{
// 자식의 halt 호출
haltChild();
// 내부 상태 초기화
retry_count_ = 0;
elapsed_time_ = 0.0;
// 기반 클래스의 halt 호출
DecoratorNode::halt();
}
haltChild() 메서드는 자식이 RUNNING 상태인 경우에만 자식의 halt()를 호출한다.
5. 내부 상태 관리
5.1 상태 유지 데코레이터의 초기화
RetryNode, RepeatNode, TimeoutNode 등의 상태 유지 데코레이터는 내부 카운터나 타이머를 유지한다. 이 내부 상태는 다음 시점에서 초기화된다.
- halt() 호출 시: 데코레이터가 halt되면 내부 상태가 리셋된다.
- 자식이 SUCCESS/FAILURE로 완료 시: 데코레이터의 동작이 완료되면 내부 상태가 리셋된다.
- 명시적 리셋 시:
RepeatNode에서 지정된 반복 횟수가 완료된 경우.
5.2 RUNNING 상태의 유지
자식이 RUNNING을 반환하면, 상태 유지 데코레이터는 자신도 RUNNING을 반환하여 실행 상태를 유지한다. 다음 tick에서 자식이 다시 tick되며, 데코레이터의 내부 상태(카운터, 타이머)도 함께 갱신된다.
6. 설계 시 고려 사항
6.1 자식의 tick 생략
전처리 로직에서 자식을 tick하지 않는 경우, 자식의 상태는 이전 tick의 상태를 유지한다. 자식이 RUNNING 상태로 남아 있으면 리소스 누수가 발생할 수 있으므로, 자식을 tick하지 않기로 결정한 경우 자식을 halt하여야 한다.
6.2 RUNNING 상태의 올바른 전파
데코레이터는 자식의 RUNNING 상태를 일반적으로 그대로 전파한다. RUNNING을 다른 상태로 변환하는 것은 자식의 비동기 실행을 중단시킬 수 있으므로 주의하여야 한다.
7. 참고 문헌
- Colledanchise, M., & Ogren, P. (2018). Behavior Trees in Robotics and AI: An Introduction. CRC Press.
- BehaviorTree.CPP 공식 문서. https://www.behaviortree.dev/
| 버전 | 날짜 | 변경 사항 |
|---|---|---|
| v0.1 | 2026-04-04 | 초안 작성 |