Merge pull request #94089 from dsnopek/gdext-valid-runtime-properties

GDExtension: Fix setting base class properties on a runtime class
This commit is contained in:
Rémi Verschelde 2024-07-08 23:53:12 +02:00
commit 8ed9bfdc25
No known key found for this signature in database
GPG key ID: C3336907360768E1
2 changed files with 45 additions and 19 deletions

View file

@ -76,6 +76,21 @@ class PlaceholderExtensionInstance {
StringName class_name; StringName class_name;
HashMap<StringName, Variant> properties; HashMap<StringName, Variant> properties;
// Checks if a property is from a runtime class, and not a non-runtime base class.
bool is_runtime_property(const StringName &p_property_name) {
StringName current_class_name = class_name;
while (ClassDB::is_class_runtime(current_class_name)) {
if (ClassDB::has_property(current_class_name, p_property_name, true)) {
return true;
}
current_class_name = ClassDB::get_parent_class(current_class_name);
}
return false;
}
public: public:
PlaceholderExtensionInstance(const StringName &p_class_name) { PlaceholderExtensionInstance(const StringName &p_class_name) {
class_name = p_class_name; class_name = p_class_name;
@ -83,27 +98,24 @@ public:
~PlaceholderExtensionInstance() {} ~PlaceholderExtensionInstance() {}
void set(const StringName &p_name, const Variant &p_value) { void set(const StringName &p_name, const Variant &p_value, bool &r_valid) {
bool is_default_valid = false; r_valid = is_runtime_property(p_name);
Variant default_value = ClassDB::class_get_default_property_value(class_name, p_name, &is_default_valid); if (r_valid) {
// If there's a default value, then we know it's a valid property.
if (is_default_valid) {
properties[p_name] = p_value; properties[p_name] = p_value;
} }
} }
Variant get(const StringName &p_name) { Variant get(const StringName &p_name, bool &r_valid) {
const Variant *value = properties.getptr(p_name); const Variant *value = properties.getptr(p_name);
Variant ret; Variant ret;
if (value) { if (value) {
ret = *value; ret = *value;
r_valid = true;
} else { } else {
bool is_default_valid = false; r_valid = is_runtime_property(p_name);
Variant default_value = ClassDB::class_get_default_property_value(class_name, p_name, &is_default_valid); if (r_valid) {
if (is_default_valid) { ret = ClassDB::class_get_default_property_value(class_name, p_name);
ret = default_value;
} }
} }
@ -115,10 +127,10 @@ public:
const StringName &name = *(StringName *)p_name; const StringName &name = *(StringName *)p_name;
const Variant &value = *(const Variant *)p_value; const Variant &value = *(const Variant *)p_value;
self->set(name, value); bool valid = false;
self->set(name, value, valid);
// We have to return true so Godot doesn't try to call the real setter function. return valid;
return true;
} }
static GDExtensionBool placeholder_instance_get(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name, GDExtensionVariantPtr r_ret) { static GDExtensionBool placeholder_instance_get(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name, GDExtensionVariantPtr r_ret) {
@ -126,10 +138,10 @@ public:
const StringName &name = *(StringName *)p_name; const StringName &name = *(StringName *)p_name;
Variant *value = (Variant *)r_ret; Variant *value = (Variant *)r_ret;
*value = self->get(name); bool valid = false;
*value = self->get(name, valid);
// We have to return true so Godot doesn't try to call the real getter function. return valid;
return true;
} }
static const GDExtensionPropertyInfo *placeholder_instance_get_property_list(GDExtensionClassInstancePtr p_instance, uint32_t *r_count) { static const GDExtensionPropertyInfo *placeholder_instance_get_property_list(GDExtensionClassInstancePtr p_instance, uint32_t *r_count) {
@ -172,9 +184,9 @@ public:
static GDExtensionObjectPtr placeholder_class_create_instance(void *p_class_userdata) { static GDExtensionObjectPtr placeholder_class_create_instance(void *p_class_userdata) {
ClassDB::ClassInfo *ti = (ClassDB::ClassInfo *)p_class_userdata; ClassDB::ClassInfo *ti = (ClassDB::ClassInfo *)p_class_userdata;
// Find the closest native parent. // Find the closest native parent, that isn't a runtime class.
ClassDB::ClassInfo *native_parent = ti->inherits_ptr; ClassDB::ClassInfo *native_parent = ti->inherits_ptr;
while (native_parent->gdextension) { while (native_parent->gdextension || native_parent->is_runtime) {
native_parent = native_parent->inherits_ptr; native_parent = native_parent->inherits_ptr;
} }
ERR_FAIL_NULL_V(native_parent->creation_func, nullptr); ERR_FAIL_NULL_V(native_parent->creation_func, nullptr);
@ -1952,6 +1964,14 @@ bool ClassDB::is_class_reloadable(const StringName &p_class) {
return ti->reloadable; return ti->reloadable;
} }
bool ClassDB::is_class_runtime(const StringName &p_class) {
OBJTYPE_RLOCK;
ClassInfo *ti = classes.getptr(p_class);
ERR_FAIL_NULL_V_MSG(ti, false, "Cannot get class '" + String(p_class) + "'.");
return ti->is_runtime;
}
void ClassDB::add_resource_base_extension(const StringName &p_extension, const StringName &p_class) { void ClassDB::add_resource_base_extension(const StringName &p_extension, const StringName &p_class) {
if (resource_base_extensions.has(p_extension)) { if (resource_base_extensions.has(p_extension)) {
return; return;
@ -2063,6 +2083,11 @@ void ClassDB::register_extension_class(ObjectGDExtension *p_extension) {
ClassInfo *parent = classes.getptr(p_extension->parent_class_name); ClassInfo *parent = classes.getptr(p_extension->parent_class_name);
#ifdef TOOLS_ENABLED
// @todo This is a limitation of the current implementation, but it should be possible to remove.
ERR_FAIL_COND_MSG(p_extension->is_runtime && parent->gdextension && !parent->is_runtime, "Extension runtime class " + String(p_extension->class_name) + " cannot descend from " + parent->name + " which isn't also a runtime class");
#endif
ClassInfo c; ClassInfo c;
c.api = p_extension->editor_class ? API_EDITOR_EXTENSION : API_EXTENSION; c.api = p_extension->editor_class ? API_EDITOR_EXTENSION : API_EXTENSION;
c.gdextension = p_extension; c.gdextension = p_extension;

View file

@ -460,6 +460,7 @@ public:
static bool is_class_exposed(const StringName &p_class); static bool is_class_exposed(const StringName &p_class);
static bool is_class_reloadable(const StringName &p_class); static bool is_class_reloadable(const StringName &p_class);
static bool is_class_runtime(const StringName &p_class);
static void add_resource_base_extension(const StringName &p_extension, const StringName &p_class); static void add_resource_base_extension(const StringName &p_extension, const StringName &p_class);
static void get_resource_base_extensions(List<String> *p_extensions); static void get_resource_base_extensions(List<String> *p_extensions);