Skip to content

Commit

Permalink
add the ability to load substitution rules from JSON
Browse files Browse the repository at this point in the history
  • Loading branch information
facontidavide committed Mar 20, 2023
1 parent d7073b8 commit 2371eac
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 43 deletions.
15 changes: 15 additions & 0 deletions docs/substitution_sample.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"TestNodeConfigs": {
"MyTest": {
"async_delay": 2000,
"return_status": "SUCCESS",
"post_script": "msg ='message SUBSTITUED'"
}
},

"SubstitutionRules": {
"mysub/action_*": "TestAction",
"talk": "TestSaySomething",
"last_action": "MyTest"
}
}
98 changes: 63 additions & 35 deletions examples/t11_replace_rules.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,48 @@
static const char* xml_text = R"(
<root BTCPP_format="4">
<BehaviorTree ID="MainTree">
<Sequence>
<SaySomething name="talk" message="hello world"/>
<Fallback>
<AlwaysFailure name="failing_action"/>
<SubTree ID="MySub" name="mysub"/>
</Fallback>
<SaySomething message="before last_action"/>
<Script code="msg:='after last_action'"/>
<AlwaysSuccess name="last_action"/>
<SaySomething message="{msg}"/>
</Sequence>
</BehaviorTree>
<BehaviorTree ID="MySub">
<Sequence>
<AlwaysSuccess name="action_subA"/>
<AlwaysSuccess name="action_subB"/>
</Sequence>
</BehaviorTree>
<BehaviorTree ID="MainTree">
<Sequence>
<SaySomething name="talk" message="hello world"/>
<Fallback>
<AlwaysFailure name="failing_action"/>
<SubTree ID="MySub" name="mysub"/>
</Fallback>
<SaySomething message="before last_action"/>
<Script code="msg:='after last_action'"/>
<AlwaysSuccess name="last_action"/>
<SaySomething message="{msg}"/>
</Sequence>
</BehaviorTree>
<BehaviorTree ID="MySub">
<Sequence>
<AlwaysSuccess name="action_subA"/>
<AlwaysSuccess name="action_subB"/>
</Sequence>
</BehaviorTree>
</root>
)";

static const char* json_text = R"(
{
"TestNodeConfigs": {
"MyTest": {
"async_delay": 2000,
"return_status": "SUCCESS",
"post_script": "msg ='message SUBSTITUED'"
}
},
"SubstitutionRules": {
"mysub/action_*": "TestAction",
"talk": "TestSaySomething",
"last_action": "MyTest"
}
}
)";

// clang-format on

int main(int argc, char** argv)
Expand Down Expand Up @@ -60,28 +78,38 @@ int main(int argc, char** argv)
return BT::NodeStatus::SUCCESS;
});

// These configurations will be passed to a TestNode
BT::TestNodeConfig test_config;
// Convert the node in asynchronous and wait 2000 ms
test_config.async_delay = std::chrono::milliseconds(2000);
// Execute this postcondition, once completed
test_config.post_script = "msg ='message SUBSTITUED'";

//----------------------------
// pass "no_sub" as first argument to avoid adding rules
bool skip_substitution = (argc == 2) && std::string(argv[1]) == "no_sub";

if(!skip_substitution)
{
// Substitute nodes which match this wildcard pattern with TestAction
factory.addSubstitutionRule("mysub/action_*", "TestAction");

// Substitute the node with name [talk] with TestSaySomething
factory.addSubstitutionRule("talk", "TestSaySomething");
// we can use a JSOn file to configure the substitution rules
// or do it manually
bool const USE_JSON = true;

// Substitute the node with name [last_action] with a TestNode,
// configured using test_config
factory.addSubstitutionRule("last_action", test_config);
if(USE_JSON)
{
factory.loadSubstitutionRuleFromJSON(json_text);
}
else {
// Substitute nodes which match this wildcard pattern with TestAction
factory.addSubstitutionRule("mysub/action_*", "TestAction");

// Substitute the node with name [talk] with TestSaySomething
factory.addSubstitutionRule("talk", "TestSaySomething");

// This configuration will be passed to a TestNode
BT::TestNodeConfig test_config;
// Convert the node in asynchronous and wait 2000 ms
test_config.async_delay = std::chrono::milliseconds(2000);
// Execute this postcondition, once completed
test_config.post_script = "msg ='message SUBSTITUED'";

// Substitute the node with name [last_action] with a TestNode,
// configured using test_config
factory.addSubstitutionRule("last_action", test_config);
}
}

factory.registerBehaviorTreeFromText(xml_text);
Expand Down
15 changes: 15 additions & 0 deletions include/behaviortree_cpp/bt_factory.h
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,21 @@ class BehaviorTreeFactory
*/
void addSubstitutionRule(StringView filter, SubstitutionRule rule);

