Skip to content

Proc-macro interface for async-await export #975

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gdnative-async/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ mod rt;

pub use executor::{set_boxed_executor, set_executor};
pub use future::Yield;
pub use method::{Async, AsyncMethod, Spawner};
pub use method::{Async, AsyncMethod, Spawner, StaticArgs, StaticArgsAsyncMethod};
pub use rt::{register_runtime, terminate_runtime, Context};
120 changes: 115 additions & 5 deletions gdnative-async/src/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::sync::Arc;
use futures_task::{LocalFutureObj, LocalSpawn, SpawnError};

use gdnative_core::core_types::{ToVariant, Variant};
use gdnative_core::export::{Method, NativeClass, Varargs};
use gdnative_core::export::{FromVarargs, Method, NativeClass, Varargs};
use gdnative_core::log::{self, Site};
use gdnative_core::object::TInstance;

Expand Down Expand Up @@ -36,24 +36,130 @@ pub trait AsyncMethod<C: NativeClass>: Send + Sync + 'static {
}
}

/// Trait for async methods whose argument lists are known at compile time. Not to
/// be confused with a "static method". When exported, such methods return
/// `FunctionState`-like objects that can be manually resumed or yielded to completion.
///
/// Async methods are always spawned locally on the thread where they were created,
/// and never sent to another thread. This is so that we can ensure the safety of
/// emitting signals from the `FunctionState`-like object. If you need to off-load
/// some task to another thread, consider using something like
/// `futures::future::Remote` to spawn it remotely on a thread pool.
pub trait StaticArgsAsyncMethod<C: NativeClass>: Send + Sync + 'static {
type Args: FromVarargs;

/// Spawns the future for result of this method with `spawner`. This is done so
/// that implementors of this trait do not have to name their future types.
///
/// If the `spawner` object is not used, the Godot side of the call will fail, output an
/// error, and return a `Nil` variant.
fn spawn_with(&self, spawner: Spawner<'_, C, Self::Args>);

/// Returns an optional site where this method is defined. Used for logging errors in FFI wrappers.
///
/// Default implementation returns `None`.
#[inline]
fn site() -> Option<Site<'static>> {
None
}
}

/// Adapter for methods whose arguments are statically determined. If the arguments would fail to
/// type check, the method will print the errors to Godot's debug console and return `null`.
#[derive(Clone, Copy, Default, Debug)]
pub struct StaticArgs<F> {
f: F,
}

impl<F> StaticArgs<F> {
/// Wrap `f` in an adapter that implements `AsyncMethod`.
#[inline]
pub fn new(f: F) -> Self {
StaticArgs { f }
}
}

impl<C: NativeClass, F: StaticArgsAsyncMethod<C>> AsyncMethod<C> for StaticArgs<F> {
#[inline]
fn spawn_with(&self, spawner: Spawner<'_, C>) {
let spawner = spawner.try_map_args(|mut args| match args.read_many::<F::Args>() {
Ok(parsed) => {
if let Err(err) = args.done() {
err.with_site(F::site().unwrap_or_default()).log_error();
return None;
}
Some(parsed)
}
Err(errors) => {
for err in errors {
err.with_site(F::site().unwrap_or_default()).log_error();
}
None
}
});

match spawner {
Ok(spawner) => F::spawn_with(&self.f, spawner),
Err(spawner) => spawner.spawn(|_context, _this, ()| async { Variant::nil() }),
}
}

#[inline]
fn site() -> Option<Site<'static>> {
F::site()
}
}

/// A helper structure for working around naming future types. See [`Spawner::spawn`].
pub struct Spawner<'a, C: NativeClass> {
pub struct Spawner<'a, C: NativeClass, A = Varargs<'a>> {
sp: &'static dyn LocalSpawn,
ctx: Context,
this: TInstance<'a, C>,
args: Varargs<'a>,
args: A,
result: &'a mut Option<Result<(), SpawnError>>,
/// Remove Send and Sync
_marker: PhantomData<*const ()>,
}

