Merge pull request #181 from limbonaut/bt-instance

Implement `BTInstance` - runtime instance of `BehaviorTree`
This commit is contained in:
Serhii Snitsaruk 2024-08-04 10:15:08 +02:00 committed by GitHub
commit d2ba904243
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 758 additions and 335 deletions

View File

@ -25,7 +25,7 @@ void BBVariable::set_value(const Variant &p_value) {
data->value_changed = true; data->value_changed = true;
if (is_bound()) { 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."); ERR_FAIL_COND_MSG(!obj, "Blackboard: Failed to get bound object.");
#ifdef LIMBOAI_MODULE #ifdef LIMBOAI_MODULE
bool r_valid; bool r_valid;
@ -39,7 +39,7 @@ void BBVariable::set_value(const Variant &p_value) {
Variant BBVariable::get_value() const { Variant BBVariable::get_value() const {
if (is_bound()) { 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."); ERR_FAIL_COND_V_MSG(!obj, data->value, "Blackboard: Failed to get bound object.");
#ifdef LIMBOAI_MODULE #ifdef LIMBOAI_MODULE
bool r_valid; bool r_valid;

View File

@ -77,13 +77,15 @@ void BehaviorTree::copy_other(const Ref<BehaviorTree> &p_other) {
root_task = p_other->get_root_task(); root_task = p_other->get_root_task();
} }
Ref<BTTask> BehaviorTree::instantiate(Node *p_agent, const Ref<Blackboard> &p_blackboard, Node *p_scene_root) const { Ref<BTInstance> BehaviorTree::instantiate(Node *p_agent, const Ref<Blackboard> &p_blackboard, Node *p_instance_owner) const {
ERR_FAIL_COND_V_MSG(root_task == nullptr, memnew(BTTask), "Trying to instance a behavior tree with no valid root task."); 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, memnew(BTTask), "Trying to instance a behavior tree with no valid agent."); ERR_FAIL_NULL_V_MSG(p_agent, nullptr, "BehaviorTree: Instantiation failed - agent can't be null.");
ERR_FAIL_NULL_V_MSG(p_scene_root, memnew(BTTask), "Trying to instance a behavior tree with no valid scene root."); ERR_FAIL_NULL_V_MSG(p_instance_owner, nullptr, "BehaviorTree: Instantiation failed -- instance owner can't be null.");
Ref<BTTask> inst = root_task->clone(); Node *scene_root = p_instance_owner->get_owner();
inst->initialize(p_agent, p_blackboard, p_scene_root); 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()).");
return inst; Ref<BTTask> 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() { void BehaviorTree::_plan_changed() {
@ -116,7 +118,7 @@ void BehaviorTree::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_root_task"), &BehaviorTree::get_root_task); ClassDB::bind_method(D_METHOD("get_root_task"), &BehaviorTree::get_root_task);
ClassDB::bind_method(D_METHOD("clone"), &BehaviorTree::clone); ClassDB::bind_method(D_METHOD("clone"), &BehaviorTree::clone);
ClassDB::bind_method(D_METHOD("copy_other", "other"), &BehaviorTree::copy_other); ClassDB::bind_method(D_METHOD("copy_other", "other"), &BehaviorTree::copy_other);
ClassDB::bind_method(D_METHOD("instantiate", "agent", "blackboard", "scene_root"), &BehaviorTree::instantiate); ClassDB::bind_method(D_METHOD("instantiate", "agent", "blackboard", "instance_owner"), &BehaviorTree::instantiate);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "description", PROPERTY_HINT_MULTILINE_TEXT), "set_description", "get_description"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "description", PROPERTY_HINT_MULTILINE_TEXT), "set_description", "get_description");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "blackboard_plan", PROPERTY_HINT_RESOURCE_TYPE, "BlackboardPlan", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT), "set_blackboard_plan", "get_blackboard_plan"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "blackboard_plan", PROPERTY_HINT_RESOURCE_TYPE, "BlackboardPlan", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT), "set_blackboard_plan", "get_blackboard_plan");

View File

@ -13,6 +13,7 @@
#define BEHAVIOR_TREE_H #define BEHAVIOR_TREE_H
#include "../blackboard/blackboard_plan.h" #include "../blackboard/blackboard_plan.h"
#include "bt_instance.h"
#include "tasks/bt_task.h" #include "tasks/bt_task.h"
#ifdef LIMBOAI_MODULE #ifdef LIMBOAI_MODULE
@ -58,7 +59,7 @@ public:
Ref<BehaviorTree> clone() const; Ref<BehaviorTree> clone() const;
void copy_other(const Ref<BehaviorTree> &p_other); void copy_other(const Ref<BehaviorTree> &p_other);
Ref<BTTask> instantiate(Node *p_agent, const Ref<Blackboard> &p_blackboard, Node *p_scene_root) const; Ref<BTInstance> instantiate(Node *p_agent, const Ref<Blackboard> &p_blackboard, Node *p_instance_owner) const;
BehaviorTree(); BehaviorTree();
~BehaviorTree(); ~BehaviorTree();

150
bt/bt_instance.cpp Normal file
View File

@ -0,0 +1,150 @@
/**
* 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 <godot_cpp/classes/performance.hpp>
#include <godot_cpp/classes/time.hpp>
#endif
Ref<BTInstance> BTInstance::create(Ref<BTTask> 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<BTInstance> 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;
}
BT::Status BTInstance::update(double p_delta) {
ERR_FAIL_COND_V(!root_task.is_valid(), BT::FRESH);
#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
return last_status;
}
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
if (LimboDebugger::get_singleton()->is_active()) {
LimboDebugger::get_singleton()->register_bt_instance(get_instance_id());
}
#endif
}
void BTInstance::unregister_with_debugger() {
#ifdef DEBUG_ENABLED
if (LimboDebugger::get_singleton()->is_active()) {
LimboDebugger::get_singleton()->unregister_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);
ClassDB::bind_method(D_METHOD("unregister_with_debugger"), &BTInstance::unregister_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
}

61
bt/bt_instance.h Normal file
View File

@ -0,0 +1,61 @@
/**
* 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<BTTask> root_task;
uint64_t owner_node_id = 0;
String source_bt_path;
BT::Status last_status = BT::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<BTTask> get_root_task() const { return root_task; }
_FORCE_INLINE_ Node *get_owner_node() const { return owner_node_id ? Object::cast_to<Node>(OBJECT_DB_GET_INSTANCE(owner_node_id)) : nullptr; }
_FORCE_INLINE_ BT::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; }
BT::Status update(double p_delta);
void set_monitor_performance(bool p_monitor);
bool get_monitor_performance() const;
void register_with_debugger();
void unregister_with_debugger();
static Ref<BTInstance> create(Ref<BTTask> p_root_task, String p_source_bt_path, Node *p_owner_node);
BTInstance() = default;
~BTInstance();
};
#endif // BT_INSTANCE_H

View File

@ -11,7 +11,6 @@
#include "bt_player.h" #include "bt_player.h"
#include "../editor/debugger/limbo_debugger.h"
#include "../util/limbo_compat.h" #include "../util/limbo_compat.h"
#include "../util/limbo_string_names.h" #include "../util/limbo_string_names.h"
@ -44,24 +43,19 @@
VARIANT_ENUM_CAST(BTPlayer::UpdateMode); VARIANT_ENUM_CAST(BTPlayer::UpdateMode);
void BTPlayer::_load_tree() { void BTPlayer::_load_tree() {
#ifdef DEBUG_ENABLED bt_instance.unref();
if (tree_instance.is_valid() && IS_DEBUGGER_ACTIVE()) {
LimboDebugger::get_singleton()->unregister_bt_instance(tree_instance, get_path());
}
#endif
tree_instance.unref();
ERR_FAIL_COND_MSG(!behavior_tree.is_valid(), "BTPlayer: Initialization failed - needs a valid behavior tree."); 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."); 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); 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)); ERR_FAIL_NULL_MSG(agent, vformat("BTPlayer: Initialization failed - can't get agent with path '%s'.", agent_node));
Node *scene_root = get_owner(); 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)."); 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);
ERR_FAIL_COND_MSG(bt_instance.is_null(), "BTPlayer: Failed to instantiate behavior tree.");
#ifdef DEBUG_ENABLED #ifdef DEBUG_ENABLED
if (IS_DEBUGGER_ACTIVE()) { bt_instance->set_monitor_performance(monitor_performance);
LimboDebugger::get_singleton()->register_bt_instance(tree_instance, get_path()); bt_instance->register_with_debugger();
} #endif // DEBUG_ENABLED
#endif
} }
void BTPlayer::_update_blackboard_plan() { void BTPlayer::_update_blackboard_plan() {
@ -87,7 +81,7 @@ void BTPlayer::set_behavior_tree(const Ref<BehaviorTree> &p_tree) {
_update_blackboard_plan(); _update_blackboard_plan();
} else { } else {
behavior_tree = p_tree; behavior_tree = p_tree;
if (get_owner()) { if (get_owner() && is_inside_tree()) {
_load_tree(); _load_tree();
} }
} }
@ -95,7 +89,7 @@ void BTPlayer::set_behavior_tree(const Ref<BehaviorTree> &p_tree) {
void BTPlayer::set_agent_node(const NodePath &p_agent_node) { void BTPlayer::set_agent_node(const NodePath &p_agent_node) {
agent_node = 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."); 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 +113,25 @@ void BTPlayer::set_active(bool p_active) {
} }
void BTPlayer::update(double p_delta) { 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())); ERR_PRINT_ONCE(vformat("BTPlayer doesn't have a behavior tree with a valid root task to execute (owner: %s)", get_owner()));
return; return;
} }
#ifdef DEBUG_ENABLED
double start = GET_TICKS_USEC();
#endif
if (active) { if (active) {
last_status = tree_instance->execute(p_delta); BT::Status status = bt_instance->update(p_delta);
emit_signal(LimboStringNames::get_singleton()->updated, last_status); emit_signal(LW_NAME(updated), status);
if (last_status == BTTask::SUCCESS || last_status == BTTask::FAILURE) { #ifndef DISABLE_DEPRECATED
emit_signal(LimboStringNames::get_singleton()->behavior_tree_finished, last_status); if (status == BTTask::SUCCESS || status == BTTask::FAILURE) {
emit_signal(LW_NAME(behavior_tree_finished), status);
} }
#endif // DISABLE_DEPRECATED
} }
#ifdef DEBUG_ENABLED
double end = GET_TICKS_USEC();
update_time_acc += (end - start);
update_time_n += 1.0;
#endif
} }
void BTPlayer::restart() { 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."); 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.");
tree_instance->abort(); bt_instance->get_root_task()->abort();
set_active(true); set_active(true);
} }
@ -154,42 +140,9 @@ void BTPlayer::restart() {
void BTPlayer::_set_monitor_performance(bool p_monitor_performance) { void BTPlayer::_set_monitor_performance(bool p_monitor_performance) {
monitor_performance = p_monitor_performance; monitor_performance = p_monitor_performance;
if (!get_owner() && monitor_performance) { if (bt_instance.is_valid()) {
// Don't add custom monitor if not in scene. bt_instance->set_monitor_performance(monitor_performance);
return;
} }
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 #endif // ! DEBUG_ENABLED
@ -222,21 +175,17 @@ void BTPlayer::_notification(int p_notification) {
} break; } break;
case NOTIFICATION_ENTER_TREE: { case NOTIFICATION_ENTER_TREE: {
#ifdef DEBUG_ENABLED #ifdef DEBUG_ENABLED
if (tree_instance.is_valid() && IS_DEBUGGER_ACTIVE()) { if (bt_instance.is_valid()) {
LimboDebugger::get_singleton()->register_bt_instance(tree_instance, get_path()); bt_instance->set_monitor_performance(monitor_performance);
} bt_instance->register_with_debugger();
if (monitor_performance) {
_add_custom_monitor();
} }
#endif // DEBUG_ENABLED #endif // DEBUG_ENABLED
} break; } break;
case NOTIFICATION_EXIT_TREE: { case NOTIFICATION_EXIT_TREE: {
#ifdef DEBUG_ENABLED #ifdef DEBUG_ENABLED
if (tree_instance.is_valid() && IS_DEBUGGER_ACTIVE()) { if (bt_instance.is_valid()) {
LimboDebugger::get_singleton()->unregister_bt_instance(tree_instance, get_path()); bt_instance->set_monitor_performance(false);
} bt_instance->unregister_with_debugger();
if (monitor_performance) {
_remove_custom_monitor();
} }
#endif // DEBUG_ENABLED #endif // DEBUG_ENABLED
@ -266,9 +215,8 @@ void BTPlayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("update", "delta"), &BTPlayer::update); ClassDB::bind_method(D_METHOD("update", "delta"), &BTPlayer::update);
ClassDB::bind_method(D_METHOD("restart"), &BTPlayer::restart); 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::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"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "agent_node"), "set_agent_node", "get_agent_node");
@ -281,9 +229,12 @@ void BTPlayer::_bind_methods() {
BIND_ENUM_CONSTANT(PHYSICS); BIND_ENUM_CONSTANT(PHYSICS);
BIND_ENUM_CONSTANT(MANUAL); BIND_ENUM_CONSTANT(MANUAL);
ADD_SIGNAL(MethodInfo("behavior_tree_finished", PropertyInfo(Variant::INT, "status")));
ADD_SIGNAL(MethodInfo("updated", PropertyInfo(Variant::INT, "status"))); ADD_SIGNAL(MethodInfo("updated", PropertyInfo(Variant::INT, "status")));
#ifndef DISABLE_DEPRECATED
ADD_SIGNAL(MethodInfo("behavior_tree_finished", PropertyInfo(Variant::INT, "status")));
#endif
#ifdef DEBUG_ENABLED #ifdef DEBUG_ENABLED
ClassDB::bind_method(D_METHOD("_set_monitor_performance", "enable"), &BTPlayer::_set_monitor_performance); ClassDB::bind_method(D_METHOD("_set_monitor_performance", "enable"), &BTPlayer::_set_monitor_performance);
ClassDB::bind_method(D_METHOD("_get_monitor_performance"), &BTPlayer::_get_monitor_performance); ClassDB::bind_method(D_METHOD("_get_monitor_performance"), &BTPlayer::_get_monitor_performance);

View File

@ -15,6 +15,7 @@
#include "../blackboard/blackboard.h" #include "../blackboard/blackboard.h"
#include "../blackboard/blackboard_plan.h" #include "../blackboard/blackboard_plan.h"
#include "behavior_tree.h" #include "behavior_tree.h"
#include "bt_instance.h"
#include "tasks/bt_task.h" #include "tasks/bt_task.h"
#ifdef LIMBOAI_MODULE #ifdef LIMBOAI_MODULE
@ -42,9 +43,8 @@ private:
UpdateMode update_mode = UpdateMode::PHYSICS; UpdateMode update_mode = UpdateMode::PHYSICS;
bool active = true; bool active = true;
Ref<Blackboard> blackboard; Ref<Blackboard> blackboard;
int last_status = -1;
Ref<BTTask> tree_instance; Ref<BTInstance> bt_instance;
void _load_tree(); void _load_tree();
void _update_blackboard_plan(); void _update_blackboard_plan();
@ -75,9 +75,8 @@ public:
void update(double p_delta); void update(double p_delta);
void restart(); void restart();
int get_last_status() const { return last_status; }
Ref<BTTask> get_tree_instance() { return tree_instance; } Ref<BTInstance> get_bt_instance() { return bt_instance; }
BTPlayer(); BTPlayer();
~BTPlayer(); ~BTPlayer();
@ -86,19 +85,11 @@ public:
private: private:
bool monitor_performance = false; 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); void _set_monitor_performance(bool p_monitor_performance);
bool _get_monitor_performance() const { return monitor_performance; } bool _get_monitor_performance() const { return monitor_performance; }
void _add_custom_monitor(); #endif // * DEBUG_ENABLED
void _remove_custom_monitor();
double _get_mean_update_time_msec();
#endif // DEBUG_ENABLED
}; };
#endif // BT_PLAYER_H #endif // BT_PLAYER_H

View File

@ -11,7 +11,6 @@
#include "bt_state.h" #include "bt_state.h"
#include "../editor/debugger/limbo_debugger.h"
#include "../util/limbo_compat.h" #include "../util/limbo_compat.h"
#include "../util/limbo_string_names.h" #include "../util/limbo_string_names.h"
@ -54,18 +53,17 @@ void BTState::_setup() {
ERR_FAIL_COND_MSG(behavior_tree.is_null(), "BTState: BehaviorTree is not assigned."); ERR_FAIL_COND_MSG(behavior_tree.is_null(), "BTState: BehaviorTree is not assigned.");
Node *scene_root = get_owner(); Node *scene_root = get_owner();
ERR_FAIL_NULL_MSG(scene_root, "BTState: Initialization failed - can't get scene root (make sure the BTState's owner property is set)."); ERR_FAIL_NULL_MSG(scene_root, "BTState: Initialization failed - can't get scene root (make sure the BTState's owner property is set).");
tree_instance = behavior_tree->instantiate(get_agent(), get_blackboard(), scene_root); bt_instance = behavior_tree->instantiate(get_agent(), get_blackboard(), this);
ERR_FAIL_COND_MSG(bt_instance.is_null(), "BTState: Initialization failed - can't instantiate behavior tree.");
#ifdef DEBUG_ENABLED #ifdef DEBUG_ENABLED
if (tree_instance.is_valid() && IS_DEBUGGER_ACTIVE()) { bt_instance->register_with_debugger();
LimboDebugger::get_singleton()->register_bt_instance(tree_instance, get_path());
}
#endif #endif
} }
void BTState::_exit() { void BTState::_exit() {
if (tree_instance.is_valid()) { if (bt_instance.is_valid()) {
tree_instance->abort(); bt_instance->get_root_task()->abort();
} else { } else {
ERR_PRINT_ONCE("BTState: BehaviorTree is not assigned."); ERR_PRINT_ONCE("BTState: BehaviorTree is not assigned.");
} }
@ -78,8 +76,8 @@ void BTState::_update(double p_delta) {
// Bail out if a transition happened in the meantime. // Bail out if a transition happened in the meantime.
return; return;
} }
ERR_FAIL_NULL(tree_instance); ERR_FAIL_NULL(bt_instance);
int status = tree_instance->execute(p_delta); BT::Status status = bt_instance->update(p_delta);
if (status == BTTask::SUCCESS) { if (status == BTTask::SUCCESS) {
get_root()->dispatch(success_event, Variant()); get_root()->dispatch(success_event, Variant());
} else if (status == BTTask::FAILURE) { } else if (status == BTTask::FAILURE) {
@ -92,15 +90,15 @@ void BTState::_notification(int p_notification) {
switch (p_notification) { switch (p_notification) {
#ifdef DEBUG_ENABLED #ifdef DEBUG_ENABLED
case NOTIFICATION_ENTER_TREE: { case NOTIFICATION_ENTER_TREE: {
if (tree_instance.is_valid() && IS_DEBUGGER_ACTIVE()) { if (bt_instance.is_valid()) {
LimboDebugger::get_singleton()->register_bt_instance(tree_instance, get_path()); bt_instance->register_with_debugger();
} }
} break; } break;
#endif // DEBUG_ENABLED #endif // DEBUG_ENABLED
case NOTIFICATION_EXIT_TREE: { case NOTIFICATION_EXIT_TREE: {
#ifdef DEBUG_ENABLED #ifdef DEBUG_ENABLED
if (tree_instance.is_valid() && IS_DEBUGGER_ACTIVE()) { if (bt_instance.is_valid()) {
LimboDebugger::get_singleton()->unregister_bt_instance(tree_instance, get_path()); bt_instance->unregister_with_debugger();
} }
#endif // DEBUG_ENABLED #endif // DEBUG_ENABLED
@ -117,7 +115,7 @@ void BTState::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_behavior_tree", "behavior_tree"), &BTState::set_behavior_tree); ClassDB::bind_method(D_METHOD("set_behavior_tree", "behavior_tree"), &BTState::set_behavior_tree);
ClassDB::bind_method(D_METHOD("get_behavior_tree"), &BTState::get_behavior_tree); ClassDB::bind_method(D_METHOD("get_behavior_tree"), &BTState::get_behavior_tree);
ClassDB::bind_method(D_METHOD("get_tree_instance"), &BTState::get_tree_instance); ClassDB::bind_method(D_METHOD("get_bt_instance"), &BTState::get_bt_instance);
ClassDB::bind_method(D_METHOD("set_success_event", "event"), &BTState::set_success_event); ClassDB::bind_method(D_METHOD("set_success_event", "event"), &BTState::set_success_event);
ClassDB::bind_method(D_METHOD("get_success_event"), &BTState::get_success_event); ClassDB::bind_method(D_METHOD("get_success_event"), &BTState::get_success_event);

View File

@ -22,7 +22,7 @@ class BTState : public LimboState {
private: private:
Ref<BehaviorTree> behavior_tree; Ref<BehaviorTree> behavior_tree;
Ref<BTTask> tree_instance; Ref<BTInstance> bt_instance;
StringName success_event; StringName success_event;
StringName failure_event; StringName failure_event;
@ -42,7 +42,7 @@ public:
void set_behavior_tree(const Ref<BehaviorTree> &p_value); void set_behavior_tree(const Ref<BehaviorTree> &p_value);
Ref<BehaviorTree> get_behavior_tree() const { return behavior_tree; } Ref<BehaviorTree> get_behavior_tree() const { return behavior_tree; }
Ref<BTTask> get_tree_instance() const { return tree_instance; } Ref<BTInstance> get_bt_instance() const { return bt_instance; }
void set_success_event(const StringName &p_success_event) { success_event = p_success_event; } void set_success_event(const StringName &p_success_event) { success_event = p_success_event; }
StringName get_success_event() const { return success_event; } StringName get_success_event() const { return success_event; }

View File

@ -83,6 +83,7 @@ def get_doc_classes():
"BTDynamicSequence", "BTDynamicSequence",
"BTFail", "BTFail",
"BTForEach", "BTForEach",
"BTInstance",
"BTInvert", "BTInvert",
"BTNewScope", "BTNewScope",
"BTParallel", "BTParallel",

View File

@ -42,8 +42,8 @@ func _ready() -> void:
func _physics_process(_delta: float) -> void: func _physics_process(_delta: float) -> void:
var inst: BTTask = bt_player.get_tree_instance() var inst: BTInstance = bt_player.get_bt_instance()
var bt_data: BehaviorTreeData = BehaviorTreeData.create_from_tree_instance(inst) var bt_data: BehaviorTreeData = BehaviorTreeData.create_from_bt_instance(inst)
behavior_tree_view.update_tree(bt_data) behavior_tree_view.update_tree(bt_data)

View File

@ -257,8 +257,8 @@ anchor_right = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0
offset_left = 4.0 offset_left = 4.0
offset_top = 4.0 offset_top = 4.0
offset_right = -4.0 offset_right = 1020.0
offset_bottom = -4.0 offset_bottom = 704.0
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
gutters_draw_line_numbers = true gutters_draw_line_numbers = true

View File

@ -55,17 +55,17 @@ Methods
.. table:: .. table::
:widths: auto :widths: auto
+-----------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +-----------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| :ref:`BehaviorTree<class_BehaviorTree>` | :ref:`clone<class_BehaviorTree_method_clone>`\ (\ ) |const| | | :ref:`BehaviorTree<class_BehaviorTree>` | :ref:`clone<class_BehaviorTree_method_clone>`\ (\ ) |const| |
+-----------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +-----------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |void| | :ref:`copy_other<class_BehaviorTree_method_copy_other>`\ (\ other\: :ref:`BehaviorTree<class_BehaviorTree>`\ ) | | |void| | :ref:`copy_other<class_BehaviorTree_method_copy_other>`\ (\ other\: :ref:`BehaviorTree<class_BehaviorTree>`\ ) |
+-----------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +-----------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| :ref:`BTTask<class_BTTask>` | :ref:`get_root_task<class_BehaviorTree_method_get_root_task>`\ (\ ) |const| | | :ref:`BTTask<class_BTTask>` | :ref:`get_root_task<class_BehaviorTree_method_get_root_task>`\ (\ ) |const| |
+-----------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +-----------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| :ref:`BTTask<class_BTTask>` | :ref:`instantiate<class_BehaviorTree_method_instantiate>`\ (\ agent\: ``Node``, blackboard\: :ref:`Blackboard<class_Blackboard>`, scene_root\: ``Node``\ ) |const| | | :ref:`BTInstance<class_BTInstance>` | :ref:`instantiate<class_BehaviorTree_method_instantiate>`\ (\ agent\: ``Node``, blackboard\: :ref:`Blackboard<class_Blackboard>`, instance_owner\: ``Node``\ ) |const| |
+-----------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +-----------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |void| | :ref:`set_root_task<class_BehaviorTree_method_set_root_task>`\ (\ task\: :ref:`BTTask<class_BTTask>`\ ) | | |void| | :ref:`set_root_task<class_BehaviorTree_method_set_root_task>`\ (\ task\: :ref:`BTTask<class_BTTask>`\ ) |
+-----------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +-----------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
.. rst-class:: classref-section-separator .. rst-class:: classref-section-separator
@ -121,7 +121,7 @@ Stores and manages variables that will be used in constructing new :ref:`Blackbo
- |void| **set_description**\ (\ value\: ``String``\ ) - |void| **set_description**\ (\ value\: ``String``\ )
- ``String`` **get_description**\ (\ ) - ``String`` **get_description**\ (\ )
User-provided description of the BehaviorTree. User-provided description of the **BehaviorTree**.
.. rst-class:: classref-section-separator .. rst-class:: classref-section-separator
@ -172,9 +172,9 @@ Returns the root task of the BehaviorTree resource.
.. rst-class:: classref-method .. rst-class:: classref-method
:ref:`BTTask<class_BTTask>` **instantiate**\ (\ agent\: ``Node``, blackboard\: :ref:`Blackboard<class_Blackboard>`, scene_root\: ``Node``\ ) |const| :ref:`🔗<class_BehaviorTree_method_instantiate>` :ref:`BTInstance<class_BTInstance>` **instantiate**\ (\ agent\: ``Node``, blackboard\: :ref:`Blackboard<class_Blackboard>`, instance_owner\: ``Node``\ ) |const| :ref:`🔗<class_BehaviorTree_method_instantiate>`
Instantiates the behavior tree and returns the root :ref:`BTTask<class_BTTask>`. ``scene_root`` should be the root node of the scene that the Behavior Tree will be used in (e.g., the owner of the node that contains the behavior tree). Instantiates the behavior tree and returns :ref:`BTInstance<class_BTInstance>`. ``instance_owner`` should be the scene node that will own the behavior tree instance. This is typically a :ref:`BTPlayer<class_BTPlayer>`, :ref:`BTState<class_BTState>`, or a custom player node that controls the behavior tree execution.
.. rst-class:: classref-item-separator .. rst-class:: classref-item-separator

View File

@ -31,9 +31,9 @@ Methods
.. table:: .. table::
:widths: auto :widths: auto
+-------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+ +-------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+
| :ref:`BehaviorTreeData<class_BehaviorTreeData>` | :ref:`create_from_tree_instance<class_BehaviorTreeData_method_create_from_tree_instance>`\ (\ tree_instance\: :ref:`BTTask<class_BTTask>`\ ) |static| | | :ref:`BehaviorTreeData<class_BehaviorTreeData>` | :ref:`create_from_bt_instance<class_BehaviorTreeData_method_create_from_bt_instance>`\ (\ bt_instance\: :ref:`BTInstance<class_BTInstance>`\ ) |static| |
+-------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+ +-------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+
.. rst-class:: classref-section-separator .. rst-class:: classref-section-separator
@ -44,15 +44,15 @@ Methods
Method Descriptions Method Descriptions
------------------- -------------------
.. _class_BehaviorTreeData_method_create_from_tree_instance: .. _class_BehaviorTreeData_method_create_from_bt_instance:
.. rst-class:: classref-method .. rst-class:: classref-method
:ref:`BehaviorTreeData<class_BehaviorTreeData>` **create_from_tree_instance**\ (\ tree_instance\: :ref:`BTTask<class_BTTask>`\ ) |static| :ref:`🔗<class_BehaviorTreeData_method_create_from_tree_instance>` :ref:`BehaviorTreeData<class_BehaviorTreeData>` **create_from_bt_instance**\ (\ bt_instance\: :ref:`BTInstance<class_BTInstance>`\ ) |static| :ref:`🔗<class_BehaviorTreeData_method_create_from_bt_instance>`
Returns current state of the ``tree_instance`` encoded as a **BehaviorTreeData**, suitable for use with :ref:`BehaviorTreeView<class_BehaviorTreeView>`. Returns current state of the ``bt_instance`` encoded as a **BehaviorTreeData**, suitable for use with :ref:`BehaviorTreeView<class_BehaviorTreeView>`.
Behavior tree instance can be acquired with :ref:`BTPlayer.get_tree_instance<class_BTPlayer_method_get_tree_instance>`. Behavior tree instance can be acquired with :ref:`BTPlayer.get_bt_instance<class_BTPlayer_method_get_bt_instance>`.
.. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)` .. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)`
.. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)` .. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)`

View File

@ -0,0 +1,207 @@
:github_url: hide
.. DO NOT EDIT THIS FILE!!!
.. Generated automatically from Godot engine sources.
.. Generator: https://github.com/godotengine/godot/tree/master/doc/tools/make_rst.py.
.. XML source: https://github.com/godotengine/godot/tree/master/modules/limboai/doc_classes/BTInstance.xml.
.. _class_BTInstance:
BTInstance
==========
**Inherits:**
Represents a runtime instance of a :ref:`BehaviorTree<class_BehaviorTree>` resource.
.. rst-class:: classref-introduction-group
Description
-----------
Can be created using the :ref:`BehaviorTree.instantiate<class_BehaviorTree_method_instantiate>` method.
.. rst-class:: classref-reftable-group
Properties
----------
.. table::
:widths: auto
+----------+---------------------------------------------------------------------------+-----------+
| ``bool`` | :ref:`monitor_performance<class_BTInstance_property_monitor_performance>` | ``false`` |
+----------+---------------------------------------------------------------------------+-----------+
.. rst-class:: classref-reftable-group
Methods
-------
.. table::
:widths: auto
+-------------------------------+-----------------------------------------------------------------------------------------+
| :ref:`Status<enum_BT_Status>` | :ref:`get_last_status<class_BTInstance_method_get_last_status>`\ (\ ) |const| |
+-------------------------------+-----------------------------------------------------------------------------------------+
| ``Node`` | :ref:`get_owner_node<class_BTInstance_method_get_owner_node>`\ (\ ) |const| |
+-------------------------------+-----------------------------------------------------------------------------------------+
| :ref:`BTTask<class_BTTask>` | :ref:`get_root_task<class_BTInstance_method_get_root_task>`\ (\ ) |const| |
+-------------------------------+-----------------------------------------------------------------------------------------+
| ``String`` | :ref:`get_source_bt_path<class_BTInstance_method_get_source_bt_path>`\ (\ ) |const| |
+-------------------------------+-----------------------------------------------------------------------------------------+
| |void| | :ref:`register_with_debugger<class_BTInstance_method_register_with_debugger>`\ (\ ) |
+-------------------------------+-----------------------------------------------------------------------------------------+
| |void| | :ref:`unregister_with_debugger<class_BTInstance_method_unregister_with_debugger>`\ (\ ) |
+-------------------------------+-----------------------------------------------------------------------------------------+
| :ref:`Status<enum_BT_Status>` | :ref:`update<class_BTInstance_method_update>`\ (\ delta\: ``float``\ ) |
+-------------------------------+-----------------------------------------------------------------------------------------+
.. rst-class:: classref-section-separator
----
.. rst-class:: classref-descriptions-group
Signals
-------
.. _class_BTInstance_signal_freed:
.. rst-class:: classref-signal
**freed**\ (\ ) :ref:`🔗<class_BTInstance_signal_freed>`
Emitted when the behavior tree instance is freed. Used by debugger to unregister.
.. rst-class:: classref-item-separator
----
.. _class_BTInstance_signal_updated:
.. rst-class:: classref-signal
**updated**\ (\ status\: ``int``\ ) :ref:`🔗<class_BTInstance_signal_updated>`
Emitted when the behavior tree instance has finished updating.
.. rst-class:: classref-section-separator
----
.. rst-class:: classref-descriptions-group
Property Descriptions
---------------------
.. _class_BTInstance_property_monitor_performance:
.. rst-class:: classref-property
``bool`` **monitor_performance** = ``false`` :ref:`🔗<class_BTInstance_property_monitor_performance>`
.. rst-class:: classref-property-setget
- |void| **set_monitor_performance**\ (\ value\: ``bool``\ )
- ``bool`` **get_monitor_performance**\ (\ )
If ``true``, adds a performance monitor for this instance to "Debugger->Monitors" in the editor.
.. rst-class:: classref-section-separator
----
.. rst-class:: classref-descriptions-group
Method Descriptions
-------------------
.. _class_BTInstance_method_get_last_status:
.. rst-class:: classref-method
:ref:`Status<enum_BT_Status>` **get_last_status**\ (\ ) |const| :ref:`🔗<class_BTInstance_method_get_last_status>`
Returns the execution status of the last update.
.. rst-class:: classref-item-separator
----
.. _class_BTInstance_method_get_owner_node:
.. rst-class:: classref-method
``Node`` **get_owner_node**\ (\ ) |const| :ref:`🔗<class_BTInstance_method_get_owner_node>`
Returns the scene ``Node`` that owns this behavior tree instance.
.. rst-class:: classref-item-separator
----
.. _class_BTInstance_method_get_root_task:
.. rst-class:: classref-method
:ref:`BTTask<class_BTTask>` **get_root_task**\ (\ ) |const| :ref:`🔗<class_BTInstance_method_get_root_task>`
Returns the root task of the behavior tree instance.
.. rst-class:: classref-item-separator
----
.. _class_BTInstance_method_get_source_bt_path:
.. rst-class:: classref-method
``String`` **get_source_bt_path**\ (\ ) |const| :ref:`🔗<class_BTInstance_method_get_source_bt_path>`
Returns the file path to the behavior tree resource that was used to create this instance.
.. rst-class:: classref-item-separator
----
.. _class_BTInstance_method_register_with_debugger:
.. rst-class:: classref-method
|void| **register_with_debugger**\ (\ ) :ref:`🔗<class_BTInstance_method_register_with_debugger>`
Registers the behavior tree instance with the debugger.
.. rst-class:: classref-item-separator
----
.. _class_BTInstance_method_unregister_with_debugger:
.. rst-class:: classref-method
|void| **unregister_with_debugger**\ (\ ) :ref:`🔗<class_BTInstance_method_unregister_with_debugger>`
Unregisters the behavior tree instance from the debugger. This is typically not necessary, as the debugger will automatically unregister the instance when it is freed.
.. rst-class:: classref-item-separator
----
.. _class_BTInstance_method_update:
.. rst-class:: classref-method
:ref:`Status<enum_BT_Status>` **update**\ (\ delta\: ``float``\ ) :ref:`🔗<class_BTInstance_method_update>`
Ticks the behavior tree instance and returns its status.
.. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)`
.. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)`
.. |vararg| replace:: :abbr:`vararg (This method accepts any number of arguments after the ones described here.)`
.. |constructor| replace:: :abbr:`constructor (This method is used to construct a type.)`
.. |static| replace:: :abbr:`static (This method doesn't need an instance to be called, so it can be called directly using the class name.)`
.. |operator| replace:: :abbr:`operator (This method describes a valid operator to use with this type as left-hand operand.)`
.. |bitfield| replace:: :abbr:`BitField (This value is an integer composed as a bitmask of the following flags.)`
.. |void| replace:: :abbr:`void (No return value.)`

View File

@ -55,15 +55,13 @@ Methods
.. table:: .. table::
:widths: auto :widths: auto
+-----------------------------+-----------------------------------------------------------------------------+ +-------------------------------------+----------------------------------------------------------------------+
| ``int`` | :ref:`get_last_status<class_BTPlayer_method_get_last_status>`\ (\ ) |const| | | :ref:`BTInstance<class_BTInstance>` | :ref:`get_bt_instance<class_BTPlayer_method_get_bt_instance>`\ (\ ) |
+-----------------------------+-----------------------------------------------------------------------------+ +-------------------------------------+----------------------------------------------------------------------+
| :ref:`BTTask<class_BTTask>` | :ref:`get_tree_instance<class_BTPlayer_method_get_tree_instance>`\ (\ ) |
+-----------------------------+-----------------------------------------------------------------------------+
| |void| | :ref:`restart<class_BTPlayer_method_restart>`\ (\ ) | | |void| | :ref:`restart<class_BTPlayer_method_restart>`\ (\ ) |
+-----------------------------+-----------------------------------------------------------------------------+ +-------------------------------------+----------------------------------------------------------------------+
| |void| | :ref:`update<class_BTPlayer_method_update>`\ (\ delta\: ``float``\ ) | | |void| | :ref:`update<class_BTPlayer_method_update>`\ (\ delta\: ``float``\ ) |
+-----------------------------+-----------------------------------------------------------------------------+ +-------------------------------------+----------------------------------------------------------------------+
.. rst-class:: classref-section-separator .. rst-class:: classref-section-separator
@ -80,6 +78,8 @@ Signals
**behavior_tree_finished**\ (\ status\: ``int``\ ) :ref:`🔗<class_BTPlayer_signal_behavior_tree_finished>` **behavior_tree_finished**\ (\ status\: ``int``\ ) :ref:`🔗<class_BTPlayer_signal_behavior_tree_finished>`
**Deprecated:** Use :ref:`updated<class_BTPlayer_signal_updated>` signal instead.
Emitted when the behavior tree has finished executing and returned ``SUCCESS`` or ``FAILURE``. Emitted when the behavior tree has finished executing and returned ``SUCCESS`` or ``FAILURE``.
Argument ``status`` holds the status returned by the behavior tree. See :ref:`Status<enum_BT_Status>`. Argument ``status`` holds the status returned by the behavior tree. See :ref:`Status<enum_BT_Status>`.
@ -265,25 +265,13 @@ Determines when the behavior tree is executed. See :ref:`UpdateMode<enum_BTPlaye
Method Descriptions Method Descriptions
------------------- -------------------
.. _class_BTPlayer_method_get_last_status: .. _class_BTPlayer_method_get_bt_instance:
.. rst-class:: classref-method .. rst-class:: classref-method
``int`` **get_last_status**\ (\ ) |const| :ref:`🔗<class_BTPlayer_method_get_last_status>` :ref:`BTInstance<class_BTInstance>` **get_bt_instance**\ (\ ) :ref:`🔗<class_BTPlayer_method_get_bt_instance>`
Returns the behavior tree's last execution status. See :ref:`Status<enum_BT_Status>`. Returns the behavior tree instance.
.. rst-class:: classref-item-separator
----
.. _class_BTPlayer_method_get_tree_instance:
.. rst-class:: classref-method
:ref:`BTTask<class_BTTask>` **get_tree_instance**\ (\ ) :ref:`🔗<class_BTPlayer_method_get_tree_instance>`
Returns the root task of the instantiated behavior tree.
.. rst-class:: classref-item-separator .. rst-class:: classref-item-separator

View File

@ -45,9 +45,9 @@ Methods
.. table:: .. table::
:widths: auto :widths: auto
+-----------------------------+--------------------------------------------------------------------------------+ +-------------------------------------+----------------------------------------------------------------------------+
| :ref:`BTTask<class_BTTask>` | :ref:`get_tree_instance<class_BTState_method_get_tree_instance>`\ (\ ) |const| | | :ref:`BTInstance<class_BTInstance>` | :ref:`get_bt_instance<class_BTState_method_get_bt_instance>`\ (\ ) |const| |
+-----------------------------+--------------------------------------------------------------------------------+ +-------------------------------------+----------------------------------------------------------------------------+
.. rst-class:: classref-section-separator .. rst-class:: classref-section-separator
@ -114,13 +114,13 @@ HSM event that will be dispatched when the behavior tree results in ``SUCCESS``.
Method Descriptions Method Descriptions
------------------- -------------------
.. _class_BTState_method_get_tree_instance: .. _class_BTState_method_get_bt_instance:
.. rst-class:: classref-method .. rst-class:: classref-method
:ref:`BTTask<class_BTTask>` **get_tree_instance**\ (\ ) |const| :ref:`🔗<class_BTState_method_get_tree_instance>` :ref:`BTInstance<class_BTInstance>` **get_bt_instance**\ (\ ) |const| :ref:`🔗<class_BTState_method_get_bt_instance>`
Returns the root task of the instantiated behavior tree. Returns the behavior tree instance.
.. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)` .. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)`
.. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)` .. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)`

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="BTInstance" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
Represents a runtime instance of a [BehaviorTree] resource.
</brief_description>
<description>
Can be created using the [method BehaviorTree.instantiate] method.
</description>
<tutorials>
</tutorials>
<methods>
<method name="get_last_status" qualifiers="const">
<return type="int" enum="BT.Status" />
<description>
Returns the execution status of the last update.
</description>
</method>
<method name="get_owner_node" qualifiers="const">
<return type="Node" />
<description>
Returns the scene [Node] that owns this behavior tree instance.
</description>
</method>
<method name="get_root_task" qualifiers="const">
<return type="BTTask" />
<description>
Returns the root task of the behavior tree instance.
</description>
</method>
<method name="get_source_bt_path" qualifiers="const">
<return type="String" />
<description>
Returns the file path to the behavior tree resource that was used to create this instance.
</description>
</method>
<method name="register_with_debugger">
<return type="void" />
<description>
Registers the behavior tree instance with the debugger.
</description>
</method>
<method name="unregister_with_debugger">
<return type="void" />
<description>
Unregisters the behavior tree instance from the debugger. This is typically not necessary, as the debugger will automatically unregister the instance when it is freed.
</description>
</method>
<method name="update">
<return type="int" enum="BT.Status" />
<param index="0" name="delta" type="float" />
<description>
Ticks the behavior tree instance and returns its status.
</description>
</method>
</methods>
<members>
<member name="monitor_performance" type="bool" setter="set_monitor_performance" getter="get_monitor_performance" default="false">
If [code]true[/code], adds a performance monitor for this instance to "Debugger-&gt;Monitors" in the editor.
</member>
</members>
<signals>
<signal name="freed">
<description>
Emitted when the behavior tree instance is freed. Used by debugger to unregister.
</description>
</signal>
<signal name="updated">
<param index="0" name="status" type="int" />
<description>
Emitted when the behavior tree instance has finished updating.
</description>
</signal>
</signals>
</class>

View File

@ -10,16 +10,10 @@
<tutorials> <tutorials>
</tutorials> </tutorials>
<methods> <methods>
<method name="get_last_status" qualifiers="const"> <method name="get_bt_instance">
<return type="int" /> <return type="BTInstance" />
<description> <description>
Returns the behavior tree's last execution status. See [enum BT.Status]. Returns the behavior tree instance.
</description>
</method>
<method name="get_tree_instance">
<return type="BTTask" />
<description>
Returns the root task of the instantiated behavior tree.
</description> </description>
</method> </method>
<method name="restart"> <method name="restart">
@ -60,7 +54,7 @@
</member> </member>
</members> </members>
<signals> <signals>
<signal name="behavior_tree_finished"> <signal name="behavior_tree_finished" deprecated="Use [signal updated] signal instead.">
<param index="0" name="status" type="int" /> <param index="0" name="status" type="int" />
<description> <description>
Emitted when the behavior tree has finished executing and returned [code]SUCCESS[/code] or [code]FAILURE[/code]. Emitted when the behavior tree has finished executing and returned [code]SUCCESS[/code] or [code]FAILURE[/code].

View File

@ -9,10 +9,10 @@
<tutorials> <tutorials>
</tutorials> </tutorials>
<methods> <methods>
<method name="get_tree_instance" qualifiers="const"> <method name="get_bt_instance" qualifiers="const">
<return type="BTTask" /> <return type="BTInstance" />
<description> <description>
Returns the root task of the instantiated behavior tree. Returns the behavior tree instance.
</description> </description>
</method> </method>
</methods> </methods>

View File

@ -35,12 +35,12 @@
</description> </description>
</method> </method>
<method name="instantiate" qualifiers="const"> <method name="instantiate" qualifiers="const">
<return type="BTTask" /> <return type="BTInstance" />
<param index="0" name="agent" type="Node" /> <param index="0" name="agent" type="Node" />
<param index="1" name="blackboard" type="Blackboard" /> <param index="1" name="blackboard" type="Blackboard" />
<param index="2" name="scene_root" type="Node" /> <param index="2" name="instance_owner" type="Node" />
<description> <description>
Instantiates the behavior tree and returns the root [BTTask]. [param scene_root] should be the root node of the scene that the Behavior Tree will be used in (e.g., the owner of the node that contains the behavior tree). Instantiates the behavior tree and returns [BTInstance]. [param instance_owner] should be the scene node that will own the behavior tree instance. This is typically a [BTPlayer], [BTState], or a custom player node that controls the behavior tree execution.
</description> </description>
</method> </method>
<method name="set_root_task"> <method name="set_root_task">
@ -56,7 +56,7 @@
Stores and manages variables that will be used in constructing new [Blackboard] instances. Stores and manages variables that will be used in constructing new [Blackboard] instances.
</member> </member>
<member name="description" type="String" setter="set_description" getter="get_description" default="&quot;&quot;"> <member name="description" type="String" setter="set_description" getter="get_description" default="&quot;&quot;">
User-provided description of the BehaviorTree. User-provided description of the [BehaviorTree].
</member> </member>
</members> </members>
<signals> <signals>

View File

@ -10,12 +10,12 @@
<tutorials> <tutorials>
</tutorials> </tutorials>
<methods> <methods>
<method name="create_from_tree_instance" qualifiers="static"> <method name="create_from_bt_instance" qualifiers="static">
<return type="BehaviorTreeData" /> <return type="BehaviorTreeData" />
<param index="0" name="tree_instance" type="BTTask" /> <param index="0" name="bt_instance" type="BTInstance" />
<description> <description>
Returns current state of the [param tree_instance] encoded as a [BehaviorTreeData], suitable for use with [BehaviorTreeView]. Returns current state of the [param bt_instance] encoded as a [BehaviorTreeData], suitable for use with [BehaviorTreeView].
Behavior tree instance can be acquired with [method BTPlayer.get_tree_instance]. Behavior tree instance can be acquired with [method BTPlayer.get_bt_instance].
</description> </description>
</method> </method>
</methods> </methods>

View File

@ -17,14 +17,15 @@
//**** BehaviorTreeData //**** BehaviorTreeData
Array BehaviorTreeData::serialize(const Ref<BTTask> &p_tree_instance, const NodePath &p_player_path, const String &p_bt_resource_path) { Array BehaviorTreeData::serialize(const Ref<BTInstance> &p_instance) {
Array arr; Array arr;
arr.push_back(p_player_path); arr.push_back(uint64_t(p_instance->get_instance_id()));
arr.push_back(p_bt_resource_path); 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 // Flatten tree into list depth first
List<Ref<BTTask>> stack; List<Ref<BTTask>> stack;
stack.push_back(p_tree_instance); stack.push_back(p_instance->get_root_task());
while (stack.size()) { while (stack.size()) {
Ref<BTTask> task = stack.front()->get(); Ref<BTTask> task = stack.front()->get();
stack.pop_front(); stack.pop_front();
@ -54,15 +55,17 @@ Array BehaviorTreeData::serialize(const Ref<BTTask> &p_tree_instance, const Node
} }
Ref<BehaviorTreeData> BehaviorTreeData::deserialize(const Array &p_array) { Ref<BehaviorTreeData> BehaviorTreeData::deserialize(const Array &p_array) {
ERR_FAIL_COND_V(p_array.size() < 2, nullptr); ERR_FAIL_COND_V(p_array.size() < 3, nullptr);
ERR_FAIL_COND_V(p_array[0].get_type() != Variant::NODE_PATH, nullptr); ERR_FAIL_COND_V(p_array[0].get_type() != Variant::INT, nullptr);
ERR_FAIL_COND_V(p_array[1].get_type() != Variant::STRING, 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<BehaviorTreeData> data = memnew(BehaviorTreeData); Ref<BehaviorTreeData> data = memnew(BehaviorTreeData);
data->bt_player_path = p_array[0]; data->bt_instance_id = uint64_t(p_array[0]);
data->bt_resource_path = p_array[1]; 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) { while (p_array.size() > idx + 1) {
ERR_FAIL_COND_V(p_array.size() < idx + 7, nullptr); ERR_FAIL_COND_V(p_array.size() < idx + 7, nullptr);
ERR_FAIL_COND_V(p_array[idx].get_type() != Variant::INT, nullptr); ERR_FAIL_COND_V(p_array[idx].get_type() != Variant::INT, nullptr);
@ -80,12 +83,16 @@ Ref<BehaviorTreeData> BehaviorTreeData::deserialize(const Array &p_array) {
return data; return data;
} }
Ref<BehaviorTreeData> BehaviorTreeData::create_from_tree_instance(const Ref<BTTask> &p_tree_instance) { Ref<BehaviorTreeData> BehaviorTreeData::create_from_bt_instance(const Ref<BTInstance> &p_bt_instance) {
Ref<BehaviorTreeData> data = memnew(BehaviorTreeData); Ref<BehaviorTreeData> 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 // Flatten tree into list depth first
List<Ref<BTTask>> stack; List<Ref<BTTask>> stack;
stack.push_back(p_tree_instance); stack.push_back(p_bt_instance->get_root_task());
while (stack.size()) { while (stack.size()) {
Ref<BTTask> task = stack.front()->get(); Ref<BTTask> task = stack.front()->get();
stack.pop_front(); stack.pop_front();
@ -115,9 +122,7 @@ Ref<BehaviorTreeData> BehaviorTreeData::create_from_tree_instance(const Ref<BTTa
} }
void BehaviorTreeData::_bind_methods() { void BehaviorTreeData::_bind_methods() {
// ClassDB::bind_static_method("BehaviorTreeData", D_METHOD("serialize", "p_tree_instance", "p_player_path", "p_bt_resource_path"), &BehaviorTreeData::serialize); ClassDB::bind_static_method("BehaviorTreeData", D_METHOD("create_from_bt_instance", "bt_instance"), &BehaviorTreeData::create_from_bt_instance);
// ClassDB::bind_static_method("BehaviorTreeData", D_METHOD("deserialize", "p_array"), &BehaviorTreeData::deserialize);
ClassDB::bind_static_method("BehaviorTreeData", D_METHOD("create_from_tree_instance", "tree_instance"), &BehaviorTreeData::create_from_tree_instance);
} }
BehaviorTreeData::BehaviorTreeData() { BehaviorTreeData::BehaviorTreeData() {

View File

@ -12,6 +12,7 @@
#ifndef BEHAVIOR_TREE_DATA_H #ifndef BEHAVIOR_TREE_DATA_H
#define BEHAVIOR_TREE_DATA_H #define BEHAVIOR_TREE_DATA_H
#include "../../bt/bt_instance.h"
#include "../../bt/tasks/bt_task.h" #include "../../bt/tasks/bt_task.h"
class BehaviorTreeData : public RefCounted { class BehaviorTreeData : public RefCounted {
@ -46,13 +47,14 @@ public:
}; };
List<TaskData> tasks; List<TaskData> tasks;
NodePath bt_player_path; uint64_t bt_instance_id = 0;
String bt_resource_path; NodePath node_owner_path;
String source_bt_path;
public: public:
static Array serialize(const Ref<BTTask> &p_tree_instance, const NodePath &p_player_path, const String &p_bt_resource_path); static Array serialize(const Ref<BTInstance> &p_instance);
static Ref<BehaviorTreeData> deserialize(const Array &p_array); static Ref<BehaviorTreeData> deserialize(const Array &p_array);
static Ref<BehaviorTreeData> create_from_tree_instance(const Ref<BTTask> &p_tree_instance); static Ref<BehaviorTreeData> create_from_bt_instance(const Ref<BTInstance> &p_bt_instance);
BehaviorTreeData(); BehaviorTreeData();
}; };

View File

@ -11,13 +11,13 @@
#include "limbo_debugger.h" #include "limbo_debugger.h"
#include "../../bt/bt_instance.h"
#include "../../bt/tasks/bt_task.h" #include "../../bt/tasks/bt_task.h"
#include "../../util/limbo_compat.h" #include "../../util/limbo_compat.h"
#include "behavior_tree_data.h" #include "behavior_tree_data.h"
#ifdef LIMBOAI_MODULE #ifdef LIMBOAI_MODULE
#include "core/debugger/engine_debugger.h" #include "core/debugger/engine_debugger.h"
#include "core/error/error_macros.h"
#include "core/io/resource.h" #include "core/io/resource.h"
#include "core/string/node_path.h" #include "core/string/node_path.h"
#include "scene/main/scene_tree.h" #include "scene/main/scene_tree.h"
@ -33,9 +33,6 @@
//**** LimboDebugger //**** LimboDebugger
LimboDebugger *LimboDebugger::singleton = nullptr; LimboDebugger *LimboDebugger::singleton = nullptr;
LimboDebugger *LimboDebugger::get_singleton() {
return singleton;
}
LimboDebugger::LimboDebugger() { LimboDebugger::LimboDebugger() {
singleton = this; singleton = this;
@ -63,14 +60,6 @@ void LimboDebugger::deinitialize() {
} }
void LimboDebugger::_bind_methods() { 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 #ifdef DEBUG_ENABLED
@ -99,100 +88,96 @@ bool LimboDebugger::parse_message_gdext(const String &p_msg, const Array &p_args
} }
#endif // LIMBOAI_GDEXTENSION #endif // LIMBOAI_GDEXTENSION
void LimboDebugger::register_bt_instance(Ref<BTTask> p_instance, NodePath p_player_path) { void LimboDebugger::register_bt_instance(uint64_t p_instance_id) {
ERR_FAIL_COND(p_instance.is_null()); ERR_FAIL_COND(p_instance_id == 0);
ERR_FAIL_COND(p_player_path.is_empty()); if (!IS_DEBUGGER_ACTIVE()) {
if (active_trees.has(p_player_path)) {
return; return;
} }
active_trees.insert(p_player_path, p_instance); BTInstance *inst = Object::cast_to<BTInstance>(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;
}
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) { if (session_active) {
_send_active_bt_players(); _send_active_bt_players();
} }
} }
void LimboDebugger::unregister_bt_instance(Ref<BTTask> p_instance, NodePath p_player_path) { void LimboDebugger::unregister_bt_instance(uint64_t p_instance_id) {
ERR_FAIL_COND(p_instance.is_null()); if (!active_bt_instances.has(p_instance_id)) {
ERR_FAIL_COND(p_player_path.is_empty()); return;
ERR_FAIL_COND(!active_trees.has(p_player_path)); }
if (tracked_player == p_player_path) { if (tracked_instance_id == p_instance_id) {
_untrack_tree(); _untrack_tree();
} }
active_trees.erase(p_player_path); active_bt_instances.erase(p_instance_id);
if (session_active) { if (session_active) {
_send_active_bt_players(); _send_active_bt_players();
} }
} }
void LimboDebugger::_track_tree(NodePath p_path) { bool LimboDebugger::is_active() const {
ERR_FAIL_COND(!active_trees.has(p_path)); return IS_DEBUGGER_ACTIVE();
}
void LimboDebugger::_track_tree(uint64_t p_instance_id) {
ERR_FAIL_COND(p_instance_id == 0);
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); tracked_instance_id = p_instance_id;
ERR_FAIL_COND(node == nullptr);
tracked_player = p_path; BTInstance *inst = Object::cast_to<BTInstance>(OBJECT_DB_GET_INSTANCE(p_instance_id));
ERR_FAIL_NULL(inst);
Ref<Resource> bt = node->get(LW_NAME(behavior_tree)); inst->connect(LW_NAME(updated), callable_mp(this, &LimboDebugger::_on_bt_instance_updated).bind(p_instance_id));
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));
}
} }
void LimboDebugger::_untrack_tree() { void LimboDebugger::_untrack_tree() {
if (tracked_player.is_empty()) { if (tracked_instance_id == 0) {
return; return;
} }
NodePath was_tracking = tracked_player; BTInstance *inst = Object::cast_to<BTInstance>(OBJECT_DB_GET_INSTANCE(tracked_instance_id));
tracked_player = NodePath(); if (inst) {
inst->disconnect(LW_NAME(updated), callable_mp(this, &LimboDebugger::_on_bt_instance_updated));
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));
} }
tracked_instance_id = 0;
} }
void LimboDebugger::_send_active_bt_players() { void LimboDebugger::_send_active_bt_players() {
Array arr; Array arr;
for (KeyValue<NodePath, Ref<BTTask>> kv : active_trees) { for (uint64_t instance_id : active_bt_instances) {
arr.append(kv.key); arr.append(instance_id);
BTInstance *inst = Object::cast_to<BTInstance>(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); EngineDebugger::get_singleton()->send_message("limboai:active_bt_players", arr);
} }
void LimboDebugger::_on_bt_updated(int _status, NodePath p_path) { void LimboDebugger::_on_bt_instance_updated(int _status, uint64_t p_instance_id) {
if (p_path != tracked_player) { if (p_instance_id != tracked_instance_id) {
return; return;
} }
Array arr = BehaviorTreeData::serialize(active_trees.get(tracked_player), tracked_player, bt_resource_path); BTInstance *inst = Object::cast_to<BTInstance>(OBJECT_DB_GET_INSTANCE(p_instance_id));
EngineDebugger::get_singleton()->send_message("limboai:bt_update", arr); ERR_FAIL_NULL(inst);
} Array arr = BehaviorTreeData::serialize(inst);
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);
EngineDebugger::get_singleton()->send_message("limboai:bt_update", arr); EngineDebugger::get_singleton()->send_message("limboai:bt_update", arr);
} }

View File

@ -12,6 +12,7 @@
#ifndef LIMBO_DEBUGGER_H #ifndef LIMBO_DEBUGGER_H
#define LIMBO_DEBUGGER_H #define LIMBO_DEBUGGER_H
#include "../../bt/bt_instance.h"
#include "../../bt/tasks/bt_task.h" #include "../../bt/tasks/bt_task.h"
#ifdef LIMBOAI_MODULE #ifdef LIMBOAI_MODULE
@ -23,6 +24,7 @@
#ifdef LIMBOAI_GDEXTENSION #ifdef LIMBOAI_GDEXTENSION
#include <godot_cpp/classes/object.hpp> #include <godot_cpp/classes/object.hpp>
#include <godot_cpp/core/class_db.hpp> #include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/templates/hash_set.hpp>
#include <godot_cpp/variant/node_path.hpp> #include <godot_cpp/variant/node_path.hpp>
#endif // LIMBOAI_GDEXTENSION #endif // LIMBOAI_GDEXTENSION
@ -37,7 +39,7 @@ private:
public: public:
static void initialize(); static void initialize();
static void deinitialize(); static void deinitialize();
static LimboDebugger *get_singleton(); _FORCE_INLINE_ static LimboDebugger *get_singleton() { return singleton; }
~LimboDebugger(); ~LimboDebugger();
@ -46,17 +48,15 @@ protected:
#ifdef DEBUG_ENABLED #ifdef DEBUG_ENABLED
private: private:
HashMap<NodePath, Ref<BTTask>> active_trees; HashSet<uint64_t> active_bt_instances;
NodePath tracked_player; uint64_t tracked_instance_id = 0;
String bt_resource_path;
bool session_active = false; bool session_active = false;
void _track_tree(NodePath p_path); void _track_tree(uint64_t p_instance_id);
void _untrack_tree(); void _untrack_tree();
void _send_active_bt_players(); void _send_active_bt_players();
void _on_bt_updated(int status, NodePath p_path); void _on_bt_instance_updated(int status, uint64_t p_instance_id);
void _on_state_updated(float _delta, NodePath p_path);
public: public:
static Error parse_message(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured); static Error parse_message(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured);
@ -64,8 +64,9 @@ public:
bool parse_message_gdext(const String &p_msg, const Array &p_args); bool parse_message_gdext(const String &p_msg, const Array &p_args);
#endif #endif
void register_bt_instance(Ref<BTTask> p_instance, NodePath p_player_path); void register_bt_instance(uint64_t p_instance_id);
void unregister_bt_instance(Ref<BTTask> p_instance, NodePath p_player_path); void unregister_bt_instance(uint64_t p_instance_id);
bool is_active() const;
#endif // ! DEBUG_ENABLED #endif // ! DEBUG_ENABLED
}; };

View File

@ -58,7 +58,7 @@
//**** LimboDebuggerTab //**** LimboDebuggerTab
void LimboDebuggerTab::_reset_controls() { void LimboDebuggerTab::_reset_controls() {
bt_player_list->clear(); bt_instance_list->clear();
bt_view->clear(); bt_view->clear();
alert_box->hide(); alert_box->hide();
info_message->set_text(TTR("Run project to start debugging.")); info_message->set_text(TTR("Run project to start debugging."));
@ -68,7 +68,7 @@ void LimboDebuggerTab::_reset_controls() {
} }
void LimboDebuggerTab::start_session() { void LimboDebuggerTab::start_session() {
bt_player_list->clear(); bt_instance_list->clear();
bt_view->clear(); bt_view->clear();
alert_box->hide(); alert_box->hide();
info_message->set_text(TTR("Pick a player from the list to display behavior tree.")); 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()); session->send_message("limboai:stop_session", Array());
} }
void LimboDebuggerTab::update_active_bt_players(const Array &p_node_paths) { void LimboDebuggerTab::update_active_bt_instances(const Array &p_data) {
active_bt_players.clear(); active_bt_instances.clear();
for (int i = 0; i < p_node_paths.size(); i++) { for (int i = 0; i < p_data.size(); i += 2) {
active_bt_players.push_back(p_node_paths[i]); 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() { uint64_t LimboDebuggerTab::get_selected_bt_instance_id() {
if (!bt_player_list->is_anything_selected()) { if (!bt_instance_list->is_anything_selected()) {
return ""; 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<BehaviorTreeData> &p_data) { void LimboDebuggerTab::update_behavior_tree(const Ref<BehaviorTreeData> &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); resource_header->set_disabled(false);
bt_view->update_tree(p_data); bt_view->update_tree(p_data);
info_message->hide(); info_message->hide();
@ -108,58 +110,58 @@ void LimboDebuggerTab::_show_alert(const String &p_message) {
alert_box->set_visible(!p_message.is_empty()); alert_box->set_visible(!p_message.is_empty());
} }
void LimboDebuggerTab::_update_bt_player_list(const List<String> &p_node_paths, const String &p_filter) { void LimboDebuggerTab::_update_bt_instance_list(const Vector<BTInstanceInfo> &p_instances, const String &p_filter) {
// Remember selected item. // Remember selected item.
String selected_player = ""; uint64_t selected_instance_id = 0;
if (bt_player_list->is_anything_selected()) { if (bt_instance_list->is_anything_selected()) {
selected_player = bt_player_list->get_item_text(bt_player_list->get_selected_items()[0]); 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; int select_idx = -1;
bool selection_filtered_out = false; bool selection_filtered_out = false;
for (const String &p : p_node_paths) { for (const BTInstanceInfo &info : p_instances) {
if (p_filter.is_empty() || p.contains(p_filter)) { if (p_filter.is_empty() || info.owner_node_path.contains(p_filter)) {
int idx = bt_player_list->add_item(p); 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". // Make item text shortened from the left, e.g ".../Agent/BTPlayer".
bt_player_list->set_item_text_direction(idx, TEXT_DIRECTION_RTL); bt_instance_list->set_item_text_direction(idx, TEXT_DIRECTION_RTL);
if (p == selected_player) { if (info.instance_id == selected_instance_id) {
select_idx = idx; select_idx = idx;
} }
} else if (p == selected_player) { } else if (info.instance_id == selected_instance_id) {
selection_filtered_out = true; selection_filtered_out = true;
} }
} }
// Restore selected item. // Restore selected item.
if (select_idx > -1) { if (select_idx > -1) {
bt_player_list->select(select_idx); bt_instance_list->select(select_idx);
} else if (!selected_player.is_empty()) { } else if (selected_instance_id != 0) {
if (selection_filtered_out) { if (selection_filtered_out) {
session->send_message("limboai:untrack_bt_player", Array()); session->send_message("limboai:untrack_bt_player", Array());
bt_view->clear(); bt_view->clear();
_show_alert(""); _show_alert("");
} else { } 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(); alert_box->hide();
bt_view->clear(); bt_view->clear();
info_message->set_text(TTR("Waiting for behavior tree update.")); info_message->set_text(TTR("Waiting for behavior tree update."));
info_message->show(); info_message->show();
resource_header->set_text(TTR("Waiting for data")); resource_header->set_text(TTR("Waiting for data"));
resource_header->set_disabled(true); resource_header->set_disabled(true);
NodePath path = bt_player_list->get_item_text(p_idx);
Array msg_data; 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); session->send_message("limboai:track_bt_player", msg_data);
} }
void LimboDebuggerTab::_filter_changed(String p_text) { 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) { void LimboDebuggerTab::_window_visibility_changed(bool p_visible) {
@ -185,7 +187,7 @@ void LimboDebuggerTab::_notification(int p_what) {
case NOTIFICATION_READY: { case NOTIFICATION_READY: {
resource_header->connect(LW_NAME(pressed), callable_mp(this, &LimboDebuggerTab::_resource_header_pressed)); 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)); 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)); update_interval->connect("value_changed", callable_mp(bt_view, &BehaviorTreeView::set_update_interval_msec));
Ref<ConfigFile> cf; Ref<ConfigFile> cf;
@ -271,11 +273,11 @@ LimboDebuggerTab::LimboDebuggerTab() {
filter_players->set_placeholder(TTR("Filter Players")); filter_players->set_placeholder(TTR("Filter Players"));
list_box->add_child(filter_players); list_box->add_child(filter_players);
bt_player_list = memnew(ItemList); bt_instance_list = memnew(ItemList);
bt_player_list->set_custom_minimum_size(Size2(240.0 * EDSCALE, 0.0)); bt_instance_list->set_custom_minimum_size(Size2(240.0 * EDSCALE, 0.0));
bt_player_list->set_h_size_flags(SIZE_FILL); bt_instance_list->set_h_size_flags(SIZE_FILL);
bt_player_list->set_v_size_flags(SIZE_EXPAND_FILL); bt_instance_list->set_v_size_flags(SIZE_EXPAND_FILL);
list_box->add_child(bt_player_list); list_box->add_child(bt_instance_list);
view_box = memnew(VBoxContainer); view_box = memnew(VBoxContainer);
hsc->add_child(view_box); 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); ERR_FAIL_NULL_V(tab, false);
bool captured = true; bool captured = true;
if (p_message == "limboai:active_bt_players") { 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") { } else if (p_message == "limboai:bt_update") {
Ref<BehaviorTreeData> data = BehaviorTreeData::deserialize(p_data); Ref<BehaviorTreeData> 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); tab->update_behavior_tree(data);
} }
} else { } else {

View File

@ -51,13 +51,18 @@ class LimboDebuggerTab : public PanelContainer {
GDCLASS(LimboDebuggerTab, PanelContainer); GDCLASS(LimboDebuggerTab, PanelContainer);
private: private:
List<String> active_bt_players; struct BTInstanceInfo {
uint64_t instance_id = 0;
String owner_node_path;
};
Vector<BTInstanceInfo> active_bt_instances;
Ref<EditorDebuggerSession> session; Ref<EditorDebuggerSession> session;
VBoxContainer *root_vb = nullptr; VBoxContainer *root_vb = nullptr;
HBoxContainer *toolbar = nullptr; HBoxContainer *toolbar = nullptr;
HSplitContainer *hsc = nullptr; HSplitContainer *hsc = nullptr;
Label *info_message = nullptr; Label *info_message = nullptr;
ItemList *bt_player_list = nullptr; ItemList *bt_instance_list = nullptr;
BehaviorTreeView *bt_view = nullptr; BehaviorTreeView *bt_view = nullptr;
VBoxContainer *view_box = nullptr; VBoxContainer *view_box = nullptr;
HBoxContainer *alert_box = nullptr; HBoxContainer *alert_box = nullptr;
@ -71,8 +76,8 @@ private:
void _reset_controls(); void _reset_controls();
void _show_alert(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 _update_bt_instance_list(const Vector<BTInstanceInfo> &p_instances, const String &p_filter);
void _bt_selected(int p_idx); void _bt_instance_selected(int p_idx);
void _filter_changed(String p_text); void _filter_changed(String p_text);
void _window_visibility_changed(bool p_visible); void _window_visibility_changed(bool p_visible);
void _resource_header_pressed(); void _resource_header_pressed();
@ -84,9 +89,9 @@ protected:
public: public:
void start_session(); void start_session();
void stop_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; } 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<BehaviorTreeData> &p_data); void update_behavior_tree(const Ref<BehaviorTreeData> &p_data);
void setup(Ref<EditorDebuggerSession> p_session, CompatWindowWrapper *p_wrapper); void setup(Ref<EditorDebuggerSession> p_session, CompatWindowWrapper *p_wrapper);

View File

@ -148,6 +148,7 @@ void initialize_limboai_module(ModuleInitializationLevel p_level) {
GDREGISTER_ABSTRACT_CLASS(BT); GDREGISTER_ABSTRACT_CLASS(BT);
GDREGISTER_ABSTRACT_CLASS(BTTask); GDREGISTER_ABSTRACT_CLASS(BTTask);
GDREGISTER_CLASS(BehaviorTree); GDREGISTER_CLASS(BehaviorTree);
GDREGISTER_CLASS(BTInstance);
GDREGISTER_CLASS(BTPlayer); GDREGISTER_CLASS(BTPlayer);
GDREGISTER_CLASS(BTState); GDREGISTER_CLASS(BTState);

View File

@ -54,6 +54,7 @@
#define GET_SCRIPT(m_obj) (m_obj->get_script_instance() ? m_obj->get_script_instance()->get_script() : nullptr) #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 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 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) { _FORCE_INLINE_ bool OBJECT_HAS_PROPERTY(Object *p_obj, const StringName &p_prop) {
bool r_valid; bool r_valid;
@ -112,6 +113,7 @@ using namespace godot;
#define GET_SCRIPT(m_obj) (m_obj->get_script()) #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 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 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) { _FORCE_INLINE_ bool OBJECT_HAS_PROPERTY(Object *p_obj, const StringName &p_prop) {
return Variant(p_obj).has_key(p_prop); return Variant(p_obj).has_key(p_prop);

View File

@ -85,6 +85,7 @@ LimboStringNames::LimboStringNames() {
font_color = SN("font_color"); font_color = SN("font_color");
font_size = SN("font_size"); font_size = SN("font_size");
Forward = SN("Forward"); Forward = SN("Forward");
freed = SN("freed");
gui_input = SN("gui_input"); gui_input = SN("gui_input");
GuiOptionArrow = SN("GuiOptionArrow"); GuiOptionArrow = SN("GuiOptionArrow");
GuiTreeArrowDown = SN("GuiTreeArrowDown"); GuiTreeArrowDown = SN("GuiTreeArrowDown");

View File

@ -101,6 +101,7 @@ public:
StringName font_size; StringName font_size;
StringName font; StringName font;
StringName Forward; StringName Forward;
StringName freed;
StringName gui_input; StringName gui_input;
StringName GuiOptionArrow; StringName GuiOptionArrow;
StringName GuiTreeArrowDown; StringName GuiTreeArrowDown;