diff --git a/CMakeLists.txt b/CMakeLists.txt
index 16c1ed7..2af6946 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -6,7 +6,7 @@ set(CMAKE_CXX_STANDARD 20)
option(BT_TEST "Enables testing" OFF)
-add_library(bt INTERFACE)
+add_library(bt SHARED bt.cc)
set_target_properties(bt PROPERTIES PUBLIC_HEADER "bt.h")
if(BT_TEST)
diff --git a/README.md b/README.md
index 392b435..9105f08 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# bt.h
+# bt.cc
![](https://github.com/hit9/bt/actions/workflows/tests.yml/badge.svg)
![](https://img.shields.io/badge/license-BSD3-brightgreen)
@@ -15,7 +15,7 @@ Requires at least C++20.
## Installation
-Just copy the header file `bt.h` and include it.
+Copy away `bt.h` and `bt.cc`.
## Features
@@ -592,7 +592,7 @@ Reference:
* **Visualization** [↑]
- There's a simple real time behavior tree visualization function implemented in `bt.h`.
+ There's a simple real time behavior tree visualization function implemented in `bt.cc`.
It simply displays the tree structure and execution status on the console.
The nodes colored in green are those currently executing and are synchronized with the latest tick cycles.
@@ -635,7 +635,7 @@ Reference:
* **Ticker Loop** [↑]
- There's a simple builtin ticker loop implemented in `bt.h`, to use it:
+ There's a simple builtin ticker loop implemented in `bt.cc`, to use it:
```cpp
root.TickForever(ctx, 100ms);
@@ -678,7 +678,7 @@ Reference:
It's a common case to emit and receive signals in a behavior tree.
But signal (or event) handling is a complex stuff, I don't want to couple with it in this small library.
- General ideas to introduce signal handling into bt.h is:
+ General ideas to introduce signal handling into bt.cc is:
1. Creates a custom decorator node, supposed named `OnSignalNode`.
2. Creates a custom builder class, and add a method named `OnSignal`.
@@ -686,7 +686,7 @@ Reference:
4. The data passing along with the fired signal, can be placed onto the blackboard temporary.
5. You can move the OnSignal node as high as possible to make the entire behavior tree more event-driven.
- Here's an example in detail to combine my tiny signal library [blinker.h](https://github.com/hit9/blinker.h) with bt.h,
+ Here's an example in detail to combine my tiny signal library [blinker.h](https://github.com/hit9/blinker.h) with bt.cc,
please checkout the code example in folder [example/onsignal](example/onsignal).
```cpp
diff --git a/README.zh.md b/README.zh.md
index 21cdc40..ed8169f 100644
--- a/README.zh.md
+++ b/README.zh.md
@@ -1,4 +1,4 @@
-# bt.h
+# bt.cc
![](https://github.com/hit9/bt/actions/workflows/tests.yml/badge.svg)
![](https://img.shields.io/badge/license-BSD3-brightgreen)
@@ -13,7 +13,7 @@
## 安装
-只需要拷贝走头文件 `bt.h`
+只需要拷贝走 `bt.h` 和 `bt.cc`
## 特点
@@ -237,7 +237,7 @@ while(...) {
// 任何一个有实体状态的节点都应该定义一个类型成员, 叫做 Blob
using Blob = ANodeBlob;
// 需要重载这个方法, 返回一个指向基础类 NodeBlob 类型的指针
- // getNodeBlob 是 bt.h 提供的方法, 定义在 `Node` 中
+ // getNodeBlob 是库提供的方法, 定义在 `Node` 中
NodeBlob* GetNodeBlob() const override { return getNodeBlob(); }
// 在这个类的方法中,可以用 getNodeBlob() 来获取数据块的指针, 来查询和修改实体相关的数据。
@@ -580,7 +580,7 @@ while(...) {
* **可视化** [↑]
- `bt.h` 实现了一个简单的实时把行为树运行状态可视化展示的函数,绿色的节点表示正在被 tick 的节点。
+ `bt.cc` 实现了一个简单的实时把行为树运行状态可视化展示的函数,绿色的节点表示正在被 tick 的节点。
用法示例:
@@ -618,7 +618,7 @@ while(...) {
* **Tick 循环** [↑]
- `bt.h` 中内置了一个简单额 tick 主循环:
+ `bt.cc` 中内置了一个简单额 tick 主循环:
```cpp
root.TickForever(ctx, 100ms);
@@ -659,9 +659,9 @@ while(...) {
* **信号和事件** [↑]
- 在行为树中释放和处理信号是常见的情况, 但是信号和事件的处理是一个复杂的事情, 我并不想让其和 bt.h 这个很小的库耦合起来.
+ 在行为树中释放和处理信号是常见的情况, 但是信号和事件的处理是一个复杂的事情, 我并不想让其和 bt.cc 这个很小的库耦合起来.
- 一般来说, 要想把信号处理的带入 bt.h 中, 思路如下:
+ 一般来说, 要想把信号处理的带入 bt.cc 中, 思路如下:
1. 创建一个自定义的装饰节点, 比如说叫做 `OnSignalNode`.
2. 创建一个自定义的 Builder 类, 添加一个方法叫做 `OnSignal`.
diff --git a/bt.cc b/bt.cc
new file mode 100644
index 0000000..c560138
--- /dev/null
+++ b/bt.cc
@@ -0,0 +1,621 @@
+// Copyright (c) 2024 Chao Wang .
+// License: BSD, Version: 0.4.0. https://github.com/hit9/bt.cc
+// A lightweight behavior tree library that separates data and behavior.
+
+#include "bt.h"
+
+#include // for max
+#include // for printf
+#include // for mt19937
+#include // for this_thread::sleep_for
+
+namespace bt {
+
+/////////////////
+/// TreeBlob
+/////////////////
+
+std::pair ITreeBlob::make(const NodeId id, const size_t size, const std::size_t cap) {
+ if (cap) reserve(cap);
+ std::size_t idx = id - 1;
+ if (exist(idx)) return {get(idx), false};
+ return {allocate(idx, size), true};
+}
+
+void* DynamicTreeBlob::allocate(const std::size_t idx, const std::size_t size) {
+ if (m.size() <= idx) {
+ m.resize(idx + 1);
+ e.resize(idx + 1, false);
+ }
+ auto p = std::make_unique_for_overwrite(size);
+ auto rp = p.get();
+ std::fill_n(rp, size, 0);
+ m[idx] = std::move(p);
+ e[idx] = true;
+ return rp;
+}
+
+void DynamicTreeBlob::reserve(const std::size_t cap) {
+ if (m.capacity() < cap) {
+ m.reserve(cap);
+ e.reserve(cap);
+ }
+}
+
+////////////////////////////
+/// Node
+////////////////////////////
+
+// Returns char representation of given status.
+static const char statusRepr(Status s) {
+ switch (s) {
+ case Status::UNDEFINED:
+ return 'U';
+ case Status::RUNNING:
+ return 'R';
+ case Status::SUCCESS:
+ return 'S';
+ case Status::FAILURE:
+ return 'F';
+ }
+ return 'U';
+}
+
+void Node::makeVisualizeString(std::string& s, int depth, ull seq) {
+ auto b = GetNodeBlob();
+ if (depth > 0) s += " |";
+ for (int i = 1; i < depth; i++) s += "---|";
+ if (depth > 0) s += "- ";
+ if (b->lastSeq == seq) s += "\033[32m"; // color if catches up with seq.
+ s += Name();
+ s.push_back('(');
+ s.push_back(statusRepr(b->lastStatus));
+ s.push_back(')');
+ if (b->lastSeq == seq) s += "\033[0m";
+}
+
+unsigned int Node::GetPriorityCurrentTick(const Context& ctx) {
+ if (ctx.seq != priorityCurrentTickSeq) priorityCurrentTick = 0;
+ // try cache in this tick firstly.
+ if (!priorityCurrentTick) {
+ priorityCurrentTick = Priority(ctx);
+ priorityCurrentTickSeq = ctx.seq;
+ }
+ return priorityCurrentTick;
+}
+
+void Node::Traverse(TraversalCallback& pre, TraversalCallback& post, Ptr& ptr) {
+ pre(*this, ptr);
+ post(*this, ptr);
+}
+
+Status Node::Tick(const Context& ctx) {
+ auto b = GetNodeBlob();
+ // First run of current round.
+ if (!b->running) OnEnter(ctx);
+ b->running = true;
+
+ auto status = Update(ctx);
+ b->lastStatus = status;
+ b->lastSeq = ctx.seq;
+
+ // Last run of current round.
+ if (status == Status::FAILURE || status == Status::SUCCESS) {
+ OnTerminate(ctx, status);
+ b->running = false; // reset
+ }
+ return status;
+}
+
+////////////////////////////////////////////////
+/// Node > InternalNode > SingleNode
+////////////////////////////////////////////////
+
+void SingleNode::makeVisualizeString(std::string& s, int depth, ull seq) {
+ Node::makeVisualizeString(s, depth, seq);
+ if (child != nullptr) {
+ s.push_back('\n');
+ child->makeVisualizeString(s, depth + 1, seq);
+ }
+}
+
+void SingleNode::Traverse(TraversalCallback& pre, TraversalCallback& post, Ptr& ptr) {
+ pre(*this, ptr);
+ if (child != nullptr) child->Traverse(pre, post, child);
+ post(*this, ptr);
+}
+
+////////////////////////////////////////////////
+/// Node > InternalNode > CompositeNode
+////////////////////////////////////////////////
+
+void CompositeNode::makeVisualizeString(std::string& s, int depth, ull seq) {
+ Node::makeVisualizeString(s, depth, seq);
+ for (auto& child : children) {
+ if (child != nullptr) {
+ s.push_back('\n');
+ child->makeVisualizeString(s, depth + 1, seq);
+ }
+ }
+}
+
+void CompositeNode::Traverse(TraversalCallback& pre, TraversalCallback& post, Ptr& ptr) {
+ pre(*this, ptr);
+ for (auto& child : children)
+ if (child != nullptr) child->Traverse(pre, post, child);
+ post(*this, ptr);
+}
+
+unsigned int CompositeNode::Priority(const Context& ctx) const {
+ unsigned int ans = 0;
+ for (int i = 0; i < children.size(); i++)
+ if (considerable(i)) ans = std::max(ans, children[i]->GetPriorityCurrentTick(ctx));
+ return ans;
+}
+
+//////////////////////////////////////////////////////////////
+/// Node > InternalNode > CompositeNode > _Internal Impls
+///////////////////////////////////////////////////////////////
+
+void _InternalStatefulCompositeNode::OnTerminate(const Context& ctx, Status status) {
+ auto& t = getNodeBlob()->st;
+ std::fill(t.begin(), t.end(), false);
+}
+
+void _InternalStatefulCompositeNode::OnBlobAllocated(NodeBlob* blob) const {
+ auto ptr = static_cast(blob);
+ ptr->st.resize(children.size(), false);
+}
+
+_MixedQueueHelper::_MixedQueueHelper(Cmp cmp, const std::size_t n) {
+ // reserve capacity for q1.
+ q1Container.reserve(n);
+ q1 = &q1Container;
+ // reserve capacity for q2.
+ decltype(q2) _q2(cmp);
+ q2.swap(_q2);
+ q2.reserve(n);
+}
+
+int _MixedQueueHelper::pop() {
+ if (use1) return (*q1)[q1Front++];
+ int v = q2.top();
+ q2.pop();
+ return v;
+}
+
+void _MixedQueueHelper::push(int v) {
+ if (use1) {
+ if (q1 != &q1Container) throw std::runtime_error("bt: cant push on outside q1 container");
+ return q1->push_back(v);
+ }
+ q2.push(v);
+}
+
+bool _MixedQueueHelper::empty() const {
+ if (use1) return q1Front == q1->size();
+ return q2.empty();
+}
+
+void _MixedQueueHelper::clear() {
+ if (use1) {
+ if (q1 != &q1Container) throw std::runtime_error("bt: cant clear on outside q1 container");
+ q1->resize(0);
+ q1Front = 0;
+ return;
+ }
+ q2.clear();
+}
+
+void _MixedQueueHelper::setQ1Container(std::vector* c) {
+ q1 = c;
+ q1Front = 0;
+}
+
+void _InternalPriorityCompositeNode::refresh(const Context& ctx) {
+ areAllEqual = true;
+ // v is the first valid priority value.
+ unsigned int v = 0;
+
+ for (int i = 0; i < children.size(); i++) {
+ if (!considerable(i)) continue;
+ p[i] = children[i]->GetPriorityCurrentTick(ctx);
+ if (!v) v = p[i];
+ if (v != p[i]) areAllEqual = false;
+ }
+}
+
+void _InternalPriorityCompositeNode::enqueue() {
+ // if all priorities are equal, use q1 O(N)
+ // otherwise, use q2 O(n*logn)
+ q.setflag(areAllEqual);
+
+ // We have to consider all children, and all priorities are equal,
+ // then, we should just use a pre-exist vector to avoid a O(n) copy to q1.
+ if ((!isParatialConsidered()) && areAllEqual) {
+ q.setQ1Container(&simpleQ1Container);
+ return; // no need to perform enqueue
+ }
+
+ q.resetQ1Container();
+
+ // Clear and enqueue.
+ q.clear();
+ for (int i = 0; i < children.size(); i++)
+ if (considerable(i)) q.push(i);
+}
+
+void _InternalPriorityCompositeNode::internalOnBuild() {
+ CompositeNode::internalOnBuild();
+ // pre-allocate capacity for p.
+ p.resize(children.size());
+ // initialize simpleQ1Container;
+ for (int i = 0; i < children.size(); i++) simpleQ1Container.push_back(i);
+ // Compare priorities between children, where a and b are indexes.
+ // priority from large to smaller, so use `less`: pa < pb
+ // order: from small to larger, so use `greater`: a > b
+ auto cmp = [&](const int a, const int b) { return p[a] < p[b] || a > b; };
+ q = _MixedQueueHelper(cmp, children.size());
+}
+
+Status _InternalPriorityCompositeNode::Update(const Context& ctx) {
+ refresh(ctx);
+ enqueue();
+ // propagates ticks
+ return update(ctx);
+}
+
+//////////////////////////////////////////////////////////////
+/// Node > InternalNode > CompositeNode > SequenceNode
+///////////////////////////////////////////////////////////////
+
+Status _InternalSequenceNodeBase::update(const Context& ctx) {
+ // propagates ticks, one by one sequentially.
+ while (!q.empty()) {
+ auto i = q.pop();
+ auto status = children[i]->Tick(ctx);
+ if (status == Status::RUNNING) return Status::RUNNING;
+ // F if any child F.
+ if (status == Status::FAILURE) {
+ onChildFailure(i);
+ return Status::FAILURE;
+ }
+ // S
+ onChildSuccess(i);
+ }
+ // S if all children S.
+ return Status::SUCCESS;
+}
+
+//////////////////////////////////////////////////////////////
+/// Node > InternalNode > CompositeNode > SelectorNode
+///////////////////////////////////////////////////////////////
+
+Status _InternalSelectorNodeBase::update(const Context& ctx) {
+ // select a success children.
+ while (!q.empty()) {
+ auto i = q.pop();
+ auto status = children[i]->Tick(ctx);
+ if (status == Status::RUNNING) return Status::RUNNING;
+ // S if any child S.
+ if (status == Status::SUCCESS) {
+ onChildSuccess(i);
+ return Status::SUCCESS;
+ }
+ // F
+ onChildFailure(i);
+ }
+ // F if all children F.
+ return Status::FAILURE;
+}
+
+//////////////////////////////////////////////////////////////
+/// Node > InternalNode > CompositeNode > RandomSelectorNode
+///////////////////////////////////////////////////////////////
+
+static std::mt19937 rng(std::random_device{}()); // seed random
+
+Status _InternalRandomSelectorNodeBase::Update(const Context& ctx) {
+ refresh(ctx);
+ // Sum of weights/priorities.
+ unsigned int total = 0;
+ for (int i = 0; i < children.size(); i++)
+ if (considerable(i)) total += p[i];
+
+ // random select one, in range [1, total]
+ std::uniform_int_distribution distribution(1, total);
+
+ auto select = [&]() -> int {
+ unsigned int v = distribution(rng); // gen random unsigned int between [0, sum]
+ unsigned int s = 0; // sum of iterated children.
+ for (int i = 0; i < children.size(); i++) {
+ if (!considerable(i)) continue;
+ s += p[i];
+ if (v <= s) return i;
+ }
+ return 0; // won't reach here.
+ };
+
+ // While still have children considerable.
+ // total reaches 0 only if no children left,
+ // notes that Priority() always returns a positive value.
+ while (total) {
+ int i = select();
+ auto status = children[i]->Tick(ctx);
+ if (status == Status::RUNNING) return Status::RUNNING;
+ // S if any child S.
+ if (status == Status::SUCCESS) {
+ onChildSuccess(i);
+ return Status::SUCCESS;
+ }
+ // Failure, it shouldn't be considered any more in this tick.
+ onChildFailure(i);
+ // remove its weight from total, won't be consider again.
+ total -= p[i];
+ // updates the upper bound of distribution.
+ distribution.param(std::uniform_int_distribution::param_type(1, total));
+ }
+ // F if all children F.
+ return Status::FAILURE;
+}
+
+//////////////////////////////////////////////////////////////
+/// Node > InternalNode > CompositeNode > ParallelNode
+///////////////////////////////////////////////////////////////
+
+Status _InternalParallelNodeBase::update(const Context& ctx) {
+ // Propagates tick to all considerable children.
+ int cntFailure = 0, cntSuccess = 0, total = 0;
+ while (!q.empty()) {
+ auto i = q.pop();
+ auto status = children[i]->Tick(ctx);
+ total++;
+ if (status == Status::FAILURE) {
+ cntFailure++;
+ onChildFailure(i);
+ }
+ if (status == Status::SUCCESS) {
+ cntSuccess++;
+ onChildSuccess(i);
+ }
+ }
+
+ // S if all children S.
+ if (cntSuccess == total) return Status::SUCCESS;
+ // F if any child F.
+ if (cntFailure > 0) return Status::FAILURE;
+ return Status::RUNNING;
+}
+
+//////////////////////////////////////////////////////////////
+/// Node > InternalNode > CompositeNode > Decorator
+///////////////////////////////////////////////////////////////
+
+Status InvertNode::Update(const Context& ctx) {
+ auto status = child->Tick(ctx);
+ switch (status) {
+ case Status::RUNNING:
+ return Status::RUNNING;
+ case Status::FAILURE:
+ return Status::SUCCESS;
+ default:
+ return Status::FAILURE;
+ }
+}
+
+void ConditionalRunNode::Traverse(TraversalCallback& pre, TraversalCallback& post, Ptr& ptr) {
+ pre(*this, ptr);
+ if (condition != nullptr) condition->Traverse(pre, post, condition);
+ if (child != nullptr) child->Traverse(pre, post, child);
+ post(*this, ptr);
+}
+
+Status ConditionalRunNode::Update(const Context& ctx) {
+ if (condition->Tick(ctx) == Status::SUCCESS) return child->Tick(ctx);
+ return Status::FAILURE;
+}
+
+Status RepeatNode::Update(const Context& ctx) {
+ if (n == 0) return Status::SUCCESS;
+ auto status = child->Tick(ctx);
+ if (status == Status::RUNNING) return Status::RUNNING;
+ if (status == Status::FAILURE) return Status::FAILURE;
+ // Count success until n times, -1 will never stop.
+ if (++(getNodeBlob()->cnt) == n) return Status::SUCCESS;
+ // Otherwise, it's still running.
+ return Status::RUNNING;
+}
+
+void TimeoutNode::OnEnter(const Context& ctx) {
+ getNodeBlob()->startAt = std::chrono::steady_clock::now();
+}
+
+Status TimeoutNode::Update(const Context& ctx) {
+ // Check if timeout at first.
+ auto now = std::chrono::steady_clock::now();
+ if (now > getNodeBlob()->startAt + duration) return Status::FAILURE;
+ return child->Tick(ctx);
+}
+
+void DelayNode::OnEnter(const Context& ctx) {
+ getNodeBlob()->firstRunAt = std::chrono::steady_clock::now();
+}
+void DelayNode::OnTerminate(const Context& ctx, Status status) {
+ getNodeBlob()->firstRunAt = Timepoint::min();
+}
+
+Status DelayNode::Update(const Context& ctx) {
+ auto now = std::chrono::steady_clock::now();
+ if (now < getNodeBlob()->firstRunAt + duration) return Status::RUNNING;
+ return child->Tick(ctx);
+}
+
+void RetryNode::OnEnter(const Context& ctx) {
+ auto b = getNodeBlob();
+ b->cnt = 0;
+ b->lastRetryAt = Timepoint::min();
+}
+
+void RetryNode::OnTerminate(const Context& ctx, Status status) {
+ auto b = getNodeBlob();
+ b->cnt = 0;
+ b->lastRetryAt = status == Status::FAILURE ? std::chrono::steady_clock::now() : Timepoint::min();
+}
+
+Status RetryNode::Update(const Context& ctx) {
+ auto b = getNodeBlob();
+
+ if (maxRetries != -1 && b->cnt > maxRetries) return Status::FAILURE;
+
+ // If has failures before, and retry timepoint isn't arriving.
+ auto now = std::chrono::steady_clock::now();
+ if (b->cnt > 0 && now < b->lastRetryAt + interval) return Status::RUNNING;
+
+ // Time to run/retry.
+ auto status = child->Tick(ctx);
+ switch (status) {
+ case Status::RUNNING:
+ [[fallthrough]];
+ case Status::SUCCESS:
+ return status;
+ default:
+ // Failure
+ if (++b->cnt > maxRetries && maxRetries != -1) return Status::FAILURE; // exeeds max retries.
+ return Status::RUNNING; // continues retry
+ }
+}
+
+//////////////////////////////////////////////////////////////
+/// Node > SingleNode > RootNode
+///////////////////////////////////////////////////////////////
+
+void RootNode::Visualize(ull seq) {
+ // CSI[2J clears screen.
+ // CSI[H moves the cursor to top-left corner
+ printf("\x1B[2J\x1B[H");
+ // Make a string.
+ std::string s;
+ makeVisualizeString(s, 0, seq);
+ printf("%s", s.c_str());
+}
+
+void RootNode::TickForever(Context& ctx, std::chrono::nanoseconds interval, bool visualize,
+ std::function post) {
+ auto lastTickAt = std::chrono::steady_clock::now();
+
+ while (true) {
+ auto nextTickAt = lastTickAt + interval;
+
+ // Time delta between last tick and current tick.
+ ctx.delta = std::chrono::steady_clock::now() - lastTickAt;
+ ++ctx.seq;
+ Tick(ctx);
+ if (post != nullptr) post(ctx);
+ if (visualize) Visualize(ctx.seq);
+
+ // Catch up with next tick.
+ lastTickAt = std::chrono::steady_clock::now();
+ if (lastTickAt < nextTickAt) {
+ std::this_thread::sleep_for(nextTickAt - lastTickAt);
+ }
+ }
+}
+
+//////////////////////////////////////////////////////////////
+/// Tree Builder
+///////////////////////////////////////////////////////////////
+
+void _InternalBuilderBase::maintainNodeBindInfo(Node& node, RootNode* root) {
+ node.root = root;
+ root->n++;
+ node.id = ++nextNodeId;
+}
+void _InternalBuilderBase::maintainSizeInfoOnRootBind(RootNode* root, std::size_t rootNodeSize,
+ std::size_t blobSize) {
+ root->size = rootNodeSize;
+ root->treeSize += rootNodeSize;
+ root->maxSizeNode = rootNodeSize;
+ root->maxSizeNodeBlob = blobSize;
+}
+
+void _InternalBuilderBase::maintainSizeInfoOnSubtreeAttach(RootNode& subtree, RootNode* root) {
+ root->treeSize += subtree.treeSize;
+ root->maxSizeNode = std::max(root->maxSizeNode, subtree.maxSizeNode);
+ root->maxSizeNodeBlob = std::max(root->maxSizeNodeBlob, subtree.maxSizeNodeBlob);
+}
+
+void _InternalBuilderBase::onRootAttach(RootNode* root, std::size_t size, std::size_t blobSize) {
+ maintainNodeBindInfo(*root, root);
+ maintainSizeInfoOnRootBind(root, size, blobSize);
+}
+
+void _InternalBuilderBase::onSubtreeAttach(RootNode& subtree, RootNode* root) {
+ // Resets root in sub tree recursively.
+ TraversalCallback pre = [&](Node& node, Ptr& ptr) { maintainNodeBindInfo(node, root); };
+ subtree.Traverse(pre, NullTraversalCallback, NullNodePtr);
+ maintainSizeInfoOnSubtreeAttach(subtree, root);
+}
+
+void _InternalBuilderBase::onNodeBuild(Node* node) {
+ node->internalOnBuild();
+ node->OnBuild();
+}
+
+void _InternalBuilderBase::_maintainSizeInfoOnNodeAttach(Node& node, RootNode* root, std::size_t nodeSize,
+ std::size_t nodeBlobSize) {
+ node.size = nodeSize;
+ root->treeSize += nodeSize;
+ root->maxSizeNode = std::max(root->maxSizeNode, nodeSize);
+ root->maxSizeNodeBlob = std::max(root->maxSizeNodeBlob, nodeBlobSize);
+}
+
+void _InternalBuilderBase::validate(Node* node) {
+ auto e = node->Validate();
+ if (!e.empty()) {
+ std::string s = "bt build: ";
+ s += node->Name();
+ s += ' ';
+ s += e;
+ throw std::runtime_error(s);
+ }
+}
+void _InternalBuilderBase::validateIndent() {
+ if (level > stack.size()) {
+ auto node = stack.top();
+ std::string s = "bt build: too much indent ";
+ s += "below ";
+ s += node->Name();
+ throw std::runtime_error(s);
+ }
+}
+
+void _InternalBuilderBase::pop() {
+ validate(stack.top()); // validate before pop
+ onNodeBuild(stack.top());
+ stack.pop();
+}
+// Adjust stack to current indent level.
+void _InternalBuilderBase::adjust() {
+ validateIndent();
+ while (level < stack.size()) pop();
+}
+void _InternalBuilderBase::attachLeafNode(Ptr p) {
+ adjust();
+ // Append to stack's top as a child.
+ onNodeBuild(p.get());
+ stack.top()->Append(std::move(p));
+ // resets level.
+ level = 1;
+}
+
+void _InternalBuilderBase::attachInternalNode(Ptr p) {
+ adjust();
+ // Append to stack's top as a child, and replace the top.
+ auto parent = stack.top();
+ stack.push(p.get());
+ parent->Append(std::move(p));
+ // resets level.
+ level = 1;
+}
+
+} // namespace bt
diff --git a/bt.h b/bt.h
index a67e076..e644b7f 100644
--- a/bt.h
+++ b/bt.h
@@ -1,126 +1,32 @@
// Copyright (c) 2024 Chao Wang .
-// License: BSD. https://github.com/hit9/bt.h
-//
+// License: BSD, Version: 0.4.0. https://github.com/hit9/bt.cc
// A lightweight behavior tree library that separates data and behavior.
-//
-// Requirements: at least C++20.
-//
-// Features
-// ~~~~~~~~
-//
-// 1. Nodes store no data states, behaviors and data are separated.
-// 2. Builds a behavior tree in tree structure codes, concise and expressive,
-// and supports to extend the builder.
-// 3. Built-in multiple decorators, and supports custom decoration nodes,
-// 4. Supports composite nodes with priority child nodes, and random selector.
-// 5. Also supports continuous memory fixed sized tree blob.
-//
-// Code Example
-// ~~~~~~~~~~~~
-//
-// To structure a tree:
-//
-// bt::Tree root;
-// root
-// .Sequence()
-// ._().Action()
-// ._().Repeat(3)
-// ._()._().Action()
-// .End();
-//
-// Prepares a TreeBlob, e.g. for each entity:
-//
-// // A TreeBlob holds all the internal state data.
-// bt::DynamicTreeBlob blob;
-//
-// // Or use a fixed size tree blob:
-// bt::FixedTreeBlob<8, 64> blob;
-//
-// Run ticking:
-//
-// bt::Context ctx;
-//
-// // In the ticking loop.
-// while(...) {
-// // for each blob
-// for (auto& blob : allBlobs) {
-// root.BindTreeBlob(blob);
-// root.Tick(ctx)
-// root.UnbindTreeBlob();
-// }
-// }
-//
-// Classes Structure
-// ~~~~~~~~~~~~~~~~~
-//
-// Node
-// | InternalNode
-// | | SingleNode
-// | | | RootNode
-// | | | DecoratorNode
-// | | CompositeNode
-// | | | SelectorNode
-// | | | ParallelNode
-// | | | SequenceNode
-// | LeafNode
-// | | ActionNode
-// | | ConditionNode
-
-// Version: 0.3.5
#ifndef HIT9_BT_H
#define HIT9_BT_H
-#include // for max, min
#include
-#include // for NDEBUG mode
-#include // for milliseconds, high_resolution_clock
+#include // for milliseconds, steady_clock
#include // for memset
#include
-#include // for cout, flush
-#include // for unique_ptr
-#include // for priority_queue
-#include // for mt19937
+#include // for unique_ptr
+#include // for priority_queue
#include
#include // for runtime_error
#include
#include
-#include // for this_thread::sleep_for
#include // for is_base_of_v
+#include // for pair
#include
namespace bt {
-
-////////////////////////////
-/// Status
-////////////////////////////
-
enum class Status { UNDEFINED = 0, RUNNING = 1, SUCCESS = 2, FAILURE = 3 };
-// Returns char representation of given status.
-static const char statusRepr(Status s) {
- switch (s) {
- case Status::UNDEFINED:
- return 'U';
- case Status::RUNNING:
- return 'R';
- case Status::SUCCESS:
- return 'S';
- case Status::FAILURE:
- return 'F';
- }
- return 'U';
-}
-
using ull = unsigned long long;
// Node instance's id type.
using NodeId = unsigned int;
-////////////////////////////
-/// Context
-////////////////////////////
-
// Tick/Update's Context.
struct Context {
ull seq; // Tick seq number.
@@ -138,6 +44,10 @@ struct Context {
Context(T data) : data(data), seq(0) {}
};
+////////////////////////////
+/// TreeBlob
+////////////////////////////
+
// NodeBlob is the base class to store node's internal entity-related data and states.
struct NodeBlob {
bool running = false; // is still running?
@@ -149,10 +59,6 @@ struct NodeBlob {
template
concept TNodeBlob = std::is_base_of_v;
-////////////////////////////
-/// TreeBlob
-////////////////////////////
-
// ITreeBlob is an internal interface base class for FixedTreeBlob and DynamicTreeBlob.
// A TreeBlob stores the entity-related states data for all nodes in a tree.
// One tree blob for one entity.
@@ -166,7 +72,7 @@ class ITreeBlob {
// returns nullptr if not exist.
virtual void* get(const std::size_t idx) = 0;
// pre-reserve enough capacity if need.
- virtual void reserve(const std::size_t cap) = 0;
+ virtual void reserve(const std::size_t cap) {}
public:
virtual ~ITreeBlob() {}
@@ -175,14 +81,15 @@ class ITreeBlob {
// Parameter cb is an optional function to be called after the blob is first allocated.
template
B* Make(const NodeId id, const std::function& cb, const std::size_t cap = 0) {
- if (cap) reserve(cap);
- std::size_t idx = id - 1;
- if (exist(idx)) return static_cast(get(idx));
- auto p = static_cast(allocate(idx, sizeof(B)));
- new (p) B(); // call constructor
- if (cb != nullptr) cb(p);
- return p;
+ auto [p, b] = make(id, sizeof(B), cap);
+ if (!b) return static_cast(p);
+ auto q = new (p) B(); // call constructor
+ if (cb != nullptr) cb(q);
+ return q;
}
+
+ private:
+ std::pair make(const NodeId id, size_t size, const std::size_t cap = 0);
};
// FixedTreeBlob is just a continuous buffer, implements ITreeBlob.
@@ -200,7 +107,6 @@ class FixedTreeBlob final : public ITreeBlob {
}
bool exist(const std::size_t idx) override { return static_cast(buf[idx][0]); }
void* get(const std::size_t idx) override { return &buf[idx][1]; }
- void reserve(const std::size_t cap) override {}
public:
FixedTreeBlob() { memset(buf, 0, sizeof(buf)); }
@@ -213,26 +119,10 @@ class DynamicTreeBlob final : public ITreeBlob {
std::vector e; // index => exist, dynamic
protected:
- void* allocate(const std::size_t idx, const std::size_t size) override {
- if (m.size() <= idx) {
- m.resize(idx + 1);
- e.resize(idx + 1, false);
- }
- auto p = std::make_unique_for_overwrite(size);
- auto rp = p.get();
- std::fill_n(rp, size, 0);
- m[idx] = std::move(p);
- e[idx] = true;
- return rp;
- };
+ void* allocate(const std::size_t idx, const std::size_t size) override;
bool exist(const std::size_t idx) override { return e.size() > idx && e[idx]; }
void* get(const std::size_t idx) override { return m[idx].get(); }
- void reserve(const std::size_t cap) override {
- if (m.capacity() < cap) {
- m.reserve(cap);
- e.reserve(cap);
- }
- }
+ void reserve(const std::size_t cap) override;
public:
DynamicTreeBlob() {}
@@ -275,40 +165,25 @@ class Node {
unsigned int priorityCurrentTick = 0;
// tick seq when the priority cache was set.
ull priorityCurrentTickSeq = 0;
-
- protected:
- NodeId id = 0;
// holding a pointer to the root.
IRootNode* root = nullptr;
// size of this node, available after tree built.
std::size_t size = 0;
+ // friend with _InternalBuilderBase to access member root, size and id etc.
+ friend class _InternalBuilderBase;
+
+ protected:
+ NodeId id = 0;
+
// Internal helper method to return the raw pointer to the node blob.
template
B* getNodeBlob() const {
- assert(id != 0);
- assert(root != nullptr);
- auto b = root->GetTreeBlob();
- assert(b != nullptr);
const auto cb = [&](NodeBlob* blob) { OnBlobAllocated(blob); };
- // allocate, or get if exist
- return b->Make(id, cb, root->NumNodes());
+ return root->GetTreeBlob()->Make(id, cb, root->NumNodes()); // get or alloc
}
-
// Internal method to visualize tree.
- virtual void makeVisualizeString(std::string& s, int depth, ull seq) {
- auto b = GetNodeBlob();
- if (depth > 0) s += " |";
- for (int i = 1; i < depth; i++) s += "---|";
- if (depth > 0) s += "- ";
- if (b->lastSeq == seq) s += "\033[32m"; // color if catches up with seq.
- s += Name();
- s.push_back('(');
- s.push_back(statusRepr(b->lastStatus));
- s.push_back(')');
- if (b->lastSeq == seq) s += "\033[0m";
- }
-
+ virtual void makeVisualizeString(std::string& s, int depth, ull seq);
// Internal onBuild method.
// Separating from the public hook api OnBuild, so there's no need to call parent
// class's OnBuild for custom OnBuild overridings.
@@ -318,9 +193,6 @@ class Node {
friend class SingleNode;
friend class CompositeNode;
- // friend with _InternalBuilderBase to access member root, size and id etc.
- friend class _InternalBuilderBase;
-
public:
// Every stateful Node class should declare its own Blob type member.
using Blob = NodeBlob;
@@ -328,9 +200,8 @@ class Node {
Node(std::string_view name = "Node") : name(name) {}
virtual ~Node() = default;
- /////////////////////////////////////////
// Simple Getters
- /////////////////////////////////////////
+ // ~~~~~~~~~~~~~~
// Returns the id of this node.
NodeId Id() const { return id; }
@@ -340,73 +211,37 @@ class Node {
virtual std::string_view Name() const { return name; }
// Returns last status of this node.
bt::Status LastStatus() const { return GetNodeBlob()->lastStatus; }
-
// Helps to access the node blob's pointer, which stores the entity-related data and states.
// Any Node has a NodeBlob class should override this.
virtual NodeBlob* GetNodeBlob() const { return getNodeBlob(); }
-
// Internal method to query priority of this node in current tick.
- unsigned int GetPriorityCurrentTick(const Context& ctx) {
- if (ctx.seq != priorityCurrentTickSeq) priorityCurrentTick = 0;
- // try cache in this tick firstly.
- if (!priorityCurrentTick) {
- priorityCurrentTick = Priority(ctx);
- priorityCurrentTickSeq = ctx.seq;
- }
- return priorityCurrentTick;
- }
+ unsigned int GetPriorityCurrentTick(const Context& ctx);
- /////////////////////////////////////////
// API
- /////////////////////////////////////////
+ // ~~~
// Traverse the subtree of the current node recursively and execute the given function callback functions.
// The callback function pre will be called pre-order, and the post will be called post-order.
// Pass NullTraversalCallback for empty callbacks.
- virtual void Traverse(TraversalCallback& pre, TraversalCallback& post, Ptr& ptr) {
- pre(*this, ptr);
- post(*this, ptr);
- }
-
+ virtual void Traverse(TraversalCallback& pre, TraversalCallback& post, Ptr& ptr);
// Main entry function, should be called on every tick.
- Status Tick(const Context& ctx) {
- auto b = GetNodeBlob();
- // First run of current round.
- if (!b->running) OnEnter(ctx);
- b->running = true;
-
- auto status = Update(ctx);
- b->lastStatus = status;
- b->lastSeq = ctx.seq;
-
- // Last run of current round.
- if (status == Status::FAILURE || status == Status::SUCCESS) {
- OnTerminate(ctx, status);
- b->running = false; // reset
- }
- return status;
- }
+ Status Tick(const Context& ctx);
- /////////////////////////////////////////
// Public Virtual Functions To Override
- /////////////////////////////////////////
+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Hook function to be called on this node's first run.
// Nice to call parent class' OnEnter before your implementation.
virtual void OnEnter(const Context& ctx){};
-
// Hook function to be called once this node goes into success or failure.
// Nice to call parent class' OnTerminate after your implementation
virtual void OnTerminate(const Context& ctx, Status status){};
-
// Hook function to be called on this node's build is finished.
// Nice to call parent class' OnBuild after your implementation
virtual void OnBuild() {}
-
// Main update function to be implemented by all subclasses.
// It's the body part of function Tick().
virtual Status Update(const Context& ctx) { return Status::SUCCESS; };
-
// Returns the priority of this node, should be strictly larger than 0, the larger the higher.
// By default, all nodes' priorities are equal, to 1.
// Providing this method is primarily for selecting children by dynamic priorities.
@@ -415,10 +250,8 @@ class Node {
// Another optimization is to separate calculation from getter, for example, pre-cache the result
// somewhere on the blackboard, and just ask it from memory here.
virtual unsigned int Priority(const Context& ctx) const { return 1; }
-
// Hook function to be called on a blob's first allocation.
virtual void OnBlobAllocated(NodeBlob* blob) const {}
-
// Validate whether the node is builded correctly.
// Returns error message, empty string for good.
virtual std::string_view Validate() const { return ""; }
@@ -446,10 +279,8 @@ concept TLeafNode = std::is_base_of_v;
/// Node > LeafNode > ConditionNode
/////////////////////////////////////
-// ConditionNode succeeds only if Check() returns true, it never returns
-// RUNNING.
-// The checker is stored directly on the tree structure.
-// Note that the provided checker should be independent with any entities's stateful data.
+// ConditionNode succeeds only if Check() returns true, it never returns RUNNING.
+// Note that checker should be independent with any entities's stateful data, it's stored on the tree.
class ConditionNode : public LeafNode {
public:
using Checker = std::function;
@@ -463,7 +294,6 @@ class ConditionNode : public LeafNode {
private:
Checker checker = nullptr;
};
-
using Condition = ConditionNode; // alias
// Concept TCondition for all classes derived from Condition.
@@ -511,22 +341,12 @@ class SingleNode : public InternalNode {
protected:
Ptr child;
- void makeVisualizeString(std::string& s, int depth, ull seq) override {
- Node::makeVisualizeString(s, depth, seq);
- if (child != nullptr) {
- s.push_back('\n');
- child->makeVisualizeString(s, depth + 1, seq);
- }
- }
+ void makeVisualizeString(std::string& s, int depth, ull seq) override;
public:
SingleNode(std::string_view name = "SingleNode", Ptr child = nullptr)
: InternalNode(name), child(std::move(child)) {}
- void Traverse(TraversalCallback& pre, TraversalCallback& post, Ptr& ptr) override {
- pre(*this, ptr);
- if (child != nullptr) child->Traverse(pre, post, child);
- post(*this, ptr);
- }
+ void Traverse(TraversalCallback& pre, TraversalCallback& post, Ptr& ptr) override;
std::string_view Validate() const override { return child == nullptr ? "no child node provided" : ""; }
void Append(Ptr node) override { child = std::move(node); }
unsigned int Priority(const Context& ctx) const override { return child->GetPriorityCurrentTick(ctx); }
@@ -550,48 +370,23 @@ class CompositeNode : public InternalNode {
// Internal hook function to be called after a child goes failure.
virtual void onChildFailure(const int i){};
- void makeVisualizeString(std::string& s, int depth, ull seq) override {
- Node::makeVisualizeString(s, depth, seq);
- for (auto& child : children) {
- if (child != nullptr) {
- s.push_back('\n');
- child->makeVisualizeString(s, depth + 1, seq);
- }
- }
- }
+ void makeVisualizeString(std::string& s, int depth, ull seq) override;
public:
CompositeNode(std::string_view name = "CompositeNode", PtrList&& cs = {}) : InternalNode(name) {
children.swap(cs);
}
- void Traverse(TraversalCallback& pre, TraversalCallback& post, Ptr& ptr) override {
- pre(*this, ptr);
- for (auto& child : children)
- if (child != nullptr) child->Traverse(pre, post, child);
- post(*this, ptr);
- }
-
+ void Traverse(TraversalCallback& pre, TraversalCallback& post, Ptr& ptr) override;
void Append(Ptr node) override { children.push_back(std::move(node)); }
std::string_view Validate() const override { return children.empty() ? "children empty" : ""; }
-
// Returns the max priority of considerable children.
- unsigned int Priority(const Context& ctx) const final override {
- unsigned int ans = 0;
- for (int i = 0; i < children.size(); i++)
- if (considerable(i)) ans = std::max(ans, children[i]->GetPriorityCurrentTick(ctx));
- return ans;
- }
+ unsigned int Priority(const Context& ctx) const final override;
};
//////////////////////////////////////////////////////////////
/// Node > InternalNode > CompositeNode > _Internal Impls
///////////////////////////////////////////////////////////////
-struct _InternalStatefulCompositeNodeBlob : NodeBlob {
- // st[i] => should we skip considering children at index i ?
- std::vector st;
-};
-
// Always skip children that already succeeded or failure during current round.
class _InternalStatefulCompositeNode : virtual public CompositeNode {
protected:
@@ -600,16 +395,14 @@ class _InternalStatefulCompositeNode : virtual public CompositeNode {
void skip(const int i) { getNodeBlob()->st[i] = true; }
public:
- using Blob = _InternalStatefulCompositeNodeBlob;
+ struct Blob : NodeBlob {
+ // st[i] => should we skip considering children at index i ?
+ std::vector st;
+ };
+
NodeBlob* GetNodeBlob() const override { return getNodeBlob(); }
- void OnTerminate(const Context& ctx, Status status) override {
- auto& t = getNodeBlob()->st;
- std::fill(t.begin(), t.end(), false);
- }
- void OnBlobAllocated(NodeBlob* blob) const override {
- auto ptr = static_cast(blob);
- ptr->st.resize(children.size(), false);
- }
+ void OnTerminate(const Context& ctx, Status status) override;
+ void OnBlobAllocated(NodeBlob* blob) const override;
};
// _MixedQueueHelper is a helper queue wrapper for _InternalPriorityCompositeNode .
@@ -629,61 +422,24 @@ class _MixedQueueHelper {
};
private:
- // use a pre-allocated vector instead of a std::queue
- // The q1 will be pushed all and then poped all, so a simple vector is enough,
- // and neither needs a circular queue.
- // And here we use a pointer, allowing temporarily replace q1's container from
- // outside existing container.
+ // use a pre-allocated vector instead of a std::queue, q1 will be pushed all and then poped all,
+ // so a simple vector is enough, and neither needs a circular queue.
+ // And here we use a pointer, allowing temporarily replace q1's container from outside existing container.
std::vector* q1;
std::vector q1Container;
int q1Front = 0;
-
_priority_queue> q2;
-
bool use1; // using q1? otherwise q2
public:
_MixedQueueHelper() {}
- _MixedQueueHelper(Cmp cmp, const std::size_t n) {
- // reserve capacity for q1.
- q1Container.reserve(n);
- q1 = &q1Container;
- // reserve capacity for q2.
- decltype(q2) _q2(cmp);
- q2.swap(_q2);
- q2.reserve(n);
- }
+ _MixedQueueHelper(Cmp cmp, const std::size_t n);
void setflag(bool u1) { use1 = u1; }
- int pop() {
- if (use1) return (*q1)[q1Front++];
- int v = q2.top();
- q2.pop();
- return v;
- }
- void push(int v) {
- if (use1) {
- if (q1 != &q1Container) throw std::runtime_error("bt: cant push on outside q1 container");
- return q1->push_back(v);
- }
- q2.push(v);
- }
- bool empty() const {
- if (use1) return q1Front == q1->size();
- return q2.empty();
- }
- void clear() {
- if (use1) {
- if (q1 != &q1Container) throw std::runtime_error("bt: cant clear on outside q1 container");
- q1->resize(0);
- q1Front = 0;
- return;
- }
- q2.clear();
- }
- void setQ1Container(std::vector* c) {
- q1 = c;
- q1Front = 0;
- }
+ int pop();
+ void push(int v);
+ bool empty() const;
+ void clear();
+ void setQ1Container(std::vector* c);
void resetQ1Container(void) { q1 = &q1Container; }
};
@@ -694,84 +450,32 @@ class _InternalPriorityCompositeNode : virtual public CompositeNode {
// p[i] stands for i'th child's priority.
// Since p will be refreshed on each tick, so it's stateless.
std::vector p;
-
// q contains a simple queue and a simple queue, depending on:
// if priorities of considerable children are all equal in this tick.
_MixedQueueHelper q;
-
// Are all priorities of considerable children equal on this tick?
// Refreshed by function refresh on every tick.
bool areAllEqual;
-
// simpleQ1Container contains [0...n-1]
// Used as a temp container for q1 for "non-stateful && non-priorities" compositors.
std::vector simpleQ1Container;
- // Although only considerable children's priorities are refreshed,
- // and the q only picks considerable children, but p and q are still stateless with entities.
+ // Explains that: p and q are still stateless with entities, reason:
// p and q are consistent with respect to "which child nodes to consider".
// If we change the blob binding, the new tick won't be affected by previous blob.
+ void internalOnBuild() override;
// Refresh priorities for considerable children.
- void refresh(const Context& ctx) {
- areAllEqual = true;
- // v is the first valid priority value.
- unsigned int v = 0;
-
- for (int i = 0; i < children.size(); i++) {
- if (!considerable(i)) continue;
- p[i] = children[i]->GetPriorityCurrentTick(ctx);
- if (!v) v = p[i];
- if (v != p[i]) areAllEqual = false;
- }
- }
-
- void enqueue() {
- // if all priorities are equal, use q1 O(N)
- // otherwise, use q2 O(n*logn)
- q.setflag(areAllEqual);
-
- // We have to consider all children, and all priorities are equal,
- // then, we should just use a pre-exist vector to avoid a O(n) copy to q1.
- if ((!isParatialConsidered()) && areAllEqual) {
- q.setQ1Container(&simpleQ1Container);
- return; // no need to perform enqueue
- }
-
- q.resetQ1Container();
-
- // Clear and enqueue.
- q.clear();
- for (int i = 0; i < children.size(); i++)
- if (considerable(i)) q.push(i);
- }
-
+ void refresh(const Context& ctx);
+ // Enqueue considerable children.
+ void enqueue();
// update is an internal method to propagates tick() to children in the q1/q2.
// it will be called by Update.
- virtual Status update(const Context& ctx) = 0;
-
- void internalOnBuild() override {
- CompositeNode::internalOnBuild();
- // pre-allocate capacity for p.
- p.resize(children.size());
- // initialize simpleQ1Container;
- for (int i = 0; i < children.size(); i++) simpleQ1Container.push_back(i);
- // Compare priorities between children, where a and b are indexes.
- // priority from large to smaller, so use `less`: pa < pb
- // order: from small to larger, so use `greater`: a > b
- auto cmp = [&](const int a, const int b) { return p[a] < p[b] || a > b; };
- q = _MixedQueueHelper(cmp, children.size());
- }
+ virtual Status update(const Context& ctx) { return bt::Status::UNDEFINED; }
public:
_InternalPriorityCompositeNode() {}
-
- Status Update(const Context& ctx) override {
- refresh(ctx);
- enqueue();
- // propagates ticks
- return update(ctx);
- }
+ Status Update(const Context& ctx) override;
};
//////////////////////////////////////////////////////////////
@@ -780,23 +484,7 @@ class _InternalPriorityCompositeNode : virtual public CompositeNode {
class _InternalSequenceNodeBase : virtual public _InternalPriorityCompositeNode {
protected:
- Status update(const Context& ctx) override {
- // propagates ticks, one by one sequentially.
- while (!q.empty()) {
- auto i = q.pop();
- auto status = children[i]->Tick(ctx);
- if (status == Status::RUNNING) return Status::RUNNING;
- // F if any child F.
- if (status == Status::FAILURE) {
- onChildFailure(i);
- return Status::FAILURE;
- }
- // S
- onChildSuccess(i);
- }
- // S if all children S.
- return Status::SUCCESS;
- }
+ Status update(const Context& ctx) override;
};
// SequenceNode runs children one by one, and succeeds only if all children succeed.
@@ -823,23 +511,7 @@ class StatefulSequenceNode final : public _InternalStatefulCompositeNode, public
class _InternalSelectorNodeBase : virtual public _InternalPriorityCompositeNode {
protected:
- Status update(const Context& ctx) override {
- // select a success children.
- while (!q.empty()) {
- auto i = q.pop();
- auto status = children[i]->Tick(ctx);
- if (status == Status::RUNNING) return Status::RUNNING;
- // S if any child S.
- if (status == Status::SUCCESS) {
- onChildSuccess(i);
- return Status::SUCCESS;
- }
- // F
- onChildFailure(i);
- }
- // F if all children F.
- return Status::FAILURE;
- }
+ Status update(const Context& ctx) override;
};
// SelectorNode succeeds if any child succeeds.
@@ -864,59 +536,10 @@ class StatefulSelectorNode : public _InternalStatefulCompositeNode, public _Inte
/// Node > InternalNode > CompositeNode > RandomSelectorNode
///////////////////////////////////////////////////////////////
-static std::mt19937 rng(std::random_device{}()); // seed random
-
// Weighted random selector.
class _InternalRandomSelectorNodeBase : virtual public _InternalPriorityCompositeNode {
- protected:
- Status update(const Context& ctx) override {
- // Sum of weights/priorities.
- unsigned int total = 0;
- for (int i = 0; i < children.size(); i++)
- if (considerable(i)) total += p[i];
-
- // random select one, in range [1, total]
- std::uniform_int_distribution distribution(1, total);
-
- auto select = [&]() -> int {
- unsigned int v = distribution(rng); // gen random unsigned int between [0, sum]
- unsigned int s = 0; // sum of iterated children.
- for (int i = 0; i < children.size(); i++) {
- if (!considerable(i)) continue;
- s += p[i];
- if (v <= s) return i;
- }
- return 0; // won't reach here.
- };
-
- // While still have children considerable.
- // total reaches 0 only if no children left,
- // notes that Priority() always returns a positive value.
- while (total) {
- int i = select();
- auto status = children[i]->Tick(ctx);
- if (status == Status::RUNNING) return Status::RUNNING;
- // S if any child S.
- if (status == Status::SUCCESS) {
- onChildSuccess(i);
- return Status::SUCCESS;
- }
- // Failure, it shouldn't be considered any more in this tick.
- onChildFailure(i);
- // remove its weight from total, won't be consider again.
- total -= p[i];
- // updates the upper bound of distribution.
- distribution.param(std::uniform_int_distribution::param_type(1, total));
- }
- // F if all children F.
- return Status::FAILURE;
- }
-
public:
- Status Update(const Context& ctx) override {
- refresh(ctx);
- return update(ctx);
- }
+ Status Update(const Context& ctx) override;
};
// RandomSelectorNode selects children via weighted random selection.
@@ -943,29 +566,8 @@ class StatefulRandomSelectorNode final : virtual public _InternalStatefulComposi
///////////////////////////////////////////////////////////////
class _InternalParallelNodeBase : virtual public _InternalPriorityCompositeNode {
- Status update(const Context& ctx) override {
- // Propagates tick to all considerable children.
- int cntFailure = 0, cntSuccess = 0, total = 0;
- while (!q.empty()) {
- auto i = q.pop();
- auto status = children[i]->Tick(ctx);
- total++;
- if (status == Status::FAILURE) {
- cntFailure++;
- onChildFailure(i);
- }
- if (status == Status::SUCCESS) {
- cntSuccess++;
- onChildSuccess(i);
- }
- }
-
- // S if all children S.
- if (cntSuccess == total) return Status::SUCCESS;
- // F if any child F.
- if (cntFailure > 0) return Status::FAILURE;
- return Status::RUNNING;
- }
+ protected:
+ Status update(const Context& ctx) override;
};
// ParallelNode succeeds if all children succeed but runs all children
@@ -997,7 +599,7 @@ class DecoratorNode : public SingleNode {
DecoratorNode(std::string_view name = "Decorator", Ptr child = nullptr)
: SingleNode(name, std::move(child)) {}
- // To create a custom DecoratorNode.
+ // To create a custom DecoratorNode: https://github.com/hit9/bt.cc#custom-decorator
// You should derive from DecoratorNode and override the function Update.
// Status Update(const Context&) override;
};
@@ -1007,18 +609,7 @@ class InvertNode : public DecoratorNode {
public:
InvertNode(std::string_view name = "Invert", Ptr child = nullptr)
: DecoratorNode(name, std::move(child)) {}
-
- Status Update(const Context& ctx) override {
- auto status = child->Tick(ctx);
- switch (status) {
- case Status::RUNNING:
- return Status::RUNNING;
- case Status::FAILURE:
- return Status::SUCCESS;
- default:
- return Status::FAILURE;
- }
- }
+ Status Update(const Context& ctx) override;
};
// ConditionalRunNode executes its child if given condition returns true.
@@ -1027,28 +618,13 @@ class ConditionalRunNode : public DecoratorNode {
// Condition node to check.
Ptr condition;
- protected:
public:
ConditionalRunNode(Ptr condition = nullptr, std::string_view name = "ConditionalRun",
Ptr child = nullptr)
: DecoratorNode(std::string(name) + '<' + std::string(condition->Name()) + '>', std::move(child)),
condition(std::move(condition)) {}
-
- void Traverse(TraversalCallback& pre, TraversalCallback& post, Ptr& ptr) override {
- pre(*this, ptr);
- if (condition != nullptr) condition->Traverse(pre, post, condition);
- if (child != nullptr) child->Traverse(pre, post, child);
- post(*this, ptr);
- }
- Status Update(const Context& ctx) override {
- if (condition->Tick(ctx) == Status::SUCCESS) return child->Tick(ctx);
- return Status::FAILURE;
- }
-};
-
-struct RepeatNodeBlob : NodeBlob {
- // Count how many times already executed for this round.
- int cnt = 0;
+ void Traverse(TraversalCallback& pre, TraversalCallback& post, Ptr& ptr) override;
+ Status Update(const Context& ctx) override;
};
// RepeatNode repeats its child for exactly n times.
@@ -1059,7 +635,9 @@ class RepeatNode : public DecoratorNode {
int n;
public:
- using Blob = RepeatNodeBlob;
+ struct Blob : NodeBlob {
+ int cnt = 0; // how many times of execution in this round.
+ };
RepeatNode(int n, std::string_view name = "Repeat", Ptr child = nullptr)
: DecoratorNode(name, std::move(child)), n(n) {}
@@ -1068,91 +646,47 @@ class RepeatNode : public DecoratorNode {
void OnEnter(const Context& ctx) override { getNodeBlob()->cnt = 0; }
// Reset counter on termination.
void OnTerminate(const Context& ctx, Status status) override { getNodeBlob()->cnt = 0; }
-
- Status Update(const Context& ctx) override {
- if (n == 0) return Status::SUCCESS;
- auto status = child->Tick(ctx);
- if (status == Status::RUNNING) return Status::RUNNING;
- if (status == Status::FAILURE) return Status::FAILURE;
- // Count success until n times, -1 will never stop.
- if (++(getNodeBlob()->cnt) == n) return Status::SUCCESS;
- // Otherwise, it's still running.
- return Status::RUNNING;
- }
+ Status Update(const Context& ctx) override;
};
-template
-using TimePoint = std::chrono::time_point;
-
-template
-struct TimeoutNodeBlob : NodeBlob {
- // Time point of this node starts.
- TimePoint startAt = TimePoint::min();
-};
+using Timepoint = std::chrono::time_point;
// Timeout runs its child for at most given duration, fails on timeout.
-template
class TimeoutNode : public DecoratorNode {
protected:
std::chrono::milliseconds duration;
public:
- using Blob = TimeoutNodeBlob;
+ struct Blob : NodeBlob {
+ Timepoint startAt; // timepoint when this node starts.
+ };
TimeoutNode(std::chrono::milliseconds d, std::string_view name = "Timeout", Ptr child = nullptr)
: DecoratorNode(name, std::move(child)), duration(d) {}
-
NodeBlob* GetNodeBlob() const override { return getNodeBlob(); }
- void OnEnter(const Context& ctx) override { getNodeBlob()->startAt = Clock::now(); };
-
- Status Update(const Context& ctx) override {
- // Check if timeout at first.
- auto now = Clock::now();
- if (now > getNodeBlob()->startAt + duration) return Status::FAILURE;
- return child->Tick(ctx);
- }
-};
-
-template
-struct DelayNodeBlob : NodeBlob {
- // The time point this node first run.
- TimePoint firstRunAt;
+ void OnEnter(const Context& ctx) override;
+ Status Update(const Context& ctx) override;
};
// DelayNode runs its child node after given duration.
-template
class DelayNode : public DecoratorNode {
protected:
// Duration to wait.
std::chrono::milliseconds duration;
public:
- using Blob = DelayNodeBlob;
+ struct Blob : NodeBlob {
+ Timepoint firstRunAt; // timepoint this node first run.
+ };
DelayNode(std::chrono::milliseconds duration, std::string_view name = "Delay", Ptr c = nullptr)
: DecoratorNode(name, std::move(c)), duration(duration) {}
NodeBlob* GetNodeBlob() const override { return getNodeBlob(); }
- void OnEnter(const Context& ctx) override { getNodeBlob()->firstRunAt = Clock::now(); };
- void OnTerminate(const Context& ctx, Status status) override {
- getNodeBlob()->firstRunAt = TimePoint::min();
- };
-
- Status Update(const Context& ctx) override {
- auto now = Clock::now();
- if (now < getNodeBlob()->firstRunAt + duration) return Status::RUNNING;
- return child->Tick(ctx);
- }
-};
-
-template
-struct RetryNodeBlob : NodeBlob {
- // Times already retried.
- int cnt = 0;
- // Timepoint last retried at.
- TimePoint lastRetryAt;
+ void OnEnter(const Context& ctx) override;
+ void OnTerminate(const Context& ctx, Status status) override;
+ Status Update(const Context& ctx) override;
};
// RetryNode retries its child node on failure.
-template
class RetryNode : public DecoratorNode {
protected:
// Max retry times, -1 for unlimited.
@@ -1161,45 +695,18 @@ class RetryNode : public DecoratorNode {
std::chrono::milliseconds interval;
public:
- using Blob = RetryNodeBlob;
+ struct Blob : NodeBlob {
+ int cnt = 0; // Times already retried.
+ Timepoint lastRetryAt; // Timepoint last retried at.
+ };
RetryNode(int maxRetries, std::chrono::milliseconds interval, std::string_view name = "Retry",
Ptr child = nullptr)
: DecoratorNode(name, std::move(child)), maxRetries(maxRetries), interval(interval) {}
NodeBlob* GetNodeBlob() const override { return getNodeBlob(); }
- void OnEnter(const Context& ctx) override {
- auto b = getNodeBlob();
- b->cnt = 0;
- b->lastRetryAt = TimePoint::min();
- }
- void OnTerminate(const Context& ctx, Status status) override {
- auto b = getNodeBlob();
- b->cnt = 0;
- b->lastRetryAt = status == Status::FAILURE ? Clock::now() : TimePoint::min();
- }
-
- Status Update(const Context& ctx) override {
- auto b = getNodeBlob();
-
- if (maxRetries != -1 && b->cnt > maxRetries) return Status::FAILURE;
-
- // If has failures before, and retry timepoint isn't arriving.
- auto now = Clock::now();
- if (b->cnt > 0 && now < b->lastRetryAt + interval) return Status::RUNNING;
-
- // Time to run/retry.
- auto status = child->Tick(ctx);
- switch (status) {
- case Status::RUNNING:
- [[fallthrough]];
- case Status::SUCCESS:
- return status;
- default:
- // Failure
- if (++b->cnt > maxRetries && maxRetries != -1) return Status::FAILURE; // exeeds max retries.
- return Status::RUNNING; // continues retry
- }
- }
+ void OnEnter(const Context& ctx) override;
+ void OnTerminate(const Context& ctx, Status status) override;
+ Status Update(const Context& ctx) override;
};
//////////////////////////////////////////////////////////////
@@ -1226,9 +733,17 @@ class RootNode : public SingleNode, public IRootNode {
RootNode(std::string_view name = "Root") : SingleNode(name) {}
Status Update(const Context& ctx) override { return child->Tick(ctx); }
- //////////////////////////
+ // Visualize the tree to console.
+ void Visualize(ull seq);
+ // Handy function to run tick loop forever.
+ // Parameter interval specifies the time interval between ticks.
+ // Parameter visualize enables debugging visualization on the console.
+ // Parameter is a hook to be called after each tick.
+ void TickForever(Context& ctx, std::chrono::nanoseconds interval, bool visualize = false,
+ std::function post = nullptr);
+
/// Blob Apis
- //////////////////////////
+ /// ~~~~~~~~~
// Binds a tree blob.
void BindTreeBlob(ITreeBlob& b) { blob = &b; }
@@ -1237,9 +752,8 @@ class RootNode : public SingleNode, public IRootNode {
// Unbind current tree blob.
void UnbindTreeBlob() { blob = nullptr; }
- //////////////////////////
/// Size Info
- //////////////////////////
+ /// ~~~~~~~~~
// Returns the total number of nodes in this tree.
// Available once the tree is built.
@@ -1253,159 +767,62 @@ class RootNode : public SingleNode, public IRootNode {
// Returns the max size of the node blob struct for this tree.
// Available once the tree is built.
std::size_t MaxSizeNodeBlob() const { return maxSizeNodeBlob; }
-
- //////////////////////////
- /// Visualization
- //////////////////////////
-
- // Visualize the tree to console.
- void Visualize(ull seq) {
- // CSI[2J clears screen.
- // CSI[H moves the cursor to top-left corner
- std::cout << "\x1B[2J\x1B[H" << std::flush;
- // Make a string.
- std::string s;
- makeVisualizeString(s, 0, seq);
- std::cout << s << std::flush;
- }
-
- //////////////////////////
- /// Ticking
- //////////////////////////
-
- // Handy function to run tick loop forever.
- // Parameter interval specifies the time interval between ticks.
- // Parameter visualize enables debugging visualization on the console.
- // Parameter is a hook to be called after each tick.
- template
- void TickForever(Context& ctx, std::chrono::nanoseconds interval, bool visualize = false,
- std::function post = nullptr) {
- auto lastTickAt = Clock::now();
-
- while (true) {
- auto nextTickAt = lastTickAt + interval;
-
- // Time delta between last tick and current tick.
- ctx.delta = Clock::now() - lastTickAt;
- ++ctx.seq;
- Tick(ctx);
- if (post != nullptr) post(ctx);
- if (visualize) Visualize(ctx.seq);
-
- // Catch up with next tick.
- lastTickAt = Clock::now();
- if (lastTickAt < nextTickAt) {
- std::this_thread::sleep_for(nextTickAt - lastTickAt);
- }
- }
- }
};
//////////////////////////////////////////////////////////////
/// Tree Builder
///////////////////////////////////////////////////////////////
-// A internal proxy base class to setup Node's internal bindings.
class _InternalBuilderBase {
private:
// Node id incrementer for a tree.
// unique inside this builder instance.
NodeId nextNodeId = 0;
- void maintainNodeBindInfo(Node& node, RootNode* root) {
- node.root = root;
- root->n++;
- node.id = ++nextNodeId;
- }
-
- void maintainSizeInfoOnRootBind(RootNode* root, std::size_t rootNodeSize, std::size_t blobSize) {
- root->size = rootNodeSize;
- root->treeSize += rootNodeSize;
- root->maxSizeNode = rootNodeSize;
- root->maxSizeNodeBlob = blobSize;
- }
-
+ void maintainNodeBindInfo(Node& node, RootNode* root);
+ void maintainSizeInfoOnRootBind(RootNode* root, std::size_t rootNodeSize, std::size_t blobSize);
+ void _maintainSizeInfoOnNodeAttach(Node& node, RootNode* root, std::size_t nodeSize,
+ std::size_t nodeBlobSize);
template
void maintainSizeInfoOnNodeAttach(T& node, RootNode* root) {
- node.size = sizeof(T);
- root->treeSize += sizeof(T);
- root->maxSizeNode = std::max(root->maxSizeNode, sizeof(T));
- root->maxSizeNodeBlob = std::max(root->maxSizeNodeBlob, sizeof(typename T::Blob));
- }
-
- void maintainSizeInfoOnSubtreeAttach(RootNode& subtree, RootNode* root) {
- root->treeSize += subtree.treeSize;
- root->maxSizeNode = std::max(root->maxSizeNode, subtree.maxSizeNode);
- root->maxSizeNodeBlob = std::max(root->maxSizeNodeBlob, subtree.maxSizeNodeBlob);
+ _maintainSizeInfoOnNodeAttach(node, root, sizeof(T), sizeof(typename T::Blob));
}
+ void maintainSizeInfoOnSubtreeAttach(RootNode& subtree, RootNode* root);
protected:
+ std::stack stack;
+ // indent level to insert new node, starts from 1.
+ int level = 1;
+ RootNode* root = nullptr;
+
+ _InternalBuilderBase() : level(1) {}
+
template
void onNodeAttach(T& node, RootNode* root) {
maintainNodeBindInfo(node, root);
maintainSizeInfoOnNodeAttach(node, root);
}
- void onRootAttach(RootNode* root, std::size_t size, std::size_t blobSize) {
- maintainNodeBindInfo(*root, root);
- maintainSizeInfoOnRootBind(root, size, blobSize);
- }
- void onSubtreeAttach(RootNode& subtree, RootNode* root) {
- // Resets root in sub tree recursively.
- TraversalCallback pre = [&](Node& node, Ptr& ptr) { maintainNodeBindInfo(node, root); };
- subtree.Traverse(pre, NullTraversalCallback, NullNodePtr);
- maintainSizeInfoOnSubtreeAttach(subtree, root);
- }
- void onNodeBuild(Node* node) {
- node->internalOnBuild();
- node->OnBuild();
- }
+ void onRootAttach(RootNode* root, std::size_t size, std::size_t blobSize);
+ void onSubtreeAttach(RootNode& subtree, RootNode* root);
+ void onNodeBuild(Node* node);
+
+ // Validate node.
+ void validate(Node* node);
+ // Validate indent level.
+ void validateIndent();
+ // Pops an internal node from the stack.
+ void pop();
+ // Adjust stack to current indent level.
+ void adjust();
+
+ void attachLeafNode(Ptr p);
+ void attachInternalNode(Ptr p);
};
// Builder helps to build a tree.
// The template parameter D is the derived class, a behavior tree class.
template
class Builder : public _InternalBuilderBase {
- private:
- std::stack stack;
- // indent level to insert new node, starts from 1.
- int level;
- RootNode* root = nullptr;
- // Validate node.
- void validate(Node* node) {
- auto e = node->Validate();
- if (!e.empty()) {
- std::string s = "bt build: ";
- s += node->Name();
- s += ' ';
- s += e;
- throw std::runtime_error(s);
- }
- }
-
- // Validate indent level.
- void validateIndent() {
- if (level > stack.size()) {
- auto node = stack.top();
- std::string s = "bt build: too much indent ";
- s += "below ";
- s += node->Name();
- throw std::runtime_error(s);
- }
- }
-
- // pops an internal node from the stack.
- void pop() {
- validate(stack.top()); // validate before pop
- onNodeBuild(stack.top());
- stack.pop();
- }
-
- // Adjust stack to current indent level.
- void adjust() {
- validateIndent();
- while (level < stack.size()) pop();
- }
-
protected:
// Bind a tree root onto this builder.
void bindRoot(RootNode& r) {
@@ -1413,30 +830,16 @@ class Builder : public _InternalBuilderBase {
root = &r;
onRootAttach(root, sizeof(D), sizeof(typename D::Blob));
}
-
// Creates a leaf node.
auto& attachLeafNode(Ptr p) {
- adjust();
- // Append to stack's top as a child.
- onNodeBuild(p.get());
- stack.top()->Append(std::move(p));
- // resets level.
- level = 1;
+ _InternalBuilderBase::attachLeafNode(std::move(p));
return *static_cast(this);
}
-
// Creates an internal node with optional children.
auto& attachInternalNode(Ptr p) {
- adjust();
- // Append to stack's top as a child, and replace the top.
- auto parent = stack.top();
- stack.push(p.get());
- parent->Append(std::move(p));
- // resets level.
- level = 1;
+ _InternalBuilderBase::attachInternalNode(std::move(p));
return *static_cast(this);
}
-
// make a new node onto this tree, returns the unique_ptr.
// Any node creation should use this function.
template
@@ -1447,23 +850,21 @@ class Builder : public _InternalBuilderBase {
};
public:
- Builder() : level(1), root(nullptr) {}
+ Builder() : _InternalBuilderBase() {}
~Builder() {}
+ // Should be called on the end of the build process.
+ void End() {
+ while (stack.size()) pop(); // clears the stack
+ }
// Increases indent level to append node.
auto& _() {
level++;
return static_cast(*this);
}
- // Should be called on the end of the build process.
- void End() {
- while (stack.size()) pop(); // clears the stack
- }
-
- ///////////////////////////////////
- // General creators.
- ///////////////////////////////////
+ // General creators
+ // ~~~~~~~~~~~~~~~~
// C is a function to attach an arbitrary new Node.
// It can be used to attach custom node implementation.
@@ -1471,8 +872,7 @@ class Builder : public _InternalBuilderBase {
// root
// .C()
// ._().Action()
- // .End()
- // ;
+ // .End();
template
auto& C(Args... args) {
if constexpr (std::is_base_of_v) // LeafNode
@@ -1480,7 +880,6 @@ class Builder : public _InternalBuilderBase {
else // InternalNode.
return attachInternalNode(make(false, std::forward(args)...));
}
-
// Attach a node through move, rarely used.
template
auto& M(T&& inst) {
@@ -1490,47 +889,38 @@ class Builder : public _InternalBuilderBase {
return attachInternalNode(make(true, std::move(inst)));
}
- ///////////////////////////////////
// CompositeNode creators
- ///////////////////////////////////
+ // ~~~~~~~~~~~~~~~~~~~~~~
// A SequenceNode executes its children one by one sequentially,
// it succeeds only if all children succeed.
auto& Sequence() { return C("Sequence"); }
-
// A StatefulSequenceNode behaves like a sequence node, executes its children sequentially, succeeds if
// all children succeed, fails if any child fails. What's the difference is, a StatefulSequenceNode skips
// the succeeded children instead of always starting from the first child.
auto& StatefulSequence() { return C("Sequence*"); }
-
// A SelectorNode succeeds if any child succeeds, fails only if all children fail.
auto& Selector() { return C("Selector"); }
-
// A StatefulSelectorNode behaves like a selector node, executes its children sequentially, succeeds if
// any child succeeds, fails if all child fail. What's the difference is, a StatefulSelectorNode skips the
// failure children instead of always starting from the first child.
auto& StatefulSelector() { return C("Selector*"); }
-
// A ParallelNode executes its children parallelly.
// It succeeds if all children succeed, and fails if any child fails.
auto& Parallel() { return C("Parallel"); }
-
// A StatefulParallelNode behaves like a parallel node, executes its children parallelly, succeeds if all
// succeed, fails if all child fail. What's the difference is, a StatefulParallelNode will skip the
// "already success" children instead of executing every child all the time.
auto& StatefulParallel() { return C("Parallel*"); }
-
// A RandomSelectorNode determines a child via weighted random selection.
// It continues to randomly select a child, propagating tick, until some child succeeds.
auto& RandomSelector() { return C("RandomSelector"); }
-
// A StatefulRandomSelector behaves like a random selector node, the difference is, a
// StatefulRandomSelector will skip already failed children during a round.
auto& StatefulRandomSelector() { return C("RandomSelector*"); }
- ///////////////////////////////////
// LeafNode creators
- ///////////////////////////////////
+ // ~~~~~~~~~~~~~~~~~
// Creates an Action node by providing implemented Action class.
// Code example::
@@ -1541,7 +931,6 @@ class Builder : public _InternalBuilderBase {
auto& Action(Args&&... args) {
return C(std::forward(args)...);
}
-
// Creates a ConditionNode from a lambda function.
// Code example::
// root
@@ -1550,7 +939,6 @@ class Builder : public _InternalBuilderBase {
// ._().Action()
// .End();
auto& Condition(ConditionNode::Checker checker) { return C(checker); }
-
// Creates a ConditionNode by providing implemented Condition class.
// Code example::
// root
@@ -1563,9 +951,8 @@ class Builder : public _InternalBuilderBase {
return C(std::forward(args)...);
}
- ///////////////////////////////////
- // DecoratorNode creators.
- ///////////////////////////////////
+ // DecoratorNode creators
+ // ~~~~~~~~~~~~~~~~~~~~~~
// Inverts the status of decorated node.
// Parameter `child` the node to be decorated.
@@ -1575,7 +962,6 @@ class Builder : public _InternalBuilderBase {
// ._().Condition()
// .End();
auto& Invert() { return C("Invert"); }
-
// Alias to Invert, just named 'Not'.
// Code exapmle::
// root
@@ -1583,7 +969,6 @@ class Builder : public _InternalBuilderBase {
// ._().Condition()
// .End();
auto& Not() { return C("Not"); }
-
// Creates a invert condition of given Condition class.
// Code exapmle::
// root
@@ -1595,7 +980,6 @@ class Builder : public _InternalBuilderBase {
auto& Not(ConditionArgs... args) {
return C("Not", make(false, std::forward(args)...));
}
-
// Repeat creates a RepeatNode.
// It will repeat the decorated node for exactly n times.
// Providing n=-1 means to repeat forever.
@@ -1606,14 +990,12 @@ class Builder : public _InternalBuilderBase {
// ._().Action()
// .End();
auto& Repeat(int n) { return C(n, "Repeat"); }
-
// Alias to Repeat.
// Code exapmle::
// root
// .Loop(3)
// ._().Action();
auto& Loop(int n) { return C(n, "Loop"); }
-
// Timeout creates a TimeoutNode.
// It executes the decorated node for at most given duration.
// Code exapmle::
@@ -1621,11 +1003,7 @@ class Builder : public _InternalBuilderBase {
// .Timeout(3000ms)
// ._().Action()
// .End();
- template
- auto& Timeout(std::chrono::milliseconds duration) {
- return C>(duration, "Timeout");
- }
-
+ auto& Timeout(std::chrono::milliseconds duration) { return C(duration, "Timeout"); }
// Delay creates a DelayNode.
// Wait for given duration before execution of decorated node.
// Code exapmle::
@@ -1633,11 +1011,7 @@ class Builder : public _InternalBuilderBase {
// .Delay(3000ms)
// ._().Action()
// .End();
- template
- auto& Delay(std::chrono::milliseconds duration) {
- return C>(duration, "Delay");
- }
-
+ auto& Delay(std::chrono::milliseconds duration) { return C(duration, "Delay"); }
// Retry creates a RetryNode.
// It executes the decorated node for at most n times.
// A retry will only be initiated if the decorated node fails.
@@ -1647,17 +1021,11 @@ class Builder : public _InternalBuilderBase {
// .Retry(1, 3000ms)
// ._().Action()
// .End();
- template
- auto& Retry(int n, std::chrono::milliseconds interval) {
- return C>(n, interval, "Retry");
- }
-
+ auto& Retry(int n, std::chrono::milliseconds interval) { return C(n, interval, "Retry"); }
// Alias for Retry(-1, interval)
- template
auto& RetryForever(std::chrono::milliseconds interval) {
- return C>(-1, interval, "RetryForever");
+ return C(-1, interval, "RetryForever");
}
-
// If creates a ConditionalRunNode.
// It executes the decorated node only if the condition goes true.
// Code example::
@@ -1670,14 +1038,12 @@ class Builder : public _InternalBuilderBase {
auto condition = make(false, std::forward(args)...);
return C(std::move(condition), "If");
}
-
// If creates a ConditionalRunNode from lambda function.
// Code example::
// root
// .If([=](const Context& ctx) { return false; })
// .End();
auto& If(ConditionNode::Checker checker) { return If(checker); }
-
// Switch is just an alias to Selector.
// Code example::
// root
@@ -1685,49 +1051,38 @@ class Builder : public _InternalBuilderBase {
// ._().Case()
// ._()._().Action()
// ._().Case()
- // ._()._().Sequence()
- // ._()._()._().Action()
- // ._()._()._().Action()
+ // ._()._().Action()
// ._().Case([=](const Context& ctx) { return false; })
// ._()._().Action()
// .End();
auto& Switch() { return C("Switch"); }
-
// Stateful version `Switch` based on StatefulSelectorNode.
auto& StatefulSwitch() { return C("Switch*"); }
-
// Alias to If, for working alongs with Switch.
template
auto& Case(ConditionArgs&&... args) {
auto condition = make(false, std::forward(args)...);
return C(std::move(condition), "Case");
}
-
// Case creates a ConditionalRunNode from lambda function.
auto& Case(ConditionNode::Checker checker) { return Case(checker); }
- ///////////////////////////////////
- // Subtree creators.
- ///////////////////////////////////
+ // Subtree creators
+ // ~~~~~~~~~~~~~~~~
// Attach a sub behavior tree into this tree.
// Code example::
- //
// bt::Tree subtree;
// subtree
- // .Parallel()
- // ._().Action()
- // ._().Action(;
+ // .Action()
// .End();
- //
// root
// .Sequence()
// ._().Subtree(std::move(subtree))
// .End();
auto& Subtree(RootNode&& subtree) {
onSubtreeAttach(subtree, root);
- // move to the tree
- return M(std::move(subtree));
+ return M(std::move(subtree)); // move
}
};
@@ -1735,8 +1090,7 @@ class Builder : public _InternalBuilderBase {
/// Tree
///////////////////////////////////////////////////////////////
-// Behavior Tree.
-// Please keep this class simple enough.
+// Behavior Tree, please keep this class simple enough.
class Tree : public RootNode, public Builder {
public:
Tree(std::string_view name = "Root") : RootNode(name), Builder() { bindRoot(*this); }
diff --git a/changelog b/changelog
index 18d3cce..7b41efd 100644
--- a/changelog
+++ b/changelog
@@ -1,3 +1,11 @@
+0.4.0
+-----
+
+* Separates to bt.h and bt.cc.
+* Remove template Clock, use std::chrono::steady_clock.
+* Use printf instead of iostream
+* Rename project to `bt.cc`
+
0.3.5
-----
diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt
index d9dc1ba..6b66fa1 100644
--- a/example/CMakeLists.txt
+++ b/example/CMakeLists.txt
@@ -7,4 +7,4 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
include_directories("..")
# Targets
-add_executable(bt_example main.cc)
+add_executable(bt_example main.cc ../bt.cc)
diff --git a/example/main.cc b/example/main.cc
index dd794d7..4d87d55 100644
--- a/example/main.cc
+++ b/example/main.cc
@@ -1,4 +1,5 @@
#include
+#include
#include "bt.h"
using namespace std::chrono_literals;
diff --git a/example/onsignal/CMakeLists.txt b/example/onsignal/CMakeLists.txt
index 5e5a95a..c8892dc 100644
--- a/example/onsignal/CMakeLists.txt
+++ b/example/onsignal/CMakeLists.txt
@@ -7,4 +7,4 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
include_directories("../..")
# Targets
-add_executable(bt_example_onsignal main.cc)
+add_executable(bt_example_onsignal main.cc ../../bt.cc)
diff --git a/example/onsignal/main.cc b/example/onsignal/main.cc
index 9f399be..bd964a5 100644
--- a/example/onsignal/main.cc
+++ b/example/onsignal/main.cc
@@ -1,5 +1,4 @@
#include
-#include
#include
#include
#include
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 8097798..0d44466 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -12,8 +12,8 @@ include_directories(".." ".")
find_package(Catch2 CONFIG REQUIRED)
# Targets
-file(GLOB TEST_SOURCES *_test.cc)
-file(GLOB BENCHMARK_SOURCES *_benchmark.cc)
+file(GLOB TEST_SOURCES *_test.cc ../bt.cc)
+file(GLOB BENCHMARK_SOURCES *_benchmark.cc ../bt.cc)
add_executable(bt_tests ${TEST_SOURCES})
add_executable(bt_benchmark ${BENCHMARK_SOURCES})
diff --git a/tests/condition_test.cc b/tests/condition_test.cc
index cee8def..1e73cd6 100644
--- a/tests/condition_test.cc
+++ b/tests/condition_test.cc
@@ -24,7 +24,6 @@ TEST_CASE("Condition/1", "[simplest condition - constructed from template]") {
// Tick#1
root.BindTreeBlob(e.blob);
- std::cout << "tick1" << std::endl;
++ctx.seq;
root.Tick(ctx);
// A should not started.
@@ -32,7 +31,6 @@ TEST_CASE("Condition/1", "[simplest condition - constructed from template]") {
REQUIRE(bb->statusA == bt::Status::UNDEFINED);
root.UnbindTreeBlob();
- std::cout << "tick2" << std::endl;
// Tick#2: Make C true.
root.BindTreeBlob(e.blob);
bb->shouldC = true;
@@ -45,7 +43,6 @@ TEST_CASE("Condition/1", "[simplest condition - constructed from template]") {
REQUIRE(root.LastStatus() == bt::Status::RUNNING);
root.UnbindTreeBlob();
- std::cout << "tick3" << std::endl;
// Tick#3: Make A Success.
root.BindTreeBlob(e.blob);
bb->shouldA = bt::Status::SUCCESS;
diff --git a/tests/delay_test.cc b/tests/delay_test.cc
index ded9f85..136fb2a 100644
--- a/tests/delay_test.cc
+++ b/tests/delay_test.cc
@@ -7,8 +7,7 @@
using namespace std::chrono_literals;
-TEMPLATE_TEST_CASE("Delay/1", "[simple delay]", Entity,
- (EntityFixedBlob<16, sizeof(bt::DelayNode<>::Blob)>)) {
+TEMPLATE_TEST_CASE("Delay/1", "[simple delay]", Entity, (EntityFixedBlob<16, sizeof(bt::DelayNode::Blob)>)) {
bt::Tree root;
auto bb = std::make_shared();
bt::Context ctx(bb);
@@ -26,7 +25,9 @@ TEMPLATE_TEST_CASE("Delay/1", "[simple delay]", Entity,
REQUIRE(bb->counterA == 0);
// Tick#1: A is not started.
root.BindTreeBlob(e.blob);
- ++ctx.seq;root.Tick(ctx);;
+ ++ctx.seq;
+ root.Tick(ctx);
+ ;
REQUIRE(bb->counterA == 0);
REQUIRE(root.LastStatus() == bt::Status::RUNNING);
root.UnbindTreeBlob();
@@ -36,7 +37,9 @@ TEMPLATE_TEST_CASE("Delay/1", "[simple delay]", Entity,
// Tick#2: A is started.
root.BindTreeBlob(e.blob);
- ++ctx.seq;root.Tick(ctx);;
+ ++ctx.seq;
+ root.Tick(ctx);
+ ;
REQUIRE(bb->counterA == 1);
root.UnbindTreeBlob();
}
diff --git a/tests/retry_test.cc b/tests/retry_test.cc
index 4ce1cf8..a8f8217 100644
--- a/tests/retry_test.cc
+++ b/tests/retry_test.cc
@@ -8,7 +8,7 @@
using namespace std::chrono_literals;
TEMPLATE_TEST_CASE("Retry/1", "[simple retry success]", Entity,
- (EntityFixedBlob<16, sizeof(bt::RetryNode<>::Blob)>)) {
+ (EntityFixedBlob<16, sizeof(bt::RetryNode::Blob)>)) {
bt::Tree root;
auto bb = std::make_shared();
bt::Context ctx(bb);
@@ -24,13 +24,15 @@ TEMPLATE_TEST_CASE("Retry/1", "[simple retry success]", Entity,
root.BindTreeBlob(e.blob);
// Tick#1
- ++ctx.seq;root.Tick(ctx);
+ ++ctx.seq;
+ root.Tick(ctx);
REQUIRE(bb->counterA == 1);
REQUIRE(root.LastStatus() == bt::Status::RUNNING);
// Tick#2: Makes A success.
bb->shouldA = bt::Status::SUCCESS;
- ++ctx.seq;root.Tick(ctx);
+ ++ctx.seq;
+ root.Tick(ctx);
REQUIRE(bb->counterA == 2);
REQUIRE(root.LastStatus() == bt::Status::SUCCESS);
@@ -38,7 +40,7 @@ TEMPLATE_TEST_CASE("Retry/1", "[simple retry success]", Entity,
}
TEMPLATE_TEST_CASE("Retry/2", "[simple retry final failure]", Entity,
- (EntityFixedBlob<16, sizeof(bt::RetryNode<>::Blob)>)) {
+ (EntityFixedBlob<16, sizeof(bt::RetryNode::Blob)>)) {
bt::Tree root;
auto bb = std::make_shared();
bt::Context ctx(bb);
@@ -55,13 +57,15 @@ TEMPLATE_TEST_CASE("Retry/2", "[simple retry final failure]", Entity,
root.BindTreeBlob(e.blob);
// Tick#1
- ++ctx.seq;root.Tick(ctx);
+ ++ctx.seq;
+ root.Tick(ctx);
REQUIRE(bb->counterA == 1);
REQUIRE(root.LastStatus() == bt::Status::RUNNING);
// Tick#2: Makes A failure
bb->shouldA = bt::Status::FAILURE;
- ++ctx.seq;root.Tick(ctx);
+ ++ctx.seq;
+ root.Tick(ctx);
REQUIRE(bb->counterA == 2);
REQUIRE(root.LastStatus() == bt::Status::RUNNING);
@@ -69,22 +73,25 @@ TEMPLATE_TEST_CASE("Retry/2", "[simple retry final failure]", Entity,
for (int i = 1; i <= 2; i++) {
std::this_thread::sleep_for(30ms);
bb->shouldA = bt::Status::RUNNING;
- ++ctx.seq;root.Tick(ctx);
+ ++ctx.seq;
+ root.Tick(ctx);
REQUIRE(root.LastStatus() == bt::Status::RUNNING);
bb->shouldA = bt::Status::FAILURE;
- ++ctx.seq;root.Tick(ctx);
+ ++ctx.seq;
+ root.Tick(ctx);
REQUIRE(root.LastStatus() == bt::Status::RUNNING);
}
// Next the whole tree should failure.
- ++ctx.seq;root.Tick(ctx);
+ ++ctx.seq;
+ root.Tick(ctx);
REQUIRE(root.LastStatus() == bt::Status::FAILURE);
root.UnbindTreeBlob();
}
TEMPLATE_TEST_CASE("Retry/3", "[simple retry final success ]", Entity,
- (EntityFixedBlob<16, sizeof(bt::RetryNode<>::Blob)>)) {
+ (EntityFixedBlob<16, sizeof(bt::RetryNode::Blob)>)) {
bt::Tree root;
auto bb = std::make_shared();
bt::Context ctx(bb);
@@ -100,26 +107,29 @@ TEMPLATE_TEST_CASE("Retry/3", "[simple retry final success ]", Entity,
root.BindTreeBlob(e.blob);
// Tick#1
- ++ctx.seq;root.Tick(ctx);
+ ++ctx.seq;
+ root.Tick(ctx);
REQUIRE(bb->counterA == 1);
REQUIRE(root.LastStatus() == bt::Status::RUNNING);
// Tick#2: Makes A failure
bb->shouldA = bt::Status::FAILURE;
- ++ctx.seq;root.Tick(ctx);
+ ++ctx.seq;
+ root.Tick(ctx);
REQUIRE(bb->counterA == 2);
REQUIRE(root.LastStatus() == bt::Status::RUNNING);
// Tick#3: Makes A ok.
std::this_thread::sleep_for(30ms);
bb->shouldA = bt::Status::SUCCESS;
- ++ctx.seq;root.Tick(ctx);
+ ++ctx.seq;
+ root.Tick(ctx);
REQUIRE(root.LastStatus() == bt::Status::SUCCESS);
root.UnbindTreeBlob();
}
TEMPLATE_TEST_CASE("Retry/4", "[simple retry forever ]", Entity,
- (EntityFixedBlob<16, sizeof(bt::RetryNode<>::Blob)>)) {
+ (EntityFixedBlob<16, sizeof(bt::RetryNode::Blob)>)) {
bt::Tree root;
auto bb = std::make_shared();
bt::Context ctx(bb);
@@ -136,13 +146,15 @@ TEMPLATE_TEST_CASE("Retry/4", "[simple retry forever ]", Entity,
root.BindTreeBlob(e.blob);
// Tick#1
- ++ctx.seq;root.Tick(ctx);
+ ++ctx.seq;
+ root.Tick(ctx);
REQUIRE(bb->counterA == 1);
REQUIRE(root.LastStatus() == bt::Status::RUNNING);
// Tick#2: Makes A failure
bb->shouldA = bt::Status::FAILURE;
- ++ctx.seq;root.Tick(ctx);
+ ++ctx.seq;
+ root.Tick(ctx);
REQUIRE(bb->counterA == 2);
REQUIRE(root.LastStatus() == bt::Status::RUNNING);
@@ -150,7 +162,8 @@ TEMPLATE_TEST_CASE("Retry/4", "[simple retry forever ]", Entity,
for (int i = 0; i < 30; i++) {
std::this_thread::sleep_for(1ms);
bb->shouldA = bt::Status::FAILURE;
- ++ctx.seq;root.Tick(ctx);
+ ++ctx.seq;
+ root.Tick(ctx);
REQUIRE(root.LastStatus() == bt::Status::RUNNING);
}
root.UnbindTreeBlob();
diff --git a/tests/timeout_test.cc b/tests/timeout_test.cc
index 88747f1..d44239f 100644
--- a/tests/timeout_test.cc
+++ b/tests/timeout_test.cc
@@ -8,7 +8,7 @@
using namespace std::chrono_literals;
TEMPLATE_TEST_CASE("Timeout/1", "[simple timeout success]", Entity,
- (EntityFixedBlob<16, sizeof(bt::TimeoutNode<>::Blob)>)) {
+ (EntityFixedBlob<16, sizeof(bt::TimeoutNode::Blob)>)) {
bt::Tree root;
auto bb = std::make_shared();
bt::Context ctx(bb);
@@ -24,20 +24,22 @@ TEMPLATE_TEST_CASE("Timeout/1", "[simple timeout success]", Entity,
TestType e;
root.BindTreeBlob(e.blob);
// Tick#1: A is not started.
- ++ctx.seq;root.Tick(ctx);
+ ++ctx.seq;
+ root.Tick(ctx);
REQUIRE(bb->counterA == 1);
REQUIRE(root.LastStatus() == bt::Status::RUNNING);
// Tick#2: Makes A success.
bb->shouldA = bt::Status::SUCCESS;
- ++ctx.seq;root.Tick(ctx);
+ ++ctx.seq;
+ root.Tick(ctx);
REQUIRE(bb->counterA == 2);
REQUIRE(root.LastStatus() == bt::Status::SUCCESS);
root.UnbindTreeBlob();
}
TEMPLATE_TEST_CASE("Timeout/2", "[simple timeout failure]", Entity,
- (EntityFixedBlob<16, sizeof(bt::TimeoutNode<>::Blob)>)) {
+ (EntityFixedBlob<16, sizeof(bt::TimeoutNode::Blob)>)) {
bt::Tree root;
auto bb = std::make_shared();
bt::Context ctx(bb);
@@ -53,20 +55,22 @@ TEMPLATE_TEST_CASE("Timeout/2", "[simple timeout failure]", Entity,
TestType e;
root.BindTreeBlob(e.blob);
// Tick#1: A is not started.
- ++ctx.seq;root.Tick(ctx);
+ ++ctx.seq;
+ root.Tick(ctx);
REQUIRE(bb->counterA == 1);
REQUIRE(root.LastStatus() == bt::Status::RUNNING);
// Tick#2: Makes A failure.
bb->shouldA = bt::Status::FAILURE;
- ++ctx.seq;root.Tick(ctx);
+ ++ctx.seq;
+ root.Tick(ctx);
REQUIRE(bb->counterA == 2);
REQUIRE(root.LastStatus() == bt::Status::FAILURE);
root.UnbindTreeBlob();
}
TEMPLATE_TEST_CASE("Timeout/3", "[simple timeout timedout]", Entity,
- (EntityFixedBlob<16, sizeof(bt::TimeoutNode<>::Blob)>)) {
+ (EntityFixedBlob<16, sizeof(bt::TimeoutNode::Blob)>)) {
bt::Tree root;
auto bb = std::make_shared();
bt::Context ctx(bb);
@@ -82,13 +86,15 @@ TEMPLATE_TEST_CASE("Timeout/3", "[simple timeout timedout]", Entity,
TestType e;
root.BindTreeBlob(e.blob);
// Tick#1: A is not started.
- ++ctx.seq;root.Tick(ctx);
+ ++ctx.seq;
+ root.Tick(ctx);
REQUIRE(bb->counterA == 1);
REQUIRE(root.LastStatus() == bt::Status::RUNNING);
// Tick#2: should timeout
std::this_thread::sleep_for(110ms);
- ++ctx.seq;root.Tick(ctx);
+ ++ctx.seq;
+ root.Tick(ctx);
REQUIRE(bb->counterA == 1);
REQUIRE(root.LastStatus() == bt::Status::FAILURE);
root.UnbindTreeBlob();