diff --git a/packages/core/src/any_props.rs b/packages/core/src/any_props.rs index 2da03686a..eb9ac1598 100644 --- a/packages/core/src/any_props.rs +++ b/packages/core/src/any_props.rs @@ -4,6 +4,7 @@ use futures_util::Future; use crate::{ factory::{ComponentReturn, RenderReturn}, + innerlude::Scoped, scopes::{Scope, ScopeState}, Element, }; @@ -69,10 +70,10 @@ impl<'a, P, A, F: ComponentReturn<'a, A>> AnyProps<'a> for VComponentProps<'a, P // Make sure the scope ptr is not null // self.props.state.set(scope); - let scope = Scope { + let scope = cx.bump().alloc(Scoped { props: unsafe { &*self.props }, scope: cx, - }; + }); // Call the render function directly (self.render_fn)(scope).as_return(cx) diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index b475bf077..d4fbc22bb 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -1,4 +1,4 @@ -use crate::{nodes::VNode, virtualdom::VirtualDom}; +use crate::{nodes::VNode, virtual_dom::VirtualDom}; #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct ElementId(pub usize); diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index dff3d4cdd..dc754ae94 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -1,31 +1,28 @@ use std::pin::Pin; use crate::factory::{FiberLeaf, RenderReturn}; -use crate::innerlude::SuspenseContext; +use crate::innerlude::{Renderer, SuspenseContext}; use crate::mutations::Mutation; use crate::mutations::Mutation::*; use crate::nodes::VNode; use crate::nodes::{DynamicNode, TemplateNode}; -use crate::virtualdom::VirtualDom; +use crate::virtual_dom::VirtualDom; use crate::{AttributeValue, Element, ElementId, TemplateAttribute}; use bumpalo::boxed::Box as BumpBox; use futures_util::Future; impl VirtualDom { /// Create this template and write its mutations - pub fn create<'a>( - &mut self, - mutations: &mut Vec>, - template: &'a VNode<'a>, - ) -> usize { + pub fn create<'a>(&mut self, mutations: &mut Renderer<'a>, template: &'a VNode<'a>) -> usize { // The best renderers will have templates prehydrated // Just in case, let's create the template using instructions anyways if !self.templates.contains_key(&template.template.id) { for node in template.template.roots { + let mutations = &mut mutations.template_mutations; self.create_static_node(mutations, template, node); } - mutations.push(SaveTemplate { + mutations.template_mutations.push(SaveTemplate { name: template.template.id, m: template.template.roots.len(), }); @@ -162,7 +159,7 @@ impl VirtualDom { pub fn create_dynamic_node<'a>( &mut self, - mutations: &mut Vec>, + mutations: &mut Renderer<'a>, template: &'a VNode<'a>, node: &'a DynamicNode<'a>, idx: usize, @@ -183,44 +180,61 @@ impl VirtualDom { DynamicNode::Component { props, placeholder, .. } => { - println!("creaitng component"); let id = self.new_scope(unsafe { std::mem::transmute(props.get()) }); - let render_ret = self.run_scope(id); - let render_ret: &mut RenderReturn = unsafe { std::mem::transmute(render_ret) }; + // if boundary or subtree, start working on a new stack of mutations + match render_ret { + RenderReturn::Sync(None) | RenderReturn::Async(_) => { + let new_id = self.next_element(template); + placeholder.set(Some(new_id)); + self.scopes[id.0].placeholder.set(Some(new_id)); + mutations.push(AssignId { + id: new_id, + path: &template.template.node_paths[idx][1..], + }); + 0 + } + RenderReturn::Sync(Some(template)) => { + let mutations_to_this_point = mutations.len(); + self.scope_stack.push(id); - let created = self.create(mutations, template); + let mut created = self.create(mutations, template); self.scope_stack.pop(); + + if !self.waiting_on.is_empty() { + if let Some(boundary) = + self.scopes[id.0].has_context::() + { + let mut boundary_mut = boundary.borrow_mut(); + let split_off = mutations.split_off(mutations_to_this_point); + + let split_off = unsafe { std::mem::transmute(split_off) }; + + println!("SPLIT OFF: {:#?}", split_off); + + boundary_mut.mutations.mutations = split_off; + boundary_mut.waiting_on.extend(self.waiting_on.drain(..)); + + // Since this is a boundary, use it as a placeholder + let new_id = self.next_element(template); + placeholder.set(Some(new_id)); + self.scopes[id.0].placeholder.set(Some(new_id)); + mutations.push(AssignId { + id: new_id, + path: &template.template.node_paths[idx][1..], + }); + created = 0; + } + } + + // handle any waiting on futures accumulated by async calls down the tree + // if this is a boundary, we split off the tree created } - - // whenever the future is polled later, we'll revisit it - // For now, just set the placeholder - RenderReturn::Sync(None) => { - let new_id = self.next_element(template); - placeholder.set(Some(new_id)); - self.scopes[id.0].placeholder.set(Some(new_id)); - mutations.push(AssignId { - id: new_id, - path: &template.template.node_paths[idx][1..], - }); - 0 - } - - RenderReturn::Async(fut) => { - let new_id = self.next_element(template); - placeholder.set(Some(new_id)); - self.scopes[id.0].placeholder.set(Some(new_id)); - mutations.push(AssignId { - id: new_id, - path: &template.template.node_paths[idx][1..], - }); - 0 - } } } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 57ba619e6..b34155b7a 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -1,6 +1,7 @@ use std::any::Any; -use crate::virtualdom::VirtualDom; +use crate::innerlude::Renderer; +use crate::virtual_dom::VirtualDom; use crate::{Attribute, AttributeValue, TemplateNode}; use crate::any_props::VComponentProps; @@ -25,13 +26,13 @@ pub struct DirtyScope { } impl<'b> VirtualDom { - pub fn diff_scope(&mut self, mutations: &mut Vec>, scope: ScopeId) { + pub fn diff_scope(&mut self, mutations: &mut Renderer<'b>, scope: ScopeId) { let scope_state = &mut self.scopes[scope.0]; } pub fn diff_node( &mut self, - muts: &mut Vec>, + muts: &mut Renderer<'b>, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>, ) { @@ -176,7 +177,7 @@ impl<'b> VirtualDom { // the change list stack is in the same state when this function returns. fn diff_non_keyed_children( &mut self, - muts: &mut Vec>, + muts: &mut Renderer<'b>, old: &'b [VNode<'b>], new: &'b [VNode<'b>], ) { @@ -216,7 +217,7 @@ impl<'b> VirtualDom { // The stack is empty upon entry. fn diff_keyed_children( &mut self, - muts: &mut Vec>, + muts: &mut Renderer<'b>, old: &'b [VNode<'b>], new: &'b [VNode<'b>], ) { @@ -295,7 +296,7 @@ impl<'b> VirtualDom { // /// If there is no offset, then this function returns None and the diffing is complete. // fn diff_keyed_ends( // &mut self, - // muts: &mut Vec>, + // muts: &mut Renderer<'b>, // old: &'b [VNode<'b>], // new: &'b [VNode<'b>], // ) -> Option<(usize, usize)> { @@ -354,7 +355,7 @@ impl<'b> VirtualDom { // #[allow(clippy::too_many_lines)] // fn diff_keyed_middle( // &mut self, - // muts: &mut Vec>, + // muts: &mut Renderer<'b>, // old: &'b [VNode<'b>], // new: &'b [VNode<'b>], // ) { @@ -532,7 +533,7 @@ impl<'b> VirtualDom { /// Remove these nodes from the dom /// Wont generate mutations for the inner nodes - fn remove_nodes(&mut self, muts: &mut Vec>, nodes: &'b [VNode<'b>]) { + fn remove_nodes(&mut self, muts: &mut Renderer<'b>, nodes: &'b [VNode<'b>]) { // } } diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index 32cbed5a8..80a1e401b 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -1,4 +1,4 @@ -use crate::{arena::ElementId, virtualdom::VirtualDom, Attribute, AttributeValue}; +use crate::{arena::ElementId, virtual_dom::VirtualDom, Attribute, AttributeValue}; use std::cell::Cell; /// User Events are events that are shuttled from the renderer into the [`VirtualDom`] through the scheduler channel. diff --git a/packages/core/src/garbage.rs b/packages/core/src/garbage.rs index 8203fe1c2..bc28f7a84 100644 --- a/packages/core/src/garbage.rs +++ b/packages/core/src/garbage.rs @@ -1,4 +1,4 @@ -use crate::{nodes::VNode, scopes::ScopeId, virtualdom::VirtualDom, DynamicNode}; +use crate::{nodes::VNode, scopes::ScopeId, virtual_dom::VirtualDom, DynamicNode}; impl VirtualDom { pub fn drop_scope(&mut self, id: ScopeId) { diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index c387bf85e..77fd8c452 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -14,7 +14,7 @@ mod properties; mod scheduler; mod scope_arena; mod scopes; -mod virtualdom; +mod virtual_dom; pub(crate) mod innerlude { pub use crate::arena::*; @@ -25,7 +25,7 @@ pub(crate) mod innerlude { pub use crate::properties::*; pub use crate::scheduler::*; pub use crate::scopes::*; - pub use crate::virtualdom::*; + pub use crate::virtual_dom::*; /// An [`Element`] is a possibly-none [`VNode`] created by calling `render` on [`Scope`] or [`ScopeState`]. /// @@ -83,6 +83,9 @@ pub use crate::innerlude::{ Scope, ScopeId, ScopeState, + Scoped, + SuspenseBoundary, + SuspenseContext, TaskId, Template, TemplateAttribute, @@ -98,8 +101,8 @@ pub use crate::innerlude::{ pub mod prelude { pub use crate::innerlude::{ fc_to_builder, Attribute, DynamicNode, Element, EventPriority, LazyNodes, NodeFactory, - Properties, Scope, ScopeId, ScopeState, TaskId, Template, TemplateAttribute, TemplateNode, - UiEvent, VNode, VirtualDom, + Properties, Scope, ScopeId, ScopeState, Scoped, SuspenseBoundary, SuspenseContext, TaskId, + Template, TemplateAttribute, TemplateNode, UiEvent, VNode, VirtualDom, }; } @@ -132,3 +135,24 @@ macro_rules! to_owned { let mut $es = $es.to_owned(); )*} } + +/// A helper macro for values into callbacks for async environements. +/// +/// +macro_rules! callback { + () => {}; +} + +/// Convert a hook into a hook with an implicit dependency list by analyzing the closure. +/// +/// ``` +/// // Convert hooks with annoying dependencies into... +/// +/// let val = use_effect(cx, (val,) |(val,)| println!("thing {val}")) +/// +/// // a simple closure +/// let val = use_effect!(cx, |val| async { println!("thing {val}")) }); +/// ``` +macro_rules! make_dep_fn { + () => {}; +} diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 9d991fcc5..e6a26c0cb 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -1,9 +1,81 @@ use crate::arena::ElementId; +#[derive(Debug)] pub struct Renderer<'a> { - mutations: Vec>, + pub subtree: usize, + pub mutations: Vec>, + pub template_mutations: Vec>, + // mutations: Vec>, } +impl<'a> Renderer<'a> { + pub fn new(subtree: usize) -> Self { + Self { + subtree, + mutations: Vec::new(), + template_mutations: Vec::new(), + } + } +} + +impl<'a> std::ops::Deref for Renderer<'a> { + type Target = Vec>; + + fn deref(&self) -> &Self::Target { + &self.mutations + } +} + +impl std::ops::DerefMut for Renderer<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.mutations + } +} + +// impl<'a> Renderer<'a> { +// pub fn new(subtree: usize) -> Self { +// Self { +// mutations: vec![Mutations { +// subtree, +// mutations: Vec::new(), +// }], +// } +// } +// } +// impl<'a> Renderer<'a> { +// pub fn push(&mut self, mutation: Mutation<'a>) { +// self.mutations.last_mut().unwrap().mutations.push(mutation) +// } + +// pub fn extend(&mut self, mutations: impl IntoIterator>) { +// self.mutations +// .last_mut() +// .unwrap() +// .mutations +// .extend(mutations) +// } + +// pub fn len(&self) -> usize { +// self.mutations.last().unwrap().mutations.len() +// } + +// pub fn split_off(&mut self, idx: usize) -> Renderer<'a> { +// let mut mutations = self.mutations.split_off(idx); +// let subtree = mutations.pop().unwrap().subtree; +// Renderer { mutations } +// } +// } + +// #[derive(Debug)] +// pub struct Mutations<'a> { +// subtree: usize, +// mutations: Vec>, +// } + +/* +each subtree has its own numbering scheme +*/ + #[derive(Debug)] pub enum Mutation<'a> { SetAttribute { diff --git a/packages/core/src/scheduler/suspense.rs b/packages/core/src/scheduler/suspense.rs index 995428e4c..f4c9ac140 100644 --- a/packages/core/src/scheduler/suspense.rs +++ b/packages/core/src/scheduler/suspense.rs @@ -6,7 +6,10 @@ use std::{ }; use super::{waker::RcWake, SchedulerMsg}; -use crate::{innerlude::Mutation, Element, ScopeId}; +use crate::{ + innerlude::{Mutation, Renderer}, + Element, ScopeId, +}; use futures_task::Waker; use futures_util::Future; @@ -14,27 +17,27 @@ use futures_util::Future; pub struct SuspenseId(pub usize); pub type SuspenseContext = Rc>; + /// Essentially a fiber in React pub struct SuspenseBoundary { pub id: ScopeId, pub waiting_on: HashSet, - pub mutations: Vec>, + pub mutations: Renderer<'static>, } impl SuspenseBoundary { - pub fn new(id: ScopeId) -> Self { - Self { + pub fn new(id: ScopeId) -> Rc> { + Rc::new(RefCell::new(Self { id, waiting_on: Default::default(), - mutations: Default::default(), - } + mutations: Renderer::new(0), + })) } } pub struct SuspenseLeaf { pub id: SuspenseId, pub scope_id: ScopeId, - pub boundary: ScopeId, pub tx: futures_channel::mpsc::UnboundedSender, pub notified: Cell, @@ -43,10 +46,10 @@ pub struct SuspenseLeaf { impl RcWake for SuspenseLeaf { fn wake_by_ref(arc_self: &Rc) { - if arc_self.notified.get() { - return; - } - arc_self.notified.set(true); + // if arc_self.notified.get() { + // return; + // } + // arc_self.notified.set(true); _ = arc_self .tx .unbounded_send(SchedulerMsg::SuspenseNotified(arc_self.id)); diff --git a/packages/core/src/scheduler/wait.rs b/packages/core/src/scheduler/wait.rs index 17ddfac4c..03b9a57d5 100644 --- a/packages/core/src/scheduler/wait.rs +++ b/packages/core/src/scheduler/wait.rs @@ -3,7 +3,7 @@ use futures_util::{FutureExt, StreamExt}; use crate::{ factory::RenderReturn, - innerlude::{Mutation, SuspenseContext}, + innerlude::{Mutation, Renderer, SuspenseContext}, VNode, VirtualDom, }; @@ -38,6 +38,8 @@ impl VirtualDom { } SchedulerMsg::SuspenseNotified(id) => { + println!("suspense notified"); + let leaf = self .scheduler .handle @@ -63,11 +65,19 @@ impl VirtualDom { // continue rendering the tree until we hit yet another suspended component if let futures_task::Poll::Ready(new_nodes) = as_pinned_mut.poll_unpin(&mut cx) { - let boundary = &self.scopes[leaf.boundary.0] + let boundary = &self.scopes[leaf.scope_id.0] .consume_context::() .unwrap(); + println!("ready pool"); + let mut fiber = boundary.borrow_mut(); + + println!( + "Existing mutations {:?}, scope {:?}", + fiber.mutations, fiber.id + ); + let scope = &mut self.scopes[scope_id.0]; let arena = scope.current_arena(); @@ -77,18 +87,24 @@ impl VirtualDom { if let RenderReturn::Sync(Some(template)) = ret { let mutations = &mut fiber.mutations; let template: &VNode = unsafe { std::mem::transmute(template) }; - let mutations: &mut Vec = + let mutations: &mut Renderer = unsafe { std::mem::transmute(mutations) }; self.scope_stack.push(scope_id); self.create(mutations, template); self.scope_stack.pop(); - println!("{:?}", mutations); + println!("{:#?}", mutations); + } else { + println!("nodes arent right"); } + } else { + println!("not ready"); } } } + + // now proces any events. If we end up running a component and it generates mutations, then we should run those mutations } } } diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index bbf3d00e2..49902cb88 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -14,7 +14,7 @@ use crate::{ factory::RenderReturn, innerlude::{SuspenseId, SuspenseLeaf}, scopes::{ScopeId, ScopeState}, - virtualdom::VirtualDom, + virtual_dom::VirtualDom, }; impl VirtualDom { @@ -77,13 +77,13 @@ impl VirtualDom { let mut leaves = self.scheduler.handle.leaves.borrow_mut(); let entry = leaves.vacant_entry(); let key = entry.key(); + let suspense_id = SuspenseId(key); let leaf = Rc::new(SuspenseLeaf { scope_id, task: task.as_mut(), - id: SuspenseId(key), + id: suspense_id, tx: self.scheduler.handle.sender.clone(), - boundary: ScopeId(0), notified: false.into(), }); @@ -112,6 +112,7 @@ impl VirtualDom { // Insert the future into fiber leaves and break _ => { entry.insert(leaf); + self.waiting_on.push(suspense_id); break; } }; diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 380da37b8..bfc3ee9bc 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -19,23 +19,14 @@ use crate::{ TaskId, }; -pub struct Scope<'a, T = ()> { +pub type Scope<'a, T = ()> = &'a Scoped<'a, T>; + +pub struct Scoped<'a, T = ()> { pub scope: &'a ScopeState, pub props: &'a T, } -impl Copy for Scope<'_, T> {} - -impl Clone for Scope<'_, T> { - fn clone(&self) -> Self { - Self { - props: self.props, - scope: self.scope, - } - } -} - -impl<'a, T> std::ops::Deref for Scope<'a, T> { +impl<'a, T> std::ops::Deref for Scoped<'a, T> { type Target = &'a ScopeState; fn deref(&self) -> &Self::Target { @@ -298,6 +289,19 @@ impl ScopeState { } } + /// Return any context of type T if it exists on this scope + pub fn has_context(&self) -> Option { + match self.shared_contexts.borrow().get(&TypeId::of::()) { + Some(shared) => Some( + (*shared + .downcast_ref::() + .expect("Context of type T should exist")) + .clone(), + ), + None => None, + } + } + /// Pushes the future onto the poll queue to be polled after the component renders. pub fn push_future(&self, fut: impl Future + 'static) -> TaskId { self.tasks.spawn(self.id, fut) diff --git a/packages/core/src/virtualdom.rs b/packages/core/src/virtual_dom.rs similarity index 71% rename from packages/core/src/virtualdom.rs rename to packages/core/src/virtual_dom.rs index 66986ec54..d68e5edb6 100644 --- a/packages/core/src/virtualdom.rs +++ b/packages/core/src/virtual_dom.rs @@ -3,7 +3,7 @@ use crate::arena::ElementPath; use crate::component::Component; use crate::diff::DirtyScope; use crate::factory::RenderReturn; -use crate::innerlude::{Scheduler, SchedulerMsg}; +use crate::innerlude::{Renderer, Scheduler, SchedulerMsg}; use crate::mutations::Mutation; use crate::nodes::{Template, TemplateId}; @@ -13,20 +13,22 @@ use crate::{ }; use crate::{scheduler, Element, Scope}; use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender}; -use scheduler::{SuspenseBoundary, SuspenseContext}; +use futures_util::Future; +use scheduler::{SuspenseBoundary, SuspenseContext, SuspenseId}; use slab::Slab; -use std::cell::RefCell; use std::collections::{BTreeSet, HashMap}; -use std::rc::Rc; pub struct VirtualDom { pub(crate) templates: HashMap>, pub(crate) elements: Slab, pub(crate) scopes: Slab, - pub(crate) scope_stack: Vec, pub(crate) element_stack: Vec, pub(crate) dirty_scopes: BTreeSet, pub(crate) scheduler: Scheduler, + + // While diffing we need some sort of way of breaking off a stream of suspended mutations. + pub(crate) scope_stack: Vec, + pub(crate) waiting_on: Vec, } impl VirtualDom { @@ -40,6 +42,7 @@ impl VirtualDom { scope_stack: Vec::new(), element_stack: vec![ElementId(0)], dirty_scopes: BTreeSet::new(), + waiting_on: Vec::new(), scheduler, }; @@ -49,21 +52,25 @@ impl VirtualDom { let root = res.new_scope(props); // the root component is always a suspense boundary for any async children - res.scopes[root.0].provide_context(Rc::new(RefCell::new(SuspenseBoundary::new(root)))); - + res.scopes[root.0].provide_context(SuspenseBoundary::new(root)); assert_eq!(root, ScopeId(0)); res } /// Render the virtualdom, without processing any suspense. - pub fn rebuild<'a>(&'a mut self, mutations: &mut Vec>) { + /// + /// This does register futures with wakers, but does not process any of them. + pub fn rebuild<'a>(&'a mut self) -> Renderer<'a> { + let mut mutations = Renderer::new(0); let root_node: &RenderReturn = self.run_scope(ScopeId(0)); let root_node: &RenderReturn = unsafe { std::mem::transmute(root_node) }; + + let mut created = 0; match root_node { RenderReturn::Sync(Some(node)) => { self.scope_stack.push(ScopeId(0)); - self.create(mutations, node); + created = self.create(&mut mutations, node); self.scope_stack.pop(); } RenderReturn::Sync(None) => { @@ -71,22 +78,22 @@ impl VirtualDom { } RenderReturn::Async(_) => unreachable!(), } + + mutations.push(Mutation::AppendChildren { m: created }); + + mutations } /// Render what you can given the timeline and then move on - pub async fn render_with_deadline<'a>( - &'a mut self, - future: impl std::future::Future, - mutations: &mut Vec>, - ) { + /// + /// It's generally a good idea to put some sort of limit on the suspense process in case a future is having issues. + pub async fn render_with_deadline( + &mut self, + deadline: impl Future, + ) -> Vec { todo!() } - // Whenever the future is canceled, the VirtualDom will be - pub async fn render<'a>(&'a mut self, mutations: &mut Vec>) { - // - } - pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> { self.scopes.get(id.0) } diff --git a/packages/core/tests/simple_syntax.rs b/packages/core/tests/simple_syntax.rs index 65fa4fb15..b2c3c8e2e 100644 --- a/packages/core/tests/simple_syntax.rs +++ b/packages/core/tests/simple_syntax.rs @@ -21,6 +21,5 @@ async fn ChildAsync(cx: Scope<'_>) -> Element { fn it_works() { let mut dom = VirtualDom::new(app); - let mut mutations = vec![]; - dom.rebuild(&mut mutations); + let mut mutations = dom.rebuild(); } diff --git a/packages/core/tests/suspend.rs b/packages/core/tests/suspend.rs index 7ac05a1fa..df6dcb413 100644 --- a/packages/core/tests/suspend.rs +++ b/packages/core/tests/suspend.rs @@ -1,13 +1,11 @@ -use std::{cell::Cell, ptr::null_mut, time::Duration}; - use dioxus_core::*; +use std::{cell::RefCell, rc::Rc, time::Duration}; #[tokio::test] async fn it_works() { let mut dom = VirtualDom::new(app); - let mut mutations = vec![]; - dom.rebuild(&mut mutations); + let mutations = dom.rebuild(); println!("mutations: {:?}", mutations); @@ -15,8 +13,23 @@ async fn it_works() { } fn app(cx: Scope) -> Element { - let dy = cx.component(async_child, (), "async_child"); - VNode::single_component(&cx, dy, "app") + println!("running root app"); + + VNode::single_component( + cx, + cx.component(suspense_boundary, (), "suspense_boundary"), + "app", + ) +} + +fn suspense_boundary(cx: Scope) -> Element { + println!("running boundary"); + + let _ = cx.use_hook(|| { + cx.provide_context(Rc::new(RefCell::new(SuspenseBoundary::new(cx.scope_id())))) + }); + + VNode::single_component(cx, cx.component(async_child, (), "async_child"), "app") } async fn async_child(cx: Scope<'_>) -> Element { @@ -34,8 +47,9 @@ async fn async_child(cx: Scope<'_>) -> Element { println!("Future awaited and complete"); - let dy = cx.component(async_child, (), "async_child"); - VNode::single_component(&cx, dy, "app") + VNode::single_component(cx, cx.component(async_text, (), "async_text"), "app") +} - // VNode::single_text(&cx, &[TemplateNode::Text("it works!")], "beauty") +async fn async_text(cx: Scope<'_>) -> Element { + VNode::single_text(&cx, &[TemplateNode::Text("it works!")], "beauty") } diff --git a/packages/core/tests/task.rs b/packages/core/tests/task.rs index 04ac5f95a..45b5e2588 100644 --- a/packages/core/tests/task.rs +++ b/packages/core/tests/task.rs @@ -6,8 +6,7 @@ use dioxus_core::*; async fn it_works() { let mut dom = VirtualDom::new(app); - let mut mutations = vec![]; - dom.rebuild(&mut mutations); + let mutations = dom.rebuild(); println!("mutations: {:?}", mutations); diff --git a/packages/dioxus/Cargo.toml b/packages/dioxus/Cargo.toml index 853c11202..0675cd2af 100644 --- a/packages/dioxus/Cargo.toml +++ b/packages/dioxus/Cargo.toml @@ -32,6 +32,7 @@ rand = { version = "0.8.4", features = ["small_rng"] } criterion = "0.3.5" thiserror = "1.0.30" env_logger = "0.9.0" +tokio = { version = "1.21.2", features = ["full"] } # dioxus-edit-stream = { path = "../edit-stream" } [[bench]] diff --git a/packages/dioxus/tests/rsx_syntax.rs b/packages/dioxus/tests/rsx_syntax.rs index fd86a03c3..b83d6b7dd 100644 --- a/packages/dioxus/tests/rsx_syntax.rs +++ b/packages/dioxus/tests/rsx_syntax.rs @@ -1,3 +1,5 @@ +use std::future::IntoFuture; + use dioxus::prelude::*; fn basic_syntax_is_a_template(cx: Scope) -> Element { @@ -23,41 +25,63 @@ fn basic_syntax_is_a_template(cx: Scope) -> Element { }) } -fn basic_template(cx: Scope) -> Element { - cx.render(rsx! { - div { - basic_child { } - async_child { } - } - }) +#[inline_props] +fn suspense_boundary<'a>(cx: Scope<'a>, children: Element<'a>) -> Element { + cx.use_hook(|| cx.provide_context(SuspenseBoundary::new(cx.scope_id()))); + cx.render(rsx! { children }) } fn basic_child(cx: Scope) -> Element { - todo!() + cx.render(rsx! { + div { "basic child 1" } + }) } async fn async_child(cx: Scope<'_>) -> Element { - todo!() + let username = use_future!(cx, || async { + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + "async child 1" + }); + + let age = use_future!(cx, || async { + tokio::time::sleep(std::time::Duration::from_secs(2)).await; + println!("long future completed"); + 1234 + }); + + let (_user, _age) = use_future!(cx, || async { + tokio::join!( + tokio::time::sleep(std::time::Duration::from_secs(1)), + tokio::time::sleep(std::time::Duration::from_secs(2)) + ); + ("async child 1", 1234) + }) + .await; + + let (username, age) = tokio::join!(username.into_future(), age.into_future()); + + cx.render(rsx!( + div { "Hello! {username}, you are {age}, {_user} {_age}" } + )) } -#[test] -fn basic_prints() { - let mut dom = VirtualDom::new(basic_template); +#[tokio::test] +async fn basic_prints() { + let mut dom = VirtualDom::new(|cx| { + cx.render(rsx! { + div { + h1 { "var" } + suspense_boundary { + basic_child { } + async_child { } + } + } + }) + }); - let mut edits = Vec::new(); - dom.rebuild(&mut edits); - dbg!(edits); + dbg!(dom.rebuild()); - let mut edits = Vec::new(); - dom.rebuild(&mut edits); + dom.wait_for_work().await; - dbg!(edits); - // let renderer = dioxus_edit_stream::Mutations::default(); - // - // dbg!(renderer.edits); - - // takes_it(basic_child); + dbg!(dom.rebuild()); } - -// fn takes_it(f: fn(Scope) -> Element) {} -// fn takes_it(f: fn(Scope) -> Element) {} diff --git a/packages/hooks/src/usefuture.rs b/packages/hooks/src/usefuture.rs index 1aa7800ec..a23f09691 100644 --- a/packages/hooks/src/usefuture.rs +++ b/packages/hooks/src/usefuture.rs @@ -1,6 +1,12 @@ #![allow(missing_docs)] use dioxus_core::{ScopeState, TaskId}; -use std::{any::Any, cell::Cell, future::Future, rc::Rc, sync::Arc}; +use std::{ + any::Any, + cell::{Cell, RefCell}, + future::{Future, IntoFuture}, + rc::Rc, + sync::Arc, +}; /// A future that resolves to a value. /// @@ -28,16 +34,13 @@ where let state = cx.use_hook(move || UseFuture { update: cx.schedule_update(), needs_regen: Cell::new(true), - slot: Rc::new(Cell::new(None)), - value: None, + values: Default::default(), task: Cell::new(None), dependencies: Vec::new(), + waker: Default::default(), }); - if let Some(value) = state.slot.take() { - state.value = Some(value); - state.task.set(None); - } + *state.waker.borrow_mut() = None; if dependencies.clone().apply(&mut state.dependencies) || state.needs_regen.get() { // We don't need regen anymore @@ -47,8 +50,9 @@ where let fut = future(dependencies.out()); // Clone in our cells - let slot = state.slot.clone(); + let values = state.values.clone(); let schedule_update = state.update.clone(); + let waker = state.waker.clone(); // Cancel the current future if let Some(current) = state.task.take() { @@ -57,8 +61,16 @@ where state.task.set(Some(cx.push_future(async move { let res = fut.await; - slot.set(Some(res)); - schedule_update(); + values.borrow_mut().push(Box::leak(Box::new(res))); + + // if there's a waker, we dont re-render the component. Instead we just progress that future + match waker.borrow().as_ref() { + Some(waker) => waker.wake_by_ref(), + None => { + println!("scheduling update"); + // schedule_update() + } + } }))); } @@ -74,10 +86,10 @@ pub enum FutureState<'a, T> { pub struct UseFuture { update: Arc, needs_regen: Cell, - value: Option, - slot: Rc>>, task: Cell>, dependencies: Vec>, + waker: Rc>>, + values: Rc>>, } pub enum UseFutureState<'a, T> { @@ -105,22 +117,25 @@ impl UseFuture { // clears the value in the future slot without starting the future over pub fn clear(&self) -> Option { - (self.update)(); - self.slot.replace(None) + todo!() + // (self.update)(); + // self.slot.replace(None) } // Manually set the value in the future slot without starting the future over pub fn set(&self, new_value: T) { - self.slot.set(Some(new_value)); - self.needs_regen.set(true); - (self.update)(); + // self.slot.set(Some(new_value)); + // self.needs_regen.set(true); + // (self.update)(); + todo!() } /// Return any value, even old values if the future has not yet resolved. /// /// If the future has never completed, the returned value will be `None`. pub fn value(&self) -> Option<&T> { - self.value.as_ref() + // self.value.as_ref() + todo!() } /// Get the ID of the future in Dioxus' internal scheduler @@ -130,18 +145,49 @@ impl UseFuture { /// Get the current stateof the future. pub fn state(&self) -> UseFutureState { - match (&self.task.get(), &self.value) { - // If we have a task and an existing value, we're reloading - (Some(_), Some(val)) => UseFutureState::Reloading(val), + todo!() + // match (&self.task.get(), &self.value) { + // // If we have a task and an existing value, we're reloading + // (Some(_), Some(val)) => UseFutureState::Reloading(val), - // no task, but value - we're done - (None, Some(val)) => UseFutureState::Complete(val), + // // no task, but value - we're done + // (None, Some(val)) => UseFutureState::Complete(val), - // no task, no value - something's wrong? return pending - (None, None) => UseFutureState::Pending, + // // no task, no value - something's wrong? return pending + // (None, None) => UseFutureState::Pending, - // Task, no value - we're still pending - (Some(_), None) => UseFutureState::Pending, + // // Task, no value - we're still pending + // (Some(_), None) => UseFutureState::Pending, + // } + } +} + +impl<'a, T> IntoFuture for &'a UseFuture { + type Output = &'a T; + type IntoFuture = UseFutureAwait<'a, T>; + fn into_future(self) -> Self::IntoFuture { + UseFutureAwait { hook: self } + } +} + +pub struct UseFutureAwait<'a, T> { + hook: &'a UseFuture, +} + +impl<'a, T> Future for UseFutureAwait<'a, T> { + type Output = &'a T; + + fn poll( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll { + println!("polling future"); + match self.hook.values.borrow_mut().last().cloned() { + Some(value) => std::task::Poll::Ready(unsafe { &*value }), + None => { + self.hook.waker.replace(Some(cx.waker().clone())); + std::task::Poll::Pending + } } } } @@ -239,6 +285,18 @@ impl_dep!(A = a, B = b, C = c, D = d, E = e, F = f,); impl_dep!(A = a, B = b, C = c, D = d, E = e, F = f, G = g,); impl_dep!(A = a, B = b, C = c, D = d, E = e, F = f, G = g, H = h,); +#[macro_export] +macro_rules! use_future { + ($cx:ident, || $($rest:tt)*) => { use_future( $cx, (), |_| $($rest)* ) }; + ($cx:ident, | $($args:tt),* | $($rest:tt)*) => { + use_future( + $cx, + ($($args),*), + |($($args),*)| $($rest)* + ) + }; +} + #[cfg(test)] mod tests { use super::*; @@ -256,22 +314,32 @@ mod tests { e: i32, } - fn app(cx: Scope) -> Element { + async fn app(cx: Scope<'_, MyProps>) -> Element { // should only ever run once - let fut = use_future(&cx, (), |_| async move { - // - }); + let fut = use_future(cx, (), |_| async move {}); // runs when a is changed - let fut = use_future(&cx, (&cx.props.a,), |(a,)| async move { - // - }); + let fut = use_future(cx, (&cx.props.a,), |(a,)| async move {}); // runs when a or b is changed - let fut = use_future(&cx, (&cx.props.a, &cx.props.b), |(a, b)| async move { - // + let fut = use_future(cx, (&cx.props.a, &cx.props.b), |(a, b)| async move { 123 }); + + let a = use_future!(cx, || async move { + // do the thing! }); + let b = &123; + let c = &123; + + let a = use_future!(cx, |b, c| async move { + let a = b + c; + let blah = "asd"; + }); + + let g2 = a.await; + + let g = fut.await; + None } } diff --git a/packages/hooks/src/usestate.rs b/packages/hooks/src/usestate.rs index 78cf643b1..15ba5fd3d 100644 --- a/packages/hooks/src/usestate.rs +++ b/packages/hooks/src/usestate.rs @@ -472,6 +472,8 @@ fn api_makes_sense() { } }); - cx.render(LazyNodes::new(|f| f.static_text("asd"))) + // cx.render(LazyNodes::new(|f| f.static_text("asd"))) + + todo!() } } diff --git a/packages/hooks/src/usesuspense.rs b/packages/hooks/src/usesuspense.rs deleted file mode 100644 index d4405bd88..000000000 --- a/packages/hooks/src/usesuspense.rs +++ /dev/null @@ -1,41 +0,0 @@ -use std::{cell::Cell, future::Future, rc::Rc}; - -use dioxus_core::{Element, ScopeState, TaskId}; - -pub fn use_suspense + 'static>( - cx: &ScopeState, - create_future: impl FnOnce() -> F, - render: impl FnOnce(&R) -> Element, -) -> Element { - let sus = cx.use_hook(|| { - let fut = create_future(); - - let wip_value: Rc>> = Default::default(); - - let wip = wip_value.clone(); - let new_fut = async move { - let val = fut.await; - wip.set(Some(val)); - }; - - let task = cx.push_future(new_fut); - SuspenseInner { - _task: task, - value: None, - _wip_value: wip_value, - } - }); - - if let Some(value) = sus.value.as_ref() { - render(value) - } else { - // generate a placeholder node if the future isnt ready - None - } -} - -struct SuspenseInner { - _task: TaskId, - _wip_value: Rc>>, - value: Option, -}