Skip to content

Commit da2ca06

Browse files
Merge branch 'master' into feature/remove-lexy
2 parents 7580a2a + 1792196 commit da2ca06

6 files changed

Lines changed: 381 additions & 5 deletions

File tree

Doxyfile

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -781,7 +781,8 @@ WARN_LOGFILE =
781781
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
782782
# Note: If this tag is empty the current directory is searched.
783783

784-
INPUT = ./include
784+
INPUT = ./include \
785+
./docs/mainpage.md
785786

786787
# This tag can be used to specify the character encoding of the source files
787788
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
@@ -974,7 +975,7 @@ FILTER_SOURCE_PATTERNS =
974975
# (index.html). This can be useful if you have a project on for instance GitHub
975976
# and want to reuse the introduction page also for the doxygen output.
976977

977-
USE_MDFILE_AS_MAINPAGE =
978+
USE_MDFILE_AS_MAINPAGE = docs/mainpage.md
978979

979980
#---------------------------------------------------------------------------
980981
# Configuration options related to source browsing
@@ -1184,7 +1185,8 @@ HTML_STYLESHEET =
11841185
# list). For an example see the documentation.
11851186
# This tag requires that the tag GENERATE_HTML is set to YES.
11861187

1187-
HTML_EXTRA_STYLESHEET =
1188+
HTML_EXTRA_STYLESHEET = 3rdparty/doxygen-awesome-css/doxygen-awesome.css \
1189+
3rdparty/doxygen-awesome-css/doxygen-awesome-sidebar-only.css
11881190

11891191
# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
11901192
# other source files which should be copied to the HTML output directory. Note

