@@ -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;
@@ -654,3 +677,180 @@ TEST(BlackboardTest, TimestampedInterface)
654
677
ASSERT_EQ (stamp_opt->seq , 2 );
655
678
ASSERT_GE (stamp_opt->time .count (), nsec_before);
656
679
}
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