Skip to content

Commit afa1bce

Browse files
match subtrees by name in ImportTreeFromJSON (#1227)
ExportTreeToJSON keys the document by subtree name and nlohmann::json iterates keys in alphabetical order, so pairing the n-th object with tree.subtrees[n] restored each blackboard into a different subtree. Look every subtree up by name instead.
1 parent d1098a6 commit afa1bce

2 files changed

Lines changed: 67 additions & 4 deletions

File tree

‎src/bt_factory.cpp‎

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -858,11 +858,23 @@ void ImportTreeFromJSON(const nlohmann::json& json, Tree& tree)
858858
throw std::runtime_error("Number of blackboards don't match:");
859859
}
860860

861-
size_t index = 0;
862-
for(const auto& [key, array] : json.items())
861+
// The JSON object is keyed by subtree name (see ExportTreeToJSON) and is
862+
// iterated in alphabetical order, which is unrelated to the order of
863+
// tree.subtrees. Look up each blackboard by name, not by position.
864+
for(const auto& subtree : tree.subtrees)
863865
{
864-
auto& subtree = tree.subtrees.at(index++);
865-
ImportBlackboardFromJSON(array, *subtree->blackboard);
866+
auto sub_name = subtree->instance_name;
867+
if(sub_name.empty())
868+
{
869+
sub_name = subtree->tree_ID;
870+
}
871+
const auto it = json.find(sub_name);
872+
if(it == json.end())
873+
{
874+
throw std::runtime_error(
875+
StrCat("ImportTreeFromJSON: missing blackboard of subtree [", sub_name, "]"));
876+
}
877+
ImportBlackboardFromJSON(*it, *subtree->blackboard);
866878
}
867879
}
868880

‎tests/gtest_blackboard.cpp‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,57 @@ TEST(BlackboardTest, BlackboardRestoreSizeMismatch)
743743
ASSERT_NO_THROW(BlackboardRestore(big_backup, big_tree));
744744
}
745745

746+
TEST(BlackboardTest, ImportTreeFromJSONMatchesByName)
747+
{
748+
BT::BehaviorTreeFactory factory;
749+
750+
// The subtrees are created in the order [MainTree, Zeta, Alpha], but the
751+
// JSON object is sorted by name: [Alpha, MainTree, Zeta].
752+
const std::string xml_text = R"(
753+
<root BTCPP_format="4" main_tree_to_execute="MainTree">
754+
<BehaviorTree ID="MainTree">
755+
<Sequence>
756+
<Script code=" owner:='main' " />
757+
<SubTree ID="Zeta" />
758+
<SubTree ID="Alpha" />
759+
</Sequence>
760+
</BehaviorTree>
761+
<BehaviorTree ID="Zeta">
762+
<Script code=" owner:='zeta' " />
763+
</BehaviorTree>
764+
<BehaviorTree ID="Alpha">
765+
<Script code=" owner:='alpha'; only_in_alpha:=42 " />
766+
</BehaviorTree>
767+
</root> )";
768+
769+
auto tree = factory.createTreeFromText(xml_text);
770+
ASSERT_EQ(tree.tickWhileRunning(), BT::NodeStatus::SUCCESS);
771+
ASSERT_EQ(tree.subtrees.size(), 3u);
772+
773+
const std::vector<std::string> expected_owner = { "main", "zeta", "alpha" };
774+
775+
// Each blackboard must be restored into the subtree it was exported from.
776+
const auto json = ExportTreeToJSON(tree);
777+
ImportTreeFromJSON(json, tree);
778+
779+
for(size_t i = 0; i < tree.subtrees.size(); i++)
780+
{
781+
const auto& blackboard = tree.subtrees[i]->blackboard;
782+
ASSERT_EQ(blackboard->get<std::string>("owner"), expected_owner[i]);
783+
}
784+
// this entry exists only in the blackboard of the last subtree
785+
ASSERT_EQ(tree.subtrees[0]->blackboard->getEntry("only_in_alpha"), nullptr);
786+
ASSERT_EQ(tree.subtrees[1]->blackboard->getEntry("only_in_alpha"), nullptr);
787+
ASSERT_NE(tree.subtrees[2]->blackboard->getEntry("only_in_alpha"), nullptr);
788+
789+
// A JSON that doesn't describe one of the subtrees is rejected, instead of
790+
// being imported into whichever subtree has the same position.
791+
auto renamed = json;
792+
renamed["Unknown"] = renamed["MainTree"];
793+
renamed.erase("MainTree");
794+
ASSERT_THROW(ImportTreeFromJSON(renamed, tree), std::runtime_error);
795+
}
796+
746797
TEST(BlackboardTest, RootBlackboard)
747798
{
748799
BT::BehaviorTreeFactory factory;

0 commit comments

Comments
 (0)