docs/mainpage.md

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# BehaviorTree.CPP {#mainpage}
2+
3+
C++ library for behavior tree execution.
4+
5+
## Quick Start
6+
- @ref BT::BehaviorTreeFactory - Create and register nodes
7+
- @ref BT::Blackboard - Shared data storage
8+
- @ref BT::TreeNode - Base class for all nodes
9+
10+
## Node Types
11+
12+
| Category | Description | Key Classes |
13+
|----------|-------------|-------------|
14+
| **Actions** | Execute tasks | @ref BT::SyncActionNode, @ref BT::StatefulActionNode |
15+
| **Conditions** | Check state | @ref BT::ConditionNode |
16+
| **Control** | Flow control | @ref BT::SequenceNode, @ref BT::FallbackNode, @ref BT::ParallelNode |
17+
| **Decorators** | Modify behavior | @ref BT::RetryNode, @ref BT::TimeoutNode, @ref BT::InverterNode |
18+
19+
## Built-in Control Nodes
20+
21+
### Sequences
22+
- @ref BT::SequenceNode
23+
- @ref BT::ReactiveSequence
24+
- @ref BT::SequenceWithMemory
25+
26+
### Fallbacks
27+
- @ref BT::FallbackNode
28+
- @ref BT::ReactiveFallback
29+
30+
### Parallels
31+
- @ref BT::ParallelNode
32+
- @ref BT::ParallelAllNode
33+
34+
### Conditional
35+
- @ref BT::IfThenElseNode
36+
- @ref BT::WhileDoElseNode
37+
- @ref BT::SwitchNode
38+
- @ref BT::ManualSelectorNode
39+
40+
## Built-in Decorators
41+
42+
### Repetition
43+
- @ref BT::RetryNode
44+
- @ref BT::RepeatNode
45+
- @ref BT::LoopNode
46+
47+
### Timing
48+
- @ref BT::TimeoutNode
49+
- @ref BT::DelayNode
50+
51+
### Result Modification
52+
- @ref BT::InverterNode
53+
- @ref BT::ForceSuccessNode
54+
- @ref BT::ForceFailureNode
55+
56+
### Execution Control
57+
- @ref BT::RunOnceNode
58+
- @ref BT::KeepRunningUntilFailureNode
59+
60+
### Subtrees
61+
- @ref BT::SubTreeNode
62+
63+
### Preconditions
64+
- @ref BT::PreconditionNode
65+
- @ref BT::EntryUpdatedDecorator
66+
67+
## Built-in Actions
68+
69+
### Status
70+
- @ref BT::AlwaysSuccessNode
71+
- @ref BT::AlwaysFailureNode
72+
73+
### Blackboard
74+
- @ref BT::SetBlackboardNode
75+
- @ref BT::UnsetBlackboardNode
76+
77+
### Utility
78+
- @ref BT::SleepNode
79+
- @ref BT::TestNode
80+
81+
### Scripting
82+
- @ref BT::ScriptNode
83+
- @ref BT::ScriptCondition
84+
85+
### Entry Updated
86+
- @ref BT::EntryUpdatedAction
87+
88+
### Queue
89+
- @ref BT::PopFromQueue
90+
- @ref BT::QueueSize
91+
92+
## Core Concepts
93+
- @ref BT::PortInfo - Type-safe port system
94+
- @ref BT::Expected - Result type for error handling
95+
- @ref BT::NodeStatus - Node execution states
96+
97+
## Logging & Tools
98+
- @ref BT::FileLogger2 - File logging
99+
- @ref BT::StdCoutLogger - Console output
100+
- @ref BT::Groot2Publisher - Groot2 editor integration
101+
102+
## Resources
103+
- [GitHub Repository](https://github.com/BehaviorTree/BehaviorTree.CPP)
104+
- [Groot2 Editor](https://www.behaviortree.dev/)

include/behaviortree_cpp/tree_node.h

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,27 @@ inline Expected<Timestamp> TreeNode::getInputStamped(const std::string& key,
526526
}
527527
}
528528

529+
// Helper lambda to parse string using the stored converter if available,
530+
// otherwise fall back to convertFromString<T>. This fixes the plugin issue
531+
// where convertFromString<T> specializations are not visible across shared
532+
// library boundaries (issue #953).
533+
auto parseStringWithConverter = [this, &key](const std::string& str) -> T {
534+
if(config().manifest)
535+
{
536+
auto port_it = config().manifest->ports.find(key);
537+
if(port_it != config().manifest->ports.end())
538+
{
539+
const auto& converter = port_it->second.converter();
540+
if(converter)
541+
{
542+
return converter(str).template cast<T>();
543+
}
544+
}
545+
}
546+
// Fall back to parseString which calls convertFromString
547+
return parseString<T>(str);
548+
};
549+
529550
auto blackboard_ptr = getRemappedKey(key, port_value_str);
530551
try
531552
{
@@ -534,7 +555,7 @@ inline Expected<Timestamp> TreeNode::getInputStamped(const std::string& key,
534555
{
535556
try
536557
{
537-
destination = parseString<T>(port_value_str);
558+
destination = parseStringWithConverter(port_value_str);
538559
}
539560
catch(std::exception& ex)
540561
{
@@ -566,7 +587,7 @@ inline Expected<Timestamp> TreeNode::getInputStamped(const std::string& key,
566587
{
567588
if(!std::is_same_v<T, std::string> && any_value.isString())
568589
{
569-
destination = parseString<T>(any_value.cast<std::string>());
590+
destination = parseStringWithConverter(any_value.cast<std::string>());
570591
}
571592
else
572593
{

tests/CMakeLists.txt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
######################################################
22
# TESTS
33

4+
######################################################
5+
# Plugin for Issue #953 test (must be built before tests)
6+
# This plugin has a custom type with convertFromString ONLY in the plugin
7+
add_library(plugin_issue953 SHARED plugin_issue953/plugin_issue953.cpp)
8+
target_compile_definitions(plugin_issue953 PRIVATE BT_PLUGIN_EXPORT)
9+
set_target_properties(plugin_issue953 PROPERTIES
10+
PREFIX ""
11+
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
12+
)
13+
target_link_libraries(plugin_issue953 ${BTCPP_LIBRARY})
14+
15+
######################################################
16+
417
set(BT_TESTS
518
src/action_test_node.cpp
619
src/condition_test_node.cpp
@@ -38,6 +51,7 @@ set(BT_TESTS
3851
gtest_while_do_else.cpp
3952
gtest_interface.cpp
4053
gtest_simple_string.cpp
54+
gtest_plugin_issue953.cpp
4155

4256
script_parser_test.cpp
4357
test_helper.hpp
@@ -79,3 +93,9 @@ endif()
7993
target_include_directories(behaviortree_cpp_test PRIVATE include)
8094
target_link_libraries(behaviortree_cpp_test ${BTCPP_LIBRARY} bt_sample_nodes)
8195
target_compile_definitions(behaviortree_cpp_test PRIVATE BT_TEST_FOLDER="${CMAKE_CURRENT_SOURCE_DIR}")
96+
97+
# Ensure plugin is built before tests run, and tests can find it
98+
add_dependencies(behaviortree_cpp_test plugin_issue953)
99+
target_compile_definitions(behaviortree_cpp_test PRIVATE
100+
BT_PLUGIN_ISSUE953_PATH="$<TARGET_FILE:plugin_issue953>"
101+
)

tests/gtest_plugin_issue953.cpp

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/**
2+
* Test for Issue #953: convertFromString specialization in plugins not visible
3+
* to main application.
4+
*
5+
* This test loads a plugin (plugin_issue953.so) that defines:
6+
* - A custom type (Issue953Type)
7+
* - The convertFromString<Issue953Type> specialization (ONLY in the plugin)
8+
* - An action node that uses getInput<Issue953Type>()
9+
*
10+
* The key point: this test file does NOT have access to the convertFromString
11+
* specialization. Before the fix, getInput() would fail. After the fix, it
12+
* works because the StringConverter is stored in PortInfo.
13+
*/
14+
15+
#include "behaviortree_cpp/bt_factory.h"
16+
17+
#include <filesystem>
18+
19+
#include <gtest/gtest.h>
20+
21+
using namespace BT;
22+
23+
// Plugin path is defined at compile time via CMake
24+
#ifndef BT_PLUGIN_ISSUE953_PATH
25+
#define BT_PLUGIN_ISSUE953_PATH "plugin_issue953.so"
26+
#endif
27+
28+
class PluginIssue953Test : public testing::Test
29+
{
30+
protected:
31+
void SetUp() override
32+
{
33+
plugin_path_ = BT_PLUGIN_ISSUE953_PATH;
34+
35+
if(!std::filesystem::exists(plugin_path_))
36+
{
37+
GTEST_SKIP() << "Plugin not found at: " << plugin_path_ << ". "
38+
<< "Make sure it's built before running this test.";
39+
}
40+
}
41+
42+
std::string plugin_path_;
43+
};
44+
45+
// Test that getInput works for a custom type defined only in the plugin
46+
TEST_F(PluginIssue953Test, GetInputUsesStoredConverter)
47+
{
48+
// This XML uses a literal string value for the input port
49+
const char* xml_text = R"(
50+
<root BTCPP_format="4">
51+
<BehaviorTree ID="MainTree">
52+
<Issue953Action input="42;hello_world;3.14159"/>
53+
</BehaviorTree>
54+
</root>
55+
)";
56+
57+
BehaviorTreeFactory factory;
58+
59+
// Load the plugin - this registers Issue953Action
60+
// The plugin has the convertFromString<Issue953Type> specialization,
61+
// but THIS file does not.
62+
factory.registerFromPlugin(plugin_path_);
63+
64+
auto tree = factory.createTreeFromText(xml_text);
65+
66+
// This should work because:
67+
// 1. InputPort<Issue953Type>() was called in the plugin
68+
// 2. GetAnyFromStringFunctor captured convertFromString at that point
69+
// 3. The fix makes getInput() use that stored converter
70+
auto status = tree.tickWhileRunning();
71+
72+
ASSERT_EQ(status, NodeStatus::SUCCESS);
73+
74+
// Verify the parsed values via output ports
75+
auto bb = tree.rootBlackboard();
76+
EXPECT_EQ(bb->get<int>("out_id"), 42);
77+
EXPECT_EQ(bb->get<std::string>("out_name"), "hello_world");
78+
EXPECT_DOUBLE_EQ(bb->get<double>("out_value"), 3.14159);
79+
}
80+
81+
// Test with blackboard - value stored as string, then parsed on read
82+
TEST_F(PluginIssue953Test, GetInputFromBlackboardString)
83+
{
84+
const char* xml_text = R"(
85+
<root BTCPP_format="4">
86+
<BehaviorTree ID="MainTree">
87+
<Sequence>
88+
<Script code="my_data := '99;from_script;2.718'" />
89+
<Issue953Action input="{my_data}"/>
90+
</Sequence>
91+
</BehaviorTree>
92+
</root>
93+
)";
94+
95+
BehaviorTreeFactory factory;
96+
factory.registerFromPlugin(plugin_path_);
97+
98+
auto tree = factory.createTreeFromText(xml_text);
99+
auto status = tree.tickWhileRunning();
100+
101+
ASSERT_EQ(status, NodeStatus::SUCCESS);
102+
103+
auto bb = tree.rootBlackboard();
104+
EXPECT_EQ(bb->get<int>("out_id"), 99);
105+
EXPECT_EQ(bb->get<std::string>("out_name"), "from_script");
106+
EXPECT_DOUBLE_EQ(bb->get<double>("out_value"), 2.718);
107+
}
108+
109+
// Test with SubTree port remapping
110+
TEST_F(PluginIssue953Test, GetInputViaSubtreeRemapping)
111+
{
112+
const char* xml_text = R"(
113+
<root BTCPP_format="4" main_tree_to_execute="MainTree">
114+
<BehaviorTree ID="MainTree">
115+
<SubTree ID="Issue953SubTree" data="123;subtree_test;1.5"/>
116+
</BehaviorTree>
117+
118+
<BehaviorTree ID="Issue953SubTree">
119+
<Issue953Action input="{data}"/>
120+
</BehaviorTree>
121+
</root>
122+
)";
123+
124+
BehaviorTreeFactory factory;
125+
factory.registerFromPlugin(plugin_path_);
126+
127+
auto tree = factory.createTreeFromText(xml_text);
128+
auto status = tree.tickWhileRunning();
129+
130+
ASSERT_EQ(status, NodeStatus::SUCCESS);
131+
132+
// Get the subtree's blackboard to check output
133+
auto subtree_bb = tree.subtrees[1]->blackboard;
134+
EXPECT_EQ(subtree_bb->get<int>("out_id"), 123);
135+
EXPECT_EQ(subtree_bb->get<std::string>("out_name"), "subtree_test");
136+
EXPECT_DOUBLE_EQ(subtree_bb->get<double>("out_value"), 1.5);
137+
}

0 commit comments

Comments
 (0)