Comments (8)
I will finally have time to work on this the next week. Hopefully we get the changes merged by the end of the next week
from behaviortree.cpp.
@facontidavide I believe this is a suitable example of a tree that starts simple and is backchained once to make a more complex and capable version. Included the test below and in this gist https://gist.github.com/asasine/bc20da23d1954d2f712f215bfacdc659
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "behaviortree_cpp/leaf_node.h"
#include "behaviortree_cpp/bt_factory.h"
namespace BT::test
{
using ::testing::Assign;
using ::testing::DoAll;
using ::testing::Return;
/// @brief Mocked @c BT::LeafNode that allows callers to trivially customize behavior of @c tick() and @c halt().
/// @tparam NodeTypeT The type of leaf node this class is.
template <NodeType NodeTypeT>
class MockLeafNode : public LeafNode
{
public:
MockLeafNode(const std::string& name)
: LeafNode(name, {})
{
}
// Made public for setting actions and expectations
MOCK_METHOD(NodeStatus, tick, (), (override));
MOCK_METHOD(void, halt, (), (override));
NodeType type() const override
{
return NodeTypeT;
}
};
TEST(PPA, EnsureWarm)
{
// This test shows the basic structure of a PPA: a fallback of a postcondition and an action to make that
// postcondition true.
/*
<BehaviorTree ID="EnsureWarm">
<ReactiveFallback>
<IsWarm />
<ReactiveSequence>
<IsHoldingJacket />
<WearJacket />
</ReactiveSequence>
</ReactiveFallback>
</BehaviorTree>
*/
// The final condition of the PPA; the thing that make_warm achieves. For this example, we're only warm after
// WearJacket returns success.
MockLeafNode<NodeType::CONDITION> is_warm("IsWarm");
NodeStatus is_warm_return = NodeStatus::FAILURE;
EXPECT_CALL(is_warm, tick())
.WillRepeatedly([&is_warm_return]() { return is_warm_return; });
// For this example, we already have a jacket
MockLeafNode<NodeType::CONDITION> is_holding_jacket("IsHoldingJacket");
EXPECT_CALL(is_holding_jacket, tick())
.Times(2)
.WillRepeatedly(Return(NodeStatus::SUCCESS));
// Putting the jacket on takes two ticks, and updates the return value of IsWarm
MockLeafNode<NodeType::ACTION> wear_jacket("WearJacket");
EXPECT_CALL(wear_jacket, tick())
.WillOnce(Return(NodeStatus::RUNNING))
.WillOnce(DoAll(Assign(&is_warm_return, NodeStatus::SUCCESS), Return(NodeStatus::SUCCESS)));
ReactiveSequence make_warm("MakeWarm");
make_warm.addChild(&is_holding_jacket);
make_warm.addChild(&wear_jacket);
ReactiveFallback ensure_warm("EnsureWarm");
ensure_warm.addChild(&is_warm);
ensure_warm.addChild(&make_warm);
// first tick: not warm, have a jacket: start wearing it
EXPECT_EQ(ensure_warm.executeTick(), NodeStatus::RUNNING);
// second tick: warm (wearing succeeded)
EXPECT_EQ(ensure_warm.executeTick(), NodeStatus::SUCCESS);
// third tick: still warm (just the postcondition ticked)
EXPECT_EQ(ensure_warm.executeTick(), NodeStatus::SUCCESS);
}
TEST(PPA, EnsureWarmWithEnsureHoldingHacket)
{
// This test backchains on HoldingHacket => EnsureHoldingHacket to iteratively add reactivity and functionality to the tree.
// The general structure of the PPA remains the same.
/*
<BehaviorTree ID="EnsureWarm">
<ReactiveFallback>
<IsWarm />
<ReactiveSequence>
<SubTree ID="EnsureHoldingJacket" />
<WearJacket />
</ReactiveSequence>
</ReactiveFallback>
</BehaviorTree>
<BehaviorTree ID="EnsureHoldingJacket">
<ReactiveFallback>
<IsHoldingJacket />
<ReactiveSequence>
<IsNearCloset />
<GrabJacket />
</ReactiveSequence>
</ReactiveFallback>
</BehaviorTree>
*/
// Same postcondition as first test.
MockLeafNode<NodeType::CONDITION> is_warm("IsWarm");
NodeStatus is_warm_return = NodeStatus::FAILURE; // initially: not warm
EXPECT_CALL(is_warm, tick())
.WillRepeatedly([&is_warm_return]() { return is_warm_return; });
// The new PPA's postcondition is the same condition that we're backchaining on.
MockLeafNode<NodeType::CONDITION> is_holding_jacket("IsHoldingJacket");
NodeStatus is_holding_jacket_return = NodeStatus::FAILURE; // initially: not holding a jacket
EXPECT_CALL(is_holding_jacket, tick())
.WillRepeatedly([&is_holding_jacket_return]() { return is_holding_jacket_return; });
// For this test, we're already near a closet. This condition could be backchained!
MockLeafNode<NodeType::CONDITION> is_near_closet("IsNearCloset");
EXPECT_CALL(is_near_closet, tick())
.Times(2)
.WillRepeatedly(Return(NodeStatus::SUCCESS));
// Same as first test's action: running once, then success, updating this PPA's postcondition.
MockLeafNode<NodeType::ACTION> grab_jacket("GrabJacket");
EXPECT_CALL(grab_jacket, tick())
.WillOnce(Return(NodeStatus::RUNNING))
.WillRepeatedly(DoAll(Assign(&is_holding_jacket_return, NodeStatus::SUCCESS), Return(NodeStatus::SUCCESS)));
// Same as first test.
MockLeafNode<NodeType::ACTION> wear_jacket("WearJacket");
EXPECT_CALL(wear_jacket, tick())
.WillOnce(Return(NodeStatus::RUNNING))
.WillOnce(DoAll(Assign(&is_warm_return, NodeStatus::SUCCESS), Return(NodeStatus::SUCCESS)));
// The new PPA's precondition-action (PA) is the same form as the root PPA's PA. Similar to MakeWarm, this PA ticks
// running twice and updates the postcondition afterwards.
ReactiveSequence grab_jacket_from_closet("GrabJacketFromCloset");
grab_jacket_from_closet.addChild(&is_near_closet);
grab_jacket_from_closet.addChild(&grab_jacket);
// For this example, our precondition is another PPA: it short circuits if we already have a jacket, otherwise it
// fetches one asynchronously.
ReactiveFallback ensure_holding_jacket("EnsureHoldingJacket");
ensure_holding_jacket.addChild(&is_holding_jacket);
ensure_holding_jacket.addChild(&grab_jacket_from_closet);
ReactiveSequence make_warm("MakeWarm");
make_warm.addChild(&ensure_holding_jacket); // Use the new PPA within PA, not the original precondition.
make_warm.addChild(&wear_jacket);
ReactiveFallback ensure_warm("EnsureWarm");
ensure_warm.addChild(&is_warm);
ensure_warm.addChild(&make_warm);
// BUG(755): need to disable exception throwing on reactive control nodes to support backchaining.
ReactiveSequence::EnableException(false);
ReactiveFallback::EnableException(false);
// first tick: not warm, no jacket, near a closet: start grabbing a jacket
EXPECT_EQ(ensure_warm.executeTick(), NodeStatus::RUNNING);
// first tick: not warm, has a jacket (grabbing succeeded), not wearing: start wearing
EXPECT_EQ(ensure_warm.executeTick(), NodeStatus::RUNNING);
// third tick: warm (wearing succeeded)
EXPECT_EQ(ensure_warm.executeTick(), NodeStatus::SUCCESS);
// fourth tick: still warm (just the postcondition ticked)
EXPECT_EQ(ensure_warm.executeTick(), NodeStatus::SUCCESS);
}
}
Any of the conditions in the tree could be further backchained. One more level could look like:
<BehaviorTree ID="EnsureWarm">
<ReactiveFallback>
<IsWarm />
<ReactiveSequence>
<SubTree ID="EnsureHoldingJacket" />
<WearJacket />
</ReactiveSequence>
</ReactiveFallback>
</BehaviorTree>
<BehaviorTree ID="EnsureHoldingJacket">
<ReactiveFallback>
<IsHoldingJacket />
<ReactiveSequence>
<SubTree ID="EnsureNearCloset" />
<GrabJacket />
</ReactiveSequence>
</ReactiveFallback>
</BehaviorTree>
<BehaviorTree ID="EnsureNearCloset">
<ReactiveFallback>
<IsNearCloset />
<ReactiveSequence>
<IsPathToClosetAvailable />
<WalkToCloset />
</ReactiveSequence>
</ReactiveFallback>
</BehaviorTree>
Branching is trivially supportable if an action (or its precondition) fails. Additional explicit conditions are backchainable themselves with the same pattern. Each PPA can be tested in isolation by modifying the environment such that the preconditions hold, and then included in a larger tree with confidence that the PPA adds reactivity without unnecessary complexity: to the caller, it's all just EnsureWarm
.
<BehaviorTree ID="EnsureWarm">
<ReactiveFallback>
<IsWarm />
<ReactiveSequence>
<SubTree ID="EnsureHoldingJacket" />
<WearJacket />
</ReactiveSequence>
<ReactiveSequence>
<!-- This could be backchained to give the agent the ability to find and use the thermostat. -->
<IsInside />
<AdjustThermostat />
</ReactiveSequence>
<ReactiveSequence>
<!-- This could be backchained to give the agent the ability to acquire wood. -->
<IsWoodNearby />
<MakeFire />
</ReactiveSequence>
</ReactiveFallback>
</BehaviorTree>
<BehaviorTree ID="EnsureHoldingJacket">
<!-- Trimming this example: we can't acquire a jacket for whatever reason. -->
<AlwaysFailure />
</BehaviorTree>
from behaviortree.cpp.
See open PR #770
from behaviortree.cpp.
I am planning to change this in 4.6
It would be great if you or anyone else help me defining a "backchain" unit test that has the behavior that people wants/expect.
Do we all agree, that NONE of the green boxes should run concurrently?
Also, the image is nice, but WHICH sequence here should be reactive and which ones shouldn't?
I think that this is the problem wit papers and publications that just cherry-pick examples but don't use this "in production": the lack of details
from behaviortree.cpp.
I believe that this code is correct, I just need to remove the exception.
I need to stop the following ones (if you "Wear jacket", you interrupt "eating the banana"), but also the one of the left, that should all be prevalently stopped already or be Conditions that need reset.
from behaviortree.cpp.
It would be great if you or anyone else help me defining a "backchain" unit test that has the behavior that people wants/expect.
I'll work one up next week and post it here/provide a PR.
Do we all agree, that NONE of the green boxes should run concurrently?
I do agree with this. IMO concurrency is something you opt into explicitly with the use of Parallel
or other control nodes. With the backchaining model, only one leaf node is ever running at a time.
Also, the image is nice, but WHICH sequence here should be reactive and which ones shouldn't?
I think that this is the problem wit papers and publications that just cherry-pick examples but don't use this "in production": the lack of details
I tend to agree. I've found over time that paper/publications almost exclusively use reactive control nodes. Memory control nodes are only a thing that I come across regularly in BT software libraries. They're useful for certain side effects; we often use them for logging in our PPA actions to only log once per individual run of the action.
I believe that this code is correct, I just need to remove the exception.
I need to stop the following ones (if you "Wear jacket", you interrupt "eating the banana"), but also the one of the left, that should all be prevalently stopped already or be Conditions that need reset.
Agreed; the exception is problematic but the halting code looks good. You're spot on that the left nodes are probably already stopped. Despite calling halt on them, most/all wouldn't be reset (in BehaviorTree.CPP) because halts aren't propagated to nodes in a non-running state.
from behaviortree.cpp.
I created the branch reactive_change_issue_755
I preferred to change the suggested code a little, but you saved me a lot of time. Please have a look
from behaviortree.cpp.
I don't see this branch in the main repo.
from behaviortree.cpp.
Related Issues (20)
- Questions about using multithreading in version 3.8
- Re-ordering of ports and node TreeNodesModels in different Groot versions HOT 3
- [4.x] How to Pause and resume the tree? HOT 1
- `Sequence` of one repeated `StatefulActionNode` does not yield sequential execution
- Outdated static_assert error message HOT 2
- Missing a macro-node with an arbitrary number of nodes (parallel loop) HOT 1
- [3.8.6] Undefined symbol: Blackboard.getAny() HOT 2
- Consider updating to ament_export_targets (Modern CMake) HOT 8
- Idea: compile-time if-then-else node HOT 2
- Post script executes even when the node is skipped HOT 4
- change signature of registerNodeType to accept StringView HOT 3
- Bind is giving error for using function in registersimplecondition HOT 2
- Unable to link behaviortree_cpp to my CMake Project on Windows - (Missing .lib File) HOT 1
- Error sending information via a blackboard port on an action node HOT 5
- Allow concatenation of string and numbers in scripting HOT 2
- `logic_error` on `BT::BehaviorTreeFactory::registerNodeType()` When Setting `InputPort` `default_value` to `nullptr` After Commit 789ce6ea0ad3627923bd2389b8fb9199ffab6d84 HOT 5
- Tree Execution Error When Setting `InputPort` Value to Empty String with `std::optional<T>` Default Value as `std::nullopt` After Commit 789ce6ea0ad3627923bd2389b8fb9199ffab6d84 HOT 3
- Type checking XML/Cpp HOT 1
- Dynamic modification of the tree structure HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from behaviortree.cpp.