Skip to content

Commit cd59838

Browse files
bors[bot]toasteater
andauthored
Merge #622
622: Implement emplacement construction of instances r=toasteater a=toasteater This adds the `Instance::emplace` constructor, that allows users to construct `Instance`s from existing values. The `NativeClass` trait and its derive macro are modifed to allow script types without zero-argument constructors: - A default implementation that panics is added for `NativeClass::init` - A `#[no_constructor]` attribute is added to the derive macro. - Additionally, doc-comments are added to the `NativeClass` macro, with explanations and examples for the available attributes. Close #621 Co-authored-by: toasteater <[email protected]>
2 parents 97a0b41 + b0e23a7 commit cd59838

File tree

9 files changed

+299
-45
lines changed

9 files changed

+299
-45
lines changed

gdnative-core/src/nativescript/class.rs

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,17 @@ use crate::private::{get_api, ReferenceCountedClassPlaceholder};
1616
use crate::ref_kind::{ManuallyManaged, RefCounted};
1717
use crate::thread_access::{NonUniqueThreadAccess, Shared, ThreadAccess, ThreadLocal, Unique};
1818

19+
use super::emplace;
20+
1921
/// Trait used for describing and initializing a Godot script class.
2022
///
2123
/// This trait is used to provide data and functionality to the
2224
/// "data-part" of the class, such as name, initialization and information
2325
/// about exported properties.
2426
///
27+
/// A derive macro is available for this trait. See documentation on the
28+
/// `NativeClass` macro for detailed usage and examples.
29+
///
2530
/// For exported methods, see the [`NativeClassMethods`] trait.
2631
///
2732
/// [`NativeClassMethods`]: ./trait.NativeClassMethods.html
@@ -59,11 +64,22 @@ pub trait NativeClass: Sized + 'static {
5964
/// To identify which class has to be used, a library-unique name has to be given.
6065
fn class_name() -> &'static str;
6166

62-
/// Function that creates a value of `Self`, used for the script-instance.
67+
/// Function that creates a value of `Self`, used for the script-instance. The default
68+
/// implementation simply panics.
6369
///
6470
/// This function has a reference to the owner object as a parameter, which can be used to
6571
/// set state on the owner upon creation or to query values
66-
fn init(owner: TRef<'_, Self::Base, Shared>) -> Self;
72+
///
73+
/// It is possible to declare script classes without zero-argument constructors. Instances
74+
/// of such scripts can only be created from Rust using `Instance::emplace`. See
75+
/// documentation on `Instance::emplace` for an example.
76+
#[inline]
77+
fn init(_owner: TRef<'_, Self::Base, Shared>) -> Self {
78+
panic!(
79+
"{} does not have a zero-argument constructor",
80+
Self::class_name()
81+
)
82+
}
6783

6884
/// Register any exported properties to Godot.
6985
#[inline]
@@ -84,6 +100,22 @@ pub trait NativeClass: Sized + 'static {
84100
{
85101
Instance::new()
86102
}
103+
104+
/// Convenience method to emplace `self` into an `Instance<Self, Unique>`. This is a new
105+
/// `Self::Base` with the script attached.
106+
///
107+
/// If `Self::Base` is manually-managed, then the resulting `Instance` must be passed to
108+
/// the engine or manually freed with `Instance::free`. Otherwise, the base object will be
109+
/// leaked.
110+
///
111+
/// Must be called after the library is initialized.
112+
#[inline]
113+
fn emplace(self) -> Instance<Self, Unique>
114+
where
115+
Self::Base: Instanciable,
116+
{
117+
Instance::emplace(self)
118+
}
87119
}
88120

