From f689449b2c1694756b893b3ff088f9d534fa8117 Mon Sep 17 00:00:00 2001 From: Zeerek Date: Tue, 4 Aug 2026 22:09:03 +0000 Subject: [PATCH 1/6] Add subtree registration system --- behaviortree_cpp_pluginlib/CMakeLists.txt | 1 + .../register_behaviortree_cpp_plugin.cmake | 72 +++++++++++++++++++ behaviortree_cpp_pluginlib/package.xml | 1 + behaviortree_cpp_pluginlib/src/factory.cpp | 53 ++++++++++++++ .../CMakeLists.txt | 9 ++- .../test/subtrees/standalone_subtree.xml | 7 ++ .../test/subtrees/subtree_uses_a.xml | 7 ++ .../test/test_factory.cpp | 30 ++++++++ 8 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 behaviortree_cpp_pluginlib_tests/test/subtrees/standalone_subtree.xml create mode 100644 behaviortree_cpp_pluginlib_tests/test/subtrees/subtree_uses_a.xml diff --git a/behaviortree_cpp_pluginlib/CMakeLists.txt b/behaviortree_cpp_pluginlib/CMakeLists.txt index c186f6d..9ccd3b6 100644 --- a/behaviortree_cpp_pluginlib/CMakeLists.txt +++ b/behaviortree_cpp_pluginlib/CMakeLists.txt @@ -43,6 +43,7 @@ target_link_libraries(${PROJECT_NAME} pluginlib::pluginlib PRIVATE rcutils::rcutils + ament_index_cpp::ament_index_cpp ) # Install and export resources diff --git a/behaviortree_cpp_pluginlib/cmake/register_behaviortree_cpp_plugin.cmake b/behaviortree_cpp_pluginlib/cmake/register_behaviortree_cpp_plugin.cmake index 24865b4..1ffedf6 100644 --- a/behaviortree_cpp_pluginlib/cmake/register_behaviortree_cpp_plugin.cmake +++ b/behaviortree_cpp_pluginlib/cmake/register_behaviortree_cpp_plugin.cmake @@ -17,13 +17,19 @@ # # Example usage: # register_behaviortree_cpp_plugin(my_library) +# register_behaviortree_cpp_plugin(my_library SUBTREES trees/patrol.xml trees/dock.xml) # # :param TARGET: name of a valid CMake shared library target to export plugins from # :type TARGET: string +# :param SUBTREES: optional list of subtree XML files to ship with this plugin. Each file is +# installed and registered so that BT::PluginAwareFactory loads it automatically, making its +# definitions available to any loaded tree via . +# :type SUBTREES: list of files # # @public # function(register_behaviortree_cpp_plugin arg_TARGET) + cmake_parse_arguments(ARG "" "" "SUBTREES" ${ARGN}) if(NOT arg_TARGET) message(FATAL_ERROR "register_behaviortree_cpp_plugin() called without TARGET argument") endif() @@ -57,4 +63,70 @@ function(register_behaviortree_cpp_plugin arg_TARGET) ) list(APPEND __PLUGINLIB_PLUGIN_CATEGORIES "behaviortree_cpp") set(__PLUGINLIB_PLUGIN_CATEGORIES "${__PLUGINLIB_PLUGIN_CATEGORIES}" PARENT_SCOPE) + + # Optionally ship subtree XML alongside this plugin. The marker suffix is the target name, which + # is unique within the package, so this composes with a separate register_behaviortree_cpp_subtrees() + # call without colliding on the ament resource marker path (see helper below). + if(ARG_SUBTREES) + _register_behaviortree_cpp_subtrees("${arg_TARGET}" ${ARG_SUBTREES}) + endif() +endfunction() + +# +# Register BehaviorTree.CPP subtree XML files without a C++ plugin target. +# +# Use this for packages that ship reusable subtrees but build no node plugin library. The subtrees +# are installed and registered so that BT::PluginAwareFactory loads them automatically. +# +# Example usage: +# register_behaviortree_cpp_subtrees(FILES trees/patrol.xml trees/dock.xml) +# register_behaviortree_cpp_subtrees(NAME navigation FILES trees/patrol.xml) +# +# :param NAME: optional group suffix, used to build a unique resource marker. Defaults to "subtrees". +# Pass distinct NAMEs when calling this more than once in a single package. +# :type NAME: string +# :param FILES: list of subtree XML files to install and register. +# :type FILES: list of files +# +# @public +# +function(register_behaviortree_cpp_subtrees) + cmake_parse_arguments(ARG "" "NAME" "FILES" ${ARGN}) + if(NOT ARG_FILES) + message(FATAL_ERROR "register_behaviortree_cpp_subtrees() called without FILES argument") + endif() + set(marker_suffix "subtrees") + if(ARG_NAME) + set(marker_suffix "${ARG_NAME}") + endif() + _register_behaviortree_cpp_subtrees("${marker_suffix}" ${ARG_FILES}) +endfunction() + +# +# Internal helper: install subtree XML files and register them in the ament resource index under +# the "behaviortree_cpp_subtrees" category, so BT::PluginAwareFactory can discover them at runtime. +# +# The resource marker is named "__" to keep it unique per call: +# ament_index_register_resource() uses file(GENERATE), which hard-errors if the same marker path is +# written twice with different content. The runtime does not rely on the marker name; it reads the +# install prefix from the resource and the package-relative path from the marker content. +# +function(_register_behaviortree_cpp_subtrees marker_suffix) + set(marker_content "") + foreach(subtree_xml ${ARGN}) + get_filename_component(subtree_abs "${subtree_xml}" ABSOLUTE) + if(NOT EXISTS "${subtree_abs}") + message(FATAL_ERROR "register subtrees: file does not exist: ${subtree_abs}") + endif() + get_filename_component(subtree_name "${subtree_abs}" NAME) + install(FILES "${subtree_abs}" DESTINATION share/${PROJECT_NAME}/behaviortree_subtrees) + # Path is share-relative and includes the package folder, so the runtime resolves it as + # /share/ under both merged and isolated colcon installs. + string(APPEND marker_content "${PROJECT_NAME}/behaviortree_subtrees/${subtree_name}\n") + endforeach() + + ament_index_register_resource("behaviortree_cpp_subtrees" + CONTENT "${marker_content}" + PACKAGE_NAME "${PROJECT_NAME}__${marker_suffix}" + ) endfunction() diff --git a/behaviortree_cpp_pluginlib/package.xml b/behaviortree_cpp_pluginlib/package.xml index e09de7e..f9876d9 100644 --- a/behaviortree_cpp_pluginlib/package.xml +++ b/behaviortree_cpp_pluginlib/package.xml @@ -11,6 +11,7 @@ ament_cmake ament_cmake_auto + ament_index_cpp behaviortree_cpp pluginlib rcutils diff --git a/behaviortree_cpp_pluginlib/src/factory.cpp b/behaviortree_cpp_pluginlib/src/factory.cpp index bac0bc7..ba09e63 100644 --- a/behaviortree_cpp_pluginlib/src/factory.cpp +++ b/behaviortree_cpp_pluginlib/src/factory.cpp @@ -14,10 +14,14 @@ #include "behaviortree_cpp_pluginlib/factory.hpp" +#include #include +#include #include #include +#include "ament_index_cpp/get_resource.hpp" +#include "ament_index_cpp/get_resources.hpp" #include "behaviortree_cpp/bt_factory.h" #include "behaviortree_cpp_pluginlib/plugin.hpp" #include "rcutils/logging_macros.h" @@ -40,10 +44,59 @@ PluginAwareFactory::PluginAwareFactory(const std::vector & plugin_x class_library_path.c_str()); plugin->registerTypes(*this); } + + // Phase 2: now that every node type from every discovered plugin has been registered above, + // load any subtree XML files shipped by plugin packages. Ordering matters: BT.CPP runs its + // XML verification at registration time and throws if a subtree references a concrete node + // type that is not yet registered, so this must happen strictly after the loop above. + // Subtrees are discovered via the "behaviortree_cpp_subtrees" ament resource index category, + // populated by the register_behaviortree_cpp_subtrees() CMake helper. + const std::string subtree_resource = "behaviortree_cpp_subtrees"; + for (const auto & [marker_name, install_prefix] : + ament_index_cpp::get_resources(subtree_resource)) + { + std::string content; + if (!ament_index_cpp::get_resource(subtree_resource, marker_name, content)) { + continue; + } + // Content is a newline-separated list of share-relative paths, each already including the + // providing package's folder (e.g. "my_pkg/behaviortree_subtrees/foo.xml"). The install + // prefix comes from the resource itself, so this resolves correctly under both merged and + // isolated colcon installs. + std::istringstream stream(content); + std::string relative_path; + while (std::getline(stream, relative_path)) { + if (relative_path.empty()) { + continue; // A trailing newline yields an empty final token. + } + const std::filesystem::path subtree_path = + std::filesystem::path(install_prefix) / "share" / relative_path; + try { + registerBehaviorTreeFromFile(subtree_path); + RCUTILS_LOG_INFO_NAMED( + "behaviortree_cpp_pluginlib", + "Registered subtree(s) from %s", + subtree_path.c_str()); + } catch (const std::exception & e) { + // Keep the factory usable even if one package ships a malformed subtree or one that + // references a node no loaded plugin provides. Log loudly and skip it. + RCUTILS_LOG_ERROR_NAMED( + "behaviortree_cpp_pluginlib", + "Failed to register subtree from %s: %s", + subtree_path.c_str(), + e.what()); + } + } + } } PluginAwareFactory::~PluginAwareFactory() { + // Clear registered tree definitions first. These hold only parsed XML owned by the + // behaviortree_cpp library (not by any dlopen'd plugin), so this is defensive/symmetric + // rather than strictly required for lifetime correctness. + clearRegisteredBehaviorTrees(); + // First grab all the IDs, since unregistering them modifies the map and invalidates iterators std::vector ids_to_unregister; for (const auto & [id, _] : builders()) { diff --git a/behaviortree_cpp_pluginlib_tests/CMakeLists.txt b/behaviortree_cpp_pluginlib_tests/CMakeLists.txt index b281899..1e4b640 100644 --- a/behaviortree_cpp_pluginlib_tests/CMakeLists.txt +++ b/behaviortree_cpp_pluginlib_tests/CMakeLists.txt @@ -35,12 +35,19 @@ if(BUILD_TESTING) # Create test libraries add_library(test_plugin_a SHARED test/plugin_a.cpp) target_link_libraries(test_plugin_a PUBLIC behaviortree_cpp_pluginlib::behaviortree_cpp_pluginlib) - register_behaviortree_cpp_plugin(test_plugin_a) + # Ship a subtree that references CustomNodeA1 (from this same plugin) via the SUBTREES keyword. + register_behaviortree_cpp_plugin(test_plugin_a + SUBTREES ${CMAKE_CURRENT_SOURCE_DIR}/test/subtrees/subtree_uses_a.xml) add_library(test_plugin_b SHARED test/plugin_b.cpp) target_link_libraries(test_plugin_b PRIVATE behaviortree_cpp_pluginlib::behaviortree_cpp_pluginlib) register_behaviortree_cpp_plugin(test_plugin_b) + # Also exercise the standalone (node-less) subtree registration path, and prove two subtree + # markers coexist within one package. + register_behaviortree_cpp_subtrees(NAME standalone + FILES ${CMAKE_CURRENT_SOURCE_DIR}/test/subtrees/standalone_subtree.xml) + # In the special case of a test-only package that isn't normally installed on target systems, # we may install the test targets to run install-space testing on them (pluginlib loading) install( diff --git a/behaviortree_cpp_pluginlib_tests/test/subtrees/standalone_subtree.xml b/behaviortree_cpp_pluginlib_tests/test/subtrees/standalone_subtree.xml new file mode 100644 index 0000000..80e0951 --- /dev/null +++ b/behaviortree_cpp_pluginlib_tests/test/subtrees/standalone_subtree.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/behaviortree_cpp_pluginlib_tests/test/subtrees/subtree_uses_a.xml b/behaviortree_cpp_pluginlib_tests/test/subtrees/subtree_uses_a.xml new file mode 100644 index 0000000..4852e24 --- /dev/null +++ b/behaviortree_cpp_pluginlib_tests/test/subtrees/subtree_uses_a.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/behaviortree_cpp_pluginlib_tests/test/test_factory.cpp b/behaviortree_cpp_pluginlib_tests/test/test_factory.cpp index 9164541..ec4d6ee 100644 --- a/behaviortree_cpp_pluginlib_tests/test/test_factory.cpp +++ b/behaviortree_cpp_pluginlib_tests/test/test_factory.cpp @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include #include #include @@ -29,3 +30,32 @@ TEST(Factory, AutofactoryEndToEnd) ASSERT_NO_THROW(builders.at("CustomNodeB2")); ASSERT_THROW(builders.at("NonexistentNode"), std::out_of_range); } + +TEST(Factory, SubtreesShippedByPluginsAreRegistered) +{ + BT::PluginAwareFactory factory; + + const auto trees = factory.registeredBehaviorTrees(); + // Shipped via the SUBTREES keyword on test_plugin_a. + ASSERT_NE(std::find(trees.begin(), trees.end(), "SubtreeUsesA"), trees.end()); + // Shipped via the standalone register_behaviortree_cpp_subtrees() path. + ASSERT_NE(std::find(trees.begin(), trees.end(), "StandaloneSubtree"), trees.end()); + + // Each subtree instantiates directly: its concrete node was registered before it was loaded. + ASSERT_NO_THROW(factory.createTree("SubtreeUsesA")); + ASSERT_NO_THROW(factory.createTree("StandaloneSubtree")); +} + +TEST(Factory, ShippedSubtreeIsUsableFromAnotherTree) +{ + BT::PluginAwareFactory factory; + + // A tree loaded later can reference the shipped subtree "for free" via . + factory.registerBehaviorTreeFromText( + R"( + + + + )"); + ASSERT_NO_THROW(factory.createTree("Main")); +} From 2f5f49d97cfa33e6b0b6f4803f3b07da8129e055 Mon Sep 17 00:00:00 2001 From: Zeerek Date: Tue, 4 Aug 2026 22:23:30 +0000 Subject: [PATCH 2/6] Clean up comments --- .../register_behaviortree_cpp_plugin.cmake | 17 +++++-------- behaviortree_cpp_pluginlib/src/factory.cpp | 24 +++++++------------ .../CMakeLists.txt | 5 ++-- 3 files changed, 16 insertions(+), 30 deletions(-) diff --git a/behaviortree_cpp_pluginlib/cmake/register_behaviortree_cpp_plugin.cmake b/behaviortree_cpp_pluginlib/cmake/register_behaviortree_cpp_plugin.cmake index 1ffedf6..cb7eb7d 100644 --- a/behaviortree_cpp_pluginlib/cmake/register_behaviortree_cpp_plugin.cmake +++ b/behaviortree_cpp_pluginlib/cmake/register_behaviortree_cpp_plugin.cmake @@ -64,9 +64,7 @@ function(register_behaviortree_cpp_plugin arg_TARGET) list(APPEND __PLUGINLIB_PLUGIN_CATEGORIES "behaviortree_cpp") set(__PLUGINLIB_PLUGIN_CATEGORIES "${__PLUGINLIB_PLUGIN_CATEGORIES}" PARENT_SCOPE) - # Optionally ship subtree XML alongside this plugin. The marker suffix is the target name, which - # is unique within the package, so this composes with a separate register_behaviortree_cpp_subtrees() - # call without colliding on the ament resource marker path (see helper below). + # Ship subtree XML alongside this plugin, keyed by the (package-unique) target name. if(ARG_SUBTREES) _register_behaviortree_cpp_subtrees("${arg_TARGET}" ${ARG_SUBTREES}) endif() @@ -103,13 +101,11 @@ function(register_behaviortree_cpp_subtrees) endfunction() # -# Internal helper: install subtree XML files and register them in the ament resource index under -# the "behaviortree_cpp_subtrees" category, so BT::PluginAwareFactory can discover them at runtime. +# Internal helper: install subtree XML files and register them in the "behaviortree_cpp_subtrees" +# ament resource index category for BT::PluginAwareFactory to discover at runtime. # -# The resource marker is named "__" to keep it unique per call: -# ament_index_register_resource() uses file(GENERATE), which hard-errors if the same marker path is -# written twice with different content. The runtime does not rely on the marker name; it reads the -# install prefix from the resource and the package-relative path from the marker content. +# The marker is named "__" so repeated calls in one package don't +# collide (ament_index_register_resource uses file(GENERATE), which errors on a reused path). # function(_register_behaviortree_cpp_subtrees marker_suffix) set(marker_content "") @@ -120,8 +116,7 @@ function(_register_behaviortree_cpp_subtrees marker_suffix) endif() get_filename_component(subtree_name "${subtree_abs}" NAME) install(FILES "${subtree_abs}" DESTINATION share/${PROJECT_NAME}/behaviortree_subtrees) - # Path is share-relative and includes the package folder, so the runtime resolves it as - # /share/ under both merged and isolated colcon installs. + # Share-relative path including the package folder, resolved at runtime as /share/. string(APPEND marker_content "${PROJECT_NAME}/behaviortree_subtrees/${subtree_name}\n") endforeach() diff --git a/behaviortree_cpp_pluginlib/src/factory.cpp b/behaviortree_cpp_pluginlib/src/factory.cpp index ba09e63..1684ebc 100644 --- a/behaviortree_cpp_pluginlib/src/factory.cpp +++ b/behaviortree_cpp_pluginlib/src/factory.cpp @@ -45,12 +45,11 @@ PluginAwareFactory::PluginAwareFactory(const std::vector & plugin_x plugin->registerTypes(*this); } - // Phase 2: now that every node type from every discovered plugin has been registered above, - // load any subtree XML files shipped by plugin packages. Ordering matters: BT.CPP runs its - // XML verification at registration time and throws if a subtree references a concrete node - // type that is not yet registered, so this must happen strictly after the loop above. - // Subtrees are discovered via the "behaviortree_cpp_subtrees" ament resource index category, - // populated by the register_behaviortree_cpp_subtrees() CMake helper. + // Load shipped subtree XML only after every node type is registered above: BT.CPP verifies + // XML at registration time and rejects subtrees referencing an unregistered node. + // Content is a newline-separated list of share-relative paths registered by the + // register_behaviortree_cpp_subtrees() CMake helper; the prefix comes from the resource, so it + // resolves under both merged and isolated installs. const std::string subtree_resource = "behaviortree_cpp_subtrees"; for (const auto & [marker_name, install_prefix] : ament_index_cpp::get_resources(subtree_resource)) @@ -59,15 +58,11 @@ PluginAwareFactory::PluginAwareFactory(const std::vector & plugin_x if (!ament_index_cpp::get_resource(subtree_resource, marker_name, content)) { continue; } - // Content is a newline-separated list of share-relative paths, each already including the - // providing package's folder (e.g. "my_pkg/behaviortree_subtrees/foo.xml"). The install - // prefix comes from the resource itself, so this resolves correctly under both merged and - // isolated colcon installs. std::istringstream stream(content); std::string relative_path; while (std::getline(stream, relative_path)) { if (relative_path.empty()) { - continue; // A trailing newline yields an empty final token. + continue; } const std::filesystem::path subtree_path = std::filesystem::path(install_prefix) / "share" / relative_path; @@ -78,8 +73,8 @@ PluginAwareFactory::PluginAwareFactory(const std::vector & plugin_x "Registered subtree(s) from %s", subtree_path.c_str()); } catch (const std::exception & e) { - // Keep the factory usable even if one package ships a malformed subtree or one that - // references a node no loaded plugin provides. Log loudly and skip it. + // Skip a malformed subtree, or one referencing a node no loaded plugin provides, + // rather than failing construction. RCUTILS_LOG_ERROR_NAMED( "behaviortree_cpp_pluginlib", "Failed to register subtree from %s: %s", @@ -92,9 +87,6 @@ PluginAwareFactory::PluginAwareFactory(const std::vector & plugin_x PluginAwareFactory::~PluginAwareFactory() { - // Clear registered tree definitions first. These hold only parsed XML owned by the - // behaviortree_cpp library (not by any dlopen'd plugin), so this is defensive/symmetric - // rather than strictly required for lifetime correctness. clearRegisteredBehaviorTrees(); // First grab all the IDs, since unregistering them modifies the map and invalidates iterators diff --git a/behaviortree_cpp_pluginlib_tests/CMakeLists.txt b/behaviortree_cpp_pluginlib_tests/CMakeLists.txt index 1e4b640..a1336e8 100644 --- a/behaviortree_cpp_pluginlib_tests/CMakeLists.txt +++ b/behaviortree_cpp_pluginlib_tests/CMakeLists.txt @@ -35,7 +35,7 @@ if(BUILD_TESTING) # Create test libraries add_library(test_plugin_a SHARED test/plugin_a.cpp) target_link_libraries(test_plugin_a PUBLIC behaviortree_cpp_pluginlib::behaviortree_cpp_pluginlib) - # Ship a subtree that references CustomNodeA1 (from this same plugin) via the SUBTREES keyword. + # Subtree referencing CustomNodeA1, shipped via the SUBTREES keyword. register_behaviortree_cpp_plugin(test_plugin_a SUBTREES ${CMAKE_CURRENT_SOURCE_DIR}/test/subtrees/subtree_uses_a.xml) @@ -43,8 +43,7 @@ if(BUILD_TESTING) target_link_libraries(test_plugin_b PRIVATE behaviortree_cpp_pluginlib::behaviortree_cpp_pluginlib) register_behaviortree_cpp_plugin(test_plugin_b) - # Also exercise the standalone (node-less) subtree registration path, and prove two subtree - # markers coexist within one package. + # Exercise the standalone subtree registration path (and a second marker in one package). register_behaviortree_cpp_subtrees(NAME standalone FILES ${CMAKE_CURRENT_SOURCE_DIR}/test/subtrees/standalone_subtree.xml) From e67e250465a842567d13ec9574809fa59342427f Mon Sep 17 00:00:00 2001 From: Zeerek Date: Tue, 4 Aug 2026 22:23:49 +0000 Subject: [PATCH 3/6] pre-commit --- behaviortree_cpp_pluginlib/src/factory.cpp | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/behaviortree_cpp_pluginlib/src/factory.cpp b/behaviortree_cpp_pluginlib/src/factory.cpp index 1684ebc..8427917 100644 --- a/behaviortree_cpp_pluginlib/src/factory.cpp +++ b/behaviortree_cpp_pluginlib/src/factory.cpp @@ -51,9 +51,7 @@ PluginAwareFactory::PluginAwareFactory(const std::vector & plugin_x // register_behaviortree_cpp_subtrees() CMake helper; the prefix comes from the resource, so it // resolves under both merged and isolated installs. const std::string subtree_resource = "behaviortree_cpp_subtrees"; - for (const auto & [marker_name, install_prefix] : - ament_index_cpp::get_resources(subtree_resource)) - { + for (const auto & [marker_name, install_prefix] : ament_index_cpp::get_resources(subtree_resource)) { std::string content; if (!ament_index_cpp::get_resource(subtree_resource, marker_name, content)) { continue; @@ -64,22 +62,15 @@ PluginAwareFactory::PluginAwareFactory(const std::vector & plugin_x if (relative_path.empty()) { continue; } - const std::filesystem::path subtree_path = - std::filesystem::path(install_prefix) / "share" / relative_path; + const std::filesystem::path subtree_path = std::filesystem::path(install_prefix) / "share" / relative_path; try { registerBehaviorTreeFromFile(subtree_path); - RCUTILS_LOG_INFO_NAMED( - "behaviortree_cpp_pluginlib", - "Registered subtree(s) from %s", - subtree_path.c_str()); + RCUTILS_LOG_INFO_NAMED("behaviortree_cpp_pluginlib", "Registered subtree(s) from %s", subtree_path.c_str()); } catch (const std::exception & e) { // Skip a malformed subtree, or one referencing a node no loaded plugin provides, // rather than failing construction. RCUTILS_LOG_ERROR_NAMED( - "behaviortree_cpp_pluginlib", - "Failed to register subtree from %s: %s", - subtree_path.c_str(), - e.what()); + "behaviortree_cpp_pluginlib", "Failed to register subtree from %s: %s", subtree_path.c_str(), e.what()); } } } From 422274594920399290ceed0465f16922da15d527 Mon Sep 17 00:00:00 2001 From: Zeerek Date: Tue, 4 Aug 2026 22:46:57 +0000 Subject: [PATCH 4/6] Update readme with instructions on subtree usage --- behaviortree_cpp_pluginlib/README.md | 35 ++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/behaviortree_cpp_pluginlib/README.md b/behaviortree_cpp_pluginlib/README.md index bae61d9..6b39b03 100644 --- a/behaviortree_cpp_pluginlib/README.md +++ b/behaviortree_cpp_pluginlib/README.md @@ -68,6 +68,41 @@ Simple as 1, 2, 3: depend on this package, register your library as a plugin pro } ``` +## Registering Subtrees + +You can also ship reusable subtrees as `.xml` files. They are discovered and registered by `BT::PluginAwareFactory` automatically, so any loaded tree can reference them via `` without loading files by hand. + +1. Write a subtree XML with a single `` (the `ID` is the name you reference as a ``): + + ```xml + + + + + + + + + ``` + +2. `CMakeLists.txt` - register the file(s). Use the `SUBTREES` keyword to ship them alongside a plugin library (e.g. one that provides the nodes the subtree uses): + + ```cmake + register_behaviortree_cpp_plugin(my_plugin_library + SUBTREES trees/go_and_beep.xml + ) + ``` + + Or, for a package that ships subtrees but builds no plugin library, use the standalone function: + + ```cmake + register_behaviortree_cpp_subtrees(FILES trees/go_and_beep.xml) + # Pass a distinct NAME when calling more than once in a single package: + # register_behaviortree_cpp_subtrees(NAME navigation FILES trees/go_and_beep.xml) + ``` + +Note: a subtree may only reference built-in nodes or nodes provided by a loaded plugin. Referencing a node that is registered manually after the factory is constructed is not supported — such a subtree is logged and skipped at load time. + ## Loading Plugins To load all registered plugins, link against the exported library target and use the `BT::PluginAwareFactory` From 1272147395166cfc6b16849184278e0f07381dfa Mon Sep 17 00:00:00 2001 From: Zeerek Date: Tue, 4 Aug 2026 22:50:47 +0000 Subject: [PATCH 5/6] Add subtree instructional into the readme.md --- behaviortree_cpp_pluginlib/README.md | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/behaviortree_cpp_pluginlib/README.md b/behaviortree_cpp_pluginlib/README.md index 6b39b03..c00479e 100644 --- a/behaviortree_cpp_pluginlib/README.md +++ b/behaviortree_cpp_pluginlib/README.md @@ -77,10 +77,7 @@ You can also ship reusable subtrees as `.xml` files. They are discovered and reg ```xml - - - - + ... ``` @@ -125,6 +122,22 @@ To load all registered plugins, link against the exported library target and use ... ``` +3. All registered nodes _and_ subtrees are now available. Reference a shipped subtree by its `ID` from any tree you load - no need to load its file yourself: + + ```c++ + factory.registerBehaviorTreeFromText(R"( + + + + + + + )"); + + auto tree = factory.createTree("Main"); + tree.tickWhileRunning(); + ``` + # Implementation Details For more information about what's happening under the hood to enable these usage patterns, see [DEVELOPING.md](./DEVELOPING.md) From 53dc6e87b3813b76ff25cfcffd55ea9f9e6845dc Mon Sep 17 00:00:00 2001 From: Zeerek Date: Tue, 4 Aug 2026 22:54:59 +0000 Subject: [PATCH 6/6] Good loading comments --- behaviortree_cpp_pluginlib/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/behaviortree_cpp_pluginlib/README.md b/behaviortree_cpp_pluginlib/README.md index c00479e..bc188c9 100644 --- a/behaviortree_cpp_pluginlib/README.md +++ b/behaviortree_cpp_pluginlib/README.md @@ -134,6 +134,9 @@ To load all registered plugins, link against the exported library target and use )"); + // Or load the tree definition from a file instead: + // factory.registerBehaviorTreeFromFile("path/to/main.xml"); + auto tree = factory.createTree("Main"); tree.tickWhileRunning(); ```