diff --git a/bindings_generator/api.json b/bindings_generator/api.json index f10df5e5a..6b2e89504 100644 --- a/bindings_generator/api.json +++ b/bindings_generator/api.json @@ -150414,7 +150414,7 @@ }, { "name": "new", - "return_type": "Object", + "return_type": "Variant", "is_editor": false, "is_noscript": false, "is_const": false, diff --git a/bindings_generator/src/lib.rs b/bindings_generator/src/lib.rs index b2a948795..5596d58a3 100644 --- a/bindings_generator/src/lib.rs +++ b/bindings_generator/src/lib.rs @@ -106,6 +106,10 @@ fn generate_class_bindings( generate_singleton_getter(output_types_impls, class)?; } + if class.name == "GDNativeLibrary" { + generate_gdnative_library_singleton_getter(output_types_impls, class)?; + } + if class.instanciable { if class.is_refcounted() { generate_reference_ctor(output_types_impls, class)?; @@ -158,6 +162,10 @@ fn generate_class_bindings( if class.is_refcounted() && class.instanciable { generate_drop(output_trait_impls, class)?; } + + if class.instanciable { + generate_instanciable_impl(output_trait_impls, class)?; + } } // methods and method table diff --git a/bindings_generator/src/special_methods.rs b/bindings_generator/src/special_methods.rs index 8cbe0b0ca..5725a6325 100644 --- a/bindings_generator/src/special_methods.rs +++ b/bindings_generator/src/special_methods.rs @@ -112,7 +112,24 @@ impl FromVariant for {name} {{ "object::add_ref(obj);" } else { "// Not reference-counted." - } + }, + )?; + + Ok(()) +} + +pub fn generate_instanciable_impl(output: &mut impl Write, class: &GodotClass) -> GeneratorResult { + assert!(class.instanciable); + + writeln!( + output, + r#" +impl Instanciable for {name} {{ + fn construct() -> Self {{ + {name}::new() + }} +}}"#, + name = class.name, )?; Ok(()) @@ -306,6 +323,27 @@ impl Drop for {name} {{ Ok(()) } +pub fn generate_gdnative_library_singleton_getter(output: &mut impl Write, class: &GodotClass) -> GeneratorResult { + assert_eq!("GDNativeLibrary", class.name); + writeln!( + output, + r#" +/// Returns the GDNativeLibrary object of this library. Can be used to construct NativeScript objects. +/// +/// See also `Instance::new` for a typed API. +#[inline] +pub fn current_library() -> Self {{ + let this = get_gdnative_library_sys(); + + Self {{ + this + }} +}}"# + )?; + + Ok(()) +} + pub fn class_name_to_snake_case(name: &str) -> String { // TODO: this is a quick-n-dirty band-aid, it'd be better to // programmatically do the right conversion, but to_snake_case diff --git a/examples/hello_world/src/lib.rs b/examples/hello_world/src/lib.rs index ec2228137..f6ded1010 100644 --- a/examples/hello_world/src/lib.rs +++ b/examples/hello_world/src/lib.rs @@ -3,6 +3,7 @@ extern crate gdnative; #[derive(gdnative::NativeClass)] #[inherit(gdnative::Node)] +#[user_data(gdnative::user_data::ArcData)] struct HelloWorld; #[gdnative::methods] diff --git a/examples/scene_create/src/lib.rs b/examples/scene_create/src/lib.rs index a18dbd022..ef3814ca5 100644 --- a/examples/scene_create/src/lib.rs +++ b/examples/scene_create/src/lib.rs @@ -19,6 +19,9 @@ struct SceneCreate { children_spawned: u32, } +// Assume godot objects are safe to Send +unsafe impl Send for SceneCreate { } + // Demonstrates Scene creation, calling to/from gdscript // // 1. Child scene is created when spawn_one is called diff --git a/examples/spinning_cube/src/lib.rs b/examples/spinning_cube/src/lib.rs index c0dfa51d9..f39ef90fe 100644 --- a/examples/spinning_cube/src/lib.rs +++ b/examples/spinning_cube/src/lib.rs @@ -12,6 +12,7 @@ struct RustTest { impl godot::NativeClass for RustTest { type Base = godot::MeshInstance; + type UserData = godot::user_data::MutexData; fn class_name() -> &'static str { "RustTest" @@ -30,7 +31,7 @@ impl godot::NativeClass for RustTest { step: 0.01, slider: true, }, - getter: |this: &mut RustTest| this.rotate_speed, + getter: |this: &RustTest| this.rotate_speed, setter: |this: &mut RustTest, v| this.rotate_speed = v, usage: PropertyUsage::DEFAULT, }); @@ -41,7 +42,7 @@ impl godot::NativeClass for RustTest { hint: PropertyHint::Enum { values: &["Hello", "World", "Testing"], }, - getter: |_: &mut RustTest| GodotString::from_str("Hello"), + getter: |_: &RustTest| GodotString::from_str("Hello"), setter: (), usage: PropertyUsage::DEFAULT, }); @@ -52,7 +53,7 @@ impl godot::NativeClass for RustTest { hint: PropertyHint::Flags { values: &["A", "B", "C", "D"], }, - getter: |_: &mut RustTest| 0, + getter: |_: &RustTest| 0, setter: (), usage: PropertyUsage::DEFAULT, }); diff --git a/gdnative-core/Cargo.toml b/gdnative-core/Cargo.toml index 7b4955e84..2876078ec 100644 --- a/gdnative-core/Cargo.toml +++ b/gdnative-core/Cargo.toml @@ -17,6 +17,7 @@ gdnative-sys = { path = "../gdnative-sys", version = "0.5.0" } libc = "0.2" bitflags = "1.0" euclid = "0.19.6" +parking_lot = "0.9.0" [build-dependencies] gdnative_bindings_generator = { path = "../bindings_generator", version = "0.2.0" } diff --git a/gdnative-core/src/class.rs b/gdnative-core/src/class.rs index d72154e4b..dd91859fb 100644 --- a/gdnative-core/src/class.rs +++ b/gdnative-core/src/class.rs @@ -1,10 +1,14 @@ use crate::get_api; -use crate::object; use crate::sys; +use crate::object; +use crate::Variant; +use crate::ToVariant; +use crate::GodotString; use crate::GodotObject; -use std::cell::RefCell; -use std::marker::PhantomData; -use std::ops::Deref; +use crate::Instanciable; +use crate::UserData; +use crate::Map; +use crate::MapMut; /// Trait used for describing and initializing a Godot script class. /// @@ -38,6 +42,11 @@ pub trait NativeClass: Sized { /// implementation does**. type Base: GodotObject; + /// User-data wrapper type of the class. + /// + /// See module-level documentation on `user_data` for more info. + type UserData: UserData; + /// The name of the class. /// /// In GDNative+NativeScript many classes can be defined in one dynamic library. @@ -60,71 +69,169 @@ pub trait NativeClassMethods: NativeClass { fn register(builder: &crate::init::ClassBuilder); } -/// A reference to a rust native script. -pub struct NativeRef { - this: *mut sys::godot_object, - _marker: PhantomData, +/// A reference to a GodotObject with a rust NativeClass attached. +#[derive(Eq, PartialEq, Ord, PartialOrd, Debug)] +pub struct Instance { + owner: T::Base, + script: T::UserData, } -impl NativeRef { - /// Try to cast into a godot object reference. - pub fn cast(&self) -> Option +impl Instance { + /// Creates a `T::Base` with the script `T` attached. Both `T::Base` and `T` must have zero + /// argument constructors. + /// + /// Must be called after the library is initialized. + pub fn new() -> Self where - O: GodotObject, + T::Base: Instanciable, { - object::godot_cast::(self.this) - } - - /// Creates a new reference to the same object. - pub fn new_ref(&self) -> Self { unsafe { - object::add_ref(self.this); + let gd_api = get_api(); + + // The API functions take NUL-terminated C strings. &CStr is not used for its runtime cost. + let class_name = b"NativeScript\0".as_ptr() as *const libc::c_char; + let ctor = (gd_api.godot_get_class_constructor)(class_name).unwrap(); + let set_class_name = (gd_api.godot_method_bind_get_method)(class_name, b"set_class_name\0".as_ptr() as *const libc::c_char); + let set_library = (gd_api.godot_method_bind_get_method)(class_name, b"set_library\0".as_ptr() as *const libc::c_char); + let object_set_script = crate::ObjectMethodTable::get(gd_api).set_script; + + let native_script = ctor(); + object::init_ref_count(native_script); + + + let script_class_name = GodotString::from(T::class_name()); + let mut args: [*const libc::c_void; 1] = [script_class_name.sys() as *const _]; + (gd_api.godot_method_bind_ptrcall)(set_class_name, native_script, args.as_mut_ptr(), std::ptr::null_mut()); + + let mut args: [*const libc::c_void; 1] = [crate::get_gdnative_library_sys()]; + (gd_api.godot_method_bind_ptrcall)(set_library, native_script, args.as_mut_ptr(), std::ptr::null_mut()); + + let owner = T::Base::construct(); - Self { - this: self.this, - _marker: PhantomData, + assert_ne!(std::ptr::null_mut(), owner.to_sys(), "base object should not be null"); + + let mut args: [*const libc::c_void; 1] = [native_script as *const _]; + (gd_api.godot_method_bind_ptrcall)(object_set_script, owner.to_sys(), args.as_mut_ptr(), std::ptr::null_mut()); + + let script_ptr = (gd_api.godot_nativescript_get_userdata)(owner.to_sys()) as *const libc::c_void; + + assert_ne!(std::ptr::null(), script_ptr, "script instance should not be null"); + + let script = T::UserData::clone_from_user_data_unchecked(script_ptr); + + object::unref(native_script); + + Instance { + owner, + script, } } } - fn get_impl(&self) -> &RefCell { - unsafe { - let api = get_api(); - let ud = (api.godot_nativescript_get_userdata)(self.this); - &*(ud as *const _ as *const RefCell) - } + pub fn into_base(self) -> T::Base { + self.owner } - #[doc(hidden)] - pub unsafe fn sys(&self) -> *mut sys::godot_object { - self.this + /// Calls a function with a NativeClass instance and its owner, and returns its return + /// value. Can be used on reference counted types for multiple times. + pub fn map(&self, op: F) -> Result::Err> + where + T::Base: Clone, + T::UserData: Map, + F: FnOnce(&T, T::Base) -> U, + { + self.script.map(|script| op(script, self.owner.clone())) + } + + /// Calls a function with a NativeClass instance and its owner, and returns its return + /// value. Can be used on reference counted types for multiple times. + pub fn map_mut(&self, op: F) -> Result::Err> + where + T::Base: Clone, + T::UserData: MapMut, + F: FnOnce(&mut T, T::Base) -> U, + { + self.script.map_mut(|script| op(script, self.owner.clone())) + } + + /// Calls a function with a NativeClass instance and its owner, and returns its return + /// value. Can be used for multiple times via aliasing: + /// + /// ```ignore + /// unsafe { + /// instance.map_aliased(/* ... */); + /// // instance.owner may be invalid now, but you can still: + /// instance.map_aliased(/* ... */); + /// instance.map_aliased(/* ... */); // ...for multiple times + /// } + /// ``` + /// + /// For reference-counted types behaves like the safe `map`, which should be preferred. + pub unsafe fn map_aliased(&self, op: F) -> Result::Err> + where + T::UserData: Map, + F: FnOnce(&T, T::Base) -> U, + { + self.script.map(|script| op(script, T::Base::from_sys(self.owner.to_sys()))) + } + + /// Calls a function with a NativeClass instance and its owner, and returns its return + /// value. Can be used for multiple times via aliasing: + /// + /// ```ignore + /// unsafe { + /// instance.map_mut_aliased(/* ... */); + /// // instance.owner may be invalid now, but you can still: + /// instance.map_mut_aliased(/* ... */); + /// instance.map_mut_aliased(/* ... */); // ...for multiple times + /// } + /// ``` + /// + /// For reference-counted types behaves like the safe `map_mut`, which should be preferred. + pub unsafe fn map_mut_aliased(&self, op: F) -> Result::Err> + where + T::UserData: MapMut, + F: FnOnce(&mut T, T::Base) -> U, + { + self.script.map_mut(|script| op(script, T::Base::from_sys(self.owner.to_sys()))) } #[doc(hidden)] - pub unsafe fn from_sys(ptr: *mut sys::godot_object) -> Self { - object::add_ref(ptr); + pub unsafe fn from_sys_unchecked(ptr: *mut sys::godot_object) -> Self { + let api = get_api(); + let user_data = (api.godot_nativescript_get_userdata)(ptr); + Self::from_raw(ptr, user_data) + } - NativeRef { - this: ptr, - _marker: PhantomData, - } + #[doc(hidden)] + pub unsafe fn from_raw(ptr: *mut sys::godot_object, user_data: *mut libc::c_void) -> Self { + let owner = T::Base::from_sys(ptr); + let script_ptr = user_data as *const libc::c_void; + let script = T::UserData::clone_from_user_data_unchecked(script_ptr); + Instance { owner, script } } } -impl Deref for NativeRef { - type Target = RefCell; - fn deref(&self) -> &Self::Target { - self.get_impl() +impl Clone for Instance +where + T: NativeClass, + T::Base: Clone, +{ + fn clone(&self) -> Self { + Instance { + owner: self.owner.clone(), + script: self.script.clone(), + } } } -impl Drop for NativeRef { - fn drop(&mut self) { - unsafe { - if object::unref(self.this) { - (get_api().godot_object_destroy)(self.this); - } - } +impl ToVariant for Instance +where + T: NativeClass, + T::Base: ToVariant, +{ + fn to_variant(&self) -> Variant { + self.owner.to_variant() } } @@ -240,7 +347,7 @@ macro_rules! godot_class_build_methods { #[macro_export] macro_rules! godot_class { ( -class $name:ident: $owner:ty { +class $name:ident ($user_data:ty) : $owner:ty { fields { $( $(#[$fattr:meta])* @@ -276,6 +383,7 @@ class $name:ident: $owner:ty { impl $crate::NativeClass for $name { type Base = $owner; + type UserData = $user_data; fn class_name() -> &'static str { stringify!($name) } @@ -288,6 +396,36 @@ class $name:ident: $owner:ty { } } ); + ( +class $name:ident: $owner:ty { + fields { + $( + $(#[$fattr:meta])* + $fname:ident : $fty:ty, + )* + } + setup($builder:ident) $pbody:block + constructor($owner_name:ident : $owner_ty:ty) $construct:block + + $($tt:tt)* +} + ) => ( + godot_class! { + class $name ($crate::user_data::DefaultUserData<$name>) : $owner { + fields { + $( + $(#[$fattr])* + $fname : $fty, + )* + } + + setup($builder) $pbody + constructor($owner_name : $owner_ty) $construct + + $($tt)* + } + } + ); } #[cfg(test)] @@ -311,3 +449,23 @@ godot_class! { } } } + +#[cfg(test)] +godot_class! { + class TestReturnInstanceClass: super::Object { + + fields { + } + + setup(_builder) {} + + constructor(_owner: super::Object) { + TestReturnInstanceClass { + } + } + + export fn answer(&mut self, _owner: super::Object) -> Instance { + Instance::new() + } + } +} \ No newline at end of file diff --git a/gdnative-core/src/init.rs b/gdnative-core/src/init.rs index 18b8eecf5..e7fec331c 100644 --- a/gdnative-core/src/init.rs +++ b/gdnative-core/src/init.rs @@ -27,6 +27,8 @@ use crate::NativeClass; use crate::ToVariant; use crate::FromVariant; use crate::Variant; +use crate::Map; +use crate::MapMut; use libc; use std::ffi::CString; use std::marker::PhantomData; @@ -66,12 +68,10 @@ impl InitHandle { this: *mut sys::godot_object, _method_data: *mut libc::c_void, ) -> *mut libc::c_void { - use std::cell::RefCell; - let val = C::init(C::Base::from_sys(this)); - let wrapper = Box::new(RefCell::new(val)); - Box::into_raw(wrapper) as *mut _ + let wrapper = C::UserData::new(val); + C::UserData::into_user_data(wrapper) as *mut _ } sys::godot_instance_create_func { @@ -87,9 +87,7 @@ impl InitHandle { _method_data: *mut libc::c_void, user_data: *mut libc::c_void, ) -> () { - use std::cell::RefCell; - - let wrapper: Box> = Box::from_raw(user_data as *mut _); + let wrapper = C::UserData::consume_user_data_unchecked(user_data); drop(wrapper) } @@ -138,12 +136,10 @@ impl InitHandle { this: *mut sys::godot_object, _method_data: *mut libc::c_void, ) -> *mut libc::c_void { - use std::cell::RefCell; - let val = C::init(C::Base::from_sys(this)); - let wrapper = Box::new(RefCell::new(val)); - Box::into_raw(wrapper) as *mut _ + let wrapper = C::UserData::new(val); + C::UserData::into_user_data(wrapper) as *mut _ } sys::godot_instance_create_func { @@ -159,9 +155,7 @@ impl InitHandle { _method_data: *mut libc::c_void, user_data: *mut libc::c_void, ) -> () { - use std::cell::RefCell; - - let wrapper: Box> = Box::from_raw(user_data as *mut _); + let wrapper = C::UserData::consume_user_data_unchecked(user_data); drop(wrapper) } @@ -530,11 +524,11 @@ unsafe impl PropertyGetter for () { unsafe impl PropertySetter for F where C: NativeClass, + C::UserData: MapMut, T: FromVariant, F: Fn(&mut C, T), { unsafe fn as_godot_function(self) -> sys::godot_property_set_func { - use std::cell::RefCell; let mut set = sys::godot_property_set_func::default(); let data = Box::new(self); set.method_data = Box::into_raw(data) as *mut _; @@ -546,16 +540,18 @@ where val: *mut sys::godot_variant, ) where C: NativeClass, + C::UserData: MapMut, T: FromVariant, F: Fn(&mut C, T), { unsafe { - let rust_ty = &*(class as *mut RefCell); - let mut rust_ty = rust_ty.borrow_mut(); + let rust_ty = C::UserData::clone_from_user_data_unchecked(class as *const _); let func = &mut *(method as *mut F); if let Some(val) = T::from_variant(Variant::cast_ref(val)) { - func(&mut *rust_ty, val); + if let Err(err) = rust_ty.map_mut(|rust_ty| func(rust_ty, val)) { + godot_error!("gdnative-core: cannot call property setter: {:?}", err); + } } else { godot_error!("Incorrect type passed to property"); } @@ -577,11 +573,11 @@ where unsafe impl PropertyGetter for F where C: NativeClass, + C::UserData: Map, T: ToVariant, - F: Fn(&mut C) -> T, + F: Fn(&C) -> T, { unsafe fn as_godot_function(self) -> sys::godot_property_get_func { - use std::cell::RefCell; let mut get = sys::godot_property_get_func::default(); let data = Box::new(self); get.method_data = Box::into_raw(data) as *mut _; @@ -593,15 +589,20 @@ where ) -> sys::godot_variant where C: NativeClass, + C::UserData: Map, T: ToVariant, - F: Fn(&mut C) -> T, + F: Fn(&C) -> T, { unsafe { - let rust_ty = &*(class as *mut RefCell); - let mut rust_ty = rust_ty.borrow_mut(); + let rust_ty = C::UserData::clone_from_user_data_unchecked(class as *const _); let func = &mut *(method as *mut F); - let ret = func(&mut *rust_ty); - ret.to_variant().forget() + match rust_ty.map(|rust_ty| func(rust_ty)) { + Ok(ret) => ret.to_variant().forget(), + Err(err) => { + godot_error!("gdnative-core: cannot call property getter: {:?}", err); + Variant::new().to_sys() + }, + } } } get.get_func = Some(invoke::); diff --git a/gdnative-core/src/lib.rs b/gdnative-core/src/lib.rs index 2910acf15..61ab8d403 100644 --- a/gdnative-core/src/lib.rs +++ b/gdnative-core/src/lib.rs @@ -30,6 +30,7 @@ pub extern crate gdnative_sys as sys; pub extern crate libc; #[macro_use] extern crate bitflags; +extern crate parking_lot; pub mod geom; @@ -52,6 +53,7 @@ pub mod object; mod rid; mod string; mod string_array; +pub mod user_data; mod variant; mod variant_array; mod vector2; @@ -72,7 +74,11 @@ pub use crate::int32_array::*; pub use crate::internal::*; pub use crate::node_path::*; pub use crate::object::GodotObject; +pub use crate::object::Instanciable; pub use crate::rid::*; +pub use crate::user_data::UserData; +pub use crate::user_data::Map; +pub use crate::user_data::MapMut; pub use crate::string::*; pub use crate::string_array::*; pub use crate::variant::*; @@ -86,11 +92,18 @@ use std::mem; #[doc(hidden)] pub static mut GODOT_API: Option = None; +#[doc(hidden)] +pub static mut GDNATIVE_LIBRARY_SYS: Option<*mut sys::godot_object> = None; #[inline] #[doc(hidden)] pub fn get_api() -> &'static GodotApi { unsafe { GODOT_API.as_ref().expect("API not bound") } } +#[inline] +#[doc(hidden)] +pub fn get_gdnative_library_sys() -> *mut sys::godot_object { + unsafe { GDNATIVE_LIBRARY_SYS.expect("GDNativeLibrary not bound") } +} #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] diff --git a/gdnative-core/src/macros.rs b/gdnative-core/src/macros.rs index 353e6993b..834f91751 100644 --- a/gdnative-core/src/macros.rs +++ b/gdnative-core/src/macros.rs @@ -32,6 +32,7 @@ macro_rules! godot_gdnative_init { pub extern "C" fn $fn_name(options: *mut $crate::sys::godot_gdnative_init_options) { unsafe { $crate::GODOT_API = Some($crate::GodotApi::from_raw((*options).api_struct)); + $crate::GDNATIVE_LIBRARY_SYS = Some((*options).gd_native_library); } let api = $crate::get_api(); // Force the initialization of the method table of common types. This way we can @@ -323,14 +324,11 @@ macro_rules! godot_wrap_constructor { this: *mut $crate::sys::godot_object, _method_data: *mut $crate::libc::c_void, ) -> *mut $crate::libc::c_void { - use std::boxed::Box; - use std::cell::RefCell; - // let val = $c($crate::NativeInstanceHeader{ this: this }); let val = $c(); - let wrapper = Box::new(RefCell::new(val)); - Box::into_raw(wrapper) as *mut _ + let wrapper = <$_name as $crate::NativeClass>::UserData::new(val); + wrapper.into_user_data() as *mut $crate::libc::c_void } constructor @@ -348,10 +346,7 @@ macro_rules! godot_wrap_destructor { _method_data: *mut $crate::libc::c_void, user_data: *mut $crate::libc::c_void, ) -> () { - use std::boxed::Box; - use std::cell::RefCell; - - let wrapper: Box> = unsafe { Box::from_raw(user_data as *mut _) }; + let wrapper = <$_name as $crate::NativeClass>::UserData::consume_user_data_unchecked(user_data); drop(wrapper) } @@ -370,15 +365,14 @@ macro_rules! godot_wrap_method_parameter_count { } } -/// Convenience macro to wrap an object's method into a function pointer -/// that can be passed to the engine when registering a class. +#[doc(hidden)] #[macro_export] -macro_rules! godot_wrap_method { - // "proper" definition +macro_rules! godot_wrap_method_inner { ( $type_name:ty, + $map_method:ident, fn $method_name:ident( - &mut $self:ident, + $self:ident, $owner:ident : $owner_ty:ty $(,$pname:ident : $pty:ty)* ) -> $retty:ty @@ -393,11 +387,10 @@ macro_rules! godot_wrap_method { args: *mut *mut $crate::sys::godot_variant ) -> $crate::sys::godot_variant { - use std::cell::RefCell; use std::panic::{self, AssertUnwindSafe}; - use $crate::GodotObject; + use $crate::Instance; - let $owner: $owner_ty = <$type_name as $crate::NativeClass>::Base::from_sys(this); + let __instance: Instance<$type_name> = Instance::from_raw(this, user_data); let num_params = godot_wrap_method_parameter_count!($($pname,)*); if num_args != num_params { @@ -420,11 +413,10 @@ macro_rules! godot_wrap_method { offset += 1; )* - let __rust_val = &*(user_data as *mut RefCell<$type_name>); - let mut __rust_val = __rust_val.borrow_mut(); - - let rust_ret = match panic::catch_unwind(AssertUnwindSafe(|| { - __rust_val.$method_name($owner, $($pname,)*) + let rust_ret = match panic::catch_unwind(AssertUnwindSafe(move || { + let ret = __instance.$map_method(|__rust_val, $owner| __rust_val.$method_name($owner, $($pname,)*)); + std::mem::drop(__instance); + ret })) { Ok(val) => val, Err(err) => { @@ -432,35 +424,67 @@ macro_rules! godot_wrap_method { } }; - <$retty as $crate::ToVariant>::to_variant(&rust_ret).forget() + match rust_ret { + Ok(val) => <$retty as $crate::ToVariant>::to_variant(&val).forget(), + Err(err) => { + godot_error!("gdnative-core: method call failed with error: {:?}", err); + $crate::Variant::new().to_sys() + } + } } method } }; - // non mut self +} + +/// Convenience macro to wrap an object's method into a function pointer +/// that can be passed to the engine when registering a class. +#[macro_export] +macro_rules! godot_wrap_method { + // mutable + ( + $type_name:ty, + fn $method_name:ident( + &mut $self:ident, + $owner:ident : $owner_ty:ty + $(,$pname:ident : $pty:ty)* + ) -> $retty:ty + ) => { + godot_wrap_method_inner!( + $type_name, + map_mut_aliased, + fn $method_name( + $self, + $owner: $owner_ty + $(,$pname : $pty)* + ) -> $retty + ) + }; + // immutable ( $type_name:ty, fn $method_name:ident( - &self, + & $self:ident, $owner:ident : $owner_ty:ty $(,$pname:ident : $pty:ty)* ) -> $retty:ty ) => { - godot_wrap_method!( + godot_wrap_method_inner!( $type_name, + map_aliased, fn $method_name( - &mut self, - $owner : $owner_ty + $self, + $owner: $owner_ty $(,$pname : $pty)* ) -> $retty ) }; - // non mut self without return type + // mutable without return type ( $type_name:ty, fn $method_name:ident( - &self, + &mut $self:ident, $owner:ident : $owner_ty:ty $(,$pname:ident : $pty:ty)* ) @@ -468,17 +492,17 @@ macro_rules! godot_wrap_method { godot_wrap_method!( $type_name, fn $method_name( - &mut self, - $owner : $owner_ty + &mut $self, + $owner: $owner_ty $(,$pname : $pty)* ) -> () ) }; - // without return type + // immutable without return type ( $type_name:ty, fn $method_name:ident( - &mut self, + & $self:ident, $owner:ident : $owner_ty:ty $(,$pname:ident : $pty:ty)* ) @@ -486,8 +510,8 @@ macro_rules! godot_wrap_method { godot_wrap_method!( $type_name, fn $method_name( - &mut self, - $owner : $owner_ty + & $self, + $owner: $owner_ty $(,$pname : $pty)* ) -> () ) diff --git a/gdnative-core/src/object.rs b/gdnative-core/src/object.rs index ab30a4d84..c8ac6bba9 100644 --- a/gdnative-core/src/object.rs +++ b/gdnative-core/src/object.rs @@ -12,6 +12,11 @@ pub unsafe trait GodotObject { unsafe fn from_sys(obj: *mut sys::godot_object) -> Self; } +/// GodotObjects that have a zero argument constructor. +pub trait Instanciable: GodotObject { + fn construct() -> Self; +} + // This function assumes the godot_object is reference counted. pub unsafe fn add_ref(obj: *mut sys::godot_object) { use crate::ReferenceMethodTable; diff --git a/gdnative-core/src/user_data.rs b/gdnative-core/src/user_data.rs new file mode 100644 index 000000000..c366b009d --- /dev/null +++ b/gdnative-core/src/user_data.rs @@ -0,0 +1,390 @@ +//! Customizable user-data wrappers. +//! +//! ## `NativeClass` and user-data +//! +//! In Godot Engine, scripted behavior is attached to base objects through "script instances": +//! objects that store script state, and allow dynamic dispatch of overridden methods. GDNative +//! exposes this to native languages as a `void *` pointer, known as "user-data", that may point +//! to anything defined by the native library in question. +//! +//! Godot is written in C++, and unlike Rust, it doesn't have the same strict reference aliasing +//! constraints. This user-data pointer can be aliased mutably, and called freely from different +//! threads by the engine or other scripts. Thus, to maintain safety, wrapper types are be used +//! to make sure that the Rust rules for references are always held for the `self` argument, and +//! no UB can occur because we freed `owner` or put another script on it. +//! +//! ## Which wrapper to use? +//! +//! ### Use a `MutexData` when: +//! +//! - You don't want to handle locks explicitly. +//! - Your `NativeClass` type is only `Send`, but not `Sync`. +//! +//! ### Use a `RwLockData` when: +//! +//! - You don't want to handle locks explicitly. +//! - Some of your exported methods take `&self`, and you don't need them to be exclusive. +//! - Your `NativeClass` type is `Send + Sync`. +//! +//! ### Use a `ArcData` when: +//! +//! - You want safety for your methods, but can't tolerate lock overhead on each method call. +//! - You want fine grained lock control for parallelism. +//! - All your exported methods take `&self`. +//! - Your `NativeClass` type is `Send + Sync`. + +use std::mem; +use std::fmt::Debug; +use std::sync::Arc; +use std::time::Duration; +use std::marker::PhantomData; +use parking_lot::{Mutex, RwLock}; + +use crate::NativeClass; + +/// Trait for customizable user-data wrappers. +/// +/// See module-level documentation for detailed explanation on user-data. +pub unsafe trait UserData: Sized + Clone { + type Target: NativeClass; + + /// Creates a new owned wrapper from a `NativeClass` instance. + fn new(val: Self::Target) -> Self; + + /// Takes a native instance and returns an opaque pointer that can be used to recover it. + /// + /// This gives "ownership" to the engine. + unsafe fn into_user_data(self) -> *const libc::c_void; + + /// Takes an opaque pointer produced by `into_user_data` and "consumes" it to produce the + /// original instance, keeping the reference count. + /// + /// This should be used when "ownership" is taken from the engine, i.e. destructors. + /// Use elsewhere can lead to premature drops of the instance contained inside. + unsafe fn consume_user_data_unchecked(ptr: *const libc::c_void) -> Self; + + /// Takes an opaque pointer produced by `into_user_data` and "clones" it to produce the + /// original instance, increasing the reference count. + /// + /// This should be used when user data is "borrowed" from the engine. + unsafe fn clone_from_user_data_unchecked(ptr: *const libc::c_void) -> Self; +} + +/// Trait for wrappers that can be mapped immutably. +pub trait Map: UserData { + type Err: Debug; + + /// Maps a `&T` to `U`. Called for methods that take `&self`. + fn map(&self, op: F) -> Result + where + F: FnOnce(&Self::Target) -> U; +} + +/// Trait for wrappers that can be mapped mutably. +pub trait MapMut: UserData { + type Err: Debug; + + /// Maps a `&mut T` to `U`. Called for methods that take `&mut self`. + fn map_mut(&self, op: F) -> Result + where + F: FnOnce(&mut Self::Target) -> U; +} + +/// The default user data wrapper used by derive macro, when no `user_data` attribute is present. +/// This may change in the future. +pub type DefaultUserData = MutexData; + +/// Error type indicating that an operation can't fail. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub enum Infallible { } + +/// Policies to deal with potential deadlocks +/// +/// As Godot allows mutable pointer aliasing, doing certain things in exported method bodies may +/// lead to the engine calling another method on `owner`, leading to another locking attempt +/// within the same thread: +/// +/// - Variant calls on anything may dispatch to a script method. +/// - Anything that could emit signals, that are connected to in a non-deferred manner. +/// +/// As there is no universal way to deal with such situations, behavior of locking wrappers can +/// be customized using this enum. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub enum DeadlockPolicy { + /// Block on all locks. Deadlocks are possible. + Allow, + + /// Never block on any locks. Methods will return Nil immediately if the lock isn't + /// available. Deadlocks are prevented. + Pessimistic, + + /// Block on locks for at most `Duration`. Methods return Nil on timeout. Deadlocks are + /// prevented. + Timeout(Duration), +} + +/// Trait defining associated constants for locking wrapper options +/// +/// This is required because constant generics ([RFC 2000][rfc-2000]) isn't available in stable +/// rust yet. +/// +/// See also `DeadlockPolicy`. +/// +/// [rfc-2000]: https://github.com/rust-lang/rfcs/blob/master/text/2000-const-generics.md +pub trait LockOptions { + const DEADLOCK_POLICY: DeadlockPolicy; +} + +/// Default lock policy that may change in future versions. +/// +/// Currently, it has a deadlock policy of `Allow`. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] +pub struct DefaultLockPolicy; + +impl LockOptions for DefaultLockPolicy { + const DEADLOCK_POLICY: DeadlockPolicy = DeadlockPolicy::Allow; +} + +/// Error indicating that a lock wasn't obtained. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] +pub struct LockFailed; + +/// User-data wrapper encapsulating a `Arc>`. +/// +/// The underlying `Mutex` may change in the future. The current implementation is +/// `parking_lot`. +#[derive(Debug)] +pub struct MutexData { + lock: Arc>, + _marker: PhantomData<*const OPT>, +} + +unsafe impl UserData for MutexData +where + T: NativeClass + Send, + OPT: LockOptions, +{ + type Target = T; + + fn new(val: Self::Target) -> Self { + MutexData { + lock: Arc::new(Mutex::new(val)), + _marker: PhantomData, + } + } + + unsafe fn into_user_data(self) -> *const libc::c_void { + Arc::into_raw(self.lock) as *const libc::c_void + } + + unsafe fn consume_user_data_unchecked(ptr: *const libc::c_void) -> Self { + MutexData { + lock: Arc::from_raw(ptr as *const Mutex), + _marker: PhantomData, + } + } + + unsafe fn clone_from_user_data_unchecked(ptr: *const libc::c_void) -> Self { + let borrowed = Arc::from_raw(ptr as *const Mutex); + let lock = borrowed.clone(); + mem::forget(borrowed); + MutexData { + lock, + _marker: PhantomData, + } + } +} + +impl Map for MutexData +where + T: NativeClass + Send, + OPT: LockOptions, +{ + type Err = LockFailed; + + fn map(&self, op: F) -> Result + where + F: FnOnce(&T) -> U, + { + self.map_mut(|val| op(val)) + } +} + + +impl MapMut for MutexData +where + T: NativeClass + Send, + OPT: LockOptions, +{ + type Err = LockFailed; + + fn map_mut(&self, op: F) -> Result + where + F: FnOnce(&mut T) -> U, + { + let mut guard = match OPT::DEADLOCK_POLICY { + DeadlockPolicy::Allow => self.lock.lock(), + DeadlockPolicy::Pessimistic => self.lock.try_lock().ok_or(LockFailed)?, + DeadlockPolicy::Timeout(dur) => self.lock.try_lock_for(dur).ok_or(LockFailed)?, + }; + + Ok(op(&mut *guard)) + } +} + +impl Clone for MutexData { + fn clone(&self) -> Self { + MutexData { + lock: self.lock.clone(), + _marker: PhantomData, + } + } +} + +/// User-data wrapper encapsulating a `Arc>`. +/// +/// The underlying `RwLock` may change in the future. The current implementation is +/// `parking_lot`. +#[derive(Debug)] +pub struct RwLockData { + lock: Arc>, + _marker: PhantomData<*const OPT>, +} + +unsafe impl UserData for RwLockData +where + T: NativeClass + Send + Sync, + OPT: LockOptions, +{ + type Target = T; + + fn new(val: Self::Target) -> Self { + RwLockData { + lock: Arc::new(RwLock::new(val)), + _marker: PhantomData, + } + } + + unsafe fn into_user_data(self) -> *const libc::c_void { + Arc::into_raw(self.lock) as *const libc::c_void + } + + unsafe fn consume_user_data_unchecked(ptr: *const libc::c_void) -> Self { + RwLockData { + lock: Arc::from_raw(ptr as *const RwLock), + _marker: PhantomData, + } + } + + unsafe fn clone_from_user_data_unchecked(ptr: *const libc::c_void) -> Self { + let borrowed = Arc::from_raw(ptr as *const RwLock); + let lock = borrowed.clone(); + mem::forget(borrowed); + RwLockData { + lock, + _marker: PhantomData, + } + } +} + +impl Map for RwLockData +where + T: NativeClass + Send + Sync, + OPT: LockOptions, +{ + type Err = LockFailed; + + fn map(&self, op: F) -> Result + where + F: FnOnce(&T) -> U, + { + let guard = match OPT::DEADLOCK_POLICY { + DeadlockPolicy::Allow => self.lock.read(), + DeadlockPolicy::Pessimistic => self.lock.try_read().ok_or(LockFailed)?, + DeadlockPolicy::Timeout(dur) => self.lock.try_read_for(dur).ok_or(LockFailed)?, + }; + + Ok(op(&*guard)) + } +} + + +impl MapMut for RwLockData +where + T: NativeClass + Send + Sync, + OPT: LockOptions, +{ + type Err = LockFailed; + + fn map_mut(&self, op: F) -> Result + where + F: FnOnce(&mut T) -> U, + { + let mut guard = match OPT::DEADLOCK_POLICY { + DeadlockPolicy::Allow => self.lock.write(), + DeadlockPolicy::Pessimistic => self.lock.try_write().ok_or(LockFailed)?, + DeadlockPolicy::Timeout(dur) => self.lock.try_write_for(dur).ok_or(LockFailed)?, + }; + + Ok(op(&mut *guard)) + } +} + +impl Clone for RwLockData { + fn clone(&self) -> Self { + RwLockData { + lock: self.lock.clone(), + _marker: PhantomData, + } + } +} + +/// User-data wrapper encapsulating a `Arc`. Does not implement `MapMut`. +#[derive(Debug)] +pub struct ArcData(Arc); + +unsafe impl UserData for ArcData +where + T: NativeClass + Send + Sync, +{ + type Target = T; + + fn new(val: Self::Target) -> Self { + ArcData(Arc::new(val)) + } + + unsafe fn into_user_data(self) -> *const libc::c_void { + Arc::into_raw(self.0) as *const libc::c_void + } + + unsafe fn consume_user_data_unchecked(ptr: *const libc::c_void) -> Self { + ArcData(Arc::from_raw(ptr as *const T)) + } + + unsafe fn clone_from_user_data_unchecked(ptr: *const libc::c_void) -> Self { + let borrowed = Arc::from_raw(ptr as *const T); + let arc = borrowed.clone(); + mem::forget(borrowed); + ArcData(arc) + } +} + +impl Map for ArcData +where + T: NativeClass + Send + Sync, +{ + type Err = Infallible; + + fn map(&self, op: F) -> Result + where + F: FnOnce(&T) -> U, + { + Ok(op(&*self.0)) + } +} + +impl Clone for ArcData { + fn clone(&self) -> Self { + ArcData(self.0.clone()) + } +} \ No newline at end of file diff --git a/gdnative-derive/src/derive_macro.rs b/gdnative-derive/src/derive_macro.rs index aafd5b510..587c5fdff 100644 --- a/gdnative-derive/src/derive_macro.rs +++ b/gdnative-derive/src/derive_macro.rs @@ -4,6 +4,7 @@ use syn::{Data, DeriveInput, Fields, Ident, Type}; pub(crate) struct DeriveData { pub(crate) name: Ident, pub(crate) base: Type, + pub(crate) user_data: Type, } pub(crate) fn parse_derive_input(input: TokenStream) -> DeriveData { @@ -26,6 +27,19 @@ pub(crate) fn parse_derive_input(input: TokenStream) -> DeriveData { let base = syn::parse::(inherit_attr.tts.clone().into()) .expect("`inherits` attribute requires the base type as an argument."); + let user_data = input + .attrs + .iter() + .find(|a| a.path.segments[0].ident == "user_data") + .map(|attr| { + syn::parse::(attr.tts.clone().into()) + .expect("`userdata` attribute requires a type as an argument.") + }) + .unwrap_or_else(|| { + syn::parse::(quote! { ::gdnative::user_data::DefaultUserData<#ident> }.into()) + .expect("quoted tokens should be a valid type") + }); + // make sure it's a struct let struct_data = if let Data::Struct(data) = input.data { data @@ -38,5 +52,5 @@ pub(crate) fn parse_derive_input(input: TokenStream) -> DeriveData { // TODO } - DeriveData { name: ident, base } + DeriveData { name: ident, base, user_data } } diff --git a/gdnative-derive/src/lib.rs b/gdnative-derive/src/lib.rs index 9f8a76773..c121caa8f 100644 --- a/gdnative-derive/src/lib.rs +++ b/gdnative-derive/src/lib.rs @@ -54,7 +54,7 @@ pub fn methods(meta: TokenStream, input: TokenStream) -> TokenStream { TokenStream::from(output) } -#[proc_macro_derive(NativeClass, attributes(inherit, export))] +#[proc_macro_derive(NativeClass, attributes(inherit, export, user_data))] pub fn derive_native_class(input: TokenStream) -> TokenStream { let data = derive_macro::parse_derive_input(input.clone()); @@ -62,6 +62,7 @@ pub fn derive_native_class(input: TokenStream) -> TokenStream { let trait_impl = { let name = data.name; let base = data.base; + let user_data = data.user_data; // string variant needed for the `class_name` function. let name_str = quote!(#name).to_string(); @@ -69,6 +70,7 @@ pub fn derive_native_class(input: TokenStream) -> TokenStream { quote!( impl gdnative::NativeClass for #name { type Base = #base; + type UserData = #user_data; fn class_name() -> &'static str { #name_str diff --git a/test/src/lib.rs b/test/src/lib.rs index 1ab4fbc5d..ba7207112 100644 --- a/test/src/lib.rs +++ b/test/src/lib.rs @@ -1,4 +1,6 @@ use gdnative::*; +use std::sync::Arc; +use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; #[no_mangle] pub extern "C" fn run_tests( @@ -24,6 +26,9 @@ pub extern "C" fn run_tests( status &= test_constructor(); status &= test_underscore_method_binding(); + status &= test_rust_class_construction(); + status &= test_owner_free_ub(); + gdnative::Variant::from_bool(status).forget() } @@ -60,6 +65,133 @@ fn test_underscore_method_binding() -> bool { ok } +struct Foo(i64); + +impl NativeClass for Foo { + type Base = Reference; + type UserData = user_data::ArcData; + fn class_name() -> &'static str { + "Foo" + } + fn init(_owner: Reference) -> Foo { + Foo(42) + } + fn register_properties(_builder: &init::ClassBuilder) { + } +} + +#[methods] +impl Foo { + #[export] + fn answer(&self, _owner: Reference) -> i64 { + self.0 + } +} + +fn test_rust_class_construction() -> bool { + println!(" -- test_rust_class_construction"); + + let ok = std::panic::catch_unwind(|| { + let foo = Instance::::new(); + assert_eq!(Ok(42), foo.map(|foo, owner| { + foo.answer(owner) + })); + assert_eq!(Some(42), unsafe { foo.into_base().call("answer".into(), &[]) }.try_to_i64()); + }).is_ok(); + + if !ok { + godot_error!(" !! Test test_rust_class_construction failed"); + } + + ok +} + +struct Bar(i64, Option>); + +impl NativeClass for Bar { + type Base = Node; + type UserData = user_data::RwLockData; + fn class_name() -> &'static str { + "Bar" + } + fn init(_owner: Node) -> Bar { + Bar(42, None) + } + fn register_properties(_builder: &init::ClassBuilder) { + } +} + +impl Bar { + fn set_drop_counter(&mut self, counter: Arc) { + self.1 = Some(counter); + } +} + +#[methods] +impl Bar { + #[export] + fn free_is_not_ub(&mut self, owner: Node) -> bool { + unsafe { + owner.free(); + } + assert_eq!(42, self.0, "self should not point to garbage"); + true + } + + #[export] + fn set_script_is_not_ub(&mut self, mut owner: Node) -> bool { + unsafe { + owner.set_script(None); + } + assert_eq!(42, self.0, "self should not point to garbage"); + true + } +} + +impl Drop for Bar { + fn drop(&mut self) { + let counter = self.1.take().expect("drop counter should be set"); + counter.fetch_add(1, AtomicOrdering::AcqRel); + self.0 = 0; + } +} + +fn test_owner_free_ub() -> bool { + println!(" -- test_owner_free_ub"); + + let ok = std::panic::catch_unwind(|| { + let drop_counter = Arc::new(AtomicUsize::new(0)); + + let bar = Instance::::new(); + unsafe { + bar.map_mut_aliased(|bar, _| bar.set_drop_counter(drop_counter.clone())).expect("lock should not fail"); + let mut base = bar.into_base(); + assert_eq!(Some(true), base.call("set_script_is_not_ub".into(), &[]).try_to_bool()); + base.free(); + } + + let bar = Instance::::new(); + unsafe { + bar.map_mut_aliased(|bar, _| bar.set_drop_counter(drop_counter.clone())).expect("lock should not fail"); + assert_eq!(Some(true), bar.into_base().call("free_is_not_ub".into(), &[]).try_to_bool()); + } + + // the values are eventually dropped + assert_eq!(2, drop_counter.load(AtomicOrdering::Acquire)); + }).is_ok(); + + if !ok { + godot_error!(" !! Test test_owner_free_ub failed"); + } + + ok +} + +fn init(handle: init::InitHandle) { + handle.add_class::(); + handle.add_class::(); +} + godot_gdnative_init!(); -godot_nativescript_init!(); +godot_nativescript_init!(init); godot_gdnative_terminate!();