-
-
Notifications
You must be signed in to change notification settings - Fork 218
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
Fix DynGd<T,D>
export, implement proper export for Array<DynGd<T,D>>
#1056
Fix DynGd<T,D>
export, implement proper export for Array<DynGd<T,D>>
#1056
Conversation
Array<DynGd<T,D>>
Array<DynGd<T,D>>
export
API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-1056 |
Draft - allegedly there are some problems with exporting Nodes in Array (it allows to export only the very first one) which I have to investigate further. |
16149b7
to
2388568
Compare
One open issue – should we support something among the lines of #[export]
nodes_concrete: Array<DynGd<SomeUserDeclaredGDExtensionNode, dyn ExampleTrait>>,
#[export]
resources_concrete: Array<DynGd<SomeUserDeclaredGDExtensionResource, dyn ExampleTrait>>, ? I don't see reason to do so, since… we can just export Might be revisited after inheritance will be added to |
Array<DynGd<T,D>>
exportDynGd<T,D>
export, implement proper export for Array<DynGd<T,D>>
D: ?Sized + 'static, | ||
{ | ||
// Exporting multiple node types is not supported. | ||
if T::inherits::<classes::Node>() { |
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.
I'm not a fan of these multiple T::inherits:: checks when we are exporting Array<DynGd<...>> but it is rarely called (mostly in the editor), so we should be fine?
I can write independent implementation for all the cases (exporting DynGd
, exporting Array<DynGd>
, and finally Array<Gd>
)
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.
To understand, what is your concern here? That T::inherits::<classes::Node>()
is a runtime check as opposed to trait static dispatch?
Because I would expect the compiler to be reasonably good at optimizing this, as all is known at compile time 🤔
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.
hmm, that's true – it shouldn't be performance bottleneck in any capacity 🤔
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.
Yeah, it's just much nicer to write procedural code than factor this out into multiple traits. The latter is often necessary because of bounds or because code wouldn't compile otherwise; not so here.
Same reason why C++ added if constexpr
(aka "static if").
(Maybe there's an easy way to "cache" node/resource deriving, but I'd keep this separate from this PR)
D: ?Sized + 'static, | ||
{ | ||
// Exporting multiple node types is not supported. | ||
if T::inherits::<classes::Node>() { |
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.
To understand, what is your concern here? That T::inherits::<classes::Node>()
is a runtime check as opposed to trait static dispatch?
Because I would expect the compiler to be reasonably good at optimizing this, as all is known at compile time 🤔
godot-core/src/registry/class.rs
Outdated
#[cfg(debug_assertions)] | ||
let relations_iter = relations_iter.filter_map(|implementor| { | ||
if classes::is_derived_base_cached(implementor.parent_class_name?, T::class_name()) { | ||
Some(implementor) | ||
} else { | ||
None | ||
} | ||
}); |
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.
Could you add a comment what this does?
@@ -244,7 +243,8 @@ fn register_classes_and_dyn_traits( | |||
let metadata = ClassMetadata {}; | |||
|
|||
// Transpose Class->Trait relations to Trait->Class relations. | |||
for (trait_type_id, dyn_trait_impl) in info.dynify_fns_by_trait.drain() { | |||
for (trait_type_id, mut dyn_trait_impl) in info.dynify_fns_by_trait.drain() { | |||
dyn_trait_impl.parent_class_name = info.parent_class_name; |
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.
Was this part of the bug, or why wasn't parent_class_name
set already correctly in info
?
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.
We don't have access to parent_class_name while registering given DynTraitImpl
(see: godot-macros/src/class/godot_dyn.rs
) thus it must be copied over.
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.
But why wasn't this present before this PR? Bug? 🙂
And could you add a short comment above that line, writing that it's only available now and not when registering DynTraitImpl
?
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.
But why wasn't this present before this PR
To make it clear – it is name of the base inherited by given rust class not the name of said class; It haven't been included earlier, because there was no need to do so 🤷.
I decided to extended the docs anyway:
- docstring for parent_class_name tells us why do we need Base ClassName at all
gdext/godot-core/src/registry/plugin.rs
Lines 514 to 522 in 58aca38
/// Base inherited class required for `DynGd<T, D>` exports (i.e. one specified in `#[class(base = ...)]`). /// /// Godot doesn't guarantee availability of all the GDExtension classes through the ClassDb while generating `PropertyHintInfo` for our exports. /// Therefore, we rely on the built-in inherited base class in such cases. /// Only [`class_name`][DynTraitImpl::class_name] is available at the time of adding given `DynTraitImpl` to plugin registry with `#[godot_dyn]`; /// It is important to fill this information before registration. /// /// See also [`get_dyn_property_hint_string`][crate::registry::class::get_dyn_property_hint_string]. pub(crate) parent_class_name: Option<ClassName>, - I also included the info why we are filling this info so late (mildly important, since
#[export]
s are impossible to test and filling it with rest of the class info works more often than not 😄)
gdext/godot-core/src/registry/class.rs
Line 247 in c526f69
// Note: Must be done after filling out the class info since plugins are being iterated in unspecified order.
godot-core/src/obj/dyn_gd.rs
Outdated
fn element_type_string() -> String { | ||
object_export_element_type_string::<T, String>(get_dyn_property_hint_string::<T, D>()) | ||
} |
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.
Maybe declare intermediate variable for the inner expr
godot-core/src/registry/property.rs
Outdated
/// Creates `hint_string` to be used for given `GodotClass` when used as an `ArrayElement`. | ||
pub(crate) fn object_export_element_type_string<T, S>(class_hint: S) -> String | ||
where | ||
T: GodotClass, | ||
S: Display, | ||
{ |
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.
If you use impl Display
instead of a named generic parameter S
, the call site won't have to specify the type of the 2nd generic argument.
@@ -515,7 +515,7 @@ struct RefcDynGdExporter { | |||
#[var] | |||
first: Option<DynGd<Object, dyn Health>>, | |||
#[export] | |||
second: Option<DynGd<foreign::NodeHealth, dyn InstanceIdProvider<Id = InstanceId>>>, | |||
second: Option<DynGd<Node, dyn InstanceIdProvider<Id = InstanceId>>>, |
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.
With concrete types being allowed again, this could again be foreign::NodeHealth
.
Maybe add a comment before #[export]
:
// Using DynGd with concrete type foreign::NodeHealth doesn't give benefits over Gd, but is allowed.
ab0a097
to
e67d86f
Compare
- Add base inherited class to DynTraitImpl - it is necessary due to lack of guaranteed order of registering classes (meaning that we are unable to check if given GDExtension class inherits the other – since it might not be loaded yet) . - Implement `element_type_string` for `DynGd<T, D>` ArrayElement. - Generate proper hint string while exporting Resource-based `DynGd<T, D>`. Don't include classes that don't inherit the base class T (for example Objects/Nodes for `DynGd<Resource, D>`). - Use base class while exporting Node-based `DynGd<T, D>`. In other words – `#[export] DynGd<T, D>` and `#[export] Gd<T>` works identically editor-wise. - 2.0 compatibility – allow to use `DynGd<MyRustClass, D>` and `Array<DynGd<MyRustClass, D>>` even if that doesn't make sense semantically (just use Gd<MyRustClass> instead). - Extract `object_export_element_type_string` from `Gd<T>`'s ArrayElement` to share implementation between both.
e67d86f
to
dcbaca9
Compare
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.
Thanks a lot!
element_type_string
forDynGd<T, D>
.object_export_element_type_string
fromGd<T>
's ArrayElement` to share implementation between both.#[export]
forDynGd<T, D>
works as it would beGd<T>
for Node-based classes – Godot doesn't allow to export multiple node types via editor.#[export]
forDynGd<T, D>
works the same for resource-based classes.Manual testing:
Given test library:
Test library
One is able to get desired results:
manual testing
Exporting non-dynGd node yields proper error on runtime:
For some reasonClassDb might have not all the GDExtension classes loaded while generatinghint_string
for our exports – thus the fallback to registering parent_class while registering the class itself (built-in classes will always be accessible via ClassDb):ol'reliable godot_print! debugging
EDIT: It seems that given
PropertyHintInfo
is being generated before all the GDExtension classes are properly registered in Godot, I'm not sure if there is any sane way – outside of applied workaround – to deal with this issue 🤷