Skip to content

Commit 6f68ed6

Browse files
facontidavideclaude
andcommitted
Fix #1065: upgrade weakly-typed blackboard entries from subtree string remapping
When a SubTree passes a constant string literal (e.g., queue="1;2;3") to a child tree's port, the subtree remapping stores it as a plain std::string in the child blackboard without type information. Later, when a strongly-typed node like LoopDouble reads the entry, Any::cast<SharedQueue<double>>() fails because there's no conversion path from string to the expected type. Fix: in createNodeFromXML, when initializing port entries, detect when an existing blackboard entry is weakly typed but the port manifest declares a strong type. In this case, convert the string value using the port's parseString() method and upgrade the entry's type info in place. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent d0aedda commit 6f68ed6

File tree

2 files changed

+91
-0
lines changed

2 files changed

+91
-0
lines changed

src/xml_parsing.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,31 @@ TreeNode::Ptr XMLParser::PImpl::createNodeFromXML(const XMLElement* element,
929929
demangle(prev_info->type()), "] and, later type [",
930930
demangle(port_info.type()), "] was used somewhere else.");
931931
}
932+
933+
// If the existing entry is not strongly typed (e.g. set as a plain
934+
// string from subtree remapping) but the port IS strongly typed,
935+
// upgrade the entry by converting the string value. Issue #1065.
936+
if(!prev_info->isStronglyTyped() && port_info.isStronglyTyped())
937+
{
938+
auto entry = blackboard->getEntry(port_key);
939+
if(entry)
940+
{
941+
std::scoped_lock lock(entry->entry_mutex);
942+
if(!entry->value.empty() && entry->value.isString())
943+
{
944+
auto str_val = entry->value.tryCast<std::string>();
945+
if(str_val)
946+
{
947+
auto typed_val = port_info.parseString(*str_val);
948+
if(!typed_val.empty())
949+
{
950+
entry->info = port_info;
951+
entry->value = typed_val;
952+
}
953+
}
954+
}
955+
}
956+
}
932957
}
933958
else
934959
{

tests/gtest_ports.cpp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,3 +581,69 @@ TEST(PortTest, DefaultWronglyOverriden)
581581
// This is correct
582582
ASSERT_NO_THROW(auto tree = factory.createTreeFromText(xml_txt_correct));
583583
}
584+
585+
// Issue #1065: passing a string literal like "1;2;3" through a SubTree port
586+
// to a LoopDouble node should work, but fails because the subtree remapping
587+
// stores the value as a plain std::string in the blackboard without converting
588+
// it to SharedQueue<double>.
589+
class CollectDoubleAction : public SyncActionNode
590+
{
591+
public:
592+
CollectDoubleAction(const std::string& name, const NodeConfig& config,
593+
std::vector<double>* collected)
594+
: SyncActionNode(name, config), collected_(collected)
595+
{}
596+
597+
NodeStatus tick() override
598+
{
599+
double val = 0;
600+
if(getInput("value", val))
601+
{
602+
collected_->push_back(val);
603+
return NodeStatus::SUCCESS;
604+
}
605+
return NodeStatus::FAILURE;
606+
}
607+
608+
static PortsList providedPorts()
609+
{
610+
return { BT::InputPort<double>("value") };
611+
}
612+
613+
private:
614+
std::vector<double>* collected_;
615+
};
616+
617+
TEST(PortTest, SubtreeStringLiteralToLoopDouble_Issue1065)
618+
{
619+
// The main tree passes a string literal "1;2;3" to the subtree port "queue".
620+
// Inside the subtree, LoopDouble should parse it and iterate over the values.
621+
std::string xml_txt = R"(
622+
<root BTCPP_format="4">
623+
<BehaviorTree ID="MainTree">
624+
<SubTree ID="LoopSubTree" queue="1;2;3" />
625+
</BehaviorTree>
626+
627+
<BehaviorTree ID="LoopSubTree">
628+
<LoopDouble queue="{queue}" value="{number}">
629+
<CollectDouble value="{number}" />
630+
</LoopDouble>
631+
</BehaviorTree>
632+
</root>
633+
)";
634+
635+
std::vector<double> collected;
636+
637+
BehaviorTreeFactory factory;
638+
factory.registerNodeType<CollectDoubleAction>("CollectDouble", &collected);
639+
factory.registerBehaviorTreeFromText(xml_txt);
640+
641+
auto tree = factory.createTree("MainTree");
642+
auto status = tree.tickWhileRunning();
643+
644+
ASSERT_EQ(status, NodeStatus::SUCCESS);
645+
ASSERT_EQ(collected.size(), 3u);
646+
EXPECT_DOUBLE_EQ(collected[0], 1.0);
647+
EXPECT_DOUBLE_EQ(collected[1], 2.0);
648+
EXPECT_DOUBLE_EQ(collected[2], 3.0);
649+
}

0 commit comments

Comments
 (0)