Skip to content

Commit

Permalink
GDExtension: Add compatibility system for virtual methods
Browse files Browse the repository at this point in the history
  • Loading branch information
dsnopek committed Jan 5, 2025
1 parent bdf625b commit eaa66c2
Show file tree
Hide file tree
Showing 9 changed files with 197 additions and 51 deletions.
18 changes: 15 additions & 3 deletions core/extension/extension_api_dump.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1020,7 +1020,19 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) {
d2["is_required"] = (F.flags & METHOD_FLAG_VIRTUAL_REQUIRED) ? true : false;
d2["is_vararg"] = false;
d2["is_virtual"] = true;
// virtual functions have no hash since no MethodBind is involved
d2["hash"] = mi.get_compatibility_hash();

Vector<uint32_t> compat_hashes = ClassDB::get_virtual_method_compatibility_hashes(class_name, method_name);
Array compatibility;
if (compat_hashes.size()) {
for (int i = 0; i < compat_hashes.size(); i++) {
compatibility.push_back(compat_hashes[i]);
}
}
if (compatibility.size() > 0) {
d2["hash_compatibility"] = compatibility;
}

bool has_return = mi.return_val.type != Variant::NIL || (mi.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT);
if (has_return) {
PropertyInfo pinfo = mi.return_val;
Expand Down Expand Up @@ -1473,8 +1485,8 @@ static bool compare_dict_array(const Dictionary &p_old_api, const Dictionary &p_

if (p_compare_hashes) {
if (!old_elem.has("hash")) {
if (old_elem.has("is_virtual") && bool(old_elem["is_virtual"]) && !new_elem.has("hash")) {
continue; // No hash for virtual methods, go on.
if (old_elem.has("is_virtual") && bool(old_elem["is_virtual"]) && !old_elem.has("hash")) {
continue; // Virtual methods didn't used to have hashes, so skip check if it's missing in the old file.
}

failed = true;
Expand Down
22 changes: 15 additions & 7 deletions core/extension/gdextension.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ void GDExtension::_register_extension_class(GDExtensionClassLibraryPtr p_library
nullptr, // GDExtensionClassCreateInstance2 create_instance_func; /* this one is mandatory */
p_extension_funcs->free_instance_func, // GDExtensionClassFreeInstance free_instance_func; /* this one is mandatory */
nullptr, // GDExtensionClassRecreateInstance recreate_instance_func;
p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func;
nullptr, // GDExtensionClassGetVirtual get_virtual_func;
nullptr, // GDExtensionClassGetVirtualCallData get_virtual_call_data_func;
nullptr, // GDExtensionClassCallVirtualWithData call_virtual_func;
p_extension_funcs->class_userdata, // void *class_userdata;
Expand All @@ -271,6 +271,8 @@ void GDExtension::_register_extension_class(GDExtensionClassLibraryPtr p_library
p_extension_funcs->free_property_list_func, // GDExtensionClassFreePropertyList free_property_list_func;
p_extension_funcs->create_instance_func, // GDExtensionClassCreateInstance create_instance_func;
p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid;
p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func;
nullptr,
};
_register_extension_class_internal(p_library, p_class_name, p_parent_class_name, &class_info4, &legacy);
}
Expand All @@ -296,8 +298,8 @@ void GDExtension::_register_extension_class2(GDExtensionClassLibraryPtr p_librar
nullptr, // GDExtensionClassCreateInstance2 create_instance_func; /* this one is mandatory */
p_extension_funcs->free_instance_func, // GDExtensionClassFreeInstance free_instance_func; /* this one is mandatory */
p_extension_funcs->recreate_instance_func, // GDExtensionClassRecreateInstance recreate_instance_func;
p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func;
p_extension_funcs->get_virtual_call_data_func, // GDExtensionClassGetVirtualCallData get_virtual_call_data_func;
nullptr, // GDExtensionClassGetVirtual get_virtual_func;
nullptr, // GDExtensionClassGetVirtualCallData get_virtual_call_data_func;
p_extension_funcs->call_virtual_with_data_func, // GDExtensionClassCallVirtualWithData call_virtual_func;
p_extension_funcs->class_userdata, // void *class_userdata;
};
Expand All @@ -307,6 +309,8 @@ void GDExtension::_register_extension_class2(GDExtensionClassLibraryPtr p_librar
p_extension_funcs->free_property_list_func, // GDExtensionClassFreePropertyList free_property_list_func;
p_extension_funcs->create_instance_func, // GDExtensionClassCreateInstance create_instance_func;
p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid;
p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func;
p_extension_funcs->get_virtual_call_data_func, // GDExtensionClassGetVirtual get_virtual_func;
};
_register_extension_class_internal(p_library, p_class_name, p_parent_class_name, &class_info4, &legacy);
}
Expand All @@ -332,8 +336,8 @@ void GDExtension::_register_extension_class3(GDExtensionClassLibraryPtr p_librar
nullptr, // GDExtensionClassCreateInstance2 create_instance_func; /* this one is mandatory */
p_extension_funcs->free_instance_func, // GDExtensionClassFreeInstance free_instance_func; /* this one is mandatory */
p_extension_funcs->recreate_instance_func, // GDExtensionClassRecreateInstance recreate_instance_func;
p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func;
p_extension_funcs->get_virtual_call_data_func, // GDExtensionClassGetVirtualCallData get_virtual_call_data_func;
nullptr, // GDExtensionClassGetVirtual get_virtual_func;
nullptr, // GDExtensionClassGetVirtualCallData get_virtual_call_data_func;
p_extension_funcs->call_virtual_with_data_func, // GDExtensionClassCallVirtualWithData call_virtual_func;
p_extension_funcs->class_userdata, // void *class_userdata;
};
Expand All @@ -343,6 +347,8 @@ void GDExtension::_register_extension_class3(GDExtensionClassLibraryPtr p_librar
nullptr, // GDExtensionClassFreePropertyList free_property_list_func;
p_extension_funcs->create_instance_func, // GDExtensionClassCreateInstance2 create_instance_func;
p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid;
p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func;
p_extension_funcs->get_virtual_call_data_func, // GDExtensionClassGetVirtual get_virtual_func;
};
_register_extension_class_internal(p_library, p_class_name, p_parent_class_name, &class_info4, &legacy);
}
Expand Down Expand Up @@ -431,6 +437,8 @@ void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr
extension->gdextension.free_property_list = p_deprecated_funcs->free_property_list_func;
extension->gdextension.create_instance = p_deprecated_funcs->create_instance_func;
extension->gdextension.get_rid = p_deprecated_funcs->get_rid_func;
extension->gdextension.get_virtual = p_deprecated_funcs->get_virtual_func;
extension->gdextension.get_virtual_call_data = p_deprecated_funcs->get_virtual_call_data_func;
}
#endif // DISABLE_DEPRECATED
extension->gdextension.notification2 = p_extension_funcs->notification_func;
Expand All @@ -441,8 +449,8 @@ void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr
extension->gdextension.create_instance2 = p_extension_funcs->create_instance_func;
extension->gdextension.free_instance = p_extension_funcs->free_instance_func;
extension->gdextension.recreate_instance = p_extension_funcs->recreate_instance_func;
extension->gdextension.get_virtual = p_extension_funcs->get_virtual_func;
extension->gdextension.get_virtual_call_data = p_extension_funcs->get_virtual_call_data_func;
extension->gdextension.get_virtual2 = p_extension_funcs->get_virtual_func;
extension->gdextension.get_virtual_call_data2 = p_extension_funcs->get_virtual_call_data_func;
extension->gdextension.call_virtual_with_data = p_extension_funcs->call_virtual_with_data_func;

extension->gdextension.reloadable = self->reloadable;
Expand Down
2 changes: 2 additions & 0 deletions core/extension/gdextension.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ class GDExtension : public Resource {
GDExtensionClassFreePropertyList free_property_list_func = nullptr;
GDExtensionClassCreateInstance create_instance_func = nullptr;
GDExtensionClassGetRID get_rid_func = nullptr;
GDExtensionClassGetVirtual get_virtual_func = nullptr;
GDExtensionClassGetVirtualCallData get_virtual_call_data_func = nullptr;
#endif // DISABLE_DEPRECATED
};

Expand Down
6 changes: 4 additions & 2 deletions core/extension/gdextension_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,9 @@ typedef GDExtensionObjectPtr (*GDExtensionClassCreateInstance2)(void *p_class_us
typedef void (*GDExtensionClassFreeInstance)(void *p_class_userdata, GDExtensionClassInstancePtr p_instance);
typedef GDExtensionClassInstancePtr (*GDExtensionClassRecreateInstance)(void *p_class_userdata, GDExtensionObjectPtr p_object);
typedef GDExtensionClassCallVirtual (*GDExtensionClassGetVirtual)(void *p_class_userdata, GDExtensionConstStringNamePtr p_name);
typedef GDExtensionClassCallVirtual (*GDExtensionClassGetVirtual2)(void *p_class_userdata, GDExtensionConstStringNamePtr p_name, uint32_t p_hash);
typedef void *(*GDExtensionClassGetVirtualCallData)(void *p_class_userdata, GDExtensionConstStringNamePtr p_name);
typedef void *(*GDExtensionClassGetVirtualCallData2)(void *p_class_userdata, GDExtensionConstStringNamePtr p_name, uint32_t p_hash);
typedef void (*GDExtensionClassCallVirtualWithData)(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name, void *p_virtual_call_userdata, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_ret);

typedef struct {
Expand Down Expand Up @@ -384,14 +386,14 @@ typedef struct {
GDExtensionClassFreeInstance free_instance_func; // Destructor; mandatory.
GDExtensionClassRecreateInstance recreate_instance_func;
// Queries a virtual function by name and returns a callback to invoke the requested virtual function.
GDExtensionClassGetVirtual get_virtual_func;
GDExtensionClassGetVirtual2 get_virtual_func;
// Paired with `call_virtual_with_data_func`, this is an alternative to `get_virtual_func` for extensions that
// need or benefit from extra data when calling virtual functions.
// Returns user data that will be passed to `call_virtual_with_data_func`.
// Returning `NULL` from this function signals to Godot that the virtual function is not overridden.
// Data returned from this function should be managed by the extension and must be valid until the extension is deinitialized.
// You should supply either `get_virtual_func`, or `get_virtual_call_data_func` with `call_virtual_with_data_func`.
GDExtensionClassGetVirtualCallData get_virtual_call_data_func;
GDExtensionClassGetVirtualCallData2 get_virtual_call_data_func;
// Used to call virtual functions when `get_virtual_call_data_func` is not null.
GDExtensionClassCallVirtualWithData call_virtual_with_data_func;
void *class_userdata; // Per-class user data, later accessible in instance bindings.
Expand Down
37 changes: 36 additions & 1 deletion core/object/class_db.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -938,7 +938,7 @@ void ClassDB::get_method_list_with_compatibility(const StringName &p_class, List

#ifdef DEBUG_METHODS_ENABLED
for (const MethodInfo &E : type->virtual_methods) {
Pair<MethodInfo, uint32_t> pair(E, 0);
Pair<MethodInfo, uint32_t> pair(E, E.get_compatibility_hash());
p_methods->push_back(pair);
}

Expand Down Expand Up @@ -2015,6 +2015,22 @@ void ClassDB::add_virtual_method(const StringName &p_class, const MethodInfo &p_
#endif
}

void ClassDB::add_virtual_compatibility_method(const StringName &p_class, const MethodInfo &p_method, bool p_virtual, const Vector<String> &p_arg_names, bool p_object_core) {
ERR_FAIL_COND_MSG(!classes.has(p_class), vformat("Request for nonexistent class '%s'.", p_class));

OBJTYPE_WLOCK;

HashMap<StringName, Vector<uint32_t>> &virtual_methods_compat = classes[p_class].virtual_methods_compat;

Check failure on line 2023 in core/object/class_db.cpp

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Minimal template (target=template_release, tests=yes, everything disabled)

'struct ClassDB::ClassInfo' has no member named 'virtual_methods_compat'

Vector<uint32_t> *compat_hashes = virtual_methods_compat.getptr(p_method.name);
if (!compat_hashes) {
virtual_methods_compat[p_method.name] = Vector<uint32_t>();
compat_hashes = &virtual_methods_compat[p_method.name];
}

compat_hashes->push_back(p_method.get_compatibility_hash());
}

void ClassDB::get_virtual_methods(const StringName &p_class, List<MethodInfo> *p_methods, bool p_no_inheritance) {
ERR_FAIL_COND_MSG(!classes.has(p_class), vformat("Request for nonexistent class '%s'.", p_class));

Expand All @@ -2036,6 +2052,25 @@ void ClassDB::get_virtual_methods(const StringName &p_class, List<MethodInfo> *p
#endif
}

Vector<uint32_t> ClassDB::get_virtual_method_compatibility_hashes(const StringName &p_class, const StringName &p_name) {
OBJTYPE_RLOCK;

ClassInfo *type = classes.getptr(p_class);

while (type) {
if (type->virtual_methods_compat.has(p_name)) {

Check failure on line 2061 in core/object/class_db.cpp

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Minimal template (target=template_release, tests=yes, everything disabled)

'struct ClassDB::ClassInfo' has no member named 'virtual_methods_compat'
Vector<uint32_t> *compat_hashes = type->virtual_methods_compat.getptr(p_name);

Check failure on line 2062 in core/object/class_db.cpp

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Minimal template (target=template_release, tests=yes, everything disabled)

'struct ClassDB::ClassInfo' has no member named 'virtual_methods_compat'
if (compat_hashes) {
return *compat_hashes;
}
break;
}
type = type->inherits_ptr;
}

return Vector<uint32_t>();
}

void ClassDB::add_extension_class_virtual_method(const StringName &p_class, const GDExtensionClassVirtualMethodInfo *p_method_info) {
ERR_FAIL_COND_MSG(!classes.has(p_class), vformat("Request for nonexistent class '%s'.", p_class));

Expand Down
5 changes: 5 additions & 0 deletions core/object/class_db.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ class ClassDB {
HashMap<StringName, MethodInfo> virtual_methods_map;
HashMap<StringName, Vector<Error>> method_error_values;
HashMap<StringName, List<StringName>> linked_properties;
#endif
#ifndef DISABLE_DEPRECATED
HashMap<StringName, Vector<uint32_t>> virtual_methods_compat;
#endif
HashMap<StringName, PropertySetGet> property_setget;

Expand Down Expand Up @@ -452,8 +455,10 @@ class ClassDB {
static Vector<uint32_t> get_method_compatibility_hashes(const StringName &p_class, const StringName &p_name);

static void add_virtual_method(const StringName &p_class, const MethodInfo &p_method, bool p_virtual = true, const Vector<String> &p_arg_names = Vector<String>(), bool p_object_core = false);
static void add_virtual_compatibility_method(const StringName &p_class, const MethodInfo &p_method, bool p_virtual = true, const Vector<String> &p_arg_names = Vector<String>(), bool p_object_core = false);
static void get_virtual_methods(const StringName &p_class, List<MethodInfo> *p_methods, bool p_no_inheritance = false);
static void add_extension_class_virtual_method(const StringName &p_class, const GDExtensionClassVirtualMethodInfo *p_method_info);
static Vector<uint32_t> get_virtual_method_compatibility_hashes(const StringName &p_class, const StringName &p_name);

static void bind_integer_constant(const StringName &p_class, const StringName &p_enum, const StringName &p_name, int64_t p_constant, bool p_is_bitfield = false);
static void get_integer_constant_list(const StringName &p_class, List<String> *p_constants, bool p_no_inheritance = false);
Expand Down
Loading

0 comments on commit eaa66c2

Please sign in to comment.