Skip to content

Commit c464b49

Browse files
author
toasteater
committed
Add safe high-level interface for stateful methods
This lifts the unsafe FFI code out of macros and into proper library code as a generic function. Stateful methods are also properly supported using the `Method` trait. This is the second step in simplifying the `godot_wrap_method` macro.
1 parent e232b80 commit c464b49

File tree

4 files changed

+206
-87
lines changed

4 files changed

+206
-87
lines changed

gdnative-core/src/nativescript/init.rs

Lines changed: 6 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ use super::emplace;
4848
pub mod method;
4949
pub mod property;
5050

51+
pub use self::method::{Method, Varargs, RpcMode, ScriptMethod, MethodBuilder, ScriptMethodAttributes, ScriptMethodFn};
5152
pub use self::property::{Export, ExportInfo, PropertyBuilder, Usage as PropertyUsage};
5253

5354
/// A handle that can register new classes to the engine during initialization.
@@ -213,37 +214,6 @@ impl InitHandle {
213214
}
214215
}
215216

216-
pub type ScriptMethodFn = unsafe extern "C" fn(
217-
*mut sys::godot_object,
218-
*mut libc::c_void,
219-
*mut libc::c_void,
220-
libc::c_int,
221-
*mut *mut sys::godot_variant,
222-
) -> sys::godot_variant;
223-
224-
pub enum RpcMode {
225-
Disabled,
226-
Remote,
227-
RemoteSync,
228-
Master,
229-
Puppet,
230-
MasterSync,
231-
PuppetSync,
232-
}
233-
234-
pub struct ScriptMethodAttributes {
235-
pub rpc_mode: RpcMode,
236-
}
237-
238-
pub struct ScriptMethod<'l> {
239-
pub name: &'l str,
240-
pub method_ptr: Option<ScriptMethodFn>,
241-
pub attributes: ScriptMethodAttributes,
242-
243-
pub method_data: *mut libc::c_void,
244-
pub free_func: Option<unsafe extern "C" fn(*mut libc::c_void) -> ()>,
245-
}
246-
247217
#[derive(Debug)]
248218
pub struct ClassBuilder<C> {
249219
init_handle: *mut libc::c_void,
@@ -301,6 +271,11 @@ impl<C: NativeClass> ClassBuilder<C> {
301271
self.add_method_with_rpc_mode(name, method, RpcMode::Disabled);
302272
}
303273

274+
#[inline]
275+
pub fn build_method<'a, F: Method<C>>(&'a self, name: &'a str, method: F) -> MethodBuilder<'a, C, F> {
276+
MethodBuilder::new(self, name, method)
277+
}
278+
304279
/// Returns a `PropertyBuilder` which can be used to add a property to the class being
305280
/// registered.
306281
///

gdnative-core/src/nativescript/init/method.rs

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,139 @@
11
use std::fmt;
22
use std::marker::PhantomData;
33

4+
use crate::thread_access::Shared;
5+
use crate::nativescript::class::{NativeClass, RefInstance};
46
use crate::core_types::{FromVariant, FromVariantError, Variant};
7+
use crate::object::{Ref, TRef};
8+
9+
use super::ClassBuilder;
10+
11+
pub struct MethodBuilder<'a, C, F> {
12+
class_builder: &'a super::ClassBuilder<C>,
13+
name: &'a str,
14+
method: F,
15+
16+
rpc_mode: RpcMode,
17+
}
18+
19+
impl<'a, C, F> MethodBuilder<'a, C, F>
20+
where
21+
C: NativeClass,
22+
F: Method<C>,
23+
{
24+
pub(super) fn new(class_builder: &'a ClassBuilder<C>, name: &'a str, method: F) -> Self {
25+
MethodBuilder {
26+
class_builder,
27+
name,
28+
method,
29+
rpc_mode: RpcMode::Disabled,
30+
}
31+
}
32+
33+
/// Set a RPC mode for this method.
34+
#[inline]
35+
pub fn with_rpc_mode(mut self, rpc_mode: RpcMode) -> Self {
36+
self.rpc_mode = rpc_mode;
37+
self
38+
}
39+
40+
/// Register the method.
41+
#[inline]
42+
pub fn done(self) {
43+
let method_data = Box::into_raw(Box::new(self.method));
44+
45+
let script_method = ScriptMethod {
46+
name: self.name,
47+
method_ptr: Some(method_wrapper::<C, F>),
48+
attributes: ScriptMethodAttributes {
49+
rpc_mode: self.rpc_mode,
50+
},
51+
method_data: method_data as *mut libc::c_void,
52+
free_func: Some(free_func::<F>),
53+
};
54+
55+
self.class_builder.add_method_advanced(script_method);
56+
}
57+
}
58+
59+
impl<'a, C, F> MethodBuilder<'a, C, F>
60+
where
61+
C: NativeClass,
62+
F: Method<C> + Copy + Default,
63+
{
64+
/// Register the method as a stateless method. Stateless methods do not have data
65+
/// pointers and destructors and is thus slightly lighter. This is intended for ZSTs,
66+
/// but can be used with any `Method` type with `Copy + Default`.
67+
#[inline]
68+
pub fn done_stateless(self) {
69+
let script_method = ScriptMethod {
70+
name: self.name,
71+
method_ptr: Some(method_wrapper::<C, Stateless<F>>),
72+
attributes: ScriptMethodAttributes {
73+
rpc_mode: self.rpc_mode,
74+
},
75+
method_data: 1 as *mut libc::c_void,
76+
free_func: None,
77+
};
78+
79+
self.class_builder.add_method_advanced(script_method);
80+
}
81+
}
82+
83+
pub type ScriptMethodFn = unsafe extern "C" fn(
84+
*mut sys::godot_object,
85+
*mut libc::c_void,
86+
*mut libc::c_void,
87+
libc::c_int,
88+
*mut *mut sys::godot_variant,
89+
) -> sys::godot_variant;
90+
91+
pub enum RpcMode {
92+
Disabled,
93+
Remote,
94+
RemoteSync,
95+
Master,
96+
Puppet,
97+
MasterSync,
98+
PuppetSync,
99+
}
100+
101+
impl Default for RpcMode {
102+
#[inline]
103+
fn default() -> Self {
104+
RpcMode::Disabled
105+
}
106+
}
107+
108+
pub struct ScriptMethodAttributes {
109+
pub rpc_mode: RpcMode,
110+
}
111+
112+
pub struct ScriptMethod<'l> {
113+
pub name: &'l str,
114+
pub method_ptr: Option<ScriptMethodFn>,
115+
pub attributes: ScriptMethodAttributes,
116+
117+
pub method_data: *mut libc::c_void,
118+
pub free_func: Option<unsafe extern "C" fn(*mut libc::c_void) -> ()>,
119+
}
120+
121+
/// Low-level trait for stateful, variadic methods that can be called on a native script type.
122+
pub trait Method<C: NativeClass>: Send + Sync + 'static {
123+
fn call(&self, this: RefInstance<'_, C, Shared>, args: Varargs<'_>) -> Variant;
124+
}
125+
126+
/// Wrapper for stateless methods that produces values with `Copy` and `Default`.
127+
struct Stateless<F> {
128+
_marker: PhantomData<F>,
129+
}
130+
131+
impl<C: NativeClass, F: Method<C> + Copy + Default> Method<C> for Stateless<F> {
132+
fn call(&self, this: RefInstance<'_, C, Shared>, args: Varargs<'_>) -> Variant {
133+
let f = F::default();
134+
f.call(this, args)
135+
}
136+
}
5137

