Skip to content

Commit 6689a00

Browse files
committed
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. Async exports are unsupported by the legacy `godot_wrap_method` shim.
1 parent fbdb0ac commit 6689a00

File tree

8 files changed

+717
-252
lines changed

8 files changed

+717
-252
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-derive/src/lib.rs

Lines changed: 79 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ extern crate quote;
77

88
use proc_macro::TokenStream;
99
use proc_macro2::TokenStream as TokenStream2;
10+
use quote::ToTokens;
1011
use syn::{AttributeArgs, DeriveInput, ItemFn, ItemImpl};
1112

1213
mod extend_bounds;
@@ -18,7 +19,12 @@ mod variant;
1819

1920
/// Collects method signatures of all functions in a `NativeClass` that have the `#[method]` attribute and registers them with Godot.
2021
///
21-
/// For example, in the following class
22+
/// **Important**: Only one `impl` block per struct may be attributed with `#[methods]`.
23+
///
24+
/// For more context, please refer to [gdnative::derive::NativeClass](NativeClass).
25+
///
26+
/// ## Example
27+
///
2228
/// ```
2329
/// use gdnative::prelude::*;
2430
///
@@ -36,34 +42,6 @@ mod variant;
3642
/// }
3743
///
3844
/// ```
39-
/// Will expand to
40-
/// ```
41-
/// use gdnative::prelude::*;
42-
/// struct Foo {}
43-
/// impl NativeClass for Foo {
44-
/// type Base = gdnative::api::Reference;
45-
/// type UserData = gdnative::export::user_data::LocalCellData<Self>;
46-
/// }
47-
/// impl gdnative::export::StaticallyNamed for Foo {
48-
/// const CLASS_NAME: &'static str = "Foo";
49-
/// }
50-
/// impl gdnative::export::NativeClassMethods for Foo {
51-
/// fn nativeclass_register(builder: &ClassBuilder<Self>) {
52-
/// use gdnative::export::*;
53-
/// builder.method("foo", gdnative::export::godot_wrap_method!(Foo, false, fn foo(&self, #[base] _base: &Reference, bar: i64) -> i64))
54-
/// .with_rpc_mode(RpcMode::Disabled)
55-
/// .done_stateless();
56-
/// }
57-
/// }
58-
/// impl Foo {
59-
/// fn foo(&self, _owner: &Reference, bar: i64) -> i64 {
60-
/// bar
61-
/// }
62-
/// }
63-
/// ```
64-
/// **Important**: Only one `impl` block per struct may be attributed with `#[methods]`.
65-
///
66-
/// For more context, please refer to [gdnative::derive::NativeClass](NativeClass).
6745
#[proc_macro_attribute]
6846
pub fn methods(meta: TokenStream, input: TokenStream) -> TokenStream {
6947
if syn::parse::<syn::parse::Nothing>(meta.clone()).is_err() {
@@ -237,18 +215,19 @@ pub fn profiled(meta: TokenStream, input: TokenStream) -> TokenStream {
237215
///
238216
/// **Important**: This needs to be added to one and only one `impl` block for a given `NativeClass`.
239217
///
240-
/// For additional details about how `#[methods]` expands, please refer to [gdnative::methods](macro@methods)
241-
///
242218
/// ### `#[method]`
243219
/// Registers the attributed function signature to be used by Godot.
244220
///
245221
/// This attribute was formerly called `#[export]`, but is not directly related to the concept of
246222
/// [exporting](https://docs.godotengine.org/en/stable/tutorials/export/exporting_basics.html) in GDScript.
247223
///
248224
/// A valid function signature must have:
249-
/// - `&self` or `&mut self` as its first parameter
250-
/// - Optionally, `&T` or `TRef<T>` where T refers to the type declared in `#[inherit(T)]` attribute as it's second parameter;
251-
/// this is typically called the _base_. The parameter must be attributed with `#[base]`.
225+
/// - `self`, `&self` or `&mut self` as its first parameter, if applicable.
226+
/// - Up of one of each of the following special arguments, in any order, denoted by the attributes:
227+
/// - `#[base]` - A reference to the base/owner object. This may be `&T` or `TRef<T>`m where `T` refers to
228+
/// the type declared in `#[inherit(T)]` attribute for the `NativeClass` type.
229+
/// - `#[async_ctx]` - The [async context](gdnative::tasks::Context), for async methods. See the `async` argument
230+
/// below.
252231
/// - Any number of required parameters, which must have the type `Variant` or must implement the `FromVariant` trait.
253232
/// `FromVariant` is implemented for most common types.
254233
/// - Any number of optional parameters annotated with `#[opt]`. Same rules as for required parameters apply.
@@ -257,6 +236,10 @@ pub fn profiled(meta: TokenStream, input: TokenStream) -> TokenStream {
257236
/// or be a `Variant` type.
258237
///
259238
/// ```ignore
239+
/// // Associated function
240+
/// #[method]
241+
/// fn foo();
242+
///
260243
/// // No access to base parameter
261244
/// #[method]
262245
/// fn foo(&self);
@@ -268,6 +251,20 @@ pub fn profiled(meta: TokenStream, input: TokenStream) -> TokenStream {
268251
/// // Access base parameter as TRef<T>
269252
/// #[method]
270253
/// fn foo(&self, #[base] base: TRef<Reference>);
254+
///
255+
/// // Access only the async context. Both variations are valid.
256+
/// #[method]
257+
/// async fn foo(#[async_ctx] ctx: Arc<Context>);
258+
/// #[method(async)]
259+
/// fn foo(#[async_ctx] ctx: Arc<Context>) -> impl Future<Output = ()> + 'static;
260+
///
261+
/// // Access the base parameter as TRef<T>, and the async context. Both variations are valid.
262+
/// // Note the absence of `async fn`s here: this is due to a current limitation in Rust's lifetime elision rules.
263+
/// // See the `async` attribute argument down below for more details.
264+
/// #[method(async)]
265+
/// fn foo(&self, #[base] base: TRef<Reference>, #[async_ctx] ctx: Arc<Context>) -> impl Future<Output = ()> + 'static;
266+
/// #[method(async)]
267+
/// fn foo(&self, #[async_ctx] ctx: Arc<Context>, #[base] base: TRef<Reference>) -> impl Future<Output = ()> + 'static;
271268
/// ```
272269
///
273270
/// **Note**: Marking a function with `#[method]` does not have any effect unless inside an `impl` block that has the `#[methods]` attribute.
@@ -307,6 +304,29 @@ pub fn profiled(meta: TokenStream, input: TokenStream) -> TokenStream {
307304
/// }
308305
/// ```
309306
///
307+
/// - `async`
308+
///
309+
/// Marks the function as async. This is used for functions that aren't `async` themselves, but return `Future`s instead.
310+
/// This is especially useful for working around Rust's lifetime elision rules, which put the lifetime of `&self` into the
311+
/// return value for `async fn`s. The `impl Future` syntax instead allows one to explicitly specify a `'static` lifetime,
312+
/// as required by the async runtime:
313+
///
314+
/// ```ignore
315+
/// // This will NOT compile: Rust assumes that any futures returned by an `async fn` may only live as long as each of its
316+
/// // arguments, and there is no way to tell it otherwise. As a result, it will emit some cryptic complaints about lifetime.
317+
/// #[method]
318+
/// async fn answer(&self) -> i32 {
319+
/// 42
320+
/// }
321+
///
322+
/// // This, however, compiles, thanks to the explicit `'static` lifetime in the return signature.
323+
/// #[method(async)]
324+
/// fn answer(&self) -> impl Future<Output = i32> + 'static {
325+
/// async { 42 }
326+
/// }
327+
///
328+
/// ```
329+
///
310330
///
311331
/// #### `Node` virtual functions
312332
///
@@ -525,7 +545,31 @@ fn crate_gdnative_core() -> proc_macro2::TokenStream {
525545
proc_macro_crate::FoundCrate::Itself => quote!(crate),
526546
proc_macro_crate::FoundCrate::Name(name) => {
527547
let ident = proc_macro2::Ident::new(&name, proc_macro2::Span::call_site());
528-
quote!( #ident )
548+
ident.to_token_stream()
549+
}
550+
}
551+
}
552+
553+
/// Returns the (possibly renamed or imported as `gdnative`) identifier of the `gdnative_async` crate,
554+
/// if found.
555+
fn crate_gdnative_async() -> proc_macro2::TokenStream {
556+
if let Ok(found_crate) = proc_macro_crate::crate_name("gdnative-async") {
557+
return match found_crate {
558+
proc_macro_crate::FoundCrate::Itself => quote!(crate),
559+
proc_macro_crate::FoundCrate::Name(name) => {
560+
let ident = proc_macro2::Ident::new(&name, proc_macro2::Span::call_site());
561+
ident.to_token_stream()
562+
}
563+
};
564+
}
565+
566+
let found_crate = proc_macro_crate::crate_name("gdnative").expect("crate not found");
567+
568+
match found_crate {
569+
proc_macro_crate::FoundCrate::Itself => quote!(crate::tasks),
570+
proc_macro_crate::FoundCrate::Name(name) => {
571+
let ident = proc_macro2::Ident::new(&name, proc_macro2::Span::call_site());
572+
quote!( #ident::tasks )
529573
}
530574
}
531575
}

0 commit comments

Comments
 (0)