@@ -458,6 +458,29 @@ struct Point
458458 double y;
459459};
460460
461+ // Template specialization to converts a string to Point.
462+ namespace BT
463+ {
464+ template <>
465+ [[nodiscard]] Point convertFromString (StringView str)
466+ {
467+ // We expect real numbers separated by semicolons
468+ auto parts = splitString (str, ' ;' );
469+ if (parts.size () != 2 )
470+ {
471+ throw RuntimeError (" invalid input)" );
472+ }
473+ else
474+ {
475+ Point output{ 0.0 , 0.0 };
476+ output.x = convertFromString<double >(parts[0 ]);
477+ output.y = convertFromString<double >(parts[1 ]);
478+ // std::cout << "Building a position 2d object " << output.x << "; " << output.y << "\n" << std::flush;
479+ return output;
480+ }
481+ }
482+ } // end namespace BT
483+
461484TEST (BlackboardTest, SetBlackboard_Issue725)
462485{
463486 BT::BehaviorTreeFactory factory;
@@ -654,3 +677,180 @@ TEST(BlackboardTest, TimestampedInterface)
654677 ASSERT_EQ (stamp_opt->seq , 2 );
655678 ASSERT_GE (stamp_opt->time .count (), nsec_before);
656679}
680+
681+ TEST (BlackboardTest, SetBlackboard_Upd_Ts_SeqId)
682+ {
683+ BT::BehaviorTreeFactory factory;
684+
685+ const std::string xml_text = R"(
686+ <root BTCPP_format="4">
687+ <BehaviorTree ID="MainTree">
688+ <Sequence>
689+ <Script code="other_point:=first_point" />
690+ <Sleep msec="5" />
691+ <SetBlackboard value="{second_point}" output_key="other_point" />
692+ </Sequence>
693+ </BehaviorTree>
694+ </root> )" ;
695+
696+ factory.registerBehaviorTreeFromText (xml_text);
697+ auto tree = factory.createTree (" MainTree" );
698+ auto & blackboard = tree.subtrees .front ()->blackboard ;
699+
700+ const Point point1 = { 2 , 2 };
701+ const Point point2 = { 3 , 3 };
702+ blackboard->set (" first_point" , point1);
703+ blackboard->set (" second_point" , point2);
704+
705+ tree.tickExactlyOnce ();
706+ const auto entry_ptr = blackboard->getEntry (" other_point" );
707+ const auto ts1 = entry_ptr->stamp ;
708+ const auto seq_id1 = entry_ptr->sequence_id ;
709+ tree.tickWhileRunning ();
710+ const auto ts2 = entry_ptr->stamp ;
711+ const auto seq_id2 = entry_ptr->sequence_id ;
712+
713+ ASSERT_GT (ts2.count (), ts1.count ());
714+ ASSERT_GT (seq_id2, seq_id1);
715+ }
716+
717+ TEST (BlackboardTest, SetBlackboard_ChangeType1)
718+ {
719+ BT::BehaviorTreeFactory factory;
720+
721+ const std::string xml_text = R"(
722+ <root BTCPP_format="4">
723+ <BehaviorTree ID="MainTree">
724+ <Sequence>
725+ <SetBlackboard value="{first_point}" output_key="other_point" />
726+ <Sleep msec="5" />
727+ <SetBlackboard value="{random_str}" output_key="other_point" />
728+ </Sequence>
729+ </BehaviorTree>
730+ </root> )" ;
731+
732+ factory.registerBehaviorTreeFromText (xml_text);
733+ auto tree = factory.createTree (" MainTree" );
734+ auto & blackboard = tree.subtrees .front ()->blackboard ;
735+
736+ const Point point = { 2 , 7 };
737+ blackboard->set (" first_point" , point);
738+ blackboard->set (" random_str" , " Hello!" );
739+
740+ // First tick should succeed
741+ ASSERT_NO_THROW (tree.tickExactlyOnce ());
742+ const auto entry_ptr = blackboard->getEntry (" other_point" );
743+ std::this_thread::sleep_for (std::chrono::milliseconds{ 5 });
744+ // Second tick should throw due to type mismatch
745+ EXPECT_THROW ({ tree.tickWhileRunning (); }, BT::LogicError);
746+ }
747+
748+ TEST (BlackboardTest, SetBlackboard_ChangeType2)
749+ {
750+ BT::BehaviorTreeFactory factory;
751+
752+ const std::string xml_text = R"(
753+ <root BTCPP_format="4">
754+ <BehaviorTree ID="MainTree">
755+ <Sequence>
756+ <SetBlackboard value="{first_point}" output_key="other_point" />
757+ <Sleep msec="5" />
758+ <SetBlackboard value="{random_num}" output_key="other_point" />
759+ </Sequence>
760+ </BehaviorTree>
761+ </root> )" ;
762+
763+ factory.registerBehaviorTreeFromText (xml_text);
764+ auto tree = factory.createTree (" MainTree" );
765+ auto & blackboard = tree.subtrees .front ()->blackboard ;
766+
767+ const Point point = { 2 , 7 };
768+ blackboard->set (" first_point" , point);
769+ blackboard->set (" random_num" , 57 );
770+
771+ // First tick should succeed
772+ ASSERT_NO_THROW (tree.tickExactlyOnce ());
773+ const auto entry_ptr = blackboard->getEntry (" other_point" );
774+ std::this_thread::sleep_for (std::chrono::milliseconds{ 5 });
775+ // Second tick should throw due to type mismatch
776+ EXPECT_THROW ({ tree.tickWhileRunning (); }, BT::LogicError);
777+ }
778+
779+ // Simple Action that updates an instance of Point in the blackboard
780+ class UpdatePosition : public BT ::SyncActionNode
781+ {
782+ public:
783+ UpdatePosition (const std::string& name, const BT::NodeConfig& config)
784+ : BT::SyncActionNode(name, config)
785+ {}
786+
787+ BT::NodeStatus tick () override
788+ {
789+ const auto in_pos = getInput<Point>(" pos_in" );
790+ if (!in_pos.has_value ())
791+ return BT::NodeStatus::FAILURE;
792+ Point _pos = in_pos.value ();
793+ _pos.x += getInput<double >(" x" ).value_or (0.0 );
794+ _pos.y += getInput<double >(" y" ).value_or (0.0 );
795+ setOutput (" pos_out" , _pos);
796+ return BT::NodeStatus::SUCCESS;
797+ }
798+
799+ static BT::PortsList providedPorts ()
800+ {
801+ return { BT::InputPort<Point>(" pos_in" , { 0.0 , 0.0 }, " Initial position" ),
802+ BT::InputPort<double >(" x" ), BT::InputPort<double >(" y" ),
803+ BT::OutputPort<Point>(" pos_out" ) };
804+ }
805+
806+ private:
807+ };
808+
809+ TEST (BlackboardTest, SetBlackboard_WithPortRemapping)
810+ {
811+ BT::BehaviorTreeFactory factory;
812+
813+ const std::string xml_text = R"(
814+ <?xml version="1.0"?>
815+ <root BTCPP_format="4" main_tree_to_execute="MainTree">
816+ <BehaviorTree ID="MainTree">
817+ <Sequence>
818+ <SetBlackboard output_key="pos" value="0.0;0.0" />
819+ <Repeat num_cycles="3">
820+ <Sequence>
821+ <UpdatePosition pos_in="{pos}" x="0.1" y="0.2" pos_out="{pos}"/>
822+ <SubTree ID="UpdPosPlus" _autoremap="true" new_pos="2.2;2.4" />
823+ <Sleep msec="125"/>
824+ <SetBlackboard output_key="pos" value="22.0;22.0" />
825+ </Sequence>
826+ </Repeat>
827+ </Sequence>
828+ </BehaviorTree>
829+ <BehaviorTree ID="UpdPosPlus">
830+ <Sequence>
831+ <SetBlackboard output_key="pos" value="3.0;5.0" />
832+ <SetBlackboard output_key="pos" value="{new_pos}" />
833+ </Sequence>
834+ </BehaviorTree>
835+ </root>
836+ )" ;
837+
838+ factory.registerNodeType <UpdatePosition>(" UpdatePosition" );
839+ factory.registerBehaviorTreeFromText (xml_text);
840+ auto tree = factory.createTree (" MainTree" );
841+ auto & blackboard = tree.subtrees .front ()->blackboard ;
842+
843+ // First tick should succeed and update the value within the subtree
844+ ASSERT_NO_THROW (tree.tickExactlyOnce ());
845+
846+ const auto entry_ptr = blackboard->getEntry (" pos" );
847+ ASSERT_EQ (entry_ptr->value .type (), typeid (Point));
848+
849+ const auto x = entry_ptr->value .cast <Point>().x ;
850+ const auto y = entry_ptr->value .cast <Point>().y ;
851+ ASSERT_EQ (x, 2.2 );
852+ ASSERT_EQ (y, 2.4 );
853+
854+ // Tick till the end with no crashes
855+ ASSERT_NO_THROW (tree.tickWhileRunning (););
856+ }
0 commit comments