6138
/// Interface to a list of borrowed method arguments with a convenient interface
7139
/// for common operations with them. Can also be used as an iterator.
@@ -224,3 +356,53 @@ impl<'a> fmt::Display for ArgumentError<'a> {
224356
}
225357
}
226358
}
359+
360+
unsafe extern "C" fn method_wrapper<C: NativeClass, F: Method<C>>(
361+
this: *mut sys::godot_object,
362+
method_data: *mut libc::c_void,
363+
user_data: *mut libc::c_void,
364+
num_args: libc::c_int,
365+
args: *mut *mut sys::godot_variant,
366+
) -> sys::godot_variant {
367+
if user_data.is_null() {
368+
godot_error!(
369+
"gdnative-core: user data pointer for {} is null (did the constructor fail?)",
370+
C::class_name(),
371+
);
372+
return Variant::new().forget();
373+
}
374+
375+
let this = match std::ptr::NonNull::new(this) {
376+
Some(this) => this,
377+
None => {
378+
godot_error!(
379+
"gdnative-core: base object pointer for {} is null (probably a bug in Godot)",
380+
C::class_name(),
381+
);
382+
return Variant::new().forget();
383+
},
384+
};
385+
386+
let result = std::panic::catch_unwind(move || {
387+
let method = &*(method_data as *const F);
388+
389+
let this: Ref<C::Base, Shared> = Ref::from_sys(this);
390+
let this: TRef<'_, C::Base, _> = this.assume_safe_unchecked();
391+
let this: RefInstance<'_, C, _> = RefInstance::from_raw_unchecked(this, user_data);
392+
393+
let args = Varargs::from_sys(num_args, args);
394+
395+
F::call(method, this, args)
396+
});
397+
398+
result
399+
.unwrap_or_else(|_| {
400+
godot_error!("gdnative-core: method panicked (check stderr for output)");
401+
Variant::new()
402+
})
403+
.forget()
404+
}
405+
406+
unsafe extern "C" fn free_func<F>(method_data: *mut libc::c_void) {
407+
drop(Box::from_raw(method_data as *mut F))
408+
}

