wip: pre any props
This commit is contained in:
1 parent
12808ec0aa
commit
493591400f
14 files changed
+399
-381
No files matched your search
@@ -13,10 +13,7 @@ pub trait AnyProps<'a> {
|
||||
unsafe fn memoize(&self, other: &dyn AnyProps) -> bool;
|
||||
}
|
||||
|
||||
pub(crate) struct VComponentProps<'a, P, A, F = Element<'a>>
|
||||
where
|
||||
F: ComponentReturn<'a, A>,
|
||||
{
|
||||
pub(crate) struct VComponentProps<'a, P, A, F: ComponentReturn<'a, A> = Element<'a>> {
|
||||
pub render_fn: fn(Scope<'a, P>) -> F,
|
||||
pub memo: unsafe fn(&P, &P) -> bool,
|
||||
pub props: P,
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
use std::cell::Cell;
|
||||
|
||||
use bumpalo::Bump;
|
||||
|
||||
use crate::factory::RenderReturn;
|
||||
use bumpalo::Bump;
|
||||
|
||||
pub struct BumpFrame {
|
||||
pub bump: Bump,
|
||||
pub node: Cell<*mut RenderReturn<'static>>,
|
||||
}
|
||||
|
||||
impl BumpFrame {
|
||||
pub fn new(capacity: usize) -> Self {
|
||||
let bump = Bump::with_capacity(capacity);
|
||||
@@ -22,6 +21,7 @@ impl BumpFrame {
|
||||
self.node.set(std::ptr::null_mut());
|
||||
}
|
||||
|
||||
/// Creates a new lifetime out of thin air
|
||||
pub unsafe fn load_node<'b>(&self) -> &'b RenderReturn<'b> {
|
||||
unsafe { std::mem::transmute(&*self.node.get()) }
|
||||
}
|
||||
|
||||
+15
-18
@@ -1,5 +1,5 @@
|
||||
use crate::factory::RenderReturn;
|
||||
use crate::innerlude::{Mutations, SuspenseContext};
|
||||
use crate::innerlude::{Mutations, SuspenseContext, VText};
|
||||
use crate::mutations::Mutation;
|
||||
use crate::mutations::Mutation::*;
|
||||
use crate::nodes::VNode;
|
||||
@@ -70,9 +70,11 @@ impl VirtualDom {
|
||||
&template.dynamic_nodes[*id],
|
||||
*id,
|
||||
),
|
||||
DynamicNode::Text {
|
||||
id: slot, value, ..
|
||||
} => {
|
||||
DynamicNode::Text(VText {
|
||||
id: slot,
|
||||
value,
|
||||
inner,
|
||||
}) => {
|
||||
let id = self.next_element(template, template.template.node_paths[*id]);
|
||||
slot.set(id);
|
||||
mutations.push(CreateTextNode { value, id });
|
||||
@@ -237,7 +239,7 @@ impl VirtualDom {
|
||||
idx: usize,
|
||||
) -> usize {
|
||||
match &node {
|
||||
DynamicNode::Text { id, value, .. } => {
|
||||
DynamicNode::Text(VText { id, value, inner }) => {
|
||||
let new_id = self.next_element(template, template.template.node_paths[idx]);
|
||||
id.set(new_id);
|
||||
mutations.push(HydrateText {
|
||||
@@ -248,17 +250,12 @@ impl VirtualDom {
|
||||
0
|
||||
}
|
||||
|
||||
DynamicNode::Component {
|
||||
props,
|
||||
placeholder,
|
||||
scope: scope_slot,
|
||||
..
|
||||
} => {
|
||||
DynamicNode::Component(component) => {
|
||||
let scope = self
|
||||
.new_scope(unsafe { std::mem::transmute(props.get()) })
|
||||
.new_scope(unsafe { std::mem::transmute(component.props.get()) })
|
||||
.id;
|
||||
|
||||
scope_slot.set(Some(scope));
|
||||
component.scope.set(Some(scope));
|
||||
|
||||
let return_nodes = unsafe { self.run_scope(scope).extend_lifetime_ref() };
|
||||
|
||||
@@ -269,7 +266,7 @@ impl VirtualDom {
|
||||
|
||||
RenderReturn::Async(_) => {
|
||||
let new_id = self.next_element(template, template.template.node_paths[idx]);
|
||||
placeholder.set(Some(new_id));
|
||||
component.placeholder.set(Some(new_id));
|
||||
self.scopes[scope.0].placeholder.set(Some(new_id));
|
||||
|
||||
mutations.push(AssignId {
|
||||
@@ -306,7 +303,7 @@ impl VirtualDom {
|
||||
// Since this is a boundary, use it as a placeholder
|
||||
let new_id =
|
||||
self.next_element(template, template.template.node_paths[idx]);
|
||||
placeholder.set(Some(new_id));
|
||||
component.placeholder.set(Some(new_id));
|
||||
self.scopes[scope.0].placeholder.set(Some(new_id));
|
||||
mutations.push(AssignId {
|
||||
id: new_id,
|
||||
@@ -342,9 +339,9 @@ impl VirtualDom {
|
||||
}
|
||||
}
|
||||
|
||||
DynamicNode::Fragment { nodes, .. } => {
|
||||
//
|
||||
nodes
|
||||
DynamicNode::Fragment(frag) => {
|
||||
// Todo: if there's no children create a placeholder instead ?
|
||||
frag.nodes
|
||||
.iter()
|
||||
.fold(0, |acc, child| acc + self.create(mutations, child))
|
||||
}
|
||||
|
||||
+188
-168
@@ -1,7 +1,7 @@
|
||||
use std::any::Any;
|
||||
|
||||
use crate::factory::RenderReturn;
|
||||
use crate::innerlude::Mutations;
|
||||
use crate::innerlude::{Mutations, VComponent, VFragment, VText};
|
||||
use crate::virtual_dom::VirtualDom;
|
||||
use crate::{Attribute, AttributeValue, TemplateNode};
|
||||
|
||||
@@ -42,16 +42,12 @@ impl<'b> VirtualDom {
|
||||
pub fn diff_scope(&mut self, mutations: &mut Mutations<'b>, scope: ScopeId) {
|
||||
let scope_state = &mut self.scopes[scope.0];
|
||||
|
||||
let cur_arena = scope_state.previous_frame();
|
||||
let prev_arena = scope_state.current_frame();
|
||||
// let cur_arena = scope_state.current_frame();
|
||||
// let prev_arena = scope_state.previous_frame();
|
||||
|
||||
// relax the borrow checker
|
||||
let cur_arena: &BumpFrame = unsafe { std::mem::transmute(cur_arena) };
|
||||
let prev_arena: &BumpFrame = unsafe { std::mem::transmute(prev_arena) };
|
||||
// Load the old and new bump arenas
|
||||
let cur_arena = scope_state.current_frame();
|
||||
let prev_arena = scope_state.previous_frame();
|
||||
|
||||
// Make sure the nodes arent null (they've been set properly)
|
||||
// This is a rough check to make sure we're not entering any UB
|
||||
assert_ne!(
|
||||
cur_arena.node.get(),
|
||||
std::ptr::null_mut(),
|
||||
@@ -64,11 +60,11 @@ impl<'b> VirtualDom {
|
||||
);
|
||||
|
||||
self.scope_stack.push(scope);
|
||||
|
||||
let left = unsafe { prev_arena.load_node() };
|
||||
let right = unsafe { cur_arena.load_node() };
|
||||
|
||||
self.diff_maybe_node(mutations, left, right);
|
||||
unsafe {
|
||||
let cur_arena = cur_arena.load_node();
|
||||
let prev_arena = prev_arena.load_node();
|
||||
self.diff_maybe_node(mutations, prev_arena, cur_arena);
|
||||
}
|
||||
self.scope_stack.pop();
|
||||
}
|
||||
|
||||
@@ -109,10 +105,9 @@ impl<'b> VirtualDom {
|
||||
left_template: &'b VNode<'b>,
|
||||
right_template: &'b VNode<'b>,
|
||||
) {
|
||||
println!("diffing {:?} and {:?}", left_template, right_template);
|
||||
|
||||
if left_template.template.id != right_template.template.id {
|
||||
// do a light diff of the roots nodes.
|
||||
todo!("lightly diff roots and replace");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -122,23 +117,21 @@ impl<'b> VirtualDom {
|
||||
.zip(right_template.dynamic_attrs.iter())
|
||||
.enumerate()
|
||||
{
|
||||
debug_assert!(left_attr.name == right_attr.name);
|
||||
debug_assert!(left_attr.value == right_attr.value);
|
||||
|
||||
// Move over the ID from the old to the new
|
||||
right_attr
|
||||
.mounted_element
|
||||
.set(left_attr.mounted_element.get());
|
||||
|
||||
if left_attr.value != right_attr.value {
|
||||
println!("DIFF ATTR: {:?} -> {:?}", left_attr, right_attr);
|
||||
let value = "todo!()";
|
||||
muts.push(Mutation::SetAttribute {
|
||||
id: left_attr.mounted_element.get(),
|
||||
name: left_attr.name,
|
||||
value,
|
||||
ns: right_attr.namespace,
|
||||
});
|
||||
// todo: add more types of attribute values
|
||||
if let AttributeValue::Text(text) = right_attr.value {
|
||||
muts.push(Mutation::SetAttribute {
|
||||
id: left_attr.mounted_element.get(),
|
||||
name: left_attr.name,
|
||||
value: text,
|
||||
ns: right_attr.namespace,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,102 +141,188 @@ impl<'b> VirtualDom {
|
||||
.zip(right_template.dynamic_nodes.iter())
|
||||
.enumerate()
|
||||
{
|
||||
#[rustfmt::skip]
|
||||
use DynamicNode::*;
|
||||
|
||||
match (left_node, right_node) {
|
||||
(DynamicNode::Component { props: lprops, .. }, DynamicNode::Component { static_props: is_static , props: rprops, .. }) => {
|
||||
let left_props = unsafe { &mut *lprops.get()};
|
||||
let right_props = unsafe { &mut *rprops.get()};
|
||||
(Component(left), Component(right)) => self.diff_vcomponent(muts, left, right),
|
||||
(Text(left), Text(right)) => self.diff_vtext(muts, left, right),
|
||||
(Fragment(left), Fragment(right)) => self.diff_vfragment(muts, left, right),
|
||||
|
||||
// Ensure these two props are of the same component type
|
||||
match left_props.as_ptr() == right_props.as_ptr() {
|
||||
true => {
|
||||
//
|
||||
(Placeholder(_), Placeholder(_)) => todo!(),
|
||||
|
||||
if *is_static {
|
||||
let props_are_same = unsafe { left_props.memoize(right_props) };
|
||||
|
||||
if props_are_same{
|
||||
//
|
||||
} else {
|
||||
//
|
||||
}
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
false => todo!(),
|
||||
}
|
||||
//
|
||||
},
|
||||
// Make sure to drop all the fragment children properly
|
||||
(DynamicNode::Fragment { .. }, right) => todo!(),
|
||||
|
||||
(Text(left), right) => {
|
||||
todo!()
|
||||
// let m = self.create_dynamic_node(muts, right_template, right, idx);
|
||||
// muts.push(Mutation::ReplaceWith { id: lid.get(), m });
|
||||
}
|
||||
(Placeholder(_), _) => todo!(),
|
||||
// Make sure to drop the component properly
|
||||
(DynamicNode::Component { .. }, right) => {
|
||||
(Component { .. }, right) => {
|
||||
// remove all the component roots except for the first
|
||||
// replace the first with the new node
|
||||
let m = self.create_dynamic_node(muts, right_template, right, idx);
|
||||
todo!()
|
||||
},
|
||||
|
||||
(DynamicNode::Text { id: lid, value: lvalue, .. }, DynamicNode::Text { id: rid, value: rvalue, .. }) => {
|
||||
rid.set(lid.get());
|
||||
if lvalue != rvalue {
|
||||
muts.push(Mutation::SetText {
|
||||
id: lid.get(),
|
||||
value: rvalue,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
(DynamicNode::Text { id: lid, .. }, right) => {
|
||||
let m = self.create_dynamic_node(muts, right_template, right, idx);
|
||||
muts.push(Mutation::ReplaceWith { id: lid.get(), m });
|
||||
}
|
||||
|
||||
(DynamicNode::Placeholder(_), DynamicNode::Placeholder(_)) => todo!(),
|
||||
(DynamicNode::Placeholder(_), _) => todo!(),
|
||||
|
||||
|
||||
(DynamicNode::Fragment { nodes: lnodes, ..}, DynamicNode::Fragment { nodes: rnodes, ..}) => {
|
||||
|
||||
|
||||
// match (old, new) {
|
||||
// ([], []) => rp.set(lp.get()),
|
||||
// ([], _) => {
|
||||
// //
|
||||
// todo!()
|
||||
// },
|
||||
// (_, []) => {
|
||||
// todo!()
|
||||
// },
|
||||
// _ => {
|
||||
// let new_is_keyed = new[0].key.is_some();
|
||||
// let old_is_keyed = old[0].key.is_some();
|
||||
|
||||
// debug_assert!(
|
||||
// new.iter().all(|n| n.key.is_some() == new_is_keyed),
|
||||
// "all siblings must be keyed or all siblings must be non-keyed"
|
||||
// );
|
||||
// debug_assert!(
|
||||
// old.iter().all(|o| o.key.is_some() == old_is_keyed),
|
||||
// "all siblings must be keyed or all siblings must be non-keyed"
|
||||
// );
|
||||
|
||||
// if new_is_keyed && old_is_keyed {
|
||||
// self.diff_keyed_children(muts, old, new);
|
||||
// } else {
|
||||
// self.diff_non_keyed_children(muts, old, new);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
},
|
||||
|
||||
// Make sure to drop all the fragment children properly
|
||||
(DynamicNode::Fragment { .. }, right) => todo!(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn diff_vcomponent(
|
||||
&mut self,
|
||||
muts: &mut Mutations<'b>,
|
||||
left: &'b VComponent<'b>,
|
||||
right: &'b VComponent<'b>,
|
||||
) {
|
||||
let left_props = unsafe { &mut *left.props.get() };
|
||||
let right_props = unsafe { &mut *right.props.get() };
|
||||
|
||||
// Ensure these two props are of the same component type
|
||||
match left_props.as_ptr() == right_props.as_ptr() {
|
||||
true => {
|
||||
//
|
||||
if left.static_props {
|
||||
let props_are_same = unsafe { left_props.memoize(right_props) };
|
||||
|
||||
if props_are_same {
|
||||
//
|
||||
} else {
|
||||
//
|
||||
}
|
||||
} else {
|
||||
}
|
||||
}
|
||||
false => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Lightly diff the two templates, checking only their roots.
|
||||
///
|
||||
/// The goal here is to preserve any existing component state that might exist. This is to preserve some React-like
|
||||
/// behavior where the component state is preserved when the component is re-rendered.
|
||||
///
|
||||
/// This is implemented by iterating each root, checking if the component is the same, if it is, then diff it.
|
||||
///
|
||||
/// We then pass the new template through "create" which should be smart enough to skip roots.
|
||||
///
|
||||
/// Currently, we only handle the case where the roots are the same component list. If there's any sort of deviation,
|
||||
/// IE more nodes, less nodes, different nodes, or expressions, then we just replace the whole thing.
|
||||
///
|
||||
/// This is mostly implemented to help solve the issue where the same component is rendered under two different
|
||||
/// conditions:
|
||||
///
|
||||
/// ```rust
|
||||
/// if enabled {
|
||||
/// rsx!{ Component { enabled_sign: "abc" } }
|
||||
/// } else {
|
||||
/// rsx!{ Component { enabled_sign: "xyz" } }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// However, we should not that it's explicit in the docs that this is not a guarantee. If you need to preserve state,
|
||||
/// then you should be passing in separate props instead.
|
||||
///
|
||||
/// ```
|
||||
///
|
||||
/// let props = if enabled {
|
||||
/// ComponentProps { enabled_sign: "abc" }
|
||||
/// } else {
|
||||
/// ComponentProps { enabled_sign: "xyz" }
|
||||
/// };
|
||||
///
|
||||
/// rsx! {
|
||||
/// Component { ..props }
|
||||
/// }
|
||||
/// ```
|
||||
fn light_diff_tempaltes(
|
||||
&mut self,
|
||||
muts: &mut Mutations<'b>,
|
||||
left: &'b VNode<'b>,
|
||||
right: &'b VNode<'b>,
|
||||
) -> bool {
|
||||
if left.template.roots.len() != right.template.roots.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut components = vec![];
|
||||
|
||||
// run through the components, ensuring they're the same
|
||||
for (l, r) in left.template.roots.iter().zip(right.template.roots.iter()) {
|
||||
match (l, r) {
|
||||
(TemplateNode::Dynamic(l), TemplateNode::Dynamic(r)) => {
|
||||
components.push(match (&left.dynamic_nodes[*l], &right.dynamic_nodes[*r]) {
|
||||
(DynamicNode::Component(l), DynamicNode::Component(r)) => {
|
||||
let l_props = unsafe { &*l.props.get() };
|
||||
let r_props = unsafe { &*r.props.get() };
|
||||
|
||||
(l, r)
|
||||
}
|
||||
_ => return false,
|
||||
})
|
||||
}
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
|
||||
// run through the components, diffing them
|
||||
for (l, r) in components {
|
||||
self.diff_vcomponent(muts, l, r);
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn diff_vtext(&mut self, muts: &mut Mutations<'b>, left: &'b VText<'b>, right: &'b VText<'b>) {
|
||||
right.id.set(left.id.get());
|
||||
if left.value != right.value {
|
||||
muts.push(Mutation::SetText {
|
||||
id: left.id.get(),
|
||||
value: right.value,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn diff_vfragment(
|
||||
&mut self,
|
||||
muts: &mut Mutations<'b>,
|
||||
left: &'b VFragment<'b>,
|
||||
right: &'b VFragment<'b>,
|
||||
) {
|
||||
// match (left.nodes, right.nodes) {
|
||||
// ([], []) => rp.set(lp.get()),
|
||||
// ([], _) => {
|
||||
// //
|
||||
// todo!()
|
||||
// }
|
||||
// (_, []) => {
|
||||
// todo!()
|
||||
// }
|
||||
// _ => {
|
||||
// let new_is_keyed = new[0].key.is_some();
|
||||
// let old_is_keyed = old[0].key.is_some();
|
||||
|
||||
// debug_assert!(
|
||||
// new.iter().all(|n| n.key.is_some() == new_is_keyed),
|
||||
// "all siblings must be keyed or all siblings must be non-keyed"
|
||||
// );
|
||||
// debug_assert!(
|
||||
// old.iter().all(|o| o.key.is_some() == old_is_keyed),
|
||||
// "all siblings must be keyed or all siblings must be non-keyed"
|
||||
// );
|
||||
|
||||
// if new_is_keyed && old_is_keyed {
|
||||
// self.diff_keyed_children(muts, old, new);
|
||||
// } else {
|
||||
// self.diff_non_keyed_children(muts, old, new);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
todo!()
|
||||
}
|
||||
|
||||
// Diff children that are not keyed.
|
||||
//
|
||||
// The parent must be on the top of the change list stack when entering this
|
||||
@@ -614,62 +693,3 @@ impl<'b> VirtualDom {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
// /// Lightly diff the two templates and apply their edits to the dom
|
||||
// fn light_diff_template_roots(
|
||||
// &'a mut self,
|
||||
// mutations: &mut Vec<Mutation<'a>>,
|
||||
// left: &VNode,
|
||||
// right: &VNode,
|
||||
// ) {
|
||||
// match right.template.roots.len().cmp(&left.template.roots.len()) {
|
||||
// std::cmp::Ordering::Less => {
|
||||
// // remove the old nodes at the end
|
||||
// }
|
||||
// std::cmp::Ordering::Greater => {
|
||||
// // add the extra nodes.
|
||||
// }
|
||||
// std::cmp::Ordering::Equal => {}
|
||||
// }
|
||||
|
||||
// for (left_node, right_node) in left.template.roots.iter().zip(right.template.roots.iter()) {
|
||||
// if let (TemplateNode::Dynamic(lidx), TemplateNode::Dynamic(ridx)) =
|
||||
// (left_node, right_node)
|
||||
// {
|
||||
// let left_node = &left.dynamic_nodes[*lidx];
|
||||
// let right_node = &right.dynamic_nodes[*ridx];
|
||||
|
||||
// // match (left_node, right_node) {
|
||||
// // (
|
||||
// // DynamicNode::Component {
|
||||
// // name,
|
||||
// // can_memoize,
|
||||
// // props,
|
||||
// // },
|
||||
// // DynamicNode::Component {
|
||||
// // name,
|
||||
// // can_memoize,
|
||||
// // props,
|
||||
// // },
|
||||
// // ) => todo!(),
|
||||
// // (
|
||||
// // DynamicNode::Component {
|
||||
// // name,
|
||||
// // can_memoize,
|
||||
// // props,
|
||||
// // },
|
||||
// // DynamicNode::Fragment { children },
|
||||
// // ) => todo!(),
|
||||
// // (
|
||||
// // DynamicNode::Fragment { children },
|
||||
// // DynamicNode::Component {
|
||||
// // name,
|
||||
// // can_memoize,
|
||||
// // props,
|
||||
// // },
|
||||
// // ) => todo!(),
|
||||
// // _ => {}
|
||||
// // }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
@@ -10,7 +10,7 @@ use std::future::Future;
|
||||
use crate::{
|
||||
any_props::{AnyProps, VComponentProps},
|
||||
arena::ElementId,
|
||||
innerlude::{DynamicNode, EventHandler},
|
||||
innerlude::{DynamicNode, EventHandler, VComponent, VFragment, VText},
|
||||
Attribute, AttributeValue, Element, LazyNodes, Properties, Scope, ScopeState, VNode,
|
||||
};
|
||||
|
||||
@@ -18,11 +18,11 @@ impl ScopeState {
|
||||
/// Create some text that's allocated along with the other vnodes
|
||||
pub fn text<'a>(&'a self, args: Arguments) -> DynamicNode<'a> {
|
||||
let (text, _) = self.raw_text(args);
|
||||
DynamicNode::Text {
|
||||
DynamicNode::Text(VText {
|
||||
id: Cell::new(ElementId(0)),
|
||||
value: text,
|
||||
inner: false,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn raw_text_inline<'a>(&'a self, args: Arguments) -> &'a str {
|
||||
@@ -87,13 +87,13 @@ impl ScopeState {
|
||||
// self.scope.items.borrow_mut().borrowed_props.push(vcomp);
|
||||
// }
|
||||
|
||||
DynamicNode::Component {
|
||||
DynamicNode::Component(VComponent {
|
||||
name: fn_name,
|
||||
static_props: P::IS_STATIC,
|
||||
props: Cell::new(detached_dyn),
|
||||
placeholder: Cell::new(None),
|
||||
scope: Cell::new(None),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new [`EventHandler`] from an [`FnMut`]
|
||||
@@ -176,10 +176,10 @@ impl<'a, 'b, T: IntoDynNode<'a>> IntoDynNode<'a> for &Option<T> {
|
||||
|
||||
impl<'a, 'b> IntoDynNode<'a> for LazyNodes<'a, 'b> {
|
||||
fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
|
||||
DynamicNode::Fragment {
|
||||
DynamicNode::Fragment(VFragment {
|
||||
nodes: cx.bump().alloc([self.call(cx)]),
|
||||
inner: false,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,10 +248,10 @@ where
|
||||
|
||||
match children.len() {
|
||||
0 => DynamicNode::Placeholder(Cell::new(ElementId(0))),
|
||||
_ => DynamicNode::Fragment {
|
||||
_ => DynamicNode::Fragment(VFragment {
|
||||
inner: false,
|
||||
nodes: children,
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
use crate::innerlude::*;
|
||||
|
||||
/// Create inline fragments using Component syntax.
|
||||
///
|
||||
/// ## Details
|
||||
///
|
||||
/// Fragments capture a series of children without rendering extra nodes.
|
||||
///
|
||||
/// Creating fragments explicitly with the Fragment component is particularly useful when rendering lists or tables and
|
||||
/// a key is needed to identify each item.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// rsx!{
|
||||
/// Fragment { key: "abc" }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Usage
|
||||
///
|
||||
/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
|
||||
/// Try to avoid highly nested fragments if you can. Unlike React, there is no protection against infinitely nested fragments.
|
||||
///
|
||||
/// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro.
|
||||
///
|
||||
/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
|
||||
#[allow(non_upper_case_globals, non_snake_case)]
|
||||
pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
|
||||
let children = cx.props.0.as_ref()?;
|
||||
Some(VNode {
|
||||
node_id: children.node_id.clone(),
|
||||
key: children.key.clone(),
|
||||
parent: children.parent.clone(),
|
||||
template: children.template,
|
||||
root_ids: children.root_ids,
|
||||
dynamic_nodes: children.dynamic_nodes,
|
||||
dynamic_attrs: children.dynamic_attrs,
|
||||
})
|
||||
}
|
||||
|
||||
pub struct FragmentProps<'a>(Element<'a>);
|
||||
pub struct FragmentBuilder<'a, const BUILT: bool>(Element<'a>);
|
||||
impl<'a> FragmentBuilder<'a, false> {
|
||||
pub fn children(self, children: Element<'a>) -> FragmentBuilder<'a, true> {
|
||||
FragmentBuilder(children)
|
||||
}
|
||||
}
|
||||
impl<'a, const A: bool> FragmentBuilder<'a, A> {
|
||||
pub fn build(self) -> FragmentProps<'a> {
|
||||
FragmentProps(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Access the children elements passed into the component
|
||||
///
|
||||
/// This enables patterns where a component is passed children from its parent.
|
||||
///
|
||||
/// ## Details
|
||||
///
|
||||
/// Unlike React, Dioxus allows *only* lists of children to be passed from parent to child - not arbitrary functions
|
||||
/// or classes. If you want to generate nodes instead of accepting them as a list, consider declaring a closure
|
||||
/// on the props that takes Context.
|
||||
///
|
||||
/// If a parent passes children into a component, the child will always re-render when the parent re-renders. In other
|
||||
/// words, a component cannot be automatically memoized if it borrows nodes from its parent, even if the component's
|
||||
/// props are valid for the static lifetime.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// fn App(cx: Scope) -> Element {
|
||||
/// cx.render(rsx!{
|
||||
/// CustomCard {
|
||||
/// h1 {}
|
||||
/// p {}
|
||||
/// }
|
||||
/// })
|
||||
/// }
|
||||
///
|
||||
/// #[derive(PartialEq, Props)]
|
||||
/// struct CardProps {
|
||||
/// children: Element
|
||||
/// }
|
||||
///
|
||||
/// fn CustomCard(cx: Scope<CardProps>) -> Element {
|
||||
/// cx.render(rsx!{
|
||||
/// div {
|
||||
/// h1 {"Title card"}
|
||||
/// {cx.props.children}
|
||||
/// }
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
impl<'a> Properties for FragmentProps<'a> {
|
||||
type Builder = FragmentBuilder<'a, false>;
|
||||
const IS_STATIC: bool = false;
|
||||
fn builder() -> Self::Builder {
|
||||
FragmentBuilder(None)
|
||||
}
|
||||
unsafe fn memoize(&self, _other: &Self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -17,18 +17,18 @@ impl<'b> VirtualDom {
|
||||
template: &'b VNode<'b>,
|
||||
gen_roots: bool,
|
||||
) {
|
||||
for node in template.dynamic_nodes.iter() {
|
||||
match node {
|
||||
DynamicNode::Text { id, .. } => {}
|
||||
// for node in template.dynamic_nodes.iter() {
|
||||
// match node {
|
||||
// DynamicNode::Text { id, .. } => {}
|
||||
|
||||
DynamicNode::Component { .. } => {
|
||||
todo!()
|
||||
}
|
||||
// DynamicNode::Component { .. } => {
|
||||
// todo!()
|
||||
// }
|
||||
|
||||
DynamicNode::Fragment { inner, nodes } => {}
|
||||
DynamicNode::Placeholder(_) => todo!(),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
// DynamicNode::Fragment { inner, nodes } => {}
|
||||
// DynamicNode::Placeholder(_) => todo!(),
|
||||
// _ => todo!(),
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ mod create;
|
||||
mod diff;
|
||||
mod events;
|
||||
mod factory;
|
||||
mod fragment;
|
||||
mod garbage;
|
||||
mod lazynodes;
|
||||
mod mutations;
|
||||
@@ -18,6 +19,7 @@ mod virtual_dom;
|
||||
pub(crate) mod innerlude {
|
||||
pub use crate::arena::*;
|
||||
pub use crate::events::*;
|
||||
pub use crate::fragment::*;
|
||||
pub use crate::lazynodes::*;
|
||||
pub use crate::mutations::*;
|
||||
pub use crate::nodes::*;
|
||||
|
||||
+53
-44
@@ -3,28 +3,38 @@ use std::{
|
||||
any::{Any, TypeId},
|
||||
cell::{Cell, RefCell},
|
||||
hash::Hasher,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
pub type TemplateId = &'static str;
|
||||
|
||||
/// A reference to a template along with any context needed to hydrate it
|
||||
///
|
||||
/// The dynamic parts of the template are stored separately from the static parts. This allows faster diffing by skipping
|
||||
/// static parts of the template.
|
||||
#[derive(Debug)]
|
||||
pub struct VNode<'a> {
|
||||
// The ID assigned for the root of this template
|
||||
pub node_id: Cell<ElementId>,
|
||||
|
||||
/// The key given to the root of this template.
|
||||
///
|
||||
/// In fragments, this is the key of the first child. In other cases, it is the key of the root.
|
||||
pub key: Option<&'a str>,
|
||||
|
||||
// When rendered, this template will be linked to its parent manually
|
||||
/// When rendered, this template will be linked to its parent manually
|
||||
pub parent: Option<ElementId>,
|
||||
|
||||
/// The static nodes and static descriptor of the template
|
||||
pub template: Template<'static>,
|
||||
|
||||
/// The IDs for the roots of this template - to be used when moving the template around and removing it from
|
||||
/// the actual Dom
|
||||
pub root_ids: &'a [Cell<ElementId>],
|
||||
|
||||
/// The dynamic parts of the template
|
||||
pub dynamic_nodes: &'a [DynamicNode<'a>],
|
||||
|
||||
/// The dynamic parts of the template
|
||||
pub dynamic_attrs: &'a [Attribute<'a>],
|
||||
}
|
||||
|
||||
@@ -109,25 +119,40 @@ pub enum TemplateNode<'a> {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DynamicNode<'a> {
|
||||
Component {
|
||||
name: &'static str,
|
||||
static_props: bool,
|
||||
props: Cell<*mut dyn AnyProps<'a>>,
|
||||
placeholder: Cell<Option<ElementId>>,
|
||||
scope: Cell<Option<ScopeId>>,
|
||||
},
|
||||
Text {
|
||||
id: Cell<ElementId>,
|
||||
value: &'a str,
|
||||
inner: bool,
|
||||
},
|
||||
Fragment {
|
||||
nodes: &'a [VNode<'a>],
|
||||
inner: bool,
|
||||
},
|
||||
Component(VComponent<'a>),
|
||||
Text(VText<'a>),
|
||||
Fragment(VFragment<'a>),
|
||||
Placeholder(Cell<ElementId>),
|
||||
}
|
||||
|
||||
impl<'a> DynamicNode<'a> {
|
||||
pub fn is_component(&self) -> bool {
|
||||
matches!(self, DynamicNode::Component(_))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VComponent<'a> {
|
||||
pub name: &'static str,
|
||||
pub static_props: bool,
|
||||
pub props: Cell<*mut dyn AnyProps<'a>>,
|
||||
pub placeholder: Cell<Option<ElementId>>,
|
||||
pub scope: Cell<Option<ScopeId>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VText<'a> {
|
||||
pub id: Cell<ElementId>,
|
||||
pub value: &'a str,
|
||||
pub inner: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VFragment<'a> {
|
||||
pub nodes: &'a [VNode<'a>],
|
||||
pub inner: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TemplateAttribute<'a> {
|
||||
Static {
|
||||
@@ -163,32 +188,16 @@ impl<'a> AttributeValue<'a> {
|
||||
cx: &'a ScopeState,
|
||||
mut f: impl FnMut(UiEvent<T>) + 'a,
|
||||
) -> AttributeValue<'a> {
|
||||
println!("creating listener with type od id {:?}", TypeId::of::<T>());
|
||||
|
||||
let f = cx.bump().alloc(move |a: UiEvent<dyn Any>| {
|
||||
println!(
|
||||
"incoming: {:?}, desired {:?}",
|
||||
a.data.type_id(),
|
||||
std::any::TypeId::of::<T>()
|
||||
);
|
||||
|
||||
let data = a.data.downcast::<T>().unwrap();
|
||||
|
||||
// println!("casting to type {:?}", TypeId::of::<T>());
|
||||
|
||||
f(UiEvent {
|
||||
bubbles: a.bubbles,
|
||||
data,
|
||||
})
|
||||
|
||||
// println!("listener called");
|
||||
// if let Some(event) = a.downcast::<T>() {
|
||||
// println!("downcast success");
|
||||
// f(event)
|
||||
// }
|
||||
}) as &mut dyn FnMut(UiEvent<dyn Any>);
|
||||
|
||||
AttributeValue::Listener(RefCell::new(f))
|
||||
AttributeValue::Listener(RefCell::new(cx.bump().alloc(
|
||||
move |event: UiEvent<dyn Any>| {
|
||||
if let Ok(data) = event.data.downcast::<T>() {
|
||||
f(UiEvent {
|
||||
bubbles: event.bubbles,
|
||||
data,
|
||||
})
|
||||
}
|
||||
},
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,101 +1,5 @@
|
||||
use crate::innerlude::*;
|
||||
|
||||
pub struct FragmentProps<'a>(Element<'a>);
|
||||
pub struct FragmentBuilder<'a, const BUILT: bool>(Element<'a>);
|
||||
impl<'a> FragmentBuilder<'a, false> {
|
||||
pub fn children(self, children: Element<'a>) -> FragmentBuilder<'a, true> {
|
||||
FragmentBuilder(children)
|
||||
}
|
||||
}
|
||||
impl<'a, const A: bool> FragmentBuilder<'a, A> {
|
||||
pub fn build(self) -> FragmentProps<'a> {
|
||||
FragmentProps(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Access the children elements passed into the component
|
||||
///
|
||||
/// This enables patterns where a component is passed children from its parent.
|
||||
///
|
||||
/// ## Details
|
||||
///
|
||||
/// Unlike React, Dioxus allows *only* lists of children to be passed from parent to child - not arbitrary functions
|
||||
/// or classes. If you want to generate nodes instead of accepting them as a list, consider declaring a closure
|
||||
/// on the props that takes Context.
|
||||
///
|
||||
/// If a parent passes children into a component, the child will always re-render when the parent re-renders. In other
|
||||
/// words, a component cannot be automatically memoized if it borrows nodes from its parent, even if the component's
|
||||
/// props are valid for the static lifetime.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// fn App(cx: Scope) -> Element {
|
||||
/// cx.render(rsx!{
|
||||
/// CustomCard {
|
||||
/// h1 {}
|
||||
/// p {}
|
||||
/// }
|
||||
/// })
|
||||
/// }
|
||||
///
|
||||
/// #[derive(PartialEq, Props)]
|
||||
/// struct CardProps {
|
||||
/// children: Element
|
||||
/// }
|
||||
///
|
||||
/// fn CustomCard(cx: Scope<CardProps>) -> Element {
|
||||
/// cx.render(rsx!{
|
||||
/// div {
|
||||
/// h1 {"Title card"}
|
||||
/// {cx.props.children}
|
||||
/// }
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
impl<'a> Properties for FragmentProps<'a> {
|
||||
type Builder = FragmentBuilder<'a, false>;
|
||||
const IS_STATIC: bool = false;
|
||||
fn builder() -> Self::Builder {
|
||||
FragmentBuilder(None)
|
||||
}
|
||||
unsafe fn memoize(&self, _other: &Self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Create inline fragments using Component syntax.
|
||||
///
|
||||
/// ## Details
|
||||
///
|
||||
/// Fragments capture a series of children without rendering extra nodes.
|
||||
///
|
||||
/// Creating fragments explicitly with the Fragment component is particularly useful when rendering lists or tables and
|
||||
/// a key is needed to identify each item.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// rsx!{
|
||||
/// Fragment { key: "abc" }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Usage
|
||||
///
|
||||
/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
|
||||
/// Try to avoid highly nested fragments if you can. Unlike React, there is no protection against infinitely nested fragments.
|
||||
///
|
||||
/// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro.
|
||||
///
|
||||
/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
|
||||
#[allow(non_upper_case_globals, non_snake_case)]
|
||||
pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
|
||||
// let i = cx.props.0.as_ref().map(|f| f.decouple());
|
||||
// cx.render(LazyNodes::new(|f| f.fragment_from_iter(i)))
|
||||
todo!("Fragment")
|
||||
}
|
||||
|
||||
/// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus
|
||||
/// on how to memoize the props and some additional optimizations that can be made. We strongly encourage using the
|
||||
/// derive macro to implement the `Properties` trait automatically as guarantee that your memoization strategy is safe.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::{
|
||||
any_props::AnyProps,
|
||||
arena::ElementId,
|
||||
bump_frame::BumpFrame,
|
||||
factory::RenderReturn,
|
||||
innerlude::{SuspenseId, SuspenseLeaf},
|
||||
@@ -19,18 +18,16 @@ use std::{
|
||||
impl VirtualDom {
|
||||
pub(super) fn new_scope(&mut self, props: *mut dyn AnyProps<'static>) -> &mut ScopeState {
|
||||
let parent = self.acquire_current_scope_raw();
|
||||
let container = self.acquire_current_container();
|
||||
let entry = self.scopes.vacant_entry();
|
||||
let height = unsafe { parent.map(|f| (*f).height).unwrap_or(0) + 1 };
|
||||
let id = ScopeId(entry.key());
|
||||
|
||||
entry.insert(ScopeState {
|
||||
parent,
|
||||
container,
|
||||
id,
|
||||
height,
|
||||
props,
|
||||
placeholder: None.into(),
|
||||
placeholder: Default::default(),
|
||||
node_arena_1: BumpFrame::new(50),
|
||||
node_arena_2: BumpFrame::new(50),
|
||||
spawned_tasks: Default::default(),
|
||||
@@ -43,13 +40,6 @@ impl VirtualDom {
|
||||
})
|
||||
}
|
||||
|
||||
fn acquire_current_container(&self) -> ElementId {
|
||||
self.element_stack
|
||||
.last()
|
||||
.copied()
|
||||
.expect("Always have a container")
|
||||
}
|
||||
|
||||
fn acquire_current_scope_raw(&mut self) -> Option<*mut ScopeState> {
|
||||
self.scope_stack
|
||||
.last()
|
||||
@@ -65,15 +55,12 @@ impl VirtualDom {
|
||||
}
|
||||
|
||||
pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn {
|
||||
println!("run_scope: {:?}", scope_id);
|
||||
|
||||
let mut new_nodes = unsafe {
|
||||
let scope = &mut self.scopes[scope_id.0];
|
||||
println!("run_scope: scope: {:?}", scope.render_cnt.get());
|
||||
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: &dyn AnyProps = mem::transmute(&*scope.props);
|
||||
props.render(scope).extend_lifetime()
|
||||
};
|
||||
|
||||
@@ -126,16 +113,12 @@ impl VirtualDom {
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
todo: use proper mutability here
|
||||
let scope = &self.scopes[scope_id.0];
|
||||
|
||||
right now we're aliasing the scope, which is not allowed
|
||||
*/
|
||||
// We write on top of the previous frame and then make it the current by pushing the generation forward
|
||||
let frame = scope.previous_frame();
|
||||
|
||||
let scope = &mut self.scopes[scope_id.0];
|
||||
let frame = scope.current_frame();
|
||||
|
||||
// set the head of the bump frame
|
||||
// set the new head of the bump frame
|
||||
let alloced = frame.bump.alloc(new_nodes);
|
||||
frame.node.set(alloced);
|
||||
|
||||
|
||||
@@ -27,6 +27,11 @@ use std::{
|
||||
/// component render.
|
||||
pub type Scope<'a, T = ()> = &'a Scoped<'a, T>;
|
||||
|
||||
// This ScopedType exists because we want to limit the amount of monomorphization that occurs when making inner
|
||||
// state type generic over props. When the state is generic, it causes every method to be monomorphized for every
|
||||
// instance of Scope<T> in the codebase.
|
||||
//
|
||||
//
|
||||
/// A wrapper around a component's [`ScopeState`] and properties. The [`ScopeState`] provides the majority of methods
|
||||
/// for the VirtualDom and component state.
|
||||
pub struct Scoped<'a, T = ()> {
|
||||
@@ -64,7 +69,6 @@ pub struct ScopeState {
|
||||
pub(crate) node_arena_2: BumpFrame,
|
||||
|
||||
pub(crate) parent: Option<*mut ScopeState>,
|
||||
pub(crate) container: ElementId,
|
||||
pub(crate) id: ScopeId,
|
||||
|
||||
pub(crate) height: u32,
|
||||
@@ -78,7 +82,7 @@ pub struct ScopeState {
|
||||
pub(crate) tasks: Rc<Scheduler>,
|
||||
pub(crate) spawned_tasks: HashSet<TaskId>,
|
||||
|
||||
pub(crate) props: *mut dyn AnyProps<'static>,
|
||||
pub(crate) props: *const dyn AnyProps<'static>,
|
||||
pub(crate) placeholder: Cell<Option<ElementId>>,
|
||||
}
|
||||
|
||||
|
||||
@@ -151,7 +151,6 @@ use std::{
|
||||
pub struct VirtualDom {
|
||||
pub(crate) templates: HashMap<TemplateId, Template<'static>>,
|
||||
pub(crate) scopes: Slab<ScopeState>,
|
||||
pub(crate) element_stack: Vec<ElementId>,
|
||||
pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
|
||||
pub(crate) scheduler: Rc<Scheduler>,
|
||||
|
||||
@@ -236,7 +235,6 @@ impl VirtualDom {
|
||||
scopes: Slab::default(),
|
||||
elements: Default::default(),
|
||||
scope_stack: Vec::new(),
|
||||
element_stack: vec![ElementId(0)],
|
||||
dirty_scopes: BTreeSet::new(),
|
||||
collected_leaves: Vec::new(),
|
||||
finished_fibers: Vec::new(),
|
||||
|
||||
@@ -6,6 +6,6 @@ use std::rc::Rc;
|
||||
pub fn use_atom_root(cx: &ScopeState) -> &Rc<AtomRoot> {
|
||||
cx.use_hook(|| match cx.consume_context::<Rc<AtomRoot>>() {
|
||||
Some(root) => root,
|
||||
None => cx.provide_root_context(Rc::new(AtomRoot::new(cx.schedule_update_any()))),
|
||||
None => panic!("No atom root found in context. Did you forget place an AtomRoot component at the top of your app?"),
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user