diff --git a/gdnative-core/src/object/as_arg.rs b/gdnative-core/src/object/as_arg.rs new file mode 100644 index 000000000..f456678a8 --- /dev/null +++ b/gdnative-core/src/object/as_arg.rs @@ -0,0 +1,182 @@ +use crate::export::user_data::Map; +use crate::export::NativeClass; +use crate::object::ownership::{Ownership, Shared, Unique}; +use crate::object::{GodotObject, Instance, Null, Ref, SubClass, TInstance, TRef}; + +/// Trait for safe conversion from Godot object references into API method arguments. This is +/// a sealed trait with no public interface. +/// +/// In order to enforce thread safety statically, the ability to be passed to the engine is only +/// given to some reference types. Specifically, they are: +/// +/// - All *owned* `Ref` references. The `Unique` access is lost if passed into a +/// method. +/// - Owned and borrowed `Shared` references, including temporary ones (`TRef`). +/// +/// It's unsound to pass `ThreadLocal` references to the engine because there is no guarantee +/// that the reference will stay on the same thread. +/// +/// To explicitly pass a null reference to the engine, use `Null::null` or `GodotObject::null`. +pub trait AsArg: private::Sealed { + #[doc(hidden)] + fn as_arg_ptr(&self) -> *mut sys::godot_object; + + #[doc(hidden)] + #[inline] + unsafe fn to_arg_variant(&self) -> crate::core_types::Variant { + crate::core_types::Variant::from_object_ptr(self.as_arg_ptr()) + } +} + +/// Trait for safe conversion from Godot object references into Variant. This is +/// a sealed trait with no public interface. +/// +/// Used for `Variant` methods and implementations as a trait bound to improve type inference. +pub trait AsVariant: AsArg<::Target> { + type Target; +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Sealed + +mod private { + pub trait Sealed {} +} + +// Null +impl<'a, T> private::Sealed for Null {} + +// Temporary references (shared ownership) +impl<'a, T: GodotObject> private::Sealed for TRef<'a, T, Shared> {} +impl<'a, T: GodotObject> private::Sealed for &'a Ref {} +impl<'a, T: NativeClass> private::Sealed for TInstance<'a, T, Shared> {} +impl<'a, T: NativeClass> private::Sealed for &'a Instance {} + +// Persistent references (any ownership) +impl private::Sealed for Ref {} +impl private::Sealed for Instance {} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Null + +impl<'a, T: GodotObject> AsArg for Null { + #[inline] + fn as_arg_ptr(&self) -> *mut sys::godot_object { + std::ptr::null_mut() + } +} + +impl<'a, T: GodotObject> AsVariant for Null { + type Target = T; +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// TRef + +impl<'a, T, U> AsArg for TRef<'a, T, Shared> +where + T: GodotObject + SubClass, + U: GodotObject, +{ + #[inline] + fn as_arg_ptr(&self) -> *mut sys::godot_object { + self.as_ptr() + } +} + +impl<'a, T: GodotObject> AsVariant for TRef<'a, T, Shared> { + type Target = T; +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Ref + +impl AsArg for Ref +where + T: GodotObject + SubClass, + U: GodotObject, +{ + #[inline] + fn as_arg_ptr(&self) -> *mut sys::godot_object { + self.as_ptr() + } +} + +impl AsArg for Ref +where + T: GodotObject + SubClass, + U: GodotObject, +{ + #[inline] + fn as_arg_ptr(&self) -> *mut sys::godot_object { + self.as_ptr() + } +} + +impl AsVariant for Ref { + type Target = T; +} + +impl<'a, T, U> AsArg for &'a Ref +where + T: GodotObject + SubClass, + U: GodotObject, +{ + #[inline] + fn as_arg_ptr(&self) -> *mut sys::godot_object { + self.as_ptr() + } +} + +impl AsVariant for Ref { + type Target = T; +} + +impl<'a, T: GodotObject> AsVariant for &'a Ref { + type Target = T; +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// TInstance + +impl<'a, T, U> AsArg for TInstance<'a, T, Shared> +where + T: NativeClass, + T::Base: GodotObject + SubClass, + T::UserData: Map, + U: GodotObject, +{ + #[inline] + fn as_arg_ptr(&self) -> *mut sys::godot_object { + self.as_base_ptr() + } +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Instance + +impl AsArg for Instance +where + T: NativeClass, + T::Base: GodotObject + SubClass, + T::UserData: Map, + U: GodotObject, +{ + #[inline] + fn as_arg_ptr(&self) -> *mut sys::godot_object { + self.as_base_ptr() + } +} + +impl<'a, T, U> AsArg for &'a Instance +where + T: NativeClass, + T::Base: GodotObject + SubClass, + T::UserData: Map, + U: GodotObject, +{ + #[inline] + fn as_arg_ptr(&self) -> *mut sys::godot_object { + self.as_base_ptr() + } +} diff --git a/gdnative-core/src/object/instance.rs b/gdnative-core/src/object/instance.rs index 1ac0d90d7..69f8610db 100644 --- a/gdnative-core/src/object/instance.rs +++ b/gdnative-core/src/object/instance.rs @@ -229,6 +229,11 @@ impl Instance { pub fn script(&self) -> &T::UserData { &self.script } + + /// Convert to a nullable raw pointer. Used for AsArg. + pub(super) fn as_base_ptr(&self) -> *mut sys::godot_object { + self.owner.as_ptr() + } } impl Instance @@ -464,6 +469,11 @@ impl<'a, T: NativeClass, Own: Ownership> TInstance<'a, T, Own> { let script = T::UserData::clone_from_user_data_unchecked(user_data); TInstance { owner, script } } + + /// Convert to a nullable raw pointer. Used for AsArg. + pub(super) fn as_base_ptr(&self) -> *mut sys::godot_object { + self.owner.as_ptr() + } } impl<'a, T: NativeClass, Own: NonUniqueOwnership> TInstance<'a, T, Own> { diff --git a/gdnative-core/src/object/mod.rs b/gdnative-core/src/object/mod.rs index 1fc1fb107..9d6de0de1 100644 --- a/gdnative-core/src/object/mod.rs +++ b/gdnative-core/src/object/mod.rs @@ -25,6 +25,7 @@ use crate::export::NativeClass; use crate::private::{get_api, ManuallyManagedClassPlaceholder, ReferenceCountedClassPlaceholder}; use crate::sys; +pub use as_arg::*; pub use instance::*; pub use new_ref::NewRef; pub use raw::RawObject; @@ -33,6 +34,7 @@ pub mod bounds; pub mod memory; pub mod ownership; +mod as_arg; mod instance; mod new_ref; mod raw; @@ -305,8 +307,6 @@ unsafe impl Send for Ref {} /// `Ref` is `Sync` if the thread access is `Shared`. unsafe impl Sync for Ref {} -impl private::Sealed for Ref {} - /// `Ref` is `Copy` if the underlying object is manually-managed, and the access is not /// `Unique`. impl Copy for Ref @@ -994,39 +994,6 @@ impl<'a, T: GodotObject> TRef<'a, T, Shared> { } } -/// Trait for safe conversion from Godot object references into API method arguments. This is -/// a sealed trait with no public interface. -/// -/// In order to enforce thread safety statically, the ability to be passed to the engine is only -/// given to some reference types. Specifically, they are: -/// -/// - All *owned* `Ref` references. The `Unique` access is lost if passed into a -/// method. -/// - Owned and borrowed `Shared` references, including temporary ones (`TRef`). -/// -/// It's unsound to pass `ThreadLocal` references to the engine because there is no guarantee -/// that the reference will stay on the same thread. -/// -/// To explicitly pass a null reference to the engine, use `Null::null` or `GodotObject::null`. -pub trait AsArg: private::Sealed { - #[doc(hidden)] - fn as_arg_ptr(&self) -> *mut sys::godot_object; - - #[doc(hidden)] - #[inline] - unsafe fn to_arg_variant(&self) -> crate::core_types::Variant { - crate::core_types::Variant::from_object_ptr(self.as_arg_ptr()) - } -} - -/// Trait for safe conversion from Godot object references into Variant. This is -/// a sealed trait with no public interface. -/// -/// Used for `Variant` methods and implementations as a trait bound to improve type inference. -pub trait AsVariant: AsArg<::Target> { - type Target; -} - /// Represents an explicit null reference in method arguments. This works around type inference /// issues with `Option`. You may create `Null`s with `Null::null` or `GodotObject::null`. pub struct Null(PhantomData); @@ -1040,76 +1007,3 @@ impl Null { Null(PhantomData) } } - -impl<'a, T> private::Sealed for Null {} -impl<'a, T: GodotObject> AsArg for Null { - #[inline] - fn as_arg_ptr(&self) -> *mut sys::godot_object { - std::ptr::null_mut() - } -} -impl<'a, T: GodotObject> AsVariant for Null { - type Target = T; -} - -impl<'a, T: GodotObject> private::Sealed for TRef<'a, T, Shared> {} -impl<'a, T, U> AsArg for TRef<'a, T, Shared> -where - T: GodotObject + SubClass, - U: GodotObject, -{ - #[inline] - fn as_arg_ptr(&self) -> *mut sys::godot_object { - self.as_ptr() - } -} -impl<'a, T: GodotObject> AsVariant for TRef<'a, T, Shared> { - type Target = T; -} - -impl AsArg for Ref -where - T: GodotObject + SubClass, - U: GodotObject, -{ - #[inline] - fn as_arg_ptr(&self) -> *mut sys::godot_object { - self.as_ptr() - } -} -impl AsVariant for Ref { - type Target = T; -} - -impl AsArg for Ref -where - T: GodotObject + SubClass, - U: GodotObject, -{ - #[inline] - fn as_arg_ptr(&self) -> *mut sys::godot_object { - self.as_ptr() - } -} -impl AsVariant for Ref { - type Target = T; -} - -impl<'a, T: GodotObject> private::Sealed for &'a Ref {} -impl<'a, T, U> AsArg for &'a Ref -where - T: GodotObject + SubClass, - U: GodotObject, -{ - #[inline] - fn as_arg_ptr(&self) -> *mut sys::godot_object { - self.as_ptr() - } -} -impl<'a, T: GodotObject> AsVariant for &'a Ref { - type Target = T; -} - -mod private { - pub trait Sealed {} -} diff --git a/test/project/addons/editor_test_runner/plugin.gd b/test/project/addons/editor_test_runner/plugin.gd index c8c0902c9..f5a6c5e82 100644 --- a/test/project/addons/editor_test_runner/plugin.gd +++ b/test/project/addons/editor_test_runner/plugin.gd @@ -15,7 +15,7 @@ func _enter_tree(): print("Opening editor normally for the test project. To run tests, pass `--run-editor-tests` to the executable.") func _run_tests(): - print(" -- Rust gdnative test suite:") + print(" -- Rust GDNative test suite (called from editor):") gdn = GDNative.new() var status = false; @@ -26,12 +26,13 @@ func _run_tests(): gdn.terminate() else: - print(" -- Could not load the gdnative library.") + print(" -- Could not load the GDNative library.") + print() if status: - print(" -- Test run completed successfully.") + print(" All tests PASSED.") else: - print(" -- Test run completed with errors.") + print(" Tests FAILED.") OS.exit_code = 1 print(" -- exiting.") diff --git a/test/project/main.gd b/test/project/main.gd index 0018a620a..e17975f95 100644 --- a/test/project/main.gd +++ b/test/project/main.gd @@ -3,7 +3,7 @@ extends Node var gdn func _ready(): - print(" -- Rust gdnative test suite:") + print(" -- Rust GDNative test suite:") _timeout() gdn = GDNative.new() @@ -24,12 +24,13 @@ func _ready(): gdn.terminate() else: - print(" -- Could not load the gdnative library.") + print(" -- Could not load the GDNative library.") + print() if status: - print(" -- Test run completed successfully.") + print(" All tests PASSED.") else: - print(" -- Test run completed with errors.") + print(" Tests FAILED.") OS.exit_code = 1 print(" -- exiting.") diff --git a/test/src/lib.rs b/test/src/lib.rs index 4bdc9dcf0..432ab3b42 100644 --- a/test/src/lib.rs +++ b/test/src/lib.rs @@ -2,6 +2,7 @@ use gdnative::prelude::*; +mod test_as_arg; mod test_async; mod test_constructor; mod test_derive; @@ -63,17 +64,18 @@ pub extern "C" fn run_tests( status &= test_rust_class_construction(); status &= test_from_instance_id(); + status &= test_as_arg::run_tests(); status &= test_async::run_tests(); + status &= test_constructor::run_tests(); status &= test_derive::run_tests(); status &= test_free_ub::run_tests(); - status &= test_constructor::run_tests(); status &= test_map_owned::run_tests(); status &= test_register::run_tests(); status &= test_return_leak::run_tests(); status &= test_serde::run_tests(); + status &= test_vararray_return::run_tests(); status &= test_variant_call_args::run_tests(); status &= test_variant_ops::run_tests(); - status &= test_vararray_return::run_tests(); gdnative::core_types::Variant::new(status).forget() } @@ -260,16 +262,17 @@ fn init(handle: InitHandle) { handle.add_class::(); handle.add_class::(); + test_as_arg::register(handle); test_async::register(handle); + test_constructor::register(handle); test_derive::register(handle); test_free_ub::register(handle); - test_constructor::register(handle); test_map_owned::register(handle); test_register::register(handle); test_return_leak::register(handle); + test_vararray_return::register(handle); test_variant_call_args::register(handle); test_variant_ops::register(handle); - test_vararray_return::register(handle); } fn terminate(_term_info: &gdnative::init::TerminateInfo) { diff --git a/test/src/test_as_arg.rs b/test/src/test_as_arg.rs new file mode 100644 index 000000000..b55b0f558 --- /dev/null +++ b/test/src/test_as_arg.rs @@ -0,0 +1,131 @@ +use gdnative::derive::{methods, NativeClass}; +use gdnative::prelude::NodeExt; +use gdnative::prelude::*; +use std::ops::Deref; + +pub(crate) fn register(handle: InitHandle) { + handle.add_class::(); +} + +pub(crate) fn run_tests() -> bool { + println!(" -- test_as_arg"); + + let ok = std::panic::catch_unwind(|| { + println!(" -- test_ref_as_arg"); + test_ref_as_arg(); + + println!(" -- test_instance_as_arg"); + test_instance_as_arg(); + }) + .is_ok(); + + if !ok { + godot_error!(" !! Test test_as_arg failed"); + } + + ok +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- + +#[derive(NativeClass, Debug)] +#[inherit(Spatial)] +#[no_constructor] +struct MyNode { + secret: &'static str, +} + +#[methods] +impl MyNode {} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- + +fn test_ref_as_arg() { + // Ref + add_node_with(|n: Ref| n); + + // Ref + add_node_with(|n: Ref| n.into_shared()); + + // &Ref + let mut keeper = Node2D::new().into_shared(); // keep Ref alive so we can return a reference to it + add_node_with(|n: Ref| { + keeper = n.into_shared(); + &keeper + }); + + // TRef + add_node_with(|n: Ref| unsafe { n.into_shared().assume_safe() }); +} + +fn test_instance_as_arg() { + // Instance + add_instance_with(|n: Instance| n); + + // Instance + add_instance_with(|n: Instance| n.into_shared()); + + // &Instance + let mut keeper = MyNode { secret: "" }.emplace().into_shared(); // keep Instance alive so we can return a reference to it + add_instance_with(|n: Instance| { + keeper = n.into_shared(); + &keeper + }); + + // TInstance + add_instance_with(|n: Instance| unsafe { n.into_shared().assume_safe() }); +} + +fn add_node_with(to_arg: F) +where + F: FnOnce(Ref) -> T, + T: AsArg, +{ + let parent = Node::new(); + let child = Node2D::new(); + let child_id: i64 = child.get_instance_id(); + child.set_name("ch"); + + let child: T = to_arg(child); + + parent.add_child(child, /*legible_unique_name*/ true); + + let found = parent.get_node("ch").expect("get_node() for Ref"); + let found_tref = unsafe { found.assume_safe() }; + + assert_eq!(found_tref.get_instance_id(), child_id); +} + +fn add_instance_with(to_arg: F) +where + F: FnOnce(Instance) -> T, + T: AsArg, +{ + let parent = Node::new(); + let child = MyNode { secret: "yes" }.emplace(); + + let child_id: i64 = child + .map(|_, node| { + node.set_name("ch"); + node.get_instance_id() + }) + .expect("child.map()"); + + let child: T = to_arg(child); + + parent.add_child(child, /*legible_unique_name*/ true); + + let found: TInstance = unsafe { + parent + .deref() + .get_node_as_instance::("ch") + .expect("get_node() for Instance") + }; + + found + .map(|user, node| { + assert_eq!(node.get_instance_id(), child_id); + assert_eq!(user.secret, "yes"); + }) + .expect("found.map()") +}