中文 | English
Lightweight, header-only behavior tree library for embedded systems.
- Header-only: Single file
bt/behavior_tree.hpp, zero external dependencies - C++14 standard: No C++17 required
- Type-safe context: Template parameter eliminates
void*casting (shared blackboard) - Lambda captures: Per-node data via closures, replacing C-style
void* user_data - All standard node types: Action, Condition, Sequence, Selector, Parallel, Inverter
- Async operations: RUNNING status with position resume for cooperative multitasking
- Lifecycle callbacks: on_enter/on_exit for resource management
- Factory helpers: Convenience functions for common node configurations
- Cache-friendly layout: Hot data fields placed first in node structure
-fno-exceptions,-fno-rtticompatible- MISRA C++ compliant subset (Rules 5-0-13, 6-3-1, 12-8-1)
include(FetchContent)
FetchContent_Declare(
bt
GIT_REPOSITORY https://gitee.com/liudegui/bt-cpp.git
GIT_TAG master
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(bt)
target_link_libraries(your_target PRIVATE bt)Copy include/bt/behavior_tree.hpp into your project.
#include <bt/behavior_tree.hpp>
#include <cstdio>
struct AppContext { int step = 0; };
int main() {
AppContext ctx;
bt::Node<AppContext> a1("Check");
a1.set_type(bt::NodeType::kCondition)
.set_tick([](AppContext&) {
std::printf("[Check] OK\n");
return bt::Status::kSuccess;
});
bt::Node<AppContext> a2("Run");
a2.set_type(bt::NodeType::kAction)
.set_tick([](AppContext& c) {
++c.step;
std::printf("[Run] step %d\n", c.step);
return bt::Status::kSuccess;
});
bt::Node<AppContext> root("Root");
bt::Node<AppContext>* children[] = {&a1, &a2};
root.set_type(bt::NodeType::kSequence).SetChildren(children);
bt::BehaviorTree<AppContext> tree(root, ctx);
bt::Status result = tree.Tick();
std::printf("Result: %s\n", bt::StatusToString(result));
return 0;
}enum class Status : uint8_t { kSuccess, kFailure, kRunning, kError };
constexpr const char* StatusToString(Status s) noexcept;
constexpr const char* NodeTypeToString(NodeType t) noexcept;
constexpr bool IsLeafType(NodeType t) noexcept;
constexpr bool IsCompositeType(NodeType t) noexcept;// Callback types
using TickFn = std::function<Status(Context&)>;
using CallbackFn = std::function<void(Context&)>;
// Configuration (fluent API, returns *this)
Node& set_type(NodeType type) noexcept;
Node& set_tick(TickFn fn);
Node& set_on_enter(CallbackFn fn);
Node& set_on_exit(CallbackFn fn);
Node& SetChildren(Node* const* children, uint16_t count) noexcept;
Node& SetChildren(Node* const (&children)[N]) noexcept; // auto-deduces size
Node& SetChild(Node& child) noexcept; // for decorators
Node& set_parallel_policy(ParallelPolicy policy) noexcept;
// Query
const char* name() const noexcept;
NodeType type() const noexcept;
Status status() const noexcept;
uint16_t children_count() const noexcept;
uint16_t current_child_index() const noexcept;
bool has_tick() const noexcept;
bool has_on_enter() const noexcept;
bool has_on_exit() const noexcept;
bool is_finished() const noexcept;
bool is_running() const noexcept;
ParallelPolicy parallel_policy() const noexcept;
// Execution
Status Tick(Context& ctx) noexcept;
void Reset() noexcept;explicit BehaviorTree(NodeType& root, Context& context) noexcept;
Status Tick() noexcept; // Execute one tree tick
void Reset() noexcept; // Reset all nodes
NodeType& root() const noexcept;
Context& context() noexcept;
const Context& context() const noexcept;
Status last_status() const noexcept;
uint32_t tick_count() const noexcept;namespace bt::factory {
Node<Ctx>& MakeAction(Node<Ctx>& node, TickFn tick);
Node<Ctx>& MakeCondition(Node<Ctx>& node, TickFn tick);
Node<Ctx>& MakeSequence(Node<Ctx>& node, children, count);
Node<Ctx>& MakeSelector(Node<Ctx>& node, children, count);
Node<Ctx>& MakeParallel(Node<Ctx>& node, children, count, policy);
Node<Ctx>& MakeInverter(Node<Ctx>& node, Node<Ctx>& child);
}Root (Sequence)
+-- Condition (leaf: check state)
+-- Parallel (composite: concurrent tasks)
| +-- Action (leaf: async operation)
| +-- Action (leaf: async operation)
+-- Selector (composite: fallback logic)
| +-- Action (leaf: primary path)
| +-- Action (leaf: fallback path)
+-- Inverter (decorator: flip result)
+-- Condition (leaf)
Node execution rules:
| Type | Logic |
|---|---|
| Sequence | All children must succeed (AND). Stops on first failure. |
| Selector | First successful child wins (OR). Stops on first success. |
| Parallel | Ticks all children each frame. Policy determines result. |
| Inverter | Flips SUCCESS <-> FAILURE. RUNNING/ERROR pass through. |
| Action | Leaf node: executes user-defined tick function. |
| Condition | Leaf node: checks a condition (should not return RUNNING). |
| C Pattern | C++14 Approach |
|---|---|
void* user_data |
Lambda captures (type-safe, no casting) |
void* blackboard |
Template Context parameter |
| Function pointers | std::function with closures |
#define BT_INIT_ACTION(...) |
Fluent API + factory helpers |
| Manual type enum dispatch | Same (optimal for cache/branch prediction) |
bt_set_blackboard() recursive |
Context passed via Tick(Context&) parameter |
| Example | Description |
|---|---|
| basic_example.cpp | Minimal BT: action, sequence, selector |
| bt_example.cpp | Full demo: parallel, inverter, callbacks, statistics |
| async_example.cpp | Multi-threaded async I/O via std::async + std::future |
| threadpool_example.cpp | Thread pool async I/O via progschj/ThreadPool |
| benchmark_example.cpp | Framework overhead measurement (ns/tick) |
Both async examples demonstrate non-blocking leaf nodes with real background threads:
Main thread (BT tick loop, 50ms interval)
|
+-- Tick 1: launch I/O tasks on background threads
+-- Tick 2: poll futures (non-blocking wait_for(0s))
+-- Tick 3: some tasks complete, others still RUNNING
+-- Tick 4: all done -> SUCCESS
std::async: Each task spawns a new thread. Simple but creates/destroys threads per task.
Thread pool: Fixed pool of N worker threads. Tasks queue up and reuse threads. Better for high-frequency task submission and bounded resource usage.
Framework overhead is negligible for typical embedded tick rates:
| Scenario | avg (ns) | p99 (ns) |
|---|---|---|
| Flat Sequence (8 actions) | 113 | 213 |
| Deep Nesting (5 levels) | 71 | 126 |
| Parallel (4 children) | 71 | 120 |
| Selector early exit (1/8) | 54 | 102 |
| Realistic tree (8 nodes) | 94 | 186 |
| Hand-written if-else (8 ops) | 29 | 37 |
BT overhead vs hand-written: ~4x. At 20Hz tick rate (50ms interval), this is < 0.001% of the tick budget.
See docs/design_zh.md for architecture rationale and design decisions.
mkdir -p build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
cmake --build . -j$(nproc)
ctest --output-on-failure
./examples/bt_basic_example
./examples/bt_example
./examples/bt_async_example
./examples/bt_threadpool_example
./examples/bt_benchmark- bt_simulation -- C language version with embedded device simulation
- hsm-cpp -- C++14 hierarchical state machine library
- mccc -- Lock-free MPSC message bus
MIT