Skip to content

Commit e0f2dca

Browse files
committed
Async Signals
1 parent b2bff88 commit e0f2dca

File tree

2 files changed

+346
-0
lines changed

2 files changed

+346
-0
lines changed

godot-core/src/builtin/signal.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@ use crate::obj::bounds::DynMemory;
1919
use crate::obj::{Bounds, Gd, GodotClass, InstanceId};
2020
use sys::{ffi_methods, GodotFfi};
2121

22+
#[cfg(since_api = "4.2")]
23+
mod futures;
24+
25+
#[cfg(since_api = "4.2")]
26+
pub use futures::{FromSignalArgs, GuaranteedSignalFuture, SignalFuture};
27+
28+
// Only exported for itest.
29+
#[cfg(all(since_api = "4.2", feature = "trace"))]
30+
pub use futures::GuaranteedSignalFutureResolver;
31+
2232
/// A `Signal` represents a signal of an Object instance in Godot.
2333
///
2434
/// Signals are composed of a reference to an `Object` and the name of the signal on this object.
Lines changed: 336 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,336 @@
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 std::fmt::Display;
9+
use std::future::Future;
10+
use std::pin::Pin;
11+
use std::sync::{Arc, Mutex};
12+
use std::task::{Context, Poll, Waker};
13+
14+
use crate::builtin::{Callable, RustCallable, Variant};
15+
use crate::classes::object::ConnectFlags;
16+
use crate::meta::FromGodot;
17+
use crate::obj::EngineEnum;
18+
19+
use super::Signal;
20+
21+
struct SignalFutureState<Output> {
22+
output: Option<Output>,
23+
waker: Option<Waker>,
24+
}
25+
26+
// Not derived, otherwise an extra bound `Output: Default` is required.
27+
impl<Output> Default for SignalFutureState<Output> {
28+
fn default() -> Self {
29+
Self {
30+
output: None,
31+
waker: None,
32+
}
33+
}
34+
}
35+
36+
pub struct SignalFuture<R: FromSignalArgs> {
37+
state: Arc<Mutex<SignalFutureState<R>>>,
38+
callable: Callable,
39+
signal: Signal,
40+
}
41+
42+
impl<R: FromSignalArgs> SignalFuture<R> {
43+
fn new(signal: Signal) -> Self {
44+
let state = Arc::new(Mutex::new(SignalFutureState::<R>::default()));
45+
let callback_state = state.clone();
46+
47+
#[cfg(not(feature = "experimental-threads"))]
48+
let create_callable = Callable::from_local_fn;
49+
50+
#[cfg(feature = "experimental-threads")]
51+
let create_callable = Callable::from_sync_fn;
52+
53+
// The callable requires that the return value is Sync + Send.
54+
let callable = create_callable("SignalFuture::resolve", move |args: &[&Variant]| {
55+
let mut lock = callback_state.lock().unwrap();
56+
let waker = lock.waker.take();
57+
58+
lock.output.replace(R::from_args(args));
59+
drop(lock);
60+
61+
if let Some(waker) = waker {
62+
waker.wake();
63+
}
64+
65+
Ok(Variant::nil())
66+
});
67+
68+
signal.connect(&callable, ConnectFlags::ONE_SHOT.ord() as i64);
69+
70+
Self {
71+
state,
72+
callable,
73+
signal,
74+
}
75+
}
76+
}
77+
78+
impl<R: FromSignalArgs> Future for SignalFuture<R> {
79+
type Output = R;
80+
81+
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
82+
let mut lock = self.state.lock().unwrap();
83+
84+
if let Some(result) = lock.output.take() {
85+
return Poll::Ready(result);
86+
}
87+
88+
lock.waker.replace(cx.waker().clone());
89+
90+
Poll::Pending
91+
}
92+
}
93+
94+
impl<R: FromSignalArgs> Drop for SignalFuture<R> {
95+
fn drop(&mut self) {
96+
// The callable might alredy be destroyed, this occurs during engine shutdown.
97+
if !self.callable.is_valid() {
98+
return;
99+
}
100+
101+
// If the future gets dropped after the signal object was already freed, it will be None. This can occur when the object is freed
102+
// and later the async task is canceled.
103+
if self.signal.object().is_none() {
104+
return;
105+
}
106+
107+
if self.signal.is_connected(&self.callable) {
108+
self.signal.disconnect(&self.callable);
109+
}
110+
}
111+
}
112+
113+
// Only public for itest.
114+
#[cfg_attr(feature = "trace", derive(Default))]
115+
pub struct GuaranteedSignalFutureResolver<R> {
116+
state: Arc<Mutex<(GuaranteedSignalFutureState<R>, Option<Waker>)>>,
117+
}
118+
119+
impl<R> Clone for GuaranteedSignalFutureResolver<R> {
120+
fn clone(&self) -> Self {
121+
Self {
122+
state: self.state.clone(),
123+
}
124+
}
125+
}
126+
127+
impl<R> GuaranteedSignalFutureResolver<R> {
128+
fn new(state: Arc<Mutex<(GuaranteedSignalFutureState<R>, Option<Waker>)>>) -> Self {
129+
Self { state }
130+
}
131+
}
132+
133+
impl<R> std::hash::Hash for GuaranteedSignalFutureResolver<R> {
134+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
135+
state.write_usize(Arc::as_ptr(&self.state) as usize);
136+
}
137+
}
138+
139+
impl<R> PartialEq for GuaranteedSignalFutureResolver<R> {
140+
fn eq(&self, other: &Self) -> bool {
141+
Arc::ptr_eq(&self.state, &other.state)
142+
}
143+
}
144+
145+
impl<R: FromSignalArgs> RustCallable for GuaranteedSignalFutureResolver<R> {
146+
fn invoke(&mut self, args: &[&Variant]) -> Result<Variant, ()> {
147+
let mut lock = self.state.lock().unwrap();
148+
let (state, waker) = &mut *lock;
149+
let waker = waker.take();
150+
151+
*state = GuaranteedSignalFutureState::Ready(R::from_args(args));
152+
drop(lock);
153+
154+
if let Some(waker) = waker {
155+
waker.wake();
156+
}
157+
158+
Ok(Variant::nil())
159+
}
160+
}
161+
162+
impl<R> Display for GuaranteedSignalFutureResolver<R> {
163+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164+
write!(
165+
f,
166+
"GuaranteedSignalFutureResolver::<{}>",
167+
std::any::type_name::<R>()
168+
)
169+
}
170+
}
171+
172+
// this resolver will resolve the future when it's being dropped (i.e. the engine removes all connected signal callables). This is very unusual.
173+
impl<R> Drop for GuaranteedSignalFutureResolver<R> {
174+
fn drop(&mut self) {
175+
let mut lock = self.state.lock().unwrap();
176+
let (state, waker) = &mut *lock;
177+
178+
if !matches!(state, GuaranteedSignalFutureState::Pending) {
179+
return;
180+
}
181+
182+
*state = GuaranteedSignalFutureState::Dead;
183+
184+
if let Some(waker) = waker {
185+
waker.wake_by_ref();
186+
}
187+
}
188+
}
189+
190+
#[derive(Default)]
191+
enum GuaranteedSignalFutureState<T> {
192+
#[default]
193+
Pending,
194+
Ready(T),
195+
Dead,
196+
Dropped,
197+
}
198+
199+
impl<T> GuaranteedSignalFutureState<T> {
200+
fn take(&mut self) -> Self {
201+
let new_value = match self {
202+
Self::Pending => Self::Pending,
203+
Self::Ready(_) | Self::Dead => Self::Dead,
204+
Self::Dropped => Self::Dropped,
205+
};
206+
207+
std::mem::replace(self, new_value)
208+
}
209+
}
210+
211+
/// The guaranteed signal future will always resolve, but might resolve to `None` if the owning object is freed
212+
/// before the signal is emitted.
213+
///
214+
/// This is inconsistent with how awaiting signals in Godot work and how async works in rust. The behavior was requested as part of some
215+
/// user feedback for the initial POC.
216+
pub struct GuaranteedSignalFuture<R: FromSignalArgs> {
217+
state: Arc<Mutex<(GuaranteedSignalFutureState<R>, Option<Waker>)>>,
218+
callable: GuaranteedSignalFutureResolver<R>,
219+
signal: Signal,
220+
}
221+
222+
impl<R: FromSignalArgs> GuaranteedSignalFuture<R> {
223+
fn new(signal: Signal) -> Self {
224+
let state = Arc::new(Mutex::new((
225+
GuaranteedSignalFutureState::Pending,
226+
Option::<Waker>::None,
227+
)));
228+
229+
// The callable currently requires that the return value is Sync + Send.
230+
let callable = GuaranteedSignalFutureResolver::new(state.clone());
231+
232+
signal.connect(
233+
&Callable::from_custom(callable.clone()),
234+
ConnectFlags::ONE_SHOT.ord() as i64,
235+
);
236+
237+
Self {
238+
state,
239+
callable,
240+
signal,
241+
}
242+
}
243+
}
244+
245+
impl<R: FromSignalArgs> Future for GuaranteedSignalFuture<R> {
246+
type Output = Option<R>;
247+
248+
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
249+
let mut lock = self.state.lock().unwrap();
250+
let (state, waker) = &mut *lock;
251+
252+
waker.replace(cx.waker().clone());
253+
254+
let value = state.take();
255+
256+
match value {
257+
GuaranteedSignalFutureState::Pending => Poll::Pending,
258+
GuaranteedSignalFutureState::Dropped => unreachable!(),
259+
GuaranteedSignalFutureState::Dead => Poll::Ready(None),
260+
GuaranteedSignalFutureState::Ready(value) => Poll::Ready(Some(value)),
261+
}
262+
}
263+
}
264+
265+
impl<R: FromSignalArgs> Drop for GuaranteedSignalFuture<R> {
266+
fn drop(&mut self) {
267+
// The callable might alredy be destroyed, this occurs during engine shutdown.
268+
if self.signal.object().is_none() {
269+
return;
270+
}
271+
272+
let mut lock = self.state.lock().unwrap();
273+
let (state, _) = &mut *lock;
274+
275+
*state = GuaranteedSignalFutureState::Dropped;
276+
277+
let gd_callable = Callable::from_custom(self.callable.clone());
278+
279+
if self.signal.is_connected(&gd_callable) {
280+
self.signal.disconnect(&gd_callable);
281+
}
282+
}
283+
}
284+
285+
pub trait FromSignalArgs: Sync + Send + 'static {
286+
fn from_args(args: &[&Variant]) -> Self;
287+
}
288+
289+
impl<R: FromGodot + Sync + Send + 'static> FromSignalArgs for R {
290+
fn from_args(args: &[&Variant]) -> Self {
291+
args.first()
292+
.map(|arg| (*arg).to_owned())
293+
.unwrap_or_default()
294+
.to()
295+
}
296+
}
297+
298+
// more of these should be generated via macro to support more than two signal arguments
299+
impl<R1: FromGodot + Sync + Send + 'static, R2: FromGodot + Sync + Send + 'static> FromSignalArgs
300+
for (R1, R2)
301+
{
302+
fn from_args(args: &[&Variant]) -> Self {
303+
(args[0].to(), args[0].to())
304+
}
305+
}
306+
307+
impl Signal {
308+
pub fn to_guaranteed_future<R: FromSignalArgs>(&self) -> GuaranteedSignalFuture<R> {
309+
GuaranteedSignalFuture::new(self.clone())
310+
}
311+
312+
pub fn to_future<R: FromSignalArgs>(&self) -> SignalFuture<R> {
313+
SignalFuture::new(self.clone())
314+
}
315+
}
316+
317+
#[cfg(test)]
318+
mod tests {
319+
use crate::sys;
320+
use std::sync::Arc;
321+
322+
use super::GuaranteedSignalFutureResolver;
323+
324+
/// Test that the hash of a cloned future resolver is equal to its original version. With this equality in place, we can create new
325+
/// Callables that are equal to their original version but have separate reference counting.
326+
#[test]
327+
fn guaranteed_future_resolver_cloned_hash() {
328+
let resolver_a = GuaranteedSignalFutureResolver::<u8>::new(Arc::default());
329+
let resolver_b = resolver_a.clone();
330+
331+
let hash_a = sys::hash_value(&resolver_a);
332+
let hash_b = sys::hash_value(&resolver_b);
333+
334+
assert_eq!(hash_a, hash_b);
335+
}
336+
}

0 commit comments

Comments
 (0)