Skip to content

Commit 69f6e4a

Browse files
authored
add unit test for subtree with default values and autoremap enabled (#1272)
Fix #1271: with _autoremap="true", SubTree model input_port defaults were never applied. Behavior change: for a modelled input port with a literal default that is not explicitly remapped, the default is now set in the SubTree blackboard if no entry with that name exists in the parent blackboard when the tree is created (creation-time rule, documented in PORT_CONNECTION_RULES.md). A parent key created only at runtime (e.g. by a Script) is shadowed by the default.
1 parent 180324d commit 69f6e4a

3 files changed

Lines changed: 145 additions & 0 deletions

File tree

‎docs/PORT_CONNECTION_RULES.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,12 @@ InputPort<Point2D>("pointE", R"(json:{"x":9,"y":10})", "description")
179179

180180
**Test reference:** `gtest_ports.cpp`
181181

182+
## SubTree Model Defaults with `_autoremap`
183+
184+
A `<SubTree>` entry in `<TreeNodesModel>` can declare an `input_port` with a literal `default`. With `_autoremap="true"`, that default is applied **only if no entry with that name exists in the parent blackboard chain at the moment the tree is created**. If the key is missing at that point, the literal default is stored in the SubTree's own blackboard and shadows the parent: a value written to the parent later (for instance by a `<Script>` that runs before the SubTree) is **not** seen by the SubTree. To make the parent value win, create the key in the blackboard before `createTree()` (e.g. `bb->set("in_value", 7)`), or remap the port explicitly (`in_value="{in_value}"`). Output and bidirectional ports and `{key}` defaults are not affected.
185+
186+
**Test reference:** `gtest_subtree.cpp` - `SubtreeModelDefaultWithAutoremap`, `SubtreeModelDefaultWithAutoremapParentWins`, `SubtreeModelDefaultWithAutoremapCreationTimeRule` tests
187+
182188
## Validation Timeline
183189

184190
### At Tree Creation (XML Parsing)

‎src/xml_parsing.cpp‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,6 +1208,18 @@ void BT::XMLParser::PImpl::recursivelyCreateSubtree(
12081208
for(const auto& [port_name, port_info] : subtree_model_ports)
12091209
{
12101210
auto it = subtree_remapping.find(port_name);
1211+
const auto& default_value = port_info.defaultValueString();
1212+
// _autoremap shares the port with the parent key of the same name; if
1213+
// that key doesn't exist (yet), an input port gets its literal default
1214+
// (#1271). NOTE: checked at creation time, so a parent key created
1215+
// later (e.g. by a Script) is shadowed by the default.
1216+
if(it == subtree_remapping.end() && do_autoremap &&
1217+
port_info.direction() == PortDirection::INPUT && !default_value.empty() &&
1218+
!TreeNode::isBlackboardPointer(default_value) &&
1219+
!new_bb->getEntry(port_name))
1220+
{
1221+
subtree_remapping.insert({ port_name, default_value });
1222+
}
12111223
// don't override existing remapping
12121224
if(it == subtree_remapping.end() && !do_autoremap)
12131225
{

‎tests/gtest_subtree.cpp‎

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,133 @@ TEST(SubTree, SubtreeModels)
595595
tree.tickWhileRunning();
596596
}
597597

598+
TEST(SubTree, SubtreeModelDefault)
599+
{
600+
// clang-format off
601+
602+
static const char* xml_text = R"(
603+
<root main_tree_to_execute = "MainTree" BTCPP_format="4">
604+
<TreeNodesModel>
605+
<SubTree ID="MySub">
606+
<input_port name="in_value" default="42"/>
607+
</SubTree>
608+
</TreeNodesModel>
609+
610+
<BehaviorTree ID="MainTree">
611+
<Sequence>
612+
<SubTree ID="MySub" />
613+
</Sequence>
614+
</BehaviorTree>
615+
616+
<BehaviorTree ID="MySub">
617+
<Sequence>
618+
<ScriptCondition code="in_value==42" />
619+
</Sequence>
620+
</BehaviorTree>
621+
</root>
622+
)";
623+
624+
// clang-format on
625+
626+
BehaviorTreeFactory factory;
627+
auto tree = factory.createTreeFromText(xml_text);
628+
tree.tickWhileRunning();
629+
}
630+
631+
TEST(SubTree, SubtreeModelDefaultWithAutoremap)
632+
{
633+
// clang-format off
634+
635+
static const char* xml_text = R"(
636+
<root main_tree_to_execute = "MainTree" BTCPP_format="4">
637+
<TreeNodesModel>
638+
<SubTree ID="MySub">
639+
<input_port name="in_value" default="42"/>
640+
</SubTree>
641+
</TreeNodesModel>
642+
643+
<BehaviorTree ID="MainTree">
644+
<Sequence>
645+
<SubTree ID="MySub" _autoremap="true" />
646+
</Sequence>
647+
</BehaviorTree>
648+
649+
<BehaviorTree ID="MySub">
650+
<Sequence>
651+
<ScriptCondition code="in_value==42" />
652+
</Sequence>
653+
</BehaviorTree>
654+
</root>
655+
)";
656+
657+
// clang-format on
658+
659+
BehaviorTreeFactory factory;
660+
auto tree = factory.createTreeFromText(xml_text);
661+
tree.tickWhileRunning();
662+
}
663+
664+
// With _autoremap, a parent key of the same name wins over the model default
665+
// (#1271), and the default stays local to the SubTree.
666+
TEST(SubTree, SubtreeModelDefaultWithAutoremapParentWins)
667+
{
668+
static const char* xml_text = R"(
669+
<root main_tree_to_execute="MainTree" BTCPP_format="4">
670+
<TreeNodesModel>
671+
<SubTree ID="MySub">
672+
<input_port name="in_value" default="42"/>
673+
<input_port name="other" default="43"/>
674+
</SubTree>
675+
</TreeNodesModel>
676+
677+
<BehaviorTree ID="MainTree">
678+
<SubTree ID="MySub" _autoremap="true" />
679+
</BehaviorTree>
680+
681+
<BehaviorTree ID="MySub">
682+
<ScriptCondition code="in_value==7 &amp;&amp; other==43" />
683+
</BehaviorTree>
684+
</root>)";
685+
686+
BehaviorTreeFactory factory;
687+
auto root_bb = Blackboard::create();
688+
root_bb->set("in_value", 7);
689+
auto tree = factory.createTreeFromText(xml_text, root_bb);
690+
EXPECT_EQ(tree.tickWhileRunning(), NodeStatus::SUCCESS);
691+
EXPECT_FALSE(root_bb->getEntry("other"));
692+
}
693+
694+
// Deliberate creation-time rule (#1271): the model default is applied only if no
695+
// entry named "in_value" exists in the parent blackboard chain when the tree is
696+
// CREATED. A key written later (here by a Script) does not win; to make the parent
697+
// value win, set the key in the blackboard before createTree (see test above).
698+
TEST(SubTree, SubtreeModelDefaultWithAutoremapCreationTimeRule)
699+
{
700+
static const char* xml_text = R"(
701+
<root main_tree_to_execute="MainTree" BTCPP_format="4">
702+
<TreeNodesModel>
703+
<SubTree ID="MySub">
704+
<input_port name="in_value" default="42"/>
705+
</SubTree>
706+
</TreeNodesModel>
707+
708+
<BehaviorTree ID="MainTree">
709+
<Sequence>
710+
<Script code="in_value:=7" />
711+
<SubTree ID="MySub" _autoremap="true" />
712+
</Sequence>
713+
</BehaviorTree>
714+
715+
<BehaviorTree ID="MySub">
716+
<ScriptCondition code="in_value==42" />
717+
</BehaviorTree>
718+
</root>)";
719+
720+
BehaviorTreeFactory factory;
721+
auto tree = factory.createTreeFromText(xml_text);
722+
EXPECT_EQ(tree.tickWhileRunning(), NodeStatus::SUCCESS);
723+
}
724+
598725
class PrintToConsole : public BT::SyncActionNode
599726
{
600727
public:

0 commit comments

Comments
 (0)