From fc26f51ff29268a6c4da6e7b6f77088402480616 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Sat, 3 Aug 2024 11:07:06 +0200 Subject: [PATCH] Implement BTInstance - runtime instance of BehaviorTree --- blackboard/bb_variable.cpp | 4 +- bt/behavior_tree.cpp | 16 +-- bt/behavior_tree.h | 3 +- bt/bt_instance.cpp | 138 ++++++++++++++++++++++ bt/bt_instance.h | 60 ++++++++++ bt/bt_player.cpp | 95 +++------------ bt/bt_player.h | 17 +-- editor/debugger/behavior_tree_data.cpp | 35 +++--- editor/debugger/behavior_tree_data.h | 10 +- editor/debugger/limbo_debugger.cpp | 111 +++++++---------- editor/debugger/limbo_debugger.h | 16 +-- editor/debugger/limbo_debugger_plugin.cpp | 78 ++++++------ editor/debugger/limbo_debugger_plugin.h | 17 ++- register_types.cpp | 1 + util/limbo_compat.h | 2 + util/limbo_string_names.cpp | 1 + util/limbo_string_names.h | 1 + 17 files changed, 367 insertions(+), 238 deletions(-) create mode 100644 bt/bt_instance.cpp create mode 100644 bt/bt_instance.h diff --git a/blackboard/bb_variable.cpp b/blackboard/bb_variable.cpp index fac7d08..092357c 100644 --- a/blackboard/bb_variable.cpp +++ b/blackboard/bb_variable.cpp @@ -25,7 +25,7 @@ void BBVariable::set_value(const Variant &p_value) { data->value_changed = true; if (is_bound()) { - Object *obj = ObjectDB::get_instance(ObjectID(data->bound_object)); + Object *obj = OBJECT_DB_GET_INSTANCE(data->bound_object); ERR_FAIL_COND_MSG(!obj, "Blackboard: Failed to get bound object."); #ifdef LIMBOAI_MODULE bool r_valid; @@ -39,7 +39,7 @@ void BBVariable::set_value(const Variant &p_value) { Variant BBVariable::get_value() const { if (is_bound()) { - Object *obj = ObjectDB::get_instance(ObjectID(data->bound_object)); + Object *obj = OBJECT_DB_GET_INSTANCE(data->bound_object); ERR_FAIL_COND_V_MSG(!obj, data->value, "Blackboard: Failed to get bound object."); #ifdef LIMBOAI_MODULE bool r_valid; diff --git a/bt/behavior_tree.cpp b/bt/behavior_tree.cpp index b15f480..b607c19 100644 --- a/bt/behavior_tree.cpp +++ b/bt/behavior_tree.cpp @@ -77,13 +77,15 @@ void BehaviorTree::copy_other(const Ref &p_other) { root_task = p_other->get_root_task(); } -Ref BehaviorTree::instantiate(Node *p_agent, const Ref &p_blackboard, Node *p_scene_root) const { - ERR_FAIL_COND_V_MSG(root_task == nullptr, memnew(BTTask), "Trying to instance a behavior tree with no valid root task."); - ERR_FAIL_NULL_V_MSG(p_agent, memnew(BTTask), "Trying to instance a behavior tree with no valid agent."); - ERR_FAIL_NULL_V_MSG(p_scene_root, memnew(BTTask), "Trying to instance a behavior tree with no valid scene root."); - Ref inst = root_task->clone(); - inst->initialize(p_agent, p_blackboard, p_scene_root); - return inst; +Ref BehaviorTree::instantiate(Node *p_agent, const Ref &p_blackboard, Node *p_instance_owner) const { + ERR_FAIL_COND_V_MSG(root_task == nullptr, nullptr, "BehaviorTree: Instantiation failed - BT has no valid root task."); + ERR_FAIL_NULL_V_MSG(p_agent, nullptr, "BehaviorTree: Instantiation failed - agent can't be null."); + ERR_FAIL_NULL_V_MSG(p_instance_owner, nullptr, "BehaviorTree: Instantiation failed -- instance owner can't be null."); + Node *scene_root = p_instance_owner->get_owner(); + ERR_FAIL_NULL_V_MSG(scene_root, nullptr, "BehaviorTree: Instantiation failed - can't get scene root, because instance_owner not owned by a scene node. Hint: Try my_player.set_owner(get_owner())."); + Ref root_copy = root_task->clone(); + root_copy->initialize(p_agent, p_blackboard, scene_root); + return BTInstance::create(root_copy, get_path(), p_instance_owner); } void BehaviorTree::_plan_changed() { diff --git a/bt/behavior_tree.h b/bt/behavior_tree.h index 46507c5..58b2db7 100644 --- a/bt/behavior_tree.h +++ b/bt/behavior_tree.h @@ -13,6 +13,7 @@ #define BEHAVIOR_TREE_H #include "../blackboard/blackboard_plan.h" +#include "bt_instance.h" #include "tasks/bt_task.h" #ifdef LIMBOAI_MODULE @@ -58,7 +59,7 @@ public: Ref clone() const; void copy_other(const Ref &p_other); - Ref instantiate(Node *p_agent, const Ref &p_blackboard, Node *p_scene_root) const; + Ref instantiate(Node *p_agent, const Ref &p_blackboard, Node *p_instance_owner) const; BehaviorTree(); ~BehaviorTree(); diff --git a/bt/bt_instance.cpp b/bt/bt_instance.cpp new file mode 100644 index 0000000..2cfb9b0 --- /dev/null +++ b/bt/bt_instance.cpp @@ -0,0 +1,138 @@ +/** + * bt_instance.cpp + * ============================================================================= + * Copyright 2021-2024 Serhii Snitsaruk + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + * ============================================================================= + */ + +#include "bt_instance.h" + +#include "../editor/debugger/limbo_debugger.h" +#include "behavior_tree.h" + +#ifdef LIMBOAI_MODULE +#include "core/os/time.h" +#include "main/performance.h" +#endif + +#ifdef LIMBOAI_GDEXTENSION +#include +#include +#endif + +Ref BTInstance::create(Ref p_root_task, String p_source_bt_path, Node *p_owner_node) { + ERR_FAIL_NULL_V(p_root_task, nullptr); + ERR_FAIL_NULL_V(p_owner_node, nullptr); + Ref inst; + inst.instantiate(); + inst->root_task = p_root_task; + inst->owner_node_id = p_owner_node->get_instance_id(); + inst->source_bt_path = p_source_bt_path; + return inst; +} + +void BTInstance::update(double p_delta) { + ERR_FAIL_COND(!root_task.is_valid()); + +#ifdef DEBUG_ENABLED + double start = Time::get_singleton()->get_ticks_usec(); +#endif + + last_status = root_task->execute(p_delta); + emit_signal(LimboStringNames::get_singleton()->updated, last_status); + +#ifdef DEBUG_ENABLED + double end = Time::get_singleton()->get_ticks_usec(); + update_time_acc += (end - start); + update_time_n += 1.0; +#endif +} + +void BTInstance::set_monitor_performance(bool p_monitor) { +#ifdef DEBUG_ENABLED + monitor_performance = p_monitor; + if (monitor_performance) { + _add_custom_monitor(); + } else { + _remove_custom_monitor(); + } +#endif +} + +bool BTInstance::get_monitor_performance() const { +#ifdef DEBUG_ENABLED + return monitor_performance; +#else + return false; +#endif +} + +void BTInstance::register_with_debugger() { +#ifdef DEBUG_ENABLED + LimboDebugger::get_singleton()->register_bt_instance(get_instance_id()); +#endif +} + +#ifdef DEBUG_ENABLED + +double BTInstance::_get_mean_update_time_msec_and_reset() { + if (update_time_n) { + double mean_time_msec = (update_time_acc * 0.001) / update_time_n; + update_time_acc = 0.0; + update_time_n = 0.0; + return mean_time_msec; + } + return 0.0; +} + +void BTInstance::_add_custom_monitor() { + ERR_FAIL_NULL(get_owner_node()); + ERR_FAIL_NULL(root_task); + ERR_FAIL_NULL(root_task->get_agent()); + + if (monitor_id == StringName()) { + monitor_id = vformat("LimboAI/update_ms|%s_%s_%s", root_task->get_agent()->get_name(), get_owner_node()->get_name(), + String(itos(get_instance_id())).md5_text().substr(0, 4)); + } + if (!Performance::get_singleton()->has_custom_monitor(monitor_id)) { + PERFORMANCE_ADD_CUSTOM_MONITOR(monitor_id, callable_mp(this, &BTInstance::_get_mean_update_time_msec_and_reset)); + } +} + +void BTInstance::_remove_custom_monitor() { + if (monitor_id != StringName() && Performance::get_singleton()->has_custom_monitor(monitor_id)) { + Performance::get_singleton()->remove_custom_monitor(monitor_id); + } +} + +#endif // * DEBUG_ENABLED + +void BTInstance::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_root_task"), &BTInstance::get_root_task); + ClassDB::bind_method(D_METHOD("get_owner_node"), &BTInstance::get_owner_node); + ClassDB::bind_method(D_METHOD("get_last_status"), &BTInstance::get_last_status); + ClassDB::bind_method(D_METHOD("get_source_bt_path"), &BTInstance::get_source_bt_path); + + ClassDB::bind_method(D_METHOD("set_monitor_performance", "monitor"), &BTInstance::set_monitor_performance); + ClassDB::bind_method(D_METHOD("get_monitor_performance"), &BTInstance::get_monitor_performance); + + ClassDB::bind_method(D_METHOD("update", "delta"), &BTInstance::update); + + ClassDB::bind_method(D_METHOD("register_with_debugger"), &BTInstance::register_with_debugger); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "monitor_performance"), "set_monitor_performance", "get_monitor_performance"); + + ADD_SIGNAL(MethodInfo("updated", PropertyInfo(Variant::INT, "status"))); + ADD_SIGNAL(MethodInfo("freed")); +} + +BTInstance::~BTInstance() { + emit_signal(LW_NAME(freed)); +#ifdef DEBUG_ENABLED + _remove_custom_monitor(); +#endif +} diff --git a/bt/bt_instance.h b/bt/bt_instance.h new file mode 100644 index 0000000..b037fdb --- /dev/null +++ b/bt/bt_instance.h @@ -0,0 +1,60 @@ +/** + * bt_instance.h + * ============================================================================= + * Copyright 2021-2024 Serhii Snitsaruk + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + * ============================================================================= + */ +#ifndef BT_INSTANCE_H +#define BT_INSTANCE_H + +#include "tasks/bt_task.h" + +class BTInstance : public RefCounted { + GDCLASS(BTInstance, RefCounted); + +private: + Ref root_task; + uint64_t owner_node_id; + String source_bt_path; + BTTask::Status last_status = BTTask::FRESH; + +#ifdef DEBUG_ENABLED + bool monitor_performance = false; + StringName monitor_id; + double update_time_acc = 0.0; + double update_time_n = 0.0; + + double _get_mean_update_time_msec_and_reset(); + void _add_custom_monitor(); + void _remove_custom_monitor(); + +#endif // * DEBUG_ENABLED + +protected: + static void _bind_methods(); + +public: + _FORCE_INLINE_ Ref get_root_task() const { return root_task; } + _FORCE_INLINE_ Node *get_owner_node() const { return Object::cast_to(OBJECT_DB_GET_INSTANCE(owner_node_id)); } + _FORCE_INLINE_ BTTask::Status get_last_status() const { return last_status; } + _FORCE_INLINE_ bool is_instance_valid() const { return root_task.is_valid(); } + _FORCE_INLINE_ String get_source_bt_path() const { return source_bt_path; } + + void update(double p_delta); + + void set_monitor_performance(bool p_monitor); + bool get_monitor_performance() const; + + void register_with_debugger(); + + static Ref create(Ref p_root_task, String p_source_bt_path, Node *p_owner_node); + + BTInstance() = default; + ~BTInstance(); +}; + +#endif // BT_INSTANCE_H diff --git a/bt/bt_player.cpp b/bt/bt_player.cpp index ad0b515..e9d673d 100644 --- a/bt/bt_player.cpp +++ b/bt/bt_player.cpp @@ -11,7 +11,6 @@ #include "bt_player.h" -#include "../editor/debugger/limbo_debugger.h" #include "../util/limbo_compat.h" #include "../util/limbo_string_names.h" @@ -44,24 +43,18 @@ VARIANT_ENUM_CAST(BTPlayer::UpdateMode); void BTPlayer::_load_tree() { -#ifdef DEBUG_ENABLED - if (tree_instance.is_valid() && IS_DEBUGGER_ACTIVE()) { - LimboDebugger::get_singleton()->unregister_bt_instance(tree_instance, get_path()); - } -#endif - tree_instance.unref(); + bt_instance.unref(); ERR_FAIL_COND_MSG(!behavior_tree.is_valid(), "BTPlayer: Initialization failed - needs a valid behavior tree."); ERR_FAIL_COND_MSG(!behavior_tree->get_root_task().is_valid(), "BTPlayer: Initialization failed - behavior tree has no valid root task."); Node *agent = GET_NODE(this, agent_node); ERR_FAIL_NULL_MSG(agent, vformat("BTPlayer: Initialization failed - can't get agent with path '%s'.", agent_node)); Node *scene_root = get_owner(); ERR_FAIL_NULL_MSG(scene_root, "BTPlayer: Initialization failed - can't get scene root (make sure the BTPlayer's owner property is set)."); - tree_instance = behavior_tree->instantiate(agent, blackboard, scene_root); + bt_instance = behavior_tree->instantiate(agent, blackboard, this); #ifdef DEBUG_ENABLED - if (IS_DEBUGGER_ACTIVE()) { - LimboDebugger::get_singleton()->register_bt_instance(tree_instance, get_path()); - } -#endif + bt_instance->set_monitor_performance(monitor_performance); + bt_instance->register_with_debugger(); +#endif // DEBUG_ENABLED } void BTPlayer::_update_blackboard_plan() { @@ -95,7 +88,7 @@ void BTPlayer::set_behavior_tree(const Ref &p_tree) { void BTPlayer::set_agent_node(const NodePath &p_agent_node) { agent_node = p_agent_node; - if (tree_instance.is_valid()) { + if (bt_instance.is_valid()) { ERR_PRINT("BTPlayer: Agent node cannot be set after the behavior tree is instantiated. This change will not affect the behavior tree instance."); } } @@ -119,33 +112,19 @@ void BTPlayer::set_active(bool p_active) { } void BTPlayer::update(double p_delta) { - if (!tree_instance.is_valid()) { + if (!bt_instance.is_valid()) { ERR_PRINT_ONCE(vformat("BTPlayer doesn't have a behavior tree with a valid root task to execute (owner: %s)", get_owner())); return; } -#ifdef DEBUG_ENABLED - double start = GET_TICKS_USEC(); -#endif - 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); - } + bt_instance->update(p_delta); } - -#ifdef DEBUG_ENABLED - double end = GET_TICKS_USEC(); - update_time_acc += (end - start); - update_time_n += 1.0; -#endif } void BTPlayer::restart() { - ERR_FAIL_COND_MSG(tree_instance.is_null(), "BTPlayer: Restart failed - no valid tree instance. Make sure the BTPlayer has a valid behavior tree with a valid root task."); - tree_instance->abort(); + ERR_FAIL_COND_MSG(bt_instance.is_null(), "BTPlayer: Restart failed - no valid tree instance. Make sure the BTPlayer has a valid behavior tree with a valid root task."); + bt_instance->get_root_task()->abort(); set_active(true); } @@ -154,42 +133,9 @@ void BTPlayer::restart() { void BTPlayer::_set_monitor_performance(bool p_monitor_performance) { monitor_performance = p_monitor_performance; - if (!get_owner() && monitor_performance) { - // Don't add custom monitor if not in scene. - return; + if (bt_instance.is_valid()) { + bt_instance->set_monitor_performance(monitor_performance); } - - if (monitor_performance) { - _add_custom_monitor(); - } else { - _remove_custom_monitor(); - } -} - -void BTPlayer::_add_custom_monitor() { - if (monitor_id == StringName()) { - monitor_id = vformat("LimboAI/update_ms|%s_%s_%s", get_owner()->get_name(), get_name(), - String(itos(get_instance_id())).md5_text().substr(0, 4)); - } - if (!Performance::get_singleton()->has_custom_monitor(monitor_id)) { - PERFORMANCE_ADD_CUSTOM_MONITOR(monitor_id, callable_mp(this, &BTPlayer::_get_mean_update_time_msec)); - } -} - -void BTPlayer::_remove_custom_monitor() { - if (monitor_id != StringName() && Performance::get_singleton()->has_custom_monitor(monitor_id)) { - Performance::get_singleton()->remove_custom_monitor(monitor_id); - } -} - -double BTPlayer::_get_mean_update_time_msec() { - if (update_time_n) { - double mean_time_msec = (update_time_acc * 0.001) / update_time_n; - update_time_acc = 0.0; - update_time_n = 0.0; - return mean_time_msec; - } - return 0.0; } #endif // ! DEBUG_ENABLED @@ -222,21 +168,15 @@ void BTPlayer::_notification(int p_notification) { } break; case NOTIFICATION_ENTER_TREE: { #ifdef DEBUG_ENABLED - if (tree_instance.is_valid() && IS_DEBUGGER_ACTIVE()) { - LimboDebugger::get_singleton()->register_bt_instance(tree_instance, get_path()); - } - if (monitor_performance) { - _add_custom_monitor(); + if (bt_instance.is_valid()) { + bt_instance->set_monitor_performance(monitor_performance); } #endif // DEBUG_ENABLED } break; case NOTIFICATION_EXIT_TREE: { #ifdef DEBUG_ENABLED - if (tree_instance.is_valid() && IS_DEBUGGER_ACTIVE()) { - LimboDebugger::get_singleton()->unregister_bt_instance(tree_instance, get_path()); - } - if (monitor_performance) { - _remove_custom_monitor(); + if (bt_instance.is_valid()) { + bt_instance->set_monitor_performance(false); } #endif // DEBUG_ENABLED @@ -266,9 +206,8 @@ void BTPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("update", "delta"), &BTPlayer::update); ClassDB::bind_method(D_METHOD("restart"), &BTPlayer::restart); - ClassDB::bind_method(D_METHOD("get_last_status"), &BTPlayer::get_last_status); - ClassDB::bind_method(D_METHOD("get_tree_instance"), &BTPlayer::get_tree_instance); + ClassDB::bind_method(D_METHOD("get_bt_instance"), &BTPlayer::get_bt_instance); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "behavior_tree", PROPERTY_HINT_RESOURCE_TYPE, "BehaviorTree"), "set_behavior_tree", "get_behavior_tree"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "agent_node"), "set_agent_node", "get_agent_node"); diff --git a/bt/bt_player.h b/bt/bt_player.h index 9ff705c..62f6d9f 100644 --- a/bt/bt_player.h +++ b/bt/bt_player.h @@ -15,6 +15,7 @@ #include "../blackboard/blackboard.h" #include "../blackboard/blackboard_plan.h" #include "behavior_tree.h" +#include "bt_instance.h" #include "tasks/bt_task.h" #ifdef LIMBOAI_MODULE @@ -42,9 +43,8 @@ private: UpdateMode update_mode = UpdateMode::PHYSICS; bool active = true; Ref blackboard; - int last_status = -1; - Ref tree_instance; + Ref bt_instance; void _load_tree(); void _update_blackboard_plan(); @@ -75,9 +75,8 @@ public: void update(double p_delta); void restart(); - int get_last_status() const { return last_status; } - Ref get_tree_instance() { return tree_instance; } + Ref get_bt_instance() { return bt_instance; } BTPlayer(); ~BTPlayer(); @@ -86,19 +85,11 @@ public: private: bool monitor_performance = false; - StringName monitor_id; - double update_time_acc = 0.0; - double update_time_n = 0.0; void _set_monitor_performance(bool p_monitor_performance); bool _get_monitor_performance() const { return monitor_performance; } - void _add_custom_monitor(); - void _remove_custom_monitor(); - - double _get_mean_update_time_msec(); - -#endif // DEBUG_ENABLED +#endif // * DEBUG_ENABLED }; #endif // BT_PLAYER_H diff --git a/editor/debugger/behavior_tree_data.cpp b/editor/debugger/behavior_tree_data.cpp index c82312f..efe4ea8 100644 --- a/editor/debugger/behavior_tree_data.cpp +++ b/editor/debugger/behavior_tree_data.cpp @@ -17,14 +17,15 @@ //**** BehaviorTreeData -Array BehaviorTreeData::serialize(const Ref &p_tree_instance, const NodePath &p_player_path, const String &p_bt_resource_path) { +Array BehaviorTreeData::serialize(const Ref &p_instance) { Array arr; - arr.push_back(p_player_path); - arr.push_back(p_bt_resource_path); + arr.push_back(uint64_t(p_instance->get_instance_id())); + arr.push_back(p_instance->get_owner_node() ? p_instance->get_owner_node()->get_path() : NodePath()); + arr.push_back(p_instance->get_source_bt_path()); // Flatten tree into list depth first List> stack; - stack.push_back(p_tree_instance); + stack.push_back(p_instance->get_root_task()); while (stack.size()) { Ref task = stack.front()->get(); stack.pop_front(); @@ -54,15 +55,17 @@ Array BehaviorTreeData::serialize(const Ref &p_tree_instance, const Node } Ref BehaviorTreeData::deserialize(const Array &p_array) { - ERR_FAIL_COND_V(p_array.size() < 2, nullptr); - ERR_FAIL_COND_V(p_array[0].get_type() != Variant::NODE_PATH, nullptr); - ERR_FAIL_COND_V(p_array[1].get_type() != Variant::STRING, nullptr); + ERR_FAIL_COND_V(p_array.size() < 3, nullptr); + ERR_FAIL_COND_V(p_array[0].get_type() != Variant::INT, nullptr); + ERR_FAIL_COND_V(p_array[1].get_type() != Variant::NODE_PATH, nullptr); + ERR_FAIL_COND_V(p_array[2].get_type() != Variant::STRING, nullptr); Ref data = memnew(BehaviorTreeData); - data->bt_player_path = p_array[0]; - data->bt_resource_path = p_array[1]; + data->bt_instance_id = uint64_t(p_array[0]); + data->node_owner_path = p_array[1]; + data->source_bt_path = p_array[2]; - int idx = 2; + int idx = 3; while (p_array.size() > idx + 1) { ERR_FAIL_COND_V(p_array.size() < idx + 7, nullptr); ERR_FAIL_COND_V(p_array[idx].get_type() != Variant::INT, nullptr); @@ -80,12 +83,16 @@ Ref BehaviorTreeData::deserialize(const Array &p_array) { return data; } -Ref BehaviorTreeData::create_from_tree_instance(const Ref &p_tree_instance) { +Ref BehaviorTreeData::create_from_bt_instance(const Ref &p_bt_instance) { Ref data = memnew(BehaviorTreeData); + data->bt_instance_id = p_bt_instance->get_instance_id(); + data->node_owner_path = p_bt_instance->get_owner_node() ? p_bt_instance->get_owner_node()->get_path() : NodePath(); + data->source_bt_path = p_bt_instance->get_source_bt_path(); + // Flatten tree into list depth first List> stack; - stack.push_back(p_tree_instance); + stack.push_back(p_bt_instance->get_root_task()); while (stack.size()) { Ref task = stack.front()->get(); stack.pop_front(); @@ -115,9 +122,7 @@ Ref BehaviorTreeData::create_from_tree_instance(const Ref tasks; - NodePath bt_player_path; - String bt_resource_path; + uint64_t bt_instance_id; + NodePath node_owner_path; + String source_bt_path; public: - static Array serialize(const Ref &p_tree_instance, const NodePath &p_player_path, const String &p_bt_resource_path); + static Array serialize(const Ref &p_instance); static Ref deserialize(const Array &p_array); - static Ref create_from_tree_instance(const Ref &p_tree_instance); + static Ref create_from_bt_instance(const Ref &p_bt_instance); BehaviorTreeData(); }; diff --git a/editor/debugger/limbo_debugger.cpp b/editor/debugger/limbo_debugger.cpp index 9b28333..89294e2 100644 --- a/editor/debugger/limbo_debugger.cpp +++ b/editor/debugger/limbo_debugger.cpp @@ -11,13 +11,13 @@ #include "limbo_debugger.h" +#include "../../bt/bt_instance.h" #include "../../bt/tasks/bt_task.h" #include "../../util/limbo_compat.h" #include "behavior_tree_data.h" #ifdef LIMBOAI_MODULE #include "core/debugger/engine_debugger.h" -#include "core/error/error_macros.h" #include "core/io/resource.h" #include "core/string/node_path.h" #include "scene/main/scene_tree.h" @@ -63,14 +63,6 @@ void LimboDebugger::deinitialize() { } void LimboDebugger::_bind_methods() { -#ifdef DEBUG_ENABLED - -#ifdef LIMBOAI_GDEXTENSION - ClassDB::bind_method(D_METHOD("parse_message_gdext"), &LimboDebugger::parse_message_gdext); -#endif - ClassDB::bind_method(D_METHOD("_on_bt_updated", "status", "path"), &LimboDebugger::_on_bt_updated); - ClassDB::bind_method(D_METHOD("_on_state_updated", "delta", "path"), &LimboDebugger::_on_state_updated); -#endif // ! DEBUG_ENABLED } #ifdef DEBUG_ENABLED @@ -99,100 +91,87 @@ bool LimboDebugger::parse_message_gdext(const String &p_msg, const Array &p_args } #endif // LIMBOAI_GDEXTENSION -void LimboDebugger::register_bt_instance(Ref p_instance, NodePath p_player_path) { - ERR_FAIL_COND(p_instance.is_null()); - ERR_FAIL_COND(p_player_path.is_empty()); - if (active_trees.has(p_player_path)) { +void LimboDebugger::register_bt_instance(uint64_t p_instance_id) { + ERR_FAIL_COND(p_instance_id == 0); + BTInstance *inst = Object::cast_to(OBJECT_DB_GET_INSTANCE(p_instance_id)); + ERR_FAIL_NULL(inst); + ERR_FAIL_COND(!inst->is_instance_valid()); + + if (active_bt_instances.has(p_instance_id)) { return; } - active_trees.insert(p_player_path, p_instance); + if (!inst->is_connected(LW_NAME(freed), callable_mp(this, &LimboDebugger::unregister_bt_instance).bind(p_instance_id))) { + inst->connect(LW_NAME(freed), callable_mp(this, &LimboDebugger::unregister_bt_instance).bind(p_instance_id)); + } + + active_bt_instances.insert(p_instance_id); if (session_active) { _send_active_bt_players(); } } -void LimboDebugger::unregister_bt_instance(Ref p_instance, NodePath p_player_path) { - ERR_FAIL_COND(p_instance.is_null()); - ERR_FAIL_COND(p_player_path.is_empty()); - ERR_FAIL_COND(!active_trees.has(p_player_path)); +void LimboDebugger::unregister_bt_instance(uint64_t p_instance_id) { + if (!active_bt_instances.has(p_instance_id)) { + return; + } - if (tracked_player == p_player_path) { + if (tracked_instance_id == p_instance_id) { _untrack_tree(); } - active_trees.erase(p_player_path); + active_bt_instances.erase(p_instance_id); if (session_active) { _send_active_bt_players(); } } -void LimboDebugger::_track_tree(NodePath p_path) { - ERR_FAIL_COND(!active_trees.has(p_path)); +void LimboDebugger::_track_tree(uint64_t p_instance_id) { + ERR_FAIL_COND(!active_bt_instances.has(p_instance_id)); - if (!tracked_player.is_empty()) { - _untrack_tree(); - } + _untrack_tree(); - Node *node = SCENE_TREE()->get_root()->get_node_or_null(p_path); - ERR_FAIL_COND(node == nullptr); + tracked_instance_id = p_instance_id; - tracked_player = p_path; - - Ref bt = node->get(LW_NAME(behavior_tree)); - - if (bt.is_valid()) { - bt_resource_path = bt->get_path(); - } else { - bt_resource_path = ""; - } - - if (node->is_class("BTPlayer")) { - node->connect(LW_NAME(updated), callable_mp(this, &LimboDebugger::_on_bt_updated).bind(p_path)); - } else if (node->is_class("BTState")) { - node->connect(LW_NAME(updated), callable_mp(this, &LimboDebugger::_on_state_updated).bind(p_path)); - } + BTInstance *inst = Object::cast_to(OBJECT_DB_GET_INSTANCE(p_instance_id)); + ERR_FAIL_NULL(inst); + inst->connect(LW_NAME(updated), callable_mp(this, &LimboDebugger::_on_bt_instance_updated).bind(p_instance_id)); } void LimboDebugger::_untrack_tree() { - if (tracked_player.is_empty()) { + if (tracked_instance_id == 0) { return; } - NodePath was_tracking = tracked_player; - tracked_player = NodePath(); - - Node *node = SCENE_TREE()->get_root()->get_node_or_null(was_tracking); - ERR_FAIL_COND(node == nullptr); - - if (node->is_class("BTPlayer")) { - node->disconnect(LW_NAME(updated), callable_mp(this, &LimboDebugger::_on_bt_updated)); - } else if (node->is_class("BTState")) { - node->disconnect(LW_NAME(updated), callable_mp(this, &LimboDebugger::_on_state_updated)); + BTInstance *inst = Object::cast_to(OBJECT_DB_GET_INSTANCE(tracked_instance_id)); + if (inst) { + inst->disconnect(LW_NAME(updated), callable_mp(this, &LimboDebugger::_on_bt_instance_updated)); } + tracked_instance_id = 0; } void LimboDebugger::_send_active_bt_players() { Array arr; - for (KeyValue> kv : active_trees) { - arr.append(kv.key); + for (uint64_t instance_id : active_bt_instances) { + arr.append(instance_id); + BTInstance *inst = Object::cast_to(OBJECT_DB_GET_INSTANCE(instance_id)); + if (inst == nullptr) { + ERR_PRINT("LimboDebugger::_send_active_bt_players: Registered BTInstance not found (no longer exists?)."); + continue; + } + Node *owner_node = inst->get_owner_node(); + arr.append(owner_node ? owner_node->get_path() : NodePath()); } EngineDebugger::get_singleton()->send_message("limboai:active_bt_players", arr); } -void LimboDebugger::_on_bt_updated(int _status, NodePath p_path) { - if (p_path != tracked_player) { +void LimboDebugger::_on_bt_instance_updated(int _status, uint64_t p_instance_id) { + if (p_instance_id != tracked_instance_id) { return; } - Array arr = BehaviorTreeData::serialize(active_trees.get(tracked_player), tracked_player, bt_resource_path); - EngineDebugger::get_singleton()->send_message("limboai:bt_update", arr); -} - -void LimboDebugger::_on_state_updated(float _delta, NodePath p_path) { - if (p_path != tracked_player) { - return; - } - Array arr = BehaviorTreeData::serialize(active_trees.get(tracked_player), tracked_player, bt_resource_path); + BTInstance *inst = Object::cast_to(OBJECT_DB_GET_INSTANCE(p_instance_id)); + ERR_FAIL_NULL(inst); + Array arr = BehaviorTreeData::serialize(inst); EngineDebugger::get_singleton()->send_message("limboai:bt_update", arr); } diff --git a/editor/debugger/limbo_debugger.h b/editor/debugger/limbo_debugger.h index 1130d30..05becbe 100644 --- a/editor/debugger/limbo_debugger.h +++ b/editor/debugger/limbo_debugger.h @@ -12,6 +12,7 @@ #ifndef LIMBO_DEBUGGER_H #define LIMBO_DEBUGGER_H +#include "../../bt/bt_instance.h" #include "../../bt/tasks/bt_task.h" #ifdef LIMBOAI_MODULE @@ -23,6 +24,7 @@ #ifdef LIMBOAI_GDEXTENSION #include #include +#include #include #endif // LIMBOAI_GDEXTENSION @@ -46,17 +48,15 @@ protected: #ifdef DEBUG_ENABLED private: - HashMap> active_trees; - NodePath tracked_player; - String bt_resource_path; + HashSet active_bt_instances; + uint64_t tracked_instance_id; bool session_active = false; - void _track_tree(NodePath p_path); + void _track_tree(uint64_t p_instance_id); void _untrack_tree(); void _send_active_bt_players(); - void _on_bt_updated(int status, NodePath p_path); - void _on_state_updated(float _delta, NodePath p_path); + void _on_bt_instance_updated(int status, uint64_t p_instance_id); public: static Error parse_message(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured); @@ -64,8 +64,8 @@ public: bool parse_message_gdext(const String &p_msg, const Array &p_args); #endif - void register_bt_instance(Ref p_instance, NodePath p_player_path); - void unregister_bt_instance(Ref p_instance, NodePath p_player_path); + void register_bt_instance(uint64_t p_instance_id); + void unregister_bt_instance(uint64_t p_instance_id); #endif // ! DEBUG_ENABLED }; diff --git a/editor/debugger/limbo_debugger_plugin.cpp b/editor/debugger/limbo_debugger_plugin.cpp index 697796f..53ae161 100644 --- a/editor/debugger/limbo_debugger_plugin.cpp +++ b/editor/debugger/limbo_debugger_plugin.cpp @@ -58,7 +58,7 @@ //**** LimboDebuggerTab void LimboDebuggerTab::_reset_controls() { - bt_player_list->clear(); + bt_instance_list->clear(); bt_view->clear(); alert_box->hide(); info_message->set_text(TTR("Run project to start debugging.")); @@ -68,7 +68,7 @@ void LimboDebuggerTab::_reset_controls() { } void LimboDebuggerTab::start_session() { - bt_player_list->clear(); + bt_instance_list->clear(); bt_view->clear(); alert_box->hide(); info_message->set_text(TTR("Pick a player from the list to display behavior tree.")); @@ -81,23 +81,25 @@ void LimboDebuggerTab::stop_session() { session->send_message("limboai:stop_session", Array()); } -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]); +void LimboDebuggerTab::update_active_bt_instances(const Array &p_data) { + active_bt_instances.clear(); + for (int i = 0; i < p_data.size(); i += 2) { + BTInstanceInfo info{ p_data[i], p_data[i + 1] }; + + active_bt_instances.push_back(info); } - _update_bt_player_list(active_bt_players, filter_players->get_text()); + _update_bt_instance_list(active_bt_instances, filter_players->get_text()); } -String LimboDebuggerTab::get_selected_bt_player() { - if (!bt_player_list->is_anything_selected()) { - return ""; +uint64_t LimboDebuggerTab::get_selected_bt_instance_id() { + if (!bt_instance_list->is_anything_selected()) { + return 0; } - return bt_player_list->get_item_text(bt_player_list->get_selected_items()[0]); + return bt_instance_list->get_item_metadata(bt_instance_list->get_selected_items()[0]); } void LimboDebuggerTab::update_behavior_tree(const Ref &p_data) { - resource_header->set_text(p_data->bt_resource_path); + resource_header->set_text(p_data->source_bt_path); resource_header->set_disabled(false); bt_view->update_tree(p_data); info_message->hide(); @@ -108,58 +110,58 @@ void LimboDebuggerTab::_show_alert(const String &p_message) { alert_box->set_visible(!p_message.is_empty()); } -void LimboDebuggerTab::_update_bt_player_list(const List &p_node_paths, const String &p_filter) { +void LimboDebuggerTab::_update_bt_instance_list(const Vector &p_instances, const String &p_filter) { // Remember selected item. - String selected_player = ""; - if (bt_player_list->is_anything_selected()) { - selected_player = bt_player_list->get_item_text(bt_player_list->get_selected_items()[0]); + uint64_t selected_instance_id = 0; + if (bt_instance_list->is_anything_selected()) { + selected_instance_id = uint64_t(bt_instance_list->get_item_metadata(bt_instance_list->get_selected_items()[0])); } - bt_player_list->clear(); + bt_instance_list->clear(); int select_idx = -1; 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); + for (const BTInstanceInfo &info : p_instances) { + if (p_filter.is_empty() || info.owner_node_path.contains(p_filter)) { + int idx = bt_instance_list->add_item(info.owner_node_path); + bt_instance_list->set_item_metadata(idx, info.instance_id); // 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) { + bt_instance_list->set_item_text_direction(idx, TEXT_DIRECTION_RTL); + if (info.instance_id == selected_instance_id) { select_idx = idx; } - } else if (p == selected_player) { + } else if (info.instance_id == selected_instance_id) { selection_filtered_out = true; } } // Restore selected item. if (select_idx > -1) { - bt_player_list->select(select_idx); - } else if (!selected_player.is_empty()) { + bt_instance_list->select(select_idx); + } else if (selected_instance_id != 0) { 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."); + _show_alert(TTR("Behavior tree instance is no longer present.")); } } } -void LimboDebuggerTab::_bt_selected(int p_idx) { +void LimboDebuggerTab::_bt_instance_selected(int p_idx) { alert_box->hide(); bt_view->clear(); info_message->set_text(TTR("Waiting for behavior tree update.")); info_message->show(); resource_header->set_text(TTR("Waiting for data")); resource_header->set_disabled(true); - NodePath path = bt_player_list->get_item_text(p_idx); Array msg_data; - msg_data.push_back(path); + msg_data.push_back(bt_instance_list->get_item_metadata(p_idx)); session->send_message("limboai:track_bt_player", msg_data); } void LimboDebuggerTab::_filter_changed(String p_text) { - _update_bt_player_list(active_bt_players, p_text); + _update_bt_instance_list(active_bt_instances, p_text); } void LimboDebuggerTab::_window_visibility_changed(bool p_visible) { @@ -185,7 +187,7 @@ void LimboDebuggerTab::_notification(int p_what) { case NOTIFICATION_READY: { 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)); + bt_instance_list->connect(LW_NAME(item_selected), callable_mp(this, &LimboDebuggerTab::_bt_instance_selected)); update_interval->connect("value_changed", callable_mp(bt_view, &BehaviorTreeView::set_update_interval_msec)); Ref cf; @@ -271,11 +273,11 @@ LimboDebuggerTab::LimboDebuggerTab() { filter_players->set_placeholder(TTR("Filter Players")); 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); - list_box->add_child(bt_player_list); + bt_instance_list = memnew(ItemList); + bt_instance_list->set_custom_minimum_size(Size2(240.0 * EDSCALE, 0.0)); + bt_instance_list->set_h_size_flags(SIZE_FILL); + bt_instance_list->set_v_size_flags(SIZE_EXPAND_FILL); + list_box->add_child(bt_instance_list); view_box = memnew(VBoxContainer); hsc->add_child(view_box); @@ -352,10 +354,10 @@ bool LimboDebuggerPlugin::_capture(const String &p_message, const Array &p_data, ERR_FAIL_NULL_V(tab, false); bool captured = true; if (p_message == "limboai:active_bt_players") { - tab->update_active_bt_players(p_data); + tab->update_active_bt_instances(p_data); } else if (p_message == "limboai:bt_update") { Ref data = BehaviorTreeData::deserialize(p_data); - if (data->bt_player_path == NodePath(tab->get_selected_bt_player())) { + if (data->bt_instance_id == tab->get_selected_bt_instance_id()) { tab->update_behavior_tree(data); } } else { diff --git a/editor/debugger/limbo_debugger_plugin.h b/editor/debugger/limbo_debugger_plugin.h index ca48099..0da1567 100644 --- a/editor/debugger/limbo_debugger_plugin.h +++ b/editor/debugger/limbo_debugger_plugin.h @@ -51,13 +51,18 @@ class LimboDebuggerTab : public PanelContainer { GDCLASS(LimboDebuggerTab, PanelContainer); private: - List active_bt_players; + struct BTInstanceInfo { + uint64_t instance_id; + String owner_node_path; + }; + + Vector active_bt_instances; Ref session; VBoxContainer *root_vb = nullptr; HBoxContainer *toolbar = nullptr; HSplitContainer *hsc = nullptr; Label *info_message = nullptr; - ItemList *bt_player_list = nullptr; + ItemList *bt_instance_list = nullptr; BehaviorTreeView *bt_view = nullptr; VBoxContainer *view_box = nullptr; HBoxContainer *alert_box = nullptr; @@ -71,8 +76,8 @@ private: void _reset_controls(); void _show_alert(const String &p_message); - void _update_bt_player_list(const List &p_node_paths, const String &p_filter); - void _bt_selected(int p_idx); + void _update_bt_instance_list(const Vector &p_instances, const String &p_filter); + void _bt_instance_selected(int p_idx); void _filter_changed(String p_text); void _window_visibility_changed(bool p_visible); void _resource_header_pressed(); @@ -84,9 +89,9 @@ protected: public: void start_session(); void stop_session(); - void update_active_bt_players(const Array &p_node_paths); + void update_active_bt_instances(const Array &p_data); BehaviorTreeView *get_behavior_tree_view() const { return bt_view; } - String get_selected_bt_player(); + uint64_t get_selected_bt_instance_id(); void update_behavior_tree(const Ref &p_data); void setup(Ref p_session, CompatWindowWrapper *p_wrapper); diff --git a/register_types.cpp b/register_types.cpp index 967f7e9..a04ef19 100644 --- a/register_types.cpp +++ b/register_types.cpp @@ -148,6 +148,7 @@ void initialize_limboai_module(ModuleInitializationLevel p_level) { GDREGISTER_ABSTRACT_CLASS(BT); GDREGISTER_ABSTRACT_CLASS(BTTask); GDREGISTER_CLASS(BehaviorTree); + GDREGISTER_CLASS(BTInstance); GDREGISTER_CLASS(BTPlayer); GDREGISTER_CLASS(BTState); diff --git a/util/limbo_compat.h b/util/limbo_compat.h index 21172e4..e75bacc 100644 --- a/util/limbo_compat.h +++ b/util/limbo_compat.h @@ -54,6 +54,7 @@ #define GET_SCRIPT(m_obj) (m_obj->get_script_instance() ? m_obj->get_script_instance()->get_script() : nullptr) #define ADD_STYLEBOX_OVERRIDE(m_control, m_name, m_stylebox) (m_control->add_theme_style_override(m_name, m_stylebox)) #define GET_NODE(m_parent, m_path) m_parent->get_node(m_path) +#define OBJECT_DB_GET_INSTANCE(m_id) ObjectDB::get_instance(ObjectID(m_id)) _FORCE_INLINE_ bool OBJECT_HAS_PROPERTY(Object *p_obj, const StringName &p_prop) { bool r_valid; @@ -112,6 +113,7 @@ using namespace godot; #define GET_SCRIPT(m_obj) (m_obj->get_script()) #define ADD_STYLEBOX_OVERRIDE(m_control, m_name, m_stylebox) (m_control->add_theme_stylebox_override(m_name, m_stylebox)) #define GET_NODE(m_parent, m_path) m_parent->get_node_internal(m_path) +#define OBJECT_DB_GET_INSTANCE(m_id) ObjectDB::get_instance(m_id) _FORCE_INLINE_ bool OBJECT_HAS_PROPERTY(Object *p_obj, const StringName &p_prop) { return Variant(p_obj).has_key(p_prop); diff --git a/util/limbo_string_names.cpp b/util/limbo_string_names.cpp index c4f7d50..ee8d5f5 100644 --- a/util/limbo_string_names.cpp +++ b/util/limbo_string_names.cpp @@ -85,6 +85,7 @@ LimboStringNames::LimboStringNames() { font_color = SN("font_color"); font_size = SN("font_size"); Forward = SN("Forward"); + freed = SN("freed"); gui_input = SN("gui_input"); GuiOptionArrow = SN("GuiOptionArrow"); GuiTreeArrowDown = SN("GuiTreeArrowDown"); diff --git a/util/limbo_string_names.h b/util/limbo_string_names.h index d7ee502..9194b81 100644 --- a/util/limbo_string_names.h +++ b/util/limbo_string_names.h @@ -101,6 +101,7 @@ public: StringName font_size; StringName font; StringName Forward; + StringName freed; StringName gui_input; StringName GuiOptionArrow; StringName GuiTreeArrowDown;