-
-
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 various issues with pointer calls #204
Conversation
0428cb4
to
604481f
Compare
Thanks a lot! I'm not sure if
The trait itself is not unsafe to implement -- some of its methods are unsafe to call. I know it's a border case because implementing |
If the safety invariants i've listed for Not having the So we'd then have a weird situation where every single use of So in my opinion, we should allow people who call That is what
It's not my fault for instance if i call a function There's also just the fact that we have unsafe code relying on this invariant. Like our |
Good point, I think you're right here. It's confusing to me that both trait and its functions are unsafe, but now that I think about it, it makes sense:
It's just that I don't recall seeing such APIs in Rust; it's usually one or the other.
Extensibility is possibly via |
Ideally we should be able to split it in two, one trait that does the ffi-calls, and one that does the initialization/cleanup. I tried it but it became a really big and complicated refactor. |
Yeah, I'd focus on getting a working version first that passes CI. Cleanups can follow later 🙂 |
Trying with latest CI changes... bors try |
tryBuild succeeded: |
931a28a
to
a732ab3
Compare
a732ab3
to
d6623a1
Compare
Make `Gd` in virtual method-calls use `ref_get_object`/`ref_set_object` if it inherits from `RefCounted` Add some tests of objects in pointer calls Add tests for Array passing through pointer calls Add more tests for refcounted Add a test for _input Rename some stuff in TestContext Add unsafe to rect and such GodotFfi impl along with `SAFETY` comments
c3273b6
to
535abd9
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.
Fantastic work! Thanks a lot for combining the multiple PRs and coming up with a model that streamlines GodotFfi
, considers the Ref
-call for virtuals and fixes various bugs along the way 🥇 also good job at finding example classes that exhibit behavior useful in virtual method tests!
I added some comments, many of it just minor documentation questions/suggestions 🙂
One thing, you have a lot of
// SAFETY:
// This type is transparently represented as `Self` in Godot, so `*mut Self` is sound.
What does the "transparently" mean here? Can it be omitted?
godot-core/src/builtin/array.rs
Outdated
// SAFETY: | ||
// Nothing special needs to be done beyond a `std::mem::swap` when returning an Array. |
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.
Is this meant to be before move_return_ptr
? There is no unsafe
in that context.
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 are safety invariants that need to be upheld when implementing move_return_ptr
in GodotFfi
. Im just documenting here that the default impl provided by ffi_methods
is a sound impl for move_return_ptr
.
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 // SAFETY:
block should then be above unsafe impl
and not fn
.
This is a technicality, but may become relevant if we activate clippy::undocumented_unsafe_blocks
in the future.
godot-core/src/builtin/array.rs
Outdated
// SAFETY: | ||
// Arrays are properly initialized through a `from_sys` call, but the ref-count should be | ||
// incremented as that is the callee's responsibility. | ||
// | ||
// Using `std::mem::forget(array.share())` increments the ref count. |
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 should probably be moved inside the function, as it documents what's happening there and not how the function should be called (in the latter case, the convention is to use a # Safety
block).
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 is me documenting that the implementation of from_arg_ptr
upholds the safety invariants that GodotFfi
requires from an implementation of from_arg_ptr
. I could move it inside if you want.
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.
In that case, it should probably be part of the unsafe impl
block docs, see comment above.
It's a bit non-local, but you could make two paragraphs in the impl
docs, one for each function. The code is still relatively close, so that one sees the impl
docs when looking at the function.
godot-core/src/obj/traits.rs
Outdated
/// Returns `true` if argument and return pointers are passed as `Ref<T>` pointers given this | ||
/// [`CallType`]. | ||
fn pass_as_ref(_call_type: CallType) -> bool { | ||
false | ||
} |
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.
Re comment: it's not clear what Ref<T>
refers to. I assume you mean the Godot C++ code? How does this impact gdext, or what are the semantics?
These things would be nice to quickly mention 🙂
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.
true, i think i document it better in CallType
so i can probably just explicitly refer the reader to that.
godot-core/src/builtin/string.rs
Outdated
fn write_string_sys = write_sys; | ||
} | ||
|
||
/// Move `self` into a system pointer. |
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 additionally mention that ownership is transferred to Godot and the destructor of self
is not called.
godot-ffi/src/godot_ffi.rs
Outdated
/// `ptr` must be a valid _type ptr_: it must follow Godot's convention to encode `Self`, | ||
/// which is different depending on the type. | ||
unsafe fn write_sys(&self, dst: sys::GDExtensionTypePtr); | ||
/// `ptr` must encode `Self` according to the given `call_type`'s encoding of argument values. |
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.
You could use bullet points here:
/// * `ptr` must be ...
/// which is ...
/// * `ptr` must ...
(also some other places)
godot-ffi/src/godot_ffi.rs
Outdated
unsafe fn try_from_arg( | ||
ptr: sys::GDExtensionTypePtr, | ||
call_type: CallType, | ||
) -> Result<Self, $Via> { | ||
let via = <$Via as GodotFfi>::from_arg_ptr(ptr, call_type); | ||
// Godot does not always give us a valid `i64`/`f64`, nor `Self` when it does a pointer | ||
// call, so we cannot use `try_from` here. | ||
Ok(via as Self) | ||
} |
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 point of $Via
was that you would use an intermediate type across the FFI boundary (usually i64
), and that other types such as u32
would be converted to that.
Now you unconditionally convert via
into Self
, even though this value may not be representable -- I don't think you should abandon the try_from
here.
The ; lossy
would allow as
-casting, because for floats, we take precision loss into account.
Maybe I'm not understanding the "Godot does not always give us a valid ..." comment.
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.
ill look into it again i just remember that we'd get weird inconsistent values when going through the Via
type because we'd be interpreting garbage bits as actual bits in the integer or something.
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 then that would be another bug. In any case, "garbage values" wouldn't just be fixed by truncating integers. At best, you hide some problems.
In general, we must make sure we use the same type on Rust side as we do on C (Godot API) side. Not every integer type can be passed to Godot, they typically use i64
.
It's probably best if we limit #[func]
interfaces to i64
as well. That doesn't mean the user cannot declare an i16
parameter, but behind the scenes, we'd pass an i64
from GDScript. Otherwise it's impossible for Rust to recognize truncations.
itest/godot/ManualFfiTests.gd
Outdated
@@ -72,22 +72,116 @@ func test_export(): | |||
obj.free() | |||
node.free() | |||
|
|||
func test_untyped_array_pass_to_user_func(): | |||
func test_untyped_array_pass_to_user_func_varcall(): |
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.
Question, are all these tests still needed with the new ones in GenFfi
?
The latter now tests both ptrcall and varcall, as well as many new types.
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.
ill check again
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 array/dictionary ones are definitely not necessary, however the object and refcounted one doesn't seem to have anything similar in GenFfi
so those are still gonna be useful.
Also the InheritTest
still uses ArrayTest
. It's likely not strictly necessary as the issue there was caused by pointer calls. but it's still useful to have as we dont test inheriting much elsewhere.
itest/rust/src/runner.rs
Outdated
let ctx = TestContext { | ||
_scene_tree: scene_tree, | ||
root_node, | ||
test_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 don't think the new fields in TestContext
are necessary:
_scene_tree
is unused- the user can always call
Node::get_tree()
if absolutely necessary
- the user can always call
root_node
is only used forpush_input()
- Must this happen on the root node? Can it not be the node itself or its parent? I don't want to generally encourage people to touch the scene tree too much, as we have zero test isolation and any side effects are persisted. Of course we cannot prevent it, but we should also not serve things on a silver platter 😉
- If it's needed, we can also consider
get_tree()
+get_root()
, even if it's a bit ugly with intermediate unwraps.
Also, please don't rename scene_tree
-> test_node
while there are so many PRs open, this creates extra conflicts for little benefit. I can take care of this later if necessary. The main idea was that you would get access to the scene tree through that field, even if it's technically not a SceneTree
but a Node
. Maybe test_node
is clearer though.
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 don't think the new fields in
TestContext
are necessary:* `_scene_tree` is unused
fair, i kept it cause it was the old name of the field, which was confusing cause it wasn't actually the scene tree.
* `root_node` is only used for `push_input()` * Must this happen on the _root_ node? Can it not be the node itself or its parent? I don't want to generally encourage people to touch the scene tree too much, as we have zero test isolation and any side effects are persisted. Of course we cannot _prevent_ it, but we should also not serve things on a silver platter 😉 * If it's needed, we can also consider `get_tree()` + `get_root()`, even if it's a bit ugly with intermediate unwraps.
push_input
must be done on a viewport. i guess it might be possible to make a viewport and add the node as a child and call push_input? ill check if that's possible.
Also, please don't rename
scene_tree
->test_node
while there are so many PRs open, this creates extra conflicts for little benefit. I can take care of this later if necessary. The main idea was that you would get access to the scene tree through that field, even if it's technically not aSceneTree
but aNode
. Maybetest_node
is clearer though.
fair, it was very confusing to me because i thought it was the scene tree at first but it actually isnt...
use godot::prelude::{ | ||
is_equal_approx, varray, Color, PackedByteArray, PackedColorArray, PackedFloat32Array, | ||
PackedInt32Array, PackedStringArray, PackedVector2Array, PackedVector3Array, ToVariant, | ||
Variant, VariantArray, Vector2, Vector3, | ||
}; |
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.
You could use godot::builtin
here, like already for GodotString
above.
In test code, I would generally avoid the prelude (we can use that in examples though). The idea is that tests are mainly relevant for developers, so knowing where exactly symbols are located can help the understanding. Examples on the other hand are read by end users.
[GodotString::from("extension")].into_iter().collect() | ||
} | ||
|
||
fn handles_type(&self, type_: godot::prelude::StringName) -> bool { |
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.
Any particular reason why StringName
is not imported alongside the other builtins?
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.
not really, i just had rust analyzer add all default impls and it apparently added it as a fully qualified name and i didnt notice.
ill change it.
bors try |
tryBuild failed: |
The error[E0308]: arguments to this function are incorrect
--> itest/rust/src/virtual_methods_test.rs:334:9
|
331 | / assert_eq_approx!(
332 | | arr.get(2).to::<PackedFloat32Array>().get(3),
333 | | arr_rust.get(2).to::<PackedFloat32Array>().get(3),
334 | | is_equal_approx
| | ^^^^^^^^^^^^^^^
335 | | );
| | -
| |_____|
| |_____expected `f64`, found `f32`
| expected `f64`, found `f32`
|
note: function defined here
--> /Users/runner/work/gdext/gdext/godot-core/src/builtin/math.rs:18:8
|
18 | pub fn is_equal_approx(a: real, b: real) -> bool {
| ^^^^^^^^^^^^^^^
For more information about this error, try `rustc --explain E0308`. Looks like |
Well every type that implements |
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.
Some of the // SAFETY
comments should be moved directly above the unsafe
blocks; see comments for details.
I think the "transparently" doesn't add much or can even be confusing; I'd remove it from the wording.
godot-core/src/builtin/array.rs
Outdated
// SAFETY: | ||
// Nothing special needs to be done beyond a `std::mem::swap` when returning an Array. |
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 // SAFETY:
block should then be above unsafe impl
and not fn
.
This is a technicality, but may become relevant if we activate clippy::undocumented_unsafe_blocks
in the future.
godot-core/src/builtin/array.rs
Outdated
// SAFETY: | ||
// Arrays are properly initialized through a `from_sys` call, but the ref-count should be | ||
// incremented as that is the callee's responsibility. | ||
// | ||
// Using `std::mem::forget(array.share())` increments the ref count. |
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.
In that case, it should probably be part of the unsafe impl
block docs, see comment above.
It's a bit non-local, but you could make two paragraphs in the impl
docs, one for each function. The code is still relatively close, so that one sees the impl
docs when looking at the function.
godot-ffi/src/godot_ffi.rs
Outdated
unsafe fn try_from_arg( | ||
ptr: sys::GDExtensionTypePtr, | ||
call_type: CallType, | ||
) -> Result<Self, $Via> { | ||
let via = <$Via as GodotFfi>::from_arg_ptr(ptr, call_type); | ||
// Godot does not always give us a valid `i64`/`f64`, nor `Self` when it does a pointer | ||
// call, so we cannot use `try_from` here. | ||
Ok(via as Self) | ||
} |
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 then that would be another bug. In any case, "garbage values" wouldn't just be fixed by truncating integers. At best, you hide some problems.
In general, we must make sure we use the same type on Rust side as we do on C (Godot API) side. Not every integer type can be passed to Godot, they typically use i64
.
It's probably best if we limit #[func]
interfaces to i64
as well. That doesn't mean the user cannot declare an i16
parameter, but behind the scenes, we'd pass an i64
from GDScript. Otherwise it's impossible for Rust to recognize truncations.
Add another virtual method return test Make test that causes memory leak use `#[itest(skip)]` Move the logic for determining whether to use `Ref` or not entirely into `Mem` Remove some unnecessary manual ffi tests Rename CallType
496f022
to
8206697
Compare
Thanks a lot! 🚀 bors r+ |
Build succeeded: |
Combines #165 and #200 with more complete fixes.
Overview
Properly clones strings and shares arrays/dictionaries in pointer calls to ensure they're not prematurely freed.
Frees the value currently in the
ret
pointer in pointer calls, to avoid memory leak.Moves
ret_val
intoret
, to avoid premature destruction.Changes integer conversions in pointer calls to use
as
, since that seems like the right way of converting the values we get from godot.try_from
lead to wrong conversions sometimes.Objects inheriting from
RefCounted
now useref_get_object
andref_set_object
in virtual method calls.Details
Add
from_arg_ptr
andmove_return_ptr
toGodotFfi
that will be used when converting argument pointers in pointer calls, and moving things into a pointer.Add
CallType
sofrom_arg_ptr
andmove_return_ptr
can have different behavior depending on whether it's a virtual method call or not.Remove
write_sys
inGodotFfi
.Override
from_arg_ptr
inGodotString
,NodePath
,StringName
,Array
,Dictionary
,Gd
.Override
move_return_ptr
inGd
.Rename
try_write_sys
totry_return
, and have it usemove_return_ptr
instead ofwrite_sys
.Rename
try_from_sys
totry_from_arg
, and have it usefrom_arg_ptr
instead offrom_sys
.Add
u8
,u16
,u32
to the ptrcall tests.Add a couple of test of virtual methods.
Fix a test for virtual method with return values to actually call the virtual method as a virtual method.
closes #195
closes #189