From b70f93fdb0a283f95b6aeddca0f4ac3790e450ba Mon Sep 17 00:00:00 2001 From: hit9 Date: Mon, 29 Apr 2024 12:14:40 +0800 Subject: [PATCH 01/13] Separate cpp with header --- CMakeLists.txt | 2 +- bt.cc | 480 ++++++++++++++++++++++++++++++++ bt.h | 461 ++++-------------------------- example/CMakeLists.txt | 2 +- example/onsignal/CMakeLists.txt | 2 +- tests/CMakeLists.txt | 4 +- 6 files changed, 539 insertions(+), 412 deletions(-) create mode 100644 bt.cc 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/bt.cc b/bt.cc new file mode 100644 index 0000000..52b417e --- /dev/null +++ b/bt.cc @@ -0,0 +1,480 @@ +#include "bt.h" + +#include // for cout, flush + +namespace bt { + +///////////////// +/// TreeBlob +///////////////// + +std::pair ITreeBlob::make(const NodeId id, 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) { + // 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; +} + +Status _InternalRandomSelectorNodeBase::Update(const Context& ctx) { + refresh(ctx); + return update(ctx); +} + +////////////////////////////////////////////////////////////// +/// 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; +} + +////////////////////////////////////////////////////////////// +/// Node > SingleNode > RootNode +/////////////////////////////////////////////////////////////// + +void RootNode::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; +} + +////////////////////////////////////////////////////////////// +/// 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(); +} + +} // namespace bt diff --git a/bt.h b/bt.h index a67e076..e4af62f 100644 --- a/bt.h +++ b/bt.h @@ -77,10 +77,9 @@ #include // for milliseconds, high_resolution_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 // for mt19937 #include #include // for runtime_error #include @@ -97,21 +96,6 @@ namespace bt { 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. @@ -175,14 +159,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. @@ -213,26 +198,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() {} @@ -296,18 +265,7 @@ class Node { } // 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 @@ -346,15 +304,7 @@ class Node { 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 @@ -363,29 +313,10 @@ class Node { // 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 @@ -511,22 +442,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,37 +471,17 @@ 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; }; ////////////////////////////////////////////////////////////// @@ -602,14 +503,8 @@ class _InternalStatefulCompositeNode : virtual public CompositeNode { public: using Blob = _InternalStatefulCompositeNodeBlob; 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 . @@ -644,46 +539,13 @@ class _MixedQueueHelper { 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,15 +556,12 @@ 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; @@ -713,65 +572,20 @@ class _InternalPriorityCompositeNode : virtual public CompositeNode { // If we change the blob binding, the new tick won't be affected by previous blob. // 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 - } + void refresh(const Context& ctx); - q.resetQ1Container(); - - // Clear and enqueue. - q.clear(); - for (int i = 0; i < children.size(); i++) - if (considerable(i)) q.push(i); - } + // 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()); - } + void internalOnBuild() override; public: _InternalPriorityCompositeNode() {} - - Status Update(const Context& ctx) override { - refresh(ctx); - enqueue(); - // propagates ticks - return update(ctx); - } + Status Update(const Context& ctx) override; }; ////////////////////////////////////////////////////////////// @@ -780,23 +594,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 +621,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 +646,13 @@ 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; - } + Status update(const Context& ctx) override; 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 +679,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 @@ -1007,18 +722,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. @@ -1033,17 +737,8 @@ class ConditionalRunNode : public DecoratorNode { 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; - } + void Traverse(TraversalCallback& pre, TraversalCallback& post, Ptr& ptr) override; + Status Update(const Context& ctx) override; }; struct RepeatNodeBlob : NodeBlob { @@ -1069,16 +764,7 @@ class RepeatNode : public DecoratorNode { // 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 @@ -1226,6 +912,9 @@ 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); + ////////////////////////// /// Blob Apis ////////////////////////// @@ -1254,21 +943,6 @@ class RootNode : public SingleNode, public IRootNode { // 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 ////////////////////////// @@ -1312,19 +986,8 @@ class _InternalBuilderBase { // 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); template void maintainSizeInfoOnNodeAttach(T& node, RootNode* root) { node.size = sizeof(T); @@ -1332,12 +995,7 @@ class _InternalBuilderBase { 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); - } + void maintainSizeInfoOnSubtreeAttach(RootNode& subtree, RootNode* root); protected: template @@ -1345,20 +1003,9 @@ class _InternalBuilderBase { 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); }; // Builder helps to build a tree. 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/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/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}) From f43ed71e997f7d30bcbfdc07f2d30eacb1a416fc Mon Sep 17 00:00:00 2001 From: hit9 Date: Mon, 29 Apr 2024 12:18:38 +0800 Subject: [PATCH 02/13] remove useless cout in tests --- tests/condition_test.cc | 3 --- 1 file changed, 3 deletions(-) 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; From 09c31272cce2850cf00c15c909d670f09ff260b8 Mon Sep 17 00:00:00 2001 From: hit9 Date: Mon, 29 Apr 2024 12:42:58 +0800 Subject: [PATCH 03/13] Move internal implements to builder base --- bt.cc | 58 +++++++++++++++++++++ bt.h | 162 +++++++++++----------------------------------------------- 2 files changed, 89 insertions(+), 131 deletions(-) diff --git a/bt.cc b/bt.cc index 52b417e..241ad2c 100644 --- a/bt.cc +++ b/bt.cc @@ -1,6 +1,7 @@ #include "bt.h" #include // for cout, flush +#include // for mt19937 namespace bt { @@ -477,4 +478,61 @@ void _InternalBuilderBase::onNodeBuild(Node* node) { 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 e4af62f..32bdb26 100644 --- a/bt.h +++ b/bt.h @@ -1,72 +1,8 @@ // Copyright (c) 2024 Chao Wang . -// License: BSD. https://github.com/hit9/bt.h +// License: BSD. https://github.com/hit9/bt.h, Version: 0.3.5 // // 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 @@ -79,7 +15,6 @@ #include #include // for unique_ptr #include // for priority_queue -#include // for mt19937 #include #include // for runtime_error #include @@ -979,7 +914,6 @@ class RootNode : public SingleNode, public IRootNode { /// Tree Builder /////////////////////////////////////////////////////////////// -// A internal proxy base class to setup Node's internal bindings. class _InternalBuilderBase { private: // Node id incrementer for a tree. @@ -988,16 +922,22 @@ class _InternalBuilderBase { 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)); + _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); @@ -1006,53 +946,24 @@ class _InternalBuilderBase { 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) { @@ -1063,24 +974,13 @@ class Builder : public _InternalBuilderBase { // 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); } @@ -1094,20 +994,20 @@ 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. /////////////////////////////////// From 7610735c7e32ef9d1dc528b8cc875c37e3f01495 Mon Sep 17 00:00:00 2001 From: hit9 Date: Mon, 29 Apr 2024 14:12:23 +0800 Subject: [PATCH 04/13] Fix tests --- bt.cc | 82 +++++++++++++++++++++++++ bt.h | 137 ++++++++---------------------------------- tests/delay_test.cc | 2 +- tests/retry_test.cc | 8 +-- tests/timeout_test.cc | 6 +- 5 files changed, 116 insertions(+), 119 deletions(-) diff --git a/bt.cc b/bt.cc index 241ad2c..e83b445 100644 --- a/bt.cc +++ b/bt.cc @@ -2,6 +2,7 @@ #include // for cout, flush #include // for mt19937 +#include // for this_thread::sleep_for namespace bt { @@ -424,6 +425,65 @@ Status RepeatNode::Update(const Context& ctx) { 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 /////////////////////////////////////////////////////////////// @@ -438,6 +498,28 @@ void RootNode::Visualize(ull seq) { std::cout << s << std::flush; } +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 /////////////////////////////////////////////////////////////// diff --git a/bt.h b/bt.h index 32bdb26..c12b9b0 100644 --- a/bt.h +++ b/bt.h @@ -19,7 +19,6 @@ #include // for runtime_error #include #include -#include // for this_thread::sleep_for #include // for is_base_of_v #include @@ -702,78 +701,58 @@ class RepeatNode : public DecoratorNode { Status Update(const Context& ctx) override; }; -template -using TimePoint = std::chrono::time_point; +using Timepoint = std::chrono::time_point; -template struct TimeoutNodeBlob : NodeBlob { // Time point of this node starts. - TimePoint startAt = TimePoint::min(); + Timepoint startAt = Timepoint::min(); }; // 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; + using Blob = TimeoutNodeBlob; 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); - } + void OnEnter(const Context& ctx) override; + Status Update(const Context& ctx) override; }; -template struct DelayNodeBlob : NodeBlob { // The time point this node first run. - TimePoint firstRunAt; + Timepoint firstRunAt; }; // 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; + using Blob = DelayNodeBlob; 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); - } + void OnEnter(const Context& ctx) override; + void OnTerminate(const Context& ctx, Status status) override; + Status Update(const Context& ctx) override; }; -template struct RetryNodeBlob : NodeBlob { // Times already retried. int cnt = 0; // Timepoint last retried at. - TimePoint lastRetryAt; + Timepoint lastRetryAt; }; // RetryNode retries its child node on failure. -template class RetryNode : public DecoratorNode { protected: // Max retry times, -1 for unlimited. @@ -782,45 +761,15 @@ class RetryNode : public DecoratorNode { std::chrono::milliseconds interval; public: - using Blob = RetryNodeBlob; + using Blob = RetryNodeBlob; 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; }; ////////////////////////////////////////////////////////////// @@ -850,6 +799,13 @@ class RootNode : public SingleNode, public IRootNode { // 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 ////////////////////////// @@ -877,37 +833,6 @@ 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; } - - ////////////////////////// - /// 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); - } - } - } }; ////////////////////////////////////////////////////////////// @@ -1168,10 +1093,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. @@ -1180,10 +1102,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. @@ -1194,15 +1113,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. diff --git a/tests/delay_test.cc b/tests/delay_test.cc index ded9f85..04fdfc4 100644 --- a/tests/delay_test.cc +++ b/tests/delay_test.cc @@ -8,7 +8,7 @@ using namespace std::chrono_literals; TEMPLATE_TEST_CASE("Delay/1", "[simple delay]", Entity, - (EntityFixedBlob<16, sizeof(bt::DelayNode<>::Blob)>)) { + (EntityFixedBlob<16, sizeof(bt::DelayNode::Blob)>)) { bt::Tree root; auto bb = std::make_shared(); bt::Context ctx(bb); diff --git a/tests/retry_test.cc b/tests/retry_test.cc index 4ce1cf8..5b4a9d2 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); @@ -38,7 +38,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); @@ -84,7 +84,7 @@ TEMPLATE_TEST_CASE("Retry/2", "[simple retry final failure]", Entity, } 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); @@ -119,7 +119,7 @@ TEMPLATE_TEST_CASE("Retry/3", "[simple retry final success ]", Entity, } 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); diff --git a/tests/timeout_test.cc b/tests/timeout_test.cc index 88747f1..c00cbef 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); @@ -37,7 +37,7 @@ TEMPLATE_TEST_CASE("Timeout/1", "[simple timeout success]", Entity, } 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); @@ -66,7 +66,7 @@ TEMPLATE_TEST_CASE("Timeout/2", "[simple timeout failure]", Entity, } 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); From 15d55cebb97edcef073aeb565b3d8121c17a4c33 Mon Sep 17 00:00:00 2001 From: hit9 Date: Mon, 29 Apr 2024 14:13:06 +0800 Subject: [PATCH 05/13] reformat --- tests/delay_test.cc | 11 +++++++---- tests/retry_test.cc | 39 ++++++++++++++++++++++++++------------- tests/timeout_test.cc | 18 ++++++++++++------ 3 files changed, 45 insertions(+), 23 deletions(-) diff --git a/tests/delay_test.cc b/tests/delay_test.cc index 04fdfc4..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 5b4a9d2..a8f8217 100644 --- a/tests/retry_test.cc +++ b/tests/retry_test.cc @@ -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); @@ -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,15 +73,18 @@ 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(); @@ -100,20 +107,23 @@ 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(); } @@ -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 c00cbef..d44239f 100644 --- a/tests/timeout_test.cc +++ b/tests/timeout_test.cc @@ -24,13 +24,15 @@ 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(); @@ -53,13 +55,15 @@ 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(); @@ -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(); From ba4a17d3e6a8d87a266e05261c7712d0781d08cc Mon Sep 17 00:00:00 2001 From: hit9 Date: Mon, 29 Apr 2024 14:50:43 +0800 Subject: [PATCH 06/13] shrink lines of code --- bt.h | 95 +++++++++++++++++++++--------------------------------------- 1 file changed, 33 insertions(+), 62 deletions(-) diff --git a/bt.h b/bt.h index c12b9b0..dda6513 100644 --- a/bt.h +++ b/bt.h @@ -9,7 +9,6 @@ #include // for max, min #include -#include // for NDEBUG mode #include // for milliseconds, high_resolution_clock #include // for memset #include @@ -20,6 +19,7 @@ #include #include #include // for is_base_of_v +#include // for pair #include namespace bt { @@ -84,7 +84,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() {} @@ -119,7 +119,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)); } @@ -179,23 +178,22 @@ class Node { // 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. @@ -210,9 +208,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; @@ -232,11 +227,9 @@ 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); @@ -422,11 +415,6 @@ class CompositeNode : public InternalNode { /// 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: @@ -435,7 +423,11 @@ 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; void OnBlobAllocated(NodeBlob* blob) const override; @@ -507,14 +499,11 @@ class _InternalPriorityCompositeNode : virtual public CompositeNode { // Refresh priorities for considerable children. 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; public: @@ -665,7 +654,6 @@ class ConditionalRunNode : public DecoratorNode { // Condition node to check. Ptr condition; - protected: public: ConditionalRunNode(Ptr condition = nullptr, std::string_view name = "ConditionalRun", Ptr child = nullptr) @@ -675,11 +663,6 @@ class ConditionalRunNode : public DecoratorNode { Status Update(const Context& ctx) override; }; -struct RepeatNodeBlob : NodeBlob { - // Count how many times already executed for this round. - int cnt = 0; -}; - // RepeatNode repeats its child for exactly n times. // Fails immediately if its child fails. class RepeatNode : public DecoratorNode { @@ -688,7 +671,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) {} @@ -703,31 +688,23 @@ class RepeatNode : public DecoratorNode { using Timepoint = std::chrono::time_point; -struct TimeoutNodeBlob : NodeBlob { - // Time point of this node starts. - Timepoint startAt = Timepoint::min(); -}; - // Timeout runs its child for at most given duration, fails on timeout. 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; Status Update(const Context& ctx) override; }; -struct DelayNodeBlob : NodeBlob { - // The time point this node first run. - Timepoint firstRunAt; -}; - // DelayNode runs its child node after given duration. class DelayNode : public DecoratorNode { protected: @@ -735,7 +712,10 @@ class DelayNode : public DecoratorNode { 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) {} @@ -745,13 +725,6 @@ class DelayNode : public DecoratorNode { Status Update(const Context& ctx) override; }; -struct RetryNodeBlob : NodeBlob { - // Times already retried. - int cnt = 0; - // Timepoint last retried at. - Timepoint lastRetryAt; -}; - // RetryNode retries its child node on failure. class RetryNode : public DecoratorNode { protected: @@ -761,7 +734,11 @@ 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) {} @@ -1147,9 +1124,7 @@ class Builder : public _InternalBuilderBase { // ._().Case() // ._()._().Action() // ._().Case() - // ._()._().Sequence() - // ._()._()._().Action() - // ._()._()._().Action() + // ._()._().Action() // ._().Case([=](const Context& ctx) { return false; }) // ._()._().Action() // .End(); @@ -1177,11 +1152,8 @@ class Builder : public _InternalBuilderBase { // // bt::Tree subtree; // subtree - // .Parallel() - // ._().Action() - // ._().Action(; + // .Action() // .End(); - // // root // .Sequence() // ._().Subtree(std::move(subtree)) @@ -1197,8 +1169,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); } From 35c40c7aee643adb16af274c6edca7569b95c453 Mon Sep 17 00:00:00 2001 From: hit9 Date: Mon, 29 Apr 2024 15:15:07 +0800 Subject: [PATCH 07/13] 0.4.0: sperate cpp files --- bt.cc | 19 ++++---- bt.h | 100 +++++++++++---------------------------- changelog | 6 +++ example/main.cc | 1 + example/onsignal/main.cc | 1 - 5 files changed, 44 insertions(+), 83 deletions(-) diff --git a/bt.cc b/bt.cc index e83b445..6fee758 100644 --- a/bt.cc +++ b/bt.cc @@ -1,8 +1,13 @@ +// 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 cout, flush -#include // for mt19937 -#include // for this_thread::sleep_for +#include // for max +#include // for cout, flush +#include // for mt19937 +#include // for this_thread::sleep_for namespace bt { @@ -310,7 +315,8 @@ Status _InternalSelectorNodeBase::update(const Context& ctx) { static std::mt19937 rng(std::random_device{}()); // seed random -Status _InternalRandomSelectorNodeBase::update(const Context& ctx) { +Status _InternalRandomSelectorNodeBase::Update(const Context& ctx) { + refresh(ctx); // Sum of weights/priorities. unsigned int total = 0; for (int i = 0; i < children.size(); i++) @@ -353,11 +359,6 @@ Status _InternalRandomSelectorNodeBase::update(const Context& ctx) { return Status::FAILURE; } -Status _InternalRandomSelectorNodeBase::Update(const Context& ctx) { - refresh(ctx); - return update(ctx); -} - ////////////////////////////////////////////////////////////// /// Node > InternalNode > CompositeNode > ParallelNode /////////////////////////////////////////////////////////////// diff --git a/bt.h b/bt.h index dda6513..8442ffc 100644 --- a/bt.h +++ b/bt.h @@ -1,15 +1,12 @@ // Copyright (c) 2024 Chao Wang . -// License: BSD. https://github.com/hit9/bt.h, Version: 0.3.5 -// +// License: BSD, Version: 0.4.0. https://github.com/hit9/bt.cc // A lightweight behavior tree library that separates data and behavior. -// #ifndef HIT9_BT_H #define HIT9_BT_H -#include // for max, min #include -#include // for milliseconds, high_resolution_clock +#include // for milliseconds, steady_clock #include // for memset #include #include // for unique_ptr @@ -23,11 +20,6 @@ #include namespace bt { - -//////////////////////////// -/// Status -//////////////////////////// - enum class Status { UNDEFINED = 0, RUNNING = 1, SUCCESS = 2, FAILURE = 3 }; using ull = unsigned long long; @@ -35,10 +27,6 @@ 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. @@ -56,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? @@ -67,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. @@ -177,7 +165,6 @@ class Node { unsigned int priorityCurrentTick = 0; // tick seq when the priority cache was set. ull priorityCurrentTickSeq = 0; - // holding a pointer to the root. IRootNode* root = nullptr; // size of this node, available after tree built. @@ -195,10 +182,8 @@ class Node { const auto cb = [&](NodeBlob* blob) { OnBlobAllocated(blob); }; 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); - // 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. @@ -215,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; } @@ -233,38 +217,31 @@ class Node { // Internal method to query priority of this node in current tick. 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); - // Main entry function, should be called on every tick. 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. @@ -273,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 ""; } @@ -304,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; @@ -321,7 +294,6 @@ class ConditionNode : public LeafNode { private: Checker checker = nullptr; }; - using Condition = ConditionNode; // alias // Concept TCondition for all classes derived from Condition. @@ -492,19 +464,18 @@ class _InternalPriorityCompositeNode : virtual public CompositeNode { // 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); // 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; + virtual Status update(const Context& ctx) { return bt::Status::UNDEFINED; } public: _InternalPriorityCompositeNode() {} @@ -571,9 +542,6 @@ class StatefulSelectorNode : public _InternalStatefulCompositeNode, public _Inte // Weighted random selector. class _InternalRandomSelectorNodeBase : virtual public _InternalPriorityCompositeNode { - protected: - Status update(const Context& ctx) override; - public: Status Update(const Context& ctx) override; }; @@ -635,7 +603,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; }; @@ -682,7 +650,6 @@ 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; }; @@ -697,7 +664,6 @@ class TimeoutNode : public DecoratorNode { 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(); } @@ -715,7 +681,6 @@ class DelayNode : public DecoratorNode { 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) {} @@ -738,7 +703,6 @@ class RetryNode : public DecoratorNode { 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) {} @@ -775,7 +739,6 @@ class RootNode : public SingleNode, public IRootNode { // 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. @@ -783,9 +746,8 @@ class RootNode : public SingleNode, public IRootNode { 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; } @@ -794,9 +756,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. @@ -910,9 +871,8 @@ class Builder : public _InternalBuilderBase { return static_cast(*this); } - /////////////////////////////////// - // General creators. - /////////////////////////////////// + // General creators + // ~~~~~~~~~~~~~~~~ // C is a function to attach an arbitrary new Node. // It can be used to attach custom node implementation. @@ -920,8 +880,7 @@ class Builder : public _InternalBuilderBase { // root // .C() // ._().Action() - // .End() - // ; + // .End(); template auto& C(Args... args) { if constexpr (std::is_base_of_v) // LeafNode @@ -939,9 +898,8 @@ 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. @@ -977,9 +935,8 @@ class Builder : public _InternalBuilderBase { // 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:: @@ -1012,9 +969,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. @@ -1143,9 +1099,8 @@ class Builder : public _InternalBuilderBase { // 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:: @@ -1160,8 +1115,7 @@ class Builder : public _InternalBuilderBase { // .End(); auto& Subtree(RootNode&& subtree) { onSubtreeAttach(subtree, root); - // move to the tree - return M(std::move(subtree)); + return M(std::move(subtree)); // move } }; diff --git a/changelog b/changelog index 18d3cce..e38139b 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,9 @@ +0.4.0 +----- + +* Separates to bt.h and bt.cc. +* Remove template Clock, use std::chrono::steady_clock. + 0.3.5 ----- 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/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 From cbd7a87851c79298b39db6de1e8190604238781e Mon Sep 17 00:00:00 2001 From: hit9 Date: Mon, 29 Apr 2024 15:25:11 +0800 Subject: [PATCH 08/13] use printf instead of cout --- bt.cc | 7 +++---- bt.h | 28 ---------------------------- changelog | 1 + 3 files changed, 4 insertions(+), 32 deletions(-) diff --git a/bt.cc b/bt.cc index 6fee758..e9756fc 100644 --- a/bt.cc +++ b/bt.cc @@ -5,7 +5,6 @@ #include "bt.h" #include // for max -#include // for cout, flush #include // for mt19937 #include // for this_thread::sleep_for @@ -15,7 +14,7 @@ namespace bt { /// TreeBlob ///////////////// -std::pair ITreeBlob::make(const NodeId id, size_t size, const std::size_t cap) { +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}; @@ -492,11 +491,11 @@ Status RetryNode::Update(const Context& ctx) { void RootNode::Visualize(ull seq) { // CSI[2J clears screen. // CSI[H moves the cursor to top-left corner - std::cout << "\x1B[2J\x1B[H" << std::flush; + printf("\x1B[2J\x1B[H"); // Make a string. std::string s; makeVisualizeString(s, 0, seq); - std::cout << s << std::flush; + printf("%s", s.c_str()); } void RootNode::TickForever(Context& ctx, std::chrono::nanoseconds interval, bool visualize, diff --git a/bt.h b/bt.h index 8442ffc..481e793 100644 --- a/bt.h +++ b/bt.h @@ -834,19 +834,16 @@ class Builder : public _InternalBuilderBase { root = &r; onRootAttach(root, sizeof(D), sizeof(typename D::Blob)); } - // Creates a leaf node. auto& attachLeafNode(Ptr p) { _InternalBuilderBase::attachLeafNode(std::move(p)); return *static_cast(this); } - // Creates an internal node with optional children. auto& attachInternalNode(Ptr p) { _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 @@ -864,7 +861,6 @@ class Builder : public _InternalBuilderBase { void End() { while (stack.size()) pop(); // clears the stack } - // Increases indent level to append node. auto& _() { level++; @@ -888,7 +884,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) { @@ -904,33 +899,26 @@ class Builder : public _InternalBuilderBase { // 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*"); } @@ -947,7 +935,6 @@ class Builder : public _InternalBuilderBase { auto& Action(Args&&... args) { return C(std::forward(args)...); } - // Creates a ConditionNode from a lambda function. // Code example:: // root @@ -956,7 +943,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 @@ -980,7 +966,6 @@ class Builder : public _InternalBuilderBase { // ._().Condition() // .End(); auto& Invert() { return C("Invert"); } - // Alias to Invert, just named 'Not'. // Code exapmle:: // root @@ -988,7 +973,6 @@ class Builder : public _InternalBuilderBase { // ._().Condition() // .End(); auto& Not() { return C("Not"); } - // Creates a invert condition of given Condition class. // Code exapmle:: // root @@ -1000,7 +984,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. @@ -1011,14 +994,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:: @@ -1027,7 +1008,6 @@ class Builder : public _InternalBuilderBase { // ._().Action() // .End(); 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:: @@ -1036,7 +1016,6 @@ class Builder : public _InternalBuilderBase { // ._().Action() // .End(); 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. @@ -1047,12 +1026,10 @@ class Builder : public _InternalBuilderBase { // ._().Action() // .End(); auto& Retry(int n, std::chrono::milliseconds interval) { return C(n, interval, "Retry"); } - // Alias for Retry(-1, interval) auto& RetryForever(std::chrono::milliseconds interval) { return C(-1, interval, "RetryForever"); } - // If creates a ConditionalRunNode. // It executes the decorated node only if the condition goes true. // Code example:: @@ -1065,14 +1042,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 @@ -1085,17 +1060,14 @@ class Builder : public _InternalBuilderBase { // ._()._().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); } diff --git a/changelog b/changelog index e38139b..6dff74f 100644 --- a/changelog +++ b/changelog @@ -3,6 +3,7 @@ * Separates to bt.h and bt.cc. * Remove template Clock, use std::chrono::steady_clock. +* Use printf instead of iostream 0.3.5 ----- From 3bb6056660dcf381ba184d7b630f2bb373d68e8d Mon Sep 17 00:00:00 2001 From: hit9 Date: Mon, 29 Apr 2024 15:34:37 +0800 Subject: [PATCH 09/13] remove empty lines --- bt.h | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/bt.h b/bt.h index 481e793..e644b7f 100644 --- a/bt.h +++ b/bt.h @@ -422,17 +422,13 @@ 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: @@ -1076,7 +1072,6 @@ class Builder : public _InternalBuilderBase { // Attach a sub behavior tree into this tree. // Code example:: - // // bt::Tree subtree; // subtree // .Action() From 4ccd55c91f1399bf857e041406b85149c8b83162 Mon Sep 17 00:00:00 2001 From: hit9 Date: Mon, 29 Apr 2024 15:38:11 +0800 Subject: [PATCH 10/13] Rename to bt.cc --- README.md | 12 ++++++------ README.zh.md | 14 +++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) 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`. From 61162c6d643fa85352bbcd14e8cf6cc99b579bf0 Mon Sep 17 00:00:00 2001 From: hit9 Date: Mon, 29 Apr 2024 15:38:32 +0800 Subject: [PATCH 11/13] Add changelog for renaming --- changelog | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog b/changelog index 6dff74f..7b41efd 100644 --- a/changelog +++ b/changelog @@ -4,6 +4,7 @@ * 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 ----- From 3027e8830120208efe60f0c65bf5c06735285983 Mon Sep 17 00:00:00 2001 From: hit9 Date: Mon, 29 Apr 2024 15:39:46 +0800 Subject: [PATCH 12/13] Add printf headerfile cstdio --- bt.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/bt.cc b/bt.cc index e9756fc..984dc3b 100644 --- a/bt.cc +++ b/bt.cc @@ -7,6 +7,7 @@ #include // for max #include // for mt19937 #include // for this_thread::sleep_for +#include // for printf namespace bt { From 15b64f35befd9a2e427c5ecb9b4637e7bee43a1c Mon Sep 17 00:00:00 2001 From: hit9 Date: Mon, 29 Apr 2024 15:43:24 +0800 Subject: [PATCH 13/13] remformat --- bt.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bt.cc b/bt.cc index 984dc3b..c560138 100644 --- a/bt.cc +++ b/bt.cc @@ -5,9 +5,9 @@ #include "bt.h" #include // for max +#include // for printf #include // for mt19937 #include // for this_thread::sleep_for -#include // for printf namespace bt {