Skip to content

Commit 2371eac

Browse files
committed
add the ability to load substitution rules from JSON
1 parent d7073b8 commit 2371eac

File tree

6 files changed

+201
-43
lines changed

6 files changed

+201
-43
lines changed

docs/substitution_sample.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"TestNodeConfigs": {
3+
"MyTest": {
4+
"async_delay": 2000,
5+
"return_status": "SUCCESS",
6+
"post_script": "msg ='message SUBSTITUED'"
7+
}
8+
},
9+
10+
"SubstitutionRules": {
11+
"mysub/action_*": "TestAction",
12+
"talk": "TestSaySomething",
13+
"last_action": "MyTest"
14+
}
15+
}

examples/t11_replace_rules.cpp

Lines changed: 63 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,48 @@
66
static const char* xml_text = R"(
77
<root BTCPP_format="4">
88
9-
<BehaviorTree ID="MainTree">
10-
<Sequence>
11-
<SaySomething name="talk" message="hello world"/>
12-
<Fallback>
13-
<AlwaysFailure name="failing_action"/>
14-
<SubTree ID="MySub" name="mysub"/>
15-
</Fallback>
16-
<SaySomething message="before last_action"/>
17-
<Script code="msg:='after last_action'"/>
18-
<AlwaysSuccess name="last_action"/>
19-
<SaySomething message="{msg}"/>
20-
</Sequence>
21-
</BehaviorTree>
22-
23-
<BehaviorTree ID="MySub">
24-
<Sequence>
25-
<AlwaysSuccess name="action_subA"/>
26-
<AlwaysSuccess name="action_subB"/>
27-
</Sequence>
28-
</BehaviorTree>
9+
<BehaviorTree ID="MainTree">
10+
<Sequence>
11+
<SaySomething name="talk" message="hello world"/>
12+
<Fallback>
13+
<AlwaysFailure name="failing_action"/>
14+
<SubTree ID="MySub" name="mysub"/>
15+
</Fallback>
16+
<SaySomething message="before last_action"/>
17+
<Script code="msg:='after last_action'"/>
18+
<AlwaysSuccess name="last_action"/>
19+
<SaySomething message="{msg}"/>
20+
</Sequence>
21+
</BehaviorTree>
22+
23+
<BehaviorTree ID="MySub">
24+
<Sequence>
25+
<AlwaysSuccess name="action_subA"/>
26+
<AlwaysSuccess name="action_subB"/>
27+
</Sequence>
28+
</BehaviorTree>
2929
3030
</root>
3131
)";
3232

33+
static const char* json_text = R"(
34+
{
35+
"TestNodeConfigs": {
36+
"MyTest": {
37+
"async_delay": 2000,
38+
"return_status": "SUCCESS",
39+
"post_script": "msg ='message SUBSTITUED'"
40+
}
41+
},
42+
43+
"SubstitutionRules": {
44+
"mysub/action_*": "TestAction",
45+
"talk": "TestSaySomething",
46+
"last_action": "MyTest"
47+
}
48+
}
49+
)";
50+
3351
// clang-format on
3452

3553
int main(int argc, char** argv)
@@ -60,28 +78,38 @@ int main(int argc, char** argv)
6078
return BT::NodeStatus::SUCCESS;
6179
});
6280

63-
// These configurations will be passed to a TestNode
64-
BT::TestNodeConfig test_config;
65-
// Convert the node in asynchronous and wait 2000 ms
66-
test_config.async_delay = std::chrono::milliseconds(2000);
67-
// Execute this postcondition, once completed
68-
test_config.post_script = "msg ='message SUBSTITUED'";
69-
7081
//----------------------------
7182
// pass "no_sub" as first argument to avoid adding rules
7283
bool skip_substitution = (argc == 2) && std::string(argv[1]) == "no_sub";
7384

