WIP update tests

This commit is contained in:
Evan Almloff committed 2022-04-12 07:51:57 -05:00
1 parent 13a99bce9c
commit 92010b0bab
10 files changed
+597 -244

No files matched your search

+1
View File
@@ -1,3 +1,4 @@
pub mod layout_attributes;
pub mod real_dom;
pub mod real_dom_new_api;
pub use dioxus_native_core_macro;
+184 -90
View File
@@ -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,
{
}
}
+170 -43
View File
@@ -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)