Skip to content

Commit

Permalink
Try #247:
Browse files Browse the repository at this point in the history
  • Loading branch information
bors[bot] authored May 3, 2023
2 parents e27a69e + b9d7fec commit 0a3819b
Show file tree
Hide file tree
Showing 6 changed files with 229 additions and 5 deletions.
24 changes: 23 additions & 1 deletion godot-core/src/obj/gd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ use std::ptr;
use godot_ffi as sys;
use godot_ffi::VariantType;
use sys::types::OpaqueObject;
use sys::{ffi_methods, interface_fn, static_assert_eq_size, GodotFfi, PtrcallType};
use sys::{
ffi_methods, interface_fn, static_assert_eq_size, GodotFfi, GodotNullablePtrSized, PtrcallType,
};

use crate::builtin::meta::{ClassName, VariantMetadata};
use crate::builtin::{
Expand Down Expand Up @@ -569,6 +571,8 @@ where
}
}

unsafe impl<T: GodotClass> GodotNullablePtrSized for Gd<T> {}

impl<T: GodotClass> Gd<T> {
/// Runs `init_fn` on the address of a pointer (initialized to null). If that pointer is still null after the `init_fn` call,
/// then `None` will be returned; otherwise `Gd::from_obj_sys(ptr)`.
Expand Down Expand Up @@ -706,6 +710,24 @@ impl<T: GodotClass> ToVariant for Gd<T> {
}
}

impl<T: GodotClass> ToVariant for Option<Gd<T>> {
fn to_variant(&self) -> Variant {
match self {
Some(gd) => gd.to_variant(),
None => Variant::nil(),
}
}
}

impl<T: GodotClass> FromVariant for Option<Gd<T>> {
fn try_from_variant(variant: &Variant) -> Result<Self, VariantConversionError> {
match variant.get_type() {
VariantType::Nil => Ok(None),
_ => Gd::try_from_variant(variant).map(Some),
}
}
}

