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::{
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)
+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)]
pub struct ElementId(pub usize);
+51 -37
View File
@@ -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<Mutation<'a>>,
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<Mutation<'a>>,
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::<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
}
// 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 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<Mutation<'b>>, 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<Mutation<'b>>,
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<Mutation<'b>>,
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<Mutation<'b>>,
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<Mutation<'b>>,
// 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<Mutation<'b>>,
// 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<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;
/// 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 {
pub fn drop_scope(&mut self, id: ScopeId) {
+28 -4
View File
@@ -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 {
() => {};
}
+73 -1
View File
@@ -1,9 +1,81 @@
use crate::arena::ElementId;
#[derive(Debug)]
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)]
pub enum Mutation<'a> {
SetAttribute {
+14 -11
View File
@@ -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<RefCell<SuspenseBoundary>>;
/// Essentially a fiber in React
pub struct SuspenseBoundary {
pub id: ScopeId,
pub waiting_on: HashSet<SuspenseId>,
pub mutations: Vec<Mutation<'static>>,
pub mutations: Renderer<'static>,
}
impl SuspenseBoundary {
pub fn new(id: ScopeId) -> Self {
Self {
pub fn new(id: ScopeId) -> Rc<RefCell<Self>> {
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<SchedulerMsg>,
pub notified: Cell<bool>,
@@ -43,10 +46,10 @@ pub struct SuspenseLeaf {
impl RcWake for SuspenseLeaf {
fn wake_by_ref(arc_self: &Rc<Self>) {
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));
+20 -4
View File
@@ -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::<SuspenseContext>()
.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<Mutation> =
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
}
}
}
+4 -3
View File
@@ -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;
}
};
+17 -13
View File
@@ -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<T> Copy for Scope<'_, 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> {
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<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.
pub fn push_future(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
self.tasks.spawn(self.id, fut)
@@ -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<TemplateId, Template<'static>>,
pub(crate) elements: Slab<ElementPath>,
pub(crate) scopes: Slab<ScopeState>,
pub(crate) scope_stack: Vec<ScopeId>,
pub(crate) element_stack: Vec<ElementId>,
pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
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 {
@@ -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<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 = 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<Output = ()>,
mutations: &mut Vec<Mutation<'a>>,
) {
///
/// 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<Output = ()>,
) -> Vec<Mutation> {
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> {
self.scopes.get(id.0)
}