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

View File

@ -31,11 +31,12 @@ public:
};
List<TaskData> tasks;
NodePath bt_player_path;
void serialize(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() {}
};

View File

@ -42,13 +42,13 @@ void LimboDebugger::deinitialize() {
#ifdef DEBUG_ENABLED
Error LimboDebugger::parse_message(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured) {
r_captured = true;
if (p_msg == "track_bt_updates") {
if (p_msg == "track_bt_player") {
singleton->_track_tree(p_args[0]);
} else if (p_msg == "untrack_bt_updates") {
// unregister bt for updates
} else if (p_msg == "untrack_bt_player") {
singleton->_untrack_tree();
} else if (p_msg == "start_session") {
singleton->session_active = true;
singleton->_send_active_behavior_trees();
singleton->_send_active_bt_players();
} else if (p_msg == "stop_session") {
singleton->session_active = false;
} else {
@ -62,11 +62,12 @@ void LimboDebugger::register_bt_instance(Ref<BTTask> p_instance, NodePath p_path
active_trees.insert(p_path, p_instance);
if (session_active) {
_send_active_behavior_trees();
_send_active_bt_players();
}
}
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));
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);
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;
for (KeyValue<NodePath, Ref<BTTask>> kv : active_trees) {
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) {
@ -128,7 +129,7 @@ void LimboDebugger::_on_bt_updated(int _status, NodePath p_path) {
return;
}
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);
}
@ -137,7 +138,7 @@ void LimboDebugger::_on_state_updated(float _delta, NodePath p_path) {
return;
}
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);
}

View File

@ -31,7 +31,7 @@ private:
void _track_tree(NodePath p_path);
void _untrack_tree();
void _send_active_behavior_trees();
void _send_active_bt_players();
void _on_bt_updated(int status, 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/item_list.h"
#include "scene/gui/label.h"
#include "scene/gui/line_edit.h"
#include "scene/gui/split_container.h"
#include "scene/gui/texture_rect.h"
/////////////////////// LimboDebuggerTab
void LimboDebuggerTab::start_session() {
bt_list->clear();
bt_player_list->clear();
bt_view->clear();
info_box->hide();
hsc->show();
message->hide();
alert_box->hide();
info_message->set_text(TTR("Pick a player from the list to display behavior tree."));
info_message->show();
session->send_message("limboai:start_session", Array());
}
void LimboDebuggerTab::stop_session() {
hsc->hide();
message->show();
bt_player_list->clear();
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());
}
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.
String selected_bt = "";
if (bt_list->is_anything_selected()) {
selected_bt = bt_list->get_item_text(bt_list->get_selected_items().get(0));
String selected_player = "";
if (bt_player_list->is_anything_selected()) {
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;
for (int i = 0; i < p_node_paths.size(); i++) {
bt_list->add_item(p_node_paths[i]);
// Make item text shortened from the left, e.g ".../Agent/BTPlayer".
bt_list->set_item_text_direction(i, TEXT_DIRECTION_RTL);
if (p_node_paths[i] == selected_bt) {
select_idx = i;
bool selection_filtered_out = false;
for (const String &p : p_node_paths) {
if (p_filter.is_empty() || p.contains(p_filter)) {
int idx = bt_player_list->add_item(p);
// Make item text shortened from the left, e.g ".../Agent/BTPlayer".
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.
if (select_idx > -1) {
bt_list->select(select_idx);
} else if (!selected_bt.is_empty()) {
_set_info_message(TTR("Node instance is gone"));
bt_player_list->select(select_idx);
} else if (!selected_player.is_empty()) {
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) {
info_box->hide();
alert_box->hide();
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;
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) {
@ -83,39 +124,49 @@ LimboDebuggerTab::LimboDebuggerTab(Ref<EditorDebuggerSession> p_session) {
hsc = memnew(HSplitContainer);
add_child(hsc);
bt_list = memnew(ItemList);
hsc->add_child(bt_list);
bt_list->set_custom_minimum_size(Size2(240.0 * EDSCALE, 0.0));
bt_list->connect(SNAME("item_selected"), callable_mp(this, &LimboDebuggerTab::_bt_selected));
VBoxContainer *list_box = memnew(VBoxContainer);
hsc->add_child(list_box);
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);
{
hsc->add_child(view_box);
hsc->add_child(view_box);
bt_view = memnew(BehaviorTreeView);
view_box->add_child(bt_view);
bt_view->set_h_size_flags(Control::SIZE_EXPAND_FILL);
bt_view->set_v_size_flags(Control::SIZE_EXPAND_FILL);
bt_view = memnew(BehaviorTreeView);
bt_view->set_h_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);
view_box->add_child(info_box);
info_box->hide();
alert_box = memnew(HBoxContainer);
alert_box->hide();
view_box->add_child(alert_box);
info_icon = memnew(TextureRect);
info_box->add_child(info_icon);
info_icon->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);
alert_icon = memnew(TextureRect);
alert_box->add_child(alert_icon);
alert_icon->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);
info_message = memnew(Label);
info_box->add_child(info_message);
info_message->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
}
alert_message = memnew(Label);
alert_box->add_child(alert_message);
alert_message->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
message = memnew(Label);
add_child(message);
message->set_text(TTR("Run project to start debugging"));
message->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
message->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
message->set_anchors_preset(Control::PRESET_CENTER);
info_message = memnew(Label);
info_message->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
info_message->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
info_message->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART);
info_message->set_custom_minimum_size(Size2(100 * EDSCALE, 0));
info_message->set_anchors_and_offsets_preset(PRESET_FULL_RECT, PRESET_MODE_KEEP_SIZE, 8 * EDSCALE);
bt_view->add_child(info_message);
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 captured = true;
if (p_message == "limboai:active_behavior_trees") {
tab->update_bt_list(p_data);
if (p_message == "limboai:active_bt_players") {
tab->update_active_bt_players(p_data);
} else if (p_message == "limboai:bt_update") {
BehaviorTreeData data = BehaviorTreeData();
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 {
captured = false;
}
@ -151,4 +204,4 @@ bool LimboDebuggerPlugin::has_capture(const String &p_capture) const {
LimboDebuggerPlugin::LimboDebuggerPlugin() {
tab = nullptr;
}
}

View File

@ -7,6 +7,7 @@
#include "core/object/object.h"
#include "core/typedefs.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 "scene/gui/box_container.h"
#include "scene/gui/item_list.h"
@ -18,24 +19,30 @@ class LimboDebuggerTab : public PanelContainer {
GDCLASS(LimboDebuggerTab, PanelContainer);
private:
List<String> active_bt_players;
Ref<EditorDebuggerSession> session;
HSplitContainer *hsc;
Label *message;
ItemList *bt_list;
Label *info_message;
ItemList *bt_player_list;
BehaviorTreeView *bt_view;
VBoxContainer *view_box;
HBoxContainer *info_box;
TextureRect *info_icon;
Label *info_message;
HBoxContainer *alert_box;
TextureRect *alert_icon;
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 _filter_changed(String p_text);
public:
void start_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; }
String get_selected_bt_player();
void update_behavior_tree(const BehaviorTreeData &p_data);
LimboDebuggerTab(Ref<EditorDebuggerSession> p_session);
};