Compare commits

...

17 Commits

Author SHA1 Message Date
Serhii Snitsaruk 8b2770116d
Merge pull request #183 from limbonaut/btplayer-allow-changing-instance
BTPlayer: Allow switching BT instance at runtime
2024-08-04 13:16:48 +02:00
Serhii Snitsaruk 40863313dd
Check if blackboard is null in `BehaviorTree.instantiate()` 2024-08-04 12:41:25 +02:00
Serhii Snitsaruk ee12a56e96
BTPlayer: Allow changing BT instance at runtime 2024-08-04 12:36:44 +02:00
Serhii Snitsaruk 0f82fa3d64
Merge pull request #182 from limbonaut/btstate-monitor-performance
Allow monitoring BT performance in `BTState`
2024-08-04 11:39:02 +02:00
Serhii Snitsaruk df11afaf44
Allow monitoring BT performance in BTState 2024-08-04 10:37:41 +02:00
Serhii Snitsaruk d2ba904243
Merge pull request #181 from limbonaut/bt-instance
Implement `BTInstance` - runtime instance of `BehaviorTree`
2024-08-04 10:15:08 +02:00
Serhii Snitsaruk 63ef3e0555
BTPlayer: Fix `updated` signal and deprecate `behavior_tree_finished` signal 2024-08-03 16:11:47 +02:00
Serhii Snitsaruk 869b6465b9
Unregister BT instance with debugger if BTPlayer is removed from tree 2024-08-03 14:57:57 +02:00
Serhii Snitsaruk 91edd1c0b5
Documentation fixes 2024-08-03 14:39:27 +02:00
Serhii Snitsaruk 6b4c97e545
Fix BehaviorTree.instantiate() method binding 2024-08-03 14:29:24 +02:00
Serhii Snitsaruk 5f5b62a4ea
Fix BTState setup 2024-08-03 14:17:26 +02:00
Serhii Snitsaruk 68a9492f3d
Fix uninitialized integers 2024-08-03 13:48:15 +02:00
Serhii Snitsaruk 9abfe4ce95
Update class docs 2024-08-03 13:14:31 +02:00
Serhii Snitsaruk 6c794d6a7e
Refactor BTInstance.update() 2024-08-03 11:56:32 +02:00
Serhii Snitsaruk 47ad95b265
Fix demo API calls 2024-08-03 11:40:56 +02:00
Serhii Snitsaruk c4c9b5fe09
Utilize BTInstance in BTState 2024-08-03 11:39:23 +02:00
Serhii Snitsaruk fc26f51ff2
Implement BTInstance - runtime instance of BehaviorTree 2024-08-03 11:07:06 +02:00
32 changed files with 913 additions and 342 deletions

View File

@ -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;

View File

