Skip to content

Commit edb98df

Browse files
committed
Async Signals
1 parent b2bff88 commit edb98df

File tree

1 file changed

+310
-0
lines changed

1 file changed

+310
-0
lines changed

godot-core/src/builtin/signal.rs

Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ 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+
pub use futures::*;
24+
2225
/// A `Signal` represents a signal of an Object instance in Godot.
2326
///
2427
/// Signals are composed of a reference to an `Object` and the name of the signal on this object.
@@ -213,3 +216,310 @@ impl fmt::Display for Signal {
213216
write!(f, "{}", self.to_variant())
214217
}
215218
}
219+
220+
// ----------------------------------------------------------------------------------------------------------------------------------------
221+
// Implementation of a rust future for Godot Signals
222+
#[cfg(since_api = "4.2")]
223+
mod futures {
224+
use std::fmt::Display;
225+
use std::future::Future;
226+
use std::pin::Pin;
227+
use std::sync::{Arc, Mutex};
228+
use std::task::{Context, Poll, Waker};
229+
230+
use crate::builtin::{Callable, RustCallable, Variant};
231+
use crate::classes::object::ConnectFlags;
232+
use crate::meta::FromGodot;
233+
use crate::obj::EngineEnum;
234+
235+
use super::Signal;
236+
237+
pub struct SignalFuture<R: FromSignalArgs> {
238+
state: Arc<Mutex<(Option<R>, Option<Waker>)>>,
239+
callable: Callable,
240+
signal: Signal,
241+
}
242+
243+
impl<R: FromSignalArgs> SignalFuture<R> {
244+
fn new(signal: Signal) -> Self {
245+
let state = Arc::new(Mutex::new((None, Option::<Waker>::None)));
246+
let callback_state = state.clone();
247+
248+
// the callable currently requires that the return value is Sync + Send
249+
let callable = Callable::from_local_fn("async_task", move |args: &[&Variant]| {
250+
let mut lock = callback_state.lock().unwrap();
251+
let waker = lock.1.take();
252+
253+
lock.0.replace(R::from_args(args));
254+
drop(lock);
255+
256+
if let Some(waker) = waker {
257+
waker.wake();
258+
}
259+
260+
Ok(Variant::nil())
261+
});
262+
263+
signal.connect(&callable, ConnectFlags::ONE_SHOT.ord() as i64);
264+
265+
Self {
266+
state,
267+
callable,
268+
signal,
269+
}
270+
}
271+
}
272+
273+
impl<R: FromSignalArgs> Future for SignalFuture<R> {
274+
type Output = R;
275+
276+
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
277+
let mut lock = self.state.lock().unwrap();
278+
279+
if let Some(result) = lock.0.take() {
280+
return Poll::Ready(result);
281+
}
282+
283+
lock.1.replace(cx.waker().clone());
284+
285+
Poll::Pending
286+
}
287+
}
288+
289+
impl<R: FromSignalArgs> Drop for SignalFuture<R> {
290+
fn drop(&mut self) {
291+
if !self.callable.is_valid() {
292+
return;
293+
}
294+
295+
if self.signal.object().is_none() {
296+
return;
297+
}
298+
299+
if self.signal.is_connected(&self.callable) {
300+
self.signal.disconnect(&self.callable);
301+
}
302+
}
303+
}
304+
305+
struct GuaranteedSignalFutureResolver<R> {
306+
state: Arc<Mutex<(GuaranteedSignalFutureState<R>, Option<Waker>)>>,
307+
}
308+
309+
impl<R> Clone for GuaranteedSignalFutureResolver<R> {
310+
fn clone(&self) -> Self {
311+
Self {
312+
state: self.state.clone(),
313+
}
314+
}
315+
}
316+
317+
impl<R> GuaranteedSignalFutureResolver<R> {
318+
fn new(state: Arc<Mutex<(GuaranteedSignalFutureState<R>, Option<Waker>)>>) -> Self {
319+
Self { state }
320+
}
321+
}
322+
323+
impl<R> std::hash::Hash for GuaranteedSignalFutureResolver<R> {
324+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
325+
state.write_usize(Arc::as_ptr(&self.state) as usize);
326+
}
327+
}
328+
329+
impl<R> PartialEq for GuaranteedSignalFutureResolver<R> {
330+
fn eq(&self, other: &Self) -> bool {
331+
Arc::ptr_eq(&self.state, &other.state)
332+
}
333+
}
334+
335+
impl<R: FromSignalArgs> RustCallable for GuaranteedSignalFutureResolver<R> {
336+
fn invoke(&mut self, args: &[&Variant]) -> Result<Variant, ()> {
337+
let mut lock = self.state.lock().unwrap();
338+
let waker = lock.1.take();
339+
340+
lock.0 = GuaranteedSignalFutureState::Ready(R::from_args(args));
341+
drop(lock);
342+
343+
if let Some(waker) = waker {
344+
waker.wake();
345+
}
346+
347+
Ok(Variant::nil())
348+
}
349+
}
350+
351+
impl<R> Display for GuaranteedSignalFutureResolver<R> {
352+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
353+
write!(
354+
f,
355+
"GuaranteedSignalFutureResolver::<{}>",
356+
std::any::type_name::<R>()
357+
)
358+
}
359+
}
360+
361+
// this resolver will resolve the future when it's being dropped (i.e. the engine removes all connected signal callables). This is very unusual.
362+
impl<R> Drop for GuaranteedSignalFutureResolver<R> {
363+
fn drop(&mut self) {
364+
let mut lock = self.state.lock().unwrap();
365+
366+
if !matches!(lock.0, GuaranteedSignalFutureState::Pending) {
367+
return;
368+
}
369+
370+
lock.0 = GuaranteedSignalFutureState::Dead;
371+
372+
if let Some(ref waker) = lock.1 {
373+
waker.wake_by_ref();
374+
}
375+
}
376+
}
377+
378+
#[derive(Default)]
379+
enum GuaranteedSignalFutureState<T> {
380+
#[default]
381+
Pending,
382+
Ready(T),
383+
Dead,
384+
Dropped,
385+
}
386+
387+
impl<T> GuaranteedSignalFutureState<T> {
388+
fn take(&mut self) -> Self {
389+
let new_value = match self {
390+
Self::Pending => Self::Pending,
391+
Self::Ready(_) | Self::Dead => Self::Dead,
392+
Self::Dropped => Self::Dropped,
393+
};
394+
395+
std::mem::replace(self, new_value)
396+
}
397+
}
398+
399+
/// The guaranteed signal future will always resolve, but might resolve to `None` if the owning object is freed
400+
/// before the signal is emitted.
401+
///
402+
/// This is inconsistent with how awaiting signals in Godot work and how async works in rust. The behavior was requested as part of some
403+
/// user feedback for the initial POC.
404+
pub struct GuaranteedSignalFuture<R: FromSignalArgs> {
405+
state: Arc<Mutex<(GuaranteedSignalFutureState<R>, Option<Waker>)>>,
406+
callable: GuaranteedSignalFutureResolver<R>,
407+
signal: Signal,
408+
}
409+
410+
impl<R: FromSignalArgs> GuaranteedSignalFuture<R> {
411+
fn new(signal: Signal) -> Self {
412+
let state = Arc::new(Mutex::new((
413+
GuaranteedSignalFutureState::Pending,
414+
Option::<Waker>::None,
415+
)));
416+
417+
// the callable currently requires that the return value is Sync + Send
418+
let callable = GuaranteedSignalFutureResolver::new(state.clone());
419+
420+
signal.connect(
421+
&Callable::from_custom(callable.clone()),
422+
ConnectFlags::ONE_SHOT.ord() as i64,
423+
);
424+
425+
Self {
426+
state,
427+
callable,
428+
signal,
429+
}
430+
}
431+
}
432+
433+
impl<R: FromSignalArgs> Future for GuaranteedSignalFuture<R> {
434+
type Output = Option<R>;
435+
436+
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
437+
let mut lock = self.state.lock().unwrap();
438+
439+
lock.1.replace(cx.waker().clone());
440+
441+
let value = lock.0.take();
442+
443+
match value {
444+
GuaranteedSignalFutureState::Pending => Poll::Pending,
445+
GuaranteedSignalFutureState::Dropped => unreachable!(),
446+
GuaranteedSignalFutureState::Dead => Poll::Ready(None),
447+
GuaranteedSignalFutureState::Ready(value) => Poll::Ready(Some(value)),
448+
}
449+
}
450+
}
451+
452+
impl<R: FromSignalArgs> Drop for GuaranteedSignalFuture<R> {
453+
fn drop(&mut self) {
454+
if self.signal.object().is_none() {
455+
return;
456+
}
457+
458+
self.state.lock().unwrap().0 = GuaranteedSignalFutureState::Dropped;
459+
460+
let gd_callable = Callable::from_custom(self.callable.clone());
461+
462+
if self.signal.is_connected(&gd_callable) {
463+
self.signal.disconnect(&gd_callable);
464+
}
465+
}
466+
}
467+
468+
pub trait FromSignalArgs: Sync + Send + 'static {
469+
fn from_args(args: &[&Variant]) -> Self;
470+
}
471+
472+
impl<R: FromGodot + Sync + Send + 'static> FromSignalArgs for R {
473+
fn from_args(args: &[&Variant]) -> Self {
474+
args.first()
475+
.map(|arg| (*arg).to_owned())
476+
.unwrap_or_default()
477+
.to()
478+
}
479+
}
480+
481+
// more of these should be generated via macro to support more than two signal arguments
482+
impl<R1: FromGodot + Sync + Send + 'static, R2: FromGodot + Sync + Send + 'static>
483+
FromSignalArgs for (R1, R2)
484+
{
485+
fn from_args(args: &[&Variant]) -> Self {
486+
(args[0].to(), args[0].to())
487+
}
488+
}
489+
490+
impl Signal {
491+
pub fn to_guaranteed_future<R: FromSignalArgs>(&self) -> GuaranteedSignalFuture<R> {
492+
GuaranteedSignalFuture::new(self.clone())
493+
}
494+
495+
pub fn to_future<R: FromSignalArgs>(&self) -> SignalFuture<R> {
496+
SignalFuture::new(self.clone())
497+
}
498+
}
499+
500+
#[cfg(test)]
501+
mod tests {
502+
use std::{
503+
hash::{DefaultHasher, Hash, Hasher},
504+
sync::Arc,
505+
};
506+
507+
use super::GuaranteedSignalFutureResolver;
508+
509+
#[test]
510+
fn guaranteed_future_waker_cloned_hash() {
511+
let waker_a = GuaranteedSignalFutureResolver::<u8>::new(Arc::default());
512+
let waker_b = waker_a.clone();
513+
514+
let mut hasher = DefaultHasher::new();
515+
waker_a.hash(&mut hasher);
516+
let hash_a = hasher.finish();
517+
518+
let mut hasher = DefaultHasher::new();
519+
waker_b.hash(&mut hasher);
520+
let hash_b = hasher.finish();
521+
522+
assert_eq!(hash_a, hash_b);
523+
}
524+
}
525+
}

0 commit comments

Comments
 (0)