Skip to content

Commit d0538f0

Browse files
bors[bot]chitoyuu
andauthored
Merge #975
975: Proc-macro interface for async-await export r=chitoyuu a=chitoyuu This is a two-part PR that accomplishes the following: ## Fully proceduralize method exports Converted the remaining macro_rules! used by the `#[methods]` macro into procedural macros, increasing flexibility for future expansion and reducing reliance on `#[doc(hidden)]` macro items. This makes it easier to support more combinations of method signatures, without having to manually write out n^2 patterns. ## Proc-macro interface for async-await export Added support for exporting `async fn`s and `fn`s that return futures through the proc-macro interface. - Added `#[method(async)]`, for methods that return futures but aren't async themselves. This is especially useful for working around lifetime elision (Rust putting the lifetime of `&self` into the return values of `async fn`s). - Added the `#[ctx]` attribute that denotes the async context. Special arguments (`#[base]`, `#[ctx]`, and possibly `#[self]` in the future) can now be declared in any order, so long as they precede all regular arguments. - Methods without receivers can now be exported. However, they still currently use `Map` semantics. Utterly double close #284. ## Error messages As a side effect to this refactoring, the macros are now capable of generating some fairly precisely spanned error messages: <details> <summary>derive_fail_methods_special_args.stderr</summary> ``` error: the method receiver cannot be optional (instead, remove the argument entirely) --> tests/ui/derive_fail_methods_special_args.rs:14:25 | 14 | async fn optional(#[opt] self, #[base] #[opt] _owner: &Node, #[ctx] #[opt] ctx: ()) {} | ^^^ error: the base/owner object cannot be optional (instead, remove the argument entirely) --> tests/ui/derive_fail_methods_special_args.rs:14:46 | 14 | async fn optional(#[opt] self, #[base] #[opt] _owner: &Node, #[ctx] #[opt] ctx: ()) {} | ^^^ error: the async context cannot be optional (instead, remove the argument entirely) --> tests/ui/derive_fail_methods_special_args.rs:14:75 | 14 | async fn optional(#[opt] self, #[base] #[opt] _owner: &Node, #[ctx] #[opt] ctx: ()) {} | ^^^ error: the method receiver cannot also be the base/owner object --> tests/ui/derive_fail_methods_special_args.rs:17:16 | 17 | fn based(#[base] self, #[base] _base: &Node, #[base] #[base] _basil: &Node, #[base] #[base] #[base] _basin: &Node) {} | ^^^^ error: duplicate attribute --> tests/ui/derive_fail_methods_special_args.rs:17:60 | 17 | fn based(#[base] self, #[base] _base: &Node, #[base] #[base] _basil: &Node, #[base] #[base] #[base] _basin: &Node) {} | ^^^^ error: the special parameter base/owner object must only be declared once (the same parameter is already defined at #1) --> tests/ui/derive_fail_methods_special_args.rs:17:66 | 17 | fn based(#[base] self, #[base] _base: &Node, #[base] #[base] _basil: &Node, #[base] #[base] #[base] _basin: &Node) {} | ^^^^^^ error: duplicate attribute --> tests/ui/derive_fail_methods_special_args.rs:17:91 | 17 | fn based(#[base] self, #[base] _base: &Node, #[base] #[base] _basil: &Node, #[base] #[base] #[base] _basin: &Node) {} | ^^^^ error: duplicate attribute --> tests/ui/derive_fail_methods_special_args.rs:17:99 | 17 | fn based(#[base] self, #[base] _base: &Node, #[base] #[base] _basil: &Node, #[base] #[base] #[base] _basin: &Node) {} | ^^^^ error: the special parameter base/owner object must only be declared once (the same parameter is already defined at #1) --> tests/ui/derive_fail_methods_special_args.rs:17:105 | 17 | fn based(#[base] self, #[base] _base: &Node, #[base] #[base] _basil: &Node, #[base] #[base] #[base] _basin: &Node) {} | ^^^^^^ error: the async context is only available to async methods --> tests/ui/derive_fail_methods_special_args.rs:20:26 | 20 | fn sync(self, #[ctx] ctx: ()) {} | ^^^ ``` </details> Close #439. ## Compatibility There aren't any breaking changes that I'm aware of. A shim for the legacy `godot_wrap_method` macro is kept for compatibility. It can be removed in the next breaking release. Async exports are unsupported by this shim. Co-authored-by: Chitose Yuuzaki <chitoyuu@potatoes.gay>
2 parents e74a1f8 + 6689a00 commit d0538f0

