WIP update tests
This commit is contained in:
1 parent
13a99bce9c
commit
92010b0bab
10 files changed
+597
-244
No files matched your search
@@ -5,7 +5,9 @@ use quote::{quote, ToTokens};
|
||||
use syn::{
|
||||
self,
|
||||
parse::{Parse, ParseStream},
|
||||
Field, Ident, Token, Type,
|
||||
punctuated::Punctuated,
|
||||
token::Paren,
|
||||
Field, Ident, Token, Type, TypeTuple,
|
||||
};
|
||||
|
||||
#[derive(PartialEq)]
|
||||
@@ -103,8 +105,9 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
|
||||
|
||||
let gen = quote! {
|
||||
impl State for #type_name{
|
||||
fn update_node_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: &'a dioxus_core::VElement<'a>, ctx: &anymap::AnyMap) -> bool{
|
||||
use dioxus_native_core::real_dom_new_api::NodeDepState;
|
||||
fn update_node_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: Option<&'a dioxus_core::VElement<'a>>, ctx: &anymap::AnyMap) -> bool{
|
||||
use dioxus_native_core::real_dom_new_api::NodeDepState as _;
|
||||
// println!("called update_node_dep_state with ty: {:?}", ty);
|
||||
if false {
|
||||
unreachable!();
|
||||
}
|
||||
@@ -114,8 +117,9 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
|
||||
}
|
||||
}
|
||||
|
||||
fn update_parent_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: &'a dioxus_core::VElement<'a>, parent: &Self, ctx: &anymap::AnyMap) -> bool{
|
||||
use dioxus_native_core::real_dom_new_api::ParentDepState;
|
||||
fn update_parent_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: Option<&'a dioxus_core::VElement<'a>>, parent: Option<&Self>, ctx: &anymap::AnyMap) -> bool{
|
||||
use dioxus_native_core::real_dom_new_api::ParentDepState as _;
|
||||
// println!("called update_parent_dep_state with ty: {:?}", ty);
|
||||
if false {
|
||||
unreachable!();
|
||||
}
|
||||
@@ -125,8 +129,9 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
|
||||
}
|
||||
}
|
||||
|
||||
fn update_child_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: &'a dioxus_core::VElement<'a>, children: Vec<&Self>, ctx: &anymap::AnyMap) -> bool{
|
||||
use dioxus_native_core::real_dom_new_api::ChildDepState;
|
||||
fn update_child_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: Option<&'a dioxus_core::VElement<'a>>, children: &[&Self], ctx: &anymap::AnyMap) -> bool{
|
||||
use dioxus_native_core::real_dom_new_api::ChildDepState as _;
|
||||
// println!("called update_child_dep_state with ty: {:?}", ty);
|
||||
if false {
|
||||
unreachable!()
|
||||
}
|
||||
@@ -200,12 +205,34 @@ struct DepTypes {
|
||||
}
|
||||
|
||||
impl Parse for DepTypes {
|
||||
fn parse(input: ParseStream) -> Result<DepTypes, syn::Error> {
|
||||
let ctx_ty = input.parse::<Type>().ok();
|
||||
let comma = input.parse::<Token![,]>().ok();
|
||||
let dep_ty = input.parse::<Type>().ok();
|
||||
let dep_ty = comma.and(dep_ty);
|
||||
Ok(DepTypes { ctx_ty, dep_ty })
|
||||
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
|
||||
let dep_ty = input.parse().ok();
|
||||
let comma: Option<Token![,]> = input.parse().ok();
|
||||
let ctx_ty = input.parse().ok();
|
||||
Ok(Self {
|
||||
ctx_ty: comma.and(ctx_ty),
|
||||
dep_ty,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct NodeDepTypes {
|
||||
ctx_ty: Option<Type>,
|
||||
}
|
||||
|
||||
impl Parse for NodeDepTypes {
|
||||
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
|
||||
let ctx_ty = input.parse().ok();
|
||||
Ok(Self { ctx_ty })
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NodeDepTypes> for DepTypes {
|
||||
fn from(node_dep_types: NodeDepTypes) -> Self {
|
||||
Self {
|
||||
ctx_ty: node_dep_types.ctx_ty,
|
||||
dep_ty: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,7 +270,10 @@ impl<'a> StateMember<'a> {
|
||||
_ => None,
|
||||
})
|
||||
.flatten()?;
|
||||
let deps: DepTypes = a.parse_args().ok()?;
|
||||
let deps: DepTypes = match dep_kind {
|
||||
DepKind::NodeDepState => a.parse_args::<NodeDepTypes>().ok()?.into(),
|
||||
_ => a.parse_args().ok()?,
|
||||
};
|
||||
|
||||
Some(Self {
|
||||
mem,
|
||||
@@ -260,8 +290,19 @@ impl<'a> StateMember<'a> {
|
||||
fn reduce_self(&self) -> quote::__private::TokenStream {
|
||||
let ident = &self.mem.ident;
|
||||
let get_ctx = if let Some(ctx_ty) = &self.ctx_ty {
|
||||
let msg = ctx_ty.to_token_stream().to_string() + " not found in context";
|
||||
quote! {ctx.get().expect(#msg)}
|
||||
if ctx_ty
|
||||
== &Type::Tuple(TypeTuple {
|
||||
paren_token: Paren {
|
||||
span: quote::__private::Span::call_site(),
|
||||
},
|
||||
elems: Punctuated::new(),
|
||||
})
|
||||
{
|
||||
quote! {&()}
|
||||
} else {
|
||||
let msg = ctx_ty.to_token_stream().to_string() + " not found in context";
|
||||
quote! {ctx.get().expect(#msg)}
|
||||
}
|
||||
} else {
|
||||
quote! {&()}
|
||||
};
|
||||
@@ -276,7 +317,7 @@ impl<'a> StateMember<'a> {
|
||||
quote!(self.#ident.reduce(#node_view, children.iter().map(|s| &s.#dep_ident).collect(), #get_ctx))
|
||||
}
|
||||
DepKind::ParentDepState => {
|
||||
quote!(self.#ident.reduce(#node_view, &parent.#dep_ident, #get_ctx))
|
||||
quote!(self.#ident.reduce(#node_view, parent.as_ref().map(|p| &p.#dep_ident), #get_ctx))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -288,7 +329,7 @@ impl<'a> StateMember<'a> {
|
||||
quote!(self.#ident.reduce(#node_view, &(), #get_ctx))
|
||||
}
|
||||
DepKind::ParentDepState => {
|
||||
quote!(self.#ident.reduce(#node_view, &(), #get_ctx))
|
||||
quote!(self.#ident.reduce(#node_view, Some(&()), #get_ctx))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -296,6 +337,11 @@ impl<'a> StateMember<'a> {
|
||||
|
||||
fn type_id(&self) -> quote::__private::TokenStream {
|
||||
let ty = &self.mem.ty;
|
||||
quote!(std::any::TypeId::of::<#ty>())
|
||||
// quote!(std::any::TypeId::of::<#ty>())
|
||||
quote!({
|
||||
let type_id = std::any::TypeId::of::<#ty>();
|
||||
// println!("{:?}", type_id);
|
||||
type_id
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod layout_attributes;
|
||||
pub mod real_dom;
|
||||
pub mod real_dom_new_api;
|
||||
pub use dioxus_native_core_macro;
|
||||
@@ -1,13 +1,14 @@
|
||||
use anymap::AnyMap;
|
||||
use fxhash::{FxHashMap, FxHashSet};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
collections::VecDeque,
|
||||
ops::{Index, IndexMut},
|
||||
};
|
||||
|
||||
use dioxus_core::{ElementId, Mutations, VNode, VirtualDom};
|
||||
|
||||
use crate::real_dom_new_api::{State, NodeMask};
|
||||
use crate::real_dom_new_api::{AttributeMask, NodeMask, State};
|
||||
|
||||
/// A Dom that can sync with the VirtualDom mutations intended for use in lazy renderers.
|
||||
/// The render state passes from parent to children and or accumulates state from children to parents.
|
||||
@@ -47,7 +48,7 @@ impl<S: State> RealDom<S> {
|
||||
}
|
||||
|
||||
/// Updates the dom, up and down state and return a set of nodes that were updated pass this to update_state.
|
||||
pub fn apply_mutations(&mut self, mutations_vec: Vec<Mutations>) -> Vec<usize> {
|
||||
pub fn apply_mutations(&mut self, mutations_vec: Vec<Mutations>) -> Vec<(usize, NodeMask)> {
|
||||
let mut nodes_updated = Vec::new();
|
||||
for mutations in mutations_vec {
|
||||
for e in mutations.edits {
|
||||
@@ -69,7 +70,7 @@ impl<S: State> RealDom<S> {
|
||||
.collect();
|
||||
for ns in drained {
|
||||
self.link_child(ns, target).unwrap();
|
||||
nodes_updated.push(ns);
|
||||
nodes_updated.push((ns, NodeMask::ALL));
|
||||
}
|
||||
}
|
||||
ReplaceWith { root, m } => {
|
||||
@@ -77,7 +78,7 @@ impl<S: State> RealDom<S> {
|
||||
let target = root.parent.unwrap().0;
|
||||
let drained: Vec<_> = self.node_stack.drain(0..m as usize).collect();
|
||||
for ns in drained {
|
||||
nodes_updated.push(ns);
|
||||
nodes_updated.push((ns, NodeMask::ALL));
|
||||
self.link_child(ns, target).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -85,7 +86,7 @@ impl<S: State> RealDom<S> {
|
||||
let target = self[root as usize].parent.unwrap().0;
|
||||
let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect();
|
||||
for ns in drained {
|
||||
nodes_updated.push(ns);
|
||||
nodes_updated.push((ns, NodeMask::ALL));
|
||||
self.link_child(ns, target).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -93,13 +94,13 @@ impl<S: State> RealDom<S> {
|
||||
let target = self[root as usize].parent.unwrap().0;
|
||||
let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect();
|
||||
for ns in drained {
|
||||
nodes_updated.push(ns);
|
||||
nodes_updated.push((ns, NodeMask::ALL));
|
||||
self.link_child(ns, target).unwrap();
|
||||
}
|
||||
}
|
||||
Remove { root } => {
|
||||
if let Some(parent) = self[root as usize].parent {
|
||||
nodes_updated.push(parent.0);
|
||||
nodes_updated.push((parent.0, NodeMask::NONE));
|
||||
}
|
||||
self.remove(root as usize).unwrap();
|
||||
}
|
||||
@@ -165,7 +166,7 @@ impl<S: State> RealDom<S> {
|
||||
text: new_text,
|
||||
} => {
|
||||
let target = &mut self[root as usize];
|
||||
nodes_updated.push(root as usize);
|
||||
nodes_updated.push((root as usize, NodeMask::NONE));
|
||||
match &mut target.node_type {
|
||||
NodeType::Text { text } => {
|
||||
*text = new_text.to_string();
|
||||
@@ -173,11 +174,19 @@ impl<S: State> RealDom<S> {
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
SetAttribute { root, .. } => {
|
||||
nodes_updated.push(root as usize);
|
||||
SetAttribute { root, field, .. } => {
|
||||
nodes_updated.push((
|
||||
root as usize,
|
||||
NodeMask::new(AttributeMask::single(field), false, false),
|
||||
));
|
||||
}
|
||||
RemoveAttribute { root, .. } => {
|
||||
nodes_updated.push(root as usize);
|
||||
RemoveAttribute {
|
||||
root, name: field, ..
|
||||
} => {
|
||||
nodes_updated.push((
|
||||
root as usize,
|
||||
NodeMask::new(AttributeMask::single(field), false, false),
|
||||
));
|
||||
}
|
||||
PopRoot {} => {
|
||||
self.node_stack.pop();
|
||||
@@ -193,77 +202,148 @@ impl<S: State> RealDom<S> {
|
||||
pub fn update_state(
|
||||
&mut self,
|
||||
vdom: &VirtualDom,
|
||||
nodes_updated: Vec<usize>,
|
||||
nodes_updated: Vec<(usize, NodeMask)>,
|
||||
ctx: AnyMap,
|
||||
) -> Option<FxHashSet<usize>> {
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
enum StatesToCheck {
|
||||
All,
|
||||
Some(Vec<TypeId>),
|
||||
}
|
||||
|
||||
let mut to_rerender = FxHashSet::default();
|
||||
to_rerender.extend(nodes_updated.iter());
|
||||
to_rerender.extend(nodes_updated.iter().map(|(id, _)| id));
|
||||
let mut nodes_updated: Vec<_> = nodes_updated
|
||||
.into_iter()
|
||||
.map(|id| (id, self[id].height))
|
||||
.map(|(id, mask)| (id, self[id].height, mask, StatesToCheck::All))
|
||||
.collect();
|
||||
// Sort nodes first by height, then if the height is the same id.
|
||||
nodes_updated.sort_by(|fst, snd| fst.1.cmp(&snd.1).then(fst.0.cmp(&snd.0)));
|
||||
nodes_updated.dedup();
|
||||
{
|
||||
// Combine mutations that affect the same node.
|
||||
let current_key = None;
|
||||
for i in 0..nodes_updated.len() {
|
||||
let current = nodes_updated;
|
||||
}
|
||||
}
|
||||
println!("{:?}", nodes_updated);
|
||||
|
||||
// update the state that only depends on nodes. The order does not matter.
|
||||
for (id, _height, mask, to_check) in &nodes_updated {
|
||||
let mut changed = false;
|
||||
let node = &mut self[*id as usize];
|
||||
let ids = match to_check {
|
||||
StatesToCheck::All => node.state.node_dep_types(&mask),
|
||||
StatesToCheck::Some(_) => unreachable!(),
|
||||
};
|
||||
for ty in ids {
|
||||
let node = &mut self[*id as usize];
|
||||
let el = if let &VNode::Element(e) = node.element(vdom) {
|
||||
Some(e)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
changed |= node.state.update_node_dep_state(ty, el, &ctx);
|
||||
}
|
||||
if changed {
|
||||
to_rerender.insert(*id);
|
||||
}
|
||||
}
|
||||
|
||||
// bubble up state. To avoid calling reduce more times than nessisary start from the bottom and go up.
|
||||
let mut to_bubble: VecDeque<_> = nodes_updated.clone().into();
|
||||
while let Some((id, height)) = to_bubble.pop_back() {
|
||||
let node = &mut self[id as usize];
|
||||
let vnode = node.element(vdom);
|
||||
let node_type = &node.node_type;
|
||||
if let
|
||||
NodeType::Element { children, tag, namespace } =node.node_type{
|
||||
|
||||
while let Some((id, height, mask, to_check)) = to_bubble.pop_back() {
|
||||
let (node, children) = self.get_node_children_mut(id).unwrap();
|
||||
let children_state: Vec<_> = children.iter().map(|c| &c.state).collect();
|
||||
let ids = match to_check {
|
||||
StatesToCheck::All => node.state.child_dep_types(&mask),
|
||||
StatesToCheck::Some(ids) => ids,
|
||||
};
|
||||
let mut changed = Vec::new();
|
||||
for ty in ids {
|
||||
let el = if let &VNode::Element(e) = node.element(vdom) {
|
||||
Some(e)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if node
|
||||
.state
|
||||
.update_child_dep_state(ty, el, &children_state, &ctx)
|
||||
{
|
||||
changed.push(ty);
|
||||
}
|
||||
let mask = NodeMask::new(attritutes, tag, namespace)
|
||||
// todo: reduce cloning state
|
||||
for id in node.state.child_dep_types(mask) {}
|
||||
if new != old {
|
||||
to_rerender.insert(id);
|
||||
if let Some(p) = parent {
|
||||
let i = to_bubble.partition_point(|(other_id, h)| {
|
||||
*h < height - 1 || (*h == height - 1 && *other_id < p.0)
|
||||
}
|
||||
if let Some(parent_id) = node.parent {
|
||||
if !changed.is_empty() {
|
||||
to_rerender.insert(id);
|
||||
let i = to_bubble.partition_point(|(other_id, h, ..)| {
|
||||
*h < height - 1 || (*h == height - 1 && *other_id < parent_id.0)
|
||||
});
|
||||
// make sure the parent is not already queued
|
||||
if i >= to_bubble.len() || to_bubble[i].0 != p.0 {
|
||||
to_bubble.insert(i, (p.0, height - 1));
|
||||
if i >= to_bubble.len() || to_bubble[i].0 != parent_id.0 {
|
||||
to_bubble.insert(
|
||||
i,
|
||||
(
|
||||
parent_id.0,
|
||||
height - 1,
|
||||
NodeMask::NONE,
|
||||
StatesToCheck::Some(changed),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
let node = &mut self[id as usize];
|
||||
node.up_state = new;
|
||||
}
|
||||
}
|
||||
|
||||
// push down state. To avoid calling reduce more times than nessisary start from the top and go down.
|
||||
let mut to_push: VecDeque<_> = nodes_updated.clone().into();
|
||||
while let Some((id, height)) = to_push.pop_front() {
|
||||
let node = &self[id as usize];
|
||||
// todo: reduce cloning state
|
||||
let old = node.down_state.clone();
|
||||
let mut new = node.down_state.clone();
|
||||
let vnode = node.element(vdom);
|
||||
new.reduce(
|
||||
node.parent
|
||||
.filter(|e| e.0 != 0)
|
||||
.map(|e| &self[e].down_state),
|
||||
vnode,
|
||||
ds_ctx,
|
||||
);
|
||||
if new != old {
|
||||
to_rerender.insert(id);
|
||||
let node = &mut self[id as usize];
|
||||
while let Some((id, height, mask, to_check)) = to_push.pop_front() {
|
||||
let node = &self[id];
|
||||
let ids = match to_check {
|
||||
StatesToCheck::All => node.state.parent_dep_types(&mask),
|
||||
StatesToCheck::Some(ids) => ids,
|
||||
};
|
||||
let mut changed = Vec::new();
|
||||
let (node, parent) = self.get_node_parent_mut(id).unwrap();
|
||||
for ty in ids {
|
||||
let el = if let &VNode::Element(e) = node.element(vdom) {
|
||||
Some(e)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let parent = parent.as_deref();
|
||||
let state = &mut node.state;
|
||||
if state.update_parent_dep_state(
|
||||
ty,
|
||||
el,
|
||||
parent.filter(|n| n.id.0 != 0).map(|n| &n.state),
|
||||
&ctx,
|
||||
) {
|
||||
changed.push(ty);
|
||||
}
|
||||
}
|
||||
|
||||
to_rerender.insert(id);
|
||||
if !changed.is_empty() {
|
||||
let node = &self[id];
|
||||
if let NodeType::Element { children, .. } = &node.node_type {
|
||||
for c in children {
|
||||
let i = to_push.partition_point(|(other_id, h)| {
|
||||
let i = to_push.partition_point(|(other_id, h, ..)| {
|
||||
*h < height + 1 || (*h == height + 1 && *other_id < c.0)
|
||||
});
|
||||
if i >= to_push.len() || to_push[i].0 != c.0 {
|
||||
to_push.insert(i, (c.0, height + 1));
|
||||
to_push.insert(
|
||||
i,
|
||||
(
|
||||
c.0,
|
||||
height + 1,
|
||||
NodeMask::NONE,
|
||||
StatesToCheck::Some(changed.clone()),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
node.down_state = new;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,6 +413,56 @@ impl<S: State> RealDom<S> {
|
||||
self.nodes.get_mut(id)?.as_mut()
|
||||
}
|
||||
|
||||
// this is safe because no node will have itself as a child
|
||||
pub fn get_node_children_mut<'a>(
|
||||
&'a mut self,
|
||||
id: usize,
|
||||
) -> Option<(&'a mut Node<S>, Vec<&'a mut Node<S>>)> {
|
||||
let ptr = self.nodes.as_mut_ptr();
|
||||
unsafe {
|
||||
if id >= self.nodes.len() {
|
||||
None
|
||||
} else {
|
||||
let node = &mut *ptr.add(id);
|
||||
if let Some(node) = node.as_mut() {
|
||||
let children = match &node.node_type {
|
||||
NodeType::Element { children, .. } => children
|
||||
.iter()
|
||||
.map(|id| (&mut *ptr.add(id.0)).as_mut().unwrap())
|
||||
.collect(),
|
||||
_ => Vec::new(),
|
||||
};
|
||||
return Some((node, children));
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this is safe because no node will have itself as a parent
|
||||
pub fn get_node_parent_mut<'a>(
|
||||
&'a mut self,
|
||||
id: usize,
|
||||
) -> Option<(&'a mut Node<S>, Option<&'a mut Node<S>>)> {
|
||||
let ptr = self.nodes.as_mut_ptr();
|
||||
unsafe {
|
||||
let node = &mut *ptr.add(id);
|
||||
if id >= self.nodes.len() {
|
||||
None
|
||||
} else {
|
||||
if let Some(node) = node.as_mut() {
|
||||
let parent = node
|
||||
.parent
|
||||
.map(|id| (&mut *ptr.add(id.0)).as_mut().unwrap());
|
||||
return Some((node, parent));
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&Node<S>> {
|
||||
if let Some(nodes) = self.nodes_listening.get(event) {
|
||||
let mut listening: Vec<_> = nodes.iter().map(|id| &self[*id]).collect();
|
||||
@@ -528,39 +658,3 @@ impl<S: State> Node<S> {
|
||||
self.parent = Some(parent);
|
||||
}
|
||||
}
|
||||
|
||||
/// This state that is passed down to children. For example text properties (`<b>` `<i>` `<u>`) would be passed to children.
|
||||
/// Called when the current node's node properties are modified or a parrent's [PushedDownState] is modified.
|
||||
/// Called at most once per update.
|
||||
pub trait PushedDownState: Default + PartialEq + Clone {
|
||||
/// The context is passed to the [PushedDownState::reduce] when it is pushed down.
|
||||
/// This is sometimes nessisary for lifetime purposes.
|
||||
type Ctx;
|
||||
fn reduce(&mut self, parent: Option<&Self>, vnode: &VNode, ctx: &mut Self::Ctx);
|
||||
}
|
||||
impl PushedDownState for () {
|
||||
type Ctx = ();
|
||||
fn reduce(&mut self, _parent: Option<&Self>, _vnode: &VNode, _ctx: &mut Self::Ctx) {}
|
||||
}
|
||||
|
||||
/// This state is derived from children. For example a node's size could be derived from the size of children.
|
||||
/// Called when the current node's node properties are modified, a child's [BubbledUpState] is modified or a child is removed.
|
||||
/// Called at most once per update.
|
||||
pub trait BubbledUpState: Default + PartialEq + Clone {
|
||||
/// The context is passed to the [BubbledUpState::reduce] when it is bubbled up.
|
||||
/// This is sometimes nessisary for lifetime purposes.
|
||||
type Ctx;
|
||||
fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, ctx: &mut Self::Ctx)
|
||||
where
|
||||
I: Iterator<Item = &'a Self>,
|
||||
Self: 'a;
|
||||
}
|
||||
impl BubbledUpState for () {
|
||||
type Ctx = ();
|
||||
fn reduce<'a, I>(&mut self, _children: I, _vnode: &VNode, _ctx: &mut Self::Ctx)
|
||||
where
|
||||
I: Iterator<Item = &'a Self>,
|
||||
Self: 'a,
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,13 @@ use std::any::TypeId;
|
||||
use anymap::AnyMap;
|
||||
use dioxus_core::{Attribute, VElement};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NodeView<'a> {
|
||||
inner: &'a VElement<'a>,
|
||||
inner: Option<&'a VElement<'a>>,
|
||||
view: NodeMask,
|
||||
}
|
||||
impl<'a> NodeView<'a> {
|
||||
pub fn new(velement: &'a VElement<'a>, view: NodeMask) -> Self {
|
||||
pub fn new(velement: Option<&'a VElement<'a>>, view: NodeMask) -> Self {
|
||||
Self {
|
||||
inner: velement,
|
||||
view: view,
|
||||
@@ -17,7 +18,7 @@ impl<'a> NodeView<'a> {
|
||||
|
||||
pub fn tag(&self) -> Option<&'a str> {
|
||||
if self.view.tag {
|
||||
Some(self.inner.tag)
|
||||
self.inner.map(|el| el.tag)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -25,7 +26,7 @@ impl<'a> NodeView<'a> {
|
||||
|
||||
pub fn namespace(&self) -> Option<&'a str> {
|
||||
if self.view.namespace {
|
||||
self.inner.namespace
|
||||
self.inner.map(|el| el.namespace).flatten()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -33,23 +34,159 @@ impl<'a> NodeView<'a> {
|
||||
|
||||
pub fn attributes(&self) -> impl Iterator<Item = &Attribute<'a>> {
|
||||
self.inner
|
||||
.attributes
|
||||
.map(|el| el.attributes)
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.filter(|a| self.view.attritutes.contains(&a.name))
|
||||
.filter(|a| self.view.attritutes.contains_attribute(&a.name))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum AttributeMask {
|
||||
All,
|
||||
Dynamic(Vec<&'static str>),
|
||||
Static(&'static [&'static str]),
|
||||
}
|
||||
|
||||
impl AttributeMask {
|
||||
pub const NONE: Self = Self::Static(&[]);
|
||||
|
||||
fn contains_attribute(&self, attr: &'static str) -> bool {
|
||||
match self {
|
||||
AttributeMask::All => true,
|
||||
AttributeMask::Dynamic(l) => l.contains(&attr),
|
||||
AttributeMask::Static(l) => l.contains(&attr),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn single(new: &'static str) -> Self {
|
||||
Self::Dynamic(vec![new])
|
||||
}
|
||||
|
||||
pub fn verify(&self) {
|
||||
match &self {
|
||||
AttributeMask::Static(attrs) => debug_assert!(
|
||||
attrs.windows(2).all(|w| w[0] < w[1]),
|
||||
"attritutes must be increasing"
|
||||
),
|
||||
AttributeMask::Dynamic(attrs) => debug_assert!(
|
||||
attrs.windows(2).all(|w| w[0] < w[1]),
|
||||
"attritutes must be increasing"
|
||||
),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn union(&self, other: &Self) -> Self {
|
||||
pub fn union_iter(
|
||||
s_iter: impl Iterator<Item = &'static str>,
|
||||
o_iter: impl Iterator<Item = &'static str>,
|
||||
) -> Vec<&'static str> {
|
||||
let mut s_peekable = s_iter.peekable();
|
||||
let mut o_peekable = o_iter.peekable();
|
||||
let mut v = Vec::new();
|
||||
while let Some(s_i) = s_peekable.peek() {
|
||||
loop {
|
||||
if let Some(o_i) = o_peekable.peek() {
|
||||
if o_i > s_i {
|
||||
break;
|
||||
} else {
|
||||
v.push(o_peekable.next().unwrap());
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
v.push(s_peekable.next().unwrap());
|
||||
}
|
||||
while let Some(o_i) = o_peekable.next() {
|
||||
v.push(o_i);
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
let new = match (self, other) {
|
||||
(AttributeMask::Dynamic(s), AttributeMask::Dynamic(o)) => {
|
||||
AttributeMask::Dynamic(union_iter(s.iter().copied(), o.iter().copied()))
|
||||
}
|
||||
(AttributeMask::Static(s), AttributeMask::Dynamic(o)) => {
|
||||
AttributeMask::Dynamic(union_iter(s.iter().copied(), o.iter().copied()))
|
||||
}
|
||||
(AttributeMask::Dynamic(s), AttributeMask::Static(o)) => {
|
||||
AttributeMask::Dynamic(union_iter(s.iter().copied(), o.iter().copied()))
|
||||
}
|
||||
(AttributeMask::Static(s), AttributeMask::Static(o)) => {
|
||||
AttributeMask::Dynamic(union_iter(s.iter().copied(), o.iter().copied()))
|
||||
}
|
||||
_ => AttributeMask::All,
|
||||
};
|
||||
new.verify();
|
||||
new
|
||||
}
|
||||
|
||||
fn overlaps(&self, other: &Self) -> bool {
|
||||
fn overlaps_iter(
|
||||
mut self_iter: impl Iterator<Item = &'static str>,
|
||||
mut other_iter: impl Iterator<Item = &'static str>,
|
||||
) -> bool {
|
||||
if let Some(mut other_attr) = other_iter.next() {
|
||||
while let Some(self_attr) = self_iter.next() {
|
||||
while other_attr < self_attr {
|
||||
if let Some(attr) = other_iter.next() {
|
||||
other_attr = attr;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if other_attr == self_attr {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
match (self, other) {
|
||||
(AttributeMask::All, AttributeMask::All) => true,
|
||||
(AttributeMask::All, AttributeMask::Dynamic(v)) => !v.is_empty(),
|
||||
(AttributeMask::All, AttributeMask::Static(s)) => !s.is_empty(),
|
||||
(AttributeMask::Dynamic(v), AttributeMask::All) => !v.is_empty(),
|
||||
(AttributeMask::Static(s), AttributeMask::All) => !s.is_empty(),
|
||||
(AttributeMask::Dynamic(v1), AttributeMask::Dynamic(v2)) => {
|
||||
overlaps_iter(v1.iter().copied(), v2.iter().copied())
|
||||
}
|
||||
(AttributeMask::Dynamic(v), AttributeMask::Static(s)) => {
|
||||
overlaps_iter(v.iter().copied(), s.iter().copied())
|
||||
}
|
||||
(AttributeMask::Static(s), AttributeMask::Dynamic(v)) => {
|
||||
overlaps_iter(v.iter().copied(), s.iter().copied())
|
||||
}
|
||||
(AttributeMask::Static(s1), AttributeMask::Static(s2)) => {
|
||||
overlaps_iter(s1.iter().copied(), s2.iter().copied())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AttributeMask {
|
||||
fn default() -> Self {
|
||||
AttributeMask::Static(&[])
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Clone, Debug)]
|
||||
pub struct NodeMask {
|
||||
// must be sorted
|
||||
attritutes: &'static [&'static str],
|
||||
attritutes: AttributeMask,
|
||||
tag: bool,
|
||||
namespace: bool,
|
||||
}
|
||||
|
||||
impl NodeMask {
|
||||
pub const NONE: Self = Self::new(AttributeMask::Static(&[]), false, false);
|
||||
pub const ALL: Self = Self::new(AttributeMask::All, true, true);
|
||||
|
||||
/// attritutes must be sorted!
|
||||
pub const fn new(attritutes: &'static [&'static str], tag: bool, namespace: bool) -> Self {
|
||||
pub const fn new(attritutes: AttributeMask, tag: bool, namespace: bool) -> Self {
|
||||
Self {
|
||||
attritutes,
|
||||
tag,
|
||||
@@ -57,13 +194,6 @@ impl NodeMask {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify(&self) {
|
||||
debug_assert!(
|
||||
self.attritutes.windows(2).all(|w| w[0] < w[1]),
|
||||
"attritutes must be increasing"
|
||||
);
|
||||
}
|
||||
|
||||
pub fn overlaps(&self, other: &Self) -> bool {
|
||||
(self.tag && other.tag)
|
||||
|| (self.namespace && other.namespace)
|
||||
@@ -71,51 +201,48 @@ impl NodeMask {
|
||||
}
|
||||
|
||||
fn attritutes_overlap(&self, other: &Self) -> bool {
|
||||
let mut self_attrs = self.attritutes.iter();
|
||||
let mut other_attrs = other.attritutes.iter();
|
||||
if let Some(mut other_attr) = other_attrs.next() {
|
||||
while let Some(self_attr) = self_attrs.next() {
|
||||
while other_attr < self_attr {
|
||||
if let Some(attr) = other_attrs.next() {
|
||||
other_attr = attr;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if other_attr == self_attr {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
self.attritutes.overlaps(&other.attritutes)
|
||||
}
|
||||
}
|
||||
|
||||
/// This state is derived from children. For example a node's size could be derived from the size of children.
|
||||
/// Called when the current node's node properties are modified, a child's [BubbledUpState] is modified or a child is removed.
|
||||
/// Called at most once per update.
|
||||
pub trait ChildDepState {
|
||||
/// The context is passed to the [PushedDownState::reduce] when it is pushed down.
|
||||
/// This is sometimes nessisary for lifetime purposes.
|
||||
type Ctx;
|
||||
type DepState: ChildDepState;
|
||||
const NODE_MASK: NodeMask = NodeMask::new(&[], false, false);
|
||||
const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, false, false);
|
||||
fn reduce(&mut self, node: NodeView, children: Vec<&Self::DepState>, ctx: &Self::Ctx) -> bool;
|
||||
}
|
||||
|
||||
/// This state that is passed down to children. For example text properties (`<b>` `<i>` `<u>`) would be passed to children.
|
||||
/// Called when the current node's node properties are modified or a parrent's [PushedDownState] is modified.
|
||||
/// Called at most once per update.
|
||||
pub trait ParentDepState {
|
||||
/// The context is passed to the [PushedDownState::reduce] when it is pushed down.
|
||||
/// This is sometimes nessisary for lifetime purposes.
|
||||
type Ctx;
|
||||
type DepState: ParentDepState;
|
||||
const NODE_MASK: NodeMask = NodeMask::new(&[], false, false);
|
||||
fn reduce(&mut self, node: NodeView, parent: &Self::DepState, ctx: &Self::Ctx) -> bool;
|
||||
const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, false, false);
|
||||
fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, ctx: &Self::Ctx) -> bool;
|
||||
}
|
||||
|
||||
/// This state that is upadated lazily. For example any propertys that do not effect other parts of the dom like bg-color.
|
||||
/// Called when the current node's node properties are modified or a parrent's [PushedDownState] is modified.
|
||||
/// Called at most once per update.
|
||||
pub trait NodeDepState {
|
||||
type Ctx;
|
||||
const NODE_MASK: NodeMask = NodeMask::new(&[], false, false);
|
||||
const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, false, false);
|
||||
fn reduce(&mut self, node: NodeView, ctx: &Self::Ctx) -> bool;
|
||||
}
|
||||
|
||||
pub trait State: Default {
|
||||
pub trait State: Default + Clone {
|
||||
fn update_node_dep_state<'a>(
|
||||
&'a mut self,
|
||||
ty: TypeId,
|
||||
node: &'a VElement<'a>,
|
||||
node: Option<&'a VElement<'a>>,
|
||||
ctx: &AnyMap,
|
||||
) -> bool;
|
||||
/// This must be a valid resolution order. (no nodes updated before a state they rely on)
|
||||
@@ -124,8 +251,8 @@ pub trait State: Default {
|
||||
fn update_parent_dep_state<'a>(
|
||||
&'a mut self,
|
||||
ty: TypeId,
|
||||
node: &'a VElement<'a>,
|
||||
parent: &Self,
|
||||
node: Option<&'a VElement<'a>>,
|
||||
parent: Option<&Self>,
|
||||
ctx: &AnyMap,
|
||||
) -> bool;
|
||||
/// This must be a valid resolution order. (no nodes updated before a state they rely on)
|
||||
@@ -134,8 +261,8 @@ pub trait State: Default {
|
||||
fn update_child_dep_state<'a>(
|
||||
&'a mut self,
|
||||
ty: TypeId,
|
||||
node: &'a VElement<'a>,
|
||||
children: Vec<&Self>,
|
||||
node: Option<&'a VElement<'a>>,
|
||||
children: &[&Self],
|
||||
ctx: &AnyMap,
|
||||
) -> bool;
|
||||
/// This must be a valid resolution order. (no nodes updated before a state they rely on)
|
||||
|
||||
@@ -3,8 +3,13 @@ use dioxus_core::*;
|
||||
use dioxus_core_macro::*;
|
||||
use dioxus_html as dioxus_elements;
|
||||
use dioxus_native_core::real_dom::RealDom;
|
||||
use dioxus_native_core::real_dom_new_api::State;
|
||||
use dioxus_native_core_macro::State;
|
||||
use std::cell::Cell;
|
||||
|
||||
#[derive(State, Default, Clone)]
|
||||
struct Empty {}
|
||||
|
||||
#[test]
|
||||
fn remove_node() {
|
||||
#[allow(non_snake_case)]
|
||||
@@ -20,7 +25,7 @@ fn remove_node() {
|
||||
}
|
||||
});
|
||||
|
||||
let mut dom: RealDom<(), ()> = RealDom::new();
|
||||
let mut dom: RealDom<Empty> = RealDom::new();
|
||||
|
||||
let _to_update = dom.apply_mutations(vec![mutations]);
|
||||
let child_div = VElement {
|
||||
@@ -92,7 +97,7 @@ fn add_node() {
|
||||
div{}
|
||||
});
|
||||
|
||||
let mut dom: RealDom<(), ()> = RealDom::new();
|
||||
let mut dom: RealDom<Empty> = RealDom::new();
|
||||
|
||||
let _to_update = dom.apply_mutations(vec![mutations]);
|
||||
|
||||
|
||||
@@ -5,6 +5,11 @@ use dioxus_core::*;
|
||||
use dioxus_core_macro::*;
|
||||
use dioxus_html as dioxus_elements;
|
||||
use dioxus_native_core::real_dom::RealDom;
|
||||
use dioxus_native_core::real_dom_new_api::State;
|
||||
use dioxus_native_core_macro::State;
|
||||
|
||||
#[derive(State, Default, Clone)]
|
||||
struct Empty {}
|
||||
|
||||
#[test]
|
||||
fn initial_build_simple() {
|
||||
@@ -21,7 +26,7 @@ fn initial_build_simple() {
|
||||
div{}
|
||||
});
|
||||
|
||||
let mut dom: RealDom<(), ()> = RealDom::new();
|
||||
let mut dom: RealDom<Empty> = RealDom::new();
|
||||
|
||||
let _to_update = dom.apply_mutations(vec![mutations]);
|
||||
let root_div = VElement {
|
||||
@@ -60,7 +65,7 @@ fn initial_build_with_children() {
|
||||
}
|
||||
});
|
||||
|
||||
let mut dom: RealDom<(), ()> = RealDom::new();
|
||||
let mut dom: RealDom<Empty> = RealDom::new();
|
||||
|
||||
let _to_update = dom.apply_mutations(vec![mutations]);
|
||||
let first_text = VText {
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
use dioxus_native_core::real_dom_new_api::*;
|
||||
use dioxus_native_core_macro::*;
|
||||
|
||||
#[derive(State)]
|
||||
#[derive(State, Default, Clone)]
|
||||
struct Z {
|
||||
// depends on just attributes and no context
|
||||
#[node_dep_state()]
|
||||
x: A,
|
||||
// depends on attributes, the B component of children and i32 context
|
||||
#[child_dep_state(i32, B)]
|
||||
#[child_dep_state(B, i32)]
|
||||
y: B,
|
||||
// depends on attributes, the C component of it's parent and a u8 context
|
||||
#[parent_dep_state(u8, C)]
|
||||
#[parent_dep_state(C, u8)]
|
||||
z: C,
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ struct Z {
|
||||
|
||||
use dioxus_native_core::real_dom_new_api::NodeDepState;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[derive(Default, Clone)]
|
||||
struct A;
|
||||
impl NodeDepState for A {
|
||||
type Ctx = ();
|
||||
@@ -31,7 +31,7 @@ impl NodeDepState for A {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[derive(Default, Clone)]
|
||||
struct B;
|
||||
impl ChildDepState for B {
|
||||
type Ctx = i32;
|
||||
@@ -46,7 +46,7 @@ impl ChildDepState for B {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[derive(Default, Clone)]
|
||||
struct C;
|
||||
impl ParentDepState for C {
|
||||
type Ctx = u8;
|
||||
@@ -54,7 +54,7 @@ impl ParentDepState for C {
|
||||
fn reduce(
|
||||
&mut self,
|
||||
_: dioxus_native_core::real_dom_new_api::NodeView,
|
||||
_: &Self::DepState,
|
||||
_: Option<&Self::DepState>,
|
||||
_: &u8,
|
||||
) -> bool {
|
||||
todo!()
|
||||
|
||||
@@ -1,60 +1,125 @@
|
||||
use anymap::AnyMap;
|
||||
use dioxus_core::VNode;
|
||||
use dioxus_core::*;
|
||||
use dioxus_core_macro::*;
|
||||
use dioxus_html as dioxus_elements;
|
||||
use dioxus_native_core::real_dom::*;
|
||||
use dioxus_native_core::real_dom_new_api::{
|
||||
AttributeMask, ChildDepState, NodeDepState, NodeMask, NodeView, ParentDepState, State,
|
||||
};
|
||||
use dioxus_native_core_macro::State;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
struct CallCounter(u32);
|
||||
impl BubbledUpState for CallCounter {
|
||||
#[derive(Debug, Clone, Default, State)]
|
||||
struct CallCounterState {
|
||||
#[child_dep_state(ChildDepCallCounter)]
|
||||
child_counter: ChildDepCallCounter,
|
||||
#[parent_dep_state(ParentDepCallCounter)]
|
||||
parent_counter: ParentDepCallCounter,
|
||||
#[node_dep_state()]
|
||||
node_counter: NodeDepCallCounter,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct ChildDepCallCounter(u32);
|
||||
impl ChildDepState for ChildDepCallCounter {
|
||||
type Ctx = ();
|
||||
|
||||
fn reduce<'a, I>(&mut self, _children: I, _vnode: &VNode, _ctx: &mut Self::Ctx)
|
||||
where
|
||||
I: Iterator<Item = &'a Self>,
|
||||
Self: 'a,
|
||||
{
|
||||
type DepState = Self;
|
||||
const NODE_MASK: NodeMask = NodeMask::ALL;
|
||||
fn reduce(
|
||||
&mut self,
|
||||
_node: NodeView,
|
||||
_children: Vec<&Self::DepState>,
|
||||
_ctx: &Self::Ctx,
|
||||
) -> bool {
|
||||
self.0 += 1;
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl PushedDownState for CallCounter {
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct ParentDepCallCounter(u32);
|
||||
impl ParentDepState for ParentDepCallCounter {
|
||||
type Ctx = ();
|
||||
|
||||
fn reduce(&mut self, _parent: Option<&Self>, _vnode: &VNode, _ctx: &mut Self::Ctx) {
|
||||
type DepState = Self;
|
||||
const NODE_MASK: NodeMask = NodeMask::ALL;
|
||||
fn reduce(
|
||||
&mut self,
|
||||
_node: NodeView,
|
||||
_parent: Option<&Self::DepState>,
|
||||
_ctx: &Self::Ctx,
|
||||
) -> bool {
|
||||
self.0 += 1;
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct NodeDepCallCounter(u32);
|
||||
impl NodeDepState for NodeDepCallCounter {
|
||||
type Ctx = ();
|
||||
const NODE_MASK: NodeMask = NodeMask::ALL;
|
||||
fn reduce(&mut self, _node: NodeView, _ctx: &Self::Ctx) -> bool {
|
||||
self.0 += 1;
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
struct BubbledUpStateTester(String, Vec<Box<BubbledUpStateTester>>);
|
||||
impl BubbledUpState for BubbledUpStateTester {
|
||||
struct BubbledUpStateTester(Option<String>, Vec<Box<BubbledUpStateTester>>);
|
||||
impl ChildDepState for BubbledUpStateTester {
|
||||
type Ctx = u32;
|
||||
|
||||
fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, ctx: &mut Self::Ctx)
|
||||
where
|
||||
I: Iterator<Item = &'a Self>,
|
||||
Self: 'a,
|
||||
{
|
||||
type DepState = Self;
|
||||
const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, true, false);
|
||||
fn reduce(&mut self, node: NodeView, children: Vec<&Self::DepState>, ctx: &Self::Ctx) -> bool {
|
||||
assert_eq!(*ctx, 42);
|
||||
*self = BubbledUpStateTester(
|
||||
vnode.mounted_id().to_string(),
|
||||
children.map(|c| Box::new(c.clone())).collect(),
|
||||
node.tag().map(|s| s.to_string()),
|
||||
children.into_iter().map(|c| Box::new(c.clone())).collect(),
|
||||
);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
struct PushedDownStateTester(String, Option<Box<PushedDownStateTester>>);
|
||||
impl PushedDownState for PushedDownStateTester {
|
||||
struct PushedDownStateTester(Option<String>, Option<Box<PushedDownStateTester>>);
|
||||
impl ParentDepState for PushedDownStateTester {
|
||||
type Ctx = u32;
|
||||
|
||||
fn reduce(&mut self, parent: Option<&Self>, vnode: &VNode, ctx: &mut Self::Ctx) {
|
||||
type DepState = Self;
|
||||
const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, true, false);
|
||||
fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, ctx: &Self::Ctx) -> bool {
|
||||
assert_eq!(*ctx, 42);
|
||||
*self = PushedDownStateTester(
|
||||
vnode.mounted_id().to_string(),
|
||||
node.tag().map(|s| s.to_string()),
|
||||
parent.map(|c| Box::new(c.clone())),
|
||||
);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(State, Clone, Default, Debug)]
|
||||
struct StateTester {
|
||||
#[child_dep_state(BubbledUpStateTester, u32)]
|
||||
bubbled: BubbledUpStateTester,
|
||||
#[parent_dep_state(PushedDownStateTester, u32)]
|
||||
pushed: PushedDownStateTester,
|
||||
#[node_dep_state(u32)]
|
||||
node: NodeStateTester,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
struct NodeStateTester(Option<String>, Vec<(String, String)>);
|
||||
impl NodeDepState for NodeStateTester {
|
||||
type Ctx = u32;
|
||||
const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, true, false);
|
||||
fn reduce(&mut self, node: NodeView, ctx: &Self::Ctx) -> bool {
|
||||
assert_eq!(*ctx, 42);
|
||||
*self = NodeStateTester(
|
||||
node.tag().map(|s| s.to_string()),
|
||||
node.attributes()
|
||||
.map(|a| (a.name.to_string(), a.value.to_string()))
|
||||
.collect(),
|
||||
);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,71 +137,68 @@ fn state_initial() {
|
||||
|
||||
let mutations = vdom.create_vnodes(rsx! {
|
||||
div {
|
||||
p{}
|
||||
p{
|
||||
color: "red"
|
||||
}
|
||||
h1{}
|
||||
}
|
||||
});
|
||||
|
||||
let mut dom: RealDom<BubbledUpStateTester, PushedDownStateTester> = RealDom::new();
|
||||
let mut dom: RealDom<StateTester> = RealDom::new();
|
||||
|
||||
let nodes_updated = dom.apply_mutations(vec![mutations]);
|
||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut 42, &mut 42);
|
||||
let mut ctx = AnyMap::new();
|
||||
ctx.insert(42u32);
|
||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, ctx);
|
||||
|
||||
let root_div = &dom[1];
|
||||
assert_eq!(root_div.up_state.0, "1");
|
||||
assert_eq!(root_div.state.bubbled.0, Some("div".to_string()));
|
||||
assert_eq!(
|
||||
root_div.up_state.1,
|
||||
root_div.state.bubbled.1,
|
||||
vec![
|
||||
Box::new(BubbledUpStateTester("2".to_string(), Vec::new())),
|
||||
Box::new(BubbledUpStateTester("3".to_string(), Vec::new()))
|
||||
Box::new(BubbledUpStateTester(Some("p".to_string()), Vec::new())),
|
||||
Box::new(BubbledUpStateTester(Some("h1".to_string()), Vec::new()))
|
||||
]
|
||||
);
|
||||
assert_eq!(root_div.down_state.0, "1");
|
||||
assert_eq!(root_div.down_state.1, None);
|
||||
assert_eq!(root_div.state.pushed.0, Some("div".to_string()));
|
||||
assert_eq!(root_div.state.pushed.1, None);
|
||||
assert_eq!(root_div.state.node.0, Some("div".to_string()));
|
||||
assert_eq!(root_div.state.node.1, vec![]);
|
||||
|
||||
let child_p = &dom[2];
|
||||
assert_eq!(child_p.up_state.0, "2");
|
||||
assert_eq!(child_p.up_state.1, Vec::new());
|
||||
assert_eq!(child_p.down_state.0, "2");
|
||||
assert_eq!(child_p.state.bubbled.0, Some("p".to_string()));
|
||||
assert_eq!(child_p.state.bubbled.1, Vec::new());
|
||||
assert_eq!(child_p.state.pushed.0, Some("p".to_string()));
|
||||
assert_eq!(
|
||||
child_p.down_state.1,
|
||||
Some(Box::new(PushedDownStateTester("1".to_string(), None)))
|
||||
child_p.state.pushed.1,
|
||||
Some(Box::new(PushedDownStateTester(
|
||||
Some("div".to_string()),
|
||||
None
|
||||
)))
|
||||
);
|
||||
assert_eq!(child_p.state.node.0, Some("p".to_string()));
|
||||
assert_eq!(
|
||||
child_p.state.node.1,
|
||||
vec![("color".to_string(), "red".to_string())]
|
||||
);
|
||||
|
||||
let child_h1 = &dom[3];
|
||||
assert_eq!(child_h1.up_state.0, "3");
|
||||
assert_eq!(child_h1.up_state.1, Vec::new());
|
||||
assert_eq!(child_h1.down_state.0, "3");
|
||||
assert_eq!(child_h1.state.bubbled.0, Some("h1".to_string()));
|
||||
assert_eq!(child_h1.state.bubbled.1, Vec::new());
|
||||
assert_eq!(child_h1.state.pushed.0, Some("h1".to_string()));
|
||||
assert_eq!(
|
||||
child_h1.down_state.1,
|
||||
Some(Box::new(PushedDownStateTester("1".to_string(), None)))
|
||||
child_h1.state.pushed.1,
|
||||
Some(Box::new(PushedDownStateTester(
|
||||
Some("div".to_string()),
|
||||
None
|
||||
)))
|
||||
);
|
||||
assert_eq!(child_h1.state.node.0, Some("h1".to_string()));
|
||||
assert_eq!(child_h1.state.node.1, vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_reduce_initally_called_minimally() {
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
struct CallCounter(u32);
|
||||
impl BubbledUpState for CallCounter {
|
||||
type Ctx = ();
|
||||
|
||||
fn reduce<'a, I>(&mut self, _children: I, _vnode: &VNode, _ctx: &mut Self::Ctx)
|
||||
where
|
||||
I: Iterator<Item = &'a Self>,
|
||||
Self: 'a,
|
||||
{
|
||||
self.0 += 1;
|
||||
}
|
||||
}
|
||||
|
||||
impl PushedDownState for CallCounter {
|
||||
type Ctx = ();
|
||||
|
||||
fn reduce(&mut self, _parent: Option<&Self>, _vnode: &VNode, _ctx: &mut Self::Ctx) {
|
||||
self.0 += 1;
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn Base(cx: Scope) -> Element {
|
||||
rsx!(cx, div {
|
||||
@@ -178,14 +240,15 @@ fn state_reduce_initally_called_minimally() {
|
||||
}
|
||||
});
|
||||
|
||||
let mut dom: RealDom<CallCounter, CallCounter> = RealDom::new();
|
||||
let mut dom: RealDom<CallCounterState> = RealDom::new();
|
||||
|
||||
let nodes_updated = dom.apply_mutations(vec![mutations]);
|
||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut (), &mut ());
|
||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
|
||||
|
||||
dom.traverse_depth_first(|n| {
|
||||
assert_eq!(n.up_state.0, 1);
|
||||
assert_eq!(n.down_state.0, 1);
|
||||
assert_eq!(n.state.child_counter.0, 1);
|
||||
assert_eq!(n.state.parent_counter.0, 1);
|
||||
assert_eq!(n.state.node_counter.0, 1);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -194,6 +257,7 @@ fn state_reduce_down_called_minimally_on_update() {
|
||||
#[allow(non_snake_case)]
|
||||
fn Base(cx: Scope) -> Element {
|
||||
rsx!(cx, div {
|
||||
width: "100%",
|
||||
div{
|
||||
div{
|
||||
p{}
|
||||
@@ -233,10 +297,10 @@ fn state_reduce_down_called_minimally_on_update() {
|
||||
}
|
||||
});
|
||||
|
||||
let mut dom: RealDom<CallCounter, CallCounter> = RealDom::new();
|
||||
let mut dom: RealDom<CallCounterState> = RealDom::new();
|
||||
|
||||
let nodes_updated = dom.apply_mutations(vec![mutations]);
|
||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut (), &mut ());
|
||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
|
||||
let nodes_updated = dom.apply_mutations(vec![Mutations {
|
||||
edits: vec![DomEdit::SetAttribute {
|
||||
root: 1,
|
||||
@@ -247,11 +311,13 @@ fn state_reduce_down_called_minimally_on_update() {
|
||||
dirty_scopes: fxhash::FxHashSet::default(),
|
||||
refs: Vec::new(),
|
||||
}]);
|
||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut (), &mut ());
|
||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
|
||||
|
||||
dom.traverse_depth_first(|n| {
|
||||
assert_eq!(n.down_state.0, 2);
|
||||
println!("{:?}", n.state);
|
||||
// assert_eq!(n.state.parent_counter.0, 2);
|
||||
});
|
||||
panic!()
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -259,6 +325,7 @@ fn state_reduce_up_called_minimally_on_update() {
|
||||
#[allow(non_snake_case)]
|
||||
fn Base(cx: Scope) -> Element {
|
||||
rsx!(cx, div {
|
||||
width: "100%",
|
||||
div{
|
||||
div{
|
||||
p{}
|
||||
@@ -298,10 +365,10 @@ fn state_reduce_up_called_minimally_on_update() {
|
||||
}
|
||||
});
|
||||
|
||||
let mut dom: RealDom<CallCounter, CallCounter> = RealDom::new();
|
||||
let mut dom: RealDom<CallCounterState> = RealDom::new();
|
||||
|
||||
let nodes_updated = dom.apply_mutations(vec![mutations]);
|
||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut (), &mut ());
|
||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
|
||||
let nodes_updated = dom.apply_mutations(vec![Mutations {
|
||||
edits: vec![DomEdit::SetAttribute {
|
||||
root: 4,
|
||||
@@ -312,9 +379,11 @@ fn state_reduce_up_called_minimally_on_update() {
|
||||
dirty_scopes: fxhash::FxHashSet::default(),
|
||||
refs: Vec::new(),
|
||||
}]);
|
||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut (), &mut ());
|
||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
|
||||
|
||||
dom.traverse_depth_first(|n| {
|
||||
assert_eq!(n.up_state.0, if n.id.0 > 4 { 1 } else { 2 });
|
||||
println!("{:?}", n.state);
|
||||
// assert_eq!(n.state.child_counter.0, if n.id.0 > 4 { 1 } else { 2 });
|
||||
});
|
||||
panic!()
|
||||
}
|
||||
@@ -25,3 +25,4 @@ futures = "0.3.19"
|
||||
stretch2 = { git = "https://github.com/DioxusLabs/stretch" }
|
||||
smallvec = "1.6"
|
||||
fxhash = "0.2"
|
||||
anymap = "0.12.1"
|
||||
@@ -6,7 +6,9 @@ use crossterm::{
|
||||
};
|
||||
use dioxus_core::exports::futures_channel::mpsc::unbounded;
|
||||
use dioxus_core::*;
|
||||
use dioxus_native_core::real_dom::RealDom;
|
||||
use dioxus_native_core::{
|
||||
dioxus_native_core_macro::State, real_dom::RealDom, real_dom_new_api::State,
|
||||
};
|
||||
use futures::{
|
||||
channel::mpsc::{UnboundedReceiver, UnboundedSender},
|
||||
pin_mut, StreamExt,
|
||||
@@ -29,6 +31,9 @@ pub use config::*;
|
||||
pub use hooks::*;
|
||||
pub use render::*;
|
||||
|
||||
#[derive(Debug, Clone, State, Default)]
|
||||
struct NodeState {}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TuiContext {
|
||||
tx: UnboundedSender<InputEvent>,
|
||||
|
||||
Reference in new issue
Block a user