Skip to content

Commit 181cf5a

Browse files
L4co77claude
andcommitted
Preserve numeric types for literal subtree port values
When literal values are passed to SubTree ports (not blackboard remapping), detect numeric types (int64_t, double) before storing them in the child blackboard. Previously all literals were stored as std::string, which caused type-mismatch errors in Script expressions that tried to do arithmetic. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3633e27 commit 181cf5a

File tree

2 files changed

+91
-2
lines changed

2 files changed

+91
-2
lines changed

src/xml_parsing.cpp

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1147,10 +1147,61 @@ void BT::XMLParser::PImpl::recursivelyCreateSubtree(
11471147
}
11481148
else
11491149
{
1150-
// constant string: just set that constant value into the BB
1150+
// constant value: set it into the BB with appropriate type
11511151
// IMPORTANT: this must not be autoremapped!!!
11521152
new_bb->enableAutoRemapping(false);
1153-
new_bb->set(attr_name, static_cast<std::string>(attr_value));
1153+
const std::string str_value(attr_value);
1154+
1155+
// Check if this port has a strongly-typed declaration in the model.
1156+
// If so, store as string and let the normal convertFromString<T>
1157+
// mechanism handle type conversion when the node reads the port.
1158+
bool port_is_strongly_typed = false;
1159+
if(subtree_model_it != subtree_models.end())
1160+
{
1161+
const auto& model_ports = subtree_model_it->second.ports;
1162+
auto port_it = model_ports.find(attr_name);
1163+
if(port_it != model_ports.end() && port_it->second.isStronglyTyped())
1164+
{
1165+
port_is_strongly_typed = true;
1166+
}
1167+
}
1168+
1169+
bool stored = false;
1170+
// Only attempt numeric detection for ports without a declared type,
1171+
// so that Script expressions can do arithmetic on literal values.
1172+
if(!port_is_strongly_typed && !str_value.empty())
1173+
{
1174+
// Try integer first (no decimal point, no exponent notation)
1175+
if(str_value.find('.') == std::string::npos &&
1176+
str_value.find('e') == std::string::npos &&
1177+
str_value.find('E') == std::string::npos)
1178+
{
1179+
try
1180+
{
1181+
const int64_t int_val = convertFromString<int64_t>(str_value);
1182+
new_bb->set(attr_name, int_val);
1183+
stored = true;
1184+
}
1185+
catch(...)
1186+
{}
1187+
}
1188+
// Try double
1189+
if(!stored)
1190+
{
1191+
try
1192+
{
1193+
const double dbl_val = convertFromString<double>(str_value);
1194+
new_bb->set(attr_name, dbl_val);
1195+
stored = true;
1196+
}
1197+
catch(...)
1198+
{}
1199+
}
1200+
}
1201+
if(!stored)
1202+
{
1203+
new_bb->set(attr_name, str_value);
1204+
}
11541205
new_bb->enableAutoRemapping(do_autoremap);
11551206
}
11561207
}

tests/gtest_subtree.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -974,3 +974,41 @@ TEST(SubTree, NestedDuplicateNames_ShouldFail)
974974
// Should throw RuntimeError because of duplicate SubTree names
975975
ASSERT_THROW((void)factory.createTreeFromText(xml_text), RuntimeError);
976976
}
977+
978+
// Regression test: literal numeric values passed to subtrees should preserve
979+
// their numeric type so that Script expressions can do arithmetic.
980+
TEST(SubTree, LiteralNumericPortsPreserveType)
981+
{
982+
// clang-format off
983+
static const char* xml_text = R"(
984+
<root BTCPP_format="4" main_tree_to_execute="MainTree">
985+
986+
<BehaviorTree ID="MainTree">
987+
<Sequence>
988+
<SubTree ID="DoMath" int_val="42" dbl_val="3.14" str_val="hello"
989+
remapped_val="{from_parent}" />
990+
</Sequence>
991+
</BehaviorTree>
992+
993+
<BehaviorTree ID="DoMath">
994+
<Sequence>
995+
<ScriptCondition code=" int_val + 1 == 43 " />
996+
<ScriptCondition code=" dbl_val > 3.0 " />
997+
<ScriptCondition code=" remapped_val + 1 == 101 " />
998+
</Sequence>
999+
</BehaviorTree>
1000+
1001+
</root>
1002+
)";
1003+
// clang-format on
1004+
1005+
BehaviorTreeFactory factory;
1006+
1007+
auto tree = factory.createTreeFromText(xml_text);
1008+
1009+
// Set the remapped parent value as an integer
1010+
tree.rootBlackboard()->set("from_parent", 100);
1011+
1012+
const auto status = tree.tickWhileRunning();
1013+
ASSERT_EQ(status, NodeStatus::SUCCESS);
1014+
}

0 commit comments

Comments
 (0)