Iniitial work on transitions

This commit is contained in:
Greg Johnston 2022-08-05 07:53:34 -04:00
parent 4da115b52c
commit ef032092f1
9 changed files with 154 additions and 41 deletions

View file

@ -2,6 +2,10 @@
- [x] Resource
- [x] Suspense
- [ ] Transitions
- [ ] Tests for Suspense/Transitions
- [ ] Bugs in Suspense/Transitions
- [ ] infinite loop if reading resource in effect
- [ ] rendering bugs if `Suspense` doesn't have a parent
- [ ] Router
- [ ] Docs (and clippy warning to insist on docs)
- [ ] Read through + understand...

View file

@ -31,7 +31,7 @@ async fn fetch_cats(count: u32) -> Result<Vec<String>, ()> {
pub fn fetch_example(cx: Scope) -> web_sys::Element {
let (cat_count, set_cat_count) = cx.create_signal::<u32>(3);
let cats = cx.create_ref(cx.create_resource(cat_count.clone(), |count| fetch_cats(*count)));
let cats = cx.create_ref(cx.create_resource(cat_count, |count| fetch_cats(*count)));
view! {
<div>
@ -47,7 +47,7 @@ pub fn fetch_example(cx: Scope) -> web_sys::Element {
/>
</label>
<div>
//<Suspense fallback={"Loading (Suspense Fallback)...".to_string()}>
<Suspense fallback={"Loading (Suspense Fallback)...".to_string()}>
{move || match &*cats.read() {
ResourceState::Idle => view! { <p>"(no data)"</p> },
ResourceState::Pending { .. } => view! { <p>"Loading..."</p> },
@ -64,7 +64,7 @@ pub fn fetch_example(cx: Scope) -> web_sys::Element {
}</div>
}
}}
//</Suspense>
</Suspense>
</div>
</div>
}

View file

@ -123,19 +123,6 @@ pub fn insert_expression<'a>(
mut current: Child<'a>,
before: Option<&web_sys::Node>,
) -> Child<'a> {
crate::warn!(
"insert {:?} on {} to replace {:?}",
new_value,
parent.node_name(),
current
);
if let Child::Node(node) = &current {
crate::log!(
"current's parent = {}",
node.parent_node().unwrap().node_name()
);
}
if new_value == &current {
current
} else {
@ -180,11 +167,11 @@ pub fn insert_expression<'a>(
}
}
Child::Node(old_node) => {
crate::warn!(
/* crate::warn!(
"replacing old node with new node\n\nparents are {} and {}",
old_node.parent_node().unwrap().node_name(),
node.parent_node().unwrap().node_name()
);
); */
replace_with(old_node.unchecked_ref(), node);
Child::Node(node.clone())
}

View file

@ -12,6 +12,7 @@ mod scope_arena;
mod signal;
mod spawn;
mod suspense;
mod transition;
pub use effect::*;
pub use resource::*;
@ -20,6 +21,7 @@ pub use scope::*;
pub use signal::*;
pub use spawn::*;
pub use suspense::*;
pub use transition::*;
#[cfg(test)]
mod tests {

View file

@ -10,6 +10,22 @@ pub enum ResourceState<T> {
Ready { data: T },
}
impl<T> std::fmt::Debug for ResourceState<T>
where
T: std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Idle => write!(f, "Idle"),
Self::Pending { abort_handle } => f
.debug_struct("Pending")
.field("abort_handle", abort_handle)
.finish(),
Self::Ready { data } => f.debug_struct("Ready").field("data", data).finish(),
}
}
}
pub struct Resource<'a, S, T, Fu>
where
S: 'static + Clone,

View file

