From ded2ed2fef7cf255f2a60675678a900b1625b4b9 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Fri, 14 Apr 2023 10:16:26 +0200 Subject: [PATCH] Add elapsed, timing info and improve drawing --- bt/actions/bt_random_wait.cpp | 4 +- bt/actions/bt_random_wait.h | 1 - bt/actions/bt_wait.cpp | 7 +-- bt/actions/bt_wait.h | 3 - bt/bt_task.cpp | 17 ++++-- bt/bt_task.h | 4 +- bt/decorators/bt_delay.cpp | 7 +-- bt/decorators/bt_delay.h | 2 - bt/decorators/bt_time_limit.cpp | 7 +-- bt/decorators/bt_time_limit.h | 2 - debugger/behavior_tree_data.cpp | 21 ++++--- debugger/behavior_tree_data.h | 9 +-- debugger/behavior_tree_view.cpp | 102 +++++++++++++++++++++++++++----- debugger/behavior_tree_view.h | 17 +++++- register_types.cpp | 3 + 15 files changed, 141 insertions(+), 65 deletions(-) diff --git a/bt/actions/bt_random_wait.cpp b/bt/actions/bt_random_wait.cpp index f0c4a93..d7a28c5 100644 --- a/bt/actions/bt_random_wait.cpp +++ b/bt/actions/bt_random_wait.cpp @@ -10,13 +10,11 @@ String BTRandomWait::_generate_name() const { } void BTRandomWait::_enter() { - time_passed = 0.0; duration = Math::random(min_duration, max_duration); } int BTRandomWait::_tick(double p_delta) { - time_passed += p_delta; - if (time_passed < duration) { + if (get_elapsed_time() < duration) { return RUNNING; } else { return SUCCESS; diff --git a/bt/actions/bt_random_wait.h b/bt/actions/bt_random_wait.h index 4b15109..99559de 100644 --- a/bt/actions/bt_random_wait.h +++ b/bt/actions/bt_random_wait.h @@ -13,7 +13,6 @@ private: double min_duration = 1.0; double max_duration = 2.0; - double time_passed = 0.0; double duration = 0.0; protected: diff --git a/bt/actions/bt_wait.cpp b/bt/actions/bt_wait.cpp index 8a67d9a..cba28c0 100644 --- a/bt/actions/bt_wait.cpp +++ b/bt/actions/bt_wait.cpp @@ -10,13 +10,8 @@ String BTWait::_generate_name() const { return vformat("Wait %s sec", Math::snapped(duration, 0.001)); } -void BTWait::_enter() { - time_passed = 0.0; -} - int BTWait::_tick(double p_delta) { - time_passed += p_delta; - if (time_passed < duration) { + if (get_elapsed_time() < duration) { return RUNNING; } else { return SUCCESS; diff --git a/bt/actions/bt_wait.h b/bt/actions/bt_wait.h index 5c544cf..9793b39 100644 --- a/bt/actions/bt_wait.h +++ b/bt/actions/bt_wait.h @@ -12,13 +12,10 @@ class BTWait : public BTAction { private: double duration = 1.0; - double time_passed = 0.0; - protected: static void _bind_methods(); virtual String _generate_name() const override; - virtual void _enter() override; virtual int _tick(double p_delta) override; public: diff --git a/bt/bt_task.cpp b/bt/bt_task.cpp index 2bf83f8..76f0e62 100644 --- a/bt/bt_task.cpp +++ b/bt/bt_task.cpp @@ -132,20 +132,28 @@ Ref BTTask::clone() const { int BTTask::execute(double p_delta) { if (status != RUNNING) { + // Reset children status. + if (status != FRESH) { + for (int i = 0; i < get_child_count(); i++) { + children.get(i)->cancel(); + } + } if (!GDVIRTUAL_CALL(_enter)) { _enter(); } + } else { + elapsed += p_delta; } if (!GDVIRTUAL_CALL(_tick, p_delta, status)) { status = _tick(p_delta); } - last_tick_usec = Engine::get_singleton()->get_frame_ticks(); if (status != RUNNING) { if (!GDVIRTUAL_CALL(_exit)) { _exit(); } + elapsed = 0.0; } return status; } @@ -160,6 +168,7 @@ void BTTask::cancel() { } } status = FRESH; + elapsed = 0.0; } Ref BTTask::get_child(int p_idx) const { @@ -277,13 +286,12 @@ void BTTask::_bind_methods() { // Properties, setters and getters. ClassDB::bind_method(D_METHOD("get_agent"), &BTTask::get_agent); ClassDB::bind_method(D_METHOD("set_agent", "p_agent"), &BTTask::set_agent); - ClassDB::bind_method(D_METHOD("_get_children"), &BTTask::_get_children); ClassDB::bind_method(D_METHOD("_set_children", "p_children"), &BTTask::_set_children); - ClassDB::bind_method(D_METHOD("get_blackboard"), &BTTask::get_blackboard); ClassDB::bind_method(D_METHOD("get_parent"), &BTTask::get_parent); ClassDB::bind_method(D_METHOD("get_status"), &BTTask::get_status); + ClassDB::bind_method(D_METHOD("get_elapsed_time"), &BTTask::get_elapsed_time); ADD_PROPERTY(PropertyInfo(Variant::STRING, "custom_name"), "set_custom_name", "get_custom_name"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "agent", PROPERTY_HINT_RESOURCE_TYPE, "Node", 0), "set_agent", "get_agent"); @@ -291,6 +299,7 @@ void BTTask::_bind_methods() { // ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "parent", PROPERTY_HINT_RESOURCE_TYPE, "BTTask", 0), "", "get_parent"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "children", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_children", "_get_children"); ADD_PROPERTY(PropertyInfo(Variant::INT, "status", PROPERTY_HINT_NONE, "", 0), "", "get_status"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "elapsed_time"), "", "get_elapsed_time"); GDVIRTUAL_BIND(_setup); GDVIRTUAL_BIND(_enter); @@ -312,7 +321,7 @@ BTTask::BTTask() { parent = nullptr; children = Vector>(); status = FRESH; - last_tick_usec = 0; + elapsed = 0.0; } BTTask::~BTTask() { diff --git a/bt/bt_task.h b/bt/bt_task.h index 09c521e..5ee40a8 100644 --- a/bt/bt_task.h +++ b/bt/bt_task.h @@ -33,7 +33,7 @@ private: BTTask *parent; Vector> children; int status; - int last_tick_usec; + double elapsed; Array _get_children() const; void _set_children(Array children); @@ -76,7 +76,7 @@ public: int execute(double p_delta); void cancel(); int get_status() const { return status; } - int get_last_tick_usec() const { return last_tick_usec; } + double get_elapsed_time() const { return elapsed; }; Ref get_child(int p_idx) const; int get_child_count() const; diff --git a/bt/decorators/bt_delay.cpp b/bt/decorators/bt_delay.cpp index 03c28c9..b909aff 100644 --- a/bt/decorators/bt_delay.cpp +++ b/bt/decorators/bt_delay.cpp @@ -12,14 +12,9 @@ String BTDelay::_generate_name() const { return vformat("Delay %s sec", Math::snapped(seconds, 0.001)); } -void BTDelay::_enter() { - time_passed = 0.0; -} - int BTDelay::_tick(double p_delta) { ERR_FAIL_COND_V_MSG(get_child_count() == 0, FAILURE, "BT decorator has no child."); - time_passed += p_delta; - if (time_passed <= seconds) { + if (get_elapsed_time() <= seconds) { return RUNNING; } return get_child(0)->execute(p_delta); diff --git a/bt/decorators/bt_delay.h b/bt/decorators/bt_delay.h index f67b6bd..86d76c3 100644 --- a/bt/decorators/bt_delay.h +++ b/bt/decorators/bt_delay.h @@ -11,13 +11,11 @@ class BTDelay : public BTDecorator { private: double seconds = 1.0; - double time_passed = 0.0; protected: static void _bind_methods(); virtual String _generate_name() const override; - virtual void _enter() override; virtual int _tick(double p_delta) override; public: diff --git a/bt/decorators/bt_time_limit.cpp b/bt/decorators/bt_time_limit.cpp index b95d612..42531a2 100644 --- a/bt/decorators/bt_time_limit.cpp +++ b/bt/decorators/bt_time_limit.cpp @@ -7,15 +7,10 @@ String BTTimeLimit::_generate_name() const { return vformat("TimeLimit %s sec", Math::snapped(time_limit, 0.001)); } -void BTTimeLimit::_enter() { - time_passed = 0.0; -} - int BTTimeLimit::_tick(double p_delta) { ERR_FAIL_COND_V_MSG(get_child_count() == 0, FAILURE, "BT decorator has no child."); - time_passed += p_delta; int status = get_child(0)->execute(p_delta); - if (status == RUNNING and time_passed >= time_limit) { + if (status == RUNNING and get_elapsed_time() >= time_limit) { get_child(0)->cancel(); return FAILURE; } diff --git a/bt/decorators/bt_time_limit.h b/bt/decorators/bt_time_limit.h index 97d1e4b..86c1404 100644 --- a/bt/decorators/bt_time_limit.h +++ b/bt/decorators/bt_time_limit.h @@ -11,13 +11,11 @@ class BTTimeLimit : public BTDecorator { private: double time_limit = 5.0; - double time_passed = 0.0; protected: static void _bind_methods(); virtual String _generate_name() const override; - virtual void _enter() override; virtual int _tick(double p_delta) override; public: diff --git a/debugger/behavior_tree_data.cpp b/debugger/behavior_tree_data.cpp index aae3cfd..cbb8f2d 100644 --- a/debugger/behavior_tree_data.cpp +++ b/debugger/behavior_tree_data.cpp @@ -9,6 +9,7 @@ BehaviorTreeData::BehaviorTreeData(const Ref &p_instance) { // Flatten tree into list depth first List> stack; stack.push_back(p_instance); + int id = 0; while (stack.size()) { Ref task = stack[0]; stack.pop_front(); @@ -19,20 +20,23 @@ BehaviorTreeData::BehaviorTreeData(const Ref &p_instance) { } tasks.push_back(TaskData( + id, task->get_task_name(), num_children, task->get_status(), - task->get_last_tick_usec(), + task->get_elapsed_time(), task->get_class())); + id += 1; } } void BehaviorTreeData::serialize(Array &p_arr) { for (const TaskData &td : tasks) { + p_arr.push_back(td.id); p_arr.push_back(td.name); p_arr.push_back(td.num_children); p_arr.push_back(td.status); - p_arr.push_back(td.last_tick_usec); + p_arr.push_back(td.elapsed_time); p_arr.push_back(td.type_name); } } @@ -42,13 +46,14 @@ void BehaviorTreeData::deserialize(const Array &p_arr) { int idx = 0; while (p_arr.size() > idx) { - ERR_FAIL_COND(p_arr.size() < 5); - ERR_FAIL_COND(p_arr[idx].get_type() != Variant::STRING); - ERR_FAIL_COND(p_arr[idx + 1].get_type() != Variant::INT); + ERR_FAIL_COND(p_arr.size() < 6); + ERR_FAIL_COND(p_arr[idx].get_type() != Variant::INT); + ERR_FAIL_COND(p_arr[idx + 1].get_type() != Variant::STRING); ERR_FAIL_COND(p_arr[idx + 2].get_type() != Variant::INT); ERR_FAIL_COND(p_arr[idx + 3].get_type() != Variant::INT); - ERR_FAIL_COND(p_arr[idx + 4].get_type() != Variant::STRING); - tasks.push_back(TaskData(p_arr[idx], p_arr[idx + 1], p_arr[idx + 2], p_arr[idx + 3], p_arr[idx + 4])); - idx += 5; + ERR_FAIL_COND(p_arr[idx + 4].get_type() != Variant::FLOAT); + ERR_FAIL_COND(p_arr[idx + 5].get_type() != Variant::STRING); + tasks.push_back(TaskData(p_arr[idx], p_arr[idx + 1], p_arr[idx + 2], p_arr[idx + 3], p_arr[idx + 4], p_arr[idx + 5])); + idx += 6; } } diff --git a/debugger/behavior_tree_data.h b/debugger/behavior_tree_data.h index 708c607..88957e3 100644 --- a/debugger/behavior_tree_data.h +++ b/debugger/behavior_tree_data.h @@ -9,20 +9,21 @@ class BehaviorTreeData { public: struct TaskData { + int id = 0; String name; - // ObjectID object_id; int num_children = 0; int status = 0; - int last_tick_usec = 0; + double elapsed_time = 0.0; String type_name; // String script_path; // String resource_path; - TaskData(const String &p_name, int p_num_children, int p_status, int p_last_tick_usec, const String &p_type_name) { + TaskData(int p_id, const String &p_name, int p_num_children, int p_status, double p_elapsed_time, const String &p_type_name) { + id = p_id; name = p_name; num_children = p_num_children; status = p_status; - last_tick_usec = p_last_tick_usec; + elapsed_time = p_elapsed_time; type_name = p_type_name; } diff --git a/debugger/behavior_tree_view.cpp b/debugger/behavior_tree_view.cpp index 672888b..9a81bf0 100644 --- a/debugger/behavior_tree_view.cpp +++ b/debugger/behavior_tree_view.cpp @@ -3,12 +3,51 @@ #include "behavior_tree_view.h" #include "behavior_tree_data.h" #include "core/math/color.h" +#include "core/math/math_defs.h" +#include "core/object/callable_method_pointer.h" +#include "core/typedefs.h" +#include "editor/editor_scale.h" #include "modules/limboai/bt/bt_task.h" #include "modules/limboai/limbo_utility.h" +#include "scene/resources/style_box.h" + +void BehaviorTreeView::_draw_running_status(Object *p_obj, Rect2 p_rect) { + p_rect = p_rect.grow_side(SIDE_LEFT, p_rect.get_position().x); + sbf_running.draw(tree->get_canvas_item(), p_rect); +} + +void BehaviorTreeView::_draw_success_status(Object *p_obj, Rect2 p_rect) { + p_rect = p_rect.grow_side(SIDE_LEFT, p_rect.get_position().x); + sbf_success.draw(tree->get_canvas_item(), p_rect); +} + +void BehaviorTreeView::_draw_failure_status(Object *p_obj, Rect2 p_rect) { + p_rect = p_rect.grow_side(SIDE_LEFT, p_rect.get_position().x); + sbf_failure.draw(tree->get_canvas_item(), p_rect); +} + +void BehaviorTreeView::_item_collapsed(Object *p_obj) { + TreeItem *item = Object::cast_to(p_obj); + if (!item) { + return; + } + int id = item->get_metadata(0); + bool collapsed = item->is_collapsed(); + if (!collapsed_ids.has(id) && collapsed) { + collapsed_ids.push_back(item->get_metadata(0)); + } else if (collapsed_ids.has(id) && !collapsed) { + collapsed_ids.erase(id); + } +} void BehaviorTreeView::update_tree(const BehaviorTreeData &p_data) { + // Remember selected. + int selected_id = -1; + if (tree->get_selected()) { + selected_id = tree->get_selected()->get_metadata(0); + } + tree->clear(); - int recent_tick_usec = -1; TreeItem *parent = nullptr; List> parents; for (const BehaviorTreeData::TaskData &task_data : p_data.tasks) { @@ -24,24 +63,28 @@ void BehaviorTreeView::update_tree(const BehaviorTreeData &p_data) { } TreeItem *item = tree->create_item(parent); + // Do this first because it resets properties of the cell... + item->set_cell_mode(0, TreeItem::CELL_MODE_CUSTOM); + + item->set_metadata(0, task_data.id); item->set_text(0, task_data.name); item->set_icon(0, LimboUtility::get_singleton()->get_task_icon(task_data.type_name)); + item->set_text(1, rtos(Math::snapped(task_data.elapsed_time, 0.01))); - // First task in list is the root task and has most recent tick time. - if (recent_tick_usec == -1) { - recent_tick_usec = task_data.last_tick_usec; + if (task_data.status == BTTask::SUCCESS) { + item->set_custom_draw(0, this, SNAME("_draw_success_status")); + } else if (task_data.status == BTTask::FAILURE) { + item->set_custom_draw(0, this, SNAME("_draw_failure_status")); + } else if (task_data.status == BTTask::RUNNING) { + item->set_custom_draw(0, this, SNAME("_draw_running_status")); } - // Item BG color depends on age of the task status. - int timediff = recent_tick_usec - task_data.last_tick_usec; - if (timediff <= 300000) { - float alpha = (300000 - timediff) / 300000.0; - if (task_data.status == BTTask::SUCCESS) { - item->set_custom_bg_color(0, Color(0, 0.5, 0, alpha)); - } else if (task_data.status == BTTask::FAILURE) { - item->set_custom_bg_color(0, Color(0.5, 0, 0, alpha)); - } else if (task_data.status == BTTask::RUNNING) { - item->set_custom_bg_color(0, Color(0.5, 0.5, 0, alpha)); - } + + if (task_data.id == selected_id) { + tree->set_selected(item); + } + + if (collapsed_ids.has(task_data.id)) { + item->set_collapsed(true); } // Add in front of parents stack if it expects children. @@ -53,13 +96,40 @@ void BehaviorTreeView::update_tree(const BehaviorTreeData &p_data) { void BehaviorTreeView::clear() { tree->clear(); + collapsed_ids.clear(); +} + +void BehaviorTreeView::_bind_methods() { + ClassDB::bind_method(D_METHOD("_draw_running_status"), &BehaviorTreeView::_draw_running_status); + ClassDB::bind_method(D_METHOD("_draw_success_status"), &BehaviorTreeView::_draw_success_status); + ClassDB::bind_method(D_METHOD("_draw_failure_status"), &BehaviorTreeView::_draw_failure_status); + ClassDB::bind_method(D_METHOD("_item_collapsed"), &BehaviorTreeView::_item_collapsed); } BehaviorTreeView::BehaviorTreeView() { tree = memnew(Tree); add_child(tree); - tree->set_columns(1); + tree->set_columns(2); tree->set_column_expand(0, true); + tree->set_column_expand(1, false); + tree->set_column_custom_minimum_width(1, 40.0 * EDSCALE); tree->set_anchor(SIDE_RIGHT, ANCHOR_END); tree->set_anchor(SIDE_BOTTOM, ANCHOR_END); + + sbf_running.set_border_color(Color(1.0, 1.0, 0.0)); + sbf_running.set_bg_color(Color(1.0, 1.0, 0, 0.1)); + sbf_running.set_border_width(SIDE_LEFT, 4.0); + sbf_running.set_border_width(SIDE_RIGHT, 4.0); + + sbf_success.set_border_color(Color(0.0, 0.8, 0.0)); + sbf_success.set_bg_color(Color(0.0, 0.8, 0.0, 0.1)); + sbf_success.set_border_width(SIDE_LEFT, 4.0); + sbf_success.set_border_width(SIDE_RIGHT, 4.0); + + sbf_failure.set_border_color(Color(1.0, 0.0, 0.0)); + sbf_failure.set_bg_color(Color(1.0, 0.0, 0.0, 0.1)); + sbf_failure.set_border_width(SIDE_LEFT, 4.0); + sbf_failure.set_border_width(SIDE_RIGHT, 4.0); + + tree->connect(SNAME("item_collapsed"), callable_mp(this, &BehaviorTreeView::_item_collapsed)); } diff --git a/debugger/behavior_tree_view.h b/debugger/behavior_tree_view.h index 11e3675..9a9a024 100644 --- a/debugger/behavior_tree_view.h +++ b/debugger/behavior_tree_view.h @@ -8,18 +8,31 @@ #include "core/object/object.h" #include "scene/gui/control.h" #include "scene/gui/tree.h" +#include "scene/resources/style_box.h" class BehaviorTreeView : public Control { GDCLASS(BehaviorTreeView, Control); private: Tree *tree; + StyleBoxFlat sbf_running; + StyleBoxFlat sbf_success; + StyleBoxFlat sbf_failure; + Vector collapsed_ids; + + void _draw_success_status(Object *p_obj, Rect2 p_rect); + void _draw_running_status(Object *p_obj, Rect2 p_rect); + void _draw_failure_status(Object *p_obj, Rect2 p_rect); + void _item_collapsed(Object *p_obj); + +protected: + static void _bind_methods(); public: - BehaviorTreeView(); - void update_tree(const BehaviorTreeData &p_data); void clear(); + + BehaviorTreeView(); }; #endif // BEHAVIOR_TREE_VIEW \ No newline at end of file diff --git a/register_types.cpp b/register_types.cpp index c10602e..7b30fb9 100644 --- a/register_types.cpp +++ b/register_types.cpp @@ -73,6 +73,7 @@ #include "bt/decorators/bt_time_limit.h" #include "core/os/memory.h" #include "core/string/print_string.h" +#include "debugger/behavior_tree_view.h" #include "limbo_hsm.h" #include "limbo_state.h" #include "limbo_string_names.h" @@ -167,6 +168,8 @@ void initialize_limboai_module(ModuleInitializationLevel p_level) { GDREGISTER_CLASS(BBVector3Array); GDREGISTER_CLASS(BBVariant); + GDREGISTER_CLASS(BehaviorTreeView); + _limbo_utility = memnew(LimboUtility); GDREGISTER_CLASS(LimboUtility);