From 5aa07b602e0103f886f150efcb7828a9c02125f1 Mon Sep 17 00:00:00 2001 From: hit9 Date: Wed, 3 Jul 2024 14:41:11 +0800 Subject: [PATCH 1/2] 0.4.3: Add new decorators: `ForceSuccess` and `ForceFailure`. #15 --- README.md | 11 ++++ README.zh.md | 9 +++ bt.cc | 8 +++ bt.h | 24 +++++++ changelog | 5 ++ tests/forece_success_and_failure_test.cc | 83 ++++++++++++++++++++++++ 6 files changed, 140 insertions(+) create mode 100644 tests/forece_success_and_failure_test.cc diff --git a/README.md b/README.md index 9105f08..042ec15 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,7 @@ Reference: - [Timeout](#timeout) - [Delay](#delay) - [Retry](#retry) + - [ForceSuccess/ForceFailure](#force-success-and-force-failure) - [Custom Decorator](#custom-decorator) - [Sub Tree](#subtree) - [Tick Context](#context) @@ -498,6 +499,16 @@ Reference: ._().Action() ``` + * `ForceSuccess` executes its child node and checks its status, + returns `RUNNING` if the decorated node is `RUNNING`, otherwise always returns `SUCCESS`. [↑] + + ```cpp + .ForceSuccess() + ._().Actino() + ``` + + `ForceFailure` returns `RUNNING` if the decorated node is `RUNNING`, otherwise always returns `FAILURE`. + * **Custom Decorator** [↑] To implement a custom decorator, just inherits from `bt::DecoratorNode`: diff --git a/README.zh.md b/README.zh.md index ed8169f..1dcd72d 100644 --- a/README.zh.md +++ b/README.zh.md @@ -114,6 +114,7 @@ while(...) { - [Timeout 超时](#timeout) - [Delay 延时](#delay) - [Retry 重试](#retry) + - [ForceSuccess/ForceFailure 强制成功 和 强制失败](#force-success-and-force-failure) - [自定义装饰器](#custom-decorator) - [子树](#subtree) - [Tick 上下文](#context) @@ -486,6 +487,14 @@ while(...) { ._().Action() ``` + * `ForceSuccess` 会执行它装饰的节点, 如果仍在执行, 则返回执行, 否则强制返回成功. [↑] + `ForceFailure` 是类似的, 如果所装饰的节点仍在执行, 则返回执行, 否则强制返回失败. + + ```cpp + .ForceSuccess() + ._().Actino() + ``` + * **自定义装饰器** [↑] 要定义一个自定义的装饰器,可以继承 `bt::DecoratorNode`: diff --git a/bt.cc b/bt.cc index f5a0bf3..02826ac 100644 --- a/bt.cc +++ b/bt.cc @@ -485,6 +485,14 @@ Status RetryNode::Update(const Context& ctx) { } } +Status ForceSuccessNode::Update(const Context& ctx) { + return (child->Update(ctx) == Status::RUNNING) ? Status::RUNNING : Status::SUCCESS; +} + +Status ForceFailureNode::Update(const Context& ctx) { + return (child->Update(ctx) == Status::RUNNING) ? Status::RUNNING : Status::FAILURE; +} + ////////////////////////////////////////////////////////////// /// Node > SingleNode > RootNode /////////////////////////////////////////////////////////////// diff --git a/bt.h b/bt.h index 9bda27b..9ce689a 100644 --- a/bt.h +++ b/bt.h @@ -720,6 +720,22 @@ class RetryNode : public DecoratorNode { Status Update(const Context& ctx) override; }; +// ForceSuccessNode returns RUNNING if the decorated node is RUNNING, else always SUCCESS. +class ForceSuccessNode : public DecoratorNode { + public: + ForceSuccessNode(std::string_view name = "ForceSuccess", Ptr child = nullptr) + : DecoratorNode(name, std::move(child)) {} + Status Update(const Context& ctx) override; +}; + +// ForceFailureNode returns Failure if the decorated node is RUNNING, else always FAILURE. +class ForceFailureNode : public DecoratorNode { + public: + ForceFailureNode(std::string_view name = "ForceFailure", Ptr child = nullptr) + : DecoratorNode(name, std::move(child)) {} + Status Update(const Context& ctx) override; +}; + ////////////////////////////////////////////////////////////// /// Node > SingleNode > RootNode /////////////////////////////////////////////////////////////// @@ -1037,6 +1053,14 @@ class Builder : public _InternalBuilderBase { auto& RetryForever(std::chrono::milliseconds interval) { return C(-1, interval, "RetryForever"); } + // Forces a node to be success. + // It executes the decorated node and checks its status. + // Returns RUNNING if the decorated node is RUNNING, else always returns SUCCESS. + auto& ForceSuccess() { return C("ForceSuccess"); } + // Forces a node to be failure. + // It executes the decorated node and checks its status. + // Returns RUNNING if the decorated node is RUNNING, else always returns FAILURE. + auto& ForceFailure() { return C("ForceFailure"); } // If creates a ConditionalRunNode. // It executes the decorated node only if the condition goes true. // Code example:: diff --git a/changelog b/changelog index 566ef41..dcf568b 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,8 @@ +0.4.3 +----- + +* Add two new decorators: `ForceSuccess` and `ForceFailure`. #15 + 0.4.2 ----- diff --git a/tests/forece_success_and_failure_test.cc b/tests/forece_success_and_failure_test.cc new file mode 100644 index 0000000..7f669e8 --- /dev/null +++ b/tests/forece_success_and_failure_test.cc @@ -0,0 +1,83 @@ +#include + +#include "bt.h" +#include "types.h" + +TEST_CASE("ForceSuccess", "[simple force success test]") { + bt::Tree root; + auto bb = std::make_shared(); + bt::Context ctx(bb); + + // clang-format off + root + .ForceSuccess() + ._().Action() + .End() + ; + // clang-format on + + Entity e; + root.BindTreeBlob(e.blob); + + // Tick#1 + ++ctx.seq; + root.Tick(ctx); + REQUIRE(bb->counterA == 1); + REQUIRE(root.LastStatus() == bt::Status::RUNNING); // report running as is + + // Tick#2: Makes A failed. + bb->shouldA = bt::Status::FAILURE; + ++ctx.seq; + root.Tick(ctx); + REQUIRE(bb->counterA == 2); + REQUIRE(bb->statusA == bt::Status::FAILURE); + REQUIRE(root.LastStatus() == bt::Status::SUCCESS); // still success. + + // Tick#3: Makes A success. + bb->shouldA = bt::Status::SUCCESS; + ++ctx.seq; + root.Tick(ctx); + REQUIRE(bb->counterA == 3); + REQUIRE(bb->statusA == bt::Status::SUCCESS); + REQUIRE(root.LastStatus() == bt::Status::SUCCESS); // still success. +} + +TEST_CASE("ForceFailure", "[simple force failure test]") { + bt::Tree root; + auto bb = std::make_shared(); + bt::Context ctx(bb); + + // clang-format off + root + .ForceFailure() + ._().Action() + .End() + ; + // clang-format on + + Entity e; + root.BindTreeBlob(e.blob); + + // Tick#1 + ++ctx.seq; + root.Tick(ctx); + REQUIRE(bb->counterA == 1); + REQUIRE(root.LastStatus() == bt::Status::RUNNING); // report running as is + + // Tick#2: Makes A failed. + bb->shouldA = bt::Status::FAILURE; + ++ctx.seq; + root.Tick(ctx); + REQUIRE(bb->counterA == 2); + REQUIRE(bb->statusA == bt::Status::FAILURE); + REQUIRE(root.LastStatus() == bt::Status::FAILURE); // should also failure. + + // Tick#3: Makes A success. + bb->shouldA = bt::Status::SUCCESS; + ++ctx.seq; + root.Tick(ctx); + REQUIRE(bb->counterA == 3); + REQUIRE(bb->statusA == bt::Status::SUCCESS); + REQUIRE(root.LastStatus() == bt::Status::FAILURE); // still failure. +} + From ffe565e168fd39045f192d6b9f3fda1385b958b6 Mon Sep 17 00:00:00 2001 From: hit9 Date: Wed, 3 Jul 2024 14:50:04 +0800 Subject: [PATCH 2/2] upgrade to 0.4.3 --- bt.cc | 2 +- bt.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bt.cc b/bt.cc index 02826ac..a29d707 100644 --- a/bt.cc +++ b/bt.cc @@ -1,5 +1,5 @@ // Copyright (c) 2024 Chao Wang . -// License: BSD, Version: 0.4.1. https://github.com/hit9/bt.cc +// License: BSD, Version: 0.4.3. https://github.com/hit9/bt.cc // A lightweight behavior tree library that separates data and behavior. #include "bt.h" diff --git a/bt.h b/bt.h index 9ce689a..b2b2c7b 100644 --- a/bt.h +++ b/bt.h @@ -1,5 +1,5 @@ // Copyright (c) 2024 Chao Wang . -// License: BSD, Version: 0.4.2. https://github.com/hit9/bt.cc +// License: BSD, Version: 0.4.3. https://github.com/hit9/bt.cc // A lightweight behavior tree library that separates data and behavior. #ifndef HIT9_BT_H