-
-
Notifications
You must be signed in to change notification settings - Fork 21.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
GDExtension: Add compatibility system for virtual methods #100674
base: master
Are you sure you want to change the base?
GDExtension: Add compatibility system for virtual methods #100674
Conversation
GDVIRTUAL also includes calls to scripts, so is this going to benefit scripts as well? |
No, the goal here is entirely about GDExtension binary compatibility. Although, your comment makes me realize that the
Eventually. Right now, one GDExtension can't extend a class from another GDExtension, so the problem doesn't exist there yet. But it's a good question: I'll try to think through if the API added here will work for that case too |
So, I implemented this in godot-rust/gdext#991. Was much more work than I anticipated 😁
So, the way I did this was:
That's it, right? Binding maintainers always check against the single hash from the JSON? Is there an easy way to test/simulate whether this works? I assume we can't unit-test this, so maybe compile against one Godot version and run in another? Otherwise I wonder if we should merge this soon and get user feedback to iron out things during remaining 4.4 cycle... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As long as we are reasonably certain that the initial hashes won't change, this looks good to me!
@@ -1020,7 +1020,8 @@ 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_hash(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you imagine that there's any way how the hash is going to change in the future?
Just because I remember the big issue we had with re-hashing methods in 4.0.x, and also a few other cases where it wasn't clear how const-ness and some other properties would affect hashes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The hash is computed here the same way as with non-virtual methods.
Hopefully, we won't have to mass change how the hashes are done, but there's always the possibility of fixing bugs in the hashing process, which is why we had those issues previously. If that comes up for virtual methods, I imagine we'll handle it in the same way?
a few other cases where it wasn't clear how const-ness and some other properties would affect hashes.
Well, const
-ness does affect the hashes. I think the discussion earlier was about whether it should or not? Since const
-ness doesn't actually affect binary compatibility, it's a judgement call on our part of whether that should change the hash, but if remember right a number of people (including @vnen?) were in favor of const
-ness being part of the hash.
Anyway, in this PR it's calculating the hash the same way as with non-virtual methods, which I personally think is fine.
Yep, that is correct! That's the same thing as the godot-cpp PR.
I still need to do a full proper test as well, but yes, that's basically how you'd do it. I've got a small personal project that has a
I'd be in favor of that! Lemme see how quickly I can get this out of a draft state. |
31f174e
to
f826fb1
Compare
f826fb1
to
8db041a
Compare
8db041a
to
eaa66c2
Compare
eaa66c2
to
f93e13d
Compare
Alright, I've done a bunch of testing over the last several days and this seems to be working nicely for the 3 cases:
So, I've moved the bits that add a real compatibility virtual method for There's only one thing in this PR that I'm not crazy about, but I think it's acceptable and better than the alternative, and that's in this bit of code: #define _GDVIRTUAL_GET_DEPRECATED(m_virtual, m_name_sn, m_compat)\\
else if (m_compat || ClassDB::get_virtual_method_compatibility_hashes(get_class_static(), m_name_sn).size() == 0) {\\
if (_get_extension()->get_virtual_call_data && _get_extension()->call_virtual_with_data) {\\
m_virtual = _get_extension()->get_virtual_call_data(_get_extension()->class_userdata, &m_name_sn);\\
} else if (_get_extension()->get_virtual) {\\
m_virtual = (void *)_get_extension()->get_virtual(_get_extension()->class_userdata, &m_name_sn);\\
}\\
} The call to However, it isn't ideal, because it's doing a runtime check for something that we know ahead of time when doing the In my opinion, the non-ideal runtime check in this PR is the way to go, because it only happens in very specific circumstances, and the alternative (while theoretically better) is a messy thing for Godot contributors to think about, and if we ever remove deprecated code from before Godot 4.4, it'd be totally unnecessary, since this whole thing is just to account for GDExtensions built for Godot 4.3 and earlier. Anyway, taking this out of DRAFT now! |
This is the start of a compatibility system that will allow us to change the signature of virtual methods, and not crash existing GDExtensions that implemented the old version of the virtual methods.
It allows registering compatibility virtual methods, like:
Then when we're calling the virtual method, we can do something like this:
This also changes the
extension_api.json
file to include a hash for the signature of each virtual method, and updates the GDExtension interface to include a hash argument when Godot is asking the GDExtension for a particular virtual method.The idea is: the bindings will hang on to the hashes for all the virtual methods they support, and when Godot asks for a particular virtual method, if the hash doesn't match what the GDExtension has, then return
nullptr
as if the GDExtension didn't implement the virtual method at all. So, if a GDExtension was compiled with an older hash, then the firstGDVIRTUAL_CALL()
will returnfalse
(indicating that the virtual method isn't implemented), but then the second one will succeed because the hash will match what the GDExtension has.PR godotengine/godot-cpp#1676 are the companion changes for godot-cpp
Marking as DRAFT because there's still a couple things to do:
Make a godot-cpp PR that implements thisDo lots and lots more testing :-)Store the hashes for the compatibility methods, so we can include them in the JSONCompare the hashes for virtual methods when comparing JSON files, so that we can detected missing compatibility methods via CINOTE: This presently includes the changes from PR #100447 so that I'd have a change to test, since there is a virtual method signature that we want to change there.UPDATE: I've moved the changes for PR #100447 to PR #101154, which gives a concrete use-case to test.