Skip to content

Commit

Permalink
restructure & v0.2 compat
Browse files Browse the repository at this point in the history
  • Loading branch information
TitanNano committed Feb 9, 2025
1 parent d31963d commit e9838b5
Show file tree
Hide file tree
Showing 6 changed files with 330 additions and 306 deletions.
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
use std::any::type_name;
/*
* Copyright (c) godot-rust; Bromeon and contributors.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

use std::cell::RefCell;
use std::fmt::{Debug, Display};
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use std::sync::Arc;
use std::task::{Context, Poll, Wake, Waker};
use std::thread::{self, ThreadId};

use crate::builtin::{Callable, RustCallable, Signal, Variant};
use crate::classes::object::ConnectFlags;
use crate::builtin::{Callable, Variant};
use crate::classes::Os;
use crate::meta::{FromGodot, ToGodot};
use crate::obj::EngineEnum;
use crate::meta::ToGodot;

// ----------------------------------------------------------------------------------------------------------------------------------------------
// Public interface

pub fn godot_task(future: impl Future<Output = ()> + 'static) -> TaskHandle {
let os = Os::singleton();
Expand Down Expand Up @@ -44,6 +50,9 @@ pub fn godot_task(future: impl Future<Output = ()> + 'static) -> TaskHandle {
task_handle
}

// ----------------------------------------------------------------------------------------------------------------------------------------------
// Async Runtime

thread_local! { pub(crate) static ASYNC_RUNTIME: RefCell<AsyncRuntime> = RefCell::new(AsyncRuntime::new()); }

#[derive(Default)]
Expand Down Expand Up @@ -244,7 +253,7 @@ impl GodotWaker {

impl Wake for GodotWaker {
fn wake(self: std::sync::Arc<Self>) {
let callable = Callable::from_fn("GodotWaker::wake", move |_args| {
let callable = Callable::from_local_fn("GodotWaker::wake", move |_args| {
let current_thread = thread::current().id();

if self.thread_id != current_thread {
Expand Down Expand Up @@ -293,294 +302,3 @@ impl Wake for GodotWaker {
callable.to_variant().call("call_deferred", &[]);
}
}

pub struct SignalFuture<R: FromSignalArgs> {
state: Arc<Mutex<(Option<R>, Option<Waker>)>>,
callable: Callable,
signal: Signal,
}

impl<R: FromSignalArgs> SignalFuture<R> {
fn new(signal: Signal) -> Self {
let state = Arc::new(Mutex::new((None, Option::<Waker>::None)));
let callback_state = state.clone();

// the callable currently requires that the return value is Sync + Send
let callable = Callable::from_fn("async_task", move |args: &[&Variant]| {
let mut lock = callback_state.lock().unwrap();
let waker = lock.1.take();

lock.0.replace(R::from_args(args));
drop(lock);

if let Some(waker) = waker {
waker.wake();
}

Ok(Variant::nil())
});

signal.connect(callable.clone(), ConnectFlags::ONE_SHOT.ord() as i64);

Self {
state,
callable,
signal,
}
}
}

impl<R: FromSignalArgs> Future for SignalFuture<R> {
type Output = R;

fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut lock = self.state.lock().unwrap();

if let Some(result) = lock.0.take() {
return Poll::Ready(result);
}

lock.1.replace(cx.waker().clone());

Poll::Pending
}
}

impl<R: FromSignalArgs> Drop for SignalFuture<R> {
fn drop(&mut self) {
if !self.callable.is_valid() {
return;
}

if self.signal.object().is_none() {
return;
}

if self.signal.is_connected(self.callable.clone()) {
self.signal.disconnect(self.callable.clone());
}
}
}

struct GuaranteedSignalFutureWaker<R> {
state: Arc<Mutex<(GuaranteedSignalFutureState<R>, Option<Waker>)>>,
}

impl<R> Clone for GuaranteedSignalFutureWaker<R> {
fn clone(&self) -> Self {
Self {
state: self.state.clone(),
}
}
}

impl<R> GuaranteedSignalFutureWaker<R> {
fn new(state: Arc<Mutex<(GuaranteedSignalFutureState<R>, Option<Waker>)>>) -> Self {
Self { state }
}
}

impl<R> std::hash::Hash for GuaranteedSignalFutureWaker<R> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write_usize(Arc::as_ptr(&self.state) as usize);
}
}

impl<R> PartialEq for GuaranteedSignalFutureWaker<R> {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.state, &other.state)
}
}

impl<R: FromSignalArgs> RustCallable for GuaranteedSignalFutureWaker<R> {
fn invoke(&mut self, args: &[&Variant]) -> Result<Variant, ()> {
let mut lock = self.state.lock().unwrap();
let waker = lock.1.take();

lock.0 = GuaranteedSignalFutureState::Ready(R::from_args(args));
drop(lock);

if let Some(waker) = waker {
waker.wake();
}

Ok(Variant::nil())
}
}

impl<R> Display for GuaranteedSignalFutureWaker<R> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "SafeCallable::<{}>", type_name::<R>())
}
}

impl<R> Drop for GuaranteedSignalFutureWaker<R> {
fn drop(&mut self) {
let mut lock = self.state.lock().unwrap();

if !matches!(lock.0, GuaranteedSignalFutureState::Pending) {
return;
}

lock.0 = GuaranteedSignalFutureState::Dead;

if let Some(ref waker) = lock.1 {
waker.wake_by_ref();
}
}
}

#[derive(Default)]
enum GuaranteedSignalFutureState<T> {
#[default]
Pending,
Ready(T),
Dead,
Dropped,
}

impl<T> GuaranteedSignalFutureState<T> {
fn take(&mut self) -> Self {
let new_value = match self {
Self::Pending => Self::Pending,
Self::Ready(_) | Self::Dead => Self::Dead,
Self::Dropped => Self::Dropped,
};

std::mem::replace(self, new_value)
}
}

pub struct GuaranteedSignalFuture<R: FromSignalArgs> {
state: Arc<Mutex<(GuaranteedSignalFutureState<R>, Option<Waker>)>>,
callable: GuaranteedSignalFutureWaker<R>,
signal: Signal,
}

impl<R: FromSignalArgs + Debug> GuaranteedSignalFuture<R> {
fn new(signal: Signal) -> Self {
let state = Arc::new(Mutex::new((
GuaranteedSignalFutureState::Pending,
Option::<Waker>::None,
)));

// the callable currently requires that the return value is Sync + Send
let callable = GuaranteedSignalFutureWaker::new(state.clone());

signal.connect(
Callable::from_custom(callable.clone()),
ConnectFlags::ONE_SHOT.ord() as i64,
);

Self {
state,
callable,
signal,
}
}
}

impl<R: FromSignalArgs> Future for GuaranteedSignalFuture<R> {
type Output = Option<R>;

fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut lock = self.state.lock().unwrap();

lock.1.replace(cx.waker().clone());

let value = lock.0.take();

match value {
GuaranteedSignalFutureState::Pending => Poll::Pending,
GuaranteedSignalFutureState::Dropped => unreachable!(),
GuaranteedSignalFutureState::Dead => Poll::Ready(None),
GuaranteedSignalFutureState::Ready(value) => Poll::Ready(Some(value)),
}
}
}

impl<R: FromSignalArgs> Drop for GuaranteedSignalFuture<R> {
fn drop(&mut self) {
if self.signal.object().is_none() {
return;
}

self.state.lock().unwrap().0 = GuaranteedSignalFutureState::Dropped;

let gd_callable = Callable::from_custom(self.callable.clone());

if self.signal.is_connected(gd_callable.clone()) {
self.signal.disconnect(gd_callable);
}
}
}

pub trait FromSignalArgs: Sync + Send + 'static {
fn from_args(args: &[&Variant]) -> Self;
}

impl<R: FromGodot + Sync + Send + 'static> FromSignalArgs for R {
fn from_args(args: &[&Variant]) -> Self {
args.first()
.map(|arg| (*arg).to_owned())
.unwrap_or_default()
.to()
}
}

// more of these should be generated via macro to support more than two signal arguments
impl<R1: FromGodot + Sync + Send + 'static, R2: FromGodot + Sync + Send + 'static> FromSignalArgs
for (R1, R2)
{
fn from_args(args: &[&Variant]) -> Self {
(args[0].to(), args[0].to())
}
}

// Signal should implement IntoFuture for convenience. Keeping ToSignalFuture around might still be desirable, though. It allows to reuse i
// the same signal instance multiple times.
pub trait ToSignalFuture<R: FromSignalArgs> {
fn to_future(&self) -> SignalFuture<R>;
}

impl<R: FromSignalArgs> ToSignalFuture<R> for Signal {
fn to_future(&self) -> SignalFuture<R> {
SignalFuture::new(self.clone())
}
}

pub trait ToGuaranteedSignalFuture<R: FromSignalArgs + Debug> {
fn to_guaranteed_future(&self) -> GuaranteedSignalFuture<R>;
}

impl<R: FromSignalArgs + Debug> ToGuaranteedSignalFuture<R> for Signal {
fn to_guaranteed_future(&self) -> GuaranteedSignalFuture<R> {
GuaranteedSignalFuture::new(self.clone())
}
}

#[cfg(test)]
mod tests {
use std::{
hash::{DefaultHasher, Hash, Hasher},
sync::Arc,
};

use super::GuaranteedSignalFutureWaker;

#[test]
fn guaranteed_future_waker_cloned_hash() {
let waker_a = GuaranteedSignalFutureWaker::<u8>::new(Arc::default());
let waker_b = waker_a.clone();

let mut hasher = DefaultHasher::new();
waker_a.hash(&mut hasher);
let hash_a = hasher.finish();

let mut hasher = DefaultHasher::new();
waker_b.hash(&mut hasher);
let hash_b = hasher.finish();

assert_eq!(hash_a, hash_b);
}
}
2 changes: 2 additions & 0 deletions godot-core/src/builtin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ pub mod __prelude_reexport {
use super::*;

pub use aabb::*;
pub use async_runtime::*;
pub use basis::*;
pub use callable::*;
pub use collections::containers::*;
Expand Down Expand Up @@ -203,6 +204,7 @@ mod macros;

// Other modules
mod aabb;
mod async_runtime;
mod basis;
mod callable;
mod collections;
Expand Down
Loading

0 comments on commit e9838b5

Please sign in to comment.