impl<T: GodotClass> PartialEq for Gd<T> {
/// ⚠️ Returns whether two `Gd` pointers point to the same object.
///
Expand Down
57 changes: 55 additions & 2 deletions godot-ffi/src/godot_ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

use crate as sys;
use std::fmt::Debug;
use crate::{self as sys, interface_fn, ptr_then};
use std::{fmt::Debug, ptr};

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

/// Marks a type as having a nullable counterpart in Godot.
///
/// # Safety
///
/// The type has to have a pointer sized counter part in Godot which needs to be nullable.
/// So far only types that inherit from Object are nullable.
pub unsafe trait GodotNullablePtrSized: GodotFfi {}

unsafe impl<T> GodotFfi for Option<T>
where
T: GodotNullablePtrSized,
{
fn sys(&self) -> sys::GDExtensionTypePtr {
match self {
Some(value) => value.sys(),
None => {
let nil_ptr = ptr::null_mut();
let new_nil = interface_fn!(variant_new_nil);

unsafe {
new_nil(nil_ptr);
}

nil_ptr as sys::GDExtensionTypePtr
}
}
}

unsafe fn from_sys(ptr: sys::GDExtensionTypePtr) -> Self {
ptr_then(ptr, |ptr| T::from_sys(ptr))
}

unsafe fn from_sys_init(init_fn: impl FnOnce(sys::GDExtensionTypePtr)) -> Self {
let mut raw = std::mem::MaybeUninit::uninit();
init_fn(raw.as_mut_ptr() as sys::GDExtensionTypePtr);

Self::from_sys(raw.assume_init())
}

unsafe fn from_arg_ptr(ptr: sys::GDExtensionTypePtr, call_type: PtrcallType) -> Self {
ptr_then(ptr, |ptr| T::from_arg_ptr(ptr, call_type))
}

unsafe fn move_return_ptr(self, ptr: sys::GDExtensionTypePtr, call_type: PtrcallType) {
match self {
Some(value) => value.move_return_ptr(ptr, call_type),
None => {
interface_fn!(variant_new_nil)(ptr as sys::GDExtensionVariantPtr);
}
}
}
}

/// An indication of what type of pointer call is being made.
#[derive(Default, Copy, Clone, Eq, PartialEq, Debug)]
pub enum PtrcallType {
Expand Down
2 changes: 1 addition & 1 deletion godot-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ mod plugins;
#[doc(hidden)]
pub use paste;

pub use crate::godot_ffi::{GodotFfi, GodotFuncMarshal, PtrcallType};
pub use crate::godot_ffi::{GodotFfi, GodotFuncMarshal, GodotNullablePtrSized, PtrcallType};
pub use gen::central::*;
pub use gen::gdextension_interface::*;

Expand Down
86 changes: 85 additions & 1 deletion itest/godot/ManualFfiTests.gd
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,88 @@ func test_refcounted_as_object_return_from_user_func_varcall():
func test_refcounted_as_object_return_from_user_func_ptrcall():
var obj_test: ObjectTest = ObjectTest.new()
var obj: MockRefCountedRust = obj_test.return_refcounted_as_object()
assert_eq(obj.i, 42)
assert_eq(obj.i, 42)

func test_option_refcounted_none_varcall():
var ffi := OptionFfiTest.new()

var from_rust: Variant = ffi.return_option_refcounted_none()
assert_that(ffi.accept_option_refcounted_none(from_rust), "ffi.accept_option_refcounted_none(from_rust)")

var from_gdscript: Variant = null
var mirrored: Variant = ffi.mirror_option_refcounted(from_gdscript)
assert_eq(mirrored, from_gdscript, "mirrored == from_gdscript")

func test_option_refcounted_none_ptrcall():
var ffi := OptionFfiTest.new()

var from_rust: Object = ffi.return_option_refcounted_none()
assert_that(ffi.accept_option_refcounted_none(from_rust), "ffi.accept_option_refcounted_none(from_rust)")

var from_gdscript: Object = null
var mirrored: Object = ffi.mirror_option_refcounted(from_gdscript)
assert_eq(mirrored, from_gdscript, "mirrored == from_gdscript")

func test_option_refcounted_some_varcall():
var ffi := OptionFfiTest.new()

var from_rust: Variant = ffi.return_option_refcounted_some()
assert_that(ffi.accept_option_refcounted_some(from_rust), "ffi.accept_option_refcounted_some(from_rust)")

var from_gdscript: Variant = RefCounted.new()
var mirrored: Variant = ffi.mirror_option_refcounted(from_gdscript)
assert_eq(mirrored, from_gdscript, "mirrored == from_gdscript")

func test_option_refcounted_some_ptrcall():
var ffi := OptionFfiTest.new()

var from_rust: Object = ffi.return_option_refcounted_some()
assert_that(ffi.accept_option_refcounted_some(from_rust), "ffi.accept_option_refcounted_some(from_rust)")

var from_gdscript: Object = RefCounted.new()
var mirrored: Object = ffi.mirror_option_refcounted(from_gdscript)
assert_eq(mirrored, from_gdscript, "mirrored == from_gdscript")

func test_option_node_none_varcall():
var ffi := OptionFfiTest.new()

var from_rust: Variant = ffi.return_option_node_none()
assert_that(ffi.accept_option_node_none(from_rust), "ffi.accept_option_node_none(from_rust)")

var from_gdscript: Variant = null
var mirrored: Variant = ffi.mirror_option_node(from_gdscript)
assert_eq(mirrored, from_gdscript, "mirrored == from_gdscript")

func test_option_node_none_ptrcall():
var ffi := OptionFfiTest.new()

var from_rust: Node = ffi.return_option_node_none()
assert_that(ffi.accept_option_node_none(from_rust), "ffi.accept_option_node_none(from_rust)")

var from_gdscript: Node = null
var mirrored: Node = ffi.mirror_option_node(from_gdscript)
assert_eq(mirrored, from_gdscript, "mirrored == from_gdscript")

func test_option_node_some_varcall():
var ffi := OptionFfiTest.new()

var from_rust: Variant = ffi.return_option_node_some()
assert_that(ffi.accept_option_node_some(from_rust), "ffi.accept_option_node_some(from_rust)")

var from_gdscript: Variant = Node.new()
var mirrored: Variant = ffi.mirror_option_node(from_gdscript)
assert_eq(mirrored, from_gdscript, "mirrored == from_gdscript")
from_gdscript.free()
from_rust.free()

func test_option_node_some_ptrcall():
var ffi := OptionFfiTest.new()

var from_rust: Node = ffi.return_option_node_some()
assert_that(ffi.accept_option_node_some(from_rust), "ffi.accept_option_node_some(from_rust)")

var from_gdscript: Node = Node.new()
var mirrored: Node = ffi.mirror_option_node(from_gdscript)
assert_eq(mirrored, from_gdscript, "mirrored == from_gdscript")
from_gdscript.free()
from_rust.free()
1 change: 1 addition & 0 deletions itest/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ mod gdscript_ffi_test;
mod init_test;
mod node_test;
mod object_test;
mod option_ffi_test;
mod packed_array_test;
mod projection_test;
mod quaternion_test;
Expand Down
64 changes: 64 additions & 0 deletions itest/rust/src/option_ffi_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

use godot::prelude::{godot_api, Gd, GodotClass, Node, RefCounted};

#[derive(GodotClass, Debug)]
#[class(base = RefCounted, init)]
struct OptionFfiTest;

#[godot_api]
impl OptionFfiTest {
#[func]
fn return_option_refcounted_none(&self) -> Option<Gd<RefCounted>> {
None
}

#[func]
fn accept_option_refcounted_none(&self, value: Option<Gd<RefCounted>>) -> bool {
value.is_none()
}

#[func]
fn return_option_refcounted_some(&self) -> Option<Gd<RefCounted>> {
Some(RefCounted::new())
}

#[func]
fn accept_option_refcounted_some(&self, value: Option<Gd<RefCounted>>) -> bool {
value.is_some()
}

#[func]
fn mirror_option_refcounted(&self, value: Option<Gd<RefCounted>>) -> Option<Gd<RefCounted>> {
value
}

#[func]
fn return_option_node_none(&self) -> Option<Gd<Node>> {
None
}

#[func]
fn accept_option_node_none(&self, value: Option<Gd<Node>>) -> bool {
value.is_none()
}

#[func]
fn return_option_node_some(&self) -> Option<Gd<Node>> {
Some(Node::new_alloc())
}

#[func]
fn accept_option_node_some(&self, value: Option<Gd<Node>>) -> bool {
value.is_some()
}

#[func]
fn mirror_option_node(&self, value: Option<Gd<Node>>) -> Option<Gd<Node>> {
value
}
}

0 comments on commit 0a3819b

Please sign in to comment.