@@ -458,6 +458,29 @@ struct Point
458
458
double y;
459
459
};
460
460
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
+
461
484
TEST (BlackboardTest, SetBlackboard_Issue725)
462
485
{
463
486
BT::BehaviorTreeFactory factory;
@@ -691,7 +714,7 @@ TEST(BlackboardTest, SetBlackboard_Upd_Ts_SeqId)
691
714
ASSERT_GT (seq_id2, seq_id1);
692
715
}
693
716
694
- TEST (BlackboardTest, SetBlackboard_ChangeType )
717
+ TEST (BlackboardTest, SetBlackboard_ChangeType1 )
695
718
{
696
719
BT::BehaviorTreeFactory factory;
697
720
@@ -717,7 +740,117 @@ TEST(BlackboardTest, SetBlackboard_ChangeType)
717
740
// First tick should succeed
718
741
ASSERT_NO_THROW (tree.tickExactlyOnce ());
719
742
const auto entry_ptr = blackboard->getEntry (" other_point" );
743
+ std::this_thread::sleep_for (std::chrono::milliseconds{ 5 });
720
744
// Second tick should throw due to type mismatch
721
- EXPECT_THROW ({ tree.tickWhileRunning (); }, BT::LogicError);
722
- // EXPECT_EQ();
745
+ EXPECT_THROW ({ tree.tickExactlyOnce (); }, 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.tickExactlyOnce (); }, 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 (););
723
856
}
0 commit comments