/**
* @brief loadSubstitutionRuleFromJSON will parse a JSON file to
* create a set of substitution rules. See Tutorial 11
* for an example of the syntax.
*
* @param json_text the JSON file as text (BOT the path of the file)
*/
void loadSubstitutionRuleFromJSON(const std::string& json_text);

/**
* @brief substitutionRules return the current substitution rules.
*/
const std::unordered_map<std::string, SubstitutionRule>&
substitutionRules() const;

private:
std::unordered_map<std::string, NodeBuilder> builders_;
std::unordered_map<std::string, TreeNodeManifest> manifests_;
Expand Down
46 changes: 46 additions & 0 deletions src/bt_factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <fstream>
#include "behaviortree_cpp/bt_factory.h"
#include "behaviortree_cpp/utils/shared_library.h"
#include "behaviortree_cpp/contrib/json.hpp"
#include "behaviortree_cpp/xml_parsing.h"
#include "wildcards/wildcards.hpp"

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

void BehaviorTreeFactory::loadSubstitutionRuleFromJSON(const std::string &json_text)
{
auto const json = nlohmann::json::parse(json_text);

std::unordered_map<std::string, TestNodeConfig> configs;

auto test_configs = json.at("TestNodeConfigs");
for(auto const& [name, test_config]: test_configs.items())
{
auto& config = configs[name];

auto status = test_config.at("return_status").get<std::string>();
config.return_status = convertFromString<NodeStatus>(status);
if(test_config.contains("async_delay"))
{
config.async_delay =
std::chrono::milliseconds(test_config["async_delay"].get<int>());
}
if(test_config.contains("post_script"))
{
config.post_script = test_config["post_script"].get<std::string>();
}
}

auto substitutions = json.at("SubstitutionRules");
for(auto const& [node_name, test]: substitutions.items())
{
auto test_name = test.get<std::string>();
auto it = configs.find(test_name);
if(it == configs.end())
{
addSubstitutionRule(node_name, test_name);
}
else {
addSubstitutionRule(node_name, it->second);
}
}
}

const std::unordered_map<std::string, BehaviorTreeFactory::SubstitutionRule> &
BehaviorTreeFactory::substitutionRules() const
{
return substitution_rules_;
}


void Tree::initialize()
{
Expand Down
19 changes: 11 additions & 8 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,28 @@
set(BT_TESTS
src/action_test_node.cpp
src/condition_test_node.cpp
gtest_tree.cpp
gtest_sequence.cpp
gtest_parallel.cpp
gtest_fallback.cpp
gtest_factory.cpp
gtest_decorator.cpp

gtest_blackboard.cpp
gtest_coroutines.cpp
gtest_decorator.cpp
gtest_factory.cpp
gtest_fallback.cpp
gtest_parallel.cpp
gtest_preconditions.cpp
gtest_postconditions.cpp
gtest_match.cpp
gtest_ports.cpp
gtest_json.cpp
gtest_sequence.cpp
gtest_skipping.cpp
gtest_substitution.cpp
gtest_subtree.cpp
gtest_switch.cpp
gtest_tree.cpp
gtest_wakeup.cpp
test_helper.hpp

script_parser_test.cpp
gtest_coroutines.cpp
test_helper.hpp
)

set(TEST_DEPENDECIES
Expand Down
51 changes: 51 additions & 0 deletions tests/gtest_substitution.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#include <gtest/gtest.h>
#include "behaviortree_cpp/bt_factory.h"

using namespace BT;

static const char* json_text = R"(
{
"TestNodeConfigs": {
"TestA": {
"async_delay": 2000,
"return_status": "SUCCESS",
"post_script": "msg ='message SUBSTITUED'"
},
"TestB": {
"return_status": "FAILURE"
}
},
"SubstitutionRules": {
"actionA": "TestA",
"actionB": "TestB",
"actionC": "NotAConfig"
}
}
)";

TEST(Substitution, Parser)
{
BehaviorTreeFactory factory;

factory.loadSubstitutionRuleFromJSON(json_text);

const auto& rules = factory.substitutionRules();

ASSERT_EQ(rules.size(), 3);
ASSERT_EQ(rules.count("actionA"), 1);
ASSERT_EQ(rules.count("actionB"), 1);
ASSERT_EQ(rules.count("actionC"), 1);

auto configA = std::get_if<TestNodeConfig>(&rules.at("actionA"));
ASSERT_EQ(configA->return_status, NodeStatus::SUCCESS);
ASSERT_EQ(configA->async_delay, std::chrono::milliseconds(2000));
ASSERT_EQ(configA->post_script, "msg ='message SUBSTITUED'");

auto configB = std::get_if<TestNodeConfig>(&rules.at("actionB"));
ASSERT_EQ(configB->return_status, NodeStatus::FAILURE);
ASSERT_EQ(configB->async_delay, std::chrono::milliseconds(0));
ASSERT_TRUE(configB->post_script.empty());

ASSERT_EQ(*std::get_if<std::string>(&rules.at("actionC")), "NotAConfig");
}

0 comments on commit 2371eac

Please sign in to comment.