89121
/// Trait used to provide information of Godot-exposed methods of a script class.
@@ -171,6 +203,61 @@ impl<T: NativeClass> Instance<T, Unique> {
171203
#[inline]
172204
#[allow(clippy::new_without_default)]
173205
pub fn new() -> Self
206+
where
207+
T::Base: Instanciable,
208+
{
209+
Self::maybe_emplace(None)
210+
}
211+
212+
/// Creates a `T::Base` with a given instance of the script `T` attached. `T::Base` must
213+
/// have a zero-argument constructor.
214+
///
215+
/// This may be used to create instances of scripts that do not have zero-argument
216+
/// constructors:
217+
///
218+
/// ```ignore
219+
/// // This type does not have a zero-argument constructor. As a result, `Instance::new`
220+
/// // will panic and `Foo.new` from GDScript will result in errors when the object is used.
221+
/// #[derive(NativeScript)]
222+
/// #[inherit(Reference)]
223+
/// #[no_constructor]
224+
/// struct MyIntVector(i64, i64);
225+
///
226+
/// #[methods]
227+
/// impl MyIntVector {
228+
/// // - snip -
229+
/// }
230+
///
231+
/// // With `Instance::emplace`, however, we can expose "constructors" from a factory
232+
/// // auto-load for our script type.
233+
/// #[derive(NativeScript)]
234+
/// #[inherit(Node)]
235+
/// #[user_data(Aether<Self>)]
236+
/// struct MyIntVectorFactory;
237+
///
238+
/// #[methods]
239+
/// impl MyIntVectorFactory {
240+
/// #[export]
241+
/// fn make(&self, _owner: &Node, x: i64, y: i64) -> Instance<MyIntVector, Unique> {
242+
/// Instance::emplace(MyIntVector(x, y))
243+
/// }
244+
/// }
245+
/// ```
246+
///
247+
/// If `T::Base` is manually-managed, then the resulting `Instance` must be passed to
248+
/// the engine or manually freed with `Instance::free`. Otherwise, the base object will be
249+
/// leaked.
250+
///
251+
/// Must be called after the library is initialized.
252+
#[inline]
253+
pub fn emplace(script: T) -> Self
254+
where
255+
T::Base: Instanciable,
256+
{
257+
Self::maybe_emplace(Some(script))
258+
}
259+
260+
fn maybe_emplace(script: Option<T>) -> Self
174261
where
175262
T::Base: Instanciable,
176263
{
@@ -223,6 +310,10 @@ impl<T: NativeClass> Instance<T, Unique> {
223310
std::ptr::null_mut(),
224311
);
225312

313+
if let Some(script) = script {
314+
emplace::place(script);
315+
}
316+
226317
let mut args: [*const sys::godot_variant; 0] = [];
227318
let variant = (gd_api.godot_method_bind_call)(
228319
nativescript_methods.new,
@@ -232,6 +323,11 @@ impl<T: NativeClass> Instance<T, Unique> {
232323
std::ptr::null_mut(),
233324
);
234325

326+
assert!(
327+
emplace::take::<T>().is_none(),
328+
"emplacement value should be taken by the constructor wrapper (this is a bug in the bindings)",
329+
);
330+
235331
let variant = Variant::from_sys(variant);
236332

237333
let owner = variant
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//! Support code for script emplacement.
2+
3+
use std::any::Any;
4+
use std::cell::RefCell;
5+
6+
use super::NativeClass;
7+
8+
thread_local! {
9+
static CELL: RefCell<Option<Box<dyn Any>>> = RefCell::default();
10+
}
11+
12+
/// Place a script to be taken by the emplacement constructor. Must be called
13+
/// directly before `NativeScript::_new` for intended behavior.
14+
///
15+
/// # Panics
16+
///
17+
/// If there is already a value placed for this thread, or if the thread is
18+
/// exiting. This is always a bug in the bindings.
19+
pub fn place<T: NativeClass>(script: T) {
20+
CELL.with(|f| {
21+
if f.replace(Some(Box::new(script))).is_some() {
22+
panic!(
23+
"there is already a value in the emplacement cell (this is a bug in the bindings)"
24+
);
25+
}
26+
});
27+
}
28+
29+
/// Take the script stored for emplacement and return it. Returns `None` if
30+
/// there is no value in store.
31+
///
32+
/// # Panics
33+
///
34+
/// If there is a value in store but it is of the incorrect type. This is always
35+
/// a bug in the bindings.
36+
pub fn take<T: NativeClass>() -> Option<T> {
37+
CELL.with(|f| f.borrow_mut().take())
38+
.map(|script| match script.downcast() {
39+
Ok(script) => *script,
40+
Err(any) => panic!(
41+
"expecting {} in the emplacement cell, got {:?} (this is a bug in the bindings)",
42+
T::class_name(),
43+
any.type_id(),
44+
),
45+
})
46+
}

gdnative-core/src/nativescript/init.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ use crate::nativescript::NativeClassMethods;
4343
use crate::nativescript::UserData;
4444
use crate::private::get_api;
4545

46+
use super::emplace;
47+
4648
pub mod property;
4749

4850
pub use self::property::{Export, ExportInfo, PropertyBuilder, Usage as PropertyUsage};
@@ -123,7 +125,8 @@ impl InitHandle {
123125
};
124126

125127
let val = match panic::catch_unwind(AssertUnwindSafe(|| {
126-
C::init(TRef::new(C::Base::cast_ref(owner)))
128+
emplace::take()
129+
.unwrap_or_else(|| C::init(TRef::new(C::Base::cast_ref(owner))))
127130
})) {
128131
Ok(val) => val,
129132
Err(_) => {

gdnative-core/src/nativescript/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Types and functions related to the NativeScript extension of GDNative.
22
3+
mod emplace;
34
mod macros;
45

56
pub mod class;

gdnative-derive/src/lib.rs

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,91 @@ pub fn profiled(meta: TokenStream, input: TokenStream) -> TokenStream {
5252
profiled::derive_profiled(meta, input)
5353
}
5454

55+
/// Makes it possible to use a type as a NativeScript.
56+
///
57+
/// ## Required attributes
58+
///
59+
/// The following attributes are required on the type deriving `NativeClass`:
60+
///
61+
/// ### `#[inherit(gdnative::api::BaseClass)]`
62+
///
63+
/// Sets `gdnative::api::BaseClass` as the base class for the script. This *must* be
64+
/// a type from the generated Godot API (that implements `GodotObject`). All `owner`
65+
/// arguments of exported methods must be references (`TRef`, `Ref`, or `&`) to this
66+
/// type.
67+
///
68+
/// Inheritance from other scripts, either in Rust or other languages, is
69+
/// not supported.
70+
///
71+
/// ## Optional type attributes
72+
///
73+
/// Behavior of the derive macro can be customized using attribute on the type:
74+
///
75+
/// ### `#[user_data(gdnative::user_data::SomeWrapper<Self>)]`
76+
///
77+
/// Use the given type as the user-data wrapper. See the module-level docs on
78+
/// `gdnative::user_data` for more information.
79+
///
80+
/// ### `#[register_with(path::to::function)]`
81+
///
82+
/// Use a custom function to register signals, properties or methods, in addition
83+
/// to the one generated by `#[methods]`:
84+
///
85+
/// ```ignore
86+
/// #[derive(NativeClass)]
87+
/// #[inherit(Reference)]
88+
/// #[register_with(my_register_function)]
89+
/// struct Foo;
90+
///
91+
/// fn my_register_function(builder: &ClassBuilder<Foo>) {
92+
/// builder.add_signal(Signal { name: "foo", args: &[] });
93+
/// builder.add_property::<f32>("bar")
94+
/// .with_getter(|_, _| 42.0)
95+
/// .with_hint(FloatHint::Range(RangeHint::new(0.0, 100.0)))
96+
/// .done();
97+
/// }
98+
/// ```
99+
///
100+
/// ### `#[no_constructor]`
101+
///
102+
/// Indicates that this type has no zero-argument constructor. Instances of such
103+
/// scripts can only be created from Rust using `Instance::emplace`. `Instance::new`
104+
/// or `ScriptName.new` from GDScript will result in panics at runtime.
105+
///
106+
/// See documentation on `Instance::emplace` for an example on how this can be used.
107+
///
108+
/// ## Optional field attributes
109+
///
110+
/// ### `#[property]`
111+
///
112+
/// Convenience attribute to register a field as a property. Possible arguments for
113+
/// the attribute are:
114+
///
115+
/// - `path = "my_category/my_property_name"`
116+
///
117+
/// Puts the property under the `my_category` category and renames it to
118+
/// `my_property_name` in the inspector and for GDScript.
119+
///
120+
/// - `default = 42.0`
121+
///
122+
/// Sets the default value *in the inspector* for this property. The setter is *not*
123+
/// guaranteed to be called by the engine with the value.
124+
///
125+
/// - `before_get` / `after_get` / `before_set` / `after_set` `= "Self::hook_method"`
126+
///
127+
/// Call hook methods with `self` and `owner` before and/or after the generated property
128+
/// accessors.
55129
#[proc_macro_derive(
56130
NativeClass,
57-
attributes(inherit, export, opt, user_data, property, register_with)
131+
attributes(
132+
inherit,
133+
export,
134+
opt,
135+
user_data,
136+
property,
137+
register_with,
138+
no_constructor
139+
)
58140
)]
59141
pub fn derive_native_class(input: TokenStream) -> TokenStream {
60142
native_script::derive_native_class(input)

gdnative-derive/src/native_script.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub(crate) struct DeriveData {
1212
pub(crate) register_callback: Option<Path>,
1313
pub(crate) user_data: Type,
1414
pub(crate) properties: HashMap<Ident, PropertyAttrArgs>,
15+
pub(crate) no_constructor: bool,
1516
}
1617

1718
pub(crate) fn derive_native_class(input: TokenStream) -> TokenStream {
@@ -74,6 +75,16 @@ pub(crate) fn derive_native_class(input: TokenStream) -> TokenStream {
7475
// string variant needed for the `class_name` function.
7576
let name_str = quote!(#name).to_string();
7677

78+
let init = if data.no_constructor {
79+
None
80+
} else {
81+
Some(quote! {
82+
fn init(owner: ::gdnative::TRef<Self::Base>) -> Self {
83+
Self::new(::gdnative::nativescript::OwnerArg::from_safe_ref(owner))
84+
}
85+
})
86+
};
87+
7788
quote!(
7889
impl ::gdnative::nativescript::NativeClass for #name {
7990
type Base = #base;
@@ -83,9 +94,7 @@ pub(crate) fn derive_native_class(input: TokenStream) -> TokenStream {
8394
#name_str
8495
}
8596

86-
fn init(owner: ::gdnative::TRef<Self::Base>) -> Self {
87-
Self::new(::gdnative::nativescript::OwnerArg::from_safe_ref(owner))
88-
}
97+
#init
8998

9099
fn register_properties(builder: &::gdnative::nativescript::init::ClassBuilder<Self>) {
91100
#(#properties)*;
@@ -146,6 +155,11 @@ fn parse_derive_input(input: TokenStream) -> Result<DeriveData, TokenStream> {
146155
.expect("quoted tokens for default userdata should be a valid type"))
147156
})?;
148157

158+
let no_constructor = input
159+
.attrs
160+
.iter()
161+
.any(|a| a.path.is_ident("no_constructor"));
162+
149163
// make sure it's a struct
150164
let struct_data = if let Data::Struct(data) = input.data {
151165
data
@@ -212,5 +226,6 @@ fn parse_derive_input(input: TokenStream) -> Result<DeriveData, TokenStream> {
212226
register_callback,
213227
user_data,
214228
properties,
229+
no_constructor,
215230
})
216231
}

0 commit comments

Comments
 (0)