Skip to content

Commit d31963d

Browse files
committed
Provide future that resolves despite freed object
1 parent f79a2c0 commit d31963d

File tree

2 files changed

+214
-7
lines changed

2 files changed

+214
-7
lines changed

godot-core/src/tools/async_support.rs

+192-5
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1+
use std::any::type_name;
12
use std::cell::RefCell;
3+
use std::fmt::{Debug, Display};
24
use std::future::Future;
35
use std::marker::PhantomData;
46
use std::pin::Pin;
57
use std::sync::{Arc, Mutex};
68
use std::task::{Context, Poll, Wake, Waker};
79
use std::thread::{self, ThreadId};
810

9-
use crate::builtin::{Callable, Signal, Variant};
11+
use crate::builtin::{Callable, RustCallable, Signal, Variant};
1012
use crate::classes::object::ConnectFlags;
1113
use crate::classes::Os;
12-
use crate::godot_warn;
1314
use crate::meta::{FromGodot, ToGodot};
1415
use crate::obj::EngineEnum;
1516

@@ -348,22 +349,172 @@ impl<R: FromSignalArgs> Future for SignalFuture<R> {
348349
impl<R: FromSignalArgs> Drop for SignalFuture<R> {
349350
fn drop(&mut self) {
350351
if !self.callable.is_valid() {
351-
godot_warn!("dropping furure but callable no longer exists!");
352352
return;
353353
}
354354

355355
if self.signal.object().is_none() {
356-
godot_warn!("dropping furure but signal owner no longer exists!");
357356
return;
358357
}
359358

360359
if self.signal.is_connected(self.callable.clone()) {
361-
godot_warn!("dropping furure but signal still connected!");
362360
self.signal.disconnect(self.callable.clone());
363361
}
364362
}
365363
}
366364

365+
struct GuaranteedSignalFutureWaker<R> {
366+
state: Arc<Mutex<(GuaranteedSignalFutureState<R>, Option<Waker>)>>,
367+
}
368+
369+
impl<R> Clone for GuaranteedSignalFutureWaker<R> {
370+
fn clone(&self) -> Self {
371+
Self {
372+
state: self.state.clone(),
373+
}
374+
}
375+
}
376+
377+
impl<R> GuaranteedSignalFutureWaker<R> {
378+
fn new(state: Arc<Mutex<(GuaranteedSignalFutureState<R>, Option<Waker>)>>) -> Self {
379+
Self { state }
380+
}
381+
}
382+
383+
impl<R> std::hash::Hash for GuaranteedSignalFutureWaker<R> {
384+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
385+
state.write_usize(Arc::as_ptr(&self.state) as usize);
386+
}
387+
}
388+
389+
impl<R> PartialEq for GuaranteedSignalFutureWaker<R> {
390+
fn eq(&self, other: &Self) -> bool {
391+
Arc::ptr_eq(&self.state, &other.state)
392+
}
393+
}
394+
395+
impl<R: FromSignalArgs> RustCallable for GuaranteedSignalFutureWaker<R> {
396+
fn invoke(&mut self, args: &[&Variant]) -> Result<Variant, ()> {
397+
let mut lock = self.state.lock().unwrap();
398+
let waker = lock.1.take();
399+
400+
lock.0 = GuaranteedSignalFutureState::Ready(R::from_args(args));
401+
drop(lock);
402+
403+
if let Some(waker) = waker {
404+
waker.wake();
405+
}
406+
407+
Ok(Variant::nil())
408+
}
409+
}
410+
411+
impl<R> Display for GuaranteedSignalFutureWaker<R> {
412+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
413+
write!(f, "SafeCallable::<{}>", type_name::<R>())
414+
}
415+
}
416+
417+
impl<R> Drop for GuaranteedSignalFutureWaker<R> {
418+
fn drop(&mut self) {
419+
let mut lock = self.state.lock().unwrap();
420+
421+
if !matches!(lock.0, GuaranteedSignalFutureState::Pending) {
422+
return;
423+
}
424+
425+
lock.0 = GuaranteedSignalFutureState::Dead;
426+
427+
if let Some(ref waker) = lock.1 {
428+
waker.wake_by_ref();
429+
}
430+
}
431+
}
432+
433+
#[derive(Default)]
434+
enum GuaranteedSignalFutureState<T> {
435+
#[default]
436+
Pending,
437+
Ready(T),
438+
Dead,
439+
Dropped,
440+
}
441+
442+
impl<T> GuaranteedSignalFutureState<T> {
443+
fn take(&mut self) -> Self {
444+
let new_value = match self {
445+
Self::Pending => Self::Pending,
446+
Self::Ready(_) | Self::Dead => Self::Dead,
447+
Self::Dropped => Self::Dropped,
448+
};
449+
450+
std::mem::replace(self, new_value)
451+
}
452+
}
453+
454+
pub struct GuaranteedSignalFuture<R: FromSignalArgs> {
455+
state: Arc<Mutex<(GuaranteedSignalFutureState<R>, Option<Waker>)>>,
456+
callable: GuaranteedSignalFutureWaker<R>,
457+
signal: Signal,
458+
}
459+
460+
impl<R: FromSignalArgs + Debug> GuaranteedSignalFuture<R> {
461+
fn new(signal: Signal) -> Self {
462+
let state = Arc::new(Mutex::new((
463+
GuaranteedSignalFutureState::Pending,
464+
Option::<Waker>::None,
465+
)));
466+
467+
// the callable currently requires that the return value is Sync + Send
468+
let callable = GuaranteedSignalFutureWaker::new(state.clone());
469+
470+
signal.connect(
471+
Callable::from_custom(callable.clone()),
472+
ConnectFlags::ONE_SHOT.ord() as i64,
473+
);
474+
475+
Self {
476+
state,
477+
callable,
478+
signal,
479+
}
480+
}
481+
}
482+
483+
impl<R: FromSignalArgs> Future for GuaranteedSignalFuture<R> {
484+
type Output = Option<R>;
485+
486+
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
487+
let mut lock = self.state.lock().unwrap();
488+
489+
lock.1.replace(cx.waker().clone());
490+
491+
let value = lock.0.take();
492+
493+
match value {
494+
GuaranteedSignalFutureState::Pending => Poll::Pending,
495+
GuaranteedSignalFutureState::Dropped => unreachable!(),
496+
GuaranteedSignalFutureState::Dead => Poll::Ready(None),
497+
GuaranteedSignalFutureState::Ready(value) => Poll::Ready(Some(value)),
498+
}
499+
}
500+
}
501+
502+
impl<R: FromSignalArgs> Drop for GuaranteedSignalFuture<R> {
503+
fn drop(&mut self) {
504+
if self.signal.object().is_none() {
505+
return;
506+
}
507+
508+
self.state.lock().unwrap().0 = GuaranteedSignalFutureState::Dropped;
509+
510+
let gd_callable = Callable::from_custom(self.callable.clone());
511+
512+
if self.signal.is_connected(gd_callable.clone()) {
513+
self.signal.disconnect(gd_callable);
514+
}
515+
}
516+
}
517+
367518
pub trait FromSignalArgs: Sync + Send + 'static {
368519
fn from_args(args: &[&Variant]) -> Self;
369520
}
@@ -397,3 +548,39 @@ impl<R: FromSignalArgs> ToSignalFuture<R> for Signal {
397548
SignalFuture::new(self.clone())
398549
}
399550
}
551+
552+
pub trait ToGuaranteedSignalFuture<R: FromSignalArgs + Debug> {
553+
fn to_guaranteed_future(&self) -> GuaranteedSignalFuture<R>;
554+
}
555+
556+
impl<R: FromSignalArgs + Debug> ToGuaranteedSignalFuture<R> for Signal {
557+
fn to_guaranteed_future(&self) -> GuaranteedSignalFuture<R> {
558+
GuaranteedSignalFuture::new(self.clone())
559+
}
560+
}
561+
562+
#[cfg(test)]
563+
mod tests {
564+
use std::{
565+
hash::{DefaultHasher, Hash, Hasher},
566+
sync::Arc,
567+
};
568+
569+
use super::GuaranteedSignalFutureWaker;
570+
571+
#[test]
572+
fn guaranteed_future_waker_cloned_hash() {
573+
let waker_a = GuaranteedSignalFutureWaker::<u8>::new(Arc::default());
574+
let waker_b = waker_a.clone();
575+
576+
let mut hasher = DefaultHasher::new();
577+
waker_a.hash(&mut hasher);
578+
let hash_a = hasher.finish();
579+
580+
let mut hasher = DefaultHasher::new();
581+
waker_b.hash(&mut hasher);
582+
let hash_b = hasher.finish();
583+
584+
assert_eq!(hash_a, hash_b);
585+
}
586+
}

itest/rust/src/engine_tests/async_test.rs

+22-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use godot::builtin::Signal;
2-
use godot::classes::{Engine, SceneTree};
2+
use godot::classes::{Engine, Object, SceneTree};
33
use godot::global::godot_print;
4-
use godot::tools::{godot_task, ToSignalFuture};
4+
use godot::obj::NewAlloc;
5+
use godot::tools::{godot_task, ToGuaranteedSignalFuture, ToSignalFuture};
56

67
use crate::framework::itest;
78

@@ -66,3 +67,22 @@ fn cancel_async_task() {
6667

6768
handle.cancel();
6869
}
70+
71+
#[itest]
72+
fn async_task_guaranteed_signal_future() {
73+
let mut obj = Object::new_alloc();
74+
75+
let signal = Signal::from_object_signal(&obj, "script_changed");
76+
77+
godot_task(async move {
78+
godot_print!("starting task with guaranteed signal future...");
79+
80+
let result: Option<()> = signal.to_guaranteed_future().await;
81+
82+
assert!(result.is_none());
83+
84+
godot_print!("task asserted!");
85+
});
86+
87+
obj.call_deferred("free".into(), &[]);
88+
}

0 commit comments

Comments
 (0)