Skip to content
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

impl GodotFfi for Option<T> when T is pointer sized and nullable #240 #247

Merged
merged 1 commit into from
May 24, 2023

Conversation

TitanNano
Copy link
Contributor

@TitanNano TitanNano commented Apr 27, 2023

ToVariant and FromVariant are implemented for Option<Gd<T>> as well as they are a requirement of #[godot_api].

Closes #240.

godot-ffi/src/godot_ffi.rs Outdated Show resolved Hide resolved
@lilizoey
Copy link
Member

if you wanna talk a bit about design for this, feel free to check out the discord thread for this issue. i've been experimenting with a couple of things there.

if this just works then that'd be great, but i didn't really think this would work as an implementation.

Copy link
Member

@lilizoey lilizoey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i dont think doing

let nil_ptr = ptr::null_mut();
interface_fn!(variant_new_nil)(nil_ptr);

is correct, since you're telling godot to initialize a new object into a null-pointer. im a bit surprised it works.

also is there a reason you can't just pass around null-pointers directly in this code? does that break something?

also could you change the title to reflect what the PR actually does.

godot-ffi/src/godot_ffi.rs Outdated Show resolved Hide resolved
godot-ffi/src/godot_ffi.rs Outdated Show resolved Hide resolved
godot-ffi/src/godot_ffi.rs Outdated Show resolved Hide resolved
godot-ffi/src/godot_ffi.rs Outdated Show resolved Hide resolved
@TitanNano
Copy link
Contributor Author

if you wanna talk a bit about design for this, feel free to check out the discord thread for this issue. i've been experimenting with a couple of things there.

if this just works then that'd be great, but i didn't really think this would work as an implementation.

Unfortunately, I don't use Discord, so this thread is completely in accessible to me.

The initial changes did indeed not really work for Option::None but I have revised it, and so far, I am able to pass null and Node3D as arguments from GDScript to Rust. Returning Option::None and Some() also work but as I explained in another comment, it doesn't really make sense for primitive types.

@lilizoey
Copy link
Member

lilizoey commented Apr 29, 2023

if you wanna talk a bit about design for this, feel free to check out the discord thread for this issue. i've been experimenting with a couple of things there.
if this just works then that'd be great, but i didn't really think this would work as an implementation.

Unfortunately, I don't use Discord, so this thread is completely in accessible to me.

The initial changes did indeed not really work for Option::None but I have revised it, and so far, I am able to pass null and Node3D as arguments from GDScript to Rust. Returning Option::None and Some() also work but as I explained in another comment, it doesn't really make sense for primitive types.

right, that is what i initially assumed. I'll have a closer look at everything later but to summarize a bit from the discord thread:

the basic issue is rust orphan rules. as you might have noticed, it's not possible to blanket implement

unsafe impl<T: GodotClass> GodotFfi for Option<Gd<T>> {}

so how do we work around this?

your idea does work, but it means we now have a bunch of not really useful types. but if it doesn't break anything then it's not the worst honestly. though I'd need more time to be sure that there's no UB.

if we want to only implement it for Option<Gd<T>> then i essentially had three ideas.

first, use some kind of newtype representing a nullable Gd. im not sure how possible this is, as i never tested it, and it might require users to use that nullable Gd type. which isn't ideal.

second, create a new trait something like NullableGodotFfi which is just like GodotFfi but takes/returns Option<Self>. then blanket impl

impl<T: NullableGodotFfi> GodotFfi for Option<T>

now we implement both GodotFfi and NullableGodotFfi for Gd<T>.
this works perfectly but requires duplicating basically the entire trait which isnt ideal. i was trying to think of ways to avoid the duplication somehow but couldn't quite think of anything.

thirdly, we could move GodotFuncMarshal into godot-core. this would let us blanket implement it for Option<Gd<T>>. this will likely work, but im not entirely sure if it's the right thing to do. especially as we might like to split up core eventually for performance reasons.

@lilizoey lilizoey added feature Adds functionality to the library c: engine Godot classes (nodes, resources, ...) labels Apr 29, 2023
@TitanNano
Copy link
Contributor Author

second, create a new trait something like NullableGodotFfi which is just like GodotFfi but takes/returns Option. then blanket impl

impl<T: NullableGodotFfi> GodotFfi for Option<T>

[...]

this works perfectly but requires duplicating basically the entire trait which isnt ideal. i was trying to think of ways to avoid the duplication somehow but couldn't quite think of anything.

Why not just a simple marker trait?

trait NullableGodotFfi: GodotFfi {}

and we constraint the impl of GodotFfi for Option<T> to that, i.e.

impl<T: NullableGodotFfi> GodotFfi for Option<T> { ... }

@lilizoey
Copy link
Member

lilizoey commented Apr 30, 2023

second, create a new trait something like NullableGodotFfi which is just like GodotFfi but takes/returns Option. then blanket impl

impl<T: NullableGodotFfi> GodotFfi for Option<T>

[...]
this works perfectly but requires duplicating basically the entire trait which isnt ideal. i was trying to think of ways to avoid the duplication somehow but couldn't quite think of anything.

Why not just a simple marker trait?

trait NullableGodotFfi: GodotFfi {}

and we constraint the impl of GodotFfi for Option<T> to that, i.e.

impl<T: NullableGodotFfi> GodotFfi for Option<T> { ... }

that probably works yeah! if you make it unsafe and add the safety requirement that anything that implements the trait is nullable and pointer-sized then we should be good to use your implementation (maybe with a couple of tweaks still). since we can then rely on the guarantee that anything that implements that trait is pointer sized.

maybe a different name would be more appropriate too. since it declares that the type is both nullable and pointer-sized in godot. though from what you're saying it sounds like every type that is nullable is pointer-sized. but i feel like it'd still be good for the name to reflect that.

