1293.34 ReactiveSequence의 조건 재평가 메커니즘
1. 조건 재평가의 필요성
ReactiveSequence에서 조건 재평가(condition re-evaluation)란, 매 Tick마다 이미 SUCCESS를 반환한 조건 노드들을 다시 Tick하여 전제 조건의 유효성을 지속적으로 검증하는 메커니즘이다. 표준 Sequence(WithMemory)에서는 한 번 SUCCESS를 반환한 조건 노드가 이후 Tick에서 건너뛰어지므로, 해당 조건이 이후에 위반되더라도 이를 감지할 수 없다. ReactiveSequence의 조건 재평가는 이러한 한계를 극복하여, 동적으로 변화하는 환경에서 행동의 전제 조건이 지속적으로 충족되는지를 보장한다(Colledanchise & Ogren, 2018).
2. 조건 재평가의 동작 원리
ReactiveSequence에서 조건 재평가는 다음과 같은 과정으로 수행된다.
function ReactiveSequence.tick():
for i = 0 to children.size() - 1:
child_status = children[i].tick() // 모든 자식을 매번 재Tick
if child_status == FAILURE:
haltRunningChildren()
return FAILURE // 조건 위반 시 즉시 중단
if child_status == RUNNING:
haltChildrenAfter(i + 1)
return RUNNING
return SUCCESS
이 알고리즘에서 핵심은 인덱스 0부터 시작하는 반복문이 매 Tick마다 동일하게 실행된다는 것이다. WithMemory Sequence가 기억된 인덱스에서 재개하는 것과 달리, ReactiveSequence는 저장된 인덱스가 존재하지 않으며 항상 첫 번째 자식부터 순차적으로 Tick한다.
3. 조건 노드와 액션 노드의 역할 분리
ReactiveSequence에서 조건 재평가가 효과적으로 동작하려면, 자식 노드가 조건 노드와 액션 노드로 명확히 분리되어야 한다.
3.1 조건 노드의 특성
조건 노드는 동기적으로 실행되며, 항상 SUCCESS 또는 FAILURE를 즉시 반환한다. RUNNING을 반환하지 않는 것이 조건 노드의 핵심적 특성이다. 이는 조건 평가가 단일 Tick 내에서 완료되어야 함을 의미하며, 블랙보드 값 비교, 센서 데이터 임계값 확인, 상태 플래그 검사 등 경량 연산으로 구현된다.
class IsBatteryOk : public BT::ConditionNode {
public:
IsBatteryOk(const std::string& name, const BT::NodeConfig& config)
: BT::ConditionNode(name, config) {}
BT::NodeStatus tick() override {
double battery_level;
getInput("battery_level", battery_level);
return (battery_level > 10.0)
? BT::NodeStatus::SUCCESS
: BT::NodeStatus::FAILURE;
}
};
3.2 액션 노드의 특성
액션 노드는 비동기적으로 실행되며, RUNNING을 반환하여 장기 실행 작업의 진행 중임을 표시한다. ReactiveSequence에서 액션 노드는 일반적으로 마지막 자식으로 배치되어, 모든 전제 조건이 충족된 경우에만 Tick을 수신한다.
4. 조건 위반 시의 즉각적 반응
조건 재평가의 가장 중요한 효과는 조건 위반에 대한 즉각적 반응이다. 다음 실행 흐름을 통해 이를 구체적으로 분석한다.
ReactiveSequence
├── IsBatteryOk (조건)
├── IsMotorHealthy (조건)
└── PerformTask (비동기 액션)
4.1 정상 실행에서 조건 위반까지의 흐름
| Tick | IsBatteryOk | IsMotorHealthy | PerformTask | ReactiveSequence | 비고 |
|---|---|---|---|---|---|
| 1 | SUCCESS | SUCCESS | RUNNING | RUNNING | 작업 시작 |
| 2 | SUCCESS | SUCCESS | RUNNING | RUNNING | 작업 진행 |
| 3 | FAILURE | (Tick 안 됨) | Halt | FAILURE | 배터리 부족 감지 |
Tick 3에서 IsBatteryOk가 FAILURE를 반환하면, ReactiveSequence는 IsMotorHealthy와 PerformTask를 Tick하지 않고 즉시 FAILURE를 반환한다. 이때 RUNNING 상태였던 PerformTask에 Halt가 호출되어 진행 중이던 작업이 안전하게 중단된다.
4.2 WithMemory Sequence와의 비교
동일한 자식 구조를 가진 WithMemory Sequence의 경우, Tick 3에서 IsBatteryOk는 재평가되지 않는다. Tick 1에서 이미 SUCCESS를 반환하였으므로 건너뛰어지며, PerformTask가 직접 Tick된다. 따라서 배터리 부족 상태에서도 작업이 계속 진행되는 위험이 발생한다.
| 특성 | ReactiveSequence | WithMemory Sequence |
|---|---|---|
| 조건 재평가 시점 | 매 Tick | 최초 1회 |
| 조건 위반 감지 지연 | 1 Tick 이내 | 감지 불가 |
| 액션 중단 시점 | 조건 위반 즉시 | 중단 없음 |
| 안전성 보장 수준 | 지속적 | 초기 검증만 |
5. 다중 조건의 재평가 순서
ReactiveSequence에 복수의 조건 노드가 배치된 경우, 왼쪽에서 오른쪽 순서로 재평가가 수행된다. 앞쪽 조건이 FAILURE를 반환하면 뒤쪽 조건은 평가되지 않으므로, 조건 노드의 배치 순서는 성능과 의미론 양면에서 중요하다.
ReactiveSequence
├── Condition_A (평가 비용: 낮음, 위반 빈도: 높음)
├── Condition_B (평가 비용: 중간, 위반 빈도: 중간)
├── Condition_C (평가 비용: 높음, 위반 빈도: 낮음)
└── Action (비동기)
5.1 배치 순서 최적화 원칙
- 위반 빈도가 높은 조건을 앞에 배치: 빈번하게 FAILURE를 반환하는 조건을 앞쪽에 배치하면, 해당 조건이 FAILURE를 반환할 때 뒤쪽의 고비용 조건 평가를 생략할 수 있다.
- 평가 비용이 낮은 조건을 앞에 배치: 경량 조건을 앞쪽에 배치하여 불필요한 고비용 연산을 방지한다.
- 안전 조건을 최우선에 배치: 비상 정지, 시스템 무결성 등 안전에 직접 관련된 조건을 가장 앞에 배치하여, 최소한의 지연으로 안전 위반을 감지한다.
6. 조건 재평가와 블랙보드의 상호작용
조건 노드는 블랙보드에서 데이터를 읽어 평가를 수행하는 것이 일반적이다. ReactiveSequence에서 매 Tick마다 조건이 재평가되므로, 블랙보드 값의 변화가 최대 1 Tick 지연 내에 감지된다.
class IsDistanceSafe : public BT::ConditionNode {
public:
IsDistanceSafe(const std::string& name, const BT::NodeConfig& config)
: BT::ConditionNode(name, config) {}
static BT::PortsList providedPorts() {
return { BT::InputPort<double>("min_distance"),
BT::InputPort<double>("current_distance") };
}
BT::NodeStatus tick() override {
double min_dist, cur_dist;
getInput("min_distance", min_dist);
getInput("current_distance", cur_dist);
return (cur_dist > min_dist)
? BT::NodeStatus::SUCCESS
: BT::NodeStatus::FAILURE;
}
};
블랙보드의 current_distance 값이 외부 구독자(ROS2 토픽 콜백 등)에 의해 갱신되면, 다음 Tick에서 조건 노드가 이를 반영하여 변경된 판정을 반환한다.
7. 조건 재평가의 비용과 관리
조건 재평가는 매 Tick마다 모든 조건을 다시 평가하므로, 조건의 수와 개별 평가 비용에 따라 Tick 실행 시간이 증가한다.
T_{tick} = \sum_{i=0}^{k-1} T_{cond_i} + T_{action\_running}
여기서 k는 조건 노드의 수, T_{cond_i}는 i번째 조건 노드의 평가 시간, T_{action\_running}은 액션 노드의 onRunning() 실행 시간이다. 조건 평가가 블랙보드 값 비교와 같은 O(1) 연산인 경우 추가 비용은 무시할 수 있으나, 복잡한 기하학적 연산이나 외부 서비스 호출이 포함된 경우에는 Tick 예산을 초과할 위험이 있다.
7.1 고비용 조건의 처리 방법
고비용 연산을 조건 노드에서 직접 수행하는 대신, 별도의 주기적 갱신 메커니즘을 통해 결과를 블랙보드에 캐시하고, 조건 노드는 캐시된 값만 참조하는 패턴이 권장된다.
<ReactiveSequence>
<!-- 경량 조건: 블랙보드 캐시 값만 참조 -->
<CheckBlackboardValue key="is_path_valid" expected="true"/>
<CheckBlackboardValue key="battery_ok" expected="true"/>
<!-- 비동기 액션 -->
<NavigateToGoal/>
</ReactiveSequence>
이 패턴에서 is_path_valid와 같은 고비용 판정 결과는 별도의 노드나 ROS2 콜백에서 비동기적으로 갱신되며, 조건 노드는 단순한 블랙보드 값 비교만 수행한다.
8. 조건 재평가에 의한 액션 재시작
조건이 일시적으로 위반되었다가 복구되면, ReactiveSequence의 액션 노드는 IDLE 상태에서 새로 시작된다. 이는 조건 위반 시 Halt가 호출되어 액션이 IDLE 상태로 리셋되기 때문이다.
Tick 1: ReactiveSequence → Cond(SUCCESS), Action(RUNNING) → RUNNING
Tick 2: ReactiveSequence → Cond(SUCCESS), Action(RUNNING) → RUNNING
Tick 3: ReactiveSequence → Cond(FAILURE) → FAILURE
Action에 Halt 호출 → Action 상태: IDLE
Tick 4: ReactiveSequence → Cond(SUCCESS), Action(RUNNING) → RUNNING
Action.onStart() 호출 → 처음부터 재시작
이 재시작 동작은 안전성 측면에서는 바람직하지만, 작업 진행 상태가 소실된다는 단점이 있다. 작업 진행 상태의 보존이 필요한 경우, 액션 노드가 블랙보드에 체크포인트를 기록하여 재시작 시 이전 진행 지점부터 재개하도록 구현할 수 있다.
9. 조건 재평가의 안전성 패턴
ReactiveSequence의 조건 재평가는 로봇공학에서 런타임 안전성을 보장하는 핵심 패턴이다. 다음은 대표적인 안전성 보장 구조이다.
<ReactiveSequence>
<IsEmergencyStopInactive/>
<IsBatteryAbove threshold="5.0"/>
<IsLocalizationValid/>
<IsWithinGeofence/>
<ExecuteMission/>
</ReactiveSequence>
이 구조에서 네 가지 안전 조건이 매 Tick마다 재평가되며, 어느 하나라도 위반되면 임무 실행이 즉시 중단된다. 이는 연속 감시(continuous monitoring)를 통한 안전성 보장의 전형적인 구현이며, 기능 안전(functional safety) 요구 사항을 행동 트리 수준에서 충족시키는 설계 방법이다.
참고 문헌
- 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/