Skip to content

Commit

Permalink
new default port capability: keys
Browse files Browse the repository at this point in the history
  • Loading branch information
facontidavide committed Feb 7, 2024
1 parent c4ad3cd commit 789ce6e
Show file tree
Hide file tree
Showing 4 changed files with 247 additions and 43 deletions.
150 changes: 138 additions & 12 deletions include/behaviortree_cpp/basic_types.h
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
#pragma once

#include <chrono>
#include <iostream>
#include <vector>
#include <unordered_map>
#include <typeinfo>
#include <functional>
#include <chrono>
#include <string_view>
#include <typeinfo>
#include <unordered_map>
#include <utility>
#include <variant>
#include <vector>
Expand Down Expand Up @@ -156,11 +155,17 @@ inline StringConverter GetAnyFromStringFunctor<void>()

//------------------------------------------------------------------

template <typename T>
constexpr bool IsConvertibleToString()
{
return std::is_convertible_v<T, std::string> ||
std::is_convertible_v<T, std::string_view>;
}

template<typename T> [[nodiscard]]
std::string toStr(const T& value)
{
if constexpr (std::is_convertible_v<T, std::string> ||
std::is_convertible_v<T, std::string_view>)
if constexpr (IsConvertibleToString<T>())
{
return value;
}
Expand All @@ -169,8 +174,9 @@ std::string toStr(const T& value)
throw LogicError(
StrCat("Function BT::toStr<T>() not specialized for type [",
BT::demangle(typeid(T)), "]")
);
} else {
);
}
else {
return std::to_string(value);
}
}
Expand Down Expand Up @@ -383,45 +389,165 @@ std::pair<std::string, PortInfo> CreatePort(PortDirection direction,
}

//----------
/** Syntactic sugar to invoke CreatePort<T>(PortDirection::INPUT, ...)
*
* @param name the name of the port
* @param description optional human-readable description
*/
template <typename T = AnyTypeAllowed> [[nodiscard]]
inline std::pair<std::string, PortInfo> InputPort(StringView name,
StringView description = {})
{
return CreatePort<T>(PortDirection::INPUT, name, description);
}

/** Syntactic sugar to invoke CreatePort<T>(PortDirection::OUTPUT,...)
*
* @param name the name of the port
* @param description optional human-readable description
*/
template <typename T = AnyTypeAllowed> [[nodiscard]]
inline std::pair<std::string, PortInfo> OutputPort(StringView name,
StringView description = {})
{
return CreatePort<T>(PortDirection::OUTPUT, name, description);
}

/** Syntactic sugar to invoke CreatePort<T>(PortDirection::INOUT,...)
*
* @param name the name of the port
* @param description optional human-readable description
*/
template <typename T = AnyTypeAllowed> [[nodiscard]]
inline std::pair<std::string, PortInfo> BidirectionalPort(StringView name,
StringView description = {})
{
return CreatePort<T>(PortDirection::INOUT, name, description);
}
//----------
template <typename T = AnyTypeAllowed> [[nodiscard]]
inline std::pair<std::string, PortInfo> InputPort(StringView name, const T& default_value,
/** Syntactic sugar to invoke CreatePort<T>(PortDirection::INPUT,...)
* It also sets the PortInfo::defaultValue()
*
* @param name the name of the port
* @param default_value default value of the port, either type T of BlackboardKey
* @param description optional human-readable description
*/
template <typename T = AnyTypeAllowed, typename DefaultT = T> [[nodiscard]]
inline std::pair<std::string, PortInfo> InputPort(StringView name,
const DefaultT& default_value,
StringView description)
{
static_assert(std::is_same_v<T, DefaultT> ||
IsConvertibleToString<DefaultT>() ||
std::is_convertible_v<DefaultT, T>,
"The default value must be either the same of the port or BlackboardKey");

auto out = CreatePort<T>(PortDirection::INPUT, name, description);
out.second.setDefaultValue(default_value);
return out;
}

template <typename T = AnyTypeAllowed> [[nodiscard]]
/** Syntactic sugar to invoke CreatePort<T>(PortDirection::INOUT,...)
* It also sets the PortInfo::defaultValue()
*
* @param name the name of the port
* @param default_value default value of the port, either type T of BlackboardKey
* @param description optional human-readable description
*/
template <typename T = AnyTypeAllowed, typename DefaultT = T> [[nodiscard]]
inline std::pair<std::string, PortInfo> BidirectionalPort(StringView name,
const T& default_value,
const DefaultT& default_value,
StringView description)
{
static_assert(std::is_same_v<T, DefaultT> ||
IsConvertibleToString<DefaultT>() ||
std::is_convertible_v<DefaultT, T>,
"The default value must be either the same of the port or BlackboardKey");

auto out = CreatePort<T>(PortDirection::INOUT, name, description);
out.second.setDefaultValue(default_value);
return out;
}

/** Syntactic sugar to invoke CreatePort<T>(PortDirection::OUTPUT,...)
* It also sets the PortInfo::defaultValue()
*
* @param name the name of the port
* @param default_value default blackboard entry where the output is written
* @param description optional human-readable description
*/
template <typename T = AnyTypeAllowed> [[nodiscard]]
inline std::pair<std::string, PortInfo> OutputPort(StringView name,
StringView default_value,
StringView description)
{
if(default_value.empty() || default_value.front() != '{' || default_value.back() != '}')
{
throw LogicError("Output port can only refer to blackboard entries, i.e. use the syntax '{port_name}'");
}
auto out = CreatePort<T>(PortDirection::OUTPUT, name, description);
out.second.setDefaultValue(default_value);
return out;
}

//----------

// /** Syntactic sugar to invoke CreatePort<T>(PortDirection::INPUT,...)
// * It also sets the default value to the blackboard entry specified
// * in "default_key"
// *
// * @param name the name of the port
// * @param default_key the key of an entry in the blackbard
// * @param description optional human-readable description
// */
// template <typename T> [[nodiscard]]
// inline std::pair<std::string, PortInfo> InputPort(
// StringView name,
// BlackboardKey default_key,
// StringView description)
// {
// auto out = CreatePort<T>(PortDirection::INPUT, name, description);
// out.second.setDefaultValue(default_key);
// return out;
// }

// /** Syntactic sugar to invoke CreatePort<T>(PortDirection::INOUT,...)
// * It also sets the default value to the blackboard entry specified
// * in "default_key"
// *
// * @param name the name of the port
// * @param default_key the key of an entry in the blackbard
// * @param description optional human-readable description
// */
// template <typename T> [[nodiscard]]
// inline std::pair<std::string, PortInfo> BidirectionalPort(
// StringView name,
// BlackboardKey default_key,
// StringView description)
// {
// auto out = CreatePort<T>(PortDirection::INOUT, name, description);
// out.second.setDefaultValue(default_key);
// return out;
// }

// /** Syntactic sugar to invoke CreatePort<T>(PortDirection::OUTPUT,...)
// * It also sets the default value to the blackboard entry specified
// * in "default_key"
// *
// * @param name the name of the port
// * @param default_key the key of an entry in the blackbard
// * @param description optional human-readable description
// */
// template <typename T> [[nodiscard]]
// inline std::pair<std::string, PortInfo> OutputPort(
// StringView name,
// BlackboardKey default_key,
// StringView description)
// {
// auto out = CreatePort<T>(PortDirection::OUTPUT, name, description);
// out.second.setDefaultValue(default_key);
// return out;
// }
//----------

using PortsList = std::unordered_map<std::string, PortInfo>;
Expand Down
48 changes: 30 additions & 18 deletions include/behaviortree_cpp/tree_node.h
Original file line number Diff line number Diff line change
Expand Up @@ -396,28 +396,40 @@ inline Result TreeNode::getInput(const std::string& key, T& destination) const
}
};

