Blackboard: Add variable-to-property binding interface

This commit is contained in:
Serhii Snitsaruk 2024-01-27 16:38:58 +01:00
parent a6717ec76d
commit c81c1ec872
6 changed files with 91 additions and 22 deletions

View File

@ -20,19 +20,35 @@ void BBVariable::unref() {
data = nullptr; data = nullptr;
} }
// void BBVariable::init_ref() {
// if (data) {
// unref();
// }
// data = memnew(Data);
// data->refcount.init();
// }
void BBVariable::set_value(const Variant &p_value) { 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 { 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; return data->value;
} }
@ -89,6 +105,19 @@ void BBVariable::copy_prop_info(const BBVariable &p_other) {
data->hint_string = p_other.data->hint_string; 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 { bool BBVariable::operator==(const BBVariable &p_var) const {
if (data == p_var.data) { if (data == p_var.data) {
return true; return true;

View File

@ -14,14 +14,10 @@
#ifdef LIMBOAI_MODULE #ifdef LIMBOAI_MODULE
#include "core/object/object.h" #include "core/object/object.h"
#include "core/templates/safe_refcount.h"
#include "core/variant/variant.h"
#endif // LIMBOAI_MODULE #endif // LIMBOAI_MODULE
#ifdef LIMBOAI_GDEXTENSION #ifdef LIMBOAI_GDEXTENSION
#include "godot_cpp/core/defs.hpp" #include "godot_cpp/core/object.hpp"
#include "godot_cpp/templates/safe_refcount.hpp"
#include "godot_cpp/variant/variant.hpp"
using namespace godot; using namespace godot;
#endif // LIMBOAI_GDEXTENSION #endif // LIMBOAI_GDEXTENSION
@ -33,14 +29,14 @@ private:
Variant::Type type = Variant::NIL; Variant::Type type = Variant::NIL;
PropertyHint hint = PropertyHint::PROPERTY_HINT_NONE; PropertyHint hint = PropertyHint::PROPERTY_HINT_NONE;
String hint_string; String hint_string;
// bool bound = false;
// uint64_t bound_object = 0; NodePath binding_path;
// StringName bound_property; uint64_t bound_object = 0;
StringName bound_property;
}; };
Data *data = nullptr; Data *data = nullptr;
void unref(); void unref();
// void init_ref();
public: public:
void set_value(const Variant &p_value); void set_value(const Variant &p_value);
@ -60,10 +56,15 @@ public:
bool is_same_prop_info(const BBVariable &p_other) const; bool is_same_prop_info(const BBVariable &p_other) const;
void copy_prop_info(const BBVariable &p_other); 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); // * Runtime binding methods
// void unbind(); 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;
bool operator!=(const BBVariable &p_var) const; bool operator!=(const BBVariable &p_var) const;

View File

@ -12,7 +12,6 @@
#include "blackboard.h" #include "blackboard.h"
#ifdef LIMBOAI_MODULE #ifdef LIMBOAI_MODULE
#include "core/error/error_macros.h"
#include "core/variant/variant.h" #include "core/variant/variant.h"
#include "scene/main/node.h" #include "scene/main/node.h"
#endif // LIMBOAI_MODULE #endif // LIMBOAI_MODULE
@ -62,6 +61,16 @@ void Blackboard::erase_var(const String &p_name) {
data.erase(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) { void Blackboard::add_var(const String &p_name, const BBVariable &p_var) {
ERR_FAIL_COND(data.has(p_name)); ERR_FAIL_COND(data.has(p_name));
data.insert(p_name, p_var); 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("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("prefetch_nodepath_vars", "p_node"), &Blackboard::prefetch_nodepath_vars);
ClassDB::bind_method(D_METHOD("top"), &Blackboard::top); 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);
} }

View File

@ -51,6 +51,9 @@ public:
bool has_var(const String &p_name) const; bool has_var(const String &p_name) const;
void erase_var(const String &p_name); 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 add_var(const String &p_name, const BBVariable &p_var);
void prefetch_nodepath_vars(Node *p_node); void prefetch_nodepath_vars(Node *p_node);

View File

@ -11,6 +11,15 @@
<tutorials> <tutorials>
</tutorials> </tutorials>
<methods> <methods>
<method name="bind_var_to_property">
<return type="void" />
<param index="0" name="p_name" type="String" />
<param index="1" name="p_object" type="Object" />
<param index="2" name="p_property" type="StringName" />
<description>
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.
</description>
</method>
<method name="erase_var"> <method name="erase_var">
<return type="void" /> <return type="void" />
<param index="0" name="p_name" type="String" /> <param index="0" name="p_name" type="String" />
@ -67,5 +76,12 @@
Returns the topmost [Blackboard] in the scope chain. Returns the topmost [Blackboard] in the scope chain.
</description> </description>
</method> </method>
<method name="unbind_var">
<return type="void" />
<param index="0" name="p_name" type="String" />
<description>
Remove binding from a variable.
</description>
</method>
</methods> </methods>
</class> </class>

View File

@ -54,6 +54,11 @@
#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))
_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) #define VARIANT_EVALUATE(m_op, m_lvalue, m_rvalue, r_ret) r_ret = Variant::evaluate(m_op, m_lvalue, m_rvalue)
// * Virtual calls // * Virtual calls
@ -133,6 +138,10 @@ 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))
_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) \ #define VARIANT_EVALUATE(m_op, m_lvalue, m_rvalue, r_ret) \
{ \ { \
bool r_valid; \ bool r_valid; \