From c81c1ec872c360b9234e4f15a91283e07b4fd4b3 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Sat, 27 Jan 2024 16:38:58 +0100 Subject: [PATCH] Blackboard: Add variable-to-property binding interface --- blackboard/bb_variable.cpp | 47 ++++++++++++++++++++++++++++++-------- blackboard/bb_variable.h | 25 ++++++++++---------- blackboard/blackboard.cpp | 13 ++++++++++- blackboard/blackboard.h | 3 +++ doc_classes/Blackboard.xml | 16 +++++++++++++ util/limbo_compat.h | 9 ++++++++ 6 files changed, 91 insertions(+), 22 deletions(-) diff --git a/blackboard/bb_variable.cpp b/blackboard/bb_variable.cpp index 12ad83b..0dfb0e7 100644 --- a/blackboard/bb_variable.cpp +++ b/blackboard/bb_variable.cpp @@ -20,19 +20,35 @@ void BBVariable::unref() { data = nullptr; } -// void BBVariable::init_ref() { -// if (data) { -// unref(); -// } -// data = memnew(Data); -// data->refcount.init(); -// } - void BBVariable::set_value(const Variant &p_value) { - data->value = p_value; + data->value = p_value; // Setting value even when bound as a fallback in case the binding fails. + + if (is_bound()) { + Object *obj = ObjectDB::get_instance(ObjectID(data->bound_object)); + ERR_FAIL_COND_MSG(!obj, "Blackboard: Failed to get bound object."); +#ifdef LIMBOAI_MODULE + bool r_valid; + obj->set(data->bound_property, p_value, &r_valid); + ERR_FAIL_COND_MSG(!r_valid, vformat("Blackboard: Failed to set bound property `%s` on %s", data->bound_property, obj)); +#elif LIMBOAI_GDEXTENSION + obj->set(data->bound_property, p_value); +#endif + } } Variant BBVariable::get_value() const { + if (is_bound()) { + Object *obj = ObjectDB::get_instance(ObjectID(data->bound_object)); + ERR_FAIL_COND_V_MSG(!obj, data->value, "Blackboard: Failed to get bound object."); +#ifdef LIMBOAI_MODULE + bool r_valid; + Variant ret = obj->get(data->bound_property, &r_valid); + ERR_FAIL_COND_V_MSG(!r_valid, data->value, vformat("Blackboard: Failed to get bound property `%s` on %s", data->bound_property, obj)); +#elif LIMBOAI_GDEXTENSION + Variant ret = obj->get(data->bound_property); +#endif + return ret; + } return data->value; } @@ -89,6 +105,19 @@ void BBVariable::copy_prop_info(const BBVariable &p_other) { data->hint_string = p_other.data->hint_string; } +void BBVariable::bind(Object *p_object, const StringName &p_property) { + ERR_FAIL_NULL_MSG(p_object, "Blackboard: Binding failed - object is null."); + ERR_FAIL_COND_MSG(p_property == StringName(), "Blackboard: Binding failed - property name is empty."); + ERR_FAIL_COND_MSG(!OBJECT_HAS_PROPERTY(p_object, p_property), vformat("Blackboard: Binding failed - %s has no property `%s`.", p_object, p_property)); + data->bound_object = p_object->get_instance_id(); + data->bound_property = p_property; +} + +void BBVariable::unbind() { + data->bound_object = 0; + data->bound_property = StringName(); +} + bool BBVariable::operator==(const BBVariable &p_var) const { if (data == p_var.data) { return true; diff --git a/blackboard/bb_variable.h b/blackboard/bb_variable.h index cccf2b8..aca84fd 100644 --- a/blackboard/bb_variable.h +++ b/blackboard/bb_variable.h @@ -14,14 +14,10 @@ #ifdef LIMBOAI_MODULE #include "core/object/object.h" -#include "core/templates/safe_refcount.h" -#include "core/variant/variant.h" #endif // LIMBOAI_MODULE #ifdef LIMBOAI_GDEXTENSION -#include "godot_cpp/core/defs.hpp" -#include "godot_cpp/templates/safe_refcount.hpp" -#include "godot_cpp/variant/variant.hpp" +#include "godot_cpp/core/object.hpp" using namespace godot; #endif // LIMBOAI_GDEXTENSION @@ -33,14 +29,14 @@ private: Variant::Type type = Variant::NIL; PropertyHint hint = PropertyHint::PROPERTY_HINT_NONE; String hint_string; - // bool bound = false; - // uint64_t bound_object = 0; - // StringName bound_property; + + NodePath binding_path; + uint64_t bound_object = 0; + StringName bound_property; }; Data *data = nullptr; void unref(); - // void init_ref(); public: void set_value(const Variant &p_value); @@ -60,10 +56,15 @@ public: bool is_same_prop_info(const BBVariable &p_other) const; void copy_prop_info(const BBVariable &p_other); - // bool is_bound() { return bound; } + // * Editor binding methods + String get_binding_path() const { return data->binding_path; } + void set_binding_path(const String &p_binding_path) { data->binding_path = p_binding_path; } + bool has_binding() { return data->binding_path.is_empty(); } - // void bind(Node *p_root, NodePath p_path); - // void unbind(); + // * Runtime binding methods + bool is_bound() const { return data->bound_object != 0; } + void bind(Object *p_object, const StringName &p_property); + void unbind(); bool operator==(const BBVariable &p_var) const; bool operator!=(const BBVariable &p_var) const; diff --git a/blackboard/blackboard.cpp b/blackboard/blackboard.cpp index 62584e0..6e3f8c8 100644 --- a/blackboard/blackboard.cpp +++ b/blackboard/blackboard.cpp @@ -12,7 +12,6 @@ #include "blackboard.h" #ifdef LIMBOAI_MODULE -#include "core/error/error_macros.h" #include "core/variant/variant.h" #include "scene/main/node.h" #endif // LIMBOAI_MODULE @@ -62,6 +61,16 @@ void Blackboard::erase_var(const String &p_name) { data.erase(p_name); } +void Blackboard::bind_var_to_property(const String &p_name, Object *p_object, const StringName &p_property) { + ERR_FAIL_COND_MSG(!data.has(p_name), "Blackboard: Binding failed - can't bind variable that doesn't exist."); + data[p_name].bind(p_object, p_property); +} + +void Blackboard::unbind_var(const String &p_name) { + ERR_FAIL_COND_MSG(data.has(p_name), "Blackboard: Can't unbind variable that doesn't exist."); + data[p_name].unbind(); +} + void Blackboard::add_var(const String &p_name, const BBVariable &p_var) { ERR_FAIL_COND(data.has(p_name)); data.insert(p_name, p_var); @@ -89,4 +98,6 @@ void Blackboard::_bind_methods() { ClassDB::bind_method(D_METHOD("erase_var", "p_name"), &Blackboard::erase_var); ClassDB::bind_method(D_METHOD("prefetch_nodepath_vars", "p_node"), &Blackboard::prefetch_nodepath_vars); ClassDB::bind_method(D_METHOD("top"), &Blackboard::top); + ClassDB::bind_method(D_METHOD("bind_var_to_property", "p_name", "p_object", "p_property"), &Blackboard::bind_var_to_property); + ClassDB::bind_method(D_METHOD("unbind_var", "p_name"), &Blackboard::unbind_var); } diff --git a/blackboard/blackboard.h b/blackboard/blackboard.h index 42c04c7..eba492b 100644 --- a/blackboard/blackboard.h +++ b/blackboard/blackboard.h @@ -51,6 +51,9 @@ public: bool has_var(const String &p_name) const; void erase_var(const String &p_name); + void bind_var_to_property(const String &p_name, Object *p_object, const StringName &p_property); + void unbind_var(const String &p_name); + void add_var(const String &p_name, const BBVariable &p_var); void prefetch_nodepath_vars(Node *p_node); diff --git a/doc_classes/Blackboard.xml b/doc_classes/Blackboard.xml index 91f4492..adb20f6 100644 --- a/doc_classes/Blackboard.xml +++ b/doc_classes/Blackboard.xml @@ -11,6 +11,15 @@ + + + + + + + Establish a binding between a variable and the object's property specified by [param p_property] and [param p_object]. Changes to the variable update the property, and vice versa. + + @@ -67,5 +76,12 @@ Returns the topmost [Blackboard] in the scope chain. + + + + + Remove binding from a variable. + + diff --git a/util/limbo_compat.h b/util/limbo_compat.h index fdfce9a..690682b 100644 --- a/util/limbo_compat.h +++ b/util/limbo_compat.h @@ -54,6 +54,11 @@ #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)) +_FORCE_INLINE_ bool OBJECT_HAS_PROPERTY(Object *p_obj, const StringName &p_prop) { + bool r_valid; + return Variant(p_obj).has_key(p_prop, r_valid); +} + #define VARIANT_EVALUATE(m_op, m_lvalue, m_rvalue, r_ret) r_ret = Variant::evaluate(m_op, m_lvalue, m_rvalue) // * Virtual calls @@ -133,6 +138,10 @@ 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)) +_FORCE_INLINE_ bool OBJECT_HAS_PROPERTY(Object *p_obj, const StringName &p_prop) { + return Variant(p_obj).has_key(p_prop); +} + #define VARIANT_EVALUATE(m_op, m_lvalue, m_rvalue, r_ret) \ { \ bool r_valid; \