Debugger: Add node filter and improve UX

This commit is contained in:
Serhii Snitsaruk 2023-04-15 09:01:37 +02:00
parent c362001ed4
commit 4e0305de51
6 changed files with 150 additions and 83 deletions

View File

@ -5,7 +5,9 @@
//// BehaviorTreeData //// BehaviorTreeData
BehaviorTreeData::BehaviorTreeData(const Ref<BTTask> &p_instance) { BehaviorTreeData::BehaviorTreeData(const Ref<BTTask> &p_instance, const NodePath &p_player_path) {
bt_player_path = p_player_path;
// Flatten tree into list depth first // Flatten tree into list depth first
List<Ref<BTTask>> stack; List<Ref<BTTask>> stack;
stack.push_back(p_instance); stack.push_back(p_instance);
@ -31,6 +33,7 @@ BehaviorTreeData::BehaviorTreeData(const Ref<BTTask> &p_instance) {
} }
void BehaviorTreeData::serialize(Array &p_arr) { void BehaviorTreeData::serialize(Array &p_arr) {
p_arr.push_back(bt_player_path);
for (const TaskData &td : tasks) { for (const TaskData &td : tasks) {
p_arr.push_back(td.id); p_arr.push_back(td.id);
p_arr.push_back(td.name); p_arr.push_back(td.name);
@ -43,10 +46,12 @@ void BehaviorTreeData::serialize(Array &p_arr) {
void BehaviorTreeData::deserialize(const Array &p_arr) { void BehaviorTreeData::deserialize(const Array &p_arr) {
ERR_FAIL_COND(tasks.size() != 0); ERR_FAIL_COND(tasks.size() != 0);
ERR_FAIL_COND(p_arr.size() < 1);
int idx = 0; ERR_FAIL_COND(p_arr[0].get_type() != Variant::NODE_PATH);
while (p_arr.size() > idx) { bt_player_path = p_arr[0];
ERR_FAIL_COND(p_arr.size() < 6); int idx = 1;
while (p_arr.size() > idx + 1) {
ERR_FAIL_COND(p_arr.size() < idx + 6);
ERR_FAIL_COND(p_arr[idx].get_type() != Variant::INT); 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 + 1].get_type() != Variant::STRING);
ERR_FAIL_COND(p_arr[idx + 2].get_type() != Variant::INT); ERR_FAIL_COND(p_arr[idx + 2].get_type() != Variant::INT);

View File

@ -31,11 +31,12 @@ public:
}; };
List<TaskData> tasks; List<TaskData> tasks;
NodePath bt_player_path;
void serialize(Array &p_arr); void serialize(Array &p_arr);
void deserialize(const Array &p_arr); void deserialize(const Array &p_arr);
BehaviorTreeData(const Ref<BTTask> &p_instance); BehaviorTreeData(const Ref<BTTask> &p_instance, const NodePath &p_player_path);
BehaviorTreeData() {} BehaviorTreeData() {}
}; };

View File

@ -42,13 +42,13 @@ void LimboDebugger::deinitialize() {
#ifdef DEBUG_ENABLED #ifdef DEBUG_ENABLED
Error LimboDebugger::parse_message(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured) { Error LimboDebugger::parse_message(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured) {
r_captured = true; r_captured = true;
if (p_msg == "track_bt_updates") { if (p_msg == "track_bt_player") {
singleton->_track_tree(p_args[0]); singleton->_track_tree(p_args[0]);
} else if (p_msg == "untrack_bt_updates") { } else if (p_msg == "untrack_bt_player") {
// unregister bt for updates singleton->_untrack_tree();
} else if (p_msg == "start_session") { } else if (p_msg == "start_session") {
singleton->session_active = true; singleton->session_active = true;
singleton->_send_active_behavior_trees(); singleton->_send_active_bt_players();
} else if (p_msg == "stop_session") { } else if (p_msg == "stop_session") {
singleton->session_active = false; singleton->session_active = false;
} else { } else {
@ -62,11 +62,12 @@ void LimboDebugger::register_bt_instance(Ref<BTTask> p_instance, NodePath p_path
active_trees.insert(p_path, p_instance); active_trees.insert(p_path, p_instance);
if (session_active) { if (session_active) {
_send_active_behavior_trees(); _send_active_bt_players();
} }
} }
void LimboDebugger::unregister_bt_instance(Ref<BTTask> p_instance, NodePath p_path) { void LimboDebugger::unregister_bt_instance(Ref<BTTask> p_instance, NodePath p_path) {
ERR_FAIL_COND(p_path.is_empty());
ERR_FAIL_COND(!active_trees.has(p_path)); ERR_FAIL_COND(!active_trees.has(p_path));
if (tracked_tree == p_path) { if (tracked_tree == p_path) {
@ -75,7 +76,7 @@ void LimboDebugger::unregister_bt_instance(Ref<BTTask> p_instance, NodePath p_pa
active_trees.erase(p_path); active_trees.erase(p_path);
if (session_active) { if (session_active) {
_send_active_behavior_trees(); _send_active_bt_players();
} }
} }
@ -115,12 +116,12 @@ void LimboDebugger::_untrack_tree() {
} }
} }
void LimboDebugger::_send_active_behavior_trees() { void LimboDebugger::_send_active_bt_players() {
Array arr; Array arr;
for (KeyValue<NodePath, Ref<BTTask>> kv : active_trees) { for (KeyValue<NodePath, Ref<BTTask>> kv : active_trees) {
arr.append(kv.key); arr.append(kv.key);
} }
EngineDebugger::get_singleton()->send_message("limboai:active_behavior_trees", arr); EngineDebugger::get_singleton()->send_message("limboai:active_bt_players", arr);
} }
void LimboDebugger::_on_bt_updated(int _status, NodePath p_path) { void LimboDebugger::_on_bt_updated(int _status, NodePath p_path) {
@ -128,7 +129,7 @@ void LimboDebugger::_on_bt_updated(int _status, NodePath p_path) {
return; return;
} }
Array arr; Array arr;
BehaviorTreeData(active_trees.get(tracked_tree)).serialize(arr); BehaviorTreeData(active_trees.get(tracked_tree), tracked_tree).serialize(arr);
EngineDebugger::get_singleton()->send_message("limboai:bt_update", arr); EngineDebugger::get_singleton()->send_message("limboai:bt_update", arr);
} }
@ -137,7 +138,7 @@ void LimboDebugger::_on_state_updated(float _delta, NodePath p_path) {
return; return;
} }
Array arr; Array arr;
BehaviorTreeData(active_trees.get(tracked_tree)).serialize(arr); BehaviorTreeData(active_trees.get(tracked_tree), tracked_tree).serialize(arr);
EngineDebugger::get_singleton()->send_message("limboai:bt_update", arr); EngineDebugger::get_singleton()->send_message("limboai:bt_update", arr);
} }

View File

@ -31,7 +31,7 @@ private:
void _track_tree(NodePath p_path); void _track_tree(NodePath p_path);
void _untrack_tree(); void _untrack_tree();
void _send_active_behavior_trees(); void _send_active_bt_players();
void _on_bt_updated(int status, NodePath p_path); void _on_bt_updated(int status, NodePath p_path);
void _on_state_updated(float _delta, NodePath p_path); void _on_state_updated(float _delta, NodePath p_path);

View File

@ -16,65 +16,106 @@
#include "scene/gui/control.h" #include "scene/gui/control.h"
#include "scene/gui/item_list.h" #include "scene/gui/item_list.h"
#include "scene/gui/label.h" #include "scene/gui/label.h"
#include "scene/gui/line_edit.h"
#include "scene/gui/split_container.h" #include "scene/gui/split_container.h"
#include "scene/gui/texture_rect.h" #include "scene/gui/texture_rect.h"
/////////////////////// LimboDebuggerTab /////////////////////// LimboDebuggerTab
void LimboDebuggerTab::start_session() { void LimboDebuggerTab::start_session() {
bt_list->clear(); bt_player_list->clear();
bt_view->clear(); bt_view->clear();
info_box->hide(); alert_box->hide();
hsc->show(); info_message->set_text(TTR("Pick a player from the list to display behavior tree."));
message->hide(); info_message->show();
session->send_message("limboai:start_session", Array()); session->send_message("limboai:start_session", Array());
} }
void LimboDebuggerTab::stop_session() { void LimboDebuggerTab::stop_session() {
hsc->hide(); bt_player_list->clear();
message->show(); bt_view->clear();
alert_box->hide();
info_message->set_text(TTR("Run project to start debugging."));
info_message->show();
session->send_message("limboai:stop_session", Array()); session->send_message("limboai:stop_session", Array());
} }
void LimboDebuggerTab::update_bt_list(const Array &p_node_paths) { void LimboDebuggerTab::update_active_bt_players(const Array &p_node_paths) {
active_bt_players.clear();
for (int i = 0; i < p_node_paths.size(); i++) {
active_bt_players.push_back(p_node_paths[i]);
}
_update_bt_player_list(active_bt_players, filter_players->get_text());
}
String LimboDebuggerTab::get_selected_bt_player() {
if (!bt_player_list->is_anything_selected()) {
return "";
}
return bt_player_list->get_item_text(bt_player_list->get_selected_items()[0]);
}
void LimboDebuggerTab::update_behavior_tree(const BehaviorTreeData &p_data) {
bt_view->update_tree(p_data);
info_message->hide();
}
void LimboDebuggerTab::_show_alert(const String &p_message) {
alert_message->set_text(p_message);
alert_icon->set_texture(get_theme_icon(SNAME("NodeInfo"), SNAME("EditorIcons")));
alert_box->set_visible(!p_message.is_empty());
}
void LimboDebuggerTab::_update_bt_player_list(const List<String> &p_node_paths, const String &p_filter) {
// Remember selected item. // Remember selected item.
String selected_bt = ""; String selected_player = "";
if (bt_list->is_anything_selected()) { if (bt_player_list->is_anything_selected()) {
selected_bt = bt_list->get_item_text(bt_list->get_selected_items().get(0)); selected_player = bt_player_list->get_item_text(bt_player_list->get_selected_items().get(0));
} }
bt_list->clear(); bt_player_list->clear();
int select_idx = -1; int select_idx = -1;
for (int i = 0; i < p_node_paths.size(); i++) { bool selection_filtered_out = false;
bt_list->add_item(p_node_paths[i]); for (const String &p : p_node_paths) {
// Make item text shortened from the left, e.g ".../Agent/BTPlayer". if (p_filter.is_empty() || p.contains(p_filter)) {
bt_list->set_item_text_direction(i, TEXT_DIRECTION_RTL); int idx = bt_player_list->add_item(p);
if (p_node_paths[i] == selected_bt) { // Make item text shortened from the left, e.g ".../Agent/BTPlayer".
select_idx = i; bt_player_list->set_item_text_direction(idx, TEXT_DIRECTION_RTL);
if (p == selected_player) {
select_idx = idx;
}
} else if (p == selected_player) {
selection_filtered_out = true;
} }
} }
// Restore selected item. // Restore selected item.
if (select_idx > -1) { if (select_idx > -1) {
bt_list->select(select_idx); bt_player_list->select(select_idx);
} else if (!selected_bt.is_empty()) { } else if (!selected_player.is_empty()) {
_set_info_message(TTR("Node instance is gone")); if (selection_filtered_out) {
session->send_message("limboai:untrack_bt_player", Array());
bt_view->clear();
_show_alert("");
} else {
_show_alert("BehaviorTree player is no longer present.");
}
} }
} }
void LimboDebuggerTab::_set_info_message(const String &p_message) {
info_message->set_text(p_message);
info_icon->set_texture(get_theme_icon(SNAME("NodeInfo"), SNAME("EditorIcons")));
info_box->set_visible(!p_message.is_empty());
}
void LimboDebuggerTab::_bt_selected(int p_idx) { void LimboDebuggerTab::_bt_selected(int p_idx) {
info_box->hide(); alert_box->hide();
bt_view->clear(); bt_view->clear();
NodePath path = bt_list->get_item_text(p_idx); info_message->set_text(TTR("Waiting for behavior tree update."));
info_message->show();
NodePath path = bt_player_list->get_item_text(p_idx);
Array data; Array data;
data.push_back(path); data.push_back(path);
session->send_message("limboai:track_bt_updates", data); session->send_message("limboai:track_bt_player", data);
}
void LimboDebuggerTab::_filter_changed(String p_text) {
_update_bt_player_list(active_bt_players, p_text);
} }
LimboDebuggerTab::LimboDebuggerTab(Ref<EditorDebuggerSession> p_session) { LimboDebuggerTab::LimboDebuggerTab(Ref<EditorDebuggerSession> p_session) {
@ -83,39 +124,49 @@ LimboDebuggerTab::LimboDebuggerTab(Ref<EditorDebuggerSession> p_session) {
hsc = memnew(HSplitContainer); hsc = memnew(HSplitContainer);
add_child(hsc); add_child(hsc);
bt_list = memnew(ItemList); VBoxContainer *list_box = memnew(VBoxContainer);
hsc->add_child(bt_list); hsc->add_child(list_box);
bt_list->set_custom_minimum_size(Size2(240.0 * EDSCALE, 0.0));
bt_list->connect(SNAME("item_selected"), callable_mp(this, &LimboDebuggerTab::_bt_selected)); filter_players = memnew(LineEdit);
filter_players->set_placeholder(TTR("Filter Players"));
filter_players->connect(SNAME("text_changed"), callable_mp(this, &LimboDebuggerTab::_filter_changed));
list_box->add_child(filter_players);
bt_player_list = memnew(ItemList);
bt_player_list->set_custom_minimum_size(Size2(240.0 * EDSCALE, 0.0));
bt_player_list->set_h_size_flags(SIZE_FILL);
bt_player_list->set_v_size_flags(SIZE_EXPAND_FILL);
bt_player_list->connect(SNAME("item_selected"), callable_mp(this, &LimboDebuggerTab::_bt_selected));
list_box->add_child(bt_player_list);
view_box = memnew(VBoxContainer); view_box = memnew(VBoxContainer);
{ hsc->add_child(view_box);
hsc->add_child(view_box);
bt_view = memnew(BehaviorTreeView); bt_view = memnew(BehaviorTreeView);
view_box->add_child(bt_view); bt_view->set_h_size_flags(Control::SIZE_EXPAND_FILL);
bt_view->set_h_size_flags(Control::SIZE_EXPAND_FILL); bt_view->set_v_size_flags(Control::SIZE_EXPAND_FILL);
bt_view->set_v_size_flags(Control::SIZE_EXPAND_FILL); view_box->add_child(bt_view);
info_box = memnew(HBoxContainer); alert_box = memnew(HBoxContainer);
view_box->add_child(info_box); alert_box->hide();
info_box->hide(); view_box->add_child(alert_box);
info_icon = memnew(TextureRect); alert_icon = memnew(TextureRect);
info_box->add_child(info_icon); alert_box->add_child(alert_icon);
info_icon->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); alert_icon->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);
info_message = memnew(Label); alert_message = memnew(Label);
info_box->add_child(info_message); alert_box->add_child(alert_message);
info_message->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER); alert_message->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
}
message = memnew(Label); info_message = memnew(Label);
add_child(message); info_message->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
message->set_text(TTR("Run project to start debugging")); info_message->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
message->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); info_message->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART);
message->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER); info_message->set_custom_minimum_size(Size2(100 * EDSCALE, 0));
message->set_anchors_preset(Control::PRESET_CENTER); info_message->set_anchors_and_offsets_preset(PRESET_FULL_RECT, PRESET_MODE_KEEP_SIZE, 8 * EDSCALE);
bt_view->add_child(info_message);
stop_session(); stop_session();
} }
@ -133,12 +184,14 @@ void LimboDebuggerPlugin::setup_session(int p_idx) {
bool LimboDebuggerPlugin::capture(const String &p_message, const Array &p_data, int p_session) { bool LimboDebuggerPlugin::capture(const String &p_message, const Array &p_data, int p_session) {
bool captured = true; bool captured = true;
if (p_message == "limboai:active_behavior_trees") { if (p_message == "limboai:active_bt_players") {
tab->update_bt_list(p_data); tab->update_active_bt_players(p_data);
} else if (p_message == "limboai:bt_update") { } else if (p_message == "limboai:bt_update") {
BehaviorTreeData data = BehaviorTreeData(); BehaviorTreeData data = BehaviorTreeData();
data.deserialize(p_data); data.deserialize(p_data);
tab->get_behavior_tree_view()->update_tree(data); if (data.bt_player_path == tab->get_selected_bt_player()) {
tab->update_behavior_tree(data);
}
} else { } else {
captured = false; captured = false;
} }
@ -151,4 +204,4 @@ bool LimboDebuggerPlugin::has_capture(const String &p_capture) const {
LimboDebuggerPlugin::LimboDebuggerPlugin() { LimboDebuggerPlugin::LimboDebuggerPlugin() {
tab = nullptr; tab = nullptr;
} }

View File

@ -7,6 +7,7 @@
#include "core/object/object.h" #include "core/object/object.h"
#include "core/typedefs.h" #include "core/typedefs.h"
#include "editor/plugins/editor_debugger_plugin.h" #include "editor/plugins/editor_debugger_plugin.h"
#include "modules/limboai/debugger/behavior_tree_data.h"
#include "modules/limboai/debugger/behavior_tree_view.h" #include "modules/limboai/debugger/behavior_tree_view.h"
#include "scene/gui/box_container.h" #include "scene/gui/box_container.h"
#include "scene/gui/item_list.h" #include "scene/gui/item_list.h"
@ -18,24 +19,30 @@ class LimboDebuggerTab : public PanelContainer {
GDCLASS(LimboDebuggerTab, PanelContainer); GDCLASS(LimboDebuggerTab, PanelContainer);
private: private:
List<String> active_bt_players;
Ref<EditorDebuggerSession> session; Ref<EditorDebuggerSession> session;
HSplitContainer *hsc; HSplitContainer *hsc;
Label *message; Label *info_message;
ItemList *bt_list; ItemList *bt_player_list;
BehaviorTreeView *bt_view; BehaviorTreeView *bt_view;
VBoxContainer *view_box; VBoxContainer *view_box;
HBoxContainer *info_box; HBoxContainer *alert_box;
TextureRect *info_icon; TextureRect *alert_icon;
Label *info_message; Label *alert_message;
LineEdit *filter_players;
void _set_info_message(const String &p_message); void _show_alert(const String &p_message);
void _update_bt_player_list(const List<String> &p_node_paths, const String &p_filter);
void _bt_selected(int p_idx); void _bt_selected(int p_idx);
void _filter_changed(String p_text);
public: public:
void start_session(); void start_session();
void stop_session(); void stop_session();
void update_bt_list(const Array &p_node_paths); void update_active_bt_players(const Array &p_node_paths);
BehaviorTreeView *get_behavior_tree_view() const { return bt_view; } BehaviorTreeView *get_behavior_tree_view() const { return bt_view; }
String get_selected_bt_player();
void update_behavior_tree(const BehaviorTreeData &p_data);
LimboDebuggerTab(Ref<EditorDebuggerSession> p_session); LimboDebuggerTab(Ref<EditorDebuggerSession> p_session);
}; };