1297.21 람다 함수 기반 조건 노드 생성
1. 람다 함수의 활용 배경
C++11에서 도입된 람다 함수(lambda function)는 이름 없는 함수 객체를 인라인(inline)으로 정의할 수 있는 기능이다. BehaviorTree.CPP의 SimpleConditionNode 메커니즘과 결합하면, 별도의 클래스 정의 없이 조건 노드를 간결하게 생성할 수 있다. 이 방식은 단순한 조건 평가 로직을 신속하게 구현하고 테스트하는 데 적합하다(Faconti & Colledanchise, 2022).
2. 기본 람다 기반 조건 노드
가장 단순한 형태의 람다 기반 조건 노드는 다음과 같이 등록한다:
BT::BehaviorTreeFactory factory;
factory.registerSimpleCondition(
"IsValuePositive",
[](BT::TreeNode&%20self) -> BT::NodeStatus {
double value;
self.getInput("value", value);
return (value > 0.0) ? BT::NodeStatus::SUCCESS : BT::NodeStatus::FAILURE;
},
{ BT::InputPort<double>("value", "평가 대상 값") }
);
람다의 매개변수 self는 현재 노드에 대한 참조로, 블랙보드 포트 접근 등 TreeNode의 모든 메서드를 사용할 수 있다.
3. 캡처를 활용한 외부 데이터 참조
3.1 값 캡처
람다의 캡처(capture) 기능을 통해 등록 시점의 외부 변수를 참조할 수 있다. 상수 매개변수를 고정하는 용도로 활용된다.
double safety_margin = 0.3;
factory.registerSimpleCondition(
"IsDistanceSafe",
[safety_margin](BT::TreeNode&%20self) -> BT::NodeStatus {
double distance;
self.getInput("distance", distance);
return (distance > safety_margin)
? BT::NodeStatus::SUCCESS
: BT::NodeStatus::FAILURE;
},
{ BT::InputPort<double>("distance", "장애물까지의 거리") }
);
safety_margin은 값으로 캡처되므로, 등록 이후 원본 변수가 변경되어도 람다 내의 값에 영향을 미치지 않는다.
3.2 참조 캡처를 통한 동적 데이터 접근
원자적 변수(atomic variable)나 공유 포인터(shared pointer)를 참조 또는 값 캡처하여 비동기적으로 갱신되는 데이터를 조건 노드에서 참조할 수 있다.
auto shared_data = std::make_shared<std::atomic<double>>(0.0);
// ROS2 구독자에서 데이터 갱신
auto sub = node->create_subscription<std_msgs::msg::Float64>(
"/battery_level", 10,
[shared_data](const%20std_msgs::msg::Float64::SharedPtr%20msg) {
shared_data->store(msg->data);
});
// 공유 포인터를 값 캡처하여 안전한 참조
factory.registerSimpleCondition(
"IsBatteryOk",
[shared_data](BT::TreeNode&%20self) -> BT::NodeStatus {
double threshold;
self.getInput("threshold", threshold);
double battery = shared_data->load();
return (battery >= threshold)
? BT::NodeStatus::SUCCESS
: BT::NodeStatus::FAILURE;
},
{ BT::InputPort<double>("threshold", 20.0, "배터리 임계값") }
);
이 패턴에서 shared_data는 std::shared_ptr로 캡처되므로, 구독자 콜백과 조건 노드 양쪽에서 안전하게 접근할 수 있다. std::atomic을 사용하여 스레드 안전성(thread safety)을 보장한다.
3.3 ROS2 노드 캡처
ROS2 노드의 기능(파라미터 읽기, 시계 접근 등)이 필요한 경우, ROS2 노드의 공유 포인터를 캡처한다.
auto ros_node = std::make_shared<rclcpp::Node>("condition_helper");
factory.registerSimpleCondition(
"IsParameterTrue",
[ros_node](BT::TreeNode&%20self) -> BT::NodeStatus {
std::string param_name;
self.getInput("param_name", param_name);
bool param_value = ros_node->get_parameter(param_name).as_bool();
return param_value ? BT::NodeStatus::SUCCESS : BT::NodeStatus::FAILURE;
},
{ BT::InputPort<std::string>("param_name", "확인할 파라미터 이름") }
);
4. 복수 입력 포트를 사용하는 람다
복수의 입력 포트를 선언하여 다양한 매개변수를 수신할 수 있다.
factory.registerSimpleCondition(
"IsInRange",
[](BT::TreeNode&%20self) -> BT::NodeStatus {
double value, min_val, max_val;
self.getInput("value", value);
self.getInput("min", min_val);
self.getInput("max", max_val);
return (value >= min_val && value <= max_val)
? BT::NodeStatus::SUCCESS
: BT::NodeStatus::FAILURE;
},
{
BT::InputPort<double>("value", "현재 값"),
BT::InputPort<double>("min", "최소 허용값"),
BT::InputPort<double>("max", "최대 허용값")
}
);
5. 람다 기반 조건 노드의 XML 사용
등록된 람다 기반 조건 노드는 클래스 기반 노드와 동일한 방식으로 XML에서 사용된다.
<BehaviorTree>
<Sequence>
<Condition ID="IsBatteryOk" threshold="25.0"/>
<Condition ID="IsInRange"
value="{altitude}" min="10.0" max="120.0"/>
<Action ID="Fly"/>
</Sequence>
</BehaviorTree>
6. 캡처 시 주의 사항
6.1 수명 관리
람다가 캡처하는 객체의 수명은 행동 트리의 수명보다 길어야 한다. 지역 변수를 참조로 캡처하면, 해당 변수가 소멸된 후 람다가 호출될 때 미정의 동작(undefined behavior)이 발생한다. std::shared_ptr를 값으로 캡처하면 수명 문제를 방지할 수 있다.
// 위험: 지역 변수의 참조 캡처
void setupConditions(BT::BehaviorTreeFactory& factory)
{
double threshold = 10.0;
factory.registerSimpleCondition(
"Bad",
[&threshold](BT::TreeNode&%20self) -> BT::NodeStatus {
// threshold는 함수 종료 후 소멸 → 미정의 동작
double value;
self.getInput("value", value);
return (value > threshold)
? BT::NodeStatus::SUCCESS
: BT::NodeStatus::FAILURE;
},
{ BT::InputPort<double>("value") }
);
} // threshold 소멸
6.2 스레드 안전성
캡처된 데이터가 복수의 스레드에서 접근되는 경우(예: ROS2 콜백과 tick 스레드), std::atomic, std::mutex 등을 사용하여 동기화해야 한다.
7. 적용 범위와 한계
람다 기반 조건 노드는 다음의 한계가 있다:
- 상태 관리 제한: 멤버 변수를 통한 체계적 상태 관리가 어렵다. 캐싱 등이 필요하면 클래스 기반 구현이 적합하다.
- 생성자 초기화 불가: 노드 인스턴스별 초기화 로직을 구현할 수 없다.
- 테스트 용이성 저하: 독립된 클래스가 아니므로 단위 테스트 프레임워크에서의 격리된 테스트가 어렵다.
8. 참고 문헌
- Colledanchise, M., & Ogren, P. (2018). Behavior Trees in Robotics and AI: An Introduction. CRC Press.
- Faconti, D., & Colledanchise, M. (2022). BehaviorTree.CPP Documentation. https://www.behaviortree.dev/
version: 0.1.0