impl<'a, C: NativeClass> Spawner<'a, C> {
impl<'a, C: NativeClass, A> Spawner<'a, C, A> {
fn try_map_args<F, R>(self, f: F) -> Result<Spawner<'a, C, R>, Spawner<'a, C, ()>>
where
F: FnOnce(A) -> Option<R>,
{
let Spawner {
sp,
ctx,
this,
args,
result,
..
} = self;
match f(args) {
Some(args) => Ok(Spawner {
sp,
ctx,
this,
args,
result,
_marker: PhantomData,
}),
None => Err(Spawner {
sp,
ctx,
this,
args: (),
result,
_marker: PhantomData,
}),
}
}

/// Consumes this `Spawner` and spawns a future returned by the closure. This indirection
/// is necessary so that implementors of the `AsyncMethod` trait do not have to name their
/// future types.
pub fn spawn<F, R>(self, f: F)
where
F: FnOnce(Arc<Context>, TInstance<'_, C>, Varargs<'_>) -> R,
F: FnOnce(Arc<Context>, TInstance<'_, C>, A) -> R,
R: Future<Output = Variant> + 'static,
{
let ctx = Arc::new(self.ctx);
Expand Down Expand Up @@ -123,4 +229,8 @@ impl<C: NativeClass, F: AsyncMethod<C>> Method<C> for Async<F> {
Variant::nil()
}
}

fn site() -> Option<Site<'static>> {
F::site()
}
}
1 change: 1 addition & 0 deletions gdnative-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type-tag-fallback = []

[dependencies]
gdnative-sys = { path = "../gdnative-sys", version = "=0.11.0" }
gdnative-derive = { path = "../gdnative-derive", version = "=0.11.0" }
gdnative-impl-proc-macros = { path = "../impl/proc-macros", version = "=0.11.0" }
ahash = "0.8"
approx = "0.5"
Expand Down
224 changes: 2 additions & 222 deletions gdnative-core/src/export/macros.rs
Original file line number Diff line number Diff line change
@@ -1,222 +1,2 @@
#![macro_use]

#[doc(hidden)]
#[macro_export]
macro_rules! godot_wrap_method_if_deref {
(true, $ret:expr) => {
std::ops::Deref::deref(&$ret)
};
(false, $ret:expr) => {
$ret
};
}

// The ways of emit warnings is a terrible hack.
// This is because there is no way to emit warnings from macros in stable Rust.
//
// Follow these steps to emit warnings.
// - Detect whether reference types are used in gdnative-derive::methods::derive_methods().
// - Expand the call to the deprecated_reference_return!() macro to user code.
#[doc(hidden)]
#[macro_export]
#[deprecated = "This function does not actually pass by reference to the Godot engine. You can clarify by writing #[method(deref_return)]."]
macro_rules! deprecated_reference_return {
() => {};
}

#[doc(hidden)]
#[macro_export]
#[deprecated = "\n#[export] is deprecated and will be removed in a future godot-rust version. Use #[method] instead. \n\
For more information, see https://godot-rust.github.io/docs/gdnative/derive/derive.NativeClass.html."]
macro_rules! deprecated_export_syntax {
() => {};
}

#[doc(hidden)]
#[macro_export]
macro_rules! godot_wrap_method_void {
($ident:ident, $void:tt) => {
$ident
};
}

#[doc(hidden)]
#[macro_export]
macro_rules! godot_wrap_method_inner {
(
$type_name:ty,
$is_deref_return:ident,
$map_method:ident,
fn $method_name:ident(
$self:ident
$(, #[base] $base:ident : $base_ty:ty)?
$(, $pname:ident : $pty:ty)*
$(, #[opt] $opt_pname:ident : $opt_pty:ty)*
) -> $retty:ty
) => {
{
#[derive(Copy, Clone, Default)]
struct ThisMethod;

use $crate::export::{NativeClass, OwnerArg};
use $crate::object::{Instance, TInstance};
use ::gdnative::derive::FromVarargs;

#[derive(FromVarargs)]
#[allow(clippy::used_underscore_binding)]
struct Args {
$($pname: $pty,)*
$(#[opt] $opt_pname: $opt_pty,)*
}

#[allow(unused_variables, unused_assignments, unused_mut)]
impl $crate::export::StaticArgsMethod<$type_name> for ThisMethod {
type Args = Args;
fn call(
&self,
this: TInstance<'_, $type_name, $crate::object::ownership::Shared>,
Args { $($pname,)* $($opt_pname,)* }: Args,
) -> $crate::core_types::Variant {
this
.$map_method(|__rust_val, __base| {
#[allow(unused_unsafe)]
unsafe {
let ret = __rust_val.$method_name(
$(OwnerArg::from_safe_ref($crate::godot_wrap_method_void!(__base,$base)),)?
$($pname,)*
$($opt_pname,)*
);
gdnative::core_types::OwnedToVariant::owned_to_variant(
$crate::godot_wrap_method_if_deref!($is_deref_return, ret)
)
}
})
.unwrap_or_else(|err| {
$crate::godot_error!("gdnative-core: method call failed with error: {}", err);
$crate::godot_error!("gdnative-core: check module level documentation on gdnative::user_data for more information");
$crate::core_types::Variant::nil()
})
}

fn site() -> Option<$crate::log::Site<'static>> {
Some($crate::godot_site!($type_name::$method_name))
}
}

$crate::export::StaticArgs::new(ThisMethod)
}
};
}

#[doc(hidden)]
#[macro_export]
macro_rules! godot_wrap_method_return_type {
() => {
()
};
($retty:ty) => {
$retty: ty
};
}

/// 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,
$is_deref_return:ident,
fn $method_name:ident(
&mut $self:ident
$(, #[base] $base:ident : $base_ty:ty)?
$(, $pname:ident : $pty:ty)*
$(, #[opt] $opt_pname:ident : $opt_pty:ty)*
$(,)?
) $(-> $retty:ty)?
) => {
$crate::godot_wrap_method_inner!(
$type_name,
$is_deref_return,
map_mut,
fn $method_name(
$self
$(, #[base] $base : $base_ty)?
$(, $pname : $pty)*
$(, #[opt] $opt_pname : $opt_pty)*
) -> godot_wrap_method_return_type!($($retty)?)
)
};
// immutable
(
$type_name:ty,
$is_deref_return:ident,
fn $method_name:ident(
& $self:ident
$(, #[base] $base:ident : $base_ty:ty)?
$(, $pname:ident : $pty:ty)*
$(, #[opt] $opt_pname:ident : $opt_pty:ty)*
$(,)?
) $(-> $retty:ty)?
) => {
$crate::godot_wrap_method_inner!(
$type_name,
$is_deref_return,
map,
fn $method_name(
$self
$(, #[base] $base : $base_ty)?
$(, $pname : $pty)*
$(, #[opt] $opt_pname : $opt_pty)*
) -> godot_wrap_method_return_type!($($retty)?)
)
};
// owned
(
$type_name:ty,
$is_deref_return:ident,
fn $method_name:ident(
mut $self:ident
$(, #[base] $base:ident : $base_ty:ty)?
$(, $pname:ident : $pty:ty)*
$(, #[opt] $opt_pname:ident : $opt_pty:ty)*
$(,)?
) $(-> $retty:ty)?
) => {
$crate::godot_wrap_method_inner!(
$type_name,
$is_deref_return,
map_owned,
fn $method_name(
$self
$(, #[base] $base : $base_ty)?
$(, $pname : $pty)*
$(, #[opt] $opt_pname : $opt_pty)*
) -> godot_wrap_method_return_type!($($retty)?)
)
};
// owned
(
$type_name:ty,
$is_deref_return:ident,
fn $method_name:ident(
$self:ident
$(, #[base] $base:ident : $base_ty:ty)?
$(, $pname:ident : $pty:ty)*
$(, #[opt] $opt_pname:ident : $opt_pty:ty)*
$(,)?
) $(-> $retty:ty)?
) => {
$crate::godot_wrap_method_inner!(
$type_name,
$is_deref_return,
map_owned,
fn $method_name(
$self
$(, #[base] $base : $base_ty)?
$(, $pname : $pty)*
$(, #[opt] $opt_pname : $opt_pty)*
) -> godot_wrap_method_return_type!($($retty)?)
)
};
}
#[doc(inline)]
pub use gdnative_derive::godot_wrap_method;
Loading