Skip to content

Commit 0a3819b

Browse files
authored
Try #247:
2 parents e27a69e + b9d7fec commit 0a3819b

File tree

6 files changed

+229
-5
lines changed

6 files changed

+229
-5
lines changed

godot-core/src/obj/gd.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ use std::ptr;
1212
use godot_ffi as sys;
1313
use godot_ffi::VariantType;
1414
use sys::types::OpaqueObject;
15-
use sys::{ffi_methods, interface_fn, static_assert_eq_size, GodotFfi, PtrcallType};
15+
use sys::{
16+
ffi_methods, interface_fn, static_assert_eq_size, GodotFfi, GodotNullablePtrSized, PtrcallType,
17+
};
1618

1719
use crate::builtin::meta::{ClassName, VariantMetadata};
1820
use crate::builtin::{
@@ -569,6 +571,8 @@ where
569571
}
570572
}
571573

574+
unsafe impl<T: GodotClass> GodotNullablePtrSized for Gd<T> {}
575+
572576
impl<T: GodotClass> Gd<T> {
573577
/// Runs `init_fn` on the address of a pointer (initialized to null). If that pointer is still null after the `init_fn` call,
574578
/// then `None` will be returned; otherwise `Gd::from_obj_sys(ptr)`.
@@ -706,6 +710,24 @@ impl<T: GodotClass> ToVariant for Gd<T> {
706710
}
707711
}
708712

