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;
]