gdnative-core/src/nativescript/macros.rs

Lines changed: 15 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -94,49 +94,20 @@ macro_rules! godot_wrap_method_inner {
9494
) -> $retty:ty
9595
) => {
9696
{
97-
#[allow(unused_unsafe, unused_variables, unused_assignments, unused_mut)]
98-
#[allow(clippy::transmute_ptr_to_ptr)]
99-
unsafe extern "C" fn method(
100-
this: *mut $crate::sys::godot_object,
101-
method_data: *mut $crate::libc::c_void,
102-
user_data: *mut $crate::libc::c_void,
103-
num_args: $crate::libc::c_int,
104-
args: *mut *mut $crate::sys::godot_variant
105-
) -> $crate::sys::godot_variant {
97+
#[derive(Copy, Clone, Default)]
98+
struct ThisMethod;
10699

107-
use std::panic::{self, AssertUnwindSafe};
108-
use $crate::nativescript::{NativeClass, Instance, RefInstance, OwnerArg};
109-
use $crate::nativescript::init::method::Varargs;
110-
use $crate::object::{GodotObject, Ref, TRef};
100+
use $crate::nativescript::{NativeClass, Instance, RefInstance, OwnerArg};
111101

112-
if user_data.is_null() {
113-
$crate::godot_error!(
114-
"gdnative-core: user data pointer for {} is null (did the constructor fail?)",
115-
stringify!($type_name),
116-
);
117-
return $crate::core_types::Variant::new().forget();
118-
}
119-
120-
let this = match std::ptr::NonNull::new(this) {
121-
Some(this) => this,
122-
None => {
123-
$crate::godot_error!(
124-
"gdnative-core: base object pointer for {} is null (probably a bug in Godot)",
125-
stringify!($type_name),
126-
);
127-
return $crate::core_types::Variant::new().forget();
128-
},
129-
};
130-
131-
let __catch_result = panic::catch_unwind(move || {
132-
let this: Ref<<$type_name as NativeClass>::Base, $crate::thread_access::Shared> = Ref::from_sys(this);
133-
let this: TRef<'_, <$type_name as NativeClass>::Base, _> = this.assume_safe_unchecked();
134-
let __instance: RefInstance<'_, $type_name, _> = RefInstance::from_raw_unchecked(this, user_data);
135-
136-
let mut args = Varargs::from_sys(num_args, args);
102+
#[allow(unused_variables, unused_assignments, unused_mut)]
103+
impl $crate::nativescript::init::Method<$type_name> for ThisMethod {
104+
fn call(
105+
&self,
106+
this: RefInstance<'_, $type_name, $crate::thread_access::Shared>,
107+
mut args: $crate::nativescript::init::method::Varargs<'_>,
108+
) -> $crate::core_types::Variant {
137109
let mut is_failure = false;
138110

139-
let mut offset = 0;
140111
$(
141112
let $pname = args.read()
142113
.with_name(stringify!($pname))
@@ -176,7 +147,7 @@ macro_rules! godot_wrap_method_inner {
176147
let $pname = $pname.unwrap();
177148
)*
178149

179-
let __ret = __instance
150+
this
180151
.$map_method(|__rust_val, $owner| {
181152
let ret = __rust_val.$method_name(
182153
OwnerArg::from_safe_ref($owner),
@@ -189,22 +160,11 @@ macro_rules! godot_wrap_method_inner {
189160
$crate::godot_error!("gdnative-core: method call failed with error: {}", err);
190161
$crate::godot_error!("gdnative-core: check module level documentation on gdnative::user_data for more information");
191162
$crate::core_types::Variant::new()
192-
});
193-
194-
std::mem::drop(__instance);
195-
196-
__ret
197-
});
198-
199-
__catch_result
200-
.unwrap_or_else(|_err| {
201-
$crate::godot_error!("gdnative-core: method panicked (check stderr for output)");
202-
$crate::core_types::Variant::new()
203-
})
204-
.forget()
163+
})
164+
}
205165
}
206-
207-
method
166+
167+
ThisMethod
208168
}
209169
};
210170
}

gdnative-derive/src/methods.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,9 @@ pub(crate) fn derive_methods(item_impl: ItemImpl) -> TokenStream2 {
131131
fn #name ( #( #args )* ) -> #ret_ty
132132
);
133133

134-
#builder.add_method_with_rpc_mode(#name_string, method, #rpc);
134+
#builder.build_method(#name_string, method)
135+
.with_rpc_mode(#rpc)
136+
.done_stateless();
135137
}
136138
)
137139
})

0 commit comments

Comments
 (0)