BehaviorTree.CPP icon indicating copy to clipboard operation
BehaviorTree.CPP copied to clipboard

header files and cpp files

Open j-corvo opened this issue 3 years ago • 1 comments

Hello,

I'm a beginner when it comes to behavior trees and I'm having issues when splitting the declaration of BT nodes between hpp files and cpp files.

1st case:

  • Only with one hpp file and a bt_factory_node

movebase_node.hpp:

#ifndef __MOVEBASE_BT_NODE_HPP_
#define __MOVEBASE_BT_NODE_HPP_

#include "behaviortree_cpp_v3/action_node.h"

// Custom type
struct Pose2D
{
  double x, y, theta;
};

inline void SleepMS(int ms)
{
  std::this_thread::sleep_for(std::chrono::milliseconds(ms));
}

namespace BT
{
  // This template specialization is needed only if you want
  // to AUTOMATICALLY convert a NodeParameter into a Pose2D
  // In other words, implement it if you want to be able to do:
  //
  //   TreeNode::getInput<Pose2D>(key, ...)
  //
  template <> inline Pose2D convertFromString(StringView key)
  {
    // three real numbers separated by semicolons
    auto parts = BT::splitString(key, ';');
    if (parts.size() != 3)
    {
      throw BT::RuntimeError("invalid input)");
    }
    else
    {
      Pose2D output;
      output.x = convertFromString<double>(parts[0]);
      output.y = convertFromString<double>(parts[1]);
      output.theta = convertFromString<double>(parts[2]);
      return output;
    }
  }
} // end namespace BT

// This is an asynchronous operation that will run in a separate thread.
// It requires the input port "goal".

class MoveBaseAction : public BT::AsyncActionNode
{
public:
  // Any TreeNode with ports must have a constructor with this signature
  MoveBaseAction(const std::string &name, const BT::NodeConfiguration &config)
      : AsyncActionNode(name, config)
  {
  }

  // It is mandatory to define this static method.
  static BT::PortsList providedPorts()
  {
    return {BT::InputPort<Pose2D>("goal")};
  }

  BT::NodeStatus tick() override 
  {
    Pose2D goal;
    if (!getInput<Pose2D>("goal", goal))
    {
      throw BT::RuntimeError("missing required input [goal]");
    }

    printf("[ MoveBase: STARTED ]. goal: x=%.f y=%.1f theta=%.2f\n", goal.x, goal.y, goal.theta);

    _halt_requested.store(false);
    int count = 0;

    // Pretend that "computing" takes 250 milliseconds.
    // It is up to you to check periodicall _halt_requested and interrupt
    // this tick() if it is true.
    while (!_halt_requested && count++ < 25)
    {
      SleepMS(10);
    }

    std::cout << "[ MoveBase: FINISHED ]" << std::endl;
    return _halt_requested ? BT::NodeStatus::FAILURE : BT::NodeStatus::SUCCESS;
  }

  virtual void halt() override
  {
    _halt_requested.store(true);
  }

private:
  std::atomic_bool _halt_requested;
};

#endif   // MOVEBASE_BT_NODE_H

bt_factory_node.cpp:

#include "behaviortree_cpp_v3/bt_factory.h"
#include "battery_interface.hpp"
#include "say_something.hpp"
#include "movebase_node.hpp"

int main()
{
  BT::BehaviorTreeFactory factory;
  factory.registerSimpleCondition("BatteryOK", std::bind(CheckBattery));
  factory.registerNodeType<MoveBaseAction>("MoveBase");
  factory.registerNodeType<SaySomething>("SaySomething");

  auto tree = factory.createTreeFromFile("/home/rythm/thesis_ws/src/BT_example/xml/simple_sequence.xml");

  BT::NodeStatus status;

  std::cout << "\n--- 1st executeTick() ---" << std::endl;
  status = tree.tickRoot();

  SleepMS(150);
  std::cout << "\n--- 2nd executeTick() ---" << std::endl;
  status = tree.tickRoot();

  SleepMS(150);
  std::cout << "\n--- 3rd executeTick() ---" << std::endl;
  status = tree.tickRoot();

  std::cout << std::endl;

  return 0;
}

Result: It runs perfectly as it should

############################################################### 2nd case: Slip between hpp and cpp files.

movebase_node.hpp:

#ifndef __MOVEBASE_BT_NODE_HPP_
#define __MOVEBASE_BT_NODE_HPP_

#include "behaviortree_cpp_v3/action_node.h"