7485
if(!skip_substitution)
7586
{
76-
// Substitute nodes which match this wildcard pattern with TestAction
77-
factory.addSubstitutionRule("mysub/action_*", "TestAction");
78-
79-
// Substitute the node with name [talk] with TestSaySomething
80-
factory.addSubstitutionRule("talk", "TestSaySomething");
87+
// we can use a JSOn file to configure the substitution rules
88+
// or do it manually
89+
bool const USE_JSON = true;
8190

82-
// Substitute the node with name [last_action] with a TestNode,
83-
// configured using test_config
84-
factory.addSubstitutionRule("last_action", test_config);
91+
if(USE_JSON)
92+
{
93+
factory.loadSubstitutionRuleFromJSON(json_text);
94+
}
95+
else {
96+
// Substitute nodes which match this wildcard pattern with TestAction
97+
factory.addSubstitutionRule("mysub/action_*", "TestAction");
98+
99+
// Substitute the node with name [talk] with TestSaySomething
100+
factory.addSubstitutionRule("talk", "TestSaySomething");
101+
102+
// This configuration will be passed to a TestNode
103+
BT::TestNodeConfig test_config;
104+
// Convert the node in asynchronous and wait 2000 ms
105+
test_config.async_delay = std::chrono::milliseconds(2000);
106+
// Execute this postcondition, once completed
107+
test_config.post_script = "msg ='message SUBSTITUED'";
108+
109+
// Substitute the node with name [last_action] with a TestNode,
110+
// configured using test_config
111+
factory.addSubstitutionRule("last_action", test_config);
112+
}
85113
}
86114

87115
factory.registerBehaviorTreeFromText(xml_text);

include/behaviortree_cpp/bt_factory.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,21 @@ class BehaviorTreeFactory
439439
*/
440440
void addSubstitutionRule(StringView filter, SubstitutionRule rule);
441441

442+
/**
443+
* @brief loadSubstitutionRuleFromJSON will parse a JSON file to
444+
* create a set of substitution rules. See Tutorial 11
445+
* for an example of the syntax.
446+
*
447+
* @param json_text the JSON file as text (BOT the path of the file)
448+
*/
449+
void loadSubstitutionRuleFromJSON(const std::string& json_text);
450+
451+
/**
452+
* @brief substitutionRules return the current substitution rules.
453+
*/
454+
const std::unordered_map<std::string, SubstitutionRule>&
455+
substitutionRules() const;
456+
442457
private:
443458
std::unordered_map<std::string, NodeBuilder> builders_;
444459
std::unordered_map<std::string, TreeNodeManifest> manifests_;

src/bt_factory.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <fstream>
1515
#include "behaviortree_cpp/bt_factory.h"
1616
#include "behaviortree_cpp/utils/shared_library.h"
17+
#include "behaviortree_cpp/contrib/json.hpp"
1718
#include "behaviortree_cpp/xml_parsing.h"
1819
#include "wildcards/wildcards.hpp"
1920

@@ -419,6 +420,51 @@ void BehaviorTreeFactory::addSubstitutionRule(StringView filter, SubstitutionRul
419420
substitution_rules_[std::string(filter)] = rule;
420421
}
421422

423+
void BehaviorTreeFactory::loadSubstitutionRuleFromJSON(const std::string &json_text)
424+
{
425+
auto const json = nlohmann::json::parse(json_text);
426+
427+
std::unordered_map<std::string, TestNodeConfig> configs;
428+
429+
auto test_configs = json.at("TestNodeConfigs");
430+
for(auto const& [name, test_config]: test_configs.items())
431+
{
432+
auto& config = configs[name];
433+
434+
auto status = test_config.at("return_status").get<std::string>();
435+
config.return_status = convertFromString<NodeStatus>(status);
436+
if(test_config.contains("async_delay"))
437+
{
438+
config.async_delay =
439+
std::chrono::milliseconds(test_config["async_delay"].get<int>());
440+
}
441+
if(test_config.contains("post_script"))
442+
{
443+
config.post_script = test_config["post_script"].get<std::string>();
444+
}
445+
}
446+
447+
auto substitutions = json.at("SubstitutionRules");
448+
for(auto const& [node_name, test]: substitutions.items())
449+
{
450+
auto test_name = test.get<std::string>();
451+
auto it = configs.find(test_name);
452+
if(it == configs.end())
453+
{
454+
addSubstitutionRule(node_name, test_name);
455+
}
456+
else {
457+
addSubstitutionRule(node_name, it->second);
458+
}
459+
}
460+
}
461+
462+
const std::unordered_map<std::string, BehaviorTreeFactory::SubstitutionRule> &
463+
BehaviorTreeFactory::substitutionRules() const
464+
{
465+
return substitution_rules_;
466+
}
467+
422468