File tree

14 files changed

+946
-428
lines changed

14 files changed

+946
-428
lines changed

gdnative-async/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@ mod rt;
1616

1717
pub use executor::{set_boxed_executor, set_executor};
1818
pub use future::Yield;
19-
pub use method::{Async, AsyncMethod, Spawner};
19+
pub use method::{Async, AsyncMethod, Spawner, StaticArgs, StaticArgsAsyncMethod};
2020
pub use rt::{register_runtime, terminate_runtime, Context};

gdnative-async/src/method.rs

Lines changed: 115 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::sync::Arc;
55
use futures_task::{LocalFutureObj, LocalSpawn, SpawnError};
66

77
use gdnative_core::core_types::{ToVariant, Variant};
8-
use gdnative_core::export::{Method, NativeClass, Varargs};
8+
use gdnative_core::export::{FromVarargs, Method, NativeClass, Varargs};
99
use gdnative_core::log::{self, Site};
1010
use gdnative_core::object::TInstance;
1111

@@ -36,24 +36,130 @@ pub trait AsyncMethod<C: NativeClass>: Send + Sync + 'static {
3636
}
3737
}
3838

39+
/// Trait for async methods whose argument lists are known at compile time. Not to
40+
/// be confused with a "static method". When exported, such methods return
41+
/// `FunctionState`-like objects that can be manually resumed or yielded to completion.
42+
///
43+
/// Async methods are always spawned locally on the thread where they were created,
44+
/// and never sent to another thread. This is so that we can ensure the safety of
45+
/// emitting signals from the `FunctionState`-like object. If you need to off-load
46+
/// some task to another thread, consider using something like
47+
/// `futures::future::Remote` to spawn it remotely on a thread pool.
48+
pub trait StaticArgsAsyncMethod<C: NativeClass>: Send + Sync + 'static {
49+
type Args: FromVarargs;
50+
51+
/// Spawns the future for result of this method with `spawner`. This is done so
52+
/// that implementors of this trait do not have to name their future types.
53+
///
54+
/// If the `spawner` object is not used, the Godot side of the call will fail, output an
55+
/// error, and return a `Nil` variant.
56+
fn spawn_with(&self, spawner: Spawner<'_, C, Self::Args>);
57+
58+
/// Returns an optional site where this method is defined. Used for logging errors in FFI wrappers.
59+
///
60+
/// Default implementation returns `None`.
61+
#[inline]
62+
fn site() -> Option<Site<'static>> {
63+
None
64+
}
65+
}
66+
67+
/// Adapter for methods whose arguments are statically determined. If the arguments would fail to
68+
/// type check, the method will print the errors to Godot's debug console and return `null`.
69+
#[derive(Clone, Copy, Default, Debug)]
70+
pub struct StaticArgs<F> {
71+
f: F,
72+
}
73+
74+
impl<F> StaticArgs<F> {
75+
/// Wrap `f` in an adapter that implements `AsyncMethod`.
76+
#[inline]
77+
pub fn new(f: F) -> Self {
78+
StaticArgs { f }
79+
}
80+
}
81+
82+
impl<C: NativeClass, F: StaticArgsAsyncMethod<C>> AsyncMethod<C> for StaticArgs<F> {
83+
#[inline]
84+
fn spawn_with(&self, spawner: Spawner<'_, C>) {
85+
let spawner = spawner.try_map_args(|mut args| match args.read_many::<F::Args>() {
86+
Ok(parsed) => {
87+
if let Err(err) = args.done() {
88+
err.with_site(F::site().unwrap_or_default()).log_error();
89+
return None;
90+
}
91+
Some(parsed)
92+
}
93+
Err(errors) => {
94+
for err in errors {
95+
err.with_site(F::site().unwrap_or_default()).log_error();
96+
}
97+
None
98+
}
99+
});
100+
101+
match spawner {
102+
Ok(spawner) => F::spawn_with(&self.f, spawner),
103+
Err(spawner) => spawner.spawn(|_context, _this, ()| async { Variant::nil() }),
104+
}
105+
}
106+
107+
#[inline]
108+
fn site() -> Option<Site<'static>> {
109+
F::site()
110+
}
111+
}
112+
39113
/// A helper structure for working around naming future types. See [`Spawner::spawn`].
40-
pub struct Spawner<'a, C: NativeClass> {
114+
pub struct Spawner<'a, C: NativeClass, A = Varargs<'a>> {
41115
sp: &'static dyn LocalSpawn,
42116
ctx: Context,
43117
this: TInstance<'a, C>,
44-
args: Varargs<'a>,
118+
args: A,
45119
result: &'a mut Option<Result<(), SpawnError>>,
46120
/// Remove Send and Sync
47121
_marker: PhantomData<*const ()>,
48122
}
49123

