From 9abfeadc2191c0684c05a9b11fdd9713a00f709a Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Thu, 13 Apr 2023 09:29:45 +0200 Subject: [PATCH] Implement basics of behavior tree debugger --- SCsub | 1 + bt/bt_player.cpp | 18 ++++ bt/bt_state.cpp | 17 ++++ bt/bt_state.h | 6 ++ bt/bt_task.cpp | 2 + bt/bt_task.h | 2 + debugger/behavior_tree_data.cpp | 54 ++++++++++ debugger/behavior_tree_data.h | 41 ++++++++ debugger/behavior_tree_view.cpp | 65 ++++++++++++ debugger/behavior_tree_view.h | 25 +++++ debugger/limbo_debugger.cpp | 144 +++++++++++++++++++++++++++ debugger/limbo_debugger.h | 47 +++++++++ debugger/limbo_debugger_plugin.cpp | 152 +++++++++++++++++++++++++++++ debugger/limbo_debugger_plugin.h | 60 ++++++++++++ editor/limbo_ai_editor_plugin.cpp | 40 ++------ editor/limbo_ai_editor_plugin.h | 2 - limbo_utility.cpp | 34 +++++++ limbo_utility.h | 2 + register_types.cpp | 4 + 19 files changed, 682 insertions(+), 34 deletions(-) create mode 100644 debugger/behavior_tree_data.cpp create mode 100644 debugger/behavior_tree_data.h create mode 100644 debugger/behavior_tree_view.cpp create mode 100644 debugger/behavior_tree_view.h create mode 100644 debugger/limbo_debugger.cpp create mode 100644 debugger/limbo_debugger.h create mode 100644 debugger/limbo_debugger_plugin.cpp create mode 100644 debugger/limbo_debugger_plugin.h diff --git a/SCsub b/SCsub index 3c14de0..17b1d32 100644 --- a/SCsub +++ b/SCsub @@ -12,5 +12,6 @@ module_env.add_source_files(env.modules_sources, "bt/actions/*.cpp") module_env.add_source_files(env.modules_sources, "bt/decorators/*.cpp") module_env.add_source_files(env.modules_sources, "bt/conditions/*.cpp") module_env.add_source_files(env.modules_sources, "bb_param/*.cpp") +module_env.add_source_files(env.modules_sources, "debugger/*.cpp") if env.editor_build: module_env.add_source_files(env.modules_sources, "editor/*.cpp") diff --git a/bt/bt_player.cpp b/bt/bt_player.cpp index 4ecff8a..ed03749 100644 --- a/bt/bt_player.cpp +++ b/bt/bt_player.cpp @@ -12,10 +12,16 @@ #include "core/os/memory.h" #include "core/variant/variant.h" #include "modules/limboai/blackboard.h" +#include "modules/limboai/debugger/limbo_debugger.h" VARIANT_ENUM_CAST(BTPlayer::UpdateMode); void BTPlayer::_load_tree() { +#ifdef DEBUG_ENABLED + if (tree_instance.is_valid()) { + LimboDebugger::get_singleton()->unregister_bt_instance(tree_instance, get_path()); + } +#endif tree_instance.unref(); ERR_FAIL_COND_MSG(!behavior_tree.is_valid(), "BTPlayer: Needs a valid behavior tree."); ERR_FAIL_COND_MSG(!behavior_tree->get_root_task().is_valid(), "BTPlayer: Behavior tree has no valid root task."); @@ -23,6 +29,9 @@ void BTPlayer::_load_tree() { blackboard->prefetch_nodepath_vars(this); } tree_instance = behavior_tree->instantiate(get_owner(), blackboard); +#ifdef DEBUG_ENABLED + LimboDebugger::get_singleton()->register_bt_instance(tree_instance, get_path()); +#endif } void BTPlayer::set_behavior_tree(const Ref &p_tree) { @@ -52,6 +61,7 @@ void BTPlayer::update(double p_delta) { } if (active) { last_status = tree_instance->execute(p_delta); + emit_signal(LimboStringNames::get_singleton()->updated, last_status); if (last_status == BTTask::SUCCESS || last_status == BTTask::FAILURE) { emit_signal(LimboStringNames::get_singleton()->behavior_tree_finished, last_status); } @@ -81,6 +91,13 @@ void BTPlayer::_notification(int p_notification) { set_active(active); } } break; +#ifdef DEBUG_ENABLED + case NOTIFICATION_EXIT_TREE: { + if (tree_instance.is_valid()) { + LimboDebugger::get_singleton()->unregister_bt_instance(tree_instance, get_path()); + } + } break; +#endif } } @@ -115,6 +132,7 @@ void BTPlayer::_bind_methods() { BIND_ENUM_CONSTANT(MANUAL); ADD_SIGNAL(MethodInfo("behavior_tree_finished", PropertyInfo(Variant::INT, "p_status"))); + ADD_SIGNAL(MethodInfo("updated", PropertyInfo(Variant::INT, "p_status"))); } BTPlayer::BTPlayer() { diff --git a/bt/bt_state.cpp b/bt/bt_state.cpp index 67e1e6b..8238d93 100644 --- a/bt/bt_state.cpp +++ b/bt/bt_state.cpp @@ -5,11 +5,17 @@ #include "core/object/class_db.h" #include "core/variant/variant.h" #include "modules/limboai/bt/bt_task.h" +#include "modules/limboai/debugger/limbo_debugger.h" #include "modules/limboai/limbo_state.h" +#include "modules/limboai/limbo_string_names.h" void BTState::_setup() { ERR_FAIL_COND_MSG(behavior_tree.is_null(), "BTState: BehaviorTree is not assigned."); tree_instance = behavior_tree->instantiate(get_agent(), get_blackboard()); + +#ifdef DEBUG_ENABLED + LimboDebugger::get_singleton()->register_bt_instance(tree_instance, get_path()); +#endif } void BTState::_exit() { @@ -20,6 +26,7 @@ void BTState::_exit() { void BTState::_update(double p_delta) { ERR_FAIL_COND(tree_instance == nullptr); int status = tree_instance->execute(p_delta); + emit_signal(LimboStringNames::get_singleton()->updated, p_delta); if (status == BTTask::SUCCESS) { get_root()->dispatch(success_event, Variant()); } else if (status == BTTask::FAILURE) { @@ -27,6 +34,16 @@ void BTState::_update(double p_delta) { } } +#ifdef DEBUG_ENABLED +void BTState::_notification(int p_notification) { + switch (p_notification) { + case NOTIFICATION_EXIT_TREE: { + LimboDebugger::get_singleton()->unregister_bt_instance(tree_instance, get_path()); + } break; + } +} +#endif // DEBUG_ENABLED + void BTState::_bind_methods() { ClassDB::bind_method(D_METHOD("set_behavior_tree", "p_value"), &BTState::set_behavior_tree); ClassDB::bind_method(D_METHOD("get_behavior_tree"), &BTState::get_behavior_tree); diff --git a/bt/bt_state.h b/bt/bt_state.h index 5082e6f..39067d3 100644 --- a/bt/bt_state.h +++ b/bt/bt_state.h @@ -36,6 +36,12 @@ public: String get_failure_event() const { return failure_event; } BTState(); + +#ifdef DEBUG_ENABLED +protected: + void _notification(int p_notification); + +#endif }; #endif // BT_STATE_H \ No newline at end of file diff --git a/bt/bt_task.cpp b/bt/bt_task.cpp index 0bc11f5..2bf83f8 100644 --- a/bt/bt_task.cpp +++ b/bt/bt_task.cpp @@ -140,6 +140,7 @@ int BTTask::execute(double 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)) { @@ -311,6 +312,7 @@ BTTask::BTTask() { parent = nullptr; children = Vector>(); status = FRESH; + last_tick_usec = 0; } BTTask::~BTTask() { diff --git a/bt/bt_task.h b/bt/bt_task.h index 793880a..09c521e 100644 --- a/bt/bt_task.h +++ b/bt/bt_task.h @@ -33,6 +33,7 @@ private: BTTask *parent; Vector> children; int status; + int last_tick_usec; Array _get_children() const; void _set_children(Array children); @@ -75,6 +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; } Ref get_child(int p_idx) const; int get_child_count() const; diff --git a/debugger/behavior_tree_data.cpp b/debugger/behavior_tree_data.cpp new file mode 100644 index 0000000..aae3cfd --- /dev/null +++ b/debugger/behavior_tree_data.cpp @@ -0,0 +1,54 @@ +/* behavior_tree_data.cpp */ + +#include "behavior_tree_data.h" +#include "core/templates/list.h" + +//// BehaviorTreeData + +BehaviorTreeData::BehaviorTreeData(const Ref &p_instance) { + // Flatten tree into list depth first + List> stack; + stack.push_back(p_instance); + while (stack.size()) { + Ref task = stack[0]; + stack.pop_front(); + + int num_children = task->get_child_count(); + for (int i = 0; i < num_children; i++) { + stack.push_front(task->get_child(num_children - 1 - i)); + } + + tasks.push_back(TaskData( + task->get_task_name(), + num_children, + task->get_status(), + task->get_last_tick_usec(), + task->get_class())); + } +} + +void BehaviorTreeData::serialize(Array &p_arr) { + for (const TaskData &td : tasks) { + 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.type_name); + } +} + +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() < 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[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; + } +} diff --git a/debugger/behavior_tree_data.h b/debugger/behavior_tree_data.h new file mode 100644 index 0000000..708c607 --- /dev/null +++ b/debugger/behavior_tree_data.h @@ -0,0 +1,41 @@ +/* behavior_tree_data.h */ + +#ifndef BEHAVIOR_TREE_DATA_H +#define BEHAVIOR_TREE_DATA_H + +#include "core/object/object.h" +#include "modules/limboai/bt/bt_task.h" + +class BehaviorTreeData { +public: + struct TaskData { + String name; + // ObjectID object_id; + int num_children = 0; + int status = 0; + int last_tick_usec = 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) { + name = p_name; + num_children = p_num_children; + status = p_status; + last_tick_usec = p_last_tick_usec; + type_name = p_type_name; + } + + TaskData() {} + }; + + List tasks; + + void serialize(Array &p_arr); + void deserialize(const Array &p_arr); + + BehaviorTreeData(const Ref &p_instance); + BehaviorTreeData() {} +}; + +#endif // BEHAVIOR_TREE_DATA \ No newline at end of file diff --git a/debugger/behavior_tree_view.cpp b/debugger/behavior_tree_view.cpp new file mode 100644 index 0000000..672888b --- /dev/null +++ b/debugger/behavior_tree_view.cpp @@ -0,0 +1,65 @@ +/* behavior_tree_view.cpp */ + +#include "behavior_tree_view.h" +#include "behavior_tree_data.h" +#include "core/math/color.h" +#include "modules/limboai/bt/bt_task.h" +#include "modules/limboai/limbo_utility.h" + +void BehaviorTreeView::update_tree(const BehaviorTreeData &p_data) { + tree->clear(); + int recent_tick_usec = -1; + 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(); + } + } + + TreeItem *item = tree->create_item(parent); + item->set_text(0, task_data.name); + item->set_icon(0, LimboUtility::get_singleton()->get_task_icon(task_data.type_name)); + + // 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; + } + // 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)); + } + } + + // Add in front of parents stack if it expects children. + if (task_data.num_children) { + parents.push_front(Pair(item, task_data.num_children)); + } + } +} + +void BehaviorTreeView::clear() { + tree->clear(); +} + +BehaviorTreeView::BehaviorTreeView() { + tree = memnew(Tree); + add_child(tree); + tree->set_columns(1); + tree->set_column_expand(0, true); + tree->set_anchor(SIDE_RIGHT, ANCHOR_END); + tree->set_anchor(SIDE_BOTTOM, ANCHOR_END); +} diff --git a/debugger/behavior_tree_view.h b/debugger/behavior_tree_view.h new file mode 100644 index 0000000..11e3675 --- /dev/null +++ b/debugger/behavior_tree_view.h @@ -0,0 +1,25 @@ +/* behavior_tree_view.h */ + +#ifndef BEHAVIOR_TREE_VIEW_H +#define BEHAVIOR_TREE_VIEW_H + +#include "behavior_tree_data.h" +#include "core/object/class_db.h" +#include "core/object/object.h" +#include "scene/gui/control.h" +#include "scene/gui/tree.h" + +class BehaviorTreeView : public Control { + GDCLASS(BehaviorTreeView, Control); + +private: + Tree *tree; + +public: + BehaviorTreeView(); + + void update_tree(const BehaviorTreeData &p_data); + void clear(); +}; + +#endif // BEHAVIOR_TREE_VIEW \ No newline at end of file diff --git a/debugger/limbo_debugger.cpp b/debugger/limbo_debugger.cpp new file mode 100644 index 0000000..673000d --- /dev/null +++ b/debugger/limbo_debugger.cpp @@ -0,0 +1,144 @@ +/* limbo_debugger.cpp */ + +#include "limbo_debugger.h" +#include "behavior_tree_data.h" +#include "core/debugger/engine_debugger.h" +#include "core/error/error_macros.h" +#include "core/string/node_path.h" +#include "modules/limboai/bt/bt_task.h" +#include "scene/main/scene_tree.h" +#include "scene/main/window.h" + +//// LimboDebugger + +LimboDebugger *LimboDebugger::singleton = nullptr; +LimboDebugger *LimboDebugger::get_singleton() { + return singleton; +} + +LimboDebugger::LimboDebugger() { + singleton = this; +#ifdef DEBUG_ENABLED + EngineDebugger::register_message_capture("limboai", EngineDebugger::Capture(nullptr, LimboDebugger::parse_message)); +#endif +} + +LimboDebugger::~LimboDebugger() { + singleton = nullptr; +} + +void LimboDebugger::initialize() { + if (EngineDebugger::is_active()) { + memnew(LimboDebugger); + } +} + +void LimboDebugger::deinitialize() { + if (singleton) { + memdelete(singleton); + } +} + +#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") { + singleton->_track_tree(p_args[0]); + } else if (p_msg == "untrack_bt_updates") { + // unregister bt for updates + } else if (p_msg == "start_session") { + singleton->session_active = true; + singleton->_send_active_behavior_trees(); + } else if (p_msg == "stop_session") { + singleton->session_active = false; + } else { + r_captured = false; + } + return OK; +} + +void LimboDebugger::register_bt_instance(Ref p_instance, NodePath p_path) { + ERR_FAIL_COND(active_trees.has(p_path)); + + active_trees.insert(p_path, p_instance); + if (session_active) { + _send_active_behavior_trees(); + } +} + +void LimboDebugger::unregister_bt_instance(Ref p_instance, NodePath p_path) { + ERR_FAIL_COND(!active_trees.has(p_path)); + + if (tracked_tree == p_path) { + _untrack_tree(); + } + active_trees.erase(p_path); + + if (session_active) { + _send_active_behavior_trees(); + } +} + +void LimboDebugger::_track_tree(NodePath p_path) { + ERR_FAIL_COND(!active_trees.has(p_path)); + + if (!tracked_tree.is_empty()) { + _untrack_tree(); + } + + Node *node = SceneTree::get_singleton()->get_root()->get_node(p_path); + ERR_FAIL_COND(node == nullptr); + + tracked_tree = p_path; + if (node->is_class("BTPlayer")) { + node->connect(SNAME("updated"), callable_mp(this, &LimboDebugger::_on_bt_updated).bind(p_path)); + } else if (node->is_class("BTState")) { + node->connect(SNAME("updated"), callable_mp(this, &LimboDebugger::_on_state_updated).bind(p_path)); + } +} + +void LimboDebugger::_untrack_tree() { + if (tracked_tree.is_empty()) { + return; + } + + NodePath was_tracking = tracked_tree; + tracked_tree = NodePath(); + + Node *node = SceneTree::get_singleton()->get_root()->get_node(was_tracking); + ERR_FAIL_COND(node == nullptr); + + if (node->is_class("BTPlayer")) { + node->disconnect(SNAME("updated"), callable_mp(this, &LimboDebugger::_on_bt_updated)); + } else if (node->is_class("BTState")) { + node->disconnect(SNAME("updated"), callable_mp(this, &LimboDebugger::_on_state_updated)); + } +} + +void LimboDebugger::_send_active_behavior_trees() { + Array arr; + for (KeyValue> kv : active_trees) { + arr.append(kv.key); + } + EngineDebugger::get_singleton()->send_message("limboai:active_behavior_trees", arr); +} + +void LimboDebugger::_on_bt_updated(int _status, NodePath p_path) { + if (p_path != tracked_tree) { + return; + } + Array arr; + BehaviorTreeData(active_trees.get(tracked_tree)).serialize(arr); + EngineDebugger::get_singleton()->send_message("limboai:bt_update", arr); +} + +void LimboDebugger::_on_state_updated(float _delta, NodePath p_path) { + if (p_path != tracked_tree) { + return; + } + Array arr; + BehaviorTreeData(active_trees.get(tracked_tree)).serialize(arr); + EngineDebugger::get_singleton()->send_message("limboai:bt_update", arr); +} + +#endif // DEBUG_ENABLED diff --git a/debugger/limbo_debugger.h b/debugger/limbo_debugger.h new file mode 100644 index 0000000..b4d0bd3 --- /dev/null +++ b/debugger/limbo_debugger.h @@ -0,0 +1,47 @@ +/* limbo_debugger.h */ + +#ifndef LIMBO_DEBUGGER_H +#define LIMBO_DEBUGGER_H + +#include "core/object/class_db.h" +#include "core/object/object.h" +#include "core/string/node_path.h" +#include "modules/limboai/bt/bt_task.h" + +class LimboDebugger : public Object { + GDCLASS(LimboDebugger, Object); + +private: + static LimboDebugger *singleton; + + LimboDebugger(); + +public: + static void initialize(); + static void deinitialize(); + static LimboDebugger *get_singleton(); + + ~LimboDebugger(); + +#ifdef DEBUG_ENABLED +private: + HashMap> active_trees; + NodePath tracked_tree; + bool session_active = false; + + void _track_tree(NodePath p_path); + void _untrack_tree(); + void _send_active_behavior_trees(); + + void _on_bt_updated(int status, NodePath p_path); + void _on_state_updated(float _delta, NodePath p_path); + +public: + static Error parse_message(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured); + + void register_bt_instance(Ref p_instance, NodePath p_path); + + void unregister_bt_instance(Ref p_instance, NodePath p_path); +#endif // DEBUG_ENABLED +}; +#endif // LIMBO_DEBUGGER \ No newline at end of file diff --git a/debugger/limbo_debugger_plugin.cpp b/debugger/limbo_debugger_plugin.cpp new file mode 100644 index 0000000..7aa7c61 --- /dev/null +++ b/debugger/limbo_debugger_plugin.cpp @@ -0,0 +1,152 @@ +/* limbo_debugger_view.h */ + +#include "limbo_debugger_plugin.h" +#include "core/debugger/engine_debugger.h" +#include "core/math/math_defs.h" +#include "core/object/callable_method_pointer.h" +#include "core/os/memory.h" +#include "core/string/print_string.h" +#include "core/variant/array.h" +#include "editor/editor_scale.h" +#include "editor/plugins/editor_debugger_plugin.h" +#include "limbo_debugger.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/control.h" +#include "scene/gui/item_list.h" +#include "scene/gui/label.h" +#include "scene/gui/split_container.h" +#include "scene/gui/texture_rect.h" + +/////////////////////// LimboDebuggerTab + +void LimboDebuggerTab::start_session() { + bt_list->clear(); + bt_view->clear(); + info_box->hide(); + hsc->show(); + message->hide(); + session->send_message("limboai:start_session", Array()); +} + +void LimboDebuggerTab::stop_session() { + hsc->hide(); + message->show(); + session->send_message("limboai:stop_session", Array()); +} + +void LimboDebuggerTab::update_bt_list(const Array &p_items) { + // 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)); + } + + bt_list->clear(); + int select_idx = -1; + for (int i = 0; i < p_items.size(); i++) { + bt_list->add_item(p_items[i]); + if (p_items[i] == selected_bt) { + select_idx = i; + } + } + + // 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")); + } +} + +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(); + bt_view->clear(); + NodePath path = bt_list->get_item_text(p_idx); + Array data; + data.push_back(path); + session->send_message("limboai:track_bt_updates", data); +} + +LimboDebuggerTab::LimboDebuggerTab(Ref p_session) { + session = p_session; + + hsc = memnew(HSplitContainer); + add_child(hsc); + + bt_list = memnew(ItemList); + hsc->add_child(bt_list); + bt_list->set_custom_minimum_size(Size2(280.0 * EDSCALE, 0.0)); + bt_list->connect(SNAME("item_selected"), callable_mp(this, &LimboDebuggerTab::_bt_selected)); + + view_box = memnew(VBoxContainer); + { + 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); + + info_box = memnew(HBoxContainer); + view_box->add_child(info_box); + info_box->hide(); + + info_icon = memnew(TextureRect); + info_box->add_child(info_icon); + info_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); + } + + 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); + + stop_session(); +} + +//////////////////////// LimboDebuggerPlugin + +void LimboDebuggerPlugin::setup_session(int p_idx) { + Ref session = get_session(p_idx); + tab = memnew(LimboDebuggerTab(session)); + tab->set_name("LimboAI"); + session->connect(SNAME("started"), callable_mp(tab, &LimboDebuggerTab::start_session)); + session->connect(SNAME("stopped"), callable_mp(tab, &LimboDebuggerTab::stop_session)); + session->add_session_tab(tab); +} + +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); + } else if (p_message == "limboai:bt_update") { + BehaviorTreeData data = BehaviorTreeData(); + data.deserialize(p_data); + tab->get_behavior_tree_view()->update_tree(data); + } else { + captured = false; + } + return captured; +} + +bool LimboDebuggerPlugin::has_capture(const String &p_capture) const { + return p_capture == "limboai"; +} + +LimboDebuggerPlugin::LimboDebuggerPlugin() { + tab = nullptr; +} \ No newline at end of file diff --git a/debugger/limbo_debugger_plugin.h b/debugger/limbo_debugger_plugin.h new file mode 100644 index 0000000..6d139eb --- /dev/null +++ b/debugger/limbo_debugger_plugin.h @@ -0,0 +1,60 @@ +/* limbo_debugger_plugin.h */ + +#ifndef LIMBO_DEBUGGER_PLUGIN_H +#define LIMBO_DEBUGGER_PLUGIN_H + +#include "core/object/class_db.h" +#include "core/object/object.h" +#include "core/typedefs.h" +#include "editor/plugins/editor_debugger_plugin.h" +#include "modules/limboai/debugger/behavior_tree_view.h" +#include "scene/gui/box_container.h" +#include "scene/gui/item_list.h" +#include "scene/gui/panel_container.h" +#include "scene/gui/split_container.h" +#include "scene/gui/texture_rect.h" + +class LimboDebuggerTab : public PanelContainer { + GDCLASS(LimboDebuggerTab, PanelContainer); + +private: + Ref session; + HSplitContainer *hsc; + Label *message; + ItemList *bt_list; + BehaviorTreeView *bt_view; + VBoxContainer *view_box; + HBoxContainer *info_box; + TextureRect *info_icon; + Label *info_message; + + _FORCE_INLINE_ void _set_info_message(const String &p_message); + void _bt_selected(int p_idx); + +protected: + // void _notification(int p_notification); + +public: + void start_session(); + void stop_session(); + void update_bt_list(const Array &p_items); + BehaviorTreeView *get_behavior_tree_view() { return bt_view; } + + LimboDebuggerTab(Ref p_session); +}; + +class LimboDebuggerPlugin : public EditorDebuggerPlugin { + GDCLASS(LimboDebuggerPlugin, EditorDebuggerPlugin); + +private: + LimboDebuggerTab *tab; + +public: + void setup_session(int p_idx) override; + bool has_capture(const String &p_capture) const override; + bool capture(const String &p_message, const Array &p_data, int p_session) override; + + LimboDebuggerPlugin(); +}; + +#endif // LIMBO_DEBUGGER_PLUGIN \ No newline at end of file diff --git a/editor/limbo_ai_editor_plugin.cpp b/editor/limbo_ai_editor_plugin.cpp index b874bf4..d443af5 100644 --- a/editor/limbo_ai_editor_plugin.cpp +++ b/editor/limbo_ai_editor_plugin.cpp @@ -47,6 +47,8 @@ #include "modules/limboai/bt/composites/bt_parallel.h" #include "modules/limboai/bt/composites/bt_selector.h" #include "modules/limboai/bt/composites/bt_sequence.h" +#include "modules/limboai/debugger/limbo_debugger_plugin.h" +#include "modules/limboai/limbo_utility.h" #include "scene/gui/box_container.h" #include "scene/gui/button.h" #include "scene/gui/control.h" @@ -82,11 +84,13 @@ void TaskTree::_update_item(TreeItem *p_item) { Ref task = p_item->get_metadata(0); ERR_FAIL_COND_MSG(!task.is_valid(), "Invalid task reference in metadata."); p_item->set_text(0, task->get_task_name()); + String type_arg; if (task->get_script_instance() && !task->get_script_instance()->get_script()->get_path().is_empty()) { - p_item->set_icon(0, LimboAIEditor::get_task_icon(task->get_script_instance()->get_script()->get_path())); + type_arg = task->get_script_instance()->get_script()->get_path(); } else { - p_item->set_icon(0, LimboAIEditor::get_task_icon(task->get_class())); + type_arg = task->get_class(); } + p_item->set_icon(0, LimboUtility::get_singleton()->get_task_icon(type_arg)); p_item->set_editable(0, false); for (int i = 0; i < p_item->get_button_count(0); i++) { @@ -455,7 +459,7 @@ void TaskPanel::refresh() { TaskSection *sec = memnew(TaskSection(cat)); for (String task_meta : tasks) { - Ref icon = LimboAIEditor::get_task_icon(task_meta); + Ref icon = LimboUtility::get_singleton()->get_task_icon(task_meta); String tname; if (task_meta.begins_with("res:")) { tname = task_meta.get_file().get_basename().trim_prefix("BT").to_pascal_case(); @@ -1019,35 +1023,6 @@ void LimboAIEditor::apply_changes() { } } -Ref LimboAIEditor::get_task_icon(String p_script_path_or_class) { - ERR_FAIL_COND_V_MSG(p_script_path_or_class.is_empty(), Variant(), "BTTask: script path or class cannot be empty."); - - String base_type = p_script_path_or_class; - if (p_script_path_or_class.begins_with("res:")) { - Ref