@ -77,13 +77,16 @@ void BehaviorTree::copy_other(const Ref<BehaviorTree> &p_other) {
root_task = p_other->get_root_task();
}
Ref<BTTask> BehaviorTree::instantiate(Node *p_agent, const Ref<Blackboard> &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<BTTask> inst = root_task->clone();
inst->initialize(p_agent, p_blackboard, p_scene_root);
return inst;
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, 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.");
ERR_FAIL_NULL_V_MSG(p_blackboard, nullptr, "BehaviorTree: Instantiation failed - blackboard 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<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() {
@ -116,7 +119,7 @@ void BehaviorTree::_bind_methods() {
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("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::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
#include "../blackboard/blackboard_plan.h"
#include "bt_instance.h"
#include "tasks/bt_task.h"
#ifdef LIMBOAI_MODULE
@ -58,7 +59,7 @@ public:
Ref<BehaviorTree> clone() const;
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();

154
bt/bt_instance.cpp Normal file
View File

@ -0,0 +1,154 @@
/**
* 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("get_agent"), &BTInstance::get_agent);
ClassDB::bind_method(D_METHOD("get_blackboard"), &BTInstance::get_blackboard);
ClassDB::bind_method(D_METHOD("is_instance_valid"), &BTInstance::is_instance_valid);
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
}

64
bt/bt_instance.h Normal file
View File

@ -0,0 +1,64 @@
/**
* 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_ String get_source_bt_path() const { return source_bt_path; }
_FORCE_INLINE_ Node *get_agent() const { return root_task.is_valid() ? root_task->get_agent() : nullptr; }
_FORCE_INLINE_ Ref<Blackboard> get_blackboard() const { return root_task.is_valid() ? root_task->get_blackboard() : Ref<Blackboard>(); }
_FORCE_INLINE_ bool is_instance_valid() const { return root_task.is_valid(); }
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 "../editor/debugger/limbo_debugger.h"
#include "../util/limbo_compat.h"
#include "../util/limbo_string_names.h"
@ -44,24 +43,19 @@
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);
ERR_FAIL_COND_MSG(bt_instance.is_null(), "BTPlayer: Failed to instantiate behavior tree.");
#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() {
@ -75,6 +69,18 @@ void BTPlayer::_update_blackboard_plan() {
blackboard_plan->set_base_plan(behavior_tree.is_valid() ? behavior_tree->get_blackboard_plan() : nullptr);
}
void BTPlayer::set_bt_instance(const Ref<BTInstance> &p_bt_instance) {
ERR_FAIL_COND_MSG(p_bt_instance.is_null(), "BTPlayer: Failed to set behavior tree instance - instance is null.");
ERR_FAIL_COND_MSG(!p_bt_instance->is_instance_valid(), "BTPlayer: Failed to set behavior tree instance - instance is not valid.");
bt_instance = p_bt_instance;
blackboard = p_bt_instance->get_blackboard();
agent_node = p_bt_instance->get_agent()->get_path();
blackboard_plan.unref();
behavior_tree.unref();
}
void BTPlayer::set_behavior_tree(const Ref<BehaviorTree> &p_tree) {
if (Engine::get_singleton()->is_editor_hint()) {
if (behavior_tree.is_valid() && behavior_tree->is_connected(LW_NAME(plan_changed), callable_mp(this, &BTPlayer::_update_blackboard_plan))) {
@ -87,7 +93,7 @@ void BTPlayer::set_behavior_tree(const Ref<BehaviorTree> &p_tree) {
_update_blackboard_plan();
} else {
behavior_tree = p_tree;
if (get_owner()) {
if (get_owner() && is_inside_tree()) {
_load_tree();
}
}
@ -95,7 +101,7 @@ void BTPlayer::set_behavior_tree(const Ref<BehaviorTree> &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 +125,25 @@ 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::Status status = bt_instance->update(p_delta);
emit_signal(LW_NAME(updated), status);
#ifndef DISABLE_DEPRECATED
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() {
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 +152,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 +187,17 @@ 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);
bt_instance->register_with_debugger();
}
#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);
bt_instance->unregister_with_debugger();
}
#endif // DEBUG_ENABLED
@ -266,9 +227,9 @@ 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);
ClassDB::bind_method(D_METHOD("set_bt_instance", "bt_instance"), &BTPlayer::set_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");
@ -281,9 +242,12 @@ void BTPlayer::_bind_methods() {
BIND_ENUM_CONSTANT(PHYSICS);
BIND_ENUM_CONSTANT(MANUAL);
ADD_SIGNAL(MethodInfo("behavior_tree_finished", 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
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);

View File

@ -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> blackboard;
int last_status = -1;
Ref<BTTask> tree_instance;
Ref<BTInstance> bt_instance;
void _load_tree();
void _update_blackboard_plan();
@ -75,9 +75,9 @@ public:
void update(double p_delta);
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; }
void set_bt_instance(const Ref<BTInstance> &p_bt_instance);
BTPlayer();
~BTPlayer();
@ -86,19 +86,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

View File

@ -11,7 +11,6 @@
#include "bt_state.h"
#include "../editor/debugger/limbo_debugger.h"
#include "../util/limbo_compat.h"
#include "../util/limbo_string_names.h"
@ -38,6 +37,16 @@ void BTState::set_behavior_tree(const Ref<BehaviorTree> &p_tree) {
_update_blackboard_plan();
}
#ifdef DEBUG_ENABLED
void BTState::_set_monitor_performance(bool p_monitor) {
monitor_performance = p_monitor;
if (bt_instance.is_valid()) {
bt_instance->set_monitor_performance(monitor_performance);
}
}
#endif // DEBUG_ENABLED
void BTState::_update_blackboard_plan() {
if (get_blackboard_plan().is_null()) {
set_blackboard_plan(memnew(BlackboardPlan));
@ -54,18 +63,18 @@ void BTState::_setup() {
ERR_FAIL_COND_MSG(behavior_tree.is_null(), "BTState: BehaviorTree is not assigned.");
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).");
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
if (tree_instance.is_valid() && IS_DEBUGGER_ACTIVE()) {
LimboDebugger::get_singleton()->register_bt_instance(tree_instance, get_path());
}
bt_instance->register_with_debugger();
bt_instance->set_monitor_performance(monitor_performance);
#endif
}
void BTState::_exit() {
if (tree_instance.is_valid()) {
tree_instance->abort();
if (bt_instance.is_valid()) {
bt_instance->get_root_task()->abort();
} else {
ERR_PRINT_ONCE("BTState: BehaviorTree is not assigned.");
}
@ -78,8 +87,8 @@ void BTState::_update(double p_delta) {
// Bail out if a transition happened in the meantime.
return;
}
ERR_FAIL_NULL(tree_instance);
int status = tree_instance->execute(p_delta);
ERR_FAIL_NULL(bt_instance);
BT::Status status = bt_instance->update(p_delta);
if (status == BTTask::SUCCESS) {
get_root()->dispatch(success_event, Variant());
} else if (status == BTTask::FAILURE) {
@ -92,16 +101,19 @@ void BTState::_notification(int p_notification) {
switch (p_notification) {
#ifdef DEBUG_ENABLED
case NOTIFICATION_ENTER_TREE: {
if (tree_instance.is_valid() && IS_DEBUGGER_ACTIVE()) {
LimboDebugger::get_singleton()->register_bt_instance(tree_instance, get_path());
if (bt_instance.is_valid()) {
bt_instance->register_with_debugger();
bt_instance->set_monitor_performance(monitor_performance);
}
} break;
#endif // DEBUG_ENABLED
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 (bt_instance.is_valid()) {
bt_instance->unregister_with_debugger();
bt_instance->set_monitor_performance(false);
}
#endif // DEBUG_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
@ -117,7 +129,7 @@ void BTState::_bind_methods() {
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_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("get_success_event"), &BTState::get_success_event);
@ -128,6 +140,12 @@ void BTState::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "behavior_tree", PROPERTY_HINT_RESOURCE_TYPE, "BehaviorTree"), "set_behavior_tree", "get_behavior_tree");
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "success_event"), "set_success_event", "get_success_event");
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "failure_event"), "set_failure_event", "get_failure_event");
#ifdef DEBUG_ENABLED
ClassDB::bind_method(D_METHOD("_set_monitor_performance", "enable"), &BTState::_set_monitor_performance);
ClassDB::bind_method(D_METHOD("_get_monitor_performance"), &BTState::_get_monitor_performance);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "monitor_performance"), "_set_monitor_performance", "_get_monitor_performance");
#endif // DEBUG_ENABLED
}
BTState::BTState() {

View File

@ -22,7 +22,7 @@ class BTState : public LimboState {
private:
Ref<BehaviorTree> behavior_tree;
Ref<BTTask> tree_instance;
Ref<BTInstance> bt_instance;
StringName success_event;
StringName failure_event;
@ -42,7 +42,7 @@ public:
void set_behavior_tree(const Ref<BehaviorTree> &p_value);
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; }
StringName get_success_event() const { return success_event; }
@ -51,6 +51,14 @@ public:
StringName get_failure_event() const { return failure_event; }
BTState();
#ifdef DEBUG_ENABLED
private:
bool monitor_performance = false;
void _set_monitor_performance(bool p_monitor);
bool _get_monitor_performance() const { return monitor_performance; }
#endif
};
#endif // BT_STATE_H

View File

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

View File

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

View File

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

View File

@ -55,17 +55,17 @@ Methods
.. table::
:widths: auto
+-----------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+-----------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| :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>`\ ) |
+-----------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+-----------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| :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>`\ ) |
+-----------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+-----------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
.. 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``\ )
- ``String`` **get_description**\ (\ )
User-provided description of the BehaviorTree.
User-provided description of the **BehaviorTree**.
.. rst-class:: classref-section-separator
@ -172,9 +172,9 @@ Returns the root task of the BehaviorTree resource.
.. 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. Make sure to pass a :ref:`Blackboard<class_Blackboard>` with values populated from :ref:`blackboard_plan<class_BehaviorTree_property_blackboard_plan>`. See also ``BlackboardPlan.populate_blackboard`` & ``BlackboardPlan.create_blackboard``.
.. rst-class:: classref-item-separator

View File

@ -31,9 +31,9 @@ Methods
.. table::
: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
@ -44,15 +44,15 @@ Methods
Method Descriptions
-------------------
.. _class_BehaviorTreeData_method_create_from_tree_instance:
.. _class_BehaviorTreeData_method_create_from_bt_instance:
.. 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.)`
.. |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,249 @@
: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
+-------------------------------------+-----------------------------------------------------------------------------------------+
| ``Node`` | :ref:`get_agent<class_BTInstance_method_get_agent>`\ (\ ) |const| |
+-------------------------------------+-----------------------------------------------------------------------------------------+
| :ref:`Blackboard<class_Blackboard>` | :ref:`get_blackboard<class_BTInstance_method_get_blackboard>`\ (\ ) |const| |
+-------------------------------------+-----------------------------------------------------------------------------------------+
| :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| |
+-------------------------------------+-----------------------------------------------------------------------------------------+
| ``bool`` | :ref:`is_instance_valid<class_BTInstance_method_is_instance_valid>`\ (\ ) |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_agent:
.. rst-class:: classref-method
``Node`` **get_agent**\ (\ ) |const| :ref:`🔗<class_BTInstance_method_get_agent>`
Returns the agent of the behavior tree instance.
.. rst-class:: classref-item-separator
----
.. _class_BTInstance_method_get_blackboard:
.. rst-class:: classref-method
:ref:`Blackboard<class_Blackboard>` **get_blackboard**\ (\ ) |const| :ref:`🔗<class_BTInstance_method_get_blackboard>`
Returns the blackboard of the behavior tree instance.
.. rst-class:: classref-item-separator
----
.. _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_is_instance_valid:
.. rst-class:: classref-method
``bool`` **is_instance_valid**\ (\ ) |const| :ref:`🔗<class_BTInstance_method_is_instance_valid>`
Returns ``true`` if the behavior tree instance is properly initialized and can be used.
.. 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,15 @@ Methods
.. table::
:widths: auto
+-----------------------------+-----------------------------------------------------------------------------+
| ``int`` | :ref:`get_last_status<class_BTPlayer_method_get_last_status>`\ (\ ) |const| |
+-----------------------------+-----------------------------------------------------------------------------+
| :ref:`BTTask<class_BTTask>` | :ref:`get_tree_instance<class_BTPlayer_method_get_tree_instance>`\ (\ ) |
+-----------------------------+-----------------------------------------------------------------------------+
+-------------------------------------+------------------------------------------------------------------------------------------------------------------------+
| :ref:`BTInstance<class_BTInstance>` | :ref:`get_bt_instance<class_BTPlayer_method_get_bt_instance>`\ (\ ) |
+-------------------------------------+------------------------------------------------------------------------------------------------------------------------+
| |void| | :ref:`restart<class_BTPlayer_method_restart>`\ (\ ) |
+-----------------------------+-----------------------------------------------------------------------------+
+-------------------------------------+------------------------------------------------------------------------------------------------------------------------+
| |void| | :ref:`set_bt_instance<class_BTPlayer_method_set_bt_instance>`\ (\ bt_instance\: :ref:`BTInstance<class_BTInstance>`\ ) |
+-------------------------------------+------------------------------------------------------------------------------------------------------------------------+
| |void| | :ref:`update<class_BTPlayer_method_update>`\ (\ delta\: ``float``\ ) |
+-----------------------------+-----------------------------------------------------------------------------+
+-------------------------------------+------------------------------------------------------------------------------------------------------------------------+
.. rst-class:: classref-section-separator
@ -80,6 +80,8 @@ Signals
**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``.
Argument ``status`` holds the status returned by the behavior tree. See :ref:`Status<enum_BT_Status>`.
@ -265,25 +267,13 @@ Determines when the behavior tree is executed. See :ref:`UpdateMode<enum_BTPlaye
Method Descriptions
-------------------
.. _class_BTPlayer_method_get_last_status:
.. _class_BTPlayer_method_get_bt_instance:
.. 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>`.
.. 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.
Returns the behavior tree instance.
.. rst-class:: classref-item-separator
@ -301,6 +291,18 @@ Resets the behavior tree's execution. Each running task will be aborted and the
----
.. _class_BTPlayer_method_set_bt_instance:
.. rst-class:: classref-method
|void| **set_bt_instance**\ (\ bt_instance\: :ref:`BTInstance<class_BTInstance>`\ ) :ref:`🔗<class_BTPlayer_method_set_bt_instance>`
Sets the :ref:`BTInstance<class_BTInstance>` to play. This method is useful when you want to switch to a different behavior tree instance at runtime. See also :ref:`BehaviorTree.instantiate<class_BehaviorTree_property_instantiate>`.
.. rst-class:: classref-item-separator
----
.. _class_BTPlayer_method_update:
.. rst-class:: classref-method

View File

@ -29,13 +29,15 @@ Properties
.. table::
:widths: auto
+-----------------------------------------+------------------------------------------------------------+----------------+
+-----------------------------------------+------------------------------------------------------------------------+----------------+
| :ref:`BehaviorTree<class_BehaviorTree>` | :ref:`behavior_tree<class_BTState_property_behavior_tree>` | |
+-----------------------------------------+------------------------------------------------------------+----------------+
+-----------------------------------------+------------------------------------------------------------------------+----------------+
| ``StringName`` | :ref:`failure_event<class_BTState_property_failure_event>` | ``&"failure"`` |
+-----------------------------------------+------------------------------------------------------------+----------------+
+-----------------------------------------+------------------------------------------------------------------------+----------------+
| ``bool`` | :ref:`monitor_performance<class_BTState_property_monitor_performance>` | ``false`` |
+-----------------------------------------+------------------------------------------------------------------------+----------------+
| ``StringName`` | :ref:`success_event<class_BTState_property_success_event>` | ``&"success"`` |
+-----------------------------------------+------------------------------------------------------------+----------------+
+-----------------------------------------+------------------------------------------------------------------------+----------------+
.. rst-class:: classref-reftable-group
@ -45,9 +47,9 @@ Methods
.. table::
: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
@ -92,6 +94,18 @@ HSM event that will be dispatched when the behavior tree results in ``FAILURE``.
----
.. _class_BTState_property_monitor_performance:
.. rst-class:: classref-property
``bool`` **monitor_performance** = ``false`` :ref:`🔗<class_BTState_property_monitor_performance>`
If ``true``, adds a performance monitor to "Debugger->Monitors" for each instance of this **BTState** node.
.. rst-class:: classref-item-separator
----
.. _class_BTState_property_success_event:
.. rst-class:: classref-property
@ -114,13 +128,13 @@ HSM event that will be dispatched when the behavior tree results in ``SUCCESS``.
Method Descriptions
-------------------
.. _class_BTState_method_get_tree_instance:
.. _class_BTState_method_get_bt_instance:
.. 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.)`
.. |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,92 @@
<?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_agent" qualifiers="const">
<return type="Node" />
<description>
Returns the agent of the behavior tree instance.
</description>
</method>
<method name="get_blackboard" qualifiers="const">
<return type="Blackboard" />
<description>
Returns the blackboard of the behavior tree instance.
</description>
</method>
<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="is_instance_valid" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if the behavior tree instance is properly initialized and can be used.
</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>
<methods>
<method name="get_last_status" qualifiers="const">
<return type="int" />
<method name="get_bt_instance">
<return type="BTInstance" />
<description>
Returns the behavior tree's last execution status. See [enum BT.Status].
</description>
</method>
<method name="get_tree_instance">
<return type="BTTask" />
<description>
Returns the root task of the instantiated behavior tree.
Returns the behavior tree instance.
</description>
</method>
<method name="restart">
@ -28,6 +22,13 @@
Resets the behavior tree's execution. Each running task will be aborted and the next tree execution will start anew. This method does not reset [Blackboard].
</description>
</method>
<method name="set_bt_instance">
<return type="void" />
<param index="0" name="bt_instance" type="BTInstance" />
<description>
Sets the [BTInstance] to play. This method is useful when you want to switch to a different behavior tree instance at runtime. See also [member BehaviorTree.instantiate].
</description>
</method>
<method name="update">
<return type="void" />
<param index="0" name="delta" type="float" />
@ -60,7 +61,7 @@
</member>
</members>
<signals>
<signal name="behavior_tree_finished">
<signal name="behavior_tree_finished" deprecated="Use [signal updated] signal instead.">
<param index="0" name="status" type="int" />
<description>
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>
<methods>
<method name="get_tree_instance" qualifiers="const">
<return type="BTTask" />
<method name="get_bt_instance" qualifiers="const">
<return type="BTInstance" />
<description>
Returns the root task of the instantiated behavior tree.
Returns the behavior tree instance.
</description>
</method>
</methods>
@ -23,6 +23,9 @@
<member name="failure_event" type="StringName" setter="set_failure_event" getter="get_failure_event" default="&amp;&quot;failure&quot;">
HSM event that will be dispatched when the behavior tree results in [code]FAILURE[/code]. See [method LimboState.dispatch].
</member>
<member name="monitor_performance" type="bool" setter="_set_monitor_performance" getter="_get_monitor_performance" default="false">
If [code]true[/code], adds a performance monitor to "Debugger-&gt;Monitors" for each instance of this [BTState] node.
</member>
<member name="success_event" type="StringName" setter="set_success_event" getter="get_success_event" default="&amp;&quot;success&quot;">
HSM event that will be dispatched when the behavior tree results in [code]SUCCESS[/code]. See [method LimboState.dispatch].
</member>

View File

@ -35,12 +35,12 @@
</description>
</method>
<method name="instantiate" qualifiers="const">
<return type="BTTask" />
<return type="BTInstance" />
<param index="0" name="agent" type="Node" />
<param index="1" name="blackboard" type="Blackboard" />
<param index="2" name="scene_root" type="Node" />
<param index="2" name="instance_owner" type="Node" />
<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. Make sure to pass a [Blackboard] with values populated from [member blackboard_plan]. See also [BlackboardPlan.populate_blackboard] &amp; [BlackboardPlan.create_blackboard].
</description>
</method>
<method name="set_root_task">
@ -56,7 +56,7 @@
Stores and manages variables that will be used in constructing new [Blackboard] instances.
</member>
<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>
</members>
<signals>

View File

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

View File

@ -17,14 +17,15 @@
//**** 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;
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<Ref<BTTask>> stack;
stack.push_back(p_tree_instance);
stack.push_back(p_instance->get_root_task());
while (stack.size()) {
Ref<BTTask> task = stack.front()->get();
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) {
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<BehaviorTreeData> 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> BehaviorTreeData::deserialize(const Array &p_array) {
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);
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<Ref<BTTask>> stack;
stack.push_back(p_tree_instance);
stack.push_back(p_bt_instance->get_root_task());
while (stack.size()) {
Ref<BTTask> task = stack.front()->get();
stack.pop_front();
@ -115,9 +122,7 @@ Ref<BehaviorTreeData> BehaviorTreeData::create_from_tree_instance(const Ref<BTTa
}
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("deserialize", "p_array"), &BehaviorTreeData::deserialize);
ClassDB::bind_static_method("BehaviorTreeData", D_METHOD("create_from_tree_instance", "tree_instance"), &BehaviorTreeData::create_from_tree_instance);
ClassDB::bind_static_method("BehaviorTreeData", D_METHOD("create_from_bt_instance", "bt_instance"), &BehaviorTreeData::create_from_bt_instance);
}
BehaviorTreeData::BehaviorTreeData() {

View File

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

View File

@ -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"
@ -33,9 +33,6 @@
//**** LimboDebugger
LimboDebugger *LimboDebugger::singleton = nullptr;
LimboDebugger *LimboDebugger::get_singleton() {
return singleton;
}
LimboDebugger::LimboDebugger() {
singleton = this;
@ -63,14 +60,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 +88,96 @@ bool LimboDebugger::parse_message_gdext(const String &p_msg, const Array &p_args
}
#endif // LIMBOAI_GDEXTENSION
void LimboDebugger::register_bt_instance(Ref<BTTask> 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);
if (!IS_DEBUGGER_ACTIVE()) {
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) {
_send_active_bt_players();
}
}
void LimboDebugger::unregister_bt_instance(Ref<BTTask> 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));
bool LimboDebugger::is_active() const {
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();
}
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<Resource> 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<BTInstance>(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<BTInstance>(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<NodePath, Ref<BTTask>> kv : active_trees) {
arr.append(kv.key);
for (uint64_t instance_id : active_bt_instances) {
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);
}
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<BTInstance>(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);
}

View File

@ -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 <godot_cpp/classes/object.hpp>
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/templates/hash_set.hpp>
#include <godot_cpp/variant/node_path.hpp>
#endif // LIMBOAI_GDEXTENSION
@ -37,7 +39,7 @@ private:
public:
static void initialize();
static void deinitialize();
static LimboDebugger *get_singleton();
_FORCE_INLINE_ static LimboDebugger *get_singleton() { return singleton; }
~LimboDebugger();
@ -46,17 +48,15 @@ protected:
#ifdef DEBUG_ENABLED
private:
HashMap<NodePath, Ref<BTTask>> active_trees;
NodePath tracked_player;
String bt_resource_path;
HashSet<uint64_t> active_bt_instances;
uint64_t tracked_instance_id = 0;
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,9 @@ public:
bool parse_message_gdext(const String &p_msg, const Array &p_args);
#endif
void register_bt_instance(Ref<BTTask> p_instance, NodePath p_player_path);
void unregister_bt_instance(Ref<BTTask> p_instance, NodePath p_player_path);
void register_bt_instance(uint64_t p_instance_id);
void unregister_bt_instance(uint64_t p_instance_id);
bool is_active() const;
#endif // ! DEBUG_ENABLED
};

View File

@ -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<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);
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<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.
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<ConfigFile> 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<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);
}
} else {

View File

@ -51,13 +51,18 @@ class LimboDebuggerTab : public PanelContainer {
GDCLASS(LimboDebuggerTab, PanelContainer);
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;
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<String> &p_node_paths, const String &p_filter);
void _bt_selected(int p_idx);
void _update_bt_instance_list(const Vector<BTInstanceInfo> &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<BehaviorTreeData> &p_data);
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(BTTask);
GDREGISTER_CLASS(BehaviorTree);
GDREGISTER_CLASS(BTInstance);
GDREGISTER_CLASS(BTPlayer);
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 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);

View File

@ -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");

View File

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