auto remap_it = config().input_ports.find(key);
if (remap_it == config().input_ports.end())
std::string port_value_str;

auto input_port_it = config().input_ports.find(key);
if(input_port_it != config().input_ports.end())
{
return nonstd::make_unexpected(StrCat("getInput() of node `", fullPath(),
"` failed because "
"NodeConfig::input_ports "
"does not contain the key: [",
key, "]"));
port_value_str = input_port_it->second;
}

// special case. Empty port value, we should use the default value,
// if available in the model.
// BUT, it the port type is a string, then an empty string might be
// a valid value
const std::string& port_value_str = remap_it->second;
if(port_value_str.empty() && config().manifest)
else
{
const auto& port_manifest = config().manifest->ports.at(key);
const auto& default_value = port_manifest.defaultValue();
if(!default_value.empty() && !default_value.isString())
// maybe it is declared with a default value in the manifest
auto port_manifest_it = config().manifest->ports.find(key);
if (port_manifest_it == config().manifest->ports.end())
{
return nonstd::make_unexpected(
StrCat("getInput() of node '", fullPath(),
"' failed because the manifest doesn't contain"
"the key: [", key, "]"));
}
const auto& port_info = port_manifest_it->second;
// there is a default value
if(port_info.defaultValue().empty())
{
return nonstd::make_unexpected(
StrCat("getInput() of node '", fullPath(),
"' failed because nor the manifest or the XML contain"
"the key: [", key, "]"));
}
if(port_info.defaultValue().isString())
{
port_value_str = port_info.defaultValue().cast<std::string>();
}
else
{
destination = default_value.cast<T>();
destination = port_info.defaultValue().cast<T>();
return {};
}
}
Expand Down
22 changes: 14 additions & 8 deletions src/xml_parsing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -782,16 +782,22 @@ TreeNode::Ptr XMLParser::PImpl::createNodeFromXML(const XMLElement* element,
const std::string& port_name = port_it.first;
const PortInfo& port_info = port_it.second;

auto direction = port_info.direction();

if (direction != PortDirection::OUTPUT &&
config.input_ports.count(port_name) == 0 &&
!port_info.defaultValue().empty())
const auto direction = port_info.direction();
const auto& default_string = port_info.defaultValueString();
if(!default_string.empty())
{
try {
config.input_ports.insert({port_name, port_info.defaultValueString()});
if (direction != PortDirection::OUTPUT &&
config.input_ports.count(port_name) == 0)
{
config.input_ports.insert({port_name, default_string});
}

if (direction != PortDirection::INPUT &&
config.output_ports.count(port_name) == 0 &&
TreeNode::isBlackboardPointer(default_string))
{
config.output_ports.insert({port_name, default_string});
}
catch(LogicError&) {}
}
}

Expand Down
Loading

0 comments on commit 789ce6e

Please sign in to comment.