bugfixes, testing and refactoring

This commit is contained in:
Evan Almloff committed 2022-03-26 20:32:49 -05:00
1 parent 6adfa8805c
commit 7f4e257757
20 files changed
+3888 -739

No files matched your search

+580
View File
@@ -0,0 +1,580 @@
use fxhash::{FxHashMap, FxHashSet};
use std::{
collections::VecDeque,
ops::{Index, IndexMut},
};
use dioxus_core::{ElementId, Mutations, VNode, VirtualDom};
/// A tree that can sync with dioxus mutations backed by a hashmap.
/// Intended for use in lazy native renderers with a state that passes from parrent to children and or accumulates state from children to parrents.
/// To get started implement [PushedDownState] and or [BubbledUpState] and call [Tree::apply_mutations] and [Tree::update_state].
#[derive(Debug)]
pub struct ClientTree<US: BubbledUpState = (), DS: PushedDownState = ()> {
root: usize,
nodes: Vec<Option<TreeNode<US, DS>>>,
nodes_listening: FxHashMap<&'static str, FxHashSet<usize>>,
node_stack: smallvec::SmallVec<[usize; 10]>,
}
impl<US: BubbledUpState, DS: PushedDownState> ClientTree<US, DS> {
pub fn new() -> ClientTree<US, DS> {
ClientTree {
root: 0,
nodes: {
let mut v = Vec::new();
v.push(Some(TreeNode::new(
0,
TreeNodeType::Element {
tag: "Root".to_string(),
namespace: Some("Root"),
children: Vec::new(),
},
)));
v
},
nodes_listening: FxHashMap::default(),
node_stack: smallvec::SmallVec::new(),
}
}
/// Updates the tree, 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> {
let mut nodes_updated = Vec::new();
for mutations in mutations_vec {
for e in mutations.edits {
use dioxus_core::DomEdit::*;
match e {
PushRoot { root } => self.node_stack.push(root as usize),
AppendChildren { many } => {
let target = if self.node_stack.len() >= many as usize + 1 {
*self
.node_stack
.get(self.node_stack.len() - (many as usize + 1))
.unwrap()
} else {
0
};
let drained: Vec<_> = self
.node_stack
.drain(self.node_stack.len() - many as usize..)
.collect();
for ns in drained {
self.link_child(ns, target).unwrap();
nodes_updated.push(ns);
}
}
ReplaceWith { root, m } => {
let root = self.remove(root as usize).unwrap();
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);
self.link_child(ns, target).unwrap();
}
}
InsertAfter { root, n } => {
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);
self.link_child(ns, target).unwrap();
}
}
InsertBefore { root, n } => {
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);
self.link_child(ns, target).unwrap();
}
}
Remove { root } => {
if let Some(parent) = self[root as usize].parent {
nodes_updated.push(parent.0);
}
self.remove(root as usize).unwrap();
}
CreateTextNode { root, text } => {
let n = TreeNode::new(
root,
TreeNodeType::Text {
text: text.to_string(),
},
);
self.insert(n);
self.node_stack.push(root as usize)
}
CreateElement { root, tag } => {
let n = TreeNode::new(
root,
TreeNodeType::Element {
tag: tag.to_string(),
namespace: None,
children: Vec::new(),
},
);
self.insert(n);
self.node_stack.push(root as usize)
}
CreateElementNs { root, tag, ns } => {
let n = TreeNode::new(
root,
TreeNodeType::Element {
tag: tag.to_string(),
namespace: Some(ns),
children: Vec::new(),
},
);
self.insert(n);
self.node_stack.push(root as usize)
}
CreatePlaceholder { root } => {
let n = TreeNode::new(root, TreeNodeType::Placeholder);
self.insert(n);
self.node_stack.push(root as usize)
}
NewEventListener {
event_name,
scope: _,
root,
} => {
if let Some(v) = self.nodes_listening.get_mut(event_name) {
v.insert(root as usize);
} else {
let mut hs = FxHashSet::default();
hs.insert(root as usize);
self.nodes_listening.insert(event_name, hs);
}
}
RemoveEventListener { root, event } => {
let v = self.nodes_listening.get_mut(event).unwrap();
v.remove(&(root as usize));
}
SetText {
root,
text: new_text,
} => {
let target = &mut self[root as usize];
nodes_updated.push(root as usize);
match &mut target.node_type {
TreeNodeType::Text { text } => {
*text = new_text.to_string();
}
_ => unreachable!(),
}
}
SetAttribute { root, .. } => {
nodes_updated.push(root as usize);
}
RemoveAttribute { root, .. } => {
nodes_updated.push(root as usize);
}
}
}
}
nodes_updated
}
/// Seperated from apply_mutations because Mutations require a mutable reference to the VirtualDom.
pub fn update_state(
&mut self,
vdom: &VirtualDom,
nodes_updated: Vec<usize>,
us_ctx: &mut US::Ctx,
ds_ctx: &mut DS::Ctx,
) -> Option<FxHashSet<usize>> {
let mut to_rerender = FxHashSet::default();
let mut nodes_updated: Vec<_> = nodes_updated
.into_iter()
.map(|id| (id, self[id].height))
.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();
// 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;
let up_state = &mut node.up_state;
let children = match node_type {
TreeNodeType::Element { children, .. } => Some(children),
_ => None,
};
// todo: reduce cloning state
let old = up_state.clone();
let mut new = up_state.clone();
let parent = node.parent.clone();
new.reduce(
children
.unwrap_or(&Vec::new())
.clone()
.iter()
.map(|c| &self[*c].up_state),
vnode,
us_ctx,
);
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)
});
// 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));
}
}
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];
match &node.node_type {
TreeNodeType::Element { children, .. } => {
for c in children {
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));
}
}
}
_ => (),
};
node.down_state = new;
}
}
Some(to_rerender)
}
fn link_child(&mut self, child_id: usize, parent_id: usize) -> Option<()> {
debug_assert_ne!(child_id, parent_id);
let parent = &mut self[parent_id];
parent.add_child(ElementId(child_id));
let parent_height = parent.height + 1;
self[child_id].set_parent(ElementId(parent_id));
self.increase_height(child_id, parent_height);
Some(())
}
fn increase_height(&mut self, id: usize, amount: u16) {
let n = &mut self[id];
n.height += amount;
match &n.node_type {
TreeNodeType::Element { children, .. } => {
for c in children.clone() {
self.increase_height(c.0, amount);
}
}
_ => (),
}
}
fn remove(&mut self, id: usize) -> Option<TreeNode<US, DS>> {
// We do not need to remove the node from the parent's children list for children.
fn inner<US: BubbledUpState, DS: PushedDownState>(
tree: &mut ClientTree<US, DS>,
id: usize,
) -> Option<TreeNode<US, DS>> {
let mut node = tree.nodes[id as usize].take()?;
match &mut node.node_type {
TreeNodeType::Element { children, .. } => {
for c in children {
inner(tree, c.0)?;
}
}
_ => (),
}
Some(node)
}
let mut node = self.nodes[id as usize].take()?;
if let Some(parent) = node.parent {
let parent = &mut self[parent];
parent.remove_child(ElementId(id));
}
match &mut node.node_type {
TreeNodeType::Element { children, .. } => {
for c in children {
inner(self, c.0)?;
}
}
_ => (),
}
Some(node)
}
fn insert(&mut self, node: TreeNode<US, DS>) {
let current_len = self.nodes.len();
let id = node.id.0;
if current_len - 1 < node.id.0 {
// self.nodes.reserve(1 + id - current_len);
self.nodes.extend((0..1 + id - current_len).map(|_| None));
}
self.nodes[id] = Some(node);
}
pub fn get(&self, id: usize) -> Option<&TreeNode<US, DS>> {
self.nodes.get(id)?.as_ref()
}
pub fn get_mut(&mut self, id: usize) -> Option<&mut TreeNode<US, DS>> {
self.nodes.get_mut(id)?.as_mut()
}
pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&TreeNode<US, DS>> {
if let Some(nodes) = self.nodes_listening.get(event) {
let mut listening: Vec<_> = nodes.iter().map(|id| &self[*id]).collect();
listening.sort_by(|n1, n2| (n1.height).cmp(&n2.height).reverse());
listening
} else {
Vec::new()
}
}
/// Check if the tree contains a node and its children.
pub fn contains_node(&self, node: &VNode) -> bool {
match node {
VNode::Component(_) => {
todo!()
}
VNode::Element(e) => {
if let Some(id) = e.id.get() {
let tree_node = &self[id];
match &tree_node.node_type {
TreeNodeType::Element {
tag,
namespace,
children,
} => {
tag == e.tag
&& namespace == &e.namespace
&& children.iter().copied().collect::<FxHashSet<_>>()
== e.children
.iter()
.map(|c| c.mounted_id())
.collect::<FxHashSet<_>>()
&& e.children.iter().all(|c| {
self.contains_node(c)
&& self[c.mounted_id()].parent == e.id.get()
})
}
_ => false,
}
} else {
true
}
}
VNode::Fragment(f) => f.children.iter().all(|c| self.contains_node(c)),
VNode::Placeholder(_) => true,
VNode::Text(t) => {
if let Some(id) = t.id.get() {
let tree_node = &self[id];
match &tree_node.node_type {
TreeNodeType::Text { text } => t.text == text,
_ => false,
}
} else {
true
}
}
}
}
/// Return the number of nodes in the tree.
pub fn size(&self) -> usize {
// The tree has a root node, ignore it.
self.nodes.iter().filter(|n| n.is_some()).count() - 1
}
/// Returns the id of the root node.
pub fn root_id(&self) -> usize {
self.root
}
/// Call a function for each node in the tree, depth first.
pub fn traverse(&self, mut f: impl FnMut(&TreeNode<US, DS>)) {
fn inner<US: BubbledUpState, DS: PushedDownState>(
tree: &ClientTree<US, DS>,
id: ElementId,
f: &mut impl FnMut(&TreeNode<US, DS>),
) {
let node = &tree[id];
f(node);
match &node.node_type {
TreeNodeType::Element { children, .. } => {
for c in children {
inner(tree, *c, f);
}
}
_ => (),
}
}
match &self[self.root].node_type {
TreeNodeType::Element { children, .. } => {
for c in children {
inner(self, *c, &mut f);
}
}
_ => (),
}
}
}
impl<US: BubbledUpState, DS: PushedDownState> Index<usize> for ClientTree<US, DS> {
type Output = TreeNode<US, DS>;
fn index(&self, idx: usize) -> &Self::Output {
self.get(idx).expect("Node does not exist")
}
}
impl<US: BubbledUpState, DS: PushedDownState> Index<ElementId> for ClientTree<US, DS> {
type Output = TreeNode<US, DS>;
fn index(&self, idx: ElementId) -> &Self::Output {
&self[idx.0]
}
}
impl<US: BubbledUpState, DS: PushedDownState> IndexMut<usize> for ClientTree<US, DS> {
fn index_mut(&mut self, idx: usize) -> &mut Self::Output {
self.get_mut(idx).expect("Node does not exist")
}
}
impl<US: BubbledUpState, DS: PushedDownState> IndexMut<ElementId> for ClientTree<US, DS> {
fn index_mut(&mut self, idx: ElementId) -> &mut Self::Output {
&mut self[idx.0]
}
}
/// The node is stored client side and stores only basic data about the node. For more complete information about the node see [`TreeNode::element`].
#[derive(Debug, Clone)]
pub struct TreeNode<US: BubbledUpState, DS: PushedDownState> {
/// The id of the node this node was created from.
pub id: ElementId,
/// The parent id of the node.
pub parent: Option<ElementId>,
/// State of the node that is bubbled up to its parents. The state must depend only on the node and its children.
pub up_state: US,
/// State of the node that is pushed down to the children. The state must depend only on the node itself and its parent.
pub down_state: DS,
/// Additional inforation specific to the node type
pub node_type: TreeNodeType,
/// The number of parents before the root node. The root node has height 1.
pub height: u16,
}
#[derive(Debug, Clone)]
pub enum TreeNodeType {
Text {
text: String,
},
Element {
tag: String,
namespace: Option<&'static str>,
children: Vec<ElementId>,
},
Placeholder,
}
impl<US: BubbledUpState, DS: PushedDownState> TreeNode<US, DS> {
fn new(id: u64, node_type: TreeNodeType) -> Self {
TreeNode {
id: ElementId(id as usize),
parent: None,
node_type,
down_state: DS::default(),
up_state: US::default(),
height: 0,
}
}
/// Returns a reference to the element that this node refrences.
pub fn element<'b>(&self, vdom: &'b VirtualDom) -> &'b VNode<'b> {
vdom.get_element(self.id).unwrap()
}
fn add_child(&mut self, child: ElementId) {
match &mut self.node_type {
TreeNodeType::Element { children, .. } => {
children.push(child);
}
_ => (),
}
}
fn remove_child(&mut self, child: ElementId) {
match &mut self.node_type {
TreeNodeType::Element { children, .. } => {
children.retain(|c| c != &child);
}
_ => (),
}
}
fn set_parent(&mut self, parent: ElementId) {
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 non-flexbox div'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,
{
}
}
+2 -3
View File
@@ -1,4 +1,4 @@
use crate::BubbledUpState;
use crate::client_tree::BubbledUpState;
use dioxus_core::*;
use stretch2::prelude::*;
@@ -58,7 +58,7 @@ impl BubbledUpState for StretchLayout {
apply_layout_attributes(name, value, &mut style);
}
// todo: move
// the root node fills the entire area
if el.id.get() == Some(ElementId(0)) {
apply_layout_attributes("width", "100%", &mut style);
apply_layout_attributes("height", "100%", &mut style);
@@ -69,7 +69,6 @@ impl BubbledUpState for StretchLayout {
for l in children {
child_layout.push(l.node.unwrap());
}
child_layout.reverse();
if let Some(n) = self.node {
if &stretch.children(n).unwrap() != &child_layout {
+1 -525
View File
@@ -1,527 +1,3 @@
use std::collections::{HashMap, HashSet, VecDeque};
use dioxus_core::{ElementId, Mutations, VNode, VirtualDom};
pub mod client_tree;
pub mod layout;
pub mod layout_attributes;
/// A tree that can sync with dioxus mutations backed by a hashmap.
/// Intended for use in lazy native renderers with a state that passes from parrent to children and or accumulates state from children to parrents.
/// To get started implement [PushedDownState] and or [BubbledUpState] and call [Tree::apply_mutations] and [Tree::update_state].
#[derive(Debug)]
pub struct Tree<US: BubbledUpState = (), DS: PushedDownState = ()> {
pub root: usize,
pub nodes: Vec<Option<TreeNode<US, DS>>>,
pub nodes_listening: HashMap<&'static str, HashSet<usize>>,
}
impl<US: BubbledUpState, DS: PushedDownState> Tree<US, DS> {
pub fn new() -> Tree<US, DS> {
Tree {
root: 0,
nodes: {
let mut v = Vec::new();
v.push(Some(TreeNode::new(
0,
TreeNodeType::Element {
tag: "Root".to_string(),
namespace: Some("Root"),
children: Vec::new(),
},
)));
v
},
nodes_listening: HashMap::new(),
}
}
/// Updates the tree, up and down state and return a set of nodes that were updated
pub fn apply_mutations(&mut self, mutations_vec: Vec<Mutations>) -> Vec<usize> {
let mut nodes_updated = Vec::new();
for mutations in mutations_vec {
let mut node_stack: smallvec::SmallVec<[usize; 5]> = smallvec::SmallVec::new();
for e in mutations.edits {
use dioxus_core::DomEdit::*;
match e {
PushRoot { root } => node_stack.push(root as usize),
AppendChildren { many } => {
let target = if node_stack.len() >= many as usize + 1 {
*node_stack
.get(node_stack.len() - (many as usize + 1))
.unwrap()
} else {
0
};
for ns in node_stack.drain(node_stack.len() - many as usize..).rev() {
self.link_child(ns, target).unwrap();
nodes_updated.push(ns);
}
}
ReplaceWith { root, m } => {
let root = self.remove(root as usize).unwrap();
let target = root.parent.unwrap().0;
for ns in node_stack.drain(0..m as usize) {
nodes_updated.push(ns);
self.link_child(ns, target).unwrap();
}
}
InsertAfter { root, n } => {
let target = self.get(root as usize).parent.unwrap().0;
for ns in node_stack.drain(0..n as usize) {
nodes_updated.push(ns);
self.link_child(ns, target).unwrap();
}
}
InsertBefore { root, n } => {
let target = self.get(root as usize).parent.unwrap().0;
for ns in node_stack.drain(0..n as usize) {
nodes_updated.push(ns);
self.link_child(ns, target).unwrap();
}
}
Remove { root } => {
if let Some(parent) = self.get(root as usize).parent {
nodes_updated.push(parent.0);
}
self.remove(root as usize).unwrap();
}
CreateTextNode { root, text } => {
let n = TreeNode::new(
root,
TreeNodeType::Text {
text: text.to_string(),
},
);
self.insert(n);
node_stack.push(root as usize)
}
CreateElement { root, tag } => {
let n = TreeNode::new(
root,
TreeNodeType::Element {
tag: tag.to_string(),
namespace: None,
children: Vec::new(),
},
);
self.insert(n);
node_stack.push(root as usize)
}
CreateElementNs { root, tag, ns } => {
let n = TreeNode::new(
root,
TreeNodeType::Element {
tag: tag.to_string(),
namespace: Some(ns),
children: Vec::new(),
},
);
self.insert(n);
node_stack.push(root as usize)
}
CreatePlaceholder { root } => {
let n = TreeNode::new(root, TreeNodeType::Placeholder);
self.insert(n);
node_stack.push(root as usize)
}
NewEventListener {
event_name,
scope: _,
root,
} => {
if let Some(v) = self.nodes_listening.get_mut(event_name) {
v.insert(root as usize);
} else {
let mut hs = HashSet::new();
hs.insert(root as usize);
self.nodes_listening.insert(event_name, hs);
}
}
RemoveEventListener { root, event } => {
let v = self.nodes_listening.get_mut(event).unwrap();
v.remove(&(root as usize));
}
SetText {
root,
text: new_text,
} => {
let target = self.get_mut(root as usize);
nodes_updated.push(root as usize);
match &mut target.node_type {
TreeNodeType::Text { text } => {
*text = new_text.to_string();
}
_ => unreachable!(),
}
}
SetAttribute { root, .. } => {
nodes_updated.push(root as usize);
}
RemoveAttribute { root, .. } => {
nodes_updated.push(root as usize);
}
}
}
}
nodes_updated
}
pub fn update_state(
&mut self,
vdom: &VirtualDom,
nodes_updated: Vec<usize>,
us_ctx: &mut US::Ctx,
ds_ctx: &mut DS::Ctx,
) -> Option<HashSet<usize>> {
let mut to_rerender = HashSet::new();
let mut nodes_updated: Vec<_> = nodes_updated
.into_iter()
.map(|id| (id, self.get(id).height))
.collect();
nodes_updated.dedup();
nodes_updated.sort_by_key(|(_, h)| *h);
// bubble up state. To avoid calling reduce more times than nessisary start from the bottom and go up.
// todo: this is called multable times per element?
let mut to_bubble: VecDeque<_> = nodes_updated.clone().into();
while let Some((id, height)) = to_bubble.pop_back() {
let node = self.get_mut(id as usize);
let vnode = node.element(vdom);
let node_type = &node.node_type;
let up_state = &mut node.up_state;
let children = match node_type {
TreeNodeType::Element { children, .. } => Some(children),
_ => None,
};
// todo: reduce cloning state
let old = up_state.clone();
let mut new = up_state.clone();
let parent = node.parent.clone();
new.reduce(
children
.unwrap_or(&Vec::new())
.clone()
.iter()
.map(|c| &self.get(c.0).up_state),
vnode,
us_ctx,
);
if new != old {
to_rerender.insert(id);
if let Some(p) = parent {
let i = to_bubble.partition_point(|(_, h)| *h < height - 1);
// make sure the parent is not already queued
if i >= to_bubble.len() || to_bubble.get(i).unwrap().0 != p.0 {
to_bubble.insert(i, (p.0, height - 1));
}
}
let node = self.get_mut(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.get_mut(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.map(|e| &self.get(e.0).down_state),
vnode,
ds_ctx,
);
if new != old {
to_rerender.insert(id);
let node = self.get_mut(id as usize);
match &node.node_type {
TreeNodeType::Element { children, .. } => {
for c in children {
let i = to_bubble.partition_point(|(_, h)| *h < height + 1);
to_bubble.insert(i, (c.0, height + 1));
}
}
_ => (),
};
node.down_state = new;
}
}
Some(to_rerender)
}
fn link_child(&mut self, child_id: usize, parent_id: usize) -> Option<()> {
debug_assert_ne!(child_id, parent_id);
let parent = self.get_mut(parent_id);
parent.add_child(ElementId(child_id));
let parent_height = parent.height + 1;
self.get_mut(child_id).set_parent(ElementId(parent_id));
self.increase_height(child_id, parent_height);
Some(())
}
fn increase_height(&mut self, id: usize, amount: u16) {
let n = self.get_mut(id);
n.height += amount;
match &n.node_type {
TreeNodeType::Element { children, .. } => {
for c in children.clone() {
self.increase_height(c.0, amount);
}
}
_ => (),
}
}
fn remove(&mut self, id: usize) -> Option<TreeNode<US, DS>> {
let mut node = self.nodes.get_mut(id as usize).unwrap().take().unwrap();
match &mut node.node_type {
TreeNodeType::Element { children, .. } => {
for c in children {
self.remove(c.0).unwrap();
}
}
_ => (),
}
Some(node)
}
fn insert(&mut self, node: TreeNode<US, DS>) {
let current_len = self.nodes.len();
let id = node.id.0;
if current_len - 1 < node.id.0 {
// self.nodes.reserve(1 + id - current_len);
self.nodes.extend((0..1 + id - current_len).map(|_| None));
}
self.nodes[id] = Some(node);
}
pub fn get(&self, id: usize) -> &TreeNode<US, DS> {
self.nodes.get(id).unwrap().as_ref().unwrap()
}
fn get_mut(&mut self, id: usize) -> &mut TreeNode<US, DS> {
self.nodes.get_mut(id).unwrap().as_mut().unwrap()
}
pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&TreeNode<US, DS>> {
if let Some(nodes) = self.nodes_listening.get(event) {
let mut listening: Vec<_> = nodes.iter().map(|id| self.get(*id)).collect();
listening.sort_by(|n1, n2| (n1.height).cmp(&n2.height).reverse());
listening
} else {
Vec::new()
}
}
}
/// The node is stored client side and stores render data
#[derive(Debug, Clone)]
pub struct TreeNode<US: BubbledUpState, DS: PushedDownState> {
pub id: ElementId,
pub parent: Option<ElementId>,
pub up_state: US,
pub down_state: DS,
pub node_type: TreeNodeType,
pub height: u16,
}
#[derive(Debug, Clone)]
pub enum TreeNodeType {
Text {
text: String,
},
Element {
tag: String,
namespace: Option<&'static str>,
children: Vec<ElementId>,
},
Placeholder,
}
impl<US: BubbledUpState, DS: PushedDownState> TreeNode<US, DS> {
fn new(id: u64, node_type: TreeNodeType) -> Self {
TreeNode {
id: ElementId(id as usize),
parent: None,
node_type,
down_state: DS::default(),
up_state: US::default(),
height: 0,
}
}
fn element<'b>(&self, vdom: &'b VirtualDom) -> &'b VNode<'b> {
vdom.get_element(self.id).unwrap()
}
fn add_child(&mut self, child: ElementId) {
match &mut self.node_type {
TreeNodeType::Element { children, .. } => {
children.push(child);
}
_ => (),
}
}
fn set_parent(&mut self, parent: ElementId) {
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 {
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 non-flexbox div'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 {
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,
{
}
}
// /// The nodes that need to be updated after updating a state.
// pub struct Update {
// children: bool,
// parent: bool,
// }
// /// This state is derived from children and parents.
// /// Called when the current node's node properties are modified or a parent or child's [State] is modified.
// /// Unlike [BubbledUpState] and [PushedDownState] this may be called mulable times per update. Prefer those over this.
// pub trait State: Default + PartialEq + Clone {
// fn reduce<'a, I>(&mut self, parent: Option<&Self>, children: I, vnode: &VNode) -> Update
// where
// I: Iterator<Item = &'a Self>,
// Self: 'a;
// }
// impl State for () {
// fn reduce<'a, I>(&mut self, _parent: Option<&Self>, _children: I, _vnode: &VNode) -> Update
// where
// I: Iterator<Item = &'a Self>,
// Self: 'a,
// {
// Update {
// children: false,
// parent: false,
// }
// }
// }
#[test]
fn test_insert() {
use dioxus_core::*;
use dioxus_core_macro::*;
use dioxus_html as dioxus_elements;
#[derive(Debug, Default, PartialEq, Clone)]
struct Rect {
x: u16,
y: u16,
width: u16,
height: u16,
}
impl BubbledUpState for Rect {
type Ctx = ();
fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, _ctx: &mut Self::Ctx)
where
I: Iterator<Item = &'a Self>,
Self: 'a,
{
match vnode {
VNode::Text(t) => {
*self = Rect {
x: 0,
y: 0,
width: t.text.len().try_into().unwrap(),
height: 1,
};
return;
}
_ => (),
}
self.width = 2;
self.height = 2;
for c in children {
println!("\t{c:?}");
self.width = self.width.max(c.width);
self.height += c.height;
}
}
}
#[allow(non_snake_case)]
fn Base(cx: Scope) -> Element {
rsx!(cx, div {})
}
let vdom = VirtualDom::new(Base);
let node_1 = rsx! {
div{
div{
"hello"
"hello world"
}
}
};
let node_2 = rsx! {
div{
div{
"hello"
"hello world"
}
}
};
let mutations = vdom.diff_lazynodes(node_1, node_2);
let mut tree: Tree<Rect, ()> = Tree {
root: 0,
nodes: {
let mut v = Vec::new();
v.push(Some(TreeNode::new(
0,
TreeNodeType::Element {
tag: "Root".to_string(),
namespace: Some("Root"),
children: Vec::new(),
},
)));
v
},
nodes_listening: HashMap::new(),
};
println!("{:?}", mutations);
let to_update = tree.apply_mutations(vec![mutations.0]);
let to_rerender = tree
.update_state(&vdom, to_update, &mut (), &mut ())
.unwrap();
println!("{to_rerender:?}");
panic!("{}", format!("{:?}", &tree.nodes[1..]).replace("\\", ""));
}