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_datastd::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