if it turns out there are more nullable types, or godot adds other nullable types, then we can tweak the trait slightly.

@lilizoey
Copy link
Member

lilizoey commented Apr 30, 2023

a couple of things. could you change the name of this PR to reflect what the PR actually does, rather than the issue it fixes? (such as "implements GodotFfi for Option<Gd<T>>"). and could you add "closes #240" to the description of the PR?

@TitanNano TitanNano changed the title Option<Gd<T>> does not implement ToVariant #240 impl GodotFfi for Option<T> when T is pointer sized and nullable #240 May 1, 2023
@TitanNano TitanNano changed the title impl GodotFfi for Option<T> when T is pointer sized and nullable #240 impl GodotFfi for Option<T> when T is pointer sized and nullable #240 May 1, 2023
@TitanNano
Copy link
Contributor Author

TitanNano commented May 1, 2023

though from what you're saying it sounds like every type that is nullable is pointer-sized. but i feel like it'd still be good for the name to reflect that.

if it turns out there are more nullable types, or godot adds other nullable types, then we can tweak the trait slightly.

For completeness’s sake and to document my findings:

the current Godot 4.0 source indicates that only Object and types that inherit from it are nullable based on these methods:

https://github.com/godotengine/godot/blob/9f12e7b52d944281a39b7d3a33de6700c76cc23a/modules/gdscript/gdscript_analyzer.cpp#L4966

https://github.com/godotengine/godot/blob/9f12e7b52d944281a39b7d3a33de6700c76cc23a/core/variant/variant.cpp#L511

@TitanNano TitanNano requested a review from lilizoey May 3, 2023 19:51
Copy link
Member

@lilizoey lilizoey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good! Only a couple of minor things.

I'm not sure about the name GodotNullablePtrSized but i also am not entirely sure what would be a better name than that.

I'm very interested in seeing if the memory checked builds in the CI will be happy with this.

bors try

godot-ffi/src/godot_ffi.rs Outdated Show resolved Hide resolved
godot-ffi/src/godot_ffi.rs Outdated Show resolved Hide resolved
fn mirror_option_node(&self, value: Option<Gd<Node>>) -> Option<Gd<Node>> {
value
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it looks like it should be possible to add these tests to the build.rs so they get auto-generated, can you do that if it is? dont worry about it if it would require rewriting the macro or something tho.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test generation does currently not play very well with Option. We need different tests for both Some(...) and None but the tests derive their name from the rust type. That makes it impossible to have tests for the same type with different values.
The accept methods are also implemented slightly different from what the code generation does because they operate on and check Options. I thought it's easier to just implement the tests manually, than to expand the complexity of the macros.

bors bot added a commit that referenced this pull request May 3, 2023
godot-core/src/obj/gd.rs Outdated Show resolved Hide resolved
@bors
Copy link
Contributor

bors bot commented May 3, 2023

try

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.
For more help, visit the forum.

If you want to switch to GitHub's built-in merge queue, visit their help page.

Copy link
Member

@Bromeon Bromeon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for this addition! 🙂

@lilizoey already mentioned a lot, I agree with their comments.

Regarding GodotNullablePtrSized:

  • It's not obvious at first sight why GodotFfi wasn't implemented directly on Option<Gd<T>>. Could you document that it exists primarily because of the orphan rule, that is, because GodotFfi and Gd live in separate crates? The existing general doc about the nullable pointer counterpart in Godot is great too, but you could add that there's currently just one concrete implementor: Option<Gd<T>>.
  • Maybe name it simply GodotNullablePtr? The Sized makes people think of Rust's Sized marker.

godot-core/src/obj/gd.rs Outdated Show resolved Hide resolved
godot-ffi/src/godot_ffi.rs Show resolved Hide resolved
Copy link
Member

@Bromeon Bromeon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update! Could you also fix the merge conflicts? 🙂

godot-core/src/obj/gd.rs Outdated Show resolved Hide resolved
godot-ffi/src/godot_ffi.rs Outdated Show resolved Hide resolved
godot-ffi/src/godot_ffi.rs Outdated Show resolved Hide resolved
@GodotRust
Copy link

API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-247

@TitanNano TitanNano requested a review from Bromeon May 11, 2023 17:14
@TitanNano
Copy link
Contributor Author

@lilizoey @Bromeon does one of you have time to check this out again?

Copy link
Member

@lilizoey lilizoey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems basically good to me at least!

godot-core/src/obj/gd.rs Outdated Show resolved Hide resolved
@Bromeon
Copy link
Member

Bromeon commented May 18, 2023

At the moment we can't merge because there are upstream breaking changes in gdextension_interface.h.
I'm currently updating those.

@Bromeon
Copy link
Member

Bromeon commented May 24, 2023

bors try

@Bromeon
Copy link
Member

Bromeon commented May 24, 2023

bors p=8

bors bot added a commit that referenced this pull request May 24, 2023
@bors
Copy link
Contributor

bors bot commented May 24, 2023

try

Build failed:

…t-rust#240

Additionally FromVariant and ToVariant are also implemented for Option<Gd<T>>
to satisfy all the requirements for ffi and godot_api.
@Bromeon
Copy link
Member

Bromeon commented May 24, 2023

Thanks for the quick update and the patience! 🚀

bors r+

@bors
Copy link
Contributor

bors bot commented May 24, 2023

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.
For more help, visit the forum.

If you want to switch to GitHub's built-in merge queue, visit their help page.

@bors bors bot merged commit 9e6a8a0 into godot-rust:master May 24, 2023
@TitanNano TitanNano deleted the jovan/240 branch May 24, 2023 21:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c: engine Godot classes (nodes, resources, ...) feature Adds functionality to the library
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Option<Gd<T>> does not implement ToVariant
4 participants