feat: split apart template mutations

This commit is contained in:
Jonathan Kelley committed 2022-11-08 19:39:37 -08:00
1 parent 203935834d
commit fc9fe6e560
21 files changed
+431 -222

No files matched your search

+3 -2
View File
@@ -4,6 +4,7 @@ use futures_util::Future;
use crate::{ use crate::{
factory::{ComponentReturn, RenderReturn}, factory::{ComponentReturn, RenderReturn},
innerlude::Scoped,
scopes::{Scope, ScopeState}, scopes::{Scope, ScopeState},
Element, 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 // Make sure the scope ptr is not null
// self.props.state.set(scope); // self.props.state.set(scope);
let scope = Scope { let scope = cx.bump().alloc(Scoped {
props: unsafe { &*self.props }, props: unsafe { &*self.props },
scope: cx, scope: cx,
}; });
// Call the render function directly // Call the render function directly
(self.render_fn)(scope).as_return(cx) (self.render_fn)(scope).as_return(cx)
+1 -1
View File
@@ -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)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct ElementId(pub usize); pub struct ElementId(pub usize);
+51 -37
View File
@@ -1,31 +1,28 @@
use std::pin::Pin; use std::pin::Pin;
use crate::factory::{FiberLeaf, RenderReturn}; use crate::factory::{FiberLeaf, RenderReturn};
use crate::innerlude::SuspenseContext; use crate::innerlude::{Renderer, SuspenseContext};
use crate::mutations::Mutation; use crate::mutations::Mutation;
use crate::mutations::Mutation::*; use crate::mutations::Mutation::*;
use crate::nodes::VNode; use crate::nodes::VNode;
use crate::nodes::{DynamicNode, TemplateNode}; use crate::nodes::{DynamicNode, TemplateNode};
use crate::virtualdom::VirtualDom; use crate::virtual_dom::VirtualDom;
use crate::{AttributeValue, Element, ElementId, TemplateAttribute}; use crate::{AttributeValue, Element, ElementId, TemplateAttribute};
use bumpalo::boxed::Box as BumpBox; use bumpalo::boxed::Box as BumpBox;
use futures_util::Future; use futures_util::Future;
impl VirtualDom { impl VirtualDom {
/// Create this template and write its mutations /// Create this template and write its mutations
pub fn create<'a>( pub fn create<'a>(&mut self, mutations: &mut Renderer<'a>, template: &'a VNode<'a>) -> usize {
&mut self,
mutations: &mut Vec<Mutation<'a>>,
template: &'a VNode<'a>,
) -> usize {
// The best renderers will have templates prehydrated // The best renderers will have templates prehydrated
// Just in case, let's create the template using instructions anyways // Just in case, let's create the template using instructions anyways
if !self.templates.contains_key(&template.template.id) { if !self.templates.contains_key(&template.template.id) {
for node in template.template.roots { for node in template.template.roots {
let mutations = &mut mutations.template_mutations;
self.create_static_node(mutations, template, node); self.create_static_node(mutations, template, node);
} }
mutations.push(SaveTemplate { mutations.template_mutations.push(SaveTemplate {
name: template.template.id, name: template.template.id,
m: template.template.roots.len(), m: template.template.roots.len(),
}); });
@@ -162,7 +159,7 @@ impl VirtualDom {
pub fn create_dynamic_node<'a>( pub fn create_dynamic_node<'a>(
&mut self, &mut self,
mutations: &mut Vec<Mutation<'a>>, mutations: &mut Renderer<'a>,
template: &'a VNode<'a>, template: &'a VNode<'a>,
node: &'a DynamicNode<'a>, node: &'a DynamicNode<'a>,
idx: usize, idx: usize,
@@ -183,44 +180,61 @@ impl VirtualDom {
DynamicNode::Component { DynamicNode::Component {
props, placeholder, .. props, placeholder, ..
} => { } => {
println!("creaitng component");
let id = self.new_scope(unsafe { std::mem::transmute(props.get()) }); let id = self.new_scope(unsafe { std::mem::transmute(props.get()) });
let render_ret = self.run_scope(id); let render_ret = self.run_scope(id);
let render_ret: &mut RenderReturn = unsafe { std::mem::transmute(render_ret) }; 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 { 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)) => { RenderReturn::Sync(Some(template)) => {
let mutations_to_this_point = mutations.len();
self.scope_stack.push(id); self.scope_stack.push(id);
let created = self.create(mutations, template); let mut created = self.create(mutations, template);
self.scope_stack.pop(); self.scope_stack.pop();
if !self.waiting_on.is_empty() {
if let Some(boundary) =
self.scopes[id.0].has_context::<SuspenseContext>()
{
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 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
}
} }
} }
+9 -8
View File
@@ -1,6 +1,7 @@
use std::any::Any; use std::any::Any;
use crate::virtualdom::VirtualDom; use crate::innerlude::Renderer;
use crate::virtual_dom::VirtualDom;
use crate::{Attribute, AttributeValue, TemplateNode}; use crate::{Attribute, AttributeValue, TemplateNode};
use crate::any_props::VComponentProps; use crate::any_props::VComponentProps;
@@ -25,13 +26,13 @@ pub struct DirtyScope {
} }
impl<'b> VirtualDom { impl<'b> VirtualDom {
pub fn diff_scope(&mut self, mutations: &mut Vec<Mutation<'b>>, scope: ScopeId) { pub fn diff_scope(&mut self, mutations: &mut Renderer<'b>, scope: ScopeId) {
let scope_state = &mut self.scopes[scope.0]; let scope_state = &mut self.scopes[scope.0];
} }
pub fn diff_node( pub fn diff_node(
&mut self, &mut self,
muts: &mut Vec<Mutation<'b>>, muts: &mut Renderer<'b>,
left_template: &'b VNode<'b>, left_template: &'b VNode<'b>,
right_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. // the change list stack is in the same state when this function returns.
fn diff_non_keyed_children( fn diff_non_keyed_children(
&mut self, &mut self,
muts: &mut Vec<Mutation<'b>>, muts: &mut Renderer<'b>,
old: &'b [VNode<'b>], old: &'b [VNode<'b>],
new: &'b [VNode<'b>], new: &'b [VNode<'b>],
) { ) {
@@ -216,7 +217,7 @@ impl<'b> VirtualDom {
// The stack is empty upon entry. // The stack is empty upon entry.
fn diff_keyed_children( fn diff_keyed_children(
&mut self, &mut self,
muts: &mut Vec<Mutation<'b>>, muts: &mut Renderer<'b>,
old: &'b [VNode<'b>], old: &'b [VNode<'b>],
new: &'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. // /// If there is no offset, then this function returns None and the diffing is complete.
// fn diff_keyed_ends( // fn diff_keyed_ends(
// &mut self, // &mut self,
// muts: &mut Vec<Mutation<'b>>, // muts: &mut Renderer<'b>,
// old: &'b [VNode<'b>], // old: &'b [VNode<'b>],
// new: &'b [VNode<'b>], // new: &'b [VNode<'b>],
// ) -> Option<(usize, usize)> { // ) -> Option<(usize, usize)> {
@@ -354,7 +355,7 @@ impl<'b> VirtualDom {
// #[allow(clippy::too_many_lines)] // #[allow(clippy::too_many_lines)]
// fn diff_keyed_middle( // fn diff_keyed_middle(
// &mut self, // &mut self,
// muts: &mut Vec<Mutation<'b>>, // muts: &mut Renderer<'b>,
// old: &'b [VNode<'b>], // old: &'b [VNode<'b>],
// new: &'b [VNode<'b>], // new: &'b [VNode<'b>],
// ) { // ) {
@@ -532,7 +533,7 @@ impl<'b> VirtualDom {
/// Remove these nodes from the dom /// Remove these nodes from the dom
/// Wont generate mutations for the inner nodes /// Wont generate mutations for the inner nodes
fn remove_nodes(&mut self, muts: &mut Vec<Mutation<'b>>, nodes: &'b [VNode<'b>]) { fn remove_nodes(&mut self, muts: &mut Renderer<'b>, nodes: &'b [VNode<'b>]) {
// //
} }
} }
+1 -1
View File
@@ -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; use std::cell::Cell;
/// User Events are events that are shuttled from the renderer into the [`VirtualDom`] through the scheduler channel. /// User Events are events that are shuttled from the renderer into the [`VirtualDom`] through the scheduler channel.
+1 -1
View File
@@ -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 { impl VirtualDom {
pub fn drop_scope(&mut self, id: ScopeId) { pub fn drop_scope(&mut self, id: ScopeId) {
+28 -4
View File
@@ -14,7 +14,7 @@ mod properties;
mod scheduler; mod scheduler;
mod scope_arena; mod scope_arena;
mod scopes; mod scopes;
mod virtualdom; mod virtual_dom;
pub(crate) mod innerlude { pub(crate) mod innerlude {
pub use crate::arena::*; pub use crate::arena::*;
@@ -25,7 +25,7 @@ pub(crate) mod innerlude {
pub use crate::properties::*; pub use crate::properties::*;
pub use crate::scheduler::*; pub use crate::scheduler::*;
pub use crate::scopes::*; 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`]. /// An [`Element`] is a possibly-none [`VNode`] created by calling `render` on [`Scope`] or [`ScopeState`].
/// ///
@@ -83,6 +83,9 @@ pub use crate::innerlude::{
Scope, Scope,
ScopeId, ScopeId,
ScopeState, ScopeState,
Scoped,
SuspenseBoundary,
SuspenseContext,
TaskId, TaskId,
Template, Template,
TemplateAttribute, TemplateAttribute,
@@ -98,8 +101,8 @@ pub use crate::innerlude::{
pub mod prelude { pub mod prelude {
pub use crate::innerlude::{ pub use crate::innerlude::{
fc_to_builder, Attribute, DynamicNode, Element, EventPriority, LazyNodes, NodeFactory, fc_to_builder, Attribute, DynamicNode, Element, EventPriority, LazyNodes, NodeFactory,
Properties, Scope, ScopeId, ScopeState, TaskId, Template, TemplateAttribute, TemplateNode, Properties, Scope, ScopeId, ScopeState, Scoped, SuspenseBoundary, SuspenseContext, TaskId,
UiEvent, VNode, VirtualDom, Template, TemplateAttribute, TemplateNode, UiEvent, VNode, VirtualDom,
}; };
} }
@@ -132,3 +135,24 @@ macro_rules! to_owned {
let mut $es = $es.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 {
() => {};
}
+73 -1
View File
@@ -1,9 +1,81 @@
use crate::arena::ElementId; use crate::arena::ElementId;
#[derive(Debug)]
pub struct Renderer<'a> { pub struct Renderer<'a> {
mutations: Vec<Mutation<'a>>, pub subtree: usize,
pub mutations: Vec<Mutation<'a>>,
pub template_mutations: Vec<Mutation<'a>>,
// mutations: Vec<Mutations<'a>>,
} }
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<Mutation<'a>>;
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<Item = Mutation<'a>>) {
// 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<Mutation<'a>>,
// }
/*
each subtree has its own numbering scheme
*/
#[derive(Debug)] #[derive(Debug)]
pub enum Mutation<'a> { pub enum Mutation<'a> {
SetAttribute { SetAttribute {
+14 -11
View File
@@ -6,7 +6,10 @@ use std::{
}; };
use super::{waker::RcWake, SchedulerMsg}; use super::{waker::RcWake, SchedulerMsg};
use crate::{innerlude::Mutation, Element, ScopeId}; use crate::{
innerlude::{Mutation, Renderer},
Element, ScopeId,
};
use futures_task::Waker; use futures_task::Waker;
use futures_util::Future; use futures_util::Future;
@@ -14,27 +17,27 @@ use futures_util::Future;
pub struct SuspenseId(pub usize); pub struct SuspenseId(pub usize);
pub type SuspenseContext = Rc<RefCell<SuspenseBoundary>>; pub type SuspenseContext = Rc<RefCell<SuspenseBoundary>>;
/// Essentially a fiber in React /// Essentially a fiber in React
pub struct SuspenseBoundary { pub struct SuspenseBoundary {
pub id: ScopeId, pub id: ScopeId,
pub waiting_on: HashSet<SuspenseId>, pub waiting_on: HashSet<SuspenseId>,
pub mutations: Vec<Mutation<'static>>, pub mutations: Renderer<'static>,
} }
impl SuspenseBoundary { impl SuspenseBoundary {
pub fn new(id: ScopeId) -> Self { pub fn new(id: ScopeId) -> Rc<RefCell<Self>> {
Self { Rc::new(RefCell::new(Self {
id, id,
waiting_on: Default::default(), waiting_on: Default::default(),
mutations: Default::default(), mutations: Renderer::new(0),
} }))
} }
} }
pub struct SuspenseLeaf { pub struct SuspenseLeaf {
pub id: SuspenseId, pub id: SuspenseId,
pub scope_id: ScopeId, pub scope_id: ScopeId,
pub boundary: ScopeId,
pub tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>, pub tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
pub notified: Cell<bool>, pub notified: Cell<bool>,
@@ -43,10 +46,10 @@ pub struct SuspenseLeaf {
impl RcWake for SuspenseLeaf { impl RcWake for SuspenseLeaf {
fn wake_by_ref(arc_self: &Rc<Self>) { fn wake_by_ref(arc_self: &Rc<Self>) {
if arc_self.notified.get() { // if arc_self.notified.get() {
return; // return;
} // }
arc_self.notified.set(true); // arc_self.notified.set(true);
_ = arc_self _ = arc_self
.tx .tx
.unbounded_send(SchedulerMsg::SuspenseNotified(arc_self.id)); .unbounded_send(SchedulerMsg::SuspenseNotified(arc_self.id));
+20 -4
View File
@@ -3,7 +3,7 @@ use futures_util::{FutureExt, StreamExt};
use crate::{ use crate::{
factory::RenderReturn, factory::RenderReturn,
innerlude::{Mutation, SuspenseContext}, innerlude::{Mutation, Renderer, SuspenseContext},
VNode, VirtualDom, VNode, VirtualDom,
}; };
@@ -38,6 +38,8 @@ impl VirtualDom {
} }
SchedulerMsg::SuspenseNotified(id) => { SchedulerMsg::SuspenseNotified(id) => {
println!("suspense notified");
let leaf = self let leaf = self
.scheduler .scheduler
.handle .handle
@@ -63,11 +65,19 @@ impl VirtualDom {
// continue rendering the tree until we hit yet another suspended component // 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) 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::<SuspenseContext>() .consume_context::<SuspenseContext>()
.unwrap(); .unwrap();
println!("ready pool");
let mut fiber = boundary.borrow_mut(); let mut fiber = boundary.borrow_mut();
println!(
"Existing mutations {:?}, scope {:?}",
fiber.mutations, fiber.id
);
let scope = &mut self.scopes[scope_id.0]; let scope = &mut self.scopes[scope_id.0];
let arena = scope.current_arena(); let arena = scope.current_arena();
@@ -77,18 +87,24 @@ impl VirtualDom {
if let RenderReturn::Sync(Some(template)) = ret { if let RenderReturn::Sync(Some(template)) = ret {
let mutations = &mut fiber.mutations; let mutations = &mut fiber.mutations;
let template: &VNode = unsafe { std::mem::transmute(template) }; let template: &VNode = unsafe { std::mem::transmute(template) };
let mutations: &mut Vec<Mutation> = let mutations: &mut Renderer =
unsafe { std::mem::transmute(mutations) }; unsafe { std::mem::transmute(mutations) };
self.scope_stack.push(scope_id); self.scope_stack.push(scope_id);
self.create(mutations, template); self.create(mutations, template);
self.scope_stack.pop(); 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
} }
} }
} }
+4 -3
View File
@@ -14,7 +14,7 @@ use crate::{
factory::RenderReturn, factory::RenderReturn,
innerlude::{SuspenseId, SuspenseLeaf}, innerlude::{SuspenseId, SuspenseLeaf},
scopes::{ScopeId, ScopeState}, scopes::{ScopeId, ScopeState},
virtualdom::VirtualDom, virtual_dom::VirtualDom,
}; };
impl VirtualDom { impl VirtualDom {
@@ -77,13 +77,13 @@ impl VirtualDom {
let mut leaves = self.scheduler.handle.leaves.borrow_mut(); let mut leaves = self.scheduler.handle.leaves.borrow_mut();
let entry = leaves.vacant_entry(); let entry = leaves.vacant_entry();
let key = entry.key(); let key = entry.key();
let suspense_id = SuspenseId(key);
let leaf = Rc::new(SuspenseLeaf { let leaf = Rc::new(SuspenseLeaf {
scope_id, scope_id,
task: task.as_mut(), task: task.as_mut(),
id: SuspenseId(key), id: suspense_id,
tx: self.scheduler.handle.sender.clone(), tx: self.scheduler.handle.sender.clone(),
boundary: ScopeId(0),
notified: false.into(), notified: false.into(),
}); });
@@ -112,6 +112,7 @@ impl VirtualDom {
// Insert the future into fiber leaves and break // Insert the future into fiber leaves and break
_ => { _ => {
entry.insert(leaf); entry.insert(leaf);
self.waiting_on.push(suspense_id);
break; break;
} }
}; };
+17 -13
View File
@@ -19,23 +19,14 @@ use crate::{
TaskId, 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 scope: &'a ScopeState,
pub props: &'a T, pub props: &'a T,
} }
impl<T> Copy for Scope<'_, T> {} impl<'a, T> std::ops::Deref for Scoped<'a, T> {
impl<T> 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> {
type Target = &'a ScopeState; type Target = &'a ScopeState;
fn deref(&self) -> &Self::Target { 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<T: 'static + Clone>(&self) -> Option<T> {
match self.shared_contexts.borrow().get(&TypeId::of::<T>()) {
Some(shared) => Some(
(*shared
.downcast_ref::<T>()
.expect("Context of type T should exist"))
.clone(),
),
None => None,
}
}
/// Pushes the future onto the poll queue to be polled after the component renders. /// Pushes the future onto the poll queue to be polled after the component renders.
pub fn push_future(&self, fut: impl Future<Output = ()> + 'static) -> TaskId { pub fn push_future(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
self.tasks.spawn(self.id, fut) self.tasks.spawn(self.id, fut)
@@ -3,7 +3,7 @@ use crate::arena::ElementPath;
use crate::component::Component; use crate::component::Component;
use crate::diff::DirtyScope; use crate::diff::DirtyScope;
use crate::factory::RenderReturn; use crate::factory::RenderReturn;
use crate::innerlude::{Scheduler, SchedulerMsg}; use crate::innerlude::{Renderer, Scheduler, SchedulerMsg};
use crate::mutations::Mutation; use crate::mutations::Mutation;
use crate::nodes::{Template, TemplateId}; use crate::nodes::{Template, TemplateId};
@@ -13,20 +13,22 @@ use crate::{
}; };
use crate::{scheduler, Element, Scope}; use crate::{scheduler, Element, Scope};
use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender}; use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
use scheduler::{SuspenseBoundary, SuspenseContext}; use futures_util::Future;
use scheduler::{SuspenseBoundary, SuspenseContext, SuspenseId};
use slab::Slab; use slab::Slab;
use std::cell::RefCell;
use std::collections::{BTreeSet, HashMap}; use std::collections::{BTreeSet, HashMap};
use std::rc::Rc;
pub struct VirtualDom { pub struct VirtualDom {
pub(crate) templates: HashMap<TemplateId, Template<'static>>, pub(crate) templates: HashMap<TemplateId, Template<'static>>,
pub(crate) elements: Slab<ElementPath>, pub(crate) elements: Slab<ElementPath>,
pub(crate) scopes: Slab<ScopeState>, pub(crate) scopes: Slab<ScopeState>,
pub(crate) scope_stack: Vec<ScopeId>,
pub(crate) element_stack: Vec<ElementId>, pub(crate) element_stack: Vec<ElementId>,
pub(crate) dirty_scopes: BTreeSet<DirtyScope>, pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
pub(crate) scheduler: Scheduler, 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<ScopeId>,
pub(crate) waiting_on: Vec<SuspenseId>,
} }
impl VirtualDom { impl VirtualDom {
@@ -40,6 +42,7 @@ impl VirtualDom {
scope_stack: Vec::new(), scope_stack: Vec::new(),
element_stack: vec![ElementId(0)], element_stack: vec![ElementId(0)],
dirty_scopes: BTreeSet::new(), dirty_scopes: BTreeSet::new(),
waiting_on: Vec::new(),
scheduler, scheduler,
}; };
@@ -49,21 +52,25 @@ impl VirtualDom {
let root = res.new_scope(props); let root = res.new_scope(props);
// the root component is always a suspense boundary for any async children // 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)); assert_eq!(root, ScopeId(0));
res res
} }
/// Render the virtualdom, without processing any suspense. /// Render the virtualdom, without processing any suspense.
pub fn rebuild<'a>(&'a mut self, mutations: &mut Vec<Mutation<'a>>) { ///
/// 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 = self.run_scope(ScopeId(0));
let root_node: &RenderReturn = unsafe { std::mem::transmute(root_node) }; let root_node: &RenderReturn = unsafe { std::mem::transmute(root_node) };
let mut created = 0;
match root_node { match root_node {
RenderReturn::Sync(Some(node)) => { RenderReturn::Sync(Some(node)) => {
self.scope_stack.push(ScopeId(0)); self.scope_stack.push(ScopeId(0));
self.create(mutations, node); created = self.create(&mut mutations, node);
self.scope_stack.pop(); self.scope_stack.pop();
} }
RenderReturn::Sync(None) => { RenderReturn::Sync(None) => {
@@ -71,22 +78,22 @@ impl VirtualDom {
} }
RenderReturn::Async(_) => unreachable!(), RenderReturn::Async(_) => unreachable!(),
} }
mutations.push(Mutation::AppendChildren { m: created });
mutations
} }
/// Render what you can given the timeline and then move on /// Render what you can given the timeline and then move on
pub async fn render_with_deadline<'a>( ///
&'a mut self, /// It's generally a good idea to put some sort of limit on the suspense process in case a future is having issues.
future: impl std::future::Future<Output = ()>, pub async fn render_with_deadline(
mutations: &mut Vec<Mutation<'a>>, &mut self,
) { deadline: impl Future<Output = ()>,
) -> Vec<Mutation> {
todo!() todo!()
} }
// Whenever the future is canceled, the VirtualDom will be
pub async fn render<'a>(&'a mut self, mutations: &mut Vec<Mutation<'a>>) {
//
}
pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> { pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
self.scopes.get(id.0) self.scopes.get(id.0)
} }
+1 -2
View File
@@ -21,6 +21,5 @@ async fn ChildAsync(cx: Scope<'_>) -> Element {
fn it_works() { fn it_works() {
let mut dom = VirtualDom::new(app); let mut dom = VirtualDom::new(app);
let mut mutations = vec![]; let mut mutations = dom.rebuild();
dom.rebuild(&mut mutations);
} }
+23 -9
View File
@@ -1,13 +1,11 @@
use std::{cell::Cell, ptr::null_mut, time::Duration};
use dioxus_core::*; use dioxus_core::*;
use std::{cell::RefCell, rc::Rc, time::Duration};
#[tokio::test] #[tokio::test]
async fn it_works() { async fn it_works() {
let mut dom = VirtualDom::new(app); let mut dom = VirtualDom::new(app);
let mut mutations = vec![]; let mutations = dom.rebuild();
dom.rebuild(&mut mutations);
println!("mutations: {:?}", mutations); println!("mutations: {:?}", mutations);
@@ -15,8 +13,23 @@ async fn it_works() {
} }
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
let dy = cx.component(async_child, (), "async_child"); println!("running root app");
VNode::single_component(&cx, dy, "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 { async fn async_child(cx: Scope<'_>) -> Element {
@@ -34,8 +47,9 @@ async fn async_child(cx: Scope<'_>) -> Element {
println!("Future awaited and complete"); println!("Future awaited and complete");
let dy = cx.component(async_child, (), "async_child"); VNode::single_component(cx, cx.component(async_text, (), "async_text"), "app")
VNode::single_component(&cx, dy, "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")
} }
+1 -2
View File
@@ -6,8 +6,7 @@ use dioxus_core::*;
async fn it_works() { async fn it_works() {
let mut dom = VirtualDom::new(app); let mut dom = VirtualDom::new(app);
let mut mutations = vec![]; let mutations = dom.rebuild();
dom.rebuild(&mut mutations);
println!("mutations: {:?}", mutations); println!("mutations: {:?}", mutations);
+1
View File
@@ -32,6 +32,7 @@ rand = { version = "0.8.4", features = ["small_rng"] }
criterion = "0.3.5" criterion = "0.3.5"
thiserror = "1.0.30" thiserror = "1.0.30"
env_logger = "0.9.0" env_logger = "0.9.0"
tokio = { version = "1.21.2", features = ["full"] }
# dioxus-edit-stream = { path = "../edit-stream" } # dioxus-edit-stream = { path = "../edit-stream" }
[[bench]] [[bench]]
+50 -26
View File
@@ -1,3 +1,5 @@
use std::future::IntoFuture;
use dioxus::prelude::*; use dioxus::prelude::*;
fn basic_syntax_is_a_template(cx: Scope) -> Element { 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 { #[inline_props]
cx.render(rsx! { fn suspense_boundary<'a>(cx: Scope<'a>, children: Element<'a>) -> Element {
div { cx.use_hook(|| cx.provide_context(SuspenseBoundary::new(cx.scope_id())));
basic_child { } cx.render(rsx! { children })
async_child { }
}
})
} }
fn basic_child(cx: Scope) -> Element { fn basic_child(cx: Scope) -> Element {
todo!() cx.render(rsx! {
div { "basic child 1" }
})
} }
async fn async_child(cx: Scope<'_>) -> Element { 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] #[tokio::test]
fn basic_prints() { async fn basic_prints() {
let mut dom = VirtualDom::new(basic_template); let mut dom = VirtualDom::new(|cx| {
cx.render(rsx! {
div {
h1 { "var" }
suspense_boundary {
basic_child { }
async_child { }
}
}
})
});
let mut edits = Vec::new(); dbg!(dom.rebuild());
dom.rebuild(&mut edits);
dbg!(edits);
let mut edits = Vec::new(); dom.wait_for_work().await;
dom.rebuild(&mut edits);
dbg!(edits); dbg!(dom.rebuild());
// let renderer = dioxus_edit_stream::Mutations::default();
//
// dbg!(renderer.edits);
// takes_it(basic_child);
} }
// fn takes_it(f: fn(Scope) -> Element) {}
// fn takes_it(f: fn(Scope) -> Element) {}
+104 -36
View File
@@ -1,6 +1,12 @@
#![allow(missing_docs)] #![allow(missing_docs)]
use dioxus_core::{ScopeState, TaskId}; 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. /// A future that resolves to a value.
/// ///
@@ -28,16 +34,13 @@ where
let state = cx.use_hook(move || UseFuture { let state = cx.use_hook(move || UseFuture {
update: cx.schedule_update(), update: cx.schedule_update(),
needs_regen: Cell::new(true), needs_regen: Cell::new(true),
slot: Rc::new(Cell::new(None)), values: Default::default(),
value: None,
task: Cell::new(None), task: Cell::new(None),
dependencies: Vec::new(), dependencies: Vec::new(),
waker: Default::default(),
}); });
if let Some(value) = state.slot.take() { *state.waker.borrow_mut() = None;
state.value = Some(value);
state.task.set(None);
}
if dependencies.clone().apply(&mut state.dependencies) || state.needs_regen.get() { if dependencies.clone().apply(&mut state.dependencies) || state.needs_regen.get() {
// We don't need regen anymore // We don't need regen anymore
@@ -47,8 +50,9 @@ where
let fut = future(dependencies.out()); let fut = future(dependencies.out());
// Clone in our cells // Clone in our cells
let slot = state.slot.clone(); let values = state.values.clone();
let schedule_update = state.update.clone(); let schedule_update = state.update.clone();
let waker = state.waker.clone();
// Cancel the current future // Cancel the current future
if let Some(current) = state.task.take() { if let Some(current) = state.task.take() {
@@ -57,8 +61,16 @@ where
state.task.set(Some(cx.push_future(async move { state.task.set(Some(cx.push_future(async move {
let res = fut.await; let res = fut.await;
slot.set(Some(res)); values.borrow_mut().push(Box::leak(Box::new(res)));
schedule_update();
// 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<T> { pub struct UseFuture<T> {
update: Arc<dyn Fn()>, update: Arc<dyn Fn()>,
needs_regen: Cell<bool>, needs_regen: Cell<bool>,
value: Option<T>,
slot: Rc<Cell<Option<T>>>,
task: Cell<Option<TaskId>>, task: Cell<Option<TaskId>>,
dependencies: Vec<Box<dyn Any>>, dependencies: Vec<Box<dyn Any>>,
waker: Rc<RefCell<Option<std::task::Waker>>>,
values: Rc<RefCell<Vec<*mut T>>>,
} }
pub enum UseFutureState<'a, T> { pub enum UseFutureState<'a, T> {
@@ -105,22 +117,25 @@ impl<T> UseFuture<T> {
// clears the value in the future slot without starting the future over // clears the value in the future slot without starting the future over
pub fn clear(&self) -> Option<T> { pub fn clear(&self) -> Option<T> {
(self.update)(); todo!()
self.slot.replace(None) // (self.update)();
// self.slot.replace(None)
} }
// Manually set the value in the future slot without starting the future over // Manually set the value in the future slot without starting the future over
pub fn set(&self, new_value: T) { pub fn set(&self, new_value: T) {
self.slot.set(Some(new_value)); // self.slot.set(Some(new_value));
self.needs_regen.set(true); // self.needs_regen.set(true);
(self.update)(); // (self.update)();
todo!()
} }
/// Return any value, even old values if the future has not yet resolved. /// 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`. /// If the future has never completed, the returned value will be `None`.
pub fn value(&self) -> Option<&T> { 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 /// Get the ID of the future in Dioxus' internal scheduler
@@ -130,18 +145,49 @@ impl<T> UseFuture<T> {
/// Get the current stateof the future. /// Get the current stateof the future.
pub fn state(&self) -> UseFutureState<T> { pub fn state(&self) -> UseFutureState<T> {
match (&self.task.get(), &self.value) { todo!()
// If we have a task and an existing value, we're reloading // match (&self.task.get(), &self.value) {
(Some(_), Some(val)) => UseFutureState::Reloading(val), // // 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 // // no task, but value - we're done
(None, Some(val)) => UseFutureState::Complete(val), // (None, Some(val)) => UseFutureState::Complete(val),
// no task, no value - something's wrong? return pending // // no task, no value - something's wrong? return pending
(None, None) => UseFutureState::Pending, // (None, None) => UseFutureState::Pending,
// Task, no value - we're still pending // // Task, no value - we're still pending
(Some(_), None) => UseFutureState::Pending, // (Some(_), None) => UseFutureState::Pending,
// }
}
}
impl<'a, T> IntoFuture for &'a UseFuture<T> {
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<T>,
}
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<Self::Output> {
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,);
impl_dep!(A = a, B = b, C = c, D = d, E = e, F = f, G = g, H = h,); 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -256,22 +314,32 @@ mod tests {
e: i32, e: i32,
} }
fn app(cx: Scope<MyProps>) -> Element { async fn app(cx: Scope<'_, MyProps>) -> Element {
// should only ever run once // should only ever run once
let fut = use_future(&cx, (), |_| async move { let fut = use_future(cx, (), |_| async move {});
//
});
// runs when a is changed // 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 // 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 None
} }
} }
+3 -1
View File
@@ -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!()
} }
} }
-41
View File
@@ -1,41 +0,0 @@
use std::{cell::Cell, future::Future, rc::Rc};
use dioxus_core::{Element, ScopeState, TaskId};
pub fn use_suspense<R: 'static, F: Future<Output = R> + '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<Cell<Option<R>>> = 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<R> {
_task: TaskId,
_wip_value: Rc<Cell<Option<R>>>,
value: Option<R>,
}