diff --git a/doc_classes/BehaviorTreeView.xml b/doc_classes/BehaviorTreeView.xml index e85d6d5..6e97ecc 100644 --- a/doc_classes/BehaviorTreeView.xml +++ b/doc_classes/BehaviorTreeView.xml @@ -17,4 +17,9 @@ + + + Minimum delay between two updates (in milliseconds). Set to higher values for a lower CPU load. + + diff --git a/editor/debugger/behavior_tree_data.cpp b/editor/debugger/behavior_tree_data.cpp index 9626bb4..0ec8ef1 100644 --- a/editor/debugger/behavior_tree_data.cpp +++ b/editor/debugger/behavior_tree_data.cpp @@ -25,7 +25,6 @@ Array BehaviorTreeData::serialize(const Ref &p_tree_instance, const Node // Flatten tree into list depth first List> stack; stack.push_back(p_tree_instance); - int id = 0; while (stack.size()) { Ref task = stack[0]; stack.pop_front(); @@ -41,7 +40,7 @@ Array BehaviorTreeData::serialize(const Ref &p_tree_instance, const Node script_path = s->get_path(); } - arr.push_back(id); + arr.push_back(task->get_instance_id()); arr.push_back(task->get_task_name()); arr.push_back(!task->get_custom_name().is_empty()); arr.push_back(num_children); @@ -49,8 +48,6 @@ Array BehaviorTreeData::serialize(const Ref &p_tree_instance, const Node arr.push_back(task->get_elapsed_time()); arr.push_back(task->get_class()); arr.push_back(script_path); - - id += 1; } return arr; @@ -89,7 +86,6 @@ Ref BehaviorTreeData::create_from_tree_instance(const Ref> stack; stack.push_back(p_tree_instance); - int id = 0; while (stack.size()) { Ref task = stack[0]; stack.pop_front(); @@ -106,7 +102,7 @@ Ref BehaviorTreeData::create_from_tree_instance(const Reftasks.push_back(TaskData( - id, + task->get_instance_id(), task->get_task_name(), !task->get_custom_name().is_empty(), num_children, @@ -114,7 +110,6 @@ Ref BehaviorTreeData::create_from_tree_instance(const Refget_elapsed_time(), task->get_class(), script_path)); - id += 1; } return data; } diff --git a/editor/debugger/behavior_tree_data.h b/editor/debugger/behavior_tree_data.h index e5c2335..0d5ee9e 100644 --- a/editor/debugger/behavior_tree_data.h +++ b/editor/debugger/behavior_tree_data.h @@ -22,7 +22,7 @@ protected: public: struct TaskData { - int id = 0; + uint64_t id = 0; String name; bool is_custom_name = false; int num_children = 0; @@ -31,7 +31,7 @@ public: String type_name; String script_path; - TaskData(int p_id, const String &p_name, bool p_is_custom_name, int p_num_children, int p_status, double p_elapsed_time, const String &p_type_name, const String &p_script_path) { + TaskData(uint64_t p_id, const String &p_name, bool p_is_custom_name, int p_num_children, int p_status, double p_elapsed_time, const String &p_type_name, const String &p_script_path) { id = p_id; name = p_name; is_custom_name = p_is_custom_name; diff --git a/editor/debugger/behavior_tree_view.cpp b/editor/debugger/behavior_tree_view.cpp index e98a557..c8e05d2 100644 --- a/editor/debugger/behavior_tree_view.cpp +++ b/editor/debugger/behavior_tree_view.cpp @@ -22,6 +22,7 @@ #include "core/math/color.h" #include "core/math/math_defs.h" #include "core/object/callable_method_pointer.h" +#include "core/os/time.h" #include "core/typedefs.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" @@ -30,6 +31,7 @@ #ifdef LIMBOAI_GDEXTENSION #include +#include #endif // LIMBOAI_GDEXTENSION void BehaviorTreeView::_draw_running_status(Object *p_obj, Rect2 p_rect) { @@ -52,7 +54,7 @@ void BehaviorTreeView::_item_collapsed(Object *p_obj) { if (!item) { return; } - int id = item->get_metadata(0); + uint64_t id = item->get_metadata(0); bool collapsed = item->is_collapsed(); if (!collapsed_ids.has(id) && collapsed) { collapsed_ids.push_back(item->get_metadata(0)); @@ -69,69 +71,137 @@ double BehaviorTreeView::_get_editor_scale() const { } } +inline void _item_set_elapsed_time(TreeItem *p_item, double p_elapsed) { + p_item->set_text(2, rtos(Math::snapped(p_elapsed, 0.01)).pad_decimals(2)); +} + void BehaviorTreeView::update_tree(const Ref &p_data) { + update_data = p_data; + update_pending = true; + _notification(NOTIFICATION_PROCESS); +} + +void BehaviorTreeView::_update_tree(const Ref &p_data) { // Remember selected. - int selected_id = -1; + uint64_t selected_id = 0; if (tree->get_selected()) { selected_id = tree->get_selected()->get_metadata(0); } - tree->clear(); - TreeItem *parent = nullptr; - List> parents; - for (const BehaviorTreeData::TaskData &task_data : p_data->tasks) { - // Figure out parent. - parent = nullptr; - if (parents.size()) { - Pair &p = parents[0]; - parent = p.first; - if (!(--p.second)) { - // No children left, remove it. - parents.pop_front(); + if (last_root_id != 0 && p_data->tasks.size() > 0 && last_root_id == (uint64_t)p_data->tasks[0].id) { + // * Update tree. + // ! Update routine is built on assumption that the behavior tree does NOT mutate. With little work it could detect mutations. + + TreeItem *item = tree->get_root(); + int idx = 0; + while (item) { + ERR_FAIL_COND(idx >= p_data->tasks.size()); + + const BTTask::Status current_status = (BTTask::Status)p_data->tasks[idx].status; + const BTTask::Status last_status = VariantCaster::cast(item->get_metadata(1)); + const bool status_changed = last_status != p_data->tasks[idx].status; + + if (status_changed) { + item->set_metadata(1, current_status); + if (current_status == BTTask::SUCCESS) { + item->set_custom_draw(0, this, LW_NAME(_draw_success_status)); + item->set_icon(1, theme_cache.icon_success); + } else if (current_status == BTTask::FAILURE) { + item->set_custom_draw(0, this, LW_NAME(_draw_failure_status)); + item->set_icon(1, theme_cache.icon_failure); + } else if (current_status == BTTask::RUNNING) { + item->set_custom_draw(0, this, LW_NAME(_draw_running_status)); + item->set_icon(1, theme_cache.icon_running); + } else { + item->set_custom_draw(0, this, LW_NAME(_draw_fresh)); + item->set_icon(1, nullptr); + } } + + if (status_changed || current_status == BTTask::RUNNING) { + _item_set_elapsed_time(item, p_data->tasks[idx].elapsed_time); + } + + if (item->get_first_child()) { + item = item->get_first_child(); + } else if (item->get_next()) { + item = item->get_next(); + } else { + while (item) { + item = item->get_parent(); + if (item && item->get_next()) { + item = item->get_next(); + break; + } + } + } + + idx += 1; } + ERR_FAIL_COND(idx != p_data->tasks.size()); + } else { + // * Create new tree. - 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_cell_mode(1, TreeItem::CELL_MODE_ICON); + last_root_id = p_data->tasks.size() > 0 ? p_data->tasks[0].id : 0; - item->set_metadata(0, task_data.id); + tree->clear(); + TreeItem *parent = nullptr; + List> parents; + for (const BehaviorTreeData::TaskData &task_data : p_data->tasks) { + // Figure out parent. + parent = nullptr; + if (parents.size()) { + Pair &p = parents[0]; + parent = p.first; + if (!(--p.second)) { + // No children left, remove it. + parents.pop_front(); + } + } - item->set_text(0, task_data.name); - if (task_data.is_custom_name) { - item->set_custom_font(0, theme_cache.font_custom_name); - } + 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_cell_mode(1, TreeItem::CELL_MODE_ICON); - item->set_text_alignment(2, HORIZONTAL_ALIGNMENT_RIGHT); - item->set_text(2, rtos(Math::snapped(task_data.elapsed_time, 0.01)).pad_decimals(2)); + item->set_metadata(0, task_data.id); + item->set_metadata(1, task_data.status); - String cors = (task_data.script_path.is_empty()) ? task_data.type_name : task_data.script_path; - item->set_icon(0, LimboUtility::get_singleton()->get_task_icon(cors)); - item->set_icon_max_width(0, 16 * _get_editor_scale()); // Force user icon size. + item->set_text(0, task_data.name); + if (task_data.is_custom_name) { + item->set_custom_font(0, theme_cache.font_custom_name); + } - if (task_data.status == BTTask::SUCCESS) { - item->set_custom_draw(0, this, LW_NAME(_draw_success_status)); - item->set_icon(1, theme_cache.icon_success); - } else if (task_data.status == BTTask::FAILURE) { - item->set_custom_draw(0, this, LW_NAME(_draw_failure_status)); - item->set_icon(1, theme_cache.icon_failure); - } else if (task_data.status == BTTask::RUNNING) { - item->set_custom_draw(0, this, LW_NAME(_draw_running_status)); - item->set_icon(1, theme_cache.icon_running); - } + item->set_text_alignment(2, HORIZONTAL_ALIGNMENT_RIGHT); + _item_set_elapsed_time(item, task_data.elapsed_time); - if (task_data.id == selected_id) { - tree->set_selected(item, 0); - } + String cors = (task_data.script_path.is_empty()) ? task_data.type_name : task_data.script_path; + item->set_icon(0, LimboUtility::get_singleton()->get_task_icon(cors)); + item->set_icon_max_width(0, 16 * _get_editor_scale()); // Force user icon size. - if (collapsed_ids.has(task_data.id)) { - item->set_collapsed(true); - } + if (task_data.status == BTTask::SUCCESS) { + item->set_custom_draw(0, this, LW_NAME(_draw_success_status)); + item->set_icon(1, theme_cache.icon_success); + } else if (task_data.status == BTTask::FAILURE) { + item->set_custom_draw(0, this, LW_NAME(_draw_failure_status)); + item->set_icon(1, theme_cache.icon_failure); + } else if (task_data.status == BTTask::RUNNING) { + item->set_custom_draw(0, this, LW_NAME(_draw_running_status)); + item->set_icon(1, theme_cache.icon_running); + } - // Add in front of parents stack if it expects children. - if (task_data.num_children) { - parents.push_front(Pair(item, task_data.num_children)); + if (task_data.id == selected_id) { + tree->set_selected(item, 0); + } + + if (collapsed_ids.has(task_data.id)) { + item->set_collapsed(true); + } + + // Add in front of parents stack if children are expected. + if (task_data.num_children) { + parents.push_front(Pair(item, task_data.num_children)); + } } } } @@ -139,6 +209,7 @@ void BehaviorTreeView::update_tree(const Ref &p_data) { void BehaviorTreeView::clear() { tree->clear(); collapsed_ids.clear(); + last_root_id = 0; } void BehaviorTreeView::_do_update_theme_item_cache() { @@ -197,6 +268,17 @@ void BehaviorTreeView::_notification(int p_what) { case NOTIFICATION_THEME_CHANGED: { _do_update_theme_item_cache(); } break; + case NOTIFICATION_VISIBILITY_CHANGED: { + set_process(is_visible_in_tree()); + } break; + case NOTIFICATION_PROCESS: { + int ticks_msec = Time::get_singleton()->get_ticks_msec(); + if (update_pending && (ticks_msec - last_update_msec) >= update_interval_msec) { + _update_tree(update_data); + update_pending = false; + last_update_msec = ticks_msec; + } + } break; } } @@ -206,6 +288,10 @@ void BehaviorTreeView::_bind_methods() { ClassDB::bind_method(D_METHOD("_draw_failure_status"), &BehaviorTreeView::_draw_failure_status); ClassDB::bind_method(D_METHOD("_item_collapsed"), &BehaviorTreeView::_item_collapsed); ClassDB::bind_method(D_METHOD("update_tree", "p_behavior_tree_data"), &BehaviorTreeView::update_tree); + + ClassDB::bind_method(D_METHOD("set_update_interval_msec", "p_milliseconds"), &BehaviorTreeView::set_update_interval_msec); + ClassDB::bind_method(D_METHOD("get_update_interval_msec"), &BehaviorTreeView::get_update_interval_msec); + ADD_PROPERTY(PropertyInfo(Variant::INT, "update_interval_msec"), "set_update_interval_msec", "get_update_interval_msec"); } BehaviorTreeView::BehaviorTreeView() { diff --git a/editor/debugger/behavior_tree_view.h b/editor/debugger/behavior_tree_view.h index 49205f7..3194348 100644 --- a/editor/debugger/behavior_tree_view.h +++ b/editor/debugger/behavior_tree_view.h @@ -48,14 +48,23 @@ private: Ref font_custom_name; } theme_cache; - Vector collapsed_ids; + Vector collapsed_ids; + uint64_t last_root_id = 0; + + int last_update_msec = 0; + int update_interval_msec = 0; + Ref update_data; + bool update_pending = false; 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 _draw_fresh(Object *p_obj, Rect2 p_rect) {} void _item_collapsed(Object *p_obj); double _get_editor_scale() const; + void _update_tree(const Ref &p_data); + protected: void _do_update_theme_item_cache(); @@ -64,8 +73,11 @@ protected: static void _bind_methods(); public: - void update_tree(const Ref &p_data); void clear(); + void update_tree(const Ref &p_data); + + void set_update_interval_msec(int p_milliseconds) { update_interval_msec = p_milliseconds; } + int get_update_interval_msec() const { return update_interval_msec; } BehaviorTreeView(); }; diff --git a/editor/debugger/limbo_debugger_plugin.cpp b/editor/debugger/limbo_debugger_plugin.cpp index 1067669..ad66841 100644 --- a/editor/debugger/limbo_debugger_plugin.cpp +++ b/editor/debugger/limbo_debugger_plugin.cpp @@ -16,6 +16,7 @@ #include "../../bt/behavior_tree.h" #include "../../editor/debugger/behavior_tree_data.h" #include "../../editor/debugger/behavior_tree_view.h" +#include "../../util/limbo_compat.h" #include "../../util/limbo_utility.h" #include "limbo_debugger.h" @@ -29,6 +30,7 @@ #include "core/string/ustring.h" #include "core/variant/array.h" #include "editor/editor_interface.h" +#include "editor/editor_paths.h" #include "editor/editor_scale.h" #include "editor/filesystem_dock.h" #include "editor/plugins/editor_debugger_plugin.h" @@ -37,16 +39,20 @@ #include "scene/gui/item_list.h" #include "scene/gui/label.h" #include "scene/gui/line_edit.h" +#include "scene/gui/separator.h" #include "scene/gui/split_container.h" #include "scene/gui/tab_container.h" #include "scene/gui/texture_rect.h" #endif // LIMBOAI_MODULE #ifdef LIMBOAI_GDEXTENSION +#include #include +#include #include #include #include +#include #endif // LIMBOAI_GDEXTENSION //**** LimboDebuggerTab @@ -180,6 +186,23 @@ void LimboDebuggerTab::_notification(int p_what) { resource_header->connect(LW_NAME(pressed), callable_mp(this, &LimboDebuggerTab::_resource_header_pressed)); filter_players->connect(LW_NAME(text_changed), callable_mp(this, &LimboDebuggerTab::_filter_changed)); bt_player_list->connect(LW_NAME(item_selected), callable_mp(this, &LimboDebuggerTab::_bt_selected)); + update_interval->connect("value_changed", callable_mp(bt_view, &BehaviorTreeView::set_update_interval_msec)); + + Ref cf; + cf.instantiate(); + String conf_path = PROJECT_CONFIG_FILE(); + if (cf->load(conf_path) == OK) { + Variant value = cf->get_value("debugger", "update_interval_msec", 0); + update_interval->set_value(value); + } + } break; + case NOTIFICATION_EXIT_TREE: { + Ref cf; + cf.instantiate(); + String conf_path = PROJECT_CONFIG_FILE(); + cf->load(conf_path); + cf->set_value("debugger", "update_interval_msec", update_interval->get_value()); + cf->save(conf_path); } break; case NOTIFICATION_THEME_CHANGED: { alert_icon->set_texture(get_theme_icon(LW_NAME(StatusWarning), LW_NAME(EditorIcons))); @@ -195,7 +218,6 @@ void LimboDebuggerTab::setup(Ref p_session, CompatWindowW if (p_wrapper->is_window_available()) { make_floating = memnew(CompatScreenSelect); make_floating->set_flat(true); - make_floating->set_h_size_flags(Control::SIZE_EXPAND | Control::SIZE_SHRINK_END); make_floating->set_tooltip_text(TTR("Make the LimboAI Debugger floating.")); make_floating->connect(LW_NAME(request_open_in_screen), callable_mp(window_wrapper, &CompatWindowWrapper::enable_window_on_screen).bind(true)); toolbar->add_child(make_floating); @@ -221,6 +243,22 @@ LimboDebuggerTab::LimboDebuggerTab() { resource_header->set_tooltip_text(TTR("Debugged BehaviorTree resource.\nClick to open.")); resource_header->set_disabled(true); + Label *interval_label = memnew(Label); + toolbar->add_child(interval_label); + interval_label->set_text(TTR("Update Interval:")); + interval_label->set_h_size_flags(SIZE_EXPAND | SIZE_SHRINK_END); + + update_interval = memnew(EditorSpinSlider); + toolbar->add_child(update_interval); + update_interval->set_min(0); + update_interval->set_max(1000); + update_interval->set_step(1.0); + update_interval->set_suffix("ms"); + update_interval->set_custom_minimum_size(Vector2(100 * EDSCALE, 0)); + + VSeparator *sep = memnew(VSeparator); + toolbar->add_child(sep); + hsc = memnew(HSplitContainer); hsc->set_h_size_flags(Control::SIZE_EXPAND_FILL); hsc->set_v_size_flags(Control::SIZE_EXPAND_FILL); diff --git a/editor/debugger/limbo_debugger_plugin.h b/editor/debugger/limbo_debugger_plugin.h index 4c95432..bb02e45 100644 --- a/editor/debugger/limbo_debugger_plugin.h +++ b/editor/debugger/limbo_debugger_plugin.h @@ -22,6 +22,7 @@ #include "core/object/class_db.h" #include "core/object/object.h" #include "core/typedefs.h" +#include "editor/gui/editor_spin_slider.h" #include "editor/plugins/editor_debugger_plugin.h" #include "editor/window_wrapper.h" #include "scene/gui/box_container.h" @@ -35,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -64,6 +66,7 @@ private: LineEdit *filter_players = nullptr; Button *resource_header = nullptr; Button *make_floating = nullptr; + EditorSpinSlider *update_interval = nullptr; CompatWindowWrapper *window_wrapper = nullptr; void _reset_controls(); diff --git a/util/limbo_string_names.cpp b/util/limbo_string_names.cpp index 097e008..6dd6092 100644 --- a/util/limbo_string_names.cpp +++ b/util/limbo_string_names.cpp @@ -28,6 +28,7 @@ LimboStringNames *LimboStringNames::singleton = nullptr; LimboStringNames::LimboStringNames() { _draw_failure_status = SN("_draw_failure_status"); + _draw_fresh = SN("_draw_fresh"); _draw_probability = SN("_draw_probability"); _draw_running_status = SN("_draw_running_status"); _draw_success_status = SN("_draw_success_status"); diff --git a/util/limbo_string_names.h b/util/limbo_string_names.h index 35aeace..6111c0a 100644 --- a/util/limbo_string_names.h +++ b/util/limbo_string_names.h @@ -42,6 +42,7 @@ public: _FORCE_INLINE_ static LimboStringNames *get_singleton() { return singleton; } StringName _draw_failure_status; + StringName _draw_fresh; StringName _draw_probability; StringName _draw_running_status; StringName _draw_success_status;