-
-
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
Properly export arrays, nodes, and resources by default #241
Conversation
so a major issue with this approach in particular is that you can't export arbitrary types without specifying a One thing this method enables is that we could represent some export variants using types, we could for instance do: #[export]
multiline_string: MultilineString Where |
28faf54
to
b8995d0
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!
I didn't have time to review everything yet, but here's already some feedback 🙂
godot-core/src/export.rs
Outdated
// Implement ExportDefaultInfo for some various common types so people can use them without specifying a | ||
// hint_type and hint_string. | ||
impl ExportDefaultInfo for () {} | ||
impl ExportDefaultInfo for String {} | ||
impl ExportDefaultInfo for i128 {} | ||
impl ExportDefaultInfo for isize {} | ||
impl ExportDefaultInfo for u64 {} | ||
impl ExportDefaultInfo for u128 {} | ||
impl ExportDefaultInfo for usize {} |
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.
All these types should not be exportable in my opinion.
i128
,u128
,u64
cannot be represented in Godot, so it's just unnecessary potential for errors.isize
andusize
have a platform-dependent size, so it makes shipping robust applications harder. Godot also has no equivalent for them.()
can only ever have one value (aka zero information), so what's the idea here?String
is too expensive because it needs copying + reallocation on every access (see #gdnative/1020).
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.
this just means that you can do
#[export(get = get_prop, set = set_prop)]
prop: String
you still can't export them without specifying a setter/getter. since they dont implement Export
. you just dont need to provide a hint/hint_string
i considered not doing ()
but it can be useful if you want a property exposed but dont actually need to store anything/you're storing something elsewhere than in the class.
impl_export_by_clone!(u8 => Int); | ||
|
||
// Callables can be exported, however you can't do anything with them in the editor. | ||
// But we do need to be able to export them since we can't make something a property without exporting. |
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.
since we can't make something a property without exporting.
Is that still true, after we discovered we can hide them from the editor via flag?
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.
it isnt strictly true no, but until we change this it's true. since we currently do need anything that's exposed as a property to implemented Export.
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 todo then, otherwise it is forgotten for sure.
(Not that we actively track TODOs, but we track comments even less 😉)
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.
This one would still be nice 🙂
godot-core/src/obj/gd.rs
Outdated
/// Returns whether `T` inherits from `U`. | ||
/// | ||
/// This is reflexive, i.e `T` inherits from itself. | ||
/// | ||
/// See also [`Inherits`] for a trait bound. | ||
pub fn inherits<U: GodotClass>() -> bool { | ||
if T::CLASS_NAME == U::CLASS_NAME { | ||
true | ||
} else if T::Base::CLASS_NAME == <()>::CLASS_NAME { | ||
false | ||
} else { | ||
Gd::<T::Base>::inherits::<U>() | ||
} | ||
} |
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.
Quite a clever recursive implementation! 💡
If we want to provide this, it should probably being inherent on T
and not Gd<T>
. The usage below would also support this. There are 3 options:
- global function
inherits::<T, Base>()
GodotClass
default implT::inherits::<Base>()
- extension trait
T::inherits::<Base>()
-- although I don't like defining a new trait just for this
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.
makes sense, I'd probably go for the second option. it makes sense to me that a trait for classes would have such a function.
godot-core/src/export.rs
Outdated
* file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
*/ | ||
|
||
use crate::{builtin::GodotString, engine::global::PropertyHint}; |
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.
Please separate use
statements 🙂
3c13cff
to
a36a002
Compare
godot-core/src/export.rs
Outdated
/// Create a new `ExportInfo` with a property hint of | ||
/// [`PROPERTY_HINT_NONE`](PropertyHint::PROPERTY_HINT_NONE). | ||
pub fn new_none(variant_type: VariantType) -> Self { | ||
Self { | ||
variant_type, | ||
hint: PropertyHint::PROPERTY_HINT_NONE, | ||
hint_string: GodotString::new(), | ||
} | ||
} |
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 name it with_hint_none()
or so?
Otherwise it's not really clear that "none" refers to the property hint.
godot-core/src/export.rs
Outdated
|
||
impl<T: Export> Export for Option<T> { | ||
fn export(&self) -> Self { | ||
self.as_ref().map(|val| val.export()) |
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.
Would this work?
self.as_ref().map(|val| val.export()) | |
self.as_ref().map(Export::export) |
godot-core/src/export.rs
Outdated
/// Trait for types that can be represented as a type string for use with | ||
/// [`PropertyHint::PROPERTY_HINT_TYPE_STRING`]. | ||
pub trait TypeString { | ||
/// Returns the representation of this type as a type string. | ||
/// | ||
/// See [`PropertyHint.PROPERTY_HINT_TYPE_STRING`]( | ||
/// https://docs.godotengine.org/en/stable/classes/class_%40globalscope.html#enum-globalscope-propertyhint | ||
/// ). | ||
fn type_string() -> String; | ||
} |
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 type sounds like it's some sort of string; maybe name it TypeStringHint
?
(*Hint
could apply to other similar traits/types)
godot-core/src/export.rs
Outdated
impl_export_by_clone!(f64 => Float); | ||
impl_export_by_clone!(f32 => Float); | ||
impl_export_by_clone!(i64 => Int); | ||
impl_export_by_clone!(i32 => Int); | ||
impl_export_by_clone!(i16 => Int); | ||
impl_export_by_clone!(i8 => Int); | ||
impl_export_by_clone!(u32 => Int); | ||
impl_export_by_clone!(u16 => Int); | ||
impl_export_by_clone!(u8 => Int); |
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 know they were already there, but how do these basic types (besides f64
and i64
) behave in terms of exporting?
What happens if GDScript assigns a value that doesn't fit into the target type (especially for the integers)?
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.
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.
Ok, that's at least reasonable behavior. Could you maybe add a short comment above the impl_export_by_clone!
statements explaining this?
impl_export_by_clone!(u8 => Int); | ||
|
||
// Callables can be exported, however you can't do anything with them in the editor. | ||
// But we do need to be able to export them since we can't make something a property without exporting. |
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 todo then, otherwise it is forgotten for sure.
(Not that we actively track TODOs, but we track comments even less 😉)
06d0da0
to
1a1b5a5
Compare
API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-241 |
godot-core/src/export.rs
Outdated
impl_export_by_clone!(f64 => Float); | ||
impl_export_by_clone!(f32 => Float); | ||
impl_export_by_clone!(i64 => Int); | ||
impl_export_by_clone!(i32 => Int); | ||
impl_export_by_clone!(i16 => Int); | ||
impl_export_by_clone!(i8 => Int); | ||
impl_export_by_clone!(u32 => Int); | ||
impl_export_by_clone!(u16 => Int); | ||
impl_export_by_clone!(u8 => Int); |
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.
Ok, that's at least reasonable behavior. Could you maybe add a short comment above the impl_export_by_clone!
statements explaining this?
impl_export_by_clone!(u8 => Int); | ||
|
||
// Callables can be exported, however you can't do anything with them in the editor. | ||
// But we do need to be able to export them since we can't make something a property without exporting. |
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.
This one would still be nice 🙂
@@ -0,0 +1,163 @@ | |||
/* |
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.
This would probably fit better into the existing godot::bind
module, rather than its own top-level one.
(I know the name "bind" is not great; it's coming from binding. "export" is however specifically the variable-export, and not functions/classes/etc. We can discuss this later maybe 🙂)
// But we do need to be able to export them since we can't make something a property without exporting. | ||
// And it should be possible to access Callables by property from for instance GDScript. | ||
// TODO: | ||
// Remove export impl when we can create properties without exporting them. |
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.
@Bromeon i added a TODO
1a1b5a5
to
09f2e03
Compare
There are now some merge conflicts; other than that it looks good 👍 |
Add TypeStringHint trait Add support for exporting nodes, resources, and arrays with type info
09f2e03
to
34faefa
Compare
bors r+ |
Build succeeded! The publicly hosted instance of bors-ng is deprecated and will go away soon. If you want to self-host your own instance, instructions are here. If you want to switch to GitHub's built-in merge queue, visit their help page. |
282: godot-core: delegate TypeStringHint on Option<T> to T r=Bromeon a=gg-yb So when trying to export an array of resources property from my Rust struct I encountered the problem of crashes when the user adds null to the array (i.e. adding an element, not initializing it, then reopening the scene). I will open a separate issue for this. However, in investigating this, I saw that TypeStringHint is not implemented for Option<Gd<T>>, only for Gd<T>. I feel that it should be implemented for that type as well, but I'm not sure delegating to Gd<T> is the right call here, that's why this is a draft. Currently I just want to get this topic on the radar and spark discussion. However, should it turn out that delegating to Gd<T> here is the right thing to do, I believe the change can be applied as-is. From what I've seen from #241 there are no tests for TypeStringHint yet, so I haven't added any here, either. Co-authored-by: gg-yb <[email protected]>
282: godot-core: delegate TypeStringHint on Option<T> to T r=Bromeon a=gg-yb So when trying to export an array of resources property from my Rust struct I encountered the problem of crashes when the user adds null to the array (i.e. adding an element, not initializing it, then reopening the scene). I will open a separate issue for this. However, in investigating this, I saw that TypeStringHint is not implemented for Option<Gd<T>>, only for Gd<T>. I feel that it should be implemented for that type as well, but I'm not sure delegating to Gd<T> is the right call here, that's why this is a draft. Currently I just want to get this topic on the radar and spark discussion. However, should it turn out that delegating to Gd<T> here is the right thing to do, I believe the change can be applied as-is. From what I've seen from #241 there are no tests for TypeStringHint yet, so I haven't added any here, either. Co-authored-by: gg-yb <[email protected]>
Adds in support for each type to define what
hint
andhint_string
is used for that type when exported as a property. This is used here to make arrays get properly exported, as well as nodes and resources have their type restricted to the actual type they can hold. (see #230)So now if you export a
Gd<Mesh>
orOption<Gd<Mesh>>
, you wont get a list of every single resource in existence, but rather only resources that inherit fromMesh
. And if you export a specific node-type, likeNode3D
you can only assign nodes that inherit fromNode3D
to it and not other nodes.Also i couldn't find an easy way to conditionally implement some of this stuff for
Gd
based on whether they inherited fromResource
,Node
, or not. Orphan rules prevents me from doing so based onInherits<T>
. So i added aninherits
method to check at runtime. It shouldn't have a big impact on performance since afaik each property will only call this once to register the property.I'm not sure if this is the best solution, see my comment below for a major drawback. But overall it works fairly well.
closes #230