713+
impl<T: GodotClass> ToVariant for Option<Gd<T>> {
714+
fn to_variant(&self) -> Variant {
715+
match self {
716+
Some(gd) => gd.to_variant(),
717+
None => Variant::nil(),
718+
}
719+
}
720+
}
721+
722+
impl<T: GodotClass> FromVariant for Option<Gd<T>> {
723+
fn try_from_variant(variant: &Variant) -> Result<Self, VariantConversionError> {
724+
match variant.get_type() {
725+
VariantType::Nil => Ok(None),
726+
_ => Gd::try_from_variant(variant).map(Some),
727+
}
728+
}
729+
}
730+
709731
impl<T: GodotClass> PartialEq for Gd<T> {
710732
/// ⚠️ Returns whether two `Gd` pointers point to the same object.
711733
///

godot-ffi/src/godot_ffi.rs

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
55
*/
66

7-
use crate as sys;
8-
use std::fmt::Debug;
7+
use crate::{self as sys, interface_fn, ptr_then};
8+
use std::{fmt::Debug, ptr};
99

1010
/// Adds methods to convert from and to Godot FFI pointers.
1111
/// See [crate::ffi_methods] for ergonomic implementation.
@@ -94,6 +94,59 @@ pub unsafe trait GodotFfi {
9494
unsafe fn move_return_ptr(self, dst: sys::GDExtensionTypePtr, call_type: PtrcallType);
9595
}
9696

97+
/// Marks a type as having a nullable counterpart in Godot.
98+
///
99+
/// # Safety
100+
///
101+
/// The type has to have a pointer sized counter part in Godot which needs to be nullable.
102+
/// So far only types that inherit from Object are nullable.
103+
pub unsafe trait GodotNullablePtrSized: GodotFfi {}
104+
105+
unsafe impl<T> GodotFfi for Option<T>
106+
where
107+
T: GodotNullablePtrSized,
108+
{
109+
fn sys(&self) -> sys::GDExtensionTypePtr {
110+
match self {
111+
Some(value) => value.sys(),
112+
None => {
113+
let nil_ptr = ptr::null_mut();
114+
let new_nil = interface_fn!(variant_new_nil);
115+
116+
unsafe {
117+
new_nil(nil_ptr);
118+
}
119+
120+
nil_ptr as sys::GDExtensionTypePtr
121+
}
122+
}
123+
}
124+
125+
unsafe fn from_sys(ptr: sys::GDExtensionTypePtr) -> Self {
126+
ptr_then(ptr, |ptr| T::from_sys(ptr))
127+
}
128+
129+
unsafe fn from_sys_init(init_fn: impl FnOnce(sys::GDExtensionTypePtr)) -> Self {
130+
let mut raw = std::mem::MaybeUninit::uninit();
131+
init_fn(raw.as_mut_ptr() as sys::GDExtensionTypePtr);
132+
133+
Self::from_sys(raw.assume_init())
134+
}
135+
136+
unsafe fn from_arg_ptr(ptr: sys::GDExtensionTypePtr, call_type: PtrcallType) -> Self {
137+
ptr_then(ptr, |ptr| T::from_arg_ptr(ptr, call_type))
138+
}
139+
140+
unsafe fn move_return_ptr(self, ptr: sys::GDExtensionTypePtr, call_type: PtrcallType) {
141+
match self {
142+
Some(value) => value.move_return_ptr(ptr, call_type),
143+
None => {
144+
interface_fn!(variant_new_nil)(ptr as sys::GDExtensionVariantPtr);
145+
}
146+
}
147+
}
148+
}
149+
97150
/// An indication of what type of pointer call is being made.
98151
#[derive(Default, Copy, Clone, Eq, PartialEq, Debug)]
99152
pub enum PtrcallType {

godot-ffi/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ mod plugins;
2828
#[doc(hidden)]
2929
pub use paste;
3030

31-
pub use crate::godot_ffi::{GodotFfi, GodotFuncMarshal, PtrcallType};
31+
pub use crate::godot_ffi::{GodotFfi, GodotFuncMarshal, GodotNullablePtrSized, PtrcallType};
3232
pub use gen::central::*;
3333
pub use gen::gdextension_interface::*;
3434

itest/godot/ManualFfiTests.gd

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,4 +144,88 @@ func test_refcounted_as_object_return_from_user_func_varcall():
144144
func test_refcounted_as_object_return_from_user_func_ptrcall():
145145
var obj_test: ObjectTest = ObjectTest.new()
146146
var obj: MockRefCountedRust = obj_test.return_refcounted_as_object()
147-
assert_eq(obj.i, 42)
147+
assert_eq(obj.i, 42)
148+
149+
func test_option_refcounted_none_varcall():
150+
var ffi := OptionFfiTest.new()
151+
152+
var from_rust: Variant = ffi.return_option_refcounted_none()
153+
assert_that(ffi.accept_option_refcounted_none(from_rust), "ffi.accept_option_refcounted_none(from_rust)")
154+
155+
var from_gdscript: Variant = null
156+
var mirrored: Variant = ffi.mirror_option_refcounted(from_gdscript)
157+
assert_eq(mirrored, from_gdscript, "mirrored == from_gdscript")
158+
159+
func test_option_refcounted_none_ptrcall():
160+
var ffi := OptionFfiTest.new()
161+
162+
var from_rust: Object = ffi.return_option_refcounted_none()
163+
assert_that(ffi.accept_option_refcounted_none(from_rust), "ffi.accept_option_refcounted_none(from_rust)")
164+
165+
var from_gdscript: Object = null
166+
var mirrored: Object = ffi.mirror_option_refcounted(from_gdscript)
167+
assert_eq(mirrored, from_gdscript, "mirrored == from_gdscript")
168+
169+
func test_option_refcounted_some_varcall():
170+
var ffi := OptionFfiTest.new()
171+
172+
var from_rust: Variant = ffi.return_option_refcounted_some()
173+
assert_that(ffi.accept_option_refcounted_some(from_rust), "ffi.accept_option_refcounted_some(from_rust)")
174+
175+
var from_gdscript: Variant = RefCounted.new()
176+
var mirrored: Variant = ffi.mirror_option_refcounted(from_gdscript)
177+
assert_eq(mirrored, from_gdscript, "mirrored == from_gdscript")
178+
179+
func test_option_refcounted_some_ptrcall():
180+
var ffi := OptionFfiTest.new()
181+
182+
var from_rust: Object = ffi.return_option_refcounted_some()
183+
assert_that(ffi.accept_option_refcounted_some(from_rust), "ffi.accept_option_refcounted_some(from_rust)")
184+
185+
var from_gdscript: Object = RefCounted.new()
186+
var mirrored: Object = ffi.mirror_option_refcounted(from_gdscript)
187+
assert_eq(mirrored, from_gdscript, "mirrored == from_gdscript")
188+
189+
func test_option_node_none_varcall():
190+
var ffi := OptionFfiTest.new()
191+
192+
var from_rust: Variant = ffi.return_option_node_none()
193+
assert_that(ffi.accept_option_node_none(from_rust), "ffi.accept_option_node_none(from_rust)")
194+
195+
var from_gdscript: Variant = null
196+
var mirrored: Variant = ffi.mirror_option_node(from_gdscript)
197+
assert_eq(mirrored, from_gdscript, "mirrored == from_gdscript")
198+
199+
func test_option_node_none_ptrcall():
200+
var ffi := OptionFfiTest.new()
201+
202+
var from_rust: Node = ffi.return_option_node_none()
203+
assert_that(ffi.accept_option_node_none(from_rust), "ffi.accept_option_node_none(from_rust)")
204+
205+
var from_gdscript: Node = null
206+
var mirrored: Node = ffi.mirror_option_node(from_gdscript)
207+
assert_eq(mirrored, from_gdscript, "mirrored == from_gdscript")
208+
209+
func test_option_node_some_varcall():
210+
var ffi := OptionFfiTest.new()
211+
212+
var from_rust: Variant = ffi.return_option_node_some()
213+
assert_that(ffi.accept_option_node_some(from_rust), "ffi.accept_option_node_some(from_rust)")
214+
215+
var from_gdscript: Variant = Node.new()
216+
var mirrored: Variant = ffi.mirror_option_node(from_gdscript)
217+
assert_eq(mirrored, from_gdscript, "mirrored == from_gdscript")
218+
from_gdscript.free()
219+
from_rust.free()
220+
221+
func test_option_node_some_ptrcall():
222+
var ffi := OptionFfiTest.new()
223+
224+
var from_rust: Node = ffi.return_option_node_some()
225+
assert_that(ffi.accept_option_node_some(from_rust), "ffi.accept_option_node_some(from_rust)")
226+
227+
var from_gdscript: Node = Node.new()
228+
var mirrored: Node = ffi.mirror_option_node(from_gdscript)
229+
assert_eq(mirrored, from_gdscript, "mirrored == from_gdscript")
230+
from_gdscript.free()
231+
from_rust.free()

itest/rust/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ mod gdscript_ffi_test;
2323
mod init_test;
2424
mod node_test;
2525
mod object_test;
26+
mod option_ffi_test;
2627
mod packed_array_test;
2728
mod projection_test;
2829
mod quaternion_test;

itest/rust/src/option_ffi_test.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
5+
*/
6+
7+
use godot::prelude::{godot_api, Gd, GodotClass, Node, RefCounted};
8+
9+
#[derive(GodotClass, Debug)]
10+
#[class(base = RefCounted, init)]
11+
struct OptionFfiTest;
12+
13+
#[godot_api]
14+
impl OptionFfiTest {
15+
#[func]
16+
fn return_option_refcounted_none(&self) -> Option<Gd<RefCounted>> {
17+
None
18+
}
19+
20+
#[func]
21+
fn accept_option_refcounted_none(&self, value: Option<Gd<RefCounted>>) -> bool {
22+
value.is_none()
23+
}
24+
25+
#[func]
26+
fn return_option_refcounted_some(&self) -> Option<Gd<RefCounted>> {
27+
Some(RefCounted::new())
28+
}
29+
30+
#[func]
31+
fn accept_option_refcounted_some(&self, value: Option<Gd<RefCounted>>) -> bool {
32+
value.is_some()
33+
}
34+
35+
#[func]
36+
fn mirror_option_refcounted(&self, value: Option<Gd<RefCounted>>) -> Option<Gd<RefCounted>> {
37+
value
38+
}
39+
40+
#[func]
41+
fn return_option_node_none(&self) -> Option<Gd<Node>> {
42+
None
43+
}
44+
45+
#[func]
46+
fn accept_option_node_none(&self, value: Option<Gd<Node>>) -> bool {
47+
value.is_none()
48+
}
49+
50+
#[func]
51+
fn return_option_node_some(&self) -> Option<Gd<Node>> {
52+
Some(Node::new_alloc())
53+
}
54+
55+
#[func]
56+
fn accept_option_node_some(&self, value: Option<Gd<Node>>) -> bool {
57+
value.is_some()
58+
}
59+
60+
#[func]
61+
fn mirror_option_node(&self, value: Option<Gd<Node>>) -> Option<Gd<Node>> {
62+
value
63+
}
64+
}

0 commit comments

Comments
 (0)