Skip to content

Commit 95c9d3d

Browse files
committed
Async Signals
1 parent b2bff88 commit 95c9d3d

File tree

2 files changed

+332
-0
lines changed

2 files changed

+332
-0
lines changed

godot-core/src/builtin/signal.rs

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

0 commit comments

Comments
 (0)