Skip to content

Commit 7ade97b

Browse files
committed
Async Signals
1 parent ad42a35 commit 7ade97b

File tree

5 files changed

+354
-1
lines changed

5 files changed

+354
-1
lines changed

godot-core/src/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ pub mod init;
2727
pub mod meta;
2828
pub mod obj;
2929
pub mod registry;
30+
#[cfg(since_api = "4.2")]
31+
pub mod task;
32+
#[cfg(before_api = "4.2")]
33+
pub mod task {}
3034
pub mod tools;
3135

3236
mod storage;

godot-core/src/registry/signal/typed_signal.rs

+4
Original file line numberDiff line numberDiff line change
@@ -207,4 +207,8 @@ impl<'c, C: WithBaseField, Ps: meta::ParamTuple> TypedSignal<'c, C, Ps> {
207207
c.done();
208208
});
209209
}
210+
211+
pub(crate) fn to_untyped(&self) -> crate::builtin::Signal {
212+
crate::builtin::Signal::from_object_signal(&self.receiver_object(), &*self.name)
213+
}
210214
}

godot-core/src/task/futures.rs

+330
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
/*
2+
* Copyright (c) godot-rust; Bromeon and contributors.
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
use core::panic;
9+
use std::fmt::Display;
10+
use std::future::{Future, IntoFuture};
11+
use std::pin::Pin;
12+
use std::sync::{Arc, Mutex};
13+
use std::task::{Context, Poll, Waker};
14+
15+
use crate::builtin::{Callable, RustCallable, Signal, Variant};
16+
use crate::classes::object::ConnectFlags;
17+
use crate::meta::ParamTuple;
18+
use crate::obj::{EngineBitfield, WithBaseField};
19+
use crate::registry::signal::TypedSignal;
20+
21+
/// The panicking counter part to the [`FallibleSignalFuture`].
22+
///
23+
/// This future works in the same way as `FallibleSignalFuture`, but panics when the signal object is freed, instead of resolving to a
24+
/// [`Result::Err`].
25+
pub struct SignalFuture<R: ParamTuple + Sync + Send>(FallibleSignalFuture<R>);
26+
27+
impl<R: ParamTuple + Sync + Send> SignalFuture<R> {
28+
fn new(signal: Signal) -> Self {
29+
Self(FallibleSignalFuture::new(signal))
30+
}
31+
}
32+
33+
impl<R: ParamTuple + Sync + Send> Future for SignalFuture<R> {
34+
type Output = R;
35+
36+
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
37+
let poll_result = self.get_mut().0.poll(cx);
38+
39+
match poll_result {
40+
Poll::Pending => Poll::Pending,
41+
Poll::Ready(Ok(value)) => Poll::Ready(value),
42+
Poll::Ready(Err(FallibleSignalFutureError)) => panic!(
43+
"the signal object of a SignalFuture was freed, while the future was still waiting for the signal to be emitted"
44+
),
45+
}
46+
}
47+
}
48+
49+
// Not derived, otherwise an extra bound `Output: Default` is required.
50+
struct SignalFutureData<T> {
51+
state: SignalFutureState<T>,
52+
waker: Option<Waker>,
53+
}
54+
55+
impl<T> Default for SignalFutureData<T> {
56+
fn default() -> Self {
57+
Self {
58+
state: Default::default(),
59+
waker: None,
60+
}
61+
}
62+
}
63+
64+
// Only public for itest.
65+
#[cfg_attr(feature = "trace", derive(Default))]
66+
pub struct SignalFutureResolver<R> {
67+
data: Arc<Mutex<SignalFutureData<R>>>,
68+
}
69+
70+
impl<R> Clone for SignalFutureResolver<R> {
71+
fn clone(&self) -> Self {
72+
Self {
73+
data: self.data.clone(),
74+
}
75+
}
76+
}
77+
78+
impl<R> SignalFutureResolver<R> {
79+
fn new(data: Arc<Mutex<SignalFutureData<R>>>) -> Self {
80+
Self { data }
81+
}
82+
}
83+
84+
impl<R> std::hash::Hash for SignalFutureResolver<R> {
85+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
86+
state.write_usize(Arc::as_ptr(&self.data) as usize);
87+
}
88+
}
89+
90+
impl<R> PartialEq for SignalFutureResolver<R> {
91+
fn eq(&self, other: &Self) -> bool {
92+
Arc::ptr_eq(&self.data, &other.data)
93+
}
94+
}
95+
96+
impl<R: ParamTuple + Sync + Send> RustCallable for SignalFutureResolver<R> {
97+
fn invoke(&mut self, args: &[&Variant]) -> Result<Variant, ()> {
98+
let waker = {
99+
let mut data = self.data.lock().unwrap();
100+
data.state = SignalFutureState::Ready(R::from_variant_array(args));
101+
102+
// We no longer need the waker after we resolved. If the future is polled again, we'll also get a new waker.
103+
data.waker.take()
104+
};
105+
106+
if let Some(waker) = waker {
107+
waker.wake();
108+
}
109+
110+
Ok(Variant::nil())
111+
}
112+
}
113+
114+
impl<R> Display for SignalFutureResolver<R> {
115+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116+
write!(f, "SignalFutureResolver::<{}>", std::any::type_name::<R>())
117+
}
118+
}
119+
120+
// This resolver will change the futures state when it's being dropped (i.e. the engine removes all connected signal callables). By marking
121+
// the future as dead we can resolve it to an error value the next time it gets polled.
122+
impl<R> Drop for SignalFutureResolver<R> {
123+
fn drop(&mut self) {
124+
let mut data = self.data.lock().unwrap();
125+
126+
if !matches!(data.state, SignalFutureState::Pending) {
127+
// The future is no longer pending, so no clean up is required.
128+
return;
129+
}
130+
131+
// We mark the future as dead, so the next time it gets polled we can react to it's inability to resolve.
132+
data.state = SignalFutureState::Dead;
133+
134+
// If we got a waker we trigger it to get the future polled. If there is no waker, then the future has not been polled yet and we
135+
// simply wait for the runtime to perform the first poll.
136+
if let Some(ref waker) = data.waker {
137+
waker.wake_by_ref();
138+
}
139+
}
140+
}
141+
142+
#[derive(Default)]
143+
enum SignalFutureState<T> {
144+
#[default]
145+
Pending,
146+
Ready(T),
147+
Dead,
148+
Dropped,
149+
}
150+
151+
impl<T> SignalFutureState<T> {
152+
fn take(&mut self) -> Self {
153+
let new_value = match self {
154+
Self::Pending => Self::Pending,
155+
Self::Ready(_) | Self::Dead => Self::Dead,
156+
Self::Dropped => Self::Dropped,
157+
};
158+
159+
std::mem::replace(self, new_value)
160+
}
161+
}
162+
163+
/// A future that tries to resolve as soon as the provided Godot signal was emitted.
164+
///
165+
/// The future might resolve to an error if the signal object is freed before the signal is emitted.
166+
pub struct FallibleSignalFuture<R: ParamTuple + Sync + Send> {
167+
data: Arc<Mutex<SignalFutureData<R>>>,
168+
callable: SignalFutureResolver<R>,
169+
signal: Signal,
170+
}
171+
172+
impl<R: ParamTuple + Sync + Send> FallibleSignalFuture<R> {
173+
fn new(signal: Signal) -> Self {
174+
debug_assert!(
175+
!signal.is_null(),
176+
"Failed to create a future for an invalid Signal!\nEither the signal object was already freed or the signal was not registered in the object before using it.",
177+
);
178+
179+
let data = Arc::new(Mutex::new(SignalFutureData::default()));
180+
181+
// The callable currently requires that the return value is Sync + Send.
182+
let callable = SignalFutureResolver::new(data.clone());
183+
184+
signal.connect(
185+
&Callable::from_custom(callable.clone()),
186+
ConnectFlags::ONE_SHOT.ord() as i64,
187+
);
188+
189+
Self {
190+
data,
191+
callable,
192+
signal,
193+
}
194+
}
195+
fn poll(&mut self, cx: &mut Context<'_>) -> Poll<Result<R, FallibleSignalFutureError>> {
196+
let mut data = self.data.lock().unwrap();
197+
198+
data.waker.replace(cx.waker().clone());
199+
200+
let value = data.state.take();
201+
202+
match value {
203+
SignalFutureState::Pending => Poll::Pending,
204+
SignalFutureState::Dropped => unreachable!(),
205+
SignalFutureState::Dead => Poll::Ready(Err(FallibleSignalFutureError)),
206+
SignalFutureState::Ready(value) => Poll::Ready(Ok(value)),
207+
}
208+
}
209+
}
210+
211+
/// Error that might be returned by the [`FallibleSignalFuture`].
212+
///
213+
/// This error is being resolved to when the signal object is freed before the awaited singal is emitted.
214+
#[derive(Debug)]
215+
pub struct FallibleSignalFutureError;
216+
217+
impl Display for FallibleSignalFutureError {
218+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
219+
write!(
220+
f,
221+
"The signal object was freed before the awaited signal was emitted"
222+
)
223+
}
224+
}
225+
226+
impl std::error::Error for FallibleSignalFutureError {}
227+
228+
impl<R: ParamTuple + Sync + Send> Future for FallibleSignalFuture<R> {
229+
type Output = Result<R, FallibleSignalFutureError>;
230+
231+
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
232+
self.get_mut().poll(cx)
233+
}
234+
}
235+
236+
impl<R: ParamTuple + Sync + Send> Drop for FallibleSignalFuture<R> {
237+
fn drop(&mut self) {
238+
// The callable might alredy be destroyed, this occurs during engine shutdown.
239+
if self.signal.object().is_none() {
240+
return;
241+
}
242+
243+
let mut data_lock = self.data.lock().unwrap();
244+
245+
data_lock.state = SignalFutureState::Dropped;
246+
247+
drop(data_lock);
248+
249+
// We create a new Godot Callable from our RustCallable so we get independent reference counting.
250+
let gd_callable = Callable::from_custom(self.callable.clone());
251+
252+
// is_connected will return true if the signal was never emited before the future is dropped.
253+
if self.signal.is_connected(&gd_callable) {
254+
self.signal.disconnect(&gd_callable);
255+
}
256+
}
257+
}
258+
259+
impl Signal {
260+
/// Creates a fallible future for this signal.
261+
///
262+
/// The future will resolve the next time the signal is emitted.
263+
/// See [`TrySignalFuture`] for details.
264+
///
265+
/// Since the `Signal` type does not contain information on the signal argument types, the future output type has to be inferred from
266+
/// the call to this function.
267+
pub fn to_fallible_future<R: ParamTuple + Sync + Send>(&self) -> FallibleSignalFuture<R> {
268+
FallibleSignalFuture::new(self.clone())
269+
}
270+
271+
/// Creates a future for this signal.
272+
///
273+
/// The future will resolve the next time the signal is emitted, but might panic if the signal object is freed.
274+
/// See [`SignalFuture`] for details.
275+
///
276+
/// Since the `Signal` type does not contain information on the signal argument types, the future output type has to be inferred from
277+
/// the call to this function.
278+
pub fn to_future<R: ParamTuple + Sync + Send>(&self) -> SignalFuture<R> {
279+
SignalFuture::new(self.clone())
280+
}
281+
}
282+
283+
impl<C: WithBaseField, R: ParamTuple + Sync + Send> TypedSignal<'_, C, R> {
284+
/// Creates a fallible future for this signal.
285+
///
286+
/// The future will resolve the next time the signal is emitted.
287+
/// See [`FallibleSignalFuture`] for details.
288+
pub fn to_fallible_future(&self) -> FallibleSignalFuture<R> {
289+
FallibleSignalFuture::new(self.to_untyped())
290+
}
291+
292+
/// Creates a future for this signal.
293+
///
294+
/// The future will resolve the next time the signal is emitted, but might panic if the signal object is freed.
295+
/// See [`SignalFuture`] for details.
296+
pub fn to_future(&self) -> SignalFuture<R> {
297+
SignalFuture::new(self.to_untyped())
298+
}
299+
}
300+
301+
impl<C: WithBaseField, R: ParamTuple + Sync + Send> IntoFuture for &TypedSignal<'_, C, R> {
302+
type Output = R;
303+
304+
type IntoFuture = SignalFuture<R>;
305+
306+
fn into_future(self) -> Self::IntoFuture {
307+
self.to_future()
308+
}
309+
}
310+
311+
#[cfg(test)]
312+
mod tests {
313+
use crate::sys;
314+
use std::sync::Arc;
315+
316+
use super::SignalFutureResolver;
317+
318+
/// Test that the hash of a cloned future resolver is equal to its original version. With this equality in place, we can create new
319+
/// Callables that are equal to their original version but have separate reference counting.
320+
#[test]
321+
fn future_resolver_cloned_hash() {
322+
let resolver_a = SignalFutureResolver::<u8>::new(Arc::default());
323+
let resolver_b = resolver_a.clone();
324+
325+
let hash_a = sys::hash_value(&resolver_a);
326+
let hash_b = sys::hash_value(&resolver_b);
327+
328+
assert_eq!(hash_a, hash_b);
329+
}
330+
}

godot-core/src/task/mod.rs

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* Copyright (c) godot-rust; Bromeon and contributors.
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
mod futures;
9+
10+
pub use futures::{FallibleSignalFuture, FallibleSignalFutureError, SignalFuture};
11+
12+
// Only exported for itest.
13+
#[cfg(feature = "trace")]
14+
pub use futures::SignalFutureResolver;

godot/src/lib.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
//! * [`tools`], higher-level utilities that extend the generated code, e.g. `load<T>()`.
3636
//! * [`meta`], fundamental information about types, properties and conversions.
3737
//! * [`init`], entry point and global library configuration.
38+
//! * [`task`], integration with async code.
3839
//!
3940
//! The [`prelude`] contains often-imported symbols; feel free to `use godot::prelude::*` in your code.
4041
//! <br><br>
@@ -160,7 +161,7 @@ compile_error!("The feature `double-precision` currently requires `api-custom` d
160161
// Modules
161162

162163
#[doc(inline)]
163-
pub use godot_core::{builtin, classes, global, meta, obj, tools};
164+
pub use godot_core::{builtin, classes, global, meta, obj, task, tools};
164165

165166
#[doc(hidden)]
166167
pub use godot_core::possibly_docs as docs;

0 commit comments

Comments
 (0)