gdnative_async/
method.rs

1use std::future::Future;
2use std::marker::PhantomData;
3use std::sync::Arc;
4
5use futures_task::{LocalFutureObj, LocalSpawn, SpawnError};
6
7use gdnative_core::core_types::{ToVariant, Variant};
8use gdnative_core::export::{FromVarargs, Method, NativeClass, Varargs};
9use gdnative_core::log::{self, Site};
10use gdnative_core::object::TInstance;
11
12use crate::rt::Context;
13
14/// Trait for async methods. When exported, such methods return `FunctionState`-like
15/// objects that can be manually resumed or yielded to completion.
16///
17/// Async methods are always spawned locally on the thread where they were created,
18/// and never sent to another thread. This is so that we can ensure the safety of
19/// emitting signals from the `FunctionState`-like object. If you need to off-load
20/// some task to another thread, consider using something like
21/// `futures::future::Remote` to spawn it remotely on a thread pool.
22pub trait AsyncMethod<C: NativeClass>: Send + Sync + 'static {
23    /// Spawns the future for result of this method with `spawner`. This is done so
24    /// that implementors of this trait do not have to name their future types.
25    ///
26    /// If the `spawner` object is not used, the Godot side of the call will fail, output an
27    /// error, and return a `Nil` variant.
28    fn spawn_with(&self, spawner: Spawner<'_, C>);
29
30    /// Returns an optional site where this method is defined. Used for logging errors in FFI wrappers.
31    ///
32    /// Default implementation returns `None`.
33    #[inline]
34    fn site() -> Option<Site<'static>> {
35        None
36    }
37}
38
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.
48pub 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)]
70pub struct StaticArgs<F> {
71    f: F,
72}
73
74impl<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
82impl<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
113/// A helper structure for working around naming future types. See [`Spawner::spawn`].
114pub struct Spawner<'a, C: NativeClass, A = Varargs<'a>> {
115    sp: &'static dyn LocalSpawn,
116    ctx: Context,
117    this: TInstance<'a, C>,
118    args: A,
119    result: &'a mut Option<Result<(), SpawnError>>,
120    /// Remove Send and Sync
121    _marker: PhantomData<*const ()>,
122}
123
124impl<'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
157    /// Consumes this `Spawner` and spawns a future returned by the closure. This indirection
158    /// is necessary so that implementors of the `AsyncMethod` trait do not have to name their
159    /// future types.
160    pub fn spawn<F, R>(self, f: F)
161    where
162        F: FnOnce(Arc<Context>, TInstance<'_, C>, A) -> R,
163        R: Future<Output = Variant> + 'static,
164    {
165        let ctx = Arc::new(self.ctx);
166        let future = f(Arc::clone(&ctx), self.this, self.args);
167        *self.result = Some(
168            self.sp
169                .spawn_local_obj(LocalFutureObj::new(Box::new(async move {
170                    let value = future.await;
171                    ctx.resolve(value);
172                }))),
173        );
174    }
175}
176
177/// Adapter for async methods that implements `Method` and can be registered.
178#[derive(Clone, Copy, Default, Debug)]
179pub struct Async<F> {
180    f: F,
181}
182
183impl<F> Async<F> {
184    /// Wrap `f` in an adapter that implements `Method`.
185    #[inline]
186    pub fn new(f: F) -> Self {
187        Async { f }
188    }
189}
190
191impl<C: NativeClass, F: AsyncMethod<C>> Method<C> for Async<F> {
192    fn call(&self, this: TInstance<'_, C>, args: Varargs<'_>) -> Variant {
193        if let Some(sp) = crate::executor::local_spawn() {
194            let ctx = Context::new();
195            let func_state = ctx.func_state();
196
197            let mut result = None;
198            self.f.spawn_with(Spawner {
199                sp,
200                ctx,
201                this,
202                args,
203                result: &mut result,
204                _marker: PhantomData,
205            });
206
207            match result {
208                Some(Ok(())) => func_state.to_variant(),
209                Some(Err(err)) => {
210                    log::error(
211                        Self::site().unwrap_or_default(),
212                        format_args!("unable to spawn future: {err}"),
213                    );
214                    Variant::nil()
215                }
216                None => {
217                    log::error(
218                        Self::site().unwrap_or_default(),
219                        format_args!("implementation did not spawn a future"),
220                    );
221                    Variant::nil()
222                }
223            }
224        } else {
225            log::error(
226                Self::site().unwrap_or_default(),
227                "a global executor must be set before any async methods can be called on this thread",
228            );
229            Variant::nil()
230        }
231    }
232
233    fn site() -> Option<Site<'static>> {
234        F::site()
235    }
236}