Commit 69937841 authored by Max  Bauer's avatar Max Bauer
Browse files

Link offical repo

parent 133e53ed
# The YASE package # YASE HAS MOVED
## About YASE: Please visit the official repo: https://gitlab.eclipse.org/eclipse/simopenpass/yase
The **YASE (Yet Agnostic Scenario Engine / Another Scenario Engine)** package is a simulator agnostic scenario engine. \ No newline at end of file
It is agnostic of the underlying simulator as well as of the used scenario input file format.
The architecture is hereby orientated on programming language compilers. Within these compilers, programming languages are translated into an intermediate representation by a Frontend. From here, several Backends can translate the intermediate representation into different target architectures.
!["Example of LLVM compiler"](doc/figures/compiler_example.png?raw=true)
This modular and flexible way is used within YASE. It contains an simulator and scenario file format agnostic Middleend. This Middleend can be filled by different Frontends, reading in different scenario formats. On the other side, different Backends can connect this with different simulators.
The following figure shows a **potential** expansion with potential simulation backends/ scenario formats frontends.
!["Example for a potential YASE setup"](doc/figures/scenario_compiler.png?raw=true)
## Current YASE content:
The current YASE project consist of the MiddleEnd packages:
**MiddleEnd**
* The **agnostic_behavior_tree** package with an agnostic behavior tree implementation. It allows to execute
* The **agnostic_type_system** package with an a type system, which can be adapted to any simulator.
---
Language: Cpp
BasedOnStyle: Google
ColumnLimit: 120
BinPackParameters: false
DerivePointerAlignment: false
PointerAlignment: Left
IndentPPDirectives: AfterHash
MaxEmptyLinesToKeep: 2
# Minimum version of CMake required to build this project
cmake_minimum_required(VERSION 3.6)
# Name of the project
project(agnostic_behavior_tree)
set(HDR_FILES_AGNOSTIC_BEHAVIOR_TREE
# Third party
include/${PROJECT_NAME}/third_party/any.hpp
# Basic definitions
include/${PROJECT_NAME}/behavior_node.h
include/${PROJECT_NAME}/scoped_blackboard.h
# Generic tree nodes
include/${PROJECT_NAME}/action_node.h
include/${PROJECT_NAME}/composite_node.h
include/${PROJECT_NAME}/decorator_node.h
# Composite instances
include/${PROJECT_NAME}/composite/sequence_node.h
include/${PROJECT_NAME}/composite/parallel_node.h
include/${PROJECT_NAME}/composite/selector_node.h
# Decorator instances
include/${PROJECT_NAME}/decorator/symbol_declaration_node.h
include/${PROJECT_NAME}/decorator/service_node.h
include/${PROJECT_NAME}/decorator/symbol_proxy_node.h
include/${PROJECT_NAME}/decorator/constraint_node.h
include/${PROJECT_NAME}/decorator/inverter_node.h
include/${PROJECT_NAME}/decorator/repeat_node.h
include/${PROJECT_NAME}/decorator/wait_node.h
include/${PROJECT_NAME}/decorator/until_node.h
# Action instances
include/${PROJECT_NAME}/actions/analyse_nodes.h
include/${PROJECT_NAME}/actions/functor_action_node.h
# Utils
include/${PROJECT_NAME}/utils/tree_analyzer.h
include/${PROJECT_NAME}/utils/tree_print.h
include/${PROJECT_NAME}/utils/visitors.h
include/${PROJECT_NAME}/utils/condition.h
)
set(SRC_FILES_AGNOSTIC_BEHAVIOR_TREE
# Basic definitions
src/behavior_node.cpp
src/scoped_blackboard.cpp
# Utils
src/utils/tree_analyzer.cpp
src/utils/tree_print.cpp
src/utils/visitors.cpp
)
set(TEST_FILES_AGNOSTIC_BEHAVIOR_TREE
# Core definitions
tests/test_scoped_blackboard.cpp
# Composites
tests/composites/test_sequence.cpp
tests/composites/test_parallel.cpp
tests/composites/test_selector.cpp
# Decorators
tests/decorators/test_event_service_node.cpp
tests/decorators/test_symbol_declaration_node.cpp
tests/decorators/test_argument_proxy_node.cpp
tests/decorators/test_constraint_node.cpp
tests/decorators/test_repeat.cpp
tests/decorators/test_inverter.cpp
tests/decorators/test_until.cpp
tests/decorators/test_wait.cpp
# Actions
tests/actions/test_functor_action.cpp
# Utils
tests/utils/test_condition.cpp
tests/utils/test_tree_analyzer.cpp
tests/utils/test_tree_print.cpp
# Extension
tests/test_extension.cpp
)
add_library(${PROJECT_NAME}
${HDR_FILES_AGNOSTIC_BEHAVIOR_TREE}
${SRC_FILES_AGNOSTIC_BEHAVIOR_TREE}
)
target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include)
# Add compiler flags
if(MSVC)
target_compile_options(${PROJECT_NAME} PRIVATE /W4 /WX)
else()
target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Werror -Wextra -pedantic)
endif()
################## Testing ###################
enable_testing()
find_package(GTest REQUIRED)
include(GoogleTest)
add_executable(${PROJECT_NAME}_test ${TEST_FILES_AGNOSTIC_BEHAVIOR_TREE} )
target_link_libraries(${PROJECT_NAME}_test ${PROJECT_NAME} GTest::GTest GTest::Main)
gtest_discover_tests(${PROJECT_NAME}_test)
# The Agnostic Behavior Tree package of the YASE framework
This package contains the agnostic behavior tree implementation for scenario execution.
The main design goal is a modular, simulator agnostic and deterministic execution.
!["Example behavior tree with execution tree traversal"](doc/figures/bt_example.png?raw=true )
## Architecture:
### Behavior Nodes:
The implementation contains several base behavior nodes: Composites, Decorators and Actions.
* Actions: Action nodes are nodes, which perform actual tasks in the simulator, such as a LaneChange.
* Decorators: Decorators allow to decorate other nodes to influence the behavior. As an example, the LaneChange behavior can be decorated with an AdjacentLaneFree condition decorator. This would prevent the execution of the LaneChange in situations in which the LaneChange not possible.
* Composites: Composites allow to compose other behaviors to greater behaviors. A Sequence composite may combine a FollowLane after a LaneChange in sequential manner.
!["Provided nodes and inheritance structure"](doc/figures/uml_class_diagram.png?raw=true )
### Scoped BlackBoard Container
Often it is necessary to share data among several nodes within the tree.
For this purpose the behavior nodes contain a scoped blackboard container to declare and look up data.
The following example demonstrates how two symbols (veh_1 and veh_2) are declared at a certain node.
This can then be accessed by sub nodes, such as the LaneChange nodes, which require access.
!["Example of symbol declaration"](doc/figures/symbol_propagation.png?raw=true )
### Extensions:
For the behavior tree usage within specific simulation tools it is often necessary to add further custom methods to nodes.
Such functionality can be added via composition with the Extension template.
In the unit test "test_extension.cpp" it is exemplary shown how a custom method serializeToFormatXyz() can be added to all nodes.
## Types of Behavior Nodes:
### Actions:
Actions must be defined for the specific use case and simulation environment.
Therefore predefined actions are not part of this simulator agnostic package.
The existing utility actions are only for the purpose of unit testing.
### Decorators:
The package already provides some base decorators such as the simple inverter decorator.
This node inverts the node status from Failure to Success and vice versa.
Other important decorators are the ConstraintNode and the ServiceNode, which allow condition checking and providing services for subnodes.
The SymbolDeclarationNode and the SymbolProxyNode decorator allow furthermore advanced variable scoping within the tree.
### Composites:
The package provides three base composites:
* Sequence: This composite allows to execute behaviors in sequence, e.G. a FollowLane after a LaneChange.
* Parallel: This composite allows the parallel execution of behaviors e.G. two vehicles follow a lane in parallel.
* Selector: This composite allows the situation based, event based or interruption based execution of behavior. E.G. a selector can try a LaneChange, but if this is not possible, it will perform a FollowLane behavior instead.
## Build and run tests:
### Dependencies:
* make
* cmake
* gtest
### How to build:
Create a subfolder "build" and build it:
``` shell
mkdir build && cd build
cmake ../your/path/to/agnostic_behavior_tree && make && ./agnostic_behavior_tree_test
```
## Usage:
### Tips on how to use:
* Use predefined composites/ decorators as much as possible - Relying on them reduces compatibility problems with future versions in a great extent.
* Define Tasks of Actions with onInit(), tick() and onTerminate() method.
## Other References:
The following references provide deeper understanding of BehaviorTrees, which are the state of the art for behavior modeling in the gaming/ robotic industry.
* https://www.gameaipro.com/GameAIPro/GameAIPro_Chapter06_The_Behavior_Tree_Starter_Kit.pdf
* https://arxiv.org/abs/1709.00084
* https://www.behaviortree.dev/
* https://docs.unrealengine.com/en-US/InteractiveExperiences/ArtificialIntelligence/BehaviorTrees/index.html
\ No newline at end of file
### uml: class diagram
```plantuml
@startuml
package "YASE Behavior Tree Architecture" #DDDDDD {
note right of BehaviorNode
The blackboard allows to access symbols and the
extension allows to extend use case specific methods.
end note
class BehaviorNode {
+ lookUpSymbol()
+ declareSymbol()
+ virtual tick()
+ blackboard
+ extension
}
BehaviorNode <|-- Composite
BehaviorNode <|-- Decorator
BehaviorNode <|-- Action
class Decorator {
+ child
+ setChild()
}
class Action
class Composite {
+ addChild()
+ children
}
Composite <|-- Sequence
Composite <|-- Parallel
Composite <|-- Selector
class Sequence
class Parallel
class Selector
Decorator <|-- SymbolDeclarationNode
Decorator <|-- SymbolProxyNode
Decorator <|-- ServiceNode
Decorator <|-- ConstraintNode
Decorator <|-- InverterNode
Decorator <|-- ExecuteUntilFailureNode
class SymbolDeclarationNode
class SymbolProxyNode
class ServiceNode
class ConstraintNode
class InverterNode
class ExecuteUntilFailureNode
Action <|-- TaskNode
Action <|-- UnitTestHelper
class TaskNode
class UnitTestHelper
@enduml
```
/*******************************************************************************
* Copyright (c) Max Paul Bauer - Robert Bosch GmbH - 2021
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
#ifndef AGNOSTIC_BEHAVIOR_TREE_ACTION_NODE_H
#define AGNOSTIC_BEHAVIOR_TREE_ACTION_NODE_H
#include "agnostic_behavior_tree/behavior_node.h"
namespace yase {
// The base for all action nodes
class ActionNode : public BehaviorNode {
public:
explicit ActionNode(const std::string& name) : ActionNode(name, nullptr){};
ActionNode(const std::string& name, Extension::Ptr extension_ptr)
: BehaviorNode(std::string("Action::").append(name), std::move(extension_ptr)){};
virtual ~ActionNode() override = default;
};
} // namespace yase
#endif // AGNOSTIC_BEHAVIOR_TREE_ACTION_NODE_H
/*******************************************************************************
* Copyright (c) Max Paul Bauer - Robert Bosch GmbH - 2021
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
#ifndef AGNOSTIC_BEHAVIOR_TREE_ACTIONS_ANALYSE_NODES_H
#define AGNOSTIC_BEHAVIOR_TREE_ACTIONS_ANALYSE_NODES_H
#include <agnostic_behavior_tree/action_node.h>
#include <iostream>
#include <string>
namespace yase {
// Returns N Times until it returns preffered NodeStatus. Tracks the method accesses
class AnalyseNode : public ActionNode {
public:
explicit AnalyseNode(const size_t max_repeat_ticks,
const NodeStatus return_status = NodeStatus::kSuccess,
Extension::Ptr extension_ptr = nullptr)
: AnalyseNode("", max_repeat_ticks, return_status, extension_ptr){};
AnalyseNode(const std::string name,
const size_t max_repeat_ticks,
const NodeStatus return_status = NodeStatus::kSuccess,
Extension::Ptr extension_ptr = nullptr)
: ActionNode(std::string("AnalyseNode").append(name), std::move(extension_ptr)),
m_max_repeat_ticks(max_repeat_ticks),
m_return_status(return_status){};
virtual ~AnalyseNode() override = default;
bool isInitialised() const { return m_initialised; }
size_t overallTicks() const { return m_overall_ticks; }
size_t ticksSinceInit() const { return m_ticks_since_init; }
size_t onInitCalls() const { return m_on_init_calls; }
size_t onTerminateCalls() const { return m_on_terminate_calls; }
protected:
NodeStatus tick() final {
m_overall_ticks++;
m_ticks_since_init++;
if (m_repeat_counter < m_max_repeat_ticks) {
m_repeat_counter++;
executionInfo(std::string("Ticked ")
.append(std::to_string(m_repeat_counter))
.append(" of ")
.append(std::to_string(m_max_repeat_ticks))
.append(" times"));
return NodeStatus::kRunning;
}
return m_return_status;
};
void onInit() override {
m_repeat_counter = 0;
m_initialised = true;
m_ticks_since_init = 0;
m_on_init_calls++;
}
void onTerminate() override {
m_initialised = false;
m_on_terminate_calls++;
}
private:
const size_t m_max_repeat_ticks;
const NodeStatus m_return_status;
// Variables to be reset at onInit
bool m_initialised{false};
size_t m_repeat_counter{0};
// Variables which will not be reset:
size_t m_overall_ticks{0};
size_t m_on_init_calls{0};
size_t m_ticks_since_init{0};
size_t m_on_terminate_calls{0};
};
// Returns always a Running
class AlwaysRunning : public AnalyseNode {
public:
AlwaysRunning(Extension::Ptr extension_ptr = nullptr)
: AnalyseNode("AlwaysRunning", 0, NodeStatus::kRunning, std::move(extension_ptr)){};
virtual ~AlwaysRunning() override = default;
};
// Returns always a Success
class AlwaysSuccess : public AnalyseNode {
public:
AlwaysSuccess(Extension::Ptr extension_ptr = nullptr)
: AnalyseNode("AlwaysSuccess", 0, NodeStatus::kSuccess, std::move(extension_ptr)){};
~AlwaysSuccess() override = default;
};
// Returns always a Failure
class AlwaysFailure : public AnalyseNode {
public:
AlwaysFailure(Extension::Ptr extension_ptr = nullptr)
: AnalyseNode("AlwaysFailure", 0, NodeStatus::kFailure, std::move(extension_ptr)){};
~AlwaysFailure() override = default;
};
} // namespace yase
#endif // AGNOSTIC_BEHAVIOR_TREE_ACTIONS_ANALYSE_NODES_H
/*******************************************************************************
* Copyright (c) Max Paul Bauer - Robert Bosch GmbH - 2021
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
#ifndef AGNOSTIC_BEHAVIOR_TREE_ACTIONS_FUNCTOR_ACTION_NODE_H
#define AGNOSTIC_BEHAVIOR_TREE_ACTIONS_FUNCTOR_ACTION_NODE_H
#include <agnostic_behavior_tree/action_node.h>
#include <functional>
namespace yase {
// A simple action node which allows to express logic via a lambda.
// This node eases to execute generic logic with a Functor.
class FunctorActionNode : public ActionNode {
public:
using TickFunctor = std::function<NodeStatus()>;
FunctorActionNode(const std::string& name,
FunctorActionNode::TickFunctor tick_functor,
Extension::Ptr extension_ptr = nullptr)
: ActionNode(name, extension_ptr), m_tick_functor(std::move(tick_functor)){};
explicit FunctorActionNode(FunctorActionNode::TickFunctor tick_functor, Extension::Ptr extension_ptr = nullptr)
: FunctorActionNode("UnnamedFunctorAction", std::move(tick_functor), extension_ptr){};
virtual ~FunctorActionNode() override = default;
void onInit() override{};
protected:
NodeStatus tick() final { return m_tick_functor(); };
TickFunctor m_tick_functor;
};
} // namespace yase
#endif // AGNOSTIC_BEHAVIOR_TREE_ACTIONS_FUNCTOR_ACTION_NODE_H
/*******************************************************************************
* Copyright (c) Max Paul Bauer - Robert Bosch GmbH - 2021
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
#ifndef AGNOSTIC_BEHAVIOR_TREE_BEHAVIOR_NODE_H
#define AGNOSTIC_BEHAVIOR_TREE_BEHAVIOR_NODE_H
#include "agnostic_behavior_tree/scoped_blackboard.h"
#include <exception>
#include <string>
namespace yase {
// Execution states of a node
enum class NodeStatus { kIdle = 0, kRunning, kSuccess, kFailure };
// Convert the status into an ouput string
std::string toStr(NodeStatus status, bool use_ansi_escape_code = false);
// Simulator specific Extensions can be inherited and composed to behavior nodes
class Extension {
public:
Extension() = default;
virtual ~Extension() = default;
using Ptr = std::shared_ptr<Extension>;
};
// Abstract base class for Behavior Tree Nodes.
class BehaviorNode {
public:
virtual ~BehaviorNode() { m_blackboard = nullptr; };
using Ptr = std::shared_ptr<BehaviorNode>;
// Method to invoke tick from the outside while ensuring the consistency of the node status and tick cycle tracking
NodeStatus executeTick() {
// Track current tick cycle
if (m_parent_node != nullptr) {
if (m_tick_cycle >= m_parent_node->m_tick_cycle) {
std::string error_msg = "Error while executing tick() of behavior node [";
error_msg.append(name());
error_msg.append("]: Tick cycle [")
.append(std::to_string(m_tick_cycle))
.append("] of this node is greater/equal then its parent tick cycle [");
error_msg.append(std::to_string(m_parent_node->m_tick_cycle))
.append("] - A child is not allowed to proceed faster then its parent");
throw std::logic_error(error_msg);
}
m_tick_cycle = m_parent_node->m_tick_cycle;
} else {
(m_tick_cycle)++;
}
// Clear execution info before step
m_execution_info.clear();
m_status = tick();
if (m_status == NodeStatus::kIdle) {
std::string error_msg = "Error while executing tick() of behavior node [";
error_msg.append(name());
error_msg.append("]: Returned invalid status NodeStatus::kIdle!");
throw std::logic_error(error_msg);
}
return status();
};
// Called once right before behavior is ticked the first time.
// THE CALL MUST RESET THE BEHAVIOR with all its necessary values!
virtual void onInit() = 0;
// Called after behavior is called the last time
virtual void onTerminate(){};
// Get node status
NodeStatus status() const { return m_status; };
// Get detailed execution info
std::string executionInfo() const { return m_execution_info; };
// Get tree node name
const std::string& name() const { return m_name; };