use std::cell::RefCell;
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, Ordering};
use futures::future::LocalBoxFuture;
use wasm_bindgen::prelude::*;
use yew::platform::spawn_local;
use yew::prelude::*;
use crate::root_state::{BounceRootState, BounceStates};
pub trait FutureNotion {
type Input: 'static;
type Output: 'static;
fn run<'a>(
states: &'a BounceStates,
input: &'a Self::Input,
) -> LocalBoxFuture<'a, Self::Output>;
}
#[derive(Debug)]
pub enum Deferred<T>
where
T: FutureNotion,
{
Pending {
input: Rc<T::Input>,
},
Completed {
input: Rc<T::Input>,
output: Rc<T::Output>,
},
Outdated {
input: Rc<T::Input>,
},
}
impl<T> Deferred<T>
where
T: FutureNotion,
{
pub fn is_pending(&self) -> bool {
match self {
Self::Pending { .. } => true,
Self::Completed { .. } => false,
Self::Outdated { .. } => false,
}
}
pub fn is_completed(&self) -> bool {
match self {
Self::Pending { .. } => false,
Self::Completed { .. } => true,
Self::Outdated { .. } => false,
}
}
pub fn is_outdated(&self) -> bool {
match self {
Self::Pending { .. } => false,
Self::Completed { .. } => false,
Self::Outdated { .. } => true,
}
}
pub fn input(&self) -> Rc<T::Input> {
match self {
Self::Pending { input } => input.clone(),
Self::Completed { input, .. } => input.clone(),
Self::Outdated { input } => input.clone(),
}
}
pub fn output(&self) -> Option<Rc<T::Output>> {
match self {
Self::Pending { .. } => None,
Self::Completed { output, .. } => Some(output.clone()),
Self::Outdated { .. } => None,
}
}
}
impl<T> Clone for Deferred<T>
where
T: FutureNotion,
{
fn clone(&self) -> Self {
match self {
Self::Pending { ref input } => Self::Pending {
input: input.clone(),
},
Self::Completed {
ref input,
ref output,
} => Self::Completed {
input: input.clone(),
output: output.clone(),
},
Self::Outdated { ref input } => Self::Outdated {
input: input.clone(),
},
}
}
}
#[hook]
pub fn use_future_notion_runner<T>() -> Rc<dyn Fn(T::Input)>
where
T: FutureNotion + 'static,
{
let root = use_context::<BounceRootState>().expect_throw("No bounce root found.");
Rc::new(move |input: T::Input| {
let root = root.clone();
let input = Rc::new(input);
spawn_local(async move {
root.apply_notion(Rc::new(Deferred::<T>::Pending {
input: input.clone(),
}));
let states = root.states();
let listeners = Rc::new(RefCell::new(None));
let listener_run = Rc::new(AtomicBool::new(false));
{
let listener_run = listener_run.clone();
let listeners = listeners.clone();
let root = root.clone();
let input = input.clone();
states.add_listener_callback(Rc::new(Callback::from(move |_| {
let listeners = listeners.borrow_mut().take();
let last_listener_run = listener_run.swap(true, Ordering::Relaxed);
if !last_listener_run || listeners.is_some() {
root.apply_notion(Rc::new(Deferred::<T>::Outdated {
input: input.clone(),
}));
}
})))
}
let output = T::run(&states, &input).await;
if !listener_run.load(Ordering::Relaxed) {
let _result = listeners.borrow_mut().replace(states.take_listeners());
}
root.apply_notion(Rc::new(Deferred::<T>::Completed {
input,
output: output.into(),
}));
});
})
}