// Custom type
struct Pose2D
{
  double x, y, theta;
};

inline void SleepMS(int ms)
{
  std::this_thread::sleep_for(std::chrono::milliseconds(ms));
}

namespace BT
{
  // This template specialization is needed only if you want
  // to AUTOMATICALLY convert a NodeParameter into a Pose2D
  // In other words, implement it if you want to be able to do:
  //
  //   TreeNode::getInput<Pose2D>(key, ...)
  //
  template <> inline Pose2D convertFromString(StringView key)
  {
    // three real numbers separated by semicolons
    auto parts = BT::splitString(key, ';');
    if (parts.size() != 3)
    {
      throw BT::RuntimeError("invalid input)");
    }
    else
    {
      Pose2D output;
      output.x = convertFromString<double>(parts[0]);
      output.y = convertFromString<double>(parts[1]);
      output.theta = convertFromString<double>(parts[2]);
      return output;
    }
  }
} // end namespace BT

// This is an asynchronous operation that will run in a separate thread.
// It requires the input port "goal".

class MoveBaseAction : public BT::AsyncActionNode
{
public:
  // Any TreeNode with ports must have a constructor with this signature
  MoveBaseAction(const std::string &name, const BT::NodeConfiguration &config)
      : AsyncActionNode(name, config)
  {
  }

  // It is mandatory to define this static method.
  static BT::PortsList providedPorts()
  {
    return {BT::InputPort<Pose2D>("goal")};
  }

  BT::NodeStatus tick() override;

  virtual void halt() override;

private:
  std::atomic_bool _halt_requested;
};

#endif   // MOVEBASE_BT_NODE_H

movebase_node.cpp:

#include "movebase_node.hpp"
#include "behaviortree_cpp_v3/bt_factory.h"

// This function must be implemented in the .cpp file to create
// a plugin that can be loaded at run-time
BT_REGISTER_NODES(factory)
{
  factory.registerNodeType<MoveBaseAction>("MoveBase");
}

BT::NodeStatus MoveBaseAction::tick()
{
  Pose2D goal;
  if (!getInput<Pose2D>("goal", goal))
  {
    throw BT::RuntimeError("missing required input [goal]");
  }

  printf("[ MoveBase: STARTED ]. goal: x=%.f y=%.1f theta=%.2f\n", goal.x, goal.y, goal.theta);

  _halt_requested.store(false);
  int count = 0;

  // Pretend that "computing" takes 250 milliseconds.
  // It is up to you to check periodicall _halt_requested and interrupt
  // this tick() if it is true.
  while (!_halt_requested && count++ < 25)
  {
    SleepMS(10);
  }

  std::cout << "[ MoveBase: FINISHED ]" << std::endl;
  return _halt_requested ? BT::NodeStatus::FAILURE : BT::NodeStatus::SUCCESS;
}

void MoveBaseAction::halt()
{
  _halt_requested.store(true);
}

The bt_factory_node.cpp is the same.

Result: When I compile it throws the following error: /usr/bin/ld: CMakeFiles/bt_mission_node.dir/src/bt_mission_node.cpp.o: in function MoveBaseAction::MoveBaseAction(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, BT::NodeConfiguration const&)': bt_mission_node.cpp:(.text._ZN14MoveBaseActionC2ERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERKN2BT17NodeConfigurationE[_ZN14MoveBaseActionC5ERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERKN2BT17NodeConfigurationE]+0x32): undefined reference to 'vtable for MoveBaseAction' collect2: error: ld returned 1 exit status make[2]: *** [BT_example/CMakeFiles/bt_mission_node.dir/build.make:107: /home/rythm/thesis_ws/devel/lib/BT_example/bt_mission_node] Error 1 make[1]: *** [CMakeFiles/Makefile2:808: BT_example/CMakeFiles/bt_mission_node.dir/all] Error 2 make: *** [Makefile:141: all] Error 2 Invoking "make -j16 -l16" failed

Can anyone explain to me what I am doing wrong, please?

Thank you for your help!

j-corvo avatar Feb 25 '22 22:02 j-corvo

I need the entire project or a subset that reproduce the error to be sure. Can you share it with me?

facontidavide avatar May 13 '22 10:05 facontidavide

Please provide a project I can compile and run, or I will close this issue

facontidavide avatar Oct 24 '22 10:10 facontidavide

Closing this for inactivity. Since your error was undefined reference , it is most probably an issue in the cmake configuration

facontidavide avatar Nov 22 '22 21:11 facontidavide