-
-
Notifications
You must be signed in to change notification settings - Fork 23.5k
GDExtension: Prevent compatibility breakage from change to ClassDB::instantiate() for unexposed classes
#108614
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: Prevent compatibility breakage from change to ClassDB::instantiate() for unexposed classes
#108614
Conversation
…instantiate()` for unexposed classes
Do we know what the practical impact for
I'd probably tend to this. It sounds like we made a mistake with information hiding and accidentally "leaked" internal classes. My personal course of action would be to then reduce the blast radius, not extend it 😉 It's a bit of a pity that the |
My understanding is that this affects how they load their custom resources representing scripts. So, if we keep things as they are, the existing binaries of the extension won't be able to load scripts (which is pretty fundamental to a visual scripting language :-)). I don't think the necessary changes will be that big of a deal, but they will need to make a new version, and users will need to update to it |
I cannot agree more. The more that folks decide to explore GDExtension as a way to provide editor tooling, the more we can document and clearly outline expectations without expecting someone to go look at the implementation is just going to save future headaches not only for extension developers, but Godot maintainers IMO.
For us, it depends on what we intend to do moving forward. On the one hand, we can align with the expectations that were broken in 4.2/4.3/4.4, and the changes in 4.5, by marking the classes that were internal as non-internal, which solves the issue with compatibility. Fortunately for us, we aren't bound to the same limitations of serialization as This brings me to a question of whether we could have something like This may constitute additional methods, to resolve whether a name is internal or not, etc. But I'm just wondering if such a separate, explicit, and deliberate set of APIs makes sense for the broader masses. And to @dsnopek's point about compatibility, we generally have x.y builds that are specifically tied to a specific Godot a.b version, due to API changes both in the engine and at the godot-cpp level, thus a new build would be necessary. What concerns me more is a user opening their script, seeing that the script is empty because they're using an older version built for 4.4 or 4.3 (as they don't often realize they need to upgrade), accidentally saves the project and the resource gets cleared because none of the original internal resources were loaded. |
I feel quite strongly that we should not expose a way to instantiate unexposed classes. We have a way to do it within the editor, so that GDExtensions can use internal classes for editor plugins, but I don't think that scripts or other GDExtensions should have any way to instantiate unexposed classes. They should only be directly instantiate-able by the GDExtension that declares them, and not through
I don't think it's too hard to make a factory mechanism if an extension needs it. Here's the (untested) C++ code for this that I shared on RocketChat: typedef Object *(*CreationFunc)();
template <typename T>
Object *_creation_ptr_func() {
memnew(T);
}
HashMap<StringName, CreationFunc> internal_creation_funcs;
template <typename T>
void register_internal_class(const StringName &p_name) {
internal_creation_funcs[p_name] = &_creation_ptr_func<T>();
}
Object *instantiate_internal(const StringName &p_name) {
if (internal_creation_funcs.has(p_name)) {
return internal_creation_funcs[p_name]();
}
return ClassDB::instantiate(p_name);
}
// Call this by your GDREGISTER_INTERNAL_CLASS():
register_internal_class<MyClass>();
// Now you can call instantiate_internal("...") for both exposed classes and your extension's internal classes.I don't think that's too complicated of a thing for the handful of extensions that need something like this
And this PR should prevent that |
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 implementation is valid & codestyle checks out, so this is technically good to go. The next GDExtension meeting can make the call on if this should be merged
|
Discussed at the GDExtension meeting: we think we should go for this PR. The compatibility breakage may not end up affecting very many GDExtensions, but we don't know for sure how many, so we'd like to err on the side of preserving compatibiltity. |
|
Tested on my project and it seems to fix pkdawson/imgui-godot#103 |
|
Thanks! |
…-unexposed-classes GDExtension: Prevent compatibility breakage from change to `ClassDB::instantiate()` for unexposed classes
|
I‘m sorry to leave a comment to this closed pr. // HFSMEditorPlugin
HFSMEditorPlugin *HFSMEditorPlugin::instance = nullptr;
Ref<ImageTexture> HFSMEditorPlugin::empty_icon_for_state_node = nullptr;
HFSMEditorPlugin::HFSMEditorPlugin() {
CRASH_COND_MSG(instance, "Should be instantiated only once.");
instance = this;
StateConfig::get_animation_list = &get_animation_list_for_state_config;
StateNode::get_empty_icon = &get_empty_icon_for_state_node;
...... I make an unexposed EditorPlugin become a singleton, it will be create twice, for adding editor plugin and generating document. This is certainly not the best practice for now, but this pr is definitely break compatibility. |
|
Oof. I'm assuming what's happening is the editor is creating (and then deleting) an instance of the class to get the property defaults? I'm not sure what the right course of action is at this point? This PR unbroke compatibility for some extensions. I'm not sure if there's a way we can prevent breaking both? |
|
@dsnopek I was busy with work for several months and didn't pay attention to changes in Godot. |
|
It was previously possible to use This PR was meant to make it so that unexposed classes from older GDExtensions were exposed (so they could still be created with At the time I couldn't think of a way that unexposed classes becoming exposed could actually break anything; only add entries to lists that users would need to ignore. |
|
@Daylily-Zeleen Please let me know if PR #111090 fixes your extension! |
In PR #102440 we changed
ClassDB::instantiate()to prevent creating classes that are unexposedI don't think this was never intended to work. Unexposed classes defined within Godot that are unregistered (which is all of them) cannot be instantiated from
ClassDB::instantiate(). Unexposed classes defined in a GDExtension are intended to work just like unexposed classed defined in Godot, however, in Godot 4.3 and 4.4, it is actually possible to instantiate them viaClassDB::instantiate().From the perspective of GDExtension, this behavior change (despite being a fix) is actually a compatibility breakage if an extension depended on creating unexposed classes via
ClassDB::instantiate().This PR prevents that compatibility breakage by adding a new
classdb_register_extension_class5()that honors theis_exposedflag, and changes previous versions of that function to force that flag totrue. This way older extensions that depended on the incorrect behavior ofClassDB::instantiate()should continue to work without issues, although, their unexposed classes will become exposed when the extension is loaded in Godot 4.5+I'm not sure this is actually a good idea :-)
While we're aware of one extension that depends on the old behavior, my gut feeling is that most GDExtensions that use internal classes do not depend on this, and this change will simply make all their unexposed classes become exposed (which won't break anything, but would be annoying) until they upgrade to the new function and hence opt in to the new behavior
So, the question is:
See godotengine/godot-cpp#1818 for the godot-cpp changes, which include additions to the tests to verify that unexposed classes registered from GDExtension work as expected