Skip to content

Recursive behavior trees #980

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/xml_parsing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#include <csignal>
#include <cstdio>
#include <cstring>
#include <functional>
Expand Down Expand Up @@ -934,8 +935,18 @@ void BT::XMLParser::PImpl::recursivelyCreateSubtree(const std::string& tree_ID,
}
else // special case: SubTreeNode
{
auto new_bb = Blackboard::create(blackboard);
const std::string subtree_ID = element->Attribute("ID");

// check for recursion in behavior tree
if(prefix.find(subtree_ID) != std::string::npos)
Copy link
Preview

Copilot AI Jun 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using a delimiter-aware search to verify the presence of subtree_ID within prefix. This ensures that matching does not produce false positives if one subtree ID is a substring of another.

Suggested change
if(prefix.find(subtree_ID) != std::string::npos)
std::string subtree_delimited = subtree_ID + "/";
if(prefix == subtree_ID || prefix.find(subtree_delimited) == 0)

Copilot uses AI. Check for mistakes.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ensures that matching does not produce false positives if one subtree ID is a substring of another.

Good idea, but the provided solution does not achieve this because of the added ::<UID> for subtree paths.
Is there a method used elsewhere in the library for extracting tokens from a path that could be used for this? Otherwise I can add a string splitting routine. I'm not quite as C++ literate, but in python it would look something like this:

import re

ancestors = re.split(r'::[0-9]*/?', prefix)
if subtree_ID in ancestors:
    raise RuntimeError( ... )

{
auto msg = StrCat("Recursive behavior trees are not supported. A cycle was found "
"in ",
"<Subtree ID=\"", subtree_ID, "\"> with prefix: ", prefix);
throw RuntimeError(msg);
}

auto new_bb = Blackboard::create(blackboard);
std::unordered_map<std::string, std::string> subtree_remapping;
bool do_autoremap = false;

Expand Down
60 changes: 60 additions & 0 deletions tests/gtest_subtree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -726,3 +726,63 @@ TEST(SubTree, SubtreeNameNotRegistered)
ASSERT_ANY_THROW(auto tree = factory.createTreeFromText(xml_text));
ASSERT_ANY_THROW(factory.registerBehaviorTreeFromText(xml_text));
}

TEST(SubTree, RecursiveSubtree)
{
// clang-format off

static const char* xml_text = R"(
<root BTCPP_format="4" >
<BehaviorTree ID="MainTree">
<Sequence name="root">
<AlwaysSuccess/>
<SubTree ID="MainTree" />
</Sequence>
</BehaviorTree>
</root>
)";

// clang-format on
BehaviorTreeFactory factory;
std::vector<std::string> console;
factory.registerNodeType<PrintToConsole>("PrintToConsole", &console);

ASSERT_ANY_THROW(auto tree = factory.createTreeFromText(xml_text));
}

TEST(SubTree, RecursiveCycle)
{
// clang-format off

static const char* xml_text = R"(
<root BTCPP_format="4" main_tree_to_execute="MainTree">
<BehaviorTree ID="MainTree">
<Sequence name="root">
<AlwaysSuccess/>
<SubTree ID="TreeA" />
</Sequence>
</BehaviorTree>

<BehaviorTree ID="TreeA">
<Sequence name="root">
<AlwaysSuccess/>
<SubTree ID="TreeB" />
</Sequence>
</BehaviorTree>

<BehaviorTree ID="TreeB">
<Sequence name="root">
<AlwaysSuccess/>
<SubTree ID="MainTree" />
</Sequence>
</BehaviorTree>
</root>
)";

// clang-format on
BehaviorTreeFactory factory;
std::vector<std::string> console;
factory.registerNodeType<PrintToConsole>("PrintToConsole", &console);

ASSERT_ANY_THROW(auto tree = factory.createTreeFromText(xml_text));
}
Loading