From 078b8ba833213b27e8626ffe6b0f94cdbc16faa1 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 24 Nov 2022 10:56:44 -0600 Subject: [PATCH 01/93] work on seperating tree struture from realdom --- packages/native-core/Cargo.toml | 3 +- packages/native-core/src/lib.rs | 45 +- packages/native-core/src/node.rs | 101 +++ packages/native-core/src/node_ref.rs | 9 +- packages/native-core/src/real_dom.rs | 141 +---- packages/native-core/src/state.rs | 3 +- packages/native-core/src/traversable.rs | 102 ---- packages/native-core/src/tree.rs | 781 ++++++++++++++++++++++++ packages/native-core/src/utils.rs | 428 +++++++------ 9 files changed, 1119 insertions(+), 494 deletions(-) create mode 100644 packages/native-core/src/node.rs delete mode 100644 packages/native-core/src/traversable.rs create mode 100644 packages/native-core/src/tree.rs diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index 76270671a..09c08bc61 100644 --- a/packages/native-core/Cargo.toml +++ b/packages/native-core/Cargo.toml @@ -20,6 +20,7 @@ smallvec = "1.6" rustc-hash = "1.1.0" anymap = "0.12.1" slab = "0.4" +parking_lot = "0.12.1" [dev-dependencies] -rand = "0.8.5" +rand = "0.8.5" \ No newline at end of file diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index bf24096a3..bdd7e24be 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -1,54 +1,17 @@ -use std::cmp::Ordering; - -use dioxus_core::ElementId; +use tree::NodeId; pub mod layout_attributes; +pub mod node; pub mod node_ref; pub mod real_dom; pub mod state; #[doc(hidden)] pub mod traversable; +pub mod tree; pub mod utils; /// A id for a node that lives in the real dom. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum RealNodeId { - ElementId(ElementId), - UnaccessableId(usize), -} - -impl RealNodeId { - pub fn as_element_id(&self) -> ElementId { - match self { - RealNodeId::ElementId(id) => *id, - RealNodeId::UnaccessableId(_) => panic!("Expected element id"), - } - } - - pub fn as_unaccessable_id(&self) -> usize { - match self { - RealNodeId::ElementId(_) => panic!("Expected unaccessable id"), - RealNodeId::UnaccessableId(id) => *id, - } - } -} - -impl Ord for RealNodeId { - fn cmp(&self, other: &Self) -> Ordering { - self.partial_cmp(other).unwrap() - } -} - -impl PartialOrd for RealNodeId { - fn partial_cmp(&self, other: &Self) -> Option { - match (self, other) { - (Self::ElementId(a), Self::ElementId(b)) => a.0.partial_cmp(&b.0), - (Self::UnaccessableId(a), Self::UnaccessableId(b)) => a.partial_cmp(b), - (Self::ElementId(_), Self::UnaccessableId(_)) => Some(Ordering::Greater), - (Self::UnaccessableId(_), Self::ElementId(_)) => Some(Ordering::Less), - } - } -} +type RealNodeId = NodeId; /// Used in derived state macros #[derive(Eq, PartialEq)] diff --git a/packages/native-core/src/node.rs b/packages/native-core/src/node.rs new file mode 100644 index 000000000..5d6285071 --- /dev/null +++ b/packages/native-core/src/node.rs @@ -0,0 +1,101 @@ +use crate::{state::State, tree::NodeId, RealNodeId}; +use dioxus_core::ElementId; +use rustc_hash::{FxHashMap, FxHashSet}; + +/// The node is stored client side and stores only basic data about the node. +#[derive(Debug, Clone)] +pub struct Node { + /// The transformed state of the node. + pub state: S, + /// The raw data for the node + pub node_data: NodeData, +} + +#[derive(Debug, Clone)] +pub struct NodeData { + /// The id of the node in the vdom. + pub element_id: Option, + /// Additional inforation specific to the node type + pub node_type: NodeType, + /// The number of parents before the root node. The root node has height 1. + pub height: u16, +} + +/// A type of node with data specific to the node type. The types are a subset of the [VNode] types. +#[derive(Debug, Clone)] +pub enum NodeType { + Text { + text: String, + }, + Element { + tag: String, + namespace: Option<&'static str>, + attributes: FxHashMap, + listeners: FxHashSet, + }, + Placeholder, +} + +impl Node { + pub(crate) fn new(node_type: NodeType) -> Self { + Node { + state: S::default(), + node_data: NodeData { + element_id: None, + node_type, + height: 0, + }, + } + } + + /// link a child node + fn add_child(&mut self, child: RealNodeId) { + if let NodeType::Element { children, .. } = &mut self.node_data.node_type { + children.push(child); + } + } + + /// remove a child node + fn remove_child(&mut self, child: RealNodeId) { + if let NodeType::Element { children, .. } = &mut self.node_data.node_type { + children.retain(|c| c != &child); + } + } + + /// link the parent node + fn set_parent(&mut self, parent: RealNodeId) { + self.node_data.parent = Some(parent); + } + + /// get the mounted id of the node + pub fn mounted_id(&self) -> Option { + self.node_data.element_id + } +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct OwnedAttributeDiscription { + pub name: String, + pub namespace: Option, + pub volatile: bool, +} + +/// An attribute on a DOM node, such as `id="my-thing"` or +/// `href="https://example.com"`. +#[derive(Clone, Copy, Debug)] +pub struct OwnedAttributeView<'a> { + /// The discription of the attribute. + pub attribute: &'a OwnedAttributeDiscription, + + /// The value of the attribute. + pub value: &'a OwnedAttributeValue, +} + +#[derive(Clone, Debug)] +pub enum OwnedAttributeValue { + Text(String), + Float(f32), + Int(i32), + Bool(bool), + None, +} diff --git a/packages/native-core/src/node_ref.rs b/packages/native-core/src/node_ref.rs index edbcfa696..671d85663 100644 --- a/packages/native-core/src/node_ref.rs +++ b/packages/native-core/src/node_ref.rs @@ -1,7 +1,8 @@ +use dioxus_core::ElementId; + use crate::{ - real_dom::{NodeData, NodeType, OwnedAttributeView}, + node::{NodeData, NodeType, OwnedAttributeView}, state::union_ordered_iter, - RealNodeId, }; /// A view into a [VNode] with limited access. @@ -21,8 +22,8 @@ impl<'a> NodeView<'a> { } /// Get the id of the node - pub fn id(&self) -> RealNodeId { - self.inner.id.unwrap() + pub fn id(&self) -> Option { + self.inner.element_id } /// Get the tag of the node if the tag is enabled in the mask diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 20a055778..53547de68 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -1,12 +1,14 @@ use anymap::AnyMap; -use dioxus_core::{AttributeDiscription, ElementId, Mutations, OwnedAttributeValue, VNode}; +use dioxus_core::{ElementId, Mutations, VNode}; use rustc_hash::{FxHashMap, FxHashSet}; use slab::Slab; use std::ops::{Index, IndexMut}; +use crate::node::{Node, NodeType}; use crate::node_ref::{AttributeMask, NodeMask}; use crate::state::State; use crate::traversable::Traversable; +use crate::tree::{NodeId, Tree, TreeLike, TreeView}; use crate::RealNodeId; /// A Dom that can sync with the VirtualDom mutations intended for use in lazy renderers. @@ -14,14 +16,10 @@ use crate::RealNodeId; /// To get started implement [crate::state::ParentDepState], [crate::state::NodeDepState], or [crate::state::ChildDepState] and call [RealDom::apply_mutations] to update the dom and [RealDom::update_state] to update the state of the nodes. #[derive(Debug)] pub struct RealDom { - root: usize, - nodes: Vec>>>, - // some nodes do not have an ElementId immediately, those node are stored here - internal_nodes: Slab>>, + tree: Tree>, + /// a map from element id to real node id + node_id_mapping: Vec, nodes_listening: FxHashMap<&'static str, FxHashSet>, - last: Option, - // any nodes that have children queued to be added in the form (parent, children remaining) - parents_queued: Vec<(RealNodeId, u32)>, } impl Default for RealDom { @@ -39,23 +37,15 @@ impl RealDom { listeners: FxHashSet::default(), children: Vec::new(), }); - root.node_data.id = Some(RealNodeId::ElementId(ElementId(0))); + root.node_data.element_id = Some(ElementId(0)); + let tree = Tree::new(root); + let root_id = tree.root(); + tree.get_mut(root_id).unwrap().node_data.id = root_id; RealDom { - root: 0, - nodes: vec![Some(Box::new(root))], - internal_nodes: Slab::new(), + tree, + node_id_mapping: Vec::new(), nodes_listening: FxHashMap::default(), - last: None, - parents_queued: Vec::new(), - } - } - - pub fn resolve_maybe_id(&self, id: Option) -> RealNodeId { - if let Some(id) = id { - RealNodeId::ElementId(ElementId(id as usize)) - } else { - self.last.unwrap() } } @@ -68,7 +58,7 @@ impl RealDom { nodes_updated.push((RealNodeId::ElementId(ElementId(0)), NodeMask::ALL)); for mutations in mutations_vec { for e in mutations.edits { - use dioxus_core::DomEdit::*; + use dioxus_core::Mutation::*; match e { AppendChildren { root, children } => { let target = self.resolve_maybe_id(root); @@ -751,81 +741,6 @@ impl IndexMut for RealDom { } } -/// The node is stored client side and stores only basic data about the node. -#[derive(Debug, Clone)] -pub struct Node { - /// The transformed state of the node. - pub state: S, - /// The raw data for the node - pub node_data: NodeData, -} - -#[derive(Debug, Clone)] -pub struct NodeData { - /// The id of the node this node was created from. - pub id: Option, - /// The parent id of the node. - pub parent: Option, - /// Additional inforation specific to the node type - pub node_type: NodeType, - /// The number of parents before the root node. The root node has height 1. - pub height: u16, -} - -/// A type of node with data specific to the node type. The types are a subset of the [VNode] types. -#[derive(Debug, Clone)] -pub enum NodeType { - Text { - text: String, - }, - Element { - tag: String, - namespace: Option<&'static str>, - attributes: FxHashMap, - listeners: FxHashSet, - children: Vec, - }, - Placeholder, -} - -impl Node { - fn new(node_type: NodeType) -> Self { - Node { - state: S::default(), - node_data: NodeData { - id: None, - parent: None, - node_type, - height: 0, - }, - } - } - - /// link a child node - fn add_child(&mut self, child: RealNodeId) { - if let NodeType::Element { children, .. } = &mut self.node_data.node_type { - children.push(child); - } - } - - /// remove a child node - fn remove_child(&mut self, child: RealNodeId) { - if let NodeType::Element { children, .. } = &mut self.node_data.node_type { - children.retain(|c| c != &child); - } - } - - /// link the parent node - fn set_parent(&mut self, parent: RealNodeId) { - self.node_data.parent = Some(parent); - } - - /// get the mounted id of the node - pub fn mounted_id(&self) -> RealNodeId { - self.node_data.id.unwrap() - } -} - impl Traversable for RealDom { type Id = RealNodeId; type Node = Node; @@ -869,33 +784,3 @@ impl Traversable for RealDom { self.get(id).and_then(|n| n.node_data.parent) } } - -#[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub struct OwnedAttributeDiscription { - pub name: String, - pub namespace: Option, - pub volatile: bool, -} - -impl PartialEq for OwnedAttributeDiscription { - fn eq(&self, other: &AttributeDiscription) -> bool { - self.name == other.name - && match (&self.namespace, other.namespace) { - (Some(a), Some(b)) => a == b, - (None, None) => true, - _ => false, - } - && self.volatile == other.volatile - } -} - -/// An attribute on a DOM node, such as `id="my-thing"` or -/// `href="https://example.com"`. -#[derive(Clone, Debug)] -pub struct OwnedAttributeView<'a> { - /// The discription of the attribute. - pub attribute: &'a OwnedAttributeDiscription, - - /// The value of the attribute. - pub value: &'a OwnedAttributeValue, -} diff --git a/packages/native-core/src/state.rs b/packages/native-core/src/state.rs index 60f8187e3..d858a2a78 100644 --- a/packages/native-core/src/state.rs +++ b/packages/native-core/src/state.rs @@ -1,8 +1,7 @@ use std::{cmp::Ordering, fmt::Debug}; +use crate::node::NodeData; use crate::node_ref::{NodeMask, NodeView}; -use crate::real_dom::NodeData; -use crate::traversable::Traversable; use crate::RealNodeId; use anymap::AnyMap; use rustc_hash::FxHashSet; diff --git a/packages/native-core/src/traversable.rs b/packages/native-core/src/traversable.rs deleted file mode 100644 index e51295124..000000000 --- a/packages/native-core/src/traversable.rs +++ /dev/null @@ -1,102 +0,0 @@ -/// This is a interface for a tree with the ability to jump to a specific node -pub trait Traversable { - type Id: Copy; - type Node; - - fn height(&self, id: Self::Id) -> Option; - - fn get(&self, id: Self::Id) -> Option<&Self::Node>; - fn get_mut(&mut self, id: Self::Id) -> Option<&mut Self::Node>; - - fn children(&self, node: Self::Id) -> &[Self::Id]; - fn parent(&self, node: Self::Id) -> Option; - - fn map &N, FMut: Fn(&mut Self::Node) -> &mut N>( - &mut self, - f: F, - f_mut: FMut, - ) -> Map - where - Self: Sized, - { - Map { - tree: self, - f, - f_mut, - } - } - - // this is safe because no node will have itself as it's parent - fn get_node_parent_mut( - &mut self, - id: Self::Id, - ) -> (Option<&mut Self::Node>, Option<&mut Self::Node>) { - let node = self.get_mut(id).map(|n| n as *mut _); - let parent = self - .parent(id) - .and_then(|n| self.get_mut(n)) - .map(|n| n as *mut _); - unsafe { (node.map(|n| &mut *n), parent.map(|n| &mut *n)) } - } - - // this is safe because no node will have itself as a child - fn get_node_children_mut( - &mut self, - id: Self::Id, - ) -> (Option<&mut Self::Node>, Vec<&mut Self::Node>) { - let node = self.get_mut(id).map(|n| n as *mut _); - let mut children = Vec::new(); - let children_indexes = self.children(id).to_vec(); - for id in children_indexes { - if let Some(n) = self.get_mut(id) { - children.push(unsafe { &mut *(n as *mut _) }); - } - } - unsafe { (node.map(|n| &mut *n), children) } - } -} - -/// Maps one type of tree to another. Similar to [std::iter::Map]. -pub struct Map< - 'a, - T: Traversable, - N, - F: Fn(&::Node) -> &N, - FMut: Fn(&mut ::Node) -> &mut N, -> { - f: F, - f_mut: FMut, - tree: &'a mut T, -} - -impl< - 'a, - T: Traversable, - N, - F: Fn(&::Node) -> &N, - FMut: Fn(&mut ::Node) -> &mut N, - > Traversable for Map<'a, T, N, F, FMut> -{ - type Id = ::Id; - type Node = N; - - fn height(&self, id: Self::Id) -> Option { - self.tree.height(id) - } - - fn get(&self, id: Self::Id) -> Option<&Self::Node> { - self.tree.get(id).map(&self.f) - } - - fn get_mut(&mut self, id: Self::Id) -> Option<&mut Self::Node> { - self.tree.get_mut(id).map(&self.f_mut) - } - - fn children(&self, id: Self::Id) -> &[Self::Id] { - self.tree.children(id) - } - - fn parent(&self, id: Self::Id) -> Option { - self.tree.parent(id) - } -} diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs new file mode 100644 index 000000000..76769ee54 --- /dev/null +++ b/packages/native-core/src/tree.rs @@ -0,0 +1,781 @@ +use core::panic; +use parking_lot::lock_api::RawMutex as _; +use parking_lot::{RawMutex, RwLock}; +use slab::Slab; +use std::cell::UnsafeCell; +use std::collections::VecDeque; +use std::marker::PhantomData; +use std::sync::Arc; + +#[derive(PartialEq, Eq, Clone, Copy, Debug, PartialOrd, Ord)] +pub struct NodeId(pub usize); + +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct Node { + value: T, + parent: Option, + children: Vec, +} + +#[derive(Debug)] +pub struct Tree { + nodes: Slab>, + root: NodeId, +} + +pub trait TreeView: Sized { + type Iterator<'a>: Iterator + where + T: 'a, + Self: 'a; + type IteratorMut<'a>: Iterator + where + T: 'a, + Self: 'a; + + fn root(&self) -> NodeId; + + fn get(&self, id: NodeId) -> Option<&T>; + + fn get_unchecked(&self, id: NodeId) -> &T { + unsafe { self.get(id).unwrap_unchecked() } + } + + fn get_mut(&mut self, id: NodeId) -> Option<&mut T>; + + fn get_mut_unchecked(&mut self, id: NodeId) -> &mut T { + unsafe { self.get_mut(id).unwrap_unchecked() } + } + + fn children<'a>(&'a self, id: NodeId) -> Option>; + + fn children_mut<'a>(&'a mut self, id: NodeId) -> Option>; + + fn parent_child_mut<'a>( + &'a mut self, + id: NodeId, + ) -> Option<(&'a mut T, Self::IteratorMut<'a>)> { + let mut_ptr: *mut Self = self; + unsafe { + // Safety: No node has itself as a child. + (*mut_ptr).get_mut(id).and_then(|parent| { + (*mut_ptr) + .children_mut(id) + .map(|children| (parent, children)) + }) + } + } + + fn children_ids<'a>(&'a self, id: NodeId) -> Option<&'a [NodeId]>; + + fn parent(&self, id: NodeId) -> Option<&T>; + + fn parent_mut(&mut self, id: NodeId) -> Option<&mut T>; + + fn node_parent_mut(&mut self, id: NodeId) -> Option<(&mut T, &mut T)> { + let mut_ptr: *mut Self = self; + unsafe { + // Safety: No node has itself as a parent. + (*mut_ptr) + .get_mut(id) + .and_then(|parent| (*mut_ptr).parent_mut(id).map(|children| (parent, children))) + } + } + + fn parent_id(&self, id: NodeId) -> Option; + + fn map &T2, FMut: Fn(&mut T) -> &mut T2>( + &mut self, + map: F, + map_mut: FMut, + ) -> TreeMap { + TreeMap::new(self, map, map_mut) + } + + fn size(&self) -> usize; + + fn traverse_depth_first(&self, mut f: impl FnMut(&T)) { + let mut stack = vec![self.root()]; + while let Some(id) = stack.pop() { + if let Some(node) = self.get(id) { + f(node); + if let Some(children) = self.children_ids(id) { + stack.extend(children.iter().copied().rev()); + } + } + } + } + + fn traverse_depth_first_mut(&mut self, mut f: impl FnMut(&mut T)) { + let mut stack = vec![self.root()]; + while let Some(id) = stack.pop() { + if let Some(node) = self.get_mut(id) { + f(node); + if let Some(children) = self.children_ids(id) { + stack.extend(children.iter().copied().rev()); + } + } + } + } + + fn traverse_breadth_first(&self, mut f: impl FnMut(&T)) { + let mut queue = VecDeque::new(); + queue.push_back(self.root()); + while let Some(id) = queue.pop_front() { + if let Some(node) = self.get(id) { + f(node); + if let Some(children) = self.children_ids(id) { + for id in children { + queue.push_back(*id); + } + } + } + } + } + + fn traverse_breadth_first_mut(&mut self, mut f: impl FnMut(&mut T)) { + let mut queue = VecDeque::new(); + queue.push_back(self.root()); + while let Some(id) = queue.pop_front() { + if let Some(node) = self.get_mut(id) { + f(node); + if let Some(children) = self.children_ids(id) { + for id in children { + queue.push_back(*id); + } + } + } + } + } +} + +pub trait TreeLike: TreeView { + fn new(root: T) -> Self; + + fn add_child(&mut self, parent: NodeId, value: T) -> NodeId; + + fn remove(&mut self, id: NodeId) -> Option; + + fn replace(&mut self, id: NodeId, value: T); + + fn insert_before(&mut self, id: NodeId, value: T) -> NodeId; + + fn insert_after(&mut self, id: NodeId, value: T) -> NodeId; +} + +pub struct ChildNodeIterator<'a, T, Tr: TreeView> { + tree: &'a Tr, + children_ids: &'a [NodeId], + index: usize, + node_type: PhantomData, +} + +impl<'a, T: 'a, Tr: TreeView> Iterator for ChildNodeIterator<'a, T, Tr> { + type Item = &'a T; + + fn next(&mut self) -> Option { + self.children_ids.get(self.index).map(|id| { + self.index += 1; + self.tree.get_unchecked(*id) + }) + } +} + +pub struct ChildNodeIteratorMut<'a, T, Tr: TreeView + 'a> { + tree: *mut Tr, + children_ids: &'a [NodeId], + index: usize, + node_type: PhantomData, +} + +unsafe impl<'a, T, Tr: TreeView + 'a> Sync for ChildNodeIteratorMut<'a, T, Tr> {} + +impl<'a, T, Tr: TreeView> ChildNodeIteratorMut<'a, T, Tr> { + fn tree_mut(&mut self) -> &'a mut Tr { + unsafe { &mut *self.tree } + } +} + +impl<'a, T: 'a, Tr: TreeView> Iterator for ChildNodeIteratorMut<'a, T, Tr> { + type Item = &'a mut T; + + fn next(&mut self) -> Option { + let owned = self.children_ids.get(self.index).copied(); + match owned { + Some(id) => { + self.index += 1; + + Some(self.tree_mut().get_mut_unchecked(id)) + } + None => None, + } + } +} + +impl TreeView for Tree { + type Iterator<'a> = ChildNodeIterator<'a, T, Tree> where T: 'a; + type IteratorMut<'a> = ChildNodeIteratorMut<'a, T, Tree> where T: 'a; + + fn root(&self) -> NodeId { + self.root + } + + fn get(&self, id: NodeId) -> Option<&T> { + self.nodes.get(id.0).map(|node| &node.value) + } + + fn get_mut(&mut self, id: NodeId) -> Option<&mut T> { + self.nodes.get_mut(id.0).map(|node| &mut node.value) + } + + fn children<'a>(&'a self, id: NodeId) -> Option> { + self.children_ids(id).map(|children_ids| ChildNodeIterator { + tree: self, + children_ids, + index: 0, + node_type: PhantomData, + }) + } + + fn children_mut<'a>(&'a mut self, id: NodeId) -> Option> { + let raw_ptr = self as *mut Self; + unsafe { + // Safety: No node will appear as a child twice + self.children_ids(id) + .map(|children_ids| ChildNodeIteratorMut { + tree: &mut *raw_ptr, + children_ids, + index: 0, + node_type: PhantomData, + }) + } + } + + fn children_ids<'a>(&'a self, id: NodeId) -> Option<&'a [NodeId]> { + self.nodes.get(id.0).map(|node| node.children.as_slice()) + } + + fn parent(&self, id: NodeId) -> Option<&T> { + self.nodes + .get(id.0) + .and_then(|node| node.parent.map(|id| self.nodes.get(id.0).unwrap())) + .map(|node| &node.value) + } + + fn parent_mut(&mut self, id: NodeId) -> Option<&mut T> { + let self_ptr = self as *mut Self; + unsafe { + // Safety: No node has itself as a parent. + self.nodes + .get_mut(id.0) + .and_then(move |node| { + node.parent + .map(move |id| (*self_ptr).nodes.get_mut(id.0).unwrap()) + }) + .map(|node| &mut node.value) + } + } + + fn parent_id(&self, id: NodeId) -> Option { + self.nodes.get(id.0).and_then(|node| node.parent) + } + + fn get_unchecked(&self, id: NodeId) -> &T { + unsafe { &self.nodes.get_unchecked(id.0).value } + } + + fn get_mut_unchecked(&mut self, id: NodeId) -> &mut T { + unsafe { &mut self.nodes.get_unchecked_mut(id.0).value } + } + + fn size(&self) -> usize { + self.nodes.len() + } +} + +impl TreeLike for Tree { + fn new(root: T) -> Self { + let mut nodes = Slab::default(); + let root = NodeId(nodes.insert(Node { + value: root, + parent: None, + children: Vec::new(), + })); + Self { nodes, root } + } + + fn add_child(&mut self, parent: NodeId, value: T) -> NodeId { + let node = Node { + value, + parent: Some(parent), + children: Vec::new(), + }; + let id = self.nodes.insert(node); + self.nodes + .get_mut(parent.0) + .unwrap() + .children + .push(NodeId(id)); + NodeId(id) + } + + fn remove(&mut self, id: NodeId) -> Option { + self.nodes.try_remove(id.0).map(|node| { + if let Some(parent) = node.parent { + self.nodes + .get_mut(parent.0) + .unwrap() + .children + .retain(|child| child != &id); + } + node.value + }) + } + + fn replace(&mut self, id: NodeId, value: T) { + let old = self + .nodes + .get_mut(id.0) + .expect("tried to replace a node that doesn't exist"); + old.value = value; + } + + fn insert_before(&mut self, id: NodeId, value: T) -> NodeId { + let node = self.nodes.get(id.0).unwrap(); + let parent_id = node.parent.expect("tried to insert before root"); + let new = Node { + value, + parent: Some(parent_id), + children: Vec::new(), + }; + let new_id = NodeId(self.nodes.insert(new)); + let parent = self.nodes.get_mut(parent_id.0).unwrap(); + let index = parent + .children + .iter() + .position(|child| child == &id) + .unwrap(); + parent.children.insert(index, new_id); + new_id + } + + fn insert_after(&mut self, id: NodeId, value: T) -> NodeId { + let node = self.nodes.get(id.0).unwrap(); + let parent_id = node.parent.expect("tried to insert before root"); + let new = Node { + value, + parent: Some(parent_id), + children: Vec::new(), + }; + let new_id = NodeId(self.nodes.insert(new)); + let parent = self.nodes.get_mut(parent_id.0).unwrap(); + let index = parent + .children + .iter() + .position(|child| child == &id) + .unwrap(); + parent.children.insert(index + 1, new_id); + new_id + } +} + +pub struct TreeMap<'a, T1, T2, Tr, F, FMut> +where + Tr: TreeView, + F: Fn(&T1) -> &T2, + FMut: Fn(&mut T1) -> &mut T2, +{ + tree: &'a mut Tr, + map: F, + map_mut: FMut, + in_node_type: PhantomData, + out_node_type: PhantomData, +} + +impl<'a, T1, T2, Tr, F, FMut> TreeMap<'a, T1, T2, Tr, F, FMut> +where + Tr: TreeView, + F: Fn(&T1) -> &T2, + FMut: Fn(&mut T1) -> &mut T2, +{ + pub fn new(tree: &'a mut Tr, map: F, map_mut: FMut) -> Self { + TreeMap { + tree, + map, + map_mut, + in_node_type: PhantomData, + out_node_type: PhantomData, + } + } +} + +impl<'a, T1, T2, Tr, F, FMut> TreeView for TreeMap<'a, T1, T2, Tr, F, FMut> +where + Tr: TreeView, + F: Fn(&T1) -> &T2, + FMut: Fn(&mut T1) -> &mut T2, +{ + type Iterator<'b> = ChildNodeIterator<'b, T2, TreeMap<'a, T1, T2, Tr, F, FMut>> + where + T2: 'b, + Self:'b; + type IteratorMut<'b> = ChildNodeIteratorMut<'b, T2, TreeMap<'a, T1, T2, Tr, F, FMut>> + where + T2: 'b, + Self:'b; + + fn root(&self) -> NodeId { + self.tree.root() + } + + fn get(&self, id: NodeId) -> Option<&T2> { + self.tree.get(id).map(|node| (self.map)(node)) + } + + fn get_mut(&mut self, id: NodeId) -> Option<&mut T2> { + self.tree.get_mut(id).map(|node| (self.map_mut)(node)) + } + + fn children<'b>(&'b self, id: NodeId) -> Option> { + self.children_ids(id).map(|children_ids| ChildNodeIterator { + tree: self, + children_ids, + index: 0, + node_type: PhantomData, + }) + } + + fn children_mut<'b>(&'b mut self, id: NodeId) -> Option> { + let raw_ptr = self as *mut Self; + unsafe { + // Safety: No node can be a child twice. + self.children_ids(id) + .map(|children_ids| ChildNodeIteratorMut { + tree: &mut *raw_ptr, + children_ids, + index: 0, + node_type: PhantomData, + }) + } + } + + fn children_ids<'b>(&'b self, id: NodeId) -> Option<&'b [NodeId]> { + self.tree.children_ids(id) + } + + fn parent(&self, id: NodeId) -> Option<&T2> { + self.tree.parent(id).map(|node| (self.map)(node)) + } + + fn parent_mut(&mut self, id: NodeId) -> Option<&mut T2> { + self.tree.parent_mut(id).map(|node| (self.map_mut)(node)) + } + + fn parent_id(&self, id: NodeId) -> Option { + self.tree.parent_id(id) + } + + fn get_unchecked(&self, id: NodeId) -> &T2 { + (self.map)(self.tree.get_unchecked(id)) + } + + fn get_mut_unchecked(&mut self, id: NodeId) -> &mut T2 { + (self.map_mut)(self.tree.get_mut_unchecked(id)) + } + + fn size(&self) -> usize { + self.tree.size() + } +} + +/// A view into a tree that can be shared between multiple threads. Nodes are locked invividually. +pub struct SharedView<'a, T, Tr: TreeView> { + tree: Arc>, + node_locks: Arc>>, + node_type: PhantomData, +} + +impl<'a, T, Tr: TreeView> SharedView<'a, T, Tr> { + /// Checks if a node is currently locked. Returns None if the node does not exist. + pub fn check_lock(&self, id: NodeId) -> Option { + let mut locks = self.node_locks.read(); + locks.get(id.0).map(|lock| lock.is_locked()) + } +} + +unsafe impl<'a, T, Tr: TreeView> Send for SharedView<'a, T, Tr> {} +unsafe impl<'a, T, Tr: TreeView> Sync for SharedView<'a, T, Tr> {} +impl<'a, T, Tr: TreeView> Clone for SharedView<'a, T, Tr> { + fn clone(&self) -> Self { + Self { + tree: self.tree.clone(), + node_locks: self.node_locks.clone(), + node_type: PhantomData, + } + } +} + +impl<'a, T, Tr: TreeView> SharedView<'a, T, Tr> { + pub fn new(tree: &'a mut Tr) -> Self { + let tree = Arc::new(UnsafeCell::new(tree)); + let mut node_locks = Vec::new(); + for _ in 0..unsafe { (*tree.get()).size() } { + node_locks.push(RawMutex::INIT); + } + Self { + tree, + node_locks: Arc::new(RwLock::new(node_locks)), + node_type: PhantomData, + } + } + + fn lock_node(&self, node: NodeId) { + let read = self.node_locks.read(); + let lock = read.get(node.0); + match lock { + Some(lock) => lock.lock(), + None => { + drop(read); + let mut write = self.node_locks.write(); + write.resize_with(node.0 + 1, || RawMutex::INIT); + unsafe { write.get_unchecked(node.0).lock() } + } + } + } + + fn unlock_node(&self, node: NodeId) { + let read = self.node_locks.read(); + let lock = read.get(node.0); + match lock { + Some(lock) => unsafe { lock.unlock() }, + None => { + panic!("unlocking node that was not locked") + } + } + } + + fn with_node(&self, node_id: NodeId, f: impl FnOnce(&'a mut Tr) -> R) -> R { + self.lock_node(node_id); + let tree = unsafe { &mut *self.tree.get() }; + let r = f(tree); + self.unlock_node(node_id); + r + } +} + +impl<'a, T, Tr: TreeView> TreeView for SharedView<'a, T, Tr> { + type Iterator<'b> = Tr::Iterator<'b> where T: 'b, Self: 'b; + + type IteratorMut<'b>=Tr::IteratorMut<'b> + where + T: 'b, + Self: 'b; + + fn root(&self) -> NodeId { + unsafe { (*self.tree.get()).root() } + } + + fn get(&self, id: NodeId) -> Option<&T> { + self.with_node(id, |t| t.get(id)) + } + + fn get_mut(&mut self, id: NodeId) -> Option<&mut T> { + self.with_node(id, |t| t.get_mut(id)) + } + + fn children<'b>(&'b self, id: NodeId) -> Option> { + self.with_node(id, |t| t.children(id)) + } + + fn children_mut<'b>(&'b mut self, id: NodeId) -> Option> { + self.with_node(id, |t| t.children_mut(id)) + } + + fn children_ids<'b>(&'b self, id: NodeId) -> Option<&'b [NodeId]> { + self.with_node(id, |t| t.children_ids(id)) + } + + fn parent(&self, id: NodeId) -> Option<&T> { + self.with_node(id, |t| t.get(id)) + } + + fn parent_mut(&mut self, id: NodeId) -> Option<&mut T> { + self.with_node(id, |t| t.parent_mut(id)) + } + + fn parent_id(&self, id: NodeId) -> Option { + self.with_node(id, |t| t.parent_id(id)) + } + + fn size(&self) -> usize { + unsafe { (*self.tree.get()).size() } + } +} + +#[test] +fn creation() { + let mut tree = Tree::new(1); + let parent = tree.root(); + let child = tree.add_child(parent, 0); + + println!("Tree: {:#?}", tree); + assert_eq!(tree.size(), 2); + assert_eq!(*tree.get(parent).unwrap(), 1); + assert_eq!(*tree.get(child).unwrap(), 0); + assert_eq!(tree.parent_id(parent), None); + assert_eq!(tree.parent_id(child).unwrap(), parent); + assert_eq!(tree.children_ids(parent).unwrap(), &[child]); +} + +#[test] +fn insertion() { + let mut tree = Tree::new(0); + let parent = tree.root(); + let child = tree.add_child(parent, 2); + let before = tree.insert_before(child, 1); + let after = tree.insert_after(child, 3); + + println!("Tree: {:#?}", tree); + assert_eq!(tree.size(), 4); + assert_eq!(*tree.get(parent).unwrap(), 0); + assert_eq!(*tree.get(before).unwrap(), 1); + assert_eq!(*tree.get(child).unwrap(), 2); + assert_eq!(*tree.get(after).unwrap(), 3); + assert_eq!(tree.parent_id(before).unwrap(), parent); + assert_eq!(tree.parent_id(child).unwrap(), parent); + assert_eq!(tree.parent_id(after).unwrap(), parent); + assert_eq!(tree.children_ids(parent).unwrap(), &[before, child, after]); +} + +#[test] +fn deletion() { + let mut tree = Tree::new(0); + let parent = tree.root(); + let child = tree.add_child(parent, 2); + let before = tree.insert_before(child, 1); + let after = tree.insert_after(child, 3); + + println!("Tree: {:#?}", tree); + assert_eq!(tree.size(), 4); + assert_eq!(*tree.get(parent).unwrap(), 0); + assert_eq!(*tree.get(before).unwrap(), 1); + assert_eq!(*tree.get(child).unwrap(), 2); + assert_eq!(*tree.get(after).unwrap(), 3); + assert_eq!(tree.parent_id(before).unwrap(), parent); + assert_eq!(tree.parent_id(child).unwrap(), parent); + assert_eq!(tree.parent_id(after).unwrap(), parent); + assert_eq!(tree.children_ids(parent).unwrap(), &[before, child, after]); + + tree.remove(child); + + println!("Tree: {:#?}", tree); + assert_eq!(tree.size(), 3); + assert_eq!(*tree.get(parent).unwrap(), 0); + assert_eq!(*tree.get(before).unwrap(), 1); + assert_eq!(tree.get(child), None); + assert_eq!(*tree.get(after).unwrap(), 3); + assert_eq!(tree.parent_id(before).unwrap(), parent); + assert_eq!(tree.parent_id(after).unwrap(), parent); + assert_eq!(tree.children_ids(parent).unwrap(), &[before, after]); + + tree.remove(before); + + println!("Tree: {:#?}", tree); + assert_eq!(tree.size(), 2); + assert_eq!(*tree.get(parent).unwrap(), 0); + assert_eq!(tree.get(before), None); + assert_eq!(*tree.get(after).unwrap(), 3); + assert_eq!(tree.parent_id(after).unwrap(), parent); + assert_eq!(tree.children_ids(parent).unwrap(), &[after]); + + tree.remove(after); + + println!("Tree: {:#?}", tree); + assert_eq!(tree.size(), 1); + assert_eq!(*tree.get(parent).unwrap(), 0); + assert_eq!(tree.get(after), None); + assert_eq!(tree.children_ids(parent).unwrap(), &[]); +} + +#[test] +fn shared_view() { + use std::thread; + let mut tree = Tree::new(1); + let parent = tree.root(); + let child = tree.add_child(parent, 0); + + let shared = SharedView::new(&mut tree); + + thread::scope(|s| { + let (mut shared1, mut shared2, mut shared3) = + (shared.clone(), shared.clone(), shared.clone()); + s.spawn(move || { + assert_eq!(*shared1.get_mut(parent).unwrap(), 1); + assert_eq!(*shared1.get_mut(child).unwrap(), 0); + }); + s.spawn(move || { + assert_eq!(*shared2.get_mut(child).unwrap(), 0); + assert_eq!(*shared2.get_mut(parent).unwrap(), 1); + }); + s.spawn(move || { + assert_eq!(*shared3.get_mut(parent).unwrap(), 1); + assert_eq!(*shared3.get_mut(child).unwrap(), 0); + }); + }); +} + +#[test] +fn map() { + #[derive(Debug, PartialEq)] + struct Value { + value: i32, + } + impl Value { + fn new(value: i32) -> Self { + Self { value } + } + } + let mut tree = Tree::new(Value::new(1)); + let parent = tree.root(); + let child = tree.add_child(parent, Value::new(0)); + + let mut mapped = tree.map(|x| &x.value, |x| &mut x.value); + + *mapped.get_mut(child).unwrap() = 1; + *mapped.get_mut(parent).unwrap() = 2; + + assert_eq!(*tree.get(parent).unwrap(), Value::new(2)); + assert_eq!(*tree.get(child).unwrap(), Value::new(1)); +} + +#[test] +fn traverse_depth_first() { + let mut tree = Tree::new(0); + let parent = tree.root(); + let child1 = tree.add_child(parent, 1); + tree.add_child(child1, 2); + let child2 = tree.add_child(parent, 3); + tree.add_child(child2, 4); + + let mut node_count = 0; + tree.traverse_depth_first(move |node| { + assert_eq!(*node, node_count); + node_count += 1; + }); +} + +#[test] +fn traverse_breadth_first() { + let mut tree = Tree::new(0); + let parent = tree.root(); + let child1 = tree.add_child(parent, 1); + tree.add_child(child1, 3); + let child2 = tree.add_child(parent, 2); + tree.add_child(child2, 4); + + let mut node_count = 0; + tree.traverse_breadth_first(move |node| { + assert_eq!(*node, node_count); + node_count += 1; + }); +} diff --git a/packages/native-core/src/utils.rs b/packages/native-core/src/utils.rs index aa61807a4..0a5e7ebf1 100644 --- a/packages/native-core/src/utils.rs +++ b/packages/native-core/src/utils.rs @@ -1,229 +1,225 @@ -use crate::{ - real_dom::{NodeType, RealDom}, - state::State, - RealNodeId, -}; -use dioxus_core::{DomEdit, ElementId, Mutations}; +// use crate::{real_dom::RealDom, state::State, RealNodeId}; +// use dioxus_core::{ElementId, Mutations}; -pub enum ElementProduced { - /// The iterator produced an element by progressing to the next node in a depth first order. - Progressed(RealNodeId), - /// The iterator reached the end of the tree and looped back to the root - Looped(RealNodeId), -} -impl ElementProduced { - pub fn id(&self) -> RealNodeId { - match self { - ElementProduced::Progressed(id) => *id, - ElementProduced::Looped(id) => *id, - } - } -} +// pub enum ElementProduced { +// /// The iterator produced an element by progressing to the next node in a depth first order. +// Progressed(RealNodeId), +// /// The iterator reached the end of the tree and looped back to the root +// Looped(RealNodeId), +// } +// impl ElementProduced { +// pub fn id(&self) -> RealNodeId { +// match self { +// ElementProduced::Progressed(id) => *id, +// ElementProduced::Looped(id) => *id, +// } +// } +// } -#[derive(Debug)] -enum NodePosition { - AtNode, - InChild(usize), -} +// #[derive(Debug)] +// enum NodePosition { +// AtNode, +// InChild(usize), +// } -impl NodePosition { - fn map(&self, mut f: impl FnMut(usize) -> usize) -> Self { - match self { - Self::AtNode => Self::AtNode, - Self::InChild(i) => Self::InChild(f(*i)), - } - } +// impl NodePosition { +// fn map(&self, mut f: impl FnMut(usize) -> usize) -> Self { +// match self { +// Self::AtNode => Self::AtNode, +// Self::InChild(i) => Self::InChild(f(*i)), +// } +// } - fn get_or_insert(&mut self, child_idx: usize) -> usize { - match self { - Self::AtNode => { - *self = Self::InChild(child_idx); - child_idx - } - Self::InChild(i) => *i, - } - } -} +// fn get_or_insert(&mut self, child_idx: usize) -> usize { +// match self { +// Self::AtNode => { +// *self = Self::InChild(child_idx); +// child_idx +// } +// Self::InChild(i) => *i, +// } +// } +// } -/// Focus systems need a iterator that can persist through changes in the [dioxus_core::VirtualDom]. -/// This iterator traverses the tree depth first. -/// Iterate through it with [PersistantElementIter::next] [PersistantElementIter::prev], and update it with [PersistantElementIter::prune] (with data from [`dioxus_core::prelude::VirtualDom::work_with_deadline`]). -/// The iterator loops around when it reaches the end or the beginning. -pub struct PersistantElementIter { - // stack of elements and fragments - stack: smallvec::SmallVec<[(RealNodeId, NodePosition); 5]>, -} +// /// Focus systems need a iterator that can persist through changes in the [dioxus_core::VirtualDom]. +// /// This iterator traverses the tree depth first. +// /// Iterate through it with [PersistantElementIter::next] [PersistantElementIter::prev], and update it with [PersistantElementIter::prune] (with data from [`dioxus_core::prelude::VirtualDom::work_with_deadline`]). +// /// The iterator loops around when it reaches the end or the beginning. +// pub struct PersistantElementIter { +// // stack of elements and fragments +// stack: smallvec::SmallVec<[(RealNodeId, NodePosition); 5]>, +// } -impl Default for PersistantElementIter { - fn default() -> Self { - PersistantElementIter { - stack: smallvec::smallvec![(RealNodeId::ElementId(ElementId(0)), NodePosition::AtNode)], - } - } -} +// impl Default for PersistantElementIter { +// fn default() -> Self { +// PersistantElementIter { +// stack: smallvec::smallvec![(RealNodeId::ElementId(ElementId(0)), NodePosition::AtNode)], +// } +// } +// } -impl PersistantElementIter { - pub fn new() -> Self { - Self::default() - } +// impl PersistantElementIter { +// pub fn new() -> Self { +// Self::default() +// } - /// remove stale element refreneces - /// returns true if the focused element is removed - pub fn prune(&mut self, mutations: &Mutations, rdom: &RealDom) -> bool { - let mut changed = false; - let ids_removed: Vec<_> = mutations - .edits - .iter() - .filter_map(|e| { - // nodes within templates will never be removed - if let DomEdit::Remove { root } = e { - let id = rdom.resolve_maybe_id(*root); - Some(id) - } else { - None - } - }) - .collect(); - // if any element is removed in the chain, remove it and its children from the stack - if let Some(r) = self - .stack - .iter() - .position(|(el_id, _)| ids_removed.iter().any(|id| el_id == id)) - { - self.stack.truncate(r); - changed = true; - } - // if a child is removed or inserted before or at the current element, update the child index - for (el_id, child_idx) in self.stack.iter_mut() { - if let NodePosition::InChild(child_idx) = child_idx { - if let NodeType::Element { children, .. } = &rdom[*el_id].node_data.node_type { - for m in &mutations.edits { - match m { - DomEdit::Remove { root } => { - let id = rdom.resolve_maybe_id(*root); - if children.iter().take(*child_idx + 1).any(|c| *c == id) { - *child_idx -= 1; - } - } - DomEdit::InsertBefore { root, nodes } => { - let id = rdom.resolve_maybe_id(*root); - let n = nodes.len(); - if children.iter().take(*child_idx + 1).any(|c| *c == id) { - *child_idx += n as usize; - } - } - DomEdit::InsertAfter { root, nodes } => { - let id = rdom.resolve_maybe_id(*root); - let n = nodes.len(); - if children.iter().take(*child_idx).any(|c| *c == id) { - *child_idx += n as usize; - } - } - _ => (), - } - } - } - } - } - changed - } +// /// remove stale element refreneces +// /// returns true if the focused element is removed +// pub fn prune(&mut self, mutations: &Mutations, rdom: &RealDom) -> bool { +// let mut changed = false; +// let ids_removed: Vec<_> = mutations +// .edits +// .iter() +// .filter_map(|e| { +// // nodes within templates will never be removed +// if let DomEdit::Remove { root } = e { +// let id = rdom.resolve_maybe_id(*root); +// Some(id) +// } else { +// None +// } +// }) +// .collect(); +// // if any element is removed in the chain, remove it and its children from the stack +// if let Some(r) = self +// .stack +// .iter() +// .position(|(el_id, _)| ids_removed.iter().any(|id| el_id == id)) +// { +// self.stack.truncate(r); +// changed = true; +// } +// // if a child is removed or inserted before or at the current element, update the child index +// for (el_id, child_idx) in self.stack.iter_mut() { +// if let NodePosition::InChild(child_idx) = child_idx { +// if let NodeType::Element { children, .. } = &rdom[*el_id].node_data.node_type { +// for m in &mutations.edits { +// match m { +// DomEdit::Remove { root } => { +// let id = rdom.resolve_maybe_id(*root); +// if children.iter().take(*child_idx + 1).any(|c| *c == id) { +// *child_idx -= 1; +// } +// } +// DomEdit::InsertBefore { root, nodes } => { +// let id = rdom.resolve_maybe_id(*root); +// let n = nodes.len(); +// if children.iter().take(*child_idx + 1).any(|c| *c == id) { +// *child_idx += n as usize; +// } +// } +// DomEdit::InsertAfter { root, nodes } => { +// let id = rdom.resolve_maybe_id(*root); +// let n = nodes.len(); +// if children.iter().take(*child_idx).any(|c| *c == id) { +// *child_idx += n as usize; +// } +// } +// _ => (), +// } +// } +// } +// } +// } +// changed +// } - /// get the next element - pub fn next(&mut self, rdom: &RealDom) -> ElementProduced { - if self.stack.is_empty() { - let id = RealNodeId::ElementId(ElementId(0)); - let new = (id, NodePosition::AtNode); - self.stack.push(new); - ElementProduced::Looped(id) - } else { - let (last, o_child_idx) = self.stack.last_mut().unwrap(); - let node = &rdom[*last]; - match &node.node_data.node_type { - NodeType::Element { children, .. } => { - *o_child_idx = o_child_idx.map(|i| i + 1); - // if we have children, go to the next child - let child_idx = o_child_idx.get_or_insert(0); - if child_idx >= children.len() { - self.pop(); - self.next(rdom) - } else { - let id = children[child_idx]; - if let NodeType::Element { .. } = &rdom[id].node_data.node_type { - self.stack.push((id, NodePosition::AtNode)); - } - ElementProduced::Progressed(id) - } - } +// /// get the next element +// pub fn next(&mut self, rdom: &RealDom) -> ElementProduced { +// if self.stack.is_empty() { +// let id = RealNodeId::ElementId(ElementId(0)); +// let new = (id, NodePosition::AtNode); +// self.stack.push(new); +// ElementProduced::Looped(id) +// } else { +// let (last, o_child_idx) = self.stack.last_mut().unwrap(); +// let node = &rdom[*last]; +// match &node.node_data.node_type { +// NodeType::Element { children, .. } => { +// *o_child_idx = o_child_idx.map(|i| i + 1); +// // if we have children, go to the next child +// let child_idx = o_child_idx.get_or_insert(0); +// if child_idx >= children.len() { +// self.pop(); +// self.next(rdom) +// } else { +// let id = children[child_idx]; +// if let NodeType::Element { .. } = &rdom[id].node_data.node_type { +// self.stack.push((id, NodePosition::AtNode)); +// } +// ElementProduced::Progressed(id) +// } +// } - NodeType::Text { .. } | NodeType::Placeholder { .. } => { - // we are at a leaf, so we are done - ElementProduced::Progressed(self.pop()) - } - } - } - } +// NodeType::Text { .. } | NodeType::Placeholder { .. } => { +// // we are at a leaf, so we are done +// ElementProduced::Progressed(self.pop()) +// } +// } +// } +// } - /// get the previous element - pub fn prev(&mut self, rdom: &RealDom) -> ElementProduced { - // recursively add the last child element to the stack - fn push_back( - stack: &mut smallvec::SmallVec<[(RealNodeId, NodePosition); 5]>, - new_node: RealNodeId, - rdom: &RealDom, - ) -> RealNodeId { - match &rdom[new_node].node_data.node_type { - NodeType::Element { children, .. } => { - if children.is_empty() { - new_node - } else { - stack.push((new_node, NodePosition::InChild(children.len() - 1))); - push_back(stack, *children.last().unwrap(), rdom) - } - } - _ => new_node, - } - } - if self.stack.is_empty() { - let new_node = RealNodeId::ElementId(ElementId(0)); - ElementProduced::Looped(push_back(&mut self.stack, new_node, rdom)) - } else { - let (last, o_child_idx) = self.stack.last_mut().unwrap(); - let node = &rdom[*last]; - match &node.node_data.node_type { - NodeType::Element { children, .. } => { - // if we have children, go to the next child - if let NodePosition::InChild(0) = o_child_idx { - ElementProduced::Progressed(self.pop()) - } else { - *o_child_idx = o_child_idx.map(|i| i - 1); - if let NodePosition::InChild(child_idx) = o_child_idx { - if *child_idx >= children.len() || children.is_empty() { - self.pop(); - self.prev(rdom) - } else { - let new_node = children[*child_idx]; - ElementProduced::Progressed(push_back( - &mut self.stack, - new_node, - rdom, - )) - } - } else { - self.pop(); - self.prev(rdom) - } - } - } +// /// get the previous element +// pub fn prev(&mut self, rdom: &RealDom) -> ElementProduced { +// // recursively add the last child element to the stack +// fn push_back( +// stack: &mut smallvec::SmallVec<[(RealNodeId, NodePosition); 5]>, +// new_node: RealNodeId, +// rdom: &RealDom, +// ) -> RealNodeId { +// match &rdom[new_node].node_data.node_type { +// NodeType::Element { children, .. } => { +// if children.is_empty() { +// new_node +// } else { +// stack.push((new_node, NodePosition::InChild(children.len() - 1))); +// push_back(stack, *children.last().unwrap(), rdom) +// } +// } +// _ => new_node, +// } +// } +// if self.stack.is_empty() { +// let new_node = RealNodeId::ElementId(ElementId(0)); +// ElementProduced::Looped(push_back(&mut self.stack, new_node, rdom)) +// } else { +// let (last, o_child_idx) = self.stack.last_mut().unwrap(); +// let node = &rdom[*last]; +// match &node.node_data.node_type { +// NodeType::Element { children, .. } => { +// // if we have children, go to the next child +// if let NodePosition::InChild(0) = o_child_idx { +// ElementProduced::Progressed(self.pop()) +// } else { +// *o_child_idx = o_child_idx.map(|i| i - 1); +// if let NodePosition::InChild(child_idx) = o_child_idx { +// if *child_idx >= children.len() || children.is_empty() { +// self.pop(); +// self.prev(rdom) +// } else { +// let new_node = children[*child_idx]; +// ElementProduced::Progressed(push_back( +// &mut self.stack, +// new_node, +// rdom, +// )) +// } +// } else { +// self.pop(); +// self.prev(rdom) +// } +// } +// } - NodeType::Text { .. } | NodeType::Placeholder { .. } => { - // we are at a leaf, so we are done - ElementProduced::Progressed(self.pop()) - } - } - } - } +// NodeType::Text { .. } | NodeType::Placeholder { .. } => { +// // we are at a leaf, so we are done +// ElementProduced::Progressed(self.pop()) +// } +// } +// } +// } - fn pop(&mut self) -> RealNodeId { - self.stack.pop().unwrap().0 - } -} +// fn pop(&mut self) -> RealNodeId { +// self.stack.pop().unwrap().0 +// } +// } From dcad2fe8a2c8d74f890e3ef5f0fa1fd9311ec5c0 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 24 Nov 2022 15:05:15 -0600 Subject: [PATCH 02/93] intial update to new mutations --- packages/native-core/src/lib.rs | 2 - packages/native-core/src/node.rs | 28 +- packages/native-core/src/node_ref.rs | 52 +- packages/native-core/src/real_dom.rs | 917 +++++++++------------------ packages/native-core/src/state.rs | 23 +- packages/native-core/src/tree.rs | 173 +++-- 6 files changed, 440 insertions(+), 755 deletions(-) diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index bdd7e24be..895bb7dc9 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -5,8 +5,6 @@ pub mod node; pub mod node_ref; pub mod real_dom; pub mod state; -#[doc(hidden)] -pub mod traversable; pub mod tree; pub mod utils; diff --git a/packages/native-core/src/node.rs b/packages/native-core/src/node.rs index 5d6285071..09dd03e71 100644 --- a/packages/native-core/src/node.rs +++ b/packages/native-core/src/node.rs @@ -1,4 +1,4 @@ -use crate::{state::State, tree::NodeId, RealNodeId}; +use crate::{state::State, tree::NodeId}; use dioxus_core::ElementId; use rustc_hash::{FxHashMap, FxHashSet}; @@ -13,12 +13,15 @@ pub struct Node { #[derive(Debug, Clone)] pub struct NodeData { + /// The id of the node + pub node_id: NodeId, /// The id of the node in the vdom. pub element_id: Option, /// Additional inforation specific to the node type pub node_type: NodeType, /// The number of parents before the root node. The root node has height 1. pub height: u16, + height_dirty: bool, } /// A type of node with data specific to the node type. The types are a subset of the [VNode] types. @@ -29,7 +32,7 @@ pub enum NodeType { }, Element { tag: String, - namespace: Option<&'static str>, + namespace: Option, attributes: FxHashMap, listeners: FxHashSet, }, @@ -44,29 +47,12 @@ impl Node { element_id: None, node_type, height: 0, + node_id: NodeId(0), + height_dirty: true, }, } } - /// link a child node - fn add_child(&mut self, child: RealNodeId) { - if let NodeType::Element { children, .. } = &mut self.node_data.node_type { - children.push(child); - } - } - - /// remove a child node - fn remove_child(&mut self, child: RealNodeId) { - if let NodeType::Element { children, .. } = &mut self.node_data.node_type { - children.retain(|c| c != &child); - } - } - - /// link the parent node - fn set_parent(&mut self, parent: RealNodeId) { - self.node_data.parent = Some(parent); - } - /// get the mounted id of the node pub fn mounted_id(&self) -> Option { self.node_data.element_id diff --git a/packages/native-core/src/node_ref.rs b/packages/native-core/src/node_ref.rs index 671d85663..759cc5ffb 100644 --- a/packages/native-core/src/node_ref.rs +++ b/packages/native-core/src/node_ref.rs @@ -42,7 +42,7 @@ impl<'a> NodeView<'a> { self.mask .namespace .then_some(match &self.inner.node_type { - NodeType::Element { namespace, .. } => *namespace, + NodeType::Element { namespace, .. } => namespace.as_deref(), _ => None, }) .flatten() @@ -93,7 +93,7 @@ impl<'a> NodeView<'a> { pub enum AttributeMask { All, /// A list of attribute names that are visible, this list must be sorted - Dynamic(Vec<&'static str>), + Dynamic(Vec), /// A list of attribute names that are visible, this list must be sorted Static(&'static [&'static str]), } @@ -105,14 +105,14 @@ impl AttributeMask { fn contains_attribute(&self, attr: &str) -> bool { match self { AttributeMask::All => true, - AttributeMask::Dynamic(l) => l.binary_search(&attr).is_ok(), + AttributeMask::Dynamic(l) => l.binary_search_by_key(&attr, |s| s.as_str()).is_ok(), AttributeMask::Static(l) => l.binary_search(&attr).is_ok(), } } /// Create a new dynamic attribute mask with a single attribute - pub fn single(new: &'static str) -> Self { - Self::Dynamic(vec![new]) + pub fn single(new: &str) -> Self { + Self::Dynamic(vec![new.to_string()]) } /// Ensure the attribute list is sorted. @@ -133,15 +133,27 @@ impl AttributeMask { /// Combine two attribute masks pub fn union(&self, other: &Self) -> Self { let new = match (self, other) { - (AttributeMask::Dynamic(s), AttributeMask::Dynamic(o)) => AttributeMask::Dynamic( - union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()), - ), - (AttributeMask::Static(s), AttributeMask::Dynamic(o)) => AttributeMask::Dynamic( - union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()), - ), - (AttributeMask::Dynamic(s), AttributeMask::Static(o)) => AttributeMask::Dynamic( - union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()), - ), + (AttributeMask::Dynamic(s), AttributeMask::Dynamic(o)) => { + AttributeMask::Dynamic(union_ordered_iter( + s.iter().map(|s| s.as_str()), + o.iter().map(|s| s.as_str()), + s.len() + o.len(), + )) + } + (AttributeMask::Static(s), AttributeMask::Dynamic(o)) => { + AttributeMask::Dynamic(union_ordered_iter( + s.iter().copied(), + o.iter().map(|s| s.as_str()), + s.len() + o.len(), + )) + } + (AttributeMask::Dynamic(s), AttributeMask::Static(o)) => { + AttributeMask::Dynamic(union_ordered_iter( + s.iter().map(|s| s.as_str()), + o.iter().copied(), + s.len() + o.len(), + )) + } (AttributeMask::Static(s), AttributeMask::Static(o)) => AttributeMask::Dynamic( union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()), ), @@ -153,9 +165,9 @@ impl AttributeMask { /// Check if two attribute masks overlap fn overlaps(&self, other: &Self) -> bool { - fn overlaps_iter( - self_iter: impl Iterator, - mut other_iter: impl Iterator, + fn overlaps_iter<'a>( + self_iter: impl Iterator, + mut other_iter: impl Iterator, ) -> bool { if let Some(mut other_attr) = other_iter.next() { for self_attr in self_iter { @@ -180,13 +192,13 @@ impl AttributeMask { (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()) + overlaps_iter(v1.iter().map(|s| s.as_str()), v2.iter().map(|s| s.as_str())) } (AttributeMask::Dynamic(v), AttributeMask::Static(s)) => { - overlaps_iter(v.iter().copied(), s.iter().copied()) + overlaps_iter(v.iter().map(|s| s.as_str()), s.iter().copied()) } (AttributeMask::Static(s), AttributeMask::Dynamic(v)) => { - overlaps_iter(v.iter().copied(), s.iter().copied()) + overlaps_iter(v.iter().map(|s| s.as_str()), s.iter().copied()) } (AttributeMask::Static(s1), AttributeMask::Static(s2)) => { overlaps_iter(s1.iter().copied(), s2.iter().copied()) diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 53547de68..cbeb80f2b 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -1,13 +1,11 @@ use anymap::AnyMap; -use dioxus_core::{ElementId, Mutations, VNode}; +use dioxus_core::{ElementId, Mutations}; use rustc_hash::{FxHashMap, FxHashSet}; -use slab::Slab; -use std::ops::{Index, IndexMut}; +use std::ops::{Deref, DerefMut, Index, IndexMut}; -use crate::node::{Node, NodeType}; +use crate::node::{Node, NodeData, NodeType, OwnedAttributeDiscription}; use crate::node_ref::{AttributeMask, NodeMask}; use crate::state::State; -use crate::traversable::Traversable; use crate::tree::{NodeId, Tree, TreeLike, TreeView}; use crate::RealNodeId; @@ -18,8 +16,10 @@ use crate::RealNodeId; pub struct RealDom { tree: Tree>, /// a map from element id to real node id - node_id_mapping: Vec, - nodes_listening: FxHashMap<&'static str, FxHashSet>, + node_id_mapping: Vec>, + nodes_listening: FxHashMap>, + stack: Vec, + templates: FxHashMap>, } impl Default for RealDom { @@ -32,369 +32,307 @@ impl RealDom { pub fn new() -> RealDom { let mut root = Node::new(NodeType::Element { tag: "Root".to_string(), - namespace: Some("Root"), + namespace: Some("Root".to_string()), attributes: FxHashMap::default(), listeners: FxHashSet::default(), - children: Vec::new(), }); root.node_data.element_id = Some(ElementId(0)); - let tree = Tree::new(root); + let mut tree = Tree::new(root); let root_id = tree.root(); - tree.get_mut(root_id).unwrap().node_data.id = root_id; + tree.get_mut(root_id).unwrap().node_data.node_id = root_id; RealDom { tree, node_id_mapping: Vec::new(), nodes_listening: FxHashMap::default(), + stack: vec![root_id], + templates: FxHashMap::default(), } } + fn element_to_node_id(&self, element_id: ElementId) -> RealNodeId { + self.node_id_mapping.get(element_id.0).unwrap().unwrap() + } + + fn set_element_id(&mut self, node_id: NodeId, element_id: ElementId) { + let node = self.tree.get_mut(node_id).unwrap(); + let node_id = node.node_data.node_id; + node.node_data.element_id = Some(element_id); + if self.node_id_mapping.len() <= element_id.0 { + self.node_id_mapping.resize(element_id.0 + 1, None); + } + self.node_id_mapping[element_id.0] = Some(node_id); + } + + fn load_child(&self, path: &[u8]) -> RealNodeId { + let mut current = *self.stack.last().unwrap(); + for i in path { + current = self.tree.children_ids(current).unwrap()[*i as usize]; + } + current + } + + fn create_node(&mut self, node: Node) -> RealNodeId { + let node_id = self.tree.create_node(node); + let node = self.tree.get_mut(node_id).unwrap(); + node.node_data.node_id = node_id; + node_id + } + /// Updates the dom with some mutations and return a set of nodes that were updated. Pass the dirty nodes to update_state. - pub fn apply_mutations( + pub fn apply_mutations<'a>( &mut self, - mutations_vec: Vec, + mutations_vec: Vec>, ) -> Vec<(RealNodeId, NodeMask)> { - let mut nodes_updated = Vec::new(); - nodes_updated.push((RealNodeId::ElementId(ElementId(0)), NodeMask::ALL)); + let mut nodes_updated: Vec<(RealNodeId, NodeMask)> = Vec::new(); for mutations in mutations_vec { for e in mutations.edits { use dioxus_core::Mutation::*; match e { - AppendChildren { root, children } => { - let target = self.resolve_maybe_id(root); - for id in children { - let id = RealNodeId::ElementId(ElementId(id as usize)); - self.mark_dirty(id, NodeMask::ALL, &mut nodes_updated); - self.link_child(id, target).unwrap(); + AppendChildren { m } => { + let data = self.stack.split_off(m); + let (parent, children) = data.split_first().unwrap(); + for child in children { + self.tree.add_child(*parent, *child); + nodes_updated.push((*parent, NodeMask::ALL)); } } - ReplaceWith { root, nodes } => { - let id_to_replace = self.resolve_maybe_id(root); - let target = self[id_to_replace].node_data.parent.unwrap(); - for id in nodes { - let id = RealNodeId::ElementId(ElementId(id as usize)); - self.mark_dirty(id, NodeMask::ALL, &mut nodes_updated); - self.link_child_before(id, target, id_to_replace).unwrap(); - } - self.remove(id_to_replace).unwrap(); + AssignId { path, id } => { + self.set_element_id(self.load_child(&path), id); } - InsertAfter { root, nodes } => { - let root = self.resolve_maybe_id(root); - let target = self.parent(root).unwrap(); - for id in nodes { - let id = RealNodeId::ElementId(ElementId(id as usize)); - self.mark_dirty(id, NodeMask::ALL, &mut nodes_updated); - self.link_child_after(id, target, root).unwrap(); - } - } - InsertBefore { root, nodes } => { - let root = self.resolve_maybe_id(root); - let target = self.parent(root).unwrap(); - for id in nodes { - let id = RealNodeId::ElementId(ElementId(id as usize)); - self.mark_dirty(id, NodeMask::ALL, &mut nodes_updated); - self.link_child_before(id, target, root).unwrap(); - } - } - Remove { root } => { - let id = self.resolve_maybe_id(root); - if let Some(parent) = self.parent(id) { - self.mark_dirty(parent, NodeMask::NONE, &mut nodes_updated); - } - self.remove(id).unwrap(); - } - CreateTextNode { root, text } => { - let n = Node::new(NodeType::Text { - text: text.to_string(), - }); - let id = self.insert(n, root, &mut nodes_updated); - self.mark_dirty(id, NodeMask::ALL, &mut nodes_updated); - if let Some((parent, remaining)) = self.parents_queued.last_mut() { - *remaining -= 1; - let parent = *parent; - if *remaining == 0 { - self.parents_queued.pop(); - } - self.link_child(id, parent).unwrap(); - } - self.last = Some(id); - } - CreateElement { - root, - tag, - children, - } => { - let n = Node::new(NodeType::Element { - tag: tag.to_string(), + CreateElement { name } => { + let node = Node::new(NodeType::Element { + tag: name.to_string(), namespace: None, attributes: FxHashMap::default(), listeners: FxHashSet::default(), - children: Vec::new(), }); - let id = self.insert(n, root, &mut nodes_updated); - self.mark_dirty(id, NodeMask::ALL, &mut nodes_updated); - if let Some((parent, remaining)) = self.parents_queued.last_mut() { - *remaining -= 1; - let parent = *parent; - if *remaining == 0 { - self.parents_queued.pop(); - } - self.link_child(id, parent).unwrap(); - } - self.last = Some(id); - if children > 0 { - self.parents_queued.push((id, children)); - } + let id = self.create_node(node); + self.stack.push(id); + nodes_updated.push((id, NodeMask::ALL)); } - CreateElementNs { - root, - tag, - ns, - children, - } => { - let n = Node::new(NodeType::Element { - tag: tag.to_string(), - namespace: Some(ns), + CreateElementNamespace { name, namespace } => { + let node = Node::new(NodeType::Element { + tag: name.to_string(), + namespace: Some(namespace.to_string()), attributes: FxHashMap::default(), listeners: FxHashSet::default(), - children: Vec::new(), }); - let id = self.insert(n, root, &mut nodes_updated); - self.mark_dirty(id, NodeMask::ALL, &mut nodes_updated); - if let Some((parent, remaining)) = self.parents_queued.last_mut() { - *remaining -= 1; - let parent = *parent; - if *remaining == 0 { - self.parents_queued.pop(); - } - self.link_child(id, parent).unwrap(); + let id = self.create_node(node); + self.stack.push(id); + nodes_updated.push((id, NodeMask::ALL)); + } + CreatePlaceholder { id } => { + let node = Node::new(NodeType::Placeholder); + let node_id = self.create_node(node); + self.set_element_id(node_id, id); + self.stack.push(node_id); + nodes_updated.push((node_id, NodeMask::ALL)); + } + CreateStaticPlaceholder => { + let node = Node::new(NodeType::Placeholder); + let id = self.create_node(node); + self.stack.push(id); + nodes_updated.push((id, NodeMask::ALL)); + } + CreateStaticText { value } => { + let node = Node::new(NodeType::Text { + text: value.to_string(), + }); + let id = self.create_node(node); + self.stack.push(id); + nodes_updated.push((id, NodeMask::new().with_text())); + } + CreateTextNode { value, id } => { + let node = Node::new(NodeType::Text { + text: value.to_string(), + }); + let node_id = self.create_node(node); + let node = self.tree.get_mut(node_id).unwrap(); + node.node_data.element_id = Some(id); + self.stack.push(node_id); + nodes_updated.push((node_id, NodeMask::new().with_text())); + } + HydrateText { path, value, id } => { + let node_id = self.load_child(path); + self.set_element_id(node_id, id); + let node = self.tree.get_mut(node_id).unwrap(); + if let NodeType::Text { text } = &mut node.node_data.node_type { + *text = value.to_string(); } - self.last = Some(id); - if children > 0 { - self.parents_queued.push((id, children)); + nodes_updated.push((node_id, NodeMask::new().with_text())); + } + LoadTemplate { name, index, id } => { + let template_id = self.templates[name][index]; + let clone_id = self.clone_node(template_id); + self.set_element_id(clone_id, id); + } + ReplaceWith { id, m } => { + let new_nodes = self.stack.split_off(m); + let old_node_id = self.element_to_node_id(id); + for new in new_nodes { + self.tree.insert_after(old_node_id, new); + nodes_updated.push((new, NodeMask::ALL)); + } + self.tree.remove(old_node_id); + } + ReplacePlaceholder { path, m } => { + let new_nodes = self.stack.split_off(m); + let old_node_id = self.load_child(path); + for new in new_nodes { + self.tree.insert_after(old_node_id, new); + nodes_updated.push((new, NodeMask::ALL)); + } + self.tree.remove(old_node_id); + } + InsertAfter { id, m } => { + let new_nodes = self.stack.split_off(m); + let old_node_id = self.element_to_node_id(id); + for new in new_nodes { + self.tree.insert_after(old_node_id, new); + nodes_updated.push((new, NodeMask::ALL)); } } - CreatePlaceholder { root } => { - let n = Node::new(NodeType::Placeholder); - let id = self.insert(n, root, &mut nodes_updated); - self.mark_dirty(id, NodeMask::ALL, &mut nodes_updated); - if let Some((parent, remaining)) = self.parents_queued.last_mut() { - *remaining -= 1; - let parent = *parent; - if *remaining == 0 { - self.parents_queued.pop(); - } - self.link_child(id, parent).unwrap(); + InsertBefore { id, m } => { + let new_nodes = self.stack.split_off(m); + let old_node_id = self.element_to_node_id(id); + for new in new_nodes { + self.tree.insert_before(old_node_id, new); + nodes_updated.push((new, NodeMask::ALL)); } - self.last = Some(id); + } + SaveTemplate { name, m } => { + let template = self.stack.split_off(m); + self.templates.insert(name.to_string(), template); + } + SetAttribute { + name, + value, + id, + ns, + } => { + let node_id = self.element_to_node_id(id); + let node = self.tree.get_mut(node_id).unwrap(); + if let NodeType::Element { attributes, .. } = &mut node.node_data.node_type + { + attributes.insert( + OwnedAttributeDiscription { + name: name.to_string(), + namespace: ns.map(|s| s.to_string()), + volatile: false, + }, + crate::node::OwnedAttributeValue::Text(value.to_string()), + ); + nodes_updated.push(( + node_id, + NodeMask::new_with_attrs(AttributeMask::single(name)), + )); + } + } + SetStaticAttribute { name, value, ns } => { + let node_id = self.stack.last().unwrap(); + let node = self.tree.get_mut(*node_id).unwrap(); + if let NodeType::Element { attributes, .. } = &mut node.node_data.node_type + { + attributes.insert( + OwnedAttributeDiscription { + name: name.to_string(), + namespace: ns.map(|s| s.to_string()), + volatile: false, + }, + crate::node::OwnedAttributeValue::Text(value.to_string()), + ); + nodes_updated.push(( + *node_id, + NodeMask::new_with_attrs(AttributeMask::single(name)), + )); + } + } + SetBoolAttribute { name, value, id } => { + let node_id = self.element_to_node_id(id); + let node = self.tree.get_mut(node_id).unwrap(); + if let NodeType::Element { attributes, .. } = &mut node.node_data.node_type + { + attributes.insert( + OwnedAttributeDiscription { + name: name.to_string(), + namespace: None, + volatile: false, + }, + crate::node::OwnedAttributeValue::Bool(value), + ); + nodes_updated.push(( + node_id, + NodeMask::new_with_attrs(AttributeMask::single(name)), + )); + } + } + SetInnerText { value } => { + let node_id = *self.stack.last().unwrap(); + let node = self.tree.get_mut(node_id).unwrap(); + if let NodeType::Element { .. } = &mut node.node_data.node_type { + self.tree.remove_all_children(node_id); + let text_node = Node::new(NodeType::Text { + text: value.to_string(), + }); + let text_node_id = self.create_node(text_node); + self.tree.add_child(node_id, text_node_id); + } + } + SetText { value, id } => { + let node_id = self.element_to_node_id(id); + let node = self.tree.get_mut(node_id).unwrap(); + if let NodeType::Text { text } = &mut node.node_data.node_type { + *text = value.to_string(); + } + nodes_updated.push((node_id, NodeMask::new().with_text())); } NewEventListener { event_name, scope: _, - root, + id, } => { - let id = self.resolve_maybe_id(root); - self.mark_dirty(id, NodeMask::new().with_listeners(), &mut nodes_updated); - match &mut self[id].node_data.node_type { - NodeType::Text { .. } => panic!("Text nodes cannot have listeners"), - NodeType::Element { listeners, .. } => { - listeners.insert(event_name.to_string()); + let node_id = self.element_to_node_id(id); + let node = self.tree.get_mut(node_id).unwrap(); + if let NodeType::Element { listeners, .. } = &mut node.node_data.node_type { + match self.nodes_listening.get_mut(event_name) { + Some(hs) => { + hs.insert(node_id); + } + None => { + let mut hs = FxHashSet::default(); + hs.insert(node_id); + self.nodes_listening.insert(event_name.to_string(), hs); + } } - NodeType::Placeholder => panic!("Placeholder cannot have listeners"), - } - if let Some(v) = self.nodes_listening.get_mut(event_name) { - v.insert(id); - } else { - let mut hs = FxHashSet::default(); - hs.insert(id); - self.nodes_listening.insert(event_name, hs); + listeners.insert(event_name.to_string()); } } - RemoveEventListener { root, event } => { - let id = self.resolve_maybe_id(root); - self.mark_dirty(id, NodeMask::new().with_listeners(), &mut nodes_updated); - let v = self.nodes_listening.get_mut(event).unwrap(); - v.remove(&id); - } - SetText { - root, - text: new_text, - } => { - let id = self.resolve_maybe_id(root); - self.mark_dirty(id, NodeMask::new().with_text(), &mut nodes_updated); - let target = &mut self[id]; - match &mut target.node_data.node_type { - NodeType::Text { text } => { - *text = new_text.to_string(); - } - _ => unreachable!(), + RemoveEventListener { id, event } => { + let node_id = self.element_to_node_id(id); + let node = self.tree.get_mut(node_id).unwrap(); + if let NodeType::Element { listeners, .. } = &mut node.node_data.node_type { + listeners.remove(event); } + self.nodes_listening + .get_mut(event) + .unwrap() + .remove(&node_id); } - SetAttribute { - root, - field, - ns, - value, - } => { - let id = self.resolve_maybe_id(root); - if let NodeType::Element { attributes, .. } = - &mut self[id].node_data.node_type - { - attributes.insert( - OwnedAttributeDiscription { - name: field.to_owned(), - namespace: ns.map(|a| a.to_owned()), - volatile: false, - }, - value.into(), - ); - } else { - panic!("tried to call set attribute on a non element"); - } - self.mark_dirty( - id, - NodeMask::new_with_attrs(AttributeMask::single(field)), - &mut nodes_updated, - ); - } - RemoveAttribute { - root, name: field, .. - } => { - let id = self.resolve_maybe_id(root); - self.mark_dirty( - id, - NodeMask::new_with_attrs(AttributeMask::single(field)), - &mut nodes_updated, - ); - } - CloneNode { id, new_id } => { - let id = self.resolve_maybe_id(id); - self.clone_node_into(id, &mut nodes_updated, Some(new_id)); - } - CloneNodeChildren { id, new_ids } => { - let id = self.resolve_maybe_id(id); - let bounded_self: &mut Self = self; - let unbounded_self: &mut Self = - unsafe { std::mem::transmute(bounded_self) }; - if let NodeType::Element { children, .. } = &self[id].node_data.node_type { - for (old_id, new_id) in children.iter().zip(new_ids) { - let child_id = unbounded_self.clone_node_into( - *old_id, - &mut nodes_updated, - Some(new_id), - ); - unbounded_self[child_id].node_data.parent = None; - } - } - } - FirstChild {} => { - if let NodeType::Element { children, .. } = - &self[self.last.unwrap()].node_data.node_type - { - self.last = Some(children[0]); - } else { - panic!("tried to call first child on a non element"); - } - } - NextSibling {} => { - let id = self.last.unwrap(); - if let Some(parent) = self.parent(id) { - if let NodeType::Element { children, .. } = - &self[parent].node_data.node_type - { - let index = children.iter().position(|a| *a == id).unwrap(); - self.last = Some(children[index + 1]); - } - } else { - panic!("tried to call next sibling on a non element"); - } - } - ParentNode {} => { - if let Some(parent) = self.parent(self.last.unwrap()) { - self.last = Some(parent); - } else { - panic!("tried to call parent node on a non element"); - } - } - StoreWithId { id } => { - let old_id = self.last.unwrap(); - let node = self.internal_nodes.remove(old_id.as_unaccessable_id()); - let new_id = self.insert(*node, Some(id), &mut nodes_updated); - self.update_id(old_id, new_id, &mut nodes_updated); - } - SetLastNode { id } => { - self.last = - Some(RealNodeId::ElementId(dioxus_core::ElementId(id as usize))); + Remove { id } => { + let node_id = self.element_to_node_id(id); + self.remove(node_id); } } } } // remove any nodes that were created and then removed in the same mutations from the dirty nodes list - nodes_updated.retain(|n| match n.0 { - RealNodeId::ElementId(id) => self.nodes.get(id.0).and_then(|o| o.as_ref()).is_some(), - RealNodeId::UnaccessableId(id) => self.internal_nodes.get(id).is_some(), - }); + nodes_updated.retain(|n| self.tree.get(n.0).is_some()); nodes_updated } - /// Update refrences to an old node id to a new node id - fn update_id( - &mut self, - old_id: RealNodeId, - new_id: RealNodeId, - nodes_updated: &mut Vec<(RealNodeId, NodeMask)>, - ) { - // this is safe because a node cannot have itself as a child or parent - let unbouned_self = unsafe { &mut *(self as *mut Self) }; - // update parent's link to child id - if let Some(parent) = self[new_id].node_data.parent { - if let NodeType::Element { children, .. } = &mut self[parent].node_data.node_type { - for c in children { - if *c == old_id { - *c = new_id; - break; - } - } - } - } - // update child's link to parent id - if let NodeType::Element { children, .. } = &self[new_id].node_data.node_type { - for child_id in children { - unbouned_self[*child_id].node_data.parent = Some(new_id); - } - } - // update dirty nodes - for (node, _) in nodes_updated { - if *node == old_id { - *node = new_id; - } - } - // update nodes listening - for v in self.nodes_listening.values_mut() { - if v.contains(&old_id) { - v.remove(&old_id); - v.insert(new_id); - } - } - // update last - if let Some(last) = self.last { - if last == old_id { - self.last = Some(new_id); - } - } - } - - fn mark_dirty( - &self, - gid: RealNodeId, - mask: NodeMask, - dirty_nodes: &mut Vec<(RealNodeId, NodeMask)>, - ) { - dirty_nodes.push((gid, mask)); - } - /// Update the state of the dom, after appling some mutations. This will keep the nodes in the dom up to date with their VNode counterparts. pub fn update_state( &mut self, @@ -405,140 +343,6 @@ impl RealDom { S::update(&nodes_updated, &mut state_tree, &node_tree, &ctx) } - /// Link a child and parent together - fn link_child(&mut self, child_id: RealNodeId, parent_id: RealNodeId) -> Option<()> { - let parent = &mut self[parent_id]; - parent.add_child(child_id); - let parent_height = parent.node_data.height; - self[child_id].set_parent(parent_id); - self.set_height(child_id, parent_height + 1); - - Some(()) - } - - /// Link a child and parent together with the child inserted before a marker - fn link_child_before( - &mut self, - child_id: RealNodeId, - parent_id: RealNodeId, - marker: RealNodeId, - ) -> Option<()> { - let parent = &mut self[parent_id]; - if let NodeType::Element { children, .. } = &mut parent.node_data.node_type { - let index = children.iter().position(|a| *a == marker)?; - children.insert(index, child_id); - } - let parent_height = parent.node_data.height; - self[child_id].set_parent(parent_id); - self.set_height(child_id, parent_height + 1); - - Some(()) - } - - /// Link a child and parent together with the child inserted after a marker - fn link_child_after( - &mut self, - child_id: RealNodeId, - parent_id: RealNodeId, - marker: RealNodeId, - ) -> Option<()> { - let parent = &mut self[parent_id]; - if let NodeType::Element { children, .. } = &mut parent.node_data.node_type { - let index = children.iter().position(|a| *a == marker)?; - children.insert(index + 1, child_id); - } - let parent_height = parent.node_data.height; - self[child_id].set_parent(parent_id); - self.set_height(child_id, parent_height + 1); - - Some(()) - } - - /// Recursively increase the height of a node and its children - fn set_height(&mut self, id: RealNodeId, height: u16) { - let node = &mut self[id]; - node.node_data.height = height; - if let NodeType::Element { children, .. } = &node.node_data.node_type { - for c in children.clone() { - self.set_height(c, height + 1); - } - } - } - - // remove a node and it's children from the dom. - fn remove(&mut self, id: RealNodeId) -> Option> { - // We do not need to remove the node from the parent's children list for children. - fn inner(dom: &mut RealDom, id: RealNodeId) -> Option> { - let mut node = match id { - RealNodeId::ElementId(id) => *dom.nodes[id.0].take()?, - RealNodeId::UnaccessableId(id) => *dom.internal_nodes.remove(id), - }; - if let NodeType::Element { children, .. } = &mut node.node_data.node_type { - for c in children { - inner(dom, *c); - } - } - Some(node) - } - let mut node = match id { - RealNodeId::ElementId(id) => *self.nodes[id.0].take()?, - RealNodeId::UnaccessableId(id) => *self.internal_nodes.remove(id), - }; - if let Some(parent) = node.node_data.parent { - let parent = &mut self[parent]; - parent.remove_child(id); - } - if let NodeType::Element { children, .. } = &mut node.node_data.node_type { - for c in children { - inner(self, *c)?; - } - } - Some(node) - } - - fn resize_to(&mut self, id: usize) { - let current_len = self.nodes.len(); - if current_len - 1 < id { - self.nodes.extend((0..1 + id - current_len).map(|_| None)); - } - } - - fn insert( - &mut self, - mut node: Node, - id: Option, - nodes_updated: &mut Vec<(RealNodeId, NodeMask)>, - ) -> RealNodeId { - match id { - Some(id) => { - let id = id as usize; - self.resize_to(id); - let real_id = RealNodeId::ElementId(ElementId(id)); - node.node_data.id = Some(real_id); - // move the old node to a new unaccessable id - if let Some(mut old) = self.nodes[id].take() { - let old_id = old.node_data.id.unwrap(); - let entry = self.internal_nodes.vacant_entry(); - let id = entry.key(); - let new_id = RealNodeId::UnaccessableId(id); - old.node_data.id = Some(real_id); - entry.insert(old); - self.update_id(old_id, new_id, nodes_updated); - } - self.nodes[id] = Some(Box::new(node)); - real_id - } - None => { - let entry = self.internal_nodes.vacant_entry(); - let id = entry.key(); - let real_id = RealNodeId::UnaccessableId(id); - node.node_data.id = Some(real_id); - entry.insert(Box::new(node)); - real_id - } - } - } - /// Find all nodes that are listening for an event, sorted by there height in the dom progressing starting at the bottom and progressing up. /// This can be useful to avoid creating duplicate events. pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&Node> { @@ -551,162 +355,37 @@ impl RealDom { } } - /// Check if the dom 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 dom_node = &self[id]; - match &dom_node.node_data.node_type { - NodeType::Element { - tag, - namespace, - children, - attributes, - listeners, - } => { - tag == e.tag - && namespace == &e.namespace - && children - .iter() - .zip( - e.children - .iter() - .map(|c| RealNodeId::ElementId(c.mounted_id())), - ) - .all(|(c1, c2)| *c1 == c2) - && e.children.iter().all(|c| { - self.contains_node(c) - && self[RealNodeId::ElementId(c.mounted_id())] - .node_data - .parent - == e.id.get().map(RealNodeId::ElementId) - }) - && attributes - .iter() - .zip(e.attributes.iter()) - .all(|((disc, val), b)| *disc == b.attribute && *val == b.value) - && listeners - .iter() - .zip(e.listeners.iter()) - .all(|(a, b)| *a == b.event) - } - _ => 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 dom_node = &self[RealNodeId::ElementId(id)]; - match &dom_node.node_data.node_type { - NodeType::Text { text } => t.text == text, - _ => false, - } - } else { - true - } - } - VNode::TemplateRef(_) => todo!(), - } - } - /// Return the number of nodes in the dom. pub fn size(&self) -> usize { // The dom has a root node, ignore it. - self.nodes.iter().filter(|n| n.is_some()).count() - 1 + self.tree.size() - 1 } /// Returns the id of the root node. - pub fn root_id(&self) -> usize { - self.root + pub fn root_id(&self) -> NodeId { + self.tree.root() } - /// Call a function for each node in the dom, depth first. - pub fn traverse_depth_first(&self, mut f: impl FnMut(&Node)) { - fn inner(dom: &RealDom, id: RealNodeId, f: &mut impl FnMut(&Node)) { - let node = &dom[id]; - f(node); - if let NodeType::Element { children, .. } = &node.node_data.node_type { - for c in children { - inner(dom, *c, f); - } - } - } - if let NodeType::Element { children, .. } = &self - [RealNodeId::ElementId(ElementId(self.root))] - .node_data - .node_type - { - for c in children { - inner(self, *c, &mut f); - } - } - } - - /// Call a function for each node in the dom, depth first. - pub fn traverse_depth_first_mut(&mut self, mut f: impl FnMut(&mut Node)) { - fn inner(dom: &mut RealDom, id: RealNodeId, f: &mut impl FnMut(&mut Node)) { - let node = &mut dom[id]; - f(node); - if let NodeType::Element { children, .. } = &mut node.node_data.node_type { - for c in children.clone() { - inner(dom, c, f); - } - } - } - let root = self.root; - if let NodeType::Element { children, .. } = &mut self - [RealNodeId::ElementId(ElementId(root))] - .node_data - .node_type - { - for c in children.clone() { - inner(self, c, &mut f); - } - } - } - - pub fn split( - &mut self, - ) -> ( - impl Traversable + '_, - impl Traversable + '_, - ) { + pub fn split(&mut self) -> (impl TreeView + '_, impl TreeView + '_) { let raw = self as *mut Self; - // this is safe beacuse the traversable trait does not allow mutation of the position of elements, and within elements the access is disjoint. + // this is safe beacuse the treeview trait does not allow mutation of the position of elements, and within elements the access is disjoint. ( unsafe { &mut *raw }.map(|n| &n.state, |n| &mut n.state), unsafe { &mut *raw }.map(|n| &n.node_data, |n| &mut n.node_data), ) } - /// Recurively clones a node and marks it and it's children as dirty. - fn clone_node_into( - &mut self, - id: RealNodeId, - nodes_updated: &mut Vec<(RealNodeId, NodeMask)>, - new_id: Option, - ) -> RealNodeId { - let new_id = self.insert(self[id].clone(), new_id, nodes_updated); - nodes_updated.push((new_id, NodeMask::ALL)); - // this is safe because no node has itself as a child. - let unbounded_self = unsafe { &mut *(self as *mut Self) }; - let mut node = &mut self[new_id]; - node.node_data.height = 0; - if let NodeType::Element { children, .. } = &mut node.node_data.node_type { - for c in children { - let child_id = unbounded_self.clone_node_into(*c, nodes_updated, None); - *c = child_id; - let parent_height = node.node_data.height; - unbounded_self[child_id].set_parent(new_id); - unbounded_self.set_height(child_id, parent_height + 1); + fn clone_node(&mut self, node_id: NodeId) -> RealNodeId { + let node = self.tree.get(node_id).unwrap(); + let new_node = node.clone(); + let new_id = self.create_node(new_node); + let self_ptr = self as *mut Self; + for child in self.tree.children_ids(node_id).unwrap() { + unsafe { + // this is safe because no node has itself as a child + let self_mut = &mut *self_ptr; + let child_id = self_mut.clone_node(*child); + self_mut.tree.add_child(new_id, child_id); } } new_id @@ -716,8 +395,8 @@ impl RealDom { impl Index for RealDom { type Output = Node; - fn index(&self, idx: ElementId) -> &Self::Output { - self.get(RealNodeId::ElementId(idx)).unwrap() + fn index(&self, id: ElementId) -> &Self::Output { + self.tree.get(self.element_to_node_id(id)).unwrap() } } @@ -725,62 +404,32 @@ impl Index for RealDom { type Output = Node; fn index(&self, idx: RealNodeId) -> &Self::Output { - self.get(idx).unwrap() + self.tree.get(idx).unwrap() } } impl IndexMut for RealDom { - fn index_mut(&mut self, idx: ElementId) -> &mut Self::Output { - self.get_mut(RealNodeId::ElementId(idx)).unwrap() + fn index_mut(&mut self, id: ElementId) -> &mut Self::Output { + self.tree.get_mut(self.element_to_node_id(id)).unwrap() } } impl IndexMut for RealDom { fn index_mut(&mut self, idx: RealNodeId) -> &mut Self::Output { - self.get_mut(idx).unwrap() + self.tree.get_mut(idx).unwrap() } } -impl Traversable for RealDom { - type Id = RealNodeId; - type Node = Node; +impl Deref for RealDom { + type Target = Tree>; - fn height(&self, id: Self::Id) -> Option { - let node = ::get(self, id); - Some(node?.node_data.height) + fn deref(&self) -> &Self::Target { + &self.tree } +} - fn get(&self, id: Self::Id) -> Option<&Self::Node> { - match id { - RealNodeId::ElementId(id) => { - self.nodes.get(id.0).and_then(|b| b.as_ref().map(|b| &**b)) - } - RealNodeId::UnaccessableId(id) => self.internal_nodes.get(id).map(|b| &**b), - } - } - - fn get_mut(&mut self, id: Self::Id) -> Option<&mut Self::Node> { - match id { - RealNodeId::ElementId(id) => self - .nodes - .get_mut(id.0) - .and_then(|b| b.as_mut().map(|b| &mut **b)), - RealNodeId::UnaccessableId(id) => self.internal_nodes.get_mut(id).map(|b| &mut **b), - } - } - - fn children(&self, id: Self::Id) -> &[Self::Id] { - if let Some(node) = ::get(self, id) { - match &node.node_data.node_type { - NodeType::Element { children, .. } => children, - _ => &[], - } - } else { - &[] - } - } - - fn parent(&self, id: Self::Id) -> Option { - self.get(id).and_then(|n| n.node_data.parent) +impl DerefMut for RealDom { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.tree } } diff --git a/packages/native-core/src/state.rs b/packages/native-core/src/state.rs index d858a2a78..9657a1c21 100644 --- a/packages/native-core/src/state.rs +++ b/packages/native-core/src/state.rs @@ -1,17 +1,18 @@ -use std::{cmp::Ordering, fmt::Debug}; +use std::cmp::Ordering; use crate::node::NodeData; use crate::node_ref::{NodeMask, NodeView}; +use crate::tree::TreeView; use crate::RealNodeId; use anymap::AnyMap; use rustc_hash::FxHashSet; /// Join two sorted iterators -pub(crate) fn union_ordered_iter( - s_iter: impl Iterator, - o_iter: impl Iterator, +pub(crate) fn union_ordered_iter<'a>( + s_iter: impl Iterator, + o_iter: impl Iterator, new_len_guess: usize, -) -> Vec { +) -> Vec { let mut s_peekable = s_iter.peekable(); let mut o_peekable = o_iter.peekable(); let mut v = Vec::with_capacity(new_len_guess); @@ -22,7 +23,7 @@ pub(crate) fn union_ordered_iter( break; } Ordering::Less => { - v.push(o_peekable.next().unwrap()); + v.push(o_peekable.next().unwrap().to_string()); } Ordering::Equal => { o_peekable.next(); @@ -30,10 +31,10 @@ pub(crate) fn union_ordered_iter( } } } - v.push(s_peekable.next().unwrap()); + v.push(s_peekable.next().unwrap().to_string()); } for o_i in o_peekable { - v.push(o_i); + v.push(o_i.to_string()); } for w in v.windows(2) { debug_assert!(w[1] > w[0]); @@ -207,11 +208,7 @@ pub trait NodeDepState { /// Do not implement this trait. It is only meant to be derived and used through [crate::real_dom::RealDom]. pub trait State: Default + Clone { #[doc(hidden)] - fn update< - 'a, - T: Traversable, - T2: Traversable, - >( + fn update<'a, T: TreeView, T2: TreeView>( dirty: &[(RealNodeId, NodeMask)], state_tree: &'a mut T, rdom: &'a T2, diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index 76769ee54..6931d2e23 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -7,7 +7,7 @@ use std::collections::VecDeque; use std::marker::PhantomData; use std::sync::Arc; -#[derive(PartialEq, Eq, Clone, Copy, Debug, PartialOrd, Ord)] +#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug, PartialOrd, Ord)] pub struct NodeId(pub usize); #[derive(PartialEq, Eq, Clone, Debug)] @@ -23,6 +23,31 @@ pub struct Tree { root: NodeId, } +impl Tree { + fn try_remove(&mut self, id: NodeId) -> Option> { + self.nodes.try_remove(id.0).map(|node| { + if let Some(parent) = node.parent { + self.nodes + .get_mut(parent.0) + .unwrap() + .children + .retain(|child| child != &id); + } + for child in &node.children { + self.remove_recursive(*child); + } + node + }) + } + + fn remove_recursive(&mut self, node: NodeId) { + let node = self.nodes.remove(node.0); + for child in node.children { + self.remove_recursive(child); + } + } +} + pub trait TreeView: Sized { type Iterator<'a>: Iterator where @@ -152,15 +177,19 @@ pub trait TreeView: Sized { pub trait TreeLike: TreeView { fn new(root: T) -> Self; - fn add_child(&mut self, parent: NodeId, value: T) -> NodeId; + fn create_node(&mut self, value: T) -> NodeId; + + fn add_child(&mut self, parent: NodeId, child: NodeId); fn remove(&mut self, id: NodeId) -> Option; - fn replace(&mut self, id: NodeId, value: T); + fn remove_all_children(&mut self, id: NodeId) -> Vec; - fn insert_before(&mut self, id: NodeId, value: T) -> NodeId; + fn replace(&mut self, old: NodeId, new: NodeId); - fn insert_after(&mut self, id: NodeId, value: T) -> NodeId; + fn insert_before(&mut self, id: NodeId, new: NodeId); + + fn insert_after(&mut self, id: NodeId, new: NodeId); } pub struct ChildNodeIterator<'a, T, Tr: TreeView> { @@ -304,78 +333,75 @@ impl TreeLike for Tree { Self { nodes, root } } - fn add_child(&mut self, parent: NodeId, value: T) -> NodeId { - let node = Node { + fn create_node(&mut self, value: T) -> NodeId { + NodeId(self.nodes.insert(Node { value, - parent: Some(parent), + parent: None, children: Vec::new(), - }; - let id = self.nodes.insert(node); - self.nodes - .get_mut(parent.0) - .unwrap() - .children - .push(NodeId(id)); - NodeId(id) + })) + } + + fn add_child(&mut self, parent: NodeId, new: NodeId) { + self.nodes.get_mut(parent.0).unwrap().children.push(new); + self.nodes.get_mut(new.0).unwrap().parent = Some(parent); } fn remove(&mut self, id: NodeId) -> Option { - self.nodes.try_remove(id.0).map(|node| { - if let Some(parent) = node.parent { - self.nodes - .get_mut(parent.0) - .unwrap() - .children - .retain(|child| child != &id); + self.try_remove(id).map(|node| node.value) + } + + fn remove_all_children(&mut self, id: NodeId) -> Vec { + let mut children = Vec::new(); + let self_mut = self as *mut Self; + for child in self.children_ids(id).unwrap() { + unsafe { + // Safety: No node has itself as a child + children.push((*self_mut).remove(*child).unwrap()); } - node.value - }) + } + children } - fn replace(&mut self, id: NodeId, value: T) { + fn replace(&mut self, old_id: NodeId, new_id: NodeId) { + // remove the old node let old = self - .nodes - .get_mut(id.0) + .try_remove(old_id) .expect("tried to replace a node that doesn't exist"); - old.value = value; + // update the parent's link to the child + if let Some(parent_id) = old.parent { + let parent = self.nodes.get_mut(parent_id.0).unwrap(); + for id in &mut parent.children { + if *id == old_id { + *id = new_id; + } + } + } } - fn insert_before(&mut self, id: NodeId, value: T) -> NodeId { + fn insert_before(&mut self, id: NodeId, new: NodeId) { let node = self.nodes.get(id.0).unwrap(); let parent_id = node.parent.expect("tried to insert before root"); - let new = Node { - value, - parent: Some(parent_id), - children: Vec::new(), - }; - let new_id = NodeId(self.nodes.insert(new)); + self.nodes.get_mut(new.0).unwrap().parent = Some(parent_id); let parent = self.nodes.get_mut(parent_id.0).unwrap(); let index = parent .children .iter() .position(|child| child == &id) .unwrap(); - parent.children.insert(index, new_id); - new_id + parent.children.insert(index, new); } - fn insert_after(&mut self, id: NodeId, value: T) -> NodeId { + fn insert_after(&mut self, id: NodeId, new: NodeId) { let node = self.nodes.get(id.0).unwrap(); let parent_id = node.parent.expect("tried to insert before root"); - let new = Node { - value, - parent: Some(parent_id), - children: Vec::new(), - }; - let new_id = NodeId(self.nodes.insert(new)); + self.nodes.get_mut(new.0).unwrap().parent = Some(parent_id); let parent = self.nodes.get_mut(parent_id.0).unwrap(); let index = parent .children .iter() .position(|child| child == &id) .unwrap(); - parent.children.insert(index + 1, new_id); - new_id + parent.children.insert(index + 1, new); } } @@ -498,7 +524,7 @@ pub struct SharedView<'a, T, Tr: TreeView> { impl<'a, T, Tr: TreeView> SharedView<'a, T, Tr> { /// Checks if a node is currently locked. Returns None if the node does not exist. pub fn check_lock(&self, id: NodeId) -> Option { - let mut locks = self.node_locks.read(); + let locks = self.node_locks.read(); locks.get(id.0).map(|lock| lock.is_locked()) } } @@ -616,7 +642,8 @@ impl<'a, T, Tr: TreeView> TreeView for SharedView<'a, T, Tr> { fn creation() { let mut tree = Tree::new(1); let parent = tree.root(); - let child = tree.add_child(parent, 0); + let child = tree.create_node(0); + tree.add_child(parent, child); println!("Tree: {:#?}", tree); assert_eq!(tree.size(), 2); @@ -631,9 +658,12 @@ fn creation() { fn insertion() { let mut tree = Tree::new(0); let parent = tree.root(); - let child = tree.add_child(parent, 2); - let before = tree.insert_before(child, 1); - let after = tree.insert_after(child, 3); + let child = tree.create_node(2); + tree.add_child(parent, child); + let before = tree.create_node(1); + tree.insert_before(child, before); + let after = tree.create_node(3); + tree.insert_after(child, after); println!("Tree: {:#?}", tree); assert_eq!(tree.size(), 4); @@ -651,9 +681,12 @@ fn insertion() { fn deletion() { let mut tree = Tree::new(0); let parent = tree.root(); - let child = tree.add_child(parent, 2); - let before = tree.insert_before(child, 1); - let after = tree.insert_after(child, 3); + let child = tree.create_node(2); + tree.add_child(parent, child); + let before = tree.create_node(1); + tree.insert_before(child, before); + let after = tree.create_node(3); + tree.insert_after(child, after); println!("Tree: {:#?}", tree); assert_eq!(tree.size(), 4); @@ -702,7 +735,8 @@ fn shared_view() { use std::thread; let mut tree = Tree::new(1); let parent = tree.root(); - let child = tree.add_child(parent, 0); + let child = tree.create_node(0); + tree.add_child(parent, child); let shared = SharedView::new(&mut tree); @@ -737,7 +771,8 @@ fn map() { } let mut tree = Tree::new(Value::new(1)); let parent = tree.root(); - let child = tree.add_child(parent, Value::new(0)); + let child = tree.create_node(Value::new(0)); + tree.add_child(parent, child); let mut mapped = tree.map(|x| &x.value, |x| &mut x.value); @@ -752,10 +787,14 @@ fn map() { fn traverse_depth_first() { let mut tree = Tree::new(0); let parent = tree.root(); - let child1 = tree.add_child(parent, 1); - tree.add_child(child1, 2); - let child2 = tree.add_child(parent, 3); - tree.add_child(child2, 4); + let child1 = tree.create_node(1); + tree.add_child(parent, child1); + let grandchild1 = tree.create_node(2); + tree.add_child(child1, grandchild1); + let child2 = tree.create_node(3); + tree.add_child(parent, child2); + let grandchild2 = tree.create_node(4); + tree.add_child(child2, grandchild2); let mut node_count = 0; tree.traverse_depth_first(move |node| { @@ -768,10 +807,14 @@ fn traverse_depth_first() { fn traverse_breadth_first() { let mut tree = Tree::new(0); let parent = tree.root(); - let child1 = tree.add_child(parent, 1); - tree.add_child(child1, 3); - let child2 = tree.add_child(parent, 2); - tree.add_child(child2, 4); + let child1 = tree.create_node(1); + tree.add_child(parent, child1); + let grandchild1 = tree.create_node(3); + tree.add_child(child1, grandchild1); + let child2 = tree.create_node(2); + tree.add_child(parent, child2); + let grandchild2 = tree.create_node(4); + tree.add_child(child2, grandchild2); let mut node_count = 0; tree.traverse_breadth_first(move |node| { From f9059c3f30defe5a65f16ebf9823650a4f0ad706 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 24 Nov 2022 17:41:07 -0600 Subject: [PATCH 03/93] handle height --- packages/native-core/src/node.rs | 2 - packages/native-core/src/real_dom.rs | 118 ++++++++++++++---------- packages/native-core/src/state.rs | 82 +++++++++++----- packages/native-core/src/tree.rs | 29 +++--- packages/native-core/src/worker_pool.rs | 1 + 5 files changed, 146 insertions(+), 86 deletions(-) create mode 100644 packages/native-core/src/worker_pool.rs diff --git a/packages/native-core/src/node.rs b/packages/native-core/src/node.rs index 09dd03e71..f73e0dc07 100644 --- a/packages/native-core/src/node.rs +++ b/packages/native-core/src/node.rs @@ -21,7 +21,6 @@ pub struct NodeData { pub node_type: NodeType, /// The number of parents before the root node. The root node has height 1. pub height: u16, - height_dirty: bool, } /// A type of node with data specific to the node type. The types are a subset of the [VNode] types. @@ -48,7 +47,6 @@ impl Node { node_type, height: 0, node_id: NodeId(0), - height_dirty: true, }, } } diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index cbeb80f2b..164fcd3e1 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -1,7 +1,7 @@ use anymap::AnyMap; use dioxus_core::{ElementId, Mutations}; use rustc_hash::{FxHashMap, FxHashSet}; -use std::ops::{Deref, DerefMut, Index, IndexMut}; +use std::ops::{Index, IndexMut}; use crate::node::{Node, NodeData, NodeType, OwnedAttributeDiscription}; use crate::node_ref::{AttributeMask, NodeMask}; @@ -9,6 +9,18 @@ use crate::state::State; use crate::tree::{NodeId, Tree, TreeLike, TreeView}; use crate::RealNodeId; +fn mark_dirty( + node_id: NodeId, + mask: NodeMask, + nodes_updated: &mut FxHashMap, +) { + if let Some(node) = nodes_updated.get_mut(&node_id) { + *node = node.union(&mask); + } else { + nodes_updated.insert(node_id, mask); + } +} + /// 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. /// To get started implement [crate::state::ParentDepState], [crate::state::NodeDepState], or [crate::state::ChildDepState] and call [RealDom::apply_mutations] to update the dom and [RealDom::update_state] to update the state of the nodes. @@ -79,12 +91,31 @@ impl RealDom { node_id } + fn add_child(&mut self, node_id: RealNodeId, child_id: RealNodeId) { + self.tree.add_child(node_id, child_id); + self.resolve_height(child_id); + } + + fn resolve_height(&mut self, node_id: RealNodeId) { + if let Some((node, parent)) = self.tree.node_parent_mut(node_id) { + let height = parent.node_data.height; + node.node_data.height = height + 1; + unsafe { + let self_mut = self as *mut Self; + // Safety: No node will have itself as a child + for child in self.tree.children_ids(node_id).unwrap() { + (*self_mut).resolve_height(*child); + } + } + } + } + /// Updates the dom with some mutations and return a set of nodes that were updated. Pass the dirty nodes to update_state. - pub fn apply_mutations<'a>( + pub fn apply_mutations( &mut self, - mutations_vec: Vec>, - ) -> Vec<(RealNodeId, NodeMask)> { - let mut nodes_updated: Vec<(RealNodeId, NodeMask)> = Vec::new(); + mutations_vec: Vec, + ) -> FxHashMap { + let mut nodes_updated: FxHashMap = FxHashMap::default(); for mutations in mutations_vec { for e in mutations.edits { use dioxus_core::Mutation::*; @@ -93,12 +124,12 @@ impl RealDom { let data = self.stack.split_off(m); let (parent, children) = data.split_first().unwrap(); for child in children { - self.tree.add_child(*parent, *child); - nodes_updated.push((*parent, NodeMask::ALL)); + self.add_child(*parent, *child); + mark_dirty(*parent, NodeMask::ALL, &mut nodes_updated); } } AssignId { path, id } => { - self.set_element_id(self.load_child(&path), id); + self.set_element_id(self.load_child(path), id); } CreateElement { name } => { let node = Node::new(NodeType::Element { @@ -109,7 +140,7 @@ impl RealDom { }); let id = self.create_node(node); self.stack.push(id); - nodes_updated.push((id, NodeMask::ALL)); + mark_dirty(id, NodeMask::ALL, &mut nodes_updated); } CreateElementNamespace { name, namespace } => { let node = Node::new(NodeType::Element { @@ -120,20 +151,20 @@ impl RealDom { }); let id = self.create_node(node); self.stack.push(id); - nodes_updated.push((id, NodeMask::ALL)); + mark_dirty(id, NodeMask::ALL, &mut nodes_updated); } CreatePlaceholder { id } => { let node = Node::new(NodeType::Placeholder); let node_id = self.create_node(node); self.set_element_id(node_id, id); self.stack.push(node_id); - nodes_updated.push((node_id, NodeMask::ALL)); + mark_dirty(node_id, NodeMask::ALL, &mut nodes_updated); } CreateStaticPlaceholder => { let node = Node::new(NodeType::Placeholder); let id = self.create_node(node); self.stack.push(id); - nodes_updated.push((id, NodeMask::ALL)); + mark_dirty(id, NodeMask::ALL, &mut nodes_updated); } CreateStaticText { value } => { let node = Node::new(NodeType::Text { @@ -141,7 +172,7 @@ impl RealDom { }); let id = self.create_node(node); self.stack.push(id); - nodes_updated.push((id, NodeMask::new().with_text())); + mark_dirty(id, NodeMask::new().with_text(), &mut nodes_updated); } CreateTextNode { value, id } => { let node = Node::new(NodeType::Text { @@ -151,7 +182,7 @@ impl RealDom { let node = self.tree.get_mut(node_id).unwrap(); node.node_data.element_id = Some(id); self.stack.push(node_id); - nodes_updated.push((node_id, NodeMask::new().with_text())); + mark_dirty(node_id, NodeMask::new().with_text(), &mut nodes_updated); } HydrateText { path, value, id } => { let node_id = self.load_child(path); @@ -160,7 +191,7 @@ impl RealDom { if let NodeType::Text { text } = &mut node.node_data.node_type { *text = value.to_string(); } - nodes_updated.push((node_id, NodeMask::new().with_text())); + mark_dirty(node_id, NodeMask::new().with_text(), &mut nodes_updated); } LoadTemplate { name, index, id } => { let template_id = self.templates[name][index]; @@ -172,7 +203,7 @@ impl RealDom { let old_node_id = self.element_to_node_id(id); for new in new_nodes { self.tree.insert_after(old_node_id, new); - nodes_updated.push((new, NodeMask::ALL)); + mark_dirty(new, NodeMask::ALL, &mut nodes_updated); } self.tree.remove(old_node_id); } @@ -181,7 +212,7 @@ impl RealDom { let old_node_id = self.load_child(path); for new in new_nodes { self.tree.insert_after(old_node_id, new); - nodes_updated.push((new, NodeMask::ALL)); + mark_dirty(new, NodeMask::ALL, &mut nodes_updated); } self.tree.remove(old_node_id); } @@ -190,7 +221,7 @@ impl RealDom { let old_node_id = self.element_to_node_id(id); for new in new_nodes { self.tree.insert_after(old_node_id, new); - nodes_updated.push((new, NodeMask::ALL)); + mark_dirty(new, NodeMask::ALL, &mut nodes_updated); } } InsertBefore { id, m } => { @@ -198,7 +229,7 @@ impl RealDom { let old_node_id = self.element_to_node_id(id); for new in new_nodes { self.tree.insert_before(old_node_id, new); - nodes_updated.push((new, NodeMask::ALL)); + mark_dirty(new, NodeMask::ALL, &mut nodes_updated); } } SaveTemplate { name, m } => { @@ -223,10 +254,11 @@ impl RealDom { }, crate::node::OwnedAttributeValue::Text(value.to_string()), ); - nodes_updated.push(( + mark_dirty( node_id, NodeMask::new_with_attrs(AttributeMask::single(name)), - )); + &mut nodes_updated, + ); } } SetStaticAttribute { name, value, ns } => { @@ -242,10 +274,11 @@ impl RealDom { }, crate::node::OwnedAttributeValue::Text(value.to_string()), ); - nodes_updated.push(( + mark_dirty( *node_id, NodeMask::new_with_attrs(AttributeMask::single(name)), - )); + &mut nodes_updated, + ); } } SetBoolAttribute { name, value, id } => { @@ -261,10 +294,11 @@ impl RealDom { }, crate::node::OwnedAttributeValue::Bool(value), ); - nodes_updated.push(( + mark_dirty( node_id, NodeMask::new_with_attrs(AttributeMask::single(name)), - )); + &mut nodes_updated, + ); } } SetInnerText { value } => { @@ -276,7 +310,7 @@ impl RealDom { text: value.to_string(), }); let text_node_id = self.create_node(text_node); - self.tree.add_child(node_id, text_node_id); + self.add_child(node_id, text_node_id); } } SetText { value, id } => { @@ -285,7 +319,7 @@ impl RealDom { if let NodeType::Text { text } = &mut node.node_data.node_type { *text = value.to_string(); } - nodes_updated.push((node_id, NodeMask::new().with_text())); + mark_dirty(node_id, NodeMask::new().with_text(), &mut nodes_updated); } NewEventListener { event_name, @@ -321,14 +355,14 @@ impl RealDom { } Remove { id } => { let node_id = self.element_to_node_id(id); - self.remove(node_id); + self.tree.remove(node_id); } } } } // remove any nodes that were created and then removed in the same mutations from the dirty nodes list - nodes_updated.retain(|n| self.tree.get(n.0).is_some()); + nodes_updated.retain(|k, _| self.tree.get(*k).is_some()); nodes_updated } @@ -336,7 +370,7 @@ impl RealDom { /// Update the state of the dom, after appling some mutations. This will keep the nodes in the dom up to date with their VNode counterparts. pub fn update_state( &mut self, - nodes_updated: Vec<(RealNodeId, NodeMask)>, + nodes_updated: FxHashMap, ctx: AnyMap, ) -> FxHashSet { let (mut state_tree, node_tree) = self.split(); @@ -370,8 +404,12 @@ impl RealDom { let raw = self as *mut Self; // this is safe beacuse the treeview trait does not allow mutation of the position of elements, and within elements the access is disjoint. ( - unsafe { &mut *raw }.map(|n| &n.state, |n| &mut n.state), - unsafe { &mut *raw }.map(|n| &n.node_data, |n| &mut n.node_data), + unsafe { &mut *raw } + .tree + .map(|n| &n.state, |n| &mut n.state), + unsafe { &mut *raw } + .tree + .map(|n| &n.node_data, |n| &mut n.node_data), ) } @@ -385,7 +423,7 @@ impl RealDom { // this is safe because no node has itself as a child let self_mut = &mut *self_ptr; let child_id = self_mut.clone_node(*child); - self_mut.tree.add_child(new_id, child_id); + self_mut.add_child(new_id, child_id); } } new_id @@ -419,17 +457,3 @@ impl IndexMut for RealDom { self.tree.get_mut(idx).unwrap() } } - -impl Deref for RealDom { - type Target = Tree>; - - fn deref(&self) -> &Self::Target { - &self.tree - } -} - -impl DerefMut for RealDom { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.tree - } -} diff --git a/packages/native-core/src/state.rs b/packages/native-core/src/state.rs index 9657a1c21..18d405ed7 100644 --- a/packages/native-core/src/state.rs +++ b/packages/native-core/src/state.rs @@ -5,7 +5,7 @@ use crate::node_ref::{NodeMask, NodeView}; use crate::tree::TreeView; use crate::RealNodeId; use anymap::AnyMap; -use rustc_hash::FxHashSet; +use rustc_hash::{FxHashMap, FxHashSet}; /// Join two sorted iterators pub(crate) fn union_ordered_iter<'a>( @@ -81,15 +81,15 @@ pub trait ChildDepState { /// The context is passed to the [ChildDepState::reduce] when it is resolved. type Ctx; /// A state from each child node that this node depends on. Typically this is Self, but it could be any state that is within the state tree. - /// This must be either a [ChildDepState], or [NodeDepState]. - type DepState; + /// Depstate must be a tuple containing any number of borrowed elements that are either [ChildDepState] or [NodeDepState]. + type DepState: ElementBorrowable; /// The part of a node that this state cares about. This is used to determine if the state should be updated when a node is updated. const NODE_MASK: NodeMask = NodeMask::NONE; /// Resolve the state current node's state from the state of the children, the state of the node, and some external context. fn reduce<'a>( &mut self, node: NodeView, - children: impl Iterator, + children: impl Iterator::ElementBorrowed<'a>>, ctx: &Self::Ctx, ) -> bool where @@ -143,15 +143,15 @@ pub trait ParentDepState { /// The context is passed to the [ParentDepState::reduce] when it is resolved. type Ctx; /// A state from from the parent node that this node depends on. Typically this is Self, but it could be any state that is within the state tree. - /// This must be either a [ParentDepState] or [NodeDepState] - type DepState; + /// Depstate must be a tuple containing any number of borrowed elements that are either [ParentDepState] or [NodeDepState]. + type DepState: ElementBorrowable; /// The part of a node that this state cares about. This is used to determine if the state should be updated when a node is updated. const NODE_MASK: NodeMask = NodeMask::NONE; /// Resolve the state current node's state from the state of the parent node, the state of the node, and some external context. fn reduce<'a>( &mut self, node: NodeView, - parent: Option<&'a Self::DepState>, + parent: Option<::ElementBorrowed<'a>>, ctx: &Self::Ctx, ) -> bool; } @@ -193,23 +193,27 @@ pub trait ParentDepState { /// } /// } /// ``` -/// The generic argument (Depstate) must be a tuple containing any number of borrowed elements that are either a [ChildDepState], [ParentDepState] or [NodeDepState]. -// Todo: once GATs land we can model multable dependencies better - -pub trait NodeDepState { +pub trait NodeDepState { + /// Depstate must be a tuple containing any number of borrowed elements that are either [ChildDepState], [ParentDepState] or [NodeDepState]. + type DepState: ElementBorrowable; /// The state passed to [NodeDepState::reduce] when it is resolved. type Ctx; /// The part of a node that this state cares about. This is used to determine if the state should be updated when a node is updated. const NODE_MASK: NodeMask = NodeMask::NONE; /// Resolve the state current node's state from the state of the sibling states, the state of the node, and some external context. - fn reduce(&mut self, node: NodeView, siblings: DepState, ctx: &Self::Ctx) -> bool; + fn reduce<'a>( + &mut self, + node: NodeView, + siblings: ::ElementBorrowed<'a>, + ctx: &Self::Ctx, + ) -> bool; } /// Do not implement this trait. It is only meant to be derived and used through [crate::real_dom::RealDom]. pub trait State: Default + Clone { #[doc(hidden)] fn update<'a, T: TreeView, T2: TreeView>( - dirty: &[(RealNodeId, NodeMask)], + dirty: &FxHashMap, state_tree: &'a mut T, rdom: &'a T2, ctx: &AnyMap, @@ -219,12 +223,7 @@ pub trait State: Default + Clone { impl ChildDepState for () { type Ctx = (); type DepState = (); - fn reduce<'a>( - &mut self, - _: NodeView, - _: impl Iterator, - _: &Self::Ctx, - ) -> bool + fn reduce<'a>(&mut self, _: NodeView, _: impl Iterator, _: &Self::Ctx) -> bool where Self::DepState: 'a, { @@ -235,14 +234,55 @@ impl ChildDepState for () { impl ParentDepState for () { type Ctx = (); type DepState = (); - fn reduce<'a>(&mut self, _: NodeView, _: Option<&'a Self::DepState>, _: &Self::Ctx) -> bool { + fn reduce<'a>(&mut self, _: NodeView, _: Option<()>, _: &Self::Ctx) -> bool { false } } -impl NodeDepState<()> for () { +impl NodeDepState for () { + type DepState = (); type Ctx = (); fn reduce(&mut self, _: NodeView, _sibling: (), _: &Self::Ctx) -> bool { false } } + +pub trait ElementBorrowable { + type ElementBorrowed<'a> + where + Self: 'a; + + fn borrow_elements(&self) -> Self::ElementBorrowed<'_>; +} + +macro_rules! impl_element_borrowable { + ($($t:ident),*) => { + impl< $($t),* > ElementBorrowable for ($($t,)*) { + type ElementBorrowed<'a> = ($(&'a $t,)*) where Self: 'a; + + #[allow(clippy::unused_unit, non_snake_case)] + fn borrow_elements<'a>(&'a self) -> Self::ElementBorrowed<'a> { + let ($($t,)*) = self; + ($(&$t,)*) + } + } + }; +} + +impl_element_borrowable!(); +impl_element_borrowable!(A); +impl_element_borrowable!(A, B); +impl_element_borrowable!(A, B, C); +impl_element_borrowable!(A, B, C, D); +impl_element_borrowable!(A, B, C, D, E); +impl_element_borrowable!(A, B, C, D, E, F); +impl_element_borrowable!(A, B, C, D, E, F, G); +impl_element_borrowable!(A, B, C, D, E, F, G, H); +impl_element_borrowable!(A, B, C, D, E, F, G, H, I); +impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J); +impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J, K); +impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J, K, L); +impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J, K, L, M); +impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J, K, L, M, N); +impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O); +impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P); diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index 6931d2e23..04138b855 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -72,14 +72,11 @@ pub trait TreeView: Sized { unsafe { self.get_mut(id).unwrap_unchecked() } } - fn children<'a>(&'a self, id: NodeId) -> Option>; + fn children(&self, id: NodeId) -> Option>; - fn children_mut<'a>(&'a mut self, id: NodeId) -> Option>; + fn children_mut(&mut self, id: NodeId) -> Option>; - fn parent_child_mut<'a>( - &'a mut self, - id: NodeId, - ) -> Option<(&'a mut T, Self::IteratorMut<'a>)> { + fn parent_child_mut(&mut self, id: NodeId) -> Option<(&mut T, Self::IteratorMut<'_>)> { let mut_ptr: *mut Self = self; unsafe { // Safety: No node has itself as a child. @@ -91,7 +88,7 @@ pub trait TreeView: Sized { } } - fn children_ids<'a>(&'a self, id: NodeId) -> Option<&'a [NodeId]>; + fn children_ids(&self, id: NodeId) -> Option<&[NodeId]>; fn parent(&self, id: NodeId) -> Option<&T>; @@ -257,7 +254,7 @@ impl TreeView for Tree { self.nodes.get_mut(id.0).map(|node| &mut node.value) } - fn children<'a>(&'a self, id: NodeId) -> Option> { + fn children(&self, id: NodeId) -> Option> { self.children_ids(id).map(|children_ids| ChildNodeIterator { tree: self, children_ids, @@ -266,7 +263,7 @@ impl TreeView for Tree { }) } - fn children_mut<'a>(&'a mut self, id: NodeId) -> Option> { + fn children_mut(&mut self, id: NodeId) -> Option> { let raw_ptr = self as *mut Self; unsafe { // Safety: No node will appear as a child twice @@ -280,7 +277,7 @@ impl TreeView for Tree { } } - fn children_ids<'a>(&'a self, id: NodeId) -> Option<&'a [NodeId]> { + fn children_ids(&self, id: NodeId) -> Option<&[NodeId]> { self.nodes.get(id.0).map(|node| node.children.as_slice()) } @@ -462,7 +459,7 @@ where self.tree.get_mut(id).map(|node| (self.map_mut)(node)) } - fn children<'b>(&'b self, id: NodeId) -> Option> { + fn children(&self, id: NodeId) -> Option> { self.children_ids(id).map(|children_ids| ChildNodeIterator { tree: self, children_ids, @@ -471,7 +468,7 @@ where }) } - fn children_mut<'b>(&'b mut self, id: NodeId) -> Option> { + fn children_mut(&mut self, id: NodeId) -> Option> { let raw_ptr = self as *mut Self; unsafe { // Safety: No node can be a child twice. @@ -485,7 +482,7 @@ where } } - fn children_ids<'b>(&'b self, id: NodeId) -> Option<&'b [NodeId]> { + fn children_ids(&self, id: NodeId) -> Option<&[NodeId]> { self.tree.children_ids(id) } @@ -609,15 +606,15 @@ impl<'a, T, Tr: TreeView> TreeView for SharedView<'a, T, Tr> { self.with_node(id, |t| t.get_mut(id)) } - fn children<'b>(&'b self, id: NodeId) -> Option> { + fn children(&self, id: NodeId) -> Option> { self.with_node(id, |t| t.children(id)) } - fn children_mut<'b>(&'b mut self, id: NodeId) -> Option> { + fn children_mut(&mut self, id: NodeId) -> Option> { self.with_node(id, |t| t.children_mut(id)) } - fn children_ids<'b>(&'b self, id: NodeId) -> Option<&'b [NodeId]> { + fn children_ids(&self, id: NodeId) -> Option<&[NodeId]> { self.with_node(id, |t| t.children_ids(id)) } diff --git a/packages/native-core/src/worker_pool.rs b/packages/native-core/src/worker_pool.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/packages/native-core/src/worker_pool.rs @@ -0,0 +1 @@ + From 5f4b08496cda242cbc20ea91570e937bd411f9a3 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 24 Nov 2022 17:41:12 -0600 Subject: [PATCH 04/93] update to taffy 0.2 --- packages/native-core/Cargo.toml | 3 +- packages/native-core/src/layout_attributes.rs | 42 +++++++++---------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index 09c08bc61..496faa893 100644 --- a/packages/native-core/Cargo.toml +++ b/packages/native-core/Cargo.toml @@ -15,12 +15,13 @@ dioxus-core = { path = "../core", version = "^0.2.1" } dioxus-html = { path = "../html", version = "^0.2.1" } dioxus-core-macro = { path = "../core-macro", version = "^0.2.1" } -taffy = "0.1.0" +taffy = "0.2.0" smallvec = "1.6" rustc-hash = "1.1.0" anymap = "0.12.1" slab = "0.4" parking_lot = "0.12.1" +crossbeam-deque = "0.8.2" [dev-dependencies] rand = "0.8.5" \ No newline at end of file diff --git a/packages/native-core/src/layout_attributes.rs b/packages/native-core/src/layout_attributes.rs index da23e937f..b59177e4e 100644 --- a/packages/native-core/src/layout_attributes.rs +++ b/packages/native-core/src/layout_attributes.rs @@ -343,14 +343,14 @@ fn apply_border(name: &str, value: &str, style: &mut Style) { "border-left" => {} "border-left-color" => {} "border-left-style" => { - if style.border.start == Dimension::default() { + if style.border.left == Dimension::default() { let v = Dimension::Points(1.0); - style.border.start = v; + style.border.left = v; } } "border-left-width" => { if let Some(v) = parse_value(value) { - style.border.start = v; + style.border.left = v; } } "border-radius" => {} @@ -358,11 +358,11 @@ fn apply_border(name: &str, value: &str, style: &mut Style) { "border-right-color" => {} "border-right-style" => { let v = Dimension::Points(1.0); - style.border.end = v; + style.border.right = v; } "border-right-width" => { if let Some(v) = parse_value(value) { - style.border.end = v; + style.border.right = v; } } "border-spacing" => {} @@ -375,13 +375,13 @@ fn apply_border(name: &str, value: &str, style: &mut Style) { let v = Dimension::Points(1.0); style.border.bottom = v; } - if style.border.start == Dimension::default() { + if style.border.left == Dimension::default() { let v = Dimension::Points(1.0); - style.border.start = v; + style.border.left = v; } - if style.border.end == Dimension::default() { + if style.border.right == Dimension::default() { let v = Dimension::Points(1.0); - style.border.end = v; + style.border.right = v; } } "border-top" => {} @@ -404,8 +404,8 @@ fn apply_border(name: &str, value: &str, style: &mut Style) { if values.len() == 1 { if let Some(dim) = parse_value(values[0]) { style.border = Rect { - start: dim, - end: dim, + right: dim, + left: dim, top: dim, bottom: dim, }; @@ -414,8 +414,8 @@ fn apply_border(name: &str, value: &str, style: &mut Style) { let border_widths = [ &mut style.border.top, &mut style.border.bottom, - &mut style.border.start, - &mut style.border.end, + &mut style.border.left, + &mut style.border.right, ]; for (v, width) in values.into_iter().zip(border_widths) { if let Some(w) = parse_value(v) { @@ -512,12 +512,12 @@ fn apply_padding(name: &str, value: &str, style: &mut Style) { "padding" => { style.padding.top = v; style.padding.bottom = v; - style.padding.start = v; - style.padding.end = v; + style.padding.left = v; + style.padding.right = v; } "padding-bottom" => style.padding.bottom = v, - "padding-left" => style.padding.start = v, - "padding-right" => style.padding.end = v, + "padding-left" => style.padding.left = v, + "padding-right" => style.padding.right = v, "padding-top" => style.padding.top = v, _ => {} } @@ -578,13 +578,13 @@ fn apply_margin(name: &str, value: &str, style: &mut Style) { "margin" => { style.margin.top = dim; style.margin.bottom = dim; - style.margin.start = dim; - style.margin.end = dim; + style.margin.left = dim; + style.margin.right = dim; } "margin-top" => style.margin.top = dim, "margin-bottom" => style.margin.bottom = dim, - "margin-left" => style.margin.start = dim, - "margin-right" => style.margin.end = dim, + "margin-left" => style.margin.left = dim, + "margin-right" => style.margin.right = dim, _ => {} } } From 21edea62be2fd90026f29c9bb392cfc07f4f6458 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 24 Nov 2022 17:43:55 -0600 Subject: [PATCH 05/93] add as_varient functions to OwnedAttributeValue --- packages/native-core/src/node.rs | 37 +++++++++++++++++++++++++ packages/native-core/src/worker_pool.rs | 19 +++++++++++++ 2 files changed, 56 insertions(+) diff --git a/packages/native-core/src/node.rs b/packages/native-core/src/node.rs index f73e0dc07..fc06862f9 100644 --- a/packages/native-core/src/node.rs +++ b/packages/native-core/src/node.rs @@ -83,3 +83,40 @@ pub enum OwnedAttributeValue { Bool(bool), None, } + +impl OwnedAttributeValue { + pub fn as_text(&self) -> Option<&str> { + match self { + OwnedAttributeValue::Text(text) => Some(text), + _ => None, + } + } + + pub fn as_float(&self) -> Option { + match self { + OwnedAttributeValue::Float(float) => Some(*float), + _ => None, + } + } + + pub fn as_int(&self) -> Option { + match self { + OwnedAttributeValue::Int(int) => Some(*int), + _ => None, + } + } + + pub fn as_bool(&self) -> Option { + match self { + OwnedAttributeValue::Bool(bool) => Some(*bool), + _ => None, + } + } + + pub fn as_none(&self) -> Option<()> { + match self { + OwnedAttributeValue::None => Some(()), + _ => None, + } + } +} diff --git a/packages/native-core/src/worker_pool.rs b/packages/native-core/src/worker_pool.rs index 8b1378917..5ecf377aa 100644 --- a/packages/native-core/src/worker_pool.rs +++ b/packages/native-core/src/worker_pool.rs @@ -1 +1,20 @@ +use crossbeam_deque::{Injector, Stealer, Worker}; +use std::iter; +fn find_task(local: &Worker, global: &Injector, stealers: &[Stealer]) -> Option { + // Pop a task from the local queue, if not empty. + local.pop().or_else(|| { + // Otherwise, we need to look for a task elsewhere. + iter::repeat_with(|| { + // Try stealing a batch of tasks from the global queue. + global + .steal_batch_and_pop(local) + // Or try stealing a task from one of the other threads. + .or_else(|| stealers.iter().map(|s| s.steal()).collect()) + }) + // Loop while no task was stolen and any steal operation needs to be retried. + .find(|s| !s.is_retry()) + // Extract the stolen task, if there is one. + .and_then(|s| s.success()) + }) +} From e08a2186a6ecf4f8b23cf92b586e5751295eb95c Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 24 Nov 2022 22:16:27 -0600 Subject: [PATCH 06/93] make get node parent mut optionally return a parent --- packages/native-core/src/real_dom.rs | 2 +- packages/native-core/src/tree.rs | 21 +++++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 164fcd3e1..e80510aa4 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -97,7 +97,7 @@ impl RealDom { } fn resolve_height(&mut self, node_id: RealNodeId) { - if let Some((node, parent)) = self.tree.node_parent_mut(node_id) { + if let Some((node, Some(parent))) = self.tree.node_parent_mut(node_id) { let height = parent.node_data.height; node.node_data.height = height + 1; unsafe { diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index 04138b855..c77724891 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -94,13 +94,13 @@ pub trait TreeView: Sized { fn parent_mut(&mut self, id: NodeId) -> Option<&mut T>; - fn node_parent_mut(&mut self, id: NodeId) -> Option<(&mut T, &mut T)> { + fn node_parent_mut(&mut self, id: NodeId) -> Option<(&mut T, Option<&mut T>)> { let mut_ptr: *mut Self = self; unsafe { // Safety: No node has itself as a parent. (*mut_ptr) .get_mut(id) - .and_then(|parent| (*mut_ptr).parent_mut(id).map(|children| (parent, children))) + .map(|node| (node, (*mut_ptr).parent_mut(id).map(|parent| parent))) } } @@ -819,3 +819,20 @@ fn traverse_breadth_first() { node_count += 1; }); } + +trait UpwardPass { + fn upward_pass(&mut self, node: &mut T, parent: Option<&mut T>) -> bool; + + fn resolve_pass(&mut self, tree: &mut impl TreeView, starting_nodes: &[NodeId]) { + let mut stack = Vec::new(); + for node in starting_nodes { + stack.push(*node); + } + while let Some(node_id) = stack.pop() { + let (node, parent) = tree.node_parent_mut(node_id).unwrap(); + if self.upward_pass(node, parent) { + stack.push(tree.parent_id(node_id).unwrap()); + } + } + } +} From 4096861c597a003ec247bafccf82b7c224b91bf2 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 24 Nov 2022 22:56:19 -0600 Subject: [PATCH 07/93] work on upward pass --- packages/native-core/src/tree.rs | 52 +++++++++++++++++++++---- packages/native-core/src/worker_pool.rs | 20 ---------- 2 files changed, 45 insertions(+), 27 deletions(-) delete mode 100644 packages/native-core/src/worker_pool.rs diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index c77724891..6645eabd3 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -1,4 +1,5 @@ use core::panic; +use crossbeam_deque::{Injector, Stealer, Worker}; use parking_lot::lock_api::RawMutex as _; use parking_lot::{RawMutex, RwLock}; use slab::Slab; @@ -6,6 +7,7 @@ use std::cell::UnsafeCell; use std::collections::VecDeque; use std::marker::PhantomData; use std::sync::Arc; +use std::thread; #[derive(Hash, PartialEq, Eq, Clone, Copy, Debug, PartialOrd, Ord)] pub struct NodeId(pub usize); @@ -824,15 +826,51 @@ trait UpwardPass { fn upward_pass(&mut self, node: &mut T, parent: Option<&mut T>) -> bool; fn resolve_pass(&mut self, tree: &mut impl TreeView, starting_nodes: &[NodeId]) { - let mut stack = Vec::new(); + let global = Injector::default(); for node in starting_nodes { - stack.push(*node); + global.push(*node); } - while let Some(node_id) = stack.pop() { - let (node, parent) = tree.node_parent_mut(node_id).unwrap(); - if self.upward_pass(node, parent) { - stack.push(tree.parent_id(node_id).unwrap()); + + let core_count = thread::available_parallelism() + .map(|c| c.get()) + .unwrap_or(1); + let workers: Vec<_> = (0..core_count).map(|_| Worker::new_fifo()).collect(); + let stealers: Vec<_> = workers.iter().map(|w| w.stealer()).collect(); + let shared_view = SharedView::new(tree); + thread::scope(|s| { + let global = &global; + let stealers = &stealers; + for (_, w) in (0..core_count).zip(workers.into_iter()) { + let mut shared_view = shared_view.clone(); + s.spawn(move || { + while let Some(id) = find_task(&w, &global, &stealers) { + let (node, parent) = shared_view.node_parent_mut(id).unwrap(); + if self.upward_pass(node, parent) { + if let Some(id) = shared_view.parent_id(id) { + w.push(id); + } + } + } + }); } - } + }); } } + +fn find_task(local: &Worker, global: &Injector, stealers: &[Stealer]) -> Option { + // Pop a task from the local queue, if not empty. + local.pop().or_else(|| { + // Otherwise, we need to look for a task elsewhere. + std::iter::repeat_with(|| { + // Try stealing a batch of tasks from the global queue. + global + .steal_batch_and_pop(local) + // Or try stealing a task from one of the other threads. + .or_else(|| stealers.iter().map(|s| s.steal()).collect()) + }) + // Loop while no task was stolen and any steal operation needs to be retried. + .find(|s| !s.is_retry()) + // Extract the stolen task, if there is one. + .and_then(|s| s.success()) + }) +} diff --git a/packages/native-core/src/worker_pool.rs b/packages/native-core/src/worker_pool.rs deleted file mode 100644 index 5ecf377aa..000000000 --- a/packages/native-core/src/worker_pool.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crossbeam_deque::{Injector, Stealer, Worker}; -use std::iter; - -fn find_task(local: &Worker, global: &Injector, stealers: &[Stealer]) -> Option { - // Pop a task from the local queue, if not empty. - local.pop().or_else(|| { - // Otherwise, we need to look for a task elsewhere. - iter::repeat_with(|| { - // Try stealing a batch of tasks from the global queue. - global - .steal_batch_and_pop(local) - // Or try stealing a task from one of the other threads. - .or_else(|| stealers.iter().map(|s| s.steal()).collect()) - }) - // Loop while no task was stolen and any steal operation needs to be retried. - .find(|s| !s.is_retry()) - // Extract the stolen task, if there is one. - .and_then(|s| s.success()) - }) -} From c7eeeef68e5875170ce543c68bee01e66a2f3fa5 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 25 Nov 2022 11:44:46 -0600 Subject: [PATCH 08/93] add more code for parrellel passes --- packages/native-core/Cargo.toml | 1 + packages/native-core/src/tree.rs | 238 +++++++++++++++++++++++++++---- 2 files changed, 213 insertions(+), 26 deletions(-) diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index 496faa893..0dc7e0d6e 100644 --- a/packages/native-core/Cargo.toml +++ b/packages/native-core/Cargo.toml @@ -22,6 +22,7 @@ anymap = "0.12.1" slab = "0.4" parking_lot = "0.12.1" crossbeam-deque = "0.8.2" +dashmap = "5.4.0" [dev-dependencies] rand = "0.8.5" \ No newline at end of file diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index 6645eabd3..f4ad67f2e 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -1,7 +1,14 @@ use core::panic; +use std::hash::BuildHasherDefault; +use std::sync::atomic::{AtomicU64, Ordering}; + use crossbeam_deque::{Injector, Stealer, Worker}; +use dashmap::DashSet; +use dioxus_core::ScopeId; +use dioxus_html::u; use parking_lot::lock_api::RawMutex as _; -use parking_lot::{RawMutex, RwLock}; +use parking_lot::{Mutex, RawMutex, RwLock, RwLockWriteGuard}; +use rustc_hash::{FxHashMap, FxHashSet, FxHasher}; use slab::Slab; use std::cell::UnsafeCell; use std::collections::VecDeque; @@ -822,39 +829,218 @@ fn traverse_breadth_first() { }); } +enum PassDirection { + Up, + Down, + Node, +} + +#[derive(PartialEq, Eq, Hash, Clone, Copy)] +struct PassId(u64); + trait UpwardPass { - fn upward_pass(&mut self, node: &mut T, parent: Option<&mut T>) -> bool; + fn pass_id(&self) -> PassId; + fn dependancies(&self) -> &'static [PassId]; + fn dependants(&self) -> &'static [PassId]; + fn upward_pass(&self, node: &mut T, parent: Option<&mut T>) -> bool; +} - fn resolve_pass(&mut self, tree: &mut impl TreeView, starting_nodes: &[NodeId]) { - let global = Injector::default(); - for node in starting_nodes { - global.push(*node); +trait DownwardPass { + fn pass_id(&self) -> PassId; + fn dependancies(&self) -> &'static [PassId]; + fn dependants(&self) -> &'static [PassId]; + fn downward_pass<'a>( + &self, + node: &mut T, + children: &mut dyn Iterator, + ) -> bool; +} + +trait NodePass { + fn pass_id(&self) -> PassId; + fn dependancies(&self) -> &'static [PassId]; + fn dependants(&self) -> &'static [PassId]; + fn node_pass(&self, node: &mut T) -> bool; +} + +enum AnyPass { + Upward(Box + Send + Sync>), + Downward(Box + Send + Sync>), + Node(Box + Send + Sync>), +} + +impl AnyPass { + fn pass_id(&self) -> PassId { + match self { + Self::Upward(pass) => pass.pass_id(), + Self::Downward(pass) => pass.pass_id(), + Self::Node(pass) => pass.pass_id(), } + } - let core_count = thread::available_parallelism() - .map(|c| c.get()) - .unwrap_or(1); - let workers: Vec<_> = (0..core_count).map(|_| Worker::new_fifo()).collect(); - let stealers: Vec<_> = workers.iter().map(|w| w.stealer()).collect(); - let shared_view = SharedView::new(tree); - thread::scope(|s| { - let global = &global; - let stealers = &stealers; - for (_, w) in (0..core_count).zip(workers.into_iter()) { - let mut shared_view = shared_view.clone(); - s.spawn(move || { - while let Some(id) = find_task(&w, &global, &stealers) { - let (node, parent) = shared_view.node_parent_mut(id).unwrap(); - if self.upward_pass(node, parent) { - if let Some(id) = shared_view.parent_id(id) { - w.push(id); + fn dependancies(&self) -> &'static [PassId] { + match self { + Self::Upward(pass) => pass.dependancies(), + Self::Downward(pass) => pass.dependancies(), + Self::Node(pass) => pass.dependancies(), + } + } + + fn dependants(&self) -> &'static [PassId] { + match self { + Self::Upward(pass) => pass.dependants(), + Self::Downward(pass) => pass.dependants(), + Self::Node(pass) => pass.dependants(), + } + } +} + +type FxDashSet = dashmap::DashSet>; +type FxDashMap = dashmap::DashMap>; + +#[derive(Default)] +struct DirtyNodeStates { + dirty: FxDashMap>, +} + +impl DirtyNodeStates { + fn new(starting_nodes: FxHashMap>) -> Self { + let mut this = Self::default(); + for (node, nodes) in starting_nodes { + for pass_id in nodes { + this.insert(pass_id, node); + } + } + this + } + + fn insert(&self, pass_id: PassId, node_id: NodeId) { + let pass_id = pass_id.0; + let index = pass_id / 64; + let bit = pass_id % 64; + let encoded = 1 << bit; + if let Some(dirty) = self.dirty.get(&node_id) { + if let Some(atomic) = dirty.get(index as usize) { + atomic.fetch_or(encoded, Ordering::Relaxed); + } else { + drop(dirty); + let mut write = self.dirty.get_mut(&node_id).unwrap(); + write.resize_with(index as usize + 1, || AtomicU64::new(0)); + write[index as usize].fetch_or(encoded, Ordering::Relaxed); + } + } else { + self.dirty.insert(node_id, vec![AtomicU64::new(encoded)]); + } + } + + fn all_dirty(&self, pass_id: PassId) -> impl Iterator + '_ { + let pass_id = pass_id.0; + let index = pass_id / 64; + let bit = pass_id % 64; + let encoded = 1 << bit; + self.dirty.iter().filter_map(move |entry| { + let node_id = entry.key(); + let dirty = entry.value(); + if let Some(atomic) = dirty.get(index as usize) { + if atomic.load(Ordering::Relaxed) & encoded != 0 { + Some(*node_id) + } else { + None + } + } else { + None + } + }) + } +} + +fn resolve_passes( + tree: &mut impl TreeView, + starting_nodes: FxHashMap>, + mut passes: Vec>, +) { + let dirty_nodes: Arc = Arc::new(DirtyNodeStates::new(starting_nodes)); + let global = Injector::default(); + + let core_count = thread::available_parallelism() + .map(|c| c.get()) + .unwrap_or(1); + let workers: Vec> = (0..core_count).map(|_| Worker::new_fifo()).collect(); + let stealers: Vec<_> = workers.iter().map(|w| w.stealer()).collect(); + let shared_view = SharedView::new(tree); + let mut resolved_passes: FxHashSet = FxHashSet::default(); + let current_pass: Arc>>> = Arc::new(RwLock::new(None)); + thread::scope(|s| { + let global = &global; + let stealers = &stealers; + for (_, w) in (0..core_count).zip(workers.into_iter()) { + let mut shared_view = shared_view.clone(); + let current_pass = current_pass.clone(); + let dirty_nodes = dirty_nodes.clone(); + s.spawn(move || { + while let Some(current_pass) = &*current_pass.read() { + match current_pass { + AnyPass::Upward(pass) => { + while let Some(id) = find_task(&w, global, stealers) { + let (node, parent) = shared_view.node_parent_mut(id).unwrap(); + if pass.upward_pass(node, parent) { + if let Some(id) = shared_view.parent_id(id) { + for dependant in pass.dependants() { + dirty_nodes.insert(*dependant, id); + } + w.push(id); + } + } + } + } + AnyPass::Downward(pass) => { + while let Some(id) = find_task(&w, global, stealers) { + let (node, mut children) = + shared_view.parent_child_mut(id).unwrap(); + if pass.downward_pass(node, &mut children) { + drop(children); + for id in shared_view.children_ids(id).unwrap() { + for dependant in pass.dependants() { + dirty_nodes.insert(*dependant, *id); + } + w.push(*id); + } + } + } + } + AnyPass::Node(pass) => { + while let Some(id) = find_task(&w, global, stealers) { + let node = shared_view.get_mut(id).unwrap(); + if pass.node_pass(node) { + for dependant in pass.dependants() { + dirty_nodes.insert(*dependant, id); + } + } } } } - }); + } + }); + } + while !passes.is_empty() { + for i in 0..passes.len() { + if passes[i] + .dependancies() + .iter() + .all(|id| resolved_passes.contains(id)) + { + let pass = passes.remove(i); + let pass_id = pass.pass_id(); + for node in dirty_nodes.all_dirty(pass_id) { + global.push(node); + } + resolved_passes.insert(pass_id); + break; + } } - }); - } + } + *current_pass.write() = None; + }); } fn find_task(local: &Worker, global: &Injector, stealers: &[Stealer]) -> Option { From 26d4e48480f22589e5e13a48953d4a853b2d7f7d Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 25 Nov 2022 11:45:47 -0600 Subject: [PATCH 09/93] make resolve passes public --- packages/native-core/src/tree.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index f4ad67f2e..f5b4c09bf 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -829,23 +829,17 @@ fn traverse_breadth_first() { }); } -enum PassDirection { - Up, - Down, - Node, -} - #[derive(PartialEq, Eq, Hash, Clone, Copy)] -struct PassId(u64); +pub struct PassId(u64); -trait UpwardPass { +pub trait UpwardPass { fn pass_id(&self) -> PassId; fn dependancies(&self) -> &'static [PassId]; fn dependants(&self) -> &'static [PassId]; fn upward_pass(&self, node: &mut T, parent: Option<&mut T>) -> bool; } -trait DownwardPass { +pub trait DownwardPass { fn pass_id(&self) -> PassId; fn dependancies(&self) -> &'static [PassId]; fn dependants(&self) -> &'static [PassId]; @@ -856,14 +850,14 @@ trait DownwardPass { ) -> bool; } -trait NodePass { +pub trait NodePass { fn pass_id(&self) -> PassId; fn dependancies(&self) -> &'static [PassId]; fn dependants(&self) -> &'static [PassId]; fn node_pass(&self, node: &mut T) -> bool; } -enum AnyPass { +pub enum AnyPass { Upward(Box + Send + Sync>), Downward(Box + Send + Sync>), Node(Box + Send + Sync>), @@ -954,7 +948,7 @@ impl DirtyNodeStates { } } -fn resolve_passes( +pub fn resolve_passes( tree: &mut impl TreeView, starting_nodes: FxHashMap>, mut passes: Vec>, From 9e47468c3ac40ab04719fc46aa33e9cfa2966841 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 25 Nov 2022 13:38:01 -0600 Subject: [PATCH 10/93] more work on parallel passes --- packages/native-core/src/lib.rs | 1 + packages/native-core/src/passes.rs | 432 +++++++++++++++++++++++++++++ packages/native-core/src/tree.rs | 259 +---------------- 3 files changed, 435 insertions(+), 257 deletions(-) create mode 100644 packages/native-core/src/passes.rs diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 895bb7dc9..56c4a6a73 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -3,6 +3,7 @@ use tree::NodeId; pub mod layout_attributes; pub mod node; pub mod node_ref; +pub mod passes; pub mod real_dom; pub mod state; pub mod tree; diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs new file mode 100644 index 000000000..aa25a81f2 --- /dev/null +++ b/packages/native-core/src/passes.rs @@ -0,0 +1,432 @@ +use crossbeam_deque::{Injector, Stealer, Worker}; +use parking_lot::RwLock; +use rustc_hash::{FxHashMap, FxHashSet, FxHasher}; +use std::hash::BuildHasherDefault; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::Arc; +use std::thread; +use std::time::Duration; + +use crate::tree::{NodeId, SharedView, TreeView}; + +#[derive(PartialEq, Eq, Hash, Clone, Copy)] +pub struct PassId(u64); + +pub trait UpwardPass { + fn pass_id(&self) -> PassId; + fn dependancies(&self) -> &'static [PassId]; + fn dependants(&self) -> &'static [PassId]; + fn pass<'a>(&self, node: &mut T, children: &mut dyn Iterator) -> bool; +} + +pub trait DownwardPass { + fn pass_id(&self) -> PassId; + fn dependancies(&self) -> &'static [PassId]; + fn dependants(&self) -> &'static [PassId]; + fn pass(&self, node: &mut T, parent: Option<&mut T>) -> bool; +} + +pub trait NodePass { + fn pass_id(&self) -> PassId; + fn dependancies(&self) -> &'static [PassId]; + fn dependants(&self) -> &'static [PassId]; + fn pass(&self, node: &mut T) -> bool; +} + +pub enum AnyPass { + Upward(Box + Send + Sync>), + Downward(Box + Send + Sync>), + Node(Box + Send + Sync>), +} + +impl AnyPass { + fn pass_id(&self) -> PassId { + match self { + Self::Upward(pass) => pass.pass_id(), + Self::Downward(pass) => pass.pass_id(), + Self::Node(pass) => pass.pass_id(), + } + } + + fn dependancies(&self) -> &'static [PassId] { + match self { + Self::Upward(pass) => pass.dependancies(), + Self::Downward(pass) => pass.dependancies(), + Self::Node(pass) => pass.dependancies(), + } + } +} + +type FxDashMap = dashmap::DashMap>; + +#[derive(Default)] +struct DirtyNodeStates { + dirty: FxDashMap>, +} + +impl DirtyNodeStates { + fn new(starting_nodes: FxHashMap>) -> Self { + let this = Self::default(); + for (node, nodes) in starting_nodes { + for pass_id in nodes { + this.insert(pass_id, node); + } + } + this + } + + fn insert(&self, pass_id: PassId, node_id: NodeId) { + let pass_id = pass_id.0; + let index = pass_id / 64; + let bit = pass_id % 64; + let encoded = 1 << bit; + if let Some(dirty) = self.dirty.get(&node_id) { + if let Some(atomic) = dirty.get(index as usize) { + atomic.fetch_or(encoded, Ordering::Relaxed); + } else { + drop(dirty); + let mut write = self.dirty.get_mut(&node_id).unwrap(); + write.resize_with(index as usize + 1, || AtomicU64::new(0)); + write[index as usize].fetch_or(encoded, Ordering::Relaxed); + } + } else { + let mut v = Vec::with_capacity(index as usize + 1); + v.resize_with(index as usize + 1, || AtomicU64::new(0)); + v[index as usize].fetch_or(encoded, Ordering::Relaxed); + self.dirty.insert(node_id, v); + } + } + + fn all_dirty(&self, pass_id: PassId) -> impl Iterator + '_ { + let pass_id = pass_id.0; + let index = pass_id / 64; + let bit = pass_id % 64; + let encoded = 1 << bit; + self.dirty.iter().filter_map(move |entry| { + let node_id = entry.key(); + let dirty = entry.value(); + if let Some(atomic) = dirty.get(index as usize) { + if atomic.load(Ordering::Relaxed) & encoded != 0 { + Some(*node_id) + } else { + None + } + } else { + None + } + }) + } +} + +fn get_pass>( + passes: &mut Vec>, + resolved_passes: &mut FxHashSet, + dirty_nodes: &DirtyNodeStates, + shared_view: &mut SharedView, + global: &Injector, + current_pass: &RwLock>>, +) { + for i in 0..passes.len() { + if passes[i] + .dependancies() + .iter() + .all(|id| resolved_passes.contains(id)) + { + let pass = passes.remove(i); + let pass_id = pass.pass_id(); + resolved_passes.insert(pass_id); + match pass { + AnyPass::Upward(pass) => { + // Upward passes are more difficult. Right now we limit them to only one thread. + let worker = Worker::new_fifo(); + let mut queued_nodes = FxHashSet::default(); + for node in dirty_nodes.all_dirty(pass_id) { + queued_nodes.insert(node); + worker.push(node); + } + while let Some(id) = worker.pop() { + let (node, mut children) = shared_view.parent_child_mut(id).unwrap(); + if pass.pass(node, &mut children) { + drop(children); + if let Some(id) = shared_view.parent_id(id) { + for dependant in pass.dependants() { + dirty_nodes.insert(*dependant, id); + } + if !queued_nodes.contains(&id) { + queued_nodes.insert(id); + worker.push(id); + } + } + } + } + } + _ => { + for node in dirty_nodes.all_dirty(pass_id) { + global.push(node); + } + current_pass.write().replace(pass); + } + } + + break; + } + } +} + +pub fn resolve_passes( + tree: &mut impl TreeView, + starting_nodes: FxHashMap>, + mut passes: Vec>, +) { + let dirty_nodes: Arc = Arc::new(DirtyNodeStates::new(starting_nodes)); + let global = Injector::default(); + + let core_count = thread::available_parallelism() + .map(|c| c.get()) + .unwrap_or(1); + let workers: Vec> = (0..core_count).map(|_| Worker::new_fifo()).collect(); + let stealers: Vec<_> = workers.iter().map(|w| w.stealer()).collect(); + let mut shared_view = SharedView::new(tree); + let mut resolved_passes: FxHashSet = FxHashSet::default(); + let current_pass: Arc>>> = Arc::new(RwLock::new(None)); + + thread::scope(|s| { + get_pass( + &mut passes, + &mut resolved_passes, + &dirty_nodes, + &mut shared_view, + &global, + ¤t_pass, + ); + let global = &global; + let stealers = &stealers; + for (_, w) in (0..core_count).zip(workers.into_iter()) { + let mut shared_view = shared_view.clone(); + let current_pass = current_pass.clone(); + let dirty_nodes = dirty_nodes.clone(); + s.spawn(move || { + while let Some(current_pass) = &*current_pass.read() { + match current_pass { + AnyPass::Upward(_) => { + todo!("Upward passes are single threaded") + } + AnyPass::Node(pass) => { + // Node passes are the easiest to parallelize. We just run the pass on each node. + while let Some(id) = find_task(&w, global, stealers) { + let node = shared_view.get_mut(id).unwrap(); + if pass.pass(node) { + for dependant in pass.dependants() { + dirty_nodes.insert(*dependant, id); + } + } + } + } + AnyPass::Downward(pass) => { + // Downward passes are easy to parallelize. We try to keep trees localized to one thread, but allow work stealing to balance the load. + while let Some(id) = find_task(&w, global, stealers) { + let (node, parent) = shared_view.node_parent_mut(id).unwrap(); + if pass.pass(node, parent) { + for id in shared_view.children_ids(id).unwrap() { + for dependant in pass.dependants() { + dirty_nodes.insert(*dependant, *id); + } + w.push(*id); + } + } + } + } + } + } + }); + } + while !passes.is_empty() { + while !stealers.iter().all(|s| s.is_empty()) { + std::thread::sleep(Duration::from_millis(50)); + } + get_pass( + &mut passes, + &mut resolved_passes, + &dirty_nodes, + &mut shared_view, + global, + ¤t_pass, + ); + } + *current_pass.write() = None; + }); +} + +fn find_task(local: &Worker, global: &Injector, stealers: &[Stealer]) -> Option { + // Pop a task from the local queue, if not empty. + local.pop().or_else(|| { + // Otherwise, we need to look for a task elsewhere. + std::iter::repeat_with(|| { + // Try stealing a batch of tasks from the global queue. + global + .steal_batch_and_pop(local) + // Or try stealing a task from one of the other threads. + .or_else(|| stealers.iter().map(|s| s.steal()).collect()) + }) + // Loop while no task was stolen and any steal operation needs to be retried. + .find(|s| !s.is_retry()) + // Extract the stolen task, if there is one. + .and_then(|s| s.success()) + }) +} + +#[test] +fn node_pass() { + use crate::tree::{Tree, TreeLike}; + let mut tree = Tree::new(0); + let parent = tree.root(); + let child1 = tree.create_node(1); + tree.add_child(parent, child1); + let grandchild1 = tree.create_node(3); + tree.add_child(child1, grandchild1); + let child2 = tree.create_node(2); + tree.add_child(parent, child2); + let grandchild2 = tree.create_node(4); + tree.add_child(child2, grandchild2); + + struct AddPass; + + impl NodePass for AddPass { + fn pass_id(&self) -> PassId { + PassId(0) + } + + fn dependancies(&self) -> &'static [PassId] { + &[] + } + + fn dependants(&self) -> &'static [PassId] { + &[] + } + + fn pass(&self, node: &mut i32) -> bool { + *node += 1; + true + } + } + + let passes = vec![AnyPass::Node(Box::new(AddPass))]; + let mut dirty_nodes: FxHashMap> = FxHashMap::default(); + dirty_nodes.insert(tree.root(), [PassId(0)].into_iter().collect()); + resolve_passes(&mut tree, dirty_nodes, passes); + + assert_eq!(tree.get(tree.root()).unwrap(), &1); +} + +#[test] +fn down_pass() { + use crate::tree::{Tree, TreeLike}; + let mut tree = Tree::new(1); + let parent = tree.root(); + let child1 = tree.create_node(1); + tree.add_child(parent, child1); + let grandchild1 = tree.create_node(1); + tree.add_child(child1, grandchild1); + let child2 = tree.create_node(1); + tree.add_child(parent, child2); + let grandchild2 = tree.create_node(1); + tree.add_child(child2, grandchild2); + + struct AddPass; + + impl DownwardPass for AddPass { + fn pass_id(&self) -> PassId { + PassId(0) + } + + fn dependancies(&self) -> &'static [PassId] { + &[] + } + + fn dependants(&self) -> &'static [PassId] { + &[] + } + + fn pass(&self, node: &mut i32, parent: Option<&mut i32>) -> bool { + if let Some(parent) = parent { + *node += *parent; + } + true + } + } + + let passes = vec![AnyPass::Downward(Box::new(AddPass))]; + let mut dirty_nodes: FxHashMap> = FxHashMap::default(); + dirty_nodes.insert(tree.root(), [PassId(0)].into_iter().collect()); + resolve_passes(&mut tree, dirty_nodes, passes); + + assert_eq!(tree.get(tree.root()).unwrap(), &1); + assert_eq!(tree.get(child1).unwrap(), &2); + assert_eq!(tree.get(grandchild1).unwrap(), &3); + assert_eq!(tree.get(child2).unwrap(), &2); + assert_eq!(tree.get(grandchild2).unwrap(), &3); +} + +#[test] +fn up_pass() { + use crate::tree::{Tree, TreeLike}; + // Tree before: + // 0=\ + // 0=\ + // 1 + // 0=\ + // 1 + // Tree after: + // 2=\ + // 1=\ + // 1 + // 1=\ + // 1 + let mut tree = Tree::new(0); + let parent = tree.root(); + let child1 = tree.create_node(0); + tree.add_child(parent, child1); + let grandchild1 = tree.create_node(1); + tree.add_child(child1, grandchild1); + let child2 = tree.create_node(0); + tree.add_child(parent, child2); + let grandchild2 = tree.create_node(1); + tree.add_child(child2, grandchild2); + + struct AddPass; + + impl UpwardPass for AddPass { + fn pass_id(&self) -> PassId { + PassId(0) + } + + fn dependancies(&self) -> &'static [PassId] { + &[] + } + + fn dependants(&self) -> &'static [PassId] { + &[] + } + + fn pass<'a>( + &self, + node: &mut i32, + children: &mut dyn Iterator, + ) -> bool { + *node += children.map(|i| *i).sum::(); + true + } + } + + let passes = vec![AnyPass::Upward(Box::new(AddPass))]; + let mut dirty_nodes: FxHashMap> = FxHashMap::default(); + dirty_nodes.insert(grandchild1, [PassId(0)].into_iter().collect()); + dirty_nodes.insert(grandchild2, [PassId(0)].into_iter().collect()); + resolve_passes(&mut tree, dirty_nodes, passes); + + assert_eq!(tree.get(tree.root()).unwrap(), &2); + assert_eq!(tree.get(child1).unwrap(), &1); + assert_eq!(tree.get(grandchild1).unwrap(), &1); + assert_eq!(tree.get(child2).unwrap(), &1); + assert_eq!(tree.get(grandchild2).unwrap(), &1); +} diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index f5b4c09bf..4bec046be 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -1,20 +1,11 @@ use core::panic; -use std::hash::BuildHasherDefault; -use std::sync::atomic::{AtomicU64, Ordering}; - -use crossbeam_deque::{Injector, Stealer, Worker}; -use dashmap::DashSet; -use dioxus_core::ScopeId; -use dioxus_html::u; use parking_lot::lock_api::RawMutex as _; -use parking_lot::{Mutex, RawMutex, RwLock, RwLockWriteGuard}; -use rustc_hash::{FxHashMap, FxHashSet, FxHasher}; +use parking_lot::{RawMutex, RwLock}; use slab::Slab; use std::cell::UnsafeCell; use std::collections::VecDeque; use std::marker::PhantomData; use std::sync::Arc; -use std::thread; #[derive(Hash, PartialEq, Eq, Clone, Copy, Debug, PartialOrd, Ord)] pub struct NodeId(pub usize); @@ -109,7 +100,7 @@ pub trait TreeView: Sized { // Safety: No node has itself as a parent. (*mut_ptr) .get_mut(id) - .map(|node| (node, (*mut_ptr).parent_mut(id).map(|parent| parent))) + .map(|node| (node, (*mut_ptr).parent_mut(id))) } } @@ -808,249 +799,3 @@ fn traverse_depth_first() { node_count += 1; }); } - -#[test] -fn traverse_breadth_first() { - let mut tree = Tree::new(0); - let parent = tree.root(); - let child1 = tree.create_node(1); - tree.add_child(parent, child1); - let grandchild1 = tree.create_node(3); - tree.add_child(child1, grandchild1); - let child2 = tree.create_node(2); - tree.add_child(parent, child2); - let grandchild2 = tree.create_node(4); - tree.add_child(child2, grandchild2); - - let mut node_count = 0; - tree.traverse_breadth_first(move |node| { - assert_eq!(*node, node_count); - node_count += 1; - }); -} - -#[derive(PartialEq, Eq, Hash, Clone, Copy)] -pub struct PassId(u64); - -pub trait UpwardPass { - fn pass_id(&self) -> PassId; - fn dependancies(&self) -> &'static [PassId]; - fn dependants(&self) -> &'static [PassId]; - fn upward_pass(&self, node: &mut T, parent: Option<&mut T>) -> bool; -} - -pub trait DownwardPass { - fn pass_id(&self) -> PassId; - fn dependancies(&self) -> &'static [PassId]; - fn dependants(&self) -> &'static [PassId]; - fn downward_pass<'a>( - &self, - node: &mut T, - children: &mut dyn Iterator, - ) -> bool; -} - -pub trait NodePass { - fn pass_id(&self) -> PassId; - fn dependancies(&self) -> &'static [PassId]; - fn dependants(&self) -> &'static [PassId]; - fn node_pass(&self, node: &mut T) -> bool; -} - -pub enum AnyPass { - Upward(Box + Send + Sync>), - Downward(Box + Send + Sync>), - Node(Box + Send + Sync>), -} - -impl AnyPass { - fn pass_id(&self) -> PassId { - match self { - Self::Upward(pass) => pass.pass_id(), - Self::Downward(pass) => pass.pass_id(), - Self::Node(pass) => pass.pass_id(), - } - } - - fn dependancies(&self) -> &'static [PassId] { - match self { - Self::Upward(pass) => pass.dependancies(), - Self::Downward(pass) => pass.dependancies(), - Self::Node(pass) => pass.dependancies(), - } - } - - fn dependants(&self) -> &'static [PassId] { - match self { - Self::Upward(pass) => pass.dependants(), - Self::Downward(pass) => pass.dependants(), - Self::Node(pass) => pass.dependants(), - } - } -} - -type FxDashSet = dashmap::DashSet>; -type FxDashMap = dashmap::DashMap>; - -#[derive(Default)] -struct DirtyNodeStates { - dirty: FxDashMap>, -} - -impl DirtyNodeStates { - fn new(starting_nodes: FxHashMap>) -> Self { - let mut this = Self::default(); - for (node, nodes) in starting_nodes { - for pass_id in nodes { - this.insert(pass_id, node); - } - } - this - } - - fn insert(&self, pass_id: PassId, node_id: NodeId) { - let pass_id = pass_id.0; - let index = pass_id / 64; - let bit = pass_id % 64; - let encoded = 1 << bit; - if let Some(dirty) = self.dirty.get(&node_id) { - if let Some(atomic) = dirty.get(index as usize) { - atomic.fetch_or(encoded, Ordering::Relaxed); - } else { - drop(dirty); - let mut write = self.dirty.get_mut(&node_id).unwrap(); - write.resize_with(index as usize + 1, || AtomicU64::new(0)); - write[index as usize].fetch_or(encoded, Ordering::Relaxed); - } - } else { - self.dirty.insert(node_id, vec![AtomicU64::new(encoded)]); - } - } - - fn all_dirty(&self, pass_id: PassId) -> impl Iterator + '_ { - let pass_id = pass_id.0; - let index = pass_id / 64; - let bit = pass_id % 64; - let encoded = 1 << bit; - self.dirty.iter().filter_map(move |entry| { - let node_id = entry.key(); - let dirty = entry.value(); - if let Some(atomic) = dirty.get(index as usize) { - if atomic.load(Ordering::Relaxed) & encoded != 0 { - Some(*node_id) - } else { - None - } - } else { - None - } - }) - } -} - -pub fn resolve_passes( - tree: &mut impl TreeView, - starting_nodes: FxHashMap>, - mut passes: Vec>, -) { - let dirty_nodes: Arc = Arc::new(DirtyNodeStates::new(starting_nodes)); - let global = Injector::default(); - - let core_count = thread::available_parallelism() - .map(|c| c.get()) - .unwrap_or(1); - let workers: Vec> = (0..core_count).map(|_| Worker::new_fifo()).collect(); - let stealers: Vec<_> = workers.iter().map(|w| w.stealer()).collect(); - let shared_view = SharedView::new(tree); - let mut resolved_passes: FxHashSet = FxHashSet::default(); - let current_pass: Arc>>> = Arc::new(RwLock::new(None)); - thread::scope(|s| { - let global = &global; - let stealers = &stealers; - for (_, w) in (0..core_count).zip(workers.into_iter()) { - let mut shared_view = shared_view.clone(); - let current_pass = current_pass.clone(); - let dirty_nodes = dirty_nodes.clone(); - s.spawn(move || { - while let Some(current_pass) = &*current_pass.read() { - match current_pass { - AnyPass::Upward(pass) => { - while let Some(id) = find_task(&w, global, stealers) { - let (node, parent) = shared_view.node_parent_mut(id).unwrap(); - if pass.upward_pass(node, parent) { - if let Some(id) = shared_view.parent_id(id) { - for dependant in pass.dependants() { - dirty_nodes.insert(*dependant, id); - } - w.push(id); - } - } - } - } - AnyPass::Downward(pass) => { - while let Some(id) = find_task(&w, global, stealers) { - let (node, mut children) = - shared_view.parent_child_mut(id).unwrap(); - if pass.downward_pass(node, &mut children) { - drop(children); - for id in shared_view.children_ids(id).unwrap() { - for dependant in pass.dependants() { - dirty_nodes.insert(*dependant, *id); - } - w.push(*id); - } - } - } - } - AnyPass::Node(pass) => { - while let Some(id) = find_task(&w, global, stealers) { - let node = shared_view.get_mut(id).unwrap(); - if pass.node_pass(node) { - for dependant in pass.dependants() { - dirty_nodes.insert(*dependant, id); - } - } - } - } - } - } - }); - } - while !passes.is_empty() { - for i in 0..passes.len() { - if passes[i] - .dependancies() - .iter() - .all(|id| resolved_passes.contains(id)) - { - let pass = passes.remove(i); - let pass_id = pass.pass_id(); - for node in dirty_nodes.all_dirty(pass_id) { - global.push(node); - } - resolved_passes.insert(pass_id); - break; - } - } - } - *current_pass.write() = None; - }); -} - -fn find_task(local: &Worker, global: &Injector, stealers: &[Stealer]) -> Option { - // Pop a task from the local queue, if not empty. - local.pop().or_else(|| { - // Otherwise, we need to look for a task elsewhere. - std::iter::repeat_with(|| { - // Try stealing a batch of tasks from the global queue. - global - .steal_batch_and_pop(local) - // Or try stealing a task from one of the other threads. - .or_else(|| stealers.iter().map(|s| s.steal()).collect()) - }) - // Loop while no task was stolen and any steal operation needs to be retried. - .find(|s| !s.is_retry()) - // Extract the stolen task, if there is one. - .and_then(|s| s.success()) - }) -} From 7d3ac26fce80bcdb13bd216c0dfae7629edf16b0 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 25 Nov 2022 15:04:00 -0600 Subject: [PATCH 11/93] fix deadlock and add more tests --- packages/native-core/src/passes.rs | 295 ++++++++++++++++++++++++----- 1 file changed, 251 insertions(+), 44 deletions(-) diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index aa25a81f2..f7b416e5c 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -1,15 +1,14 @@ use crossbeam_deque::{Injector, Stealer, Worker}; -use parking_lot::RwLock; +use parking_lot::{Condvar, Mutex, RwLock}; use rustc_hash::{FxHashMap, FxHashSet, FxHasher}; use std::hash::BuildHasherDefault; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; use std::thread; -use std::time::Duration; use crate::tree::{NodeId, SharedView, TreeView}; -#[derive(PartialEq, Eq, Hash, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] pub struct PassId(u64); pub trait UpwardPass { @@ -124,7 +123,7 @@ fn get_pass>( dirty_nodes: &DirtyNodeStates, shared_view: &mut SharedView, global: &Injector, - current_pass: &RwLock>>, + current_pass: &mut Option>, ) { for i in 0..passes.len() { if passes[i] @@ -134,6 +133,7 @@ fn get_pass>( { let pass = passes.remove(i); let pass_id = pass.pass_id(); + println!("Running pass {:?}", pass_id); resolved_passes.insert(pass_id); match pass { AnyPass::Upward(pass) => { @@ -164,13 +164,21 @@ fn get_pass>( for node in dirty_nodes.all_dirty(pass_id) { global.push(node); } - current_pass.write().replace(pass); + println!( + "Task: {:?} {:?}", + pass_id, + dirty_nodes.all_dirty(pass_id).collect::>() + ); + current_pass.replace(pass); } } - - break; + return; } } + panic!( + "No pass found with all dependancies resolved in {:?}", + passes.iter().map(|p| p.pass_id()).collect::>() + ); } pub fn resolve_passes( @@ -178,6 +186,7 @@ pub fn resolve_passes( starting_nodes: FxHashMap>, mut passes: Vec>, ) { + assert!(!passes.is_empty()); let dirty_nodes: Arc = Arc::new(DirtyNodeStates::new(starting_nodes)); let global = Injector::default(); @@ -191,69 +200,105 @@ pub fn resolve_passes( let current_pass: Arc>>> = Arc::new(RwLock::new(None)); thread::scope(|s| { - get_pass( - &mut passes, - &mut resolved_passes, - &dirty_nodes, - &mut shared_view, - &global, - ¤t_pass, - ); + { + let mut write = current_pass.write(); + get_pass( + &mut passes, + &mut resolved_passes, + &dirty_nodes, + &mut shared_view, + &global, + &mut *write, + ); + } let global = &global; let stealers = &stealers; + let mut thread_handles = Vec::new(); + let threads_finished = Arc::new((Mutex::new(0), Condvar::new())); for (_, w) in (0..core_count).zip(workers.into_iter()) { let mut shared_view = shared_view.clone(); let current_pass = current_pass.clone(); let dirty_nodes = dirty_nodes.clone(); - s.spawn(move || { - while let Some(current_pass) = &*current_pass.read() { - match current_pass { - AnyPass::Upward(_) => { - todo!("Upward passes are single threaded") - } - AnyPass::Node(pass) => { - // Node passes are the easiest to parallelize. We just run the pass on each node. - while let Some(id) = find_task(&w, global, stealers) { - let node = shared_view.get_mut(id).unwrap(); - if pass.pass(node) { - for dependant in pass.dependants() { - dirty_nodes.insert(*dependant, id); - } - } + let threads_finished = threads_finished.clone(); + thread_handles.push(s.spawn(move || { + loop { + let read = current_pass.read(); + if let Some(current_pass) = &*read { + match current_pass { + AnyPass::Upward(_) => { + todo!("Upward passes are single threaded") } - } - AnyPass::Downward(pass) => { - // Downward passes are easy to parallelize. We try to keep trees localized to one thread, but allow work stealing to balance the load. - while let Some(id) = find_task(&w, global, stealers) { - let (node, parent) = shared_view.node_parent_mut(id).unwrap(); - if pass.pass(node, parent) { - for id in shared_view.children_ids(id).unwrap() { + AnyPass::Node(pass) => { + // Node passes are the easiest to parallelize. We just run the pass on each node. + while let Some(id) = find_task(&w, global, stealers) { + let node = shared_view.get_mut(id).unwrap(); + if pass.pass(node) { for dependant in pass.dependants() { - dirty_nodes.insert(*dependant, *id); + dirty_nodes.insert(*dependant, id); + } + } + } + } + AnyPass::Downward(pass) => { + // Downward passes are easy to parallelize. We try to keep trees localized to one thread, but allow work stealing to balance the load. + while let Some(id) = find_task(&w, global, stealers) { + let (node, parent) = shared_view.node_parent_mut(id).unwrap(); + if pass.pass(node, parent) { + for id in shared_view.children_ids(id).unwrap() { + for dependant in pass.dependants() { + dirty_nodes.insert(*dependant, *id); + } + w.push(*id); } - w.push(*id); } } } } + // unblock the rwlock + drop(read); + { + let (count, cvar) = &*threads_finished; + let mut lock = count.lock(); + *lock += 1; + if *lock == core_count { + cvar.notify_all(); + } + } + // wait for the main thread to pick the next pass + thread::park(); + } else { + break; } } - }); + })); } + let wait_for_thread_to_finish = move || { + // this will block until all threads are done with the current pass + let (count, cvar) = &*threads_finished; + let mut count = count.lock(); + cvar.wait(&mut count); + }; while !passes.is_empty() { - while !stealers.iter().all(|s| s.is_empty()) { - std::thread::sleep(Duration::from_millis(50)); - } + wait_for_thread_to_finish(); + let mut write = current_pass.write(); + println!("Threads finished"); get_pass( &mut passes, &mut resolved_passes, &dirty_nodes, &mut shared_view, global, - ¤t_pass, + &mut *write, ); + // notify all threads to start the next pass + for thread in &thread_handles { + thread.thread().unpark(); + } } *current_pass.write() = None; + for thread in &thread_handles { + thread.thread().unpark(); + } }); } @@ -318,6 +363,75 @@ fn node_pass() { assert_eq!(tree.get(tree.root()).unwrap(), &1); } +#[test] +fn dependant_node_pass() { + use crate::tree::{Tree, TreeLike}; + let mut tree = Tree::new(0); + let parent = tree.root(); + let child1 = tree.create_node(1); + tree.add_child(parent, child1); + let grandchild1 = tree.create_node(3); + tree.add_child(child1, grandchild1); + let child2 = tree.create_node(2); + tree.add_child(parent, child2); + let grandchild2 = tree.create_node(4); + tree.add_child(child2, grandchild2); + + struct AddPass; + + impl NodePass for AddPass { + fn pass_id(&self) -> PassId { + PassId(0) + } + + fn dependancies(&self) -> &'static [PassId] { + &[PassId(1)] + } + + fn dependants(&self) -> &'static [PassId] { + &[] + } + + fn pass(&self, node: &mut i32) -> bool { + println!("AddPass: {}", node); + *node += 1; + true + } + } + + struct SubtractPass; + + impl NodePass for SubtractPass { + fn pass_id(&self) -> PassId { + PassId(1) + } + + fn dependancies(&self) -> &'static [PassId] { + &[] + } + + fn dependants(&self) -> &'static [PassId] { + &[PassId(0)] + } + + fn pass(&self, node: &mut i32) -> bool { + println!("SubtractPass: {}", node); + *node -= 1; + true + } + } + + let passes = vec![ + AnyPass::Node(Box::new(AddPass)), + AnyPass::Node(Box::new(SubtractPass)), + ]; + let mut dirty_nodes: FxHashMap> = FxHashMap::default(); + dirty_nodes.insert(tree.root(), [PassId(1)].into_iter().collect()); + resolve_passes(&mut tree, dirty_nodes, passes); + + assert_eq!(*tree.get(tree.root()).unwrap(), 0); +} + #[test] fn down_pass() { use crate::tree::{Tree, TreeLike}; @@ -367,6 +481,99 @@ fn down_pass() { assert_eq!(tree.get(grandchild2).unwrap(), &3); } +#[test] +fn dependant_down_pass() { + use crate::tree::{Tree, TreeLike}; + let mut tree = Tree::new(1); + let parent = tree.root(); + let child1 = tree.create_node(1); + tree.add_child(parent, child1); + let grandchild1 = tree.create_node(1); + tree.add_child(child1, grandchild1); + let child2 = tree.create_node(1); + tree.add_child(parent, child2); + let grandchild2 = tree.create_node(1); + tree.add_child(child2, grandchild2); + + struct AddPass; + + impl DownwardPass for AddPass { + fn pass_id(&self) -> PassId { + PassId(0) + } + + fn dependancies(&self) -> &'static [PassId] { + &[PassId(1)] + } + + fn dependants(&self) -> &'static [PassId] { + &[] + } + + fn pass(&self, node: &mut i32, parent: Option<&mut i32>) -> bool { + if let Some(parent) = parent { + *node += *parent; + } + true + } + } + + struct SubtractPass; + + impl DownwardPass for SubtractPass { + fn pass_id(&self) -> PassId { + PassId(1) + } + + fn dependancies(&self) -> &'static [PassId] { + &[] + } + + fn dependants(&self) -> &'static [PassId] { + &[PassId(0)] + } + + fn pass(&self, node: &mut i32, parent: Option<&mut i32>) -> bool { + if let Some(parent) = parent { + *node -= *parent; + } + true + } + } + + let passes = vec![ + AnyPass::Downward(Box::new(AddPass)), + AnyPass::Downward(Box::new(SubtractPass)), + ]; + let mut dirty_nodes: FxHashMap> = FxHashMap::default(); + dirty_nodes.insert(tree.root(), [PassId(1)].into_iter().collect()); + resolve_passes(&mut tree, dirty_nodes, passes); + + // Tree before: + // 1=\ + // 1=\ + // 1 + // 1=\ + // 1 + // Tree after subtract: + // 1=\ + // 0=\ + // 1 + // 0=\ + // 1 + // Tree after add: + // 1=\ + // 1=\ + // 2 + // 1=\ + // 2 + assert_eq!(tree.get(tree.root()).unwrap(), &1); + assert_eq!(tree.get(child1).unwrap(), &1); + assert_eq!(tree.get(grandchild1).unwrap(), &2); + assert_eq!(tree.get(child2).unwrap(), &1); + assert_eq!(tree.get(grandchild2).unwrap(), &2); +} + #[test] fn up_pass() { use crate::tree::{Tree, TreeLike}; From 8af59d6969a34519ff0231ddbd086880f1fe49a0 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 25 Nov 2022 22:22:56 -0600 Subject: [PATCH 12/93] move height into the tree instead of the realdom --- packages/native-core/src/node.rs | 3 -- packages/native-core/src/passes.rs | 53 ++++++++++++++++++++++++-- packages/native-core/src/real_dom.rs | 21 +++-------- packages/native-core/src/tree.rs | 56 +++++++++++++++++++++++++++- 4 files changed, 110 insertions(+), 23 deletions(-) diff --git a/packages/native-core/src/node.rs b/packages/native-core/src/node.rs index fc06862f9..4530fd350 100644 --- a/packages/native-core/src/node.rs +++ b/packages/native-core/src/node.rs @@ -19,8 +19,6 @@ pub struct NodeData { pub element_id: Option, /// Additional inforation specific to the node type pub node_type: NodeType, - /// The number of parents before the root node. The root node has height 1. - pub height: u16, } /// A type of node with data specific to the node type. The types are a subset of the [VNode] types. @@ -45,7 +43,6 @@ impl Node { node_data: NodeData { element_id: None, node_type, - height: 0, node_id: NodeId(0), }, } diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index f7b416e5c..da01dfb95 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -96,6 +96,22 @@ impl DirtyNodeStates { } } + fn get(&self, pass_id: PassId, node_id: NodeId) -> bool { + let pass_id = pass_id.0; + let index = pass_id / 64; + let bit = pass_id % 64; + let encoded = 1 << bit; + if let Some(dirty) = self.dirty.get(&node_id) { + if let Some(atomic) = dirty.get(index as usize) { + atomic.load(Ordering::SeqCst) & encoded != 0 + } else { + false + } + } else { + false + } + } + fn all_dirty(&self, pass_id: PassId) -> impl Iterator + '_ { let pass_id = pass_id.0; let index = pass_id / 64; @@ -160,7 +176,23 @@ fn get_pass>( } } } - _ => { + AnyPass::Downward(pass) => { + let mut sorted: Vec<_> = dirty_nodes.all_dirty(pass_id).collect(); + sorted.sort_unstable_by_key(|id| shared_view.height(*id)); + println!( + "Task: {:?} {:?}", + pass_id, + sorted + .iter() + .map(|id| (id, shared_view.height(*id))) + .collect::>() + ); + for node in sorted.into_iter() { + global.push(node); + } + current_pass.replace(AnyPass::Downward(pass)); + } + AnyPass::Node(pass) => { for node in dirty_nodes.all_dirty(pass_id) { global.push(node); } @@ -169,7 +201,7 @@ fn get_pass>( pass_id, dirty_nodes.all_dirty(pass_id).collect::>() ); - current_pass.replace(pass); + current_pass.replace(AnyPass::Node(pass)); } } return; @@ -224,6 +256,7 @@ pub fn resolve_passes( loop { let read = current_pass.read(); if let Some(current_pass) = &*read { + let current_pass_id = current_pass.pass_id(); match current_pass { AnyPass::Upward(_) => { todo!("Upward passes are single threaded") @@ -248,7 +281,9 @@ pub fn resolve_passes( for dependant in pass.dependants() { dirty_nodes.insert(*dependant, *id); } - w.push(*id); + if !dirty_nodes.get(current_pass_id, *id) { + w.push(*id); + } } } } @@ -333,6 +368,7 @@ fn node_pass() { tree.add_child(parent, child2); let grandchild2 = tree.create_node(4); tree.add_child(child2, grandchild2); + println!("{:#?}", tree); struct AddPass; @@ -484,14 +520,19 @@ fn down_pass() { #[test] fn dependant_down_pass() { use crate::tree::{Tree, TreeLike}; + // 0 let mut tree = Tree::new(1); let parent = tree.root(); + // 1 let child1 = tree.create_node(1); tree.add_child(parent, child1); + // 2 let grandchild1 = tree.create_node(1); tree.add_child(child1, grandchild1); + // 3 let child2 = tree.create_node(1); tree.add_child(parent, child2); + // 4 let grandchild2 = tree.create_node(1); tree.add_child(child2, grandchild2); @@ -512,7 +553,10 @@ fn dependant_down_pass() { fn pass(&self, node: &mut i32, parent: Option<&mut i32>) -> bool { if let Some(parent) = parent { + println!("AddPass: {} -> {}", node, *node + *parent); *node += *parent; + } else { + println!("AddPass: {}", node); } true } @@ -535,7 +579,10 @@ fn dependant_down_pass() { fn pass(&self, node: &mut i32, parent: Option<&mut i32>) -> bool { if let Some(parent) = parent { + println!("SubtractPass: {} -> {}", node, *node - *parent); *node -= *parent; + } else { + println!("SubtractPass: {}", node); } true } diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index e80510aa4..683d3bb65 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -93,21 +93,6 @@ impl RealDom { fn add_child(&mut self, node_id: RealNodeId, child_id: RealNodeId) { self.tree.add_child(node_id, child_id); - self.resolve_height(child_id); - } - - fn resolve_height(&mut self, node_id: RealNodeId) { - if let Some((node, Some(parent))) = self.tree.node_parent_mut(node_id) { - let height = parent.node_data.height; - node.node_data.height = height + 1; - unsafe { - let self_mut = self as *mut Self; - // Safety: No node will have itself as a child - for child in self.tree.children_ids(node_id).unwrap() { - (*self_mut).resolve_height(*child); - } - } - } } /// Updates the dom with some mutations and return a set of nodes that were updated. Pass the dirty nodes to update_state. @@ -382,7 +367,11 @@ impl RealDom { pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&Node> { 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.node_data.height).cmp(&n2.node_data.height).reverse()); + listening.sort_by(|n1, n2| { + (self.tree.height(n1.node_data.node_id)) + .cmp(&self.tree.height(n2.node_data.node_id)) + .reverse() + }); listening } else { Vec::new() diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index 4bec046be..53f81ba54 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -15,6 +15,7 @@ pub struct Node { value: T, parent: Option, children: Vec, + height: u16, } #[derive(Debug)] @@ -46,6 +47,18 @@ impl Tree { self.remove_recursive(child); } } + + fn set_height(&mut self, node: NodeId, height: u16) { + let self_mut = self as *mut Self; + let node = self.nodes.get_mut(node.0).unwrap(); + node.height = height; + unsafe { + // Safety: No node has itself as a child + for child in &node.children { + (*self_mut).set_height(*child, height + 1); + } + } + } } pub trait TreeView: Sized { @@ -106,6 +119,8 @@ pub trait TreeView: Sized { fn parent_id(&self, id: NodeId) -> Option; + fn height(&self, id: NodeId) -> Option; + fn map &T2, FMut: Fn(&mut T) -> &mut T2>( &mut self, map: F, @@ -306,6 +321,10 @@ impl TreeView for Tree { self.nodes.get(id.0).and_then(|node| node.parent) } + fn height(&self, id: NodeId) -> Option { + self.nodes.get(id.0).map(|n| n.height) + } + fn get_unchecked(&self, id: NodeId) -> &T { unsafe { &self.nodes.get_unchecked(id.0).value } } @@ -326,6 +345,7 @@ impl TreeLike for Tree { value: root, parent: None, children: Vec::new(), + height: 0, })); Self { nodes, root } } @@ -335,12 +355,16 @@ impl TreeLike for Tree { value, parent: None, children: Vec::new(), + height: 0, })) } fn add_child(&mut self, parent: NodeId, new: NodeId) { - self.nodes.get_mut(parent.0).unwrap().children.push(new); self.nodes.get_mut(new.0).unwrap().parent = Some(parent); + let parent = self.nodes.get_mut(parent.0).unwrap(); + parent.children.push(new); + let height = parent.height + 1; + self.set_height(new, height); } fn remove(&mut self, id: NodeId) -> Option { @@ -372,6 +396,8 @@ impl TreeLike for Tree { *id = new_id; } } + let height = parent.height + 1; + self.set_height(new_id, height); } } @@ -386,6 +412,8 @@ impl TreeLike for Tree { .position(|child| child == &id) .unwrap(); parent.children.insert(index, new); + let height = parent.height + 1; + self.set_height(new, height); } fn insert_after(&mut self, id: NodeId, new: NodeId) { @@ -399,6 +427,8 @@ impl TreeLike for Tree { .position(|child| child == &id) .unwrap(); parent.children.insert(index + 1, new); + let height = parent.height + 1; + self.set_height(new, height); } } @@ -498,6 +528,10 @@ where self.tree.parent_id(id) } + fn height(&self, id: NodeId) -> Option { + self.tree.height(id) + } + fn get_unchecked(&self, id: NodeId) -> &T2 { (self.map)(self.tree.get_unchecked(id)) } @@ -630,6 +664,10 @@ impl<'a, T, Tr: TreeView> TreeView for SharedView<'a, T, Tr> { self.with_node(id, |t| t.parent_id(id)) } + fn height(&self, id: NodeId) -> Option { + unsafe { (*self.tree.get()).height(id) } + } + fn size(&self) -> usize { unsafe { (*self.tree.get()).size() } } @@ -644,6 +682,8 @@ fn creation() { println!("Tree: {:#?}", tree); assert_eq!(tree.size(), 2); + assert_eq!(tree.height(parent), Some(0)); + assert_eq!(tree.height(child), Some(1)); assert_eq!(*tree.get(parent).unwrap(), 1); assert_eq!(*tree.get(child).unwrap(), 0); assert_eq!(tree.parent_id(parent), None); @@ -664,6 +704,10 @@ fn insertion() { println!("Tree: {:#?}", tree); assert_eq!(tree.size(), 4); + assert_eq!(tree.height(parent), Some(0)); + assert_eq!(tree.height(child), Some(1)); + assert_eq!(tree.height(before), Some(1)); + assert_eq!(tree.height(after), Some(1)); assert_eq!(*tree.get(parent).unwrap(), 0); assert_eq!(*tree.get(before).unwrap(), 1); assert_eq!(*tree.get(child).unwrap(), 2); @@ -687,6 +731,10 @@ fn deletion() { println!("Tree: {:#?}", tree); assert_eq!(tree.size(), 4); + assert_eq!(tree.height(parent), Some(0)); + assert_eq!(tree.height(child), Some(1)); + assert_eq!(tree.height(before), Some(1)); + assert_eq!(tree.height(after), Some(1)); assert_eq!(*tree.get(parent).unwrap(), 0); assert_eq!(*tree.get(before).unwrap(), 1); assert_eq!(*tree.get(child).unwrap(), 2); @@ -700,6 +748,9 @@ fn deletion() { println!("Tree: {:#?}", tree); assert_eq!(tree.size(), 3); + assert_eq!(tree.height(parent), Some(0)); + assert_eq!(tree.height(before), Some(1)); + assert_eq!(tree.height(after), Some(1)); assert_eq!(*tree.get(parent).unwrap(), 0); assert_eq!(*tree.get(before).unwrap(), 1); assert_eq!(tree.get(child), None); @@ -712,6 +763,8 @@ fn deletion() { println!("Tree: {:#?}", tree); assert_eq!(tree.size(), 2); + assert_eq!(tree.height(parent), Some(0)); + assert_eq!(tree.height(after), Some(1)); assert_eq!(*tree.get(parent).unwrap(), 0); assert_eq!(tree.get(before), None); assert_eq!(*tree.get(after).unwrap(), 3); @@ -722,6 +775,7 @@ fn deletion() { println!("Tree: {:#?}", tree); assert_eq!(tree.size(), 1); + assert_eq!(tree.height(parent), Some(0)); assert_eq!(*tree.get(parent).unwrap(), 0); assert_eq!(tree.get(after), None); assert_eq!(tree.children_ids(parent).unwrap(), &[]); From 51f643c5dc36335b522b25331c3af833768e8c41 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 26 Nov 2022 11:50:54 -0600 Subject: [PATCH 13/93] make passes exicute in parallel instead of executing invidual passes in parellel --- packages/native-core/src/passes.rs | 637 ++++++++++++++++------------- 1 file changed, 342 insertions(+), 295 deletions(-) diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index da01dfb95..b08a65cf5 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -1,59 +1,61 @@ use crossbeam_deque::{Injector, Stealer, Worker}; -use parking_lot::{Condvar, Mutex, RwLock}; use rustc_hash::{FxHashMap, FxHashSet, FxHasher}; +use std::collections::{BTreeMap, HashMap}; use std::hash::BuildHasherDefault; +use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign}; +use std::os::raw; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; -use std::thread; -use crate::tree::{NodeId, SharedView, TreeView}; +use crate::tree::{NodeId, TreeView}; -#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] -pub struct PassId(u64); - -pub trait UpwardPass { - fn pass_id(&self) -> PassId; - fn dependancies(&self) -> &'static [PassId]; - fn dependants(&self) -> &'static [PassId]; - fn pass<'a>(&self, node: &mut T, children: &mut dyn Iterator) -> bool; +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct DirtyNodes { + map: BTreeMap>, } -pub trait DownwardPass { - fn pass_id(&self) -> PassId; - fn dependancies(&self) -> &'static [PassId]; - fn dependants(&self) -> &'static [PassId]; - fn pass(&self, node: &mut T, parent: Option<&mut T>) -> bool; -} - -pub trait NodePass { - fn pass_id(&self) -> PassId; - fn dependancies(&self) -> &'static [PassId]; - fn dependants(&self) -> &'static [PassId]; - fn pass(&self, node: &mut T) -> bool; -} - -pub enum AnyPass { - Upward(Box + Send + Sync>), - Downward(Box + Send + Sync>), - Node(Box + Send + Sync>), -} - -impl AnyPass { - fn pass_id(&self) -> PassId { - match self { - Self::Upward(pass) => pass.pass_id(), - Self::Downward(pass) => pass.pass_id(), - Self::Node(pass) => pass.pass_id(), - } +impl DirtyNodes { + pub fn insert(&mut self, depth: u16, node_id: NodeId) { + self.map + .entry(depth) + .or_insert_with(FxHashSet::default) + .insert(node_id); } - fn dependancies(&self) -> &'static [PassId] { - match self { - Self::Upward(pass) => pass.dependancies(), - Self::Downward(pass) => pass.dependancies(), - Self::Node(pass) => pass.dependancies(), + fn pop_front(&mut self) -> Option { + let (&depth, values) = self.map.iter_mut().next()?; + let key = *values.iter().next()?; + let node_id = values.take(&key)?; + if values.is_empty() { + self.map.remove(&depth); } + Some(node_id) } + + fn pop_back(&mut self) -> Option { + let (&depth, values) = self.map.iter_mut().rev().next()?; + let key = *values.iter().next()?; + let node_id = values.take(&key)?; + if values.is_empty() { + self.map.remove(&depth); + } + Some(node_id) + } +} + +#[test] +fn dirty_nodes() { + let mut dirty_nodes = DirtyNodes::default(); + + dirty_nodes.insert(1, NodeId(1)); + dirty_nodes.insert(0, NodeId(0)); + dirty_nodes.insert(2, NodeId(3)); + dirty_nodes.insert(1, NodeId(2)); + + assert_eq!(dirty_nodes.pop_front(), Some(NodeId(0))); + assert!(matches!(dirty_nodes.pop_front(), Some(NodeId(1 | 2)))); + assert!(matches!(dirty_nodes.pop_front(), Some(NodeId(1 | 2)))); + assert_eq!(dirty_nodes.pop_front(), Some(NodeId(3))); } type FxDashMap = dashmap::DashMap>; @@ -112,247 +114,248 @@ impl DirtyNodeStates { } } - fn all_dirty(&self, pass_id: PassId) -> impl Iterator + '_ { + fn all_dirty(&self, pass_id: PassId, dirty_nodes: &mut DirtyNodes, tree: &impl TreeView) { let pass_id = pass_id.0; let index = pass_id / 64; let bit = pass_id % 64; let encoded = 1 << bit; - self.dirty.iter().filter_map(move |entry| { + for entry in self.dirty.iter() { let node_id = entry.key(); let dirty = entry.value(); if let Some(atomic) = dirty.get(index as usize) { if atomic.load(Ordering::Relaxed) & encoded != 0 { - Some(*node_id) - } else { - None - } - } else { - None - } - }) - } -} - -fn get_pass>( - passes: &mut Vec>, - resolved_passes: &mut FxHashSet, - dirty_nodes: &DirtyNodeStates, - shared_view: &mut SharedView, - global: &Injector, - current_pass: &mut Option>, -) { - for i in 0..passes.len() { - if passes[i] - .dependancies() - .iter() - .all(|id| resolved_passes.contains(id)) - { - let pass = passes.remove(i); - let pass_id = pass.pass_id(); - println!("Running pass {:?}", pass_id); - resolved_passes.insert(pass_id); - match pass { - AnyPass::Upward(pass) => { - // Upward passes are more difficult. Right now we limit them to only one thread. - let worker = Worker::new_fifo(); - let mut queued_nodes = FxHashSet::default(); - for node in dirty_nodes.all_dirty(pass_id) { - queued_nodes.insert(node); - worker.push(node); - } - while let Some(id) = worker.pop() { - let (node, mut children) = shared_view.parent_child_mut(id).unwrap(); - if pass.pass(node, &mut children) { - drop(children); - if let Some(id) = shared_view.parent_id(id) { - for dependant in pass.dependants() { - dirty_nodes.insert(*dependant, id); - } - if !queued_nodes.contains(&id) { - queued_nodes.insert(id); - worker.push(id); - } - } - } - } - } - AnyPass::Downward(pass) => { - let mut sorted: Vec<_> = dirty_nodes.all_dirty(pass_id).collect(); - sorted.sort_unstable_by_key(|id| shared_view.height(*id)); - println!( - "Task: {:?} {:?}", - pass_id, - sorted - .iter() - .map(|id| (id, shared_view.height(*id))) - .collect::>() - ); - for node in sorted.into_iter() { - global.push(node); - } - current_pass.replace(AnyPass::Downward(pass)); - } - AnyPass::Node(pass) => { - for node in dirty_nodes.all_dirty(pass_id) { - global.push(node); - } - println!( - "Task: {:?} {:?}", - pass_id, - dirty_nodes.all_dirty(pass_id).collect::>() - ); - current_pass.replace(AnyPass::Node(pass)); + dirty_nodes.insert(tree.height(*node_id).unwrap(), *node_id); } } - return; } } - panic!( - "No pass found with all dependancies resolved in {:?}", - passes.iter().map(|p| p.pass_id()).collect::>() - ); } -pub fn resolve_passes( +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, PartialOrd, Ord)] +pub struct PassId(u64); + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Default)] +pub struct MemberMask(u64); + +impl MemberMask { + pub fn overlaps(&self, other: Self) -> bool { + (*self & other).0 != 0 + } +} + +impl BitAndAssign for MemberMask { + fn bitand_assign(&mut self, rhs: Self) { + self.0 &= rhs.0; + } +} + +impl BitAnd for MemberMask { + type Output = Self; + + fn bitand(self, rhs: Self) -> Self::Output { + MemberMask(self.0 & rhs.0) + } +} + +impl BitOrAssign for MemberMask { + fn bitor_assign(&mut self, rhs: Self) { + self.0 |= rhs.0; + } +} + +impl BitOr for MemberMask { + type Output = Self; + + fn bitor(self, rhs: Self) -> Self::Output { + Self(self.0 | rhs.0) + } +} + +pub struct PassReturn { + progress: bool, + mark_dirty: bool, +} + +pub trait Pass { + fn pass_id(&self) -> PassId; + fn dependancies(&self) -> &'static [PassId]; + fn dependants(&self) -> &'static [PassId]; + fn mask(&self) -> MemberMask; +} + +pub trait UpwardPass: Pass { + fn pass<'a>(&self, node: &mut T, children: &mut dyn Iterator) -> PassReturn; +} + +fn resolve_upward_pass + ?Sized>( tree: &mut impl TreeView, - starting_nodes: FxHashMap>, + pass: &P, + mut dirty: DirtyNodes, + dirty_states: &DirtyNodeStates, +) { + while let Some(id) = dirty.pop_back() { + let (node, mut children) = tree.parent_child_mut(id).unwrap(); + let result = pass.pass(node, &mut children); + drop(children); + if result.progress || result.mark_dirty { + if let Some(id) = tree.parent_id(id) { + if result.mark_dirty { + for dependant in pass.dependants() { + dirty_states.insert(*dependant, id); + } + } + if result.progress { + let height = tree.height(id).unwrap(); + dirty.insert(height, id); + } + } + } + } +} + +pub trait DownwardPass: Pass { + fn pass(&self, node: &mut T, parent: Option<&mut T>) -> PassReturn; +} + +fn resolve_downward_pass + ?Sized>( + tree: &mut impl TreeView, + pass: &P, + mut dirty: DirtyNodes, + dirty_states: &DirtyNodeStates, +) { + while let Some(id) = dirty.pop_front() { + let (node, parent) = tree.node_parent_mut(id).unwrap(); + let result = pass.pass(node, parent); + if result.mark_dirty || result.progress { + for id in tree.children_ids(id).unwrap() { + if result.mark_dirty { + for dependant in pass.dependants() { + dirty_states.insert(*dependant, *id); + } + } + if result.progress { + let height = tree.height(*id).unwrap(); + dirty.insert(height, *id); + } + } + } + } +} + +pub trait NodePass: Pass { + fn pass(&self, node: &mut T) -> bool; +} + +fn resolve_node_pass + ?Sized>( + tree: &mut impl TreeView, + pass: &P, + mut dirty: DirtyNodes, + dirty_states: &DirtyNodeStates, +) { + while let Some(id) = dirty.pop_back() { + let node = tree.get_mut(id).unwrap(); + if pass.pass(node) { + for dependant in pass.dependants() { + dirty_states.insert(*dependant, id); + } + } + } +} + +pub enum AnyPass { + Upward(Box + Send + Sync>), + Downward(Box + Send + Sync>), + Node(Box + Send + Sync>), +} + +impl AnyPass { + fn pass_id(&self) -> PassId { + match self { + Self::Upward(pass) => pass.pass_id(), + Self::Downward(pass) => pass.pass_id(), + Self::Node(pass) => pass.pass_id(), + } + } + + fn dependancies(&self) -> &'static [PassId] { + match self { + Self::Upward(pass) => pass.dependancies(), + Self::Downward(pass) => pass.dependancies(), + Self::Node(pass) => pass.dependancies(), + } + } + + fn mask(&self) -> MemberMask { + match self { + Self::Upward(pass) => pass.mask(), + Self::Downward(pass) => pass.mask(), + Self::Node(pass) => pass.mask(), + } + } + + fn resolve( + &self, + tree: &mut impl TreeView, + dirty: DirtyNodes, + dirty_states: &DirtyNodeStates, + ) { + match self { + Self::Downward(pass) => resolve_downward_pass(tree, pass.as_ref(), dirty, dirty_states), + Self::Upward(pass) => resolve_upward_pass(tree, pass.as_ref(), dirty, dirty_states), + Self::Node(pass) => resolve_node_pass(tree, pass.as_ref(), dirty, dirty_states), + } + } +} + +struct RawPointer(*mut T); +unsafe impl Send for RawPointer {} +unsafe impl Sync for RawPointer {} + +fn resolve_passes>( + tree: &mut Tr, + dirty_nodes: DirtyNodeStates, mut passes: Vec>, ) { - assert!(!passes.is_empty()); - let dirty_nodes: Arc = Arc::new(DirtyNodeStates::new(starting_nodes)); - let global = Injector::default(); - - let core_count = thread::available_parallelism() - .map(|c| c.get()) - .unwrap_or(1); - let workers: Vec> = (0..core_count).map(|_| Worker::new_fifo()).collect(); - let stealers: Vec<_> = workers.iter().map(|w| w.stealer()).collect(); - let mut shared_view = SharedView::new(tree); + let dirty_states = Arc::new(dirty_nodes); let mut resolved_passes: FxHashSet = FxHashSet::default(); - let current_pass: Arc>>> = Arc::new(RwLock::new(None)); - - thread::scope(|s| { - { - let mut write = current_pass.write(); - get_pass( - &mut passes, - &mut resolved_passes, - &dirty_nodes, - &mut shared_view, - &global, - &mut *write, - ); - } - let global = &global; - let stealers = &stealers; - let mut thread_handles = Vec::new(); - let threads_finished = Arc::new((Mutex::new(0), Condvar::new())); - for (_, w) in (0..core_count).zip(workers.into_iter()) { - let mut shared_view = shared_view.clone(); - let current_pass = current_pass.clone(); - let dirty_nodes = dirty_nodes.clone(); - let threads_finished = threads_finished.clone(); - thread_handles.push(s.spawn(move || { - loop { - let read = current_pass.read(); - if let Some(current_pass) = &*read { - let current_pass_id = current_pass.pass_id(); - match current_pass { - AnyPass::Upward(_) => { - todo!("Upward passes are single threaded") - } - AnyPass::Node(pass) => { - // Node passes are the easiest to parallelize. We just run the pass on each node. - while let Some(id) = find_task(&w, global, stealers) { - let node = shared_view.get_mut(id).unwrap(); - if pass.pass(node) { - for dependant in pass.dependants() { - dirty_nodes.insert(*dependant, id); - } - } - } - } - AnyPass::Downward(pass) => { - // Downward passes are easy to parallelize. We try to keep trees localized to one thread, but allow work stealing to balance the load. - while let Some(id) = find_task(&w, global, stealers) { - let (node, parent) = shared_view.node_parent_mut(id).unwrap(); - if pass.pass(node, parent) { - for id in shared_view.children_ids(id).unwrap() { - for dependant in pass.dependants() { - dirty_nodes.insert(*dependant, *id); - } - if !dirty_nodes.get(current_pass_id, *id) { - w.push(*id); - } - } - } - } - } - } - // unblock the rwlock - drop(read); - { - let (count, cvar) = &*threads_finished; - let mut lock = count.lock(); - *lock += 1; - if *lock == core_count { - cvar.notify_all(); - } - } - // wait for the main thread to pick the next pass - thread::park(); - } else { - break; - } + let mut resolving = Vec::new(); + while !passes.is_empty() { + let mut currently_borrowed = MemberMask::default(); + std::thread::scope(|s| { + let mut i = 0; + while i < passes.len() { + let pass = &passes[i]; + let pass_id = pass.pass_id(); + let pass_mask = pass.mask(); + if pass + .dependancies() + .iter() + .all(|d| resolved_passes.contains(d)) + && !pass_mask.overlaps(currently_borrowed) + { + let pass = passes.remove(i); + resolving.push(pass_id); + currently_borrowed |= pass_mask; + let tree_mut = tree as *mut _; + let raw_ptr = RawPointer(tree_mut); + let dirty_states = dirty_states.clone(); + s.spawn(move || unsafe { + // let tree_mut: &mut Tr = &mut *raw_ptr.0; + let raw = raw_ptr; + let tree_mut: &mut Tr = &mut *raw.0; + let mut dirty = DirtyNodes::default(); + dirty_states.all_dirty(pass_id, &mut dirty, tree_mut); + pass.resolve(tree_mut, dirty, &dirty_states); + }); + } else { + i += 1; } - })); - } - let wait_for_thread_to_finish = move || { - // this will block until all threads are done with the current pass - let (count, cvar) = &*threads_finished; - let mut count = count.lock(); - cvar.wait(&mut count); - }; - while !passes.is_empty() { - wait_for_thread_to_finish(); - let mut write = current_pass.write(); - println!("Threads finished"); - get_pass( - &mut passes, - &mut resolved_passes, - &dirty_nodes, - &mut shared_view, - global, - &mut *write, - ); - // notify all threads to start the next pass - for thread in &thread_handles { - thread.thread().unpark(); } - } - *current_pass.write() = None; - for thread in &thread_handles { - thread.thread().unpark(); - } - }); -} - -fn find_task(local: &Worker, global: &Injector, stealers: &[Stealer]) -> Option { - // Pop a task from the local queue, if not empty. - local.pop().or_else(|| { - // Otherwise, we need to look for a task elsewhere. - std::iter::repeat_with(|| { - // Try stealing a batch of tasks from the global queue. - global - .steal_batch_and_pop(local) - // Or try stealing a task from one of the other threads. - .or_else(|| stealers.iter().map(|s| s.steal()).collect()) - }) - // Loop while no task was stolen and any steal operation needs to be retried. - .find(|s| !s.is_retry()) - // Extract the stolen task, if there is one. - .and_then(|s| s.success()) - }) + // all passes are resolved at the end of the scope + }); + resolved_passes.extend(resolving.iter().copied()); + resolving.clear() + } } #[test] @@ -371,8 +374,7 @@ fn node_pass() { println!("{:#?}", tree); struct AddPass; - - impl NodePass for AddPass { + impl Pass for AddPass { fn pass_id(&self) -> PassId { PassId(0) } @@ -385,6 +387,12 @@ fn node_pass() { &[] } + fn mask(&self) -> MemberMask { + MemberMask(1) + } + } + + impl NodePass for AddPass { fn pass(&self, node: &mut i32) -> bool { *node += 1; true @@ -392,8 +400,8 @@ fn node_pass() { } let passes = vec![AnyPass::Node(Box::new(AddPass))]; - let mut dirty_nodes: FxHashMap> = FxHashMap::default(); - dirty_nodes.insert(tree.root(), [PassId(0)].into_iter().collect()); + let mut dirty_nodes: DirtyNodeStates = DirtyNodeStates::default(); + dirty_nodes.insert(PassId(0), tree.root()); resolve_passes(&mut tree, dirty_nodes, passes); assert_eq!(tree.get(tree.root()).unwrap(), &1); @@ -414,8 +422,7 @@ fn dependant_node_pass() { tree.add_child(child2, grandchild2); struct AddPass; - - impl NodePass for AddPass { + impl Pass for AddPass { fn pass_id(&self) -> PassId { PassId(0) } @@ -428,6 +435,12 @@ fn dependant_node_pass() { &[] } + fn mask(&self) -> MemberMask { + MemberMask(1) + } + } + + impl NodePass for AddPass { fn pass(&self, node: &mut i32) -> bool { println!("AddPass: {}", node); *node += 1; @@ -437,7 +450,7 @@ fn dependant_node_pass() { struct SubtractPass; - impl NodePass for SubtractPass { + impl Pass for SubtractPass { fn pass_id(&self) -> PassId { PassId(1) } @@ -450,6 +463,11 @@ fn dependant_node_pass() { &[PassId(0)] } + fn mask(&self) -> MemberMask { + MemberMask(1) + } + } + impl NodePass for SubtractPass { fn pass(&self, node: &mut i32) -> bool { println!("SubtractPass: {}", node); *node -= 1; @@ -461,8 +479,8 @@ fn dependant_node_pass() { AnyPass::Node(Box::new(AddPass)), AnyPass::Node(Box::new(SubtractPass)), ]; - let mut dirty_nodes: FxHashMap> = FxHashMap::default(); - dirty_nodes.insert(tree.root(), [PassId(1)].into_iter().collect()); + let mut dirty_nodes: DirtyNodeStates = DirtyNodeStates::default(); + dirty_nodes.insert(PassId(1), tree.root()); resolve_passes(&mut tree, dirty_nodes, passes); assert_eq!(*tree.get(tree.root()).unwrap(), 0); @@ -484,7 +502,7 @@ fn down_pass() { struct AddPass; - impl DownwardPass for AddPass { + impl Pass for AddPass { fn pass_id(&self) -> PassId { PassId(0) } @@ -497,17 +515,25 @@ fn down_pass() { &[] } - fn pass(&self, node: &mut i32, parent: Option<&mut i32>) -> bool { + fn mask(&self) -> MemberMask { + MemberMask(1) + } + } + impl DownwardPass for AddPass { + fn pass(&self, node: &mut i32, parent: Option<&mut i32>) -> PassReturn { if let Some(parent) = parent { *node += *parent; } - true + PassReturn { + progress: true, + mark_dirty: true, + } } } let passes = vec![AnyPass::Downward(Box::new(AddPass))]; - let mut dirty_nodes: FxHashMap> = FxHashMap::default(); - dirty_nodes.insert(tree.root(), [PassId(0)].into_iter().collect()); + let dirty_nodes: DirtyNodeStates = DirtyNodeStates::default(); + dirty_nodes.insert(PassId(0), tree.root()); resolve_passes(&mut tree, dirty_nodes, passes); assert_eq!(tree.get(tree.root()).unwrap(), &1); @@ -537,8 +563,7 @@ fn dependant_down_pass() { tree.add_child(child2, grandchild2); struct AddPass; - - impl DownwardPass for AddPass { + impl Pass for AddPass { fn pass_id(&self) -> PassId { PassId(0) } @@ -551,20 +576,27 @@ fn dependant_down_pass() { &[] } - fn pass(&self, node: &mut i32, parent: Option<&mut i32>) -> bool { + fn mask(&self) -> MemberMask { + MemberMask(1) + } + } + impl DownwardPass for AddPass { + fn pass(&self, node: &mut i32, parent: Option<&mut i32>) -> PassReturn { if let Some(parent) = parent { println!("AddPass: {} -> {}", node, *node + *parent); *node += *parent; } else { println!("AddPass: {}", node); } - true + PassReturn { + progress: true, + mark_dirty: true, + } } } struct SubtractPass; - - impl DownwardPass for SubtractPass { + impl Pass for SubtractPass { fn pass_id(&self) -> PassId { PassId(1) } @@ -577,14 +609,22 @@ fn dependant_down_pass() { &[PassId(0)] } - fn pass(&self, node: &mut i32, parent: Option<&mut i32>) -> bool { + fn mask(&self) -> MemberMask { + MemberMask(1) + } + } + impl DownwardPass for SubtractPass { + fn pass(&self, node: &mut i32, parent: Option<&mut i32>) -> PassReturn { if let Some(parent) = parent { println!("SubtractPass: {} -> {}", node, *node - *parent); *node -= *parent; } else { println!("SubtractPass: {}", node); } - true + PassReturn { + progress: true, + mark_dirty: true, + } } } @@ -592,8 +632,8 @@ fn dependant_down_pass() { AnyPass::Downward(Box::new(AddPass)), AnyPass::Downward(Box::new(SubtractPass)), ]; - let mut dirty_nodes: FxHashMap> = FxHashMap::default(); - dirty_nodes.insert(tree.root(), [PassId(1)].into_iter().collect()); + let mut dirty_nodes: DirtyNodeStates = DirtyNodeStates::default(); + dirty_nodes.insert(PassId(1), tree.root()); resolve_passes(&mut tree, dirty_nodes, passes); // Tree before: @@ -648,8 +688,7 @@ fn up_pass() { tree.add_child(child2, grandchild2); struct AddPass; - - impl UpwardPass for AddPass { + impl Pass for AddPass { fn pass_id(&self) -> PassId { PassId(0) } @@ -662,20 +701,28 @@ fn up_pass() { &[] } + fn mask(&self) -> MemberMask { + MemberMask(1) + } + } + impl UpwardPass for AddPass { fn pass<'a>( &self, node: &mut i32, children: &mut dyn Iterator, - ) -> bool { + ) -> PassReturn { *node += children.map(|i| *i).sum::(); - true + PassReturn { + progress: true, + mark_dirty: true, + } } } let passes = vec![AnyPass::Upward(Box::new(AddPass))]; - let mut dirty_nodes: FxHashMap> = FxHashMap::default(); - dirty_nodes.insert(grandchild1, [PassId(0)].into_iter().collect()); - dirty_nodes.insert(grandchild2, [PassId(0)].into_iter().collect()); + let mut dirty_nodes: DirtyNodeStates = DirtyNodeStates::default(); + dirty_nodes.insert(PassId(0), grandchild1); + dirty_nodes.insert(PassId(0), grandchild2); resolve_passes(&mut tree, dirty_nodes, passes); assert_eq!(tree.get(tree.root()).unwrap(), &2); From bb3be32b1958e8cabc0afb2262633ea151c7427d Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 26 Nov 2022 11:51:52 -0600 Subject: [PATCH 14/93] fix some warnings --- packages/native-core/src/passes.rs | 37 ++++++++---------------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index b08a65cf5..99dec696d 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -1,9 +1,8 @@ -use crossbeam_deque::{Injector, Stealer, Worker}; use rustc_hash::{FxHashMap, FxHashSet, FxHasher}; -use std::collections::{BTreeMap, HashMap}; +use std::collections::BTreeMap; use std::hash::BuildHasherDefault; use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign}; -use std::os::raw; + use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; @@ -61,12 +60,12 @@ fn dirty_nodes() { type FxDashMap = dashmap::DashMap>; #[derive(Default)] -struct DirtyNodeStates { +pub struct DirtyNodeStates { dirty: FxDashMap>, } impl DirtyNodeStates { - fn new(starting_nodes: FxHashMap>) -> Self { + pub fn new(starting_nodes: FxHashMap>) -> Self { let this = Self::default(); for (node, nodes) in starting_nodes { for pass_id in nodes { @@ -76,7 +75,7 @@ impl DirtyNodeStates { this } - fn insert(&self, pass_id: PassId, node_id: NodeId) { + pub fn insert(&self, pass_id: PassId, node_id: NodeId) { let pass_id = pass_id.0; let index = pass_id / 64; let bit = pass_id % 64; @@ -98,22 +97,6 @@ impl DirtyNodeStates { } } - fn get(&self, pass_id: PassId, node_id: NodeId) -> bool { - let pass_id = pass_id.0; - let index = pass_id / 64; - let bit = pass_id % 64; - let encoded = 1 << bit; - if let Some(dirty) = self.dirty.get(&node_id) { - if let Some(atomic) = dirty.get(index as usize) { - atomic.load(Ordering::SeqCst) & encoded != 0 - } else { - false - } - } else { - false - } - } - fn all_dirty(&self, pass_id: PassId, dirty_nodes: &mut DirtyNodes, tree: &impl TreeView) { let pass_id = pass_id.0; let index = pass_id / 64; @@ -311,7 +294,7 @@ struct RawPointer(*mut T); unsafe impl Send for RawPointer {} unsafe impl Sync for RawPointer {} -fn resolve_passes>( +pub fn resolve_passes>( tree: &mut Tr, dirty_nodes: DirtyNodeStates, mut passes: Vec>, @@ -400,7 +383,7 @@ fn node_pass() { } let passes = vec![AnyPass::Node(Box::new(AddPass))]; - let mut dirty_nodes: DirtyNodeStates = DirtyNodeStates::default(); + let dirty_nodes: DirtyNodeStates = DirtyNodeStates::default(); dirty_nodes.insert(PassId(0), tree.root()); resolve_passes(&mut tree, dirty_nodes, passes); @@ -479,7 +462,7 @@ fn dependant_node_pass() { AnyPass::Node(Box::new(AddPass)), AnyPass::Node(Box::new(SubtractPass)), ]; - let mut dirty_nodes: DirtyNodeStates = DirtyNodeStates::default(); + let dirty_nodes: DirtyNodeStates = DirtyNodeStates::default(); dirty_nodes.insert(PassId(1), tree.root()); resolve_passes(&mut tree, dirty_nodes, passes); @@ -632,7 +615,7 @@ fn dependant_down_pass() { AnyPass::Downward(Box::new(AddPass)), AnyPass::Downward(Box::new(SubtractPass)), ]; - let mut dirty_nodes: DirtyNodeStates = DirtyNodeStates::default(); + let dirty_nodes: DirtyNodeStates = DirtyNodeStates::default(); dirty_nodes.insert(PassId(1), tree.root()); resolve_passes(&mut tree, dirty_nodes, passes); @@ -720,7 +703,7 @@ fn up_pass() { } let passes = vec![AnyPass::Upward(Box::new(AddPass))]; - let mut dirty_nodes: DirtyNodeStates = DirtyNodeStates::default(); + let dirty_nodes: DirtyNodeStates = DirtyNodeStates::default(); dirty_nodes.insert(PassId(0), grandchild1); dirty_nodes.insert(PassId(0), grandchild2); resolve_passes(&mut tree, dirty_nodes, passes); From 42888f0cbc05045d2129c16dde48a9b50942d4b4 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 26 Nov 2022 12:13:37 -0600 Subject: [PATCH 15/93] add up dependant test --- packages/native-core/src/passes.rs | 117 +++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index 99dec696d..a21d6920a 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -714,3 +714,120 @@ fn up_pass() { assert_eq!(tree.get(child2).unwrap(), &1); assert_eq!(tree.get(grandchild2).unwrap(), &1); } + +#[test] +fn dependant_up_pass() { + use crate::tree::{Tree, TreeLike}; + // 0 + let mut tree = Tree::new(0); + let parent = tree.root(); + // 1 + let child1 = tree.create_node(0); + tree.add_child(parent, child1); + // 2 + let grandchild1 = tree.create_node(1); + tree.add_child(child1, grandchild1); + // 3 + let child2 = tree.create_node(0); + tree.add_child(parent, child2); + // 4 + let grandchild2 = tree.create_node(1); + tree.add_child(child2, grandchild2); + + struct AddPass; + impl Pass for AddPass { + fn pass_id(&self) -> PassId { + PassId(0) + } + + fn dependancies(&self) -> &'static [PassId] { + &[PassId(1)] + } + + fn dependants(&self) -> &'static [PassId] { + &[] + } + + fn mask(&self) -> MemberMask { + MemberMask(1) + } + } + impl UpwardPass for AddPass { + fn pass<'a>( + &self, + node: &mut i32, + children: &mut dyn Iterator, + ) -> PassReturn { + *node += children.map(|i| *i).sum::(); + PassReturn { + progress: true, + mark_dirty: true, + } + } + } + + struct SubtractPass; + impl Pass for SubtractPass { + fn pass_id(&self) -> PassId { + PassId(1) + } + + fn dependancies(&self) -> &'static [PassId] { + &[] + } + + fn dependants(&self) -> &'static [PassId] { + &[PassId(0)] + } + + fn mask(&self) -> MemberMask { + MemberMask(1) + } + } + impl UpwardPass for SubtractPass { + fn pass<'a>( + &self, + node: &mut i32, + children: &mut dyn Iterator, + ) -> PassReturn { + *node -= children.map(|i| *i).sum::(); + PassReturn { + progress: true, + mark_dirty: true, + } + } + } + + let passes = vec![ + AnyPass::Upward(Box::new(AddPass)), + AnyPass::Upward(Box::new(SubtractPass)), + ]; + let dirty_nodes: DirtyNodeStates = DirtyNodeStates::default(); + dirty_nodes.insert(PassId(1), grandchild1); + dirty_nodes.insert(PassId(1), grandchild2); + resolve_passes(&mut tree, dirty_nodes, passes); + + // Tree before: + // 0=\ + // 0=\ + // 1 + // 0=\ + // 1 + // Tree after subtract: + // 2=\ + // -1=\ + // 1 + // -1=\ + // 1 + // Tree after add: + // 2=\ + // 0=\ + // 1 + // 0=\ + // 1 + assert_eq!(tree.get(tree.root()).unwrap(), &2); + assert_eq!(tree.get(child1).unwrap(), &0); + assert_eq!(tree.get(grandchild1).unwrap(), &1); + assert_eq!(tree.get(child2).unwrap(), &0); + assert_eq!(tree.get(grandchild2).unwrap(), &1); +} From 4812b5124a4e243c7961ade510e46c6fc1cbc1fb Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 26 Nov 2022 12:18:23 -0600 Subject: [PATCH 16/93] clean up examples --- packages/native-core/src/passes.rs | 104 +++++++++++++++++++++-------- 1 file changed, 78 insertions(+), 26 deletions(-) diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index a21d6920a..6c8582170 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -346,14 +346,6 @@ fn node_pass() { use crate::tree::{Tree, TreeLike}; let mut tree = Tree::new(0); let parent = tree.root(); - let child1 = tree.create_node(1); - tree.add_child(parent, child1); - let grandchild1 = tree.create_node(3); - tree.add_child(child1, grandchild1); - let child2 = tree.create_node(2); - tree.add_child(parent, child2); - let grandchild2 = tree.create_node(4); - tree.add_child(child2, grandchild2); println!("{:#?}", tree); struct AddPass; @@ -371,7 +363,7 @@ fn node_pass() { } fn mask(&self) -> MemberMask { - MemberMask(1) + MemberMask(0) } } @@ -394,15 +386,6 @@ fn node_pass() { fn dependant_node_pass() { use crate::tree::{Tree, TreeLike}; let mut tree = Tree::new(0); - let parent = tree.root(); - let child1 = tree.create_node(1); - tree.add_child(parent, child1); - let grandchild1 = tree.create_node(3); - tree.add_child(child1, grandchild1); - let child2 = tree.create_node(2); - tree.add_child(parent, child2); - let grandchild2 = tree.create_node(4); - tree.add_child(child2, grandchild2); struct AddPass; impl Pass for AddPass { @@ -419,7 +402,7 @@ fn dependant_node_pass() { } fn mask(&self) -> MemberMask { - MemberMask(1) + MemberMask(0) } } @@ -447,7 +430,7 @@ fn dependant_node_pass() { } fn mask(&self) -> MemberMask { - MemberMask(1) + MemberMask(0) } } impl NodePass for SubtractPass { @@ -469,6 +452,75 @@ fn dependant_node_pass() { assert_eq!(*tree.get(tree.root()).unwrap(), 0); } +#[test] +fn independant_node_pass() { + use crate::tree::{Tree, TreeLike}; + let mut tree = Tree::new((0, 0)); + + struct AddPass1; + impl Pass for AddPass1 { + fn pass_id(&self) -> PassId { + PassId(0) + } + + fn dependancies(&self) -> &'static [PassId] { + &[] + } + + fn dependants(&self) -> &'static [PassId] { + &[] + } + + fn mask(&self) -> MemberMask { + MemberMask(0) + } + } + + impl NodePass<(i32, i32)> for AddPass1 { + fn pass(&self, node: &mut (i32, i32)) -> bool { + node.0 += 1; + true + } + } + + struct AddPass2; + impl Pass for AddPass2 { + fn pass_id(&self) -> PassId { + PassId(1) + } + + fn dependancies(&self) -> &'static [PassId] { + &[] + } + + fn dependants(&self) -> &'static [PassId] { + &[] + } + + fn mask(&self) -> MemberMask { + MemberMask(1) + } + } + + impl NodePass<(i32, i32)> for AddPass2 { + fn pass(&self, node: &mut (i32, i32)) -> bool { + node.1 += 1; + true + } + } + + let passes = vec![ + AnyPass::Node(Box::new(AddPass1)), + AnyPass::Node(Box::new(AddPass2)), + ]; + let dirty_nodes: DirtyNodeStates = DirtyNodeStates::default(); + dirty_nodes.insert(PassId(0), tree.root()); + dirty_nodes.insert(PassId(1), tree.root()); + resolve_passes(&mut tree, dirty_nodes, passes); + + assert_eq!(tree.get(tree.root()).unwrap(), &(1, 1)); +} + #[test] fn down_pass() { use crate::tree::{Tree, TreeLike}; @@ -499,7 +551,7 @@ fn down_pass() { } fn mask(&self) -> MemberMask { - MemberMask(1) + MemberMask(0) } } impl DownwardPass for AddPass { @@ -560,7 +612,7 @@ fn dependant_down_pass() { } fn mask(&self) -> MemberMask { - MemberMask(1) + MemberMask(0) } } impl DownwardPass for AddPass { @@ -593,7 +645,7 @@ fn dependant_down_pass() { } fn mask(&self) -> MemberMask { - MemberMask(1) + MemberMask(0) } } impl DownwardPass for SubtractPass { @@ -685,7 +737,7 @@ fn up_pass() { } fn mask(&self) -> MemberMask { - MemberMask(1) + MemberMask(0) } } impl UpwardPass for AddPass { @@ -749,7 +801,7 @@ fn dependant_up_pass() { } fn mask(&self) -> MemberMask { - MemberMask(1) + MemberMask(0) } } impl UpwardPass for AddPass { @@ -781,7 +833,7 @@ fn dependant_up_pass() { } fn mask(&self) -> MemberMask { - MemberMask(1) + MemberMask(0) } } impl UpwardPass for SubtractPass { From d78af904e63132848d39be49f1879c5fad383ebc Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 26 Nov 2022 15:25:02 -0600 Subject: [PATCH 17/93] work on intigrating state with passes --- packages/native-core/src/lib.rs | 8 +- packages/native-core/src/passes.rs | 153 +++++++++++++++------------ packages/native-core/src/real_dom.rs | 48 ++++----- packages/native-core/src/state.rs | 32 +++--- packages/native-core/src/tree.rs | 4 + 5 files changed, 137 insertions(+), 108 deletions(-) diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 56c4a6a73..14089c669 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -1,3 +1,6 @@ +use std::hash::BuildHasherDefault; + +use rustc_hash::FxHasher; use tree::NodeId; pub mod layout_attributes; @@ -10,7 +13,10 @@ pub mod tree; pub mod utils; /// A id for a node that lives in the real dom. -type RealNodeId = NodeId; +pub type RealNodeId = NodeId; +pub(crate) type FxDashMap = dashmap::DashMap>; +pub(crate) type FxDashSet = dashmap::DashSet>; +pub(crate) type SendAnyMap = anymap::Map; /// Used in derived state macros #[derive(Eq, PartialEq)] diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index 6c8582170..86bb50537 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -1,13 +1,11 @@ -use rustc_hash::{FxHashMap, FxHashSet, FxHasher}; +use crate::tree::{NodeId, TreeView}; +use crate::{FxDashMap, FxDashSet, SendAnyMap}; +use rustc_hash::{FxHashMap, FxHashSet}; use std::collections::BTreeMap; -use std::hash::BuildHasherDefault; use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign}; - use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; -use crate::tree::{NodeId, TreeView}; - #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct DirtyNodes { map: BTreeMap>, @@ -57,8 +55,6 @@ fn dirty_nodes() { assert_eq!(dirty_nodes.pop_front(), Some(NodeId(3))); } -type FxDashMap = dashmap::DashMap>; - #[derive(Default)] pub struct DirtyNodeStates { dirty: FxDashMap>, @@ -167,7 +163,12 @@ pub trait Pass { } pub trait UpwardPass: Pass { - fn pass<'a>(&self, node: &mut T, children: &mut dyn Iterator) -> PassReturn; + fn pass<'a>( + &self, + node: &mut T, + children: &mut dyn Iterator, + ctx: &SendAnyMap, + ) -> PassReturn; } fn resolve_upward_pass + ?Sized>( @@ -175,12 +176,15 @@ fn resolve_upward_pass + ?Sized>( pass: &P, mut dirty: DirtyNodes, dirty_states: &DirtyNodeStates, + nodes_updated: &FxDashSet, + ctx: &SendAnyMap, ) { while let Some(id) = dirty.pop_back() { let (node, mut children) = tree.parent_child_mut(id).unwrap(); - let result = pass.pass(node, &mut children); + let result = pass.pass(node, &mut children, ctx); drop(children); if result.progress || result.mark_dirty { + nodes_updated.insert(id); if let Some(id) = tree.parent_id(id) { if result.mark_dirty { for dependant in pass.dependants() { @@ -197,7 +201,7 @@ fn resolve_upward_pass + ?Sized>( } pub trait DownwardPass: Pass { - fn pass(&self, node: &mut T, parent: Option<&mut T>) -> PassReturn; + fn pass(&self, node: &mut T, parent: Option<&mut T>, ctx: &SendAnyMap) -> PassReturn; } fn resolve_downward_pass + ?Sized>( @@ -205,11 +209,14 @@ fn resolve_downward_pass + ?Sized>( pass: &P, mut dirty: DirtyNodes, dirty_states: &DirtyNodeStates, + nodes_updated: &FxDashSet, + ctx: &SendAnyMap, ) { while let Some(id) = dirty.pop_front() { let (node, parent) = tree.node_parent_mut(id).unwrap(); - let result = pass.pass(node, parent); + let result = pass.pass(node, parent, ctx); if result.mark_dirty || result.progress { + nodes_updated.insert(id); for id in tree.children_ids(id).unwrap() { if result.mark_dirty { for dependant in pass.dependants() { @@ -226,7 +233,7 @@ fn resolve_downward_pass + ?Sized>( } pub trait NodePass: Pass { - fn pass(&self, node: &mut T) -> bool; + fn pass(&self, node: &mut T, ctx: &SendAnyMap) -> bool; } fn resolve_node_pass + ?Sized>( @@ -234,10 +241,13 @@ fn resolve_node_pass + ?Sized>( pass: &P, mut dirty: DirtyNodes, dirty_states: &DirtyNodeStates, + nodes_updated: &FxDashSet, + ctx: &SendAnyMap, ) { while let Some(id) = dirty.pop_back() { let node = tree.get_mut(id).unwrap(); - if pass.pass(node) { + if pass.pass(node, ctx) { + nodes_updated.insert(id); for dependant in pass.dependants() { dirty_states.insert(*dependant, id); } @@ -245,14 +255,14 @@ fn resolve_node_pass + ?Sized>( } } -pub enum AnyPass { - Upward(Box + Send + Sync>), - Downward(Box + Send + Sync>), - Node(Box + Send + Sync>), +pub enum AnyPass { + Upward(&'static (dyn UpwardPass + Send + Sync + 'static)), + Downward(&'static (dyn DownwardPass + Send + Sync + 'static)), + Node(&'static (dyn NodePass + Send + Sync + 'static)), } impl AnyPass { - fn pass_id(&self) -> PassId { + pub fn pass_id(&self) -> PassId { match self { Self::Upward(pass) => pass.pass_id(), Self::Downward(pass) => pass.pass_id(), @@ -260,7 +270,7 @@ impl AnyPass { } } - fn dependancies(&self) -> &'static [PassId] { + pub fn dependancies(&self) -> &'static [PassId] { match self { Self::Upward(pass) => pass.dependancies(), Self::Downward(pass) => pass.dependancies(), @@ -281,11 +291,19 @@ impl AnyPass { tree: &mut impl TreeView, dirty: DirtyNodes, dirty_states: &DirtyNodeStates, + nodes_updated: &FxDashSet, + ctx: &SendAnyMap, ) { match self { - Self::Downward(pass) => resolve_downward_pass(tree, pass.as_ref(), dirty, dirty_states), - Self::Upward(pass) => resolve_upward_pass(tree, pass.as_ref(), dirty, dirty_states), - Self::Node(pass) => resolve_node_pass(tree, pass.as_ref(), dirty, dirty_states), + Self::Downward(pass) => { + resolve_downward_pass(tree, *pass, dirty, dirty_states, nodes_updated, ctx) + } + Self::Upward(pass) => { + resolve_upward_pass(tree, *pass, dirty, dirty_states, nodes_updated, ctx) + } + Self::Node(pass) => { + resolve_node_pass(tree, *pass, dirty, dirty_states, nodes_updated, ctx) + } } } } @@ -297,11 +315,14 @@ unsafe impl Sync for RawPointer {} pub fn resolve_passes>( tree: &mut Tr, dirty_nodes: DirtyNodeStates, - mut passes: Vec>, -) { + mut passes: Vec<&AnyPass>, + ctx: SendAnyMap, +) -> FxDashSet { let dirty_states = Arc::new(dirty_nodes); let mut resolved_passes: FxHashSet = FxHashSet::default(); let mut resolving = Vec::new(); + let nodes_updated = Arc::new(FxDashSet::default()); + let ctx = Arc::new(ctx); while !passes.is_empty() { let mut currently_borrowed = MemberMask::default(); std::thread::scope(|s| { @@ -322,13 +343,16 @@ pub fn resolve_passes>( let tree_mut = tree as *mut _; let raw_ptr = RawPointer(tree_mut); let dirty_states = dirty_states.clone(); + let nodes_updated = nodes_updated.clone(); + let ctx = ctx.clone(); s.spawn(move || unsafe { // let tree_mut: &mut Tr = &mut *raw_ptr.0; let raw = raw_ptr; + // this is safe because the member_mask acts as a per-member mutex and we have verified that the pass does not overlap with any other pass let tree_mut: &mut Tr = &mut *raw.0; let mut dirty = DirtyNodes::default(); dirty_states.all_dirty(pass_id, &mut dirty, tree_mut); - pass.resolve(tree_mut, dirty, &dirty_states); + pass.resolve(tree_mut, dirty, &dirty_states, &nodes_updated, &ctx); }); } else { i += 1; @@ -339,14 +363,13 @@ pub fn resolve_passes>( resolved_passes.extend(resolving.iter().copied()); resolving.clear() } + std::sync::Arc::try_unwrap(nodes_updated).unwrap() } #[test] fn node_pass() { use crate::tree::{Tree, TreeLike}; let mut tree = Tree::new(0); - let parent = tree.root(); - println!("{:#?}", tree); struct AddPass; impl Pass for AddPass { @@ -368,16 +391,17 @@ fn node_pass() { } impl NodePass for AddPass { - fn pass(&self, node: &mut i32) -> bool { + fn pass(&self, node: &mut i32, _: &SendAnyMap) -> bool { *node += 1; true } } - let passes = vec![AnyPass::Node(Box::new(AddPass))]; + let add_pass = AnyPass::Node(&AddPass); + let passes = vec![&add_pass]; let dirty_nodes: DirtyNodeStates = DirtyNodeStates::default(); dirty_nodes.insert(PassId(0), tree.root()); - resolve_passes(&mut tree, dirty_nodes, passes); + resolve_passes(&mut tree, dirty_nodes, passes, SendAnyMap::new()); assert_eq!(tree.get(tree.root()).unwrap(), &1); } @@ -407,8 +431,7 @@ fn dependant_node_pass() { } impl NodePass for AddPass { - fn pass(&self, node: &mut i32) -> bool { - println!("AddPass: {}", node); + fn pass(&self, node: &mut i32, _: &SendAnyMap) -> bool { *node += 1; true } @@ -434,20 +457,18 @@ fn dependant_node_pass() { } } impl NodePass for SubtractPass { - fn pass(&self, node: &mut i32) -> bool { - println!("SubtractPass: {}", node); + fn pass(&self, node: &mut i32, _: &SendAnyMap) -> bool { *node -= 1; true } } - let passes = vec![ - AnyPass::Node(Box::new(AddPass)), - AnyPass::Node(Box::new(SubtractPass)), - ]; + let add_pass = AnyPass::Node(&AddPass); + let subtract_pass = AnyPass::Node(&SubtractPass); + let passes = vec![&add_pass, &subtract_pass]; let dirty_nodes: DirtyNodeStates = DirtyNodeStates::default(); dirty_nodes.insert(PassId(1), tree.root()); - resolve_passes(&mut tree, dirty_nodes, passes); + resolve_passes(&mut tree, dirty_nodes, passes, SendAnyMap::new()); assert_eq!(*tree.get(tree.root()).unwrap(), 0); } @@ -477,7 +498,7 @@ fn independant_node_pass() { } impl NodePass<(i32, i32)> for AddPass1 { - fn pass(&self, node: &mut (i32, i32)) -> bool { + fn pass(&self, node: &mut (i32, i32), _: &SendAnyMap) -> bool { node.0 += 1; true } @@ -503,20 +524,19 @@ fn independant_node_pass() { } impl NodePass<(i32, i32)> for AddPass2 { - fn pass(&self, node: &mut (i32, i32)) -> bool { + fn pass(&self, node: &mut (i32, i32), _: &SendAnyMap) -> bool { node.1 += 1; true } } - let passes = vec![ - AnyPass::Node(Box::new(AddPass1)), - AnyPass::Node(Box::new(AddPass2)), - ]; + let add_pass1 = AnyPass::Node(&AddPass1); + let add_pass2 = AnyPass::Node(&AddPass2); + let passes = vec![&add_pass1, &add_pass2]; let dirty_nodes: DirtyNodeStates = DirtyNodeStates::default(); dirty_nodes.insert(PassId(0), tree.root()); dirty_nodes.insert(PassId(1), tree.root()); - resolve_passes(&mut tree, dirty_nodes, passes); + resolve_passes(&mut tree, dirty_nodes, passes, SendAnyMap::new()); assert_eq!(tree.get(tree.root()).unwrap(), &(1, 1)); } @@ -555,7 +575,7 @@ fn down_pass() { } } impl DownwardPass for AddPass { - fn pass(&self, node: &mut i32, parent: Option<&mut i32>) -> PassReturn { + fn pass(&self, node: &mut i32, parent: Option<&mut i32>, _: &SendAnyMap) -> PassReturn { if let Some(parent) = parent { *node += *parent; } @@ -566,10 +586,11 @@ fn down_pass() { } } - let passes = vec![AnyPass::Downward(Box::new(AddPass))]; + let add_pass = AnyPass::Downward(&AddPass); + let passes = vec![&add_pass]; let dirty_nodes: DirtyNodeStates = DirtyNodeStates::default(); dirty_nodes.insert(PassId(0), tree.root()); - resolve_passes(&mut tree, dirty_nodes, passes); + resolve_passes(&mut tree, dirty_nodes, passes, SendAnyMap::new()); assert_eq!(tree.get(tree.root()).unwrap(), &1); assert_eq!(tree.get(child1).unwrap(), &2); @@ -616,12 +637,10 @@ fn dependant_down_pass() { } } impl DownwardPass for AddPass { - fn pass(&self, node: &mut i32, parent: Option<&mut i32>) -> PassReturn { + fn pass(&self, node: &mut i32, parent: Option<&mut i32>, _: &SendAnyMap) -> PassReturn { if let Some(parent) = parent { - println!("AddPass: {} -> {}", node, *node + *parent); *node += *parent; } else { - println!("AddPass: {}", node); } PassReturn { progress: true, @@ -649,12 +668,10 @@ fn dependant_down_pass() { } } impl DownwardPass for SubtractPass { - fn pass(&self, node: &mut i32, parent: Option<&mut i32>) -> PassReturn { + fn pass(&self, node: &mut i32, parent: Option<&mut i32>, _: &SendAnyMap) -> PassReturn { if let Some(parent) = parent { - println!("SubtractPass: {} -> {}", node, *node - *parent); *node -= *parent; } else { - println!("SubtractPass: {}", node); } PassReturn { progress: true, @@ -663,13 +680,12 @@ fn dependant_down_pass() { } } - let passes = vec![ - AnyPass::Downward(Box::new(AddPass)), - AnyPass::Downward(Box::new(SubtractPass)), - ]; + let add_pass = AnyPass::Downward(&AddPass); + let subtract_pass = AnyPass::Downward(&SubtractPass); + let passes = vec![&add_pass, &subtract_pass]; let dirty_nodes: DirtyNodeStates = DirtyNodeStates::default(); dirty_nodes.insert(PassId(1), tree.root()); - resolve_passes(&mut tree, dirty_nodes, passes); + resolve_passes(&mut tree, dirty_nodes, passes, SendAnyMap::new()); // Tree before: // 1=\ @@ -745,6 +761,7 @@ fn up_pass() { &self, node: &mut i32, children: &mut dyn Iterator, + _: &SendAnyMap, ) -> PassReturn { *node += children.map(|i| *i).sum::(); PassReturn { @@ -754,11 +771,12 @@ fn up_pass() { } } - let passes = vec![AnyPass::Upward(Box::new(AddPass))]; + let add_pass = AnyPass::Upward(&AddPass); + let passes = vec![&add_pass]; let dirty_nodes: DirtyNodeStates = DirtyNodeStates::default(); dirty_nodes.insert(PassId(0), grandchild1); dirty_nodes.insert(PassId(0), grandchild2); - resolve_passes(&mut tree, dirty_nodes, passes); + resolve_passes(&mut tree, dirty_nodes, passes, SendAnyMap::new()); assert_eq!(tree.get(tree.root()).unwrap(), &2); assert_eq!(tree.get(child1).unwrap(), &1); @@ -809,6 +827,7 @@ fn dependant_up_pass() { &self, node: &mut i32, children: &mut dyn Iterator, + _: &SendAnyMap, ) -> PassReturn { *node += children.map(|i| *i).sum::(); PassReturn { @@ -841,6 +860,7 @@ fn dependant_up_pass() { &self, node: &mut i32, children: &mut dyn Iterator, + _: &SendAnyMap, ) -> PassReturn { *node -= children.map(|i| *i).sum::(); PassReturn { @@ -850,14 +870,13 @@ fn dependant_up_pass() { } } - let passes = vec![ - AnyPass::Upward(Box::new(AddPass)), - AnyPass::Upward(Box::new(SubtractPass)), - ]; + let add_pass = AnyPass::Upward(&AddPass); + let subtract_pass = AnyPass::Upward(&SubtractPass); + let passes = vec![&add_pass, &subtract_pass]; let dirty_nodes: DirtyNodeStates = DirtyNodeStates::default(); dirty_nodes.insert(PassId(1), grandchild1); dirty_nodes.insert(PassId(1), grandchild2); - resolve_passes(&mut tree, dirty_nodes, passes); + resolve_passes(&mut tree, dirty_nodes, passes, SendAnyMap::new()); // Tree before: // 0=\ diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 683d3bb65..48c1febcc 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -1,13 +1,13 @@ -use anymap::AnyMap; use dioxus_core::{ElementId, Mutations}; use rustc_hash::{FxHashMap, FxHashSet}; use std::ops::{Index, IndexMut}; -use crate::node::{Node, NodeData, NodeType, OwnedAttributeDiscription}; +use crate::node::{Node, NodeType, OwnedAttributeDiscription}; use crate::node_ref::{AttributeMask, NodeMask}; +use crate::passes::DirtyNodeStates; use crate::state::State; use crate::tree::{NodeId, Tree, TreeLike, TreeView}; -use crate::RealNodeId; +use crate::{FxDashSet, RealNodeId, SendAnyMap}; fn mark_dirty( node_id: NodeId, @@ -96,10 +96,7 @@ impl RealDom { } /// Updates the dom with some mutations and return a set of nodes that were updated. Pass the dirty nodes to update_state. - pub fn apply_mutations( - &mut self, - mutations_vec: Vec, - ) -> FxHashMap { + pub fn apply_mutations(&mut self, mutations_vec: Vec) -> DirtyNodeStates { let mut nodes_updated: FxHashMap = FxHashMap::default(); for mutations in mutations_vec { for e in mutations.edits { @@ -346,20 +343,28 @@ impl RealDom { } } - // remove any nodes that were created and then removed in the same mutations from the dirty nodes list - nodes_updated.retain(|k, _| self.tree.get(*k).is_some()); + let dirty_nodes = DirtyNodeStates::default(); + for (n, mask) in nodes_updated { + // remove any nodes that were created and then removed in the same mutations from the dirty nodes list + if self.tree.contains(n) { + for (m, p) in S::MASKS.iter().zip(S::PASSES.iter()) { + if mask.overlaps(m) { + dirty_nodes.insert(p.pass_id(), n); + } + } + } + } - nodes_updated + dirty_nodes } /// Update the state of the dom, after appling some mutations. This will keep the nodes in the dom up to date with their VNode counterparts. pub fn update_state( &mut self, - nodes_updated: FxHashMap, - ctx: AnyMap, - ) -> FxHashSet { - let (mut state_tree, node_tree) = self.split(); - S::update(&nodes_updated, &mut state_tree, &node_tree, &ctx) + nodes_updated: DirtyNodeStates, + ctx: SendAnyMap, + ) -> FxDashSet { + S::update(nodes_updated, &mut self.tree, ctx) } /// Find all nodes that are listening for an event, sorted by there height in the dom progressing starting at the bottom and progressing up. @@ -389,19 +394,6 @@ impl RealDom { self.tree.root() } - pub fn split(&mut self) -> (impl TreeView + '_, impl TreeView + '_) { - let raw = self as *mut Self; - // this is safe beacuse the treeview trait does not allow mutation of the position of elements, and within elements the access is disjoint. - ( - unsafe { &mut *raw } - .tree - .map(|n| &n.state, |n| &mut n.state), - unsafe { &mut *raw } - .tree - .map(|n| &n.node_data, |n| &mut n.node_data), - ) - } - fn clone_node(&mut self, node_id: NodeId) -> RealNodeId { let node = self.tree.get(node_id).unwrap(); let new_node = node.clone(); diff --git a/packages/native-core/src/state.rs b/packages/native-core/src/state.rs index 18d405ed7..09ae4333b 100644 --- a/packages/native-core/src/state.rs +++ b/packages/native-core/src/state.rs @@ -1,11 +1,10 @@ use std::cmp::Ordering; -use crate::node::NodeData; +use crate::node::Node; use crate::node_ref::{NodeMask, NodeView}; +use crate::passes::{resolve_passes, AnyPass, DirtyNodeStates}; use crate::tree::TreeView; -use crate::RealNodeId; -use anymap::AnyMap; -use rustc_hash::{FxHashMap, FxHashSet}; +use crate::{FxDashSet, RealNodeId, SendAnyMap}; /// Join two sorted iterators pub(crate) fn union_ordered_iter<'a>( @@ -204,20 +203,29 @@ pub trait NodeDepState { fn reduce<'a>( &mut self, node: NodeView, - siblings: ::ElementBorrowed<'a>, + node_state: ::ElementBorrowed<'a>, ctx: &Self::Ctx, ) -> bool; } /// Do not implement this trait. It is only meant to be derived and used through [crate::real_dom::RealDom]. -pub trait State: Default + Clone { +pub trait State: Default + Clone + 'static { #[doc(hidden)] - fn update<'a, T: TreeView, T2: TreeView>( - dirty: &FxHashMap, - state_tree: &'a mut T, - rdom: &'a T2, - ctx: &AnyMap, - ) -> FxHashSet; + const PASSES: &'static [AnyPass>]; + #[doc(hidden)] + const MASKS: &'static [NodeMask]; + + #[doc(hidden)] + fn update>>( + dirty: DirtyNodeStates, + tree: &mut T, + ctx: SendAnyMap, + ) -> FxDashSet { + let set = FxDashSet::default(); + let passes = Self::PASSES.iter().collect(); + resolve_passes(tree, dirty, passes, ctx); + set + } } impl ChildDepState for () { diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index 53f81ba54..0547fc3a9 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -73,6 +73,10 @@ pub trait TreeView: Sized { fn root(&self) -> NodeId; + fn contains(&self, id: NodeId) -> bool { + self.get(id).is_some() + } + fn get(&self, id: NodeId) -> Option<&T>; fn get_unchecked(&self, id: NodeId) -> &T { From 0d780fee611aeb5c2a81186c22d88797d9c8a2b5 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 3 Dec 2022 18:28:25 -0600 Subject: [PATCH 18/93] update to new mutations --- packages/native-core/src/real_dom.rs | 176 +++++++++++++-------------- 1 file changed, 83 insertions(+), 93 deletions(-) diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 48c1febcc..b9ca35c39 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -1,8 +1,8 @@ -use dioxus_core::{ElementId, Mutations}; +use dioxus_core::{ElementId, Mutations, TemplateNode}; use rustc_hash::{FxHashMap, FxHashSet}; use std::ops::{Index, IndexMut}; -use crate::node::{Node, NodeType, OwnedAttributeDiscription}; +use crate::node::{Node, NodeType, OwnedAttributeDiscription, OwnedAttributeValue}; use crate::node_ref::{AttributeMask, NodeMask}; use crate::passes::DirtyNodeStates; use crate::state::State; @@ -95,46 +95,89 @@ impl RealDom { self.tree.add_child(node_id, child_id); } + fn create_template_node( + &mut self, + node: &TemplateNode, + mutations_vec: &mut FxHashMap, + ) -> Option { + match node { + TemplateNode::Element { + tag, + namespace, + attrs, + children, + } => { + let node = Node::new(NodeType::Element { + tag: tag.to_string(), + namespace: namespace.map(|s| s.to_string()), + attributes: attrs + .iter() + .filter_map(|attr| match attr { + dioxus_core::TemplateAttribute::Static { + name, + value, + namespace, + } => Some(( + OwnedAttributeDiscription { + namespace: namespace.map(|s| s.to_string()), + name: name.to_string(), + volatile: false, + }, + OwnedAttributeValue::Text(value.to_string()), + )), + dioxus_core::TemplateAttribute::Dynamic(_) => None, + }) + .collect(), + listeners: FxHashSet::default(), + }); + let node_id = self.create_node(node); + for child in *children { + if let Some(child_id) = self.create_template_node(child, mutations_vec) { + self.add_child(node_id, child_id); + } + } + self.stack.push(node_id); + Some(node_id) + } + TemplateNode::Text(txt) => { + let node_id = self.create_node(Node::new(NodeType::Text { + text: txt.to_string(), + })); + self.stack.push(node_id); + Some(node_id) + } + _ => None, + } + } + /// Updates the dom with some mutations and return a set of nodes that were updated. Pass the dirty nodes to update_state. pub fn apply_mutations(&mut self, mutations_vec: Vec) -> DirtyNodeStates { let mut nodes_updated: FxHashMap = FxHashMap::default(); for mutations in mutations_vec { + for template in mutations.templates { + let mut template_root_ids = Vec::new(); + for root in template.roots { + if let Some(id) = self.create_template_node(root, &mut nodes_updated) { + template_root_ids.push(id); + } + } + self.templates + .insert(template.name.to_string(), template_root_ids); + } for e in mutations.edits { use dioxus_core::Mutation::*; match e { - AppendChildren { m } => { - let data = self.stack.split_off(m); - let (parent, children) = data.split_first().unwrap(); + AppendChildren { id, m } => { + let children = self.stack.split_off(m); + let parent = self.element_to_node_id(id); for child in children { - self.add_child(*parent, *child); - mark_dirty(*parent, NodeMask::ALL, &mut nodes_updated); + self.add_child(parent, child); + mark_dirty(parent, NodeMask::ALL, &mut nodes_updated); } } AssignId { path, id } => { self.set_element_id(self.load_child(path), id); } - CreateElement { name } => { - let node = Node::new(NodeType::Element { - tag: name.to_string(), - namespace: None, - attributes: FxHashMap::default(), - listeners: FxHashSet::default(), - }); - let id = self.create_node(node); - self.stack.push(id); - mark_dirty(id, NodeMask::ALL, &mut nodes_updated); - } - CreateElementNamespace { name, namespace } => { - let node = Node::new(NodeType::Element { - tag: name.to_string(), - namespace: Some(namespace.to_string()), - attributes: FxHashMap::default(), - listeners: FxHashSet::default(), - }); - let id = self.create_node(node); - self.stack.push(id); - mark_dirty(id, NodeMask::ALL, &mut nodes_updated); - } CreatePlaceholder { id } => { let node = Node::new(NodeType::Placeholder); let node_id = self.create_node(node); @@ -142,20 +185,6 @@ impl RealDom { self.stack.push(node_id); mark_dirty(node_id, NodeMask::ALL, &mut nodes_updated); } - CreateStaticPlaceholder => { - let node = Node::new(NodeType::Placeholder); - let id = self.create_node(node); - self.stack.push(id); - mark_dirty(id, NodeMask::ALL, &mut nodes_updated); - } - CreateStaticText { value } => { - let node = Node::new(NodeType::Text { - text: value.to_string(), - }); - let id = self.create_node(node); - self.stack.push(id); - mark_dirty(id, NodeMask::new().with_text(), &mut nodes_updated); - } CreateTextNode { value, id } => { let node = Node::new(NodeType::Text { text: value.to_string(), @@ -214,10 +243,6 @@ impl RealDom { mark_dirty(new, NodeMask::ALL, &mut nodes_updated); } } - SaveTemplate { name, m } => { - let template = self.stack.split_off(m); - self.templates.insert(name.to_string(), template); - } SetAttribute { name, value, @@ -243,26 +268,6 @@ impl RealDom { ); } } - SetStaticAttribute { name, value, ns } => { - let node_id = self.stack.last().unwrap(); - let node = self.tree.get_mut(*node_id).unwrap(); - if let NodeType::Element { attributes, .. } = &mut node.node_data.node_type - { - attributes.insert( - OwnedAttributeDiscription { - name: name.to_string(), - namespace: ns.map(|s| s.to_string()), - volatile: false, - }, - crate::node::OwnedAttributeValue::Text(value.to_string()), - ); - mark_dirty( - *node_id, - NodeMask::new_with_attrs(AttributeMask::single(name)), - &mut nodes_updated, - ); - } - } SetBoolAttribute { name, value, id } => { let node_id = self.element_to_node_id(id); let node = self.tree.get_mut(node_id).unwrap(); @@ -283,18 +288,6 @@ impl RealDom { ); } } - SetInnerText { value } => { - let node_id = *self.stack.last().unwrap(); - let node = self.tree.get_mut(node_id).unwrap(); - if let NodeType::Element { .. } = &mut node.node_data.node_type { - self.tree.remove_all_children(node_id); - let text_node = Node::new(NodeType::Text { - text: value.to_string(), - }); - let text_node_id = self.create_node(text_node); - self.add_child(node_id, text_node_id); - } - } SetText { value, id } => { let node_id = self.element_to_node_id(id); let node = self.tree.get_mut(node_id).unwrap(); @@ -303,42 +296,39 @@ impl RealDom { } mark_dirty(node_id, NodeMask::new().with_text(), &mut nodes_updated); } - NewEventListener { - event_name, - scope: _, - id, - } => { + NewEventListener { name, scope: _, id } => { let node_id = self.element_to_node_id(id); let node = self.tree.get_mut(node_id).unwrap(); if let NodeType::Element { listeners, .. } = &mut node.node_data.node_type { - match self.nodes_listening.get_mut(event_name) { + match self.nodes_listening.get_mut(name) { Some(hs) => { hs.insert(node_id); } None => { let mut hs = FxHashSet::default(); hs.insert(node_id); - self.nodes_listening.insert(event_name.to_string(), hs); + self.nodes_listening.insert(name.to_string(), hs); } } - listeners.insert(event_name.to_string()); + listeners.insert(name.to_string()); } } - RemoveEventListener { id, event } => { + RemoveEventListener { id, name } => { let node_id = self.element_to_node_id(id); let node = self.tree.get_mut(node_id).unwrap(); if let NodeType::Element { listeners, .. } = &mut node.node_data.node_type { - listeners.remove(event); + listeners.remove(name); } - self.nodes_listening - .get_mut(event) - .unwrap() - .remove(&node_id); + self.nodes_listening.get_mut(name).unwrap().remove(&node_id); } Remove { id } => { let node_id = self.element_to_node_id(id); self.tree.remove(node_id); } + PushRoot { id } => { + let node_id = self.element_to_node_id(id); + self.stack.push(node_id); + } } } } From 53094a7bbd31c0a87f4d2a896d213b6294216210 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 3 Dec 2022 18:28:32 -0600 Subject: [PATCH 19/93] work on implementing macro --- packages/native-core-macro/src/lib.rs | 428 +++++++++----------------- packages/native-core/src/passes.rs | 4 +- 2 files changed, 144 insertions(+), 288 deletions(-) diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index 852477fc8..d5bce0e92 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -154,77 +154,21 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { .map(|m| &m.mem.ident) .collect(); let member_types = state_strct.state_members.iter().map(|m| &m.mem.ty); - let resolve_members = state_strct + let impl_members = state_strct .state_members .iter() - .map(|m| state_strct.resolve(m)); + .map(|m| state_strct.impl_pass(m)); - let child_types = state_strct.child_states.iter().map(|s| &s.ty); - let child_members = state_strct.child_states.iter().map(|s| &s.ident); + // let child_types = state_strct.child_states.iter().map(|s| &s.ty); + // let child_members = state_strct.child_states.iter().map(|s| &s.ident); let gen = quote! { + #(#impl_members)* impl State for #type_name { - fn update<'a, T: dioxus_native_core::traversable::Traversable,T2: dioxus_native_core::traversable::Traversable>( - dirty: &[(dioxus_native_core::RealNodeId, dioxus_native_core::node_ref::NodeMask)], - state_tree: &'a mut T, - rdom: &'a T2, - ctx: &anymap::AnyMap, - ) -> rustc_hash::FxHashSet{ - #[derive(Clone, Copy)] - struct MembersDirty { - #(#members: bool, )* - } - - impl MembersDirty { - fn new() -> Self { - Self {#(#members: false),*} - } - - fn any(&self) -> bool { - #(self.#members || )* false - } - - fn union(self, other: Self) -> Self { - Self {#(#members: self.#members || other.#members),*} - } - } - - let mut dirty_elements = rustc_hash::FxHashSet::default(); - // the states of any elements that are dirty - let mut states: rustc_hash::FxHashMap = rustc_hash::FxHashMap::default(); - - for (id, mask) in dirty { - let members_dirty = MembersDirty { - #(#members: #member_types::NODE_MASK.overlaps(mask),)* - }; - if members_dirty.any(){ - if let Some(state) = states.get_mut(id){ - *state = state.union(members_dirty); - } - else{ - states.insert(*id, members_dirty); - } - } - dirty_elements.insert(*id); - } - - #( - #resolve_members; - )* - - #( - dirty_elements.extend( - <#child_types as dioxus_native_core::state::State>::update( - dirty, - &mut state_tree.map(|n| &n.#child_members, |n| &mut n.#child_members), - rdom, - ctx, - ) - ); - )* - - dirty_elements - } + const PASSES: &'static [AnyPass>] = &[ + AnyPass(#(&#member_types),*) + ]; + const MASKS: &'static [NodeMask] = &[#(#member_types::NODE_MASK),*]; } }; gen.into() @@ -246,7 +190,11 @@ struct Struct { impl Struct { fn new(name: Ident, fields: &[&Field]) -> Self { - let members = fields.iter().filter_map(|f| Member::parse(f)).collect(); + let members = fields + .iter() + .enumerate() + .filter_map(|(i, f)| Member::parse(f, 1 << i)) + .collect(); Self { name, members } } } @@ -260,7 +208,7 @@ impl<'a> StateStruct<'a> { /// Parse the state structure, and find a resolution order that will allow us to update the state for each node in after the state(s) it depends on have been resolved. fn parse(fields: &[&'a Field], strct: &'a Struct) -> Result { let mut parse_err = Ok(()); - let mut unordered_state_members: Vec<_> = strct + let mut state_members: Vec<_> = strct .members .iter() .zip(fields.iter()) @@ -273,41 +221,10 @@ impl<'a> StateStruct<'a> { }) .collect(); parse_err?; - - let mut state_members = Vec::new(); - // Keep adding members that have had all of there dependancies resolved until there are no more members left. - while !unordered_state_members.is_empty() { - let mut resolved = false; - for i in 0..unordered_state_members.len() { - let mem = &mut unordered_state_members[i]; - // if this member has all of its dependancies resolved other than itself, resolve it next. - if mem.dep_mems.iter().all(|(dep, resolved)| { - *resolved || (*dep == mem.mem && mem.dep_kind != DependencyKind::Node) - }) { - let mem = unordered_state_members.remove(i); - // mark any dependency that depends on this member as resolved - for member in unordered_state_members.iter_mut() { - for (dep, resolved) in &mut member.dep_mems { - *resolved |= *dep == mem.mem; - } - } - state_members.push(mem); - resolved = true; - break; - } - } - if !resolved { - return Err(Error::new( - strct.name.span(), - format!( - "{} has circular dependacy in {:?}", - strct.name, - unordered_state_members - .iter() - .map(|m| format!("{}", &m.mem.ident)) - .collect::>() - ), - )); + for i in 0..state_members.len() { + let deps = state_members[i].dep_mems.clone(); + for dep in deps { + state_members[dep as usize].dependant_mems.push(i as u64); } } @@ -351,175 +268,6 @@ impl<'a> StateStruct<'a> { } dependants } - - // Mark the states that depend on the current state as dirty - fn update_dependants(&self, mem: &Member) -> impl ToTokens { - let dep = self.get_depenadants(mem); - let update_child_dependants = if dep.child.is_empty() { - quote!() - } else { - let insert = dep.child.iter().map(|d|{ - if *d == mem { - quote! { - let seeking = dioxus_native_core::HeightOrdering::new(state_tree.height(parent_id).unwrap(), parent_id); - if let Err(idx) = resolution_order - .binary_search_by(|ordering| ordering.cmp(&seeking).reverse()){ - resolution_order.insert( - idx, - seeking, - ); - } - } - } else { - quote! {} - } - }); - let update: Vec<_> = dep - .child - .iter() - .map(|d| { - let ident = &d.ident; - quote! { - dirty.#ident = true; - } - }) - .collect(); - quote! { - if let Some(parent_id) = state_tree.parent(id) { - #(#insert)* - if let Some(dirty) = states.get_mut(&parent_id) { - #(#update)* - } - else { - let mut dirty = MembersDirty::new(); - #(#update)* - states.insert(parent_id, dirty); - } - } - } - }; - let node_dependants: Vec<_> = dep.node.iter().map(|d| &d.ident).collect(); - let update_node_dependants = quote! {#(members_dirty.#node_dependants = true;)*}; - let update_parent_dependants = if dep.parent.is_empty() { - quote!() - } else { - let insert = dep.parent.iter().map(|d| { - if *d == mem { - quote! { - let seeking = dioxus_native_core::HeightOrdering::new(state_tree.height(*child_id).unwrap(), *child_id); - if let Err(idx) = resolution_order - .binary_search(&seeking){ - resolution_order.insert( - idx, - seeking, - ); - } - } - } else { - quote! {} - } - }); - let update: Vec<_> = dep - .parent - .iter() - .map(|d| { - let ident = &d.ident; - quote! { - dirty.#ident = true; - } - }) - .collect(); - quote! { - for child_id in state_tree.children(id) { - #(#insert)* - if let Some(dirty) = states.get_mut(&child_id) { - #(#update)* - } - else { - let mut dirty = MembersDirty::new(); - #(#update)* - states.insert(*child_id, dirty); - } - } - } - }; - - quote! { - #update_node_dependants - #update_child_dependants - #update_parent_dependants - } - } - - // Generate code to resolve this state - fn resolve(&self, mem: &StateMember) -> impl ToTokens { - let reduce_member = mem.reduce_self(); - let update_dependant = self.update_dependants(mem.mem); - let member = &mem.mem.ident; - - match mem.dep_kind { - DependencyKind::Parent => { - quote! { - // resolve parent dependant state - let mut resolution_order = states.keys().copied().map(|id| dioxus_native_core::HeightOrdering::new(state_tree.height(id).unwrap(), id)).collect::>(); - resolution_order.sort(); - let mut i = 0; - while i < resolution_order.len(){ - let id = resolution_order[i].id; - let node = rdom.get(id).unwrap(); - let members_dirty = states.get_mut(&id).unwrap(); - let (current_state, parent) = state_tree.get_node_parent_mut(id); - let current_state = current_state.unwrap(); - if members_dirty.#member && #reduce_member { - dirty_elements.insert(id); - #update_dependant - } - i += 1; - } - } - } - DependencyKind::Child => { - quote! { - // resolve child dependant state - let mut resolution_order = states.keys().copied().map(|id| dioxus_native_core::HeightOrdering::new(state_tree.height(id).unwrap(), id)).collect::>(); - resolution_order.sort_by(|height_ordering1, height_ordering2| { - height_ordering1.cmp(&height_ordering2).reverse() - }); - let mut i = 0; - while i < resolution_order.len(){ - let id = resolution_order[i].id; - let node = rdom.get(id).unwrap(); - let members_dirty = states.get_mut(&id).unwrap(); - let (current_state, children) = state_tree.get_node_children_mut(id); - let current_state = current_state.unwrap(); - if members_dirty.#member && #reduce_member { - dirty_elements.insert(id); - #update_dependant - } - i += 1; - } - } - } - DependencyKind::Node => { - quote! { - // resolve node dependant state - let mut resolution_order = states.keys().copied().collect::>(); - let mut i = 0; - while i < resolution_order.len(){ - let id = resolution_order[i]; - let node = rdom.get(id).unwrap(); - let members_dirty = states.get_mut(&id).unwrap(); - let current_state = state_tree.get_mut(id).unwrap(); - if members_dirty.#member && #reduce_member { - dirty_elements.insert(id); - #update_dependant - } - i += 1; - } - } - } - } - } } fn try_parenthesized(input: ParseStream) -> Result { @@ -562,13 +310,15 @@ impl Parse for Dependency { /// The type of the member and the ident of the member #[derive(PartialEq, Debug)] struct Member { + id: u64, ty: Type, ident: Ident, } impl Member { - fn parse(field: &Field) -> Option { + fn parse(field: &Field, id: u64) -> Option { Some(Self { + id, ty: field.ty.clone(), ident: field.ident.as_ref()?.clone(), }) @@ -581,7 +331,9 @@ struct StateMember<'a> { // the kind of dependncies this state has dep_kind: DependencyKind, // the depenancy and if it is satified - dep_mems: Vec<(&'a Member, bool)>, + dep_mems: Vec, + // any members that depend on this member + dependant_mems: Vec, // the context this state requires ctx_ty: Option, } @@ -610,7 +362,7 @@ impl<'a> StateMember<'a> { .iter() .filter_map(|name| { if let Some(found) = parent.members.iter().find(|m| &m.ident == name) { - Some((found, false)) + Some((found.id, false)) } else { err = Err(Error::new( name.span(), @@ -624,6 +376,7 @@ impl<'a> StateMember<'a> { mem, dep_kind, dep_mems, + dependant_mems: Vec::new(), ctx_ty: dependency.ctx_ty, }) } @@ -638,8 +391,9 @@ impl<'a> StateMember<'a> { } /// generate code to call the resolve function for the state. This does not handle checking if resolving the state is necessary, or marking the states that depend on this state as dirty. - fn reduce_self(&self) -> quote::__private::TokenStream { + fn impl_pass(&self, parent_type: Type) -> quote::__private::TokenStream { let ident = &self.mem.ident; + let ty = &self.mem.ty; let get_ctx = if let Some(ctx_ty) = &self.ctx_ty { if ctx_ty == &parse_quote!(()) { quote! {&()} @@ -652,23 +406,125 @@ impl<'a> StateMember<'a> { }; let ty = &self.mem.ty; - let node_view = quote!(dioxus_native_core::node_ref::NodeView::new(node, #ty::NODE_MASK)); + let node_view = quote!(dioxus_native_core::node_ref::NodeView::new(unsafe{&*{&node.node_data as *const _}}, #ty::NODE_MASK)); let dep_idents = self.dep_mems.iter().map(|m| &m.0.ident); - match self.dep_kind { + let impl_specific = match self.dep_kind { DependencyKind::Node => { - quote!({ - current_state.#ident.reduce(#node_view, (#(¤t_state.#dep_idents,)*), #get_ctx) - }) + quote! { + impl NodePass<#parent_type> for #ty { + fn pass(&self, node: &mut T, ctx: &SendAnyMap) -> bool { + node.state.#ident.reduce(#node_view, (#(&node.state.#dep_idents,)*), #get_ctx) + } + } + } } DependencyKind::Child => { - quote!({ - current_state.#ident.reduce(#node_view, children.iter().map(|c| (#(&c.#dep_idents)*)), #get_ctx) - }) + let update = if self.dep_mems.iter().any(|id| id == self.mem.id) { + quote! { + if update { + PassReturn{ + progress: true, + mark_dirty: true, + } + } else { + PassReturn{ + progress: false, + mark_dirty: false, + } + } + } + } else { + quote! { + if update { + PassReturn{ + progress: false, + mark_dirty: true, + } + } else { + PassReturn{ + progress: false, + mark_dirty: false, + } + } + } + }; + quote!( + pub trait UpwardPass: Pass { + fn pass<'a>( + &self, + node: &mut T, + children: &mut dyn Iterator, + ctx: &SendAnyMap, + ) -> PassReturn { + let update = node.state.#ident.reduce(#node_view, children.map(|c| (#(&c.state.#dep_idents,)*)), #get_ctx); + #update + } + } + ) } DependencyKind::Parent => { - quote!({ - current_state.#ident.reduce(#node_view, parent.as_ref().map(|p| (#(&p.#dep_idents)*)), #get_ctx) - }) + let update = if self.dep_mems.iter().any(|id| id == self.mem.id) { + quote! { + if update { + PassReturn{ + progress: true, + mark_dirty: true, + } + } else { + PassReturn{ + progress: false, + mark_dirty: false, + } + } + } + } else { + quote! { + if update { + PassReturn{ + progress: false, + mark_dirty: true, + } + } else { + PassReturn{ + progress: false, + mark_dirty: false, + } + } + } + }; + quote!( + impl DownwardPass<#parent_type> for #ty { + fn pass(&self, node: &mut T, parent: Option<&mut T>, ctx: &SendAnyMap) -> PassReturn{ + let update = current_state.state.#ident.reduce(#node_view, parent.as_ref().map(|p| (#(&p.state.#dep_idents,)*)), #get_ctx); + #update + } + } + ) + } + }; + let pass_id = self.mem.id; + let depenancies = &self.dep_mems; + let dependants = &self.dependant_mems; + let mask = self + .dep_mems + .iter() + .map(|m| m.0.id) + .fold(self.mem.id, |a, b| a | b); + quote! { + #impl_specific + impl Pass for #ty { + fn pass_id(&self) -> PassId { + PassId(#pass_id) + } + fn dependancies(&self) -> &'static [PassId] { + &[#(PassId(#depenancies)),*] + } + fn dependants(&self) -> &'static [PassId] { + &[#(PassId(#dependants)),*] + } + fn mask(&self) -> MemberMask { + MemberMask(#mask) + } } } } diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index 86bb50537..26276c6f3 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -151,8 +151,8 @@ impl BitOr for MemberMask { } pub struct PassReturn { - progress: bool, - mark_dirty: bool, + pub progress: bool, + pub mark_dirty: bool, } pub trait Pass { From 29a61b1855dd5f096bb3604afcf6ed36fcc0d152 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 3 Dec 2022 19:35:37 -0600 Subject: [PATCH 20/93] make the macro compile --- packages/native-core-macro/src/lib.rs | 64 +- .../tests/called_minimally_on_build.rs | 435 ++++---- .../native-core-macro/tests/change_nodes.rs | 15 +- .../native-core-macro/tests/initial_build.rs | 102 +- .../tests/peristant_iterator.rs | 618 ++++++------ .../native-core-macro/tests/update_state.rs | 948 +++++++++--------- packages/native-core/src/lib.rs | 4 +- packages/native-core/src/real_dom.rs | 18 +- 8 files changed, 1092 insertions(+), 1112 deletions(-) diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index d5bce0e92..b2c259efa 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -148,16 +148,12 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { let strct = Struct::new(type_name.clone(), &fields); match StateStruct::parse(&fields, &strct) { Ok(state_strct) => { - let members: Vec<_> = state_strct - .state_members - .iter() - .map(|m| &m.mem.ident) - .collect(); - let member_types = state_strct.state_members.iter().map(|m| &m.mem.ty); + let member_types1 = state_strct.state_members.iter().map(|m| &m.mem.ty); + let member_types2 = state_strct.state_members.iter().map(|m| &m.mem.ty); let impl_members = state_strct .state_members .iter() - .map(|m| state_strct.impl_pass(m)); + .map(|m| m.impl_pass(state_strct.ty)); // let child_types = state_strct.child_states.iter().map(|s| &s.ty); // let child_members = state_strct.child_states.iter().map(|s| &s.ident); @@ -165,10 +161,10 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { let gen = quote! { #(#impl_members)* impl State for #type_name { - const PASSES: &'static [AnyPass>] = &[ - AnyPass(#(&#member_types),*) + const PASSES: &'static [dioxus_native_core::AnyPass>] = &[ + #(dioxus_native_core::AnyPass(&#member_types1)),* ]; - const MASKS: &'static [NodeMask] = &[#(#member_types::NODE_MASK),*]; + const MASKS: &'static [dioxus_native_core::NodeMask] = &[#(#member_types2::NODE_MASK),*]; } }; gen.into() @@ -177,12 +173,6 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { } } -struct Depenadants<'a> { - node: Vec<&'a Member>, - child: Vec<&'a Member>, - parent: Vec<&'a Member>, -} - struct Struct { name: Ident, members: Vec, @@ -201,7 +191,9 @@ impl Struct { struct StateStruct<'a> { state_members: Vec>, + #[allow(unused)] child_states: Vec<&'a Member>, + ty: &'a Ident, } impl<'a> StateStruct<'a> { @@ -222,7 +214,7 @@ impl<'a> StateStruct<'a> { .collect(); parse_err?; for i in 0..state_members.len() { - let deps = state_members[i].dep_mems.clone(); + let deps: Vec<_> = state_members[i].dep_mems.iter().map(|m| m.id).collect(); for dep in deps { state_members[dep as usize].dependant_mems.push(i as u64); } @@ -244,30 +236,11 @@ impl<'a> StateStruct<'a> { // members need to be sorted so that members are updated after the members they depend on Ok(Self { + ty: &strct.name, state_members, child_states: child_states.collect(), }) } - - fn get_depenadants(&self, mem: &Member) -> Depenadants { - let mut dependants = Depenadants { - node: Vec::new(), - child: Vec::new(), - parent: Vec::new(), - }; - for member in &self.state_members { - for (dep, _) in &member.dep_mems { - if *dep == mem { - match member.dep_kind { - DependencyKind::Node => dependants.node.push(member.mem), - DependencyKind::Child => dependants.child.push(member.mem), - DependencyKind::Parent => dependants.parent.push(member.mem), - } - } - } - } - dependants - } } fn try_parenthesized(input: ParseStream) -> Result { @@ -331,7 +304,7 @@ struct StateMember<'a> { // the kind of dependncies this state has dep_kind: DependencyKind, // the depenancy and if it is satified - dep_mems: Vec, + dep_mems: Vec<&'a Member>, // any members that depend on this member dependant_mems: Vec, // the context this state requires @@ -362,7 +335,7 @@ impl<'a> StateMember<'a> { .iter() .filter_map(|name| { if let Some(found) = parent.members.iter().find(|m| &m.ident == name) { - Some((found.id, false)) + Some(found) } else { err = Err(Error::new( name.span(), @@ -391,9 +364,8 @@ impl<'a> StateMember<'a> { } /// generate code to call the resolve function for the state. This does not handle checking if resolving the state is necessary, or marking the states that depend on this state as dirty. - fn impl_pass(&self, parent_type: Type) -> quote::__private::TokenStream { + fn impl_pass(&self, parent_type: &Ident) -> quote::__private::TokenStream { let ident = &self.mem.ident; - let ty = &self.mem.ty; let get_ctx = if let Some(ctx_ty) = &self.ctx_ty { if ctx_ty == &parse_quote!(()) { quote! {&()} @@ -407,7 +379,7 @@ impl<'a> StateMember<'a> { let ty = &self.mem.ty; let node_view = quote!(dioxus_native_core::node_ref::NodeView::new(unsafe{&*{&node.node_data as *const _}}, #ty::NODE_MASK)); - let dep_idents = self.dep_mems.iter().map(|m| &m.0.ident); + let dep_idents = self.dep_mems.iter().map(|m| &m.ident); let impl_specific = match self.dep_kind { DependencyKind::Node => { quote! { @@ -419,7 +391,7 @@ impl<'a> StateMember<'a> { } } DependencyKind::Child => { - let update = if self.dep_mems.iter().any(|id| id == self.mem.id) { + let update = if self.dep_mems.iter().any(|m| m.id == self.mem.id) { quote! { if update { PassReturn{ @@ -463,7 +435,7 @@ impl<'a> StateMember<'a> { ) } DependencyKind::Parent => { - let update = if self.dep_mems.iter().any(|id| id == self.mem.id) { + let update = if self.dep_mems.iter().any(|m| m.id == self.mem.id) { quote! { if update { PassReturn{ @@ -503,12 +475,12 @@ impl<'a> StateMember<'a> { } }; let pass_id = self.mem.id; - let depenancies = &self.dep_mems; + let depenancies = self.dep_mems.iter().map(|m| m.id); let dependants = &self.dependant_mems; let mask = self .dep_mems .iter() - .map(|m| m.0.id) + .map(|m| m.id) .fold(self.mem.id, |a, b| a | b); quote! { #impl_specific diff --git a/packages/native-core-macro/tests/called_minimally_on_build.rs b/packages/native-core-macro/tests/called_minimally_on_build.rs index 7063574aa..7935f9693 100644 --- a/packages/native-core-macro/tests/called_minimally_on_build.rs +++ b/packages/native-core-macro/tests/called_minimally_on_build.rs @@ -1,255 +1,246 @@ -use anymap::AnyMap; -use dioxus::prelude::*; -use dioxus_native_core::node_ref::*; -use dioxus_native_core::real_dom::*; -use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State}; -use dioxus_native_core_macro::State; +// use anymap::AnyMap; +// use dioxus::prelude::*; +// use dioxus_native_core::node_ref::*; +// use dioxus_native_core::real_dom::*; +// use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State}; +// use dioxus_native_core_macro::State; -macro_rules! dep { - ( child( $name:ty, $dep:ty ) ) => { - impl ChildDepState for $name { - type Ctx = (); - type DepState = $dep; - const NODE_MASK: NodeMask = NodeMask::ALL; - fn reduce<'a>( - &mut self, - _: NodeView, - _: impl Iterator, - _: &Self::Ctx, - ) -> bool - where - Self::DepState: 'a, - { - self.0 += 1; - true - } - } - }; +// macro_rules! dep { +// ( child( $name:ty, $dep:ty ) ) => { +// impl ChildDepState for $name { +// type Ctx = (); +// type DepState = $dep; +// const NODE_MASK: NodeMask = NodeMask::ALL; +// fn reduce<'a>( +// &mut self, +// _: NodeView, +// _: impl Iterator, +// _: &Self::Ctx, +// ) -> bool +// where +// Self::DepState: 'a, +// { +// self.0 += 1; +// true +// } +// } +// }; - ( parent( $name:ty, $dep:ty ) ) => { - impl ParentDepState for $name { - type Ctx = (); - type DepState = $dep; - const NODE_MASK: NodeMask = NodeMask::ALL; - fn reduce( - &mut self, - _: NodeView, - _: Option<&Self::DepState>, - _: &Self::Ctx, - ) -> bool { - self.0 += 1; - true - } - } - }; +// ( parent( $name:ty, $dep:ty ) ) => { +// impl ParentDepState for $name { +// type Ctx = (); +// type DepState = $dep; +// const NODE_MASK: NodeMask = NodeMask::ALL; +// fn reduce(&mut self, _: NodeView, _: Option<&Self::DepState>, _: &Self::Ctx) -> bool { +// self.0 += 1; +// true +// } +// } +// }; - ( node( $name:ty, ($($l:lifetime),*), $dep:ty ) ) => { - impl<$($l),*> NodeDepState<$dep> for $name { - type Ctx = (); - const NODE_MASK: NodeMask = NodeMask::ALL; - fn reduce( - &mut self, - _: NodeView, - _: $dep, - _: &Self::Ctx, - ) -> bool { - self.0 += 1; - true - } - } - }; -} +// ( node( $name:ty, $dep:ty ) ) => { +// impl NodeDepState for $name { +// type Ctx = (); +// type DepState<'a> = $dep; +// const NODE_MASK: NodeMask = NodeMask::ALL; +// fn reduce(&mut self, _: NodeView, _: $dep, _: &Self::Ctx) -> bool { +// self.0 += 1; +// true +// } +// } +// }; +// } -macro_rules! test_state{ - ( $s:ty, child: ( $( $child:ident ),* ), node: ( $( $node:ident ),* ), parent: ( $( $parent:ident ),* ) ) => { - #[test] - fn state_reduce_initally_called_minimally() { - #[allow(non_snake_case)] - fn Base(cx: Scope) -> Element { - render!(div { - div{ - div{ - p{} - } - p{ - "hello" - } - div{ - h1{} - } - p{ - "world" - } - } - }) - } +// macro_rules! test_state{ +// ( $s:ty, child: ( $( $child:ident ),* ), node: ( $( $node:ident ),* ), parent: ( $( $parent:ident ),* ) ) => { +// #[test] +// fn state_reduce_initally_called_minimally() { +// #[allow(non_snake_case)] +// fn Base(cx: Scope) -> Element { +// render!(div { +// div{ +// div{ +// p{} +// } +// p{ +// "hello" +// } +// div{ +// h1{} +// } +// p{ +// "world" +// } +// } +// }) +// } - let vdom = VirtualDom::new(Base); +// let vdom = VirtualDom::new(Base); - let mutations = vdom.create_vnodes(rsx! { - div { - div{ - div{ - p{} - } - p{ - "hello" - } - div{ - h1{} - } - p{ - "world" - } - } - } - }); +// let mutations = vdom.create_vnodes(rsx! { +// div { +// div{ +// div{ +// p{} +// } +// p{ +// "hello" +// } +// div{ +// h1{} +// } +// p{ +// "world" +// } +// } +// } +// }); - let mut dom: RealDom<$s> = RealDom::new(); +// let mut dom: RealDom<$s> = RealDom::new(); - let nodes_updated = dom.apply_mutations(vec![mutations]); - let _to_rerender = dom.update_state(nodes_updated, AnyMap::new()); +// let nodes_updated = dom.apply_mutations(vec![mutations]); +// let _to_rerender = dom.update_state(nodes_updated, AnyMap::new()); - dom.traverse_depth_first(|n| { - $( - assert_eq!(n.state.$child.0, 1); - )* - $( - assert_eq!(n.state.$node.0, 1); - )* - $( - assert_eq!(n.state.$parent.0, 1); - )* - }); - } - } -} +// dom.traverse_depth_first(|n| { +// $( +// assert_eq!(n.state.$child.0, 1); +// )* +// $( +// assert_eq!(n.state.$node.0, 1); +// )* +// $( +// assert_eq!(n.state.$parent.0, 1); +// )* +// }); +// } +// } +// } -mod node_depends_on_child_and_parent { - use super::*; - #[derive(Debug, Clone, Default, PartialEq)] - struct Node(i32); - dep!(node(Node, ('a, 'b), (&'a Child, &'b Parent))); +// mod node_depends_on_child_and_parent { +// use super::*; +// #[derive(Debug, Clone, Default, PartialEq)] +// struct Node(i32); +// dep!(node(Node, (&'a Child, &'a Parent))); - #[derive(Debug, Clone, Default, PartialEq)] - struct Child(i32); - dep!(child(Child, Child)); +// #[derive(Debug, Clone, Default, PartialEq)] +// struct Child(i32); +// dep!(child(Child, Child)); - #[derive(Debug, Clone, Default, PartialEq)] - struct Parent(i32); - dep!(parent(Parent, Parent)); +// #[derive(Debug, Clone, Default, PartialEq)] +// struct Parent(i32); +// dep!(parent(Parent, Parent)); - #[derive(Debug, Clone, Default, State)] - struct StateTester { - #[node_dep_state((child, parent))] - node: Node, - #[child_dep_state(child)] - child: Child, - #[parent_dep_state(parent)] - parent: Parent, - } +// #[derive(Debug, Clone, Default, State)] +// struct StateTester { +// #[node_dep_state((child, parent))] +// node: Node, +// #[child_dep_state(child)] +// child: Child, +// #[parent_dep_state(parent)] +// parent: Parent, +// } - test_state!(StateTester, child: (child), node: (node), parent: (parent)); -} +// test_state!(StateTester, child: (child), node: (node), parent: (parent)); +// } -mod child_depends_on_node_that_depends_on_parent { - use super::*; - #[derive(Debug, Clone, Default, PartialEq)] - struct Node(i32); - dep!(node(Node, ('a), (&'a Parent,))); +// mod child_depends_on_node_that_depends_on_parent { +// use super::*; +// #[derive(Debug, Clone, Default, PartialEq)] +// struct Node(i32); +// dep!(node(Node, (&'a Parent,))); - #[derive(Debug, Clone, Default, PartialEq)] - struct Child(i32); - dep!(child(Child, Node)); +// #[derive(Debug, Clone, Default, PartialEq)] +// struct Child(i32); +// dep!(child(Child, Node)); - #[derive(Debug, Clone, Default, PartialEq)] - struct Parent(i32); - dep!(parent(Parent, Parent)); +// #[derive(Debug, Clone, Default, PartialEq)] +// struct Parent(i32); +// dep!(parent(Parent, Parent)); - #[derive(Debug, Clone, Default, State)] - struct StateTester { - #[node_dep_state(parent)] - node: Node, - #[child_dep_state(node)] - child: Child, - #[parent_dep_state(parent)] - parent: Parent, - } +// #[derive(Debug, Clone, Default, State)] +// struct StateTester { +// #[node_dep_state(parent)] +// node: Node, +// #[child_dep_state(node)] +// child: Child, +// #[parent_dep_state(parent)] +// parent: Parent, +// } - test_state!(StateTester, child: (child), node: (node), parent: (parent)); -} +// test_state!(StateTester, child: (child), node: (node), parent: (parent)); +// } -mod parent_depends_on_node_that_depends_on_child { - use super::*; - #[derive(Debug, Clone, Default, PartialEq)] - struct Node(i32); - dep!(node(Node, ('a), (&'a Child,))); +// mod parent_depends_on_node_that_depends_on_child { +// use super::*; +// #[derive(Debug, Clone, Default, PartialEq)] +// struct Node(i32); +// dep!(node(Node, (&'a Child,))); - #[derive(Debug, Clone, Default, PartialEq)] - struct Child(i32); - dep!(child(Child, Child)); +// #[derive(Debug, Clone, Default, PartialEq)] +// struct Child(i32); +// dep!(child(Child, Child)); - #[derive(Debug, Clone, Default, PartialEq)] - struct Parent(i32); - dep!(parent(Parent, Node)); +// #[derive(Debug, Clone, Default, PartialEq)] +// struct Parent(i32); +// dep!(parent(Parent, Node)); - #[derive(Debug, Clone, Default, State)] - struct StateTester { - #[node_dep_state(child)] - node: Node, - #[child_dep_state(child)] - child: Child, - #[parent_dep_state(node)] - parent: Parent, - } +// #[derive(Debug, Clone, Default, State)] +// struct StateTester { +// #[node_dep_state(child)] +// node: Node, +// #[child_dep_state(child)] +// child: Child, +// #[parent_dep_state(node)] +// parent: Parent, +// } - test_state!(StateTester, child: (child), node: (node), parent: (parent)); -} +// test_state!(StateTester, child: (child), node: (node), parent: (parent)); +// } -mod node_depends_on_other_node_state { - use super::*; - #[derive(Debug, Clone, Default, PartialEq)] - struct Node1(i32); - dep!(node(Node1, ('a), (&'a Node2,))); +// mod node_depends_on_other_node_state { +// use super::*; +// #[derive(Debug, Clone, Default, PartialEq)] +// struct Node1(i32); +// dep!(node(Node1, (&'a Node2,))); - #[derive(Debug, Clone, Default, PartialEq)] - struct Node2(i32); - dep!(node(Node2, (), ())); +// #[derive(Debug, Clone, Default, PartialEq)] +// struct Node2(i32); +// dep!(node(Node2, ())); - #[derive(Debug, Clone, Default, State)] - struct StateTester { - #[node_dep_state((node2))] - node1: Node1, - #[node_dep_state()] - node2: Node2, - } +// #[derive(Debug, Clone, Default, State)] +// struct StateTester { +// #[node_dep_state((node2))] +// node1: Node1, +// #[node_dep_state()] +// node2: Node2, +// } - test_state!(StateTester, child: (), node: (node1, node2), parent: ()); -} +// test_state!(StateTester, child: (), node: (node1, node2), parent: ()); +// } -mod node_child_and_parent_state_depends_on_self { - use super::*; - #[derive(Debug, Clone, Default, PartialEq)] - struct Node(i32); - dep!(node(Node, (), ())); +// mod node_child_and_parent_state_depends_on_self { +// use super::*; +// #[derive(Debug, Clone, Default, PartialEq)] +// struct Node(i32); +// dep!(node(Node, ())); - #[derive(Debug, Clone, Default, PartialEq)] - struct Child(i32); - dep!(child(Child, Child)); +// #[derive(Debug, Clone, Default, PartialEq)] +// struct Child(i32); +// dep!(child(Child, Child)); - #[derive(Debug, Clone, Default, PartialEq)] - struct Parent(i32); - dep!(parent(Parent, Parent)); +// #[derive(Debug, Clone, Default, PartialEq)] +// struct Parent(i32); +// dep!(parent(Parent, Parent)); - #[derive(Debug, Clone, Default, State)] - struct StateTester { - #[node_dep_state()] - node: Node, - #[child_dep_state(child)] - child: Child, - #[parent_dep_state(parent)] - parent: Parent, - } +// #[derive(Debug, Clone, Default, State)] +// struct StateTester { +// #[node_dep_state()] +// node: Node, +// #[child_dep_state(child)] +// child: Child, +// #[parent_dep_state(parent)] +// parent: Parent, +// } - test_state!(StateTester, child: (child), node: (node), parent: (parent)); -} +// test_state!(StateTester, child: (child), node: (node), parent: (parent)); +// } diff --git a/packages/native-core-macro/tests/change_nodes.rs b/packages/native-core-macro/tests/change_nodes.rs index 2197cdbce..ccc103549 100644 --- a/packages/native-core-macro/tests/change_nodes.rs +++ b/packages/native-core-macro/tests/change_nodes.rs @@ -2,7 +2,8 @@ use dioxus::core::ElementId; use dioxus::prelude::*; use dioxus_native_core::real_dom::RealDom; use dioxus_native_core::state::State; -use dioxus_native_core::RealNodeId; +use dioxus_native_core::tree::*; +use dioxus_native_core::NodeId; use dioxus_native_core_macro::State; #[derive(State, Default, Clone)] @@ -34,13 +35,13 @@ fn remove_node() { let _to_update = dom.apply_mutations(vec![create]); - assert_eq!(dom[RealNodeId::ElementId(ElementId(0))].node_data.height, 0); - assert_eq!(dom[RealNodeId::UnaccessableId(0)].node_data.height, 1); + assert_eq!(dom.tree.height(NodeId(0)), Some(0)); + assert_eq!(dom.tree.height(NodeId(1)), Some(1)); dom.apply_mutations(vec![edit]); assert_eq!(dom.size(), 3); - assert_eq!(dom[RealNodeId::ElementId(ElementId(0))].node_data.height, 0); + assert_eq!(dom.tree.height(NodeId(0)), Some(0)); } #[test] @@ -68,11 +69,11 @@ fn add_node() { let _to_update = dom.apply_mutations(vec![create]); assert_eq!(dom.size(), 2); - assert_eq!(dom[RealNodeId::ElementId(ElementId(2))].node_data.height, 1); + assert_eq!(dom.tree.height(NodeId(2)), Some(1)); dom.apply_mutations(vec![update]); assert_eq!(dom.size(), 3); - assert_eq!(dom[RealNodeId::ElementId(ElementId(3))].node_data.height, 0); - assert_eq!(dom[RealNodeId::UnaccessableId(0)].node_data.height, 1); + assert_eq!(dom.tree.height(NodeId(3)), Some(0)); + assert_eq!(dom.tree.height(NodeId(0)), Some(1)); } diff --git a/packages/native-core-macro/tests/initial_build.rs b/packages/native-core-macro/tests/initial_build.rs index d9b35f37a..86605aca9 100644 --- a/packages/native-core-macro/tests/initial_build.rs +++ b/packages/native-core-macro/tests/initial_build.rs @@ -1,63 +1,63 @@ -use dioxus::core::ElementId; -use dioxus::prelude::*; -use dioxus_native_core::real_dom::RealDom; -use dioxus_native_core::state::State; -use dioxus_native_core::RealNodeId; -use dioxus_native_core_macro::State; +// use dioxus::core::ElementId; +// use dioxus::prelude::*; +// use dioxus_native_core::real_dom::RealDom; +// use dioxus_native_core::state::State; +// use dioxus_native_core::RealNodeId; +// use dioxus_native_core_macro::State; -#[derive(Default, Clone, State)] -struct Empty {} +// #[derive(Default, Clone, State)] +// struct Empty {} -#[test] -fn initial_build_simple() { - #[allow(non_snake_case)] - fn Base(cx: Scope) -> Element { - render!(div {}) - } +// #[test] +// fn initial_build_simple() { +// #[allow(non_snake_case)] +// fn Base(cx: Scope) -> Element { +// render!(div {}) +// } - let vdom = VirtualDom::new(Base); +// let vdom = VirtualDom::new(Base); - let mutations = vdom.create_vnodes(rsx! { - div{} - }); +// let mutations = vdom.create_vnodes(rsx! { +// div{} +// }); - let mut dom: RealDom = RealDom::new(); +// let mut dom: RealDom = RealDom::new(); - let _to_update = dom.apply_mutations(vec![mutations]); +// let _to_update = dom.apply_mutations(vec![mutations]); - assert_eq!(dom.size(), 2); - assert_eq!(dom[RealNodeId::ElementId(ElementId(2))].node_data.height, 1); -} +// assert_eq!(dom.size(), 2); +// assert_eq!(dom[RealNodeId::ElementId(ElementId(2))].node_data.height, 1); +// } -#[test] -fn initial_build_with_children() { - #[allow(non_snake_case)] - fn Base(cx: Scope) -> Element { - render!(div {}) - } +// #[test] +// fn initial_build_with_children() { +// #[allow(non_snake_case)] +// fn Base(cx: Scope) -> Element { +// render!(div {}) +// } - let vdom = VirtualDom::new(Base); +// let vdom = VirtualDom::new(Base); - let mutations = vdom.create_vnodes(rsx! { - div{ - div{ - "hello" - p{ - "world" - } - "hello world" - } - } - }); +// let mutations = vdom.create_vnodes(rsx! { +// div{ +// div{ +// "hello" +// p{ +// "world" +// } +// "hello world" +// } +// } +// }); - let mut dom: RealDom = RealDom::new(); +// let mut dom: RealDom = RealDom::new(); - let _to_update = dom.apply_mutations(vec![mutations]); - assert_eq!(dom.size(), 2); - assert_eq!(dom[RealNodeId::ElementId(ElementId(2))].node_data.height, 1); - assert_eq!(dom[RealNodeId::UnaccessableId(6)].node_data.height, 2); - assert_eq!(dom[RealNodeId::UnaccessableId(5)].node_data.height, 3); - assert_eq!(dom[RealNodeId::UnaccessableId(8)].node_data.height, 3); - assert_eq!(dom[RealNodeId::UnaccessableId(10)].node_data.height, 3); - assert_eq!(dom[RealNodeId::UnaccessableId(9)].node_data.height, 4); -} +// let _to_update = dom.apply_mutations(vec![mutations]); +// assert_eq!(dom.size(), 2); +// assert_eq!(dom[RealNodeId::ElementId(ElementId(2))].node_data.height, 1); +// assert_eq!(dom[RealNodeId::UnaccessableId(6)].node_data.height, 2); +// assert_eq!(dom[RealNodeId::UnaccessableId(5)].node_data.height, 3); +// assert_eq!(dom[RealNodeId::UnaccessableId(8)].node_data.height, 3); +// assert_eq!(dom[RealNodeId::UnaccessableId(10)].node_data.height, 3); +// assert_eq!(dom[RealNodeId::UnaccessableId(9)].node_data.height, 4); +// } diff --git a/packages/native-core-macro/tests/peristant_iterator.rs b/packages/native-core-macro/tests/peristant_iterator.rs index b999b503c..b1de9392e 100644 --- a/packages/native-core-macro/tests/peristant_iterator.rs +++ b/packages/native-core-macro/tests/peristant_iterator.rs @@ -1,333 +1,333 @@ -use dioxus::core_macro::rsx_without_templates; -use dioxus::prelude::*; -use dioxus_native_core::{ - real_dom::{NodeType, RealDom}, - state::State, - utils::PersistantElementIter, -}; -use dioxus_native_core_macro::State; +// use dioxus::core_macro::rsx_without_templates; +// use dioxus::prelude::*; +// use dioxus_native_core::{ +// real_dom::{NodeType, RealDom}, +// state::State, +// utils::PersistantElementIter, +// }; +// use dioxus_native_core_macro::State; -#[derive(State, Default, Clone)] -struct Empty {} +// #[derive(State, Default, Clone)] +// struct Empty {} -#[test] -#[allow(unused_variables)] -fn traverse() { - #[allow(non_snake_case)] - fn Base(cx: Scope) -> Element { - render!(div {}) - } - let vdom = VirtualDom::new(Base); - let mutations = vdom.create_vnodes(rsx! { - div{ - div{ - "hello" - p{ - "world" - } - "hello world" - } - } - }); +// #[test] +// #[allow(unused_variables)] +// fn traverse() { +// #[allow(non_snake_case)] +// fn Base(cx: Scope) -> Element { +// render!(div {}) +// } +// let vdom = VirtualDom::new(Base); +// let mutations = vdom.create_vnodes(rsx! { +// div{ +// div{ +// "hello" +// p{ +// "world" +// } +// "hello world" +// } +// } +// }); - let mut rdom: RealDom = RealDom::new(); +// let mut rdom: RealDom = RealDom::new(); - let _to_update = rdom.apply_mutations(vec![mutations]); +// let _to_update = rdom.apply_mutations(vec![mutations]); - let mut iter = PersistantElementIter::new(); - let div_tag = "div".to_string(); - assert!(matches!( - &rdom[iter.next(&rdom).id()].node_data.node_type, - NodeType::Element { tag: div_tag, .. } - )); - assert!(matches!( - &rdom[iter.next(&rdom).id()].node_data.node_type, - NodeType::Element { tag: div_tag, .. } - )); - let text1 = "hello".to_string(); - assert!(matches!( - &rdom[iter.next(&rdom).id()].node_data.node_type, - NodeType::Text { text: text1, .. } - )); - let p_tag = "p".to_string(); - assert!(matches!( - &rdom[iter.next(&rdom).id()].node_data.node_type, - NodeType::Element { tag: p_tag, .. } - )); - let text2 = "world".to_string(); - assert!(matches!( - &rdom[iter.next(&rdom).id()].node_data.node_type, - NodeType::Text { text: text2, .. } - )); - let text3 = "hello world".to_string(); - assert!(matches!( - &rdom[iter.next(&rdom).id()].node_data.node_type, - NodeType::Text { text: text3, .. } - )); - assert!(matches!( - &rdom[iter.next(&rdom).id()].node_data.node_type, - NodeType::Element { tag: div_tag, .. } - )); +// let mut iter = PersistantElementIter::new(); +// let div_tag = "div".to_string(); +// assert!(matches!( +// &rdom[iter.next(&rdom).id()].node_data.node_type, +// NodeType::Element { tag: div_tag, .. } +// )); +// assert!(matches!( +// &rdom[iter.next(&rdom).id()].node_data.node_type, +// NodeType::Element { tag: div_tag, .. } +// )); +// let text1 = "hello".to_string(); +// assert!(matches!( +// &rdom[iter.next(&rdom).id()].node_data.node_type, +// NodeType::Text { text: text1, .. } +// )); +// let p_tag = "p".to_string(); +// assert!(matches!( +// &rdom[iter.next(&rdom).id()].node_data.node_type, +// NodeType::Element { tag: p_tag, .. } +// )); +// let text2 = "world".to_string(); +// assert!(matches!( +// &rdom[iter.next(&rdom).id()].node_data.node_type, +// NodeType::Text { text: text2, .. } +// )); +// let text3 = "hello world".to_string(); +// assert!(matches!( +// &rdom[iter.next(&rdom).id()].node_data.node_type, +// NodeType::Text { text: text3, .. } +// )); +// assert!(matches!( +// &rdom[iter.next(&rdom).id()].node_data.node_type, +// NodeType::Element { tag: div_tag, .. } +// )); - assert!(matches!( - &rdom[iter.prev(&rdom).id()].node_data.node_type, - NodeType::Text { text: text3, .. } - )); - assert!(matches!( - &rdom[iter.prev(&rdom).id()].node_data.node_type, - NodeType::Text { text: text2, .. } - )); - assert!(matches!( - &rdom[iter.prev(&rdom).id()].node_data.node_type, - NodeType::Element { tag: p_tag, .. } - )); - assert!(matches!( - &rdom[iter.prev(&rdom).id()].node_data.node_type, - NodeType::Text { text: text1, .. } - )); - assert!(matches!( - &rdom[iter.prev(&rdom).id()].node_data.node_type, - NodeType::Element { tag: div_tag, .. } - )); - assert!(matches!( - &rdom[iter.prev(&rdom).id()].node_data.node_type, - NodeType::Element { tag: div_tag, .. } - )); - assert!(matches!( - &rdom[iter.prev(&rdom).id()].node_data.node_type, - NodeType::Element { tag: div_tag, .. } - )); - assert!(matches!( - &rdom[iter.prev(&rdom).id()].node_data.node_type, - NodeType::Text { text: text3, .. } - )); -} +// assert!(matches!( +// &rdom[iter.prev(&rdom).id()].node_data.node_type, +// NodeType::Text { text: text3, .. } +// )); +// assert!(matches!( +// &rdom[iter.prev(&rdom).id()].node_data.node_type, +// NodeType::Text { text: text2, .. } +// )); +// assert!(matches!( +// &rdom[iter.prev(&rdom).id()].node_data.node_type, +// NodeType::Element { tag: p_tag, .. } +// )); +// assert!(matches!( +// &rdom[iter.prev(&rdom).id()].node_data.node_type, +// NodeType::Text { text: text1, .. } +// )); +// assert!(matches!( +// &rdom[iter.prev(&rdom).id()].node_data.node_type, +// NodeType::Element { tag: div_tag, .. } +// )); +// assert!(matches!( +// &rdom[iter.prev(&rdom).id()].node_data.node_type, +// NodeType::Element { tag: div_tag, .. } +// )); +// assert!(matches!( +// &rdom[iter.prev(&rdom).id()].node_data.node_type, +// NodeType::Element { tag: div_tag, .. } +// )); +// assert!(matches!( +// &rdom[iter.prev(&rdom).id()].node_data.node_type, +// NodeType::Text { text: text3, .. } +// )); +// } -#[test] -#[allow(unused_variables)] -fn persist_removes() { - #[allow(non_snake_case)] - fn Base(cx: Scope) -> Element { - render!(div {}) - } - let vdom = VirtualDom::new(Base); - let (build, update) = vdom.diff_lazynodes( - rsx_without_templates! { - div{ - p{ - key: "1", - "hello" - } - p{ - key: "2", - "world" - } - p{ - key: "3", - "hello world" - } - } - }, - rsx_without_templates! { - div{ - p{ - key: "1", - "hello" - } - p{ - key: "3", - "hello world" - } - } - }, - ); +// #[test] +// #[allow(unused_variables)] +// fn persist_removes() { +// #[allow(non_snake_case)] +// fn Base(cx: Scope) -> Element { +// render!(div {}) +// } +// let vdom = VirtualDom::new(Base); +// let (build, update) = vdom.diff_lazynodes( +// rsx_without_templates! { +// div{ +// p{ +// key: "1", +// "hello" +// } +// p{ +// key: "2", +// "world" +// } +// p{ +// key: "3", +// "hello world" +// } +// } +// }, +// rsx_without_templates! { +// div{ +// p{ +// key: "1", +// "hello" +// } +// p{ +// key: "3", +// "hello world" +// } +// } +// }, +// ); - let mut rdom: RealDom = RealDom::new(); +// let mut rdom: RealDom = RealDom::new(); - let _to_update = rdom.apply_mutations(vec![build]); +// let _to_update = rdom.apply_mutations(vec![build]); - // this will end on the node that is removed - let mut iter1 = PersistantElementIter::new(); - // this will end on the after node that is removed - let mut iter2 = PersistantElementIter::new(); - // div - iter1.next(&rdom).id(); - iter2.next(&rdom).id(); - // p - iter1.next(&rdom).id(); - iter2.next(&rdom).id(); - // "hello" - iter1.next(&rdom).id(); - iter2.next(&rdom).id(); - // p - iter1.next(&rdom).id(); - iter2.next(&rdom).id(); - // "world" - iter1.next(&rdom).id(); - iter2.next(&rdom).id(); - // p - iter2.next(&rdom).id(); - // "hello world" - iter2.next(&rdom).id(); +// // this will end on the node that is removed +// let mut iter1 = PersistantElementIter::new(); +// // this will end on the after node that is removed +// let mut iter2 = PersistantElementIter::new(); +// // div +// iter1.next(&rdom).id(); +// iter2.next(&rdom).id(); +// // p +// iter1.next(&rdom).id(); +// iter2.next(&rdom).id(); +// // "hello" +// iter1.next(&rdom).id(); +// iter2.next(&rdom).id(); +// // p +// iter1.next(&rdom).id(); +// iter2.next(&rdom).id(); +// // "world" +// iter1.next(&rdom).id(); +// iter2.next(&rdom).id(); +// // p +// iter2.next(&rdom).id(); +// // "hello world" +// iter2.next(&rdom).id(); - iter1.prune(&update, &rdom); - iter2.prune(&update, &rdom); - let _to_update = rdom.apply_mutations(vec![update]); +// iter1.prune(&update, &rdom); +// iter2.prune(&update, &rdom); +// let _to_update = rdom.apply_mutations(vec![update]); - let p_tag = "p".to_string(); - let idx = iter1.next(&rdom).id(); - assert!(matches!( - &rdom[idx].node_data.node_type, - NodeType::Element { tag: p_tag, .. } - )); - let text = "hello world".to_string(); - let idx = iter1.next(&rdom).id(); - assert!(matches!( - &rdom[idx].node_data.node_type, - NodeType::Text { text, .. } - )); - let div_tag = "div".to_string(); - let idx = iter2.next(&rdom).id(); - assert!(matches!( - &rdom[idx].node_data.node_type, - NodeType::Element { tag: div_tag, .. } - )); -} +// let p_tag = "p".to_string(); +// let idx = iter1.next(&rdom).id(); +// assert!(matches!( +// &rdom[idx].node_data.node_type, +// NodeType::Element { tag: p_tag, .. } +// )); +// let text = "hello world".to_string(); +// let idx = iter1.next(&rdom).id(); +// assert!(matches!( +// &rdom[idx].node_data.node_type, +// NodeType::Text { text, .. } +// )); +// let div_tag = "div".to_string(); +// let idx = iter2.next(&rdom).id(); +// assert!(matches!( +// &rdom[idx].node_data.node_type, +// NodeType::Element { tag: div_tag, .. } +// )); +// } -#[test] -#[allow(unused_variables)] -fn persist_instertions_before() { - #[allow(non_snake_case)] - fn Base(cx: Scope) -> Element { - render!(div {}) - } - let vdom = VirtualDom::new(Base); - let (build, update) = vdom.diff_lazynodes( - rsx_without_templates! { - div{ - p{ - key: "1", - "hello" - } - p{ - key: "3", - "hello world" - } - } - }, - rsx_without_templates! { - div{ - p{ - key: "1", - "hello" - } - p{ - key: "2", - "world" - } - p{ - key: "3", - "hello world" - } - } - }, - ); +// #[test] +// #[allow(unused_variables)] +// fn persist_instertions_before() { +// #[allow(non_snake_case)] +// fn Base(cx: Scope) -> Element { +// render!(div {}) +// } +// let vdom = VirtualDom::new(Base); +// let (build, update) = vdom.diff_lazynodes( +// rsx_without_templates! { +// div{ +// p{ +// key: "1", +// "hello" +// } +// p{ +// key: "3", +// "hello world" +// } +// } +// }, +// rsx_without_templates! { +// div{ +// p{ +// key: "1", +// "hello" +// } +// p{ +// key: "2", +// "world" +// } +// p{ +// key: "3", +// "hello world" +// } +// } +// }, +// ); - let mut rdom: RealDom = RealDom::new(); +// let mut rdom: RealDom = RealDom::new(); - let _to_update = rdom.apply_mutations(vec![build]); +// let _to_update = rdom.apply_mutations(vec![build]); - let mut iter = PersistantElementIter::new(); - // div - iter.next(&rdom).id(); - // p - iter.next(&rdom).id(); - // "hello" - iter.next(&rdom).id(); - // p - iter.next(&rdom).id(); - // "hello world" - iter.next(&rdom).id(); +// let mut iter = PersistantElementIter::new(); +// // div +// iter.next(&rdom).id(); +// // p +// iter.next(&rdom).id(); +// // "hello" +// iter.next(&rdom).id(); +// // p +// iter.next(&rdom).id(); +// // "hello world" +// iter.next(&rdom).id(); - iter.prune(&update, &rdom); - let _to_update = rdom.apply_mutations(vec![update]); +// iter.prune(&update, &rdom); +// let _to_update = rdom.apply_mutations(vec![update]); - let p_tag = "div".to_string(); - let idx = iter.next(&rdom).id(); - assert!(matches!( - &rdom[idx].node_data.node_type, - NodeType::Element { tag: p_tag, .. } - )); -} +// let p_tag = "div".to_string(); +// let idx = iter.next(&rdom).id(); +// assert!(matches!( +// &rdom[idx].node_data.node_type, +// NodeType::Element { tag: p_tag, .. } +// )); +// } -#[test] -#[allow(unused_variables)] -fn persist_instertions_after() { - #[allow(non_snake_case)] - fn Base(cx: Scope) -> Element { - render!(div {}) - } - let vdom = VirtualDom::new(Base); - let (build, update) = vdom.diff_lazynodes( - rsx_without_templates! { - div{ - p{ - key: "1", - "hello" - } - p{ - key: "2", - "world" - } - } - }, - rsx_without_templates! { - div{ - p{ - key: "1", - "hello" - } - p{ - key: "2", - "world" - } - p{ - key: "3", - "hello world" - } - } - }, - ); +// #[test] +// #[allow(unused_variables)] +// fn persist_instertions_after() { +// #[allow(non_snake_case)] +// fn Base(cx: Scope) -> Element { +// render!(div {}) +// } +// let vdom = VirtualDom::new(Base); +// let (build, update) = vdom.diff_lazynodes( +// rsx_without_templates! { +// div{ +// p{ +// key: "1", +// "hello" +// } +// p{ +// key: "2", +// "world" +// } +// } +// }, +// rsx_without_templates! { +// div{ +// p{ +// key: "1", +// "hello" +// } +// p{ +// key: "2", +// "world" +// } +// p{ +// key: "3", +// "hello world" +// } +// } +// }, +// ); - let mut rdom: RealDom = RealDom::new(); +// let mut rdom: RealDom = RealDom::new(); - let _to_update = rdom.apply_mutations(vec![build]); +// let _to_update = rdom.apply_mutations(vec![build]); - let mut iter = PersistantElementIter::new(); - // div - iter.next(&rdom).id(); - // p - iter.next(&rdom).id(); - // "hello" - iter.next(&rdom).id(); - // p - iter.next(&rdom).id(); - // "world" - iter.next(&rdom).id(); +// let mut iter = PersistantElementIter::new(); +// // div +// iter.next(&rdom).id(); +// // p +// iter.next(&rdom).id(); +// // "hello" +// iter.next(&rdom).id(); +// // p +// iter.next(&rdom).id(); +// // "world" +// iter.next(&rdom).id(); - iter.prune(&update, &rdom); - let _to_update = rdom.apply_mutations(vec![update]); +// iter.prune(&update, &rdom); +// let _to_update = rdom.apply_mutations(vec![update]); - let p_tag = "p".to_string(); - let idx = iter.next(&rdom).id(); - assert!(matches!( - &rdom[idx].node_data.node_type, - NodeType::Element { tag: p_tag, .. } - )); - let text = "hello world".to_string(); - let idx = iter.next(&rdom).id(); - assert!(matches!( - &rdom[idx].node_data.node_type, - NodeType::Text { text, .. } - )); -} +// let p_tag = "p".to_string(); +// let idx = iter.next(&rdom).id(); +// assert!(matches!( +// &rdom[idx].node_data.node_type, +// NodeType::Element { tag: p_tag, .. } +// )); +// let text = "hello world".to_string(); +// let idx = iter.next(&rdom).id(); +// assert!(matches!( +// &rdom[idx].node_data.node_type, +// NodeType::Text { text, .. } +// )); +// } diff --git a/packages/native-core-macro/tests/update_state.rs b/packages/native-core-macro/tests/update_state.rs index 2123ee1f4..2e6a04dec 100644 --- a/packages/native-core-macro/tests/update_state.rs +++ b/packages/native-core-macro/tests/update_state.rs @@ -1,516 +1,516 @@ -use anymap::AnyMap; -use dioxus::core::ElementId; -use dioxus::core::{AttributeValue, DomEdit, Mutations}; -use dioxus::core_macro::rsx_without_templates; -use dioxus::prelude::*; -use dioxus_native_core::real_dom::*; -use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State}; -use dioxus_native_core::{node_ref::*, RealNodeId}; -use dioxus_native_core_macro::State; +// use anymap::AnyMap; +// use dioxus::core::ElementId; +// use dioxus::core::{AttributeValue, DomEdit, Mutations}; +// use dioxus::core_macro::rsx_without_templates; +// use dioxus::prelude::*; +// use dioxus_native_core::real_dom::*; +// use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State}; +// use dioxus_native_core::{node_ref::*, RealNodeId}; +// use dioxus_native_core_macro::State; -#[derive(Debug, Clone, Default, State)] -struct CallCounterStatePart1 { - #[child_dep_state(child_counter)] - child_counter: ChildDepCallCounter, -} +// #[derive(Debug, Clone, Default, State)] +// struct CallCounterStatePart1 { +// #[child_dep_state(child_counter)] +// child_counter: ChildDepCallCounter, +// } -#[derive(Debug, Clone, Default, State)] -struct CallCounterStatePart2 { - #[parent_dep_state(parent_counter)] - parent_counter: ParentDepCallCounter, -} +// #[derive(Debug, Clone, Default, State)] +// struct CallCounterStatePart2 { +// #[parent_dep_state(parent_counter)] +// parent_counter: ParentDepCallCounter, +// } -#[derive(Debug, Clone, Default, State)] -struct CallCounterStatePart3 { - #[node_dep_state()] - node_counter: NodeDepCallCounter, -} +// #[derive(Debug, Clone, Default, State)] +// struct CallCounterStatePart3 { +// #[node_dep_state()] +// node_counter: NodeDepCallCounter, +// } -#[derive(Debug, Clone, Default, State)] -struct CallCounterState { - #[child_dep_state(child_counter)] - child_counter: ChildDepCallCounter, - #[state] - part2: CallCounterStatePart2, - #[parent_dep_state(parent_counter)] - parent_counter: ParentDepCallCounter, - #[state] - part1: CallCounterStatePart1, - #[state] - part3: CallCounterStatePart3, - #[node_dep_state()] - node_counter: NodeDepCallCounter, -} +// #[derive(Debug, Clone, Default, State)] +// struct CallCounterState { +// #[child_dep_state(child_counter)] +// child_counter: ChildDepCallCounter, +// #[state] +// part2: CallCounterStatePart2, +// #[parent_dep_state(parent_counter)] +// parent_counter: ParentDepCallCounter, +// #[state] +// part1: CallCounterStatePart1, +// #[state] +// part3: CallCounterStatePart3, +// #[node_dep_state()] +// node_counter: NodeDepCallCounter, +// } -#[derive(Debug, Clone, Default)] -struct ChildDepCallCounter(u32); -impl ChildDepState for ChildDepCallCounter { - type Ctx = (); - type DepState = Self; - const NODE_MASK: NodeMask = NodeMask::ALL; - fn reduce<'a>( - &mut self, - _: NodeView, - _: impl Iterator, - _: &Self::Ctx, - ) -> bool - where - Self::DepState: 'a, - { - self.0 += 1; - true - } -} +// #[derive(Debug, Clone, Default)] +// struct ChildDepCallCounter(u32); +// impl ChildDepState for ChildDepCallCounter { +// type Ctx = (); +// type DepState = Self; +// const NODE_MASK: NodeMask = NodeMask::ALL; +// fn reduce<'a>( +// &mut self, +// _: NodeView, +// _: impl Iterator, +// _: &Self::Ctx, +// ) -> bool +// where +// Self::DepState: 'a, +// { +// self.0 += 1; +// true +// } +// } -#[derive(Debug, Clone, Default)] -struct ParentDepCallCounter(u32); -impl ParentDepState for ParentDepCallCounter { - type 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 ParentDepCallCounter(u32); +// impl ParentDepState for ParentDepCallCounter { +// type 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, _sibling: (), _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, _sibling: (), _ctx: &Self::Ctx) -> bool { +// self.0 += 1; +// true +// } +// } -#[allow(clippy::vec_box)] -#[derive(Debug, Clone, PartialEq, Default)] -struct BubbledUpStateTester(Option, Vec>); -impl ChildDepState for BubbledUpStateTester { - type Ctx = u32; - type DepState = Self; - const NODE_MASK: NodeMask = NodeMask::new().with_tag(); - fn reduce<'a>( - &mut self, - node: NodeView, - children: impl Iterator, - ctx: &Self::Ctx, - ) -> bool - where - Self::DepState: 'a, - { - assert_eq!(*ctx, 42); - *self = BubbledUpStateTester( - node.tag().map(|s| s.to_string()), - children.into_iter().map(|c| Box::new(c.clone())).collect(), - ); - true - } -} +// #[allow(clippy::vec_box)] +// #[derive(Debug, Clone, PartialEq, Default)] +// struct BubbledUpStateTester(Option, Vec>); +// impl ChildDepState for BubbledUpStateTester { +// type Ctx = u32; +// type DepState = Self; +// const NODE_MASK: NodeMask = NodeMask::new().with_tag(); +// fn reduce<'a>( +// &mut self, +// node: NodeView, +// children: impl Iterator, +// ctx: &Self::Ctx, +// ) -> bool +// where +// Self::DepState: 'a, +// { +// assert_eq!(*ctx, 42); +// *self = BubbledUpStateTester( +// 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(Option, Option>); -impl ParentDepState for PushedDownStateTester { - type Ctx = u32; - type DepState = Self; - const NODE_MASK: NodeMask = NodeMask::new().with_tag(); - fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, ctx: &Self::Ctx) -> bool { - assert_eq!(*ctx, 42); - *self = PushedDownStateTester( - node.tag().map(|s| s.to_string()), - parent.map(|c| Box::new(c.clone())), - ); - true - } -} +// #[derive(Debug, Clone, PartialEq, Default)] +// struct PushedDownStateTester(Option, Option>); +// impl ParentDepState for PushedDownStateTester { +// type Ctx = u32; +// type DepState = Self; +// const NODE_MASK: NodeMask = NodeMask::new().with_tag(); +// fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, ctx: &Self::Ctx) -> bool { +// assert_eq!(*ctx, 42); +// *self = PushedDownStateTester( +// node.tag().map(|s| s.to_string()), +// parent.map(|c| Box::new(c.clone())), +// ); +// true +// } +// } -#[derive(Debug, Clone, PartialEq, Default)] -struct NodeStateTester(Option, Vec<(String, String)>); -impl NodeDepState<()> for NodeStateTester { - type Ctx = u32; - const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::All).with_tag(); - fn reduce(&mut self, node: NodeView, _sibling: (), ctx: &Self::Ctx) -> bool { - assert_eq!(*ctx, 42); - *self = NodeStateTester( - node.tag().map(|s| s.to_string()), - node.attributes() - .map(|iter| { - iter.map(|a| { - ( - a.attribute.name.to_string(), - a.value.as_text().unwrap().to_string(), - ) - }) - .collect() - }) - .unwrap_or_default(), - ); - true - } -} +// #[derive(Debug, Clone, PartialEq, Default)] +// struct NodeStateTester(Option, Vec<(String, String)>); +// impl NodeDepState<()> for NodeStateTester { +// type Ctx = u32; +// const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::All).with_tag(); +// fn reduce(&mut self, node: NodeView, _sibling: (), ctx: &Self::Ctx) -> bool { +// assert_eq!(*ctx, 42); +// *self = NodeStateTester( +// node.tag().map(|s| s.to_string()), +// node.attributes() +// .map(|iter| { +// iter.map(|a| { +// ( +// a.attribute.name.to_string(), +// a.value.as_text().unwrap().to_string(), +// ) +// }) +// .collect() +// }) +// .unwrap_or_default(), +// ); +// true +// } +// } -#[derive(State, Clone, Default, Debug)] -struct StateTester { - #[child_dep_state(bubbled, u32)] - bubbled: BubbledUpStateTester, - #[parent_dep_state(pushed, u32)] - pushed: PushedDownStateTester, - #[node_dep_state(NONE, u32)] - node: NodeStateTester, -} +// #[derive(State, Clone, Default, Debug)] +// struct StateTester { +// #[child_dep_state(bubbled, u32)] +// bubbled: BubbledUpStateTester, +// #[parent_dep_state(pushed, u32)] +// pushed: PushedDownStateTester, +// #[node_dep_state(NONE, u32)] +// node: NodeStateTester, +// } -#[test] -fn state_initial() { - #[allow(non_snake_case)] - fn Base(cx: Scope) -> Element { - render!(div { - p{} - h1{} - }) - } +// #[test] +// fn state_initial() { +// #[allow(non_snake_case)] +// fn Base(cx: Scope) -> Element { +// render!(div { +// p{} +// h1{} +// }) +// } - let vdom = VirtualDom::new(Base); +// let vdom = VirtualDom::new(Base); - let mutations = vdom.create_vnodes(rsx! { - div { - p{ - color: "red" - } - h1{} - } - }); +// let mutations = vdom.create_vnodes(rsx! { +// div { +// p{ +// color: "red" +// } +// h1{} +// } +// }); - let mut dom: RealDom = RealDom::new(); +// let mut dom: RealDom = RealDom::new(); - let nodes_updated = dom.apply_mutations(vec![mutations]); - let mut ctx = AnyMap::new(); - ctx.insert(42u32); - let _to_rerender = dom.update_state(nodes_updated, ctx); +// let nodes_updated = dom.apply_mutations(vec![mutations]); +// let mut ctx = AnyMap::new(); +// ctx.insert(42u32); +// let _to_rerender = dom.update_state(nodes_updated, ctx); - let root_div = &dom[RealNodeId::ElementId(ElementId(2))]; - assert_eq!(root_div.state.bubbled.0, Some("div".to_string())); - assert_eq!( - root_div.state.bubbled.1, - vec![ - Box::new(BubbledUpStateTester(Some("p".to_string()), Vec::new())), - Box::new(BubbledUpStateTester(Some("h1".to_string()), Vec::new())) - ] - ); - assert_eq!(root_div.state.pushed.0, Some("div".to_string())); - assert_eq!( - root_div.state.pushed.1, - Some(Box::new(PushedDownStateTester( - Some("Root".to_string()), - None - ))) - ); - assert_eq!(root_div.state.node.0, Some("div".to_string())); - assert_eq!(root_div.state.node.1, vec![]); +// let root_div = &dom[RealNodeId::ElementId(ElementId(2))]; +// assert_eq!(root_div.state.bubbled.0, Some("div".to_string())); +// assert_eq!( +// root_div.state.bubbled.1, +// vec![ +// Box::new(BubbledUpStateTester(Some("p".to_string()), Vec::new())), +// Box::new(BubbledUpStateTester(Some("h1".to_string()), Vec::new())) +// ] +// ); +// assert_eq!(root_div.state.pushed.0, Some("div".to_string())); +// assert_eq!( +// root_div.state.pushed.1, +// Some(Box::new(PushedDownStateTester( +// Some("Root".to_string()), +// None +// ))) +// ); +// assert_eq!(root_div.state.node.0, Some("div".to_string())); +// assert_eq!(root_div.state.node.1, vec![]); - let child_p = &dom[RealNodeId::UnaccessableId(3)]; - 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.state.pushed.1, - Some(Box::new(PushedDownStateTester( - Some("div".to_string()), - Some(Box::new(PushedDownStateTester( - Some("Root".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_p = &dom[RealNodeId::UnaccessableId(3)]; +// 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.state.pushed.1, +// Some(Box::new(PushedDownStateTester( +// Some("div".to_string()), +// Some(Box::new(PushedDownStateTester( +// Some("Root".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[RealNodeId::UnaccessableId(4)]; - 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.state.pushed.1, - Some(Box::new(PushedDownStateTester( - Some("div".to_string()), - Some(Box::new(PushedDownStateTester( - Some("Root".to_string()), - None - ))) - ))) - ); - assert_eq!(child_h1.state.node.0, Some("h1".to_string())); - assert_eq!(child_h1.state.node.1, vec![]); -} +// let child_h1 = &dom[RealNodeId::UnaccessableId(4)]; +// 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.state.pushed.1, +// Some(Box::new(PushedDownStateTester( +// Some("div".to_string()), +// Some(Box::new(PushedDownStateTester( +// Some("Root".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_parent_called_minimally_on_update() { - #[allow(non_snake_case)] - fn Base(cx: Scope) -> Element { - render!(div { - width: "100%", - div{ - div{ - p{} - } - p{ - "hello" - } - div{ - h1{} - } - p{ - "world" - } - } - }) - } +// #[test] +// fn state_reduce_parent_called_minimally_on_update() { +// #[allow(non_snake_case)] +// fn Base(cx: Scope) -> Element { +// render!(div { +// width: "100%", +// div{ +// div{ +// p{} +// } +// p{ +// "hello" +// } +// div{ +// h1{} +// } +// p{ +// "world" +// } +// } +// }) +// } - let vdom = VirtualDom::new(Base); +// let vdom = VirtualDom::new(Base); - let mutations = vdom.create_vnodes(rsx_without_templates! { - div { - width: "100%", - div{ - div{ - p{} - } - p{ - "hello" - } - div{ - h1{} - } - p{ - "world" - } - } - } - }); +// let mutations = vdom.create_vnodes(rsx_without_templates! { +// div { +// width: "100%", +// div{ +// div{ +// p{} +// } +// p{ +// "hello" +// } +// div{ +// h1{} +// } +// p{ +// "world" +// } +// } +// } +// }); - let mut dom: RealDom = RealDom::new(); +// let mut dom: RealDom = RealDom::new(); - let nodes_updated = dom.apply_mutations(vec![mutations]); - let _to_rerender = dom.update_state(nodes_updated, AnyMap::new()); - let nodes_updated = dom.apply_mutations(vec![Mutations { - edits: vec![DomEdit::SetAttribute { - root: Some(1), - field: "width", - value: AttributeValue::Text("99%"), - ns: Some("style"), - }], - dirty_scopes: rustc_hash::FxHashSet::default(), - refs: Vec::new(), - }]); - let _to_rerender = dom.update_state(nodes_updated, AnyMap::new()); +// let nodes_updated = dom.apply_mutations(vec![mutations]); +// let _to_rerender = dom.update_state(nodes_updated, AnyMap::new()); +// let nodes_updated = dom.apply_mutations(vec![Mutations { +// edits: vec![DomEdit::SetAttribute { +// root: Some(1), +// field: "width", +// value: AttributeValue::Text("99%"), +// ns: Some("style"), +// }], +// dirty_scopes: rustc_hash::FxHashSet::default(), +// refs: Vec::new(), +// }]); +// let _to_rerender = dom.update_state(nodes_updated, AnyMap::new()); - dom.traverse_depth_first(|n| { - assert_eq!(n.state.part2.parent_counter.0, 2); - assert_eq!(n.state.parent_counter.0, 2); - }); -} +// dom.traverse_depth_first(|n| { +// assert_eq!(n.state.part2.parent_counter.0, 2); +// assert_eq!(n.state.parent_counter.0, 2); +// }); +// } -#[test] -fn state_reduce_child_called_minimally_on_update() { - #[allow(non_snake_case)] - fn Base(cx: Scope) -> Element { - cx.render(rsx_without_templates!(div { - div{ - div{ - p{ - width: "100%", - } - } - p{ - "hello" - } - div{ - h1{} - } - p{ - "world" - } - } - })) - } +// #[test] +// fn state_reduce_child_called_minimally_on_update() { +// #[allow(non_snake_case)] +// fn Base(cx: Scope) -> Element { +// cx.render(rsx_without_templates!(div { +// div{ +// div{ +// p{ +// width: "100%", +// } +// } +// p{ +// "hello" +// } +// div{ +// h1{} +// } +// p{ +// "world" +// } +// } +// })) +// } - let vdom = VirtualDom::new(Base); +// let vdom = VirtualDom::new(Base); - let mutations = vdom.create_vnodes(rsx_without_templates! { - div { - div{ - div{ - p{ - width: "100%", - } - } - p{ - "hello" - } - div{ - h1{} - } - p{ - "world" - } - } - } - }); +// let mutations = vdom.create_vnodes(rsx_without_templates! { +// div { +// div{ +// div{ +// p{ +// width: "100%", +// } +// } +// p{ +// "hello" +// } +// div{ +// h1{} +// } +// p{ +// "world" +// } +// } +// } +// }); - let mut dom: RealDom = RealDom::new(); +// let mut dom: RealDom = RealDom::new(); - let nodes_updated = dom.apply_mutations(vec![mutations]); - let _to_rerender = dom.update_state(nodes_updated, AnyMap::new()); - let nodes_updated = dom.apply_mutations(vec![Mutations { - edits: vec![DomEdit::SetAttribute { - root: Some(4), - field: "width", - value: AttributeValue::Text("99%"), - ns: Some("style"), - }], - dirty_scopes: rustc_hash::FxHashSet::default(), - refs: Vec::new(), - }]); - let _to_rerender = dom.update_state(nodes_updated, AnyMap::new()); +// let nodes_updated = dom.apply_mutations(vec![mutations]); +// let _to_rerender = dom.update_state(nodes_updated, AnyMap::new()); +// let nodes_updated = dom.apply_mutations(vec![Mutations { +// edits: vec![DomEdit::SetAttribute { +// root: Some(4), +// field: "width", +// value: AttributeValue::Text("99%"), +// ns: Some("style"), +// }], +// dirty_scopes: rustc_hash::FxHashSet::default(), +// refs: Vec::new(), +// }]); +// let _to_rerender = dom.update_state(nodes_updated, AnyMap::new()); - dom.traverse_depth_first(|n| { - assert_eq!( - n.state.part1.child_counter.0, - if let Some(RealNodeId::ElementId(ElementId(id))) = n.node_data.id { - if id > 4 { - 1 - } else { - 2 - } - } else { - panic!() - } - ); - assert_eq!( - n.state.child_counter.0, - if let Some(RealNodeId::ElementId(ElementId(id))) = n.node_data.id { - if id > 4 { - 1 - } else { - 2 - } - } else { - panic!() - } - ); - }); -} +// dom.traverse_depth_first(|n| { +// assert_eq!( +// n.state.part1.child_counter.0, +// if let Some(RealNodeId::ElementId(ElementId(id))) = n.node_data.id { +// if id > 4 { +// 1 +// } else { +// 2 +// } +// } else { +// panic!() +// } +// ); +// assert_eq!( +// n.state.child_counter.0, +// if let Some(RealNodeId::ElementId(ElementId(id))) = n.node_data.id { +// if id > 4 { +// 1 +// } else { +// 2 +// } +// } else { +// panic!() +// } +// ); +// }); +// } -#[derive(Debug, Clone, Default, State)] -struct UnorderedDependanciesState { - #[node_dep_state(c)] - b: BDepCallCounter, - #[node_dep_state()] - c: CDepCallCounter, - #[node_dep_state(b)] - a: ADepCallCounter, -} +// #[derive(Debug, Clone, Default, State)] +// struct UnorderedDependanciesState { +// #[node_dep_state(c)] +// b: BDepCallCounter, +// #[node_dep_state()] +// c: CDepCallCounter, +// #[node_dep_state(b)] +// a: ADepCallCounter, +// } -#[derive(Debug, Clone, Default, PartialEq)] -struct ADepCallCounter(usize, BDepCallCounter); -impl<'a> NodeDepState<(&'a BDepCallCounter,)> for ADepCallCounter { - type Ctx = (); - const NODE_MASK: NodeMask = NodeMask::NONE; - fn reduce( - &mut self, - _node: NodeView, - (sibling,): (&'a BDepCallCounter,), - _ctx: &Self::Ctx, - ) -> bool { - self.0 += 1; - self.1 = sibling.clone(); - true - } -} +// #[derive(Debug, Clone, Default, PartialEq)] +// struct ADepCallCounter(usize, BDepCallCounter); +// impl<'a> NodeDepState<(&'a BDepCallCounter,)> for ADepCallCounter { +// type Ctx = (); +// const NODE_MASK: NodeMask = NodeMask::NONE; +// fn reduce( +// &mut self, +// _node: NodeView, +// (sibling,): (&'a BDepCallCounter,), +// _ctx: &Self::Ctx, +// ) -> bool { +// self.0 += 1; +// self.1 = sibling.clone(); +// true +// } +// } -#[derive(Debug, Clone, Default, PartialEq)] -struct BDepCallCounter(usize, CDepCallCounter); -impl<'a> NodeDepState<(&'a CDepCallCounter,)> for BDepCallCounter { - type Ctx = (); - const NODE_MASK: NodeMask = NodeMask::NONE; - fn reduce( - &mut self, - _node: NodeView, - (sibling,): (&'a CDepCallCounter,), - _ctx: &Self::Ctx, - ) -> bool { - self.0 += 1; - self.1 = sibling.clone(); - true - } -} +// #[derive(Debug, Clone, Default, PartialEq)] +// struct BDepCallCounter(usize, CDepCallCounter); +// impl<'a> NodeDepState<(&'a CDepCallCounter,)> for BDepCallCounter { +// type Ctx = (); +// const NODE_MASK: NodeMask = NodeMask::NONE; +// fn reduce( +// &mut self, +// _node: NodeView, +// (sibling,): (&'a CDepCallCounter,), +// _ctx: &Self::Ctx, +// ) -> bool { +// self.0 += 1; +// self.1 = sibling.clone(); +// true +// } +// } -#[derive(Debug, Clone, Default, PartialEq)] -struct CDepCallCounter(usize); -impl NodeDepState<()> for CDepCallCounter { - type Ctx = (); - const NODE_MASK: NodeMask = NodeMask::ALL; - fn reduce(&mut self, _node: NodeView, _sibling: (), _ctx: &Self::Ctx) -> bool { - self.0 += 1; - true - } -} +// #[derive(Debug, Clone, Default, PartialEq)] +// struct CDepCallCounter(usize); +// impl NodeDepState<()> for CDepCallCounter { +// type Ctx = (); +// const NODE_MASK: NodeMask = NodeMask::ALL; +// fn reduce(&mut self, _node: NodeView, _sibling: (), _ctx: &Self::Ctx) -> bool { +// self.0 += 1; +// true +// } +// } -#[test] -fn dependancies_order_independant() { - #[allow(non_snake_case)] - fn Base(cx: Scope) -> Element { - render!(div { - width: "100%", - p{ - "hello" - } - }) - } +// #[test] +// fn dependancies_order_independant() { +// #[allow(non_snake_case)] +// fn Base(cx: Scope) -> Element { +// render!(div { +// width: "100%", +// p{ +// "hello" +// } +// }) +// } - let vdom = VirtualDom::new(Base); +// let vdom = VirtualDom::new(Base); - let mutations = vdom.create_vnodes(rsx! { - div { - width: "100%", - p{ - "hello" - } - } - }); +// let mutations = vdom.create_vnodes(rsx! { +// div { +// width: "100%", +// p{ +// "hello" +// } +// } +// }); - let mut dom: RealDom = RealDom::new(); +// let mut dom: RealDom = RealDom::new(); - let nodes_updated = dom.apply_mutations(vec![mutations]); - let _to_rerender = dom.update_state(nodes_updated, AnyMap::new()); +// let nodes_updated = dom.apply_mutations(vec![mutations]); +// let _to_rerender = dom.update_state(nodes_updated, AnyMap::new()); - let c = CDepCallCounter(1); - let b = BDepCallCounter(1, c.clone()); - let a = ADepCallCounter(1, b.clone()); - dom.traverse_depth_first(|n| { - assert_eq!(&n.state.a, &a); - assert_eq!(&n.state.b, &b); - assert_eq!(&n.state.c, &c); - }); -} +// let c = CDepCallCounter(1); +// let b = BDepCallCounter(1, c.clone()); +// let a = ADepCallCounter(1, b.clone()); +// dom.traverse_depth_first(|n| { +// assert_eq!(&n.state.a, &a); +// assert_eq!(&n.state.b, &b); +// assert_eq!(&n.state.c, &c); +// }); +// } -#[derive(Clone, Default, State)] -struct DependanciesStateTest { - #[node_dep_state(c)] - b: BDepCallCounter, - #[node_dep_state()] - c: CDepCallCounter, - #[node_dep_state(b)] - a: ADepCallCounter, - #[state] - child: UnorderedDependanciesState, -} +// #[derive(Clone, Default, State)] +// struct DependanciesStateTest { +// #[node_dep_state(c)] +// b: BDepCallCounter, +// #[node_dep_state()] +// c: CDepCallCounter, +// #[node_dep_state(b)] +// a: ADepCallCounter, +// #[state] +// child: UnorderedDependanciesState, +// } diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 14089c669..13033573e 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -1,7 +1,9 @@ use std::hash::BuildHasherDefault; +pub use node_ref::NodeMask; +pub use passes::AnyPass; use rustc_hash::FxHasher; -use tree::NodeId; +pub use tree::NodeId; pub mod layout_attributes; pub mod node; diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index b9ca35c39..dadb3909a 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -1,6 +1,6 @@ use dioxus_core::{ElementId, Mutations, TemplateNode}; use rustc_hash::{FxHashMap, FxHashSet}; -use std::ops::{Index, IndexMut}; +use std::ops::{Deref, DerefMut, Index, IndexMut}; use crate::node::{Node, NodeType, OwnedAttributeDiscription, OwnedAttributeValue}; use crate::node_ref::{AttributeMask, NodeMask}; @@ -26,7 +26,7 @@ fn mark_dirty( /// To get started implement [crate::state::ParentDepState], [crate::state::NodeDepState], or [crate::state::ChildDepState] and call [RealDom::apply_mutations] to update the dom and [RealDom::update_state] to update the state of the nodes. #[derive(Debug)] pub struct RealDom { - tree: Tree>, + pub tree: Tree>, /// a map from element id to real node id node_id_mapping: Vec>, nodes_listening: FxHashMap>, @@ -401,6 +401,20 @@ impl RealDom { } } +impl Deref for RealDom { + type Target = Tree>; + + fn deref(&self) -> &Self::Target { + &self.tree + } +} + +impl DerefMut for RealDom { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.tree + } +} + impl Index for RealDom { type Output = Node; From 0e17199e7a8f6209354f8111081e9a697ebb9bcd Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 3 Dec 2022 22:26:37 -0600 Subject: [PATCH 21/93] more progress on macro --- packages/native-core-macro/src/lib.rs | 84 +++-- .../tests/called_minimally_on_build.rs | 291 +++++++++--------- packages/native-core/src/lib.rs | 6 +- packages/native-core/src/passes.rs | 4 +- 4 files changed, 209 insertions(+), 176 deletions(-) diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index b2c259efa..a1dc397c3 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -3,7 +3,7 @@ extern crate proc_macro; mod sorted_slice; use proc_macro::TokenStream; -use quote::{quote, ToTokens}; +use quote::{quote, ToTokens, __private::Span}; use sorted_slice::StrSlice; use syn::parenthesized; use syn::parse::ParseBuffer; @@ -148,8 +148,17 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { let strct = Struct::new(type_name.clone(), &fields); match StateStruct::parse(&fields, &strct) { Ok(state_strct) => { - let member_types1 = state_strct.state_members.iter().map(|m| &m.mem.ty); - let member_types2 = state_strct.state_members.iter().map(|m| &m.mem.ty); + let passes = state_strct.state_members.iter().map(|m| { + let unit = &m.mem.unit_type; + match m.dep_kind { + DependencyKind::Node => quote! {dioxus_native_core::AnyPass::Node(&#unit)}, + DependencyKind::Child => quote! {dioxus_native_core::AnyPass::Upward(&#unit)}, + DependencyKind::Parent => { + quote! {dioxus_native_core::AnyPass::Downward(&#unit)} + } + } + }); + let member_types = state_strct.state_members.iter().map(|m| &m.mem.ty); let impl_members = state_strct .state_members .iter() @@ -162,9 +171,9 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { #(#impl_members)* impl State for #type_name { const PASSES: &'static [dioxus_native_core::AnyPass>] = &[ - #(dioxus_native_core::AnyPass(&#member_types1)),* + #(#passes),* ]; - const MASKS: &'static [dioxus_native_core::NodeMask] = &[#(#member_types2::NODE_MASK),*]; + const MASKS: &'static [dioxus_native_core::NodeMask] = &[#(#member_types::NODE_MASK),*]; } }; gen.into() @@ -183,7 +192,7 @@ impl Struct { let members = fields .iter() .enumerate() - .filter_map(|(i, f)| Member::parse(f, 1 << i)) + .filter_map(|(i, f)| Member::parse(f, i as u64)) .collect(); Self { name, members } } @@ -285,6 +294,7 @@ impl Parse for Dependency { struct Member { id: u64, ty: Type, + unit_type: Ident, ident: Ident, } @@ -293,6 +303,11 @@ impl Member { Some(Self { id, ty: field.ty.clone(), + unit_type: Ident::new( + ("_Unit".to_string() + field.ty.to_token_stream().to_string().as_str()).as_str(), + Span::call_site(), + ) + .into(), ident: field.ident.as_ref()?.clone(), }) } @@ -378,13 +393,14 @@ impl<'a> StateMember<'a> { }; let ty = &self.mem.ty; + let unit_type = &self.mem.unit_type; let node_view = quote!(dioxus_native_core::node_ref::NodeView::new(unsafe{&*{&node.node_data as *const _}}, #ty::NODE_MASK)); let dep_idents = self.dep_mems.iter().map(|m| &m.ident); let impl_specific = match self.dep_kind { DependencyKind::Node => { quote! { - impl NodePass<#parent_type> for #ty { - fn pass(&self, node: &mut T, ctx: &SendAnyMap) -> bool { + impl dioxus_native_core::NodePass> for #unit_type { + fn pass(&self, node: &mut dioxus_native_core::node::Node<#parent_type>, ctx: &dioxus_native_core::SendAnyMap) -> bool { node.state.#ident.reduce(#node_view, (#(&node.state.#dep_idents,)*), #get_ctx) } } @@ -394,12 +410,12 @@ impl<'a> StateMember<'a> { let update = if self.dep_mems.iter().any(|m| m.id == self.mem.id) { quote! { if update { - PassReturn{ + dioxus_native_core::PassReturn{ progress: true, mark_dirty: true, } } else { - PassReturn{ + dioxus_native_core::PassReturn{ progress: false, mark_dirty: false, } @@ -408,12 +424,12 @@ impl<'a> StateMember<'a> { } else { quote! { if update { - PassReturn{ + dioxus_native_core::PassReturn{ progress: false, mark_dirty: true, } } else { - PassReturn{ + dioxus_native_core::PassReturn{ progress: false, mark_dirty: false, } @@ -421,13 +437,13 @@ impl<'a> StateMember<'a> { } }; quote!( - pub trait UpwardPass: Pass { + impl dioxus_native_core::UpwardPass> for #unit_type{ fn pass<'a>( &self, - node: &mut T, - children: &mut dyn Iterator, - ctx: &SendAnyMap, - ) -> PassReturn { + node: &mut dioxus_native_core::node::Node<#parent_type>, + children: &mut dyn Iterator>, + ctx: &dioxus_native_core::SendAnyMap, + ) -> dioxus_native_core::PassReturn { let update = node.state.#ident.reduce(#node_view, children.map(|c| (#(&c.state.#dep_idents,)*)), #get_ctx); #update } @@ -438,12 +454,12 @@ impl<'a> StateMember<'a> { let update = if self.dep_mems.iter().any(|m| m.id == self.mem.id) { quote! { if update { - PassReturn{ + dioxus_native_core::PassReturn{ progress: true, mark_dirty: true, } } else { - PassReturn{ + dioxus_native_core::PassReturn{ progress: false, mark_dirty: false, } @@ -452,12 +468,12 @@ impl<'a> StateMember<'a> { } else { quote! { if update { - PassReturn{ + dioxus_native_core::PassReturn{ progress: false, mark_dirty: true, } } else { - PassReturn{ + dioxus_native_core::PassReturn{ progress: false, mark_dirty: false, } @@ -465,9 +481,9 @@ impl<'a> StateMember<'a> { } }; quote!( - impl DownwardPass<#parent_type> for #ty { - fn pass(&self, node: &mut T, parent: Option<&mut T>, ctx: &SendAnyMap) -> PassReturn{ - let update = current_state.state.#ident.reduce(#node_view, parent.as_ref().map(|p| (#(&p.state.#dep_idents,)*)), #get_ctx); + impl dioxus_native_core::DownwardPass> for #unit_type { + fn pass(&self, node: &mut dioxus_native_core::node::Node<#parent_type>, parent: Option<&mut dioxus_native_core::node::Node<#parent_type>>, ctx: &dioxus_native_core::SendAnyMap) -> dioxus_native_core::PassReturn{ + let update = node.state.#ident.reduce(#node_view, parent.as_ref().map(|p| (#(&p.state.#dep_idents,)*)), #get_ctx); #update } } @@ -483,19 +499,21 @@ impl<'a> StateMember<'a> { .map(|m| m.id) .fold(self.mem.id, |a, b| a | b); quote! { + #[derive(Clone, Copy)] + struct #unit_type; #impl_specific - impl Pass for #ty { - fn pass_id(&self) -> PassId { - PassId(#pass_id) + impl dioxus_native_core::Pass for #unit_type { + fn pass_id(&self) -> dioxus_native_core::PassId { + dioxus_native_core::PassId(#pass_id) } - fn dependancies(&self) -> &'static [PassId] { - &[#(PassId(#depenancies)),*] + fn dependancies(&self) -> &'static [dioxus_native_core::PassId] { + &[#(dioxus_native_core::PassId(#depenancies)),*] } - fn dependants(&self) -> &'static [PassId] { - &[#(PassId(#dependants)),*] + fn dependants(&self) -> &'static [dioxus_native_core::PassId] { + &[#(dioxus_native_core::PassId(#dependants)),*] } - fn mask(&self) -> MemberMask { - MemberMask(#mask) + fn mask(&self) -> dioxus_native_core::MemberMask { + dioxus_native_core::MemberMask(1 << #mask) } } } diff --git a/packages/native-core-macro/tests/called_minimally_on_build.rs b/packages/native-core-macro/tests/called_minimally_on_build.rs index 7935f9693..24cd446d4 100644 --- a/packages/native-core-macro/tests/called_minimally_on_build.rs +++ b/packages/native-core-macro/tests/called_minimally_on_build.rs @@ -1,161 +1,174 @@ -// use anymap::AnyMap; -// use dioxus::prelude::*; -// use dioxus_native_core::node_ref::*; -// use dioxus_native_core::real_dom::*; -// use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State}; -// use dioxus_native_core_macro::State; +use anymap::AnyMap; +use dioxus::prelude::*; +use dioxus_native_core::node_ref::*; +use dioxus_native_core::real_dom::*; +use dioxus_native_core::state::{ + ChildDepState, ElementBorrowable, NodeDepState, ParentDepState, State, +}; +use dioxus_native_core::tree::*; +use dioxus_native_core_macro::State; -// macro_rules! dep { -// ( child( $name:ty, $dep:ty ) ) => { -// impl ChildDepState for $name { -// type Ctx = (); -// type DepState = $dep; -// const NODE_MASK: NodeMask = NodeMask::ALL; -// fn reduce<'a>( -// &mut self, -// _: NodeView, -// _: impl Iterator, -// _: &Self::Ctx, -// ) -> bool -// where -// Self::DepState: 'a, -// { -// self.0 += 1; -// true -// } -// } -// }; +macro_rules! dep { + ( child( $name:ty, $dep:ty ) ) => { + impl ChildDepState for $name { + type Ctx = (); + type DepState = $dep; + const NODE_MASK: NodeMask = NodeMask::ALL; + fn reduce<'a>( + &mut self, + _: NodeView, + _: impl Iterator::ElementBorrowed<'a>>, + _: &Self::Ctx, + ) -> bool + where + Self::DepState: 'a, + { + self.0 += 1; + true + } + } + }; -// ( parent( $name:ty, $dep:ty ) ) => { -// impl ParentDepState for $name { -// type Ctx = (); -// type DepState = $dep; -// const NODE_MASK: NodeMask = NodeMask::ALL; -// fn reduce(&mut self, _: NodeView, _: Option<&Self::DepState>, _: &Self::Ctx) -> bool { -// self.0 += 1; -// true -// } -// } -// }; + ( parent( $name:ty, $dep:ty ) ) => { + impl ParentDepState for $name { + type Ctx = (); + type DepState = $dep; + const NODE_MASK: NodeMask = NodeMask::ALL; + fn reduce( + &mut self, + _: NodeView, + _: Option<::ElementBorrowed<'_>>, + _: &Self::Ctx, + ) -> bool { + self.0 += 1; + true + } + } + }; -// ( node( $name:ty, $dep:ty ) ) => { -// impl NodeDepState for $name { -// type Ctx = (); -// type DepState<'a> = $dep; -// const NODE_MASK: NodeMask = NodeMask::ALL; -// fn reduce(&mut self, _: NodeView, _: $dep, _: &Self::Ctx) -> bool { -// self.0 += 1; -// true -// } -// } -// }; -// } + ( node( $name:ty, $dep:ty ) ) => { + impl NodeDepState for $name { + type Ctx = (); + type DepState = $dep; + const NODE_MASK: NodeMask = NodeMask::ALL; + fn reduce( + &mut self, + _: NodeView, + _: ::ElementBorrowed<'_>, + _: &Self::Ctx, + ) -> bool { + self.0 += 1; + true + } + } + }; +} -// macro_rules! test_state{ -// ( $s:ty, child: ( $( $child:ident ),* ), node: ( $( $node:ident ),* ), parent: ( $( $parent:ident ),* ) ) => { -// #[test] -// fn state_reduce_initally_called_minimally() { -// #[allow(non_snake_case)] -// fn Base(cx: Scope) -> Element { -// render!(div { -// div{ -// div{ -// p{} -// } -// p{ -// "hello" -// } -// div{ -// h1{} -// } -// p{ -// "world" -// } -// } -// }) -// } +macro_rules! test_state{ + ( $s:ty, child: ( $( $child:ident ),* ), node: ( $( $node:ident ),* ), parent: ( $( $parent:ident ),* ) ) => { + #[test] + fn state_reduce_initally_called_minimally() { + #[allow(non_snake_case)] + fn Base(cx: Scope) -> Element { + render!(div { + div{ + div{ + p{} + } + p{ + "hello" + } + div{ + h1{} + } + p{ + "world" + } + } + }) + } -// let vdom = VirtualDom::new(Base); + let vdom = VirtualDom::new(Base); -// let mutations = vdom.create_vnodes(rsx! { -// div { -// div{ -// div{ -// p{} -// } -// p{ -// "hello" -// } -// div{ -// h1{} -// } -// p{ -// "world" -// } -// } -// } -// }); + let mutations = vdom.create_vnodes(rsx! { + div { + div{ + div{ + p{} + } + p{ + "hello" + } + div{ + h1{} + } + p{ + "world" + } + } + } + }); -// let mut dom: RealDom<$s> = RealDom::new(); + let mut dom: RealDom<$s> = RealDom::new(); -// let nodes_updated = dom.apply_mutations(vec![mutations]); -// let _to_rerender = dom.update_state(nodes_updated, AnyMap::new()); + let nodes_updated = dom.apply_mutations(vec![mutations]); + let _to_rerender = dom.update_state(nodes_updated, AnyMap::new()); -// dom.traverse_depth_first(|n| { -// $( -// assert_eq!(n.state.$child.0, 1); -// )* -// $( -// assert_eq!(n.state.$node.0, 1); -// )* -// $( -// assert_eq!(n.state.$parent.0, 1); -// )* -// }); -// } -// } -// } + dom.traverse_depth_first(|n| { + $( + assert_eq!(n.state.$child.0, 1); + )* + $( + assert_eq!(n.state.$node.0, 1); + )* + $( + assert_eq!(n.state.$parent.0, 1); + )* + }); + } + } +} -// mod node_depends_on_child_and_parent { -// use super::*; -// #[derive(Debug, Clone, Default, PartialEq)] -// struct Node(i32); -// dep!(node(Node, (&'a Child, &'a Parent))); +mod node_depends_on_child_and_parent { + use super::*; + #[derive(Debug, Clone, Default, PartialEq)] + struct Node(i32); + dep!(node(Node, (Child, Parent))); -// #[derive(Debug, Clone, Default, PartialEq)] -// struct Child(i32); -// dep!(child(Child, Child)); + #[derive(Debug, Clone, Default, PartialEq)] + struct Child(i32); + dep!(child(Child, (Child,))); -// #[derive(Debug, Clone, Default, PartialEq)] -// struct Parent(i32); -// dep!(parent(Parent, Parent)); + #[derive(Debug, Clone, Default, PartialEq)] + struct Parent(i32); + dep!(parent(Parent, (Parent,))); -// #[derive(Debug, Clone, Default, State)] -// struct StateTester { -// #[node_dep_state((child, parent))] -// node: Node, -// #[child_dep_state(child)] -// child: Child, -// #[parent_dep_state(parent)] -// parent: Parent, -// } + #[derive(Debug, Clone, Default, State)] + struct StateTester { + #[node_dep_state((child, parent))] + node: Node, + #[child_dep_state(child)] + child: Child, + #[parent_dep_state(parent)] + parent: Parent, + } -// test_state!(StateTester, child: (child), node: (node), parent: (parent)); -// } + // test_state!(StateTester, child: (child), node: (node), parent: (parent)); +} // mod child_depends_on_node_that_depends_on_parent { // use super::*; // #[derive(Debug, Clone, Default, PartialEq)] // struct Node(i32); -// dep!(node(Node, (&'a Parent,))); +// dep!(node(Node, (Parent,))); // #[derive(Debug, Clone, Default, PartialEq)] // struct Child(i32); -// dep!(child(Child, Node)); +// dep!(child(Child, (Node,))); // #[derive(Debug, Clone, Default, PartialEq)] // struct Parent(i32); -// dep!(parent(Parent, Parent)); +// dep!(parent(Parent, (Parent,))); // #[derive(Debug, Clone, Default, State)] // struct StateTester { @@ -174,15 +187,15 @@ // use super::*; // #[derive(Debug, Clone, Default, PartialEq)] // struct Node(i32); -// dep!(node(Node, (&'a Child,))); +// dep!(node(Node, (Child,))); // #[derive(Debug, Clone, Default, PartialEq)] // struct Child(i32); -// dep!(child(Child, Child)); +// dep!(child(Child, (Child,))); // #[derive(Debug, Clone, Default, PartialEq)] // struct Parent(i32); -// dep!(parent(Parent, Node)); +// dep!(parent(Parent, (Node,))); // #[derive(Debug, Clone, Default, State)] // struct StateTester { @@ -201,7 +214,7 @@ // use super::*; // #[derive(Debug, Clone, Default, PartialEq)] // struct Node1(i32); -// dep!(node(Node1, (&'a Node2,))); +// dep!(node(Node1, (Node2,))); // #[derive(Debug, Clone, Default, PartialEq)] // struct Node2(i32); @@ -226,11 +239,11 @@ // #[derive(Debug, Clone, Default, PartialEq)] // struct Child(i32); -// dep!(child(Child, Child)); +// dep!(child(Child, (Child,))); // #[derive(Debug, Clone, Default, PartialEq)] // struct Parent(i32); -// dep!(parent(Parent, Parent)); +// dep!(parent(Parent, (Parent,))); // #[derive(Debug, Clone, Default, State)] // struct StateTester { diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 13033573e..5db3a3609 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -1,7 +1,9 @@ use std::hash::BuildHasherDefault; pub use node_ref::NodeMask; -pub use passes::AnyPass; +pub use passes::{ + AnyPass, DownwardPass, MemberMask, NodePass, Pass, PassId, PassReturn, UpwardPass, +}; use rustc_hash::FxHasher; pub use tree::NodeId; @@ -18,7 +20,7 @@ pub mod utils; pub type RealNodeId = NodeId; pub(crate) type FxDashMap = dashmap::DashMap>; pub(crate) type FxDashSet = dashmap::DashSet>; -pub(crate) type SendAnyMap = anymap::Map; +pub type SendAnyMap = anymap::Map; /// Used in derived state macros #[derive(Eq, PartialEq)] diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index 26276c6f3..fadc8e71e 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -111,10 +111,10 @@ impl DirtyNodeStates { } #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, PartialOrd, Ord)] -pub struct PassId(u64); +pub struct PassId(pub u64); #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Default)] -pub struct MemberMask(u64); +pub struct MemberMask(pub u64); impl MemberMask { pub fn overlaps(&self, other: Self) -> bool { From d2047ed744ab703d958e381a5a70fb26a9184b05 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 3 Dec 2022 22:40:09 -0600 Subject: [PATCH 22/93] mark cloned nodes as dirty --- packages/native-core-macro/src/lib.rs | 3 - .../tests/called_minimally_on_build.rs | 47 +++---- .../native-core-macro/tests/change_nodes.rs | 124 +++++++++--------- packages/native-core/src/real_dom.rs | 11 +- 4 files changed, 94 insertions(+), 91 deletions(-) diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index a1dc397c3..6b9c1735b 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -164,9 +164,6 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { .iter() .map(|m| m.impl_pass(state_strct.ty)); - // let child_types = state_strct.child_states.iter().map(|s| &s.ty); - // let child_members = state_strct.child_states.iter().map(|s| &s.ident); - let gen = quote! { #(#impl_members)* impl State for #type_name { diff --git a/packages/native-core-macro/tests/called_minimally_on_build.rs b/packages/native-core-macro/tests/called_minimally_on_build.rs index 24cd446d4..978ad8f5f 100644 --- a/packages/native-core-macro/tests/called_minimally_on_build.rs +++ b/packages/native-core-macro/tests/called_minimally_on_build.rs @@ -6,6 +6,7 @@ use dioxus_native_core::state::{ ChildDepState, ElementBorrowable, NodeDepState, ParentDepState, State, }; use dioxus_native_core::tree::*; +use dioxus_native_core::SendAnyMap; use dioxus_native_core_macro::State; macro_rules! dep { @@ -112,7 +113,7 @@ macro_rules! test_state{ let mut dom: RealDom<$s> = RealDom::new(); let nodes_updated = dom.apply_mutations(vec![mutations]); - let _to_rerender = dom.update_state(nodes_updated, AnyMap::new()); + let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::new()); dom.traverse_depth_first(|n| { $( @@ -129,32 +130,32 @@ macro_rules! test_state{ } } -mod node_depends_on_child_and_parent { - use super::*; - #[derive(Debug, Clone, Default, PartialEq)] - struct Node(i32); - dep!(node(Node, (Child, Parent))); +// mod node_depends_on_child_and_parent { +// use super::*; +// #[derive(Debug, Clone, Default, PartialEq)] +// struct Node(i32); +// dep!(node(Node, (Child, Parent))); - #[derive(Debug, Clone, Default, PartialEq)] - struct Child(i32); - dep!(child(Child, (Child,))); +// #[derive(Debug, Clone, Default, PartialEq)] +// struct Child(i32); +// dep!(child(Child, (Child,))); - #[derive(Debug, Clone, Default, PartialEq)] - struct Parent(i32); - dep!(parent(Parent, (Parent,))); +// #[derive(Debug, Clone, Default, PartialEq)] +// struct Parent(i32); +// dep!(parent(Parent, (Parent,))); - #[derive(Debug, Clone, Default, State)] - struct StateTester { - #[node_dep_state((child, parent))] - node: Node, - #[child_dep_state(child)] - child: Child, - #[parent_dep_state(parent)] - parent: Parent, - } +// #[derive(Debug, Clone, Default, State)] +// struct StateTester { +// #[node_dep_state((child, parent))] +// node: Node, +// #[child_dep_state(child)] +// child: Child, +// #[parent_dep_state(parent)] +// parent: Parent, +// } - // test_state!(StateTester, child: (child), node: (node), parent: (parent)); -} +// test_state!(StateTester, child: (child), node: (node), parent: (parent)); +// } // mod child_depends_on_node_that_depends_on_parent { // use super::*; diff --git a/packages/native-core-macro/tests/change_nodes.rs b/packages/native-core-macro/tests/change_nodes.rs index ccc103549..af3cbefb8 100644 --- a/packages/native-core-macro/tests/change_nodes.rs +++ b/packages/native-core-macro/tests/change_nodes.rs @@ -1,79 +1,79 @@ -use dioxus::core::ElementId; -use dioxus::prelude::*; -use dioxus_native_core::real_dom::RealDom; -use dioxus_native_core::state::State; -use dioxus_native_core::tree::*; -use dioxus_native_core::NodeId; -use dioxus_native_core_macro::State; +// use dioxus::core::ElementId; +// use dioxus::prelude::*; +// use dioxus_native_core::real_dom::RealDom; +// use dioxus_native_core::state::State; +// use dioxus_native_core::tree::*; +// use dioxus_native_core::NodeId; +// use dioxus_native_core_macro::State; -#[derive(State, Default, Clone)] -struct Empty {} +// #[derive(State, Default, Clone)] +// struct Empty {} -#[test] -fn remove_node() { - #[allow(non_snake_case)] - fn Base(cx: Scope) -> Element { - render!(div {}) - } +// #[test] +// fn remove_node() { +// #[allow(non_snake_case)] +// fn Base(cx: Scope) -> Element { +// render!(div {}) +// } - let vdom = VirtualDom::new(Base); +// let vdom = VirtualDom::new(Base); - let mut dom: RealDom = RealDom::new(); - let (create, edit) = vdom.diff_lazynodes( - rsx! { - div{ - div{} - } - }, - rsx! { - div{} - }, - ); +// let mut dom: RealDom = RealDom::new(); +// let (create, edit) = vdom.diff_lazynodes( +// rsx! { +// div{ +// div{} +// } +// }, +// rsx! { +// div{} +// }, +// ); - println!("create: {:#?}", create); - println!("edit: {:#?}", edit); +// println!("create: {:#?}", create); +// println!("edit: {:#?}", edit); - let _to_update = dom.apply_mutations(vec![create]); +// let _to_update = dom.apply_mutations(vec![create]); - assert_eq!(dom.tree.height(NodeId(0)), Some(0)); - assert_eq!(dom.tree.height(NodeId(1)), Some(1)); +// assert_eq!(dom.tree.height(NodeId(0)), Some(0)); +// assert_eq!(dom.tree.height(NodeId(1)), Some(1)); - dom.apply_mutations(vec![edit]); +// dom.apply_mutations(vec![edit]); - assert_eq!(dom.size(), 3); - assert_eq!(dom.tree.height(NodeId(0)), Some(0)); -} +// assert_eq!(dom.size(), 3); +// assert_eq!(dom.tree.height(NodeId(0)), Some(0)); +// } -#[test] -fn add_node() { - #[allow(non_snake_case)] - fn Base(cx: Scope) -> Element { - render!(div {}) - } +// #[test] +// fn add_node() { +// #[allow(non_snake_case)] +// fn Base(cx: Scope) -> Element { +// render!(div {}) +// } - let vdom = VirtualDom::new(Base); +// let vdom = VirtualDom::new(Base); - let (create, update) = vdom.diff_lazynodes( - rsx! { - div{} - }, - rsx! { - div{ - p{} - } - }, - ); +// let (create, update) = vdom.diff_lazynodes( +// rsx! { +// div{} +// }, +// rsx! { +// div{ +// p{} +// } +// }, +// ); - let mut dom: RealDom = RealDom::new(); +// let mut dom: RealDom = RealDom::new(); - let _to_update = dom.apply_mutations(vec![create]); +// let _to_update = dom.apply_mutations(vec![create]); - assert_eq!(dom.size(), 2); - assert_eq!(dom.tree.height(NodeId(2)), Some(1)); +// assert_eq!(dom.size(), 2); +// assert_eq!(dom.tree.height(NodeId(2)), Some(1)); - dom.apply_mutations(vec![update]); +// dom.apply_mutations(vec![update]); - assert_eq!(dom.size(), 3); - assert_eq!(dom.tree.height(NodeId(3)), Some(0)); - assert_eq!(dom.tree.height(NodeId(0)), Some(1)); -} +// assert_eq!(dom.size(), 3); +// assert_eq!(dom.tree.height(NodeId(3)), Some(0)); +// assert_eq!(dom.tree.height(NodeId(0)), Some(1)); +// } diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index dadb3909a..fdc036993 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -206,7 +206,7 @@ impl RealDom { } LoadTemplate { name, index, id } => { let template_id = self.templates[name][index]; - let clone_id = self.clone_node(template_id); + let clone_id = self.clone_node(template_id, &mut nodes_updated); self.set_element_id(clone_id, id); } ReplaceWith { id, m } => { @@ -384,16 +384,21 @@ impl RealDom { self.tree.root() } - fn clone_node(&mut self, node_id: NodeId) -> RealNodeId { + fn clone_node( + &mut self, + node_id: NodeId, + nodes_updated: &mut FxHashMap, + ) -> RealNodeId { let node = self.tree.get(node_id).unwrap(); let new_node = node.clone(); let new_id = self.create_node(new_node); + mark_dirty(new_id, NodeMask::ALL, nodes_updated); let self_ptr = self as *mut Self; for child in self.tree.children_ids(node_id).unwrap() { unsafe { // this is safe because no node has itself as a child let self_mut = &mut *self_ptr; - let child_id = self_mut.clone_node(*child); + let child_id = self_mut.clone_node(*child, nodes_updated); self_mut.add_child(new_id, child_id); } } From 72184e65ef9d07564b5f8b420cdeeb8af85407ff Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 4 Dec 2022 19:43:05 -0600 Subject: [PATCH 23/93] update persistant_iterator utility --- ...ant_iterator.rs => persistent_iterator.rs} | 0 packages/native-core/src/real_dom.rs | 2 +- packages/native-core/src/utils.rs | 424 +++++++++--------- 3 files changed, 213 insertions(+), 213 deletions(-) rename packages/native-core-macro/tests/{peristant_iterator.rs => persistent_iterator.rs} (100%) diff --git a/packages/native-core-macro/tests/peristant_iterator.rs b/packages/native-core-macro/tests/persistent_iterator.rs similarity index 100% rename from packages/native-core-macro/tests/peristant_iterator.rs rename to packages/native-core-macro/tests/persistent_iterator.rs diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index fdc036993..5ef100082 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -62,7 +62,7 @@ impl RealDom { } } - fn element_to_node_id(&self, element_id: ElementId) -> RealNodeId { + pub fn element_to_node_id(&self, element_id: ElementId) -> RealNodeId { self.node_id_mapping.get(element_id.0).unwrap().unwrap() } diff --git a/packages/native-core/src/utils.rs b/packages/native-core/src/utils.rs index 0a5e7ebf1..70d016d92 100644 --- a/packages/native-core/src/utils.rs +++ b/packages/native-core/src/utils.rs @@ -1,225 +1,225 @@ -// use crate::{real_dom::RealDom, state::State, RealNodeId}; -// use dioxus_core::{ElementId, Mutations}; +use crate::{node::NodeType, real_dom::RealDom, state::State, tree::TreeView, NodeId, RealNodeId}; +use dioxus_core::{Mutation, Mutations}; -// pub enum ElementProduced { -// /// The iterator produced an element by progressing to the next node in a depth first order. -// Progressed(RealNodeId), -// /// The iterator reached the end of the tree and looped back to the root -// Looped(RealNodeId), -// } -// impl ElementProduced { -// pub fn id(&self) -> RealNodeId { -// match self { -// ElementProduced::Progressed(id) => *id, -// ElementProduced::Looped(id) => *id, -// } -// } -// } +pub enum ElementProduced { + /// The iterator produced an element by progressing to the next node in a depth first order. + Progressed(RealNodeId), + /// The iterator reached the end of the tree and looped back to the root + Looped(RealNodeId), +} +impl ElementProduced { + pub fn id(&self) -> RealNodeId { + match self { + ElementProduced::Progressed(id) => *id, + ElementProduced::Looped(id) => *id, + } + } +} -// #[derive(Debug)] -// enum NodePosition { -// AtNode, -// InChild(usize), -// } +#[derive(Debug)] +enum NodePosition { + AtNode, + InChild(usize), +} -// impl NodePosition { -// fn map(&self, mut f: impl FnMut(usize) -> usize) -> Self { -// match self { -// Self::AtNode => Self::AtNode, -// Self::InChild(i) => Self::InChild(f(*i)), -// } -// } +impl NodePosition { + fn map(&self, mut f: impl FnMut(usize) -> usize) -> Self { + match self { + Self::AtNode => Self::AtNode, + Self::InChild(i) => Self::InChild(f(*i)), + } + } -// fn get_or_insert(&mut self, child_idx: usize) -> usize { -// match self { -// Self::AtNode => { -// *self = Self::InChild(child_idx); -// child_idx -// } -// Self::InChild(i) => *i, -// } -// } -// } + fn get_or_insert(&mut self, child_idx: usize) -> usize { + match self { + Self::AtNode => { + *self = Self::InChild(child_idx); + child_idx + } + Self::InChild(i) => *i, + } + } +} -// /// Focus systems need a iterator that can persist through changes in the [dioxus_core::VirtualDom]. -// /// This iterator traverses the tree depth first. -// /// Iterate through it with [PersistantElementIter::next] [PersistantElementIter::prev], and update it with [PersistantElementIter::prune] (with data from [`dioxus_core::prelude::VirtualDom::work_with_deadline`]). -// /// The iterator loops around when it reaches the end or the beginning. -// pub struct PersistantElementIter { -// // stack of elements and fragments -// stack: smallvec::SmallVec<[(RealNodeId, NodePosition); 5]>, -// } +/// Focus systems need a iterator that can persist through changes in the [dioxus_core::VirtualDom]. +/// This iterator traverses the tree depth first. +/// Iterate through it with [PersistantElementIter::next] [PersistantElementIter::prev], and update it with [PersistantElementIter::prune] (with data from [`dioxus_core::prelude::VirtualDom::work_with_deadline`]). +/// The iterator loops around when it reaches the end or the beginning. +pub struct PersistantElementIter { + // stack of elements and fragments + stack: smallvec::SmallVec<[(RealNodeId, NodePosition); 5]>, +} -// impl Default for PersistantElementIter { -// fn default() -> Self { -// PersistantElementIter { -// stack: smallvec::smallvec![(RealNodeId::ElementId(ElementId(0)), NodePosition::AtNode)], -// } -// } -// } +impl Default for PersistantElementIter { + fn default() -> Self { + PersistantElementIter { + stack: smallvec::smallvec![(NodeId(0), NodePosition::AtNode)], + } + } +} -// impl PersistantElementIter { -// pub fn new() -> Self { -// Self::default() -// } +impl PersistantElementIter { + pub fn new() -> Self { + Self::default() + } -// /// remove stale element refreneces -// /// returns true if the focused element is removed -// pub fn prune(&mut self, mutations: &Mutations, rdom: &RealDom) -> bool { -// let mut changed = false; -// let ids_removed: Vec<_> = mutations -// .edits -// .iter() -// .filter_map(|e| { -// // nodes within templates will never be removed -// if let DomEdit::Remove { root } = e { -// let id = rdom.resolve_maybe_id(*root); -// Some(id) -// } else { -// None -// } -// }) -// .collect(); -// // if any element is removed in the chain, remove it and its children from the stack -// if let Some(r) = self -// .stack -// .iter() -// .position(|(el_id, _)| ids_removed.iter().any(|id| el_id == id)) -// { -// self.stack.truncate(r); -// changed = true; -// } -// // if a child is removed or inserted before or at the current element, update the child index -// for (el_id, child_idx) in self.stack.iter_mut() { -// if let NodePosition::InChild(child_idx) = child_idx { -// if let NodeType::Element { children, .. } = &rdom[*el_id].node_data.node_type { -// for m in &mutations.edits { -// match m { -// DomEdit::Remove { root } => { -// let id = rdom.resolve_maybe_id(*root); -// if children.iter().take(*child_idx + 1).any(|c| *c == id) { -// *child_idx -= 1; -// } -// } -// DomEdit::InsertBefore { root, nodes } => { -// let id = rdom.resolve_maybe_id(*root); -// let n = nodes.len(); -// if children.iter().take(*child_idx + 1).any(|c| *c == id) { -// *child_idx += n as usize; -// } -// } -// DomEdit::InsertAfter { root, nodes } => { -// let id = rdom.resolve_maybe_id(*root); -// let n = nodes.len(); -// if children.iter().take(*child_idx).any(|c| *c == id) { -// *child_idx += n as usize; -// } -// } -// _ => (), -// } -// } -// } -// } -// } -// changed -// } + /// remove stale element refreneces + /// returns true if the focused element is removed + pub fn prune(&mut self, mutations: &Mutations, rdom: &RealDom) -> bool { + let mut changed = false; + let ids_removed: Vec<_> = mutations + .edits + .iter() + .filter_map(|m| { + // nodes within templates will never be removedns + if let Mutation::Remove { id } = m { + Some(rdom.element_to_node_id(*id)) + } else { + None + } + }) + .collect(); + // if any element is removed in the chain, remove it and its children from the stack + if let Some(r) = self + .stack + .iter() + .position(|(el_id, _)| ids_removed.iter().any(|id| el_id == id)) + { + self.stack.truncate(r); + changed = true; + } + // if a child is removed or inserted before or at the current element, update the child index + for (el_id, child_idx) in self.stack.iter_mut() { + if let NodePosition::InChild(child_idx) = child_idx { + if let Some(children) = &rdom.tree.children_ids(*el_id) { + for m in &mutations.edits { + match m { + Mutation::Remove { id } => { + let id = rdom.element_to_node_id(*id); + if children.iter().take(*child_idx + 1).any(|c| *c == id) { + *child_idx -= 1; + } + } + Mutation::InsertBefore { id, m } => { + let id = rdom.element_to_node_id(*id); + if children.iter().take(*child_idx + 1).any(|c| *c == id) { + *child_idx += *m; + } + } + Mutation::InsertAfter { id, m } => { + let id = rdom.element_to_node_id(*id); + if children.iter().take(*child_idx).any(|c| *c == id) { + *child_idx += *m; + } + } + _ => (), + } + } + } + } + } + changed + } -// /// get the next element -// pub fn next(&mut self, rdom: &RealDom) -> ElementProduced { -// if self.stack.is_empty() { -// let id = RealNodeId::ElementId(ElementId(0)); -// let new = (id, NodePosition::AtNode); -// self.stack.push(new); -// ElementProduced::Looped(id) -// } else { -// let (last, o_child_idx) = self.stack.last_mut().unwrap(); -// let node = &rdom[*last]; -// match &node.node_data.node_type { -// NodeType::Element { children, .. } => { -// *o_child_idx = o_child_idx.map(|i| i + 1); -// // if we have children, go to the next child -// let child_idx = o_child_idx.get_or_insert(0); -// if child_idx >= children.len() { -// self.pop(); -// self.next(rdom) -// } else { -// let id = children[child_idx]; -// if let NodeType::Element { .. } = &rdom[id].node_data.node_type { -// self.stack.push((id, NodePosition::AtNode)); -// } -// ElementProduced::Progressed(id) -// } -// } + /// get the next element + pub fn next(&mut self, rdom: &RealDom) -> ElementProduced { + if self.stack.is_empty() { + let id = NodeId(0); + let new = (id, NodePosition::AtNode); + self.stack.push(new); + ElementProduced::Looped(id) + } else { + let (last, o_child_idx) = self.stack.last_mut().unwrap(); + let node = &rdom[*last]; + match &node.node_data.node_type { + NodeType::Element { .. } => { + let children = rdom.tree.children_ids(*last).unwrap(); + *o_child_idx = o_child_idx.map(|i| i + 1); + // if we have children, go to the next child + let child_idx = o_child_idx.get_or_insert(0); + if child_idx >= children.len() { + self.pop(); + self.next(rdom) + } else { + let id = children[child_idx]; + if let NodeType::Element { .. } = &rdom[id].node_data.node_type { + self.stack.push((id, NodePosition::AtNode)); + } + ElementProduced::Progressed(id) + } + } -// NodeType::Text { .. } | NodeType::Placeholder { .. } => { -// // we are at a leaf, so we are done -// ElementProduced::Progressed(self.pop()) -// } -// } -// } -// } + NodeType::Text { .. } | NodeType::Placeholder { .. } => { + // we are at a leaf, so we are done + ElementProduced::Progressed(self.pop()) + } + } + } + } -// /// get the previous element -// pub fn prev(&mut self, rdom: &RealDom) -> ElementProduced { -// // recursively add the last child element to the stack -// fn push_back( -// stack: &mut smallvec::SmallVec<[(RealNodeId, NodePosition); 5]>, -// new_node: RealNodeId, -// rdom: &RealDom, -// ) -> RealNodeId { -// match &rdom[new_node].node_data.node_type { -// NodeType::Element { children, .. } => { -// if children.is_empty() { -// new_node -// } else { -// stack.push((new_node, NodePosition::InChild(children.len() - 1))); -// push_back(stack, *children.last().unwrap(), rdom) -// } -// } -// _ => new_node, -// } -// } -// if self.stack.is_empty() { -// let new_node = RealNodeId::ElementId(ElementId(0)); -// ElementProduced::Looped(push_back(&mut self.stack, new_node, rdom)) -// } else { -// let (last, o_child_idx) = self.stack.last_mut().unwrap(); -// let node = &rdom[*last]; -// match &node.node_data.node_type { -// NodeType::Element { children, .. } => { -// // if we have children, go to the next child -// if let NodePosition::InChild(0) = o_child_idx { -// ElementProduced::Progressed(self.pop()) -// } else { -// *o_child_idx = o_child_idx.map(|i| i - 1); -// if let NodePosition::InChild(child_idx) = o_child_idx { -// if *child_idx >= children.len() || children.is_empty() { -// self.pop(); -// self.prev(rdom) -// } else { -// let new_node = children[*child_idx]; -// ElementProduced::Progressed(push_back( -// &mut self.stack, -// new_node, -// rdom, -// )) -// } -// } else { -// self.pop(); -// self.prev(rdom) -// } -// } -// } + /// get the previous element + pub fn prev(&mut self, rdom: &RealDom) -> ElementProduced { + // recursively add the last child element to the stack + fn push_back( + stack: &mut smallvec::SmallVec<[(RealNodeId, NodePosition); 5]>, + new_node: RealNodeId, + rdom: &RealDom, + ) -> RealNodeId { + match &rdom[new_node].node_data.node_type { + NodeType::Element { .. } => { + let children = rdom.tree.children_ids(new_node).unwrap(); + if children.is_empty() { + new_node + } else { + stack.push((new_node, NodePosition::InChild(children.len() - 1))); + push_back(stack, *children.last().unwrap(), rdom) + } + } + _ => new_node, + } + } + if self.stack.is_empty() { + let new_node = NodeId(0); + ElementProduced::Looped(push_back(&mut self.stack, new_node, rdom)) + } else { + let (last, o_child_idx) = self.stack.last_mut().unwrap(); + let node = &rdom[*last]; + match &node.node_data.node_type { + NodeType::Element { .. } => { + let children = rdom.tree.children_ids(*last).unwrap(); + // if we have children, go to the next child + if let NodePosition::InChild(0) = o_child_idx { + ElementProduced::Progressed(self.pop()) + } else { + *o_child_idx = o_child_idx.map(|i| i - 1); + if let NodePosition::InChild(child_idx) = o_child_idx { + if *child_idx >= children.len() || children.is_empty() { + self.pop(); + self.prev(rdom) + } else { + let new_node = children[*child_idx]; + ElementProduced::Progressed(push_back( + &mut self.stack, + new_node, + rdom, + )) + } + } else { + self.pop(); + self.prev(rdom) + } + } + } -// NodeType::Text { .. } | NodeType::Placeholder { .. } => { -// // we are at a leaf, so we are done -// ElementProduced::Progressed(self.pop()) -// } -// } -// } -// } + NodeType::Text { .. } | NodeType::Placeholder { .. } => { + // we are at a leaf, so we are done + ElementProduced::Progressed(self.pop()) + } + } + } + } -// fn pop(&mut self) -> RealNodeId { -// self.stack.pop().unwrap().0 -// } -// } + fn pop(&mut self) -> RealNodeId { + self.stack.pop().unwrap().0 + } +} From f21d099ae4f49f8a99cebbd707f60fdd7c4d9f9c Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 4 Dec 2022 19:48:16 -0600 Subject: [PATCH 24/93] fix mask generation --- packages/native-core-macro/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index 6b9c1735b..6174e756a 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -493,8 +493,8 @@ impl<'a> StateMember<'a> { let mask = self .dep_mems .iter() - .map(|m| m.id) - .fold(self.mem.id, |a, b| a | b); + .map(|m| 1 << m.id) + .fold(1 << self.mem.id, |a, b| a | b); quote! { #[derive(Clone, Copy)] struct #unit_type; @@ -510,7 +510,7 @@ impl<'a> StateMember<'a> { &[#(dioxus_native_core::PassId(#dependants)),*] } fn mask(&self) -> dioxus_native_core::MemberMask { - dioxus_native_core::MemberMask(1 << #mask) + dioxus_native_core::MemberMask(#mask) } } } From 8c07b4059f10ac53b1ac426d1bf6879e67a2268c Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 4 Dec 2022 20:39:54 -0600 Subject: [PATCH 25/93] update tui with new mutations --- packages/native-core-macro/src/lib.rs | 2 +- packages/native-core/Cargo.toml | 2 +- packages/native-core/src/lib.rs | 4 +- packages/native-core/src/real_dom.rs | 322 +++++++++++++------------- packages/tui/Cargo.toml | 3 +- packages/tui/src/focus.rs | 29 +-- packages/tui/src/hooks.rs | 169 +++++++------- packages/tui/src/layout.rs | 22 +- packages/tui/src/lib.rs | 61 +++-- packages/tui/src/node.rs | 9 +- packages/tui/src/query.rs | 19 +- packages/tui/src/render.rs | 15 +- packages/tui/src/style_attributes.rs | 8 +- 13 files changed, 335 insertions(+), 330 deletions(-) diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index 6174e756a..6c0cdb2d0 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -493,7 +493,7 @@ impl<'a> StateMember<'a> { let mask = self .dep_mems .iter() - .map(|m| 1 << m.id) + .map(|m| 1u64 << m.id) .fold(1 << self.mem.id, |a, b| a | b); quote! { #[derive(Clone, Copy)] diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index 0dc7e0d6e..f15a743f3 100644 --- a/packages/native-core/Cargo.toml +++ b/packages/native-core/Cargo.toml @@ -15,7 +15,7 @@ dioxus-core = { path = "../core", version = "^0.2.1" } dioxus-html = { path = "../html", version = "^0.2.1" } dioxus-core-macro = { path = "../core-macro", version = "^0.2.1" } -taffy = "0.2.0" +taffy = "0.2.1" smallvec = "1.6" rustc-hash = "1.1.0" anymap = "0.12.1" diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 5db3a3609..ae739af86 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -18,8 +18,8 @@ pub mod utils; /// A id for a node that lives in the real dom. pub type RealNodeId = NodeId; -pub(crate) type FxDashMap = dashmap::DashMap>; -pub(crate) type FxDashSet = dashmap::DashSet>; +pub type FxDashMap = dashmap::DashMap>; +pub type FxDashSet = dashmap::DashSet>; pub type SendAnyMap = anymap::Map; /// Used in derived state macros diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 5ef100082..f6b58a933 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -151,184 +151,180 @@ impl RealDom { } /// Updates the dom with some mutations and return a set of nodes that were updated. Pass the dirty nodes to update_state. - pub fn apply_mutations(&mut self, mutations_vec: Vec) -> DirtyNodeStates { + pub fn apply_mutations(&mut self, mutations: Mutations) -> DirtyNodeStates { let mut nodes_updated: FxHashMap = FxHashMap::default(); - for mutations in mutations_vec { - for template in mutations.templates { - let mut template_root_ids = Vec::new(); - for root in template.roots { - if let Some(id) = self.create_template_node(root, &mut nodes_updated) { - template_root_ids.push(id); + for template in mutations.templates { + let mut template_root_ids = Vec::new(); + for root in template.roots { + if let Some(id) = self.create_template_node(root, &mut nodes_updated) { + template_root_ids.push(id); + } + } + self.templates + .insert(template.name.to_string(), template_root_ids); + } + for e in mutations.edits { + use dioxus_core::Mutation::*; + match e { + AppendChildren { id, m } => { + let children = self.stack.split_off(m); + let parent = self.element_to_node_id(id); + for child in children { + self.add_child(parent, child); + mark_dirty(parent, NodeMask::ALL, &mut nodes_updated); } } - self.templates - .insert(template.name.to_string(), template_root_ids); - } - for e in mutations.edits { - use dioxus_core::Mutation::*; - match e { - AppendChildren { id, m } => { - let children = self.stack.split_off(m); - let parent = self.element_to_node_id(id); - for child in children { - self.add_child(parent, child); - mark_dirty(parent, NodeMask::ALL, &mut nodes_updated); - } + AssignId { path, id } => { + self.set_element_id(self.load_child(path), id); + } + CreatePlaceholder { id } => { + let node = Node::new(NodeType::Placeholder); + let node_id = self.create_node(node); + self.set_element_id(node_id, id); + self.stack.push(node_id); + mark_dirty(node_id, NodeMask::ALL, &mut nodes_updated); + } + CreateTextNode { value, id } => { + let node = Node::new(NodeType::Text { + text: value.to_string(), + }); + let node_id = self.create_node(node); + let node = self.tree.get_mut(node_id).unwrap(); + node.node_data.element_id = Some(id); + self.stack.push(node_id); + mark_dirty(node_id, NodeMask::new().with_text(), &mut nodes_updated); + } + HydrateText { path, value, id } => { + let node_id = self.load_child(path); + self.set_element_id(node_id, id); + let node = self.tree.get_mut(node_id).unwrap(); + if let NodeType::Text { text } = &mut node.node_data.node_type { + *text = value.to_string(); } - AssignId { path, id } => { - self.set_element_id(self.load_child(path), id); + mark_dirty(node_id, NodeMask::new().with_text(), &mut nodes_updated); + } + LoadTemplate { name, index, id } => { + let template_id = self.templates[name][index]; + let clone_id = self.clone_node(template_id, &mut nodes_updated); + self.set_element_id(clone_id, id); + } + ReplaceWith { id, m } => { + let new_nodes = self.stack.split_off(m); + let old_node_id = self.element_to_node_id(id); + for new in new_nodes { + self.tree.insert_after(old_node_id, new); + mark_dirty(new, NodeMask::ALL, &mut nodes_updated); } - CreatePlaceholder { id } => { - let node = Node::new(NodeType::Placeholder); - let node_id = self.create_node(node); - self.set_element_id(node_id, id); - self.stack.push(node_id); - mark_dirty(node_id, NodeMask::ALL, &mut nodes_updated); + self.tree.remove(old_node_id); + } + ReplacePlaceholder { path, m } => { + let new_nodes = self.stack.split_off(m); + let old_node_id = self.load_child(path); + for new in new_nodes { + self.tree.insert_after(old_node_id, new); + mark_dirty(new, NodeMask::ALL, &mut nodes_updated); } - CreateTextNode { value, id } => { - let node = Node::new(NodeType::Text { - text: value.to_string(), - }); - let node_id = self.create_node(node); - let node = self.tree.get_mut(node_id).unwrap(); - node.node_data.element_id = Some(id); - self.stack.push(node_id); - mark_dirty(node_id, NodeMask::new().with_text(), &mut nodes_updated); + self.tree.remove(old_node_id); + } + InsertAfter { id, m } => { + let new_nodes = self.stack.split_off(m); + let old_node_id = self.element_to_node_id(id); + for new in new_nodes { + self.tree.insert_after(old_node_id, new); + mark_dirty(new, NodeMask::ALL, &mut nodes_updated); } - HydrateText { path, value, id } => { - let node_id = self.load_child(path); - self.set_element_id(node_id, id); - let node = self.tree.get_mut(node_id).unwrap(); - if let NodeType::Text { text } = &mut node.node_data.node_type { - *text = value.to_string(); - } - mark_dirty(node_id, NodeMask::new().with_text(), &mut nodes_updated); + } + InsertBefore { id, m } => { + let new_nodes = self.stack.split_off(m); + let old_node_id = self.element_to_node_id(id); + for new in new_nodes { + self.tree.insert_before(old_node_id, new); + mark_dirty(new, NodeMask::ALL, &mut nodes_updated); } - LoadTemplate { name, index, id } => { - let template_id = self.templates[name][index]; - let clone_id = self.clone_node(template_id, &mut nodes_updated); - self.set_element_id(clone_id, id); + } + SetAttribute { + name, + value, + id, + ns, + } => { + let node_id = self.element_to_node_id(id); + let node = self.tree.get_mut(node_id).unwrap(); + if let NodeType::Element { attributes, .. } = &mut node.node_data.node_type { + attributes.insert( + OwnedAttributeDiscription { + name: name.to_string(), + namespace: ns.map(|s| s.to_string()), + volatile: false, + }, + crate::node::OwnedAttributeValue::Text(value.to_string()), + ); + mark_dirty( + node_id, + NodeMask::new_with_attrs(AttributeMask::single(name)), + &mut nodes_updated, + ); } - ReplaceWith { id, m } => { - let new_nodes = self.stack.split_off(m); - let old_node_id = self.element_to_node_id(id); - for new in new_nodes { - self.tree.insert_after(old_node_id, new); - mark_dirty(new, NodeMask::ALL, &mut nodes_updated); - } - self.tree.remove(old_node_id); + } + SetBoolAttribute { name, value, id } => { + let node_id = self.element_to_node_id(id); + let node = self.tree.get_mut(node_id).unwrap(); + if let NodeType::Element { attributes, .. } = &mut node.node_data.node_type { + attributes.insert( + OwnedAttributeDiscription { + name: name.to_string(), + namespace: None, + volatile: false, + }, + crate::node::OwnedAttributeValue::Bool(value), + ); + mark_dirty( + node_id, + NodeMask::new_with_attrs(AttributeMask::single(name)), + &mut nodes_updated, + ); } - ReplacePlaceholder { path, m } => { - let new_nodes = self.stack.split_off(m); - let old_node_id = self.load_child(path); - for new in new_nodes { - self.tree.insert_after(old_node_id, new); - mark_dirty(new, NodeMask::ALL, &mut nodes_updated); - } - self.tree.remove(old_node_id); + } + SetText { value, id } => { + let node_id = self.element_to_node_id(id); + let node = self.tree.get_mut(node_id).unwrap(); + if let NodeType::Text { text } = &mut node.node_data.node_type { + *text = value.to_string(); } - InsertAfter { id, m } => { - let new_nodes = self.stack.split_off(m); - let old_node_id = self.element_to_node_id(id); - for new in new_nodes { - self.tree.insert_after(old_node_id, new); - mark_dirty(new, NodeMask::ALL, &mut nodes_updated); - } - } - InsertBefore { id, m } => { - let new_nodes = self.stack.split_off(m); - let old_node_id = self.element_to_node_id(id); - for new in new_nodes { - self.tree.insert_before(old_node_id, new); - mark_dirty(new, NodeMask::ALL, &mut nodes_updated); - } - } - SetAttribute { - name, - value, - id, - ns, - } => { - let node_id = self.element_to_node_id(id); - let node = self.tree.get_mut(node_id).unwrap(); - if let NodeType::Element { attributes, .. } = &mut node.node_data.node_type - { - attributes.insert( - OwnedAttributeDiscription { - name: name.to_string(), - namespace: ns.map(|s| s.to_string()), - volatile: false, - }, - crate::node::OwnedAttributeValue::Text(value.to_string()), - ); - mark_dirty( - node_id, - NodeMask::new_with_attrs(AttributeMask::single(name)), - &mut nodes_updated, - ); - } - } - SetBoolAttribute { name, value, id } => { - let node_id = self.element_to_node_id(id); - let node = self.tree.get_mut(node_id).unwrap(); - if let NodeType::Element { attributes, .. } = &mut node.node_data.node_type - { - attributes.insert( - OwnedAttributeDiscription { - name: name.to_string(), - namespace: None, - volatile: false, - }, - crate::node::OwnedAttributeValue::Bool(value), - ); - mark_dirty( - node_id, - NodeMask::new_with_attrs(AttributeMask::single(name)), - &mut nodes_updated, - ); - } - } - SetText { value, id } => { - let node_id = self.element_to_node_id(id); - let node = self.tree.get_mut(node_id).unwrap(); - if let NodeType::Text { text } = &mut node.node_data.node_type { - *text = value.to_string(); - } - mark_dirty(node_id, NodeMask::new().with_text(), &mut nodes_updated); - } - NewEventListener { name, scope: _, id } => { - let node_id = self.element_to_node_id(id); - let node = self.tree.get_mut(node_id).unwrap(); - if let NodeType::Element { listeners, .. } = &mut node.node_data.node_type { - match self.nodes_listening.get_mut(name) { - Some(hs) => { - hs.insert(node_id); - } - None => { - let mut hs = FxHashSet::default(); - hs.insert(node_id); - self.nodes_listening.insert(name.to_string(), hs); - } + mark_dirty(node_id, NodeMask::new().with_text(), &mut nodes_updated); + } + NewEventListener { name, scope: _, id } => { + let node_id = self.element_to_node_id(id); + let node = self.tree.get_mut(node_id).unwrap(); + if let NodeType::Element { listeners, .. } = &mut node.node_data.node_type { + match self.nodes_listening.get_mut(name) { + Some(hs) => { + hs.insert(node_id); + } + None => { + let mut hs = FxHashSet::default(); + hs.insert(node_id); + self.nodes_listening.insert(name.to_string(), hs); } - listeners.insert(name.to_string()); } + listeners.insert(name.to_string()); } - RemoveEventListener { id, name } => { - let node_id = self.element_to_node_id(id); - let node = self.tree.get_mut(node_id).unwrap(); - if let NodeType::Element { listeners, .. } = &mut node.node_data.node_type { - listeners.remove(name); - } - self.nodes_listening.get_mut(name).unwrap().remove(&node_id); - } - Remove { id } => { - let node_id = self.element_to_node_id(id); - self.tree.remove(node_id); - } - PushRoot { id } => { - let node_id = self.element_to_node_id(id); - self.stack.push(node_id); + } + RemoveEventListener { id, name } => { + let node_id = self.element_to_node_id(id); + let node = self.tree.get_mut(node_id).unwrap(); + if let NodeType::Element { listeners, .. } = &mut node.node_data.node_type { + listeners.remove(name); } + self.nodes_listening.get_mut(name).unwrap().remove(&node_id); + } + Remove { id } => { + let node_id = self.element_to_node_id(id); + self.tree.remove(node_id); + } + PushRoot { id } => { + let node_id = self.element_to_node_id(id); + self.stack.push(node_id); } } } diff --git a/packages/tui/Cargo.toml b/packages/tui/Cargo.toml index 10f005bbc..116784c7b 100644 --- a/packages/tui/Cargo.toml +++ b/packages/tui/Cargo.toml @@ -23,10 +23,11 @@ crossterm = "0.23.0" anyhow = "1.0.42" tokio = { version = "1.15.0", features = ["full"] } futures = "0.3.19" -taffy = "0.1.0" +taffy = "0.2.1" smallvec = "1.6" rustc-hash = "1.1.0" anymap = "0.12.1" +futures-channel = "0.3.25" [dev-dependencies] dioxus = { path = "../dioxus" } diff --git a/packages/tui/src/focus.rs b/packages/tui/src/focus.rs index 9c070bc00..90d9e9da0 100644 --- a/packages/tui/src/focus.rs +++ b/packages/tui/src/focus.rs @@ -1,6 +1,7 @@ -use crate::{node::PreventDefault, Dom}; +use crate::{node::PreventDefault, TuiDom}; use dioxus_native_core::{ + tree::TreeView, utils::{ElementProduced, PersistantElementIter}, RealNodeId, }; @@ -10,7 +11,6 @@ use std::{cmp::Ordering, num::NonZeroU16}; use dioxus_native_core::{ node_ref::{AttributeMask, NodeMask, NodeView}, - real_dom::NodeType, state::NodeDepState, }; @@ -64,7 +64,8 @@ pub(crate) struct Focus { pub level: FocusLevel, } -impl NodeDepState<()> for Focus { +impl NodeDepState for Focus { + type DepState = (); type Ctx = (); const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::Static(FOCUS_ATTRIBUTES)).with_listeners(); @@ -77,7 +78,7 @@ impl NodeDepState<()> for Focus { { if let Some(index) = a .value - .as_int32() + .as_int() .or_else(|| a.value.as_text().and_then(|v| v.parse::().ok())) { match index.cmp(&0) { @@ -126,7 +127,7 @@ pub(crate) struct FocusState { impl FocusState { /// Returns true if the focus has changed. - pub fn progress(&mut self, rdom: &mut Dom, forward: bool) -> bool { + pub fn progress(&mut self, rdom: &mut TuiDom, forward: bool) -> bool { if let Some(last) = self.last_focused_id { if rdom[last].state.prevent_default == PreventDefault::KeyDown { return false; @@ -234,10 +235,10 @@ impl FocusState { false } - pub(crate) fn prune(&mut self, mutations: &dioxus_core::Mutations, rdom: &Dom) { + pub(crate) fn prune(&mut self, mutations: &dioxus_core::Mutations, rdom: &TuiDom) { fn remove_children( to_prune: &mut [&mut Option], - rdom: &Dom, + rdom: &TuiDom, removed: RealNodeId, ) { for opt in to_prune.iter_mut() { @@ -247,8 +248,8 @@ impl FocusState { } } } - if let NodeType::Element { children, .. } = &rdom[removed].node_data.node_type { - for child in children { + if let Some(children) = &rdom.children_ids(removed) { + for child in *children { remove_children(to_prune, rdom, *child); } } @@ -258,22 +259,22 @@ impl FocusState { } for m in &mutations.edits { match m { - dioxus_core::DomEdit::ReplaceWith { root, .. } => remove_children( + dioxus_core::Mutation::ReplaceWith { id, .. } => remove_children( &mut [&mut self.last_focused_id], rdom, - rdom.resolve_maybe_id(*root), + rdom.element_to_node_id(*id), ), - dioxus_core::DomEdit::Remove { root } => remove_children( + dioxus_core::Mutation::Remove { id } => remove_children( &mut [&mut self.last_focused_id], rdom, - rdom.resolve_maybe_id(*root), + rdom.element_to_node_id(*id), ), _ => (), } } } - pub(crate) fn set_focus(&mut self, rdom: &mut Dom, id: RealNodeId) { + pub(crate) fn set_focus(&mut self, rdom: &mut TuiDom, id: RealNodeId) { if let Some(old) = self.last_focused_id.replace(id) { rdom[old].state.focused = false; } diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 44ac276cc..197c7ea79 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -2,7 +2,8 @@ use crossterm::event::{ Event as TermEvent, KeyCode as TermKeyCode, KeyModifiers, MouseButton, MouseEventKind, }; use dioxus_core::*; -use dioxus_native_core::RealNodeId; +use dioxus_native_core::tree::TreeView; +use dioxus_native_core::NodeId; use rustc_hash::{FxHashMap, FxHashSet}; use dioxus_html::geometry::euclid::{Point2D, Rect, Size2D}; @@ -12,19 +13,25 @@ use dioxus_html::geometry::{ use dioxus_html::input_data::keyboard_types::{Code, Key, Location, Modifiers}; use dioxus_html::input_data::MouseButtonSet as DioxusMouseButtons; use dioxus_html::input_data::{MouseButton as DioxusMouseButton, MouseButtonSet}; -use dioxus_html::{event_bubbles, on::*}; +use dioxus_html::{event_bubbles, FocusData, KeyboardData, MouseData, WheelData}; use std::{ any::Any, cell::{RefCell, RefMut}, rc::Rc, - sync::Arc, time::{Duration, Instant}, }; use taffy::geometry::{Point, Size}; use taffy::{prelude::Layout, Taffy}; use crate::FocusState; -use crate::{Dom, Node}; +use crate::{TuiDom, TuiNode}; + +pub(crate) struct Event { + pub id: ElementId, + pub name: &'static str, + pub data: Rc, + pub bubbles: bool, +} // a wrapper around the input state for easier access // todo: fix loop @@ -71,12 +78,12 @@ enum EventData { Keyboard(KeyboardData), } impl EventData { - fn into_any(self) -> Arc { + fn into_any(self) -> Rc { match self { - Self::Mouse(m) => Arc::new(m), - Self::Wheel(w) => Arc::new(w), - Self::Screen(s) => Arc::new(s), - Self::Keyboard(k) => Arc::new(k), + Self::Mouse(m) => Rc::new(m), + Self::Wheel(w) => Rc::new(w), + Self::Screen(s) => Rc::new(s), + Self::Keyboard(k) => Rc::new(k), } } } @@ -164,9 +171,9 @@ impl InnerInputState { fn update( &mut self, evts: &mut Vec, - resolved_events: &mut Vec, + resolved_events: &mut Vec, layout: &Taffy, - dom: &mut Dom, + dom: &mut TuiDom, ) { let previous_mouse = self.mouse.clone(); @@ -192,33 +199,33 @@ impl InnerInputState { if old_focus != self.focus_state.last_focused_id { // elements with listeners will always have a element id - if let Some(RealNodeId::ElementId(id)) = self.focus_state.last_focused_id { - resolved_events.push(UserEvent { - scope_id: None, - priority: EventPriority::Medium, - name: "focus", - element: Some(id), - data: Arc::new(FocusData {}), - bubbles: event_bubbles("focus"), - }); - resolved_events.push(UserEvent { - scope_id: None, - priority: EventPriority::Medium, - name: "focusin", - element: Some(id), - data: Arc::new(FocusData {}), - bubbles: event_bubbles("focusin"), - }); + if let Some(id) = self.focus_state.last_focused_id { + let element = dom.tree.get(id).unwrap(); + if let Some(id) = element.node_data.element_id { + resolved_events.push(Event { + name: "focus", + id, + data: Rc::new(FocusData {}), + bubbles: event_bubbles("focus"), + }); + resolved_events.push(Event { + name: "focusin", + id, + data: Rc::new(FocusData {}), + bubbles: event_bubbles("focusin"), + }); + } } - if let Some(RealNodeId::ElementId(id)) = old_focus { - resolved_events.push(UserEvent { - scope_id: None, - priority: EventPriority::Medium, - name: "focusout", - element: Some(id), - data: Arc::new(FocusData {}), - bubbles: event_bubbles("focusout"), - }); + if let Some(id) = old_focus { + let element = dom.tree.get(id).unwrap(); + if let Some(id) = element.node_data.element_id { + resolved_events.push(Event { + name: "focusout", + id, + data: Rc::new(FocusData {}), + bubbles: event_bubbles("focusout"), + }); + } } } @@ -230,9 +237,9 @@ impl InnerInputState { fn resolve_mouse_events( &mut self, previous_mouse: Option, - resolved_events: &mut Vec, + resolved_events: &mut Vec, layout: &Taffy, - dom: &mut Dom, + dom: &mut TuiDom, ) { fn layout_contains_point(layout: &Layout, point: ScreenPoint) -> bool { let Point { x, y } = layout.location; @@ -244,28 +251,29 @@ impl InnerInputState { fn try_create_event( name: &'static str, - data: Arc, - will_bubble: &mut FxHashSet, - resolved_events: &mut Vec, - node: &Node, - dom: &Dom, + data: Rc, + will_bubble: &mut FxHashSet, + resolved_events: &mut Vec, + node: &TuiNode, + dom: &TuiDom, ) { // only trigger event if the event was not triggered already by a child - let id = node.mounted_id(); + let id = node.node_data.node_id; if will_bubble.insert(id) { - let mut parent = node.node_data.parent; - while let Some(parent_id) = parent { + let mut parent = dom.parent(id); + while let Some(current_parent) = parent { + let parent_id = current_parent.node_data.node_id; will_bubble.insert(parent_id); - parent = dom[parent_id].node_data.parent; + parent = dom.parent(parent_id); + } + if let Some(id) = node.mounted_id() { + resolved_events.push(Event { + name, + id, + data, + bubbles: event_bubbles(name), + }) } - resolved_events.push(UserEvent { - scope_id: None, - priority: EventPriority::Medium, - name, - element: Some(id.as_element_id()), - data, - bubbles: event_bubbles(name), - }) } } @@ -326,7 +334,7 @@ impl InnerInputState { if currently_contains && previously_contained { try_create_event( "mousemove", - Arc::new(prepare_mouse_data(mouse_data, &node_layout)), + Rc::new(prepare_mouse_data(mouse_data, &node_layout)), &mut will_bubble, resolved_events, node, @@ -350,7 +358,7 @@ impl InnerInputState { if currently_contains && !previously_contained { try_create_event( "mouseenter", - Arc::new(mouse_data.clone()), + Rc::new(mouse_data.clone()), &mut will_bubble, resolved_events, node, @@ -373,7 +381,7 @@ impl InnerInputState { if currently_contains && !previously_contained { try_create_event( "mouseover", - Arc::new(prepare_mouse_data(mouse_data, &node_layout)), + Rc::new(prepare_mouse_data(mouse_data, &node_layout)), &mut will_bubble, resolved_events, node, @@ -393,7 +401,7 @@ impl InnerInputState { if currently_contains { try_create_event( "mousedown", - Arc::new(prepare_mouse_data(mouse_data, &node_layout)), + Rc::new(prepare_mouse_data(mouse_data, &node_layout)), &mut will_bubble, resolved_events, node, @@ -414,7 +422,7 @@ impl InnerInputState { if currently_contains { try_create_event( "mouseup", - Arc::new(prepare_mouse_data(mouse_data, &node_layout)), + Rc::new(prepare_mouse_data(mouse_data, &node_layout)), &mut will_bubble, resolved_events, node, @@ -436,7 +444,7 @@ impl InnerInputState { if currently_contains { try_create_event( "click", - Arc::new(prepare_mouse_data(mouse_data, &node_layout)), + Rc::new(prepare_mouse_data(mouse_data, &node_layout)), &mut will_bubble, resolved_events, node, @@ -459,7 +467,7 @@ impl InnerInputState { if currently_contains { try_create_event( "contextmenu", - Arc::new(prepare_mouse_data(mouse_data, &node_layout)), + Rc::new(prepare_mouse_data(mouse_data, &node_layout)), &mut will_bubble, resolved_events, node, @@ -483,7 +491,7 @@ impl InnerInputState { if currently_contains { try_create_event( "wheel", - Arc::new(w.clone()), + Rc::new(w.clone()), &mut will_bubble, resolved_events, node, @@ -508,7 +516,7 @@ impl InnerInputState { if !currently_contains && previously_contained { try_create_event( "mouseleave", - Arc::new(prepare_mouse_data(mouse_data, &node_layout)), + Rc::new(prepare_mouse_data(mouse_data, &node_layout)), &mut will_bubble, resolved_events, node, @@ -531,7 +539,7 @@ impl InnerInputState { if !currently_contains && previously_contained { try_create_event( "mouseout", - Arc::new(prepare_mouse_data(mouse_data, &node_layout)), + Rc::new(prepare_mouse_data(mouse_data, &node_layout)), &mut will_bubble, resolved_events, node, @@ -549,7 +557,7 @@ impl InnerInputState { let currently_contains = layout_contains_point(node_layout, new_pos); if currently_contains && node.state.focus.level.focusable() { - focus_id = Some(node.mounted_id()); + focus_id = Some(node.node_data.node_id); } }); if let Some(id) = focus_id { @@ -564,12 +572,11 @@ impl InnerInputState { // } } -fn get_abs_layout(node: &Node, dom: &Dom, taffy: &Taffy) -> Layout { +fn get_abs_layout(node: &TuiNode, dom: &TuiDom, taffy: &Taffy) -> Layout { let mut node_layout = *taffy.layout(node.state.layout.node.unwrap()).unwrap(); let mut current = node; - while let Some(parent_id) = current.node_data.parent { - let parent = &dom[parent_id]; + while let Some(parent) = dom.parent(current.node_data.node_id) { current = parent; let parent_layout = taffy.layout(parent.state.layout.node.unwrap()).unwrap(); node_layout.location.x += parent_layout.location.x; @@ -614,11 +621,11 @@ impl RinkInputHandler { ) } - pub(crate) fn prune(&self, mutations: &dioxus_core::Mutations, rdom: &Dom) { + pub(crate) fn prune(&self, mutations: &dioxus_core::Mutations, rdom: &TuiDom) { self.state.borrow_mut().focus_state.prune(mutations, rdom); } - pub(crate) fn get_events(&self, layout: &Taffy, dom: &mut Dom) -> Vec { + pub(crate) fn get_events(&self, layout: &Taffy, dom: &mut TuiDom) -> Vec { let mut resolved_events = Vec::new(); (*self.state).borrow_mut().update( @@ -651,7 +658,7 @@ impl RinkInputHandler { }) .map(|evt| (evt.0, evt.1.into_any())); - let mut hm: FxHashMap<&'static str, Vec>> = FxHashMap::default(); + let mut hm: FxHashMap<&'static str, Vec>> = FxHashMap::default(); for (event, data) in events { if let Some(v) = hm.get_mut(event) { v.push(data); @@ -663,14 +670,14 @@ impl RinkInputHandler { for node in dom.get_listening_sorted(event) { for data in &datas { if node.state.focused { - resolved_events.push(UserEvent { - scope_id: None, - priority: EventPriority::Medium, - name: event, - element: Some(node.mounted_id().as_element_id()), - data: data.clone(), - bubbles: event_bubbles(event), - }); + if let Some(id) = node.mounted_id() { + resolved_events.push(Event { + name: event, + id, + data: data.clone(), + bubbles: event_bubbles(event), + }); + } } } } diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index e65e5b2df..2abebe856 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -1,12 +1,10 @@ -use std::cell::RefCell; -use std::rc::Rc; +use std::sync::{Arc, Mutex}; use dioxus_core::*; use dioxus_native_core::layout_attributes::apply_layout_attributes; +use dioxus_native_core::node::OwnedAttributeView; use dioxus_native_core::node_ref::{AttributeMask, NodeMask, NodeView}; -use dioxus_native_core::real_dom::OwnedAttributeView; use dioxus_native_core::state::ChildDepState; -use dioxus_native_core::RealNodeId; use dioxus_native_core_macro::sorted_str_slice; use taffy::prelude::*; @@ -42,8 +40,8 @@ pub(crate) struct TaffyLayout { } impl ChildDepState for TaffyLayout { - type Ctx = Rc>; - type DepState = Self; + type Ctx = Arc>; + type DepState = (Self,); // use tag to force this to be called when a node is built const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::Static(SORTED_LAYOUT_ATTRS)) @@ -54,14 +52,14 @@ impl ChildDepState for TaffyLayout { fn reduce<'a>( &mut self, node: NodeView, - children: impl Iterator, + children: impl Iterator, ctx: &Self::Ctx, ) -> bool where Self::DepState: 'a, { let mut changed = false; - let mut taffy = ctx.borrow_mut(); + let mut taffy = ctx.lock().expect("poisoned taffy"); let mut style = Style::default(); if let Some(text) = node.text() { let char_len = text.chars().count(); @@ -81,7 +79,7 @@ impl ChildDepState for TaffyLayout { taffy.set_style(n, style).unwrap(); } } else { - self.node = PossiblyUninitalized::Initialized(taffy.new_node(style, &[]).unwrap()); + self.node = PossiblyUninitalized::Initialized(taffy.new_leaf(style).unwrap()); changed = true; } } else { @@ -101,14 +99,14 @@ impl ChildDepState for TaffyLayout { } // the root node fills the entire area - if node.id() == RealNodeId::ElementId(ElementId(0)) { + if node.id() == Some(ElementId(0)) { apply_layout_attributes("width", "100%", &mut style); apply_layout_attributes("height", "100%", &mut style); } // Set all direct nodes as our children let mut child_layout = vec![]; - for l in children { + for (l,) in children { child_layout.push(l.node.unwrap()); } @@ -121,7 +119,7 @@ impl ChildDepState for TaffyLayout { } } else { self.node = PossiblyUninitalized::Initialized( - taffy.new_node(style, &child_layout).unwrap(), + taffy.new_with_children(style, &child_layout).unwrap(), ); changed = true; } diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 70d90ef0a..6b514492f 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -1,24 +1,23 @@ use anyhow::Result; -use anymap::AnyMap; use crossterm::{ event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyModifiers}, execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; -use dioxus_core::exports::futures_channel::mpsc::unbounded; +use futures_channel::mpsc::unbounded; use dioxus_core::*; -use dioxus_native_core::{real_dom::RealDom, RealNodeId}; +use dioxus_native_core::{real_dom::RealDom, NodeId, SendAnyMap, FxDashSet}; use focus::FocusState; use futures::{ channel::mpsc::{UnboundedReceiver, UnboundedSender}, pin_mut, StreamExt, }; use query::Query; -use std::cell::RefCell; +use std::{cell::RefCell, sync::{Mutex, Arc}}; use std::rc::Rc; use std::{io, time::Duration}; use taffy::Taffy; -pub use taffy::{geometry::Point, prelude::Size}; +pub use taffy::{geometry::Point, prelude::*}; use tui::{backend::CrosstermBackend, layout::Rect, Terminal}; mod config; @@ -80,10 +79,10 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) { let cx = dom.base_scope(); let rdom = Rc::new(RefCell::new(RealDom::new())); - let taffy = Rc::new(RefCell::new(Taffy::new())); - cx.provide_root_context(state); - cx.provide_root_context(TuiContext { tx: event_tx_clone }); - cx.provide_root_context(Query { + let taffy = Arc::new(Mutex::new(Taffy::new())); + cx.provide_context(state); + cx.provide_context(TuiContext { tx: event_tx_clone }); + cx.provide_context(Query { rdom: rdom.clone(), stretch: taffy.clone(), }); @@ -91,8 +90,8 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) { { let mut rdom = rdom.borrow_mut(); let mutations = dom.rebuild(); - let to_update = rdom.apply_mutations(vec![mutations]); - let mut any_map = AnyMap::new(); + let to_update = rdom.apply_mutations(mutations); + let mut any_map = SendAnyMap::new(); any_map.insert(taffy.clone()); let _to_rerender = rdom.update_state(to_update, any_map); } @@ -114,8 +113,8 @@ fn render_vdom( mut event_reciever: UnboundedReceiver, handler: RinkInputHandler, cfg: Config, - rdom: Rc>, - taffy: Rc>, + rdom: Rc>, + taffy: Arc>, mut register_event: impl FnMut(crossterm::event::Event), ) -> Result<()> { tokio::runtime::Builder::new_current_thread() @@ -133,10 +132,8 @@ fn render_vdom( terminal.clear().unwrap(); } - let mut to_rerender: rustc_hash::FxHashSet = - vec![RealNodeId::ElementId(ElementId(0))] - .into_iter() - .collect(); + let mut to_rerender =FxDashSet::default(); + to_rerender.insert(NodeId(0)); let mut updated = true; loop { @@ -153,17 +150,17 @@ fn render_vdom( if !to_rerender.is_empty() || updated { updated = false; - fn resize(dims: Rect, taffy: &mut Taffy, rdom: &Dom) { + fn resize(dims: Rect, taffy: &mut Taffy, rdom: &TuiDom) { let width = dims.width; let height = dims.height; - let root_node = rdom[ElementId(0)].state.layout.node.unwrap(); + let root_node = rdom[NodeId(0)].state.layout.node.unwrap(); taffy .compute_layout( root_node, Size { - width: taffy::prelude::Number::Defined((width - 1) as f32), - height: taffy::prelude::Number::Defined((height - 1) as f32), + width: AvailableSpace::Definite((width - 1) as f32), + height: AvailableSpace::Definite((height - 1) as f32), }, ) .unwrap(); @@ -172,15 +169,15 @@ fn render_vdom( terminal.draw(|frame| { let rdom = rdom.borrow(); // size is guaranteed to not change when rendering - resize(frame.size(), &mut taffy.borrow_mut(), &rdom); - let root = &rdom[ElementId(0)]; + resize(frame.size(), &mut taffy.lock().expect("taffy lock poisoned"), &rdom); + let root = &rdom[NodeId(0)]; render::render_vnode( frame, - &taffy.borrow(), + &taffy.lock().expect("taffy lock poisoned"), &rdom, root, cfg, - Point::zero(), + Point::ZERO, ); })?; } else { @@ -192,7 +189,7 @@ fn render_vdom( width: 100, height: 100, }, - &mut taffy.borrow_mut(), + &mut taffy.lock().expect("taffy lock poisoned"), &rdom, ); } @@ -234,23 +231,21 @@ fn render_vdom( { let evts = { let mut rdom = rdom.borrow_mut(); - handler.get_events(&taffy.borrow(), &mut rdom) + handler.get_events(&taffy.lock().expect("taffy lock poisoned"), &mut rdom) }; { updated |= handler.state().focus_state.clean(); } for e in evts { - vdom.handle_message(SchedulerMsg::Event(e)); + vdom.handle_event(e.name, e.data, e.id, e.bubbles) } let mut rdom = rdom.borrow_mut(); - let mutations = vdom.work_with_deadline(|| false); - for m in &mutations { - handler.prune(m, &rdom); - } + let mutations = vdom.render_immediate(); + handler.prune(&mutations, &rdom); // updates the dom's nodes let to_update = rdom.apply_mutations(mutations); // update the style and layout - let mut any_map = AnyMap::new(); + let mut any_map = SendAnyMap::new(); any_map.insert(taffy.clone()); to_rerender = rdom.update_state(to_update, any_map); } diff --git a/packages/tui/src/node.rs b/packages/tui/src/node.rs index daa85500e..33f74a011 100644 --- a/packages/tui/src/node.rs +++ b/packages/tui/src/node.rs @@ -4,12 +4,12 @@ use crate::style_attributes::StyleModifier; use dioxus_native_core::{real_dom::RealDom, state::*}; use dioxus_native_core_macro::{sorted_str_slice, State}; -pub(crate) type Dom = RealDom; -pub(crate) type Node = dioxus_native_core::real_dom::Node; +pub(crate) type TuiDom = RealDom; +pub(crate) type TuiNode = dioxus_native_core::node::Node; #[derive(Debug, Clone, State, Default)] pub(crate) struct NodeState { - #[child_dep_state(layout, RefCell)] + #[child_dep_state(layout, Mutex)] pub layout: TaffyLayout, #[parent_dep_state(style)] pub style: StyleModifier, @@ -45,7 +45,8 @@ impl Default for PreventDefault { } } -impl NodeDepState<()> for PreventDefault { +impl NodeDepState for PreventDefault { + type DepState = (); type Ctx = (); const NODE_MASK: dioxus_native_core::node_ref::NodeMask = diff --git a/packages/tui/src/query.rs b/packages/tui/src/query.rs index 0a8a886cc..15b7fd7f2 100644 --- a/packages/tui/src/query.rs +++ b/packages/tui/src/query.rs @@ -1,6 +1,7 @@ use std::{ cell::{Ref, RefCell}, rc::Rc, + sync::{Arc, Mutex, MutexGuard}, }; use dioxus_core::ElementId; @@ -10,7 +11,7 @@ use taffy::{ Taffy, }; -use crate::Dom; +use crate::TuiDom; /// Allows querying the layout of nodes after rendering. It will only provide a correct value after a node is rendered. /// Provided as a root context for all tui applictions. @@ -45,24 +46,28 @@ use crate::Dom; /// ``` #[derive(Clone)] pub struct Query { - pub(crate) rdom: Rc>, - pub(crate) stretch: Rc>, + pub(crate) rdom: Rc>, + pub(crate) stretch: Arc>, } impl Query { pub fn get(&self, id: ElementId) -> ElementRef { - ElementRef::new(self.rdom.borrow(), self.stretch.borrow(), id) + ElementRef::new( + self.rdom.borrow(), + self.stretch.lock().expect("taffy lock poisoned"), + id, + ) } } pub struct ElementRef<'a> { - inner: Ref<'a, Dom>, - stretch: Ref<'a, Taffy>, + inner: Ref<'a, TuiDom>, + stretch: MutexGuard<'a, Taffy>, id: ElementId, } impl<'a> ElementRef<'a> { - fn new(inner: Ref<'a, Dom>, stretch: Ref<'a, Taffy>, id: ElementId) -> Self { + fn new(inner: Ref<'a, TuiDom>, stretch: MutexGuard<'a, Taffy>, id: ElementId) -> Self { Self { inner, stretch, id } } diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index 9cea0ced0..7beb543e2 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -1,3 +1,4 @@ +use dioxus_native_core::tree::TreeView; use std::io::Stdout; use taffy::{ geometry::Point, @@ -10,7 +11,7 @@ use crate::{ style::{RinkColor, RinkStyle}, style_attributes::{BorderEdge, BorderStyle}, widget::{RinkBuffer, RinkCell, RinkWidget, WidgetWithContext}, - Config, Dom, Node, + Config, TuiDom, TuiNode, }; const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5]; @@ -18,12 +19,12 @@ const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5]; pub(crate) fn render_vnode( frame: &mut tui::Frame>, layout: &Taffy, - rdom: &Dom, - node: &Node, + rdom: &TuiDom, + node: &TuiNode, cfg: Config, parent_location: Point, ) { - use dioxus_native_core::real_dom::NodeType; + use dioxus_native_core::node::NodeType; if let NodeType::Placeholder = &node.node_data.node_type { return; @@ -68,7 +69,7 @@ pub(crate) fn render_vnode( frame.render_widget(WidgetWithContext::new(label, cfg), area); } } - NodeType::Element { children, .. } => { + NodeType::Element { .. } => { let area = Rect::new(x as u16, y as u16, *width as u16, *height as u16); // the renderer will panic if a node is rendered out of range even if the size is zero @@ -76,7 +77,7 @@ pub(crate) fn render_vnode( frame.render_widget(WidgetWithContext::new(node, cfg), area); } - for c in children { + for c in rdom.children_ids(node.node_data.node_id).unwrap() { render_vnode(frame, layout, rdom, &rdom[*c], cfg, location); } } @@ -84,7 +85,7 @@ pub(crate) fn render_vnode( } } -impl RinkWidget for &Node { +impl RinkWidget for &TuiNode { fn render(self, area: Rect, mut buf: RinkBuffer<'_>) { use tui::symbols::line::*; diff --git a/packages/tui/src/style_attributes.rs b/packages/tui/src/style_attributes.rs index 0aa674dd8..9d1a61d12 100644 --- a/packages/tui/src/style_attributes.rs +++ b/packages/tui/src/style_attributes.rs @@ -31,8 +31,8 @@ use dioxus_native_core::{ layout_attributes::parse_value, + node::OwnedAttributeView, node_ref::{AttributeMask, NodeMask, NodeView}, - real_dom::OwnedAttributeView, state::ParentDepState, }; use dioxus_native_core_macro::sorted_str_slice; @@ -48,12 +48,12 @@ pub struct StyleModifier { impl ParentDepState for StyleModifier { type Ctx = (); - type DepState = Self; + type DepState = (Self,); // todo: seperate each attribute into it's own class const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::Static(SORTED_STYLE_ATTRS)).with_element(); - fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, _: &Self::Ctx) -> bool { + fn reduce(&mut self, node: NodeView, parent: Option<(&Self,)>, _: &Self::Ctx) -> bool { let mut new = StyleModifier::default(); if parent.is_some() { new.core.fg = None; @@ -91,7 +91,7 @@ impl ParentDepState for StyleModifier { } // keep the text styling from the parent element - if let Some(parent) = parent { + if let Some((parent,)) = parent { let mut new_style = new.core.merge(parent.core); new_style.bg = new.core.bg; new.core = new_style; From 11b37ee141b856dd50abdc2b7e17dfff1ded9060 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 5 Dec 2022 09:44:43 -0600 Subject: [PATCH 26/93] more progress updating tui --- packages/core/src/arena.rs | 3 -- packages/core/src/create.rs | 5 ---- packages/core/src/diff.rs | 15 ---------- packages/core/src/scheduler/wait.rs | 16 ---------- packages/core/src/virtual_dom.rs | 7 ----- packages/native-core/src/node_ref.rs | 6 ++++ packages/native-core/src/passes.rs | 2 +- packages/native-core/src/real_dom.rs | 44 ++++++++++++++++++++-------- packages/native-core/src/utils.rs | 1 + packages/tui/src/layout.rs | 2 +- packages/tui/src/lib.rs | 7 +++-- 11 files changed, 44 insertions(+), 64 deletions(-) diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index d6af9b051..024ad5375 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -69,7 +69,6 @@ impl VirtualDom { ) } - println!("reclaiming {:?}", el); self.elements.try_remove(el.0) } @@ -95,9 +94,7 @@ impl VirtualDom { // Drop all the hooks once the children are dropped // this means we'll drop hooks bottom-up for hook in scope.hook_list.get_mut().drain(..) { - println!("dropping hook !"); drop(unsafe { BumpBox::from_raw(hook) }); - println!("hook dropped !"); } } diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index feaa276e4..b0a463c9e 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -388,11 +388,6 @@ impl<'b> VirtualDom { // Set the placeholder of the scope self.scopes[scope.0].placeholder.set(Some(new_id)); - println!( - "assigning id {:?} to path {:?}, template: {:?}", - new_id, &template.template.node_paths, template.template - ); - // Since the placeholder is already in the DOM, we don't create any new nodes self.mutations.push(AssignId { id: new_id, diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index c0b14d0bd..5f2bccbb1 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -61,17 +61,10 @@ impl<'b> VirtualDom { } fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) { - println!( - "diffing node {:#?},\n\n{:#?}", - left_template.template.name, right_template.template.name - ); - if left_template.template.name != right_template.template.name { return self.light_diff_templates(left_template, right_template); } - println!("diffing attributs..."); - for (left_attr, right_attr) in left_template .dynamic_attrs .iter() @@ -106,8 +99,6 @@ impl<'b> VirtualDom { } } - println!("diffing dyn nodes..."); - for (idx, (left_node, right_node)) in left_template .dynamic_nodes .iter() @@ -133,8 +124,6 @@ impl<'b> VirtualDom { }; } - println!("applying roots..."); - // Make sure the roots get transferred over for (left, right) in left_template .root_ids @@ -396,8 +385,6 @@ impl<'b> VirtualDom { "all siblings must be keyed or all siblings must be non-keyed" ); - println!("Diffing fragment {:?}, {:?}", old.len(), new.len()); - if new_is_keyed && old_is_keyed { self.diff_keyed_children(old, new); } else { @@ -420,8 +407,6 @@ impl<'b> VirtualDom { debug_assert!(!new.is_empty()); debug_assert!(!old.is_empty()); - println!("Diffing non keyed children"); - match dbg!(old.len().cmp(&new.len())) { Ordering::Greater => self.remove_nodes(&old[new.len()..]), Ordering::Less => self.create_and_insert_after(&new[old.len()..], old.last().unwrap()), diff --git a/packages/core/src/scheduler/wait.rs b/packages/core/src/scheduler/wait.rs index e13b0fd45..f8fdfb595 100644 --- a/packages/core/src/scheduler/wait.rs +++ b/packages/core/src/scheduler/wait.rs @@ -40,8 +40,6 @@ impl VirtualDom { } pub(crate) fn handle_suspense_wakeup(&mut self, id: SuspenseId) { - println!("suspense notified"); - let leaf = self .scheduler .leaves @@ -67,13 +65,6 @@ impl VirtualDom { // safety: we're not going to modify the suspense context but we don't want to make a clone of it let fiber = self.acquire_suspense_boundary(leaf.scope_id); - println!("ready pool"); - - println!( - "Existing mutations {:?}, scope {:?}", - fiber.mutations, fiber.id - ); - let scope = &mut self.scopes[scope_id.0]; let arena = scope.current_frame(); @@ -106,16 +97,9 @@ impl VirtualDom { std::mem::swap(&mut self.mutations, mutations); if fiber.waiting_on.borrow().is_empty() { - println!("fiber is finished!"); self.finished_fibers.push(fiber.id); - } else { - println!("fiber is not finished {:?}", fiber.waiting_on); } - } else { - println!("nodes arent right"); } - } else { - println!("not ready"); } } } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index a7046636b..dbcba7548 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -362,8 +362,6 @@ impl VirtualDom { let target_path = el_ref.path; for (idx, attr) in template.dynamic_attrs.iter().enumerate() { - println!("{:?} \n {:?} \n {:?}", attr, name, element); - let this_path = template.template.attr_paths[idx]; // listeners are required to be prefixed with "on", but they come back to the virtualdom with that missing @@ -385,8 +383,6 @@ impl VirtualDom { } } - println!("calling listeners: {:?}", listeners); - // Now that we've accumulated all the parent attributes for the target element, call them in reverse order // We check the bubble state between each call to see if the event has been stopped from bubbling for listener in listeners.drain(..).rev() { @@ -403,8 +399,6 @@ impl VirtualDom { parent_path = template.parent.and_then(|id| self.elements.get(id.0)); } - - println!("all listeners exhausted"); } /// Wait for the scheduler to have any work. @@ -527,7 +521,6 @@ impl VirtualDom { /// /// If no suspense trees are present pub async fn render_with_deadline(&mut self, deadline: impl Future) -> Mutations { - println!("render with deadline"); pin_mut!(deadline); loop { diff --git a/packages/native-core/src/node_ref.rs b/packages/native-core/src/node_ref.rs index 759cc5ffb..d178c2d18 100644 --- a/packages/native-core/src/node_ref.rs +++ b/packages/native-core/src/node_ref.rs @@ -3,6 +3,7 @@ use dioxus_core::ElementId; use crate::{ node::{NodeData, NodeType, OwnedAttributeView}, state::union_ordered_iter, + RealNodeId, }; /// A view into a [VNode] with limited access. @@ -26,6 +27,11 @@ impl<'a> NodeView<'a> { self.inner.element_id } + /// Get the node id of the node + pub fn node_id(&self) -> RealNodeId { + self.inner.node_id + } + /// Get the tag of the node if the tag is enabled in the mask pub fn tag(&self) -> Option<&'a str> { self.mask diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index fadc8e71e..fe774f7ba 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -334,7 +334,7 @@ pub fn resolve_passes>( if pass .dependancies() .iter() - .all(|d| resolved_passes.contains(d)) + .all(|d| resolved_passes.contains(d) || *d == pass_id) && !pass_mask.overlaps(currently_borrowed) { let pass = passes.remove(i); diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index f6b58a933..5c4771f0f 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -1,5 +1,6 @@ use dioxus_core::{ElementId, Mutations, TemplateNode}; use rustc_hash::{FxHashMap, FxHashSet}; +use std::fmt::Debug; use std::ops::{Deref, DerefMut, Index, IndexMut}; use crate::node::{Node, NodeType, OwnedAttributeDiscription, OwnedAttributeValue}; @@ -32,6 +33,7 @@ pub struct RealDom { nodes_listening: FxHashMap>, stack: Vec, templates: FxHashMap>, + root_initialized: bool, } impl Default for RealDom { @@ -55,10 +57,11 @@ impl RealDom { RealDom { tree, - node_id_mapping: Vec::new(), + node_id_mapping: vec![Some(root_id)], nodes_listening: FxHashMap::default(), stack: vec![root_id], templates: FxHashMap::default(), + root_initialized: false, } } @@ -99,7 +102,7 @@ impl RealDom { &mut self, node: &TemplateNode, mutations_vec: &mut FxHashMap, - ) -> Option { + ) -> RealNodeId { match node { TemplateNode::Element { tag, @@ -132,21 +135,31 @@ impl RealDom { }); let node_id = self.create_node(node); for child in *children { - if let Some(child_id) = self.create_template_node(child, mutations_vec) { - self.add_child(node_id, child_id); - } + let child_id = self.create_template_node(child, mutations_vec); + self.add_child(node_id, child_id); } self.stack.push(node_id); - Some(node_id) + node_id } TemplateNode::Text(txt) => { let node_id = self.create_node(Node::new(NodeType::Text { text: txt.to_string(), })); self.stack.push(node_id); - Some(node_id) + node_id + } + TemplateNode::Dynamic(_) => { + let node_id = self.create_node(Node::new(NodeType::Placeholder)); + self.stack.push(node_id); + node_id + } + TemplateNode::DynamicText(_) => { + let node_id = self.create_node(Node::new(NodeType::Text { + text: String::new(), + })); + self.stack.push(node_id); + node_id } - _ => None, } } @@ -156,13 +169,17 @@ impl RealDom { for template in mutations.templates { let mut template_root_ids = Vec::new(); for root in template.roots { - if let Some(id) = self.create_template_node(root, &mut nodes_updated) { - template_root_ids.push(id); - } + let id = self.create_template_node(root, &mut nodes_updated); + template_root_ids.push(id); } self.templates .insert(template.name.to_string(), template_root_ids); } + if !self.root_initialized { + self.root_initialized = true; + let root_id = self.tree.root(); + nodes_updated.insert(root_id, NodeMask::ALL); + } for e in mutations.edits { use dioxus_core::Mutation::*; match e { @@ -171,7 +188,7 @@ impl RealDom { let parent = self.element_to_node_id(id); for child in children { self.add_child(parent, child); - mark_dirty(parent, NodeMask::ALL, &mut nodes_updated); + mark_dirty(child, NodeMask::ALL, &mut nodes_updated); } } AssignId { path, id } => { @@ -207,6 +224,7 @@ impl RealDom { let template_id = self.templates[name][index]; let clone_id = self.clone_node(template_id, &mut nodes_updated); self.set_element_id(clone_id, id); + self.stack.push(clone_id); } ReplaceWith { id, m } => { let new_nodes = self.stack.split_off(m); @@ -218,7 +236,7 @@ impl RealDom { self.tree.remove(old_node_id); } ReplacePlaceholder { path, m } => { - let new_nodes = self.stack.split_off(m); + let new_nodes = self.stack.split_off(self.stack.len() - m); let old_node_id = self.load_child(path); for new in new_nodes { self.tree.insert_after(old_node_id, new); diff --git a/packages/native-core/src/utils.rs b/packages/native-core/src/utils.rs index 70d016d92..bec658624 100644 --- a/packages/native-core/src/utils.rs +++ b/packages/native-core/src/utils.rs @@ -1,5 +1,6 @@ use crate::{node::NodeType, real_dom::RealDom, state::State, tree::TreeView, NodeId, RealNodeId}; use dioxus_core::{Mutation, Mutations}; +use std::fmt::Debug; pub enum ElementProduced { /// The iterator produced an element by progressing to the next node in a depth first order. diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index 2abebe856..54513268f 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -17,7 +17,7 @@ impl PossiblyUninitalized { pub fn unwrap(self) -> T { match self { Self::Initialized(i) => i, - _ => panic!(), + _ => panic!("uninitalized"), } } pub fn ok(self) -> Option { diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 6b514492f..56a34a047 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -16,7 +16,7 @@ use query::Query; use std::{cell::RefCell, sync::{Mutex, Arc}}; use std::rc::Rc; use std::{io, time::Duration}; -use taffy::Taffy; +use taffy::{Taffy}; pub use taffy::{geometry::Point, prelude::*}; use tui::{backend::CrosstermBackend, layout::Rect, Terminal}; @@ -168,12 +168,13 @@ fn render_vdom( if let Some(terminal) = &mut terminal { terminal.draw(|frame| { let rdom = rdom.borrow(); + let mut taffy =taffy.lock().expect("taffy lock poisoned"); // size is guaranteed to not change when rendering - resize(frame.size(), &mut taffy.lock().expect("taffy lock poisoned"), &rdom); + resize(frame.size(), &mut *taffy, &rdom); let root = &rdom[NodeId(0)]; render::render_vnode( frame, - &taffy.lock().expect("taffy lock poisoned"), + &*taffy, &rdom, root, cfg, From 1587056a4be3b5310be1b72a2a7ea99cd3e69f81 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 5 Dec 2022 13:30:13 -0600 Subject: [PATCH 27/93] some basic tui examples working --- packages/native-core/src/passes.rs | 4 +++- packages/native-core/src/real_dom.rs | 9 ++++++--- packages/native-core/src/state.rs | 4 +--- packages/tui/src/lib.rs | 15 +++++++++------ packages/tui/src/node.rs | 3 ++- 5 files changed, 21 insertions(+), 14 deletions(-) diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index fe774f7ba..bdc7c7e5b 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -215,8 +215,10 @@ fn resolve_downward_pass + ?Sized>( while let Some(id) = dirty.pop_front() { let (node, parent) = tree.node_parent_mut(id).unwrap(); let result = pass.pass(node, parent, ctx); - if result.mark_dirty || result.progress { + if result.mark_dirty { nodes_updated.insert(id); + } + if result.mark_dirty || result.progress { for id in tree.children_ids(id).unwrap() { if result.mark_dirty { for dependant in pass.dependants() { diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 5c4771f0f..7ee8926d4 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -164,7 +164,10 @@ impl RealDom { } /// Updates the dom with some mutations and return a set of nodes that were updated. Pass the dirty nodes to update_state. - pub fn apply_mutations(&mut self, mutations: Mutations) -> DirtyNodeStates { + pub fn apply_mutations( + &mut self, + mutations: Mutations, + ) -> (DirtyNodeStates, FxHashMap) { let mut nodes_updated: FxHashMap = FxHashMap::default(); for template in mutations.templates { let mut template_root_ids = Vec::new(); @@ -348,7 +351,7 @@ impl RealDom { } let dirty_nodes = DirtyNodeStates::default(); - for (n, mask) in nodes_updated { + for (&n, mask) in &nodes_updated { // remove any nodes that were created and then removed in the same mutations from the dirty nodes list if self.tree.contains(n) { for (m, p) in S::MASKS.iter().zip(S::PASSES.iter()) { @@ -359,7 +362,7 @@ impl RealDom { } } - dirty_nodes + (dirty_nodes, nodes_updated) } /// Update the state of the dom, after appling some mutations. This will keep the nodes in the dom up to date with their VNode counterparts. diff --git a/packages/native-core/src/state.rs b/packages/native-core/src/state.rs index 09ae4333b..365aa151c 100644 --- a/packages/native-core/src/state.rs +++ b/packages/native-core/src/state.rs @@ -221,10 +221,8 @@ pub trait State: Default + Clone + 'static { tree: &mut T, ctx: SendAnyMap, ) -> FxDashSet { - let set = FxDashSet::default(); let passes = Self::PASSES.iter().collect(); - resolve_passes(tree, dirty, passes, ctx); - set + resolve_passes(tree, dirty, passes, ctx) } } diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 56a34a047..8b35ac54c 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -6,7 +6,7 @@ use crossterm::{ }; use futures_channel::mpsc::unbounded; use dioxus_core::*; -use dioxus_native_core::{real_dom::RealDom, NodeId, SendAnyMap, FxDashSet}; +use dioxus_native_core::{real_dom::RealDom, NodeId, SendAnyMap, FxDashSet, NodeMask}; use focus::FocusState; use futures::{ channel::mpsc::{UnboundedReceiver, UnboundedSender}, @@ -90,7 +90,7 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) { { let mut rdom = rdom.borrow_mut(); let mutations = dom.rebuild(); - let to_update = rdom.apply_mutations(mutations); + let (to_update,_) = rdom.apply_mutations(mutations); let mut any_map = SendAnyMap::new(); any_map.insert(taffy.clone()); let _to_rerender = rdom.update_state(to_update, any_map); @@ -135,16 +135,14 @@ fn render_vdom( let mut to_rerender =FxDashSet::default(); to_rerender.insert(NodeId(0)); let mut updated = true; - + loop { /* -> render the nodes in the right place with tui/crossterm -> wait for changes -> resolve events -> lazily update the layout and style based on nodes changed - use simd to compare lines for diffing? - todo: lazy re-rendering */ @@ -244,11 +242,16 @@ fn render_vdom( let mutations = vdom.render_immediate(); handler.prune(&mutations, &rdom); // updates the dom's nodes - let to_update = rdom.apply_mutations(mutations); + let (to_update, dirty) = rdom.apply_mutations(mutations); // update the style and layout let mut any_map = SendAnyMap::new(); any_map.insert(taffy.clone()); to_rerender = rdom.update_state(to_update, any_map); + for (id, mask) in dirty { + if mask.overlaps(&NodeMask::new().with_text()) { + to_rerender.insert(id); + } + } } } diff --git a/packages/tui/src/node.rs b/packages/tui/src/node.rs index 33f74a011..26ea4412e 100644 --- a/packages/tui/src/node.rs +++ b/packages/tui/src/node.rs @@ -54,7 +54,8 @@ impl NodeDepState for PreventDefault { dioxus_native_core::node_ref::AttributeMask::Static(&sorted_str_slice!([ "dioxus-prevent-default" ])), - ); + ) + .with_listeners(); fn reduce( &mut self, From 24cb503c09c4b2a670deb86cddad3d51b4cbd68b Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 5 Dec 2022 13:49:55 -0600 Subject: [PATCH 28/93] don't push template nodes onto the stack --- packages/native-core/src/real_dom.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 7ee8926d4..f5fb17603 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -138,26 +138,22 @@ impl RealDom { let child_id = self.create_template_node(child, mutations_vec); self.add_child(node_id, child_id); } - self.stack.push(node_id); node_id } TemplateNode::Text(txt) => { let node_id = self.create_node(Node::new(NodeType::Text { text: txt.to_string(), })); - self.stack.push(node_id); node_id } TemplateNode::Dynamic(_) => { let node_id = self.create_node(Node::new(NodeType::Placeholder)); - self.stack.push(node_id); node_id } TemplateNode::DynamicText(_) => { let node_id = self.create_node(Node::new(NodeType::Text { text: String::new(), })); - self.stack.push(node_id); node_id } } From 9a37da639354334f0c78b313fb3c78bb29e884fc Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 5 Dec 2022 13:53:43 -0600 Subject: [PATCH 29/93] update hover example --- packages/tui/examples/tui_hover.rs | 36 +++++++++++++++--------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/tui/examples/tui_hover.rs b/packages/tui/examples/tui_hover.rs index 4362876d2..2037adae4 100644 --- a/packages/tui/examples/tui_hover.rs +++ b/packages/tui/examples/tui_hover.rs @@ -1,7 +1,7 @@ -use std::{convert::TryInto, sync::Arc}; - use dioxus::{events::MouseData, prelude::*}; -use dioxus_core::UiEvent; +use dioxus_core::Event; +use std::rc::Rc; +use std::{convert::TryInto, sync::Arc}; fn main() { dioxus_tui::launch(app); @@ -12,7 +12,7 @@ fn app(cx: Scope) -> Element { "#".to_string() + &c.iter().map(|c| format!("{c:02X?}")).collect::() } - fn get_brightness(m: Arc) -> i32 { + fn get_brightness(m: &Rc) -> i32 { let b: i32 = m.held_buttons().len().try_into().unwrap(); 127 * b } @@ -32,8 +32,8 @@ fn app(cx: Scope) -> Element { let buttons = use_state(&cx, || "".to_string()); let modifiers = use_state(&cx, || "".to_string()); - let update_data = move |event: UiEvent| { - let mouse_data = event.data; + let update_data = move |event: Event| { + let mouse_data = event.inner(); page_coordinates.set(format!("{:?}", mouse_data.page_coordinates())); element_coordinates.set(format!("{:?}", mouse_data.element_coordinates())); @@ -62,9 +62,9 @@ fn app(cx: Scope) -> Element { justify_content: "center", align_items: "center", background_color: "{q1_color_str}", - onmouseenter: move |m| q1_color.set([get_brightness(m.data), 0, 0]), - onmousedown: move |m| q1_color.set([get_brightness(m.data), 0, 0]), - onmouseup: move |m| q1_color.set([get_brightness(m.data), 0, 0]), + onmouseenter: move |m| q1_color.set([get_brightness(m.inner()), 0, 0]), + onmousedown: move |m| q1_color.set([get_brightness(m.inner()), 0, 0]), + onmouseup: move |m| q1_color.set([get_brightness(m.inner()), 0, 0]), onwheel: move |w| q1_color.set([q1_color[0] + (10.0*w.delta().strip_units().y) as i32, 0, 0]), onmouseleave: move |_| q1_color.set([200; 3]), onmousemove: update_data, @@ -76,9 +76,9 @@ fn app(cx: Scope) -> Element { justify_content: "center", align_items: "center", background_color: "{q2_color_str}", - onmouseenter: move |m| q2_color.set([get_brightness(m.data); 3]), - onmousedown: move |m| q2_color.set([get_brightness(m.data); 3]), - onmouseup: move |m| q2_color.set([get_brightness(m.data); 3]), + onmouseenter: move |m| q2_color.set([get_brightness(m.inner()); 3]), + onmousedown: move |m| q2_color.set([get_brightness(m.inner()); 3]), + onmouseup: move |m| q2_color.set([get_brightness(m.inner()); 3]), onwheel: move |w| q2_color.set([q2_color[0] + (10.0*w.delta().strip_units().y) as i32;3]), onmouseleave: move |_| q2_color.set([200; 3]), onmousemove: update_data, @@ -96,9 +96,9 @@ fn app(cx: Scope) -> Element { justify_content: "center", align_items: "center", background_color: "{q3_color_str}", - onmouseenter: move |m| q3_color.set([0, get_brightness(m.data), 0]), - onmousedown: move |m| q3_color.set([0, get_brightness(m.data), 0]), - onmouseup: move |m| q3_color.set([0, get_brightness(m.data), 0]), + onmouseenter: move |m| q3_color.set([0, get_brightness(m.inner()), 0]), + onmousedown: move |m| q3_color.set([0, get_brightness(m.inner()), 0]), + onmouseup: move |m| q3_color.set([0, get_brightness(m.inner()), 0]), onwheel: move |w| q3_color.set([0, q3_color[1] + (10.0*w.delta().strip_units().y) as i32, 0]), onmouseleave: move |_| q3_color.set([200; 3]), onmousemove: update_data, @@ -110,9 +110,9 @@ fn app(cx: Scope) -> Element { justify_content: "center", align_items: "center", background_color: "{q4_color_str}", - onmouseenter: move |m| q4_color.set([0, 0, get_brightness(m.data)]), - onmousedown: move |m| q4_color.set([0, 0, get_brightness(m.data)]), - onmouseup: move |m| q4_color.set([0, 0, get_brightness(m.data)]), + onmouseenter: move |m| q4_color.set([0, 0, get_brightness(m.inner())]), + onmousedown: move |m| q4_color.set([0, 0, get_brightness(m.inner())]), + onmouseup: move |m| q4_color.set([0, 0, get_brightness(m.inner())]), onwheel: move |w| q4_color.set([0, 0, q4_color[2] + (10.0*w.delta().strip_units().y) as i32]), onmouseleave: move |_| q4_color.set([200; 3]), onmousemove: update_data, From 094012b07d3d45a9fbc61cff582bafecf1f02dab Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 5 Dec 2022 14:04:13 -0600 Subject: [PATCH 30/93] update benchmark --- packages/tui/benches/update.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/tui/benches/update.rs b/packages/tui/benches/update.rs index 8db50a6a2..f122ce8f2 100644 --- a/packages/tui/benches/update.rs +++ b/packages/tui/benches/update.rs @@ -73,7 +73,7 @@ fn Grid(cx: Scope) -> Element { let count = use_state(&cx, || 0); let counts = use_ref(&cx, || vec![0; size * size]); - let ctx: TuiContext = cx.consume_context().unwrap(); + let ctx: &TuiContext = cx.consume_context().unwrap(); if *count.get() + 1 >= (size * size) { ctx.quit(); } else { @@ -88,14 +88,14 @@ fn Grid(cx: Scope) -> Element { }); } - cx.render(rsx! { + render! { div{ width: "100%", height: "100%", flex_direction: "column", (0..size).map(|x| { - cx.render(rsx! { + rsx! { div{ width: "100%", height: "100%", @@ -104,7 +104,7 @@ fn Grid(cx: Scope) -> Element { { let alpha = y as f32*100.0/size as f32 + counts.read()[x*size + y] as f32; let key = format!("{}-{}", x, y); - cx.render(rsx! { + rsx! { Box{ x: x, y: y, @@ -112,15 +112,15 @@ fn Grid(cx: Scope) -> Element { hue: alpha, key: "{key}", } - }) + } } ) } - }) + } } ) } - }) + } } fn app3(cx: Scope) -> Element { From e6a11f50dd21c926648e4eb7255065dd3c37efb5 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 5 Dec 2022 14:20:45 -0600 Subject: [PATCH 31/93] update more examples --- packages/core/src/diff.rs | 3 +-- packages/tui/examples/tui_buttons.rs | 22 +++++++++++----------- packages/tui/examples/tui_color_test.rs | 8 ++++---- packages/tui/examples/tui_margin.rs | 12 ++++++------ 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 5f2bccbb1..28ffb2404 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -167,7 +167,6 @@ impl<'b> VirtualDom { ) { // Replace components that have different render fns if left.render_fn != right.render_fn { - dbg!(&left, &right); let created = self.create_component_node(right_template, right, idx); let head = unsafe { self.scopes[left.scope.get().unwrap().0] @@ -407,7 +406,7 @@ impl<'b> VirtualDom { debug_assert!(!new.is_empty()); debug_assert!(!old.is_empty()); - match dbg!(old.len().cmp(&new.len())) { + match old.len().cmp(&new.len()) { Ordering::Greater => self.remove_nodes(&old[new.len()..]), Ordering::Less => self.create_and_insert_after(&new[old.len()..], old.last().unwrap()), Ordering::Equal => {} diff --git a/packages/tui/examples/tui_buttons.rs b/packages/tui/examples/tui_buttons.rs index 5b9199baa..bd9f3da13 100644 --- a/packages/tui/examples/tui_buttons.rs +++ b/packages/tui/examples/tui_buttons.rs @@ -23,12 +23,12 @@ fn Button(cx: Scope) -> Element { cx.render(rsx! { div{ - width: "100%", - height: "100%", + width: "20px", + height: "5px", background_color: "{color}", tabindex: "{cx.props.layer}", onkeydown: |e| { - if let Code::Space = e.data.code() { + if let Code::Space = e.inner().code() { toggle.modify(|f| !f); } }, @@ -60,7 +60,7 @@ fn app(cx: Scope) -> Element { height: "100%", (1..8).map(|y| - cx.render(rsx!{ + rsx!{ div{ display: "flex", flex_direction: "row", @@ -68,26 +68,26 @@ fn app(cx: Scope) -> Element { height: "100%", (1..8).map(|x|{ if (x + y) % 2 == 0{ - cx.render(rsx!{ + rsx!{ div{ - width: "100%", - height: "100%", + width: "20px", + height: "5px", background_color: "rgb(100, 100, 100)", } - }) + } } else{ let layer = (x + y) % 3; - cx.render(rsx!{ + rsx!{ Button{ color_offset: x * y, layer: layer as u16, } - }) + } } }) } - }) + } ) } }) diff --git a/packages/tui/examples/tui_color_test.rs b/packages/tui/examples/tui_color_test.rs index f3972331e..872fec53e 100644 --- a/packages/tui/examples/tui_color_test.rs +++ b/packages/tui/examples/tui_color_test.rs @@ -17,7 +17,7 @@ fn app(cx: Scope) -> Element { (0..=steps).map(|x| { let hue = x as f32*360.0/steps as f32; - cx.render(rsx! { + rsx! { div{ width: "100%", height: "100%", @@ -25,7 +25,7 @@ fn app(cx: Scope) -> Element { (0..=steps).map(|y| { let alpha = y as f32*100.0/steps as f32; - cx.render(rsx! { + rsx! { div { left: "{x}px", top: "{y}px", @@ -33,11 +33,11 @@ fn app(cx: Scope) -> Element { height: "100%", background_color: "hsl({hue}, 100%, 50%, {alpha}%)", } - }) + } } ) } - }) + } } ) } diff --git a/packages/tui/examples/tui_margin.rs b/packages/tui/examples/tui_margin.rs index d86bc116e..6e5634726 100644 --- a/packages/tui/examples/tui_margin.rs +++ b/packages/tui/examples/tui_margin.rs @@ -11,13 +11,13 @@ fn app(cx: Scope) -> Element { height: "100%", flex_direction: "column", background_color: "black", - // margin_right: "10px", + margin_right: "10px", div { width: "70%", height: "70%", background_color: "green", - // margin_left: "4px", + margin_left: "4px", div { width: "100%", @@ -34,10 +34,10 @@ fn app(cx: Scope) -> Element { align_items: "center", flex_direction: "column", - // padding_top: "2px", - // padding_bottom: "2px", - // padding_left: "4px", - // padding_right: "4px", + padding_top: "2px", + padding_bottom: "2px", + padding_left: "4px", + padding_right: "4px", "[A]" "[A]" From a823b45af94eb4362648e6cbc2ec538821d0c82d Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 5 Dec 2022 20:18:10 -0600 Subject: [PATCH 32/93] fix root node layout --- packages/tui/examples/tui_buttons.rs | 8 ++++---- packages/tui/src/layout.rs | 7 ------- packages/tui/src/lib.rs | 22 ++++++++++++++++------ 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/packages/tui/examples/tui_buttons.rs b/packages/tui/examples/tui_buttons.rs index bd9f3da13..852dd1d94 100644 --- a/packages/tui/examples/tui_buttons.rs +++ b/packages/tui/examples/tui_buttons.rs @@ -23,8 +23,8 @@ fn Button(cx: Scope) -> Element { cx.render(rsx! { div{ - width: "20px", - height: "5px", + width: "100%", + height: "100%", background_color: "{color}", tabindex: "{cx.props.layer}", onkeydown: |e| { @@ -70,8 +70,8 @@ fn app(cx: Scope) -> Element { if (x + y) % 2 == 0{ rsx!{ div{ - width: "20px", - height: "5px", + width: "100%", + height: "100%", background_color: "rgb(100, 100, 100)", } } diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index 54513268f..1e14f159c 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -1,6 +1,5 @@ use std::sync::{Arc, Mutex}; -use dioxus_core::*; use dioxus_native_core::layout_attributes::apply_layout_attributes; use dioxus_native_core::node::OwnedAttributeView; use dioxus_native_core::node_ref::{AttributeMask, NodeMask, NodeView}; @@ -98,12 +97,6 @@ impl ChildDepState for TaffyLayout { } } - // the root node fills the entire area - if node.id() == Some(ElementId(0)) { - apply_layout_attributes("width", "100%", &mut style); - apply_layout_attributes("height", "100%", &mut style); - } - // Set all direct nodes as our children let mut child_layout = vec![]; for (l,) in children { diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 8b35ac54c..e3ac36ba6 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -152,14 +152,24 @@ fn render_vdom( let width = dims.width; let height = dims.height; let root_node = rdom[NodeId(0)].state.layout.node.unwrap(); - - taffy + + // the root node fills the entire area + + let mut style=*taffy.style(root_node).unwrap(); + style.size=Size { + width: Dimension::Points(width as f32), + height: Dimension::Points(height as f32), + }; + taffy.set_style(root_node, style).unwrap(); + + let size =Size { + width: AvailableSpace::Definite(width as f32), + height: AvailableSpace::Definite(height as f32), + }; + taffy .compute_layout( root_node, - Size { - width: AvailableSpace::Definite((width - 1) as f32), - height: AvailableSpace::Definite((height - 1) as f32), - }, + size ) .unwrap(); } From 57ef56511cd9a15e3aa865922cafb17001bb3deb Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 5 Dec 2022 20:30:05 -0600 Subject: [PATCH 33/93] ignore out of bounds renders --- packages/tui/src/lib.rs | 4 ++-- packages/tui/src/render.rs | 15 +++++++++++---- packages/tui/src/widget.rs | 5 +++-- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index e3ac36ba6..cb000f00e 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -158,12 +158,12 @@ fn render_vdom( let mut style=*taffy.style(root_node).unwrap(); style.size=Size { width: Dimension::Points(width as f32), - height: Dimension::Points(height as f32), + height: Dimension::Points(height as f32), }; taffy.set_style(root_node, style).unwrap(); let size =Size { - width: AvailableSpace::Definite(width as f32), + width: AvailableSpace::Definite(width as f32), height: AvailableSpace::Definite(height as f32), }; taffy diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index 7beb543e2..d779418fa 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -36,8 +36,15 @@ pub(crate) fn render_vnode( location.x += parent_location.x; location.y += parent_location.y; - let Point { x, y } = location; - let Size { width, height } = size; + let Point { mut x, mut y } = location; + x = x.floor(); + y = y.floor(); + let Size { + mut width, + mut height, + } = size; + width = width.ceil(); + height = height.ceil(); match &node.node_data.node_type { NodeType::Text { text } => { @@ -62,7 +69,7 @@ pub(crate) fn render_vnode( text, style: node.state.style.core, }; - let area = Rect::new(x as u16, y as u16, *width as u16, *height as u16); + let area = Rect::new(x as u16, y as u16, width as u16, height as u16); // the renderer will panic if a node is rendered out of range even if the size is zero if area.width > 0 && area.height > 0 { @@ -70,7 +77,7 @@ pub(crate) fn render_vnode( } } NodeType::Element { .. } => { - let area = Rect::new(x as u16, y as u16, *width as u16, *height as u16); + let area = Rect::new(x as u16, y as u16, width as u16, height as u16); // the renderer will panic if a node is rendered out of range even if the size is zero if area.width > 0 && area.height > 0 { diff --git a/packages/tui/src/widget.rs b/packages/tui/src/widget.rs index 416538462..7c6645692 100644 --- a/packages/tui/src/widget.rs +++ b/packages/tui/src/widget.rs @@ -22,8 +22,9 @@ impl<'a> RinkBuffer<'a> { pub fn set(&mut self, x: u16, y: u16, new: RinkCell) { let area = self.buf.area(); - if x < area.x || x > area.width || y < area.y || y > area.height { - panic!("({x}, {y}) is not in {area:?}"); + if x < area.x || x >= area.width + area.x || y < area.y || y >= area.height + area.y { + // panic!("({x}, {y}) is not in {area:?}"); + return; } let mut cell = self.buf.get_mut(x, y); cell.bg = convert(self.cfg.rendering_mode, new.bg.blend(cell.bg)); From f10d47b53d3360bf86f62e91ccae93ccd7532455 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 5 Dec 2022 20:37:55 -0600 Subject: [PATCH 34/93] update color picker example --- packages/tui/examples/tui_colorpicker.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/tui/examples/tui_colorpicker.rs b/packages/tui/examples/tui_colorpicker.rs index 553a37aa4..3b1184930 100644 --- a/packages/tui/examples/tui_colorpicker.rs +++ b/packages/tui/examples/tui_colorpicker.rs @@ -1,5 +1,5 @@ -use dioxus::core_macro::rsx_without_templates; use dioxus::prelude::*; +use dioxus::core::RenderReturn; use dioxus_tui::query::Query; use dioxus_tui::Size; @@ -10,18 +10,20 @@ fn main() { fn app(cx: Scope) -> Element { let hue = use_state(&cx, || 0.0); let brightness = use_state(&cx, || 0.0); - let tui_query: Query = cx.consume_context().unwrap(); + let tui_query: &Query = cx.consume_context().unwrap(); // disable templates so that every node has an id and can be queried - cx.render(rsx_without_templates! { + cx.render(rsx! { div{ width: "100%", background_color: "hsl({hue}, 70%, {brightness}%)", onmousemove: move |evt| { - let node = tui_query.get(cx.root_node().mounted_id()); - let Size{width, height} = node.size().unwrap(); - let pos = evt.data.element_coordinates(); - hue.set((pos.x as f32/width as f32)*255.0); - brightness.set((pos.y as f32/height as f32)*100.0); + if let RenderReturn::Sync(Ok(node))=cx.root_node(){ + let node = tui_query.get(node.root_ids[0].get()); + let Size{width, height} = node.size().unwrap(); + let pos = evt.inner().element_coordinates(); + hue.set((pos.x as f32/width as f32)*255.0); + brightness.set((pos.y as f32/height as f32)*100.0); + } }, "hsl({hue}, 70%, {brightness}%)", } From b1db7a6400e44b365a1b373714f453bf102c88ca Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 5 Dec 2022 20:43:11 -0600 Subject: [PATCH 35/93] update all events example --- packages/tui/examples/tui_all_events.rs | 50 ++++++++++++------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/tui/examples/tui_all_events.rs b/packages/tui/examples/tui_all_events.rs index ffa03e039..6995cf39d 100644 --- a/packages/tui/examples/tui_all_events.rs +++ b/packages/tui/examples/tui_all_events.rs @@ -1,6 +1,6 @@ use dioxus::prelude::*; -use dioxus_html::on::{FocusData, KeyboardData, MouseData, WheelData}; -use std::sync::Arc; +use dioxus_html::{FocusData, KeyboardData, MouseData, WheelData}; +use std::rc::Rc; fn main() { dioxus_tui::launch(app); @@ -8,20 +8,20 @@ fn main() { #[derive(Debug)] enum Event { - MouseMove(Arc), - MouseClick(Arc), - MouseDoubleClick(Arc), - MouseDown(Arc), - MouseUp(Arc), + MouseMove(Rc), + MouseClick(Rc), + MouseDoubleClick(Rc), + MouseDown(Rc), + MouseUp(Rc), - Wheel(Arc), + Wheel(Rc), - KeyDown(Arc), - KeyUp(Arc), - KeyPress(Arc), + KeyDown(Rc), + KeyUp(Rc), + KeyPress(Rc), - FocusIn(Arc), - FocusOut(Arc), + FocusIn(Rc), + FocusOut(Rc), } const MAX_EVENTS: usize = 8; @@ -37,7 +37,7 @@ fn app(cx: Scope) -> Element { // todo: remove let mut trimmed = format!("{event:?}"); trimmed.truncate(200); - cx.render(rsx!(p { "{trimmed}" })) + rsx!(p { "{trimmed}" }) }); let log_event = move |event: Event| { @@ -57,20 +57,20 @@ fn app(cx: Scope) -> Element { align_items: "center", background_color: "hsl(248, 53%, 58%)", - onmousemove: move |event| log_event(Event::MouseMove(event.data)), - onclick: move |event| log_event(Event::MouseClick(event.data)), - ondblclick: move |event| log_event(Event::MouseDoubleClick(event.data)), - onmousedown: move |event| log_event(Event::MouseDown(event.data)), - onmouseup: move |event| log_event(Event::MouseUp(event.data)), + onmousemove: move |event| log_event(Event::MouseMove(event.inner().clone())), + onclick: move |event| log_event(Event::MouseClick(event.inner().clone())), + ondblclick: move |event| log_event(Event::MouseDoubleClick(event.inner().clone())), + onmousedown: move |event| log_event(Event::MouseDown(event.inner().clone())), + onmouseup: move |event| log_event(Event::MouseUp(event.inner().clone())), - onwheel: move |event| log_event(Event::Wheel(event.data)), + onwheel: move |event| log_event(Event::Wheel(event.inner().clone())), - onkeydown: move |event| log_event(Event::KeyDown(event.data)), - onkeyup: move |event| log_event(Event::KeyUp(event.data)), - onkeypress: move |event| log_event(Event::KeyPress(event.data)), + onkeydown: move |event| log_event(Event::KeyDown(event.inner().clone())), + onkeyup: move |event| log_event(Event::KeyUp(event.inner().clone())), + onkeypress: move |event| log_event(Event::KeyPress(event.inner().clone())), - onfocusin: move |event| log_event(Event::FocusIn(event.data)), - onfocusout: move |event| log_event(Event::FocusOut(event.data)), + onfocusin: move |event| log_event(Event::FocusIn(event.inner().clone())), + onfocusout: move |event| log_event(Event::FocusOut(event.inner().clone())), "Hover, click, type or scroll to see the info down below" }, From 317aed734365796b53577b4930a9bd76425ad2cf Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 5 Dec 2022 20:45:08 -0600 Subject: [PATCH 36/93] update remaining examples --- packages/tui/examples/tui_text.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tui/examples/tui_text.rs b/packages/tui/examples/tui_text.rs index 5268c59f6..dfbf6e4d2 100644 --- a/packages/tui/examples/tui_text.rs +++ b/packages/tui/examples/tui_text.rs @@ -12,7 +12,7 @@ fn app(cx: Scope) -> Element { width: "100%", height: "100%", flex_direction: "column", - onwheel: move |evt| alpha.set((**alpha + evt.data.delta().strip_units().y as i64).min(100).max(0)), + onwheel: move |evt| alpha.set((**alpha + evt.inner().delta().strip_units().y as i64).min(100).max(0)), p { background_color: "black", From ad33f2ce96f191babc16a45fc84eb6de84749084 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 5 Dec 2022 21:36:24 -0600 Subject: [PATCH 37/93] update tests --- packages/native-core-macro/src/lib.rs | 9 +- .../tests/called_minimally_on_build.rs | 265 +++--- .../native-core-macro/tests/change_nodes.rs | 79 -- .../native-core-macro/tests/initial_build.rs | 63 -- .../tests/persistent_iterator.rs | 585 ++++++------ .../native-core-macro/tests/update_state.rs | 892 ++++++++---------- packages/native-core/src/utils.rs | 21 +- 7 files changed, 833 insertions(+), 1081 deletions(-) delete mode 100644 packages/native-core-macro/tests/change_nodes.rs delete mode 100644 packages/native-core-macro/tests/initial_build.rs diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index 6c0cdb2d0..dda96b512 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -189,7 +189,7 @@ impl Struct { let members = fields .iter() .enumerate() - .filter_map(|(i, f)| Member::parse(f, i as u64)) + .filter_map(|(i, f)| Member::parse(&name, f, i as u64)) .collect(); Self { name, members } } @@ -296,12 +296,15 @@ struct Member { } impl Member { - fn parse(field: &Field, id: u64) -> Option { + fn parse(parent: &Ident, field: &Field, id: u64) -> Option { Some(Self { id, ty: field.ty.clone(), unit_type: Ident::new( - ("_Unit".to_string() + field.ty.to_token_stream().to_string().as_str()).as_str(), + ("_Unit".to_string() + + parent.to_token_stream().to_string().as_str() + + field.ty.to_token_stream().to_string().as_str()) + .as_str(), Span::call_site(), ) .into(), diff --git a/packages/native-core-macro/tests/called_minimally_on_build.rs b/packages/native-core-macro/tests/called_minimally_on_build.rs index 978ad8f5f..84e4fb0e6 100644 --- a/packages/native-core-macro/tests/called_minimally_on_build.rs +++ b/packages/native-core-macro/tests/called_minimally_on_build.rs @@ -71,48 +71,33 @@ macro_rules! test_state{ fn state_reduce_initally_called_minimally() { #[allow(non_snake_case)] fn Base(cx: Scope) -> Element { - render!(div { - div{ + render!{ + div { div{ - p{} - } - p{ - "hello" - } - div{ - h1{} - } - p{ - "world" - } - } - }) - } - - let vdom = VirtualDom::new(Base); - - let mutations = vdom.create_vnodes(rsx! { - div { - div{ - div{ - p{} - } - p{ - "hello" - } - div{ - h1{} - } - p{ - "world" + div{ + p{} + } + p{ + "hello" + } + div{ + h1{} + } + p{ + "world" + } } } } - }); + } + + let mut vdom = VirtualDom::new(Base); + + let mutations = vdom.rebuild(); let mut dom: RealDom<$s> = RealDom::new(); - let nodes_updated = dom.apply_mutations(vec![mutations]); + let (nodes_updated, _) = dom.apply_mutations(mutations); let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::new()); dom.traverse_depth_first(|n| { @@ -130,131 +115,131 @@ macro_rules! test_state{ } } -// mod node_depends_on_child_and_parent { -// use super::*; -// #[derive(Debug, Clone, Default, PartialEq)] -// struct Node(i32); -// dep!(node(Node, (Child, Parent))); +mod node_depends_on_child_and_parent { + use super::*; + #[derive(Debug, Clone, Default, PartialEq)] + struct Node(i32); + dep!(node(Node, (Child, Parent))); -// #[derive(Debug, Clone, Default, PartialEq)] -// struct Child(i32); -// dep!(child(Child, (Child,))); + #[derive(Debug, Clone, Default, PartialEq)] + struct Child(i32); + dep!(child(Child, (Child,))); -// #[derive(Debug, Clone, Default, PartialEq)] -// struct Parent(i32); -// dep!(parent(Parent, (Parent,))); + #[derive(Debug, Clone, Default, PartialEq)] + struct Parent(i32); + dep!(parent(Parent, (Parent,))); -// #[derive(Debug, Clone, Default, State)] -// struct StateTester { -// #[node_dep_state((child, parent))] -// node: Node, -// #[child_dep_state(child)] -// child: Child, -// #[parent_dep_state(parent)] -// parent: Parent, -// } + #[derive(Debug, Clone, Default, State)] + struct StateTester { + #[node_dep_state((child, parent))] + node: Node, + #[child_dep_state(child)] + child: Child, + #[parent_dep_state(parent)] + parent: Parent, + } -// test_state!(StateTester, child: (child), node: (node), parent: (parent)); -// } + test_state!(StateTester, child: (child), node: (node), parent: (parent)); +} -// mod child_depends_on_node_that_depends_on_parent { -// use super::*; -// #[derive(Debug, Clone, Default, PartialEq)] -// struct Node(i32); -// dep!(node(Node, (Parent,))); +mod child_depends_on_node_that_depends_on_parent { + use super::*; + #[derive(Debug, Clone, Default, PartialEq)] + struct Node(i32); + dep!(node(Node, (Parent,))); -// #[derive(Debug, Clone, Default, PartialEq)] -// struct Child(i32); -// dep!(child(Child, (Node,))); + #[derive(Debug, Clone, Default, PartialEq)] + struct Child(i32); + dep!(child(Child, (Node,))); -// #[derive(Debug, Clone, Default, PartialEq)] -// struct Parent(i32); -// dep!(parent(Parent, (Parent,))); + #[derive(Debug, Clone, Default, PartialEq)] + struct Parent(i32); + dep!(parent(Parent, (Parent,))); -// #[derive(Debug, Clone, Default, State)] -// struct StateTester { -// #[node_dep_state(parent)] -// node: Node, -// #[child_dep_state(node)] -// child: Child, -// #[parent_dep_state(parent)] -// parent: Parent, -// } + #[derive(Debug, Clone, Default, State)] + struct StateTester { + #[node_dep_state(parent)] + node: Node, + #[child_dep_state(node)] + child: Child, + #[parent_dep_state(parent)] + parent: Parent, + } -// test_state!(StateTester, child: (child), node: (node), parent: (parent)); -// } + test_state!(StateTester, child: (child), node: (node), parent: (parent)); +} -// mod parent_depends_on_node_that_depends_on_child { -// use super::*; -// #[derive(Debug, Clone, Default, PartialEq)] -// struct Node(i32); -// dep!(node(Node, (Child,))); +mod parent_depends_on_node_that_depends_on_child { + use super::*; + #[derive(Debug, Clone, Default, PartialEq)] + struct Node(i32); + dep!(node(Node, (Child,))); -// #[derive(Debug, Clone, Default, PartialEq)] -// struct Child(i32); -// dep!(child(Child, (Child,))); + #[derive(Debug, Clone, Default, PartialEq)] + struct Child(i32); + dep!(child(Child, (Child,))); -// #[derive(Debug, Clone, Default, PartialEq)] -// struct Parent(i32); -// dep!(parent(Parent, (Node,))); + #[derive(Debug, Clone, Default, PartialEq)] + struct Parent(i32); + dep!(parent(Parent, (Node,))); -// #[derive(Debug, Clone, Default, State)] -// struct StateTester { -// #[node_dep_state(child)] -// node: Node, -// #[child_dep_state(child)] -// child: Child, -// #[parent_dep_state(node)] -// parent: Parent, -// } + #[derive(Debug, Clone, Default, State)] + struct StateTester { + #[node_dep_state(child)] + node: Node, + #[child_dep_state(child)] + child: Child, + #[parent_dep_state(node)] + parent: Parent, + } -// test_state!(StateTester, child: (child), node: (node), parent: (parent)); -// } + test_state!(StateTester, child: (child), node: (node), parent: (parent)); +} -// mod node_depends_on_other_node_state { -// use super::*; -// #[derive(Debug, Clone, Default, PartialEq)] -// struct Node1(i32); -// dep!(node(Node1, (Node2,))); +mod node_depends_on_other_node_state { + use super::*; + #[derive(Debug, Clone, Default, PartialEq)] + struct Node1(i32); + dep!(node(Node1, (Node2,))); -// #[derive(Debug, Clone, Default, PartialEq)] -// struct Node2(i32); -// dep!(node(Node2, ())); + #[derive(Debug, Clone, Default, PartialEq)] + struct Node2(i32); + dep!(node(Node2, ())); -// #[derive(Debug, Clone, Default, State)] -// struct StateTester { -// #[node_dep_state((node2))] -// node1: Node1, -// #[node_dep_state()] -// node2: Node2, -// } + #[derive(Debug, Clone, Default, State)] + struct StateTester { + #[node_dep_state((node2))] + node1: Node1, + #[node_dep_state()] + node2: Node2, + } -// test_state!(StateTester, child: (), node: (node1, node2), parent: ()); -// } + test_state!(StateTester, child: (), node: (node1, node2), parent: ()); +} -// mod node_child_and_parent_state_depends_on_self { -// use super::*; -// #[derive(Debug, Clone, Default, PartialEq)] -// struct Node(i32); -// dep!(node(Node, ())); +mod node_child_and_parent_state_depends_on_self { + use super::*; + #[derive(Debug, Clone, Default, PartialEq)] + struct Node(i32); + dep!(node(Node, ())); -// #[derive(Debug, Clone, Default, PartialEq)] -// struct Child(i32); -// dep!(child(Child, (Child,))); + #[derive(Debug, Clone, Default, PartialEq)] + struct Child(i32); + dep!(child(Child, (Child,))); -// #[derive(Debug, Clone, Default, PartialEq)] -// struct Parent(i32); -// dep!(parent(Parent, (Parent,))); + #[derive(Debug, Clone, Default, PartialEq)] + struct Parent(i32); + dep!(parent(Parent, (Parent,))); -// #[derive(Debug, Clone, Default, State)] -// struct StateTester { -// #[node_dep_state()] -// node: Node, -// #[child_dep_state(child)] -// child: Child, -// #[parent_dep_state(parent)] -// parent: Parent, -// } + #[derive(Debug, Clone, Default, State)] + struct StateTester { + #[node_dep_state()] + node: Node, + #[child_dep_state(child)] + child: Child, + #[parent_dep_state(parent)] + parent: Parent, + } -// test_state!(StateTester, child: (child), node: (node), parent: (parent)); -// } + test_state!(StateTester, child: (child), node: (node), parent: (parent)); +} diff --git a/packages/native-core-macro/tests/change_nodes.rs b/packages/native-core-macro/tests/change_nodes.rs deleted file mode 100644 index af3cbefb8..000000000 --- a/packages/native-core-macro/tests/change_nodes.rs +++ /dev/null @@ -1,79 +0,0 @@ -// use dioxus::core::ElementId; -// use dioxus::prelude::*; -// use dioxus_native_core::real_dom::RealDom; -// use dioxus_native_core::state::State; -// use dioxus_native_core::tree::*; -// use dioxus_native_core::NodeId; -// use dioxus_native_core_macro::State; - -// #[derive(State, Default, Clone)] -// struct Empty {} - -// #[test] -// fn remove_node() { -// #[allow(non_snake_case)] -// fn Base(cx: Scope) -> Element { -// render!(div {}) -// } - -// let vdom = VirtualDom::new(Base); - -// let mut dom: RealDom = RealDom::new(); -// let (create, edit) = vdom.diff_lazynodes( -// rsx! { -// div{ -// div{} -// } -// }, -// rsx! { -// div{} -// }, -// ); - -// println!("create: {:#?}", create); -// println!("edit: {:#?}", edit); - -// let _to_update = dom.apply_mutations(vec![create]); - -// assert_eq!(dom.tree.height(NodeId(0)), Some(0)); -// assert_eq!(dom.tree.height(NodeId(1)), Some(1)); - -// dom.apply_mutations(vec![edit]); - -// assert_eq!(dom.size(), 3); -// assert_eq!(dom.tree.height(NodeId(0)), Some(0)); -// } - -// #[test] -// fn add_node() { -// #[allow(non_snake_case)] -// fn Base(cx: Scope) -> Element { -// render!(div {}) -// } - -// let vdom = VirtualDom::new(Base); - -// let (create, update) = vdom.diff_lazynodes( -// rsx! { -// div{} -// }, -// rsx! { -// div{ -// p{} -// } -// }, -// ); - -// let mut dom: RealDom = RealDom::new(); - -// let _to_update = dom.apply_mutations(vec![create]); - -// assert_eq!(dom.size(), 2); -// assert_eq!(dom.tree.height(NodeId(2)), Some(1)); - -// dom.apply_mutations(vec![update]); - -// assert_eq!(dom.size(), 3); -// assert_eq!(dom.tree.height(NodeId(3)), Some(0)); -// assert_eq!(dom.tree.height(NodeId(0)), Some(1)); -// } diff --git a/packages/native-core-macro/tests/initial_build.rs b/packages/native-core-macro/tests/initial_build.rs deleted file mode 100644 index 86605aca9..000000000 --- a/packages/native-core-macro/tests/initial_build.rs +++ /dev/null @@ -1,63 +0,0 @@ -// use dioxus::core::ElementId; -// use dioxus::prelude::*; -// use dioxus_native_core::real_dom::RealDom; -// use dioxus_native_core::state::State; -// use dioxus_native_core::RealNodeId; -// use dioxus_native_core_macro::State; - -// #[derive(Default, Clone, State)] -// struct Empty {} - -// #[test] -// fn initial_build_simple() { -// #[allow(non_snake_case)] -// fn Base(cx: Scope) -> Element { -// render!(div {}) -// } - -// let vdom = VirtualDom::new(Base); - -// let mutations = vdom.create_vnodes(rsx! { -// div{} -// }); - -// let mut dom: RealDom = RealDom::new(); - -// let _to_update = dom.apply_mutations(vec![mutations]); - -// assert_eq!(dom.size(), 2); -// assert_eq!(dom[RealNodeId::ElementId(ElementId(2))].node_data.height, 1); -// } - -// #[test] -// fn initial_build_with_children() { -// #[allow(non_snake_case)] -// fn Base(cx: Scope) -> Element { -// render!(div {}) -// } - -// let vdom = VirtualDom::new(Base); - -// let mutations = vdom.create_vnodes(rsx! { -// div{ -// div{ -// "hello" -// p{ -// "world" -// } -// "hello world" -// } -// } -// }); - -// let mut dom: RealDom = RealDom::new(); - -// let _to_update = dom.apply_mutations(vec![mutations]); -// assert_eq!(dom.size(), 2); -// assert_eq!(dom[RealNodeId::ElementId(ElementId(2))].node_data.height, 1); -// assert_eq!(dom[RealNodeId::UnaccessableId(6)].node_data.height, 2); -// assert_eq!(dom[RealNodeId::UnaccessableId(5)].node_data.height, 3); -// assert_eq!(dom[RealNodeId::UnaccessableId(8)].node_data.height, 3); -// assert_eq!(dom[RealNodeId::UnaccessableId(10)].node_data.height, 3); -// assert_eq!(dom[RealNodeId::UnaccessableId(9)].node_data.height, 4); -// } diff --git a/packages/native-core-macro/tests/persistent_iterator.rs b/packages/native-core-macro/tests/persistent_iterator.rs index b1de9392e..c35c6ab0a 100644 --- a/packages/native-core-macro/tests/persistent_iterator.rs +++ b/packages/native-core-macro/tests/persistent_iterator.rs @@ -1,333 +1,298 @@ -// use dioxus::core_macro::rsx_without_templates; -// use dioxus::prelude::*; -// use dioxus_native_core::{ -// real_dom::{NodeType, RealDom}, -// state::State, -// utils::PersistantElementIter, -// }; -// use dioxus_native_core_macro::State; +use dioxus::prelude::*; +use dioxus_native_core::{ + real_dom::{ RealDom}, + node::NodeType, + state::State, + utils::PersistantElementIter, +}; +use dioxus_native_core_macro::State; -// #[derive(State, Default, Clone)] -// struct Empty {} +#[derive(State, Default, Clone, Debug)] +struct Empty {} -// #[test] -// #[allow(unused_variables)] -// fn traverse() { -// #[allow(non_snake_case)] -// fn Base(cx: Scope) -> Element { -// render!(div {}) -// } -// let vdom = VirtualDom::new(Base); -// let mutations = vdom.create_vnodes(rsx! { -// div{ -// div{ -// "hello" -// p{ -// "world" -// } -// "hello world" -// } -// } -// }); +#[test] +#[allow(unused_variables)] +fn traverse() { + #[allow(non_snake_case)] + fn Base(cx: Scope) -> Element { + render!( + div{ + div{ + "hello" + p{ + "world" + } + "hello world" + } + } + ) + } -// let mut rdom: RealDom = RealDom::new(); + let mut vdom = VirtualDom::new(Base); + let mutations = vdom.rebuild(); -// let _to_update = rdom.apply_mutations(vec![mutations]); + let mut rdom: RealDom = RealDom::new(); -// let mut iter = PersistantElementIter::new(); -// let div_tag = "div".to_string(); -// assert!(matches!( -// &rdom[iter.next(&rdom).id()].node_data.node_type, -// NodeType::Element { tag: div_tag, .. } -// )); -// assert!(matches!( -// &rdom[iter.next(&rdom).id()].node_data.node_type, -// NodeType::Element { tag: div_tag, .. } -// )); -// let text1 = "hello".to_string(); -// assert!(matches!( -// &rdom[iter.next(&rdom).id()].node_data.node_type, -// NodeType::Text { text: text1, .. } -// )); -// let p_tag = "p".to_string(); -// assert!(matches!( -// &rdom[iter.next(&rdom).id()].node_data.node_type, -// NodeType::Element { tag: p_tag, .. } -// )); -// let text2 = "world".to_string(); -// assert!(matches!( -// &rdom[iter.next(&rdom).id()].node_data.node_type, -// NodeType::Text { text: text2, .. } -// )); -// let text3 = "hello world".to_string(); -// assert!(matches!( -// &rdom[iter.next(&rdom).id()].node_data.node_type, -// NodeType::Text { text: text3, .. } -// )); -// assert!(matches!( -// &rdom[iter.next(&rdom).id()].node_data.node_type, -// NodeType::Element { tag: div_tag, .. } -// )); + let _to_update = rdom.apply_mutations(mutations); -// assert!(matches!( -// &rdom[iter.prev(&rdom).id()].node_data.node_type, -// NodeType::Text { text: text3, .. } -// )); -// assert!(matches!( -// &rdom[iter.prev(&rdom).id()].node_data.node_type, -// NodeType::Text { text: text2, .. } -// )); -// assert!(matches!( -// &rdom[iter.prev(&rdom).id()].node_data.node_type, -// NodeType::Element { tag: p_tag, .. } -// )); -// assert!(matches!( -// &rdom[iter.prev(&rdom).id()].node_data.node_type, -// NodeType::Text { text: text1, .. } -// )); -// assert!(matches!( -// &rdom[iter.prev(&rdom).id()].node_data.node_type, -// NodeType::Element { tag: div_tag, .. } -// )); -// assert!(matches!( -// &rdom[iter.prev(&rdom).id()].node_data.node_type, -// NodeType::Element { tag: div_tag, .. } -// )); -// assert!(matches!( -// &rdom[iter.prev(&rdom).id()].node_data.node_type, -// NodeType::Element { tag: div_tag, .. } -// )); -// assert!(matches!( -// &rdom[iter.prev(&rdom).id()].node_data.node_type, -// NodeType::Text { text: text3, .. } -// )); -// } + let mut iter = PersistantElementIter::new(); + let div_tag = "div".to_string(); + assert!(matches!( + &rdom[iter.next(&rdom).id()].node_data.node_type, + NodeType::Element { tag: div_tag, .. } + )); + assert!(matches!( + &rdom[iter.next(&rdom).id()].node_data.node_type, + NodeType::Element { tag: div_tag, .. } + )); + let text1 = "hello".to_string(); + assert!(matches!( + &rdom[iter.next(&rdom).id()].node_data.node_type, + NodeType::Text { text: text1, .. } + )); + let p_tag = "p".to_string(); + assert!(matches!( + &rdom[iter.next(&rdom).id()].node_data.node_type, + NodeType::Element { tag: p_tag, .. } + )); + let text2 = "world".to_string(); + assert!(matches!( + &rdom[iter.next(&rdom).id()].node_data.node_type, + NodeType::Text { text: text2, .. } + )); + let text3 = "hello world".to_string(); + assert!(matches!( + &rdom[iter.next(&rdom).id()].node_data.node_type, + NodeType::Text { text: text3, .. } + )); + assert!(matches!( + &rdom[iter.next(&rdom).id()].node_data.node_type, + NodeType::Element { tag: div_tag, .. } + )); -// #[test] -// #[allow(unused_variables)] -// fn persist_removes() { -// #[allow(non_snake_case)] -// fn Base(cx: Scope) -> Element { -// render!(div {}) -// } -// let vdom = VirtualDom::new(Base); -// let (build, update) = vdom.diff_lazynodes( -// rsx_without_templates! { -// div{ -// p{ -// key: "1", -// "hello" -// } -// p{ -// key: "2", -// "world" -// } -// p{ -// key: "3", -// "hello world" -// } -// } -// }, -// rsx_without_templates! { -// div{ -// p{ -// key: "1", -// "hello" -// } -// p{ -// key: "3", -// "hello world" -// } -// } -// }, -// ); + assert!(matches!( + &rdom[iter.prev(&rdom).id()].node_data.node_type, + NodeType::Text { text: text3, .. } + )); + assert!(matches!( + &rdom[iter.prev(&rdom).id()].node_data.node_type, + NodeType::Text { text: text2, .. } + )); + assert!(matches!( + &rdom[iter.prev(&rdom).id()].node_data.node_type, + NodeType::Element { tag: p_tag, .. } + )); + assert!(matches!( + &rdom[iter.prev(&rdom).id()].node_data.node_type, + NodeType::Text { text: text1, .. } + )); + assert!(matches!( + &rdom[iter.prev(&rdom).id()].node_data.node_type, + NodeType::Element { tag: div_tag, .. } + )); + assert!(matches!( + &rdom[iter.prev(&rdom).id()].node_data.node_type, + NodeType::Element { tag: div_tag, .. } + )); + assert!(matches!( + &rdom[iter.prev(&rdom).id()].node_data.node_type, + NodeType::Element { tag: div_tag, .. } + )); + assert!(matches!( + &rdom[iter.prev(&rdom).id()].node_data.node_type, + NodeType::Text { text: text3, .. } + )); +} -// let mut rdom: RealDom = RealDom::new(); +#[test] +#[allow(unused_variables)] +fn persist_removes() { + #[allow(non_snake_case)] + fn Base(cx: Scope) -> Element { + let children = match cx.generation()%2{ + 0=>3, + 1=>2, + _ => unreachable!() + }; + render!( + div{ + (0..children).map(|i|{ + rsx!{ + p{ + key: "{i}", + "{i}" + } + } + }) + } + ) + } + let mut vdom = VirtualDom::new(Base); -// let _to_update = rdom.apply_mutations(vec![build]); + let mut rdom: RealDom = RealDom::new(); -// // this will end on the node that is removed -// let mut iter1 = PersistantElementIter::new(); -// // this will end on the after node that is removed -// let mut iter2 = PersistantElementIter::new(); -// // div -// iter1.next(&rdom).id(); -// iter2.next(&rdom).id(); -// // p -// iter1.next(&rdom).id(); -// iter2.next(&rdom).id(); -// // "hello" -// iter1.next(&rdom).id(); -// iter2.next(&rdom).id(); -// // p -// iter1.next(&rdom).id(); -// iter2.next(&rdom).id(); -// // "world" -// iter1.next(&rdom).id(); -// iter2.next(&rdom).id(); -// // p -// iter2.next(&rdom).id(); -// // "hello world" -// iter2.next(&rdom).id(); + let build = vdom.rebuild(); + let _to_update = rdom.apply_mutations(build); -// iter1.prune(&update, &rdom); -// iter2.prune(&update, &rdom); -// let _to_update = rdom.apply_mutations(vec![update]); + // this will end on the node that is removed + let mut iter1 = PersistantElementIter::new(); + // this will end on the after node that is removed + let mut iter2 = PersistantElementIter::new(); + // div + iter1.next(&rdom).id(); + iter2.next(&rdom).id(); + // p + iter1.next(&rdom).id(); + iter2.next(&rdom).id(); + // "1" + iter1.next(&rdom).id(); + iter2.next(&rdom).id(); + // p + iter1.next(&rdom).id(); + iter2.next(&rdom).id(); + // "2" + iter1.next(&rdom).id(); + iter2.next(&rdom).id(); + // p + iter2.next(&rdom).id(); + // "3" + iter2.next(&rdom).id(); -// let p_tag = "p".to_string(); -// let idx = iter1.next(&rdom).id(); -// assert!(matches!( -// &rdom[idx].node_data.node_type, -// NodeType::Element { tag: p_tag, .. } -// )); -// let text = "hello world".to_string(); -// let idx = iter1.next(&rdom).id(); -// assert!(matches!( -// &rdom[idx].node_data.node_type, -// NodeType::Text { text, .. } -// )); -// let div_tag = "div".to_string(); -// let idx = iter2.next(&rdom).id(); -// assert!(matches!( -// &rdom[idx].node_data.node_type, -// NodeType::Element { tag: div_tag, .. } -// )); -// } + let update = vdom.rebuild(); + iter1.prune(&update, &rdom); + iter2.prune(&update, &rdom); + let _to_update = rdom.apply_mutations(update); -// #[test] -// #[allow(unused_variables)] -// fn persist_instertions_before() { -// #[allow(non_snake_case)] -// fn Base(cx: Scope) -> Element { -// render!(div {}) -// } -// let vdom = VirtualDom::new(Base); -// let (build, update) = vdom.diff_lazynodes( -// rsx_without_templates! { -// div{ -// p{ -// key: "1", -// "hello" -// } -// p{ -// key: "3", -// "hello world" -// } -// } -// }, -// rsx_without_templates! { -// div{ -// p{ -// key: "1", -// "hello" -// } -// p{ -// key: "2", -// "world" -// } -// p{ -// key: "3", -// "hello world" -// } -// } -// }, -// ); + let p_tag = "1".to_string(); + let idx = iter1.next(&rdom).id(); + assert!(matches!( + &rdom[idx].node_data.node_type, + NodeType::Element { tag: p_tag, .. } + )); + let text = "2".to_string(); + let idx = iter1.next(&rdom).id(); + assert!(matches!( + &rdom[idx].node_data.node_type, + NodeType::Text { text, .. } + )); + let div_tag = "div".to_string(); + let idx = iter2.next(&rdom).id(); + assert!(matches!( + &rdom[idx].node_data.node_type, + NodeType::Element { tag: div_tag, .. } + )); +} -// let mut rdom: RealDom = RealDom::new(); +#[test] +#[allow(unused_variables)] +fn persist_instertions_before() { + #[allow(non_snake_case)] + fn Base(cx: Scope) -> Element { + let children = match cx.generation()%2{ + 0=>3, + 1=>2, + _ => unreachable!() + }; + render!( + div{ + (0..children).map(|i|{ + rsx!{ + p{ + key: "{i}", + "{i}" + } + } + }) + } + ) + } + let mut vdom = VirtualDom::new(Base); -// let _to_update = rdom.apply_mutations(vec![build]); + let mut rdom: RealDom = RealDom::new(); -// let mut iter = PersistantElementIter::new(); -// // div -// iter.next(&rdom).id(); -// // p -// iter.next(&rdom).id(); -// // "hello" -// iter.next(&rdom).id(); -// // p -// iter.next(&rdom).id(); -// // "hello world" -// iter.next(&rdom).id(); + let build = vdom.rebuild(); + let _to_update = rdom.apply_mutations(build); + + let mut iter = PersistantElementIter::new(); + // div + iter.next(&rdom).id(); + // p + iter.next(&rdom).id(); + // "1" + iter.next(&rdom).id(); + // p + iter.next(&rdom).id(); + // "2" + iter.next(&rdom).id(); + + let update = vdom.rebuild(); + iter.prune(&update, &rdom); + let _to_update = rdom.apply_mutations(update); -// iter.prune(&update, &rdom); -// let _to_update = rdom.apply_mutations(vec![update]); + let p_tag = "div".to_string(); + let idx = iter.next(&rdom).id(); + assert!(matches!( + &rdom[idx].node_data.node_type, + NodeType::Element { tag: p_tag, .. } + )); +} -// let p_tag = "div".to_string(); -// let idx = iter.next(&rdom).id(); -// assert!(matches!( -// &rdom[idx].node_data.node_type, -// NodeType::Element { tag: p_tag, .. } -// )); -// } +#[test] +#[allow(unused_variables)] +fn persist_instertions_after() { + #[allow(non_snake_case)] + fn Base(cx: Scope) -> Element { + let children = match cx.generation()%2{ + 0=>3, + 1=>2, + _ => unreachable!() + }; + render!( + div{ + (0..children).map(|i|{ + rsx!{ + p{ + key: "{i}", + "{i}" + } + } + }) + } + ) + } + let mut vdom = VirtualDom::new(Base); -// #[test] -// #[allow(unused_variables)] -// fn persist_instertions_after() { -// #[allow(non_snake_case)] -// fn Base(cx: Scope) -> Element { -// render!(div {}) -// } -// let vdom = VirtualDom::new(Base); -// let (build, update) = vdom.diff_lazynodes( -// rsx_without_templates! { -// div{ -// p{ -// key: "1", -// "hello" -// } -// p{ -// key: "2", -// "world" -// } -// } -// }, -// rsx_without_templates! { -// div{ -// p{ -// key: "1", -// "hello" -// } -// p{ -// key: "2", -// "world" -// } -// p{ -// key: "3", -// "hello world" -// } -// } -// }, -// ); + let mut rdom: RealDom = RealDom::new(); -// let mut rdom: RealDom = RealDom::new(); + let build = vdom.rebuild(); + let _to_update = rdom.apply_mutations(build); -// let _to_update = rdom.apply_mutations(vec![build]); + let mut iter = PersistantElementIter::new(); + // div + iter.next(&rdom).id(); + // p + iter.next(&rdom).id(); + // "hello" + iter.next(&rdom).id(); + // p + iter.next(&rdom).id(); + // "world" + iter.next(&rdom).id(); -// let mut iter = PersistantElementIter::new(); -// // div -// iter.next(&rdom).id(); -// // p -// iter.next(&rdom).id(); -// // "hello" -// iter.next(&rdom).id(); -// // p -// iter.next(&rdom).id(); -// // "world" -// iter.next(&rdom).id(); + let update = vdom.rebuild(); + iter.prune(&update, &rdom); + let _to_update = rdom.apply_mutations(update); -// iter.prune(&update, &rdom); -// let _to_update = rdom.apply_mutations(vec![update]); - -// let p_tag = "p".to_string(); -// let idx = iter.next(&rdom).id(); -// assert!(matches!( -// &rdom[idx].node_data.node_type, -// NodeType::Element { tag: p_tag, .. } -// )); -// let text = "hello world".to_string(); -// let idx = iter.next(&rdom).id(); -// assert!(matches!( -// &rdom[idx].node_data.node_type, -// NodeType::Text { text, .. } -// )); -// } + let p_tag = "p".to_string(); + let idx = iter.next(&rdom).id(); + assert!(matches!( + &rdom[idx].node_data.node_type, + NodeType::Element { tag: p_tag, .. } + )); + let text = "hello world".to_string(); + let idx = iter.next(&rdom).id(); + assert!(matches!( + &rdom[idx].node_data.node_type, + NodeType::Text { text, .. } + )); +} diff --git a/packages/native-core-macro/tests/update_state.rs b/packages/native-core-macro/tests/update_state.rs index 2e6a04dec..456ea5f5b 100644 --- a/packages/native-core-macro/tests/update_state.rs +++ b/packages/native-core-macro/tests/update_state.rs @@ -1,516 +1,454 @@ -// use anymap::AnyMap; -// use dioxus::core::ElementId; -// use dioxus::core::{AttributeValue, DomEdit, Mutations}; -// use dioxus::core_macro::rsx_without_templates; -// use dioxus::prelude::*; -// use dioxus_native_core::real_dom::*; -// use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State}; -// use dioxus_native_core::{node_ref::*, RealNodeId}; -// use dioxus_native_core_macro::State; +use dioxus::core::ElementId; +use dioxus::core::{AttributeValue, Mutations}; +use dioxus::prelude::*; +use dioxus_native_core::real_dom::*; +use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State}; +use dioxus_native_core::tree::TreeView; +use dioxus_native_core::{node_ref::*, NodeId, RealNodeId, SendAnyMap}; +use dioxus_native_core_macro::State; -// #[derive(Debug, Clone, Default, State)] -// struct CallCounterStatePart1 { -// #[child_dep_state(child_counter)] -// child_counter: ChildDepCallCounter, -// } +#[derive(Debug, Clone, Default, State)] +struct CallCounterStatePart1 { + #[child_dep_state(child_counter)] + child_counter: ChildDepCallCounter, +} -// #[derive(Debug, Clone, Default, State)] -// struct CallCounterStatePart2 { -// #[parent_dep_state(parent_counter)] -// parent_counter: ParentDepCallCounter, -// } +#[derive(Debug, Clone, Default, State)] +struct CallCounterStatePart2 { + #[parent_dep_state(parent_counter)] + parent_counter: ParentDepCallCounter, +} -// #[derive(Debug, Clone, Default, State)] -// struct CallCounterStatePart3 { -// #[node_dep_state()] -// node_counter: NodeDepCallCounter, -// } +#[derive(Debug, Clone, Default, State)] +struct CallCounterStatePart3 { + #[node_dep_state()] + node_counter: NodeDepCallCounter, +} -// #[derive(Debug, Clone, Default, State)] -// struct CallCounterState { -// #[child_dep_state(child_counter)] -// child_counter: ChildDepCallCounter, -// #[state] -// part2: CallCounterStatePart2, -// #[parent_dep_state(parent_counter)] -// parent_counter: ParentDepCallCounter, -// #[state] -// part1: CallCounterStatePart1, -// #[state] -// part3: CallCounterStatePart3, -// #[node_dep_state()] -// node_counter: NodeDepCallCounter, -// } +#[derive(Debug, Clone, Default, State)] +struct CallCounterState { + #[child_dep_state(child_counter)] + child_counter: ChildDepCallCounter, + #[state] + part2: CallCounterStatePart2, + #[parent_dep_state(parent_counter)] + parent_counter: ParentDepCallCounter, + #[state] + part1: CallCounterStatePart1, + #[state] + part3: CallCounterStatePart3, + #[node_dep_state()] + node_counter: NodeDepCallCounter, +} -// #[derive(Debug, Clone, Default)] -// struct ChildDepCallCounter(u32); -// impl ChildDepState for ChildDepCallCounter { -// type Ctx = (); -// type DepState = Self; -// const NODE_MASK: NodeMask = NodeMask::ALL; -// fn reduce<'a>( -// &mut self, -// _: NodeView, -// _: impl Iterator, -// _: &Self::Ctx, -// ) -> bool -// where -// Self::DepState: 'a, -// { -// self.0 += 1; -// true -// } -// } +#[derive(Debug, Clone, Default)] +struct ChildDepCallCounter(u32); +impl ChildDepState for ChildDepCallCounter { + type Ctx = (); + type DepState = (Self,); + const NODE_MASK: NodeMask = NodeMask::ALL; + fn reduce<'a>( + &mut self, + _: NodeView, + _: impl Iterator, + _: &Self::Ctx, + ) -> bool + where + Self::DepState: 'a, + { + self.0 += 1; + true + } +} -// #[derive(Debug, Clone, Default)] -// struct ParentDepCallCounter(u32); -// impl ParentDepState for ParentDepCallCounter { -// type 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 ParentDepCallCounter(u32); +impl ParentDepState for ParentDepCallCounter { + type Ctx = (); + type DepState = (Self,); + const NODE_MASK: NodeMask = NodeMask::ALL; + fn reduce(&mut self, _node: NodeView, _parent: Option<(&Self,)>, _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, _sibling: (), _ctx: &Self::Ctx) -> bool { -// self.0 += 1; -// true -// } -// } +#[derive(Debug, Clone, Default)] +struct NodeDepCallCounter(u32); +impl NodeDepState for NodeDepCallCounter { + type Ctx = (); + type DepState = (); + const NODE_MASK: NodeMask = NodeMask::ALL; + fn reduce(&mut self, _node: NodeView, _sibling: (), _ctx: &Self::Ctx) -> bool { + self.0 += 1; + true + } +} -// #[allow(clippy::vec_box)] -// #[derive(Debug, Clone, PartialEq, Default)] -// struct BubbledUpStateTester(Option, Vec>); -// impl ChildDepState for BubbledUpStateTester { -// type Ctx = u32; -// type DepState = Self; -// const NODE_MASK: NodeMask = NodeMask::new().with_tag(); -// fn reduce<'a>( -// &mut self, -// node: NodeView, -// children: impl Iterator, -// ctx: &Self::Ctx, -// ) -> bool -// where -// Self::DepState: 'a, -// { -// assert_eq!(*ctx, 42); -// *self = BubbledUpStateTester( -// node.tag().map(|s| s.to_string()), -// children.into_iter().map(|c| Box::new(c.clone())).collect(), -// ); -// true -// } -// } +#[allow(clippy::vec_box)] +#[derive(Debug, Clone, PartialEq, Default)] +struct BubbledUpStateTester(Option, Vec>); +impl ChildDepState for BubbledUpStateTester { + type Ctx = u32; + type DepState = (Self,); + const NODE_MASK: NodeMask = NodeMask::new().with_tag(); + fn reduce<'a>( + &mut self, + node: NodeView, + children: impl Iterator, + ctx: &Self::Ctx, + ) -> bool + where + Self::DepState: 'a, + { + assert_eq!(*ctx, 42); + *self = BubbledUpStateTester( + 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(Option, Option>); -// impl ParentDepState for PushedDownStateTester { -// type Ctx = u32; -// type DepState = Self; -// const NODE_MASK: NodeMask = NodeMask::new().with_tag(); -// fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, ctx: &Self::Ctx) -> bool { -// assert_eq!(*ctx, 42); -// *self = PushedDownStateTester( -// node.tag().map(|s| s.to_string()), -// parent.map(|c| Box::new(c.clone())), -// ); -// true -// } -// } +#[derive(Debug, Clone, PartialEq, Default)] +struct PushedDownStateTester(Option, Option>); +impl ParentDepState for PushedDownStateTester { + type Ctx = u32; + type DepState = (Self,); + const NODE_MASK: NodeMask = NodeMask::new().with_tag(); + fn reduce(&mut self, node: NodeView, parent: Option<(&Self,)>, ctx: &Self::Ctx) -> bool { + assert_eq!(*ctx, 42); + *self = PushedDownStateTester( + node.tag().map(|s| s.to_string()), + parent.map(|(c,)| Box::new(c.clone())), + ); + true + } +} -// #[derive(Debug, Clone, PartialEq, Default)] -// struct NodeStateTester(Option, Vec<(String, String)>); -// impl NodeDepState<()> for NodeStateTester { -// type Ctx = u32; -// const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::All).with_tag(); -// fn reduce(&mut self, node: NodeView, _sibling: (), ctx: &Self::Ctx) -> bool { -// assert_eq!(*ctx, 42); -// *self = NodeStateTester( -// node.tag().map(|s| s.to_string()), -// node.attributes() -// .map(|iter| { -// iter.map(|a| { -// ( -// a.attribute.name.to_string(), -// a.value.as_text().unwrap().to_string(), -// ) -// }) -// .collect() -// }) -// .unwrap_or_default(), -// ); -// true -// } -// } +#[derive(Debug, Clone, PartialEq, Default)] +struct NodeStateTester(Option, Vec<(String, String)>); +impl NodeDepState for NodeStateTester { + type Ctx = u32; + type DepState = (); + const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::All).with_tag(); + fn reduce(&mut self, node: NodeView, _sibling: (), ctx: &Self::Ctx) -> bool { + assert_eq!(*ctx, 42); + *self = NodeStateTester( + node.tag().map(|s| s.to_string()), + node.attributes() + .map(|iter| { + iter.map(|a| { + ( + a.attribute.name.to_string(), + a.value.as_text().unwrap().to_string(), + ) + }) + .collect() + }) + .unwrap_or_default(), + ); + true + } +} -// #[derive(State, Clone, Default, Debug)] -// struct StateTester { -// #[child_dep_state(bubbled, u32)] -// bubbled: BubbledUpStateTester, -// #[parent_dep_state(pushed, u32)] -// pushed: PushedDownStateTester, -// #[node_dep_state(NONE, u32)] -// node: NodeStateTester, -// } +#[derive(State, Clone, Default, Debug)] +struct StateTester { + #[child_dep_state(bubbled, u32)] + bubbled: BubbledUpStateTester, + #[parent_dep_state(pushed, u32)] + pushed: PushedDownStateTester, + #[node_dep_state(NONE, u32)] + node: NodeStateTester, +} -// #[test] -// fn state_initial() { -// #[allow(non_snake_case)] -// fn Base(cx: Scope) -> Element { -// render!(div { -// p{} -// h1{} -// }) -// } +#[test] +fn state_initial() { + #[allow(non_snake_case)] + fn Base(cx: Scope) -> Element { + render! { + div{ + p{} + h1{} + div { + p{ + color: "red" + } + h1{} + } + } + } + } -// let vdom = VirtualDom::new(Base); + let mut vdom = VirtualDom::new(Base); -// let mutations = vdom.create_vnodes(rsx! { -// div { -// p{ -// color: "red" -// } -// h1{} -// } -// }); + let mutations = vdom.rebuild(); -// let mut dom: RealDom = RealDom::new(); + let mut dom: RealDom = RealDom::new(); -// let nodes_updated = dom.apply_mutations(vec![mutations]); -// let mut ctx = AnyMap::new(); -// ctx.insert(42u32); -// let _to_rerender = dom.update_state(nodes_updated, ctx); + let (nodes_updated, _) = dom.apply_mutations(mutations); + let mut ctx = SendAnyMap::new(); + ctx.insert(42u32); + let _to_rerender = dom.update_state(nodes_updated, ctx); -// let root_div = &dom[RealNodeId::ElementId(ElementId(2))]; -// assert_eq!(root_div.state.bubbled.0, Some("div".to_string())); -// assert_eq!( -// root_div.state.bubbled.1, -// vec![ -// Box::new(BubbledUpStateTester(Some("p".to_string()), Vec::new())), -// Box::new(BubbledUpStateTester(Some("h1".to_string()), Vec::new())) -// ] -// ); -// assert_eq!(root_div.state.pushed.0, Some("div".to_string())); -// assert_eq!( -// root_div.state.pushed.1, -// Some(Box::new(PushedDownStateTester( -// Some("Root".to_string()), -// None -// ))) -// ); -// assert_eq!(root_div.state.node.0, Some("div".to_string())); -// assert_eq!(root_div.state.node.1, vec![]); + let root_div_id = dom.children_ids(NodeId(0)).unwrap()[0]; + let root_div = &dom.get(root_div_id).unwrap(); + assert_eq!(root_div.state.bubbled.0, Some("div".to_string())); + assert_eq!( + root_div.state.bubbled.1, + vec![ + Box::new(BubbledUpStateTester(Some("p".to_string()), Vec::new())), + Box::new(BubbledUpStateTester(Some("h1".to_string()), Vec::new())) + ] + ); + assert_eq!(root_div.state.pushed.0, Some("div".to_string())); + assert_eq!( + root_div.state.pushed.1, + Some(Box::new(PushedDownStateTester( + Some("Root".to_string()), + None + ))) + ); + assert_eq!(root_div.state.node.0, Some("div".to_string())); + assert_eq!(root_div.state.node.1, vec![]); -// let child_p = &dom[RealNodeId::UnaccessableId(3)]; -// 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.state.pushed.1, -// Some(Box::new(PushedDownStateTester( -// Some("div".to_string()), -// Some(Box::new(PushedDownStateTester( -// Some("Root".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_p_id = dom.children_ids(root_div_id).unwrap()[0]; + let child_p = &dom[child_p_id]; + 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.state.pushed.1, + Some(Box::new(PushedDownStateTester( + Some("div".to_string()), + Some(Box::new(PushedDownStateTester( + Some("Root".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[RealNodeId::UnaccessableId(4)]; -// 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.state.pushed.1, -// Some(Box::new(PushedDownStateTester( -// Some("div".to_string()), -// Some(Box::new(PushedDownStateTester( -// Some("Root".to_string()), -// None -// ))) -// ))) -// ); -// assert_eq!(child_h1.state.node.0, Some("h1".to_string())); -// assert_eq!(child_h1.state.node.1, vec![]); -// } + let child_h1_id = dom.children_ids(root_div_id).unwrap()[1]; + let child_h1 = &dom[child_h1_id]; + 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.state.pushed.1, + Some(Box::new(PushedDownStateTester( + Some("div".to_string()), + Some(Box::new(PushedDownStateTester( + Some("Root".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_parent_called_minimally_on_update() { -// #[allow(non_snake_case)] -// fn Base(cx: Scope) -> Element { -// render!(div { -// width: "100%", -// div{ -// div{ -// p{} -// } -// p{ -// "hello" -// } -// div{ -// h1{} -// } -// p{ -// "world" -// } -// } -// }) -// } +#[test] +fn state_reduce_parent_called_minimally_on_update() { + #[allow(non_snake_case)] + fn Base(cx: Scope) -> Element { + let width = if cx.generation() == 0 { "100%" } else { "99%" }; + cx.render(rsx! { + div { + div{ + div{ + p{ + width: "{width}", + } + } + p{ + "hello" + } + div{ + h1{} + } + p{ + "world" + } + } + } + }) + } -// let vdom = VirtualDom::new(Base); + let mut vdom = VirtualDom::new(Base); -// let mutations = vdom.create_vnodes(rsx_without_templates! { -// div { -// width: "100%", -// div{ -// div{ -// p{} -// } -// p{ -// "hello" -// } -// div{ -// h1{} -// } -// p{ -// "world" -// } -// } -// } -// }); + let mut dom: RealDom = RealDom::new(); -// let mut dom: RealDom = RealDom::new(); + let (nodes_updated, _) = dom.apply_mutations(vdom.rebuild()); + let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::new()); + let (nodes_updated, _) = dom.apply_mutations(vdom.rebuild()); + let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::new()); -// let nodes_updated = dom.apply_mutations(vec![mutations]); -// let _to_rerender = dom.update_state(nodes_updated, AnyMap::new()); -// let nodes_updated = dom.apply_mutations(vec![Mutations { -// edits: vec![DomEdit::SetAttribute { -// root: Some(1), -// field: "width", -// value: AttributeValue::Text("99%"), -// ns: Some("style"), -// }], -// dirty_scopes: rustc_hash::FxHashSet::default(), -// refs: Vec::new(), -// }]); -// let _to_rerender = dom.update_state(nodes_updated, AnyMap::new()); + dom.traverse_depth_first(|n| { + assert_eq!(n.state.part2.parent_counter.0, 2); + assert_eq!(n.state.parent_counter.0, 2); + }); +} -// dom.traverse_depth_first(|n| { -// assert_eq!(n.state.part2.parent_counter.0, 2); -// assert_eq!(n.state.parent_counter.0, 2); -// }); -// } +#[test] +fn state_reduce_child_called_minimally_on_update() { + #[allow(non_snake_case)] + fn Base(cx: Scope) -> Element { + let width = if cx.generation() == 0 { "100%" } else { "99%" }; + cx.render(rsx! { + div { + div{ + div{ + p{ + width: "{width}", + } + } + p{ + "hello" + } + div{ + h1{} + } + p{ + "world" + } + } + } + }) + } -// #[test] -// fn state_reduce_child_called_minimally_on_update() { -// #[allow(non_snake_case)] -// fn Base(cx: Scope) -> Element { -// cx.render(rsx_without_templates!(div { -// div{ -// div{ -// p{ -// width: "100%", -// } -// } -// p{ -// "hello" -// } -// div{ -// h1{} -// } -// p{ -// "world" -// } -// } -// })) -// } + let mut vdom = VirtualDom::new(Base); -// let vdom = VirtualDom::new(Base); + let mut dom: RealDom = RealDom::new(); -// let mutations = vdom.create_vnodes(rsx_without_templates! { -// div { -// div{ -// div{ -// p{ -// width: "100%", -// } -// } -// p{ -// "hello" -// } -// div{ -// h1{} -// } -// p{ -// "world" -// } -// } -// } -// }); + let (nodes_updated, _) = dom.apply_mutations(vdom.rebuild()); + let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::new()); + let (nodes_updated, _) = dom.apply_mutations(vdom.rebuild()); + let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::new()); -// let mut dom: RealDom = RealDom::new(); + dom.traverse_depth_first(|n| { + assert_eq!(n.state.part1.child_counter.0, { + let id = n.node_data.node_id.0; + if id > 4 { + 1 + } else { + 2 + } + }); + assert_eq!(n.state.child_counter.0, { + let id = n.node_data.node_id.0; + if id > 4 { + 1 + } else { + 2 + } + }); + }); +} -// let nodes_updated = dom.apply_mutations(vec![mutations]); -// let _to_rerender = dom.update_state(nodes_updated, AnyMap::new()); -// let nodes_updated = dom.apply_mutations(vec![Mutations { -// edits: vec![DomEdit::SetAttribute { -// root: Some(4), -// field: "width", -// value: AttributeValue::Text("99%"), -// ns: Some("style"), -// }], -// dirty_scopes: rustc_hash::FxHashSet::default(), -// refs: Vec::new(), -// }]); -// let _to_rerender = dom.update_state(nodes_updated, AnyMap::new()); +#[derive(Debug, Clone, Default, State)] +struct UnorderedDependanciesState { + #[node_dep_state(c)] + b: BDepCallCounter, + #[node_dep_state()] + c: CDepCallCounter, + #[node_dep_state(b)] + a: ADepCallCounter, +} -// dom.traverse_depth_first(|n| { -// assert_eq!( -// n.state.part1.child_counter.0, -// if let Some(RealNodeId::ElementId(ElementId(id))) = n.node_data.id { -// if id > 4 { -// 1 -// } else { -// 2 -// } -// } else { -// panic!() -// } -// ); -// assert_eq!( -// n.state.child_counter.0, -// if let Some(RealNodeId::ElementId(ElementId(id))) = n.node_data.id { -// if id > 4 { -// 1 -// } else { -// 2 -// } -// } else { -// panic!() -// } -// ); -// }); -// } +#[derive(Debug, Clone, Default, PartialEq)] +struct ADepCallCounter(usize, BDepCallCounter); +impl NodeDepState for ADepCallCounter { + type Ctx = (); + type DepState = (BDepCallCounter,); + const NODE_MASK: NodeMask = NodeMask::NONE; + fn reduce( + &mut self, + _node: NodeView, + (sibling,): (&BDepCallCounter,), + _ctx: &Self::Ctx, + ) -> bool { + self.0 += 1; + self.1 = sibling.clone(); + true + } +} -// #[derive(Debug, Clone, Default, State)] -// struct UnorderedDependanciesState { -// #[node_dep_state(c)] -// b: BDepCallCounter, -// #[node_dep_state()] -// c: CDepCallCounter, -// #[node_dep_state(b)] -// a: ADepCallCounter, -// } +#[derive(Debug, Clone, Default, PartialEq)] +struct BDepCallCounter(usize, CDepCallCounter); +impl NodeDepState for BDepCallCounter { + type Ctx = (); + type DepState = (CDepCallCounter,); + const NODE_MASK: NodeMask = NodeMask::NONE; + fn reduce( + &mut self, + _node: NodeView, + (sibling,): (&CDepCallCounter,), + _ctx: &Self::Ctx, + ) -> bool { + self.0 += 1; + self.1 = sibling.clone(); + true + } +} -// #[derive(Debug, Clone, Default, PartialEq)] -// struct ADepCallCounter(usize, BDepCallCounter); -// impl<'a> NodeDepState<(&'a BDepCallCounter,)> for ADepCallCounter { -// type Ctx = (); -// const NODE_MASK: NodeMask = NodeMask::NONE; -// fn reduce( -// &mut self, -// _node: NodeView, -// (sibling,): (&'a BDepCallCounter,), -// _ctx: &Self::Ctx, -// ) -> bool { -// self.0 += 1; -// self.1 = sibling.clone(); -// true -// } -// } +#[derive(Debug, Clone, Default, PartialEq)] +struct CDepCallCounter(usize); +impl NodeDepState for CDepCallCounter { + type Ctx = (); + type DepState = (); + const NODE_MASK: NodeMask = NodeMask::ALL; + fn reduce(&mut self, _node: NodeView, _sibling: (), _ctx: &Self::Ctx) -> bool { + self.0 += 1; + true + } +} -// #[derive(Debug, Clone, Default, PartialEq)] -// struct BDepCallCounter(usize, CDepCallCounter); -// impl<'a> NodeDepState<(&'a CDepCallCounter,)> for BDepCallCounter { -// type Ctx = (); -// const NODE_MASK: NodeMask = NodeMask::NONE; -// fn reduce( -// &mut self, -// _node: NodeView, -// (sibling,): (&'a CDepCallCounter,), -// _ctx: &Self::Ctx, -// ) -> bool { -// self.0 += 1; -// self.1 = sibling.clone(); -// true -// } -// } +#[test] +fn dependancies_order_independant() { + #[allow(non_snake_case)] + fn Base(cx: Scope) -> Element { + render!(div { + width: "100%", + p{ + "hello" + } + }) + } -// #[derive(Debug, Clone, Default, PartialEq)] -// struct CDepCallCounter(usize); -// impl NodeDepState<()> for CDepCallCounter { -// type Ctx = (); -// const NODE_MASK: NodeMask = NodeMask::ALL; -// fn reduce(&mut self, _node: NodeView, _sibling: (), _ctx: &Self::Ctx) -> bool { -// self.0 += 1; -// true -// } -// } + let mut vdom = VirtualDom::new(Base); -// #[test] -// fn dependancies_order_independant() { -// #[allow(non_snake_case)] -// fn Base(cx: Scope) -> Element { -// render!(div { -// width: "100%", -// p{ -// "hello" -// } -// }) -// } + let mut dom: RealDom = RealDom::new(); -// let vdom = VirtualDom::new(Base); + let mutations = vdom.rebuild(); + let (nodes_updated, _) = dom.apply_mutations(mutations); + let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::new()); -// let mutations = vdom.create_vnodes(rsx! { -// div { -// width: "100%", -// p{ -// "hello" -// } -// } -// }); + let c = CDepCallCounter(1); + let b = BDepCallCounter(1, c.clone()); + let a = ADepCallCounter(1, b.clone()); + dom.traverse_depth_first(|n| { + assert_eq!(&n.state.a, &a); + assert_eq!(&n.state.b, &b); + assert_eq!(&n.state.c, &c); + }); +} -// let mut dom: RealDom = RealDom::new(); - -// let nodes_updated = dom.apply_mutations(vec![mutations]); -// let _to_rerender = dom.update_state(nodes_updated, AnyMap::new()); - -// let c = CDepCallCounter(1); -// let b = BDepCallCounter(1, c.clone()); -// let a = ADepCallCounter(1, b.clone()); -// dom.traverse_depth_first(|n| { -// assert_eq!(&n.state.a, &a); -// assert_eq!(&n.state.b, &b); -// assert_eq!(&n.state.c, &c); -// }); -// } - -// #[derive(Clone, Default, State)] -// struct DependanciesStateTest { -// #[node_dep_state(c)] -// b: BDepCallCounter, -// #[node_dep_state()] -// c: CDepCallCounter, -// #[node_dep_state(b)] -// a: ADepCallCounter, -// #[state] -// child: UnorderedDependanciesState, -// } +#[derive(Clone, Default, State)] +struct DependanciesStateTest { + #[node_dep_state(c)] + b: BDepCallCounter, + #[node_dep_state()] + c: CDepCallCounter, + #[node_dep_state(b)] + a: ADepCallCounter, + #[state] + child: UnorderedDependanciesState, +} diff --git a/packages/native-core/src/utils.rs b/packages/native-core/src/utils.rs index bec658624..ac6d1f6d3 100644 --- a/packages/native-core/src/utils.rs +++ b/packages/native-core/src/utils.rs @@ -2,6 +2,7 @@ use crate::{node::NodeType, real_dom::RealDom, state::State, tree::TreeView, Nod use dioxus_core::{Mutation, Mutations}; use std::fmt::Debug; +#[derive(Debug)] pub enum ElementProduced { /// The iterator produced an element by progressing to the next node in a depth first order. Progressed(RealNodeId), @@ -124,20 +125,20 @@ impl PersistantElementIter { /// get the next element pub fn next(&mut self, rdom: &RealDom) -> ElementProduced { - if self.stack.is_empty() { + let r=if self.stack.is_empty() { let id = NodeId(0); let new = (id, NodePosition::AtNode); self.stack.push(new); ElementProduced::Looped(id) } else { - let (last, o_child_idx) = self.stack.last_mut().unwrap(); + let (last, old_child_idx) = self.stack.last_mut().unwrap(); let node = &rdom[*last]; match &node.node_data.node_type { NodeType::Element { .. } => { let children = rdom.tree.children_ids(*last).unwrap(); - *o_child_idx = o_child_idx.map(|i| i + 1); + *old_child_idx = old_child_idx.map(|i| i + 1); // if we have children, go to the next child - let child_idx = o_child_idx.get_or_insert(0); + let child_idx = old_child_idx.get_or_insert(0); if child_idx >= children.len() { self.pop(); self.next(rdom) @@ -155,7 +156,9 @@ impl PersistantElementIter { ElementProduced::Progressed(self.pop()) } } - } + }; + println!("next: {:?}", r); + r } /// get the previous element @@ -183,17 +186,17 @@ impl PersistantElementIter { let new_node = NodeId(0); ElementProduced::Looped(push_back(&mut self.stack, new_node, rdom)) } else { - let (last, o_child_idx) = self.stack.last_mut().unwrap(); + let (last, old_child_idx) = self.stack.last_mut().unwrap(); let node = &rdom[*last]; match &node.node_data.node_type { NodeType::Element { .. } => { let children = rdom.tree.children_ids(*last).unwrap(); // if we have children, go to the next child - if let NodePosition::InChild(0) = o_child_idx { + if let NodePosition::InChild(0) = old_child_idx { ElementProduced::Progressed(self.pop()) } else { - *o_child_idx = o_child_idx.map(|i| i - 1); - if let NodePosition::InChild(child_idx) = o_child_idx { + *old_child_idx = old_child_idx.map(|i| i - 1); + if let NodePosition::InChild(child_idx) = old_child_idx { if *child_idx >= children.len() || children.is_empty() { self.pop(); self.prev(rdom) From 32e93dee655e3daf8892c8f2364a82f0965426f7 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 6 Dec 2022 08:27:32 -0600 Subject: [PATCH 38/93] tests passing --- .../tests/persistent_iterator.rs | 39 ++++---- .../native-core-macro/tests/update_state.rs | 92 +++++++------------ 2 files changed, 50 insertions(+), 81 deletions(-) diff --git a/packages/native-core-macro/tests/persistent_iterator.rs b/packages/native-core-macro/tests/persistent_iterator.rs index c35c6ab0a..0ad2e3ca2 100644 --- a/packages/native-core-macro/tests/persistent_iterator.rs +++ b/packages/native-core-macro/tests/persistent_iterator.rs @@ -1,9 +1,6 @@ use dioxus::prelude::*; use dioxus_native_core::{ - real_dom::{ RealDom}, - node::NodeType, - state::State, - utils::PersistantElementIter, + node::NodeType, real_dom::RealDom, state::State, utils::PersistantElementIter, }; use dioxus_native_core_macro::State; @@ -109,10 +106,10 @@ fn traverse() { fn persist_removes() { #[allow(non_snake_case)] fn Base(cx: Scope) -> Element { - let children = match cx.generation()%2{ - 0=>3, - 1=>2, - _ => unreachable!() + let children = match cx.generation() % 2 { + 0 => 3, + 1 => 2, + _ => unreachable!(), }; render!( div{ @@ -158,7 +155,8 @@ fn persist_removes() { // "3" iter2.next(&rdom).id(); - let update = vdom.rebuild(); + vdom.mark_dirty(ScopeId(0)); + let update = vdom.render_immediate(); iter1.prune(&update, &rdom); iter2.prune(&update, &rdom); let _to_update = rdom.apply_mutations(update); @@ -188,10 +186,10 @@ fn persist_removes() { fn persist_instertions_before() { #[allow(non_snake_case)] fn Base(cx: Scope) -> Element { - let children = match cx.generation()%2{ - 0=>3, - 1=>2, - _ => unreachable!() + let children = match cx.generation() % 2 { + 0 => 3, + 1 => 2, + _ => unreachable!(), }; render!( div{ @@ -212,7 +210,7 @@ fn persist_instertions_before() { let build = vdom.rebuild(); let _to_update = rdom.apply_mutations(build); - + let mut iter = PersistantElementIter::new(); // div iter.next(&rdom).id(); @@ -224,8 +222,9 @@ fn persist_instertions_before() { iter.next(&rdom).id(); // "2" iter.next(&rdom).id(); - - let update = vdom.rebuild(); + + vdom.mark_dirty(ScopeId(0)); + let update = vdom.render_immediate(); iter.prune(&update, &rdom); let _to_update = rdom.apply_mutations(update); @@ -242,10 +241,10 @@ fn persist_instertions_before() { fn persist_instertions_after() { #[allow(non_snake_case)] fn Base(cx: Scope) -> Element { - let children = match cx.generation()%2{ - 0=>3, - 1=>2, - _ => unreachable!() + let children = match cx.generation() % 2 { + 0 => 3, + 1 => 2, + _ => unreachable!(), }; render!( div{ diff --git a/packages/native-core-macro/tests/update_state.rs b/packages/native-core-macro/tests/update_state.rs index 456ea5f5b..b9b36c8ca 100644 --- a/packages/native-core-macro/tests/update_state.rs +++ b/packages/native-core-macro/tests/update_state.rs @@ -7,36 +7,12 @@ use dioxus_native_core::tree::TreeView; use dioxus_native_core::{node_ref::*, NodeId, RealNodeId, SendAnyMap}; use dioxus_native_core_macro::State; -#[derive(Debug, Clone, Default, State)] -struct CallCounterStatePart1 { - #[child_dep_state(child_counter)] - child_counter: ChildDepCallCounter, -} - -#[derive(Debug, Clone, Default, State)] -struct CallCounterStatePart2 { - #[parent_dep_state(parent_counter)] - parent_counter: ParentDepCallCounter, -} - -#[derive(Debug, Clone, Default, State)] -struct CallCounterStatePart3 { - #[node_dep_state()] - node_counter: NodeDepCallCounter, -} - #[derive(Debug, Clone, Default, State)] struct CallCounterState { #[child_dep_state(child_counter)] child_counter: ChildDepCallCounter, - #[state] - part2: CallCounterStatePart2, #[parent_dep_state(parent_counter)] parent_counter: ParentDepCallCounter, - #[state] - part1: CallCounterStatePart1, - #[state] - part3: CallCounterStatePart3, #[node_dep_state()] node_counter: NodeDepCallCounter, } @@ -69,6 +45,7 @@ impl ParentDepState for ParentDepCallCounter { const NODE_MASK: NodeMask = NodeMask::ALL; fn reduce(&mut self, _node: NodeView, _parent: Option<(&Self,)>, _ctx: &Self::Ctx) -> bool { self.0 += 1; + println!("ParentDepCallCounter::reduce on {:?}\n{}", _node, self.0); true } } @@ -170,15 +147,11 @@ fn state_initial() { #[allow(non_snake_case)] fn Base(cx: Scope) -> Element { render! { - div{ - p{} - h1{} - div { - p{ - color: "red" - } - h1{} + div { + p{ + color: "red" } + h1{} } } } @@ -262,11 +235,10 @@ fn state_reduce_parent_called_minimally_on_update() { let width = if cx.generation() == 0 { "100%" } else { "99%" }; cx.render(rsx! { div { + width: "{width}", div{ div{ - p{ - width: "{width}", - } + p{} } p{ "hello" @@ -288,12 +260,18 @@ fn state_reduce_parent_called_minimally_on_update() { let (nodes_updated, _) = dom.apply_mutations(vdom.rebuild()); let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::new()); - let (nodes_updated, _) = dom.apply_mutations(vdom.rebuild()); + vdom.mark_dirty(ScopeId(0)); + let (nodes_updated, _) = dom.apply_mutations(vdom.render_immediate()); let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::new()); + let mut is_root = true; dom.traverse_depth_first(|n| { - assert_eq!(n.state.part2.parent_counter.0, 2); - assert_eq!(n.state.parent_counter.0, 2); + if is_root { + is_root = false; + assert_eq!(n.state.parent_counter.0, 1); + } else { + assert_eq!(n.state.parent_counter.0, 2); + } }); } @@ -303,20 +281,30 @@ fn state_reduce_child_called_minimally_on_update() { fn Base(cx: Scope) -> Element { let width = if cx.generation() == 0 { "100%" } else { "99%" }; cx.render(rsx! { + // updated: 2 div { + // updated: 2 div{ + // updated: 2 div{ + // updated: 2 p{ width: "{width}", } } + // updated: 1 p{ + // updated: 1 "hello" } + // updated: 1 div{ + // updated: 1 h1{} } + // updated: 1 p{ + // updated: 1 "world" } } @@ -330,26 +318,20 @@ fn state_reduce_child_called_minimally_on_update() { let (nodes_updated, _) = dom.apply_mutations(vdom.rebuild()); let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::new()); - let (nodes_updated, _) = dom.apply_mutations(vdom.rebuild()); + vdom.mark_dirty(ScopeId(0)); + let (nodes_updated, _) = dom.apply_mutations(vdom.render_immediate()); let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::new()); + let mut traverse_count = 0; dom.traverse_depth_first(|n| { - assert_eq!(n.state.part1.child_counter.0, { - let id = n.node_data.node_id.0; - if id > 4 { - 1 - } else { - 2 - } - }); assert_eq!(n.state.child_counter.0, { - let id = n.node_data.node_id.0; - if id > 4 { + if traverse_count > 4 { 1 } else { 2 } }); + traverse_count += 1; }); } @@ -440,15 +422,3 @@ fn dependancies_order_independant() { assert_eq!(&n.state.c, &c); }); } - -#[derive(Clone, Default, State)] -struct DependanciesStateTest { - #[node_dep_state(c)] - b: BDepCallCounter, - #[node_dep_state()] - c: CDepCallCounter, - #[node_dep_state(b)] - a: ADepCallCounter, - #[state] - child: UnorderedDependanciesState, -} From 40b15f834b164c9540adabe6cea80d9f04588cf8 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 6 Dec 2022 08:31:06 -0600 Subject: [PATCH 39/93] move persistant iterator test --- packages/native-core-macro/.gitignore | 2 + .../tests/persistent_iterator.rs | 297 ----------------- packages/native-core/.gitignore | 2 + packages/native-core/Cargo.toml | 3 +- packages/native-core/src/utils.rs | 303 +++++++++++++++++- 5 files changed, 308 insertions(+), 299 deletions(-) create mode 100644 packages/native-core-macro/.gitignore delete mode 100644 packages/native-core-macro/tests/persistent_iterator.rs create mode 100644 packages/native-core/.gitignore diff --git a/packages/native-core-macro/.gitignore b/packages/native-core-macro/.gitignore new file mode 100644 index 000000000..96ef6c0b9 --- /dev/null +++ b/packages/native-core-macro/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/packages/native-core-macro/tests/persistent_iterator.rs b/packages/native-core-macro/tests/persistent_iterator.rs deleted file mode 100644 index 0ad2e3ca2..000000000 --- a/packages/native-core-macro/tests/persistent_iterator.rs +++ /dev/null @@ -1,297 +0,0 @@ -use dioxus::prelude::*; -use dioxus_native_core::{ - node::NodeType, real_dom::RealDom, state::State, utils::PersistantElementIter, -}; -use dioxus_native_core_macro::State; - -#[derive(State, Default, Clone, Debug)] -struct Empty {} - -#[test] -#[allow(unused_variables)] -fn traverse() { - #[allow(non_snake_case)] - fn Base(cx: Scope) -> Element { - render!( - div{ - div{ - "hello" - p{ - "world" - } - "hello world" - } - } - ) - } - - let mut vdom = VirtualDom::new(Base); - let mutations = vdom.rebuild(); - - let mut rdom: RealDom = RealDom::new(); - - let _to_update = rdom.apply_mutations(mutations); - - let mut iter = PersistantElementIter::new(); - let div_tag = "div".to_string(); - assert!(matches!( - &rdom[iter.next(&rdom).id()].node_data.node_type, - NodeType::Element { tag: div_tag, .. } - )); - assert!(matches!( - &rdom[iter.next(&rdom).id()].node_data.node_type, - NodeType::Element { tag: div_tag, .. } - )); - let text1 = "hello".to_string(); - assert!(matches!( - &rdom[iter.next(&rdom).id()].node_data.node_type, - NodeType::Text { text: text1, .. } - )); - let p_tag = "p".to_string(); - assert!(matches!( - &rdom[iter.next(&rdom).id()].node_data.node_type, - NodeType::Element { tag: p_tag, .. } - )); - let text2 = "world".to_string(); - assert!(matches!( - &rdom[iter.next(&rdom).id()].node_data.node_type, - NodeType::Text { text: text2, .. } - )); - let text3 = "hello world".to_string(); - assert!(matches!( - &rdom[iter.next(&rdom).id()].node_data.node_type, - NodeType::Text { text: text3, .. } - )); - assert!(matches!( - &rdom[iter.next(&rdom).id()].node_data.node_type, - NodeType::Element { tag: div_tag, .. } - )); - - assert!(matches!( - &rdom[iter.prev(&rdom).id()].node_data.node_type, - NodeType::Text { text: text3, .. } - )); - assert!(matches!( - &rdom[iter.prev(&rdom).id()].node_data.node_type, - NodeType::Text { text: text2, .. } - )); - assert!(matches!( - &rdom[iter.prev(&rdom).id()].node_data.node_type, - NodeType::Element { tag: p_tag, .. } - )); - assert!(matches!( - &rdom[iter.prev(&rdom).id()].node_data.node_type, - NodeType::Text { text: text1, .. } - )); - assert!(matches!( - &rdom[iter.prev(&rdom).id()].node_data.node_type, - NodeType::Element { tag: div_tag, .. } - )); - assert!(matches!( - &rdom[iter.prev(&rdom).id()].node_data.node_type, - NodeType::Element { tag: div_tag, .. } - )); - assert!(matches!( - &rdom[iter.prev(&rdom).id()].node_data.node_type, - NodeType::Element { tag: div_tag, .. } - )); - assert!(matches!( - &rdom[iter.prev(&rdom).id()].node_data.node_type, - NodeType::Text { text: text3, .. } - )); -} - -#[test] -#[allow(unused_variables)] -fn persist_removes() { - #[allow(non_snake_case)] - fn Base(cx: Scope) -> Element { - let children = match cx.generation() % 2 { - 0 => 3, - 1 => 2, - _ => unreachable!(), - }; - render!( - div{ - (0..children).map(|i|{ - rsx!{ - p{ - key: "{i}", - "{i}" - } - } - }) - } - ) - } - let mut vdom = VirtualDom::new(Base); - - let mut rdom: RealDom = RealDom::new(); - - let build = vdom.rebuild(); - let _to_update = rdom.apply_mutations(build); - - // this will end on the node that is removed - let mut iter1 = PersistantElementIter::new(); - // this will end on the after node that is removed - let mut iter2 = PersistantElementIter::new(); - // div - iter1.next(&rdom).id(); - iter2.next(&rdom).id(); - // p - iter1.next(&rdom).id(); - iter2.next(&rdom).id(); - // "1" - iter1.next(&rdom).id(); - iter2.next(&rdom).id(); - // p - iter1.next(&rdom).id(); - iter2.next(&rdom).id(); - // "2" - iter1.next(&rdom).id(); - iter2.next(&rdom).id(); - // p - iter2.next(&rdom).id(); - // "3" - iter2.next(&rdom).id(); - - vdom.mark_dirty(ScopeId(0)); - let update = vdom.render_immediate(); - iter1.prune(&update, &rdom); - iter2.prune(&update, &rdom); - let _to_update = rdom.apply_mutations(update); - - let p_tag = "1".to_string(); - let idx = iter1.next(&rdom).id(); - assert!(matches!( - &rdom[idx].node_data.node_type, - NodeType::Element { tag: p_tag, .. } - )); - let text = "2".to_string(); - let idx = iter1.next(&rdom).id(); - assert!(matches!( - &rdom[idx].node_data.node_type, - NodeType::Text { text, .. } - )); - let div_tag = "div".to_string(); - let idx = iter2.next(&rdom).id(); - assert!(matches!( - &rdom[idx].node_data.node_type, - NodeType::Element { tag: div_tag, .. } - )); -} - -#[test] -#[allow(unused_variables)] -fn persist_instertions_before() { - #[allow(non_snake_case)] - fn Base(cx: Scope) -> Element { - let children = match cx.generation() % 2 { - 0 => 3, - 1 => 2, - _ => unreachable!(), - }; - render!( - div{ - (0..children).map(|i|{ - rsx!{ - p{ - key: "{i}", - "{i}" - } - } - }) - } - ) - } - let mut vdom = VirtualDom::new(Base); - - let mut rdom: RealDom = RealDom::new(); - - let build = vdom.rebuild(); - let _to_update = rdom.apply_mutations(build); - - let mut iter = PersistantElementIter::new(); - // div - iter.next(&rdom).id(); - // p - iter.next(&rdom).id(); - // "1" - iter.next(&rdom).id(); - // p - iter.next(&rdom).id(); - // "2" - iter.next(&rdom).id(); - - vdom.mark_dirty(ScopeId(0)); - let update = vdom.render_immediate(); - iter.prune(&update, &rdom); - let _to_update = rdom.apply_mutations(update); - - let p_tag = "div".to_string(); - let idx = iter.next(&rdom).id(); - assert!(matches!( - &rdom[idx].node_data.node_type, - NodeType::Element { tag: p_tag, .. } - )); -} - -#[test] -#[allow(unused_variables)] -fn persist_instertions_after() { - #[allow(non_snake_case)] - fn Base(cx: Scope) -> Element { - let children = match cx.generation() % 2 { - 0 => 3, - 1 => 2, - _ => unreachable!(), - }; - render!( - div{ - (0..children).map(|i|{ - rsx!{ - p{ - key: "{i}", - "{i}" - } - } - }) - } - ) - } - let mut vdom = VirtualDom::new(Base); - - let mut rdom: RealDom = RealDom::new(); - - let build = vdom.rebuild(); - let _to_update = rdom.apply_mutations(build); - - let mut iter = PersistantElementIter::new(); - // div - iter.next(&rdom).id(); - // p - iter.next(&rdom).id(); - // "hello" - iter.next(&rdom).id(); - // p - iter.next(&rdom).id(); - // "world" - iter.next(&rdom).id(); - - let update = vdom.rebuild(); - iter.prune(&update, &rdom); - let _to_update = rdom.apply_mutations(update); - - let p_tag = "p".to_string(); - let idx = iter.next(&rdom).id(); - assert!(matches!( - &rdom[idx].node_data.node_type, - NodeType::Element { tag: p_tag, .. } - )); - let text = "hello world".to_string(); - let idx = iter.next(&rdom).id(); - assert!(matches!( - &rdom[idx].node_data.node_type, - NodeType::Text { text, .. } - )); -} diff --git a/packages/native-core/.gitignore b/packages/native-core/.gitignore new file mode 100644 index 000000000..96ef6c0b9 --- /dev/null +++ b/packages/native-core/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index f15a743f3..309fba781 100644 --- a/packages/native-core/Cargo.toml +++ b/packages/native-core/Cargo.toml @@ -25,4 +25,5 @@ crossbeam-deque = "0.8.2" dashmap = "5.4.0" [dev-dependencies] -rand = "0.8.5" \ No newline at end of file +rand = "0.8.5" +dioxus = { path = "../dioxus", version = "^0.2.1" } \ No newline at end of file diff --git a/packages/native-core/src/utils.rs b/packages/native-core/src/utils.rs index ac6d1f6d3..7a55ad6db 100644 --- a/packages/native-core/src/utils.rs +++ b/packages/native-core/src/utils.rs @@ -125,7 +125,7 @@ impl PersistantElementIter { /// get the next element pub fn next(&mut self, rdom: &RealDom) -> ElementProduced { - let r=if self.stack.is_empty() { + let r = if self.stack.is_empty() { let id = NodeId(0); let new = (id, NodePosition::AtNode); self.stack.push(new); @@ -227,3 +227,304 @@ impl PersistantElementIter { self.stack.pop().unwrap().0 } } + +#[derive(Default, Clone, Debug)] +struct Empty {} +impl State for Empty { + const PASSES: &'static [crate::AnyPass>] = &[]; + + const MASKS: &'static [crate::NodeMask] = &[]; +} + +#[test] +#[allow(unused_variables)] +fn traverse() { + use dioxus::prelude::*; + #[allow(non_snake_case)] + fn Base(cx: Scope) -> Element { + render!( + div{ + div{ + "hello" + p{ + "world" + } + "hello world" + } + } + ) + } + + let mut vdom = VirtualDom::new(Base); + let mutations = vdom.rebuild(); + + let mut rdom: RealDom = RealDom::new(); + + let _to_update = rdom.apply_mutations(mutations); + + let mut iter = PersistantElementIter::new(); + let div_tag = "div".to_string(); + assert!(matches!( + &rdom[iter.next(&rdom).id()].node_data.node_type, + NodeType::Element { tag: div_tag, .. } + )); + assert!(matches!( + &rdom[iter.next(&rdom).id()].node_data.node_type, + NodeType::Element { tag: div_tag, .. } + )); + let text1 = "hello".to_string(); + assert!(matches!( + &rdom[iter.next(&rdom).id()].node_data.node_type, + NodeType::Text { text: text1, .. } + )); + let p_tag = "p".to_string(); + assert!(matches!( + &rdom[iter.next(&rdom).id()].node_data.node_type, + NodeType::Element { tag: p_tag, .. } + )); + let text2 = "world".to_string(); + assert!(matches!( + &rdom[iter.next(&rdom).id()].node_data.node_type, + NodeType::Text { text: text2, .. } + )); + let text3 = "hello world".to_string(); + assert!(matches!( + &rdom[iter.next(&rdom).id()].node_data.node_type, + NodeType::Text { text: text3, .. } + )); + assert!(matches!( + &rdom[iter.next(&rdom).id()].node_data.node_type, + NodeType::Element { tag: div_tag, .. } + )); + + assert!(matches!( + &rdom[iter.prev(&rdom).id()].node_data.node_type, + NodeType::Text { text: text3, .. } + )); + assert!(matches!( + &rdom[iter.prev(&rdom).id()].node_data.node_type, + NodeType::Text { text: text2, .. } + )); + assert!(matches!( + &rdom[iter.prev(&rdom).id()].node_data.node_type, + NodeType::Element { tag: p_tag, .. } + )); + assert!(matches!( + &rdom[iter.prev(&rdom).id()].node_data.node_type, + NodeType::Text { text: text1, .. } + )); + assert!(matches!( + &rdom[iter.prev(&rdom).id()].node_data.node_type, + NodeType::Element { tag: div_tag, .. } + )); + assert!(matches!( + &rdom[iter.prev(&rdom).id()].node_data.node_type, + NodeType::Element { tag: div_tag, .. } + )); + assert!(matches!( + &rdom[iter.prev(&rdom).id()].node_data.node_type, + NodeType::Element { tag: div_tag, .. } + )); + assert!(matches!( + &rdom[iter.prev(&rdom).id()].node_data.node_type, + NodeType::Text { text: text3, .. } + )); +} + +#[test] +#[allow(unused_variables)] +fn persist_removes() { + use dioxus::prelude::*; + #[allow(non_snake_case)] + fn Base(cx: Scope) -> Element { + let children = match cx.generation() % 2 { + 0 => 3, + 1 => 2, + _ => unreachable!(), + }; + render!( + div{ + (0..children).map(|i|{ + rsx!{ + p{ + key: "{i}", + "{i}" + } + } + }) + } + ) + } + let mut vdom = VirtualDom::new(Base); + + let mut rdom: RealDom = RealDom::new(); + + let build = vdom.rebuild(); + let _to_update = rdom.apply_mutations(build); + + // this will end on the node that is removed + let mut iter1 = PersistantElementIter::new(); + // this will end on the after node that is removed + let mut iter2 = PersistantElementIter::new(); + // div + iter1.next(&rdom).id(); + iter2.next(&rdom).id(); + // p + iter1.next(&rdom).id(); + iter2.next(&rdom).id(); + // "1" + iter1.next(&rdom).id(); + iter2.next(&rdom).id(); + // p + iter1.next(&rdom).id(); + iter2.next(&rdom).id(); + // "2" + iter1.next(&rdom).id(); + iter2.next(&rdom).id(); + // p + iter2.next(&rdom).id(); + // "3" + iter2.next(&rdom).id(); + + vdom.mark_dirty(ScopeId(0)); + let update = vdom.render_immediate(); + iter1.prune(&update, &rdom); + iter2.prune(&update, &rdom); + let _to_update = rdom.apply_mutations(update); + + let p_tag = "1".to_string(); + let idx = iter1.next(&rdom).id(); + assert!(matches!( + &rdom[idx].node_data.node_type, + NodeType::Element { tag: p_tag, .. } + )); + let text = "2".to_string(); + let idx = iter1.next(&rdom).id(); + assert!(matches!( + &rdom[idx].node_data.node_type, + NodeType::Text { text, .. } + )); + let div_tag = "div".to_string(); + let idx = iter2.next(&rdom).id(); + assert!(matches!( + &rdom[idx].node_data.node_type, + NodeType::Element { tag: div_tag, .. } + )); +} + +#[test] +#[allow(unused_variables)] +fn persist_instertions_before() { + use dioxus::prelude::*; + #[allow(non_snake_case)] + fn Base(cx: Scope) -> Element { + let children = match cx.generation() % 2 { + 0 => 3, + 1 => 2, + _ => unreachable!(), + }; + render!( + div{ + (0..children).map(|i|{ + rsx!{ + p{ + key: "{i}", + "{i}" + } + } + }) + } + ) + } + let mut vdom = VirtualDom::new(Base); + + let mut rdom: RealDom = RealDom::new(); + + let build = vdom.rebuild(); + let _to_update = rdom.apply_mutations(build); + + let mut iter = PersistantElementIter::new(); + // div + iter.next(&rdom).id(); + // p + iter.next(&rdom).id(); + // "1" + iter.next(&rdom).id(); + // p + iter.next(&rdom).id(); + // "2" + iter.next(&rdom).id(); + + vdom.mark_dirty(ScopeId(0)); + let update = vdom.render_immediate(); + iter.prune(&update, &rdom); + let _to_update = rdom.apply_mutations(update); + + let p_tag = "div".to_string(); + let idx = iter.next(&rdom).id(); + assert!(matches!( + &rdom[idx].node_data.node_type, + NodeType::Element { tag: p_tag, .. } + )); +} + +#[test] +#[allow(unused_variables)] +fn persist_instertions_after() { + use dioxus::prelude::*; + #[allow(non_snake_case)] + fn Base(cx: Scope) -> Element { + let children = match cx.generation() % 2 { + 0 => 3, + 1 => 2, + _ => unreachable!(), + }; + render!( + div{ + (0..children).map(|i|{ + rsx!{ + p{ + key: "{i}", + "{i}" + } + } + }) + } + ) + } + let mut vdom = VirtualDom::new(Base); + + let mut rdom: RealDom = RealDom::new(); + + let build = vdom.rebuild(); + let _to_update = rdom.apply_mutations(build); + + let mut iter = PersistantElementIter::new(); + // div + iter.next(&rdom).id(); + // p + iter.next(&rdom).id(); + // "hello" + iter.next(&rdom).id(); + // p + iter.next(&rdom).id(); + // "world" + iter.next(&rdom).id(); + + let update = vdom.rebuild(); + iter.prune(&update, &rdom); + let _to_update = rdom.apply_mutations(update); + + let p_tag = "p".to_string(); + let idx = iter.next(&rdom).id(); + assert!(matches!( + &rdom[idx].node_data.node_type, + NodeType::Element { tag: p_tag, .. } + )); + let text = "hello world".to_string(); + let idx = iter.next(&rdom).id(); + assert!(matches!( + &rdom[idx].node_data.node_type, + NodeType::Text { text, .. } + )); +} From 6ee4f7df4f4fefb9dd3e4add98dfbdfa0f6f96b5 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 6 Dec 2022 08:33:51 -0600 Subject: [PATCH 40/93] update examples --- packages/native-core/src/state.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/native-core/src/state.rs b/packages/native-core/src/state.rs index 365aa151c..7e6c5266a 100644 --- a/packages/native-core/src/state.rs +++ b/packages/native-core/src/state.rs @@ -56,17 +56,17 @@ pub(crate) fn union_ordered_iter<'a>( /// impl ChildDepState for Layout { /// type Ctx = (); /// // The layout depends on the layout of the children. -/// type DepState = Layout; +/// type DepState = (Layout,); /// fn reduce<'a>( /// &mut self, /// _node: NodeView, -/// children: impl Iterator, +/// children: impl Iterator, /// _ctx: &Self::Ctx, /// ) -> bool /// where /// Self::DepState: 'a{ /// /// Children are layed out form left to right. The width of the parent is the sum of the widths and the max of the heights. -/// let new = children.copied().reduce(|c1, c2| Layout{ +/// let new = children.map(|(&c,)|c).reduce(|c1, c2| Layout{ /// width: c1.width + c2.width, /// height: c1.height.max(c2.height) /// }).unwrap_or_default(); @@ -108,7 +108,7 @@ pub trait ChildDepState { /// impl ParentDepState for FontSize { /// type Ctx = (); /// // The font size depends on the font size of the parent element. -/// type DepState = Self; +/// type DepState = (Self,); /// const NODE_MASK: NodeMask = /// NodeMask::new_with_attrs(AttributeMask::Static(&[ /// "font-size" @@ -116,12 +116,12 @@ pub trait ChildDepState { /// fn reduce<'a>( /// &mut self, /// node: NodeView, -/// parent: Option<&'a Self::DepState>, +/// parent: Option<(&'a Self,)>, /// ctx: &Self::Ctx, /// ) -> bool{ /// let old = *self; /// // If the font size was set on the parent, it is passed down to the current element -/// if let Some(parent) = parent { +/// if let Some((parent,)) = parent { /// *self = *parent; /// } /// // If the current node overrides the font size, use that size insead. @@ -168,6 +168,7 @@ pub trait ParentDepState { /// /// impl NodeDepState for TabIndex { /// type Ctx = (); +/// type DepState = (); /// const NODE_MASK: NodeMask = /// NodeMask::new_with_attrs(AttributeMask::Static(&[ /// "tabindex" From 4be64cb9f04b0e265c17d54e751759833e78fa39 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 6 Dec 2022 09:08:25 -0600 Subject: [PATCH 41/93] fix gaps in layout --- packages/native-core/src/utils.rs | 6 ++---- packages/tui/src/hooks.rs | 10 +++++++++- packages/tui/src/layout.rs | 6 ++++-- packages/tui/src/lib.rs | 25 +++++++++++++++++-------- packages/tui/src/query.rs | 23 ++++++++++++++++------- packages/tui/src/render.rs | 20 +++++++++----------- 6 files changed, 57 insertions(+), 33 deletions(-) diff --git a/packages/native-core/src/utils.rs b/packages/native-core/src/utils.rs index 7a55ad6db..3ffe45916 100644 --- a/packages/native-core/src/utils.rs +++ b/packages/native-core/src/utils.rs @@ -125,7 +125,7 @@ impl PersistantElementIter { /// get the next element pub fn next(&mut self, rdom: &RealDom) -> ElementProduced { - let r = if self.stack.is_empty() { + if self.stack.is_empty() { let id = NodeId(0); let new = (id, NodePosition::AtNode); self.stack.push(new); @@ -156,9 +156,7 @@ impl PersistantElementIter { ElementProduced::Progressed(self.pop()) } } - }; - println!("next: {:?}", r); - r + } } /// get the previous element diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 197c7ea79..b57b71e13 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -23,7 +23,7 @@ use std::{ use taffy::geometry::{Point, Size}; use taffy::{prelude::Layout, Taffy}; -use crate::FocusState; +use crate::{layout_to_screen_space, FocusState}; use crate::{TuiDom, TuiNode}; pub(crate) struct Event { @@ -243,7 +243,15 @@ impl InnerInputState { ) { fn layout_contains_point(layout: &Layout, point: ScreenPoint) -> bool { let Point { x, y } = layout.location; + let (x, y) = ( + layout_to_screen_space(x).round(), + layout_to_screen_space(y).round(), + ); let Size { width, height } = layout.size; + let (width, height) = ( + layout_to_screen_space(width).round(), + layout_to_screen_space(height).round(), + ); let layout_rect = Rect::new(Point2D::new(x, y), Size2D::new(width, height)); layout_rect.contains(point.cast()) diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index 1e14f159c..d863f13d8 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -7,6 +7,8 @@ use dioxus_native_core::state::ChildDepState; use dioxus_native_core_macro::sorted_str_slice; use taffy::prelude::*; +use crate::screen_to_layout_space; + #[derive(Debug, Clone, Copy, PartialEq)] pub(crate) enum PossiblyUninitalized { Uninitalized, @@ -66,10 +68,10 @@ impl ChildDepState for TaffyLayout { style = Style { size: Size { // characters are 1 point tall - height: Dimension::Points(1.0), + height: Dimension::Points(screen_to_layout_space(1)), // text is as long as it is declared - width: Dimension::Points(char_len as f32), + width: Dimension::Points(screen_to_layout_space(char_len as u16)), }, ..Default::default() }; diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index cb000f00e..058dfc7d5 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -35,6 +35,15 @@ pub use config::*; pub use hooks::*; pub(crate) use node::*; +// the layout space has a multiplier of 10 to minimize rounding errors +pub(crate)fn screen_to_layout_space(screen: u16) -> f32{ + screen as f32 * 10.0 +} + +pub(crate)fn layout_to_screen_space(layout: f32) -> f32{ + layout / 10.0 +} + #[derive(Clone)] pub struct TuiContext { tx: UnboundedSender, @@ -149,22 +158,22 @@ fn render_vdom( if !to_rerender.is_empty() || updated { updated = false; fn resize(dims: Rect, taffy: &mut Taffy, rdom: &TuiDom) { - let width = dims.width; - let height = dims.height; + let width = screen_to_layout_space(dims.width ); + let height = screen_to_layout_space(dims.height); let root_node = rdom[NodeId(0)].state.layout.node.unwrap(); // the root node fills the entire area let mut style=*taffy.style(root_node).unwrap(); style.size=Size { - width: Dimension::Points(width as f32), - height: Dimension::Points(height as f32), + width: Dimension::Points(width ), + height: Dimension::Points(height ), }; taffy.set_style(root_node, style).unwrap(); let size =Size { - width: AvailableSpace::Definite(width as f32), - height: AvailableSpace::Definite(height as f32), + width: AvailableSpace::Definite(width), + height: AvailableSpace::Definite(height), }; taffy .compute_layout( @@ -195,8 +204,8 @@ fn render_vdom( Rect { x: 0, y: 0, - width: 100, - height: 100, + width: 1000, + height: 1000, }, &mut taffy.lock().expect("taffy lock poisoned"), &rdom, diff --git a/packages/tui/src/query.rs b/packages/tui/src/query.rs index 15b7fd7f2..3fbcc15fa 100644 --- a/packages/tui/src/query.rs +++ b/packages/tui/src/query.rs @@ -11,7 +11,7 @@ use taffy::{ Taffy, }; -use crate::TuiDom; +use crate::{layout_to_screen_space, TuiDom}; /// Allows querying the layout of nodes after rendering. It will only provide a correct value after a node is rendered. /// Provided as a root context for all tui applictions. @@ -72,19 +72,28 @@ impl<'a> ElementRef<'a> { } pub fn size(&self) -> Option> { - self.layout().map(|l| l.size.map(|v| v as u32)) + self.layout().map(|l| l.size.map(|v| v.round() as u32)) } pub fn pos(&self) -> Option> { self.layout().map(|l| Point { - x: l.location.x as u32, - y: l.location.y as u32, + x: l.location.x.round() as u32, + y: l.location.y.round() as u32, }) } - pub fn layout(&self) -> Option<&Layout> { - self.stretch + pub fn layout(&self) -> Option { + let layout = self + .stretch .layout(self.inner[self.id].state.layout.node.ok()?) - .ok() + .ok(); + layout.map(|layout| Layout { + order: layout.order, + size: layout.size.map(|v| layout_to_screen_space(v)), + location: Point { + x: layout_to_screen_space(layout.location.x), + y: layout_to_screen_space(layout.location.y), + }, + }) } } diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index d779418fa..ef18f2989 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -8,6 +8,7 @@ use taffy::{ use tui::{backend::CrosstermBackend, layout::Rect, style::Color}; use crate::{ + layout_to_screen_space, style::{RinkColor, RinkStyle}, style_attributes::{BorderEdge, BorderStyle}, widget::{RinkBuffer, RinkCell, RinkWidget, WidgetWithContext}, @@ -36,15 +37,12 @@ pub(crate) fn render_vnode( location.x += parent_location.x; location.y += parent_location.y; - let Point { mut x, mut y } = location; - x = x.floor(); - y = y.floor(); - let Size { - mut width, - mut height, - } = size; - width = width.ceil(); - height = height.ceil(); + let Point { x: fx, y: fy } = location; + let x = layout_to_screen_space(fx).round() as u16; + let y = layout_to_screen_space(fy).round() as u16; + let Size { width, height } = *size; + let width = layout_to_screen_space(fx + width).round() as u16 + x; + let height = layout_to_screen_space(fy + height).round() as u16 - y; match &node.node_data.node_type { NodeType::Text { text } => { @@ -69,7 +67,7 @@ pub(crate) fn render_vnode( text, style: node.state.style.core, }; - let area = Rect::new(x as u16, y as u16, width as u16, height as u16); + let area = Rect::new(x, y, width, height); // the renderer will panic if a node is rendered out of range even if the size is zero if area.width > 0 && area.height > 0 { @@ -77,7 +75,7 @@ pub(crate) fn render_vnode( } } NodeType::Element { .. } => { - let area = Rect::new(x as u16, y as u16, width as u16, height as u16); + let area = Rect::new(x, y, width, height); // the renderer will panic if a node is rendered out of range even if the size is zero if area.width > 0 && area.height > 0 { From 4b1c6125d4b10458534b8350706e58d21ded7aab Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 6 Dec 2022 09:16:40 -0600 Subject: [PATCH 42/93] fix formatting --- packages/tui/examples/tui_colorpicker.rs | 2 +- packages/tui/src/lib.rs | 57 ++++++++++-------------- 2 files changed, 25 insertions(+), 34 deletions(-) diff --git a/packages/tui/examples/tui_colorpicker.rs b/packages/tui/examples/tui_colorpicker.rs index 3b1184930..e2c16d5bb 100644 --- a/packages/tui/examples/tui_colorpicker.rs +++ b/packages/tui/examples/tui_colorpicker.rs @@ -1,5 +1,5 @@ -use dioxus::prelude::*; use dioxus::core::RenderReturn; +use dioxus::prelude::*; use dioxus_tui::query::Query; use dioxus_tui::Size; diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 058dfc7d5..38638f7c5 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -4,19 +4,22 @@ use crossterm::{ execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; -use futures_channel::mpsc::unbounded; use dioxus_core::*; -use dioxus_native_core::{real_dom::RealDom, NodeId, SendAnyMap, FxDashSet, NodeMask}; +use dioxus_native_core::{real_dom::RealDom, FxDashSet, NodeId, NodeMask, SendAnyMap}; use focus::FocusState; use futures::{ channel::mpsc::{UnboundedReceiver, UnboundedSender}, pin_mut, StreamExt, }; +use futures_channel::mpsc::unbounded; use query::Query; -use std::{cell::RefCell, sync::{Mutex, Arc}}; use std::rc::Rc; +use std::{ + cell::RefCell, + sync::{Arc, Mutex}, +}; use std::{io, time::Duration}; -use taffy::{Taffy}; +use taffy::Taffy; pub use taffy::{geometry::Point, prelude::*}; use tui::{backend::CrosstermBackend, layout::Rect, Terminal}; @@ -36,11 +39,11 @@ pub use hooks::*; pub(crate) use node::*; // the layout space has a multiplier of 10 to minimize rounding errors -pub(crate)fn screen_to_layout_space(screen: u16) -> f32{ +pub(crate) fn screen_to_layout_space(screen: u16) -> f32 { screen as f32 * 10.0 } -pub(crate)fn layout_to_screen_space(layout: f32) -> f32{ +pub(crate) fn layout_to_screen_space(layout: f32) -> f32 { layout / 10.0 } @@ -99,7 +102,7 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) { { let mut rdom = rdom.borrow_mut(); let mutations = dom.rebuild(); - let (to_update,_) = rdom.apply_mutations(mutations); + let (to_update, _) = rdom.apply_mutations(mutations); let mut any_map = SendAnyMap::new(); any_map.insert(taffy.clone()); let _to_rerender = rdom.update_state(to_update, any_map); @@ -141,10 +144,10 @@ fn render_vdom( terminal.clear().unwrap(); } - let mut to_rerender =FxDashSet::default(); + let mut to_rerender = FxDashSet::default(); to_rerender.insert(NodeId(0)); let mut updated = true; - + loop { /* -> render the nodes in the right place with tui/crossterm @@ -158,45 +161,33 @@ fn render_vdom( if !to_rerender.is_empty() || updated { updated = false; fn resize(dims: Rect, taffy: &mut Taffy, rdom: &TuiDom) { - let width = screen_to_layout_space(dims.width ); + let width = screen_to_layout_space(dims.width); let height = screen_to_layout_space(dims.height); let root_node = rdom[NodeId(0)].state.layout.node.unwrap(); - + // the root node fills the entire area - - let mut style=*taffy.style(root_node).unwrap(); - style.size=Size { - width: Dimension::Points(width ), - height: Dimension::Points(height ), + + let mut style = *taffy.style(root_node).unwrap(); + style.size = Size { + width: Dimension::Points(width), + height: Dimension::Points(height), }; taffy.set_style(root_node, style).unwrap(); - - let size =Size { + + let size = Size { width: AvailableSpace::Definite(width), height: AvailableSpace::Definite(height), }; - taffy - .compute_layout( - root_node, - size - ) - .unwrap(); + taffy.compute_layout(root_node, size).unwrap(); } if let Some(terminal) = &mut terminal { terminal.draw(|frame| { let rdom = rdom.borrow(); - let mut taffy =taffy.lock().expect("taffy lock poisoned"); + let mut taffy = taffy.lock().expect("taffy lock poisoned"); // size is guaranteed to not change when rendering resize(frame.size(), &mut *taffy, &rdom); let root = &rdom[NodeId(0)]; - render::render_vnode( - frame, - &*taffy, - &rdom, - root, - cfg, - Point::ZERO, - ); + render::render_vnode(frame, &*taffy, &rdom, root, cfg, Point::ZERO); })?; } else { let rdom = rdom.borrow(); From 77381ceadd576127fa602ff61e8d7cd7092b3e79 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 6 Dec 2022 09:54:44 -0600 Subject: [PATCH 43/93] fix memory leak --- packages/native-core/src/real_dom.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index f5fb17603..ffda78ff7 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -76,6 +76,10 @@ impl RealDom { if self.node_id_mapping.len() <= element_id.0 { self.node_id_mapping.resize(element_id.0 + 1, None); } + if let Some(Some(old_id)) = self.node_id_mapping.get(element_id.0) { + // free the memory associated with the old node + self.tree.remove(*old_id); + } self.node_id_mapping[element_id.0] = Some(node_id); } From 610290238742c43513a7beb0457c8761abe79924 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 6 Dec 2022 14:24:35 -0600 Subject: [PATCH 44/93] work on optimizing web implementation --- packages/core/Cargo.toml | 2 + packages/core/src/arena.rs | 3 - packages/core/src/create.rs | 5 - packages/core/src/diff.rs | 18 +- packages/core/src/lazynodes.rs | 213 ++--------------- packages/core/src/mutations.rs | 8 +- packages/core/src/scheduler/wait.rs | 16 -- packages/core/src/scope_arena.rs | 4 +- packages/core/src/scopes.rs | 35 ++- packages/core/src/virtual_dom.rs | 14 +- packages/hooks/src/usefuture.rs | 1 - packages/interpreter/Cargo.toml | 3 +- packages/interpreter/src/bindings.rs | 3 + packages/interpreter/src/interpreter.js | 4 +- packages/interpreter/src/lib.rs | 5 + .../interpreter/src/sledgehammer_bindings.rs | 221 ++++++++++++++++++ packages/web/src/dom.rs | 95 +++++--- 17 files changed, 352 insertions(+), 298 deletions(-) create mode 100644 packages/interpreter/src/sledgehammer_bindings.rs diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index f779e6ba2..827968655 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -35,6 +35,8 @@ indexmap = "1.7" serde = { version = "1", features = ["derive"], optional = true } anyhow = "1.0.66" +smallbox = "0.8.1" + [dev-dependencies] tokio = { version = "*", features = ["full"] } dioxus = { path = "../dioxus" } diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index d6af9b051..024ad5375 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -69,7 +69,6 @@ impl VirtualDom { ) } - println!("reclaiming {:?}", el); self.elements.try_remove(el.0) } @@ -95,9 +94,7 @@ impl VirtualDom { // Drop all the hooks once the children are dropped // this means we'll drop hooks bottom-up for hook in scope.hook_list.get_mut().drain(..) { - println!("dropping hook !"); drop(unsafe { BumpBox::from_raw(hook) }); - println!("hook dropped !"); } } diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index feaa276e4..b0a463c9e 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -388,11 +388,6 @@ impl<'b> VirtualDom { // Set the placeholder of the scope self.scopes[scope.0].placeholder.set(Some(new_id)); - println!( - "assigning id {:?} to path {:?}, template: {:?}", - new_id, &template.template.node_paths, template.template - ); - // Since the placeholder is already in the DOM, we don't create any new nodes self.mutations.push(AssignId { id: new_id, diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index c0b14d0bd..28ffb2404 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -61,17 +61,10 @@ impl<'b> VirtualDom { } fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) { - println!( - "diffing node {:#?},\n\n{:#?}", - left_template.template.name, right_template.template.name - ); - if left_template.template.name != right_template.template.name { return self.light_diff_templates(left_template, right_template); } - println!("diffing attributs..."); - for (left_attr, right_attr) in left_template .dynamic_attrs .iter() @@ -106,8 +99,6 @@ impl<'b> VirtualDom { } } - println!("diffing dyn nodes..."); - for (idx, (left_node, right_node)) in left_template .dynamic_nodes .iter() @@ -133,8 +124,6 @@ impl<'b> VirtualDom { }; } - println!("applying roots..."); - // Make sure the roots get transferred over for (left, right) in left_template .root_ids @@ -178,7 +167,6 @@ impl<'b> VirtualDom { ) { // Replace components that have different render fns if left.render_fn != right.render_fn { - dbg!(&left, &right); let created = self.create_component_node(right_template, right, idx); let head = unsafe { self.scopes[left.scope.get().unwrap().0] @@ -396,8 +384,6 @@ impl<'b> VirtualDom { "all siblings must be keyed or all siblings must be non-keyed" ); - println!("Diffing fragment {:?}, {:?}", old.len(), new.len()); - if new_is_keyed && old_is_keyed { self.diff_keyed_children(old, new); } else { @@ -420,9 +406,7 @@ impl<'b> VirtualDom { debug_assert!(!new.is_empty()); debug_assert!(!old.is_empty()); - println!("Diffing non keyed children"); - - match dbg!(old.len().cmp(&new.len())) { + match old.len().cmp(&new.len()) { Ordering::Greater => self.remove_nodes(&old[new.len()..]), Ordering::Less => self.create_and_insert_after(&new[old.len()..], old.last().unwrap()), Ordering::Equal => {} diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs index 020b307d9..72b5662fd 100644 --- a/packages/core/src/lazynodes.rs +++ b/packages/core/src/lazynodes.rs @@ -11,8 +11,13 @@ //! The logic for this was borrowed from . Unfortunately, this crate does not //! support non-static closures, so we've implemented the core logic of `ValueA` in this module. +use smallbox::{ + smallbox, + space::{S1, S16, S4, S8}, + SmallBox, +}; + use crate::{innerlude::VNode, ScopeState}; -use std::mem; /// A concrete type provider for closures that build [`VNode`] structures. /// @@ -24,14 +29,7 @@ use std::mem; /// LazyNodes::new(|f| f.element("div", [], [], [] None)) /// ``` pub struct LazyNodes<'a, 'b> { - inner: StackNodeStorage<'a, 'b>, -} - -type StackHeapSize = [usize; 16]; - -enum StackNodeStorage<'a, 'b> { - Stack(LazyStack), - Heap(Box) -> Option> + 'b>), + inner: SmallBox VNode<'a> + 'b, S16>, } impl<'a, 'b> LazyNodes<'a, 'b> { @@ -44,114 +42,11 @@ impl<'a, 'b> LazyNodes<'a, 'b> { // there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch let mut slot = Some(val); - let val = move |fac: Option<&'a ScopeState>| { - fac.map( - slot.take() - .expect("LazyNodes closure to be called only once"), - ) - }; - - // miri does not know how to work with mucking directly into bytes - // just use a heap allocated type when miri is running - if cfg!(miri) { - Self { - inner: StackNodeStorage::Heap(Box::new(val)), - } - } else { - unsafe { LazyNodes::new_inner(val) } - } - } - - /// Create a new [`LazyNodes`] closure, but force it onto the heap. - pub fn new_boxed(inner: F) -> Self - where - F: FnOnce(&'a ScopeState) -> VNode<'a> + 'b, - { - // there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch - let mut slot = Some(inner); - Self { - inner: StackNodeStorage::Heap(Box::new(move |fac: Option<&'a ScopeState>| { - fac.map( - slot.take() - .expect("LazyNodes closure to be called only once"), - ) - })), - } - } - - unsafe fn new_inner(val: F) -> Self - where - F: FnMut(Option<&'a ScopeState>) -> Option> + 'b, - { - let mut ptr: *const _ = &val as &dyn FnMut(Option<&'a ScopeState>) -> Option>; - - assert_eq!( - ptr as *const u8, &val as *const _ as *const u8, - "MISUSE: Closure returned different pointer" - ); - assert_eq!( - std::mem::size_of_val(&*ptr), - std::mem::size_of::(), - "MISUSE: Closure returned a subset pointer" - ); - - let words = ptr_as_slice(&mut ptr); - assert!( - words[0] == &val as *const _ as usize, - "BUG: Pointer layout is not (data_ptr, info...)" - ); - - // - Ensure that Self is aligned same as data requires - assert!( - std::mem::align_of::() <= std::mem::align_of::(), - "TODO: Enforce alignment >{} (requires {})", - std::mem::align_of::(), - std::mem::align_of::() - ); - - let info = &words[1..]; - let data = words[0] as *mut (); - let size = mem::size_of::(); - - let stored_size = info.len() * mem::size_of::() + size; - let max_size = mem::size_of::(); - - if stored_size > max_size { - Self { - inner: StackNodeStorage::Heap(Box::new(val)), - } - } else { - let mut buf: StackHeapSize = StackHeapSize::default(); - - assert!(info.len() + round_to_words(size) <= buf.as_ref().len()); - - // Place pointer information at the end of the region - // - Allows the data to be at the start for alignment purposes - { - let info_ofs = buf.as_ref().len() - info.len(); - let info_dst = &mut buf.as_mut()[info_ofs..]; - for (d, v) in Iterator::zip(info_dst.iter_mut(), info.iter()) { - *d = *v; - } - } - - let src_ptr = data as *const u8; - let dataptr = buf.as_mut_ptr().cast::(); - - for i in 0..size { - *dataptr.add(i) = *src_ptr.add(i); - } - - std::mem::forget(val); - - Self { - inner: StackNodeStorage::Stack(LazyStack { - _align: [], - buf, - dropped: false, - }), - } + inner: smallbox!(move |f| { + let val = slot.take().expect("cannot call LazyNodes twice"); + val(f) + }), } } @@ -163,88 +58,10 @@ impl<'a, 'b> LazyNodes<'a, 'b> { /// let node = f.call(cac); /// ``` #[must_use] - pub fn call(self, f: &'a ScopeState) -> VNode<'a> { - match self.inner { - StackNodeStorage::Heap(mut lazy) => { - lazy(Some(f)).expect("Closure should not be called twice") - } - StackNodeStorage::Stack(mut stack) => stack.call(f), + pub fn call(mut self, f: &'a ScopeState) -> VNode<'a> { + if self.inner.is_heap() { + panic!(); } + (self.inner)(f) } } - -struct LazyStack { - _align: [u64; 0], - buf: StackHeapSize, - dropped: bool, -} - -impl LazyStack { - fn call<'a>(&mut self, f: &'a ScopeState) -> VNode<'a> { - let LazyStack { buf, .. } = self; - let data = buf.as_ref(); - - let info_size = - mem::size_of::<*mut dyn FnMut(Option<&'a ScopeState>) -> Option>>() - / mem::size_of::() - - 1; - - let info_ofs = data.len() - info_size; - - let g: *mut dyn FnMut(Option<&'a ScopeState>) -> Option> = - unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) }; - - self.dropped = true; - - let clos = unsafe { &mut *g }; - clos(Some(f)).unwrap() - } -} -impl Drop for LazyStack { - fn drop(&mut self) { - if !self.dropped { - let LazyStack { buf, .. } = self; - let data = buf.as_ref(); - - let info_size = - mem::size_of::<*mut dyn FnMut(Option<&ScopeState>) -> Option>>() - / mem::size_of::() - - 1; - - let info_ofs = data.len() - info_size; - - let g: *mut dyn FnMut(Option<&ScopeState>) -> Option> = - unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) }; - - self.dropped = true; - - let clos = unsafe { &mut *g }; - clos(None); - } - } -} - -/// Obtain mutable access to a pointer's words -fn ptr_as_slice(ptr: &mut T) -> &mut [usize] { - assert!(mem::size_of::() % mem::size_of::() == 0); - let words = mem::size_of::() / mem::size_of::(); - // SAFE: Points to valid memory (a raw pointer) - unsafe { core::slice::from_raw_parts_mut(ptr as *mut _ as *mut usize, words) } -} - -/// Re-construct a fat pointer -unsafe fn make_fat_ptr(data_ptr: usize, meta_vals: &[usize]) -> *mut T { - let mut rv = mem::MaybeUninit::<*mut T>::uninit(); - { - let s = ptr_as_slice(&mut rv); - s[0] = data_ptr; - s[1..].copy_from_slice(meta_vals); - } - let rv = rv.assume_init(); - assert_eq!(rv as *const (), data_ptr as *const ()); - rv -} - -fn round_to_words(len: usize) -> usize { - (len + mem::size_of::() - 1) / mem::size_of::() -} diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 3687cd956..2343c4548 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -1,6 +1,6 @@ use fxhash::FxHashSet; -use crate::{arena::ElementId, ScopeId, Template}; +use crate::{arena::ElementId, ScopeId, ScopeState, Template}; /// A container for all the relevant steps to modify the Real DOM /// @@ -257,3 +257,9 @@ pub enum Mutation<'a> { id: ElementId, }, } + +#[test] +fn sizes() { + println!("mutations: {}", std::mem::size_of::()); + println!("scope: {}", std::mem::size_of::()); +} diff --git a/packages/core/src/scheduler/wait.rs b/packages/core/src/scheduler/wait.rs index e13b0fd45..f8fdfb595 100644 --- a/packages/core/src/scheduler/wait.rs +++ b/packages/core/src/scheduler/wait.rs @@ -40,8 +40,6 @@ impl VirtualDom { } pub(crate) fn handle_suspense_wakeup(&mut self, id: SuspenseId) { - println!("suspense notified"); - let leaf = self .scheduler .leaves @@ -67,13 +65,6 @@ impl VirtualDom { // safety: we're not going to modify the suspense context but we don't want to make a clone of it let fiber = self.acquire_suspense_boundary(leaf.scope_id); - println!("ready pool"); - - println!( - "Existing mutations {:?}, scope {:?}", - fiber.mutations, fiber.id - ); - let scope = &mut self.scopes[scope_id.0]; let arena = scope.current_frame(); @@ -106,16 +97,9 @@ impl VirtualDom { std::mem::swap(&mut self.mutations, mutations); if fiber.waiting_on.borrow().is_empty() { - println!("fiber is finished!"); self.finished_fibers.push(fiber.id); - } else { - println!("fiber is not finished {:?}", fiber.waiting_on); } - } else { - println!("nodes arent right"); } - } else { - println!("not ready"); } } } diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index e94818fee..b4cc54c19 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -30,8 +30,8 @@ impl VirtualDom { height, props: Some(props), placeholder: Default::default(), - node_arena_1: BumpFrame::new(50), - node_arena_2: BumpFrame::new(50), + node_arena_1: BumpFrame::new(0), + node_arena_2: BumpFrame::new(0), spawned_tasks: Default::default(), render_cnt: Default::default(), hook_arena: Default::default(), diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index bb2776861..54402f159 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -10,6 +10,7 @@ use crate::{ Attribute, AttributeValue, Element, Event, Properties, TaskId, }; use bumpalo::{boxed::Box as BumpBox, Bump}; +use fxhash::{FxHashMap, FxHashSet}; use std::{ any::{Any, TypeId}, cell::{Cell, RefCell}, @@ -81,10 +82,10 @@ pub struct ScopeState { pub(crate) hook_list: RefCell>, pub(crate) hook_idx: Cell, - pub(crate) shared_contexts: RefCell>>, + pub(crate) shared_contexts: RefCell>>, pub(crate) tasks: Rc, - pub(crate) spawned_tasks: HashSet, + pub(crate) spawned_tasks: FxHashSet, pub(crate) props: Option>>, pub(crate) placeholder: Cell>, @@ -383,9 +384,35 @@ impl<'src> ScopeState { /// Uses the currently active [`Bump`] allocator pub fn raw_text(&'src self, args: Arguments) -> &'src str { args.as_str().unwrap_or_else(|| { - use bumpalo::core_alloc::fmt::Write; let mut str_buf = bumpalo::collections::String::new_in(self.bump()); - str_buf.write_fmt(args).unwrap(); + struct Wrapper<'a, 'b>(&'a mut bumpalo::collections::Vec<'b, u8>); + + impl<'a, 'b> std::fmt::Write for Wrapper<'a, 'b> { + fn write_str(&mut self, s: &str) -> std::fmt::Result { + unsafe { + copy(s, self.0); + } + Ok(()) + } + fn write_char(&mut self, c: char) -> std::fmt::Result { + self.0.push(c as u8); + Ok(()) + } + } + + unsafe fn copy<'a>(s: &str, buf: &mut bumpalo::collections::Vec<'a, u8>) { + let old_len = buf.len(); + buf.reserve(s.len()); + let ptr = buf.as_mut_ptr().add(old_len); + let bytes = s.as_bytes(); + let str_ptr = bytes.as_ptr(); + for o in 0..s.len() { + *ptr.add(o) = *str_ptr.add(o); + } + buf.set_len(old_len + s.len()); + } + + let _ = unsafe { std::fmt::write(&mut Wrapper(str_buf.as_mut_vec()), args) }; str_buf.into_bump_str() }) } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index a7046636b..643de2caf 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -14,6 +14,7 @@ use crate::{ AttributeValue, Element, Event, Scope, SuspenseContext, }; use futures_util::{pin_mut, StreamExt}; +use fxhash::FxHashMap; use slab::Slab; use std::{ any::Any, @@ -148,7 +149,7 @@ use std::{ /// } /// ``` pub struct VirtualDom { - pub(crate) templates: HashMap>, + pub(crate) templates: FxHashMap>, pub(crate) scopes: Slab>, pub(crate) dirty_scopes: BTreeSet, pub(crate) scheduler: Rc, @@ -362,8 +363,6 @@ impl VirtualDom { let target_path = el_ref.path; for (idx, attr) in template.dynamic_attrs.iter().enumerate() { - println!("{:?} \n {:?} \n {:?}", attr, name, element); - let this_path = template.template.attr_paths[idx]; // listeners are required to be prefixed with "on", but they come back to the virtualdom with that missing @@ -385,8 +384,6 @@ impl VirtualDom { } } - println!("calling listeners: {:?}", listeners); - // Now that we've accumulated all the parent attributes for the target element, call them in reverse order // We check the bubble state between each call to see if the event has been stopped from bubbling for listener in listeners.drain(..).rev() { @@ -403,8 +400,6 @@ impl VirtualDom { parent_path = template.parent.and_then(|id| self.elements.get(id.0)); } - - println!("all listeners exhausted"); } /// Wait for the scheduler to have any work. @@ -527,7 +522,6 @@ impl VirtualDom { /// /// If no suspense trees are present pub async fn render_with_deadline(&mut self, deadline: impl Future) -> Mutations { - println!("render with deadline"); pin_mut!(deadline); loop { @@ -538,11 +532,11 @@ impl VirtualDom { self.mutations .templates - .extend(context.mutations.borrow_mut().templates.drain(..)); + .append(&mut context.mutations.borrow_mut().templates); self.mutations .edits - .extend(context.mutations.borrow_mut().edits.drain(..)); + .append(&mut context.mutations.borrow_mut().edits); // TODO: count how many nodes are on the stack? self.mutations.push(Mutation::ReplaceWith { diff --git a/packages/hooks/src/usefuture.rs b/packages/hooks/src/usefuture.rs index a95fc3221..1eae035be 100644 --- a/packages/hooks/src/usefuture.rs +++ b/packages/hooks/src/usefuture.rs @@ -67,7 +67,6 @@ where match waker.borrow().as_ref() { Some(waker) => waker.wake_by_ref(), None => { - println!("scheduling update"); // schedule_update() } } diff --git a/packages/interpreter/Cargo.toml b/packages/interpreter/Cargo.toml index 4eb7410bd..c00d85733 100644 --- a/packages/interpreter/Cargo.toml +++ b/packages/interpreter/Cargo.toml @@ -17,7 +17,8 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] wasm-bindgen = { version = "0.2.79", optional = true } js-sys = { version = "0.3.56", optional = true } web-sys = { version = "0.3.56", optional = true, features = ["Element", "Node"] } - +sledgehammer_bindgen = { version = "0.1.1" } +sledgehammer_utils = { version = "0.1.0" } [features] default = [] diff --git a/packages/interpreter/src/bindings.rs b/packages/interpreter/src/bindings.rs index e4ffdb7a7..b3e59233a 100644 --- a/packages/interpreter/src/bindings.rs +++ b/packages/interpreter/src/bindings.rs @@ -73,4 +73,7 @@ extern "C" { #[wasm_bindgen(method)] pub fn PushRoot(this: &Interpreter, id: u32); + + #[wasm_bindgen(method)] + pub fn AppendChildren(this: &Interpreter, id: u32, m: u32); } diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index 13414b6e2..d90d8a306 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -92,8 +92,8 @@ export class Interpreter { PopRoot() { this.stack.pop(); } - AppendChildren(many) { - let root = this.stack[this.stack.length - (1 + many)]; + AppendChildren(id, many) { + let root = this.nodes[id]; let to_add = this.stack.splice(this.stack.length - many); for (let i = 0; i < many; i++) { root.appendChild(to_add[i]); diff --git a/packages/interpreter/src/lib.rs b/packages/interpreter/src/lib.rs index 076a89197..c4b5440a4 100644 --- a/packages/interpreter/src/lib.rs +++ b/packages/interpreter/src/lib.rs @@ -1,5 +1,10 @@ pub static INTERPRETER_JS: &str = include_str!("./interpreter.js"); +#[cfg(feature = "web")] +mod sledgehammer_bindings; +#[cfg(feature = "web")] +pub use sledgehammer_bindings::*; + #[cfg(feature = "web")] mod bindings; diff --git a/packages/interpreter/src/sledgehammer_bindings.rs b/packages/interpreter/src/sledgehammer_bindings.rs new file mode 100644 index 000000000..9be487c58 --- /dev/null +++ b/packages/interpreter/src/sledgehammer_bindings.rs @@ -0,0 +1,221 @@ +use js_sys::Function; +use sledgehammer_bindgen::bindgen; +use web_sys::Node; + +#[bindgen] +mod js { + const JS: &str = r#" + class ListenerMap { + constructor(root) { + // bubbling events can listen at the root element + this.global = {}; + // non bubbling events listen at the element the listener was created at + this.local = {}; + this.root = null; + this.handler = null; + } + + create(event_name, element, bubbles) { + if (bubbles) { + if (this.global[event_name] === undefined) { + this.global[event_name] = {}; + this.global[event_name].active = 1; + this.root.addEventListener(event_name, this.handler); + } else { + this.global[event_name].active++; + } + } + else { + const id = element.getAttribute("data-dioxus-id"); + if (!this.local[id]) { + this.local[id] = {}; + } + element.addEventListener(event_name, this.handler); + } + } + + remove(element, event_name, bubbles) { + if (bubbles) { + this.global[event_name].active--; + if (this.global[event_name].active === 0) { + this.root.removeEventListener(event_name, this.global[event_name].callback); + delete this.global[event_name]; + } + } + else { + const id = element.getAttribute("data-dioxus-id"); + delete this.local[id][event_name]; + if (this.local[id].length === 0) { + delete this.local[id]; + } + element.removeEventListener(event_name, this.handler); + } + } + + removeAllNonBubbling(element) { + const id = element.getAttribute("data-dioxus-id"); + delete this.local[id]; + } + } + function SetAttributeInner(node, field, value, ns) { + const name = field; + if (ns === "style") { + // ????? why do we need to do this + if (node.style === undefined) { + node.style = {}; + } + node.style[name] = value; + } else if (ns !== null && ns !== undefined && ns !== "") { + node.setAttributeNS(ns, name, value); + } else { + switch (name) { + case "value": + if (value !== node.value) { + node.value = value; + } + break; + case "checked": + node.checked = value === "true"; + break; + case "selected": + node.selected = value === "true"; + break; + case "dangerous_inner_html": + node.innerHTML = value; + break; + default: + // https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364 + if (value === "false" && bool_attrs.hasOwnProperty(name)) { + node.removeAttribute(name); + } else { + node.setAttribute(name, value); + } + } + } + } + function LoadChild(ptr, len) { + // iterate through each number and get that child + node = stack[stack.length - 1]; + ptr_end = ptr + len; + for (; ptr < ptr_end; ptr++) { + end = m.getUint8(ptr); + for (node = node.firstChild; end > 0; end--) { + node = node.nextSibling; + } + } + return node; + } + const listeners = new ListenerMap(); + let nodes = []; + let stack = []; + const templates = {}; + let node, els, end, ptr_end, k; + export function save_template(nodes, tmpl_id) { + templates[tmpl_id] = nodes; + } + export function set_node(id, node) { + nodes[id] = node; + } + export function initilize(root, handler) { + listeners.handler = handler; + nodes = [root]; + stack = [root]; + listeners.root = root; + } + function AppendChildren(id, many){ + root = nodes[id]; + els = stack.splice(stack.length-many); + for (k = 0; k < many; k++) { + root.appendChild(els[k]); + } + } + "#; + + extern "C" { + #[wasm_bindgen] + pub fn save_template(nodes: Vec, tmpl_id: u32); + + #[wasm_bindgen] + pub fn set_node(id: u32, node: Node); + + #[wasm_bindgen] + pub fn initilize(root: Node, handler: &Function); + } + + fn mount_to_root() { + "{AppendChildren(root, stack.length-1);}" + } + fn push_root(root: u32) { + "{stack.push(nodes[$root$]);}" + } + fn append_children(id: u32, many: u32) { + "{AppendChildren($id$, $many$);}" + } + fn pop_root() { + "{stack.pop();}" + } + fn replace_with(id: u32, n: u32) { + "{root = nodes[$id$]; els = stack.splice(stack.length-$n$); if (root.listening) { listeners.removeAllNonBubbling(root); } root.replaceWith(...els);}" + } + fn insert_after(id: u32, n: u32) { + "{nodes[$id$].after(...stack.splice(stack.length-$n$));}" + } + fn insert_before(id: u32, n: u32) { + "{nodes[$id$].before(...stack.splice(stack.length-$n$));}" + } + fn remove(id: u32) { + "{node = nodes[$id$]; if (node !== undefined) { if (node.listening) { listeners.removeAllNonBubbling(node); } node.remove(); }}" + } + fn create_raw_text(text: &str) { + "{stack.push(document.createTextNode($text$));}" + } + fn create_text_node(text: &str, id: u32) { + "{node = document.createTextNode($text$); nodes[$id$] = node; stack.push(node);}" + } + fn create_placeholder(id: u32) { + "{node = document.createElement('pre'); node.hidden = true; stack.push(node); nodes[$id$] = node;}" + } + fn new_event_listener(event_name: &str, id: u32, bubbles: u8) { + r#"node = nodes[id]; if(node.listening){node.listening += 1;}else{node.listening = 1;} node.setAttribute('data-dioxus-id', `\${id}`); listeners.create($event_name$, node, $bubbles$);"# + } + fn remove_event_listener(event_name: &str, id: u32, bubbles: u8) { + "{node = nodes[$id$]; node.listening -= 1; node.removeAttribute('data-dioxus-id'); listeners.remove(node, $event_name$, $bubbles$);}" + } + fn set_text(id: u32, text: &str) { + "{nodes[$id$].textContent = $text$;}" + } + fn set_attribute(id: u32, field: &str, value: &str, ns: &str) { + "{node = nodes[$id$]; SetAttributeInner(node, $field$, $value$, $ns$);}" + } + fn remove_attribute(id: u32, field: &str, ns: &str) { + r#"{name = $field$; + node = this.nodes[$id$]; + if (ns == "style") { + node.style.removeProperty(name); + } else if (ns !== null && ns !== undefined && ns !== "") { + node.removeAttributeNS(ns, name); + } else if (name === "value") { + node.value = ""; + } else if (name === "checked") { + node.checked = false; + } else if (name === "selected") { + node.selected = false; + } else if (name === "dangerous_inner_html") { + node.innerHTML = ""; + } else { + node.removeAttribute(name); + }}"# + } + fn assign_id(ptr: u32, len: u8, id: u32) { + "{nodes[$id$] = LoadChild($ptr$, $len$);}" + } + fn hydrate_text(ptr: u32, len: u8, value: &str, id: u32) { + "{node = LoadChild($ptr$, $len$); node.textContent = $value$; nodes[$id$] = node;}" + } + fn replace_placeholder(ptr: u32, len: u8, n: u32) { + "{els = stack.splice(stack.length - $n$); node = LoadChild($ptr$, $len$); node.replaceWith(...els);}" + } + fn load_template(tmpl_id: u32, index: u32, id: u32) { + "{node = templates[$tmpl_id$][$index$].cloneNode(true); nodes[$id$] = node; stack.push(node);}" + } +} diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index cf248df95..7cb137845 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -7,22 +7,22 @@ //! - tests to ensure dyn_into works for various event types. //! - Partial delegation?> -use dioxus_core::{ElementId, Mutation, Mutations, Template, TemplateAttribute, TemplateNode}; +use dioxus_core::{Mutation, Template, TemplateAttribute, TemplateNode}; use dioxus_html::{event_bubbles, CompositionData, FormData}; -use dioxus_interpreter_js::Interpreter; +use dioxus_interpreter_js::{save_template, Channel}; use futures_channel::mpsc; -use js_sys::Function; +use rustc_hash::{FxHashMap, FxHashSet}; use std::{any::Any, rc::Rc}; -use wasm_bindgen::{closure::Closure, JsCast, JsValue}; +use wasm_bindgen::{closure::Closure, JsCast}; use web_sys::{Document, Element, Event, HtmlElement}; use crate::Config; pub struct WebsysDom { document: Document, - interpreter: Interpreter, - handler: Closure, - root: Element, + templates: FxHashMap, + max_template_id: u32, + interpreter: Channel, } impl WebsysDom { @@ -34,19 +34,25 @@ impl WebsysDom { Some(root) => root, None => document.create_element("body").ok().unwrap(), }; + let interpreter = Channel::default(); + let handler: Closure = + Closure::wrap(Box::new(move |event: &web_sys::Event| { + let _ = event_channel.unbounded_send(event.clone()); + })); + + dioxus_interpreter_js::initilize(root.unchecked_into(), handler.as_ref().unchecked_ref()); + handler.forget(); Self { document, - interpreter: Interpreter::new(root.clone()), - root, - handler: Closure::wrap(Box::new(move |event: &web_sys::Event| { - let _ = event_channel.unbounded_send(event.clone()); - })), + interpreter, + templates: FxHashMap::default(), + max_template_id: 0, } } pub fn mount(&mut self) { - self.interpreter.MountToRoot(); + self.interpreter.mount_to_root(); } pub fn load_templates(&mut self, templates: &[Template]) { @@ -59,7 +65,10 @@ impl WebsysDom { roots.push(self.create_template_node(root)) } - self.interpreter.SaveTemplate(roots, template.name); + self.templates + .insert(template.name.to_owned(), self.max_template_id); + save_template(roots, self.max_template_id); + self.max_template_id += 1 } } @@ -113,41 +122,51 @@ impl WebsysDom { pub fn apply_edits(&mut self, mut edits: Vec) { use Mutation::*; - let i = &self.interpreter; - for edit in edits.drain(..) { + let i = &mut self.interpreter; + for edit in &edits { match edit { - AssignId { path, id } => i.AssignId(path, id.0 as u32), - CreatePlaceholder { id } => i.CreatePlaceholder(id.0 as u32), - CreateTextNode { value, id } => i.CreateTextNode(value.into(), id.0 as u32), - HydrateText { path, value, id } => i.HydrateText(path, value, id.0 as u32), - LoadTemplate { name, index, id } => i.LoadTemplate(name, index as u32, id.0 as u32), - ReplaceWith { id, m } => i.ReplaceWith(id.0 as u32, m as u32), - ReplacePlaceholder { path, m } => i.ReplacePlaceholder(path, m as u32), - InsertAfter { id, m } => i.InsertAfter(id.0 as u32, m as u32), - InsertBefore { id, m } => i.InsertBefore(id.0 as u32, m as u32), + AppendChildren { id, m } => i.append_children(id.0 as u32, *m as u32), + AssignId { path, id } => { + i.assign_id(path.as_ptr() as u32, path.len() as u8, id.0 as u32) + } + CreatePlaceholder { id } => i.create_placeholder(id.0 as u32), + CreateTextNode { value, id } => i.create_text_node(value, id.0 as u32), + HydrateText { path, value, id } => { + i.hydrate_text(path.as_ptr() as u32, path.len() as u8, value, id.0 as u32) + } + LoadTemplate { name, index, id } => { + if let Some(tmpl_id) = self.templates.get(*name) { + i.load_template(*tmpl_id, *index as u32, id.0 as u32) + } + } + ReplaceWith { id, m } => i.replace_with(id.0 as u32, *m as u32), + ReplacePlaceholder { path, m } => { + i.replace_placeholder(path.as_ptr() as u32, path.len() as u8, *m as u32) + } + InsertAfter { id, m } => i.insert_after(id.0 as u32, *m as u32), + InsertBefore { id, m } => i.insert_before(id.0 as u32, *m as u32), SetAttribute { name, value, id, ns, - } => i.SetAttribute(id.0 as u32, name, value.into(), ns), + } => i.set_attribute(id.0 as u32, name, value, ns.unwrap_or_default()), SetBoolAttribute { name, value, id } => { - i.SetBoolAttribute(id.0 as u32, name, value) + i.set_attribute(id.0 as u32, name, if *value { "true" } else { "false" }, "") } - SetText { value, id } => i.SetText(id.0 as u32, value.into()), - NewEventListener { name, scope, id } => { - self.interpreter.NewEventListener( - name, - id.0 as u32, - self.handler.as_ref().unchecked_ref(), - event_bubbles(&name[2..]), - ); + SetText { value, id } => i.set_text(id.0 as u32, value), + NewEventListener { name, scope: _, id } => { + i.new_event_listener(name, id.0 as u32, event_bubbles(&name[2..]) as u8); } - RemoveEventListener { name, id } => i.RemoveEventListener(name, id.0 as u32), - Remove { id } => i.Remove(id.0 as u32), - PushRoot { id } => i.PushRoot(id.0 as u32), + RemoveEventListener { name, id } => { + i.remove_event_listener(name, id.0 as u32, event_bubbles(&name[2..]) as u8) + } + Remove { id } => i.remove(id.0 as u32), + PushRoot { id } => i.push_root(id.0 as u32), } } + edits.clear(); + i.flush(); } } From d64d92e4b4bbbbde019cfdd3458b9cab3daafd65 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 7 Dec 2022 07:24:51 -0600 Subject: [PATCH 45/93] disable rif raf --- packages/web/src/lib.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 7b730db79..92cbffe4f 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -234,6 +234,7 @@ pub async fn run_with_props(root: fn(Scope) -> Element, root_prop res = rx.try_next().transpose().unwrap().ok(); } + // Todo: This is currently disabled because it has a negative impact on responce times for events but it could be re-enabled for tasks // Jank free rendering // // 1. wait for the browser to give us "idle" time @@ -242,13 +243,13 @@ pub async fn run_with_props(root: fn(Scope) -> Element, root_prop // 4. Wait for the animation frame to patch the dom // wait for the mainthread to schedule us in - let deadline = work_loop.wait_for_idle_time().await; + // let deadline = work_loop.wait_for_idle_time().await; // run the virtualdom work phase until the frame deadline is reached - let edits = dom.render_with_deadline(deadline).await; + let edits = dom.render_immediate(); // wait for the animation frame to fire so we can apply our changes - work_loop.wait_for_raf().await; + // work_loop.wait_for_raf().await; log::debug!("edits {:#?}", edits); From 08da53303d7fc35f9833d98c9d609a681303ac64 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 7 Dec 2022 08:44:54 -0600 Subject: [PATCH 46/93] optimizations aroud preallocating --- packages/core/src/create.rs | 26 +++++++++++--------------- packages/core/src/mutations.rs | 8 +------- packages/core/src/scope_arena.rs | 15 +++++++++++---- 3 files changed, 23 insertions(+), 26 deletions(-) diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index b0a463c9e..c5e2c6b1c 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -35,6 +35,9 @@ impl<'b> VirtualDom { let cur_scope = self.scope_stack.last().copied().unwrap(); + // we know that this will generate at least one mutation per node + self.mutations.edits.reserve(template.template.roots.len()); + let mut on_stack = 0; for (root_idx, root) in template.template.roots.iter().enumerate() { // We might need to generate an ID for the root node @@ -207,19 +210,12 @@ impl<'b> VirtualDom { // If it's all dynamic nodes, then we don't need to register it // Quickly run through and see if it's all just dynamic nodes - let dynamic_roots = template - .template - .roots - .iter() - .filter(|root| { - matches!( - root, - TemplateNode::Dynamic(_) | TemplateNode::DynamicText(_) - ) - }) - .count(); - - if dynamic_roots == template.template.roots.len() { + if template.template.roots.iter().all(|root| { + matches!( + root, + TemplateNode::Dynamic(_) | TemplateNode::DynamicText(_) + ) + }) { return; } @@ -290,7 +286,7 @@ impl<'b> VirtualDom { } pub(crate) fn create_fragment(&mut self, nodes: &'b [VNode<'b>]) -> usize { - nodes.iter().fold(0, |acc, child| acc + self.create(child)) + nodes.iter().map(|child| self.create(child)).sum() } pub(super) fn create_component_node( @@ -301,7 +297,7 @@ impl<'b> VirtualDom { ) -> usize { let props = component .props - .replace(None) + .take() .expect("Props to always exist when a component is being created"); let unbounded_props = unsafe { std::mem::transmute(props) }; diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 2343c4548..3687cd956 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -1,6 +1,6 @@ use fxhash::FxHashSet; -use crate::{arena::ElementId, ScopeId, ScopeState, Template}; +use crate::{arena::ElementId, ScopeId, Template}; /// A container for all the relevant steps to modify the Real DOM /// @@ -257,9 +257,3 @@ pub enum Mutation<'a> { id: ElementId, }, } - -#[test] -fn sizes() { - println!("mutations: {}", std::mem::size_of::()); - println!("scope: {}", std::mem::size_of::()); -} diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index b4cc54c19..d9e13baea 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -9,6 +9,7 @@ use crate::{ virtual_dom::VirtualDom, AttributeValue, DynamicNode, VNode, }; +use bumpalo::Bump; use futures_util::FutureExt; use std::{ mem, @@ -94,7 +95,13 @@ impl VirtualDom { let mut new_nodes = unsafe { let scope = self.scopes[scope_id.0].as_mut(); - scope.previous_frame_mut().bump.reset(); + // if this frame hasn't been intialized yet, we can guess the size of the next frame to be more efficient + if scope.previous_frame().bump.allocated_bytes() == 0 { + scope.previous_frame_mut().bump = + Bump::with_capacity(scope.current_frame().bump.allocated_bytes()); + } else { + scope.previous_frame_mut().bump.reset(); + } // Make sure to reset the hook counter so we give out hooks in the right order scope.hook_idx.set(0); @@ -160,8 +167,8 @@ impl VirtualDom { let frame = scope.previous_frame(); // set the new head of the bump frame - let alloced = &*frame.bump.alloc(new_nodes); - frame.node.set(alloced); + let allocated = &*frame.bump.alloc(new_nodes); + frame.node.set(allocated); // And move the render generation forward by one scope.render_cnt.set(scope.render_cnt.get() + 1); @@ -173,6 +180,6 @@ impl VirtualDom { }); // rebind the lifetime now that its stored internally - unsafe { mem::transmute(alloced) } + unsafe { mem::transmute(allocated) } } } From 84416cbe6a84d1b48074dae55562297ff5d73c98 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 7 Dec 2022 10:11:27 -0600 Subject: [PATCH 47/93] check if templates are ptr eq before doing full equality check --- packages/core/src/diff.rs | 4 +++- packages/core/src/scopes.rs | 30 ++---------------------------- 2 files changed, 5 insertions(+), 29 deletions(-) diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 28ffb2404..544b4bb16 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -61,7 +61,9 @@ impl<'b> VirtualDom { } fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) { - if left_template.template.name != right_template.template.name { + if !std::ptr::eq(left_template.template.name, right_template.template.name) + && left_template.template.name != right_template.template.name + { return self.light_diff_templates(left_template, right_template); } diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 54402f159..06f5bf8c3 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -384,35 +384,9 @@ impl<'src> ScopeState { /// Uses the currently active [`Bump`] allocator pub fn raw_text(&'src self, args: Arguments) -> &'src str { args.as_str().unwrap_or_else(|| { + use bumpalo::core_alloc::fmt::Write; let mut str_buf = bumpalo::collections::String::new_in(self.bump()); - struct Wrapper<'a, 'b>(&'a mut bumpalo::collections::Vec<'b, u8>); - - impl<'a, 'b> std::fmt::Write for Wrapper<'a, 'b> { - fn write_str(&mut self, s: &str) -> std::fmt::Result { - unsafe { - copy(s, self.0); - } - Ok(()) - } - fn write_char(&mut self, c: char) -> std::fmt::Result { - self.0.push(c as u8); - Ok(()) - } - } - - unsafe fn copy<'a>(s: &str, buf: &mut bumpalo::collections::Vec<'a, u8>) { - let old_len = buf.len(); - buf.reserve(s.len()); - let ptr = buf.as_mut_ptr().add(old_len); - let bytes = s.as_bytes(); - let str_ptr = bytes.as_ptr(); - for o in 0..s.len() { - *ptr.add(o) = *str_ptr.add(o); - } - buf.set_len(old_len + s.len()); - } - - let _ = unsafe { std::fmt::write(&mut Wrapper(str_buf.as_mut_vec()), args) }; + str_buf.write_fmt(args).unwrap(); str_buf.into_bump_str() }) } From 5c6c235df0e08089197f08669506fdb610f2ef07 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 7 Dec 2022 15:48:25 -0600 Subject: [PATCH 48/93] cleanup code --- packages/core/src/lazynodes.rs | 6 +----- packages/interpreter/Cargo.toml | 1 + packages/interpreter/src/lib.rs | 4 ++-- packages/web/Cargo.toml | 2 +- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs index 72b5662fd..594672707 100644 --- a/packages/core/src/lazynodes.rs +++ b/packages/core/src/lazynodes.rs @@ -11,11 +11,7 @@ //! The logic for this was borrowed from . Unfortunately, this crate does not //! support non-static closures, so we've implemented the core logic of `ValueA` in this module. -use smallbox::{ - smallbox, - space::{S1, S16, S4, S8}, - SmallBox, -}; +use smallbox::{smallbox, space::S16, SmallBox}; use crate::{innerlude::VNode, ScopeState}; diff --git a/packages/interpreter/Cargo.toml b/packages/interpreter/Cargo.toml index c00d85733..821804f08 100644 --- a/packages/interpreter/Cargo.toml +++ b/packages/interpreter/Cargo.toml @@ -23,3 +23,4 @@ sledgehammer_utils = { version = "0.1.0" } [features] default = [] web = ["wasm-bindgen", "js-sys", "web-sys"] +sledgehammer = ["wasm-bindgen", "js-sys", "web-sys"] diff --git a/packages/interpreter/src/lib.rs b/packages/interpreter/src/lib.rs index c4b5440a4..962bd6bbb 100644 --- a/packages/interpreter/src/lib.rs +++ b/packages/interpreter/src/lib.rs @@ -1,8 +1,8 @@ pub static INTERPRETER_JS: &str = include_str!("./interpreter.js"); -#[cfg(feature = "web")] +#[cfg(feature = "sledgehammer")] mod sledgehammer_bindings; -#[cfg(feature = "web")] +#[cfg(feature = "sledgehammer")] pub use sledgehammer_bindings::*; #[cfg(feature = "web")] diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index 5be66b63d..b3c39b97b 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -14,7 +14,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] dioxus-core = { path = "../core", version = "^0.2.1", features = ["serialize"] } dioxus-html = { path = "../html", version = "^0.2.1", features = ["wasm-bind"] } dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.1", features = [ - "web" + "sledgehammer" ] } js-sys = "0.3.56" From cbaf36251f39001a6c677f509db81541046596c7 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 7 Dec 2022 17:24:11 -0600 Subject: [PATCH 49/93] cargo fix --- packages/core/src/scopes.rs | 1 - packages/core/src/virtual_dom.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 270704ca3..c46bbd4f0 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -14,7 +14,6 @@ use fxhash::{FxHashMap, FxHashSet}; use std::{ any::{Any, TypeId}, cell::{Cell, RefCell}, - collections::{HashMap, HashSet}, fmt::Arguments, future::Future, rc::Rc, diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 471b8ccf7..f7500af2b 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -20,7 +20,7 @@ use std::{ any::Any, borrow::BorrowMut, cell::Cell, - collections::{BTreeSet, HashMap}, + collections::{BTreeSet}, future::Future, rc::Rc, }; From 56b027ef331250e71bda3c3d1e668b08de7a1ef1 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 7 Dec 2022 17:24:50 -0600 Subject: [PATCH 50/93] fix more lints --- packages/core/src/virtual_dom.rs | 9 +-------- packages/web/src/lib.rs | 12 ++++++------ 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index f7500af2b..7a7c516f6 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -16,14 +16,7 @@ use crate::{ use futures_util::{pin_mut, StreamExt}; use fxhash::FxHashMap; use slab::Slab; -use std::{ - any::Any, - borrow::BorrowMut, - cell::Cell, - collections::{BTreeSet}, - future::Future, - rc::Rc, -}; +use std::{any::Any, borrow::BorrowMut, cell::Cell, collections::BTreeSet, future::Future, rc::Rc}; /// A virtual node system that progresses user events and diffs UI trees. /// diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 3c8398852..7e9478614 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -53,15 +53,15 @@ // - Do the VDOM work during the idlecallback // - Do DOM work in the next requestAnimationFrame callback -use std::time::Duration; + pub use crate::cfg::Config; use crate::dom::virtual_event_from_websys_event; pub use crate::util::use_eval; use dioxus_core::{Element, ElementId, Scope, VirtualDom}; -use futures_channel::mpsc::unbounded; + use futures_util::{pin_mut, FutureExt, StreamExt}; -use gloo_timers::future::sleep; + mod cache; mod cfg; @@ -181,7 +181,7 @@ pub async fn run_with_props(root: fn(Scope) -> Element, root_prop wasm_bindgen::intern(s); } - let should_hydrate = cfg.hydrate; + let _should_hydrate = cfg.hydrate; let (tx, mut rx) = futures_channel::mpsc::unbounded(); @@ -201,7 +201,7 @@ pub async fn run_with_props(root: fn(Scope) -> Element, root_prop // the mutations come back with nothing - we need to actually mount them websys_dom.mount(); - let mut work_loop = ric_raf::RafLoop::new(); + let _work_loop = ric_raf::RafLoop::new(); loop { log::debug!("waiting for work"); @@ -214,7 +214,7 @@ pub async fn run_with_props(root: fn(Scope) -> Element, root_prop futures_util::select! { _ = work => None, - new_template = hotreload_rx.next() => { + _new_template = hotreload_rx.next() => { todo!("Implement hot reload"); None } From 029255c7aceba710132a75ba948ab02521d1a6ec Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 7 Dec 2022 20:28:01 -0600 Subject: [PATCH 51/93] widgets --- packages/native-core-macro/src/lib.rs | 3 +- packages/native-core/src/real_dom.rs | 33 +- packages/native-core/src/utils/cursor.rs | 484 ++++++++++++++++++ packages/native-core/src/utils/mod.rs | 3 + .../persistant_iterator.rs} | 0 packages/tui/Cargo.toml | 1 + packages/tui/examples/tui_widgets.rs | 89 ++++ packages/tui/src/lib.rs | 2 + packages/tui/src/prelude/mod.rs | 1 + packages/tui/src/query.rs | 2 +- packages/tui/src/render.rs | 2 +- packages/tui/src/widgets/button.rs | 56 ++ packages/tui/src/widgets/checkbox.rs | 82 +++ packages/tui/src/widgets/input.rs | 102 ++++ packages/tui/src/widgets/mod.rs | 18 + packages/tui/src/widgets/number.rs | 231 +++++++++ packages/tui/src/widgets/password.rs | 187 +++++++ packages/tui/src/widgets/slider.rs | 108 ++++ packages/tui/src/widgets/textbox.rs | 185 +++++++ 19 files changed, 1562 insertions(+), 27 deletions(-) create mode 100644 packages/native-core/src/utils/cursor.rs create mode 100644 packages/native-core/src/utils/mod.rs rename packages/native-core/src/{utils.rs => utils/persistant_iterator.rs} (100%) create mode 100644 packages/tui/examples/tui_widgets.rs create mode 100644 packages/tui/src/prelude/mod.rs create mode 100644 packages/tui/src/widgets/button.rs create mode 100644 packages/tui/src/widgets/checkbox.rs create mode 100644 packages/tui/src/widgets/input.rs create mode 100644 packages/tui/src/widgets/mod.rs create mode 100644 packages/tui/src/widgets/number.rs create mode 100644 packages/tui/src/widgets/password.rs create mode 100644 packages/tui/src/widgets/slider.rs create mode 100644 packages/tui/src/widgets/textbox.rs diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index dda96b512..5bf50afcc 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -306,8 +306,7 @@ impl Member { + field.ty.to_token_stream().to_string().as_str()) .as_str(), Span::call_site(), - ) - .into(), + ), ident: field.ident.as_ref()?.clone(), }) } diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 7d56b0dca..656c59b52 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -102,11 +102,7 @@ impl RealDom { self.tree.add_child(node_id, child_id); } - fn create_template_node( - &mut self, - node: &TemplateNode, - mutations_vec: &mut FxHashMap, - ) -> RealNodeId { + fn create_template_node(&mut self, node: &TemplateNode) -> RealNodeId { match node { TemplateNode::Element { tag, @@ -139,27 +135,18 @@ impl RealDom { }); let node_id = self.create_node(node); for child in *children { - let child_id = self.create_template_node(child, mutations_vec); + let child_id = self.create_template_node(child); self.add_child(node_id, child_id); } node_id } - TemplateNode::Text { text } => { - let node_id = self.create_node(Node::new(NodeType::Text { - text: text.to_string(), - })); - node_id - } - TemplateNode::Dynamic { .. } => { - let node_id = self.create_node(Node::new(NodeType::Placeholder)); - node_id - } - TemplateNode::DynamicText { .. } => { - let node_id = self.create_node(Node::new(NodeType::Text { - text: String::new(), - })); - node_id - } + TemplateNode::Text { text } => self.create_node(Node::new(NodeType::Text { + text: text.to_string(), + })), + TemplateNode::Dynamic { .. } => self.create_node(Node::new(NodeType::Placeholder)), + TemplateNode::DynamicText { .. } => self.create_node(Node::new(NodeType::Text { + text: String::new(), + })), } } @@ -172,7 +159,7 @@ impl RealDom { for template in mutations.templates { let mut template_root_ids = Vec::new(); for root in template.roots { - let id = self.create_template_node(root, &mut nodes_updated); + let id = self.create_template_node(root); template_root_ids.push(id); } self.templates diff --git a/packages/native-core/src/utils/cursor.rs b/packages/native-core/src/utils/cursor.rs new file mode 100644 index 000000000..86bcb71b1 --- /dev/null +++ b/packages/native-core/src/utils/cursor.rs @@ -0,0 +1,484 @@ +use std::cmp::Ordering; + +use dioxus_html::input_data::keyboard_types::{Code, Key, Modifiers}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Pos { + pub col: usize, + pub row: usize, +} + +impl Pos { + pub fn new(col: usize, row: usize) -> Self { + Self { row, col } + } + + pub fn up(&mut self, rope: &str) { + self.move_row(-1, rope); + } + + pub fn down(&mut self, rope: &str) { + self.move_row(1, rope); + } + + pub fn right(&mut self, rope: &str) { + self.move_col(1, rope); + } + + pub fn left(&mut self, rope: &str) { + self.move_col(-1, rope); + } + + pub fn move_row(&mut self, change: i32, rope: &str) { + let new = self.row as i32 + change; + if new >= 0 && new < rope.lines().count() as i32 { + self.row = new as usize; + } + } + + pub fn move_col(&mut self, change: i32, rope: &str) { + self.realize_col(rope); + let idx = self.idx(rope) as i32; + if idx + change >= 0 && idx + change <= rope.len() as i32 { + let len_line = self.len_line(rope) as i32; + let new_col = self.col as i32 + change; + let diff = new_col - len_line; + if diff > 0 { + self.down(rope); + self.col = 0; + self.move_col(diff - 1, rope); + } else if new_col < 0 { + self.up(rope); + self.col = self.len_line(rope); + self.move_col(new_col + 1, rope); + } else { + self.col = new_col as usize; + } + } + } + + pub fn col(&self, rope: &str) -> usize { + self.col.min(self.len_line(rope)) + } + + pub fn row(&self) -> usize { + self.row + } + + fn len_line(&self, rope: &str) -> usize { + let line = rope.lines().nth(self.row).unwrap_or_default(); + let len = line.len(); + if len > 0 && line.chars().nth(len - 1) == Some('\n') { + len - 1 + } else { + len + } + } + + pub fn idx(&self, rope: &str) -> usize { + rope.lines().take(self.row).map(|l| l.len()).sum::() + self.col(rope) + } + + // the column can be more than the line length, cap it + pub fn realize_col(&mut self, rope: &str) { + self.col = self.col(rope); + } +} + +impl Ord for Pos { + fn cmp(&self, other: &Self) -> Ordering { + self.row.cmp(&other.row).then(self.col.cmp(&other.col)) + } +} + +impl PartialOrd for Pos { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Cursor { + pub start: Pos, + pub end: Option, +} + +impl Cursor { + pub fn from_start(pos: Pos) -> Self { + Self { + start: pos, + end: None, + } + } + + pub fn new(start: Pos, end: Pos) -> Self { + Self { + start, + end: Some(end), + } + } + + fn move_cursor(&mut self, f: impl FnOnce(&mut Pos), shift: bool) { + if shift { + self.with_end(f); + } else { + f(&mut self.start); + self.end = None; + } + } + + fn delete_selection(&mut self, text: &mut String) -> [i32; 2] { + let first = self.first(); + let last = self.last(); + let dr = first.row as i32 - last.row as i32; + let dc = if dr != 0 { + -(last.col as i32) + } else { + first.col as i32 - last.col as i32 + }; + text.replace_range(first.idx(text)..last.idx(text), ""); + if let Some(end) = self.end.take() { + if self.start > end { + self.start = end; + } + } + [dc, dr] + } + + pub fn handle_input( + &mut self, + data: &dioxus_html::KeyboardData, + text: &mut String, + max_width: usize, + ) { + use Code::*; + match data.code() { + ArrowUp => { + self.move_cursor(|c| c.up(text), data.modifiers().contains(Modifiers::SHIFT)); + } + ArrowDown => { + self.move_cursor( + |c| c.down(text), + data.modifiers().contains(Modifiers::SHIFT), + ); + } + ArrowRight => { + if data.modifiers().contains(Modifiers::CONTROL) { + self.move_cursor( + |c| { + let mut change = 1; + let idx = c.idx(text); + let length = text.len(); + while idx + change < length { + let chr = text.chars().nth(idx + change).unwrap(); + if chr.is_whitespace() { + break; + } + change += 1; + } + c.move_col(change as i32, text); + }, + data.modifiers().contains(Modifiers::SHIFT), + ); + } else { + self.move_cursor( + |c| c.right(text), + data.modifiers().contains(Modifiers::SHIFT), + ); + } + } + ArrowLeft => { + if data.modifiers().contains(Modifiers::CONTROL) { + self.move_cursor( + |c| { + let mut change = -1; + let idx = c.idx(text) as i32; + while idx + change > 0 { + let chr = text.chars().nth((idx + change) as usize).unwrap(); + if chr == ' ' { + break; + } + change -= 1; + } + c.move_col(change as i32, text); + }, + data.modifiers().contains(Modifiers::SHIFT), + ); + } else { + self.move_cursor( + |c| c.left(text), + data.modifiers().contains(Modifiers::SHIFT), + ); + } + } + End => { + self.move_cursor( + |c| c.col = c.len_line(text), + data.modifiers().contains(Modifiers::SHIFT), + ); + } + Home => { + self.move_cursor(|c| c.col = 0, data.modifiers().contains(Modifiers::SHIFT)); + } + Backspace => { + self.start.realize_col(text); + let mut start_idx = self.start.idx(text); + if self.end.is_some() { + self.delete_selection(text); + } else if start_idx > 0 { + self.start.left(text); + text.replace_range(start_idx - 1..start_idx, ""); + if data.modifiers().contains(Modifiers::CONTROL) { + start_idx = self.start.idx(text); + while start_idx > 0 + && text + .chars() + .nth(start_idx - 1) + .filter(|c| *c != ' ') + .is_some() + { + self.start.left(text); + text.replace_range(start_idx - 1..start_idx, ""); + start_idx = self.start.idx(text); + } + } + } + } + Enter => { + if text.len() + 1 - self.selection_len(text) <= max_width { + text.insert(self.start.idx(text), '\n'); + self.start.col = 0; + self.start.down(text); + } + } + Tab => { + if text.len() + 1 - self.selection_len(text) <= max_width { + self.start.realize_col(text); + self.delete_selection(text); + text.insert(self.start.idx(text), '\t'); + self.start.right(text); + } + } + _ => { + self.start.realize_col(text); + if let Key::Character(character) = data.key() { + if text.len() + 1 - self.selection_len(text) <= max_width { + self.delete_selection(text); + let character = character.chars().next().unwrap(); + text.insert(self.start.idx(text), character); + self.start.right(text); + } + } + } + } + } + + pub fn with_end(&mut self, f: impl FnOnce(&mut Pos)) { + let mut new = self.end.take().unwrap_or_else(|| self.start.clone()); + f(&mut new); + self.end.replace(new); + } + + pub fn first(&self) -> &Pos { + if let Some(e) = &self.end { + e.min(&self.start) + } else { + &self.start + } + } + + pub fn last(&self) -> &Pos { + if let Some(e) = &self.end { + e.max(&self.start) + } else { + &self.start + } + } + + pub fn selection_len(&self, text: &str) -> usize { + self.last().idx(text) - self.first().idx(text) + } +} + +impl Default for Cursor { + fn default() -> Self { + Self { + start: Pos::new(0, 0), + end: None, + } + } +} + +#[test] +fn pos_direction_movement() { + let mut pos = Pos::new(100, 0); + let text = "hello world\nhi"; + + assert_eq!(pos.col(text), text.lines().next().unwrap_or_default().len()); + pos.down(text); + assert_eq!(pos.col(text), text.lines().nth(1).unwrap_or_default().len()); + pos.up(text); + assert_eq!(pos.col(text), text.lines().next().unwrap_or_default().len()); + pos.left(text); + assert_eq!( + pos.col(text), + text.lines().next().unwrap_or_default().len() - 1 + ); + pos.right(text); + assert_eq!(pos.col(text), text.lines().next().unwrap_or_default().len()); +} + +#[test] +fn pos_col_movement() { + let mut pos = Pos::new(100, 0); + let text = "hello world\nhi"; + + // move inside a row + pos.move_col(-5, text); + assert_eq!( + pos.col(text), + text.lines().next().unwrap_or_default().len() - 5 + ); + pos.move_col(5, text); + assert_eq!(pos.col(text), text.lines().next().unwrap_or_default().len()); + + // move between rows + pos.move_col(3, text); + assert_eq!(pos.col(text), 2); + pos.move_col(-3, text); + assert_eq!(pos.col(text), text.lines().next().unwrap_or_default().len()); + + // don't panic if moving out of range + pos.move_col(-100, text); + pos.move_col(1000, text); +} + +#[test] +fn cursor_row_movement() { + let mut pos = Pos::new(100, 0); + let text = "hello world\nhi"; + + pos.move_row(1, text); + assert_eq!(pos.row(), 1); + pos.move_row(-1, text); + assert_eq!(pos.row(), 0); + + // don't panic if moving out of range + pos.move_row(-100, text); + pos.move_row(1000, text); +} + +#[test] +fn cursor_input() { + let mut cursor = Cursor::from_start(Pos::new(0, 0)); + let mut text = "hello world\nhi".to_string(); + + for _ in 0..5 { + cursor.handle_input( + &dioxus_html::KeyboardData::new( + dioxus_html::input_data::keyboard_types::Key::ArrowRight, + dioxus_html::input_data::keyboard_types::Code::ArrowRight, + dioxus_html::input_data::keyboard_types::Location::Standard, + false, + Modifiers::empty(), + ), + &mut text, + 10, + ); + } + + for _ in 0..5 { + cursor.handle_input( + &dioxus_html::KeyboardData::new( + dioxus_html::input_data::keyboard_types::Key::Backspace, + dioxus_html::input_data::keyboard_types::Code::Backspace, + dioxus_html::input_data::keyboard_types::Location::Standard, + false, + Modifiers::empty(), + ), + &mut text, + 10, + ); + } + + assert_eq!(text, " world\nhi"); + + let goal_text = "hello world\nhi"; + let max_width = goal_text.len(); + cursor.handle_input( + &dioxus_html::KeyboardData::new( + dioxus_html::input_data::keyboard_types::Key::Character("h".to_string()), + dioxus_html::input_data::keyboard_types::Code::KeyH, + dioxus_html::input_data::keyboard_types::Location::Standard, + false, + Modifiers::empty(), + ), + &mut text, + max_width, + ); + + cursor.handle_input( + &dioxus_html::KeyboardData::new( + dioxus_html::input_data::keyboard_types::Key::Character("e".to_string()), + dioxus_html::input_data::keyboard_types::Code::KeyE, + dioxus_html::input_data::keyboard_types::Location::Standard, + false, + Modifiers::empty(), + ), + &mut text, + max_width, + ); + + cursor.handle_input( + &dioxus_html::KeyboardData::new( + dioxus_html::input_data::keyboard_types::Key::Character("l".to_string()), + dioxus_html::input_data::keyboard_types::Code::KeyL, + dioxus_html::input_data::keyboard_types::Location::Standard, + false, + Modifiers::empty(), + ), + &mut text, + max_width, + ); + + cursor.handle_input( + &dioxus_html::KeyboardData::new( + dioxus_html::input_data::keyboard_types::Key::Character("l".to_string()), + dioxus_html::input_data::keyboard_types::Code::KeyL, + dioxus_html::input_data::keyboard_types::Location::Standard, + false, + Modifiers::empty(), + ), + &mut text, + max_width, + ); + + cursor.handle_input( + &dioxus_html::KeyboardData::new( + dioxus_html::input_data::keyboard_types::Key::Character("o".to_string()), + dioxus_html::input_data::keyboard_types::Code::KeyO, + dioxus_html::input_data::keyboard_types::Location::Standard, + false, + Modifiers::empty(), + ), + &mut text, + max_width, + ); + + // these should be ignored + for _ in 0..10 { + cursor.handle_input( + &dioxus_html::KeyboardData::new( + dioxus_html::input_data::keyboard_types::Key::Character("o".to_string()), + dioxus_html::input_data::keyboard_types::Code::KeyO, + dioxus_html::input_data::keyboard_types::Location::Standard, + false, + Modifiers::empty(), + ), + &mut text, + max_width, + ); + } + + assert_eq!(text.to_string(), goal_text); +} diff --git a/packages/native-core/src/utils/mod.rs b/packages/native-core/src/utils/mod.rs new file mode 100644 index 000000000..58e98b0fa --- /dev/null +++ b/packages/native-core/src/utils/mod.rs @@ -0,0 +1,3 @@ +mod persistant_iterator; +pub use persistant_iterator::*; +pub mod cursor; diff --git a/packages/native-core/src/utils.rs b/packages/native-core/src/utils/persistant_iterator.rs similarity index 100% rename from packages/native-core/src/utils.rs rename to packages/native-core/src/utils/persistant_iterator.rs diff --git a/packages/tui/Cargo.toml b/packages/tui/Cargo.toml index 116784c7b..94aaf3296 100644 --- a/packages/tui/Cargo.toml +++ b/packages/tui/Cargo.toml @@ -13,6 +13,7 @@ license = "MIT/Apache-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +dioxus = { path = "../dioxus", version = "^0.2.1" } dioxus-core = { path = "../core", version = "^0.2.1" } dioxus-html = { path = "../html", version = "^0.2.1" } dioxus-native-core = { path = "../native-core", version = "^0.2.0" } diff --git a/packages/tui/examples/tui_widgets.rs b/packages/tui/examples/tui_widgets.rs new file mode 100644 index 000000000..298685999 --- /dev/null +++ b/packages/tui/examples/tui_widgets.rs @@ -0,0 +1,89 @@ +use dioxus::prelude::*; +use dioxus_html::FormData; +use dioxus_tui::prelude::*; +use dioxus_tui::Config; + +fn main() { + dioxus_tui::launch_cfg(app, Config::new()); +} + +fn app(cx: Scope) -> Element { + let bg_green = use_state(cx, || false); + + let color = if *bg_green.get() { "green" } else { "red" }; + cx.render(rsx! { + div{ + width: "100%", + background_color: "{color}", + flex_direction: "column", + align_items: "center", + justify_content: "center", + + Input{ + oninput: |data: FormData| if &data.value == "good"{ + bg_green.set(true); + } else{ + bg_green.set(false); + }, + r#type: "checkbox", + value: "good", + width: "50%", + height: "10%", + checked: "true", + } + Input{ + oninput: |data: FormData| if &data.value == "hello world"{ + bg_green.set(true); + } else{ + bg_green.set(false); + }, + width: "50%", + height: "10%", + maxlength: "11", + } + // Input{ + // oninput: |data: FormData| { + // if (data.value.parse::().unwrap() - 40.0).abs() < 5.0 { + // bg_green.set(true); + // } else{ + // bg_green.set(false); + // } + // }, + // r#type: "range", + // min: "20", + // max: "80", + // } + // Input{ + // oninput: |data: FormData| { + // if data.value == "10"{ + // bg_green.set(true); + // } else{ + // bg_green.set(false); + // } + // }, + // r#type: "number", + // maxlength: "4", + // } + // Input{ + // oninput: |data: FormData| { + // if data.value == "hello world"{ + // bg_green.set(true); + // } else{ + // bg_green.set(false); + // } + // }, + // r#type: "password", + // width: "13px", + // height: "3px", + // maxlength: "11", + // } + // Input{ + // onclick: |_: FormData| bg_green.set(true), + // r#type: "button", + // value: "green", + // height: "3px", + // width: "7px", + // } + } + }) +} diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 38638f7c5..b3430ab0d 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -28,11 +28,13 @@ mod focus; mod hooks; mod layout; mod node; +pub mod prelude; pub mod query; mod render; mod style; mod style_attributes; mod widget; +mod widgets; pub use config::*; pub use hooks::*; diff --git a/packages/tui/src/prelude/mod.rs b/packages/tui/src/prelude/mod.rs new file mode 100644 index 000000000..2155f7707 --- /dev/null +++ b/packages/tui/src/prelude/mod.rs @@ -0,0 +1 @@ +pub use crate::widgets::*; diff --git a/packages/tui/src/query.rs b/packages/tui/src/query.rs index 64da87650..c052ad9ea 100644 --- a/packages/tui/src/query.rs +++ b/packages/tui/src/query.rs @@ -89,7 +89,7 @@ impl<'a> ElementRef<'a> { .ok(); layout.map(|layout| Layout { order: layout.order, - size: layout.size.map(|v| layout_to_screen_space(v)), + size: layout.size.map(layout_to_screen_space), location: Point { x: layout_to_screen_space(layout.location.x), y: layout_to_screen_space(layout.location.y), diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index ef18f2989..e7de5eaac 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -41,7 +41,7 @@ pub(crate) fn render_vnode( let x = layout_to_screen_space(fx).round() as u16; let y = layout_to_screen_space(fy).round() as u16; let Size { width, height } = *size; - let width = layout_to_screen_space(fx + width).round() as u16 + x; + let width = layout_to_screen_space(fx + width).round() as u16 - x; let height = layout_to_screen_space(fy + height).round() as u16 - y; match &node.node_data.node_type { diff --git a/packages/tui/src/widgets/button.rs b/packages/tui/src/widgets/button.rs new file mode 100644 index 000000000..c01cb0a25 --- /dev/null +++ b/packages/tui/src/widgets/button.rs @@ -0,0 +1,56 @@ +use std::collections::HashMap; + +use dioxus::prelude::*; +use dioxus_elements::input_data::keyboard_types::Key; +use dioxus_html as dioxus_elements; +use dioxus_html::FormData; + +#[derive(Props)] +pub(crate) struct ButtonProps<'a> { + #[props(!optional)] + raw_onclick: Option<&'a EventHandler<'a, FormData>>, + #[props(!optional)] + value: Option<&'a str>, + #[props(!optional)] + width: Option<&'a str>, + #[props(!optional)] + height: Option<&'a str>, +} + +#[allow(non_snake_case)] +pub(crate) fn Button<'a>(cx: Scope<'a, ButtonProps>) -> Element<'a> { + let state = use_state(cx, || false); + let width = cx.props.width.unwrap_or("1px"); + let height = cx.props.height.unwrap_or("1px"); + + let single_char = width == "1px" || height == "1px"; + let text = if let Some(v) = cx.props.value { v } else { "" }; + let border_style = if single_char { "none" } else { "solid" }; + let update = || { + let new_state = !state.get(); + if let Some(callback) = cx.props.raw_onclick { + callback.call(FormData { + value: text.to_string(), + values: HashMap::new(), + files: None, + }); + } + state.set(new_state); + }; + cx.render(rsx! { + div{ + width: "{width}", + height: "{height}", + border_style: "{border_style}", + onclick: move |_| { + update(); + }, + onkeydown: move |evt|{ + if !evt.is_auto_repeating() && match evt.key(){ Key::Character(c) if c == " " =>true, Key::Enter=>true, _=>false } { + update(); + } + }, + "{text}" + } + }) +} diff --git a/packages/tui/src/widgets/checkbox.rs b/packages/tui/src/widgets/checkbox.rs new file mode 100644 index 000000000..483117212 --- /dev/null +++ b/packages/tui/src/widgets/checkbox.rs @@ -0,0 +1,82 @@ +use std::collections::HashMap; + +use dioxus::prelude::*; +use dioxus_elements::input_data::keyboard_types::Key; +use dioxus_html as dioxus_elements; +use dioxus_html::FormData; + +#[derive(Props)] +pub(crate) struct CheckBoxProps<'a> { + #[props(!optional)] + raw_oninput: Option<&'a EventHandler<'a, FormData>>, + #[props(!optional)] + value: Option<&'a str>, + #[props(!optional)] + width: Option<&'a str>, + #[props(!optional)] + height: Option<&'a str>, + #[props(!optional)] + checked: Option<&'a str>, +} + +#[allow(non_snake_case)] +pub(crate) fn CheckBox<'a>(cx: Scope<'a, CheckBoxProps>) -> Element<'a> { + let state = use_state(cx, || cx.props.checked.filter(|&c| c == "true").is_some()); + let width = cx.props.width.unwrap_or("1px"); + let height = cx.props.height.unwrap_or("1px"); + + let single_char = width == "1px" && height == "1px"; + let text = if single_char { + if *state.get() { + "☑" + } else { + "☐" + } + } else if *state.get() { + "✓" + } else { + " " + }; + let border_style = if width == "1px" || height == "1px" { + "none" + } else { + "solid" + }; + let update = move || { + let new_state = !state.get(); + if let Some(callback) = cx.props.raw_oninput { + callback.call(FormData { + value: if let Some(value) = &cx.props.value { + if new_state { + value.to_string() + } else { + String::new() + } + } else { + "on".to_string() + }, + values: HashMap::new(), + files: None, + }); + } + state.set(new_state); + }; + cx.render(rsx! { + div { + width: "{width}", + height: "{height}", + border_style: "{border_style}", + align_items: "center", + justify_content: "center", + onclick: move |_| { + update(); + }, + onkeydown: move |evt| { + if !evt.is_auto_repeating() && match evt.key(){ Key::Character(c) if c == " " =>true, Key::Enter=>true, _=>false } { + update(); + } + }, + "{text}" + } + }) +} diff --git a/packages/tui/src/widgets/input.rs b/packages/tui/src/widgets/input.rs new file mode 100644 index 000000000..2294b0f10 --- /dev/null +++ b/packages/tui/src/widgets/input.rs @@ -0,0 +1,102 @@ +use dioxus::prelude::*; +use dioxus_core::prelude::fc_to_builder; +use dioxus_html::FormData; + +use crate::widgets::button::Button; +use crate::widgets::checkbox::CheckBox; +use crate::widgets::number::NumbericInput; +use crate::widgets::password::Password; +use crate::widgets::slider::Slider; +use crate::widgets::textbox::TextBox; + +#[derive(Props)] +pub struct InputProps<'a> { + r#type: Option<&'static str>, + oninput: Option>, + onclick: Option>, + value: Option<&'a str>, + size: Option<&'a str>, + maxlength: Option<&'a str>, + width: Option<&'a str>, + height: Option<&'a str>, + min: Option<&'a str>, + max: Option<&'a str>, + step: Option<&'a str>, + checked: Option<&'a str>, +} + +#[allow(non_snake_case)] +pub fn Input<'a>(cx: Scope<'a, InputProps<'a>>) -> Element<'a> { + cx.render(match cx.props.r#type { + Some("checkbox") => { + rsx! { + CheckBox{ + raw_oninput: cx.props.oninput.as_ref(), + value: cx.props.value, + width: cx.props.width, + height: cx.props.height, + checked: cx.props.checked, + } + } + } + Some("range") => { + rsx! { + Slider{ + raw_oninput: cx.props.oninput.as_ref(), + value: cx.props.value, + width: cx.props.width, + height: cx.props.height, + max: cx.props.max, + min: cx.props.min, + step: cx.props.step, + } + } + } + Some("button") => { + rsx! { + Button{ + raw_onclick: cx.props.onclick.as_ref(), + value: cx.props.value, + width: cx.props.width, + height: cx.props.height, + } + } + } + Some("number") => { + rsx! { + NumbericInput{ + raw_oninput: cx.props.oninput.as_ref(), + value: cx.props.value, + size: cx.props.size, + max_length: cx.props.maxlength, + width: cx.props.width, + height: cx.props.height, + } + } + } + Some("password") => { + rsx! { + Password{ + raw_oninput: cx.props.oninput.as_ref(), + value: cx.props.value, + size: cx.props.size, + max_length: cx.props.maxlength, + width: cx.props.width, + height: cx.props.height, + } + } + } + _ => { + rsx! { + TextBox{ + raw_oninput: cx.props.oninput.as_ref(), + value: cx.props.value, + size: cx.props.size, + max_length: cx.props.maxlength, + width: cx.props.width, + height: cx.props.height, + } + } + } + }) +} diff --git a/packages/tui/src/widgets/mod.rs b/packages/tui/src/widgets/mod.rs new file mode 100644 index 000000000..916295e50 --- /dev/null +++ b/packages/tui/src/widgets/mod.rs @@ -0,0 +1,18 @@ +mod button; +mod checkbox; +mod input; +mod number; +mod password; +mod slider; +mod textbox; + +use dioxus_core::{ElementId, RenderReturn, Scope}; +pub use input::*; + +pub(crate) fn get_root_id(cx: Scope) -> Option { + if let RenderReturn::Sync(Ok(sync)) = cx.root_node() { + sync.root_ids.get(0).map(|id| id.get()) + } else { + None + } +} diff --git a/packages/tui/src/widgets/number.rs b/packages/tui/src/widgets/number.rs new file mode 100644 index 000000000..cef829425 --- /dev/null +++ b/packages/tui/src/widgets/number.rs @@ -0,0 +1,231 @@ +use crate::widgets::get_root_id; +use crate::widgets::Input; +use crate::Query; +use crossterm::{cursor::MoveTo, execute}; +use dioxus::prelude::*; +use dioxus_core::prelude::fc_to_builder; +use dioxus_elements::input_data::keyboard_types::Key; +use dioxus_html as dioxus_elements; +use dioxus_html::FormData; +use dioxus_native_core::utils::cursor::{Cursor, Pos}; +use std::{collections::HashMap, io::stdout}; +use taffy::geometry::Point; + +#[derive(Props)] +pub(crate) struct NumbericInputProps<'a> { + #[props(!optional)] + raw_oninput: Option<&'a EventHandler<'a, FormData>>, + #[props(!optional)] + value: Option<&'a str>, + #[props(!optional)] + size: Option<&'a str>, + #[props(!optional)] + max_length: Option<&'a str>, + #[props(!optional)] + width: Option<&'a str>, + #[props(!optional)] + height: Option<&'a str>, +} +#[allow(non_snake_case)] +pub(crate) fn NumbericInput<'a>(cx: Scope<'a, NumbericInputProps>) -> Element<'a> { + let tui_query: Query = cx.consume_context().unwrap(); + let tui_query_clone = tui_query.clone(); + + let text_ref = use_ref(cx, || { + if let Some(intial_text) = cx.props.value { + intial_text.to_string() + } else { + String::new() + } + }); + let cursor = use_ref(cx, Cursor::default); + let dragging = use_state(cx, || false); + + let text = text_ref.read().clone(); + let start_highlight = cursor.read().first().idx(&text); + let end_highlight = cursor.read().last().idx(&text); + let (text_before_first_cursor, text_after_first_cursor) = text.split_at(start_highlight); + let (text_highlighted, text_after_second_cursor) = + text_after_first_cursor.split_at(end_highlight - start_highlight); + + let max_len = cx + .props + .max_length + .as_ref() + .and_then(|s| s.parse().ok()) + .unwrap_or(usize::MAX); + + let width = cx + .props + .width + .map(|s| s.to_string()) + // px is the same as em in tui + .or_else(|| cx.props.size.map(|s| s.to_string() + "px")) + .unwrap_or_else(|| "10px".to_string()); + let height = cx.props.height.unwrap_or("3px"); + + // don't draw a border unless there is enough space + let border = if width + .strip_suffix("px") + .and_then(|w| w.parse::().ok()) + .filter(|w| *w < 3) + .is_some() + || height + .strip_suffix("px") + .and_then(|h| h.parse::().ok()) + .filter(|h| *h < 3) + .is_some() + { + "none" + } else { + "solid" + }; + + let update = |text: String| { + if let Some(input_handler) = &cx.props.raw_oninput { + input_handler.call(FormData { + value: text, + values: HashMap::new(), + files: None, + }); + } + }; + let increase = move || { + let mut text = text_ref.write(); + *text = (text.parse::().unwrap_or(0.0) + 1.0).to_string(); + update(text.clone()); + }; + let decrease = move || { + let mut text = text_ref.write(); + *text = (text.parse::().unwrap_or(0.0) - 1.0).to_string(); + update(text.clone()); + }; + + render! { + div{ + width: "{width}", + height: "{height}", + border_style: "{border}", + + onkeydown: move |k| { + let is_text = match k.key(){ + Key::ArrowLeft | Key::ArrowRight | Key::Backspace => true, + Key::Character(c) if c=="." || c== "-" || c.chars().all(|c|c.is_numeric())=> true, + _ => false, + }; + if is_text{ + let mut text = text_ref.write(); + cursor.write().handle_input(&k, &mut text, max_len); + update(text.clone()); + + let node = tui_query.get(get_root_id(cx).unwrap()); + let Point{ x, y } = node.pos().unwrap(); + + let Pos { col, row } = cursor.read().start; + let (x, y) = (col as u16 + x as u16 + if border == "none" {0} else {1}, row as u16 + y as u16 + if border == "none" {0} else {1}); + if let Ok(pos) = crossterm::cursor::position() { + if pos != (x, y){ + execute!(stdout(), MoveTo(x, y)).unwrap(); + } + } + else{ + execute!(stdout(), MoveTo(x, y)).unwrap(); + } + } + else{ + match k.key() { + Key::ArrowUp =>{ + increase(); + } + Key::ArrowDown =>{ + decrease(); + } + _ => () + } + } + }, + onmousemove: move |evt| { + if *dragging.get() { + let offset = evt.data.element_coordinates(); + let mut new = Pos::new(offset.x as usize, offset.y as usize); + if border != "none" { + new.col = new.col.saturating_sub(1); + } + // textboxs are only one line tall + new.row = 0; + + if new != cursor.read().start { + cursor.write().end = Some(new); + } + } + }, + onmousedown: move |evt| { + let offset = evt.data.element_coordinates(); + let mut new = Pos::new(offset.x as usize, offset.y as usize); + if border != "none" { + new.col = new.col.saturating_sub(1); + } + new.row = 0; + + new.realize_col(&text_ref.read()); + cursor.set(Cursor::from_start(new)); + dragging.set(true); + let node = tui_query_clone.get(get_root_id(cx).unwrap()); + let Point{ x, y } = node.pos().unwrap(); + + let Pos { col, row } = cursor.read().start; + let (x, y) = (col as u16 + x as u16 + if border == "none" {0} else {1}, row as u16 + y as u16 + if border == "none" {0} else {1}); + if let Ok(pos) = crossterm::cursor::position() { + if pos != (x, y){ + execute!(stdout(), MoveTo(x, y)).unwrap(); + } + } + else{ + execute!(stdout(), MoveTo(x, y)).unwrap(); + } + }, + onmouseup: move |_| { + dragging.set(false); + }, + onmouseleave: move |_| { + dragging.set(false); + }, + onmouseenter: move |_| { + dragging.set(false); + }, + onfocusout: |_| { + execute!(stdout(), MoveTo(0, 1000)).unwrap(); + }, + + "{text_before_first_cursor}" + + span{ + background_color: "rgba(255, 255, 255, 50%)", + + "{text_highlighted}" + } + + "{text_after_second_cursor}" + + div{ + background_color: "rgba(255, 255, 255, 50%)", + color: "black", + Input{ + r#type: "button", + onclick: move |_| { + decrease(); + } + value: "<", + } + " " + Input{ + r#type: "button", + onclick: move |_| { + increase(); + } + value: ">", + } + } + } + } +} diff --git a/packages/tui/src/widgets/password.rs b/packages/tui/src/widgets/password.rs new file mode 100644 index 000000000..bcb1b77cc --- /dev/null +++ b/packages/tui/src/widgets/password.rs @@ -0,0 +1,187 @@ +use crate::widgets::get_root_id; +use crate::Query; +use crossterm::{cursor::*, execute}; +use dioxus::prelude::*; +use dioxus_elements::input_data::keyboard_types::Key; +use dioxus_html as dioxus_elements; +use dioxus_html::FormData; +use dioxus_native_core::utils::cursor::{Cursor, Pos}; +use std::{collections::HashMap, io::stdout}; +use taffy::geometry::Point; + +#[derive(Props)] +pub(crate) struct PasswordProps<'a> { + #[props(!optional)] + raw_oninput: Option<&'a EventHandler<'a, FormData>>, + #[props(!optional)] + value: Option<&'a str>, + #[props(!optional)] + size: Option<&'a str>, + #[props(!optional)] + max_length: Option<&'a str>, + #[props(!optional)] + width: Option<&'a str>, + #[props(!optional)] + height: Option<&'a str>, +} +#[allow(non_snake_case)] +pub(crate) fn Password<'a>(cx: Scope<'a, PasswordProps>) -> Element<'a> { + let tui_query: Query = cx.consume_context().unwrap(); + let tui_query_clone = tui_query.clone(); + + let text_ref = use_ref(cx, || { + if let Some(intial_text) = cx.props.value { + intial_text.to_string() + } else { + String::new() + } + }); + let cursor = use_ref(cx, Cursor::default); + let dragging = use_state(cx, || false); + + let text = text_ref.read().clone(); + let start_highlight = cursor.read().first().idx(&text); + let end_highlight = cursor.read().last().idx(&text); + let (text_before_first_cursor, text_after_first_cursor) = text.split_at(start_highlight); + let (text_highlighted, text_after_second_cursor) = + text_after_first_cursor.split_at(end_highlight - start_highlight); + + let text_before_first_cursor = ".".repeat(text_before_first_cursor.len()); + let text_highlighted = ".".repeat(text_highlighted.len()); + let text_after_second_cursor = ".".repeat(text_after_second_cursor.len()); + + let max_len = cx + .props + .max_length + .as_ref() + .and_then(|s| s.parse().ok()) + .unwrap_or(usize::MAX); + + let width = cx + .props + .width + .map(|s| s.to_string()) + // px is the same as em in tui + .or_else(|| cx.props.size.map(|s| s.to_string() + "px")) + .unwrap_or_else(|| "10px".to_string()); + let height = cx.props.height.unwrap_or("3px"); + + // don't draw a border unless there is enough space + let border = if width + .strip_suffix("px") + .and_then(|w| w.parse::().ok()) + .filter(|w| *w < 3) + .is_some() + || height + .strip_suffix("px") + .and_then(|h| h.parse::().ok()) + .filter(|h| *h < 3) + .is_some() + { + "none" + } else { + "solid" + }; + + render! { + div{ + width: "{width}", + height: "{height}", + border_style: "{border}", + align_items: "left", + + onkeydown: move |k| { + if k.key()== Key::Enter { + return; + } + let mut text = text_ref.write(); + cursor.write().handle_input(&k, &mut text, max_len); + if let Some(input_handler) = &cx.props.raw_oninput{ + input_handler.call(FormData{ + value: text.clone(), + values: HashMap::new(), + files: None + }); + } + + let node = tui_query.get(get_root_id(cx).unwrap()); + let Point{ x, y } = node.pos().unwrap(); + + let Pos { col, row } = cursor.read().start; + let (x, y) = (col as u16 + x as u16 + if border == "none" {0} else {1}, row as u16 + y as u16 + if border == "none" {0} else {1}); + if let Ok(pos) = crossterm::cursor::position() { + if pos != (x, y){ + execute!(stdout(), MoveTo(x, y)).unwrap(); + } + } + else{ + execute!(stdout(), MoveTo(x, y)).unwrap(); + } + }, + + onmousemove: move |evt| { + if *dragging.get() { + let offset = evt.data.element_coordinates(); + let mut new = Pos::new(offset.x as usize, offset.y as usize); + if border != "none" { + new.col = new.col.saturating_sub(1); + } + // textboxs are only one line tall + new.row = 0; + + if new != cursor.read().start { + cursor.write().end = Some(new); + } + } + }, + onmousedown: move |evt| { + let offset = evt.data.element_coordinates(); + let mut new = Pos::new(offset.x as usize, offset.y as usize); + if border != "none" { + new.col = new.col.saturating_sub(1); + } + // textboxs are only one line tall + new.row = 0; + + new.realize_col(&text_ref.read()); + cursor.set(Cursor::from_start(new)); + dragging.set(true); + let node = tui_query_clone.get(get_root_id(cx).unwrap()); + let Point{ x, y } = node.pos().unwrap(); + + let Pos { col, row } = cursor.read().start; + let (x, y) = (col as u16 + x as u16 + if border == "none" {0} else {1}, row as u16 + y as u16 + if border == "none" {0} else {1}); + if let Ok(pos) = crossterm::cursor::position() { + if pos != (x, y){ + execute!(stdout(), MoveTo(x, y)).unwrap(); + } + } + else{ + execute!(stdout(), MoveTo(x, y)).unwrap(); + } + }, + onmouseup: move |_| { + dragging.set(false); + }, + onmouseleave: move |_| { + dragging.set(false); + }, + onmouseenter: move |_| { + dragging.set(false); + }, + onfocusout: |_| { + execute!(stdout(), MoveTo(0, 1000)).unwrap(); + }, + + "{text_before_first_cursor}" + + span{ + background_color: "rgba(255, 255, 255, 50%)", + + "{text_highlighted}" + } + + "{text_after_second_cursor}" + } + } +} diff --git a/packages/tui/src/widgets/slider.rs b/packages/tui/src/widgets/slider.rs new file mode 100644 index 000000000..43f0ac77e --- /dev/null +++ b/packages/tui/src/widgets/slider.rs @@ -0,0 +1,108 @@ +use std::collections::HashMap; + +use crate::widgets::get_root_id; +use crate::Query; +use dioxus::prelude::*; +use dioxus_elements::input_data::keyboard_types::Key; +use dioxus_html as dioxus_elements; +use dioxus_html::FormData; + +#[derive(Props)] +pub(crate) struct SliderProps<'a> { + #[props(!optional)] + raw_oninput: Option<&'a EventHandler<'a, FormData>>, + #[props(!optional)] + value: Option<&'a str>, + #[props(!optional)] + width: Option<&'a str>, + #[props(!optional)] + height: Option<&'a str>, + #[props(!optional)] + min: Option<&'a str>, + #[props(!optional)] + max: Option<&'a str>, + #[props(!optional)] + step: Option<&'a str>, +} + +#[allow(non_snake_case)] +pub(crate) fn Slider<'a>(cx: Scope<'a, SliderProps>) -> Element<'a> { + let tui_query: Query = cx.consume_context().unwrap(); + + let value_state = use_state(cx, || 0.0); + let value: Option = cx.props.value.and_then(|v| v.parse().ok()); + let width = cx.props.width.unwrap_or("20px"); + let height = cx.props.height.unwrap_or("1px"); + let min = cx.props.min.and_then(|v| v.parse().ok()).unwrap_or(0.0); + let max = cx.props.max.and_then(|v| v.parse().ok()).unwrap_or(100.0); + let size = max - min; + let step = cx + .props + .step + .and_then(|v| v.parse().ok()) + .unwrap_or(size / 10.0); + + let current_value = if let Some(value) = value { + value + } else { + *value_state.get() + } + .max(min) + .min(max); + let fst_width = 100.0 * (current_value - min) / size; + let snd_width = 100.0 * (max - current_value) / size; + assert!(fst_width + snd_width > 99.0 && fst_width + snd_width < 101.0); + + let update = |value: String| { + if let Some(oninput) = cx.props.raw_oninput { + oninput.call(FormData { + value, + values: HashMap::new(), + files: None, + }); + } + }; + + render! { + div{ + width: "{width}", + height: "{height}", + display: "flex", + flex_direction: "row", + onkeydown: move |event| { + match event.key() { + Key::ArrowLeft => { + value_state.set((current_value - step).max(min).min(max)); + update(value_state.current().to_string()); + } + Key::ArrowRight => { + value_state.set((current_value + step).max(min).min(max)); + update(value_state.current().to_string()); + } + _ => () + } + }, + onmousemove: move |evt| { + let mouse = evt.data; + if !mouse.held_buttons().is_empty(){ + let node = tui_query.get(get_root_id(cx).unwrap()); + let width = node.size().unwrap().width; + let offset = mouse.element_coordinates(); + value_state.set(min + size*(offset.x as f32) / width as f32); + update(value_state.current().to_string()); + } + }, + div{ + width: "{fst_width}%", + background_color: "rgba(10,10,10,0.5)", + } + div{ + "|" + } + div{ + width: "{snd_width}%", + background_color: "rgba(10,10,10,0.5)", + } + } + } +} diff --git a/packages/tui/src/widgets/textbox.rs b/packages/tui/src/widgets/textbox.rs new file mode 100644 index 000000000..f3ca3e24f --- /dev/null +++ b/packages/tui/src/widgets/textbox.rs @@ -0,0 +1,185 @@ +use crate::widgets::get_root_id; +use crate::Query; +use crossterm::{cursor::*, execute}; +use dioxus::prelude::*; +use dioxus_elements::input_data::keyboard_types::Key; +use dioxus_html as dioxus_elements; +use dioxus_html::FormData; +use dioxus_native_core::utils::cursor::{Cursor, Pos}; +use std::{collections::HashMap, io::stdout}; +use taffy::geometry::Point; + +#[derive(Props)] +pub(crate) struct TextBoxProps<'a> { + #[props(!optional)] + raw_oninput: Option<&'a EventHandler<'a, FormData>>, + #[props(!optional)] + value: Option<&'a str>, + #[props(!optional)] + size: Option<&'a str>, + #[props(!optional)] + max_length: Option<&'a str>, + #[props(!optional)] + width: Option<&'a str>, + #[props(!optional)] + height: Option<&'a str>, +} +#[allow(non_snake_case)] +pub(crate) fn TextBox<'a>(cx: Scope<'a, TextBoxProps>) -> Element<'a> { + let tui_query: Query = cx.consume_context().unwrap(); + let tui_query_clone = tui_query.clone(); + + let text_ref = use_ref(cx, || { + if let Some(intial_text) = cx.props.value { + intial_text.to_string() + } else { + String::new() + } + }); + let cursor = use_ref(cx, Cursor::default); + let dragging = use_state(cx, || false); + + let text = text_ref.read().clone(); + let start_highlight = cursor.read().first().idx(&text); + let end_highlight = cursor.read().last().idx(&text); + let (text_before_first_cursor, text_after_first_cursor) = text.split_at(start_highlight); + let (text_highlighted, text_after_second_cursor) = + text_after_first_cursor.split_at(end_highlight - start_highlight); + + let max_len = cx + .props + .max_length + .as_ref() + .and_then(|s| s.parse().ok()) + .unwrap_or(usize::MAX); + + let width = cx + .props + .width + .map(|s| s.to_string()) + // px is the same as em in tui + .or_else(|| cx.props.size.map(|s| s.to_string() + "px")) + .unwrap_or_else(|| "10px".to_string()); + let height = cx.props.height.unwrap_or("3px"); + + // don't draw a border unless there is enough space + let border = if width + .strip_suffix("px") + .and_then(|w| w.parse::().ok()) + .filter(|w| *w < 3) + .is_some() + || height + .strip_suffix("px") + .and_then(|h| h.parse::().ok()) + .filter(|h| *h < 3) + .is_some() + { + "none" + } else { + "solid" + }; + + render! { + div{ + width: "{width}", + height: "{height}", + border_style: "{border}", + flex_direction: "row", + align_items: "center", + justify_content: "center", + + onkeydown: move |k| { + if k.key() == Key::Enter { + return; + } + let mut text = text_ref.write(); + cursor.write().handle_input(&k, &mut text, max_len); + if let Some(input_handler) = &cx.props.raw_oninput{ + input_handler.call(FormData{ + value: text.clone(), + values: HashMap::new(), + files: None + }); + } + + let node = tui_query.get(get_root_id(cx).unwrap()); + let Point{ x, y } = node.pos().unwrap(); + + let Pos { col, row } = cursor.read().start; + let (x, y) = (col as u16 + x as u16 + if border == "none" {0} else {1}, row as u16 + y as u16 + if border == "none" {0} else {1}); + if let Ok(pos) = crossterm::cursor::position() { + if pos != (x, y){ + execute!(stdout(), MoveTo(x, y)).unwrap(); + } + } + else{ + execute!(stdout(), MoveTo(x, y)).unwrap(); + } + }, + + onmousemove: move |evt| { + if *dragging.get() { + let offset = evt.data.element_coordinates(); + let mut new = Pos::new(offset.x as usize, offset.y as usize); + if border != "none" { + new.col = new.col.saturating_sub(1); + } + // textboxs are only one line tall + new.row = 0; + + if new != cursor.read().start { + cursor.write().end = Some(new); + } + } + }, + onmousedown: move |evt| { + let offset = evt.data.element_coordinates(); + let mut new = Pos::new(offset.x as usize, offset.y as usize); + if border != "none" { + new.col = new.col.saturating_sub(1); + } + // textboxs are only one line tall + new.row = 0; + + new.realize_col(&text_ref.read()); + cursor.set(Cursor::from_start(new)); + dragging.set(true); + let node = tui_query_clone.get(get_root_id(cx).unwrap()); + let Point{ x, y } = node.pos().unwrap(); + + let Pos { col, row } = cursor.read().start; + let (x, y) = (col as u16 + x as u16 + if border == "none" {0} else {1}, row as u16 + y as u16 + if border == "none" {0} else {1}); + if let Ok(pos) = crossterm::cursor::position() { + if pos != (x, y){ + execute!(stdout(), MoveTo(x, y)).unwrap(); + } + } + else{ + execute!(stdout(), MoveTo(x, y)).unwrap(); + } + }, + onmouseup: move |_| { + dragging.set(false); + }, + onmouseleave: move |_| { + dragging.set(false); + }, + onmouseenter: move |_| { + dragging.set(false); + }, + onfocusout: |_| { + execute!(stdout(), MoveTo(0, 1000)).unwrap(); + }, + + "{text_before_first_cursor}" + + span{ + background_color: "rgba(255, 255, 255, 50%)", + + "{text_highlighted}" + } + + "{text_after_second_cursor}" + } + } +} From bbd49bb37cf64648ca1074bb1e02bfe1715fb270 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 7 Dec 2022 23:01:13 -0600 Subject: [PATCH 52/93] cleanup code and examples --- packages/tui/examples/tui_widgets.rs | 90 +++++++++++++++------------- packages/tui/src/layout.rs | 55 ++++++++++++++++- packages/tui/src/lib.rs | 19 +++++- packages/tui/src/widgets/button.rs | 3 + packages/tui/src/widgets/number.rs | 20 ------- packages/tui/src/widgets/password.rs | 1 - packages/tui/src/widgets/textbox.rs | 3 - 7 files changed, 120 insertions(+), 71 deletions(-) diff --git a/packages/tui/examples/tui_widgets.rs b/packages/tui/examples/tui_widgets.rs index 298685999..793057bc5 100644 --- a/packages/tui/examples/tui_widgets.rs +++ b/packages/tui/examples/tui_widgets.rs @@ -41,49 +41,53 @@ fn app(cx: Scope) -> Element { height: "10%", maxlength: "11", } - // Input{ - // oninput: |data: FormData| { - // if (data.value.parse::().unwrap() - 40.0).abs() < 5.0 { - // bg_green.set(true); - // } else{ - // bg_green.set(false); - // } - // }, - // r#type: "range", - // min: "20", - // max: "80", - // } - // Input{ - // oninput: |data: FormData| { - // if data.value == "10"{ - // bg_green.set(true); - // } else{ - // bg_green.set(false); - // } - // }, - // r#type: "number", - // maxlength: "4", - // } - // Input{ - // oninput: |data: FormData| { - // if data.value == "hello world"{ - // bg_green.set(true); - // } else{ - // bg_green.set(false); - // } - // }, - // r#type: "password", - // width: "13px", - // height: "3px", - // maxlength: "11", - // } - // Input{ - // onclick: |_: FormData| bg_green.set(true), - // r#type: "button", - // value: "green", - // height: "3px", - // width: "7px", - // } + Input{ + oninput: |data: FormData| { + if (data.value.parse::().unwrap() - 40.0).abs() < 5.0 { + bg_green.set(true); + } else{ + bg_green.set(false); + } + }, + r#type: "range", + width: "50%", + height: "10%", + min: "20", + max: "80", + } + Input{ + oninput: |data: FormData| { + if data.value == "10"{ + bg_green.set(true); + } else{ + bg_green.set(false); + } + }, + r#type: "number", + width: "50%", + height: "10%", + maxlength: "4", + } + Input{ + oninput: |data: FormData| { + if data.value == "hello world"{ + bg_green.set(true); + } else{ + bg_green.set(false); + } + }, + r#type: "password", + width: "50%", + height: "10%", + maxlength: "11", + } + Input{ + onclick: |_: FormData| bg_green.set(true), + r#type: "button", + value: "green", + width: "50%", + height: "10%", + } } }) } diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index d863f13d8..ba50b59a6 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -7,7 +7,7 @@ use dioxus_native_core::state::ChildDepState; use dioxus_native_core_macro::sorted_str_slice; use taffy::prelude::*; -use crate::screen_to_layout_space; +use crate::{screen_to_layout_space, unit_to_layout_space}; #[derive(Debug, Clone, Copy, PartialEq)] pub(crate) enum PossiblyUninitalized { @@ -105,6 +105,59 @@ impl ChildDepState for TaffyLayout { child_layout.push(l.node.unwrap()); } + fn scale_dimention(d: Dimension) -> Dimension { + match d { + Dimension::Points(p) => Dimension::Points(unit_to_layout_space(p)), + Dimension::Percent(p) => Dimension::Percent(p), + Dimension::Auto => Dimension::Auto, + Dimension::Undefined => Dimension::Undefined, + } + } + let style = Style { + position: Rect { + left: scale_dimention(style.position.left), + right: scale_dimention(style.position.right), + top: scale_dimention(style.position.top), + bottom: scale_dimention(style.position.bottom), + }, + margin: Rect { + left: scale_dimention(style.margin.left), + right: scale_dimention(style.margin.right), + top: scale_dimention(style.margin.top), + bottom: scale_dimention(style.margin.bottom), + }, + padding: Rect { + left: scale_dimention(style.padding.left), + right: scale_dimention(style.padding.right), + top: scale_dimention(style.padding.top), + bottom: scale_dimention(style.padding.bottom), + }, + border: Rect { + left: scale_dimention(style.border.left), + right: scale_dimention(style.border.right), + top: scale_dimention(style.border.top), + bottom: scale_dimention(style.border.bottom), + }, + gap: Size { + width: scale_dimention(style.gap.width), + height: scale_dimention(style.gap.height), + }, + flex_basis: scale_dimention(style.flex_basis), + size: Size { + width: scale_dimention(style.size.width), + height: scale_dimention(style.size.height), + }, + min_size: Size { + width: scale_dimention(style.min_size.width), + height: scale_dimention(style.min_size.height), + }, + max_size: Size { + width: scale_dimention(style.max_size.width), + height: scale_dimention(style.max_size.height), + }, + ..style + }; + if let PossiblyUninitalized::Initialized(n) = self.node { if self.style != style { taffy.set_style(n, style).unwrap(); diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index b3430ab0d..7b4f72ce3 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -1,5 +1,6 @@ use anyhow::Result; use crossterm::{ + cursor::{MoveTo, RestorePosition, SavePosition, Show}, event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyModifiers}, execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, @@ -45,6 +46,10 @@ pub(crate) fn screen_to_layout_space(screen: u16) -> f32 { screen as f32 * 10.0 } +pub(crate) fn unit_to_layout_space(screen: f32) -> f32 { + screen * 10.0 +} + pub(crate) fn layout_to_screen_space(layout: f32) -> f32 { layout / 10.0 } @@ -138,7 +143,13 @@ fn render_vdom( let mut terminal = (!cfg.headless).then(|| { enable_raw_mode().unwrap(); let mut stdout = std::io::stdout(); - execute!(stdout, EnterAlternateScreen, EnableMouseCapture).unwrap(); + execute!( + stdout, + EnterAlternateScreen, + EnableMouseCapture, + MoveTo(0, 1000) + ) + .unwrap(); let backend = CrosstermBackend::new(io::stdout()); Terminal::new(backend).unwrap() }); @@ -183,14 +194,16 @@ fn render_vdom( taffy.compute_layout(root_node, size).unwrap(); } if let Some(terminal) = &mut terminal { + execute!(terminal.backend_mut(), SavePosition).unwrap(); terminal.draw(|frame| { let rdom = rdom.borrow(); let mut taffy = taffy.lock().expect("taffy lock poisoned"); // size is guaranteed to not change when rendering - resize(frame.size(), &mut *taffy, &rdom); + resize(frame.size(), &mut taffy, &rdom); let root = &rdom[NodeId(0)]; - render::render_vnode(frame, &*taffy, &rdom, root, cfg, Point::ZERO); + render::render_vnode(frame, &taffy, &rdom, root, cfg, Point::ZERO); })?; + execute!(terminal.backend_mut(), RestorePosition, Show).unwrap(); } else { let rdom = rdom.borrow(); resize( diff --git a/packages/tui/src/widgets/button.rs b/packages/tui/src/widgets/button.rs index c01cb0a25..f3ebc7901 100644 --- a/packages/tui/src/widgets/button.rs +++ b/packages/tui/src/widgets/button.rs @@ -42,6 +42,9 @@ pub(crate) fn Button<'a>(cx: Scope<'a, ButtonProps>) -> Element<'a> { width: "{width}", height: "{height}", border_style: "{border_style}", + flex_direction: "row", + align_items: "center", + justify_content: "center", onclick: move |_| { update(); }, diff --git a/packages/tui/src/widgets/number.rs b/packages/tui/src/widgets/number.rs index cef829425..4478246bb 100644 --- a/packages/tui/src/widgets/number.rs +++ b/packages/tui/src/widgets/number.rs @@ -206,26 +206,6 @@ pub(crate) fn NumbericInput<'a>(cx: Scope<'a, NumbericInputProps>) -> Element<'a } "{text_after_second_cursor}" - - div{ - background_color: "rgba(255, 255, 255, 50%)", - color: "black", - Input{ - r#type: "button", - onclick: move |_| { - decrease(); - } - value: "<", - } - " " - Input{ - r#type: "button", - onclick: move |_| { - increase(); - } - value: ">", - } - } } } } diff --git a/packages/tui/src/widgets/password.rs b/packages/tui/src/widgets/password.rs index bcb1b77cc..7f8455d71 100644 --- a/packages/tui/src/widgets/password.rs +++ b/packages/tui/src/widgets/password.rs @@ -88,7 +88,6 @@ pub(crate) fn Password<'a>(cx: Scope<'a, PasswordProps>) -> Element<'a> { width: "{width}", height: "{height}", border_style: "{border}", - align_items: "left", onkeydown: move |k| { if k.key()== Key::Enter { diff --git a/packages/tui/src/widgets/textbox.rs b/packages/tui/src/widgets/textbox.rs index f3ca3e24f..8628fca29 100644 --- a/packages/tui/src/widgets/textbox.rs +++ b/packages/tui/src/widgets/textbox.rs @@ -84,9 +84,6 @@ pub(crate) fn TextBox<'a>(cx: Scope<'a, TextBoxProps>) -> Element<'a> { width: "{width}", height: "{height}", border_style: "{border}", - flex_direction: "row", - align_items: "center", - justify_content: "center", onkeydown: move |k| { if k.key() == Key::Enter { From 15701d1c87199249bfc228cdbf6b221194905408 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 7 Dec 2022 23:05:00 -0600 Subject: [PATCH 53/93] make clippy happy --- packages/tui/src/widgets/number.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/tui/src/widgets/number.rs b/packages/tui/src/widgets/number.rs index 4478246bb..05cb2d678 100644 --- a/packages/tui/src/widgets/number.rs +++ b/packages/tui/src/widgets/number.rs @@ -1,9 +1,7 @@ use crate::widgets::get_root_id; -use crate::widgets::Input; use crate::Query; use crossterm::{cursor::MoveTo, execute}; use dioxus::prelude::*; -use dioxus_core::prelude::fc_to_builder; use dioxus_elements::input_data::keyboard_types::Key; use dioxus_html as dioxus_elements; use dioxus_html::FormData; From 129a67cdc6fb78b0341bdf344289cec23f70b293 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 8 Dec 2022 06:40:00 -0600 Subject: [PATCH 54/93] fix mouse interaction with widgets --- packages/tui/src/hooks.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index b57b71e13..d627ac3e0 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -287,7 +287,10 @@ impl InnerInputState { fn prepare_mouse_data(mouse_data: &MouseData, layout: &Layout) -> MouseData { let Point { x, y } = layout.location; - let node_origin = ClientPoint::new(x.into(), y.into()); + let node_origin = ClientPoint::new( + layout_to_screen_space(x).into(), + layout_to_screen_space(y).into(), + ); let new_client_coordinates = (mouse_data.client_coordinates() - node_origin) .to_point() From 7ef180409d00365275cce6f4f675f24b87df88bf Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 8 Dec 2022 07:41:04 -0600 Subject: [PATCH 55/93] use sledgehammer-bindgen 0.1.2 --- packages/interpreter/Cargo.toml | 6 +++--- packages/interpreter/src/bindings.rs | 3 --- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/interpreter/Cargo.toml b/packages/interpreter/Cargo.toml index 821804f08..07797897a 100644 --- a/packages/interpreter/Cargo.toml +++ b/packages/interpreter/Cargo.toml @@ -17,10 +17,10 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] wasm-bindgen = { version = "0.2.79", optional = true } js-sys = { version = "0.3.56", optional = true } web-sys = { version = "0.3.56", optional = true, features = ["Element", "Node"] } -sledgehammer_bindgen = { version = "0.1.1" } -sledgehammer_utils = { version = "0.1.0" } +sledgehammer_bindgen = { version = "0.1.2", optional = true } +sledgehammer_utils = { version = "0.1.0", optional = true } [features] default = [] web = ["wasm-bindgen", "js-sys", "web-sys"] -sledgehammer = ["wasm-bindgen", "js-sys", "web-sys"] +sledgehammer = ["wasm-bindgen", "js-sys", "web-sys", "sledgehammer_bindgen", "sledgehammer_utils"] diff --git a/packages/interpreter/src/bindings.rs b/packages/interpreter/src/bindings.rs index 1393774b9..478b8ca5c 100644 --- a/packages/interpreter/src/bindings.rs +++ b/packages/interpreter/src/bindings.rs @@ -17,9 +17,6 @@ extern "C" { #[wasm_bindgen(method)] pub fn MountToRoot(this: &Interpreter); - #[wasm_bindgen(method)] - pub fn AppendChildren(this: &Interpreter, m: u32, id: u32); - #[wasm_bindgen(method)] pub fn AssignId(this: &Interpreter, path: &[u8], id: u32); From e863ef29b8841aec550b5d8d7e0a56ed8b6c9555 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 8 Dec 2022 10:44:56 -0600 Subject: [PATCH 56/93] allow returning values from use_eval --- examples/eval.rs | 15 ++++++- packages/desktop/src/controller.rs | 6 ++- packages/desktop/src/desktop_context.rs | 55 ++++++++++++++++++++++--- packages/desktop/src/lib.rs | 12 +++++- packages/web/Cargo.toml | 1 + packages/web/src/util.rs | 24 +++++++++-- 6 files changed, 100 insertions(+), 13 deletions(-) diff --git a/examples/eval.rs b/examples/eval.rs index 495288fcb..d2bfcfa25 100644 --- a/examples/eval.rs +++ b/examples/eval.rs @@ -1,4 +1,5 @@ use dioxus::prelude::*; +use dioxus_desktop::EvalResult; fn main() { dioxus_desktop::launch(app); @@ -7,6 +8,15 @@ fn main() { fn app(cx: Scope) -> Element { let script = use_state(&cx, String::new); let eval = dioxus_desktop::use_eval(&cx); + let future: &UseRef> = use_ref(&cx, || None); + if future.read().is_some() { + let future_clone = future.clone(); + cx.spawn(async move { + if let Some(fut) = future_clone.with_mut(|o| o.take()) { + println!("{:?}", fut.await) + } + }); + } cx.render(rsx! { div { @@ -16,7 +26,10 @@ fn app(cx: Scope) -> Element { oninput: move |e| script.set(e.value.clone()), } button { - onclick: move |_| eval(script), + onclick: move |_| { + let fut = eval(script); + future.set(Some(fut)); + }, "Execute" } } diff --git a/packages/desktop/src/controller.rs b/packages/desktop/src/controller.rs index f59874f85..a4c76f79f 100644 --- a/packages/desktop/src/controller.rs +++ b/packages/desktop/src/controller.rs @@ -3,6 +3,7 @@ use crate::desktop_context::{DesktopContext, UserWindowEvent}; use dioxus_core::*; #[cfg(target_os = "ios")] use objc::runtime::Object; +use serde_json::Value; use std::{ collections::HashMap, sync::Arc, @@ -17,6 +18,7 @@ use wry::{ pub(super) struct DesktopController { pub(super) webviews: HashMap, pub(super) sender: futures_channel::mpsc::UnboundedSender, + pub(super) eval_sender: tokio::sync::mpsc::UnboundedSender, pub(super) pending_edits: Arc>>, pub(super) quit_app_on_close: bool, pub(super) is_ready: Arc, @@ -38,6 +40,7 @@ impl DesktopController { let pending_edits = edit_queue.clone(); let return_sender = sender.clone(); let desktop_context_proxy = proxy.clone(); + let (eval_sender, eval_reciever) = tokio::sync::mpsc::unbounded_channel::(); std::thread::spawn(move || { // We create the runtime as multithreaded, so you can still "spawn" onto multiple threads @@ -50,7 +53,7 @@ impl DesktopController { let mut dom = VirtualDom::new_with_props_and_scheduler(root, props, (sender, receiver)); - let window_context = DesktopContext::new(desktop_context_proxy); + let window_context = DesktopContext::new(desktop_context_proxy, eval_reciever); dom.base_scope().provide_context(window_context); @@ -88,6 +91,7 @@ impl DesktopController { Self { pending_edits, sender: return_sender, + eval_sender, webviews: HashMap::new(), is_ready: Arc::new(AtomicBool::new(false)), quit_app_on_close: true, diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index f4d19bb68..463435dfe 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -1,5 +1,11 @@ use crate::controller::DesktopController; use dioxus_core::ScopeState; +use serde::de::Error; +use serde_json::Value; +use std::future::Future; +use std::future::IntoFuture; +use std::pin::Pin; +use std::rc::Rc; use wry::application::event_loop::ControlFlow; use wry::application::event_loop::EventLoopProxy; #[cfg(target_os = "ios")] @@ -33,11 +39,18 @@ pub fn use_window(cx: &ScopeState) -> &DesktopContext { pub struct DesktopContext { /// The wry/tao proxy to the current window pub proxy: ProxyType, + pub(super) eval_reciever: Rc>>, } impl DesktopContext { - pub(crate) fn new(proxy: ProxyType) -> Self { - Self { proxy } + pub(crate) fn new( + proxy: ProxyType, + eval_reciever: tokio::sync::mpsc::UnboundedReceiver, + ) -> Self { + Self { + proxy, + eval_reciever: Rc::new(tokio::sync::Mutex::new(eval_reciever)), + } } /// trigger the drag-window event @@ -245,7 +258,13 @@ pub(super) fn handler( } Eval(code) => { - if let Err(e) = webview.evaluate_script(code.as_str()) { + let script = format!( + r#"window.ipc.postMessage(JSON.stringify({{"method":"eval_result", params: (function(){{ + {} + }})()}}));"#, + code + ); + if let Err(e) = webview.evaluate_script(&script) { // we can't panic this error. log::warn!("Eval script error: {e}"); } @@ -280,10 +299,36 @@ pub(super) fn handler( } /// Get a closure that executes any JavaScript in the WebView context. -pub fn use_eval(cx: &ScopeState) -> &dyn Fn(S) { +pub fn use_eval(cx: &ScopeState) -> &dyn Fn(S) -> EvalResult { let desktop = use_window(cx).clone(); + cx.use_hook(|| { + move |script| { + desktop.eval(script); + let recv = desktop.eval_reciever.clone(); + EvalResult { reciever: recv } + } + }) +} - cx.use_hook(|| move |script| desktop.eval(script)) +/// A future that resolves to the result of a JavaScript evaluation. +pub struct EvalResult { + reciever: Rc>>, +} + +impl IntoFuture for EvalResult { + type Output = Result; + + type IntoFuture = Pin>>>; + + fn into_future(self) -> Self::IntoFuture { + Box::pin(async move { + let mut reciever = self.reciever.lock().await; + match reciever.recv().await { + Some(result) => Ok(result), + None => Err(serde_json::Error::custom("No result returned")), + } + }) as Pin>>> + } } #[cfg(target_os = "ios")] diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index e218570f4..fccc3ac02 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -13,7 +13,7 @@ mod hot_reload; mod protocol; use desktop_context::UserWindowEvent; -pub use desktop_context::{use_eval, use_window, DesktopContext}; +pub use desktop_context::{use_eval, use_window, DesktopContext, EvalResult}; pub use wry; pub use wry::application as tao; @@ -126,7 +126,11 @@ pub fn launch_with_props(root: Component

, props: P, mut cf let window = builder.build(event_loop).unwrap(); let window_id = window.id(); - let (is_ready, sender) = (desktop.is_ready.clone(), desktop.sender.clone()); + let (is_ready, sender, eval_sender) = ( + desktop.is_ready.clone(), + desktop.sender.clone(), + desktop.eval_sender.clone(), + ); let proxy = proxy.clone(); @@ -143,6 +147,10 @@ pub fn launch_with_props(root: Component

, props: P, mut cf .with_ipc_handler(move |_window: &Window, payload: String| { parse_ipc_message(&payload) .map(|message| match message.method() { + "eval_result" => { + let result = message.params(); + eval_sender.send(result).unwrap(); + } "user_event" => { let event = trigger_from_serialized(message.params()); log::trace!("User event: {:?}", event); diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index a36197e50..c3e13b1da 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -30,6 +30,7 @@ futures-util = "0.3.19" smallstr = "0.2.0" futures-channel = "0.3.21" serde_json = { version = "1.0" } +serde = { version = "1.0" } [dependencies.web-sys] version = "0.3.56" diff --git a/packages/web/src/util.rs b/packages/web/src/util.rs index 772f53608..b6518b8c7 100644 --- a/packages/web/src/util.rs +++ b/packages/web/src/util.rs @@ -1,6 +1,10 @@ //! Utilities specific to websys +use std::str::FromStr; + use dioxus_core::*; +use serde::de::Error; +use serde_json::Value; /// Get a closure that executes any JavaScript in the webpage. /// @@ -15,12 +19,24 @@ use dioxus_core::*; /// /// The closure will panic if the provided script is not valid JavaScript code /// or if it returns an uncaught error. -pub fn use_eval(cx: &ScopeState) -> &dyn Fn(S) { +pub fn use_eval( + cx: &ScopeState, +) -> &dyn Fn(S) -> Result { cx.use_hook(|| { |script: S| { - js_sys::Function::new_no_args(&script.to_string()) - .call0(&wasm_bindgen::JsValue::NULL) - .expect("failed to eval script"); + let body = script.to_string(); + if let Ok(value) = + js_sys::Function::new_no_args(&body).call0(&wasm_bindgen::JsValue::NULL) + { + if let Ok(stringified) = js_sys::JSON::stringify(&value) { + let string: String = stringified.into(); + Value::from_str(&string) + } else { + Err(serde_json::Error::custom("Failed to stringify result")) + } + } else { + Err(serde_json::Error::custom("Failed to execute script")) + } } }) } From 35bebbf0cb3a28aaf63db74c49b6fd133e123079 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 8 Dec 2022 10:57:09 -0600 Subject: [PATCH 57/93] wrap the web eval result in a compatability layer --- packages/web/src/lib.rs | 2 +- packages/web/src/util.rs | 58 ++++++++++++++++++++++++++++++---------- 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 4c0831ef5..a807317f9 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -57,7 +57,7 @@ use std::rc::Rc; pub use crate::cfg::Config; -pub use crate::util::use_eval; +pub use crate::util::{use_eval, EvalResult}; use dioxus_core::prelude::Component; use dioxus_core::SchedulerMsg; use dioxus_core::VirtualDom; diff --git a/packages/web/src/util.rs b/packages/web/src/util.rs index b6518b8c7..3ed8bd479 100644 --- a/packages/web/src/util.rs +++ b/packages/web/src/util.rs @@ -1,6 +1,9 @@ //! Utilities specific to websys -use std::str::FromStr; +use std::{ + future::{IntoFuture, Ready}, + str::FromStr, +}; use dioxus_core::*; use serde::de::Error; @@ -19,24 +22,51 @@ use serde_json::Value; /// /// The closure will panic if the provided script is not valid JavaScript code /// or if it returns an uncaught error. -pub fn use_eval( - cx: &ScopeState, -) -> &dyn Fn(S) -> Result { +pub fn use_eval(cx: &ScopeState) -> &dyn Fn(S) -> EvalResult { cx.use_hook(|| { |script: S| { let body = script.to_string(); - if let Ok(value) = - js_sys::Function::new_no_args(&body).call0(&wasm_bindgen::JsValue::NULL) - { - if let Ok(stringified) = js_sys::JSON::stringify(&value) { - let string: String = stringified.into(); - Value::from_str(&string) + EvalResult { + value: if let Ok(value) = + js_sys::Function::new_no_args(&body).call0(&wasm_bindgen::JsValue::NULL) + { + if let Ok(stringified) = js_sys::JSON::stringify(&value) { + if !stringified.is_undefined() && stringified.is_valid_utf16() { + let string: String = stringified.into(); + Value::from_str(&string) + } else { + Err(serde_json::Error::custom("Failed to stringify result")) + } + } else { + Err(serde_json::Error::custom("Failed to stringify result")) + } } else { - Err(serde_json::Error::custom("Failed to stringify result")) - } - } else { - Err(serde_json::Error::custom("Failed to execute script")) + Err(serde_json::Error::custom("Failed to execute script")) + }, } } }) } + +/// A wrapper around the result of a JavaScript evaluation. +/// This implements IntoFuture to be compatible with the desktop renderer's EvalResult. +pub struct EvalResult { + value: Result, +} + +impl EvalResult { + /// Get the result of the Javascript execution. + pub fn get(self) -> Result { + self.value + } +} + +impl IntoFuture for EvalResult { + type Output = Result; + + type IntoFuture = Ready>; + + fn into_future(self) -> Self::IntoFuture { + std::future::ready(self.value) + } +} From b254d9e761ecc51589113f2249879d9d6ef0f080 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 9 Dec 2022 15:03:56 -0800 Subject: [PATCH 58/93] chore: remove cross-crate doc comments --- examples/rsx_usage.rs | 3 +-- packages/core-macro/Cargo.toml | 2 +- packages/core-macro/src/lib.rs | 10 ---------- packages/dioxus/src/lib.rs | 2 -- packages/rsx/Cargo.toml | 8 ++++++-- 5 files changed, 8 insertions(+), 17 deletions(-) diff --git a/examples/rsx_usage.rs b/examples/rsx_usage.rs index fb7bb623c..23ab19cf9 100644 --- a/examples/rsx_usage.rs +++ b/examples/rsx_usage.rs @@ -1,5 +1,4 @@ #![allow(non_snake_case)] - //! A tour of the rsx! macro //! ------------------------ //! @@ -39,7 +38,7 @@ //! - Accept a list of vnodes as children for a Fragment component //! - Allow keyed fragments in iterators //! - Allow top-level fragments -//! + fn main() { dioxus_desktop::launch(app); } diff --git a/packages/core-macro/Cargo.toml b/packages/core-macro/Cargo.toml index f9b97a436..e8dd1e1ef 100644 --- a/packages/core-macro/Cargo.toml +++ b/packages/core-macro/Cargo.toml @@ -18,7 +18,7 @@ proc-macro = true proc-macro2 = { version = "1.0" } quote = "1.0" syn = { version = "1.0", features = ["full", "extra-traits"] } -dioxus-rsx = { path = "../rsx" } +dioxus-rsx = { path = "../rsx", version = "0.0.1" } # testing [dev-dependencies] diff --git a/packages/core-macro/src/lib.rs b/packages/core-macro/src/lib.rs index 475000016..4e4f5600a 100644 --- a/packages/core-macro/src/lib.rs +++ b/packages/core-macro/src/lib.rs @@ -26,11 +26,6 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token } /// The rsx! macro makes it easy for developers to write jsx-style markup in their components. -/// -/// ## Complete Reference Guide: -/// ```ignore -#[doc = include_str!("../../../examples/rsx_usage.rs")] -/// ``` #[proc_macro] pub fn rsx(s: TokenStream) -> TokenStream { match syn::parse::(s) { @@ -42,11 +37,6 @@ pub fn rsx(s: TokenStream) -> TokenStream { /// The render! macro makes it easy for developers to write jsx-style markup in their components. /// /// The render macro automatically renders rsx - making it unhygenic. -/// -/// ## Complete Reference Guide: -/// ```ignore -#[doc = include_str!("../../../examples/rsx_usage.rs")] -/// ``` #[proc_macro] pub fn render(s: TokenStream) -> TokenStream { match syn::parse::(s) { diff --git a/packages/dioxus/src/lib.rs b/packages/dioxus/src/lib.rs index 86adae336..fed3d2bd5 100644 --- a/packages/dioxus/src/lib.rs +++ b/packages/dioxus/src/lib.rs @@ -1,5 +1,3 @@ -#![doc = include_str!("../../../notes/README.md")] - pub use dioxus_core as core; #[cfg(feature = "hooks")] diff --git a/packages/rsx/Cargo.toml b/packages/rsx/Cargo.toml index 40b785b6e..40637eff8 100644 --- a/packages/rsx/Cargo.toml +++ b/packages/rsx/Cargo.toml @@ -1,8 +1,13 @@ [package] name = "dioxus-rsx" -version = "0.0.0" +version = "0.0.1" edition = "2018" license = "MIT/Apache-2.0" +description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences" +repository = "https://github.com/DioxusLabs/dioxus/" +homepage = "https://dioxuslabs.com" +documentation = "https://docs.rs/dioxus-rsx" +keywords = ["dom", "ui", "gui", "react", "wasm"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -10,5 +15,4 @@ license = "MIT/Apache-2.0" proc-macro2 = { version = "1.0", features = ["span-locations"] } syn = { version = "1.0", features = ["full", "extra-traits"] } quote = { version = "1.0" } -dioxus-core = { path = "../core", features = ["serialize"] } serde = { version = "1.0", features = ["derive"] } From afeb308a1f92c0e2ceb994cb06554f6b8a5a58ef Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 9 Dec 2022 16:38:05 -0800 Subject: [PATCH 59/93] chore: remove dbgs --- packages/desktop/src/protocol.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/desktop/src/protocol.rs b/packages/desktop/src/protocol.rs index cc084b403..a544aa305 100644 --- a/packages/desktop/src/protocol.rs +++ b/packages/desktop/src/protocol.rs @@ -104,10 +104,10 @@ fn get_asset_root() -> Option { #[cfg(target_os = "macos")] { let bundle = core_foundation::bundle::CFBundle::main_bundle(); - let bundle_path = dbg!(bundle.path()?); - let resources_path = dbg!(bundle.resources_path()?); - let absolute_resources_root = dbg!(bundle_path.join(resources_path)); - let canonical_resources_root = dbg!(dunce::canonicalize(absolute_resources_root).ok()?); + let bundle_path = bundle.path()?; + let resources_path = bundle.resources_path()?; + let absolute_resources_root = bundle_path.join(resources_path); + let canonical_resources_root = dunce::canonicalize(absolute_resources_root).ok()?; return Some(canonical_resources_root); } From f153cdf61a408e9dd71487601aa6467de4cc2745 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 9 Dec 2022 16:40:09 -0800 Subject: [PATCH 60/93] fix: dont panic lazynodes --- packages/core/src/lazynodes.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs index 594672707..eca7829dc 100644 --- a/packages/core/src/lazynodes.rs +++ b/packages/core/src/lazynodes.rs @@ -55,9 +55,6 @@ impl<'a, 'b> LazyNodes<'a, 'b> { /// ``` #[must_use] pub fn call(mut self, f: &'a ScopeState) -> VNode<'a> { - if self.inner.is_heap() { - panic!(); - } (self.inner)(f) } } From 1b7fe3fcde243ee83c7ff198afbac90e096bffdc Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 9 Dec 2022 16:44:08 -0800 Subject: [PATCH 61/93] fix: dont use id for append --- packages/interpreter/src/interpreter.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index 7b29c1683..3ac52a8f1 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -89,8 +89,9 @@ export class Interpreter { PopRoot() { this.stack.pop(); } - AppendChildren(id, many) { - let root = this.nodes[id]; + AppendChildren(many) { + // let root = this.nodes[id]; + let root = this.stack[this.stack.length - 1 - many]; let to_add = this.stack.splice(this.stack.length - many); for (let i = 0; i < many; i++) { root.appendChild(to_add[i]); From 6166d4eb736a1eb2d95e49edbbf01e4e079335c7 Mon Sep 17 00:00:00 2001 From: Sam Schmeink Date: Sat, 10 Dec 2022 03:56:48 +0100 Subject: [PATCH 62/93] fix: replace fxhash with rustc-hash --- packages/core/Cargo.toml | 2 +- packages/core/src/diff.rs | 4 ++-- packages/core/src/mutations.rs | 2 +- packages/core/src/scopes.rs | 2 +- packages/core/src/virtual_dom.rs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 827968655..37f78f04d 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -18,7 +18,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] bumpalo = { version = "3.6", features = ["collections", "boxed"] } # faster hashmaps -fxhash = "0.2" +rustc-hash = "1.1.0" # Used in diffing longest-increasing-subsequence = "0.1.0" diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 8abf8b4ee..e893cd2b5 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -11,7 +11,7 @@ use crate::{ AttributeValue, TemplateNode, }; -use fxhash::{FxHashMap, FxHashSet}; +use rustc_hash::{FxHashMap, FxHashSet}; use DynamicNode::*; impl<'b> VirtualDom { @@ -434,7 +434,7 @@ impl<'b> VirtualDom { // The stack is empty upon entry. fn diff_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) { if cfg!(debug_assertions) { - let mut keys = fxhash::FxHashSet::default(); + let mut keys = rustc_hash::FxHashSet::default(); let mut assert_unique_keys = |children: &'b [VNode<'b>]| { keys.clear(); for child in children { diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 2512ae9fa..ecca2a678 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -1,4 +1,4 @@ -use fxhash::FxHashSet; +use rustc_hash::FxHashSet; use crate::{arena::ElementId, ScopeId, Template}; diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index c46bbd4f0..241418fa7 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -10,7 +10,7 @@ use crate::{ Attribute, AttributeValue, Element, Event, Properties, TaskId, }; use bumpalo::{boxed::Box as BumpBox, Bump}; -use fxhash::{FxHashMap, FxHashSet}; +use rustc_hash::{FxHashMap, FxHashSet}; use std::{ any::{Any, TypeId}, cell::{Cell, RefCell}, diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 7a7c516f6..42ba90437 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -14,7 +14,7 @@ use crate::{ AttributeValue, Element, Event, Scope, SuspenseContext, }; use futures_util::{pin_mut, StreamExt}; -use fxhash::FxHashMap; +use rustc_hash::FxHashMap; use slab::Slab; use std::{any::Any, borrow::BorrowMut, cell::Cell, collections::BTreeSet, future::Future, rc::Rc}; From f7717d8eb28b57378852e0f9107bb5d5694a3c56 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 10 Dec 2022 08:29:54 -0600 Subject: [PATCH 63/93] bump sledgehammer version --- packages/interpreter/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/interpreter/Cargo.toml b/packages/interpreter/Cargo.toml index 07797897a..6fcf19f27 100644 --- a/packages/interpreter/Cargo.toml +++ b/packages/interpreter/Cargo.toml @@ -17,7 +17,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] wasm-bindgen = { version = "0.2.79", optional = true } js-sys = { version = "0.3.56", optional = true } web-sys = { version = "0.3.56", optional = true, features = ["Element", "Node"] } -sledgehammer_bindgen = { version = "0.1.2", optional = true } +sledgehammer_bindgen = { version = "0.1.3", optional = true } sledgehammer_utils = { version = "0.1.0", optional = true } [features] From 134a45b89d9c8213dd3c6e0ea4efd175300c081d Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 10 Dec 2022 14:58:13 -0600 Subject: [PATCH 64/93] fix fmt escape sequences in static attribute/text nodes --- packages/rsx/src/lib.rs | 45 ++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/packages/rsx/src/lib.rs b/packages/rsx/src/lib.rs index ccfcda8fa..f8a8bbddc 100644 --- a/packages/rsx/src/lib.rs +++ b/packages/rsx/src/lib.rs @@ -182,30 +182,36 @@ impl<'a> DynamicContext<'a> { let static_attrs = el.attributes.iter().map(|attr| match &attr.attr { ElementAttr::AttrText { name, value } if value.is_static() => { - let value = value.source.as_ref().unwrap(); - quote! { - ::dioxus::core::TemplateAttribute::Static { - name: dioxus_elements::#el_name::#name.0, - namespace: dioxus_elements::#el_name::#name.1, - value: #value, + if let Some(value) = value.to_static() { + quote! { + ::dioxus::core::TemplateAttribute::Static { + name: dioxus_elements::#el_name::#name.0, + namespace: dioxus_elements::#el_name::#name.1, + value: #value, - // todo: we don't diff these so we never apply the volatile flag - // volatile: dioxus_elements::#el_name::#name.2, + // todo: we don't diff these so we never apply the volatile flag + // volatile: dioxus_elements::#el_name::#name.2, + } } + } else { + unreachable!() } } ElementAttr::CustomAttrText { name, value } if value.is_static() => { - let value = value.source.as_ref().unwrap(); - quote! { - ::dioxus::core::TemplateAttribute::Static { - name: #name, - namespace: None, - value: #value, + if let Some(value) = value.to_static() { + quote! { + ::dioxus::core::TemplateAttribute::Static { + name: #name, + namespace: None, + value: #value, - // todo: we don't diff these so we never apply the volatile flag - // volatile: dioxus_elements::#el_name::#name.2, + // todo: we don't diff these so we never apply the volatile flag + // volatile: dioxus_elements::#el_name::#name.2, + } } + } else { + unreachable!() } } @@ -244,8 +250,11 @@ impl<'a> DynamicContext<'a> { } BodyNode::Text(text) if text.is_static() => { - let text = text.source.as_ref().unwrap(); - quote! { ::dioxus::core::TemplateNode::Text{ text: #text } } + if let Some(text) = text.to_static() { + quote! { ::dioxus::core::TemplateNode::Text{ text: #text } } + } else { + unreachable!() + } } BodyNode::RawExpr(_) From 3e7dbe868abe5593715d9cb91a734eecfd09fd34 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 10 Dec 2022 15:05:41 -0600 Subject: [PATCH 65/93] unwrap instead of using unreachable --- packages/rsx/src/lib.rs | 45 +++++++++++++++++------------------------ 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/packages/rsx/src/lib.rs b/packages/rsx/src/lib.rs index f8a8bbddc..913ff8660 100644 --- a/packages/rsx/src/lib.rs +++ b/packages/rsx/src/lib.rs @@ -182,36 +182,30 @@ impl<'a> DynamicContext<'a> { let static_attrs = el.attributes.iter().map(|attr| match &attr.attr { ElementAttr::AttrText { name, value } if value.is_static() => { - if let Some(value) = value.to_static() { - quote! { - ::dioxus::core::TemplateAttribute::Static { - name: dioxus_elements::#el_name::#name.0, - namespace: dioxus_elements::#el_name::#name.1, - value: #value, + let value = value.to_static().unwrap(); + quote! { + ::dioxus::core::TemplateAttribute::Static { + name: dioxus_elements::#el_name::#name.0, + namespace: dioxus_elements::#el_name::#name.1, + value: #value, - // todo: we don't diff these so we never apply the volatile flag - // volatile: dioxus_elements::#el_name::#name.2, - } + // todo: we don't diff these so we never apply the volatile flag + // volatile: dioxus_elements::#el_name::#name.2, } - } else { - unreachable!() } } ElementAttr::CustomAttrText { name, value } if value.is_static() => { - if let Some(value) = value.to_static() { - quote! { - ::dioxus::core::TemplateAttribute::Static { - name: #name, - namespace: None, - value: #value, + let value = value.to_static().unwrap(); + quote! { + ::dioxus::core::TemplateAttribute::Static { + name: #name, + namespace: None, + value: #value, - // todo: we don't diff these so we never apply the volatile flag - // volatile: dioxus_elements::#el_name::#name.2, - } + // todo: we don't diff these so we never apply the volatile flag + // volatile: dioxus_elements::#el_name::#name.2, } - } else { - unreachable!() } } @@ -250,11 +244,8 @@ impl<'a> DynamicContext<'a> { } BodyNode::Text(text) if text.is_static() => { - if let Some(text) = text.to_static() { - quote! { ::dioxus::core::TemplateNode::Text{ text: #text } } - } else { - unreachable!() - } + let text = text.to_static().unwrap(); + quote! { ::dioxus::core::TemplateNode::Text{ text: #text } } } BodyNode::RawExpr(_) From 0b3e8a449372acc98e7406d0448d968438991e57 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 11 Dec 2022 13:58:58 -0800 Subject: [PATCH 66/93] chore: add new import to prelude --- packages/core/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 613a4a115..7ce2b723f 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -81,9 +81,9 @@ pub use crate::innerlude::{ /// This includes types like [`Scope`], [`Element`], and [`Component`]. pub mod prelude { pub use crate::innerlude::{ - fc_to_builder, Element, Event, EventHandler, Fragment, LazyNodes, Properties, Scope, - ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, VNode, - VirtualDom, + fc_to_builder, Component, Element, Event, EventHandler, Fragment, LazyNodes, Properties, + Scope, ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, + VNode, VirtualDom, }; } From d160f38bc29621baf3d41cf3d9a15b3ea3182510 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 12 Dec 2022 11:18:36 -0800 Subject: [PATCH 67/93] fix: parsing of attributes without trailing commas --- examples/svg_basic.rs | 6 ++++++ packages/rsx/src/element.rs | 3 +-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/examples/svg_basic.rs b/examples/svg_basic.rs index e950eb339..1264aa001 100644 --- a/examples/svg_basic.rs +++ b/examples/svg_basic.rs @@ -67,6 +67,12 @@ fn app(cx: Scope) -> Element { stroke: "blue", stroke_width: "5", } + path { + d: "M9.00001 9C9 62 103.5 124 103.5 178", + stroke: "#3CC4DC", + "stroke-linecap": "square", + "stroke-width": "square", + } })) } diff --git a/packages/rsx/src/element.rs b/packages/rsx/src/element.rs index 307059f9d..ae4bfabe9 100644 --- a/packages/rsx/src/element.rs +++ b/packages/rsx/src/element.rs @@ -45,7 +45,7 @@ impl Parse for Element { content.parse::()?; - if content.peek(LitStr) && content.peek2(Token![,]) { + if content.peek(LitStr) { let value = content.parse()?; attributes.push(ElementAttrNamed { el_name: el_name.clone(), @@ -53,7 +53,6 @@ impl Parse for Element { }); } else { let value = content.parse::()?; - attributes.push(ElementAttrNamed { el_name: el_name.clone(), attr: ElementAttr::CustomAttrExpression { name, value }, From d93eed4c294443e11a4d8294b8c8ee995973870b Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 12 Dec 2022 11:20:25 -0800 Subject: [PATCH 68/93] feat: allow dynamic nodes to be into dynamic nodes --- packages/core/src/nodes.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 81ed5da06..68c12537f 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -441,6 +441,12 @@ impl<'a> IntoDynNode<'a> for VNode<'a> { } } +impl<'a> IntoDynNode<'a> for DynamicNode<'a> { + fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> { + self + } +} + // An element that's an error is currently lost into the ether impl<'a> IntoDynNode<'a> for Element<'a> { fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> { From e4d089fabc4ea4212ed6ccc3c753bcef5f50f87c Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 12 Dec 2022 11:52:56 -0800 Subject: [PATCH 69/93] fix: sledgehammer hydate text --- packages/interpreter/src/sledgehammer_bindings.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/interpreter/src/sledgehammer_bindings.rs b/packages/interpreter/src/sledgehammer_bindings.rs index 9be487c58..a81e16235 100644 --- a/packages/interpreter/src/sledgehammer_bindings.rs +++ b/packages/interpreter/src/sledgehammer_bindings.rs @@ -210,7 +210,17 @@ mod js { "{nodes[$id$] = LoadChild($ptr$, $len$);}" } fn hydrate_text(ptr: u32, len: u8, value: &str, id: u32) { - "{node = LoadChild($ptr$, $len$); node.textContent = $value$; nodes[$id$] = node;}" + r#"{ + node = LoadChild($ptr$, $len$); + if (node.nodeType == Node.TEXT_NODE) { + node.textContent = value; + } else { + let text = document.createTextNode(value); + node.replaceWith(text); + node = text; + } + nodes[$id$] = node; + }"# } fn replace_placeholder(ptr: u32, len: u8, n: u32) { "{els = stack.splice(stack.length - $n$); node = LoadChild($ptr$, $len$); node.replaceWith(...els);}" From d4b6bb8a7b634f6b95071d141adced3ba0af148c Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 12 Dec 2022 18:31:30 -0800 Subject: [PATCH 70/93] feat: fix the scope rollover bug --- examples/custom_element.rs | 8 + packages/core/src/arena.rs | 14 +- packages/core/src/create.rs | 431 ++++++++++++--------- packages/core/src/diff.rs | 279 +++++++------ packages/core/src/dirty_scope.rs | 16 +- packages/core/src/mutations.rs | 3 - packages/core/src/nodes.rs | 27 +- packages/core/src/scopes.rs | 7 +- packages/core/src/virtual_dom.rs | 8 + packages/router/Cargo.toml | 2 + packages/router/examples/simple_desktop.rs | 52 +++ packages/router/src/components/link.rs | 3 + packages/router/src/components/route.rs | 6 +- packages/router/src/service.rs | 1 + packages/rsx/src/lib.rs | 3 +- packages/web/src/dom.rs | 60 ++- packages/web/src/lib.rs | 31 +- 17 files changed, 591 insertions(+), 360 deletions(-) create mode 100644 packages/router/examples/simple_desktop.rs diff --git a/examples/custom_element.rs b/examples/custom_element.rs index 87b1fac3f..6ed2e64cd 100644 --- a/examples/custom_element.rs +++ b/examples/custom_element.rs @@ -16,6 +16,12 @@ fn main() { } fn app(cx: Scope) -> Element { + let g = cx.component(component, (), "component"); + let c = cx.make_node(g); + cx.render(rsx! { + div { c } + }) + // let nf = NodeFactory::new(cx); // let mut attrs = dioxus::core::exports::bumpalo::collections::Vec::new_in(nf.bump()); @@ -27,6 +33,8 @@ fn app(cx: Scope) -> Element { // attrs.push(nf.attr("age", format_args!("47"), None, false)); // Some(nf.raw_element("my-element", None, &[], attrs.into_bump_slice(), &[], None)) +} +fn component(cx: Scope) -> Element { todo!() } diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index 98960fca7..88642b0c2 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -34,14 +34,14 @@ impl ElementRef { impl VirtualDom { pub(crate) fn next_element(&mut self, template: &VNode, path: &'static [u8]) -> ElementId { - self.next(template, ElementPath::Deep(path)) + self.next_reference(template, ElementPath::Deep(path)) } pub(crate) fn next_root(&mut self, template: &VNode, path: usize) -> ElementId { - self.next(template, ElementPath::Root(path)) + self.next_reference(template, ElementPath::Root(path)) } - fn next(&mut self, template: &VNode, path: ElementPath) -> ElementId { + fn next_reference(&mut self, template: &VNode, path: ElementPath) -> ElementId { let entry = self.elements.vacant_entry(); let id = entry.key(); @@ -100,15 +100,15 @@ impl VirtualDom { nodes.iter().for_each(|node| self.drop_scope_inner(node)) } DynamicNode::Placeholder(t) => { - self.try_reclaim(t.get()); + self.try_reclaim(t.id.get().unwrap()); } DynamicNode::Text(t) => { - self.try_reclaim(t.id.get()); + self.try_reclaim(t.id.get().unwrap()); } }); for root in node.root_ids { - let id = root.get(); + let id = root.get().unwrap(); if id.0 != 0 { self.try_reclaim(id); } @@ -132,7 +132,7 @@ impl VirtualDom { // Only descend if the props are borrowed DynamicNode::Component(c) if !c.static_props => { self.ensure_drop_safety(c.scope.get().unwrap()); - c.props.set(None); + c.props.take(); } DynamicNode::Fragment(f) => f diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index 35c2db411..6f6f49a15 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -1,13 +1,15 @@ -use std::cell::Cell; -use std::rc::Rc; - -use crate::innerlude::{VComponent, VText}; +use crate::innerlude::{VComponent, VPlaceholder, VText}; use crate::mutations::Mutation; use crate::mutations::Mutation::*; use crate::nodes::VNode; use crate::nodes::{DynamicNode, TemplateNode}; use crate::virtual_dom::VirtualDom; use crate::{AttributeValue, ElementId, RenderReturn, ScopeId, SuspenseContext}; +use std::cell::Cell; +use std::iter::{Enumerate, Peekable}; +use std::rc::Rc; +use std::slice; +use TemplateNode::*; impl<'b> VirtualDom { /// Create a new template [`VNode`] and write it to the [`Mutations`] buffer. @@ -17,190 +19,232 @@ impl<'b> VirtualDom { self.scope_stack.push(scope); let out = self.create(template); self.scope_stack.pop(); - out } /// Create this template and write its mutations - pub(crate) fn create(&mut self, template: &'b VNode<'b>) -> usize { + pub(crate) fn create(&mut self, node: &'b VNode<'b>) -> usize { // The best renderers will have templates prehydrated and registered // Just in case, let's create the template using instructions anyways - if !self.templates.contains_key(&template.template.name) { - self.register_template(template); + if !self.templates.contains_key(&node.template.name) { + self.register_template(node); } + // we know that this will generate at least one mutation per node + self.mutations.edits.reserve(node.template.roots.len()); + // Walk the roots, creating nodes and assigning IDs // todo: adjust dynamic nodes to be in the order of roots and then leaves (ie BFS) - let mut dynamic_attrs = template.template.attr_paths.iter().enumerate().peekable(); - let mut dynamic_nodes = template.template.node_paths.iter().enumerate().peekable(); + let mut attrs = node.template.attr_paths.iter().enumerate().peekable(); + let mut nodes = node.template.node_paths.iter().enumerate().peekable(); - let cur_scope = self.scope_stack.last().copied().unwrap(); + node.template + .roots + .iter() + .enumerate() + .map(|(idx, root)| match root { + DynamicText { id } | Dynamic { id } => self.write_dynamic_root(node, *id), + Element { .. } => self.write_element_root(node, idx, &mut attrs, &mut nodes), + Text { .. } => self.write_static_text_root(node, idx), + }) + .sum() + } - // we know that this will generate at least one mutation per node - self.mutations.edits.reserve(template.template.roots.len()); + fn write_static_text_root(&mut self, node: &VNode, idx: usize) -> usize { + // Simply just load the template root, no modifications needed + self.load_template_root(node, idx); - let mut on_stack = 0; - for (root_idx, root) in template.template.roots.iter().enumerate() { - // We might need to generate an ID for the root node - on_stack += match root { - TemplateNode::DynamicText { id } | TemplateNode::Dynamic { id } => { - match &template.dynamic_nodes[*id] { - // a dynamic text node doesn't replace a template node, instead we create it on the fly - DynamicNode::Text(VText { id: slot, value }) => { - let id = self.next_element(template, template.template.node_paths[*id]); - slot.set(id); + // Text producs just one node on the stack + 1 + } - // Safety: we promise not to re-alias this text later on after committing it to the mutation - let unbounded_text = unsafe { std::mem::transmute(*value) }; - self.mutations.push(CreateTextNode { - value: unbounded_text, - id, - }); + fn write_dynamic_root(&mut self, template: &'b VNode<'b>, idx: usize) -> usize { + use DynamicNode::*; + match &template.dynamic_nodes[idx] { + node @ Fragment(_) => self.create_dynamic_node(template, node, idx), + node @ Component { .. } => self.create_dynamic_node(template, node, idx), + Placeholder(VPlaceholder { id }) => { + let id = self.set_slot(template, id, idx); + self.mutations.push(CreatePlaceholder { id }); + 1 + } + Text(VText { id, value }) => { + let id = self.set_slot(template, id, idx); + self.create_static_text(value, id); + 1 + } + } + } - 1 - } + fn create_static_text(&mut self, value: &str, id: ElementId) { + // Safety: we promise not to re-alias this text later on after committing it to the mutation + let unbounded_text: &str = unsafe { std::mem::transmute(value) }; + self.mutations.push(CreateTextNode { + value: unbounded_text, + id, + }); + } - DynamicNode::Placeholder(slot) => { - let id = self.next_element(template, template.template.node_paths[*id]); - slot.set(id); - self.mutations.push(CreatePlaceholder { id }); - 1 - } + /// We write all the descndent data for this element + /// + /// Elements can contain other nodes - and those nodes can be dynamic or static + /// + /// We want to make sure we write these nodes while on top of the root + fn write_element_root( + &mut self, + template: &'b VNode<'b>, + root_idx: usize, + dynamic_attrs: &mut Peekable>>, + dynamic_nodes: &mut Peekable>>, + ) -> usize { + // Load the template root and get the ID for the node on the stack + let root_on_stack = self.load_template_root(template, root_idx); - DynamicNode::Fragment(_) | DynamicNode::Component { .. } => { - self.create_dynamic_node(template, &template.dynamic_nodes[*id], *id) - } - } + // Write all the attributes below this root + self.write_attrs_on_root(dynamic_attrs, root_idx, root_on_stack, template); + + // Load in all of the placeholder or dynamic content under this root too + self.load_placeholders(dynamic_nodes, root_idx, template); + + 1 + } + + /// Load all of the placeholder nodes for descendents of this root node + /// + /// ```rust, ignore + /// rsx! { + /// div { + /// // This is a placeholder + /// some_value, + /// + /// // Load this too + /// "{some_text}" + /// } + /// } + /// ``` + fn load_placeholders( + &mut self, + dynamic_nodes: &mut Peekable>>, + root_idx: usize, + template: &'b VNode<'b>, + ) { + let (start, end) = match collect_dyn_node_range(dynamic_nodes, root_idx) { + Some((a, b)) => (a, b), + None => return, + }; + + for idx in start..=end { + let m = self.create_dynamic_node(template, &template.dynamic_nodes[idx], idx); + if m > 0 { + // The path is one shorter because the top node is the root + let path = &template.template.node_paths[idx][1..]; + self.mutations.push(ReplacePlaceholder { m, path }); + } + } + } + + fn write_attrs_on_root( + &mut self, + attrs: &mut Peekable>>, + root_idx: usize, + root: ElementId, + node: &VNode, + ) { + while let Some((mut attr_id, path)) = attrs.next_if(|(_, p)| p[0] == root_idx as u8) { + let id = self.assign_static_node_as_dynamic(path, root, node, attr_id); + + loop { + self.write_attribute(&node.dynamic_attrs[attr_id], id); + + // Only push the dynamic attributes forward if they match the current path (same element) + match attrs.next_if(|(_, p)| *p == path) { + Some((next_attr_id, _)) => attr_id = next_attr_id, + None => break, } + } + } + } - TemplateNode::Element { .. } | TemplateNode::Text { .. } => { - let this_id = self.next_root(template, root_idx); + fn write_attribute(&mut self, attribute: &crate::Attribute, id: ElementId) { + // Make sure we set the attribute's associated id + attribute.mounted_element.set(id); - template.root_ids[root_idx].set(this_id); - self.mutations.push(LoadTemplate { - name: template.template.name, - index: root_idx, - id: this_id, - }); + // Safety: we promise not to re-alias this text later on after committing it to the mutation + let unbounded_name = unsafe { std::mem::transmute(attribute.name) }; - // we're on top of a node that has a dynamic attribute for a descendant - // Set that attribute now before the stack gets in a weird state - while let Some((mut attr_id, path)) = - dynamic_attrs.next_if(|(_, p)| p[0] == root_idx as u8) - { - // if attribute is on a root node, then we've already created the element - // Else, it's deep in the template and we should create a new id for it - let id = match path.len() { - 1 => this_id, - _ => { - let id = self - .next_element(template, template.template.attr_paths[attr_id]); - self.mutations.push(Mutation::AssignId { - path: &path[1..], - id, - }); - id - } - }; + match &attribute.value { + AttributeValue::Text(value) => { + // Safety: we promise not to re-alias this text later on after committing it to the mutation + let unbounded_value = unsafe { std::mem::transmute(*value) }; - loop { - let attribute = template.dynamic_attrs.get(attr_id).unwrap(); - attribute.mounted_element.set(id); + self.mutations.push(SetAttribute { + name: unbounded_name, + value: unbounded_value, + ns: attribute.namespace, + id, + }) + } + AttributeValue::Bool(value) => self.mutations.push(SetBoolAttribute { + name: unbounded_name, + value: *value, + id, + }), + AttributeValue::Listener(_) => { + self.mutations.push(NewEventListener { + // all listeners start with "on" + name: &unbounded_name[2..], + id, + }) + } + AttributeValue::Float(_) => todo!(), + AttributeValue::Int(_) => todo!(), + AttributeValue::Any(_) => todo!(), + AttributeValue::None => todo!(), + } + } - // Safety: we promise not to re-alias this text later on after committing it to the mutation - let unbounded_name = unsafe { std::mem::transmute(attribute.name) }; + fn load_template_root(&mut self, template: &VNode, root_idx: usize) -> ElementId { + // Get an ID for this root since it's a real root + let this_id = self.next_root(template, root_idx); + template.root_ids[root_idx].set(Some(this_id)); - match &attribute.value { - AttributeValue::Text(value) => { - // Safety: we promise not to re-alias this text later on after committing it to the mutation - let unbounded_value = unsafe { std::mem::transmute(*value) }; + self.mutations.push(LoadTemplate { + name: template.template.name, + index: root_idx, + id: this_id, + }); - self.mutations.push(SetAttribute { - name: unbounded_name, - value: unbounded_value, - ns: attribute.namespace, - id, - }) - } - AttributeValue::Bool(value) => { - self.mutations.push(SetBoolAttribute { - name: unbounded_name, - value: *value, - id, - }) - } - AttributeValue::Listener(_) => { - self.mutations.push(NewEventListener { - // all listeners start with "on" - name: &unbounded_name[2..], - scope: cur_scope, - id, - }) - } - AttributeValue::Float(_) => todo!(), - AttributeValue::Int(_) => todo!(), - AttributeValue::Any(_) => todo!(), - AttributeValue::None => todo!(), - } + this_id + } - // Only push the dynamic attributes forward if they match the current path (same element) - match dynamic_attrs.next_if(|(_, p)| *p == path) { - Some((next_attr_id, _)) => attr_id = next_attr_id, - None => break, - } - } - } - - // We're on top of a node that has a dynamic child for a descendant - // Skip any node that's a root - let mut start = None; - let mut end = None; - - // Collect all the dynamic nodes below this root - // We assign the start and end of the range of dynamic nodes since they area ordered in terms of tree path - // - // [0] - // [1, 1] <---| - // [1, 1, 1] <---| these are the range of dynamic nodes below root 1 - // [1, 1, 2] <---| - // [2] - // - // We collect each range and then create them and replace the placeholder in the template - while let Some((idx, p)) = - dynamic_nodes.next_if(|(_, p)| p[0] == root_idx as u8) - { - if p.len() == 1 { - continue; - } - - if start.is_none() { - start = Some(idx); - } - - end = Some(idx); - } - - // - if let (Some(start), Some(end)) = (start, end) { - for idx in start..=end { - let node = &template.dynamic_nodes[idx]; - let m = self.create_dynamic_node(template, node, idx); - if m > 0 { - self.mutations.push(ReplacePlaceholder { - m, - path: &template.template.node_paths[idx][1..], - }); - } - } - } - - // elements create only one node :-) - 1 - } - }; + /// We have some dynamic attributes attached to a some node + /// + /// That node needs to be loaded at runtime, so we need to give it an ID + /// + /// If the node in question is on the stack, we just return that ID + /// + /// If the node is not on the stack, we create a new ID for it and assign it + fn assign_static_node_as_dynamic( + &mut self, + path: &'static [u8], + this_id: ElementId, + template: &VNode, + attr_id: usize, + ) -> ElementId { + if path.len() == 1 { + return this_id; } - on_stack + // if attribute is on a root node, then we've already created the element + // Else, it's deep in the template and we should create a new id for it + let id = self.next_element(template, template.template.attr_paths[attr_id]); + + self.mutations.push(Mutation::AssignId { + path: &path[1..], + id, + }); + + id } /// Insert a new template into the VirtualDom's template registry @@ -210,17 +254,9 @@ impl<'b> VirtualDom { .insert(template.template.name, template.template); // If it's all dynamic nodes, then we don't need to register it - // Quickly run through and see if it's all just dynamic nodes - if template.template.roots.iter().all(|root| { - matches!( - root, - TemplateNode::Dynamic { .. } | TemplateNode::DynamicText { .. } - ) - }) { - return; + if !template.template.is_completely_dynamic() { + self.mutations.templates.push(template.template); } - - self.mutations.templates.push(template.template); } pub(crate) fn create_dynamic_node( @@ -248,7 +284,7 @@ impl<'b> VirtualDom { let new_id = self.next_element(template, template.template.node_paths[idx]); // Make sure the text node is assigned to the correct element - text.id.set(new_id); + text.id.set(Some(new_id)); // Safety: we promise not to re-alias this text later on after committing it to the mutation let value = unsafe { std::mem::transmute(text.value) }; @@ -266,7 +302,7 @@ impl<'b> VirtualDom { pub(crate) fn create_placeholder( &mut self, - slot: &Cell, + placeholder: &VPlaceholder, template: &'b VNode<'b>, idx: usize, ) -> usize { @@ -274,7 +310,7 @@ impl<'b> VirtualDom { let id = self.next_element(template, template.template.node_paths[idx]); // Make sure the text node is assigned to the correct element - slot.set(id); + placeholder.id.set(Some(id)); // Assign the ID to the existing node in the template self.mutations.push(AssignId { @@ -296,15 +332,17 @@ impl<'b> VirtualDom { component: &'b VComponent<'b>, idx: usize, ) -> usize { - let props = component - .props - .take() - .expect("Props to always exist when a component is being created"); + let scope = match component.props.take() { + Some(props) => { + let unbounded_props = unsafe { std::mem::transmute(props) }; + let scope = self.new_scope(unbounded_props, component.name); + scope.id + } - let unbounded_props = unsafe { std::mem::transmute(props) }; + // Component is coming back, it probably still exists, right? + None => component.scope.get().unwrap(), + }; - let scope = self.new_scope(unbounded_props, component.name); - let scope = scope.id; component.scope.set(Some(scope)); let return_nodes = unsafe { self.run_scope(scope).extend_lifetime_ref() }; @@ -394,4 +432,37 @@ impl<'b> VirtualDom { 0 } + + fn set_slot( + &mut self, + template: &'b VNode<'b>, + slot: &'b Cell>, + id: usize, + ) -> ElementId { + let id = self.next_element(template, template.template.node_paths[id]); + slot.set(Some(id)); + id + } +} + +fn collect_dyn_node_range( + dynamic_nodes: &mut Peekable>>, + root_idx: usize, +) -> Option<(usize, usize)> { + let start = match dynamic_nodes.peek() { + Some((idx, p)) if p[0] == root_idx as u8 => *idx, + _ => return None, + }; + + let mut end = start; + + while let Some((idx, p)) = dynamic_nodes.next_if(|(_, p)| p[0] == root_idx as u8) { + if p.len() == 1 { + continue; + } + + end = idx; + } + + Some((start, end)) } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index e893cd2b5..1a8a9fcd7 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -1,14 +1,12 @@ -use std::cell::Cell; - use crate::{ arena::ElementId, - innerlude::{DirtyScope, VComponent, VText}, + innerlude::{DirtyScope, VComponent, VPlaceholder, VText}, mutations::Mutation, nodes::RenderReturn, nodes::{DynamicNode, VNode}, scopes::ScopeId, virtual_dom::VirtualDom, - AttributeValue, TemplateNode, + Attribute, AttributeValue, TemplateNode, }; use rustc_hash::{FxHashMap, FxHashSet}; @@ -56,89 +54,101 @@ impl<'b> VirtualDom { fn diff_err_to_ok(&mut self, _e: &anyhow::Error, _l: &'b VNode<'b>) {} fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) { - if !std::ptr::eq(left_template.template.name, right_template.template.name) - && left_template.template.name != right_template.template.name - { + // If the templates are the same, we don't need to do anything, nor do we want to + if templates_are_the_same(left_template, right_template) { + return; + } + + // If the templates are different by name, we need to replace the entire template + if templates_are_different(left_template, right_template) { return self.light_diff_templates(left_template, right_template); } - for (left_attr, right_attr) in left_template + // If the templates are the same, we can diff the attributes and children + // Start with the attributes + left_template .dynamic_attrs .iter() .zip(right_template.dynamic_attrs.iter()) - { - // Move over the ID from the old to the new - right_attr - .mounted_element - .set(left_attr.mounted_element.get()); + .for_each(|(left_attr, right_attr)| { + // Move over the ID from the old to the new + right_attr + .mounted_element + .set(left_attr.mounted_element.get()); - // We want to make sure anything listener that gets pulled is valid - if let AttributeValue::Listener(_) = right_attr.value { - self.update_template(left_attr.mounted_element.get(), right_template); - } - - if left_attr.value != right_attr.value || left_attr.volatile { - // todo: add more types of attribute values - match right_attr.value { - AttributeValue::Text(text) => { - let name = unsafe { std::mem::transmute(left_attr.name) }; - let value = unsafe { std::mem::transmute(text) }; - self.mutations.push(Mutation::SetAttribute { - id: left_attr.mounted_element.get(), - ns: right_attr.namespace, - name, - value, - }); - } - // todo: more types of attribute values - _ => todo!("other attribute types"), + // We want to make sure anything listener that gets pulled is valid + if let AttributeValue::Listener(_) = right_attr.value { + self.update_template(left_attr.mounted_element.get(), right_template); } - } - } - for (idx, (left_node, right_node)) in left_template + // If the attributes are different (or volatile), we need to update them + if left_attr.value != right_attr.value || left_attr.volatile { + self.update_attribute(right_attr, left_attr); + } + }); + + // Now diff the dynamic nodes + left_template .dynamic_nodes .iter() .zip(right_template.dynamic_nodes.iter()) .enumerate() - { - match (left_node, right_node) { - (Text(left), Text(right)) => self.diff_vtext(left, right), - (Fragment(left), Fragment(right)) => self.diff_non_empty_fragment(left, right), - (Placeholder(left), Placeholder(right)) => { - right.set(left.get()); - } - (Component(left), Component(right)) => { - self.diff_vcomponent(left, right, right_template, idx) - } - (Placeholder(left), Fragment(right)) => { - self.replace_placeholder_with_nodes(left, right) - } - (Fragment(left), Placeholder(right)) => { - self.replace_nodes_with_placeholder(left, right) - } - _ => todo!(), - }; - } + .for_each(|(idx, (left_node, right_node))| { + self.diff_dynamic_node(left_node, right_node, right_template, idx); + }); - // Make sure the roots get transferred over - for (left, right) in left_template + // Make sure the roots get transferred over while we're here + left_template .root_ids .iter() .zip(right_template.root_ids.iter()) - { - right.set(left.get()); + .for_each(|(left, right)| right.set(left.get())); + } + + fn diff_dynamic_node( + &mut self, + left_node: &'b DynamicNode<'b>, + right_node: &'b DynamicNode<'b>, + node: &'b VNode<'b>, + idx: usize, + ) { + match (left_node, right_node) { + (Text(left), Text(right)) => self.diff_vtext(left, right), + (Fragment(left), Fragment(right)) => self.diff_non_empty_fragment(left, right), + (Placeholder(left), Placeholder(right)) => right.id.set(left.id.get()), + (Component(left), Component(right)) => self.diff_vcomponent(left, right, node, idx), + (Placeholder(left), Fragment(right)) => self.replace_placeholder(left, right), + (Fragment(left), Placeholder(right)) => self.node_to_placeholder(left, right), + _ => todo!("This is an usual custom case for dynamic nodes. We don't know how to handle it yet."), + }; + } + + fn update_attribute(&mut self, right_attr: &Attribute, left_attr: &Attribute) { + // todo: add more types of attribute values + match right_attr.value { + AttributeValue::Text(text) => { + let name = unsafe { std::mem::transmute(left_attr.name) }; + let value = unsafe { std::mem::transmute(text) }; + self.mutations.push(Mutation::SetAttribute { + id: left_attr.mounted_element.get(), + ns: right_attr.namespace, + name, + value, + }); + } + // todo: more types of attribute values + _ => todo!("other attribute types"), } } - fn replace_placeholder_with_nodes(&mut self, l: &'b Cell, r: &'b [VNode<'b>]) { + fn replace_placeholder(&mut self, l: &'b VPlaceholder, r: &'b [VNode<'b>]) { let m = self.create_children(r); - let id = l.get(); + let id = l.id.get().unwrap(); self.mutations.push(Mutation::ReplaceWith { id, m }); self.reclaim(id); } - fn replace_nodes_with_placeholder(&mut self, l: &'b [VNode<'b>], r: &'b Cell) { + fn node_to_placeholder(&mut self, l: &'b [VNode<'b>], r: &'b VPlaceholder) { // Remove the old nodes, except for one self.remove_nodes(&l[1..]); @@ -147,7 +157,7 @@ impl<'b> VirtualDom { // Create the placeholder first, ensuring we get a dedicated ID for the placeholder let placeholder = self.next_element(&l[0], &[]); - r.set(placeholder); + r.id.set(Some(placeholder)); self.mutations .push(Mutation::CreatePlaceholder { id: placeholder }); @@ -184,11 +194,12 @@ impl<'b> VirtualDom { // Make sure the new vcomponent has the right scopeid associated to it let scope_id = left.scope.get().unwrap(); + right.scope.set(Some(scope_id)); // copy out the box for both let old = self.scopes[scope_id.0].props.as_ref(); - let new = right.props.replace(None).unwrap(); + let new = right.props.take().unwrap(); // If the props are static, then we try to memoize by setting the new with the old // The target scopestate still has the reference to the old props, so there's no need to update anything @@ -203,6 +214,12 @@ impl<'b> VirtualDom { // Now run the component and diff it self.run_scope(scope_id); self.diff_scope(scope_id); + + println!("diffed component scope {:?}", scope_id); + self.dirty_scopes.remove(&DirtyScope { + height: self.scopes[scope_id.0].height, + id: scope_id, + }); } /// Lightly diff the two templates, checking only their roots. @@ -257,9 +274,8 @@ impl<'b> VirtualDom { /// This just moves the ID of the old node over to the new node, and then sets the text of the new node if it's /// different. fn diff_vtext(&mut self, left: &'b VText<'b>, right: &'b VText<'b>) { - let id = left.id.get(); - - right.id.set(id); + let id = left.id.get().unwrap(); + right.id.set(Some(id)); if left.value != right.value { let value = unsafe { std::mem::transmute(right.value) }; self.mutations.push(Mutation::SetText { id, value }); @@ -271,9 +287,9 @@ impl<'b> VirtualDom { /// All IDs will be garbage collected fn replace_inner(&mut self, node: &'b VNode<'b>) -> ElementId { let id = match node.dynamic_root(0) { - None => node.root_ids[0].get(), - Some(Text(t)) => t.id.get(), - Some(Placeholder(e)) => e.get(), + None => node.root_ids[0].get().unwrap(), + Some(Text(t)) => t.id.get().unwrap(), + Some(Placeholder(e)) => e.id.get().unwrap(), Some(Fragment(nodes)) => { let id = self.replace_inner(&nodes[0]); self.remove_nodes(&nodes[1..]); @@ -311,14 +327,18 @@ impl<'b> VirtualDom { match dyn_node { Component(comp) => { - let scope = comp.scope.get().unwrap(); + let scope = comp.scope.take().unwrap(); match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } { RenderReturn::Sync(Ok(t)) => self.clean_up_node(t), _ => todo!("cannot handle nonstandard nodes"), }; } - Text(t) => self.reclaim(t.id.get()), - Placeholder(t) => self.reclaim(t.get()), + Text(t) => { + if let Some(id) = t.id.take() { + self.reclaim(id) + } + } + Placeholder(t) => self.reclaim(t.id.take().unwrap()), Fragment(nodes) => nodes.iter().for_each(|node| self.clean_up_node(node)), }; } @@ -346,25 +366,25 @@ impl<'b> VirtualDom { fn remove_root_node(&mut self, node: &'b VNode<'b>, idx: usize) { match node.dynamic_root(idx) { Some(Text(i)) => { - let id = i.id.get(); + let id = i.id.take().unwrap(); self.mutations.push(Mutation::Remove { id }); self.reclaim(id); } Some(Placeholder(e)) => { - let id = e.get(); + let id = e.id.take().unwrap(); self.mutations.push(Mutation::Remove { id }); self.reclaim(id); } Some(Fragment(nodes)) => self.remove_nodes(nodes), Some(Component(comp)) => { - let scope = comp.scope.get().unwrap(); + let scope = comp.scope.take().unwrap(); match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } { RenderReturn::Sync(Ok(t)) => self.remove_node(t), _ => todo!("cannot handle nonstandard nodes"), }; } None => { - let id = node.root_ids[idx].get(); + let id = node.root_ids[idx].get().unwrap(); self.mutations.push(Mutation::Remove { id }); self.reclaim(id); } @@ -750,12 +770,16 @@ impl<'b> VirtualDom { fn remove_node(&mut self, node: &'b VNode<'b>) { for (idx, _) in node.template.roots.iter().enumerate() { let id = match node.dynamic_root(idx) { - Some(Text(t)) => t.id.get(), - Some(Placeholder(t)) => t.get(), + Some(Text(t)) => t.id.take(), + Some(Placeholder(t)) => t.id.take(), Some(Fragment(t)) => return self.remove_nodes(t), - Some(Component(comp)) => return self.remove_component(comp.scope.get().unwrap()), + Some(Component(comp)) => { + comp.scope.set(None); + return self.remove_component(comp.scope.get().unwrap()); + } None => node.root_ids[idx].get(), - }; + } + .unwrap(); self.mutations.push(Mutation::Remove { id }) } @@ -763,7 +787,7 @@ impl<'b> VirtualDom { self.clean_up_node(node); for root in node.root_ids { - let id = root.get(); + let id = root.get().unwrap(); if id.0 != 0 { self.reclaim(id); } @@ -786,41 +810,45 @@ impl<'b> VirtualDom { /// Push all the real nodes on the stack fn push_all_real_nodes(&mut self, node: &'b VNode<'b>) -> usize { - let mut onstack = 0; - - for (idx, _) in node.template.roots.iter().enumerate() { - match node.dynamic_root(idx) { - Some(Text(t)) => { - self.mutations.push(Mutation::PushRoot { id: t.id.get() }); - onstack += 1; - } - Some(Placeholder(t)) => { - self.mutations.push(Mutation::PushRoot { id: t.get() }); - onstack += 1; - } - Some(Fragment(nodes)) => { - for node in *nodes { - onstack += self.push_all_real_nodes(node); + node.template + .roots + .iter() + .enumerate() + .map(|(idx, _)| { + match node.dynamic_root(idx) { + Some(Text(t)) => { + self.mutations.push(Mutation::PushRoot { + id: t.id.get().unwrap(), + }); + 1 } - } - Some(Component(comp)) => { - let scope = comp.scope.get().unwrap(); - onstack += + Some(Placeholder(t)) => { + self.mutations.push(Mutation::PushRoot { + id: t.id.get().unwrap(), + }); + 1 + } + Some(Fragment(nodes)) => nodes + .iter() + .map(|node| self.push_all_real_nodes(node)) + .count(), + + Some(Component(comp)) => { + let scope = comp.scope.get().unwrap(); match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } { RenderReturn::Sync(Ok(node)) => self.push_all_real_nodes(node), _ => todo!(), } - } - None => { - self.mutations.push(Mutation::PushRoot { - id: node.root_ids[idx].get(), - }); - onstack += 1; - } - }; - } - - onstack + } + None => { + self.mutations.push(Mutation::PushRoot { + id: node.root_ids[idx].get().unwrap(), + }); + 1 + } + }; + }) + .count() } fn create_children(&mut self, nodes: &'b [VNode<'b>]) -> usize { @@ -841,10 +869,10 @@ impl<'b> VirtualDom { fn find_first_element(&self, node: &'b VNode<'b>) -> ElementId { match node.dynamic_root(0) { - None => node.root_ids[0].get(), - Some(Text(t)) => t.id.get(), + None => node.root_ids[0].get().unwrap(), + Some(Text(t)) => t.id.get().unwrap(), Some(Fragment(t)) => self.find_first_element(&t[0]), - Some(Placeholder(t)) => t.get(), + Some(Placeholder(t)) => t.id.get().unwrap(), Some(Component(comp)) => { let scope = comp.scope.get().unwrap(); match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } { @@ -857,10 +885,10 @@ impl<'b> VirtualDom { fn find_last_element(&self, node: &'b VNode<'b>) -> ElementId { match node.dynamic_root(node.template.roots.len() - 1) { - None => node.root_ids.last().unwrap().get(), - Some(Text(t)) => t.id.get(), + None => node.root_ids.last().unwrap().get().unwrap(), + Some(Text(t)) => t.id.get().unwrap(), Some(Fragment(t)) => self.find_last_element(t.last().unwrap()), - Some(Placeholder(t)) => t.get(), + Some(Placeholder(t)) => t.id.get().unwrap(), Some(Component(comp)) => { let scope = comp.scope.get().unwrap(); match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } { @@ -894,6 +922,21 @@ impl<'b> VirtualDom { } } +/// Are the templates the same? +/// +/// We need to check for the obvious case, and the non-obvious case where the template as cloned +/// +/// We use the pointer of the dynamic_node list in this case +fn templates_are_the_same<'b>(left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) -> bool { + std::ptr::eq(left_template, right_template) + || std::ptr::eq(left_template.dynamic_nodes, right_template.dynamic_nodes) +} + +fn templates_are_different(left_template: &VNode, right_template: &VNode) -> bool { + !std::ptr::eq(left_template.template.name, right_template.template.name) + && left_template.template.name != right_template.template.name +} + fn matching_components<'a>( left: &'a VNode<'a>, right: &'a VNode<'a>, diff --git a/packages/core/src/dirty_scope.rs b/packages/core/src/dirty_scope.rs index 4dfbd9bd3..87fff3f45 100644 --- a/packages/core/src/dirty_scope.rs +++ b/packages/core/src/dirty_scope.rs @@ -1,19 +1,21 @@ +use std::hash::Hash; + use crate::ScopeId; -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Eq, PartialOrd, Ord)] pub struct DirtyScope { pub height: u32, pub id: ScopeId, } -impl PartialOrd for DirtyScope { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.height.cmp(&other.height)) +impl PartialEq for DirtyScope { + fn eq(&self, other: &Self) -> bool { + self.id == other.id } } -impl Ord for DirtyScope { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.height.cmp(&other.height) +impl Hash for DirtyScope { + fn hash(&self, state: &mut H) { + self.id.hash(state); } } diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index ecca2a678..f8553cd46 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -230,9 +230,6 @@ pub enum Mutation<'a> { /// The name of the event to listen for. name: &'a str, - /// The ID of the node to attach the listener to. - scope: ScopeId, - /// The ID of the node to attach the listener to. id: ElementId, }, diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 68c12537f..288b15271 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -46,7 +46,7 @@ pub struct VNode<'a> { /// The IDs for the roots of this template - to be used when moving the template around and removing it from /// the actual Dom - pub root_ids: &'a [Cell], + pub root_ids: &'a [Cell>], /// The dynamic parts of the template pub dynamic_nodes: &'a [DynamicNode<'a>], @@ -128,6 +128,18 @@ pub struct Template<'a> { pub attr_paths: &'a [&'a [u8]], } +impl<'a> Template<'a> { + /// Is this template worth caching at all, since it's completely runtime? + /// + /// There's no point in saving templates that are completely dynamic, since they'll be recreated every time anyway. + pub fn is_completely_dynamic(&self) -> bool { + use TemplateNode::*; + self.roots + .iter() + .all(|root| matches!(root, Dynamic { .. } | DynamicText { .. })) + } +} + /// A statically known node in a layout. /// /// This can be created at compile time, saving the VirtualDom time when diffing the tree @@ -201,7 +213,7 @@ pub enum DynamicNode<'a> { /// Used by suspense when a node isn't ready and by fragments that don't render anything /// /// In code, this is just an ElementId whose initial value is set to 0 upon creation - Placeholder(Cell), + Placeholder(VPlaceholder), /// A list of VNodes. /// @@ -236,7 +248,7 @@ pub struct VComponent<'a> { /// It is possible that components get folded at comppile time, so these shouldn't be really used as a key pub render_fn: *const (), - pub(crate) props: Cell + 'a>>>, + pub(crate) props: RefCell + 'a>>>, } impl<'a> std::fmt::Debug for VComponent<'a> { @@ -256,7 +268,14 @@ pub struct VText<'a> { pub value: &'a str, /// The ID of this node in the real DOM - pub id: Cell, + pub id: Cell>, +} + +/// A placeholder node, used by suspense and fragments +#[derive(Debug, Default)] +pub struct VPlaceholder { + /// The ID of this node in the real DOM + pub id: Cell>, } /// An attribute of the TemplateNode, created at compile time diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 241418fa7..51630d1ad 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -237,7 +237,10 @@ impl<'src> ScopeState { /// This method should be used when you want to schedule an update for a component pub fn schedule_update_any(&self) -> Arc { let chan = self.tasks.sender.clone(); - Arc::new(move |id| drop(chan.unbounded_send(SchedulerMsg::Immediate(id)))) + Arc::new(move |id| { + println!("Scheduling update for {:?}", id); + chan.unbounded_send(SchedulerMsg::Immediate(id)).unwrap(); + }) } /// Mark this scope as dirty, and schedule a render for it. @@ -448,7 +451,7 @@ impl<'src> ScopeState { name: fn_name, render_fn: component as *const (), static_props: P::IS_STATIC, - props: Cell::new(Some(extended)), + props: RefCell::new(Some(extended)), scope: Cell::new(None), }) } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 42ba90437..35c7848f1 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -280,6 +280,9 @@ impl VirtualDom { /// Whenever the VirtualDom "works", it will re-render this scope pub fn mark_dirty(&mut self, id: ScopeId) { let height = self.scopes[id.0].height; + + println!("marking scope {} dirty with height {}", id.0, height); + self.dirty_scopes.insert(DirtyScope { height, id }); } @@ -516,6 +519,9 @@ impl VirtualDom { pub async fn render_with_deadline(&mut self, deadline: impl Future) -> Mutations { pin_mut!(deadline); + self.process_events(); + println!("rendering with dirty scopes {:#?}", self.dirty_scopes); + loop { // first, unload any complete suspense trees for finished_fiber in self.finished_fibers.drain(..) { @@ -542,6 +548,8 @@ impl VirtualDom { if let Some(dirty) = self.dirty_scopes.iter().next().cloned() { self.dirty_scopes.remove(&dirty); + println!("diffing scope {:?}", dirty); + // if the scope is currently suspended, then we should skip it, ignoring any tasks calling for an update if self.is_scope_suspended(dirty.id) { continue; diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index 0e7cf0927..9832850a1 100644 --- a/packages/router/Cargo.toml +++ b/packages/router/Cargo.toml @@ -36,6 +36,7 @@ thiserror = "1.0.30" futures-util = "0.3.21" serde = { version = "1", optional = true } serde_urlencoded = { version = "0.7.1", optional = true } +simple_logger = "4.0.0" [features] default = ["query"] @@ -50,6 +51,7 @@ wasm-logger = "0.2.0" wasm-bindgen-test = "0.3" gloo-utils = "0.1.2" dioxus-web = { path = "../web" } +dioxus-desktop = { path = "../desktop" } [target.wasm32-unknown-unknown.dev-dependencies] dioxus-router = { path = ".", features = ["web"] } diff --git a/packages/router/examples/simple_desktop.rs b/packages/router/examples/simple_desktop.rs new file mode 100644 index 000000000..2c0ec1de5 --- /dev/null +++ b/packages/router/examples/simple_desktop.rs @@ -0,0 +1,52 @@ +use dioxus::prelude::*; +use dioxus_router::*; + +fn main() { + simple_logger::SimpleLogger::new() + .with_level(log::LevelFilter::Debug) + .with_module_level("dioxus_router", log::LevelFilter::Trace) + .with_module_level("dioxus", log::LevelFilter::Trace) + .init() + .unwrap(); + dioxus_desktop::launch(app); +} + +fn app(cx: Scope) -> Element { + cx.render(rsx! { + Router { + h1 { "Your app here" } + ul { + Link { to: "/", li { "home" } } + Link { to: "/blog", li { "blog" } } + Link { to: "/blog/tim", li { "tims' blog" } } + Link { to: "/blog/bill", li { "bills' blog" } } + Link { to: "/apples", li { "go to apples" } } + } + Route { to: "/", Home {} } + Route { to: "/blog/", BlogList {} } + Route { to: "/blog/:id/", BlogPost {} } + Route { to: "/oranges", "Oranges are not apples!" } + Redirect { from: "/apples", to: "/oranges" } + } + }) +} + +fn Home(cx: Scope) -> Element { + log::debug!("rendering home {:?}", cx.scope_id()); + cx.render(rsx! { h1 { "Home" } }) +} + +fn BlogList(cx: Scope) -> Element { + log::debug!("rendering blog list {:?}", cx.scope_id()); + cx.render(rsx! { div { "Blog List" } }) +} + +fn BlogPost(cx: Scope) -> Element { + let Some(id) = use_route(cx).segment("id") else { + return cx.render(rsx! { div { "No blog post id" } }) + }; + + log::debug!("rendering blog post {}", id); + + cx.render(rsx! { div { "{id:?}" } }) +} diff --git a/packages/router/src/components/link.rs b/packages/router/src/components/link.rs index 5098090b2..35268e94d 100644 --- a/packages/router/src/components/link.rs +++ b/packages/router/src/components/link.rs @@ -120,8 +120,11 @@ pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element { prevent_default: "{prevent_default}", target: format_args!("{}", if * new_tab { "_blank" } else { "" }), onclick: move |_| { + log::trace!("Clicked link to {}", to); + if !outerlink { if let Some(service) = svc { + log::trace!("Pushing route to {}", to); service.push_route(to, cx.props.title.map(|f| f.to_string()), None); } else { log::error!( diff --git a/packages/router/src/components/route.rs b/packages/router/src/components/route.rs index 7bfc48993..1ca13f48c 100644 --- a/packages/router/src/components/route.rs +++ b/packages/router/src/components/route.rs @@ -45,13 +45,13 @@ pub fn Route<'a>(cx: Scope<'a, RouteProps<'a>>) -> Element { router_root.register_total_route(route_context.total_route, cx.scope_id()); }); - log::trace!("Checking Route: {:?}", cx.props.to); + println!("Checking Route: {:?}", cx.props.to); if router_root.should_render(cx.scope_id()) { - log::trace!("Route should render: {:?}", cx.scope_id()); + println!("Route should render: {:?}", cx.scope_id()); cx.render(rsx!(&cx.props.children)) } else { - log::trace!("Route should *not* render: {:?}", cx.scope_id()); + println!("Route should *not* render: {:?}", cx.scope_id()); cx.render(rsx!(())) } } diff --git a/packages/router/src/service.rs b/packages/router/src/service.rs index 10e2d7d33..e089ca1be 100644 --- a/packages/router/src/service.rs +++ b/packages/router/src/service.rs @@ -180,6 +180,7 @@ impl RouterService { (self.regen_any_route)(self.router_id); for listener in self.onchange_listeners.borrow().iter() { + log::trace!("Regenerating scope {:?}", listener); (self.regen_any_route)(*listener); } diff --git a/packages/rsx/src/lib.rs b/packages/rsx/src/lib.rs index 913ff8660..cd2778428 100644 --- a/packages/rsx/src/lib.rs +++ b/packages/rsx/src/lib.rs @@ -147,7 +147,8 @@ impl<'a> ToTokens for TemplateRenderer<'a> { parent: None, key: #key_tokens, template: TEMPLATE, - root_ids: std::cell::Cell::from_mut( __cx.bump().alloc([::dioxus::core::ElementId(0); #num_roots]) as &mut [::dioxus::core::ElementId]).as_slice_of_cells(), + root_ids: std::cell::Cell::from_mut( __cx.bump().alloc([None; #num_roots]) as &mut _).as_slice_of_cells(), + // root_ids: std::cell::Cell::from_mut( __cx.bump().alloc([None; #num_roots]) as &mut [::dioxus::core::ElementId]).as_slice_of_cells(), dynamic_nodes: __cx.bump().alloc([ #( #node_printer ),* ]), dynamic_attrs: __cx.bump().alloc([ #( #dyn_attr_printer ),* ]), } diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 89a829823..7fa3d202d 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -7,7 +7,7 @@ //! - tests to ensure dyn_into works for various event types. //! - Partial delegation?> -use dioxus_core::{Mutation, Template, TemplateAttribute, TemplateNode}; +use dioxus_core::{ElementId, Mutation, Template, TemplateAttribute, TemplateNode}; use dioxus_html::{event_bubbles, CompositionData, FormData}; use dioxus_interpreter_js::{save_template, Channel}; use futures_channel::mpsc; @@ -25,8 +25,16 @@ pub struct WebsysDom { interpreter: Channel, } +pub struct UiEvent { + pub name: String, + pub bubbles: bool, + pub element: ElementId, + pub data: Rc, + pub event: Event, +} + impl WebsysDom { - pub fn new(cfg: Config, event_channel: mpsc::UnboundedSender) -> Self { + pub fn new(cfg: Config, event_channel: mpsc::UnboundedSender) -> Self { // eventually, we just want to let the interpreter do all the work of decoding events into our event type // a match here in order to avoid some error during runtime browser test let document = load_document(); @@ -38,7 +46,28 @@ impl WebsysDom { let handler: Closure = Closure::wrap(Box::new(move |event: &web_sys::Event| { - let _ = event_channel.unbounded_send(event.clone()); + let name = event.type_(); + let element = walk_event_for_id(event); + let bubbles = dioxus_html::event_bubbles(name.as_str()); + if let Some((element, target)) = element { + if target + .get_attribute("dioxus-prevent-default") + .as_deref() + .map(|f| f.trim_start_matches("on")) + == Some(&name) + { + event.prevent_default(); + } + + let data = virtual_event_from_websys_event(event.clone(), target); + let _ = event_channel.unbounded_send(UiEvent { + name, + bubbles, + element, + data, + event: event.clone(), + }); + } })); dioxus_interpreter_js::initilize(root.unchecked_into(), handler.as_ref().unchecked_ref()); @@ -56,8 +85,6 @@ impl WebsysDom { } pub fn load_templates(&mut self, templates: &[Template]) { - log::debug!("Loading templates {:?}", templates); - for template in templates { let mut roots = vec![]; @@ -310,3 +337,26 @@ fn read_input_to_data(target: Element) -> Rc { files: None, }) } + +fn walk_event_for_id(event: &web_sys::Event) -> Option<(ElementId, web_sys::Element)> { + use wasm_bindgen::JsCast; + + let mut target = event + .target() + .expect("missing target") + .dyn_into::() + .expect("not a valid element"); + + loop { + match target.get_attribute("data-dioxus-id").map(|f| f.parse()) { + Some(Ok(id)) => return Some((ElementId(id), target)), + Some(Err(_)) => return None, + + // walk the tree upwards until we actually find an event target + None => match target.parent_element() { + Some(parent) => target = parent, + None => return None, + }, + } + } +} diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index f430a7555..3f700740f 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -218,13 +218,7 @@ pub async fn run_with_props(root: fn(Scope) -> Element, root_prop // Dequeue all of the events from the channel in send order // todo: we should re-order these if possible while let Some(evt) = res { - let name = evt.type_(); - let element = walk_event_for_id(&evt); - let bubbles = dioxus_html::event_bubbles(name.as_str()); - if let Some((element, target)) = element { - let data = virtual_event_from_websys_event(evt, target); - dom.handle_event(name.as_str(), data, element, bubbles); - } + dom.handle_event(evt.name.as_str(), evt.data, evt.element, evt.bubbles); res = rx.try_next().transpose().unwrap().ok(); } @@ -250,29 +244,6 @@ pub async fn run_with_props(root: fn(Scope) -> Element, root_prop } } -fn walk_event_for_id(event: &web_sys::Event) -> Option<(ElementId, web_sys::Element)> { - use wasm_bindgen::JsCast; - - let mut target = event - .target() - .expect("missing target") - .dyn_into::() - .expect("not a valid element"); - - loop { - match target.get_attribute("data-dioxus-id").map(|f| f.parse()) { - Some(Ok(id)) => return Some((ElementId(id), target)), - Some(Err(_)) => return None, - - // walk the tree upwards until we actually find an event target - None => match target.parent_element() { - Some(parent) => target = parent, - None => return None, - }, - } - } -} - // if should_hydrate { // // todo: we need to split rebuild and initialize into two phases // // it's a waste to produce edits just to get the vdom loaded From 79e786aca58a6e86bc039c417526a118a6cb9733 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 13 Dec 2022 12:38:27 -0800 Subject: [PATCH 71/93] chore: convert to logs --- packages/router/Cargo.toml | 4 +++- packages/router/src/components/route.rs | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index 9832850a1..5b72e8117 100644 --- a/packages/router/Cargo.toml +++ b/packages/router/Cargo.toml @@ -51,7 +51,9 @@ wasm-logger = "0.2.0" wasm-bindgen-test = "0.3" gloo-utils = "0.1.2" dioxus-web = { path = "../web" } -dioxus-desktop = { path = "../desktop" } +# dioxus-desktop = { path = "../desktop", optional = true } + +# not wasm [target.wasm32-unknown-unknown.dev-dependencies] dioxus-router = { path = ".", features = ["web"] } diff --git a/packages/router/src/components/route.rs b/packages/router/src/components/route.rs index 1ca13f48c..cf665d2e1 100644 --- a/packages/router/src/components/route.rs +++ b/packages/router/src/components/route.rs @@ -45,13 +45,13 @@ pub fn Route<'a>(cx: Scope<'a, RouteProps<'a>>) -> Element { router_root.register_total_route(route_context.total_route, cx.scope_id()); }); - println!("Checking Route: {:?}", cx.props.to); + log::debug!("Checking Route: {:?}", cx.props.to); if router_root.should_render(cx.scope_id()) { - println!("Route should render: {:?}", cx.scope_id()); + log::debug!("Route should render: {:?}", cx.scope_id()); cx.render(rsx!(&cx.props.children)) } else { - println!("Route should *not* render: {:?}", cx.scope_id()); + log::debug!("Route should *not* render: {:?}", cx.scope_id()); cx.render(rsx!(())) } } From 5c20e651da3153e1a507b57b3db8c0f1bcb7639d Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 13 Dec 2022 14:44:47 -0800 Subject: [PATCH 72/93] fix: get rollover working --- Cargo.toml | 1 + .../examples => examples}/simple_desktop.rs | 11 +++- packages/core/Cargo.toml | 1 + packages/core/src/arena.rs | 11 ++-- packages/core/src/diff.rs | 51 +++++++++++++------ packages/core/src/virtual_dom.rs | 3 -- packages/router/examples/simple.rs | 8 ++- 7 files changed, 61 insertions(+), 25 deletions(-) rename {packages/router/examples => examples}/simple_desktop.rs (85%) diff --git a/Cargo.toml b/Cargo.toml index 11ea7546b..8f3a33d96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,7 @@ reqwest = { version = "0.11.9", features = ["json"] } fern = { version = "0.6.0", features = ["colored"] } thiserror = "1.0.30" env_logger = "0.9.0" +simple_logger = "4.0.0" [profile.release] opt-level = 3 diff --git a/packages/router/examples/simple_desktop.rs b/examples/simple_desktop.rs similarity index 85% rename from packages/router/examples/simple_desktop.rs rename to examples/simple_desktop.rs index 2c0ec1de5..afe4664fe 100644 --- a/packages/router/examples/simple_desktop.rs +++ b/examples/simple_desktop.rs @@ -20,6 +20,9 @@ fn app(cx: Scope) -> Element { Link { to: "/blog", li { "blog" } } Link { to: "/blog/tim", li { "tims' blog" } } Link { to: "/blog/bill", li { "bills' blog" } } + Link { to: "/blog/james", + li { "james amazing' blog" } + } Link { to: "/apples", li { "go to apples" } } } Route { to: "/", Home {} } @@ -42,11 +45,17 @@ fn BlogList(cx: Scope) -> Element { } fn BlogPost(cx: Scope) -> Element { + let Some(id) = use_route(cx).segment("id") else { return cx.render(rsx! { div { "No blog post id" } }) }; log::debug!("rendering blog post {}", id); - cx.render(rsx! { div { "{id:?}" } }) + cx.render(rsx! { + div { + h3 { "blog post: {id:?}" } + Link { to: "/blog/", "back to blog list" } + } + }) } diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 37f78f04d..03c120301 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -36,6 +36,7 @@ serde = { version = "1", features = ["derive"], optional = true } anyhow = "1.0.66" smallbox = "0.8.1" +log = "0.4.17" [dev-dependencies] tokio = { version = "*", features = ["full"] } diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index 88642b0c2..055fba3c9 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -108,9 +108,10 @@ impl VirtualDom { }); for root in node.root_ids { - let id = root.get().unwrap(); - if id.0 != 0 { - self.try_reclaim(id); + if let Some(id) = root.get() { + if id.0 != 0 { + self.try_reclaim(id); + } } } } @@ -131,7 +132,9 @@ impl VirtualDom { node.dynamic_nodes.iter().for_each(|child| match child { // Only descend if the props are borrowed DynamicNode::Component(c) if !c.static_props => { - self.ensure_drop_safety(c.scope.get().unwrap()); + if let Some(scope) = c.scope.get() { + self.ensure_drop_safety(scope); + } c.props.take(); } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 1a8a9fcd7..504a0fe16 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -113,7 +113,7 @@ impl<'b> VirtualDom { idx: usize, ) { match (left_node, right_node) { - (Text(left), Text(right)) => self.diff_vtext(left, right), + (Text(left), Text(right)) => self.diff_vtext(left, right, node), (Fragment(left), Fragment(right)) => self.diff_non_empty_fragment(left, right), (Placeholder(left), Placeholder(right)) => right.id.set(left.id.get()), (Component(left), Component(right)) => self.diff_vcomponent(left, right, node, idx), @@ -174,6 +174,10 @@ impl<'b> VirtualDom { right_template: &'b VNode<'b>, idx: usize, ) { + if std::ptr::eq(left, right) { + return; + } + // Replace components that have different render fns if left.render_fn != right.render_fn { let created = self.create_component_node(right_template, right, idx); @@ -193,7 +197,16 @@ impl<'b> VirtualDom { } // Make sure the new vcomponent has the right scopeid associated to it - let scope_id = left.scope.get().unwrap(); + let Some(scope_id) = left.scope.get() else { + return; + }; + // let scope_id = left.scope.get().unwrap_or_else(|| { + // panic!( + // "A component should always have a scope associated to it. {:?}\n {:#?}", + // right.name, + // std::backtrace::Backtrace::force_capture() + // ) + // }); right.scope.set(Some(scope_id)); @@ -260,21 +273,26 @@ impl<'b> VirtualDom { /// } /// ``` fn light_diff_templates(&mut self, left: &'b VNode<'b>, right: &'b VNode<'b>) { - match matching_components(left, right) { - None => self.replace(left, right), - Some(components) => components - .into_iter() - .enumerate() - .for_each(|(idx, (l, r))| self.diff_vcomponent(l, r, right, idx)), - } + self.replace(left, right) + // match matching_components(left, right) { + // None => self.replace(left, right), + // Some(components) => components + // .into_iter() + // .enumerate() + // .for_each(|(idx, (l, r))| self.diff_vcomponent(l, r, right, idx)), + // } } /// Diff the two text nodes /// /// This just moves the ID of the old node over to the new node, and then sets the text of the new node if it's /// different. - fn diff_vtext(&mut self, left: &'b VText<'b>, right: &'b VText<'b>) { - let id = left.id.get().unwrap(); + fn diff_vtext(&mut self, left: &'b VText<'b>, right: &'b VText<'b>, node: &'b VNode<'b>) { + let id = left + .id + .get() + .unwrap_or_else(|| self.next_element(node, &[0])); + right.id.set(Some(id)); if left.value != right.value { let value = unsafe { std::mem::transmute(right.value) }; @@ -327,11 +345,12 @@ impl<'b> VirtualDom { match dyn_node { Component(comp) => { - let scope = comp.scope.take().unwrap(); - match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } { - RenderReturn::Sync(Ok(t)) => self.clean_up_node(t), - _ => todo!("cannot handle nonstandard nodes"), - }; + if let Some(scope) = comp.scope.take() { + match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } { + RenderReturn::Sync(Ok(t)) => self.clean_up_node(t), + _ => todo!("cannot handle nonstandard nodes"), + }; + } } Text(t) => { if let Some(id) = t.id.take() { diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 35c7848f1..8b41ad951 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -520,7 +520,6 @@ impl VirtualDom { pin_mut!(deadline); self.process_events(); - println!("rendering with dirty scopes {:#?}", self.dirty_scopes); loop { // first, unload any complete suspense trees @@ -548,8 +547,6 @@ impl VirtualDom { if let Some(dirty) = self.dirty_scopes.iter().next().cloned() { self.dirty_scopes.remove(&dirty); - println!("diffing scope {:?}", dirty); - // if the scope is currently suspended, then we should skip it, ignoring any tasks calling for an update if self.is_scope_suspended(dirty.id) { continue; diff --git a/packages/router/examples/simple.rs b/packages/router/examples/simple.rs index 4c16c29a1..9b94c7a9d 100644 --- a/packages/router/examples/simple.rs +++ b/packages/router/examples/simple.rs @@ -16,6 +16,7 @@ fn app(cx: Scope) -> Element { Link { to: "/blog", li { "blog" } } Link { to: "/blog/tim", li { "tims' blog" } } Link { to: "/blog/bill", li { "bills' blog" } } + Link { to: "/blog/james", li { "james amazing' blog" } } Link { to: "/apples", li { "go to apples" } } } Route { to: "/", Home {} } @@ -42,5 +43,10 @@ fn BlogPost(cx: Scope) -> Element { log::trace!("rendering blog post {}", id); - cx.render(rsx! { div { "{id:?}" } }) + cx.render(rsx! { + div { + h3 { "blog post: {id:?}" } + Link { to: "/blog/", "back to blog list" } + } + }) } From 7e45f2aa049117c0c24dcd6b4cb2917c48b52fa9 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 13 Dec 2022 15:00:54 -0800 Subject: [PATCH 73/93] feat: add scroll to 0 for web router --- packages/router/src/components/link.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/router/src/components/link.rs b/packages/router/src/components/link.rs index 35268e94d..d077966eb 100644 --- a/packages/router/src/components/link.rs +++ b/packages/router/src/components/link.rs @@ -126,6 +126,11 @@ pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element { if let Some(service) = svc { log::trace!("Pushing route to {}", to); service.push_route(to, cx.props.title.map(|f| f.to_string()), None); + + #[cfg(feature = "web")] + { + web_sys::window().unwrap().scroll_to_with_x_and_y(0.0, 0.0); + } } else { log::error!( "Attempted to create a Link to {} outside of a Router context", cx.props From b659f4d5d607b14f154224c01c302f3fdaa48681 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 13 Dec 2022 15:18:20 -0800 Subject: [PATCH 74/93] chore: add back template diffing --- packages/core/src/diff.rs | 16 +++++++--------- packages/core/src/scopes.rs | 1 - 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 504a0fe16..a6697f2f4 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -228,7 +228,6 @@ impl<'b> VirtualDom { self.run_scope(scope_id); self.diff_scope(scope_id); - println!("diffed component scope {:?}", scope_id); self.dirty_scopes.remove(&DirtyScope { height: self.scopes[scope_id.0].height, id: scope_id, @@ -273,14 +272,13 @@ impl<'b> VirtualDom { /// } /// ``` fn light_diff_templates(&mut self, left: &'b VNode<'b>, right: &'b VNode<'b>) { - self.replace(left, right) - // match matching_components(left, right) { - // None => self.replace(left, right), - // Some(components) => components - // .into_iter() - // .enumerate() - // .for_each(|(idx, (l, r))| self.diff_vcomponent(l, r, right, idx)), - // } + match matching_components(left, right) { + None => self.replace(left, right), + Some(components) => components + .into_iter() + .enumerate() + .for_each(|(idx, (l, r))| self.diff_vcomponent(l, r, right, idx)), + } } /// Diff the two text nodes diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 51630d1ad..0e487bfa8 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -238,7 +238,6 @@ impl<'src> ScopeState { pub fn schedule_update_any(&self) -> Arc { let chan = self.tasks.sender.clone(); Arc::new(move |id| { - println!("Scheduling update for {:?}", id); chan.unbounded_send(SchedulerMsg::Immediate(id)).unwrap(); }) } From 36a82755401573608b86e7baee4145ede60e9b08 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 13 Dec 2022 15:19:52 -0800 Subject: [PATCH 75/93] cargo fmt --- examples/simple_desktop.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/simple_desktop.rs b/examples/simple_desktop.rs index afe4664fe..d8c00afac 100644 --- a/examples/simple_desktop.rs +++ b/examples/simple_desktop.rs @@ -45,7 +45,6 @@ fn BlogList(cx: Scope) -> Element { } fn BlogPost(cx: Scope) -> Element { - let Some(id) = use_route(cx).segment("id") else { return cx.render(rsx! { div { "No blog post id" } }) }; From 7beb7f0ee4bb32ea9d21430f2036f9ee8e782c7a Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 13 Dec 2022 15:30:40 -0800 Subject: [PATCH 76/93] fix: use and_then --- packages/native-core/src/real_dom.rs | 2 +- packages/tui/src/widgets/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 656c59b52..621e16577 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -306,7 +306,7 @@ impl RealDom { } mark_dirty(node_id, NodeMask::new().with_text(), &mut nodes_updated); } - NewEventListener { name, scope: _, id } => { + NewEventListener { name, id } => { let node_id = self.element_to_node_id(id); let node = self.tree.get_mut(node_id).unwrap(); if let NodeType::Element { listeners, .. } = &mut node.node_data.node_type { diff --git a/packages/tui/src/widgets/mod.rs b/packages/tui/src/widgets/mod.rs index 916295e50..25201bddb 100644 --- a/packages/tui/src/widgets/mod.rs +++ b/packages/tui/src/widgets/mod.rs @@ -11,7 +11,7 @@ pub use input::*; pub(crate) fn get_root_id(cx: Scope) -> Option { if let RenderReturn::Sync(Ok(sync)) = cx.root_node() { - sync.root_ids.get(0).map(|id| id.get()) + sync.root_ids.get(0).and_then(|id| id.get()) } else { None } From 7b10d2f6af070eb27205aa489811f524c41ea1b1 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 13 Dec 2022 15:34:35 -0800 Subject: [PATCH 77/93] ci: use ordering to better cache --- .github/workflows/docs.yml | 2 +- .github/workflows/macos.yml | 2 +- .github/workflows/main.yml | 8 ++++---- .github/workflows/windows.yml | 5 +++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 0d8e9f3b4..968d5f103 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -13,7 +13,6 @@ jobs: runs-on: ubuntu-latest environment: docs steps: - - uses: actions/checkout@v3 # NOTE: Comment out when https://github.com/rust-lang/mdBook/pull/1306 is merged and released # - name: Setup mdBook @@ -25,6 +24,7 @@ jobs: - name: Setup mdBook run: | cargo install mdbook --git https://github.com/Ruin0x11/mdBook.git --branch localization --rev e74fdb1 + - uses: actions/checkout@v3 - name: Build run: cd docs && diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index f6c06cd8c..c5616322e 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -30,13 +30,13 @@ jobs: name: Test Suite runs-on: macos-latest steps: - - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - uses: Swatinem/rust-cache@v2 + - uses: actions/checkout@v3 - uses: actions-rs/cargo@v1 with: command: test diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b5e87ab71..de6b34274 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -32,7 +32,6 @@ jobs: name: Check runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: profile: minimal @@ -41,6 +40,7 @@ jobs: - uses: Swatinem/rust-cache@v2 - run: sudo apt-get update - run: sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libayatana-appindicator3-dev + - uses: actions/checkout@v3 - uses: actions-rs/cargo@v1 with: command: check @@ -51,7 +51,6 @@ jobs: name: Test Suite runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: profile: minimal @@ -63,6 +62,7 @@ jobs: - uses: davidB/rust-cargo-make@v1 - uses: browser-actions/setup-firefox@latest - uses: jetli/wasm-pack-action@v0.4.0 + - uses: actions/checkout@v3 - uses: actions-rs/cargo@v1 with: command: make @@ -73,7 +73,6 @@ jobs: name: Rustfmt runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: profile: minimal @@ -81,6 +80,7 @@ jobs: override: true - uses: Swatinem/rust-cache@v2 - run: rustup component add rustfmt + - uses: actions/checkout@v3 - uses: actions-rs/cargo@v1 with: command: fmt @@ -91,7 +91,6 @@ jobs: name: Clippy runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: profile: minimal @@ -101,6 +100,7 @@ jobs: - run: sudo apt-get update - run: sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libayatana-appindicator3-dev - run: rustup component add clippy + - uses: actions/checkout@v3 - uses: actions-rs/cargo@v1 with: command: clippy diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index edfda431d..6ae99612f 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -47,8 +47,6 @@ jobs: # which causes failures for some of rustfmt's line-ending sensitive tests - name: disable git eol translation run: git config --global core.autocrlf false - - name: checkout - uses: actions/checkout@v3 # Run build - name: Install Rustup using win.rustup.rs @@ -66,6 +64,9 @@ jobs: if: matrix.target == 'x86_64-pc-windows-gnu' && matrix.channel == 'nightly' shell: bash + - name: checkout + uses: actions/checkout@v3 + - name: test run: | rustc -Vv From 74fba3b57986a571dc5beab58c940de8593c8954 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 13 Dec 2022 15:36:15 -0800 Subject: [PATCH 78/93] fix: remove scope from listener --- packages/core/tests/kitchen_sink.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/tests/kitchen_sink.rs b/packages/core/tests/kitchen_sink.rs index 78ef608cc..fca6fcbaa 100644 --- a/packages/core/tests/kitchen_sink.rs +++ b/packages/core/tests/kitchen_sink.rs @@ -34,7 +34,7 @@ fn dual_stream() { [ LoadTemplate { name: "template", index: 0, id: ElementId(1) }, SetAttribute { name: "class", value: "123", id: ElementId(1), ns: None }, - NewEventListener { name: "click", scope: ScopeId(0), id: ElementId(1) }, + NewEventListener { name: "click", id: ElementId(1) }, HydrateText { path: &[0, 0], value: "123", id: ElementId(2) }, AppendChildren { id: ElementId(0), m: 1 } ], From 02b1a73890489ebf9a36171c36cc8a91f5a5d7a4 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 13 Dec 2022 15:48:40 -0800 Subject: [PATCH 79/93] fix: use tokio for test --- packages/core/tests/miri_simple.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/tests/miri_simple.rs b/packages/core/tests/miri_simple.rs index 19d3a44a0..3504d4d4e 100644 --- a/packages/core/tests/miri_simple.rs +++ b/packages/core/tests/miri_simple.rs @@ -62,7 +62,7 @@ fn contexts_drop() { _ = dom.render_immediate(); } -#[test] +#[tokio::test] fn tasks_drop() { fn app(cx: Scope) -> Element { cx.spawn(async { From fc2aaa7df52a20426f08624ad4c29b2aaef4ae74 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 13 Dec 2022 15:54:59 -0800 Subject: [PATCH 80/93] fix: cargo check --- packages/core/tests/miri_simple.rs | 2 +- packages/tui/examples/tui_colorpicker.rs | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/core/tests/miri_simple.rs b/packages/core/tests/miri_simple.rs index 3504d4d4e..faf783603 100644 --- a/packages/core/tests/miri_simple.rs +++ b/packages/core/tests/miri_simple.rs @@ -63,7 +63,7 @@ fn contexts_drop() { } #[tokio::test] -fn tasks_drop() { +async fn tasks_drop() { fn app(cx: Scope) -> Element { cx.spawn(async { tokio::time::sleep(std::time::Duration::from_millis(100000)).await; diff --git a/packages/tui/examples/tui_colorpicker.rs b/packages/tui/examples/tui_colorpicker.rs index edbcbdddc..951c4d1e5 100644 --- a/packages/tui/examples/tui_colorpicker.rs +++ b/packages/tui/examples/tui_colorpicker.rs @@ -17,12 +17,14 @@ fn app(cx: Scope) -> Element { width: "100%", background_color: "hsl({hue}, 70%, {brightness}%)", onmousemove: move |evt| { - if let RenderReturn::Sync(Ok(node))=cx.root_node(){ - let node = tui_query.get(node.root_ids[0].get()); - let Size{width, height} = node.size().unwrap(); - let pos = evt.inner().element_coordinates(); - hue.set((pos.x as f32/width as f32)*255.0); - brightness.set((pos.y as f32/height as f32)*100.0); + if let RenderReturn::Sync(Ok(node)) = cx.root_node() { + if let Some(id) = node.root_ids[0].get() { + let node = tui_query.get(id); + let Size{width, height} = node.size().unwrap(); + let pos = evt.inner().element_coordinates(); + hue.set((pos.x as f32/width as f32)*255.0); + brightness.set((pos.y as f32/height as f32)*100.0); + } } }, "hsl({hue}, 70%, {brightness}%)", From cee63ae3703814e92500be37e7c917d06914dbf5 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 13 Dec 2022 16:04:41 -0800 Subject: [PATCH 81/93] fix: spawn task --- packages/core/tests/miri_stress.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/core/tests/miri_stress.rs b/packages/core/tests/miri_stress.rs index 2d9760e5d..160baa25d 100644 --- a/packages/core/tests/miri_stress.rs +++ b/packages/core/tests/miri_stress.rs @@ -12,9 +12,7 @@ fn test_memory_leak() { fn app(cx: Scope) -> Element { let val = cx.generation(); - cx.spawn(async { - tokio::time::sleep(std::time::Duration::from_millis(100000)).await; - }); + cx.spawn(async {}); if val == 2 || val == 4 { return cx.render(rsx!(())); From c90be6bdc58a2dfb9a3be71646e2156a5b169b6f Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 14 Dec 2022 08:51:28 -0800 Subject: [PATCH 82/93] fix: dont panc --- packages/core/src/arena.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index 98960fca7..c39e10b5a 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -95,7 +95,11 @@ impl VirtualDom { fn drop_scope_inner(&mut self, node: &VNode) { node.clear_listeners(); node.dynamic_nodes.iter().for_each(|node| match node { - DynamicNode::Component(c) => self.drop_scope(c.scope.get().unwrap()), + DynamicNode::Component(c) => { + if let Some(f) = c.scope.get() { + self.drop_scope(f) + } + } DynamicNode::Fragment(nodes) => { nodes.iter().for_each(|node| self.drop_scope_inner(node)) } From fa3d1ef57491ae9f6162f612021e54812304788e Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 14 Dec 2022 12:49:07 -0800 Subject: [PATCH 83/93] chore: bump wry to 23 --- packages/desktop/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 58e947574..4aaae6c64 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -20,7 +20,7 @@ serde = "1.0.136" serde_json = "1.0.79" thiserror = "1.0.30" log = "0.4.14" -wry = { version = "0.22.0" } +wry = { version = "0.23.4" } futures-channel = "0.3.21" tokio = { version = "1.16.1", features = [ "sync", From e256fe107978f2a2e96fcbe84bda928c00f87a47 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 14 Dec 2022 19:07:33 -0800 Subject: [PATCH 84/93] wip: add html event type --- packages/desktop/src/controller.rs | 17 +-- packages/desktop/src/events.rs | 60 +------- packages/desktop/src/lib.rs | 7 +- packages/html/Cargo.toml | 7 +- packages/html/src/events/drag.rs | 5 +- packages/html/src/events/form.rs | 5 +- packages/html/src/lib.rs | 6 + packages/html/src/transit.rs | 143 ++++++++++++++++++ packages/interpreter/src/interpreter.js | 6 +- packages/liveview/Cargo.toml | 5 +- packages/liveview/examples/salvo.rs | 2 +- packages/liveview/examples/warp.rs | 69 +++++---- .../liveview/src/adapters/warp_adapter.rs | 135 ++++++----------- packages/liveview/src/events.rs | 13 +- packages/liveview/src/interpreter.js | 3 +- packages/liveview/src/lib.rs | 56 +++---- packages/liveview/src/pool.rs | 46 ++++++ 17 files changed, 340 insertions(+), 245 deletions(-) create mode 100644 packages/html/src/transit.rs create mode 100644 packages/liveview/src/pool.rs diff --git a/packages/desktop/src/controller.rs b/packages/desktop/src/controller.rs index 9037189b1..c8015bc8a 100644 --- a/packages/desktop/src/controller.rs +++ b/packages/desktop/src/controller.rs @@ -1,6 +1,7 @@ use crate::desktop_context::{DesktopContext, UserWindowEvent}; -use crate::events::{decode_event, EventMessage}; +use crate::events::IpcMessage; use dioxus_core::*; +use dioxus_html::HtmlEvent; use futures_channel::mpsc::{unbounded, UnboundedSender}; use futures_util::StreamExt; #[cfg(target_os = "ios")] @@ -25,7 +26,7 @@ pub(super) struct DesktopController { pub(super) quit_app_on_close: bool, pub(super) is_ready: Arc, pub(super) proxy: EventLoopProxy, - pub(super) event_tx: UnboundedSender, + pub(super) event_tx: UnboundedSender, #[cfg(target_os = "ios")] pub(super) views: Vec<*mut Object>, @@ -40,7 +41,7 @@ impl DesktopController { proxy: EventLoopProxy, ) -> Self { let edit_queue = Arc::new(Mutex::new(Vec::new())); - let (event_tx, mut event_rx) = unbounded(); + let (event_tx, mut event_rx) = unbounded::(); let proxy2 = proxy.clone(); let pending_edits = edit_queue.clone(); @@ -68,14 +69,8 @@ impl DesktopController { loop { tokio::select! { _ = dom.wait_for_work() => {} - Some(json_value) = event_rx.next() => { - if let Ok(value) = serde_json::from_value::(json_value) { - let name = value.event.clone(); - let el_id = ElementId(value.mounted_dom_id); - if let Some(evt) = decode_event(value) { - dom.handle_event(&name, evt, el_id, dioxus_html::events::event_bubbles(&name)); - } - } + Some(value) = event_rx.next() => { + dom.handle_event(&value.name, value.data.into_any(), value.element, dioxus_html::events::event_bubbles(&value.name)); } } diff --git a/packages/desktop/src/events.rs b/packages/desktop/src/events.rs index b82d6f749..2496d6ddb 100644 --- a/packages/desktop/src/events.rs +++ b/packages/desktop/src/events.rs @@ -6,7 +6,7 @@ use serde_json::from_value; use std::any::Any; use std::rc::Rc; -#[derive(Deserialize, Serialize)] +#[derive(Deserialize, Serialize, Debug)] pub(crate) struct IpcMessage { method: String, params: serde_json::Value, @@ -31,61 +31,3 @@ pub(crate) fn parse_ipc_message(payload: &str) -> Option { } } } - -macro_rules! match_data { - ( - $m:ident; - $name:ident; - $( - $tip:ty => $($mname:literal)|* ; - )* - ) => { - match $name { - $( $($mname)|* => { - let val: $tip = from_value::<$tip>($m).ok()?; - Rc::new(val) as Rc - })* - _ => return None, - } - }; -} - -#[derive(Deserialize)] -pub struct EventMessage { - pub contents: serde_json::Value, - pub event: String, - pub mounted_dom_id: usize, -} - -pub fn decode_event(value: EventMessage) -> Option> { - let val = value.contents; - let name = value.event.as_str(); - type DragData = MouseData; - - let evt = match_data! { val; name; - MouseData => "click" | "contextmenu" | "dblclick" | "doubleclick" | "mousedown" | "mouseenter" | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup"; - ClipboardData => "copy" | "cut" | "paste"; - CompositionData => "compositionend" | "compositionstart" | "compositionupdate"; - KeyboardData => "keydown" | "keypress" | "keyup"; - FocusData => "blur" | "focus" | "focusin" | "focusout"; - FormData => "change" | "input" | "invalid" | "reset" | "submit"; - DragData => "drag" | "dragend" | "dragenter" | "dragexit" | "dragleave" | "dragover" | "dragstart" | "drop"; - PointerData => "pointerlockchange" | "pointerlockerror" | "pointerdown" | "pointermove" | "pointerup" | "pointerover" | "pointerout" | "pointerenter" | "pointerleave" | "gotpointercapture" | "lostpointercapture"; - SelectionData => "selectstart" | "selectionchange" | "select"; - TouchData => "touchcancel" | "touchend" | "touchmove" | "touchstart"; - ScrollData => "scroll"; - WheelData => "wheel"; - MediaData => "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" - | "encrypted" | "ended" | "interruptbegin" | "interruptend" | "loadeddata" - | "loadedmetadata" | "loadstart" | "pause" | "play" | "playing" | "progress" - | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend" | "timeupdate" - | "volumechange" | "waiting" | "error" | "load" | "loadend" | "timeout"; - AnimationData => "animationstart" | "animationend" | "animationiteration"; - TransitionData => "transitionend"; - ToggleData => "toggle"; - // ImageData => "load" | "error"; - // OtherData => "abort" | "afterprint" | "beforeprint" | "beforeunload" | "hashchange" | "languagechange" | "message" | "offline" | "online" | "pagehide" | "pageshow" | "popstate" | "rejectionhandled" | "storage" | "unhandledrejection" | "unload" | "userproximity" | "vrdisplayactivate" | "vrdisplayblur" | "vrdisplayconnect" | "vrdisplaydeactivate" | "vrdisplaydisconnect" | "vrdisplayfocus" | "vrdisplaypointerrestricted" | "vrdisplaypointerunrestricted" | "vrdisplaypresentchange"; - }; - - Some(evt) -} diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index a635e3f4e..d8a0f8746 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -18,6 +18,7 @@ use std::sync::Arc; use desktop_context::UserWindowEvent; pub use desktop_context::{use_eval, use_window, DesktopContext, EvalResult}; +use dioxus_html::HtmlEvent; use futures_channel::mpsc::UnboundedSender; pub use wry; pub use wry::application as tao; @@ -156,7 +157,7 @@ fn build_webview( is_ready: Arc, proxy: tao::event_loop::EventLoopProxy, eval_sender: tokio::sync::mpsc::UnboundedSender, - event_tx: UnboundedSender, + event_tx: UnboundedSender, ) -> wry::webview::WebView { let builder = cfg.window.clone(); let window = builder.build(event_loop).unwrap(); @@ -190,7 +191,9 @@ fn build_webview( eval_sender.send(result).unwrap(); } "user_event" => { - _ = event_tx.unbounded_send(message.params()); + if let Ok(evt) = serde_json::from_value(message.params()) { + _ = event_tx.unbounded_send(evt); + } } "initialize" => { is_ready.store(true, std::sync::atomic::Ordering::Relaxed); diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index 446da9303..434c0bf48 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -39,7 +39,10 @@ features = [ "ClipboardEvent", ] +[dev-dependencies] +serde_json = "*" + [features] -default = [] -serialize = ["serde", "serde_repr", "euclid/serde", "keyboard-types/serde"] +default = ["serialize"] +serialize = ["serde", "serde_repr", "euclid/serde", "keyboard-types/serde", "dioxus-core/serialize"] wasm-bind = ["web-sys", "wasm-bindgen"] diff --git a/packages/html/src/events/drag.rs b/packages/html/src/events/drag.rs index 5dd7f5c71..53c118a77 100644 --- a/packages/html/src/events/drag.rs +++ b/packages/html/src/events/drag.rs @@ -10,12 +10,11 @@ pub type DragEvent = Event; /// placing a pointer device (such as a mouse) on the touch surface and then dragging the pointer to a new location /// (such as another DOM element). Applications are free to interpret a drag and drop interaction in an /// application-specific way. +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone)] pub struct DragData { /// Inherit mouse data pub mouse: MouseData, - - /// And then add the rest of the drag data - pub data: Box, } impl_event! { diff --git a/packages/html/src/events/form.rs b/packages/html/src/events/form.rs index 5e0b6a17e..1d995fa09 100644 --- a/packages/html/src/events/form.rs +++ b/packages/html/src/events/form.rs @@ -11,9 +11,8 @@ pub struct FormData { pub value: String, pub values: HashMap, - - #[cfg_attr(feature = "serialize", serde(skip))] - pub files: Option>, + // #[cfg_attr(feature = "serialize", serde(skip))] + // pub files: Option>, } impl Debug for FormData { diff --git a/packages/html/src/lib.rs b/packages/html/src/lib.rs index ab2e49f1e..577899fe1 100644 --- a/packages/html/src/lib.rs +++ b/packages/html/src/lib.rs @@ -22,6 +22,12 @@ mod render_template; #[cfg(feature = "wasm-bind")] mod web_sys_bind; +#[cfg(feature = "serialize")] +mod transit; + +#[cfg(feature = "serialize")] +pub use transit::*; + pub use elements::*; pub use events::*; pub use global_attributes::*; diff --git a/packages/html/src/transit.rs b/packages/html/src/transit.rs new file mode 100644 index 000000000..803869f31 --- /dev/null +++ b/packages/html/src/transit.rs @@ -0,0 +1,143 @@ +use std::{any::Any, rc::Rc}; + +use crate::events::*; +use dioxus_core::ElementId; +use serde::{Deserialize, Serialize}; + +// macro_rules! match_data { +// ( +// $m:ident; +// $name:ident; +// $( +// $tip:ty => $($mname:literal)|* ; +// )* +// ) => { +// match $name { +// $( $($mname)|* => { +// let val: $tip = from_value::<$tip>($m).ok()?; +// Rc::new(val) as Rc +// })* +// _ => return None, +// } +// }; +// } + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct HtmlEvent { + pub element: ElementId, + pub name: String, + pub data: EventData, +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +#[serde(untagged)] +pub enum EventData { + Mouse(MouseData), + Clipboard(ClipboardData), + Composition(CompositionData), + Keyboard(KeyboardData), + Focus(FocusData), + Form(FormData), + Drag(DragData), + Pointer(PointerData), + Selection(SelectionData), + Touch(TouchData), + Scroll(ScrollData), + Wheel(WheelData), + Media(MediaData), + Animation(AnimationData), + Transition(TransitionData), + Toggle(ToggleData), +} + +impl EventData { + pub fn into_any(self) -> Rc { + match self { + EventData::Mouse(data) => Rc::new(data) as Rc, + EventData::Clipboard(data) => Rc::new(data) as Rc, + EventData::Composition(data) => Rc::new(data) as Rc, + EventData::Keyboard(data) => Rc::new(data) as Rc, + EventData::Focus(data) => Rc::new(data) as Rc, + EventData::Form(data) => Rc::new(data) as Rc, + EventData::Drag(data) => Rc::new(data) as Rc, + EventData::Pointer(data) => Rc::new(data) as Rc, + EventData::Selection(data) => Rc::new(data) as Rc, + EventData::Touch(data) => Rc::new(data) as Rc, + EventData::Scroll(data) => Rc::new(data) as Rc, + EventData::Wheel(data) => Rc::new(data) as Rc, + EventData::Media(data) => Rc::new(data) as Rc, + EventData::Animation(data) => Rc::new(data) as Rc, + EventData::Transition(data) => Rc::new(data) as Rc, + EventData::Toggle(data) => Rc::new(data) as Rc, + } + } +} + +#[test] +fn test_back_and_forth() { + let data = HtmlEvent { + element: ElementId(0), + data: EventData::Mouse(MouseData::default()), + name: "click".to_string(), + }; + + println!("{}", serde_json::to_string_pretty(&data).unwrap()); + + let o = r#" +{ + "element": 0, + "name": "click", + "data": { + "alt_key": false, + "button": 0, + "buttons": 0, + "client_x": 0, + "client_y": 0, + "ctrl_key": false, + "meta_key": false, + "offset_x": 0, + "offset_y": 0, + "page_x": 0, + "page_y": 0, + "screen_x": 0, + "screen_y": 0, + "shift_key": false + } +} + "#; + + let p: HtmlEvent = serde_json::from_str(o).unwrap(); +} + +// pub fn decode_event(value: ) -> Option> { +// let val = value.data; +// let name = value.event.as_str(); +// type DragData = MouseData; + +// let evt = match_data! { val; name; +// MouseData => "click" | "contextmenu" | "dblclick" | "doubleclick" | "mousedown" | "mouseenter" | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup"; +// ClipboardData => "copy" | "cut" | "paste"; +// CompositionData => "compositionend" | "compositionstart" | "compositionupdate"; +// KeyboardData => "keydown" | "keypress" | "keyup"; +// FocusData => "blur" | "focus" | "focusin" | "focusout"; +// FormData => "change" | "input" | "invalid" | "reset" | "submit"; +// DragData => "drag" | "dragend" | "dragenter" | "dragexit" | "dragleave" | "dragover" | "dragstart" | "drop"; +// PointerData => "pointerlockchange" | "pointerlockerror" | "pointerdown" | "pointermove" | "pointerup" | "pointerover" | "pointerout" | "pointerenter" | "pointerleave" | "gotpointercapture" | "lostpointercapture"; +// SelectionData => "selectstart" | "selectionchange" | "select"; +// TouchData => "touchcancel" | "touchend" | "touchmove" | "touchstart"; +// ScrollData => "scroll"; +// WheelData => "wheel"; +// MediaData => "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" +// | "encrypted" | "ended" | "interruptbegin" | "interruptend" | "loadeddata" +// | "loadedmetadata" | "loadstart" | "pause" | "play" | "playing" | "progress" +// | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend" | "timeupdate" +// | "volumechange" | "waiting" | "error" | "load" | "loadend" | "timeout"; +// AnimationData => "animationstart" | "animationend" | "animationiteration"; +// TransitionData => "transitionend"; +// ToggleData => "toggle"; +// // ImageData => "load" | "error"; +// // OtherData => "abort" | "afterprint" | "beforeprint" | "beforeunload" | "hashchange" | "languagechange" | "message" | "offline" | "online" | "pagehide" | "pageshow" | "popstate" | "rejectionhandled" | "storage" | "unhandledrejection" | "unload" | "userproximity" | "vrdisplayactivate" | "vrdisplayblur" | "vrdisplayconnect" | "vrdisplaydeactivate" | "vrdisplaydisconnect" | "vrdisplayfocus" | "vrdisplaypointerrestricted" | "vrdisplaypointerunrestricted" | "vrdisplaypresentchange"; +// }; + +// Some(evt) +// } diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index 3ac52a8f1..8e1496eb6 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -435,9 +435,9 @@ export class Interpreter { } window.ipc.postMessage( serializeIpcMessage("user_event", { - event: edit.name, - mounted_dom_id: parseInt(realId), - contents: contents, + name: edit.name, + element: parseInt(realId), + data: contents, }) ); } diff --git a/packages/liveview/Cargo.toml b/packages/liveview/Cargo.toml index 90f956289..d01660373 100644 --- a/packages/liveview/Cargo.toml +++ b/packages/liveview/Cargo.toml @@ -39,6 +39,9 @@ tower = { version = "0.4.12", optional = true } # salvo salvo = { version = "0.32.0", optional = true, features = ["ws"] } +thiserror = "1.0.37" +uuid = { version = "1.2.2", features = ["v4"] } +anyhow = "1.0.66" [dev-dependencies] tokio = { version = "1", features = ["full"] } @@ -49,4 +52,4 @@ salvo = { version = "0.32.0", features = ["affix", "ws"] } tower = "0.4.12" [features] -default = [] \ No newline at end of file +default = ["warp"] diff --git a/packages/liveview/examples/salvo.rs b/packages/liveview/examples/salvo.rs index 132a62633..5b7e271c6 100644 --- a/packages/liveview/examples/salvo.rs +++ b/packages/liveview/examples/salvo.rs @@ -8,7 +8,7 @@ async fn main() { use dioxus_core::{Element, LazyNodes, Scope}; use dioxus_liveview as liveview; - use dioxus_liveview::Liveview; + use dioxus_liveview::LiveView; use salvo::extra::affix; use salvo::extra::ws::WsHandler; use salvo::prelude::*; diff --git a/packages/liveview/examples/warp.rs b/packages/liveview/examples/warp.rs index 7b2ddfef5..d19914446 100644 --- a/packages/liveview/examples/warp.rs +++ b/packages/liveview/examples/warp.rs @@ -1,35 +1,50 @@ -#[cfg(not(feature = "warp"))] -fn main() {} +use dioxus::prelude::*; +use dioxus_liveview::LiveView; +use std::net::SocketAddr; +use warp::ws::Ws; +use warp::Filter; + +fn app(cx: Scope) -> Element { + let mut num = use_state(cx, || 0); + + cx.render(rsx! { + div { + "hello world! {num}" + button { + onclick: move |_| num += 1, + "Increment" + } + } + }) +} -#[cfg(feature = "warp")] #[tokio::main] async fn main() { - use dioxus_core::{Element, LazyNodes, Scope}; - use dioxus_liveview as liveview; - use warp::ws::Ws; - use warp::Filter; - - fn app(cx: Scope) -> Element { - cx.render(LazyNodes::new(|f| f.text(format_args!("hello world!")))) - } - pretty_env_logger::init(); - let addr = ([127, 0, 0, 1], 3030); + let addr: SocketAddr = ([127, 0, 0, 1], 3030).into(); - // todo: compactify this routing under one liveview::app method - let view = liveview::new(addr); - let body = view.body("Dioxus LiveView"); + let index = warp::path::end().map(move || { + warp::reply::html(format!( + r#" + + + Dioxus LiveView with Warp +

{glue} + + "#, + glue = dioxus_liveview::interpreter_glue(&format!("ws://{addr}/ws/app")) + )) + }); - let routes = warp::path::end() - .map(move || warp::reply::html(body.clone())) - .or(warp::path("app") - .and(warp::ws()) - .and(warp::any().map(move || view.clone())) - .map(|ws: Ws, view: liveview::Liveview| { - ws.on_upgrade(|socket| async move { - view.upgrade_warp(socket, app).await; - }) - })); - warp::serve(routes).run(addr).await; + let view = LiveView::new(addr); + + let ws = warp::path("ws") + .and(warp::ws()) + .and(warp::any().map(move || view.clone())) + .map(move |ws: Ws, view: LiveView| ws.on_upgrade(|ws| view.upgrade_warp(ws, app))); + + println!("Listening on http://{}", addr); + + warp::serve(index.or(ws)).run(addr).await; } diff --git a/packages/liveview/src/adapters/warp_adapter.rs b/packages/liveview/src/adapters/warp_adapter.rs index 398c7cdd9..39a167359 100644 --- a/packages/liveview/src/adapters/warp_adapter.rs +++ b/packages/liveview/src/adapters/warp_adapter.rs @@ -1,110 +1,75 @@ -use crate::events; +use std::{convert::Infallible, time::Duration}; + +use crate::{ + events::{self, IpcMessage}, + LiveView, LiveViewError, +}; use dioxus_core::prelude::*; +use dioxus_html::a; use futures_util::{pin_mut, SinkExt, StreamExt}; use tokio::sync::mpsc; use tokio_stream::wrappers::UnboundedReceiverStream; use tokio_util::task::LocalPoolHandle; use warp::ws::{Message, WebSocket}; -impl crate::Liveview { - pub async fn upgrade_warp(&self, ws: warp::ws::WebSocket, app: fn(Scope) -> Element) { - connect(ws, self.pool.clone(), app, ()).await; - } - pub async fn upgrade_warp_with_props( - &self, - ws: warp::ws::WebSocket, +impl LiveView { + pub async fn upgrade_warp(self, ws: WebSocket, app: fn(Scope<()>) -> Element) {} + + pub async fn upgrade_warp_with_props( + self, + ws: WebSocket, app: fn(Scope) -> Element, props: T, - ) where - T: Send + Sync + 'static, - { - connect(ws, self.pool.clone(), app, props).await; + ) { + self.pool + .spawn_pinned(move || liveview_eventloop(app, props, ws)) + .await; } } -pub async fn connect( - ws: WebSocket, - pool: LocalPoolHandle, - app: fn(Scope) -> Element, +async fn liveview_eventloop( + app: Component, props: T, -) where - T: Send + Sync + 'static, + mut ws: WebSocket, +) -> Result<(), LiveViewError> +where + T: Send + 'static, { - // Use a counter to assign a new unique ID for this user. - - // Split the socket into a sender and receive of messages. - let (mut user_ws_tx, mut user_ws_rx) = ws.split(); - - let (event_tx, event_rx) = mpsc::unbounded_channel(); - let (edits_tx, edits_rx) = mpsc::unbounded_channel(); - - let mut edits_rx = UnboundedReceiverStream::new(edits_rx); - let mut event_rx = UnboundedReceiverStream::new(event_rx); - - let vdom_fut = pool.spawn_pinned(move || async move { - let mut vdom = VirtualDom::new_with_props(app, props); - - let edits = vdom.rebuild(); - - let serialized = serde_json::to_string(&edits.edits).unwrap(); - edits_tx.send(serialized).unwrap(); - - loop { - use futures_util::future::{select, Either}; - - let new_event = { - let vdom_fut = vdom.wait_for_work(); - - pin_mut!(vdom_fut); - - match select(event_rx.next(), vdom_fut).await { - Either::Left((l, _)) => l, - Either::Right((_, _)) => None, - } - }; - - if let Some(new_event) = new_event { - vdom.handle_message(dioxus_core::SchedulerMsg::Event(new_event)); - } else { - let mutations = vdom.work_with_deadline(|| false); - for mutation in mutations { - let edits = serde_json::to_string(&mutation.edits).unwrap(); - edits_tx.send(edits).unwrap(); - } - } - } - }); + let mut vdom = VirtualDom::new_with_props(app, props); + let edits = serde_json::to_string(&vdom.rebuild()).unwrap(); + ws.send(Message::text(edits)).await.unwrap(); loop { - use futures_util::future::{select, Either}; + tokio::select! { + // poll any futures or suspense + _ = vdom.wait_for_work() => {} - match select(user_ws_rx.next(), edits_rx.next()).await { - Either::Left((l, _)) => { - if let Some(Ok(msg)) = l { - if let Ok(Some(msg)) = msg.to_str().map(events::parse_ipc_message) { - if msg.method == "user_event" { - let user_event = events::trigger_from_serialized(msg.params); - event_tx.send(user_event).unwrap(); + evt = ws.next() => { + match evt { + Some(Ok(evt)) => { + if let Ok(evt) = evt.to_str() { + let IpcMessage { name, element, bubbles, data } = serde_json::from_str(evt).unwrap(); + + vdom.handle_event(&name, data, element, bubbles); } - } else { - break; } - } else { - break; - } - } - Either::Right((edits, _)) => { - if let Some(edits) = edits { - // send the edits to the client - if user_ws_tx.send(Message::text(edits)).await.is_err() { - break; + Some(Err(e)) => { + // log this I guess? + // when would we get an error here? } - } else { - break; + None => break, } + } } - } - vdom_fut.abort(); + let edits = vdom + .render_with_deadline(tokio::time::sleep(Duration::from_millis(10))) + .await; + + ws.send(Message::text(serde_json::to_string(&edits).unwrap())) + .await + .unwrap(); + } + Ok(()) as Result<(), LiveViewError> } diff --git a/packages/liveview/src/events.rs b/packages/liveview/src/events.rs index 2c5f97cc7..a8bf4d6ca 100644 --- a/packages/liveview/src/events.rs +++ b/packages/liveview/src/events.rs @@ -11,15 +11,10 @@ use dioxus_html::events::*; #[derive(serde::Serialize, serde::Deserialize)] pub(crate) struct IpcMessage { - pub method: String, - pub params: serde_json::Value, -} - -pub(crate) fn parse_ipc_message(payload: &str) -> Option { - match serde_json::from_str(payload) { - Ok(message) => Some(message), - Err(_) => None, - } + pub name: String, + pub element: ElementId, + pub bubbles: bool, + pub data: serde_json::Value, } #[derive(serde::Serialize, serde::Deserialize)] diff --git a/packages/liveview/src/interpreter.js b/packages/liveview/src/interpreter.js index 7e99745f2..ee82a61e1 100644 --- a/packages/liveview/src/interpreter.js +++ b/packages/liveview/src/interpreter.js @@ -4,7 +4,6 @@ function main() { if (root != null) { // create a new ipc window.ipc = new IPC(root); - window.ipc.send(serializeIpcMessage("initialize")); } } @@ -970,4 +969,4 @@ function event_bubbles(event) { case "toggle": return true; } -} \ No newline at end of file +} diff --git a/packages/liveview/src/lib.rs b/packages/liveview/src/lib.rs index 540d29974..01a84c245 100644 --- a/packages/liveview/src/lib.rs +++ b/packages/liveview/src/lib.rs @@ -1,6 +1,21 @@ #![allow(dead_code)] +pub static INTERPRETER: &str = include_str!("interpreter.js"); + +pub fn interpreter_glue(url: &str) -> String { + format!( + r#" + + "# + ) +} + pub(crate) mod events; + pub mod adapters { #[cfg(feature = "warp")] pub mod warp_adapter; @@ -16,41 +31,8 @@ use std::net::SocketAddr; use tokio_util::task::LocalPoolHandle; -#[derive(Clone)] -pub struct Liveview { - pool: LocalPoolHandle, - addr: String, -} +pub mod pool; +pub use pool::*; -impl Liveview { - pub fn body(&self, header: &str) -> String { - format!( - r#" - - - - {header} - - -
- - -"#, - addr = self.addr, - interpreter = include_str!("../src/interpreter.js") - ) - } -} - -pub fn new(addr: impl Into) -> Liveview { - let addr: SocketAddr = addr.into(); - - Liveview { - pool: LocalPoolHandle::new(16), - addr: addr.to_string(), - } -} +#[derive(Debug, Clone, PartialEq, thiserror::Error)] +pub enum LiveViewError {} diff --git a/packages/liveview/src/pool.rs b/packages/liveview/src/pool.rs new file mode 100644 index 000000000..c743df05c --- /dev/null +++ b/packages/liveview/src/pool.rs @@ -0,0 +1,46 @@ +use std::net::SocketAddr; + +use tokio_util::task::LocalPoolHandle; + +#[derive(Clone)] +pub struct LiveView { + pub(crate) pool: LocalPoolHandle, + pub(crate) addr: String, +} + +impl LiveView { + pub fn body(&self, header: &str) -> String { + format!( + r#" + + + + {header} + + +
+ + +"#, + addr = self.addr, + interpreter = include_str!("../src/interpreter.js") + ) + } + + pub fn interpreter_code(&self) -> String { + include_str!("../src/interpreter.js").to_string() + } + + pub fn new(addr: impl Into) -> Self { + let addr: SocketAddr = addr.into(); + + LiveView { + pool: LocalPoolHandle::new(16), + addr: addr.to_string(), + } + } +} From 06e8e096bb195c91e34159b72846c8e8f838d554 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 15 Dec 2022 14:25:23 -0800 Subject: [PATCH 85/93] feat: more fixes to liveview --- packages/desktop/src/lib.rs | 20 +- packages/desktop/src/main.js | 7 + packages/desktop/src/protocol.rs | 9 +- packages/html/src/transit.rs | 6 + packages/interpreter/src/interpreter.js | 12 +- packages/liveview/Cargo.toml | 3 +- packages/liveview/examples/warp.rs | 14 +- .../liveview/src/adapters/warp_adapter.rs | 39 +- packages/liveview/src/events.rs | 202 --- packages/liveview/src/index.html | 15 - packages/liveview/src/interpreter.js | 972 -------------- packages/liveview/src/lib.rs | 21 +- packages/liveview/src/main.js | 35 + packages/liveview/src/pool.rs | 40 +- packages/web/dist/assets/dioxus/dioxus.js | 1143 +++++++++++++++++ .../web/dist/assets/dioxus/dioxus_bg.wasm | Bin 0 -> 37219616 bytes .../inline0.js | 152 +++ packages/web/dist/index.html | 46 + 18 files changed, 1454 insertions(+), 1282 deletions(-) create mode 100644 packages/desktop/src/main.js delete mode 100644 packages/liveview/src/events.rs delete mode 100644 packages/liveview/src/index.html delete mode 100644 packages/liveview/src/interpreter.js create mode 100644 packages/liveview/src/main.js create mode 100644 packages/web/dist/assets/dioxus/dioxus.js create mode 100644 packages/web/dist/assets/dioxus/dioxus_bg.wasm create mode 100644 packages/web/dist/assets/dioxus/snippets/dioxus-interpreter-js-459fb15b86d869f7/inline0.js create mode 100644 packages/web/dist/index.html diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index d8a0f8746..deaa93f12 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -240,16 +240,16 @@ fn build_webview( // in release mode, we don't want to show the dev tool or reload menus webview = webview.with_initialization_script( r#" - if (document.addEventListener) { - document.addEventListener('contextmenu', function(e) { - e.preventDefault(); - }, false); - } else { - document.attachEvent('oncontextmenu', function() { - window.event.returnValue = false; - }); - } - "#, + if (document.addEventListener) { + document.addEventListener('contextmenu', function(e) { + e.preventDefault(); + }, false); + } else { + document.attachEvent('oncontextmenu', function() { + window.event.returnValue = false; + }); + } + "#, ) } else { // in debug, we are okay with the reload menu showing and dev tool diff --git a/packages/desktop/src/main.js b/packages/desktop/src/main.js new file mode 100644 index 000000000..c1f0cf9f4 --- /dev/null +++ b/packages/desktop/src/main.js @@ -0,0 +1,7 @@ +export function main() { + let root = window.document.getElementById("main"); + if (root != null) { + window.interpreter = new Interpreter(root); + window.ipc.postMessage(serializeIpcMessage("initialize")); + } +} diff --git a/packages/desktop/src/protocol.rs b/packages/desktop/src/protocol.rs index a544aa305..f9c96cfea 100644 --- a/packages/desktop/src/protocol.rs +++ b/packages/desktop/src/protocol.rs @@ -52,7 +52,14 @@ pub(super) fn desktop_handler( } else if trimmed == "index.js" { Response::builder() .header("Content-Type", "text/javascript") - .body(dioxus_interpreter_js::INTERPRETER_JS.as_bytes().to_vec()) + .body( + format!( + "{} {}", + dioxus_interpreter_js::INTERPRETER_JS, + include_str!("./main.js") + ) + .into_bytes(), + ) .map_err(From::from) } else { let asset_root = asset_root diff --git a/packages/html/src/transit.rs b/packages/html/src/transit.rs index 803869f31..498feaeae 100644 --- a/packages/html/src/transit.rs +++ b/packages/html/src/transit.rs @@ -29,6 +29,12 @@ pub struct HtmlEvent { pub data: EventData, } +impl HtmlEvent { + pub fn bubbles(&self) -> bool { + event_bubbles(&self.name) + } +} + #[derive(Deserialize, Serialize, Debug, Clone)] #[serde(untagged)] pub enum EventData { diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index 8e1496eb6..7e5395809 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -1,11 +1,3 @@ -export function main() { - let root = window.document.getElementById("main"); - if (root != null) { - window.interpreter = new Interpreter(root); - window.ipc.postMessage(serializeIpcMessage("initialize")); - } -} - class ListenerMap { constructor(root) { // bubbling events can listen at the root element @@ -60,7 +52,7 @@ class ListenerMap { } } -export class Interpreter { +class Interpreter { constructor(root) { this.root = root; this.listeners = new ListenerMap(root); @@ -448,7 +440,7 @@ export class Interpreter { } } -export function serialize_event(event) { +function serialize_event(event) { switch (event.type) { case "copy": case "cut": diff --git a/packages/liveview/Cargo.toml b/packages/liveview/Cargo.toml index d01660373..97f616dd8 100644 --- a/packages/liveview/Cargo.toml +++ b/packages/liveview/Cargo.toml @@ -10,7 +10,6 @@ description = "Build server-side apps with Dioxus" license = "MIT/Apache-2.0" - # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] @@ -28,7 +27,7 @@ tokio-util = { version = "0.7.0", features = ["full"] } dioxus-html = { path = "../html", features = ["serialize"], version = "^0.2.1" } dioxus-core = { path = "../core", features = ["serialize"], version = "^0.2.1" } - +dioxus-interpreter-js = { path = "../interpreter" } # warp warp = { version = "0.3", optional = true } diff --git a/packages/liveview/examples/warp.rs b/packages/liveview/examples/warp.rs index d19914446..5c72d2010 100644 --- a/packages/liveview/examples/warp.rs +++ b/packages/liveview/examples/warp.rs @@ -29,20 +29,24 @@ async fn main() { r#" - Dioxus LiveView with Warp -
{glue} + Dioxus LiveView with Warp +
+ {glue} "#, - glue = dioxus_liveview::interpreter_glue(&format!("ws://{addr}/ws/app")) + glue = dioxus_liveview::interpreter_glue(&format!("ws://{addr}/ws/")) )) }); - let view = LiveView::new(addr); + let view = LiveView::new(); let ws = warp::path("ws") .and(warp::ws()) .and(warp::any().map(move || view.clone())) - .map(move |ws: Ws, view: LiveView| ws.on_upgrade(|ws| view.upgrade_warp(ws, app))); + .map(move |ws: Ws, view: LiveView| { + println!("Got a connection!"); + ws.on_upgrade(|ws| view.upgrade_warp(ws, app)) + }); println!("Listening on http://{}", addr); diff --git a/packages/liveview/src/adapters/warp_adapter.rs b/packages/liveview/src/adapters/warp_adapter.rs index 39a167359..2b9870959 100644 --- a/packages/liveview/src/adapters/warp_adapter.rs +++ b/packages/liveview/src/adapters/warp_adapter.rs @@ -1,19 +1,14 @@ -use std::{convert::Infallible, time::Duration}; - -use crate::{ - events::{self, IpcMessage}, - LiveView, LiveViewError, -}; +use crate::{LiveView, LiveViewError}; use dioxus_core::prelude::*; -use dioxus_html::a; -use futures_util::{pin_mut, SinkExt, StreamExt}; -use tokio::sync::mpsc; -use tokio_stream::wrappers::UnboundedReceiverStream; -use tokio_util::task::LocalPoolHandle; +use dioxus_html::HtmlEvent; +use futures_util::{SinkExt, StreamExt}; +use std::time::Duration; use warp::ws::{Message, WebSocket}; impl LiveView { - pub async fn upgrade_warp(self, ws: WebSocket, app: fn(Scope<()>) -> Element) {} + pub async fn upgrade_warp(self, ws: WebSocket, app: fn(Scope<()>) -> Element) { + self.upgrade_warp_with_props(ws, app, ()).await + } pub async fn upgrade_warp_with_props( self, @@ -48,18 +43,24 @@ where match evt { Some(Ok(evt)) => { if let Ok(evt) = evt.to_str() { - let IpcMessage { name, element, bubbles, data } = serde_json::from_str(evt).unwrap(); + // desktop uses this wrapper struct thing + #[derive(serde::Deserialize)] + struct IpcMessage { + params: HtmlEvent + } - vdom.handle_event(&name, data, element, bubbles); + let event: IpcMessage = serde_json::from_str(evt).unwrap(); + let event = event.params; + let bubbles = event.bubbles(); + vdom.handle_event(&event.name, event.data.into_any(), event.element, bubbles); } } - Some(Err(e)) => { + Some(Err(_e)) => { // log this I guess? // when would we get an error here? } - None => break, + None => return Ok(()), } - } } @@ -68,8 +69,6 @@ where .await; ws.send(Message::text(serde_json::to_string(&edits).unwrap())) - .await - .unwrap(); + .await?; } - Ok(()) as Result<(), LiveViewError> } diff --git a/packages/liveview/src/events.rs b/packages/liveview/src/events.rs deleted file mode 100644 index a8bf4d6ca..000000000 --- a/packages/liveview/src/events.rs +++ /dev/null @@ -1,202 +0,0 @@ -#![allow(dead_code)] - -//! Convert a serialized event to an event trigger - -use std::any::Any; -use std::sync::Arc; - -use dioxus_core::ElementId; -// use dioxus_html::event_bubbles; -use dioxus_html::events::*; - -#[derive(serde::Serialize, serde::Deserialize)] -pub(crate) struct IpcMessage { - pub name: String, - pub element: ElementId, - pub bubbles: bool, - pub data: serde_json::Value, -} - -#[derive(serde::Serialize, serde::Deserialize)] -struct ImEvent { - event: String, - mounted_dom_id: ElementId, - contents: serde_json::Value, -} - -pub fn trigger_from_serialized(_val: serde_json::Value) { - todo!() - // let ImEvent { - // event, - // mounted_dom_id, - // contents, - // } = serde_json::from_value(val).unwrap(); - - // let mounted_dom_id = Some(mounted_dom_id); - - // let name = event_name_from_type(&event); - // let event = make_synthetic_event(&event, contents); - - // UserEvent { - // name, - // scope_id: None, - // element: mounted_dom_id, - // data: event, - // bubbles: event_bubbles(name), - // } -} - -fn make_synthetic_event(name: &str, val: serde_json::Value) -> Arc { - match name { - "copy" | "cut" | "paste" => { - // - Arc::new(ClipboardData {}) - } - "compositionend" | "compositionstart" | "compositionupdate" => { - Arc::new(serde_json::from_value::(val).unwrap()) - } - "keydown" | "keypress" | "keyup" => { - let evt = serde_json::from_value::(val).unwrap(); - Arc::new(evt) - } - "focus" | "blur" | "focusout" | "focusin" => { - // - Arc::new(FocusData {}) - } - - // todo: these handlers might get really slow if the input box gets large and allocation pressure is heavy - // don't have a good solution with the serialized event problem - "change" | "input" | "invalid" | "reset" | "submit" => { - Arc::new(serde_json::from_value::(val).unwrap()) - } - - "click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit" - | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter" - | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => { - Arc::new(serde_json::from_value::(val).unwrap()) - } - "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture" - | "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => { - Arc::new(serde_json::from_value::(val).unwrap()) - } - "select" => { - // - Arc::new(serde_json::from_value::(val).unwrap()) - } - - "touchcancel" | "touchend" | "touchmove" | "touchstart" => { - Arc::new(serde_json::from_value::(val).unwrap()) - } - - "scroll" => Arc::new(()), - - "wheel" => Arc::new(serde_json::from_value::(val).unwrap()), - - "animationstart" | "animationend" | "animationiteration" => { - Arc::new(serde_json::from_value::(val).unwrap()) - } - - "transitionend" => Arc::new(serde_json::from_value::(val).unwrap()), - - "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted" - | "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play" - | "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend" - | "timeupdate" | "volumechange" | "waiting" => { - // - Arc::new(MediaData {}) - } - - "toggle" => Arc::new(ToggleData {}), - - _ => Arc::new(()), - } -} - -fn event_name_from_type(typ: &str) -> &'static str { - match typ { - "copy" => "copy", - "cut" => "cut", - "paste" => "paste", - "compositionend" => "compositionend", - "compositionstart" => "compositionstart", - "compositionupdate" => "compositionupdate", - "keydown" => "keydown", - "keypress" => "keypress", - "keyup" => "keyup", - "focus" => "focus", - "focusout" => "focusout", - "focusin" => "focusin", - "blur" => "blur", - "change" => "change", - "input" => "input", - "invalid" => "invalid", - "reset" => "reset", - "submit" => "submit", - "click" => "click", - "contextmenu" => "contextmenu", - "doubleclick" => "doubleclick", - "drag" => "drag", - "dragend" => "dragend", - "dragenter" => "dragenter", - "dragexit" => "dragexit", - "dragleave" => "dragleave", - "dragover" => "dragover", - "dragstart" => "dragstart", - "drop" => "drop", - "mousedown" => "mousedown", - "mouseenter" => "mouseenter", - "mouseleave" => "mouseleave", - "mousemove" => "mousemove", - "mouseout" => "mouseout", - "mouseover" => "mouseover", - "mouseup" => "mouseup", - "pointerdown" => "pointerdown", - "pointermove" => "pointermove", - "pointerup" => "pointerup", - "pointercancel" => "pointercancel", - "gotpointercapture" => "gotpointercapture", - "lostpointercapture" => "lostpointercapture", - "pointerenter" => "pointerenter", - "pointerleave" => "pointerleave", - "pointerover" => "pointerover", - "pointerout" => "pointerout", - "select" => "select", - "touchcancel" => "touchcancel", - "touchend" => "touchend", - "touchmove" => "touchmove", - "touchstart" => "touchstart", - "scroll" => "scroll", - "wheel" => "wheel", - "animationstart" => "animationstart", - "animationend" => "animationend", - "animationiteration" => "animationiteration", - "transitionend" => "transitionend", - "abort" => "abort", - "canplay" => "canplay", - "canplaythrough" => "canplaythrough", - "durationchange" => "durationchange", - "emptied" => "emptied", - "encrypted" => "encrypted", - "ended" => "ended", - "error" => "error", - "loadeddata" => "loadeddata", - "loadedmetadata" => "loadedmetadata", - "loadstart" => "loadstart", - "pause" => "pause", - "play" => "play", - "playing" => "playing", - "progress" => "progress", - "ratechange" => "ratechange", - "seeked" => "seeked", - "seeking" => "seeking", - "stalled" => "stalled", - "suspend" => "suspend", - "timeupdate" => "timeupdate", - "volumechange" => "volumechange", - "waiting" => "waiting", - "toggle" => "toggle", - _ => { - panic!("unsupported event type") - } - } -} diff --git a/packages/liveview/src/index.html b/packages/liveview/src/index.html deleted file mode 100644 index 292a1ae6a..000000000 --- a/packages/liveview/src/index.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - Dioxus app - - - -
- - - diff --git a/packages/liveview/src/interpreter.js b/packages/liveview/src/interpreter.js deleted file mode 100644 index ee82a61e1..000000000 --- a/packages/liveview/src/interpreter.js +++ /dev/null @@ -1,972 +0,0 @@ -function main() { - let root = window.document.getElementById("main"); - - if (root != null) { - // create a new ipc - window.ipc = new IPC(root); - window.ipc.send(serializeIpcMessage("initialize")); - } -} - -class IPC { - constructor(root) { - // connect to the websocket - window.interpreter = new Interpreter(root); - - this.ws = new WebSocket(WS_ADDR); - - this.ws.onopen = () => { - console.log("Connected to the websocket"); - }; - - this.ws.onerror = (err) => { - console.error("Error: ", err); - }; - - this.ws.onmessage = (event) => { - let edits = JSON.parse(event.data); - window.interpreter.handleEdits(edits); - }; - } - - send(msg) { - this.ws.send(msg); - } -} - -class ListenerMap { - constructor(root) { - // bubbling events can listen at the root element - this.global = {}; - // non bubbling events listen at the element the listener was created at - this.local = {}; - this.root = root; - } - - create(event_name, element, handler, bubbles) { - if (bubbles) { - if (this.global[event_name] === undefined) { - this.global[event_name] = {}; - this.global[event_name].active = 1; - this.global[event_name].callback = handler; - this.root.addEventListener(event_name, handler); - } else { - this.global[event_name].active++; - } - } - else { - const id = element.getAttribute("data-dioxus-id"); - if (!this.local[id]) { - this.local[id] = {}; - } - this.local[id][event_name] = handler; - element.addEventListener(event_name, handler); - } - } - - remove(element, event_name, bubbles) { - if (bubbles) { - this.global[event_name].active--; - if (this.global[event_name].active === 0) { - this.root.removeEventListener(event_name, this.global[event_name].callback); - delete this.global[event_name]; - } - } - else { - const id = element.getAttribute("data-dioxus-id"); - delete this.local[id][event_name]; - if (this.local[id].length === 0) { - delete this.local[id]; - } - element.removeEventListener(event_name, handler); - } - } -} - -class Interpreter { - constructor(root) { - this.root = root; - this.lastNode = root; - this.listeners = new ListenerMap(root); - this.handlers = {}; - this.nodes = [root]; - this.parents = []; - } - checkAppendParent() { - if (this.parents.length > 0) { - const lastParent = this.parents[this.parents.length - 1]; - lastParent[1]--; - if (lastParent[1] === 0) { - this.parents.pop(); - } - lastParent[0].appendChild(this.lastNode); - } - } - AppendChildren(root, children) { - let node; - if (root == null) { - node = this.lastNode; - } else { - node = this.nodes[root]; - } - for (let i = 0; i < children.length; i++) { - node.appendChild(this.nodes[children[i]]); - } - } - ReplaceWith(root, nodes) { - let node; - if (root == null) { - node = this.lastNode; - } else { - node = this.nodes[root]; - } - let els = []; - for (let i = 0; i < nodes.length; i++) { - els.push(this.nodes[nodes[i]]) - } - node.replaceWith(...els); - } - InsertAfter(root, nodes) { - let node; - if (root == null) { - node = this.lastNode; - } else { - node = this.nodes[root]; - } - let els = []; - for (let i = 0; i < nodes.length; i++) { - els.push(this.nodes[nodes[i]]) - } - node.after(...els); - } - InsertBefore(root, nodes) { - let node; - if (root == null) { - node = this.lastNode; - } else { - node = this.nodes[root]; - } - let els = []; - for (let i = 0; i < nodes.length; i++) { - els.push(this.nodes[nodes[i]]) - } - node.before(...els); - } - Remove(root) { - let node; - if (root == null) { - node = this.lastNode; - } else { - node = this.nodes[root]; - } - if (node !== undefined) { - node.remove(); - } - } - CreateTextNode(text, root) { - this.lastNode = document.createTextNode(text); - this.checkAppendParent(); - if (root != null) { - this.nodes[root] = this.lastNode; - } - } - CreateElement(tag, root, children) { - this.lastNode = document.createElement(tag); - this.checkAppendParent(); - if (root != null) { - this.nodes[root] = this.lastNode; - } - if (children > 0) { - this.parents.push([this.lastNode, children]); - } - } - CreateElementNs(tag, root, ns, children) { - this.lastNode = document.createElementNS(ns, tag); - this.checkAppendParent(); - if (root != null) { - this.nodes[root] = this.lastNode; - } - if (children > 0) { - this.parents.push([this.lastNode, children]); - } - } - CreatePlaceholder(root) { - this.lastNode = document.createElement("pre"); - this.lastNode.hidden = true; - this.checkAppendParent(); - if (root != null) { - this.nodes[root] = this.lastNode; - } - } - NewEventListener(event_name, root, handler, bubbles) { - let node; - if (root == null) { - node = this.lastNode; - } else { - node = this.nodes[root]; - } - node.setAttribute("data-dioxus-id", `${root}`); - this.listeners.create(event_name, node, handler, bubbles); - } - RemoveEventListener(root, event_name, bubbles) { - let node; - if (root == null) { - node = this.lastNode; - } else { - node = this.nodes[root]; - } - node.removeAttribute(`data-dioxus-id`); - this.listeners.remove(node, event_name, bubbles); - } - SetText(root, text) { - let node; - if (root == null) { - node = this.lastNode; - } else { - node = this.nodes[root]; - } - node.data = text; - } - SetAttribute(root, field, value, ns) { - const name = field; - let node; - if (root == null) { - node = this.lastNode; - } else { - node = this.nodes[root]; - } - if (ns === "style") { - // @ts-ignore - node.style[name] = value; - } else if (ns != null || ns != undefined) { - node.setAttributeNS(ns, name, value); - } else { - switch (name) { - case "value": - if (value !== node.value) { - node.value = value; - } - break; - case "checked": - node.checked = value === "true"; - break; - case "selected": - node.selected = value === "true"; - break; - case "dangerous_inner_html": - node.innerHTML = value; - break; - default: - // https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364 - if (value === "false" && bool_attrs.hasOwnProperty(name)) { - node.removeAttribute(name); - } else { - node.setAttribute(name, value); - } - } - } - } - RemoveAttribute(root, field, ns) { - const name = field; - let node; - if (root == null) { - node = this.lastNode; - } else { - node = this.nodes[root]; - } - if (ns == "style") { - node.style.removeProperty(name); - } else if (ns !== null || ns !== undefined) { - node.removeAttributeNS(ns, name); - } else if (name === "value") { - node.value = ""; - } else if (name === "checked") { - node.checked = false; - } else if (name === "selected") { - node.selected = false; - } else if (name === "dangerous_inner_html") { - node.innerHTML = ""; - } else { - node.removeAttribute(name); - } - } - CloneNode(old, new_id) { - let node; - if (old === null) { - node = this.lastNode; - } else { - node = this.nodes[old]; - } - this.nodes[new_id] = node.cloneNode(true); - } - CloneNodeChildren(old, new_ids) { - let node; - if (old === null) { - node = this.lastNode; - } else { - node = this.nodes[old]; - } - const old_node = node.cloneNode(true); - let i = 0; - for (let node = old_node.firstChild; i < new_ids.length; node = node.nextSibling) { - this.nodes[new_ids[i++]] = node; - } - } - FirstChild() { - this.lastNode = this.lastNode.firstChild; - } - NextSibling() { - this.lastNode = this.lastNode.nextSibling; - } - ParentNode() { - this.lastNode = this.lastNode.parentNode; - } - StoreWithId(id) { - this.nodes[id] = this.lastNode; - } - SetLastNode(root) { - this.lastNode = this.nodes[root]; - } - handleEdits(edits) { - for (let edit of edits) { - this.handleEdit(edit); - } - } - handleEdit(edit) { - switch (edit.type) { - case "PushRoot": - this.PushRoot(edit.root); - break; - case "AppendChildren": - this.AppendChildren(edit.root, edit.children); - break; - case "ReplaceWith": - this.ReplaceWith(edit.root, edit.nodes); - break; - case "InsertAfter": - this.InsertAfter(edit.root, edit.nodes); - break; - case "InsertBefore": - this.InsertBefore(edit.root, edit.nodes); - break; - case "Remove": - this.Remove(edit.root); - break; - case "CreateTextNode": - this.CreateTextNode(edit.text, edit.root); - break; - case "CreateElement": - this.CreateElement(edit.tag, edit.root, edit.children); - break; - case "CreateElementNs": - this.CreateElementNs(edit.tag, edit.root, edit.ns, edit.children); - break; - case "CreatePlaceholder": - this.CreatePlaceholder(edit.root); - break; - case "RemoveEventListener": - this.RemoveEventListener(edit.root, edit.event_name); - break; - case "NewEventListener": - // this handler is only provided on desktop implementations since this - // method is not used by the web implementation - let handler = (event) => { - let target = event.target; - if (target != null) { - let realId = target.getAttribute(`data-dioxus-id`); - let shouldPreventDefault = target.getAttribute( - `dioxus-prevent-default` - ); - - if (event.type === "click") { - // todo call prevent default if it's the right type of event - if (shouldPreventDefault !== `onclick`) { - if (target.tagName === "A") { - event.preventDefault(); - const href = target.getAttribute("href"); - if (href !== "" && href !== null && href !== undefined) { - window.ipc.postMessage( - serializeIpcMessage("browser_open", { href }) - ); - } - } - } - - // also prevent buttons from submitting - if (target.tagName === "BUTTON" && event.type == "submit") { - event.preventDefault(); - } - } - // walk the tree to find the real element - while (realId == null) { - // we've reached the root we don't want to send an event - if (target.parentElement === null) { - return; - } - - target = target.parentElement; - realId = target.getAttribute(`data-dioxus-id`); - } - - shouldPreventDefault = target.getAttribute( - `dioxus-prevent-default` - ); - - let contents = serialize_event(event); - - if (shouldPreventDefault === `on${event.type}`) { - event.preventDefault(); - } - - if (event.type === "submit") { - event.preventDefault(); - } - - if ( - target.tagName === "FORM" && - (event.type === "submit" || event.type === "input") - ) { - for (let x = 0; x < target.elements.length; x++) { - let element = target.elements[x]; - let name = element.getAttribute("name"); - if (name != null) { - if (element.getAttribute("type") === "checkbox") { - // @ts-ignore - contents.values[name] = element.checked ? "true" : "false"; - } else if (element.getAttribute("type") === "radio") { - if (element.checked) { - contents.values[name] = element.value; - } - } else { - // @ts-ignore - contents.values[name] = - element.value ?? element.textContent; - } - } - } - } - - if (realId === null) { - return; - } - realId = parseInt(realId); - window.ipc.send( - serializeIpcMessage("user_event", { - event: edit.event_name, - mounted_dom_id: realId, - contents: contents, - }) - ); - } - }; - this.NewEventListener(edit.event_name, edit.root, handler, event_bubbles(edit.event_name)); - - break; - case "SetText": - this.SetText(edit.root, edit.text); - break; - case "SetAttribute": - this.SetAttribute(edit.root, edit.field, edit.value, edit.ns); - break; - case "RemoveAttribute": - this.RemoveAttribute(edit.root, edit.name, edit.ns); - break; - case "CloneNode": - this.CloneNode(edit.id, edit.new_id); - break; - case "CloneNodeChildren": - this.CloneNodeChildren(edit.id, edit.new_ids); - break; - case "FirstChild": - this.FirstChild(); - break; - case "NextSibling": - this.NextSibling(); - break; - case "ParentNode": - this.ParentNode(); - break; - case "StoreWithId": - this.StoreWithId(BigInt(edit.id)); - break; - case "SetLastNode": - this.SetLastNode(BigInt(edit.id)); - break; - } - } -} - -function serialize_event(event) { - switch (event.type) { - case "copy": - case "cut": - case "past": { - return {}; - } - case "compositionend": - case "compositionstart": - case "compositionupdate": { - let { data } = event; - return { - data, - }; - } - case "keydown": - case "keypress": - case "keyup": { - let { - charCode, - key, - altKey, - ctrlKey, - metaKey, - keyCode, - shiftKey, - location, - repeat, - which, - code, - } = event; - return { - char_code: charCode, - key: key, - alt_key: altKey, - ctrl_key: ctrlKey, - meta_key: metaKey, - key_code: keyCode, - shift_key: shiftKey, - location: location, - repeat: repeat, - which: which, - code, - }; - } - case "focus": - case "blur": { - return {}; - } - case "change": { - let target = event.target; - let value; - if (target.type === "checkbox" || target.type === "radio") { - value = target.checked ? "true" : "false"; - } else { - value = target.value ?? target.textContent; - } - return { - value: value, - values: {}, - }; - } - case "input": - case "invalid": - case "reset": - case "submit": { - let target = event.target; - let value = target.value ?? target.textContent; - - if (target.type === "checkbox") { - value = target.checked ? "true" : "false"; - } - - return { - value: value, - values: {}, - }; - } - case "click": - case "contextmenu": - case "doubleclick": - case "dblclick": - case "drag": - case "dragend": - case "dragenter": - case "dragexit": - case "dragleave": - case "dragover": - case "dragstart": - case "drop": - case "mousedown": - case "mouseenter": - case "mouseleave": - case "mousemove": - case "mouseout": - case "mouseover": - case "mouseup": { - const { - altKey, - button, - buttons, - clientX, - clientY, - ctrlKey, - metaKey, - offsetX, - offsetY, - pageX, - pageY, - screenX, - screenY, - shiftKey, - } = event; - return { - alt_key: altKey, - button: button, - buttons: buttons, - client_x: clientX, - client_y: clientY, - ctrl_key: ctrlKey, - meta_key: metaKey, - offset_x: offsetX, - offset_y: offsetY, - page_x: pageX, - page_y: pageY, - screen_x: screenX, - screen_y: screenY, - shift_key: shiftKey, - }; - } - case "pointerdown": - case "pointermove": - case "pointerup": - case "pointercancel": - case "gotpointercapture": - case "lostpointercapture": - case "pointerenter": - case "pointerleave": - case "pointerover": - case "pointerout": { - const { - altKey, - button, - buttons, - clientX, - clientY, - ctrlKey, - metaKey, - pageX, - pageY, - screenX, - screenY, - shiftKey, - pointerId, - width, - height, - pressure, - tangentialPressure, - tiltX, - tiltY, - twist, - pointerType, - isPrimary, - } = event; - return { - alt_key: altKey, - button: button, - buttons: buttons, - client_x: clientX, - client_y: clientY, - ctrl_key: ctrlKey, - meta_key: metaKey, - page_x: pageX, - page_y: pageY, - screen_x: screenX, - screen_y: screenY, - shift_key: shiftKey, - pointer_id: pointerId, - width: width, - height: height, - pressure: pressure, - tangential_pressure: tangentialPressure, - tilt_x: tiltX, - tilt_y: tiltY, - twist: twist, - pointer_type: pointerType, - is_primary: isPrimary, - }; - } - case "select": { - return {}; - } - case "touchcancel": - case "touchend": - case "touchmove": - case "touchstart": { - const { altKey, ctrlKey, metaKey, shiftKey } = event; - return { - // changed_touches: event.changedTouches, - // target_touches: event.targetTouches, - // touches: event.touches, - alt_key: altKey, - ctrl_key: ctrlKey, - meta_key: metaKey, - shift_key: shiftKey, - }; - } - case "scroll": { - return {}; - } - case "wheel": { - const { deltaX, deltaY, deltaZ, deltaMode } = event; - return { - delta_x: deltaX, - delta_y: deltaY, - delta_z: deltaZ, - delta_mode: deltaMode, - }; - } - case "animationstart": - case "animationend": - case "animationiteration": { - const { animationName, elapsedTime, pseudoElement } = event; - return { - animation_name: animationName, - elapsed_time: elapsedTime, - pseudo_element: pseudoElement, - }; - } - case "transitionend": { - const { propertyName, elapsedTime, pseudoElement } = event; - return { - property_name: propertyName, - elapsed_time: elapsedTime, - pseudo_element: pseudoElement, - }; - } - case "abort": - case "canplay": - case "canplaythrough": - case "durationchange": - case "emptied": - case "encrypted": - case "ended": - case "error": - case "loadeddata": - case "loadedmetadata": - case "loadstart": - case "pause": - case "play": - case "playing": - case "progress": - case "ratechange": - case "seeked": - case "seeking": - case "stalled": - case "suspend": - case "timeupdate": - case "volumechange": - case "waiting": { - return {}; - } - case "toggle": { - return {}; - } - default: { - return {}; - } - } -} -function serializeIpcMessage(method, params = {}) { - return JSON.stringify({ method, params }); -} -const bool_attrs = { - allowfullscreen: true, - allowpaymentrequest: true, - async: true, - autofocus: true, - autoplay: true, - checked: true, - controls: true, - default: true, - defer: true, - disabled: true, - formnovalidate: true, - hidden: true, - ismap: true, - itemscope: true, - loop: true, - multiple: true, - muted: true, - nomodule: true, - novalidate: true, - open: true, - playsinline: true, - readonly: true, - required: true, - reversed: true, - selected: true, - truespeed: true, -}; - -function is_element_node(node) { - return node.nodeType == 1; -} - -function event_bubbles(event) { - switch (event) { - case "copy": - return true; - case "cut": - return true; - case "paste": - return true; - case "compositionend": - return true; - case "compositionstart": - return true; - case "compositionupdate": - return true; - case "keydown": - return true; - case "keypress": - return true; - case "keyup": - return true; - case "focus": - return false; - case "focusout": - return true; - case "focusin": - return true; - case "blur": - return false; - case "change": - return true; - case "input": - return true; - case "invalid": - return true; - case "reset": - return true; - case "submit": - return true; - case "click": - return true; - case "contextmenu": - return true; - case "doubleclick": - return true; - case "dblclick": - return true; - case "drag": - return true; - case "dragend": - return true; - case "dragenter": - return false; - case "dragexit": - return false; - case "dragleave": - return true; - case "dragover": - return true; - case "dragstart": - return true; - case "drop": - return true; - case "mousedown": - return true; - case "mouseenter": - return false; - case "mouseleave": - return false; - case "mousemove": - return true; - case "mouseout": - return true; - case "scroll": - return false; - case "mouseover": - return true; - case "mouseup": - return true; - case "pointerdown": - return true; - case "pointermove": - return true; - case "pointerup": - return true; - case "pointercancel": - return true; - case "gotpointercapture": - return true; - case "lostpointercapture": - return true; - case "pointerenter": - return false; - case "pointerleave": - return false; - case "pointerover": - return true; - case "pointerout": - return true; - case "select": - return true; - case "touchcancel": - return true; - case "touchend": - return true; - case "touchmove": - return true; - case "touchstart": - return true; - case "wheel": - return true; - case "abort": - return false; - case "canplay": - return false; - case "canplaythrough": - return false; - case "durationchange": - return false; - case "emptied": - return false; - case "encrypted": - return true; - case "ended": - return false; - case "error": - return false; - case "loadeddata": - return false; - case "loadedmetadata": - return false; - case "loadstart": - return false; - case "pause": - return false; - case "play": - return false; - case "playing": - return false; - case "progress": - return false; - case "ratechange": - return false; - case "seeked": - return false; - case "seeking": - return false; - case "stalled": - return false; - case "suspend": - return false; - case "timeupdate": - return false; - case "volumechange": - return false; - case "waiting": - return false; - case "animationstart": - return true; - case "animationend": - return true; - case "animationiteration": - return true; - case "transitionend": - return true; - case "toggle": - return true; - } -} diff --git a/packages/liveview/src/lib.rs b/packages/liveview/src/lib.rs index 01a84c245..5d111e319 100644 --- a/packages/liveview/src/lib.rs +++ b/packages/liveview/src/lib.rs @@ -1,21 +1,19 @@ -#![allow(dead_code)] - -pub static INTERPRETER: &str = include_str!("interpreter.js"); +use dioxus_interpreter_js::INTERPRETER_JS; +static MAIN_JS: &str = include_str!("./main.js"); pub fn interpreter_glue(url: &str) -> String { format!( r#" "# ) } -pub(crate) mod events; - pub mod adapters { #[cfg(feature = "warp")] pub mod warp_adapter; @@ -27,12 +25,11 @@ pub mod adapters { pub mod salvo_adapter; } -use std::net::SocketAddr; - -use tokio_util::task::LocalPoolHandle; - pub mod pool; pub use pool::*; -#[derive(Debug, Clone, PartialEq, thiserror::Error)] -pub enum LiveViewError {} +#[derive(Debug, thiserror::Error)] +pub enum LiveViewError { + #[error("Connection Failed")] + Warp(#[from] warp::Error), +} diff --git a/packages/liveview/src/main.js b/packages/liveview/src/main.js new file mode 100644 index 000000000..9b0cf5c38 --- /dev/null +++ b/packages/liveview/src/main.js @@ -0,0 +1,35 @@ +function main() { + let root = window.document.getElementById("main"); + + if (root != null) { + // create a new ipc + window.ipc = new IPC(root); + window.ipc.send(serializeIpcMessage("initialize")); + } +} + +class IPC { + constructor(root) { + // connect to the websocket + window.interpreter = new Interpreter(root); + + this.ws = new WebSocket(WS_ADDR); + + this.ws.onopen = () => { + console.log("Connected to the websocket"); + }; + + this.ws.onerror = (err) => { + console.error("Error: ", err); + }; + + this.ws.onmessage = (event) => { + let edits = JSON.parse(event.data); + window.interpreter.handleEdits(edits); + }; + } + + postMessage(msg) { + this.ws.send(msg); + } +} diff --git a/packages/liveview/src/pool.rs b/packages/liveview/src/pool.rs index c743df05c..05d7fe98e 100644 --- a/packages/liveview/src/pool.rs +++ b/packages/liveview/src/pool.rs @@ -1,46 +1,20 @@ -use std::net::SocketAddr; - use tokio_util::task::LocalPoolHandle; #[derive(Clone)] pub struct LiveView { pub(crate) pool: LocalPoolHandle, - pub(crate) addr: String, +} + +impl Default for LiveView { + fn default() -> Self { + Self::new() + } } impl LiveView { - pub fn body(&self, header: &str) -> String { - format!( - r#" - - - - {header} - - -
- - -"#, - addr = self.addr, - interpreter = include_str!("../src/interpreter.js") - ) - } - - pub fn interpreter_code(&self) -> String { - include_str!("../src/interpreter.js").to_string() - } - - pub fn new(addr: impl Into) -> Self { - let addr: SocketAddr = addr.into(); - + pub fn new() -> Self { LiveView { pool: LocalPoolHandle::new(16), - addr: addr.to_string(), } } } diff --git a/packages/web/dist/assets/dioxus/dioxus.js b/packages/web/dist/assets/dioxus/dioxus.js new file mode 100644 index 000000000..b8f5250ca --- /dev/null +++ b/packages/web/dist/assets/dioxus/dioxus.js @@ -0,0 +1,1143 @@ +import { create, update_memory, save_template, initilize } from './snippets/dioxus-interpreter-js-459fb15b86d869f7/inline0.js'; +import * as __wbg_star0 from './snippets/dioxus-interpreter-js-459fb15b86d869f7/inline0.js'; + +let wasm; + +const heap = new Array(32).fill(undefined); + +heap.push(undefined, null, true, false); + +function getObject(idx) { return heap[idx]; } + +let heap_next = heap.length; + +function dropObject(idx) { + if (idx < 36) return; + heap[idx] = heap_next; + heap_next = idx; +} + +function takeObject(idx) { + const ret = getObject(idx); + dropObject(idx); + return ret; +} + +function _assertBoolean(n) { + if (typeof(n) !== 'boolean') { + throw new Error('expected a boolean argument'); + } +} + +const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + +cachedTextDecoder.decode(); + +let cachedUint8Memory0 = new Uint8Array(); + +function getUint8Memory0() { + if (cachedUint8Memory0.byteLength === 0) { + cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8Memory0; +} + +function getStringFromWasm0(ptr, len) { + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); +} + +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1); + const idx = heap_next; + heap_next = heap[idx]; + + if (typeof(heap_next) !== 'number') throw new Error('corrupt heap'); + + heap[idx] = obj; + return idx; +} + +let WASM_VECTOR_LEN = 0; + +const cachedTextEncoder = new TextEncoder('utf-8'); + +const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' + ? function (arg, view) { + return cachedTextEncoder.encodeInto(arg, view); +} + : function (arg, view) { + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length + }; +}); + +function passStringToWasm0(arg, malloc, realloc) { + + if (typeof(arg) !== 'string') throw new Error('expected a string argument'); + + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length); + getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len); + + const mem = getUint8Memory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + } + + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3); + const view = getUint8Memory0().subarray(ptr + offset, ptr + len); + const ret = encodeString(arg, view); + if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; + } + + WASM_VECTOR_LEN = offset; + return ptr; +} + +function isLikeNone(x) { + return x === undefined || x === null; +} + +let cachedInt32Memory0 = new Int32Array(); + +function getInt32Memory0() { + if (cachedInt32Memory0.byteLength === 0) { + cachedInt32Memory0 = new Int32Array(wasm.memory.buffer); + } + return cachedInt32Memory0; +} + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} + +function makeMutClosure(arg0, arg1, dtor, f) { + const state = { a: arg0, b: arg1, cnt: 1, dtor }; + const real = (...args) => { + // First up with a closure we increment the internal reference + // count. This ensures that the Rust closure environment won't + // be deallocated while we're invoking it. + state.cnt++; + const a = state.a; + state.a = 0; + try { + return f(a, state.b, ...args); + } finally { + if (--state.cnt === 0) { + wasm.__wbindgen_export_2.get(state.dtor)(a, state.b); + + } else { + state.a = a; + } + } + }; + real.original = state; + + return real; +} + +function logError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + let error = (function () { + try { + return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); + } catch(_) { + return ""; + } + }()); + console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); + throw e; + } +} + +function _assertNum(n) { + if (typeof(n) !== 'number') throw new Error('expected a number argument'); +} +function __wbg_adapter_22(arg0, arg1, arg2) { + _assertNum(arg0); + _assertNum(arg1); + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hc67c0adeead8d38c(arg0, arg1, addHeapObject(arg2)); +} + +let stack_pointer = 32; + +function addBorrowedObject(obj) { + if (stack_pointer == 1) throw new Error('out of js stack'); + heap[--stack_pointer] = obj; + return stack_pointer; +} +function __wbg_adapter_25(arg0, arg1, arg2) { + try { + _assertNum(arg0); + _assertNum(arg1); + wasm._dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hf3d7123c3170f8e5(arg0, arg1, addBorrowedObject(arg2)); + } finally { + heap[stack_pointer++] = undefined; + } +} + +function makeClosure(arg0, arg1, dtor, f) { + const state = { a: arg0, b: arg1, cnt: 1, dtor }; + const real = (...args) => { + // First up with a closure we increment the internal reference + // count. This ensures that the Rust closure environment won't + // be deallocated while we're invoking it. + state.cnt++; + try { + return f(state.a, state.b, ...args); + } finally { + if (--state.cnt === 0) { + wasm.__wbindgen_export_2.get(state.dtor)(state.a, state.b); + state.a = 0; + + } + } + }; + real.original = state; + + return real; +} +function __wbg_adapter_28(arg0, arg1, arg2) { + _assertNum(arg0); + _assertNum(arg1); + wasm._dyn_core__ops__function__Fn__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__heed889d80ba6fff2(arg0, arg1, addHeapObject(arg2)); +} + +function __wbg_adapter_31(arg0, arg1, arg2) { + _assertNum(arg0); + _assertNum(arg1); + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h6c21192190c3cf37(arg0, arg1, addHeapObject(arg2)); +} + +function __wbg_adapter_34(arg0, arg1, arg2) { + try { + _assertNum(arg0); + _assertNum(arg1); + wasm._dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hd0907e42a697a011(arg0, arg1, addBorrowedObject(arg2)); + } finally { + heap[stack_pointer++] = undefined; + } +} + +function getCachedStringFromWasm0(ptr, len) { + if (ptr === 0) { + return getObject(len); + } else { + return getStringFromWasm0(ptr, len); + } +} + +let cachedUint32Memory0 = new Uint32Array(); + +function getUint32Memory0() { + if (cachedUint32Memory0.byteLength === 0) { + cachedUint32Memory0 = new Uint32Array(wasm.memory.buffer); + } + return cachedUint32Memory0; +} + +function getArrayJsValueFromWasm0(ptr, len) { + const mem = getUint32Memory0(); + const slice = mem.subarray(ptr / 4, ptr / 4 + len); + const result = []; + for (let i = 0; i < slice.length; i++) { + result.push(takeObject(slice[i])); + } + return result; +} + +function handleError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + wasm.__wbindgen_exn_store(addHeapObject(e)); + } +} + +async function load(module, imports) { + if (typeof Response === 'function' && module instanceof Response) { + if (typeof WebAssembly.instantiateStreaming === 'function') { + try { + return await WebAssembly.instantiateStreaming(module, imports); + + } catch (e) { + if (module.headers.get('Content-Type') != 'application/wasm') { + console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); + + } else { + throw e; + } + } + } + + const bytes = await module.arrayBuffer(); + return await WebAssembly.instantiate(bytes, imports); + + } else { + const instance = await WebAssembly.instantiate(module, imports); + + if (instance instanceof WebAssembly.Instance) { + return { instance, module }; + + } else { + return instance; + } + } +} + +function getImports() { + const imports = {}; + imports.wbg = {}; + imports.wbg.__wbindgen_cb_drop = function(arg0) { + const obj = takeObject(arg0).original; + if (obj.cnt-- == 1) { + obj.a = 0; + return true; + } + const ret = false; + _assertBoolean(ret); + return ret; + }; + imports.wbg.__wbindgen_string_new = function(arg0, arg1) { + const ret = getStringFromWasm0(arg0, arg1); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_object_clone_ref = function(arg0) { + const ret = getObject(arg0); + return addHeapObject(ret); + }; + imports.wbg.__wbg_error_f851667af71bcfc6 = function() { return logError(function (arg0, arg1) { + var v0 = getCachedStringFromWasm0(arg0, arg1); + if (arg0 !== 0) { wasm.__wbindgen_free(arg0, arg1); } + console.error(v0); +}, arguments) }; +imports.wbg.__wbg_new_abda76e883ba8a5f = function() { return logError(function () { + const ret = new Error(); + return addHeapObject(ret); +}, arguments) }; +imports.wbg.__wbg_stack_658279fe44541cf6 = function() { return logError(function (arg0, arg1) { + const ret = getObject(arg1).stack; + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; +}, arguments) }; +imports.wbg.__wbg_create_b45dd9c634b63ea1 = function() { return logError(function (arg0) { + create(arg0 >>> 0); +}, arguments) }; +imports.wbg.__wbg_updatememory_767589cd664161f3 = function() { return logError(function (arg0) { + update_memory(takeObject(arg0)); +}, arguments) }; +imports.wbg.__wbg_savetemplate_630037d6d28cd5d8 = function() { return logError(function (arg0, arg1, arg2) { + var v0 = getArrayJsValueFromWasm0(arg0, arg1).slice(); + wasm.__wbindgen_free(arg0, arg1 * 4); + save_template(v0, arg2 >>> 0); +}, arguments) }; +imports.wbg.__wbg_initilize_7a144c290d5ba790 = function() { return logError(function (arg0, arg1) { + initilize(takeObject(arg0), getObject(arg1)); +}, arguments) }; +imports.wbg.__wbg_instanceof_Window_acc97ff9f5d2c7b4 = function() { return logError(function (arg0) { + let result; + try { + result = getObject(arg0) instanceof Window; + } catch { + result = false; + } + const ret = result; + _assertBoolean(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_document_3ead31dbcad65886 = function() { return logError(function (arg0) { + const ret = getObject(arg0).document; + return isLikeNone(ret) ? 0 : addHeapObject(ret); +}, arguments) }; +imports.wbg.__wbg_location_8cc8ccf27e342c0a = function() { return logError(function (arg0) { + const ret = getObject(arg0).location; + return addHeapObject(ret); +}, arguments) }; +imports.wbg.__wbg_history_2a104346a1208269 = function() { return handleError(function (arg0) { + const ret = getObject(arg0).history; + return addHeapObject(ret); +}, arguments) }; +imports.wbg.__wbg_title_74f9ffa27e9c750f = function() { return logError(function (arg0, arg1) { + const ret = getObject(arg1).title; + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; +}, arguments) }; +imports.wbg.__wbg_createElement_976dbb84fe1661b5 = function() { return handleError(function (arg0, arg1, arg2) { + var v0 = getCachedStringFromWasm0(arg1, arg2); + const ret = getObject(arg0).createElement(v0); + return addHeapObject(ret); +}, arguments) }; +imports.wbg.__wbg_createElementNS_1561aca8ee3693c0 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { + var v0 = getCachedStringFromWasm0(arg1, arg2); + var v1 = getCachedStringFromWasm0(arg3, arg4); + const ret = getObject(arg0).createElementNS(v0, v1); + return addHeapObject(ret); +}, arguments) }; +imports.wbg.__wbg_createTextNode_300f845fab76642f = function() { return logError(function (arg0, arg1, arg2) { + var v0 = getCachedStringFromWasm0(arg1, arg2); + const ret = getObject(arg0).createTextNode(v0); + return addHeapObject(ret); +}, arguments) }; +imports.wbg.__wbg_getElementById_3a708b83e4f034d7 = function() { return logError(function (arg0, arg1, arg2) { + var v0 = getCachedStringFromWasm0(arg1, arg2); + const ret = getObject(arg0).getElementById(v0); + return isLikeNone(ret) ? 0 : addHeapObject(ret); +}, arguments) }; +imports.wbg.__wbg_instanceof_HtmlTextAreaElement_a091a90ac155d1ab = function() { return logError(function (arg0) { + let result; + try { + result = getObject(arg0) instanceof HTMLTextAreaElement; + } catch { + result = false; + } + const ret = result; + _assertBoolean(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_value_ccb32485ee1b3928 = function() { return logError(function (arg0, arg1) { + const ret = getObject(arg1).value; + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; +}, arguments) }; +imports.wbg.__wbg_pointerId_18be034781db46f3 = function() { return logError(function (arg0) { + const ret = getObject(arg0).pointerId; + _assertNum(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_width_4fed142776de682f = function() { return logError(function (arg0) { + const ret = getObject(arg0).width; + _assertNum(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_height_fb6382c90e010142 = function() { return logError(function (arg0) { + const ret = getObject(arg0).height; + _assertNum(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_pressure_c3ba152bf8a3491d = function() { return logError(function (arg0) { + const ret = getObject(arg0).pressure; + return ret; +}, arguments) }; +imports.wbg.__wbg_tangentialPressure_f6bd99bd6df8e15c = function() { return logError(function (arg0) { + const ret = getObject(arg0).tangentialPressure; + return ret; +}, arguments) }; +imports.wbg.__wbg_tiltX_fe39b58f39622392 = function() { return logError(function (arg0) { + const ret = getObject(arg0).tiltX; + _assertNum(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_tiltY_6e725ba5a979ae65 = function() { return logError(function (arg0) { + const ret = getObject(arg0).tiltY; + _assertNum(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_twist_29056239dbb46cce = function() { return logError(function (arg0) { + const ret = getObject(arg0).twist; + _assertNum(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_pointerType_bf6b13edfec8614b = function() { return logError(function (arg0, arg1) { + const ret = getObject(arg1).pointerType; + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; +}, arguments) }; +imports.wbg.__wbg_isPrimary_8a1c3caca08ecc33 = function() { return logError(function (arg0) { + const ret = getObject(arg0).isPrimary; + _assertBoolean(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_propertyName_6261bc64663a261b = function() { return logError(function (arg0, arg1) { + const ret = getObject(arg1).propertyName; + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; +}, arguments) }; +imports.wbg.__wbg_elapsedTime_f71097509836b2f2 = function() { return logError(function (arg0) { + const ret = getObject(arg0).elapsedTime; + return ret; +}, arguments) }; +imports.wbg.__wbg_pseudoElement_5c39e80f916267a2 = function() { return logError(function (arg0, arg1) { + const ret = getObject(arg1).pseudoElement; + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; +}, arguments) }; +imports.wbg.__wbg_animationName_52b570bbde323225 = function() { return logError(function (arg0, arg1) { + const ret = getObject(arg1).animationName; + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; +}, arguments) }; +imports.wbg.__wbg_elapsedTime_a5dcd61ea72b5e93 = function() { return logError(function (arg0) { + const ret = getObject(arg0).elapsedTime; + return ret; +}, arguments) }; +imports.wbg.__wbg_pseudoElement_434c37ac9cf98e15 = function() { return logError(function (arg0, arg1) { + const ret = getObject(arg1).pseudoElement; + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; +}, arguments) }; +imports.wbg.__wbg_instanceof_HtmlFormElement_1c489ff7e99e43d3 = function() { return logError(function (arg0) { + let result; + try { + result = getObject(arg0) instanceof HTMLFormElement; + } catch { + result = false; + } + const ret = result; + _assertBoolean(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_elements_e459ba1be1bb71da = function() { return logError(function (arg0) { + const ret = getObject(arg0).elements; + return addHeapObject(ret); +}, arguments) }; +imports.wbg.__wbg_instanceof_HtmlInputElement_970e4026de0fccff = function() { return logError(function (arg0) { + let result; + try { + result = getObject(arg0) instanceof HTMLInputElement; + } catch { + result = false; + } + const ret = result; + _assertBoolean(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_checked_f0b666100ef39e44 = function() { return logError(function (arg0) { + const ret = getObject(arg0).checked; + _assertBoolean(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_type_6ce8af5475dcc48f = function() { return logError(function (arg0, arg1) { + const ret = getObject(arg1).type; + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; +}, arguments) }; +imports.wbg.__wbg_value_b2a620d34c663701 = function() { return logError(function (arg0, arg1) { + const ret = getObject(arg1).value; + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; +}, arguments) }; +imports.wbg.__wbg_data_7b1f01f4e6a64fbe = function() { return logError(function (arg0) { + const ret = getObject(arg0).data; + return addHeapObject(ret); +}, arguments) }; +imports.wbg.__wbg_altKey_8643110400698556 = function() { return logError(function (arg0) { + const ret = getObject(arg0).altKey; + _assertBoolean(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_metaKey_f0f73554e84cdfb6 = function() { return logError(function (arg0) { + const ret = getObject(arg0).metaKey; + _assertBoolean(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_ctrlKey_f05ebfd7290f66a9 = function() { return logError(function (arg0) { + const ret = getObject(arg0).ctrlKey; + _assertBoolean(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_shiftKey_dbede4ad30972b26 = function() { return logError(function (arg0) { + const ret = getObject(arg0).shiftKey; + _assertBoolean(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_type_a86a71d052709b75 = function() { return logError(function (arg0, arg1) { + const ret = getObject(arg1).type; + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; +}, arguments) }; +imports.wbg.__wbg_target_bf704b7db7ad1387 = function() { return logError(function (arg0) { + const ret = getObject(arg0).target; + return isLikeNone(ret) ? 0 : addHeapObject(ret); +}, arguments) }; +imports.wbg.__wbg_preventDefault_3209279b490de583 = function() { return logError(function (arg0) { + getObject(arg0).preventDefault(); +}, arguments) }; +imports.wbg.__wbg_addEventListener_1fc744729ac6dc27 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { + var v0 = getCachedStringFromWasm0(arg1, arg2); + getObject(arg0).addEventListener(v0, getObject(arg3), getObject(arg4)); +}, arguments) }; +imports.wbg.__wbg_removeEventListener_b10f1a66647f3aa0 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { + var v0 = getCachedStringFromWasm0(arg1, arg2); + getObject(arg0).removeEventListener(v0, getObject(arg3), arg4 !== 0); +}, arguments) }; +imports.wbg.__wbg_instanceof_HtmlSelectElement_e8421685c2eaa299 = function() { return logError(function (arg0) { + let result; + try { + result = getObject(arg0) instanceof HTMLSelectElement; + } catch { + result = false; + } + const ret = result; + _assertBoolean(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_value_2527a85fd5ada680 = function() { return logError(function (arg0, arg1) { + const ret = getObject(arg1).value; + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; +}, arguments) }; +imports.wbg.__wbg_altKey_6dbe46bf3ae42d67 = function() { return logError(function (arg0) { + const ret = getObject(arg0).altKey; + _assertBoolean(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_ctrlKey_fd79f035994d9387 = function() { return logError(function (arg0) { + const ret = getObject(arg0).ctrlKey; + _assertBoolean(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_shiftKey_908ae224b8722a41 = function() { return logError(function (arg0) { + const ret = getObject(arg0).shiftKey; + _assertBoolean(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_metaKey_cdd15bf44efb510e = function() { return logError(function (arg0) { + const ret = getObject(arg0).metaKey; + _assertBoolean(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_location_af9a9961ca66ef22 = function() { return logError(function (arg0) { + const ret = getObject(arg0).location; + _assertNum(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_repeat_edc4d7c555e257a3 = function() { return logError(function (arg0) { + const ret = getObject(arg0).repeat; + _assertBoolean(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_key_ad4fc49423a94efa = function() { return logError(function (arg0, arg1) { + const ret = getObject(arg1).key; + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; +}, arguments) }; +imports.wbg.__wbg_code_06787cd3c7a60600 = function() { return logError(function (arg0, arg1) { + const ret = getObject(arg1).code; + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; +}, arguments) }; +imports.wbg.__wbg_href_bbb11e0e61ea410e = function() { return handleError(function (arg0, arg1) { + const ret = getObject(arg1).href; + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; +}, arguments) }; +imports.wbg.__wbg_protocol_70cc72a5ff78dc5f = function() { return handleError(function (arg0, arg1) { + const ret = getObject(arg1).protocol; + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; +}, arguments) }; +imports.wbg.__wbg_host_f82dbbd8bb5ef24a = function() { return handleError(function (arg0, arg1) { + const ret = getObject(arg1).host; + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; +}, arguments) }; +imports.wbg.__wbg_instanceof_Node_b1195878cdeab85c = function() { return logError(function (arg0) { + let result; + try { + result = getObject(arg0) instanceof Node; + } catch { + result = false; + } + const ret = result; + _assertBoolean(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_parentElement_0cffb3ceb0f107bd = function() { return logError(function (arg0) { + const ret = getObject(arg0).parentElement; + return isLikeNone(ret) ? 0 : addHeapObject(ret); +}, arguments) }; +imports.wbg.__wbg_textContent_77bd294928962f93 = function() { return logError(function (arg0, arg1) { + const ret = getObject(arg1).textContent; + var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; +}, arguments) }; +imports.wbg.__wbg_appendChild_e513ef0e5098dfdd = function() { return handleError(function (arg0, arg1) { + const ret = getObject(arg0).appendChild(getObject(arg1)); + return addHeapObject(ret); +}, arguments) }; +imports.wbg.__wbg_pageX_829fdc137b743208 = function() { return logError(function (arg0) { + const ret = getObject(arg0).pageX; + _assertNum(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_pageY_d67aef101099fd37 = function() { return logError(function (arg0) { + const ret = getObject(arg0).pageY; + _assertNum(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_instanceof_Element_33bd126d58f2021b = function() { return logError(function (arg0) { + let result; + try { + result = getObject(arg0) instanceof Element; + } catch { + result = false; + } + const ret = result; + _assertBoolean(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_getAttribute_3a1f0fb396184372 = function() { return logError(function (arg0, arg1, arg2, arg3) { + var v0 = getCachedStringFromWasm0(arg2, arg3); + const ret = getObject(arg1).getAttribute(v0); + var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len1 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len1; + getInt32Memory0()[arg0 / 4 + 0] = ptr1; +}, arguments) }; +imports.wbg.__wbg_setAttribute_d8436c14a59ab1af = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { + var v0 = getCachedStringFromWasm0(arg1, arg2); + var v1 = getCachedStringFromWasm0(arg3, arg4); + getObject(arg0).setAttribute(v0, v1); +}, arguments) }; +imports.wbg.__wbg_setAttributeNS_cfa0d39367e3e143 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6) { + var v0 = getCachedStringFromWasm0(arg1, arg2); + var v1 = getCachedStringFromWasm0(arg3, arg4); + var v2 = getCachedStringFromWasm0(arg5, arg6); + getObject(arg0).setAttributeNS(v0, v1, v2); +}, arguments) }; +imports.wbg.__wbg_toggleAttribute_061cab5591243f29 = function() { return handleError(function (arg0, arg1, arg2) { + var v0 = getCachedStringFromWasm0(arg1, arg2); + const ret = getObject(arg0).toggleAttribute(v0); + _assertBoolean(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_instanceof_HtmlElement_eff00d16af7bd6e7 = function() { return logError(function (arg0) { + let result; + try { + result = getObject(arg0) instanceof HTMLElement; + } catch { + result = false; + } + const ret = result; + _assertBoolean(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_style_e9380748cee29f13 = function() { return logError(function (arg0) { + const ret = getObject(arg0).style; + return addHeapObject(ret); +}, arguments) }; +imports.wbg.__wbg_setProperty_e489dfd8c0a6bffc = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { + var v0 = getCachedStringFromWasm0(arg1, arg2); + var v1 = getCachedStringFromWasm0(arg3, arg4); + getObject(arg0).setProperty(v0, v1); +}, arguments) }; +imports.wbg.__wbg_pushState_38917fb88b4add30 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5) { + var v0 = getCachedStringFromWasm0(arg2, arg3); + var v1 = getCachedStringFromWasm0(arg4, arg5); + getObject(arg0).pushState(getObject(arg1), v0, v1); +}, arguments) }; +imports.wbg.__wbg_replaceState_b6d5ce07beb296ed = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5) { + var v0 = getCachedStringFromWasm0(arg2, arg3); + var v1 = getCachedStringFromWasm0(arg4, arg5); + getObject(arg0).replaceState(getObject(arg1), v0, v1); +}, arguments) }; +imports.wbg.__wbg_length_e015bc7ac630e55b = function() { return logError(function (arg0) { + const ret = getObject(arg0).length; + _assertNum(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_item_37cbb972da31ad43 = function() { return logError(function (arg0, arg1) { + const ret = getObject(arg0).item(arg1 >>> 0); + return isLikeNone(ret) ? 0 : addHeapObject(ret); +}, arguments) }; +imports.wbg.__wbg_instanceof_IdleDeadline_4a82da4e4c7002fc = function() { return logError(function (arg0) { + let result; + try { + result = getObject(arg0) instanceof IdleDeadline; + } catch { + result = false; + } + const ret = result; + _assertBoolean(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_timeRemaining_5c5c26712f275485 = function() { return logError(function (arg0) { + const ret = getObject(arg0).timeRemaining(); + return ret; +}, arguments) }; +imports.wbg.__wbg_setonmessage_c5a806b62a0c5607 = function() { return logError(function (arg0, arg1) { + getObject(arg0).onmessage = getObject(arg1); +}, arguments) }; +imports.wbg.__wbg_new_d29e507f6606de91 = function() { return handleError(function (arg0, arg1) { + var v0 = getCachedStringFromWasm0(arg0, arg1); + const ret = new WebSocket(v0); + return addHeapObject(ret); +}, arguments) }; +imports.wbg.__wbg_deltaX_6b627fd6f4c19e51 = function() { return logError(function (arg0) { + const ret = getObject(arg0).deltaX; + return ret; +}, arguments) }; +imports.wbg.__wbg_deltaY_a5393ec7ac0f7bb4 = function() { return logError(function (arg0) { + const ret = getObject(arg0).deltaY; + return ret; +}, arguments) }; +imports.wbg.__wbg_deltaZ_8d9e7a1ac88162b3 = function() { return logError(function (arg0) { + const ret = getObject(arg0).deltaZ; + return ret; +}, arguments) }; +imports.wbg.__wbg_deltaMode_a90be314f5c676f1 = function() { return logError(function (arg0) { + const ret = getObject(arg0).deltaMode; + _assertNum(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_instanceof_CompositionEvent_c099782c181693d1 = function() { return logError(function (arg0) { + let result; + try { + result = getObject(arg0) instanceof CompositionEvent; + } catch { + result = false; + } + const ret = result; + _assertBoolean(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_data_8bd9c72cda1424e9 = function() { return logError(function (arg0, arg1) { + const ret = getObject(arg1).data; + var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; +}, arguments) }; +imports.wbg.__wbg_screenX_bda1387744fb6677 = function() { return logError(function (arg0) { + const ret = getObject(arg0).screenX; + _assertNum(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_screenY_fa513a5b7d68c34f = function() { return logError(function (arg0) { + const ret = getObject(arg0).screenY; + _assertNum(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_clientX_e39206f946859108 = function() { return logError(function (arg0) { + const ret = getObject(arg0).clientX; + _assertNum(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_clientY_e376bb2d8f470c88 = function() { return logError(function (arg0) { + const ret = getObject(arg0).clientY; + _assertNum(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_offsetX_8891849b36542d53 = function() { return logError(function (arg0) { + const ret = getObject(arg0).offsetX; + _assertNum(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_offsetY_1f52082687af467b = function() { return logError(function (arg0) { + const ret = getObject(arg0).offsetY; + _assertNum(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_ctrlKey_4795fb55a59f026c = function() { return logError(function (arg0) { + const ret = getObject(arg0).ctrlKey; + _assertBoolean(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_shiftKey_81014521a7612e6a = function() { return logError(function (arg0) { + const ret = getObject(arg0).shiftKey; + _assertBoolean(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_altKey_2b8d6d80ead4bad7 = function() { return logError(function (arg0) { + const ret = getObject(arg0).altKey; + _assertBoolean(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_metaKey_49e49046d8402fb7 = function() { return logError(function (arg0) { + const ret = getObject(arg0).metaKey; + _assertBoolean(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_button_2bb5dc0116d6b89b = function() { return logError(function (arg0) { + const ret = getObject(arg0).button; + _assertNum(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_buttons_047716c1296e3d1c = function() { return logError(function (arg0) { + const ret = getObject(arg0).buttons; + _assertNum(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_newnoargs_b5b063fc6c2f0376 = function() { return logError(function (arg0, arg1) { + var v0 = getCachedStringFromWasm0(arg0, arg1); + const ret = new Function(v0); + return addHeapObject(ret); +}, arguments) }; +imports.wbg.__wbg_call_97ae9d8645dc388b = function() { return handleError(function (arg0, arg1) { + const ret = getObject(arg0).call(getObject(arg1)); + return addHeapObject(ret); +}, arguments) }; +imports.wbg.__wbg_instanceof_Object_595a1007518cbea3 = function() { return logError(function (arg0) { + let result; + try { + result = getObject(arg0) instanceof Object; + } catch { + result = false; + } + const ret = result; + _assertBoolean(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_hasOwnProperty_43f5b0861e010743 = function() { return logError(function (arg0, arg1) { + const ret = getObject(arg0).hasOwnProperty(getObject(arg1)); + _assertBoolean(ret); + return ret; +}, arguments) }; +imports.wbg.__wbg_new_0b9bfdd97583284e = function() { return logError(function () { + const ret = new Object(); + return addHeapObject(ret); +}, arguments) }; +imports.wbg.__wbg_resolve_99fe17964f31ffc0 = function() { return logError(function (arg0) { + const ret = Promise.resolve(getObject(arg0)); + return addHeapObject(ret); +}, arguments) }; +imports.wbg.__wbg_then_11f7a54d67b4bfad = function() { return logError(function (arg0, arg1) { + const ret = getObject(arg0).then(getObject(arg1)); + return addHeapObject(ret); +}, arguments) }; +imports.wbg.__wbg_globalThis_7f206bda628d5286 = function() { return handleError(function () { + const ret = globalThis.globalThis; + return addHeapObject(ret); +}, arguments) }; +imports.wbg.__wbg_self_6d479506f72c6a71 = function() { return handleError(function () { + const ret = self.self; + return addHeapObject(ret); +}, arguments) }; +imports.wbg.__wbg_window_f2557cc78490aceb = function() { return handleError(function () { + const ret = window.window; + return addHeapObject(ret); +}, arguments) }; +imports.wbg.__wbg_global_ba75c50d1cf384f4 = function() { return handleError(function () { + const ret = global.global; + return addHeapObject(ret); +}, arguments) }; +imports.wbg.__wbindgen_is_undefined = function(arg0) { + const ret = getObject(arg0) === undefined; + _assertBoolean(ret); + return ret; +}; +imports.wbg.__wbindgen_string_get = function(arg0, arg1) { + const obj = getObject(arg1); + const ret = typeof(obj) === 'string' ? obj : undefined; + var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; +}; +imports.wbg.__wbindgen_is_string = function(arg0) { + const ret = typeof(getObject(arg0)) === 'string'; + _assertBoolean(ret); + return ret; +}; +imports.wbg.__wbg_set_bf3f89b92d5a34bf = function() { return handleError(function (arg0, arg1, arg2) { + const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2)); + _assertBoolean(ret); + return ret; +}, arguments) }; +imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(getObject(arg1)); + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; +}; +imports.wbg.__wbindgen_object_drop_ref = function(arg0) { + takeObject(arg0); +}; +imports.wbg.__wbindgen_throw = function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); +}; +imports.wbg.__wbindgen_memory = function() { + const ret = wasm.memory; + return addHeapObject(ret); +}; +imports.wbg.__wbindgen_closure_wrapper1047 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 113, __wbg_adapter_22); + return addHeapObject(ret); +}, arguments) }; +imports.wbg.__wbindgen_closure_wrapper1049 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 115, __wbg_adapter_25); + return addHeapObject(ret); +}, arguments) }; +imports.wbg.__wbindgen_closure_wrapper1051 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeClosure(arg0, arg1, 111, __wbg_adapter_28); + return addHeapObject(ret); +}, arguments) }; +imports.wbg.__wbindgen_closure_wrapper1528 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 180, __wbg_adapter_31); + return addHeapObject(ret); +}, arguments) }; +imports.wbg.__wbindgen_closure_wrapper2812 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 244, __wbg_adapter_34); + return addHeapObject(ret); +}, arguments) }; +imports['./snippets/dioxus-interpreter-js-459fb15b86d869f7/inline0.js'] = __wbg_star0; + +return imports; +} + +function initMemory(imports, maybe_memory) { + +} + +function finalizeInit(instance, module) { + wasm = instance.exports; + init.__wbindgen_wasm_module = module; + cachedInt32Memory0 = new Int32Array(); + cachedUint32Memory0 = new Uint32Array(); + cachedUint8Memory0 = new Uint8Array(); + + wasm.__wbindgen_start(); + return wasm; +} + +function initSync(module) { + const imports = getImports(); + + initMemory(imports); + + if (!(module instanceof WebAssembly.Module)) { + module = new WebAssembly.Module(module); + } + + const instance = new WebAssembly.Instance(module, imports); + + return finalizeInit(instance, module); +} + +async function init(input) { + + const imports = getImports(); + + if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) { + input = fetch(input); + } + + initMemory(imports); + + const { instance, module } = await load(await input, imports); + + return finalizeInit(instance, module); +} + +export { initSync } +export default init; diff --git a/packages/web/dist/assets/dioxus/dioxus_bg.wasm b/packages/web/dist/assets/dioxus/dioxus_bg.wasm new file mode 100644 index 0000000000000000000000000000000000000000..2e7773b19a03f846b6f67ded169c7dcc1bfe209d GIT binary patch literal 37219616 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

-KAbx2F%Z(sDw}2^j*xmhTJ!w@T(t^-wfZir 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%0Au zb0MlTq*Z6~fL+M83tJNn=Cu+>Rmq_?pEfGylh%cExRjyoy}Uuo*sdh)+Kj|$H~c9j z!z;H_);b^&hpwQaD(40yhjG{)`bvq7QB+KvMSd^(ipDOuKKh$okiVwBhA4)(T8IbjN`<(~m=>gms0DKwf28}_ZNA8jayM{tE z#JY3{;LzUxBJ00Hai1jbNw6r*a40>{U0%37^huRFP_m);0kI-uIOIvqn|e=lupb7) zTp-$n?@?oTPcnZ=OH0d4AVig*Lw||x6gRx8Y^`4<>SRgM7aqkGfPt#vyhH9IW4E-m@sSh9F6py+TFIfC<|h7Y)5 ze0V>jUtj<|=i$*&{$0DHN-ESx=NC!ur>bbte>4g|s`ogOj%x-c9Isf9UvyuTb`g7= zW7*w^me1iyiIOLhE!J#40ar>Yc``{cP9SNLo1~IV?UR__B6O>IrzUG^S24A5Hl!u~ zRAHynNI6Yz&vAUNNIKMh21%Ic3mlL%#Z6IFd@d%lU-FHZs*-1t&8U;J1QX6y$!R3T zIDjM!(Nq$ce73=4YXrIY=2qD^cN|AnhIR4iozBRZAsaP-LSFW1RpM0GLRPtRjiaYW zA2iqJM$ykC9p&d%UB?IdN@JbLG8q8DVJfn&Dn$)%)yu_JZKaN!^98hXI0LfrZM;xN zK8+WVj%TJ-%6AdpMIAX`6Q8e*&-?kylK#Uaw+2|fRk@4hV4%u1u0`+XRfebUmjx!7@c_Z(DlHSsu-q# zLIhK)fGOH6=*3*6)2q0jffbMn!)9)Uwl$;qTG}94pPI2+(1Q{(g=(Dg(^C_E)DQu_=$Cw6p6(ELnIHg;)q`W|`Z^E>=s{k|&i7VO3 z%-RCo@bbS#&=mtT^F{p>L=17xs{`yT;2cRBkme>Bh;PV#&G;Hvo9Qq?j3VF(Z}(f| z`?hr-U;N@Sw5Ir|YsFju2L6kcbN%whXb~4I*4EAT0~~~eZBTTp@^7L31Xv-9VVVxx zB?SAb1p88Rgylp>@e05(RWLxY)D6hOskk0uZg3UwQU%*1@B+gFc;N*tm9WiuC2mf^ zCslxyW^p?FfnOjZHtmI$GlW;j z!QD}fx~H)-UC};XpZ{A0|C@sS)gVwzmHQ#LM7Z{75VZ!b71!&7Y%EHApxITg)jWxV zAU$Y5$=*2j7aNo?HYm+eU?(9k^0+E|oC-lUD^ZvE{}`@p*haYW=)VQ7Jl_UaJ{xew zhQTL=D^|D-uC&3GHn<|5qz$g{t*eA9o7>;hwvAo+)Y%mq2A>qJSm8Ff(gs)B;7S`@;WH#6sS>Wd`Dnh4U1@_W zpN24K(>B7Dr#}j=D4@p276!fC23I~C?1~M8PYPG8a2s4{gDY)tr46op2wa)n+6GtJ z*p*L3cV+%N&ukOB^2A5Ml~1I*^0(wCU{}80#;$w}cIBpR(p|A(PzhJQo7{HY6)XJd zz?EyaO?TzKHrJK@;Qt=ol~wnkpaT^7C{^2^eiU5!MC{7E)F*%|H@3l*j{#S0y)s~%sx4BL{|i)YU%qGDe*#=-Q?>oqJSm8Ff(k2XQ69%;jgFZwU^iCUGX;ZajTzm?K%$v6nuB`sIz?HAI z!IjSjT(M#BN#TkWZi6draHS2dw851Rfh%*DwXrL0x+|XwT$#6Q+k`%;FH1?E8GTG+TcnXTxo+V9|Bhvx51S*cIDGySC(ueT=~gI!Ie*>yE4BG zu6#D&iVcHL3RkRf8(e9FD{XM44X%6$TzRAouC&3GPY15FZX;Y-^>2YIUu%OapAEQT z!{C#`6)W5ZSK8o88(e9FD<1+^*0#Zw|BK+t)#il%vl3ruwL%Sj%=ki`{ul9ucK8_a zgK9ioHTfi$*gw+AqH@p*TWuz-LPC z7l&Rs9v}OZoM17Zi53+8Re>fNh@4?@3JC;6NIMD=C&41oOp7eI+Kgy_`CYDv2(-P$ zFuc1?wT<@?fyPC7>N*Hln+Hu%%pS$+u~;-0MZ**qhcG=pK4B9L8Wumwp$gBVs5m2C z-Zc{abA*qIGm0jSYR1QY+8yG<(Wp%XVIgyi-0O3k9YlxbTN84y@5VIaWB)M1+Ykjh z4sSCI5Bub5jK zM+@}C(E>ej^nso%L}D|6fhI;Lk^dwM^wh61I#7a`0bh*cJoWd{f%*|0DD6&-<2(_# zdS#04JdN%=Z3-U$Yl#9!k0=__WL(dyVdS-tPvo_bPn=uGC(bS86X)xdk8=z8#JPoh z;+$9%bUN`dEVd9KpPI>cMjY~~xgzA#|HIzd2U=Ctd4KPH&bcq#3v30I0A(#+^dg{y z$b~EDJs=kmZlI##OA3mL=b~}|(G&#@6ciQB%*+fG6HARLD=I56OH3^+ODZcWXJQ&> z#%8Q>CadT3{jGh@y;qr;dY+z{KccSex3$(@d+l}h_xpQUzh8&Sk@9H~2?aWoK4+nl zG3M#nCG_l)4l)e&b(d;nE~ACZEc;W_-ck&W3@L`1NHMheKT9#>_LgF3Xh<>SNHIh{ zfvdU8)g_|;uOO2DHKb*%rWhin&R$Xsaj)w&3$7>T`1@m}Q2Z}a3{jW><#_^>D{*vj zQ}+}@+@09{e`1Ou3TMe%nw1VzgZ*Em7$Tnmp>w;Y7~;(TU5cSQD=CJ^l~7GFv_t(? zilOJND;p~*hIm4v^yi7ruXMBQD8&%b;FMO#9Ztml5)u13eGjK!v8qJX`iPQ&>X``J zuN;oWov|eqB5r?4G5U$;&%Yn3q}>^)6+}FLKQasvI6h!$>bhnZ(qj{$ zKTm}Ik}?XBYKVWU*@haGRfv}Ah#)%G6}g3cPD$vb*K+BHznB$=@rH++o95Y)(A^#xl|i6H5W*#_-vE9Gn14+Y)&Bi z;hu<`KwXnFlMAsveQAt<(Q-7GSfkvVJcktAkAflpkMBdW!#?SkC$MI+Z}Cl8ama>7 zK11^R^d(!eYJ+&LUx~Da@6xIMuD_D%3~&c2osT61o9lnBXZ<-p)+>`%Ejf(*Ku!!S z`GeKwA?zPgf{GmM2BqsPaaXH%>iF^AbIk{MQnnxct_`LJ^EQUK!EVSjKh!ef49)U* zkk5xI!z|Yoqti-KA2!FsT2d%W-ZhorDZ`Z;G5w~EQC|@iO{5XZSIOSm*pH+jxstco z#}`l>#qlV>!^VB6SPKadj|jParjy$z4!M2G{wV9qQB%o_!Cf6GeR8Dq8R?E*2KyMB zu@B`Q8c&@GZoIPkkZ!QW9n;pC!lw+aI5s48yz{yjBl7r+bVs=3((eu-ljEpSkt2`K zwO-_)NFE<$@R&k^zbR!%MGj7Jyqh}Rx6%GIcBhf+n4y{ukC^V-2KW<0(wxe_?!;bs zrO2jXd%_*caWsRJk4pSkbX|$W%=DMMDCL4O65;%8WjeK5+f?f$YMoRf;a^kNe19jW ze`mFtLax7&A`$Mi@O@JTK@ z8YCDRrO9@-HhJ>@jSsM|+{fn{aPP2JJQ0Ma>UeVcka_AnDuO>%*o zPd2~{(notM?a?L;{)OZkJd+%MPYeayPY2t-&_A0ee4`dJ$``wZfbzv;^eMTHHdYo3 zaTmEu${_BgQ+ry|&&L(iC0!0ZnFH`Hc9*FwKD1BhK4oK7<@B?b;?FH%h_a$fnYq;j zK2NLca(6i?lb5GYD=+dD8i6a_mFYJQ2Y}y~UN?~xJy+>_b~V#sg}d6V=lKPSJ1PhoHrk_#C#;e0dj#?5R|NZ zk2a_wAXABuOTy9nVnDR`l3oZP2$F83)VPRjZ!ri;x%(;ifFKh9CE1M|K~nbbjVS_r z0I3r4m4ulODYdccfSCCTV$vhfj(j(d#OYV`@dY7QC0!wsq&A>o37AxapkuyEVjEom z%C_(gvi!qfsgl>&*O=13Q0ZUrIbN0IT4|hgHkDL-y30GjGQF7}oMu!&%0?k&NVq3@ z3RVWbYAH!cSR8p076Y7Lh^K*=_C@?+CCw7BCZq%kOCTk1sMIoQ9EzJ^Ccj6U5RhHK zGY@=Ll39&ZdYdm>dK;45Y>$CYpmRFn1Ml%`S;D7_2qFdaddZ5 z+!g!p>F|5h`+n@cAN!Z%;@yegrIWij`GeT~Aof3u-OI82A+6T=AE|5by&uKy6}tB0 z*!@^P=Wv5fy1`FW`6t|fL~)Gr#>D@WGfgCg^SgEFr(Cid>}H3SxHQNAEOz{TH`ezl zYKR)%NR#i!E9*o|a>Z?l&pV`*4(LaLOoat85?U`PP(JsJLL$SK=wnIoN8%;1BZF1? z&rM`wj^QJ9uDj@LrobKUn?QsTxCy-VZH4soi19>gD`S%R{Rat*FA-Fb-aHWALk zW;`hgcSrD+f*V~zlk#qO%NPfhNk4TksCyLWm6Ta!j%NSpQeZ9VhJ#dlEGY@ujE21M zDV#uo2_*TSDtH{n$CZ4mYxPBUtShF!@1ZfRQZG@*}6s`^;voVIr6e`-0<4b;;nm&P3CzN0_(;cTybSLWjHp5MGGxTvQ z49`)9UrB-AiDlzM9m2s(n~Qb6z4&^BPNd-8Q-Z_Hl7?iqZgdi-#9Z9TI(3RWMXk;O zkLP5qQifk$fgfFSZm}9Um4j1DaGBH4hMbku)yG_SR@tAOJ@na3t0vSO1^3<(X$WcjT=p#u;W=(z`f;bR8tLb2 z)8x-n8xeVnBX@q7!wcA5pgGJ12Z1RlyWoZHLUPj2Pe1c6RdX;GHbc=#6r_{j`(AJt zL2nIJFXkZRB~((v^wB}xC`{JNOX+1lI84D^R`NCkW%kR;M!1-)!=aZ0{ZiehpI@fV z6v#^)xywT@m$SP(^wPm)(#P}u3dZ6}cZIvM?BAEPy7zI12LQr1f)guB zrb0lQaIMB?C8sP4pj#D7#c-V{i(VPr3H;V2|4R)3%{1$ayedLu5Q3-FoscK<+fZczt# z6sSxAKGPDt$l zxQw0(no>Y5P?COtKVA5+2#az=^6`i8{6u%u8?p4m1#|>rIU31(RX6AcAU*J!>_uAc zVcw*24@(r^sB#;rc7F`137kfR=e{`ooW-+yPzAcC^HZ&l>twfVeXt#}2S3waKa_1^ zIQFxDq{0n2^$FDxd-lN^}HSnTH!c_ZYld{88raEh0k(3hz+$~S2%g-3<`j8QAB~QsTyk%7)coPgGZ?~KZQEP?Xy5U`zZi0zbN zJ4q{i2kBIA|5@GjS?={sBRtPpGQ?ju>-+M&E`oN@79ZiUR{m|89_A zlXf6L1;|fHwCBY*qdcJ(l#= zbWyst$@gIM+(Z~ft?OZGDNB|7s$w#7yhmG7C%+b>A!#UiOP_2hph=g=3?Vd{>uHLg zp1NTa8tcW!#5hQaOvd1RwMRVFhILeQJglMCoz@bq3=b^+!1IYe6yMsgJ@RMp zKxc+Iah8aKJ1fiybbM!1^K46CjGp71%JaKw;$rCm=Tl~W2_?roc*c1cX~Od>2;iv5 z>G`>LGY@}0yXRX@V_3rl=_`ZiL72-*UX1n&A(bS?CQI8fA7MM@Q#&#wH?9?n;0wx_ zJ%KU=lLPZ4`$1gH0H79eCCha@^*OT7$XsfDnN zOWv|%!yo7zzf2t?8j~3`?un<0s+c|of^sk8_t7wZ9}VMoPq*>APvZw=vGLm|ev+wW zGzp50qAyXWRz?^AaZw&(DVDj;jE_3iG7K`1!D)z0R3S1{5c}IKCPT&PkQllLZFxlM zm@>S@UhgN>y@!qNbotyvX9h}PWBq$vd5NJlsBN-IDl}WAY|jYNtF=|Xvg7q^QB5bJ zKJ2VdU_5jUk`qC4N}@O6K^E%rLjRfUE>9?%A3Uc^LSk5uY#>j>>C=1;B#=US*jm;< zn&@VqSDO!)ps?pp_K=BE5nU)xwC-53qyw zLDmt@Jjt0irTh_8^D|JxU)DInl3+~LJjPS~Hl5ubyKPeRl;KA8#(4Db} z47z&)CcRuwF$U;DZp1*tiU=LifS$U_tjGJnn;A z{V?qxibmqlvOf&Ua9Epf;=aRCL<}$c!|k4jSD||ZN(dghJ3rvW5rSo72PHHQqosDR zQ8DNq!>z}Z{81`1mi@6MG!I8%?l{^FekXL($qc&3QFdGjg+w#{8{^%`Ds)d^95bT| zeBl^3u?GO3z&6vDb<`WbW?wqOqT#n~qP?=Zu^EDtztD6sBiy;~3WR{ev zegVf9xCQQlva}Cfp!*_zmJ7q6T*)j2Bp_=>J_^O45!L3-Vy?Ei|R08(UEI}^d9T^jKVK)gb=-m zrOiO!aT}lL4Y5s-4dD|F$>@TAx9byqNA`(^V8A=z8z0C%(Ty_SL=cCPbQ~Xyg$k%I z0(~%ihB`!DbRE|Oct+h3RR{y+ugPpUu#V|yIOyC*&4%+)I(J{_+dVfY8pbObN{FBi60UJfo8JCIfd?GwXhRz?)?((qE86r^&nQe~cY#TW2Uz3)zs;FVK#XY(}+^k1h;e z+``le#Y3uMRm-@ET?UQw4Sl zEs+Gx8g^^gl}V=Cxr?$2tgX9@-O+G#W(+PI}C&G zN^c7F4mydRu2=d3{u^TYcz84=ebG5i1X>_?JLrSz4qbhIjVaTvwuaTFED(v%U6qh$se=fObzqusJk-pMj&(w$=giRfsBkx*_7U$grW5w2eKDlEE_VL4?ELH5 zc_yosga2DO_`j9I4{&I@cA`IqLp)K}Cq+Ik6FKPAgQPc1L<5p{$H`le(Q1X+w3eVa z#q`FBC_*Nu8;-4bpWdCg7 zn^E>N_49uF|J_VJrBl#|9EUmt!qbj2WTliLZuU}t5|vFKQkGREaDpBG%FLWoJj9<` z-1Y{$r@1+Bozv_?Amj7tbm{bxc#XV2(&r~aR;GA-FwlOS<<6dtmq!dOd9ORCjNaqi z(CALJG_Uw|)E~vWf5X1iAM@RL)mG1^)h@~oTD{Os!K>pNhhiJaOO3HH(cKqvu&{(r z$HgcwO6flw4JV24sqBzX2M!&VxJ%poW%K~=4Y#Zkp_+(2AS@>jI02NQET#bIfN$J{Uq~l85HVOUuSn! z!hID>3i<~uDfU@`DbUbHm9JVw&@{-~Lk$7Zd2qTUeFA^F0H{A?4Pie3#4#Y`>@zdl z?^a>d`#Cw8OD(cfU4GKfj79t`>nhp? z8UpIOx`)V?0;JOr`h$EYq(cx&pgP(CeGq?cssMTISyvbPU#PI$Mz+R9MENIlnE6zG zHpYaaJE1m^XGqBd@*D!{pOZEqy>Gaxd{I|3#lV+L8NeIDCGxj}i%`|F3c(nXez`B^kmB9{ zM7O^k7s(38i@Q?~@uG|*_=sQ=!5)%6;xUQvDBsmBzRRNk3AmblPbQG`UchNoGy_uz zKarQ^^nneej12@Z-K8H0ZdQp*+IdXcC0U_=h~@&HlHLvfn#LbUWFFCc!79)r(n@&_d!e1$Fvl5^#HZ>Lm4q-wlGD7>(}nzMN38$%IGQvsHM@cg@ITr zq?*7bqTQRdLMDo`_cY|y&;%3`=H2PBSGb|==pm$rz+@pc#9?aZTtPAU62^lMINs}@%X zVfd-w_G#|)c0ZS5XJC0aqYQ4J8QQ7?zRv=_qo7r2$IT%IyU%gwmi;{1nitw?@bf8x z+XAME^W6FEZ&xX3yjC@W*B1fPk^C;*#qGXCTjqLM_KRq1QE02TR0hIp%nK+3+VNS4 z0qe!CTn4C@gnp$mG62d^5dPc!atdAHmb)v;{z}@qGPKnPw@P^?yw6`PLBHLvpx8BT zg}bH<>Rub#>MQYH7J*3M%J#arU7%`i2iFTx%K$5lt~T@Q@Bes^5HxL40{Z+4GC60aj6q5_C#fcNnjy?Gg-n0su_?lDB7v7z20vBY5D z?~_z{R8=0!q)^@JBURE&>eY7u3A`S_IkF@a*uDS!gI3P15#zsiF-Vs-Q!4s zR22LbWvMqPY8N=gLZ+Rz|;4*$A5}Qd_j&9B1J+Y{1Fkj z*!dAvUeP^%EZ-UArn0?FQsoU*`AzKJh~00f^4r+` zHhYeV{@-HvZ?S(<)&4zpZ2z4K|Dg)Mi`{?3?srspOZRvyyT@_<_bU8{*!@2Ce~cYF zf27K)geuo14tejor0lOx-1SL#AKT#JiF<$I5drbNP8{1cNxQ#6HEvAY4T-yv8fz17 zvNj1n^siwqROy=%w=VHFCyt$)se`x}`&$x+_2?Gr+?u#s?JkkOErBZBmb652!=?~& zX3lNUaf8c^iW>aws(MG_Zcp4DRQ*8WK9KZo*xEaJGG2Lj@C3yf;+>wLyXD6Lb9yz# z#%xX-C44|@;T{E7CxHVFQ14<_UxIh^(Bi+ZswB>H!P%lwS9(G}zG@5>?@kK$vc)MX zyiJ8u3a2Ib$bP!AKW36`R@kvzC3byEs4x1e@OBkGfWp~|6|Ts;bGsGphb|+|7OsRf zh!n;($@MRZt>_9|1r90BpTb#UcW?>Xa)=6JY#QIy$&)MIc{pbf5C&6taLEsGLsXdG z_jssG!3{0>VbB<^9O@3`=PqU)s{}v(0>Ay{+;~RkFcI@6PGn0}4#Ufn21amZ1d0zR z2WRlT5J%@*aX4QK-L(lCLd7E~cBC0v+!45VQgIAtWMYw71?NsTRu!L#vBErK6&?N< zR2)aKU{R6Z=Xff1dU)y#PM)gBCulshS$FKmk`*dWqT-~I)~rY=aU2y}ITJJxIs-pZ z#b;xr(r2w=>ZhpU@f5?2Q?3_NN|2TD=pPEUV#S@HGq`!GB44>SK6PPaPgKPj6w?9~ zY%C|1u(*tew^Y44IfIo&75PH8^MTYTGDMDw&_fh3F_$v&5<3VIhx< zMQ)L5@{PZk54~!p{u0%^lwy~b;6j(VOWy9kiAAI&BBYuC#A5J4HS7E`)x4aV!7SpI zmC#gh&nr1~Wl3vIbc(ems`;JRU4d9aO^x+pVTwGznsb}k8P2~Fdm_L^o^zvNQjx6= z)h(`}BuaazlukjT)E+>z0vuB5CZknwD!F>9!u4vq>S19QR?ELa^}a9Wg&3uZ&L|fA zssznaMQBz5mX$46u?y?OvsCeAqhK#v#ePz4M5s07=BbKstwN{G!!Fz^-G(aeGK0u2 ztJvMGlZx=Ky}EU(A{e?s2&%5uNwHC}edvyNsp4I9zzCTcMedePLKT7A4+?atNFlzp zuj!-uVI;s#y@tLjAalXad_=r0Q;If8&yjTMIaCn94t}8b8Gkb+D5D3POCWP4zcF?j z?K=gy7wpXaqIISn)$Dvg1s|Z`gJO1S5$rG68Q2|vL2;6b?j^9j5^N8pg$lw83U=mU z@xGuKVKkUE3G^?siKyUi71Wte82^*TByx$a{7hF&-=UdidJ9ns4eMyJMUh11pBe$HBi%zRdqMz>DIHbpi~XHu}8FNkgGS$=807{9cFbz+}YzlwtC zQ6Qa9T0!{Gm&A(HBDnlzJw$c+sk|wFl*ZpOZ^}e^&K$#=>yVBBZ9-l;gQulqU@q}} zH;2_i(z>_*yFz8bAjXs$x>F-~XX&XauH+IC2NOl!)!Pc?zQ8=5lqC?J)u%E&qHbyA zlo}%K-Owv3)D*N}V?PS-Czv-|1s`z==Y{lAyZ7Z+gBl|J z*1`S&t=U`k^KGahin(_zO^w`v_)|WH(L_r%Om{!m4-olz?PpXO2-`WBws50VTZa^P z<4MUK2bDY~3>I%}!%EYXEbv~Nd46#5byd?6RPTnQ9~tOnM8RIMu(770xLUm!28rm; zQjQM|1n#kJ$k0|qCw3SIAUMl8{O}@vbDf3_tQvQ!=BQ!~R*m6OF5p}F_l>G`B((yr zqgHr#`foI8UUy@paU}SKE!;r4FeZJ{dO8PH10`nram9PpX0wyWWpjGhK&%&eH(otu zcrhAbz{nPIxC64soC(@f`2+y|QB|JgF#k^+9fx$4jSc6Js|7P?e`@lgIX8b_l6J+Z1QR8lDy!K0$RCLk8F^!yZ9* zW>7A}|A?g^C%S3wg!F|vl{g+#1e;d>p5l}0`79^1#4No8*>PrLP|`bpUe``yee4u> zlCEV@Fc#slqi&s|O&&rp8a|NEmU=)E=A@ss_dix<3i;s9EY#7u0x#3sl&JBxSTuGr7V#Ije?vvt`8P=mNpXRT~9_5Pc zQRs)*0-HQ+VTq2e(k72lqaPNHyg67FUyFkALfC%|J`#9sO3#u$Sg&UsDD-D5KZ!7X zu%MtNS!+C?OS}lL(S-$6*5p%QO;GQrnY33hdEtiNmwO2<+i*v;J-{lLsBYQ!D@ybBF5R z!B_qRu^yRLv{co69X?=y&JCaov`T}tbz7YNj4!Je|3C)VHo*lncc}~&PzZ=@sR#NQ zR5R%lgJsBgO%Hsp0sg&l`lZ2A&#_7gTdT@>|1Pw3Yzt5DVfhd$EA@+iV;dIJQg@Bx|uqithg_Dx$R@7-8z9EXpJ8 z+G3C`=IxkAM76~r+sq&GDVEtZ>b`ZEeWMYPk~&Y*0t2WpDDw0WZTz|pzfR?ENQo4* zN9lvZinI%`%id9vAjT@!Z%TcXZX2huEIz6+MqJuzexJ|9LW$>9{&`wn$XLszly$H$ zhi~chw`d0W;YBy1nQGB(LMs+Ba+l7PXoqfi~0xi^MEn&u@}& z6s||vH`wp70Zp~s849kkC92n~*TM_ddv>4?=mkqipa}R)zH^y{RqrFUNDgw)%k^D` z1|ZHf0D5(30H*egHM6#Ec}meMdeLsbuyj{TJ`3($Em5OxnLrZ~*S|yR0CWKf1Oa+5 z9dZ!E4GwXG%HHXA%o-PN=z)B{%y)|a`hpCl_%Iq8)*-PU_)o&3i|N3>&>!1HSr&!R zsU7OnNZLtoYE;Aro?{f+f>Fz$1hFJ|939l`Xg3rlFj@u%H>TYmMOzAeHMR`!vvf0i zhFxlI9Q6}7E(DNZWl*DUrZg)y-T1Pf5FWuEV}cD^3q8aHftvyDle2K2jAeOn$CknR z@;aK)CWr-m(u#GizSSF%PVfP9yb6rFyR+0dC)LN^BSqrQ=#Y^D>qG)V@6EZuntfBv&ZC`#^_W?+^WB+l{xZNBA-#nBe!iMrfZbz( z*dNxXH|AtmOXeMFNrCN2on4aC1Nie3DnnI2%4Zf?_ zL6=un=9T5KX(ZtD62N(>TQ&<&j)QYSSpgy{ugnDZmZ(Q~W$&Z;_q9a5^vd!8bK+uH6szf4x3UZhce&{*Q*P#fWOp?m=4Bp{yvw2o4aRCMuwE0>>>7ee zfWSAH{GC*>cRJWh^WZEp_)VxyLE4+qv+9?5w+06!yqfCkvt{);4=&QqE!+-x6h@YC zEdVEVyA29n0eqscOGtU6N7ThCTuea;^8`Ef_&Y0)ze{&{(b`O8=&$^Ud&}M*O9S>D8CJ?@z7oBKmo$cn zG+POptq!TG3TUcAkhYg~@N(z7*`-puDD?wf_(4|chdTJ7Ni;V-?BW#_ zWBVhO0EvIB;y>mwQOEowmKNYPi{xM-rgFvI$zHHpu>rv9v24%^Jdq#*b<^V4r8iGP zfnSh#zaxRy_F&({`v&}ve~SWLOcmr7aqY=91biua8^lE*sL`9gAareASqE-Z^k@9Pyb_ylN>SZu)> zOpOn8AFQ&66c+~QE_oB{XL!%68tl9qT=qlkH4U-1G?d{R#$AU6UA@B#?^YY~p#V6~ z=6sWW0vn!rkx&dpkA{Bt3+&>Xh(dlu@H^|*H5LQwEYla9s`dlO*$fP-`faV=y6fhg!q`rt$uK*5b2muQ74d%VCa z88YJo=I4kOpx~MjREw`f;D5o5FCqRp&K$4=rn0SQRSh!TPV$Dh_V1N zhLO(TD9HJ+fP!n+LI$3vQ`rw2C`hK~Q@~{EjN^a+UvQ^LnN>s%o5R7J5|aF>{xtRO zbdD=7hQ)0$9H8LNM2T-s3a7E=^6d2WgH*8+mBF1OSHtvYM?eGSrE3oL^Qkf41S#GI zQq6hpys7^D?7f}e`QEssw~K&6j3e6y$+a^~gWMT3?H6V*(4sM9FL0rL;BrI6ldXg2 zUPKd%N_aM0g7|)E`p7sqK_pK^c{V@+SU1_`molG?1(f~e)v0tj(k-hj7efjPZn-Jp zcFEPD;I1ekplJS1g8>=%LsU z`(vbRDcJ9~C{pY^$k=YuV0NVmdeaaEUNgjGW4u_BI4XZu5zv9nrZ%u)#(i*tNWumU z13Uo&fkghv$}l6Xcj@M%>X;S}tg;|HJa&~qtWG2gLLh>w9PVJ=7RA+xWbU9AheUkZ z+%2|9%1(bZ!lVCODK`sf#Alfjcno&BT9hFU_7q$76k&KE4hYPvZPlUl-KdJqIX0E+ zMJ3cikpYE2RMp+Eim$}!V}sR3UkTT2Q`wE{L?a5BXkFxKpb`a`L`l;0Q|2o8xYcSD ztDsh$r04F_l2U{*b&mdjSCf$6 zC`{ueDnKmw`(iAA-!NJet>i@ca_oL*gax=q?}qz(Cy&G{d&r<)TjYMW#GnI!uSc}6 zmqB0=8wkN-Hms#s#wIU?Clml2L4-HCy(Qw;3K8#%n!jFZe!g?DSb%HnfCbcx1*}I$ z!yaw%`?JRQU0EGOde>7X{(6Np<~M|V?{ew(0}=A;U8)5NsELP$HIf+p_n#I*DpbC2 z%>6#uM>kd{`-W!w^0{chHgL~a!J)Olf`(C%{1ofmfh`dlG28Nc`_yq*3}Una2f2P_ z|1O(}?=sfhpECVhq7iDtdON^1pbf}kE==pD4WOu_C^s7wjqsiZ2X~POJQk#2+=S(ZP6iHYG6kFb$DLL-M{Q6rT@m#Zw| zbOnWv!iRvtroAz)#*JC(kD|S*4&vykJqvum#!+e++p_=K4 zA5r%0o!xWoNJcKN7+2^!G*f4V7lKEFPO@|qQ$;Q~Vot|C5yNU&KT$@(aas_a_uQ~O zg5ij6ogPGzM2-xsr%-ce6j{-GCMuLO%Q!8tf41mO8K!fVd+)Svj+t&=&>Os|2Isqz z@DG}A4e}DQH*+4hIj_T?Pd660^MmfgejN?gK_U4nqXjktU%N38CgY2!H)i-pZ1eU4b`Ihze@Jm+YvXG4S7 z%<)UiSWzF070X16(tjAGOMP)?V3R_F>P3V2Mbx{?ZJDec&kcuXhZmBwcp_JD&PYcx_t7PMT=KHdu}M4+!>-y9lNxNFnT_th=y;6`j3MUU#CM`Hc; zZlzd1r{_VIY!Y2pQ9j!!Uazu>XN5FsC*V%}@zaAxiAIH?g-X>xrk09BnU3WKSvT+y zi{Vea!|;PyAo^4feKHg8I#aw9M4C{*+>EFTVKTl1F}j7yw_1n7KtP?sv|IYu~UFcUqtjXkOyv=9{>hxi|37)4+QkV>P3$CX+ zhA}~^!h%i_p8HVXvnC^kGQpkTKV|Wz`!jxdx9;1(d6Z`CJP704ZKOcy2oO^ zt?Xjo_{dm3it0(_KLIeDsvawc`5}BNmdw7VcS7{VEG#q*B%*F8$sRKZJYQ`T?L6EBr0P{GpsV3|07HWx$k3&@It4mmd{P^TV@wK0Nz|+rwa% z*TbNg7XXX|B)Y6OJlu`kuZ}OZE@7D$HjJjp(V|P(>17}1DfTHY5eB$ zx$ZQX;|w@A8`>0+oZk1Xn9re$d3ToRqXu`?<8=7FBxhi7&!OLy1qGCumxYPg5c~NY z&R003^Q5IuKijYSssaq&Ezy^9#U1jlV;!J**I_fim~OPCUphqUf)W>kj~&{13SPo7 zE^~gB(l{zybtW z&*%4+0ARONV)@YoGIt*6$RI;_fvEk_3Bn`ZDmMl;6wg^nH*|(ZK@fJ{G}V1r2Z-pI zM+#R!97Tb%DUeysKNur{7w^$AhUFrg%`4m6I6wsjpAQ~@i43PuiI1^0F%!b zfFq{GM>IFd^AuGaIXfI>1Wa zU}Y;b(?0#1>5aw71z*^C(u8y6Pmq905v}vzlF%rkx-8@^5vtufkMxj7geW0cNT5e# zuLOdY0RXA}p}}gd5&1$}3ni3CcqjUXT780@SCit71c8IY&(RP<$LA${}NkyL7S@~pF3ClsrBuuTMdvX!F`)-0O{I3d$j`SwLPP;-JwW7 zX#C64_-lb*OY{u=9>u?i=De>u5Gqd?BnWUaaP*+*qsaayWdB2*NUPeqW0^;9Sf4Cz5wkN(o`dLimlCk#JhN#L=4r%H8TC@Os z@%B%jK%FeMaiBI$Ail*)R>E3gs0U7kN_eq+h^el@1O#Le{?N_9jg54L6vVmNU8IC`k|B<+U6kw z91ih^a(F254_QoDC>{wL804DTJb58FJPzr2kUP9h$j>2)@{t^mY%>wLpqtOe(>`19 zXO8w$yJdiIM}?odL}8TijI3lq0xl!1XohU~!*-%8;{=Ig-i=SmF^@oqIObWPx?aCI-O0Ha}Q)sap<(CKTj=hj>BgWwQsYQW}YhQ$7Oh^z_@4kYQ~U zX26MNa&aa;_&C`SBp|(ubqgXxsOSalv$#4-3(wiUN6`yp zNrT!RM|@;j9QV>KHj(`(~ z=pTEhnF@;l(VcEdou2#R7U?Gk1y=x50MXD!mN16dPNV_1Xqvx-H+)IpNH*Q)z&3CO zF!v?OZz5`9@kzbs#eBodTq-LP^AgAJ(-D^-ebcLdjc^b)c=A&MuQ_#-cmyk~$t&gB z-;}Hfegf$;{e1U;%-MTUNy$8^Otb7C^)2wgsm1?YJR$8LE zB>M8NO^jj$R~dC-XLSs7p^^Jl{C>vr6^$hxeLKy5&?QPUqOfFLy-$SPE|Hq;0+eL4 zjV1I1f%uWiSW+7FmD;>Hw^KVG`}g4q(@&H`M{1b;Uf0O-_XLcMV`RB;CnU&;t(y zje!HXLlk0mdqNpdlS^CP5EjJcY-v)XmL^fS9};;FgK7$!yL?HqxXx%n9EHDyC%sl9 zQ8>kt-oxDDC~QJ`2}tgzGh}j7;cv$%qFI1sucAMNwJ?sts1z)9lRH>vv>=Yc%!L8i zD=5rWq7uE8r#pij@#ae2NlDTVc#Z-*PwNWL;VW0%lYr4U(Ax?*@cB^4%Y%7+P?7A> z{)v6g^oP-H6Wv1+r^C{Fko+Yfny^j^Cz*9dQe~vAki*b^gj3d)2gk@h+|7laB$BA^ zAcIZ(i*&w4;(5i39(<;u&U}$T^3iTQzV*M_pNBAqdGmfysKF542NI0sTuuj4t|&N< zS~O>Qnan4F#>b?eYVEHQt(1_m*%gu7sb+$^5Sh0R*?lo~$7>Omjl22IX>lAEI^}ic zMF5NStviv$aVJVfq~RGfT(Orc(~(8wk+e?3yAs)uW@(LI^veKtmAN$0aA|C&IfslRH;U=x3)CP~yh-QlNnua}M+1981ci zhUXPG=Rw|lCYS39qPd%=dm)}vc0pJKM_ITth0V2H>g0=bNpXg?vP^UMVoG0Jl9601 zfnKuG9W>oxi{W$$YdO|2m97Is2@Pn=;8YqSPTQSgyDkYtS#_3+oUKmESURwgr+TME zk7n&RwuGb+6lDh0fud1*!z3_N9`AXnig)I9Wv5)<*}fzfySzNx(`N_k1WxeU5ZQ+> zfHEXrqb~qae6;MseC{+Jd-8sjB!#f}&0QyHpU)aldcA@VgQO3T!A;(F6k5$U31NOs zEKM*qZ`4TvBOtmq7J`DX*w268n0{;wFq+=n1dOIPHA#-g8J{VQATC4y^JqlO-!2cL zcCntum;abuMErvQ@rK|y5u|nmo0#^3*hSghKmmffPu;|Yag_l(jITJ zSp{TP-Q_`7U3gehzn=PsrfIh2d(2>fmV0id0JeGVnJED(p?!1#zzm^%eiQ@5TF1xC zsFNm{nhM$TxYf)g`^@*tDOHCi{wKz;6F6VfEc9vfrt{H z(?M5}p2Mv?@2Ub9K}DOz8bGg1=o2gz)WM_A&of&@JzS(m^CsaL!OkusK(W{L?mO9 zdA!EeX+J<(J-zF}MDS!I=dLG(GMv_``g*eP{0)suSKeD2Y0hh;^B&0tgLf03 zz5UqLA~z^OA9igG`~KLj5zweXmH=(~v9c`%tZUps0j!142c*Br>0%0Qv+0X8UE})8 zB3k3#CHp8RM+4l-xoAdfAe+!TZQerfaN6@+$`T+CIOiP8*#`KbCOZ#>{9El|+Klyw z(&R7;8djs_Y^(~3kfCebVKzFr#4!|M(Yl5rELyLrEL!IRMrP$?)@Q!cmtCJ?Uh{Yq z=rvhZB6+zhF;Q4fp90Lp<_k;LDg3kwA5CEvr4vU-R|o?vwrL-EtQwhHn_B+53b#`jhfXih+E0eKiK}tjOv$_3j(P*Z@ zN$g{|B`c9X1(k&*6G(qG(4Q*Et-;_MNwF@E($m>x^_kO!7)$S|Zf@D1VQ=sZTArPL z@Bn|NE=DNSYus7c+d7+pI@_`d=xv=-+^y6@H6iuTJY7LLRtjakuJb4x9Njgj&%`Rc zfWiT(bx9`Q5KkZtc3%T(mEK{cFQ#-rYH8Uq{_RKZM;}_TOhr`zojSGu?8W;%#uQ zKo+P2u4^jG*=MIeAF8$*!D=>Gbv0_WsN*BIB8>Gl?4pv_Sfi@HDt)sDm|f$pl>QRTVwrFNmE!F{~?ScL@ZYthd=axq%N0C|weE-)K0q){-=V-8DeBgkOoa zDuDpZ-`Wg-KyWGIGa*<5`J7d}RTZmBcq;*f^Jj4p_?iILLhQ_XtBUA8p{@XP4T|@Y z0GvOS_@K$F3ZS5I1I2~one&?}vOtK6Aa4y?_socECGIicwfG6)4I+osSX6{9?C2E& z&Grrfw3<;2B~YxkK-d~!*0L3UEJm_r^!bO<6a=kjr?+N88n|3z@-ClgCw0BBIa`pf z=?yGHfq>;2#Mx~9xs@mKYB*;cm!c(1^)Il46BNLog@v_um zd{sbbU_3-L0;}zdmHs{IaPV`xTB~a3B@l1#6=Sj<5_t*#2BsUx7t4h8TgPgVlRn|r+LRsy#SRYJV~n?tTD?S z8Q1wCTrkV@5mxWb zLLp&t%GWZN4d{BdMgp95qsr*1CG`@RZW^I*YcWM1ZW(!yuE#9PytPI=R^v+Fq^GHWE`o zuQq|@5fNWoJF#mMkKph6wNa&k{!d2TezFU3Ndz1cPG2k%YH65m|Go< zGZ>9C!)SEN$D>urx-tJFk|#uC)SO0GjM%}IP3OT}tI#mfv8=@`!7?uupD zq-B!6++9`nSJTInrZx zItK0DFEA?$?vPw>#E=Z$nW)MqZ5UR#|DRdU^c4YbCJ~*^196NS6c06z7`ISCl%8e>G%8q_6ehU9N(6IS>z8&I!e& z9jSo>`;cryxI6~$Lz#Jb8gNJD58Krb(_b8x%wGwJx+h+Sm6=0;3Q(-Xp9jyXqlwef z5b0~}s{@1~aBh@qrBrJ#OO2ZnE0EN}-7V9X7)+x*S3r4<#koucm$y1q58C!7f?Y3Zq)Hh_nuK`;P zEG=Y4cLQ(_T~Q7i@G%OEfhP}v##)P`gEiQKt@&QXZEstQ)eD@vXA*b7&g=F^kxYfE zotLaM>B}tq5>+-UZyny#4*hXglddBYY`R)O=)11Nq8J%z-617la*%gGrAr^~!F{}j z0{{CgaGLWaKa{$|I5R9P!r}}TTd6a?5xb!#vzdJ-t$5!nh%$Xz?3*&lswvYh)ADfI z8J^|K1#NK_ zOie9KK{TC`wL}@FY9UBOLCmwrYFN_E(zgKdGzu^~%}+DQXIdDS^zT}u25xDT}*A2oA7bU{15(0G;_Yg~kGhscJ^v##5h08c~SrAw`g)f-eUg9oQ zWO#d0${_p{O!(Pjo>tgss}iePwIy1FT60$b#+SP*(;Il8res&FG`aTn-j~g4PGObJ z+{Q9JGLWn^x$-w=*>=gViqRdb6W4LG1Tdz;^?Yi;UH&MrT7Zn0s{UOY1Rjb=tl6bm zxdu&gQnActVkuB9owC|nr}jGKQe6O_*8tupgLIAq}Udwsfeg)5)w^(=Nw$Z#h z^Vq8V7_h-^H6v^#Q?A5K<)Z`3=`Ud3Wwn-F0JT46PYiY=m&y=o@0!v`Xa$qdwpyz| zd;xe@pm=uphZxA|%k~A$`v*CvH?>LEB#^GvOjdHb^*M$Mz+any_?)XI)iqgmvo)=s z1DIclk0v1(tpB9->rqLp;C|LG9uFi6;rZ5DYGkBWtqixEFsugul-&vvpp?4u4Fmn9 zGln1l?RQ9NOZb$#6JdXdYz9p8Cpy5Qt~YH}R~P%LT>#j>V7&q4VFg;vWurvtm(+}m zG^D3c0zO@d?8#+z0pz2Ku*%Ekw%-#ekevqIRGp+(j>773bqY~cwtmG^fgRX|?LnVx zp?9GWPw5T<)vu}O*C_h5mVcS#+B^Pk7A9@uJ6x@)hVfJCSp0y}=O#&ItT!fW?oWpz zoId^Xb~#|locsJ>Bvi9pn2BCMRTqxjc172nuM z`dYlHTvK}M5wgunE?v)c7R%<4TJvs%B1OzYj?ltW3UO2 zijKiXD-&%ci>y-)+3<46&LaET1lWU_XM^TwRURmAZTqH)nOwTsEu~I$I2= zYOmE2=RF#VA~%_=MIBQpW^Pl;6^qZcn~dB#Io(KSq#ADnV$D8l4((dfae^)^KzgO! z6R8}cnjo!i*hRbTZYC@65J$Bh+d_V8<2m-m9-c z#eA!Z3SV}zCQdbV{T$gM&48PJGRZ_+!n2!bn}Nt&-ekUF#(g6$;(goIh#Mw?Wr5RM zI7hEKkA65WXkDZjRP)%cBCAas16P$^z@Qntk_*nJ&4o5?E~3UoEz#02Z5DF0uqApw z4>^i|{l#J0V9LcqifPlq^$x+f9&vRJw9RXo&@$b-s|$AV4cwr(W&*FGyCdPM6HDnt zWg#?|NkqTYCeG4sVcr$w9UGAbk$|QeL;{*>u<^v!Ag-V)hEcx#cne$B6-6o~ol5$# z%#4duw#u)PrD+vk2;@z?v=t^y4)fRP=sLbIAn)}u+!mkK>t7wyyER62ZcvG;$`@P} zFaycR!|bn#vnjR+tYwa&{0#=ujqI4`7`DnaRs&nX)%xBdL3aOt1*NjEaGzCh&GN?ujg~dGIkyKp${1{g*zvgzWgX*kTbvH%6cI?-fT=V`8S* z9oFu%l9tsjvWZ=JPSu}dv|hIp=#4QnY&I|JJjA+Na(Sfe^cx52>Q2?#$yEs6i^iuS z1RW?9HzgHEPE4jtS`|U~7kVY1iB~qty!)ScQOlzl0lF{qCi9~HXX!s`IbDkvwW|Ze z>(j3s4uTWgL;(eh1@PN4q=4LwuIDTy<`}#tSZUd(*(WsxfM9S+l){GWYJhSdS}UMWOt0_nwz~ELjuUs66&GB*5e$`8Q1_z z@bV3e&j>1-3VS57c)lu&V{#nrj?B!#nnhy-qeoFV*nxHBXs~~bh+GN{I5LY}E(rz- zO5rXNIAP7@Mg2J#rU>IPls?Anz$(ozjM80#m~)93C;V(EO-?zA@cS#Eq-tip3`JWx z2m(1~;K}Jt1N{^Yg7YSITRd~I!lh+$4y)M6AqiCYF+ur2}f{G7wH%%OA;t<_P!29!RH(jnNHF0t|#vaM%1K}Gmf z$eXb_hm=LxGdQw@unL2AmL8^>{k81RvA2H?cu)ZP&viXI{5(~fPo??QMOB1E*=AE( zYpb>h`SFGh24T#@7pX!Q!J1P*yoRB}ktHoW^CH!|gnD6Jm89y+R1bAdhU+?obC#4p zkp0VG{NeeRs;2ojmoWHVE=hX1YIepp(J7!^HIWpSBM)dS5F{wYcQJT+Z;6?jCQP?P zd<(Rmmxw1?&r9>w0OeZG`#V-fNbQB6voHYIFBt5vl8-yEzgk=I0KQyyLc)y&QMt}` zUXlwJzX3_y`Z;NRuYMPRegyI}9%1Du44?oFu;|Ft-zB=BhF&qXc?2;C^_2ov^>0*t zC3&@9WGo&r1p-ltaGHb|r@TlQ1b@6rRO!rSr1UPK&80%;53jK2vsa1^eYeqvg3FZX zw~I>1UaacSgaS07VkXuFB<~N}mmgk$&ipR*iFy4YYv^v3@3anoi1Mh+X$ZhCfcn|O zEIhnQ616{S-b+Yyn2lXC4av)1PutG$DsuL=>ih9PQbHRt9rgb(T9A-DxIfIGk!s-kuQ^?LT|^P&yB2#anikvm)8AReIw z4t8dh>gtSMqq%2)!gf~2;uZ3S4sTDZyY%*u&?6{D_Vz3cY6#V)*JHtNARFwgmvBx| zu@23iPi6JD=;RjFx5dOD#VDQv$(=7Xyu#Q;2<8U6fEt-2b9d=o(0ONL(qm47a`5T$daZH*yvPt#i@jNP`&_CzgtYo6_Fck}lC2_f`am8vBam zi}5r*B(az6w@x3-n3czo@4;3GQlrv2mLB`Y49se9s~QZgnAJ0)L$djfipZ-Kmj}xN z+@Pbu4b$l-0Y}<;CaUimi@a`Y&NX@kIs~k1N}Qx2ro44W%W-J7xN+TgB3lez#LFoZ zVcgmV*AIXd(1xoYpykg5JHQfN;--0SR_V4Nu+r_?w{^Sjzwh>puHCNf-tD^X-LC7f zA*=Z>yIuR=bsL1QX{ZjYxv|X{+%RA;KIQa<@rC+0jvMe?8_eVLsGO{+TvcMZh7vl9 zEv+Kf=84`hxPAs?DPPQ4)K+#@mWnyv)?}Fya=u0(Kk~&D800Hwa;=jwR}^!lcSq3; zwwrYB#?@V2b-h>fKN_kj=Czu=vACi%xUpwMqk0GW(P+wO3Kf!8-a!3ez3RcWnlN#A zQl5Yux*gPAxzt`Douj>#IP#WUp<^%#8Y}yb$<@OqSC25cdbIV-g*T*|>85o|u7U+X z*R&!2NJr?8g<9Z|w^U}9;||4bm7~f$gIv10w!u9#&uG&1uz5zH6}%VtjlAReYP&uk z8lyk$`D*|B=L@rN@6T7O=Y!%jL z#*dLHW}gLi-#*FgI+LvaFEGjKHOcDr3S1-eJWSVYHriC=>!PW+(xxJ%$bq0)u6e#e zuX!~F75!?8tExvFhod#R#xJM%`r1lKMMkO{pht5J|M~s&%!3&c^@xLPIHbnhvy_t;lR_J>&JN1uAswo$)G~l z%!j8rN9ICgEaE<|^H@Y(Lvs$J`|zCNM@FtOYe~vdYtX_(t>HbW%i%6cOqp|Ob8hrx z0#V@Mv)?*&ra8Aisg%^xcmyyKL}d3>JjtBv3y)WW{gB9V?ttdp39hd@U_SIt2u|8&G6h@ylf-aUr$HrI^;2{q=Zoe*05$ZatCME zl<6|(4uN5WB7-;@#L_}X{OA?lQPO3-V}%UYJVwmP2Lw@Uogc!PApw`L^TZ04^xx=dck?%s2Y*i|g`r#Z7H#e~+H+)5q!Llc1(F_+yNFJEHUoeZV z!+j{{Mxl2!R4AhZpu$4yc-PLPjs5z!Nw=w2@nA&z7%V2YE ztgXDGF+}>Ksc>|5_XyD~=f*YXL}1j@W)7Q`VOEYuD-hx$H@-S+%>Af2w=HJcuqf8G ztV5Q|xnpGYQBQX$Rz~C|Rwu`#(5O;VObIeGEMo?og z?kO}jr8;+x=j!pzx!u;&erSPnZYriMt=5^k!naYjtvY+mo2fbXdd&1W!I7R~9Wo$J zOU=1At#OPVCsN_W>I<8po@zNQL7~>59gbXk^+lUk6E8CfWoFUPEM8`U%s6WWnqXXf zt5Zl}noi8XCnY>rP!0T#2ilVye5jyiB{X zf^_8E8NA2@iSmpUsEZ?aW_1>w#l^FDj|sELf@!bAQk!${m33Rgxi!I18@Y3;Gv(ay zZsxgp?%XwgKG)_~uboHD^LWt-@5Z#s>-+*vE#O64V>=S0%*b6(okbUN^+H}|*3*kP zyhs*IM+oi}h@X*LSe-=|bMa!{W5O&#*X&B`d^aV&ABDKXlv|E#pRCN(&{W)#?@uK%v>1v%Q?K9m&r+Y zxfbI_Zh3VUUBSgGc$qn75pG%T%5_MxIrlzZCWMTanL}oc+*Q>Vc6Df!@FDK%HOQ^_ zDpz0hwbZo~DP=h<5!DOCx$ zs?+Oc6}g$?ojE4h`b>$vPU>zsfbp)aKf7|0U?XJM>Rh^wV|`gRnC|;F`g6NV+}>FN zAyuAUOo<$C5s8))AK+Q<)cHF(zsK6%lapv0A>7g{;h=f2mShW*?6eOh_o3uIltc-- z4<+}Z6D zq>KbOlrXPo9IWgxtV{@h$bN@naXZX4&GOw-3Qg^a{!6Avtu$qc$VLW5hofk*>h@Zr zc!is?E2HV^yAkZn`<0-+7O2D-AJ}IR@4bm1;)^en*HkBpBu8N&NM_DJ5XrWRa0@ie0JF&QGr50Se8A}kSj&d_w zqMQ_S_7Ls#oHPPRn|Amf#z7eZ&E94UrJl&HoWwYo{3LT*jdBSflG@YGq4ySacR7wV zy^)xLV@(mO=NS@=mLM3lyECg#dltPnfoQpZFHdV}|H`Zr;c1b4+`J`zK0TOkPpiiu zRG~Yk?9bO@SXA95$T}yv3wTbcfUVyb(k0>!J(+d(A|8WWf5CT^WtNIl8#-I!<{fy{ zbS!7#$RJE?r28x5s=7_Cs+YRUmSAGL&@Hyk>gHv7Xo&>0MuZNS1I-dFYZu9ZMkb-q zf#r07wf9eFx48nl+7&dyRsGnI#_c!wyj^ZYc43L7LSxeTMTjsp$~Ewk3Q-to+X}xD zqwZtfr9$d14n>x!!eVT3Y?fh*d+9gZ6kWH5m6s$bf@oZm{+fl;a`s{oW>XSjmBWiQ zm`1S0Mf%39Qv22T31aHJKm?i^js+_m$cARZiUgG!1Ql89xrkI!gJ_}zf`})QPHK=&%!I$u7O2#izw4@`$RW13 z8Z*6NkK~4{6WQPDynP+3<~K}llu~K}lnIntYw;0lFvH0$t_C9rO?ds9 z4X(=yP5-cpJxsBCt=PSZ->fsTtj%L9SJ4HkKifaTc|}db&Q@b4xbbXdW}B$p7GKzskZ~+@xmyG5~qcK7iZ@koy4g zuNokq{|NyiyW1f5saUm)c%KCMKP^Gt{Zppmp8=4Y_YRO-)XZN7AfMd_koy2~A3*+9 z1LU5Ml1OnMK<;zW`QLZadE!2%;-3MKkM12HpI0+~8G!uOK7iZ@koy4guNojX{>?ss z+~>uLGt&P)K)$A__-6nlDZ2OO#k%^_ZwJV`dL>_rS0)M>P*8w63$sQb6IV-k^IAtV z0{n1m6<7vW{93tz<3UTn0CTe^gok|F6%YLXu=g$iR#o@C|K8^@b7sykY-GfdL0KCH z7~Zc@hGCG|JO&UD6ckj{0r7P}<;Xuv7m`&;|W45&?-dwc&TnK)sc_1tUkz4rR9-@AZ5ccFxr z59j>^m_PIAe|F#?0Iw;g=N>rBG^0NpffYx!JQ^Gh75cEAj`bEKGy0236=MxIq#U2* zO*-63IHQTYcL2g;Ca?Z8eyU}>5#rkXFka22>TctW(63=qE;$Znp)~50Xik5hiW{0ji%Ksb#Q;krycnro;3j^{RGAF3YP^w`v%CEOSc; zwDx{^3|@|}`W4h!M38e7pL%6N^Hmh#fBammDJa5cswTDCt(0{K4|{yJQ_kJ1&AIzD zF13c=-nSYq)v6WO`PGo(wftUde$_DesCE~ce?W)TM|EEa%xbsZeC%;A4m7iucK8OObh>NTX{HTO3FUkB_-S3Ha&?R zw^dQ1?MY0NbNXs?PQOk=yo=1caINNU#IxEKcT;q%N!Q;_@pf~sCby~452thWTPc(0 zGMML3^2UJw0qQs?9dz z6+J&}Gbr%J8Obqodxn)pZ;(qo6#eZauJ&Ga>h()5Ym92FG31nuN=!1UG0I3|Wyunw z+AJ}M#^7XvQGL7#Mzxt>V5VZz!3v|=DJ;lKnhsPR1>XNzazO79r>GVis~YQzG&UAi z8dzpTe{HYRY%mz9PM%P!-NzYe3#lBWnvKOLRQd_J;t=hV<{6cEAFt2G8Jf1WPltR7 zJ|LF9i@j>Rm&O=dJY+T&m|1)#jP_Kk_H(NBF)6W>kb5&cs^JUuuTGJr#lx!Y^&~F4 ziv3~q;&gRubt>ji>o_Ih&JbRE$Xuh0#YMHuP_(@*9jWf<^L8Wlr>x`g9w6u6%fYD-`5? zTW!A0Xul^PsleX1i0bh-gEG&AJ5CZVW|Bjh<03_~Zt@NS;a^A7H;a?o#bSR2GJ37;u?c^Fm^x^i4 z4n0Khr)ba>pW=!`3Dju@jQ}E0ho2^SP~iC}jweoWw40tv=F}3{HymR|hQu>^8WtLl zH^jk;zK-*&mP}_nGDX~xY1jh#I@-EY9J?4m%n1B8lQ%M=d9ksW=nm=VVS3nFOPzDP(+v53n$&SRT*H;qP?S z&7fe0kMV)cW2=TagQ7IHBt^uQ)KuDBt7}UiMYq;Qj-??lEDSVTK7ISU8ppY)#x@V* zCBdJfX5my+19gIno#{ZdVO`}|SMtwdYdDA#3X&4AI6q#%!&xXC#H%RRu~1nhSwCu^ zB?&e}OKF@&?dT$Ehfq*sFVe$dmCBc|yy?(+dGdo0RLmWRij1&|qPIsyUOa7nS0gDJ z=MOAo#Ts%3j5&9H^fY&=Z;=?$n$=n^C70@z=pF8&=u>AYaKgnDUR(*`af!ojGJ4YD z!9Gt24?eKV6bxZ;+-l-L;CO zWh2q%TN+6rL|+d9(;LX#U?>{*Xiy?T_hDrZSoyipTyHzy7?Gge%_TvlQ1e#$ntSmQ zKzX}L?r?YLYCEuRl=Y&%x@p=+au(|*Vtwct?8I$yMzERdgH7O6wv1Ybz+=Bq&{lDe zT(M~6_n32W^Af7TY0c)8v$&o6wUBo+f|)et`oPoeW@vnca!qzo1!Ycw(<%VZ58BY+ zCMf$MHb|Q{t&ga+f_(0=-Ibj;=Xza;%6%x80zc(tJar!`8x7zTto)E-Ogv!zp+BZ3 zatDg-B7Z_vLUiz%f|R*Fq_heQ6d$zN0Cd3Xrz*4Q)^R3h$C>?l`M3}T<}g_7AKp22 zA-daeG>5HX8a}emg{U4ODEg34G`&w+{}&CTaHLx`=)=nOhgFP0!BgNh1yr8<{xJT1 z0~dmDVx(G-aUxLTosB2d0K$%X=$rtD*%~RrG?C1g z=m}NFYyjO{ALd#GIQkWHhWcG|IZj9$n*!Jx0I(&D6x9)&mCNyxu$Mno0JcwC$M0FB z$nU8mpnIbCaaq{jS)X|_+)*W%t4dzYK=Z>;)_ zqi9?yZgDLtB5H;_>G6`r@|tkR#Oj!!o)c;2vCU0T+$wrbp~%CUig3rKp1jA&ye>I% zDvZx|%jUGesY)){>;P~mz`nm=_Wc@mCcn+4_6*k={nG?Js!jlqWFfBE3hqoZ@-E2S z{}3a!@NYbeSJNh{aB)04)9Gf{XEH$s@0C{BPN#Ox{Jh5&XmNTqBq5kge)$&ghgSy;eGFc^RS4JC|$|x_waME zQ{?5v7U<27x5O<0B(u0%Tk6j4!uPgZZ!dGp%kc_-E-}1Wp<#7-9C70jAP*|8bgR1J zb7^jF0Lt>ZJxCDk+pS?1YeWny3xb`WJg%u+!0TE|m8TE24Bhzv-2CTv*3DCuib>~@MAPXW75LKHwj9&(*#_^eYb(8To&MJAo{Pd&N8=BSs^sl_zv=Y z6z1)2XBa)B5B5%j#k=$!^jiEIa_+Lj%)3?5Ywd3pz+@%(vZzfN`|ArP00)TqKDB=z z`yp`0pNLE%Z!n(^s6z3p3=^#aQ2dZ>nQW(kr(DSEgTBuO!n(fzX_-b|Hfa2a4FtJZ zfM%=&Toz_NAmG#I&0Y*e08Sl?`6287r|k26DkHMfs7?U&)5JUc44Vi9X5SZ2lfCM_mrgHR zr)M*wMhl=ME)p_S@JrwQo5FNJliJtsTlnuE@93Wo`Q{!L#P-4Hq8Qi?dvR2OEt$t- zp=}0o5AdQ0K%81@aepCXKX^P$UQ*@>!K$ZN`)vx4tlT&k162!V<$p+bE^pe0?Iu7q zaF#ZQGagcfw>dt6FOkak^Or+1UnTyluy5g`lW-io^Z1JjJmk z!2aweq4y>dP*DEy$!$r*DaA#$s{$*lU124h&k*RtL#p{$=BDuj%NwbqLtT3?QXjgF zCr|l|&_nY#mQ@+>{;#X7M(E5q`8L#AfF zaeCs2JC8~Va^Z%U{2OV0r@Y0}-a-cO=ys?1do$dh(ZoF*7@%}@U?2*n zTxsYQ?#sku$rF78Um7pz1VQi(J3(-KAj+jV)flVe#{{wj+gPt&>p4OYYr|Y=Sh`o2 zBfl?ACy~_)RF)fjTq*g38F-bdo5ZohP3F{*cT;SKuxcpj3_R!1P2J29BroFnP}-AE z$?`|k#GDHCKyC+~+$NHTodTKJ^BL&wNt`lQ*;4wim1CoK=$W@GEam%vYusm;CPlL2VB1uBIeU!w>|hF=lSTnZ;FgaC7#=${KP3D zl7c+5y-ZVO75taz44!+LlT?ol)M^HCdaH*>Gomi%2Ah*5tBI1--FL~eGR?7))eIH& z+Tez|hLY!LnjFTeh9y6h5O0_}r37t9utdpZ{FP2k{=13s-0%{TJR&CYrQzmW(;LBI z_B1!bod%sp4N;JGr8Oh@XmQi#Ihi>Z+YVl%_ch9qz}HOf|LboA@HKPs7_m4d?~6V* z_AMlSu7&kzUB~;b6OujZIx)$!_l}3>Inhm0*U7v&7MO7~i3#s>Qz7}Lmf3#cZBkft zCr=I_^Jq82O?NZO@l0FuGwtZmN~>04d28k76j8o^97XD8QD-%S@#YrxdULZ8+L>je z#-2k>hnwR%%AUzdWDq|Ui=ar(MyT(j=E`wmBP7h>Q@XSHT#79;!eQJ3ezJQuXauWU zNWntYN%EI-i>NEQSP!;~B9j0vj+bb>%71Q28u2( zSCWfYu)xmY3btpiWUE}|mb(?v-WlB9xs;!)1?{P?^T?iul+qrXF0z^e1cyMfrF+wK zUz!4=t2JZel?qK+<>oey0LTz<<~9x?BHSVK8doCDam$H3(u<;JPZ7uOQaV`Z#S(;H zx4TqA!xo6WoP*hUITRQr2n}_)E8GT22aN~UqT{V1(cuiA8|~wfvK_Xu9p>CM7L18) z?rOI=`uJe(+M+D#u8aOVhQiaZ_`Z%Fjo8<6?s^?N-1X7dTA}d>yO@T*+Zw&fcdfQ% z&kr`XsK6s9*69-eF>Mo{riUU=_(`DXwnZNv4Y?<34;R;E_9(cd?ZIa<6x~A6E#>%D z|DN5gtezKweq2*R9ovf8!5iNhI$T*h$iBTZw3LGQPTl^Up>Bt#cvq;tcdP7fsG(gZ zNfbr+?vST*kIH+)FdC)E;dnP&Kp<%%RHB@`*>i0aZIFWnha`jxrB4dF@O{=AqR_Sp zPQtd&J+72p?SQc}?^UjshYGF83V=+6W=#FQ!P8qMn&Upn%RFxmN|Br=6?G3E(HOm4 zQ>%b;`lg^uG-Asq{buqA^GF;`oYfk0_o(S9>S3~0MQ5m z`3VGkP>VT;!}fqvHX{^7HY#ePs4sqyaxgy{Kr)j16wei5<)=ePMsYOvs_-~TCEVA~ z5==OwE}v8FagxeFv^BEIzhgI^%=?2PWHOJ4Cd40H8G4XI$bQKhd3W$vE0G%^3LyZY z`J#`{))KIX|F~ZQfEVOoKgO!y?MH)9J=u{HyfOPs%--%Yn-R=qo2B~z+#bj!H#*wUobPE(Pb`VX_ zR@vl=dKvYq+XRJG=b*BN6oSO{(OFs1Sujd1<;ZI(M_$Hpz#q{rj9*%S+`J8usctV{)mtlVum&tf;Jiv{r+qe zT6TwkA7Ly?usKJ@;&Gl9eQAiq$T7un3B(%}k5;p0@}9#9kuutijh+(^W?XV0j4ddI zu{cGpOi*u&Jy8PUCaDPWB4vI~NSU8eJViaHGU!xeaf;zNT|F((gqPb<)aqJO=I3LA}y z0`2CCZ5cg$2IN9k=l9~${8W&|b6r6D1to|BMG#&TeR{f} z#CPqY3YviLCl9=3MJAwL@KOkQkabH+a&3xV(|i9bZ`C}TDlqDDz6V7SUQv$E(dFDZ z2JaRf!eGm`z*P*esy%pyRWq4?VQ5;&R+L}dxr*0n!6A(C!%Se^tf4)8&0}`;^U0hK zUbzbp>m3V`>k$v41YljrF@G(RJ*IIn(=h&JnKL}`9z1q%F}~SLsWY)&IR>{D4MLnB zaE{N~IbJNY%lU1Ryp3*C^xs%YVmn`m2p3DnVM)V!5vpQHG($`3OI8Be(SE*RSglQ0c%R0Qp-Ry3WxbLXO zybbQ@?bP0G;yxsOw?!}7M>!3-AG2u{j%D3Wwo`mwW+e#RlMx#aFjGW^etI%@bJCs- zj&T+Mp0fRzn7hD0LfuG_>OItm-GJDwF~qh;>gN z;m5uV-_>3P5vJgyOvzZBLZ^>~(Q~bWr>vn82PuZwFFq!3umVSmfur<8P@Z{2nAK5B$`pO&jt5_|dBBFu8XJ-$nZ$z);zZK+{!DyOLPGRwU`uKq zK`|))urBlIa7VMSE}Og%dQj_QOL9AhQWn)=3ARmXvm~WWxRH4ga7VP(YFwCtn>Nme z)KW}HuY95!V%xiijMXhYlrWXasWCj@Y2aGk^ACrp<|Hudv(~uLvkSp5*PzCqQ{$6q z>}7I7Hq=r$CWwiys7zrhom1m|YCN3A9=TMbU`+e0aRCA@^aR}LYW#UMRv_F|UI%Fx zyO`G|shP3adB2MNNL(pycvNyUh(}X^d=3ZPZWR1D1~4963GUlCl_0;<&@6*7ym?L0 z#pBh{cyJ|d4BP$;$E%WtKXM!_-y+=VrTE;2a(2P%_6N-l_x)Q^&_7czet`7UHwi z(HLMQcwL?DEL1eL5OF-8Gl>lAp$g)jXwt85EQJtnUR^B~MzMF@&D7i00Q3W2C zDXK_qbBV@YO3_jafvY0OGL6KNU&502Piwh)8k@_InybC5(Ij zxK^ttY@k$I)2$YbMNi{j`3Sfa0bA?wL|Q}_sprM?yx25T#JRdeJujsQqDnz; zDFVFIlh5fAr&GUVI2+V+Bh6A0Yy%{idiINcOVL&CD)r=h-K5jJe`4K|9_0CK6?99F zkdgRo1HM#^-GcrK@rp|EHM&HJmZcIT8bq3Ds%SjE>TCG4)!M<93M_7}T3=6VV?1qD zQQ4K%8e#Hv>3OQLm*J$=#%1ynI22f*#nc)B^0xF074@Q>S{v`kYp=jry4BsP)|{KR z`*Rb|;teFrnS!}L8>v@-p#f0aMc&({2HBdc7FHGjDn@ zfae|?@tcBE(*e`XLdc1lGf80Hen8WG(i;A*XgpK#(&7e2rAcEjnztWD%jwdPkq~%N zqk1x`8$(hb5FL6FpR`pb9i>1bs+Gc=-}FdviLbS5FQm#lzqy8~bou+{o;R=HN~(13JSQ z*!OMneNChk9Yp6vS3Zrlr@PbK>6^ivYQ;|%6RCG5&i9FVKWMcIN=8FBIq)bGeI}>M z2-IN$4e=Q2oAHj%fP{T;W6SY4O2)MZH~9&+P%+*S;;$S}u)CjNB*8=)O>7UQrZ+Mf z5DEdINp5l(5UO@lsIyy~8c(Ih0ex%SL5XjCmm;uPyT zIxXP;3jv>nWk{&`fYPESSc0Kgf{;p!0U6?~cR?zJo=z&q%Nc)pI+bS71)~7T zL~TarxMoFv0ASGdSbChUhu!hSvDt>Nb*0E0B=zD5UqWPDv|3 zV-fNsg1AUe?6tpxC2%p_&mqG4_eTec-?`phC!tXq0Uev< z2G{L2(3dru0dK0^MukPO7MKfYrEa1SX~<6L8*hS8Qc9x^)pnrqvFk@%Ud97gz$x5> zn#YF*t11ULkp!SkwYyqzL9r#^yR=oJ30&icFR>qNrY_Z!6lJB^d?cf4EDW^t>L9tQ zB^!at5C{=jq71QBt;iB=jaJ3JSL?3b3@BlH01q*A=u}3ZsfYN=LVMM^Z8ri)iZ#(= zgx8HL2<=(>Z;t4X&t!xgOMkCNeG6Lv16aYQ_ObwFzb^9O;&*wI<=UQMT z`T)C6OQtiv%f8!%F(^|F%MVgZKV*Ht4oHh7;u4ZB7vOj+guVKq;Om??85r29?qeH@ z%98$MvQHQF6c!`U8OV8G<+`gxYBA~>L5#Oc;PfYx1Y^Zevn~d1NY_i3q#BS(?W7bh z;$Xr@)3As_lH`>{y*@-Rx@NA)V@GfX!5~O;K7dv z=uLo#P-GW?v4`G(#m7U_c;b4*Xg{c10xy6Grv1rY^?D7+64XxTAKE5y0GU5i&j>59 zU-+riGt{P8B$>4ZIC%Oz_zX-!5QH}AooXTKMtMgzTl)~YV|(y>DQ~v1>~*QB-qwgB z^bNX*Vf>Irc?jhCtk{dfqkpQ)d@yD3w&5wU@t+6Xo*_K<2J(MAuL1Uc1yHYEoLufar!7Da*Vd;3d{y%@ z{Od7xVT3xxlbyqT+t<>zfT6g_uct%2q15DR^fiR+fh@~=8ee zc4o!eBR!-GV*=;cBw{)Tut|F_d^l}%B<=Kkntwr^M$u_h57uWpcoi8xKpA(_1xP zAz>&P!BEynJw$6-BlM>+MLUjR|yJtrvWa zy1~nYxFc?pyTNT+iYbCD5l|Z_gQnH)Mw;3SUVR*@GThC);7mHkEwm*35;9FoQ7_sK z&fenHEUVpZChOz*!)d$SPu8&3Se0O|*n9NYAvv7UeBR6tktluk84J@40@pG8ooshJ zX<;2WK)PNiob#Zd<+Dq01ah*=z8g5YCj~RWmd)v!=6-!@J zMYd|!h~SegpfsD3=M9ys`zSt7<*BO>*N_&)r}u_hML;8k4^);BFVfKXgP=<<{@xcx zZ_E;`fWk5qOtm=&fQL6a06YW0^M4(99{a_C=f^(>c&PvPfrkbI!1L<`JS?A|13W)@ zGw@LK%K{Hwe`(Zd8lZUo|EGAqFaSIQ6c04OPAH9Eg5qKM{K^B*_XojR z+>!VC1v|5`Db4li7r;`1*fp^q6lPw`HYm(ju@NRyl`0w}weZSN6o*|?6a7_{2=ZF0 zYt8dhT&L!P$QviD-Yd*S->Qc;>r*R;Pg1X;v_fS7WddY5C!zcH+%QCq!xFe|W-TNg z@J8vzol56^ngf)eIUtJqGvg zzD5)U=-5wF01p-|bTm61@63V{_(}lS48$d9ldT! z&<#k%fm2gyU;!2q9s6kt%tTXwj%HGW2Ape?y)U`}>iEsjo#~~65<^Gv?@F2{KM#AL zwzHk`tk6zLyQ#@xxy#6AKVv>EU?+9BdC|wv4KWkgPS{R~>+IEka~0jU02UMWG157Q z(LiH{T}a77+18|;7b!)aD9r)%y=7S=jW{f70*MzEBQin2$%C1~y z9FgTH)}aswdufF`XBoN!@@;`+r!0?8CihglXH-OR267)g1# ze}WHc<}h!B$x#V?DhdV|xBTFXTyFz1rj4>=y_~AcO`#j%4X!PcmsNe0lN@LxLL3ZQ z5aK*LQo~$LTWJcoYhW8)=t|KajgB|d$#_R)gfrJh9~eWt?|woVOiyLvO%_(9Mf&zfUesEv*T@avEbijxJY>g{~lbIlko3GIBPeSQ4F|ABX6hVW;6h9 zE+dw~mZUv+4{vTV|4L`u-RccRlh-ve9-%by?R2>VLChUx7)5!vLnFgrv}d_9gL4hcI**L}Cj?=i{BJurvh1WCA$BOkl^?hNPg^wsXJXouy* z2nt~Bq}p%Mb0d+%@K%%E2bkTx5^HFFAJEV+dN`JUkfGu9(~2cynSwnivK)q?XS|O#))gaBHzgS47@Y9q$o?$%|an?9D3I2wJ$` z2Q?{^r65r$ONf$3Y|!R}6ELy&8XSh-#be=pl9ftn6?RiIn5dsq^Rh z?r5J$fc8=Ic~iOOa&J<$rpJDIyw5K8KB=>Dxkq`lAx2=y0bTAf`LNLCu)hss)0ELC9dDtn9rw(r#ahh)woQ}`MQKOs3j2+i9e!|2_ zlc!9bHhsp-)-%qW)i%352mxL?~u9%GC zT$6PT%B?LXqdDPZ-O0*bP)wS=(!X-&7n3oZcCzji<<=CFu^f6(^;d3nF&XC#0F*ng z$l=H||CKwpm`uhFF6)|Vls=v6m|s4EiR%PJBHjC z<+_T=Y|c|zH&(et#pD}7JdP|%F!gl{i^(?wZ)TwEf@1Q!0Y|N@8?WsAVscpOm{~VL z*`%0!D>Xn+wzHUgJ3y3|b(55xS4@6SBE77etn67u8B>0&`O>P#oSn1W*wHA{YBK1%V z9+aI_O#U=Ll9+X!%1$gMe-^|E*+kh1MH!{fCp%x+@x|oNbppirLD?46p=BYRbqkdp zS4{p&R(@HxNZGN)i}6#Sl%U83yhV)8dqEX}&5%8n`~ ze=EDctUFuTxS0HZGMC7@Wy-puY(JKhU9RlNV)AzZ$HlB$q3r3!sBf|qL}2V)EYt4whLREbHq|#fDaHMYC?T zvW-PqovtCfMp^9nurxKp3T1~ClYhW#47C?1dos#GQd`ZswaPXWlm8KL-o)xc*`dYc z9|I(oS$CnbCl!-_3OIRY-9^d{DJHK=%_!?GRyHao|14Q%)~#1|a54E89YxJTqrR@b znEb1>T(j;{)egdShDrZ*R0#9Y)vtF zHnrqXwopu-3(-l+x=qSf7n85yc82T~%2pMVughB-rX9-WO=sgOvR5gaV28n_ z9_DGIJ(#E|1#ItmZSVieSzsGD_EM5ZNk)0#j)>yMB40kki~|T33v>k|VnJaa^xOdtV>L3b~52Vo24Au0`fG zjMWUes@)R3b~DSDotwEYSOd{Itsl`E&q(%sk6~xJ8Ez7K4pZDzENeIxipXn)$UDQe zx-+&xvEpV5+H_VqZsTU!q`Sn?F^8)~ZE3UF$J*JvFhK6|`hv9go}=cv&N6$^Stt&I zpz|qT;O4X6EbvMVT@`&nx47(0h~1Lj!A(cqjGyxCBY0zw?=kkWW&B=NV)Iyz#jq_2 zqcQb9R_djT(8S4tv~m(ua?T}lt~nUS#ey|o<5r`{K=boydx1#Ga=g}7&{|tTMpCv1 zzh&fzK5^{(+k=0wCG-zamwpN1QvsVRmQdDv zyIj-Q#5AsOxJfAUE#~zt{ub0N>-D!Vv7*j26v`qi7Yz#a;0t_rB|ZPmNr8_nUvd7l zIr?d=1t0}Ll7d%2Hxtsl!z;pzVe(XnCLlzosiVg?B(LiIZ%Z<0C_wKUahW<6=EB>h zynxmMhQR64;*M_Sof~y&L^qd#;J#ItV|Tbs-k*SLCLB#88|@bm}Ff zG^goCF_%|D$$;``o1;mlMFX{&cfHgmq^7V7Dl-qA+0XSb#N-D{Y%YH$N4M)HM5FH2$Qh2RZ6L`Sp;TJgdDLZ!g2^=CP?W4Lp9_D~eMW z{G1x}H|U=g8DhtZS3Tcc>s zJS1lck`wY$B{KBc52TF*KzYRrave9*7x;-(WYP40_@9s6D_N&*We+2Y&0ZiuzuSc>Ei+2l8V(l}c}R z9Q|;(Zn%z9JxPJneBRBFIXEU-=3hx_gEFU~aJw(Ojf6t`;wk0Jwzlwvtd)H@^o9Fk z?cjiniFS8@jOH21BS{|{SJqz9JsqBnYf8Wo2me{Aomcdp4p6Z@y6-H25mPS*(mD}t7bbiD2ftC(F5k)_G%w$XheNWPbtywg zi`lRR9_(DNzqc~Jo>jILi{hVN^8cQ=~hl+3ecc`rZP@rw8ptz~NV3(C!Zdg|?`cWb_dTST1?PVB8>UAurlsu&$8+zqO}*Q4rtJt!F%yicq9 zeQZ$=R(|XSamJ^8OIT=7$X4_v9?X|{ zouDB*GE4{q#XPWB5*Qu~VU9iybM%$E%tPUhT1ot>1YsvQ`UR96kc0NE9N*^JgFg#N z^XAV@ev$KG9gk}e#fC=?x~kv?m%yWX7ddFK=*Kwo6>tsl zPAtu}uO3!Gy6)zXv?U&zm@_EgsKM*Fcg3erWae93)!h1)A*itAa!vTVpbRvUxfU3N z_rI>#Q4~Ap;&MEy|DNbZ>FC7-TA-syCV@)@H`=lM(|^8M6QgB*RBWGu zg>}JtYcU$LL+RPHfd_Q8 zk~tw;+thd&N15g1mzTgJ2N)H+3i=8tf@RVDxPI2DW|fi#@TeelbT){BsJi>iY>fR^cMp2B%O$Qd4h za>J{y2^$MC>Utw|_ExT;_!{=siUVT@Jx7P;l%DH#<|~K4o1?>3sw@CoBz4eq1$lkq zBvTMgw_A^cN4k}zv^Ba<=i@RpWn4Ce$!9_&hz0M`PE!0FV0H}x%c~=f`-~=lBgp}5 z1p%84il;jUW#7l&&n!=a;RRSa>-OQ?#5h%Fbq1B5hipD!=?Df%iS|u>YpBb>TD(>PD0d#Vo4-u$d?3DBZ@(NIasjacKxW~vp zS-R^}0`&xa6&qtZK4Bf-tB$>dpS3WQg9hpStRO_>cUE4xnZmuHB<;XmfB(Uv5}^qp zp#Zi5z-AW1AC{OhRWhF(m@6ot>LbvJni9~)Kww(|XiHscfOCpNqk#ajLLZP>g@Kt= z0e}L4Prky~)y8M91`TK|Yxg7kn95fqyyM4RMk4YKq4j__en9KtP@hKzEET|}QoJ{W zCX64l*?d-oyLs}RRclIA8j0{XJ6?r)vWf~-pbrX4p zS6T*<(bPf}c0vG=EQFEQ2JYnLD;{+dP)FJ(!=n$JjIK$?2?P+qRGy`+OiYb+*BXyz zLL$1rndPcj4mot>F?OV}+@YwTap;-Aq8r5)jiGS#qgbu#L=Nf0oaAPt9r= z5C8&sCVE0>Sv-Y1oGK0-o(1g=rx~}z+)|{5Ck)mKOD`vWp6r%9`)=iN>^rW3bOPfO z&KRrV-$|te2d(9J4i9FIb}&Yn1~bnFo6BHl(d8`J<599Ho|pVUH`bZ7fF*gCSY#A5 zPv*km>x_PEH#SKcCW7LHf*Ev4`d~%CvLMW9Mn77f+_7w1H~C!vfmQ}#P zZ79bZ?bU3wS7VwZ?ZLI_I&1|oG8K9Tgg|j*<#C=+xfZPSYwgus3l#L}jqqx&?R_=I z+G~Y+$#_nlQMxUep17Rs{sMP)1N-ha#@c4ilN`psP9Br|s~I5@M#nU{`g>ROXcI(B z-rY1O+^wC@T>jmN3JGpWQ7_=>Pv+l>F3AlpKb3Z3_PJZ_Hh2_2>3BA+$H(Xvq8iPgGHiMH;)FbVlH?1V1hhQ?3+0CG8L)OLE{*`hGP`#!EBgDSfZcSl|=bc+Y@H(}Ldw z@Y(-1;PaiI4?g#Q62bJ_fDh5ycnQCv;ImIR_BPTSTsznNcE@Oe>p_BP<-U(E^N^X30s@HwXW^n%a#esS>m?f)qF zR9^ef1E1G4qn`&pei00S&o2Q!-{}LNT@Ui8e@XCpqGfN(sV83=Vj0a52PdyMQE*Aj9V(pMzO8If&vKoe%!c{O4-w^Ff%&<^tn^ z48fm!E;fJe%Z9t!`uwT6;khO_sEg|J)nOs5f!MOTljvte8FeSs=ffZqWGi!NfVwj(&BA)sXB$sB zwGlRh$U^xN%ygMP+$pZH3A!wlFWi7L%5eCB!^IXXhJei1rFzW-P>FX%6YH&ig1;uiWF%TRuw#+lzI`j7SfC-k4Qn2Yq3ejA?To1pQC z6Xvfx1qS&P#f>JwnD05Y|D45Yq^H!`rn%`&h`vHexisgQSRt4rsDfNt=sBUoR)W_anSIY$tVnuFWsTTjO^}G8 zLV2j?TsOyA05J;50Kj#JNIi!fj z6k1Y#&Nw<$f2pP62m>Ee^v4=O{S{$sF{&f<$9_RqUebTgVrkG*T4=bzXaeIyx6Jok z?#{NbV|0Omsh+5w_01U?Q$3}0c8*)w1fdy<`KW823zPEPvN0de@jYe2fSMMnS*TlHL42y` z#Zamjmr*z=$mBsi(I%@yjm!6xj|_TBj|?AHO^~OEzTv&4{JbiWQ!2DmM6tp-{EVV(Kw}ycGxb=X>{2I*eXdQc z({*&Z-r+Grt}<{>{~7hz%4K>LPwrI@^NtZcex}W$F9KP7WAX!46I~3$235o3}+gcwDpil>vl5sYCm8M<_{r zCOH(wS(kCy&FGBf5Y^`-4`~D;(^)OmT2unyBke68K5ebQr$vYK}h=(Q)P3K&% zrn4I+WeRDDp&r&qWL_N0aRf&ivNCRBqLl!B!0Ku6r0H#On0iA|NIfj}lN<%3&;sXS zjLY4Q;VPV>7bo~}<7Cob_};>SIrkRAcQ5sZ@tA}AI4j$N)L|08y)Ygj{FLYg9;xIm zOMJ4N!Kd;w)f4-x;hKr`#7LwkMs#^mWI5@HDHaetH~GS=eW4=mP^d`YwA`sH(-f|> zakdyhSd|NfKoYchN%pR{MmQ!%N z$3t|M-U(`A@M>3bZ!7=Ls);e`@xka%P!r4P42v=h?Yg=8FRdn)b0v8c?WZP2w0U#G z*sCU%b6ZST?|)xSjF|IwG0vE&npjTCU>G*OSxwAp_Rx|wx<>}Q1YDBLg6#JA|E7|d zT@ZOn4xx(a0{9<2Iw?25m5P``Cn0{anfFNC(yJhrGX=4AeF|bZ1S?X_^l5i=WS%4~ z-#ROKQTkDQS220S>Zy_!5}6!gnN+l%YKyTi*@uS>FG^m_))cHs14&>=N2D^CkD9s| zN?r(J8YPIyA%`(P+n+GGxdJDlr;Cyoa&84p>KCCRhvY?W6dA5fav0JMNzjqyki&%c z>kSg6o01ng)&G)_2*#AWkitA9-Dxk2iT1|G%-J3hejFcy9s!J$ymC@_Mdc?%6600F z%7|mwR=?ntyg&`U{2Vfwk{8B+9G^$Y>$mGNkA^!2duVwzb0V}b`4^>;ZP-!*SE{1F z09>>S;zCusAnYhbk9bL;(W-rH@aVr+VO|q?kV$%}E*?&femq;W(l83~;Q*chCXLbm93ykAQz<;P1gZ>&tJ4(y z?d)WyfF+j(aR*UOS9iYnkreu{5OIv^@pE;KfLxKpRtnYO<8h7R_oz}ln)wUbyv2-4 z;Lk{xf~Po|r>MsA9vXOQJmZ)GUJGI_UuC|!6$!~9d(TzEyQ2|CASA7^DJX_b5eX=W zX)%bQDKD}T7Xr|k>1OJZAl5V?7LA+1gTUhfm+Y1x?F=b)*f?j334{KZo>UtHrEwy> z;+L*D$ITKj1PoDw{1KbY4t{nhY6PQM43fDHwGg{E+;l50#5`V5CxdmC;IyFbWg>bF zwgC3a0)GPwX}YjPc!)N*DY;5?2ucXht{XtUw+OXEjf)=oQm(mF(IU9!Drx1PjV7YM z1CyFyjH3mxf{|AEXc5@wtaK}NQN{2x#tGJs=Q56&;xNuu>Ghw73ZowfO=AU$Sfk}} zKI8a6M=DzD)@qz}Na$gD7y*7E<6I~=p^U@ILBeGNLl?`bioabw?V;78IMH=-*_VeOO4m!wY?Ngf{H5`&wCD)O9-M#Xgu*o z(CyMm=o#-vEEFt*R$c^Z8ZFJX_5aiQ(E3G|iL`8UgP-_IE z*CQBJ>xh%_Mic3)^}A^OE;G|n5zZgf8tG{d@>Bm5w$pk$q1x5@7Fws;Srnn2rPf~! zVUQ5WfwBKmF-CI^WvuAiM6%SjdWRBmMRMYbysVu9ccQOoUqP&&>l3ToRVQu;e`1~} zrCBa{{LfeocZbPgrMu*I5nW}gzB~0G8a9Y%XBpaC-bN1d9BCS!fC|X@`vu_>kLsA=M#kgD&Ntx(B*_odhe^I9rGY#PA|L&Bt*1=v z&@4N`YO0JdS$w?I0=YvbAncPkk*x?FP#ShcRJm%sfMF@}1bN5CXr&B#MCJfS?|GF# zWO6{{yyOlqH&Dbp6b>rdTr3`o^03sa2|Fw=K@P?@yGw}*MjS6WaU<$;l7KK58~-$J z3co?GD(IvIet=ZryN3L!m|~8M{$QrsPC^twf(o(1PgrLI6d9G#c?^dj+yjk;VLFb^ zeP#Ho-t`_kuftxHdc>ySw!JJ4DH61cXAH#?hAW=lC&j9Lsdlb3$RcZiK90)jaf z@<7AO+}y4*mp5a9VvQt{Bm8;EE9%ji914Jt+=*IV57=W`2%a$M&Uhi$l~&hJA+~hg z<+3Y&Ljx>w(pXy*9c=a}qq=-Cxpya|{9@2_JW9sYiFR9}x?C}NP<2ZI>`?^W{z|hj zM~0s$5=U_F12g@Z7!Q7R-JZOr-tr+- z>+bLYHFn)e$qv+jZ*@ELf_G|T5&L|(?GKH@53Vr+?BKP63H@WhQg>Gv{a%Z%(jed< zjR1?}Kv)5ZRLNJ^z#qHpyV(_IBzsLEOhii>Kqy8Hj9w5XIY^Td3Shv4B=BaQ0(N+k zCaDvG%@~> zc0T08%YCv?6J2QF0t;#th2z+g`9heiYq3zR(J|v_HJ~$0+sG3t5{2cYK`io`p8B@I z9XJRoyv-NdNr_TB4N+_>o4hS7@~jQ(d?00M8DLdx=qFP**<$2D`b?GOV~SMh`6*%o zqG&UDk>w*Kkc`5N3ufFD!y4qyDm{~d&NR!%GZ8(vf?2a%+Y0oMcX&PIZSfqILnW5^ z@xY!g+PQ4jtpL`vpJO(OiKLcnVwR7Kuzc+DmX8t{cL6-oBGf0YcTiD7CR748ipv;( zxm#ukvtns{4l^P!ky}|7wDhxlJXe;F=XuM=)$UwTFukL$VbnDxNSX5mHyU-VYem5r zb40x?D49c{u6`jIBV{gv3wE(AAJ_9~9H(I11;~W)1_z{|VC*(lEFUkY>*Z#Qh(5l~+>GVp7H|1@t(V?bEFZ69Xsk^z zvjohpkmcj`lwgPQyjH+gRzOO=TrbNIZP6cig#(*kBGw1?QC<7@#mOD{i?e!oaEopy^6jjXD zQSc%ruIAqCCb|i-b%dvjrZKjTf*26Q+d9&DS18it9pHVbA?E>crWJV?a4lIwqU2lDk9vF%okfkz~mrd(tvg;(+tiQ%E<+hEn<+B zM6>V*E#nzob%svb?a@hQip}DP@e|C#iV{W_%E(TZCOPPme!*LPNbzYfA&y5WSxx3Z zAX#TH+%&@~9o)ewHU^MObV~zx)b(9mB-?%87I_ z(qprPBVbLH2olJzDwmb#B@XD1w<0SEqwjRU>MBIvoQs8GHL?*onXGliF7#$Y&(lwDY@)8GlJAw6G`c32jh@TvI0G5oi0A|l*>$MER*BQ~hK3@|B{`^*Du*xZk#1z{ zpyOQhbZs0buYQr`G>U`CsOX!c5Ybmj?Ukc^A+C6Habq;=v7q`mYExDhl9loBY#>+y zH(Ha1^;v~vO@eu>7)7Q|q+p`OU{gf4$@HH>$rKB=rV`XwHSlydSz>jb{rGH)4kJ|@ zMnw8D&J6etGbxx^f`({yGmxZfF( z1T4!nzzT{|DGEg=NKy2_mWVe{#rbPi_NeaY+~iex(ymIQv`NCUMq{5(Q7T5E=mar} z2BHnDgeq9jv$Dn>y-+m!MUz$Dp(vH3P;{akg^_5(p|Fb6;H>Nmn$reY zo*OCHSOQxvmv9BeQVCG1DgqRWP7t7IO0;=M11VsAn+n==tQ^g;(#tij;kQXx+F*uN z9WP+fE4U8%3S^2tYCh)gX(-Y^NYn3_psSL@(h#kqM_cAcKdI`L-$ zj?ey??BKT<)awd&s^y)~0C#z@OvR9%K{;ga3^`@fiyqu(B=B#95MrZ%2!bGjkM=J` zQ9oRTT?h^jzwtj|5-4aPa^A!~p%z-F3Q{L^;B6HqozV>-(uU~4Qw2Q>u;J9xJ6cmu z_;6JaHiVOuQS=MX0cu3TX{F}BSG*4sa}AXrBFdtP(UT|>G+ev07TG7 zjm7qaQAj4-rH|xHIN@aPhlf|yC(V&$^n%md6skDmb1*ytK^NY0Di!y?Tee>>Pk5GY6LR!DP*Vw z!MdIY%YU$zKZ}Z>0F7ev$%EUhtW2L7PxiNXyh(v2`g8M)r63Dtx;l)SVW!7~12EHL zvQ7sz2<5DFD}PoggYiLF`3q_Ji)sF7nm?wLcCYdbrT2toy=*cNkGv8h-^Z|?01mhl zL$WU>5r&G2F>1_v(;F*+QKyMXBndy%w8d~RzIcbjF(AP1*)3g`m=8Z)GvHACqVr zOrq(YNi@U3(sDCA_k(?MCR6f&B>yd9=bXv-Y4A_iwiFIlpC!GU(~n8iAtupW&m=m_ z$x!~R-cjcHT#}cJg{fuUGIwX3e1FA239PJ@ z@oL61-(O{MvHCHIE`Uk2)-#FL$!OlKt1yYoQ>I;vlgTPEiLj%W@xr|sR_VnYTM)xb zpmLFSk44SPv}C2eEReCl3`baE7L(#qZV?0fxZ7P8ZxHjV3ki$FEBLeJ|5qShxk3&z zEsAy%!V`0pi46W~#3jVV?uw5$q0jUg(H~I^PxFZRk`^?8aoBi6oVkch0&nPgOcvk_ zVdCz#s?z3onD`9D$i@Q}mhdS*>jte?v$mJQ6rXmF_H=U-=H>IqSZ>nSzMU=-$iva1 z=oWX2zHI>FCIbljwpVG}myam`gLIo|y%j)-y)tLWS5V?sP(l~kL6L3y+SMs!=fBf7 zzK7*EOkiQ7UM;YYr$#$k-WLiOWP_gqgLZ$$XZz2y`DlI<`(hF-#^X7J< zO<+L#4wzmXAZ3LQudAfZ{v#%lVW+3XhmlE)0=d}mg9_J+9kN9@#~yDu$Gj?5`6Z6n zjK+~Q-=_>_LK#ktpX3-F=!C~H{6XduFfNC+fWumQfRVb1>=ol771?`vdbmkOfYr&) zoEM321_^2)C~#9R8yn;^3Wx?e06iq^n++rkzc(bsLU5mWr{>SNqH|T_>h#gO8Zq|= z^=)nPZLGoqE>TG8kadIrk$gb@slm7ij+#E5tQwIPeB#!2C^bW2zmipYn@Vp(${v9d zHST0H#$zk5(N<1KAZi=f%xmN&g~&jb?qtR-nVlj;`ZjN3!Fq*0IND_@raCWaD#acs-m|J5eVjIkx^9omAG7j6%#IipNI3 zU#ck0{DvkO+h5CSKI9p7{} zDEi}hi12HqOnfi{p#0WpU`dUXg%_&nLfkCN!8~r37srUj^q)J^OXaATMN<@qJMF&J zx{ZQ1a9h_S?c{L)fY3p+7sc&q_x4n%XEk$ZhyvNO>Ok6|BcCMyHL~67uESdzqLe51e42kDO49{uaT_G# zsc{?85me#T5V9m&5VF;XSNgifK-l(%Qu5l^$Tq8y<8(sO%;}`YUE!a|l{^t|!$?gd zv@gjBv^@eT59Ua&66^pa*U(2Kt}DHhQ06awtx)DsURuil$^-ujs|8G}qX4*Vt-Q{(>gB2qnu>% zIq|y?s048{znhIoDBBZk=gp!KUY02wBCtIW_R|cOMzGX@tFT)Ph}m z6ViIq28Kh`fyytklP-)?LKsFkk}AhZ%411Vwa*qfMRpY|Q^XkBt6PClv`^`LOjQ1g zI}uxPp%Mi4Vo|&*wot_cQUd$v^;5a$rIaCmKii!RdnYIM&VAxJF6Z}hywcJIJmx=Ym>Il{$~Bhb14FC=i6mEvx- z-azXOGIZRiA|&%_jl7^+@&Z~b=1L$ohSQUo=Jkh5uraQbu-+|l3!^_gh524Bk;XL) z<{1?-SlLW->B4R?V-%HK=dRQ6h#xka_(4;@UL9q>QqdO1K823kDB0%y6<=>gDeA~t zx?cPJN3=QF6#pQu%p&x3W?mKA@KmdpC!eJas}ZS&27bJg7><<1%3EXPY9s zMe%Brr{2Oag~l?ThcU}?SS7J{#iF@UO2I*Du%g>OK`U66EXct>Y{DKsG^8sP>tY#$z>O zh0`6!E+58boxT(;D{bTx+{AJ`$sWig?R_?vZfOr$)W?dY2Jy1QKn+q|EeXERJb=a*|C_C-FbI z5o`}~`QKXcfRdNN2j7-LNX|llp-sNc&0dOKVi?OPaZWkzsJth;r4IkD=JKxQR@Afl z3Ci=54LB}wmO_L|Ixru}fTZLL+`^@3VTEky*m#u1-LJgJCCMJ$(o*!X2yNhBM1kEs z@ak7Yl$7*_=MD3o-92P?Cp^|toXm?&zS6BK%b>6_+Ih)-jV8Oo5TlRu0kIx-<6lru z8cK#L;7W*VU_!hQ&l1Z?~%{e`9!(D(#%0K_L$x|Ah_#_O!&W6u) zVih(GI3O0K*ydiWu7EM-qh4b5yjveVWw8!qK*H!b@E|X)IAT>X&5^Ro-1M?cr@O2^ zlL;~=*6xVAsfGoPSf(3(2Hoz;9smKJbqOet zE5Lk;1ZZVh@GW+pycpY^)NbLYM}>;iJ&nC^z2>tJILJ7R6ZO@Ub^k|n?>BG}_J9AP zb^s7%w{08Jbs4|+xObJ~8~ZOA?&wB7gKiK)Q7dn9+m{~i7w}elbBceP1tq%OZ9M=% z;DI17qjmN@g(m5CJ2nFZY->US6PAGy^*4#0xtamdwP4&e@|}@`tRxO!N8tA zu%~mhXCZAii_!n^p3YY@u&2L6_Vj-n*warqr+;B!Paim^51iBggXeU7N>M{#6p6&d0c^&#z6pKcUWhsO?Oe*STBS3HW@n$y~H+-y{Dvu;OLeQ#vc zp)nq#WJGF%P;E8{aF)i?$ZL>thXna^E$JN=;MdTh4!(P>`0Hy(V85?|B=h6K$!~;j zQ`@IvS}VTIYc}gf7&WzGue|O(ZPccoHek-wN{jRj-HhHAlT!-!o{L4WU`QHZvDdm* zu^wvO8Dc%yP`fip3-$}OsXZM$nOZ56-fLZ3FlA_g0blFdOR#6=petG{T~RcF8F3Pv z_*yxoy?2&2NDjao0F0QF6n=9aec$i%|H3Sv>bIPJbT)v&ahB{W=OoPSGG zhRRY((b{aa?yX|#FN&9?-Iw)sm)nn8x7-8%cGz>Po9`pl3i(BUS{`f23At7G%gQiurC?B8J1G5{6Za8FN%*SCI5Ve zYnd5F4`vtv45U>KQ;g_|&fx??&?8tSn~X*A1?fFp(03235FpTC4cwzzx7IXQYo&FG z=BnlOfXFTvr-iuf$81mVY+Qqq%DBwrdvKVQbZj zt#w4De3+)UdKqR?m-WEQx|F5ZZF;M;jbLbzG{DmeDTl8W9W@;j*}T`9-liM^+u)9b zdusNvCdjZ5P;ItP2GwTEquOlgOKJJhG=D5jUrx(WO}2JhefJ(sLjGAudXTErO2%?E z3Cf{cRCqO9HIglG)oLB8qOYny8lkT#C9iLYEZ;{9>fZDerZi2;<5=IJ{ICH`&z zB}|b2tAP?<{C~;r_B?eI99fcA<8bFC(A(Z&auRTimJN(T?2`sD!I8M?l zgl-<-IK2}%PCpqyiC-a<5byEVo8#o~@HZPJo*h7m0b1f6MN7OkfD*kZ@n~K6h((de zCr1?>0;hAEF$<_hd|fTI`-8})C91}M9sc+%o1YiY6)+mM1Iey`!{kDtiAG>MK+Kzw$^9yE z?i4viiVoMsvBS@uoFa`@WL~R%^-OL;+&pcO!rVOjPfTHYG8BugeVPq1 zh%g73X|CSM$sxrm zs!oI5A!WpqE0hFrYygUi+E#;N=T0xW)s!{)o!sOz$peqwWP*ZtKw>m z1W2|ZCGK)~){CAQ$s1h86_(+l)puQyyr`?3HER!vF&gGQt4E@ZxM3h8f;fL=@Bkh8Ld!;Qe*dli`oH}Ck6P1u46!tAz!I95CG->prpx0loD;bC zF0KgHJl9o^wapAwu9V^Ah?aYt!$}-Y%i1R2;5$}~( za@4oWvJgFW;pb1RSM(dz3iLW{lyHx--_k+g@BcCS;nfc--gl;?yK(L3zmp0b(T`IW0Ad5< z*B{{AAALUL+t-mER|TYb&*kv;z_T$)~AEL~AHcVi~!qq=-w5LRVeLBRsKY2lyLplgcUWW@m{`~m?x>J$hg$nN? z3;@S}xbvP0i3(CuC`Kb{B~R2!QGp4c3l-5t8i=3GC3R&oydu=Y+(v?C1A=CYvCc*p z_jxkw>9{`|I;uXeEU5Y@zMx#FdUc*v?jb?v7t;>U=}2GXGS2JAbNqNAOb8c!UYpM| zGWBc{$w!>;&xO%f#!al!RPZJ3{}TIuUL3J9PP|-|c{=Q^^+CliUJ_K?7*{w#7%Zq* z0T<6G6&o`|%HXO!3N99_vvNJkIccO(@u@M%FPd?B>JKVr&uC(Y#orw84TynHlZA`a znERqhgWN)X(qPXx5J&;a3h*3HV;@wk#w5~cW~L}-M%5mYqgoiUf+Vk|@zs_dRgKT7 z@jyVa_CTV_eb&f|DGpPHZ#CvYG{=sST`^|T&>kiYo(PS7+%%6!SvsIs28Ar#xCpaq zGhR*Q2xB*`7!!4-SU(G zcSU9p;)TswNsW1hnt6rPSWMqJWBR7CrMM$IDsu+h->t4yjd{(M@uJaKVVKQ&;gmK} zof~mgB5-pB6)TENC@Yl)kHBFStK2H}=51`_ZB*-e(hJdgHTze4+VA$F#Sl~AZlu<{ zvYouNYVG2+YCRB4thq(;#1zaVYW+g!HYl!>)-~}awZ4)4Hx}^=f}2et#W@w5-Db7^ z1;Wr2%t`AgzD2FKu)jRPD4N((s;1Gzgm7+kN~o?|bh}y)WZ+g9vGS%4L=*o~NN_P3 zYyCY6;um#~$T^#RIaJrX*iwA4>xy^qbBCxClh^*{YXLqtInk%RAcDnR;t}trqPwWs zMib8-_1;U>UZTj{K28Uvz%W&*;MJ-L$jVD^Ui)(L`qao%Sfbo=b)+% zxkKvt%OTScxoPRi?4zERduR}MH$4ZXPaPod{uPmTlZ}?1HIQ}F^908{F^H_2p30?l z8Cf^N=t)QVS$alsuX^_B4yDr!(+VuD%rkUnmKmJsj(Vn*M=c!pS8eto=`5oZh$ho8 zOg4ieif#UR%*J6Z8I%d>mL_K{r7538rdw4hTpQu=8|l99E$lR z$NZ887Q?x!(`Amx4X|rNwCW!#GYZoZ6ArTnp<+*uQrs!I@EDuPq(2v4z`R1_uspGp zCY8BG8HdujMaccls_^5fkWcvVB|<(k!f~J;*0s%E2IwKW{&Mn^wpQn%2XiK`Cu&dY<0xjXM7{MjfKoQd== z=}KO!V=XWe?^OlBASi?Cpt-11$GZug@I#~##HF~#B$gV*3kNLyF4;XxfR z&x;ltyd0y+ku!3&3aaIpN5S^Id{)n!l*Y0kc~h4##ZAU-c8bO9`3B&FHRNlwvMFf- z`12|7gZg5}d8%ptfyEpSq4z<-n zORy1_bR1ETfipXs;{Y<@^zIodn0Z3X1Z-cAl>jnw*Bv_r2WNo`@M9V z*FM8cLZYH!{G+mf)`bd-;Jbnwx@Ounn_`ezqRzVdBWU zo6J+#N<4+_ww+N)58Quk8%JN;#>3aPpHWG_bLsZ8={6{zu?Xm!k1`95_1W(7q5{P! zmSew=@rxWBt+}nYGKgojz~?kCpVMwN>cZueqT41Q5_jviZilL!1bFU_Bk`S_JsjZf zt`hT0-gR-Mc86gEXU|TDKW8c4W%py3Un#UP$=GS*k!zQMM=l3wW747Mz|Ar*r%P6P za;sz=qXpky75fQgeplbx-2iZr{md#oP*?0_Y8vaVARFro;DH>utydvFkUr54GOvm< zoD{1)TKI@EyhBfv%Pz6W9795HLYNOQd%<)OeM$?UZl8XY0o&sH{JF96QSdSo4~g$K z+FIvcobws{q`TjpEHVA$-KmzK7HQ^^18S zXN^NHlnh_|=x=Gauzy{wPldrya#YO{G~$ z*E%ghj%9&z;|1b_V3NwS;7L=l3h;lc3V`j6B&z3isUXL7DDEOiEdKK=dpIRb|q;lAHGe_@Jt3NpFBSH zjau1f^?e!Ug-WB-wk!J}j4f_Lj6#(-0W%3q<%a!Nk_qXxCC`tvCw1&YZFE7mc$$GO zNq?fho5b8usks5TwNj_brcM>PW(HgR(?q4F31GKU6h@oaJByz__<=P68Gz`<^$9f_ z*kr;0yV=~#L{aJHSc=+8&Fx)m@|e4mcLFAla-tySk4l9+ycMecdN!`N?4szPNDE}K z2R7(a8SQVPvY%{%96=T>VEaKb4th2N>Q{vEsY%xZ)Ic$$j>v4A zklEC+hBQEQ9FzuVGd#?6B#DgMYScmkZG8UK@yQUji6^ZisT$OAJIzvRAyiOz((x{8 z?lMtdN~s-ghdLq!-iauvBNoWeK*wD)LzqXgr2Id->9~iQJ(loCH3<9ah#a{a0g{eN z^b(0|pb~*m*$;SZT`UGDx`AT$?vrR4I1*$5F3SCbV>`6FX#!d-`$o50>2DL^xa-Sz{ z;Mv?y>-#THL2<6`xhH~D-VJP1s;2wtZ z{;X`nQ2qml@*f)t<)0dc@{b1Pe>Du{!%&WfWEjekOoyd%WN}3DAC6T1$)Qkwb{NV( z8kAoghVo%3ABOVFawQK-G1q_`34vheG+6 zhN1kULHVOsNwNOVm_7e~*z7rM_8c~QVx1l~d;Z&*J)a#4<>!Z?{G&nnW5ZBBER_#S z<-=0>4__+(;!r66xnU^(Xi$D;7|MsCd>G1yq5OvrTh^`i03&E*G{VV3Pk%HV*O)e{HBx3zVlr)0KEAm(k z#QGPA-K>TQV1yeLT@d-!XyT^E(84pcxY0$@P&oqnv+fGd%tFn0H(v2vcM{%vr=rlZ zv$BvU(d4d0ShM(4Ysc^CjXRkr8rKbSz}^An~C1cx~au@8U=Bh#OHD8 zt%Pr86}}0N50qd0o?avqRdYO(pEHYu@RN87Qm$9IdkXfPrJ@?XmaLmi(jSUhJ~){h zd@WnowgheTZs2D_k?72kD%H1!BTUm` zowcJQo|j(0yfk%?JzpZR?fK5q?8VALv~5F=i;atwbF2uySyu zWnH@k99u~!t?h;sz_!Wl<)2Chk~D7XYBu3gJ4bare<4)Mick%%04vX@CCmVJ)Z;pb zYniwTu+H)-})>{0# zmGB^2c_A%drK~667J;@|C3t#G^&I&1wEKEC*IV8vE8o;^w%^L|##%~vh2UnD)9Y;; z>22lo$|`x+JLyR8+DJisBQLb&@KS7URupg-)q-Qbr;;AuQc3;$8NNpHd%4X=iPqf& z%fl_vFOup@Uw~V~L(o-oj%3-gk}O;HL2cm(w-P{Lyb4)m^&;<Fx2!y; zlHHIY+xGc%oAg_-$|(dm>ux9Np6Yu3LdKAF+lanbGmZ~mtZkExYt5_x1~<3O9pMEf zW-yQ~i-tA$~SFXol(B~EmgZ_tu{;yUcGWOPZ z3PxyGM2ws5iE}Q8sGk#0@4F&y`NB*u-Y?8Rswk<{OZhOntd0w*;8^g2Bg{f0=sBV# zXb>-7BtDEld>F=}Fp}SpF^ZPtGI~Y{XK-Lti37vP6UNZkIDMnb=^51+xjK>qm=Pw7 zr}6kAg1?A-FQI2tW4JCRi0dL_Y)*C&SJUolBb~DHJ*XR_Pd38qlJmSS#kdvY@H^|8Z$k!1{ZRzI&vGmfIr7wTa2%x;~-W>YEo9lpA;*jC^*C( zF;5-m({X+gxxGnzj0@ME6CiX?D#z;!`2~TCj7lisE@Sihm;~{*CM=9{dXv zIRC}TSqb>Llw3=+D@%2NWmKg^zf{4_sNo>wSSmse&LfIj)ia@40@Dh7D)6qXR8Mg+ zwi(rmsz?c|o{)2_6gh`o!D{vFpjn5w7&}G0vPM1EQnl8&R#ZiPSM_9pvBooj>Jm1I z@-$~;pDc%J;tis=+epnu$XAfjpkldEqu)f;O~$^Ws+5fp7LFTJ>QAvT((@8FM)kan zp0^qOio2zV$hA!;0S(92ln1l`5f(kg#drtwE0C-}#&VZ>c2U)3{41*3>2|8;xe&%* zBL&sz)9Uj&e-VEkQpF*0vjz_0Py2*+@mmxvI>K(+?S_t#r36)b-Chm%g^-mB%%AoR z?W6TR*csKjNb8gtiz)~i)tbfF-J<@qkLUocMaZJo2WfrK*cnw-awWC?VhC3j+@JOp z@tqh&V@vQ=PIwM~kuRsYBhg>Xfu1Gjjz)hoQ*aT8wsB67gDflO?rRBH*ySAbSTBaj ziy{rqr9>@aX^DP&QhYzcahWzN)9OyKq|U;c6}Sf=(9$Orz|fL&587HVj9#0fgRmg% zbr3d+!YU&&Ezvl(1aI?WR`x;|Kc{;L5vkq7hqU4OTN3s?qSlWP$9YUf5Z@M!U$XojI;dE97OSk^-E0zs+WS+p}jO5mc7khctC zaD%Kt%SfOBo&|tAC{`>{W{JoY=Zd@zMheVhm zLr-HwSYmz}B@#F)(bjlZ?J-o6Rvi8xkuvV*Sg`8?+oMq2K*81-kK+&`_8wn~CkQRw z1hFmJfJc3CJb66;yDJCMhF2IALGaFdH7Nt#to6`xVhvHszb4zif z-I_)t5;OL!mS8TI$Uxj&klF?W?rgMqY|b-YX)F1Xtr;(#HAA0b+vrnlo6pX+Ur|ZF z3+eW&={Aa!43h>-@kg07Mtf$i%9O^D1HK-{3%TY+^oFvR*~4>iNzZ58U9fpc&)1@& z2~AKIZr>$h1zZX%AQ~LE>`1(vOTED@cQ=&K(7?mR<#BOyg1IpEim;dD;w7J4+^zJy zi*}`L>|EvAJOg~Cv>SEYl`7q=@~wrPOSiVetuFH~x;0QR(s7+z>(-Ux^>%C5+pRT5 zu9o12{#%Rw$L`Ualp1Cu{WiIcZj%yy!_{TH0zfSuzEbNmr~c;m7|xkd;5e+ zUSrlIR-%ZqlemY?ds6B*cAwL3wx89FGHS-7=)XoA4H$b$A}LnK^O^XtJwIJYKQnzX zzLzQgJ{sOvWNJAEE93F#+s(qs6I7lsQebfM-ZAM%gX~pV#RiYY(@yRsm2AJP zvp5A0*r}sTL8Co0eWlGk){!(_z3#Br>a;AXy*juXVu-S;cXijwQXROVV(-( zI+f3aq~U0eXRvQZ%Bjmeodv(+tP;3g@2V00ovU-x-R$U(tZOp@dRPYX7zSN;U3)y2 zDkIS?#n;=%cD?2~4zLtL-CT;-Y_@v&oDbSRt0qw~FQCN&<4za)F5-Nese#RKG3s?{ zpi9@=u>=8r2^TEUrddKu4zXoX7IAr2wnLPl0m6%yJC^brCGRqgei@Cr#!67GC%Sn# z(b(G4SmB?>N}Y8ZvN~kAt}S|(Bpf`_c2wN$N8;67CRpn_N^z%MW~aZ(Hb@b3(mXyJ z>NObe1$I;NLxvkYGdf*ApfWkywQuv+zMWdss4wf*ZZ|p8S~cmUn&LZ>4_>)|^$DaIpiqZt9q@H`M!z#! zt!e>Rikf(*o^J+xMQqn;XSjNCt6$LAJIroj+pneDud8G~Q&AUWTCZ(wkb;^R*L8=c8AAffuL&yX6M8USWmHUVWuI0nVp}0DF9~Y+WYQ9g2QEtAVOn1yB@_`VYHO^!2L{_jB$D*v8F0$`eptkTPhwd;JSh zM`n<|zE?%aNOF+X!sF4IFvB|#=)UX0QWOvvPw?}Eze9|tbRqY*1S7e1C)oiM2kDDb z2Oh)ao-W)enQ~7>?~N6)uAV6Dje_Z32GDB2SN7?nNRKCyUA;-^Du6as3~iz|aGe9L zi&_#q5=zwmcom3KX1+xjzeL*3crg+A8d+IF_nX3hBfV`2eqWEq7;k;DiZFUK)+{kz z^tO61nuB1oeM^(XNaJO#lAvA9-H=gq)arP2`F>_v-RN`@2taLSRE@7;YQSWQTFj&Y zuv{rdXmO!3Fn6XZ`sq~6Vf!6DWMjZJ6Dm7@|628}h6I#xOSUbf< zPSPZuZc4SeD0?R4LFfrA9e6~`{3BYq-y108d@E-9rQ)q zwJV_wi^NKniHlg#GSX_TpF5}-tR!LZwNwpJl4zt2=twrCBhiLgvC@ec_eK5SuDt8- z3LDsBnIkJ@Y<(t7m>@T@;gu(7P5@j~B&Imw?s5V!<=!l&TwCXwEvyLgvSUDo73bkr zw^gTc8+eR;iwn{l{{qLEmE{nFw1g3u%(seSMaSMwLsTVq0IyV`OOYA^HQ}~YO;`Ye z1EaQ8>m9V-VTu)1?Q*-+`pM9Bc|D;>p9`o?E=qM$Syr8p*J7{UE-hZAD?uSbRnZl7 zzG4yYNpIV6X%Q=pF;{Ilf0nLdxJ7FhNNZ=eU_Zb2E7Ju>G1=BnT6gT!2^ zRZhB-x{6coxDYeF>#Wq|FM*~{nI3c=5#Wh_1?pQ4+sk2li*cU`vDAT+51D2H-QI&K z*=|A16wt)BdGT$V zw}Q#+tiYP`$q=9>xsbj3IT3v$9x_ZpnJNU3BiTDbYu%ily9ok~;5S{H!0Q_DI_F0D zG((bL@~Bq+lsdsPGPdA3q6N=a`dq|mCSxr-JZ7UISsjl{p>$k7%p(K2@w^e~#8(eW z)2YD}+!Ji!kD}W|t(-EoIhwOsXJ_Sr@vBz(%og0)8b7G|JH&B{I)L5u-sg2gCfc`X z+W>-XlYpWIMR@2Yp$Mm{R`L)CX$o<#d(%`lna8Lc7gKs-oq$Uv8_HKT5WP?=Xd1{# zNPj)8dqZ_osY5`Qgrjy$gVz7;bo-rj8)T}LYQ|C?`Dx#{;H3lL3!2V*s7XVTRo$E* zh)srX3BIY>7X_quu917q)ox;Rk;kB^cb1Cr%qTgz*DR%&rQ{k^{)@zVJu7#aX#Xv} z)22d^-rZiG1!&ii+!t~m;Wsb%%0(*1Bit`yMrlT@$9ZOByoAn6nNpUPP?szl73zZQ z-CIs)z*s`k4O}eFE_SgteyblUajh=t@hOjtHlLmHQ=!Nq!spydQ$?V5w*(Wm-It-B zZ5&9r?7j>t7D^Nl?bNR_hmu_CNn(duP4x<#hA40tS*OMwOg|Ls;!f~s4LjBpnSIvc z9MY+ELi!05(818E8hw3EE*Ux>MEq7&wxXYxdVYiSEeQAI-)l#t58|7-8=Lw6EeMvk zl$cC#vT$1_l_hy5lC4Y>C_mbVs6H6=j&#M<%5=dI+Du5lQ)*Gt9ibML9ifZgU8aaf z3vcrF*$l7yF7EU$@t>3J$TSsP_;?f|ftRrTLV34eDAVAn3~%vX8t*mz1qbW$GIzJh z+z8v}bdQm}(Pq5;n_ou#6($=K%?GkYY(j_s2 zHK~#EcXlHnJSZ(s|3R)h!>eV9+0T~|38HO%VC86 z(L?C(3?uY+HS>YkKLCXGpTICe3!&fr<=_1FCJQqYE1(LDLXW6r;>Cd`w*m&eI-{**kj9-r>?AzHUX;6L}^55NmWSH^%QJ1 zXs~*0jeS$4q(*oifod9Id3`*JS&HmEZgeRglS;v3(n-9YTt1(kT2?uuytu-RTOW_7 zgQamRAq-!cHf@NnO8$H*3g;Rm+^Z08k@wcelV~c#U~LJ>^_o?q__QQLPforwRdQ>L z19!|Ha<#-$X=*x(QcT7*nkIdBB3_brt%y)by0x|OOq!6F+0~cgS^i8f{p)5)@#SV9 z-y}bC2LfiHtgwOSn+yJP7Cc3<76cS>4grNy?p6t6V~=+~`N@`(?UsN@`JG z*lCc4Q#y?DNXVk=+`8yb7NbYb$*s2*<#J-3Hq+^rYQ{`+fm5>Y%(=_Qj{{Yfq z-W{>LIeL$W%7tDsR%9 z{R(NeX2g5VV$5*-1m1XC0Ed;f5#NA;NI$j88{TAimoEOO=4k}4;8UENskqeQ^(;)&3o zt!bgr1TFeb;-6AvXwCNhQ(~A} z#9){Y+Hm;UP)GPtR&1C?0!;?m!GDcnV?dF?FgW_Xtl>~}ymGl>19fV@V&hf2KOd&p zcufPK*chV3h^dPQD=`KX8y}*`Fmd~0I?j7T6&v58+LGV>Gm4G>>q8YApS8*Q-$=3X zLHf?jCkP1FL5ToOrbw{$v7cke(%HitLyBh;T${@34Yzn>+`{L+ME)Qp=8UENs*M6u z2hlyq8%!q4T_7!osWzJ2q8@Y|Iq5p2o{$)UAA!BU_z~DqTip#_g>J3TL&%P~PPec_ zdJt(PR`P92*^v4g%8)srm@7!nA@N3f4HOz`*xs*|=-`Yx-JnFC?IiLH{OrQTY;^0~ z#u7S<^=^aPG^rdiB12?sI`DR;4XMDHF-q~Rpx~`3X1AgV=df+e897CENGpLt+_VyA zj5Loz^xkH9G_AxP+$3`(%sJE;_)pmN-^J;e8pCTP&}>L;K?BoD?BMr~0j0V-ZZU*^afsA?yZRf zS_$+UN*HJwg{@L(NGqX$rv2!>?~zvGAh+g_JLnFT;u5!}lxijPw8{6YXL8sVNBpH8 zl{y304i0(PYbPA zf(#=vFVa7wY92=9@SjpO44`5#`2hX=4RYXV?F>z9S4Kv_mcTHIX;aqzhQ#IAa zZfYr>#-un+@`4Rj$G~-#5Pllkh&oge)8m<_BWAY8_4JwL>di+jU2V@ihBDQ>cLToVH~ zd{up{w7C86ZN=&>u+rVb4Q zg^Syg)BpWz0cr)sL$@LK$r|;-_z(44OU|h2nxw)s2`llFSgWRk^R_$XZ2t*0+sKHU zY(%_+)vP~QxPxMBN{)+qKPtrg@A#!{)^4%kvDLUb#*dvIdVV>-ldlEah`-b&GP_AQM^ zSfg(gAJL<%!TwY*VNK2N7ss0{`vDQ=!*AFm^O* zibe9~Dxhmf13bLCA^MM#$j8gwL$hG|g%X+ud(Y3HuSnk3n}&{|U^<2;Ezsa~)uB`< zpji;DgPciFFcivx9h?mMg#!A8qP&4P>raN!li-3RT67GQ1*sS)@i+LC`PQIbC=69E zpkbg5%|8`JU!5SH?u0kHYl`5J%a!0=u4{=aV-@TFCWodyc z0QjxLhF@nPzQ;AavxeC=Iwz0GoCS z4>dpe^Tz}QT7%=CV5 zxW>8ZZbtNf$7)pvtW!Aj#}#r=(A~vBp$V6UYxyaqu+BIWH@H~njyf6lZ~tMxW@a6A_0uCo-c@egF+uUq3!t}R}h9LMp1;12=?8Pi;i*G_cn zM~}#a*--z>aSP>qP$B2z?F_n!pPMv?b6#8VMxfY^zgvYoj&(#IG_mS0u9XAKV}=?c0a&29`Sbe zm7U()wse39=%*oKCVN@!AEjWmf2jYNcKHEzfWbT2x6>Tpq-ahDsENDPqL!t8g*=B( zQFJfN92bi0;kS1nX13TD{j7_JD4Z)?kFGU?Z&tumQDF{lXq@k1J%7#(SNeP&DD> zOVP#z&=+a~sJLtbct8_C^hB%wN|*pDm;gLx=%c3FX^-}&!_+m30Z)gT8!AvnoBx;p zL3CKC%`84T1#FWlpJJN-1hC`SDPo{76|&&LnSqSoF3` zc3Aar<*Ju4&j^}utP7(5LUgb`y}XKsOAK~q#cRe=M^qq2x+;uyW~ghw%uwgbX1b+i z_qMj=)c}Cbxv@pb&ha=9#*_=hW}B=$4r%#?YcbV@3Jq~JCAp&sf*0ww$=x_`6XkB4 zbCXhEGD?)Pw*4A6M#4iao4AaA1O)Zq;6;o!vB1p{% zQupiFH}d<&B7k}m?p^PRIfr-+yoKE@=HIm?;v|MdYM>hBz?f?01U>QX+|_OFcDJn* zZ_l*4?ON#6^ha4IZwDYF9$rPXqIbHk=G0xp&E{O8QQVo$hvfqHkN< z@zQfjl9g_Ar+w?DMCGfr$O%4VbM<;M6b##MM^q|pK znR4i~-8*Y-1uP2)&^dRqXfpKu(bK&D5;-4GQNX9S8K2&u-={bFwV9HYPk@jQxQD{% zKS*fi-U5LAItc^P{stLcIrue>q^UC3rrq00%)y@5{^CJZ8J> z(LFEdqYJQSCb<$gr*|D>vllEi%K4XBhieqn<$yX{%$wW0>~prnXZLv@6J5_rU$(kH z^0I_w@v)`mI>y3yX`bRa%yvcD?EqdR>7O8CoPB)sNYgpw>7oScpA02PKNUvLU2mtn z2yvRxkfsr5sa@Y5z4+iQV#eh6FMl2FDClb2fp zce?3Nl*Am>~E4R3MUC*544+3C0Wx|__jw7p(5Gd5#9CRG&c?%kk>)jk( z&pfJ;k8f4=t!O>6ZoXZQ;z0t}R06pJq+>mwML6_IKDLd;2+NC0=0MTX9uIcl<$9KJ zJsIR;XzLVi#?T7>r~sq0Y3v6WZ8SxyxdA>ue?2SNTG4(YxUeMAmUVo4FooHL~uOmSBX29)Vh;0wS}lCo+>Cgt3(AW^ZW; zV1Cs5W*lWL!2(~o5ElxrXIruWF}F-WhG?6WFxyPAjmTwnTmYQkh4jni=>6^DlUDVj zcn4cM>~bOm-N5ZE#k+Vs-F5}L-7dGg1Wli=ZLjNgdrQFi9=GpO{fFz}a^!ZKb^D1k z)6gS9?+U;>Yw8Yn5N!v%dvbNkx&tjioxdET-)jk``^p*MI+t@enGS?!Qx6LOJS&Ea zdWyruxU1uI!MQuE%Q;3haxZWCoM0x|`%Z08%9v7!d@dLK; zAkG&LmO%3dTyHyqFW(DD-WLMnB|w~q^specLRg*!%3A`6=;j1fAUc)EwpnD`qNLf6 z8G>g`aE+)wOWg5xd0gtY!j_Ua-Z3zG>FVza<_QE2{&P!em_B-9#8Vg3{i zGC>k7|2-D!dI9q+VBQj3rF~aPe9Z#qx@LdGvxb~Nc@`Wm3d28*cnhqLsrt^y%%{TM zP%di~QcR#&aFspCt$7*%5_&oav2sMqOAP^vLu?`OJ$WOGTniX06rXC+Yr4qI&Rc#-}hTMMqZC|&AjAWukmxf3AFRQU(F^kveC0eEO3o$fyn_S z0xx8qyswR5t;nd9hcX50g#6XlFUTp>yq}>S4RPLG-0%7MCm@OK~Ik>)pupZc1Lzi3(Zq zJ@bxC9HD_&hcPG<((vWHh#*>%3jr{j7rD?Dw*`F7yIaA=O2BJr?+ zG@J)e(Xp_B%NmmTX;t;dxk-WihK`3a0K}D~543(fjO>`ea<9I6@H7veTF8{02Wn-? z20>{ah_r=9-t+;Wsc#A*=7g|i(jb?gMO>*wR+^L}4FOoTytY}(4sbOOuBL8mwgXK4 z6h?(Wm@)_&cwR(5ouP_$rzk*YkWdW(Isu}^-g`*KF_1fQBZ{RU!K3 zUA1vS&@%$0^CBPOF^tkxjpkZ_YEKg;SXbL~G2V$67Nf&eqF*Et`nXtYWM@m+sblr26yeHfVCKSTGu> zZZI@q&==*oJZDaTR4t|3+~j-GG<(CLe$R7r-Mo@b4RSu_{0V$>3raD7#x$@Hv7~_T zoF5<$%F8zB5h6l_ZUODH(Jx*ZFQE~Z!I$+(qF&`m70`s_2h4+cDVks5WOXl@7UVb7 zOu>Cw>M~N1wrXEQjee+$r}-6+Cf}&Gw8xW z{%q7xGh$w<<-ere9R8+JaA=Z=g0p+M#qOm|3l>*ho&_$ZBi|14*|@JwL}8MbMEy;} zH;C!j^qOTv?z}|qchgQ1wrA*Mp1O*gt|!{1KRetux1)rKk>9&qYGcI0*j~0U8cmR$ z)?TKLB@TAx=;a#9?1EKAJc`99?EJaRbUZ~K`-^6sd&lkki0 ztWBqMFTcyG6@D^};8$ob63i5m4o=X>$S2BH$-E+`aXPd{#xc>xEb^c`(c|TCd7A-| zeLCrWGo&z7XBqtKk@RQ&XFyUIiMfA)mxzav^ap{YU;Yt8(uY1Ol5znb8A<=*-v~*6 z=l=_mKK-vp(kDI|l0Nr8Gm^eEGV@s2TPamQWpeg1w=ks98q}poqqP*K7%(R>xlf^N zoy%NePGLxH{2WQ`NK{H*YD2~t;Ig-P<5Xhz3su3g4} z)`Niy?^)&|tXmF(%SmWbLlmk1IPqESs!^yUB$r&Hh2CTiGuh~JgpYXCq!efcMH(RI z+96}A5u}h%N5)fGB2Qy`nlb4(9LA#0SE^Zrs7Ns$8L=Q)XdT6LmPxAYC~E_d6zj2O z&2qEb<7+uncqm;%DW2o+L;0_pBO4U`)}ahY8Dko1g4iu@k8iLBH_%`%(qHUa+mZefHg7=u zi`|NLbObBe#7fl0c3V60o(xE$*eSSnbiW!dMA$31)oh`8mnyh}WvO`@9Kk_M%sM!S zp*^J$Hn1($Xv-xbXRAgPQ2TCTfwie65Pjf{{Je2V7~nX7tHpI@|8ny?BT0nY>h%Q$`5!duu{6fFnLZE#1ewIN$amYbV(n!>!V{Qr<580FS3es#eNcXLD z9v||H{`8WSd`>HISCC!GR`V}Ph6O;iC=-GbDJ5P>mggy4L47PPU3RL4(OxNw>*)y6wkWAL_Q-{Ur7OjAo;*W6ENBkX#_$dcm zppNBxIq?%UY2v4&Zmc`%`GnY=jed>m&;oaYmol$JcZI+?V9P46+ojT*$SQxx()=1+ z!X}`I{oky&LW3Xb>+3tt@*L-Z)v-%oTsNQ>tdC9f4VU*8?w#o;((OU}0Vf3N84hZ)rW=h@FFJ>!$t+S*7cwf)Lmi>ByT2PLO8}yaTVgDTCO-p^oU`&$>@LU(7F`rHR@l)CK zDjfK2$DdE1dB)R|ck_*|&bGDmW|JklW=FImn%fpoNyyDRIuDe;@2X^5OH^&Y7xIMJ zE>N&36X`;JLXne`m(d%+5LJOrxP^`_#<_+vU@>~(FY!*v2LqSkRq^ulAuaFMzT6xpf-l7z*NJ=_tps>*U?>MMxHmaJd$MC@#*vjd>z3_Xx;vt$Zde&!y$F*6dj( zk@R6~#e0H|+tC^4H67Sm`DTm7v+YAvv&rJOK68lag+i%`OtIXjL2t2$I@{LDSKBOZ z11X;MFxb??*uu{({$bDt@3-?AUB~4*2dHQ`MC*WaMiEctf;7%PWX*I8u;gcI)V-hc=#jRiT2`O+VpwyA$b6 zZ}ls8QShY-)014sNslRa^HcsB8oYJ)8lisMW99F82|pK%bE z&{H2`Za`g{VRFj5Qjy8&@V_CzUi>mI>j1#oCOGgz0@$y9KLGZB9|qXL0Q=b=ae)2B zVSpV5*dGCaePm?jlVR^Tt@|%?b7dYezt)x@pSZXZ;iXdK=uAoSr)orag|?9}FbCtV z8I;6CBey}~j9qn6Mt^a)o(*XWZ*#if*B)94!256`(d#(b(vyq^EhqZL#DVg$GzMgaOP zpeAK6rv|ne^}}M>B!eYa3p;tEeC=`~Kg|}<)Z=KJvYR8`kzdIBWGvA_##~=59?UJl z7k~o}c(|&D#l42dvb7wyJzY)>&H!{YUsum7vjX{!j)eJ3Zi83GQG*nxj=1%$vNT6} zzMbkgh`z8Bk&cc=S7^2yM-6N;>WE9=Iy?e(!~#}*Iu3ZXx=rL0qI$ruRW;b~)e-Ug zW@q`0bSDaBU&2LMUOvZqALF{>>q+W(8@6jQL*A~zw^6kXnF@J|Drj&u6p=i)TY@89 z^_}Y3@A-=FE3}5{Nyxv%BuH>og&d`xME&gO&u_F_J&7kRQ-|XYyH7m_JX_@<3*C%* z5+}5;Kflp_^)#wN*6{8gg##Q^&qEA$$g%~g3IRzy&xP)w`_TMG`{am==j(vm6iI?K z^fBsSuf-&Td9ZBdRW1Klb2`?-hn5*)Sqc}CsVm!QXJ&qYQl4PAbDA< zdvXPd4;-&4vD)gGtmkvA^)i4@QRD#Tt`M&SqotCLM^r!fH-ziOlf|9AW)VB#5@UQQ-iI z;9;d-=x>ZupME#86j$lyxhm6vRnwsw|14cYg{<)V{0RH~)*-*8h!gf5_ge}dVT=1^ zCD*DHL4jw%tnP&hdSfY=00YbxaWz=@b#i z{ETfyXuDbUS$SGyrBiu8Q$?j)A^igjK+g6|22b3&csg~{&3TNOgRnIDN?6!h0|kd+ zm1yNzjZ@D!^_o;HERDr1<2;!``(V*(tY)n#t63}0r6sFb+Oe9wmU=lHplQ%uFeS96 zvJ85O3MH~`q~I|$sl8?-iY8{H3aO$MG&2`{gDLIRuu)m?DwNmy1-VjKup2WIDt-EU z&$RgjxM7227^SbKsAhty5LHbZ1!{*@Rkp^>_E9vGq9D+xpcVRJ8sMnF8rH6bHLPvF zoNkk**tRc7)oV9IoUh7U4sgP#5^4&MgBtsUaK8i)Jchl{)} zfyB|aeJ0B(Z{g5ej-rMLyvMPH(2-=Bai7RCZ(3wsgAf=c9|3hnBaZ_d(!Rs5Gncl*g>6~6WfHnjI z$l1&;><~|4Hc!GQgfJNKhB+fUXpgT;hqUZmyrBK7A}%L+}H zOF4A{iIU`*U*tKhp!Z5dT;1Z-C3cxxDR-#odpsnW3)@vhfQdyEoY8UHHF^7c)WM<6 zQT9EP>GzgF?r$z&Wk*p zxWX=gL*bVjOu3nSORv>U-0GW?H}!L~!ztEn_HWpYay+}m-6$ph7TWY9fMrKNuX7gs ztHe1LNv#ev2+}N_dN;QDJ8p)u_V|wUj^ELL$LVz;?fshW_?>R6#AX6T6l=?Lzg2e} zmndBFcepO|iDKVQ3r*z8UPWV>yr4VosaxoVVv>An(Zf@9^ zTV0=rPgi|dUdy+hDV9D3ptC~m^tH^z^2m*> zJ#7VP!XnRN9+)<7bv)mT7pagjKNT{dofwCrv=^}v$M_>d#70C$#kuI{{VUFw^~6~T znJxmY&VKFPk5|-dNnSC~s>|7KOx5bt+Y%C^9 zRuw75H1?CB+mkx^kY|7vMcrd@jmsi!94q6rRe_X84zV=b`TP2k4kY;3+>^Ze9KR1b zXjI+7=nrNE7QMrDrb<^yWB%bDPVPsV!w(+DZyxBqa<{+F9ko0#`h6U&>hTiF)no2N zdm3#T#-})u2Z%R)poG>Gns4{uq`1uKEuCqf(QEPJ;jmo+QA0VrGp*=FO_Mf<%TjFA zP;xEQpi+JE98rmi8YT5;k|y*MVf1U0B#l0yT~CB|V*=dgKRz<^v9PyFz*v#IbBX^_ zUJ3#fFy_2;K_#CF>K0srOs`_0N3Uo2*v-$$XJHJYFOQdjs7}^(twmF-Iyw^bW_mNj zldt9h$UJ0_h`*Bgff6A4JJ+d?QZ7=Ea*-~8Vgm&$bj6kqa#w;kCHjmVYCLL4+DF8S z3KZoidS&#xwcbZbIv3X9#2BSEA{mIHhn9IKB?~vwDjX*%t;vZb(yDzjtKHATNTQq316=QC*C z^8J3Et~vVV2rNd6xyy?!t!?!9OfW6))qD2%a3@mWm>hHUv&;?1s@)d6qB_A@hwI|) zY32R#-(bRTDbRGmgFJnxNotrLKu$1*1k1;~x`0dlBt_p^lt<`4%pkDnqacq6Obr9Spk zNx(TMcNH|-EHtx&nq~|Gl5Y2a>y1zm6Ok5^x8oj4yr-_k0)5O-gc~T|nE8PU))?PAbhV4`XU-}0U@g~SB%qqWQg0ymDp0|q(o z?nh2bReC6uj`*TodUSN%Sx8axC9;U75i&7PoQNm47w&YQk zy~GKf*V)|5?{Xkd^1f$CIbHrX(;IzgM*bE&FR>@?;1$qB3|`Vfa8}^l-Jnk?PYr zQ4B`;vcc#P<47Tzo-if{p&T}+=$j)IKKgN!-p(L6Wdx6k3r*pXIQKII!ap}M^K_a! zJV^R7amcq5_QV;NsSAJ)OXX5zp`D8!o2nqV1~irw_4vxa{~WW1NK_FNqd%Jl7~+5~ zNvR1LF*6_|Rz+uLgM1_$6m8HONq2BRVNHRj_R@isBTxX z?JeNYpq_r77FUCL(ky=ZYPFxpey?tXTJIU{Hwvfd@d;=c*_y(BZ|(-b;^?!Bb!;xm zTP~)>Bxi}04$NMtDj%<{nE_qF{j+p_JV-*`cUY(p*z+1Mv-3V=O>F`V3BByY7?gO#z>=vx4>vDcCmstRUyyz)=fOQ}o5742>!?ex*6~%ZZ z?^7E;O_K+yY4n0Mtp&+wT7#UH5eq(Rg-ogp%qZ0ToVL;FJsT!W;T>$0V;GYQI<2+b zs&&azLbgnQv|@r2dvZ~`Z_qi_B)jI z`~}N0QlouWNI_V;yEpny6(SiznNSd=+A$iGZQjYtIPVq#S=2x z1JQq2D)5SaXC9XAj3DY=JJP$ttmJh;64m`CZg1ZP!J_x;xo&{zqM5QYWn}u8Y~DqX zNQXjun{j8@1c~@hJBU?(+#rhS7Pt~=o!xv|2u)$!FjD|dDO63k38(z?>N6;Y6)4H+;HDV~H=EG=R~^+R35z*u08`=S}5 z4LlJzAaUl=f1M7VW)=V1p~fAt^T&;m6XW)Hs=ps@YFQJmCT6mcy$Z{Xj2OwyU@pPv zFyw&>COLKjvolBKIECmZmx8ld;jE6F{JBte7VtU^ScPO{Jr3ssS+BUF>$D2^2XbD~ zqS4JodL&3_{Sdxs*EEWCkX(uuAUICLRtqWVdIE%$f}7|dD3=(hMFTAcRHuQZOIQfQ zFS;J9Na#hx5&+uDIBr_>K{m!=Xik7V#9!|}_SdZ#T?od6nQX42GT`p&`qx#D(Mzz> zB|cOfLy8O=K+OOj6SPZzYYzj8Y2D^1?x-H2UY+#9dB{vA3gB{q*m2gf$EZn5K<@fc z`RYoZm>_2ZJ4lA=HX37;KYBI$<^S=>=A?1#9Qe?>Qr}INpQSsRNfzV8`WkTficM1>SX%EiSVuy f^U9>d$ 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 z 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 YZH>cb*@y!X|lWg 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