Git Product home page Git Product logo

Comments (8)

facontidavide avatar facontidavide commented on June 1, 2024 4

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.

asasine avatar asasine commented on June 1, 2024 1

@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.

facontidavide avatar facontidavide commented on June 1, 2024 1

See open PR #770

from behaviortree.cpp.

facontidavide avatar facontidavide commented on June 1, 2024

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.

facontidavide avatar facontidavide commented on June 1, 2024

I believe that this code is correct, I just need to remove the exception.

https://github.com/BehaviorTree/BehaviorTree.CPP/blob/master/src/controls/reactive_sequence.cpp#L47-L53

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.

asasine avatar asasine commented on June 1, 2024

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.

https://github.com/BehaviorTree/BehaviorTree.CPP/blob/master/src/controls/reactive_sequence.cpp#L47-L53

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.

facontidavide avatar facontidavide commented on June 1, 2024

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.

asasine avatar asasine commented on June 1, 2024

I don't see this branch in the main repo.

from behaviortree.cpp.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.