chore: clean up scheduler code
This commit is contained in:
1 parent
c096057dd3
commit
f5bc137f01
17 files changed
+439
-294
No files matched your search
@@ -23,13 +23,8 @@ fxhash = "0.2"
|
|||||||
# Used in diffing
|
# Used in diffing
|
||||||
longest-increasing-subsequence = "0.1.0"
|
longest-increasing-subsequence = "0.1.0"
|
||||||
|
|
||||||
# internall used
|
|
||||||
log = { version = "0.4" }
|
|
||||||
|
|
||||||
futures-util = { version = "0.3", default-features = false }
|
futures-util = { version = "0.3", default-features = false }
|
||||||
|
|
||||||
smallvec = "1.6"
|
|
||||||
|
|
||||||
slab = "0.4"
|
slab = "0.4"
|
||||||
|
|
||||||
futures-channel = "0.3.21"
|
futures-channel = "0.3.21"
|
||||||
@@ -38,7 +33,6 @@ indexmap = "1.7"
|
|||||||
|
|
||||||
# Serialize the Edits for use in Webview/Liveview instances
|
# Serialize the Edits for use in Webview/Liveview instances
|
||||||
serde = { version = "1", features = ["derive"], optional = true }
|
serde = { version = "1", features = ["derive"], optional = true }
|
||||||
futures-task = "0.3.25"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { version = "*", features = ["full"] }
|
tokio = { version = "*", features = ["full"] }
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ where
|
|||||||
{
|
{
|
||||||
pub render_fn: fn(Scope<'a, P>) -> F,
|
pub render_fn: fn(Scope<'a, P>) -> F,
|
||||||
pub memo: unsafe fn(&P, &P) -> bool,
|
pub memo: unsafe fn(&P, &P) -> bool,
|
||||||
pub props: *const P,
|
pub props: P,
|
||||||
pub _marker: PhantomData<A>,
|
pub _marker: PhantomData<A>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ impl<'a> VComponentProps<'a, (), ()> {
|
|||||||
Self {
|
Self {
|
||||||
render_fn,
|
render_fn,
|
||||||
memo: <() as PartialEq>::eq,
|
memo: <() as PartialEq>::eq,
|
||||||
props: std::ptr::null_mut(),
|
props: (),
|
||||||
_marker: PhantomData,
|
_marker: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@ impl<'a, P, A, F: ComponentReturn<'a, A>> VComponentProps<'a, P, A, F> {
|
|||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
render_fn: fn(Scope<'a, P>) -> F,
|
render_fn: fn(Scope<'a, P>) -> F,
|
||||||
memo: unsafe fn(&P, &P) -> bool,
|
memo: unsafe fn(&P, &P) -> bool,
|
||||||
props: *const P,
|
props: P,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
render_fn,
|
render_fn,
|
||||||
@@ -66,12 +66,9 @@ impl<'a, P, A, F: ComponentReturn<'a, A>> AnyProps<'a> for VComponentProps<'a, P
|
|||||||
(self.memo)(real_us, real_other)
|
(self.memo)(real_us, real_other)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&self, cx: &'a ScopeState) -> RenderReturn<'a> {
|
fn render(&'a self, cx: &'a ScopeState) -> RenderReturn<'a> {
|
||||||
// Make sure the scope ptr is not null
|
|
||||||
// self.props.state.set(scope);
|
|
||||||
|
|
||||||
let scope = cx.bump().alloc(Scoped {
|
let scope = cx.bump().alloc(Scoped {
|
||||||
props: unsafe { &*self.props },
|
props: &self.props,
|
||||||
scope: cx,
|
scope: cx,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+50
-43
@@ -1,20 +1,32 @@
|
|||||||
use std::pin::Pin;
|
use crate::factory::RenderReturn;
|
||||||
|
use crate::innerlude::{Mutations, SuspenseContext};
|
||||||
use crate::factory::{FiberLeaf, RenderReturn};
|
|
||||||
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::virtual_dom::VirtualDom;
|
use crate::virtual_dom::VirtualDom;
|
||||||
use crate::{AttributeValue, Element, ElementId, TemplateAttribute};
|
use crate::{AttributeValue, ElementId, ScopeId, TemplateAttribute};
|
||||||
use bumpalo::boxed::Box as BumpBox;
|
|
||||||
use futures_util::Future;
|
|
||||||
|
|
||||||
impl VirtualDom {
|
impl VirtualDom {
|
||||||
|
pub(crate) fn create_scope<'a>(
|
||||||
|
&mut self,
|
||||||
|
scope: ScopeId,
|
||||||
|
mutations: &mut Mutations<'a>,
|
||||||
|
template: &'a VNode<'a>,
|
||||||
|
) -> usize {
|
||||||
|
self.scope_stack.push(scope);
|
||||||
|
let out = self.create(mutations, template);
|
||||||
|
self.scope_stack.pop();
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
/// Create this template and write its mutations
|
/// Create this template and write its mutations
|
||||||
pub fn create<'a>(&mut self, mutations: &mut Renderer<'a>, template: &'a VNode<'a>) -> usize {
|
pub(crate) fn create<'a>(
|
||||||
// The best renderers will have templates prehydrated
|
&mut self,
|
||||||
|
mutations: &mut Mutations<'a>,
|
||||||
|
template: &'a VNode<'a>,
|
||||||
|
) -> usize {
|
||||||
|
// The best renderers will have templates prehydrated and registered
|
||||||
// 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 {
|
||||||
@@ -31,7 +43,7 @@ impl VirtualDom {
|
|||||||
.insert(template.template.id, template.template.clone());
|
.insert(template.template.id, template.template.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk the roots backwards, creating nodes and assigning IDs
|
// Walk the roots, creating nodes and assigning IDs
|
||||||
// todo: adjust dynamic nodes to be in the order of roots and then leaves (ie BFS)
|
// todo: adjust dynamic nodes to be in the order of roots and then leaves (ie BFS)
|
||||||
let mut dynamic_attrs = template.template.attr_paths.iter().enumerate().peekable();
|
let mut dynamic_attrs = template.template.attr_paths.iter().enumerate().peekable();
|
||||||
let mut dynamic_nodes = template.template.node_paths.iter().enumerate().peekable();
|
let mut dynamic_nodes = template.template.node_paths.iter().enumerate().peekable();
|
||||||
@@ -39,25 +51,23 @@ impl VirtualDom {
|
|||||||
let mut on_stack = 0;
|
let mut on_stack = 0;
|
||||||
for (root_idx, root) in template.template.roots.iter().enumerate() {
|
for (root_idx, root) in template.template.roots.iter().enumerate() {
|
||||||
on_stack += match root {
|
on_stack += match root {
|
||||||
TemplateNode::Element { .. } | TemplateNode::Text(_) => {
|
TemplateNode::Element { .. }
|
||||||
|
| TemplateNode::Text(_)
|
||||||
|
| TemplateNode::DynamicText { .. } => {
|
||||||
mutations.push(LoadTemplate {
|
mutations.push(LoadTemplate {
|
||||||
name: template.template.id,
|
name: template.template.id,
|
||||||
index: root_idx,
|
index: root_idx,
|
||||||
});
|
});
|
||||||
|
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
|
|
||||||
TemplateNode::Dynamic(id) => {
|
TemplateNode::Dynamic(id) => {
|
||||||
self.create_dynamic_node(mutations, template, &template.dynamic_nodes[*id], *id)
|
self.create_dynamic_node(mutations, template, &template.dynamic_nodes[*id], *id)
|
||||||
}
|
}
|
||||||
|
|
||||||
TemplateNode::DynamicText { .. } => 1,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// we're on top of a node that has a dynamic attribute for a descendant
|
// we're on top of a node that has a dynamic attribute for a descendant
|
||||||
// Set that attribute now before the stack gets in a weird state
|
// Set that attribute now before the stack gets in a weird state
|
||||||
|
|
||||||
while let Some((mut attr_id, path)) =
|
while let Some((mut attr_id, path)) =
|
||||||
dynamic_attrs.next_if(|(_, p)| p[0] == root_idx as u8)
|
dynamic_attrs.next_if(|(_, p)| p[0] == root_idx as u8)
|
||||||
{
|
{
|
||||||
@@ -67,18 +77,17 @@ impl VirtualDom {
|
|||||||
id,
|
id,
|
||||||
});
|
});
|
||||||
|
|
||||||
// set any future attrs with the same path (ie same element)
|
|
||||||
loop {
|
loop {
|
||||||
let attr = &template.dynamic_attrs[attr_id];
|
let attribute = &template.dynamic_attrs[attr_id];
|
||||||
attr.mounted_element.set(id);
|
attribute.mounted_element.set(id);
|
||||||
|
|
||||||
match attr.value {
|
match attribute.value {
|
||||||
AttributeValue::Text(value) => mutations.push(SetAttribute {
|
AttributeValue::Text(value) => mutations.push(SetAttribute {
|
||||||
name: attr.name,
|
name: attribute.name,
|
||||||
value,
|
value,
|
||||||
id,
|
id,
|
||||||
}),
|
}),
|
||||||
AttributeValue::Listener(_) => {}
|
AttributeValue::Listener(_) => todo!("create listener attributes"),
|
||||||
AttributeValue::Float(_) => todo!(),
|
AttributeValue::Float(_) => todo!(),
|
||||||
AttributeValue::Int(_) => todo!(),
|
AttributeValue::Int(_) => todo!(),
|
||||||
AttributeValue::Bool(_) => todo!(),
|
AttributeValue::Bool(_) => todo!(),
|
||||||
@@ -86,10 +95,10 @@ impl VirtualDom {
|
|||||||
AttributeValue::None => todo!(),
|
AttributeValue::None => todo!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((next_attr_id, _)) = dynamic_attrs.next_if(|(_, p)| *p == path) {
|
// Only push the dynamic attributes forward if they match the current path (same element)
|
||||||
attr_id = next_attr_id
|
match dynamic_attrs.next_if(|(_, p)| *p == path) {
|
||||||
} else {
|
Some((next_attr_id, _)) => attr_id = next_attr_id,
|
||||||
break;
|
None => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,7 +122,7 @@ impl VirtualDom {
|
|||||||
on_stack
|
on_stack
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_static_node<'a>(
|
pub(crate) fn create_static_node<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
mutations: &mut Vec<Mutation<'a>>,
|
mutations: &mut Vec<Mutation<'a>>,
|
||||||
template: &'a VNode<'a>,
|
template: &'a VNode<'a>,
|
||||||
@@ -156,9 +165,9 @@ impl VirtualDom {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_dynamic_node<'a>(
|
pub(crate) fn create_dynamic_node<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
mutations: &mut Renderer<'a>,
|
mutations: &mut Mutations<'a>,
|
||||||
template: &'a VNode<'a>,
|
template: &'a VNode<'a>,
|
||||||
node: &'a DynamicNode<'a>,
|
node: &'a DynamicNode<'a>,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
@@ -172,24 +181,22 @@ impl VirtualDom {
|
|||||||
path: &template.template.node_paths[idx][1..],
|
path: &template.template.node_paths[idx][1..],
|
||||||
value,
|
value,
|
||||||
});
|
});
|
||||||
|
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
|
|
||||||
DynamicNode::Component {
|
DynamicNode::Component {
|
||||||
props, placeholder, ..
|
props, placeholder, ..
|
||||||
} => {
|
} => {
|
||||||
let id = self.new_scope(unsafe { std::mem::transmute(props.get()) });
|
let scope = self
|
||||||
let render_ret = self.run_scope(id);
|
.new_scope(unsafe { std::mem::transmute(props.get()) })
|
||||||
let render_ret: &mut RenderReturn = unsafe { std::mem::transmute(render_ret) };
|
.id;
|
||||||
|
let return_nodes = unsafe { self.run_scope(scope).extend_lifetime_ref() };
|
||||||
|
|
||||||
// if boundary or subtree, start working on a new stack of mutations
|
match return_nodes {
|
||||||
|
|
||||||
match render_ret {
|
|
||||||
RenderReturn::Sync(None) | RenderReturn::Async(_) => {
|
RenderReturn::Sync(None) | RenderReturn::Async(_) => {
|
||||||
let new_id = self.next_element(template);
|
let new_id = self.next_element(template);
|
||||||
placeholder.set(Some(new_id));
|
placeholder.set(Some(new_id));
|
||||||
self.scopes[id.0].placeholder.set(Some(new_id));
|
self.scopes[scope.0].placeholder.set(Some(new_id));
|
||||||
mutations.push(AssignId {
|
mutations.push(AssignId {
|
||||||
id: new_id,
|
id: new_id,
|
||||||
path: &template.template.node_paths[idx][1..],
|
path: &template.template.node_paths[idx][1..],
|
||||||
@@ -200,28 +207,28 @@ impl VirtualDom {
|
|||||||
RenderReturn::Sync(Some(template)) => {
|
RenderReturn::Sync(Some(template)) => {
|
||||||
let mutations_to_this_point = mutations.len();
|
let mutations_to_this_point = mutations.len();
|
||||||
|
|
||||||
self.scope_stack.push(id);
|
self.scope_stack.push(scope);
|
||||||
let mut 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 !self.collected_leaves.is_empty() {
|
||||||
if let Some(boundary) =
|
if let Some(boundary) =
|
||||||
self.scopes[id.0].has_context::<SuspenseContext>()
|
self.scopes[scope.0].has_context::<SuspenseContext>()
|
||||||
{
|
{
|
||||||
let mut boundary_mut = boundary.borrow_mut();
|
let mut boundary_mut = boundary.borrow_mut();
|
||||||
let split_off = mutations.split_off(mutations_to_this_point);
|
let split_off = mutations.split_off(mutations_to_this_point);
|
||||||
|
|
||||||
let split_off = unsafe { std::mem::transmute(split_off) };
|
let split_off = unsafe { std::mem::transmute(split_off) };
|
||||||
|
|
||||||
println!("SPLIT OFF: {:#?}", split_off);
|
|
||||||
|
|
||||||
boundary_mut.mutations.mutations = split_off;
|
boundary_mut.mutations.mutations = split_off;
|
||||||
boundary_mut.waiting_on.extend(self.waiting_on.drain(..));
|
boundary_mut
|
||||||
|
.waiting_on
|
||||||
|
.extend(self.collected_leaves.drain(..));
|
||||||
|
|
||||||
// Since this is a boundary, use it as a placeholder
|
// Since this is a boundary, use it as a placeholder
|
||||||
let new_id = self.next_element(template);
|
let new_id = self.next_element(template);
|
||||||
placeholder.set(Some(new_id));
|
placeholder.set(Some(new_id));
|
||||||
self.scopes[id.0].placeholder.set(Some(new_id));
|
self.scopes[scope.0].placeholder.set(Some(new_id));
|
||||||
mutations.push(AssignId {
|
mutations.push(AssignId {
|
||||||
id: new_id,
|
id: new_id,
|
||||||
path: &template.template.node_paths[idx][1..],
|
path: &template.template.node_paths[idx][1..],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
|
||||||
use crate::innerlude::Renderer;
|
use crate::innerlude::Mutations;
|
||||||
use crate::virtual_dom::VirtualDom;
|
use crate::virtual_dom::VirtualDom;
|
||||||
use crate::{Attribute, AttributeValue, TemplateNode};
|
use crate::{Attribute, AttributeValue, TemplateNode};
|
||||||
|
|
||||||
@@ -20,19 +20,32 @@ use crate::{
|
|||||||
use fxhash::{FxHashMap, FxHashSet};
|
use fxhash::{FxHashMap, FxHashSet};
|
||||||
use slab::Slab;
|
use slab::Slab;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct DirtyScope {
|
pub struct DirtyScope {
|
||||||
height: usize,
|
pub height: u32,
|
||||||
id: ScopeId,
|
pub id: ScopeId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for DirtyScope {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(self.height.cmp(&other.height))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for DirtyScope {
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
self.height.cmp(&other.height)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'b> VirtualDom {
|
impl<'b> VirtualDom {
|
||||||
pub fn diff_scope(&mut self, mutations: &mut Renderer<'b>, scope: ScopeId) {
|
pub fn diff_scope(&mut self, mutations: &mut Mutations<'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 Renderer<'b>,
|
muts: &mut Mutations<'b>,
|
||||||
left_template: &'b VNode<'b>,
|
left_template: &'b VNode<'b>,
|
||||||
right_template: &'b VNode<'b>,
|
right_template: &'b VNode<'b>,
|
||||||
) {
|
) {
|
||||||
@@ -177,7 +190,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 Renderer<'b>,
|
muts: &mut Mutations<'b>,
|
||||||
old: &'b [VNode<'b>],
|
old: &'b [VNode<'b>],
|
||||||
new: &'b [VNode<'b>],
|
new: &'b [VNode<'b>],
|
||||||
) {
|
) {
|
||||||
@@ -217,7 +230,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 Renderer<'b>,
|
muts: &mut Mutations<'b>,
|
||||||
old: &'b [VNode<'b>],
|
old: &'b [VNode<'b>],
|
||||||
new: &'b [VNode<'b>],
|
new: &'b [VNode<'b>],
|
||||||
) {
|
) {
|
||||||
@@ -533,7 +546,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 Renderer<'b>, nodes: &'b [VNode<'b>]) {
|
fn remove_nodes(&mut self, muts: &mut Mutations<'b>, nodes: &'b [VNode<'b>]) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,6 @@ impl ScopeState {
|
|||||||
where
|
where
|
||||||
P: Properties + 'a,
|
P: Properties + 'a,
|
||||||
{
|
{
|
||||||
let props = self.bump().alloc(props);
|
|
||||||
let as_component = component;
|
let as_component = component;
|
||||||
let vcomp = VComponentProps::new(as_component, P::memoize, props);
|
let vcomp = VComponentProps::new(as_component, P::memoize, props);
|
||||||
let as_dyn = self.bump().alloc(vcomp) as &mut dyn AnyProps;
|
let as_dyn = self.bump().alloc(vcomp) as &mut dyn AnyProps;
|
||||||
@@ -136,6 +135,15 @@ pub enum RenderReturn<'a> {
|
|||||||
Async(BumpBox<'a, dyn Future<Output = Element<'a>> + 'a>),
|
Async(BumpBox<'a, dyn Future<Output = Element<'a>> + 'a>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> RenderReturn<'a> {
|
||||||
|
pub(crate) unsafe fn extend_lifetime_ref<'c>(&self) -> &'c RenderReturn<'c> {
|
||||||
|
unsafe { std::mem::transmute(self) }
|
||||||
|
}
|
||||||
|
pub(crate) unsafe fn extend_lifetime<'c>(self) -> RenderReturn<'c> {
|
||||||
|
unsafe { std::mem::transmute(self) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type FiberLeaf<'a> = Pin<BumpBox<'a, dyn Future<Output = Element<'a>> + 'a>>;
|
pub type FiberLeaf<'a> = Pin<BumpBox<'a, dyn Future<Output = Element<'a>> + 'a>>;
|
||||||
|
|
||||||
pub trait IntoVnode<'a, A = ()> {
|
pub trait IntoVnode<'a, A = ()> {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
use crate::arena::ElementId;
|
use crate::arena::ElementId;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Renderer<'a> {
|
pub struct Mutations<'a> {
|
||||||
pub subtree: usize,
|
pub subtree: usize,
|
||||||
pub template_mutations: Vec<Mutation<'a>>,
|
pub template_mutations: Vec<Mutation<'a>>,
|
||||||
pub mutations: Vec<Mutation<'a>>,
|
pub mutations: Vec<Mutation<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Renderer<'a> {
|
impl<'a> Mutations<'a> {
|
||||||
pub fn new(subtree: usize) -> Self {
|
pub fn new(subtree: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
subtree,
|
subtree,
|
||||||
@@ -17,7 +17,7 @@ impl<'a> Renderer<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> std::ops::Deref for Renderer<'a> {
|
impl<'a> std::ops::Deref for Mutations<'a> {
|
||||||
type Target = Vec<Mutation<'a>>;
|
type Target = Vec<Mutation<'a>>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
@@ -25,52 +25,12 @@ impl<'a> std::ops::Deref for Renderer<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::DerefMut for Renderer<'_> {
|
impl std::ops::DerefMut for Mutations<'_> {
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
&mut self.mutations
|
&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
|
each subtree has its own numbering scheme
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ impl<'a> VNode<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn single_text(
|
pub fn single_text(
|
||||||
cx: &'a ScopeState,
|
_cx: &'a ScopeState,
|
||||||
text: &'static [TemplateNode<'static>],
|
text: &'static [TemplateNode<'static>],
|
||||||
id: &'static str,
|
id: &'static str,
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
use futures_util::Future;
|
|
||||||
use slab::Slab;
|
|
||||||
use std::{cell::RefCell, pin::Pin, rc::Rc, sync::Arc};
|
|
||||||
|
|
||||||
use super::{LocalTask, SchedulerMsg, SuspenseLeaf};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct SchedulerHandle(Rc<HandleInner>);
|
|
||||||
|
|
||||||
impl std::ops::Deref for SchedulerHandle {
|
|
||||||
type Target = HandleInner;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct HandleInner {
|
|
||||||
pub sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
|
|
||||||
|
|
||||||
/// Tasks created with cx.spawn
|
|
||||||
pub tasks: RefCell<Slab<Rc<LocalTask>>>,
|
|
||||||
|
|
||||||
/// Async components
|
|
||||||
pub leaves: RefCell<Slab<Rc<SuspenseLeaf>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SchedulerHandle {
|
|
||||||
pub fn new(sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>) -> Self {
|
|
||||||
Self(Rc::new(HandleInner {
|
|
||||||
sender,
|
|
||||||
tasks: RefCell::new(Slab::new()),
|
|
||||||
leaves: RefCell::new(Slab::new()),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,11 @@
|
|||||||
use slab::Slab;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use crate::ScopeId;
|
use crate::ScopeId;
|
||||||
|
use slab::Slab;
|
||||||
|
|
||||||
mod bumpslab;
|
|
||||||
mod handle;
|
|
||||||
mod suspense;
|
mod suspense;
|
||||||
mod task;
|
mod task;
|
||||||
mod wait;
|
mod wait;
|
||||||
mod waker;
|
mod waker;
|
||||||
|
|
||||||
pub use handle::*;
|
|
||||||
pub use suspense::*;
|
pub use suspense::*;
|
||||||
pub use task::*;
|
pub use task::*;
|
||||||
pub use waker::RcWake;
|
pub use waker::RcWake;
|
||||||
@@ -36,26 +31,34 @@ pub enum SchedulerMsg {
|
|||||||
SuspenseNotified(SuspenseId),
|
SuspenseNotified(SuspenseId),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Scheduler {
|
use std::{cell::RefCell, rc::Rc};
|
||||||
rx: futures_channel::mpsc::UnboundedReceiver<SchedulerMsg>,
|
|
||||||
ready_suspense: Vec<ScopeId>,
|
#[derive(Clone)]
|
||||||
pub handle: SchedulerHandle,
|
pub(crate) struct Scheduler(Rc<HandleInner>);
|
||||||
|
|
||||||
|
impl std::ops::Deref for Scheduler {
|
||||||
|
type Target = HandleInner;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct HandleInner {
|
||||||
|
pub sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
|
||||||
|
|
||||||
|
/// Tasks created with cx.spawn
|
||||||
|
pub tasks: RefCell<Slab<Rc<LocalTask>>>,
|
||||||
|
|
||||||
|
/// Async components
|
||||||
|
pub leaves: RefCell<Slab<Rc<SuspenseLeaf>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scheduler {
|
impl Scheduler {
|
||||||
pub fn new() -> Self {
|
pub fn new(sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>) -> Self {
|
||||||
let (tx, rx) = futures_channel::mpsc::unbounded();
|
Self(Rc::new(HandleInner {
|
||||||
Self {
|
sender,
|
||||||
rx,
|
tasks: RefCell::new(Slab::new()),
|
||||||
handle: SchedulerHandle::new(tx),
|
leaves: RefCell::new(Slab::new()),
|
||||||
ready_suspense: Default::default(),
|
}))
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Waits for a future to complete that marks the virtualdom as dirty
|
|
||||||
///
|
|
||||||
/// Not all messages will mark a virtualdom as dirty, so this waits for a message that has side-effects that do
|
|
||||||
pub fn wait_for_work(&mut self) {
|
|
||||||
//
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,15 @@
|
|||||||
use std::{
|
use std::{
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
pin::Pin,
|
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{waker::RcWake, SchedulerMsg};
|
use super::{waker::RcWake, SchedulerMsg};
|
||||||
use crate::{
|
use crate::{
|
||||||
innerlude::{Mutation, Renderer},
|
innerlude::{Mutation, Mutations},
|
||||||
Element, ScopeId,
|
Element, ScopeId,
|
||||||
};
|
};
|
||||||
use futures_task::Waker;
|
|
||||||
use futures_util::Future;
|
use futures_util::Future;
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||||
@@ -22,7 +21,7 @@ pub type SuspenseContext = Rc<RefCell<SuspenseBoundary>>;
|
|||||||
pub struct SuspenseBoundary {
|
pub struct SuspenseBoundary {
|
||||||
pub id: ScopeId,
|
pub id: ScopeId,
|
||||||
pub waiting_on: HashSet<SuspenseId>,
|
pub waiting_on: HashSet<SuspenseId>,
|
||||||
pub mutations: Renderer<'static>,
|
pub mutations: Mutations<'static>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SuspenseBoundary {
|
impl SuspenseBoundary {
|
||||||
@@ -30,7 +29,7 @@ impl SuspenseBoundary {
|
|||||||
Rc::new(RefCell::new(Self {
|
Rc::new(RefCell::new(Self {
|
||||||
id,
|
id,
|
||||||
waiting_on: Default::default(),
|
waiting_on: Default::default(),
|
||||||
mutations: Renderer::new(0),
|
mutations: Mutations::new(0),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,10 +45,7 @@ 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() {
|
arc_self.notified.set(true);
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// 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));
|
||||||
|
|||||||
@@ -7,15 +7,16 @@ use std::{
|
|||||||
process::Output,
|
process::Output,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
task::Poll,
|
||||||
};
|
};
|
||||||
|
|
||||||
use futures_task::{waker, ArcWake, Context, RawWaker, RawWakerVTable, Waker};
|
|
||||||
use futures_util::{pin_mut, Future, FutureExt};
|
use futures_util::{pin_mut, Future, FutureExt};
|
||||||
use slab::Slab;
|
use slab::Slab;
|
||||||
|
use std::task::{Context, RawWaker, RawWakerVTable, Waker};
|
||||||
|
|
||||||
use crate::{Element, ScopeId};
|
use crate::{Element, ScopeId};
|
||||||
|
|
||||||
use super::{waker::RcWake, HandleInner, SchedulerHandle, SchedulerMsg};
|
use super::{waker::RcWake, HandleInner, Scheduler, SchedulerMsg};
|
||||||
|
|
||||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||||
@@ -24,12 +25,29 @@ pub struct TaskId(pub usize);
|
|||||||
/// the task itself is the waker
|
/// the task itself is the waker
|
||||||
|
|
||||||
pub struct LocalTask {
|
pub struct LocalTask {
|
||||||
id: TaskId,
|
pub id: TaskId,
|
||||||
scope: ScopeId,
|
pub scope: ScopeId,
|
||||||
tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
|
pub tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
|
||||||
|
|
||||||
|
// todo: use rc and weak, or the bump slab instead of unsafecell
|
||||||
pub task: UnsafeCell<Pin<Box<dyn Future<Output = ()> + 'static>>>,
|
pub task: UnsafeCell<Pin<Box<dyn Future<Output = ()> + 'static>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LocalTask {
|
||||||
|
pub fn progress(self: &Rc<Self>) -> bool {
|
||||||
|
let waker = self.waker();
|
||||||
|
let mut cx = Context::from_waker(&waker);
|
||||||
|
|
||||||
|
// safety: the waker owns its task and everythig is single threaded
|
||||||
|
let fut = unsafe { &mut *self.task.get() };
|
||||||
|
|
||||||
|
match fut.poll_unpin(&mut cx) {
|
||||||
|
Poll::Ready(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl HandleInner {
|
impl HandleInner {
|
||||||
pub fn spawn(&self, scope: ScopeId, task: impl Future<Output = ()> + 'static) -> TaskId {
|
pub fn spawn(&self, scope: ScopeId, task: impl Future<Output = ()> + 'static) -> TaskId {
|
||||||
let mut tasks = self.tasks.borrow_mut();
|
let mut tasks = self.tasks.borrow_mut();
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
use futures_task::Context;
|
|
||||||
use futures_util::{FutureExt, StreamExt};
|
use futures_util::{FutureExt, StreamExt};
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
diff::DirtyScope,
|
||||||
factory::RenderReturn,
|
factory::RenderReturn,
|
||||||
innerlude::{Mutation, Renderer, SuspenseContext},
|
innerlude::{Mutation, Mutations, SuspenseContext},
|
||||||
VNode, VirtualDom,
|
VNode, VirtualDom,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -14,25 +15,29 @@ impl VirtualDom {
|
|||||||
///
|
///
|
||||||
/// This is cancel safe, so if the future is dropped, you can push events into the virtualdom
|
/// This is cancel safe, so if the future is dropped, you can push events into the virtualdom
|
||||||
pub async fn wait_for_work(&mut self) {
|
pub async fn wait_for_work(&mut self) {
|
||||||
|
// todo: make sure the scheduler queue is completely drained
|
||||||
loop {
|
loop {
|
||||||
match self.scheduler.rx.next().await.unwrap() {
|
match self.rx.next().await.unwrap() {
|
||||||
SchedulerMsg::Event => todo!(),
|
SchedulerMsg::Event => break,
|
||||||
SchedulerMsg::Immediate(_) => todo!(),
|
|
||||||
|
SchedulerMsg::Immediate(id) => {
|
||||||
|
let height = self.scopes[id.0].height;
|
||||||
|
self.dirty_scopes.insert(DirtyScope { height, id });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
SchedulerMsg::DirtyAll => todo!(),
|
SchedulerMsg::DirtyAll => todo!(),
|
||||||
|
|
||||||
SchedulerMsg::TaskNotified(id) => {
|
SchedulerMsg::TaskNotified(id) => {
|
||||||
let mut tasks = self.scheduler.handle.tasks.borrow_mut();
|
let mut tasks = self.scheduler.tasks.borrow_mut();
|
||||||
let local_task = &tasks[id.0];
|
let task = &tasks[id.0];
|
||||||
|
|
||||||
// attach the waker to itself
|
// If the task completes...
|
||||||
// todo: don't make a new waker every time, make it once and then just clone it
|
if task.progress() {
|
||||||
let waker = local_task.waker();
|
// Remove it from the scope so we dont try to double drop it when the scope dropes
|
||||||
let mut cx = Context::from_waker(&waker);
|
self.scopes[task.scope.0].spawned_tasks.remove(&id);
|
||||||
|
|
||||||
// safety: the waker owns its task and everythig is single threaded
|
// Remove it from the scheduler
|
||||||
let fut = unsafe { &mut *local_task.task.get() };
|
|
||||||
|
|
||||||
if let futures_task::Poll::Ready(_) = fut.poll_unpin(&mut cx) {
|
|
||||||
tasks.remove(id.0);
|
tasks.remove(id.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,7 +47,6 @@ impl VirtualDom {
|
|||||||
|
|
||||||
let leaf = self
|
let leaf = self
|
||||||
.scheduler
|
.scheduler
|
||||||
.handle
|
|
||||||
.leaves
|
.leaves
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.get(id.0)
|
.get(id.0)
|
||||||
@@ -55,16 +59,14 @@ impl VirtualDom {
|
|||||||
let waker = leaf.waker();
|
let waker = leaf.waker();
|
||||||
let mut cx = Context::from_waker(&waker);
|
let mut cx = Context::from_waker(&waker);
|
||||||
|
|
||||||
let fut = unsafe { &mut *leaf.task };
|
// Safety: the future is always pinned to the bump arena
|
||||||
|
let mut pinned = unsafe { std::pin::Pin::new_unchecked(&mut *leaf.task) };
|
||||||
let mut pinned = unsafe { std::pin::Pin::new_unchecked(fut) };
|
|
||||||
let as_pinned_mut = &mut pinned;
|
let as_pinned_mut = &mut pinned;
|
||||||
|
|
||||||
// the component finished rendering and gave us nodes
|
// the component finished rendering and gave us nodes
|
||||||
// we should attach them to that component and then render its children
|
// we should attach them to that component and then render its children
|
||||||
// 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 Poll::Ready(new_nodes) = as_pinned_mut.poll_unpin(&mut cx) {
|
||||||
{
|
|
||||||
let boundary = &self.scopes[leaf.scope_id.0]
|
let boundary = &self.scopes[leaf.scope_id.0]
|
||||||
.consume_context::<SuspenseContext>()
|
.consume_context::<SuspenseContext>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -87,7 +89,7 @@ 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 Renderer =
|
let mutations: &mut Mutations =
|
||||||
unsafe { std::mem::transmute(mutations) };
|
unsafe { std::mem::transmute(mutations) };
|
||||||
|
|
||||||
self.scope_stack.push(scope_id);
|
self.scope_stack.push(scope_id);
|
||||||
@@ -103,8 +105,6 @@ impl VirtualDom {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// now proces any events. If we end up running a component and it generates mutations, then we should run those mutations
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use futures_task::{RawWaker, RawWakerVTable, Waker};
|
use std::task::{RawWaker, RawWakerVTable, Waker};
|
||||||
use std::{mem, rc::Rc};
|
use std::{mem, rc::Rc};
|
||||||
|
|
||||||
pub trait RcWake: Sized {
|
pub trait RcWake: Sized {
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
use crate::{innerlude::SuspenseContext, scheduler::RcWake};
|
use crate::{
|
||||||
use futures_util::{pin_mut, task::noop_waker_ref};
|
any_props::AnyProps,
|
||||||
|
arena::ElementId,
|
||||||
|
bump_frame::BumpFrame,
|
||||||
|
factory::RenderReturn,
|
||||||
|
innerlude::{SuspenseId, SuspenseLeaf},
|
||||||
|
scheduler::RcWake,
|
||||||
|
scopes::{ScopeId, ScopeState},
|
||||||
|
virtual_dom::VirtualDom,
|
||||||
|
};
|
||||||
|
use futures_util::FutureExt;
|
||||||
use std::{
|
use std::{
|
||||||
mem,
|
mem,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
@@ -7,18 +16,8 @@ use std::{
|
|||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
|
||||||
any_props::AnyProps,
|
|
||||||
arena::ElementId,
|
|
||||||
bump_frame::BumpFrame,
|
|
||||||
factory::RenderReturn,
|
|
||||||
innerlude::{SuspenseId, SuspenseLeaf},
|
|
||||||
scopes::{ScopeId, ScopeState},
|
|
||||||
virtual_dom::VirtualDom,
|
|
||||||
};
|
|
||||||
|
|
||||||
impl VirtualDom {
|
impl VirtualDom {
|
||||||
pub fn new_scope(&mut self, props: *mut dyn AnyProps<'static>) -> ScopeId {
|
pub fn new_scope(&mut self, props: *mut dyn AnyProps<'static>) -> &mut ScopeState {
|
||||||
let parent = self.acquire_current_scope_raw();
|
let parent = self.acquire_current_scope_raw();
|
||||||
let container = self.acquire_current_container();
|
let container = self.acquire_current_container();
|
||||||
let entry = self.scopes.vacant_entry();
|
let entry = self.scopes.vacant_entry();
|
||||||
@@ -34,18 +33,17 @@ impl VirtualDom {
|
|||||||
placeholder: None.into(),
|
placeholder: None.into(),
|
||||||
node_arena_1: BumpFrame::new(50),
|
node_arena_1: BumpFrame::new(50),
|
||||||
node_arena_2: BumpFrame::new(50),
|
node_arena_2: BumpFrame::new(50),
|
||||||
|
spawned_tasks: Default::default(),
|
||||||
render_cnt: Default::default(),
|
render_cnt: Default::default(),
|
||||||
hook_arena: Default::default(),
|
hook_arena: Default::default(),
|
||||||
hook_vals: Default::default(),
|
hook_vals: Default::default(),
|
||||||
hook_idx: Default::default(),
|
hook_idx: Default::default(),
|
||||||
shared_contexts: Default::default(),
|
shared_contexts: Default::default(),
|
||||||
tasks: self.scheduler.handle.clone(),
|
tasks: self.scheduler.clone(),
|
||||||
});
|
})
|
||||||
|
|
||||||
id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn acquire_current_container(&self) -> ElementId {
|
fn acquire_current_container(&self) -> ElementId {
|
||||||
self.element_stack
|
self.element_stack
|
||||||
.last()
|
.last()
|
||||||
.copied()
|
.copied()
|
||||||
@@ -59,39 +57,45 @@ impl VirtualDom {
|
|||||||
.and_then(|id| self.scopes.get_mut(id.0).map(|f| f as *mut ScopeState))
|
.and_then(|id| self.scopes.get_mut(id.0).map(|f| f as *mut ScopeState))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_scope(&mut self, scope_id: ScopeId) -> &mut RenderReturn {
|
pub(crate) unsafe fn run_scope_extend<'a>(
|
||||||
|
&mut self,
|
||||||
|
scope_id: ScopeId,
|
||||||
|
) -> &'a RenderReturn<'a> {
|
||||||
|
unsafe { self.run_scope(scope_id).extend_lifetime_ref() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn {
|
||||||
let mut new_nodes = unsafe {
|
let mut new_nodes = unsafe {
|
||||||
let scope = &mut self.scopes[scope_id.0];
|
let scope = &mut self.scopes[scope_id.0];
|
||||||
scope.hook_idx.set(0);
|
scope.hook_idx.set(0);
|
||||||
|
|
||||||
|
// safety: due to how we traverse the tree, we know that the scope is not currently aliased
|
||||||
let props: &mut dyn AnyProps = mem::transmute(&mut *scope.props);
|
let props: &mut dyn AnyProps = mem::transmute(&mut *scope.props);
|
||||||
let res: RenderReturn = props.render(scope);
|
props.render(scope).extend_lifetime()
|
||||||
let res: RenderReturn<'static> = mem::transmute(res);
|
|
||||||
res
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// immediately resolve futures that can be resolved
|
// immediately resolve futures that can be resolved
|
||||||
if let RenderReturn::Async(task) = &mut new_nodes {
|
if let RenderReturn::Async(task) = &mut new_nodes {
|
||||||
use futures_util::FutureExt;
|
let mut leaves = self.scheduler.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 suspense_id = SuspenseId(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: suspense_id,
|
id: suspense_id,
|
||||||
tx: self.scheduler.handle.sender.clone(),
|
tx: self.scheduler.sender.clone(),
|
||||||
notified: false.into(),
|
notified: Default::default(),
|
||||||
});
|
});
|
||||||
|
|
||||||
let _leaf = leaf.clone();
|
|
||||||
let waker = leaf.waker();
|
let waker = leaf.waker();
|
||||||
let mut cx = Context::from_waker(&waker);
|
let mut cx = Context::from_waker(&waker);
|
||||||
|
|
||||||
|
// safety: the task is already pinned in the bump arena
|
||||||
let mut pinned = unsafe { Pin::new_unchecked(task.as_mut()) };
|
let mut pinned = unsafe { Pin::new_unchecked(task.as_mut()) };
|
||||||
|
|
||||||
|
// Keep polling until either we get a value or the future is not ready
|
||||||
loop {
|
loop {
|
||||||
match pinned.poll_unpin(&mut cx) {
|
match pinned.poll_unpin(&mut cx) {
|
||||||
// If nodes are produced, then set it and we can break
|
// If nodes are produced, then set it and we can break
|
||||||
@@ -103,8 +107,8 @@ impl VirtualDom {
|
|||||||
// If no nodes are produced but the future woke up immediately, then try polling it again
|
// If no nodes are produced but the future woke up immediately, then try polling it again
|
||||||
// This circumvents things like yield_now, but is important is important when rendering
|
// This circumvents things like yield_now, but is important is important when rendering
|
||||||
// components that are just a stream of immediately ready futures
|
// components that are just a stream of immediately ready futures
|
||||||
_ if _leaf.notified.get() => {
|
_ if leaf.notified.get() => {
|
||||||
_leaf.notified.set(false);
|
leaf.notified.set(false);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +116,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);
|
self.collected_leaves.push(suspense_id);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+17
-19
@@ -1,7 +1,7 @@
|
|||||||
use std::{
|
use std::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
collections::HashMap,
|
collections::{HashMap, HashSet},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ use crate::{
|
|||||||
any_props::AnyProps,
|
any_props::AnyProps,
|
||||||
arena::ElementId,
|
arena::ElementId,
|
||||||
bump_frame::BumpFrame,
|
bump_frame::BumpFrame,
|
||||||
innerlude::{SchedulerHandle, SchedulerMsg},
|
innerlude::{Scheduler, SchedulerMsg},
|
||||||
lazynodes::LazyNodes,
|
lazynodes::LazyNodes,
|
||||||
nodes::VNode,
|
nodes::VNode,
|
||||||
TaskId,
|
TaskId,
|
||||||
@@ -43,27 +43,28 @@ impl<'a, T> std::ops::Deref for Scoped<'a, T> {
|
|||||||
pub struct ScopeId(pub usize);
|
pub struct ScopeId(pub usize);
|
||||||
|
|
||||||
pub struct ScopeState {
|
pub struct ScopeState {
|
||||||
pub render_cnt: usize,
|
pub(crate) render_cnt: usize,
|
||||||
|
|
||||||
pub node_arena_1: BumpFrame,
|
pub(crate) node_arena_1: BumpFrame,
|
||||||
pub node_arena_2: BumpFrame,
|
pub(crate) node_arena_2: BumpFrame,
|
||||||
|
|
||||||
pub parent: Option<*mut ScopeState>,
|
pub(crate) parent: Option<*mut ScopeState>,
|
||||||
pub container: ElementId,
|
pub(crate) container: ElementId,
|
||||||
pub id: ScopeId,
|
pub(crate) id: ScopeId,
|
||||||
|
|
||||||
pub height: u32,
|
pub(crate) height: u32,
|
||||||
|
|
||||||
pub hook_arena: Bump,
|
pub(crate) hook_arena: Bump,
|
||||||
pub hook_vals: RefCell<Vec<*mut dyn Any>>,
|
pub(crate) hook_vals: RefCell<Vec<*mut dyn Any>>,
|
||||||
pub hook_idx: Cell<usize>,
|
pub(crate) hook_idx: Cell<usize>,
|
||||||
|
|
||||||
pub(crate) shared_contexts: RefCell<HashMap<TypeId, Box<dyn Any>>>,
|
pub(crate) shared_contexts: RefCell<HashMap<TypeId, Box<dyn Any>>>,
|
||||||
|
|
||||||
pub tasks: SchedulerHandle,
|
pub(crate) tasks: Scheduler,
|
||||||
|
pub(crate) spawned_tasks: HashSet<TaskId>,
|
||||||
|
|
||||||
pub props: *mut dyn AnyProps<'static>,
|
pub(crate) props: *mut dyn AnyProps<'static>,
|
||||||
pub placeholder: Cell<Option<ElementId>>,
|
pub(crate) placeholder: Cell<Option<ElementId>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScopeState {
|
impl ScopeState {
|
||||||
@@ -243,14 +244,11 @@ impl ScopeState {
|
|||||||
let parent = unsafe { &*parent };
|
let parent = unsafe { &*parent };
|
||||||
|
|
||||||
if parent.scope_id() == ScopeId(0) {
|
if parent.scope_id() == ScopeId(0) {
|
||||||
let exists = parent
|
let _ = parent
|
||||||
.shared_contexts
|
.shared_contexts
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.insert(TypeId::of::<T>(), Box::new(value.clone()));
|
.insert(TypeId::of::<T>(), Box::new(value.clone()));
|
||||||
|
|
||||||
if exists.is_some() {
|
|
||||||
log::warn!("Context already provided to parent scope - replacing it");
|
|
||||||
}
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
use crate::any_props::VComponentProps;
|
use crate::any_props::VComponentProps;
|
||||||
use crate::arena::ElementPath;
|
use crate::arena::ElementPath;
|
||||||
use crate::component::Component;
|
|
||||||
use crate::diff::DirtyScope;
|
use crate::diff::DirtyScope;
|
||||||
use crate::factory::RenderReturn;
|
use crate::factory::RenderReturn;
|
||||||
use crate::innerlude::{Renderer, Scheduler, SchedulerMsg};
|
use crate::innerlude::{Mutations, Scheduler, SchedulerMsg};
|
||||||
use crate::mutations::Mutation;
|
use crate::mutations::Mutation;
|
||||||
use crate::nodes::{Template, TemplateId};
|
use crate::nodes::{Template, TemplateId};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
arena::ElementId,
|
arena::ElementId,
|
||||||
scopes::{ScopeId, ScopeState},
|
scopes::{ScopeId, ScopeState},
|
||||||
@@ -14,10 +12,101 @@ 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 futures_util::Future;
|
use futures_util::Future;
|
||||||
use scheduler::{SuspenseBoundary, SuspenseContext, SuspenseId};
|
use scheduler::{SuspenseBoundary, SuspenseId};
|
||||||
use slab::Slab;
|
use slab::Slab;
|
||||||
use std::collections::{BTreeSet, HashMap};
|
use std::collections::{BTreeSet, HashMap};
|
||||||
|
|
||||||
|
/// A virtual node system that progresses user events and diffs UI trees.
|
||||||
|
///
|
||||||
|
/// ## Guide
|
||||||
|
///
|
||||||
|
/// Components are defined as simple functions that take [`Scope`] and return an [`Element`].
|
||||||
|
///
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// #[derive(Props, PartialEq)]
|
||||||
|
/// struct AppProps {
|
||||||
|
/// title: String
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn App(cx: Scope<AppProps>) -> Element {
|
||||||
|
/// cx.render(rsx!(
|
||||||
|
/// div {"hello, {cx.props.title}"}
|
||||||
|
/// ))
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Components may be composed to make complex apps.
|
||||||
|
///
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// fn App(cx: Scope<AppProps>) -> Element {
|
||||||
|
/// cx.render(rsx!(
|
||||||
|
/// NavBar { routes: ROUTES }
|
||||||
|
/// Title { "{cx.props.title}" }
|
||||||
|
/// Footer {}
|
||||||
|
/// ))
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// To start an app, create a [`VirtualDom`] and call [`VirtualDom::rebuild`] to get the list of edits required to
|
||||||
|
/// draw the UI.
|
||||||
|
///
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// let mut vdom = VirtualDom::new(App);
|
||||||
|
/// let edits = vdom.rebuild();
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// To inject UserEvents into the VirtualDom, call [`VirtualDom::get_scheduler_channel`] to get access to the scheduler.
|
||||||
|
///
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// let channel = vdom.get_scheduler_channel();
|
||||||
|
/// channel.send_unbounded(SchedulerMsg::UserEvent(UserEvent {
|
||||||
|
/// // ...
|
||||||
|
/// }))
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// While waiting for UserEvents to occur, call [`VirtualDom::wait_for_work`] to poll any futures inside the VirtualDom.
|
||||||
|
///
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// vdom.wait_for_work().await;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Once work is ready, call [`VirtualDom::work_with_deadline`] to compute the differences between the previous and
|
||||||
|
/// current UI trees. This will return a [`Mutations`] object that contains Edits, Effects, and NodeRefs that need to be
|
||||||
|
/// handled by the renderer.
|
||||||
|
///
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// let mutations = vdom.work_with_deadline(|| false);
|
||||||
|
/// for edit in mutations {
|
||||||
|
/// apply(edit);
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Building an event loop around Dioxus:
|
||||||
|
///
|
||||||
|
/// Putting everything together, you can build an event loop around Dioxus by using the methods outlined above.
|
||||||
|
///
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// fn App(cx: Scope) -> Element {
|
||||||
|
/// cx.render(rsx!{
|
||||||
|
/// div { "Hello World" }
|
||||||
|
/// })
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// async fn main() {
|
||||||
|
/// let mut dom = VirtualDom::new(App);
|
||||||
|
///
|
||||||
|
/// let mut inital_edits = dom.rebuild();
|
||||||
|
/// apply_edits(inital_edits);
|
||||||
|
///
|
||||||
|
/// loop {
|
||||||
|
/// dom.wait_for_work().await;
|
||||||
|
/// let frame_timeout = TimeoutFuture::new(Duration::from_millis(16));
|
||||||
|
/// let deadline = || (&mut frame_timeout).now_or_never();
|
||||||
|
/// let edits = dom.run_with_deadline(deadline).await;
|
||||||
|
/// apply_edits(edits);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
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>,
|
||||||
@@ -28,59 +117,153 @@ pub struct VirtualDom {
|
|||||||
|
|
||||||
// While diffing we need some sort of way of breaking off a stream of suspended mutations.
|
// While diffing we need some sort of way of breaking off a stream of suspended mutations.
|
||||||
pub(crate) scope_stack: Vec<ScopeId>,
|
pub(crate) scope_stack: Vec<ScopeId>,
|
||||||
pub(crate) waiting_on: Vec<SuspenseId>,
|
pub(crate) collected_leaves: Vec<SuspenseId>,
|
||||||
|
|
||||||
|
// Whenever a suspense tree is finished, we push its boundary onto this stack.
|
||||||
|
// When "render_with_deadline" is called, we pop the stack and return the mutations
|
||||||
|
pub(crate) finished_fibers: Vec<ScopeId>,
|
||||||
|
|
||||||
|
pub(crate) rx: futures_channel::mpsc::UnboundedReceiver<SchedulerMsg>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VirtualDom {
|
impl VirtualDom {
|
||||||
|
/// Create a new VirtualDom with a component that does not have special props.
|
||||||
|
///
|
||||||
|
/// # Description
|
||||||
|
///
|
||||||
|
/// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
|
||||||
|
///
|
||||||
|
/// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
|
||||||
|
/// to toss out the entire tree.
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// fn Example(cx: Scope) -> Element {
|
||||||
|
/// cx.render(rsx!( div { "hello world" } ))
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// let dom = VirtualDom::new(Example);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Note: the VirtualDom is not progressed, you must either "run_with_deadline" or use "rebuild" to progress it.
|
||||||
pub fn new(app: fn(Scope) -> Element) -> Self {
|
pub fn new(app: fn(Scope) -> Element) -> Self {
|
||||||
let scheduler = Scheduler::new();
|
Self::new_with_props(app, ())
|
||||||
|
}
|
||||||
|
|
||||||
let mut res = Self {
|
/// Create a new VirtualDom with the given properties for the root component.
|
||||||
|
///
|
||||||
|
/// # Description
|
||||||
|
///
|
||||||
|
/// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
|
||||||
|
///
|
||||||
|
/// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
|
||||||
|
/// to toss out the entire tree.
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// #[derive(PartialEq, Props)]
|
||||||
|
/// struct SomeProps {
|
||||||
|
/// name: &'static str
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn Example(cx: Scope<SomeProps>) -> Element {
|
||||||
|
/// cx.render(rsx!{ div{ "hello {cx.props.name}" } })
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// let dom = VirtualDom::new(Example);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Note: the VirtualDom is not progressed on creation. You must either "run_with_deadline" or use "rebuild" to progress it.
|
||||||
|
///
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
|
||||||
|
/// let mutations = dom.rebuild();
|
||||||
|
/// ```
|
||||||
|
pub fn new_with_props<P>(root: fn(Scope<P>) -> Element, root_props: P) -> Self
|
||||||
|
where
|
||||||
|
P: 'static,
|
||||||
|
{
|
||||||
|
let channel = futures_channel::mpsc::unbounded();
|
||||||
|
Self::new_with_props_and_scheduler(root, root_props, channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Launch the VirtualDom, but provide your own channel for receiving and sending messages into the scheduler
|
||||||
|
///
|
||||||
|
/// This is useful when the VirtualDom must be driven from outside a thread and it doesn't make sense to wait for the
|
||||||
|
/// VirtualDom to be created just to retrieve its channel receiver.
|
||||||
|
///
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// let channel = futures_channel::mpsc::unbounded();
|
||||||
|
/// let dom = VirtualDom::new_with_scheduler(Example, (), channel);
|
||||||
|
/// ```
|
||||||
|
pub fn new_with_props_and_scheduler<P: 'static>(
|
||||||
|
root: fn(Scope<P>) -> Element,
|
||||||
|
root_props: P,
|
||||||
|
(tx, rx): (
|
||||||
|
UnboundedSender<SchedulerMsg>,
|
||||||
|
UnboundedReceiver<SchedulerMsg>,
|
||||||
|
),
|
||||||
|
) -> Self {
|
||||||
|
let mut dom = Self {
|
||||||
|
rx,
|
||||||
|
scheduler: Scheduler::new(tx),
|
||||||
templates: Default::default(),
|
templates: Default::default(),
|
||||||
scopes: Slab::default(),
|
scopes: Slab::default(),
|
||||||
elements: Default::default(),
|
elements: Default::default(),
|
||||||
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(),
|
collected_leaves: Vec::new(),
|
||||||
scheduler,
|
finished_fibers: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let props = Box::into_raw(Box::new(VComponentProps::new_empty(app)));
|
dom.new_scope(Box::into_raw(Box::new(VComponentProps::new(
|
||||||
let props: *mut VComponentProps<(), ()> = unsafe { std::mem::transmute(props) };
|
root,
|
||||||
|
|_, _| unreachable!(),
|
||||||
|
root_props,
|
||||||
|
))))
|
||||||
|
// The root component is always a suspense boundary for any async children
|
||||||
|
// This could be unexpected, so we might rethink this behavior
|
||||||
|
.provide_context(SuspenseBoundary::new(ScopeId(0)));
|
||||||
|
|
||||||
let root = res.new_scope(props);
|
dom
|
||||||
|
|
||||||
// the root component is always a suspense boundary for any async children
|
|
||||||
res.scopes[root.0].provide_context(SuspenseBoundary::new(root));
|
|
||||||
assert_eq!(root, ScopeId(0));
|
|
||||||
|
|
||||||
res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render the virtualdom, without processing any suspense.
|
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch.
|
||||||
///
|
///
|
||||||
/// This does register futures with wakers, but does not process any of them.
|
/// The diff machine expects the RealDom's stack to be the root of the application.
|
||||||
pub fn rebuild<'a>(&'a mut self) -> Renderer<'a> {
|
///
|
||||||
let mut mutations = Renderer::new(0);
|
/// Tasks will not be polled with this method, nor will any events be processed from the event queue. Instead, the
|
||||||
let root_node: &RenderReturn = self.run_scope(ScopeId(0));
|
/// root component will be ran once and then diffed. All updates will flow out as mutations.
|
||||||
let root_node: &RenderReturn = unsafe { std::mem::transmute(root_node) };
|
///
|
||||||
|
/// All state stored in components will be completely wiped away.
|
||||||
|
///
|
||||||
|
/// Any templates previously registered will remain.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// static App: Component = |cx| cx.render(rsx!{ "hello world" });
|
||||||
|
///
|
||||||
|
/// let mut dom = VirtualDom::new();
|
||||||
|
/// let edits = dom.rebuild();
|
||||||
|
///
|
||||||
|
/// apply_edits(edits);
|
||||||
|
/// ```
|
||||||
|
pub fn rebuild<'a>(&'a mut self) -> Mutations<'a> {
|
||||||
|
let mut mutations = Mutations::new(0);
|
||||||
|
|
||||||
let mut created = 0;
|
let root_node = unsafe { self.run_scope_extend(ScopeId(0)) };
|
||||||
match root_node {
|
match root_node {
|
||||||
RenderReturn::Sync(Some(node)) => {
|
RenderReturn::Sync(Some(node)) => {
|
||||||
self.scope_stack.push(ScopeId(0));
|
let m = self.create_scope(ScopeId(0), &mut mutations, node);
|
||||||
created = self.create(&mut mutations, node);
|
mutations.push(Mutation::AppendChildren { m });
|
||||||
self.scope_stack.pop();
|
|
||||||
}
|
|
||||||
RenderReturn::Sync(None) => {
|
|
||||||
//
|
|
||||||
}
|
}
|
||||||
|
RenderReturn::Sync(None) => {}
|
||||||
RenderReturn::Async(_) => unreachable!("Root scope cannot be an async component"),
|
RenderReturn::Async(_) => unreachable!("Root scope cannot be an async component"),
|
||||||
}
|
}
|
||||||
|
|
||||||
mutations.push(Mutation::AppendChildren { m: created });
|
|
||||||
|
|
||||||
mutations
|
mutations
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user