50-
impl<'a, C: NativeClass> Spawner<'a, C> {
124+
impl<'a, C: NativeClass, A> Spawner<'a, C, A> {
125+
fn try_map_args<F, R>(self, f: F) -> Result<Spawner<'a, C, R>, Spawner<'a, C, ()>>
126+
where
127+
F: FnOnce(A) -> Option<R>,
128+
{
129+
let Spawner {
130+
sp,
131+
ctx,
132+
this,
133+
args,
134+
result,
135+
..
136+
} = self;
137+
match f(args) {
138+
Some(args) => Ok(Spawner {
139+
sp,
140+
ctx,
141+
this,
142+
args,
143+
result,
144+
_marker: PhantomData,
145+
}),
146+
None => Err(Spawner {
147+
sp,
148+
ctx,
149+
this,
150+
args: (),
151+
result,
152+
_marker: PhantomData,
153+
}),
154+
}
155+
}
156+
51157
/// Consumes this `Spawner` and spawns a future returned by the closure. This indirection
52158
/// is necessary so that implementors of the `AsyncMethod` trait do not have to name their
53159
/// future types.
54160
pub fn spawn<F, R>(self, f: F)
55161
where
56-
F: FnOnce(Arc<Context>, TInstance<'_, C>, Varargs<'_>) -> R,
162+
F: FnOnce(Arc<Context>, TInstance<'_, C>, A) -> R,
57163
R: Future<Output = Variant> + 'static,
58164
{
59165
let ctx = Arc::new(self.ctx);
@@ -123,4 +229,8 @@ impl<C: NativeClass, F: AsyncMethod<C>> Method<C> for Async<F> {
123229
Variant::nil()
124230
}
125231
}
232+
233+
fn site() -> Option<Site<'static>> {
234+
F::site()
235+
}
126236
}

gdnative-core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ type-tag-fallback = []
1818

1919
[dependencies]
2020
gdnative-sys = { path = "../gdnative-sys", version = "=0.11.0" }
21+
gdnative-derive = { path = "../gdnative-derive", version = "=0.11.0" }
2122
gdnative-impl-proc-macros = { path = "../impl/proc-macros", version = "=0.11.0" }
2223
ahash = "0.8"
2324
approx = "0.5"

gdnative-core/src/export/macros.rs

Lines changed: 2 additions & 222 deletions
Original file line numberDiff line numberDiff line change
@@ -1,222 +1,2 @@
1-
#![macro_use]
2-
3-
#[doc(hidden)]
4-
#[macro_export]
5-
macro_rules! godot_wrap_method_if_deref {
6-
(true, $ret:expr) => {
7-
std::ops::Deref::deref(&$ret)
8-
};
9-
(false, $ret:expr) => {
10-
$ret
11-
};
12-
}
13-
14-
// The ways of emit warnings is a terrible hack.
15-
// This is because there is no way to emit warnings from macros in stable Rust.
16-
//
17-
// Follow these steps to emit warnings.
18-
// - Detect whether reference types are used in gdnative-derive::methods::derive_methods().
19-
// - Expand the call to the deprecated_reference_return!() macro to user code.
20-
#[doc(hidden)]
21-
#[macro_export]
22-
#[deprecated = "This function does not actually pass by reference to the Godot engine. You can clarify by writing #[method(deref_return)]."]
23-
macro_rules! deprecated_reference_return {
24-
() => {};
25-
}
26-
27-
#[doc(hidden)]
28-
#[macro_export]
29-
#[deprecated = "\n#[export] is deprecated and will be removed in a future godot-rust version. Use #[method] instead. \n\
30-
For more information, see https://godot-rust.github.io/docs/gdnative/derive/derive.NativeClass.html."]
31-
macro_rules! deprecated_export_syntax {
32-
() => {};
33-
}
34-
35-
#[doc(hidden)]
36-
#[macro_export]
37-
macro_rules! godot_wrap_method_void {
38-
($ident:ident, $void:tt) => {
39-
$ident
40-
};
41-
}
42-
43-
#[doc(hidden)]
44-
#[macro_export]
45-
macro_rules! godot_wrap_method_inner {
46-
(
47-
$type_name:ty,
48-
$is_deref_return:ident,
49-
$map_method:ident,
50-
fn $method_name:ident(
51-
$self:ident
52-
$(, #[base] $base:ident : $base_ty:ty)?
53-
$(, $pname:ident : $pty:ty)*
54-
$(, #[opt] $opt_pname:ident : $opt_pty:ty)*
55-
) -> $retty:ty
56-
) => {
57-
{
58-
#[derive(Copy, Clone, Default)]
59-
struct ThisMethod;
60-
61-
use $crate::export::{NativeClass, OwnerArg};
62-
use $crate::object::{Instance, TInstance};
63-
use ::gdnative::derive::FromVarargs;
64-
65-
#[derive(FromVarargs)]
66-
#[allow(clippy::used_underscore_binding)]
67-
struct Args {
68-
$($pname: $pty,)*
69-
$(#[opt] $opt_pname: $opt_pty,)*
70-
}
71-
72-
#[allow(unused_variables, unused_assignments, unused_mut)]
73-
impl $crate::export::StaticArgsMethod<$type_name> for ThisMethod {
74-
type Args = Args;
75-
fn call(
76-
&self,
77-
this: TInstance<'_, $type_name, $crate::object::ownership::Shared>,
78-
Args { $($pname,)* $($opt_pname,)* }: Args,
79-
) -> $crate::core_types::Variant {
80-
this
81-
.$map_method(|__rust_val, __base| {
82-
#[allow(unused_unsafe)]
83-
unsafe {
84-
let ret = __rust_val.$method_name(
85-
$(OwnerArg::from_safe_ref($crate::godot_wrap_method_void!(__base,$base)),)?
86-
$($pname,)*
87-
$($opt_pname,)*
88-
);
89-
gdnative::core_types::OwnedToVariant::owned_to_variant(
90-
$crate::godot_wrap_method_if_deref!($is_deref_return, ret)
91-
)
92-
}
93-
})
94-
.unwrap_or_else(|err| {
95-
$crate::godot_error!("gdnative-core: method call failed with error: {}", err);
96-
$crate::godot_error!("gdnative-core: check module level documentation on gdnative::user_data for more information");
97-
$crate::core_types::Variant::nil()
98-
})
99-
}
100-
101-
fn site() -> Option<$crate::log::Site<'static>> {
102-
Some($crate::godot_site!($type_name::$method_name))
103-
}
104-
}
105-
106-
$crate::export::StaticArgs::new(ThisMethod)
107-
}
108-
};
109-
}
110-
111-
#[doc(hidden)]
112-
#[macro_export]
113-
macro_rules! godot_wrap_method_return_type {
114-
() => {
115-
()
116-
};
117-
($retty:ty) => {
118-
$retty: ty
119-
};
120-
}
121-
122-
/// Convenience macro to wrap an object's method into a function pointer
123-
/// that can be passed to the engine when registering a class.
124-
#[macro_export]
125-
macro_rules! godot_wrap_method {
126-
// mutable
127-
(
128-
$type_name:ty,
129-
$is_deref_return:ident,
130-
fn $method_name:ident(
131-
&mut $self:ident
132-
$(, #[base] $base:ident : $base_ty:ty)?
133-
$(, $pname:ident : $pty:ty)*
134-
$(, #[opt] $opt_pname:ident : $opt_pty:ty)*
135-
$(,)?
136-
) $(-> $retty:ty)?
137-
) => {
138-
$crate::godot_wrap_method_inner!(
139-
$type_name,
140-
$is_deref_return,
141-
map_mut,
142-
fn $method_name(
143-
$self
144-
$(, #[base] $base : $base_ty)?
145-
$(, $pname : $pty)*
146-
$(, #[opt] $opt_pname : $opt_pty)*
147-
) -> godot_wrap_method_return_type!($($retty)?)
148-
)
149-
};
150-
// immutable
151-
(
152-
$type_name:ty,
153-
$is_deref_return:ident,
154-
fn $method_name:ident(
155-
& $self:ident
156-
$(, #[base] $base:ident : $base_ty:ty)?
157-
$(, $pname:ident : $pty:ty)*
158-
$(, #[opt] $opt_pname:ident : $opt_pty:ty)*
159-
$(,)?
160-
) $(-> $retty:ty)?
161-
) => {
162-
$crate::godot_wrap_method_inner!(
163-
$type_name,
164-
$is_deref_return,
165-
map,
166-
fn $method_name(
167-
$self
168-
$(, #[base] $base : $base_ty)?
169-
$(, $pname : $pty)*
170-
$(, #[opt] $opt_pname : $opt_pty)*
171-
) -> godot_wrap_method_return_type!($($retty)?)
172-
)
173-
};
174-
// owned
175-
(
176-
$type_name:ty,
177-
$is_deref_return:ident,
178-
fn $method_name:ident(
179-
mut $self:ident
180-
$(, #[base] $base:ident : $base_ty:ty)?
181-
$(, $pname:ident : $pty:ty)*
182-
$(, #[opt] $opt_pname:ident : $opt_pty:ty)*
183-
$(,)?
184-
) $(-> $retty:ty)?
185-
) => {
186-
$crate::godot_wrap_method_inner!(
187-
$type_name,
188-
$is_deref_return,
189-
map_owned,
190-
fn $method_name(
191-
$self
192-
$(, #[base] $base : $base_ty)?
193-
$(, $pname : $pty)*
194-
$(, #[opt] $opt_pname : $opt_pty)*
195-
) -> godot_wrap_method_return_type!($($retty)?)
196-
)
197-
};
198-
// owned
199-
(
200-
$type_name:ty,
201-
$is_deref_return:ident,
202-
fn $method_name:ident(
203-
$self:ident
204-
$(, #[base] $base:ident : $base_ty:ty)?
205-
$(, $pname:ident : $pty:ty)*
206-
$(, #[opt] $opt_pname:ident : $opt_pty:ty)*
207-
$(,)?
208-
) $(-> $retty:ty)?
209-
) => {
210-
$crate::godot_wrap_method_inner!(
211-
$type_name,
212-
$is_deref_return,
213-
map_owned,
214-
fn $method_name(
215-
$self
216-
$(, #[base] $base : $base_ty)?
217-
$(, $pname : $pty)*
218-
$(, #[opt] $opt_pname : $opt_pty)*
219-
) -> godot_wrap_method_return_type!($($retty)?)
220-
)
221-
};
222-
}
1+
#[doc(inline)]
2+
pub use gdnative_derive::godot_wrap_method;

0 commit comments

Comments
 (0)