423469
void Tree::initialize()
424470
{

tests/CMakeLists.txt

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,28 @@
44
set(BT_TESTS
55
src/action_test_node.cpp
66
src/condition_test_node.cpp
7-
gtest_tree.cpp
8-
gtest_sequence.cpp
9-
gtest_parallel.cpp
10-
gtest_fallback.cpp
11-
gtest_factory.cpp
12-
gtest_decorator.cpp
7+
138
gtest_blackboard.cpp
9+
gtest_coroutines.cpp
10+
gtest_decorator.cpp
11+
gtest_factory.cpp
12+
gtest_fallback.cpp
13+
gtest_parallel.cpp
1414
gtest_preconditions.cpp
1515
gtest_postconditions.cpp
1616
gtest_match.cpp
1717
gtest_ports.cpp
1818
gtest_json.cpp
19+
gtest_sequence.cpp
1920
gtest_skipping.cpp
21+
gtest_substitution.cpp
2022
gtest_subtree.cpp
2123
gtest_switch.cpp
24+
gtest_tree.cpp
2225
gtest_wakeup.cpp
23-
test_helper.hpp
26+
2427
script_parser_test.cpp
25-
gtest_coroutines.cpp
28+
test_helper.hpp
2629
)
2730

2831
set(TEST_DEPENDECIES

tests/gtest_substitution.cpp

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#include <gtest/gtest.h>
2+
#include "behaviortree_cpp/bt_factory.h"
3+
4+
using namespace BT;
5+
6+
static const char* json_text = R"(
7+
{
8+
"TestNodeConfigs": {
9+
"TestA": {
10+
"async_delay": 2000,
11+
"return_status": "SUCCESS",
12+
"post_script": "msg ='message SUBSTITUED'"
13+
},
14+
"TestB": {
15+
"return_status": "FAILURE"
16+
}
17+
},
18+
19+
"SubstitutionRules": {
20+
"actionA": "TestA",
21+
"actionB": "TestB",
22+
"actionC": "NotAConfig"
23+
}
24+
}
25+
)";
26+
27+
TEST(Substitution, Parser)
28+
{
29+
BehaviorTreeFactory factory;
30+
31+
factory.loadSubstitutionRuleFromJSON(json_text);
32+
33+
const auto& rules = factory.substitutionRules();
34+
35+
ASSERT_EQ(rules.size(), 3);
36+
ASSERT_EQ(rules.count("actionA"), 1);
37+
ASSERT_EQ(rules.count("actionB"), 1);
38+
ASSERT_EQ(rules.count("actionC"), 1);
39+
40+
auto configA = std::get_if<TestNodeConfig>(&rules.at("actionA"));
41+
ASSERT_EQ(configA->return_status, NodeStatus::SUCCESS);
42+
ASSERT_EQ(configA->async_delay, std::chrono::milliseconds(2000));
43+
ASSERT_EQ(configA->post_script, "msg ='message SUBSTITUED'");
44+
45+
auto configB = std::get_if<TestNodeConfig>(&rules.at("actionB"));
46+
ASSERT_EQ(configB->return_status, NodeStatus::FAILURE);
47+
ASSERT_EQ(configB->async_delay, std::chrono::milliseconds(0));
48+
ASSERT_TRUE(configB->post_script.empty());
49+
50+
ASSERT_EQ(*std::get_if<std::string>(&rules.at("actionC")), "NotAConfig");
51+
}

0 commit comments

Comments
 (0)