@ -1,15 +1,19 @@
use std::{cell::RefCell, rc::Weak};
use crate::EffectInner;
use crate::{EffectInner, ReadSignal, TransitionState, WriteSignal};
pub struct RootContext {
pub(crate) stack: RefCell<Vec<Weak<EffectInner>>>,
pub(crate) transition_pending: RefCell<Option<(ReadSignal<bool>, WriteSignal<bool>)>>,
pub(crate) transition: RefCell<Option<TransitionState>>,
}
impl RootContext {
pub fn new() -> Self {
Self {
stack: RefCell::new(Vec::new()),
stack: Default::default(),
transition_pending: Default::default(),
transition: Default::default(),
}
}

View file

@ -1,12 +1,12 @@
use crate::scope_arena::ScopeArena;
use crate::{EffectInner, Resource, SignalState};
use crate::{signal_from_root_context, EffectInner, Resource};
use super::{root_context::RootContext, Effect, ReadSignal, WriteSignal};
use std::future::Future;
use std::{
any::{Any, TypeId},
cell::RefCell,
collections::{HashMap, HashSet},
collections::HashMap,
marker::PhantomData,
rc::Rc,
};
@ -18,6 +18,14 @@ pub fn create_scope<'disposer>(
root_context: &'static RootContext,
f: impl for<'a> FnOnce(Scope<'a>),
) -> ScopeDisposer<'disposer> {
// create global transition tracking if this is the first scope
{
let mut transition = root_context.transition_pending.borrow_mut();
if transition.is_none() {
*transition = Some(signal_from_root_context(root_context, false));
}
}
let inner = ScopeInner::new(&root_context);
let boxed_inner = Box::new(inner);
let inner_ptr = Box::into_raw(boxed_inner);
@ -36,7 +44,7 @@ pub fn create_scope<'disposer>(
#[derive(Clone, Copy)]
pub struct BoundedScope<'a, 'b: 'a> {
inner: &'a ScopeInner<'a>,
pub(crate) inner: &'a ScopeInner<'a>,
/// `&'b` for covariance!
_phantom: PhantomData<&'b ()>,
}
@ -87,7 +95,7 @@ impl<'a, 'b> BoundedScope<'a, 'b> {
pub fn create_resource<S, T, Fu>(
self,
source: ReadSignal<S>,
source: &ReadSignal<S>,
fetcher: impl Fn(&S) -> Fu + 'static,
) -> Resource<'a, S, T, Fu>
where
@ -95,7 +103,7 @@ impl<'a, 'b> BoundedScope<'a, 'b> {
T: 'static,
Fu: Future<Output = T> + 'static,
{
Resource::new(self, source, fetcher)
Resource::new(self, source.clone(), fetcher)
}
pub fn child_scope<F>(self, f: F) -> ScopeDisposer<'a>
@ -134,7 +142,7 @@ impl<'a, 'b> BoundedScope<'a, 'b> {
}
}
struct ScopeInner<'a> {
pub(crate) struct ScopeInner<'a> {
pub(crate) root_context: &'static RootContext,
pub(crate) parent: Option<&'a ScopeInner<'a>>,
@ -164,21 +172,7 @@ impl<'a> ScopeInner<'a> {
}
pub fn signal<T>(&self, value: T) -> (ReadSignal<T>, WriteSignal<T>) {
let state = Rc::new(SignalState {
value: RefCell::new(value),
subscriptions: RefCell::new(HashSet::new()),
});
let writer = WriteSignal {
inner: Rc::downgrade(&state),
};
let reader = ReadSignal {
stack: self.root_context,
inner: state,
};
(reader, writer)
signal_from_root_context(self.root_context, value)
}
pub fn untrack<T>(&self, f: impl Fn() -> T) -> T {

View file

@ -9,6 +9,27 @@ use crate::EffectInner;
use super::{root_context::RootContext, EffectDependency};
pub(crate) fn signal_from_root_context<T>(
root_context: &'static RootContext,
value: T,
) -> (ReadSignal<T>, WriteSignal<T>) {
let state = Rc::new(SignalState {
value: RefCell::new(value),
subscriptions: RefCell::new(HashSet::new()),
});
let writer = WriteSignal {
inner: Rc::downgrade(&state),
};
let reader = ReadSignal {
stack: root_context,
inner: state,
};
(reader, writer)
}
pub struct ReadSignal<T: 'static> {
pub(crate) stack: &'static RootContext,
pub(crate) inner: Rc<SignalState<T>>,

View file

@ -0,0 +1,85 @@
use std::{cell::RefCell, collections::HashSet, pin::Pin, rc::Rc};
use futures::{
channel::oneshot::{Canceled, Sender},
Future,
};
use crate::{BoundedScope, Effect, EffectDependency, ReadSignal, SuspenseContext};
impl<'a, 'b> BoundedScope<'a, 'b> {
pub fn use_transition<F>(self) -> (ReadSignal<bool>, impl Fn())
where
F: Fn(),
{
if let Some(transition) = self.inner.root_context.transition {
transition
}
}
/* pub fn start_transition(self, f: impl Fn()) {
// If a transition is already running, run this function
// and then return when the existing transition is done
if let Some(transition) = self.inner.root_context.transition.take() {
if transition.running {
f();
}
}
// Otherwise, if we're inside a Suspense context, create a transition and run it
if self.use_context::<SuspenseContext>().is_some() {
let t = TransitionState {
running: true,
sources: Default::default(),
effects: Default::default(),
queue: Default::default(),
pending_resources: Default::default(),
};
*self.inner.root_context.transition.borrow_mut() = Some(t);
f();
}
} */
}
pub struct TransitionState {
inner: Rc<RefCell<TransitionStateInner>>,
/* sources: Set<SignalState<any>>;
effects: Computation<any>[];
promises: Set<Promise<any>>;
disposed: Set<Computation<any>>;
queue: Set<Computation<any>>;
scheduler?: (fn: () => void) => unknown;
running: boolean;
done?: Promise<void>;
resolve?: () => void; */
}
pub(crate) struct TransitionStateInner {
pub(crate) running: bool,
pub(crate) sources: HashSet<Box<dyn EffectDependency>>,
pub(crate) effects: Vec<Effect>,
pub(crate) queue: HashSet<Effect>,
pub(crate) pending_resources: Option<HashSet<ReadSignal<usize>>>,
}
impl TransitionState {
pub fn pending(&self) -> bool {
match &self.inner.borrow().pending_resources {
Some(suspenses) => {
suspenses
.iter()
.map(|pending| *pending.get())
.sum::<usize>()
> 0
}
None => false,
}
}
pub fn complete(&self) -> bool {
match &self.pending_resources {
Some(_) => !self.pending(),
None => false,
}
}
}