From e76cd63ee394574f04535433d46bc1872278a158 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 30 Dec 2022 20:29:28 -0600 Subject: [PATCH 001/103] create new state system --- packages/native-core/src/lib.rs | 6 +- packages/native-core/src/passes.rs | 594 ++++++++++-------- packages/native-core/src/real_dom.rs | 48 +- packages/native-core/src/state.rs | 20 +- packages/native-core/src/tree.rs | 14 + .../src/utils/persistant_iterator.rs | 32 +- 6 files changed, 413 insertions(+), 301 deletions(-) diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index ae739af86..1a449ca07 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -1,16 +1,14 @@ use std::hash::BuildHasherDefault; pub use node_ref::NodeMask; -pub use passes::{ - AnyPass, DownwardPass, MemberMask, NodePass, Pass, PassId, PassReturn, UpwardPass, -}; +pub use passes::Pass; use rustc_hash::FxHasher; pub use tree::NodeId; pub mod layout_attributes; pub mod node; pub mod node_ref; -pub mod passes; +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 index bdc7c7e5b..746cb6cc8 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -1,11 +1,308 @@ -use crate::tree::{NodeId, TreeView}; -use crate::{FxDashMap, FxDashSet, SendAnyMap}; -use rustc_hash::{FxHashMap, FxHashSet}; +use anymap::any; +use anymap::AnyMap; +use rustc_hash::FxHashMap; +use rustc_hash::FxHashSet; +use std::any::{Any, TypeId}; use std::collections::BTreeMap; use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign}; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; +use crate::node::Node; +use crate::node_ref::NodeView; +use crate::state::State; +use crate::tree::{Tree, TreeView}; +use crate::{FxDashMap, FxDashSet, SendAnyMap}; +use crate::{NodeId, NodeMask}; + +pub trait Pass: Any { + /// This is a tuple of (T: Any, ..) + type ParentDependencies: Dependancy; + /// This is a tuple of (T: Any, ..) + type ChildDependencies: Dependancy; + /// This is a tuple of (T: Any, ..) + type NodeDependencies: Dependancy; + /// This is a tuple of (T: Any, ..) + type Ctx: Dependancy; + const MASK: NodeMask; + + fn pass<'a>( + &mut self, + node_view: NodeView, + node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + children: Option< + impl Iterator::ElementBorrowed<'a>>, + >, + context: ::ElementBorrowed<'a>, + ) -> bool; + + fn is_valid(&self) -> bool { + // no type can be a child and parent dependency + for type_id in Self::parent_type_ids() { + for type_id2 in Self::child_type_ids() { + if type_id == type_id2 { + return false; + } + } + } + // this type should not be a node dependency + for type_id in Self::node_type_ids() { + if type_id == TypeId::of::() { + return false; + } + } + // no states have the same type id + if Self::all_dependanices() + .into_iter() + .collect::>() + .len() + != Self::all_dependanices().len() + { + return false; + } + true + } + + fn to_type_erased() -> TypeErasedPass + where + Self: Sized, + { + TypeErasedPass { + this_type_id: TypeId::of::(), + parent_type_ids: Self::parent_type_ids().into_iter().collect(), + child_type_ids: Self::child_type_ids().into_iter().collect(), + node_type_ids: Self::node_type_ids().into_iter().collect(), + combined_dependancy_type_ids: Self::all_dependanices().into_iter().collect(), + dependants: FxHashSet::default(), + mask: Self::MASK, + pass_direction: Self::pass_direction(), + pass: Box::new( + |node_id: NodeId, any_map: &mut Tree>, context: &SendAnyMap| { + let (current_node, parent, children) = any_map + .node_parent_children_mut(node_id) + .expect("tried to run pass on node that does not exist"); + let current_node_raw = current_node as *mut Node; + let node = Self::NodeDependencies::borrow_elements_from(¤t_node.state) + .expect("tried to get a pass that does not exist"); + let parent = parent.map(|parent| { + Self::ParentDependencies::borrow_elements_from(&parent.state) + .expect("tried to get a pass that does not exist") + }); + let children = children.map(|children| { + children.map(|child| { + Self::ChildDependencies::borrow_elements_from(&child.state) + .expect("tried to get a pass that does not exist") + }) + }); + // safety: we have varified the pass is valid in the is_valid function + let myself: &mut Self = unsafe { + (*current_node_raw) + .state + .get_mut() + .expect("tried to get a pass that does not exist") + }; + let context = Self::Ctx::borrow_elements_from(context) + .expect("tried to get a pass that does not exist"); + myself.pass( + NodeView::new(¤t_node.node_data, Self::MASK), + node, + parent, + children, + context, + ) + }, + ) + as Box>, &SendAnyMap) -> bool + Send + Sync>, + } + } + + fn parent_type_ids() -> Vec { + Self::ParentDependencies::type_ids() + } + + fn child_type_ids() -> Vec { + Self::ChildDependencies::type_ids() + } + + fn node_type_ids() -> Vec { + Self::NodeDependencies::type_ids() + } + + fn all_dependanices() -> Vec { + let mut dependencies = Self::parent_type_ids(); + dependencies.extend(Self::child_type_ids()); + dependencies.extend(Self::node_type_ids()); + dependencies + } + + fn pass_direction() -> PassDirection { + if Self::child_type_ids() + .into_iter() + .any(|type_id| type_id == TypeId::of::()) + { + PassDirection::ChildToParent + } else if Self::parent_type_ids() + .into_iter() + .any(|type_id| type_id == TypeId::of::()) + { + PassDirection::ParentToChild + } else { + PassDirection::AnyOrder + } + } +} + +pub struct TypeErasedPass { + pub(crate) this_type_id: TypeId, + parent_type_ids: FxHashSet, + child_type_ids: FxHashSet, + node_type_ids: FxHashSet, + combined_dependancy_type_ids: FxHashSet, + pub(crate) dependants: FxHashSet, + pub(crate) mask: NodeMask, + pass: PassCallback, + pass_direction: PassDirection, +} + +impl TypeErasedPass { + fn resolve( + &self, + tree: &mut Tree>, + mut dirty: DirtyNodes, + dirty_states: &DirtyNodeStates, + nodes_updated: &FxDashSet, + ctx: &SendAnyMap, + ) { + match self.pass_direction { + PassDirection::ParentToChild => { + while let Some(id) = dirty.pop_front() { + if (self.pass)(id, tree, ctx) { + nodes_updated.insert(id); + for id in tree.children_ids(id).unwrap() { + for dependant in &self.dependants { + dirty_states.insert(*dependant, *id); + } + + let height = tree.height(*id).unwrap(); + dirty.insert(height, *id); + } + } + } + } + PassDirection::ChildToParent => { + while let Some(id) = dirty.pop_back() { + if (self.pass)(id, tree, ctx) { + nodes_updated.insert(id); + if let Some(id) = tree.parent_id(id) { + for dependant in &self.dependants { + dirty_states.insert(*dependant, id); + } + + let height = tree.height(id).unwrap(); + dirty.insert(height, id); + } + } + } + } + PassDirection::AnyOrder => { + while let Some(id) = dirty.pop_back() { + if (self.pass)(id, tree, ctx) { + nodes_updated.insert(id); + for dependant in &self.dependants { + dirty_states.insert(*dependant, id); + } + } + } + } + } + } +} + +pub enum PassDirection { + ParentToChild, + ChildToParent, + AnyOrder, +} + +type PassCallback = Box>, &SendAnyMap) -> bool + Send + Sync>; + +pub trait AnyMapLike { + fn get(&self) -> Option<&T>; + fn get_mut(&mut self) -> Option<&mut T>; +} + +impl AnyMapLike for AnyMap { + fn get(&self) -> Option<&T> { + self.get() + } + + fn get_mut(&mut self) -> Option<&mut T> { + self.get_mut() + } +} + +impl AnyMapLike for SendAnyMap { + fn get(&self) -> Option<&T> { + todo!() + } + + fn get_mut(&mut self) -> Option<&mut T> { + todo!() + } +} + +pub trait Dependancy { + type ElementBorrowed<'a> + where + Self: 'a; + + fn borrow_elements(&self) -> Self::ElementBorrowed<'_>; + fn borrow_elements_from(map: &T) -> Option>; + fn type_ids() -> Vec; +} + +macro_rules! impl_dependancy { + ($($t:ident),*) => { + impl< $($t: Any),* > Dependancy 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,)*) + } + + #[allow(unused_variables, clippy::unused_unit, non_snake_case)] + fn borrow_elements_from(map: &T) -> Option> { + Some(($(map.get::<$t>()?,)*)) + } + + fn type_ids() -> Vec { + vec![$(TypeId::of::<$t>()),*] + } + } + }; +} + +impl_dependancy!(); +impl_dependancy!(A); +impl_dependancy!(A, B); +impl_dependancy!(A, B, C); +impl_dependancy!(A, B, C, D); +impl_dependancy!(A, B, C, D, E); +impl_dependancy!(A, B, C, D, E, F); +impl_dependancy!(A, B, C, D, E, F, G); +impl_dependancy!(A, B, C, D, E, F, G, H); +impl_dependancy!(A, B, C, D, E, F, G, H, I); +impl_dependancy!(A, B, C, D, E, F, G, H, I, J); +impl_dependancy!(A, B, C, D, E, F, G, H, I, J, K); +impl_dependancy!(A, B, C, D, E, F, G, H, I, J, K, L); +impl_dependancy!(A, B, C, D, E, F, G, H, I, J, K, L, M); +impl_dependancy!(A, B, C, D, E, F, G, H, I, J, K, L, M, N); +impl_dependancy!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O); +impl_dependancy!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P); + #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct DirtyNodes { map: BTreeMap>, @@ -57,11 +354,11 @@ fn dirty_nodes() { #[derive(Default)] pub struct DirtyNodeStates { - dirty: FxDashMap>, + dirty: FxDashMap>, } impl DirtyNodeStates { - pub 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 { @@ -71,290 +368,71 @@ impl DirtyNodeStates { this } - 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; - 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); - } + pub fn insert(&self, pass_id: TypeId, node_id: NodeId) { + if let Some(mut dirty) = self.dirty.get_mut(&node_id) { + dirty.insert(pass_id); } 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); + let mut v = FxHashSet::default(); + v.insert(pass_id); self.dirty.insert(node_id, v); } } - 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; + fn all_dirty(&self, pass_id: TypeId, dirty_nodes: &mut DirtyNodes, tree: &impl TreeView) { 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 { - dirty_nodes.insert(tree.height(*node_id).unwrap(), *node_id); - } + if dirty.contains(&pass_id) { + dirty_nodes.insert(tree.height(*node_id).unwrap(), *node_id); } } } } -#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, PartialOrd, Ord)] -pub struct PassId(pub u64); - -#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Default)] -pub struct MemberMask(pub 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 { - pub progress: bool, - pub 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, - ctx: &SendAnyMap, - ) -> PassReturn; -} - -fn resolve_upward_pass + ?Sized>( - tree: &mut impl TreeView, - 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, 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() { - 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>, ctx: &SendAnyMap) -> PassReturn; -} - -fn resolve_downward_pass + ?Sized>( - tree: &mut impl TreeView, - 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, ctx); - 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() { - 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, ctx: &SendAnyMap) -> bool; -} - -fn resolve_node_pass + ?Sized>( - tree: &mut impl TreeView, - 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, ctx) { - nodes_updated.insert(id); - for dependant in pass.dependants() { - dirty_states.insert(*dependant, id); - } - } - } -} - -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 { - pub 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(), - } - } - - pub 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, - nodes_updated: &FxDashSet, - ctx: &SendAnyMap, - ) { - match self { - 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) - } - } - } -} - -struct RawPointer(*mut T); -unsafe impl Send for RawPointer {} -unsafe impl Sync for RawPointer {} - -pub fn resolve_passes>( - tree: &mut Tr, +pub fn resolve_passes( + tree: &mut Tree>, dirty_nodes: DirtyNodeStates, - mut passes: Vec<&AnyPass>, + mut passes: &[TypeErasedPass], ctx: SendAnyMap, ) -> FxDashSet { let dirty_states = Arc::new(dirty_nodes); - let mut resolved_passes: FxHashSet = FxHashSet::default(); + 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(); + let mut pass_indexes_remaining: Vec<_> = (0..passes.len()).collect::>(); + while !pass_indexes_remaining.is_empty() { + let mut currently_in_use = FxHashSet::::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(); + while i < pass_indexes_remaining.len() { + let passes_idx = pass_indexes_remaining[i]; + let pass = &passes[passes_idx]; + let pass_id = pass.this_type_id; + // check if the pass is ready to be run if pass - .dependancies() + .combined_dependancy_type_ids .iter() .all(|d| resolved_passes.contains(d) || *d == pass_id) - && !pass_mask.overlaps(currently_borrowed) { - let pass = passes.remove(i); + pass_indexes_remaining.remove(i); resolving.push(pass_id); - currently_borrowed |= pass_mask; - let tree_mut = tree as *mut _; - let raw_ptr = RawPointer(tree_mut); + currently_in_use.insert(pass.this_type_id); + // 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_unbounded_mut = unsafe { &mut *(tree as *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; + s.spawn(move || { let mut dirty = DirtyNodes::default(); - dirty_states.all_dirty(pass_id, &mut dirty, tree_mut); - pass.resolve(tree_mut, dirty, &dirty_states, &nodes_updated, &ctx); + dirty_states.all_dirty(pass_id, &mut dirty, tree_unbounded_mut); + pass.resolve( + tree_unbounded_mut, + dirty, + &dirty_states, + &nodes_updated, + &ctx, + ); }); } else { i += 1; diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 00b9afb03..dba68d618 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -5,7 +5,7 @@ use std::ops::{Deref, DerefMut, Index, IndexMut}; use crate::node::{Node, NodeType, OwnedAttributeDiscription, OwnedAttributeValue}; use crate::node_ref::{AttributeMask, NodeMask}; -use crate::passes::DirtyNodeStates; +use crate::passes::{resolve_passes, DirtyNodeStates, TypeErasedPass}; use crate::state::State; use crate::tree::{NodeId, Tree, TreeLike, TreeView}; use crate::{FxDashSet, RealNodeId, SendAnyMap}; @@ -25,8 +25,7 @@ fn mark_dirty( /// 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. -#[derive(Debug)] -pub struct RealDom { +pub struct RealDom { pub tree: Tree>, /// a map from element id to real node id node_id_mapping: Vec>, @@ -34,15 +33,29 @@ pub struct RealDom { stack: Vec, templates: FxHashMap>, root_initialized: bool, + pub(crate) passes: Box<[TypeErasedPass]>, } -impl Default for RealDom { +impl Debug for RealDom { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RealDom") + .field("tree", &self.tree) + .field("node_id_mapping", &self.node_id_mapping) + .field("nodes_listening", &self.nodes_listening) + .field("stack", &self.stack) + .field("templates", &self.templates) + .field("root_initialized", &self.root_initialized) + .finish() + } +} + +impl Default for RealDom { fn default() -> Self { Self::new() } } -impl RealDom { +impl RealDom { pub fn new() -> RealDom { let mut root = Node::new(NodeType::Element { tag: "Root".to_string(), @@ -55,6 +68,8 @@ impl RealDom { let root_id = tree.root(); tree.get_mut(root_id).unwrap().node_data.node_id = root_id; + let passes = S::create_passes(); + RealDom { tree, node_id_mapping: vec![Some(root_id)], @@ -62,6 +77,7 @@ impl RealDom { stack: vec![root_id], templates: FxHashMap::default(), root_initialized: false, + passes, } } @@ -346,9 +362,9 @@ impl RealDom { 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); + for pass in &*self.passes { + if mask.overlaps(&pass.mask) { + dirty_nodes.insert(pass.this_type_id, n); } } } @@ -363,7 +379,9 @@ impl RealDom { nodes_updated: DirtyNodeStates, ctx: SendAnyMap, ) -> FxDashSet { - S::update(nodes_updated, &mut self.tree, ctx) + let tree = &mut self.tree; + let passes = &self.passes; + resolve_passes(tree, nodes_updated, passes, 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. @@ -415,7 +433,7 @@ impl RealDom { } } -impl Deref for RealDom { +impl Deref for RealDom { type Target = Tree>; fn deref(&self) -> &Self::Target { @@ -423,13 +441,13 @@ impl Deref for RealDom { } } -impl DerefMut for RealDom { +impl DerefMut for RealDom { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.tree } } -impl Index for RealDom { +impl Index for RealDom { type Output = Node; fn index(&self, id: ElementId) -> &Self::Output { @@ -437,7 +455,7 @@ impl Index for RealDom { } } -impl Index for RealDom { +impl Index for RealDom { type Output = Node; fn index(&self, idx: RealNodeId) -> &Self::Output { @@ -445,13 +463,13 @@ impl Index for RealDom { } } -impl IndexMut for RealDom { +impl IndexMut for RealDom { 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 { +impl IndexMut for RealDom { fn index_mut(&mut self, idx: RealNodeId) -> &mut Self::Output { self.tree.get_mut(idx).unwrap() } diff --git a/packages/native-core/src/state.rs b/packages/native-core/src/state.rs index 7e6c5266a..beabb54c2 100644 --- a/packages/native-core/src/state.rs +++ b/packages/native-core/src/state.rs @@ -2,8 +2,8 @@ use std::cmp::Ordering; use crate::node::Node; use crate::node_ref::{NodeMask, NodeView}; -use crate::passes::{resolve_passes, AnyPass, DirtyNodeStates}; -use crate::tree::TreeView; +use crate::passes::{resolve_passes, AnyMapLike, DirtyNodeStates, Pass, TypeErasedPass}; +use crate::tree::{Tree, TreeView}; use crate::{FxDashSet, RealNodeId, SendAnyMap}; /// Join two sorted iterators @@ -210,21 +210,9 @@ 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 + 'static { +pub trait State: Default + Clone + AnyMapLike + 'static { #[doc(hidden)] - const PASSES: &'static [AnyPass>]; - #[doc(hidden)] - const MASKS: &'static [NodeMask]; - - #[doc(hidden)] - fn update>>( - dirty: DirtyNodeStates, - tree: &mut T, - ctx: SendAnyMap, - ) -> FxDashSet { - let passes = Self::PASSES.iter().collect(); - resolve_passes(tree, dirty, passes, ctx) - } + fn create_passes() -> Box<[TypeErasedPass]>; } impl ChildDepState for () { diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index 0547fc3a9..0824e0b51 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -121,6 +121,19 @@ pub trait TreeView: Sized { } } + fn node_parent_children_mut( + &mut self, + id: NodeId, + ) -> Option<(&mut T, Option<&mut T>, Option>)> { + let mut_ptr: *mut Self = self; + unsafe { + // Safety: No node has itself as a parent or child. + (*mut_ptr) + .get_mut(id) + .map(|node| (node, (*mut_ptr).parent_mut(id), (*mut_ptr).children_mut(id))) + } + } + fn parent_id(&self, id: NodeId) -> Option; fn height(&self, id: NodeId) -> Option; @@ -824,6 +837,7 @@ fn map() { Self { value } } } + let mut tree = Tree::new(Value::new(1)); let parent = tree.root(); let child = tree.create_node(Value::new(0)); diff --git a/packages/native-core/src/utils/persistant_iterator.rs b/packages/native-core/src/utils/persistant_iterator.rs index 75bcf9064..6db60f086 100644 --- a/packages/native-core/src/utils/persistant_iterator.rs +++ b/packages/native-core/src/utils/persistant_iterator.rs @@ -1,4 +1,11 @@ -use crate::{node::NodeType, real_dom::RealDom, state::State, tree::TreeView, NodeId, RealNodeId}; +use crate::{ + node::NodeType, + passes::{AnyMapLike, TypeErasedPass}, + real_dom::RealDom, + state::State, + tree::TreeView, + NodeId, RealNodeId, +}; use dioxus_core::{Mutation, Mutations}; use std::fmt::Debug; @@ -67,7 +74,7 @@ impl PersistantElementIter { /// remove stale element refreneces /// returns true if the focused element is removed - pub fn prune(&mut self, mutations: &Mutations, rdom: &RealDom) -> bool { + pub fn prune(&mut self, mutations: &Mutations, rdom: &RealDom) -> bool { let mut changed = false; let ids_removed: Vec<_> = mutations .edits @@ -124,7 +131,7 @@ impl PersistantElementIter { } /// get the next element - pub fn next(&mut self, rdom: &RealDom) -> ElementProduced { + pub fn next(&mut self, rdom: &RealDom) -> ElementProduced { if self.stack.is_empty() { let id = NodeId(0); let new = (id, NodePosition::AtNode); @@ -160,9 +167,9 @@ impl PersistantElementIter { } /// get the previous element - pub fn prev(&mut self, rdom: &RealDom) -> ElementProduced { + pub fn prev(&mut self, rdom: &RealDom) -> ElementProduced { // recursively add the last child element to the stack - fn push_back( + fn push_back( stack: &mut smallvec::SmallVec<[(RealNodeId, NodePosition); 5]>, new_node: RealNodeId, rdom: &RealDom, @@ -228,10 +235,19 @@ impl PersistantElementIter { #[derive(Default, Clone, Debug)] struct Empty {} -impl State for Empty { - const PASSES: &'static [crate::AnyPass>] = &[]; +impl AnyMapLike for Empty { + fn get(&self) -> Option<&T> { + None + } - const MASKS: &'static [crate::NodeMask] = &[]; + fn get_mut(&mut self) -> Option<&mut T> { + None + } +} +impl State for Empty { + fn create_passes() -> Box<[TypeErasedPass]> { + Box::new([]) + } } #[test] From 9e971fbe117307845711b7bfc2081d559f0ce59c Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 31 Dec 2022 22:34:16 -0600 Subject: [PATCH 002/103] work on integrating new pass system --- packages/native-core/src/lib.rs | 6 +- packages/native-core/src/node.rs | 21 +- packages/native-core/src/node_ref.rs | 65 +- packages/native-core/src/passes.rs | 1208 +++++++++-------- packages/native-core/src/real_dom.rs | 464 +++++-- packages/native-core/src/state.rs | 19 +- packages/native-core/src/tree.rs | 545 ++------ .../src/utils/persistant_iterator.rs | 69 +- packages/tui/src/focus.rs | 12 +- 9 files changed, 1251 insertions(+), 1158 deletions(-) diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 1a449ca07..6e2f7a166 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -14,8 +14,6 @@ pub mod state; pub mod tree; pub mod utils; -/// A id for a node that lives in the real dom. -pub type RealNodeId = NodeId; pub type FxDashMap = dashmap::DashMap>; pub type FxDashSet = dashmap::DashSet>; pub type SendAnyMap = anymap::Map; @@ -25,11 +23,11 @@ pub type SendAnyMap = anymap::Map; #[doc(hidden)] pub struct HeightOrdering { pub height: u16, - pub id: RealNodeId, + pub id: NodeId, } impl HeightOrdering { - pub fn new(height: u16, id: RealNodeId) -> Self { + pub fn new(height: u16, id: NodeId) -> Self { HeightOrdering { height, id } } } diff --git a/packages/native-core/src/node.rs b/packages/native-core/src/node.rs index 4530fd350..58d378596 100644 --- a/packages/native-core/src/node.rs +++ b/packages/native-core/src/node.rs @@ -21,23 +21,24 @@ pub struct NodeData { pub node_type: NodeType, } +#[derive(Debug, Clone)] +pub struct ElementNode { + pub tag: String, + pub namespace: Option, + pub attributes: FxHashMap, + pub listeners: FxHashSet, +} + /// 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, - attributes: FxHashMap, - listeners: FxHashSet, - }, + Text(String), + Element(ElementNode), Placeholder, } impl Node { - pub(crate) fn new(node_type: NodeType) -> Self { + pub fn new(node_type: NodeType) -> Self { Node { state: S::default(), node_data: NodeData { diff --git a/packages/native-core/src/node_ref.rs b/packages/native-core/src/node_ref.rs index d178c2d18..1bd361175 100644 --- a/packages/native-core/src/node_ref.rs +++ b/packages/native-core/src/node_ref.rs @@ -1,9 +1,9 @@ use dioxus_core::ElementId; use crate::{ - node::{NodeData, NodeType, OwnedAttributeView}, + node::{ElementNode, NodeData, NodeType, OwnedAttributeView}, state::union_ordered_iter, - RealNodeId, + NodeId, }; /// A view into a [VNode] with limited access. @@ -28,7 +28,7 @@ impl<'a> NodeView<'a> { } /// Get the node id of the node - pub fn node_id(&self) -> RealNodeId { + pub fn node_id(&self) -> NodeId { self.inner.node_id } @@ -37,7 +37,7 @@ impl<'a> NodeView<'a> { self.mask .tag .then_some(match &self.inner.node_type { - NodeType::Element { tag, .. } => Some(&**tag), + NodeType::Element(ElementNode { tag, .. }) => Some(&**tag), _ => None, }) .flatten() @@ -48,7 +48,7 @@ impl<'a> NodeView<'a> { self.mask .namespace .then_some(match &self.inner.node_type { - NodeType::Element { namespace, .. } => namespace.as_deref(), + NodeType::Element(ElementNode { namespace, .. }) => namespace.as_deref(), _ => None, }) .flatten() @@ -57,7 +57,7 @@ impl<'a> NodeView<'a> { /// Get any attributes that are enabled in the mask pub fn attributes<'b>(&'b self) -> Option> + 'b> { match &self.inner.node_type { - NodeType::Element { attributes, .. } => Some( + NodeType::Element(ElementNode { attributes, .. }) => Some( attributes .iter() .filter(move |(attr, _)| self.mask.attritutes.contains_attribute(&attr.name)) @@ -75,7 +75,7 @@ impl<'a> NodeView<'a> { self.mask .text .then_some(match &self.inner.node_type { - NodeType::Text { text } => Some(&**text), + NodeType::Text(text) => Some(&**text), _ => None, }) .flatten() @@ -85,7 +85,9 @@ impl<'a> NodeView<'a> { pub fn listeners(&self) -> Option + '_> { if self.mask.listeners { match &self.inner.node_type { - NodeType::Element { listeners, .. } => Some(listeners.iter().map(|l| &**l)), + NodeType::Element(ElementNode { listeners, .. }) => { + Some(listeners.iter().map(|l| &**l)) + } _ => None, } } else { @@ -238,6 +240,28 @@ impl NodeMask { .with_element() .with_listeners(); + pub fn from_node(node: &NodeType) -> Self { + match node { + NodeType::Text { .. } => Self::new().with_text(), + NodeType::Element(ElementNode { + tag: _, + namespace: _, + attributes, + listeners, + }) => { + let mut mask = Self::new_with_attrs(AttributeMask::Dynamic( + attributes.keys().map(|key| key.name.clone()).collect(), + )) + .with_element(); + if !listeners.is_empty() { + mask = mask.with_listeners(); + } + mask + } + NodeType::Placeholder => Self::new().with_element(), + } + } + /// Check if two masks overlap pub fn overlaps(&self, other: &Self) -> bool { (self.tag && other.tag) @@ -280,12 +304,22 @@ impl NodeMask { self } + /// Set the mask to view the tag + pub fn set_tag(&mut self) { + self.tag = true; + } + /// Allow the mask to view the namespace pub const fn with_namespace(mut self) -> Self { self.namespace = true; self } + /// Set the mask to view the namespace + pub fn set_namespace(&mut self) { + self.namespace = true; + } + /// Allow the mask to view the namespace and tag pub const fn with_element(self) -> Self { self.with_namespace().with_tag() @@ -297,9 +331,24 @@ impl NodeMask { self } + /// Set the mask to view the text + pub fn set_text(&mut self) { + self.text = true; + } + /// Allow the mask to view the listeners pub const fn with_listeners(mut self) -> Self { self.listeners = true; self } + + /// Set the mask to view the listeners + pub fn set_listeners(&mut self) { + self.listeners = true; + } + + /// Allow the mask to view the given attributes + pub fn add_attributes(&mut self, attributes: AttributeMask) { + self.attritutes = self.attritutes.union(&attributes); + } } diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index 746cb6cc8..8640e8fc4 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -1,16 +1,14 @@ -use anymap::any; use anymap::AnyMap; use rustc_hash::FxHashMap; use rustc_hash::FxHashSet; use std::any::{Any, TypeId}; use std::collections::BTreeMap; -use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign}; -use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; use crate::node::Node; use crate::node_ref::NodeView; use crate::state::State; +use crate::tree::TreeViewMut; use crate::tree::{Tree, TreeView}; use crate::{FxDashMap, FxDashSet, SendAnyMap}; use crate::{NodeId, NodeMask}; @@ -70,9 +68,6 @@ pub trait Pass: Any { { TypeErasedPass { this_type_id: TypeId::of::(), - parent_type_ids: Self::parent_type_ids().into_iter().collect(), - child_type_ids: Self::child_type_ids().into_iter().collect(), - node_type_ids: Self::node_type_ids().into_iter().collect(), combined_dependancy_type_ids: Self::all_dependanices().into_iter().collect(), dependants: FxHashSet::default(), mask: Self::MASK, @@ -155,10 +150,7 @@ pub trait Pass: Any { pub struct TypeErasedPass { pub(crate) this_type_id: TypeId, - parent_type_ids: FxHashSet, - child_type_ids: FxHashSet, - node_type_ids: FxHashSet, - combined_dependancy_type_ids: FxHashSet, + pub(crate) combined_dependancy_type_ids: FxHashSet, pub(crate) dependants: FxHashSet, pub(crate) mask: NodeMask, pass: PassCallback, @@ -392,7 +384,7 @@ impl DirtyNodeStates { pub fn resolve_passes( tree: &mut Tree>, dirty_nodes: DirtyNodeStates, - mut passes: &[TypeErasedPass], + passes: &[TypeErasedPass], ctx: SendAnyMap, ) -> FxDashSet { let dirty_states = Arc::new(dirty_nodes); @@ -446,539 +438,661 @@ pub fn resolve_passes( std::sync::Arc::try_unwrap(nodes_updated).unwrap() } -#[test] -fn node_pass() { - use crate::tree::{Tree, TreeLike}; - let mut tree = Tree::new(0); - - struct AddPass; - impl Pass for AddPass { - 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 for AddPass { - fn pass(&self, node: &mut i32, _: &SendAnyMap) -> bool { - *node += 1; - true - } - } - - 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, SendAnyMap::new()); - - assert_eq!(tree.get(tree.root()).unwrap(), &1); -} - -#[test] -fn dependant_node_pass() { - use crate::tree::{Tree, TreeLike}; - let mut tree = Tree::new(0); - - 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(0) - } - } - - impl NodePass for AddPass { - fn pass(&self, node: &mut i32, _: &SendAnyMap) -> bool { - *node += 1; - 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(0) - } - } - impl NodePass for SubtractPass { - fn pass(&self, node: &mut i32, _: &SendAnyMap) -> bool { - *node -= 1; - true - } - } - - 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, SendAnyMap::new()); - - 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), _: &SendAnyMap) -> 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), _: &SendAnyMap) -> bool { - node.1 += 1; - true - } - } - - 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, SendAnyMap::new()); - - assert_eq!(tree.get(tree.root()).unwrap(), &(1, 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 Pass for AddPass { - fn pass_id(&self) -> PassId { - PassId(0) - } - - fn dependancies(&self) -> &'static [PassId] { - &[] - } - - fn dependants(&self) -> &'static [PassId] { - &[] - } - - fn mask(&self) -> MemberMask { - MemberMask(0) - } - } - impl DownwardPass for AddPass { - fn pass(&self, node: &mut i32, parent: Option<&mut i32>, _: &SendAnyMap) -> PassReturn { - if let Some(parent) = parent { - *node += *parent; - } - PassReturn { - progress: true, - mark_dirty: true, - } - } - } - - 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, SendAnyMap::new()); - - 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 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); - - 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(0) - } - } - impl DownwardPass for AddPass { - fn pass(&self, node: &mut i32, parent: Option<&mut i32>, _: &SendAnyMap) -> PassReturn { - if let Some(parent) = parent { - *node += *parent; - } else { - } - 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(0) - } - } - impl DownwardPass for SubtractPass { - fn pass(&self, node: &mut i32, parent: Option<&mut i32>, _: &SendAnyMap) -> PassReturn { - if let Some(parent) = parent { - *node -= *parent; - } else { - } - PassReturn { - progress: true, - mark_dirty: true, - } - } - } - - 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, SendAnyMap::new()); - - // 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}; - // 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 Pass for AddPass { - fn pass_id(&self) -> PassId { - PassId(0) - } - - fn dependancies(&self) -> &'static [PassId] { - &[] - } - - fn dependants(&self) -> &'static [PassId] { - &[] - } - - fn mask(&self) -> MemberMask { - MemberMask(0) - } - } - impl UpwardPass for AddPass { - fn pass<'a>( - &self, - node: &mut i32, - children: &mut dyn Iterator, - _: &SendAnyMap, - ) -> PassReturn { - *node += children.map(|i| *i).sum::(); - PassReturn { - progress: true, - mark_dirty: true, - } - } - } - - 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, SendAnyMap::new()); - - 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); -} - -#[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(0) - } - } - impl UpwardPass for AddPass { - fn pass<'a>( - &self, - node: &mut i32, - children: &mut dyn Iterator, - _: &SendAnyMap, - ) -> 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(0) - } - } - impl UpwardPass for SubtractPass { - fn pass<'a>( - &self, - node: &mut i32, - children: &mut dyn Iterator, - _: &SendAnyMap, - ) -> PassReturn { - *node -= children.map(|i| *i).sum::(); - PassReturn { - progress: true, - mark_dirty: true, - } - } - } - - 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, SendAnyMap::new()); - - // 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); -} +// #[test] +// fn node_pass() { +// use crate::real_dom::RealDom; +// use crate::tree::{Tree, TreeLike}; + +// #[derive(Debug, Default, Clone, PartialEq)] +// struct Number(i32); + +// impl State for Number { +// fn create_passes() -> Box<[TypeErasedPass]> { +// Box::new([Number::to_type_erased()]) +// } +// } + +// impl AnyMapLike for Number { +// fn get(&self) -> Option<&T> { +// if TypeId::of::() == TypeId::of::() { +// Some(unsafe { &*(self as *const Self as *const T) }) +// } else { +// None +// } +// } + +// fn get_mut(&mut self) -> Option<&mut T> { +// if TypeId::of::() == TypeId::of::() { +// Some(unsafe { &mut *(self as *mut Self as *mut T) }) +// } else { +// None +// } +// } +// } + +// impl Pass for Number { +// type ChildDependencies = (); +// type NodeDependencies = (); +// type ParentDependencies = (); +// type Ctx = (); +// const MASK: NodeMask = NodeMask::new(); + +// fn pass<'a>( +// &mut self, +// node_view: NodeView, +// node: ::ElementBorrowed<'a>, +// parent: Option<::ElementBorrowed<'a>>, +// children: Option< +// impl Iterator::ElementBorrowed<'a>>, +// >, +// context: ::ElementBorrowed<'a>, +// ) -> bool { +// self.0 += 1; +// true +// } +// } + +// let mut tree: RealDom = RealDom::new(); +// tree.dirty_nodes.insert(TypeId::of::(), NodeId(0)); +// tree.update_state(SendAnyMap::new()); + +// assert_eq!(tree.get(tree.root()).unwrap().state.0, 1); +// } + +// #[test] +// fn dependant_node_pass() { +// use crate::real_dom::RealDom; + +// #[derive(Debug, Default, Clone, PartialEq)] +// struct AddNumber(i32); + +// impl Pass for AddNumber { +// type ChildDependencies = (); +// type NodeDependencies = (SubtractNumber,); +// type ParentDependencies = (); +// type Ctx = (); +// const MASK: NodeMask = NodeMask::new(); + +// fn pass<'a>( +// &mut self, +// node_view: NodeView, +// node: ::ElementBorrowed<'a>, +// parent: Option<::ElementBorrowed<'a>>, +// children: Option< +// impl Iterator::ElementBorrowed<'a>>, +// >, +// context: ::ElementBorrowed<'a>, +// ) -> bool { +// self.0 += 1; +// true +// } +// } + +// #[derive(Debug, Default, Clone, PartialEq)] +// struct SubtractNumber(i32); + +// impl Pass for SubtractNumber { +// type ChildDependencies = (); +// type NodeDependencies = (); +// type ParentDependencies = (); +// type Ctx = (); +// const MASK: NodeMask = NodeMask::new(); + +// fn pass<'a>( +// &mut self, +// node_view: NodeView, +// node: ::ElementBorrowed<'a>, +// parent: Option<::ElementBorrowed<'a>>, +// children: Option< +// impl Iterator::ElementBorrowed<'a>>, +// >, +// context: ::ElementBorrowed<'a>, +// ) -> bool { +// self.0 -= 1; +// true +// } +// } + +// #[derive(Debug, Default, Clone, PartialEq)] +// struct NumberState { +// add_number: AddNumber, +// subtract_number: SubtractNumber, +// } + +// impl AnyMapLike for NumberState { +// fn get(&self) -> Option<&T> { +// if TypeId::of::() == TypeId::of::() { +// Some(unsafe { &*(&self.add_number as *const AddNumber as *const T) }) +// } else if TypeId::of::() == TypeId::of::() { +// Some(unsafe { &*(&self.subtract_number as *const SubtractNumber as *const T) }) +// } else { +// None +// } +// } + +// fn get_mut(&mut self) -> Option<&mut T> { +// if TypeId::of::() == TypeId::of::() { +// Some(unsafe { &mut *(&mut self.add_number as *mut AddNumber as *mut T) }) +// } else if TypeId::of::() == TypeId::of::() { +// Some(unsafe { &mut *(&mut self.subtract_number as *mut SubtractNumber as *mut T) }) +// } else { +// None +// } +// } +// } + +// impl State for NumberState { +// fn create_passes() -> Box<[TypeErasedPass]> { +// Box::new([ +// AddNumber::to_type_erased(), +// SubtractNumber::to_type_erased(), +// ]) +// } +// } + +// let mut tree: RealDom = RealDom::new(); +// tree.dirty_nodes +// .insert(TypeId::of::(), NodeId(0)); +// tree.update_state(dirty_nodes, SendAnyMap::new()); + +// assert_eq!( +// tree.get(tree.root()).unwrap().state, +// NumberState { +// add_number: AddNumber(1), +// subtract_number: SubtractNumber(-1) +// } +// ); +// } + +// #[test] +// fn independant_node_pass() { +// use crate::real_dom::RealDom; +// use crate::tree::{Tree, TreeLike}; + +// #[derive(Debug, Default, Clone, PartialEq)] +// struct AddNumber(i32); + +// impl Pass for AddNumber { +// type ChildDependencies = (); +// type NodeDependencies = (); +// type ParentDependencies = (); +// type Ctx = (); +// const MASK: NodeMask = NodeMask::new(); + +// fn pass<'a>( +// &mut self, +// node_view: NodeView, +// node: ::ElementBorrowed<'a>, +// parent: Option<::ElementBorrowed<'a>>, +// children: Option< +// impl Iterator::ElementBorrowed<'a>>, +// >, +// context: ::ElementBorrowed<'a>, +// ) -> bool { +// self.0 += 1; +// true +// } +// } + +// #[derive(Debug, Default, Clone, PartialEq)] +// struct SubtractNumber(i32); + +// impl Pass for SubtractNumber { +// type ChildDependencies = (); +// type NodeDependencies = (); +// type ParentDependencies = (); +// type Ctx = (); +// const MASK: NodeMask = NodeMask::new(); + +// fn pass<'a>( +// &mut self, +// node_view: NodeView, +// node: ::ElementBorrowed<'a>, +// parent: Option<::ElementBorrowed<'a>>, +// children: Option< +// impl Iterator::ElementBorrowed<'a>>, +// >, +// context: ::ElementBorrowed<'a>, +// ) -> bool { +// self.0 -= 1; +// true +// } +// } + +// #[derive(Debug, Default, Clone, PartialEq)] +// struct NumberState { +// add_number: AddNumber, +// subtract_number: SubtractNumber, +// } + +// impl AnyMapLike for NumberState { +// fn get(&self) -> Option<&T> { +// if TypeId::of::() == TypeId::of::() { +// Some(unsafe { &*(&self.add_number as *const AddNumber as *const T) }) +// } else if TypeId::of::() == TypeId::of::() { +// Some(unsafe { &*(&self.subtract_number as *const SubtractNumber as *const T) }) +// } else { +// None +// } +// } + +// fn get_mut(&mut self) -> Option<&mut T> { +// if TypeId::of::() == TypeId::of::() { +// Some(unsafe { &mut *(&mut self.add_number as *mut AddNumber as *mut T) }) +// } else if TypeId::of::() == TypeId::of::() { +// Some(unsafe { &mut *(&mut self.subtract_number as *mut SubtractNumber as *mut T) }) +// } else { +// None +// } +// } +// } + +// impl State for NumberState { +// fn create_passes() -> Box<[TypeErasedPass]> { +// Box::new([ +// AddNumber::to_type_erased(), +// SubtractNumber::to_type_erased(), +// ]) +// } +// } + +// let mut tree: RealDom = RealDom::new(); +// tree.dirty_nodes +// .insert(TypeId::of::(), NodeId(0)); +// tree.update_state(SendAnyMap::new()); + +// assert_eq!( +// tree.get(tree.root()).unwrap().state, +// NumberState { +// add_number: AddNumber(0), +// subtract_number: SubtractNumber(-1) +// } +// ); +// } + +// #[test] +// fn down_pass() { +// use crate::real_dom::RealDom; +// use crate::tree::{Tree, TreeLike}; + +// #[derive(Debug, Default, Clone, PartialEq)] +// struct AddNumber(i32); + +// impl Pass for AddNumber { +// type ChildDependencies = (); +// type NodeDependencies = (); +// type ParentDependencies = (AddNumber,); +// type Ctx = (); +// const MASK: NodeMask = NodeMask::new(); + +// fn pass<'a>( +// &mut self, +// node_view: NodeView, +// node: ::ElementBorrowed<'a>, +// parent: Option<::ElementBorrowed<'a>>, +// children: Option< +// impl Iterator::ElementBorrowed<'a>>, +// >, +// context: ::ElementBorrowed<'a>, +// ) -> bool { +// if let Some((parent,)) = parent { +// *self.0 += *parent.0; +// } +// true +// } +// } + +// #[derive(Debug, Default, Clone, PartialEq)] +// struct NumberState { +// add_number: AddNumber, +// } + +// impl AnyMapLike for NumberState { +// fn get(&self) -> Option<&T> { +// if TypeId::of::() == TypeId::of::() { +// Some(unsafe { &*(&self.add_number as *const AddNumber as *const T) }) +// } else { +// None +// } +// } + +// fn get_mut(&mut self) -> Option<&mut T> { +// if TypeId::of::() == TypeId::of::() { +// Some(unsafe { &mut *(&mut self.add_number as *mut AddNumber as *mut T) }) +// } else { +// None +// } +// } +// } + +// impl State for NumberState { +// fn create_passes() -> Box<[TypeErasedPass]> { +// Box::new([AddNumber::to_type_erased()]) +// } +// } + +// let mut tree: RealDom = RealDom::new(); +// 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); + +// tree.dirty_nodes +// .insert(TypeId::of::(), NodeId(0)); +// tree.update_state(SendAnyMap::new()); + +// assert_eq!(tree.get(tree.root()).unwrap().state.add_number.0, 1); +// assert_eq!(tree.get(child1).unwrap().state.add_number.0, 2); +// assert_eq!(tree.get(grandchild1).unwrap().state.add_number.0, 3); +// assert_eq!(tree.get(child2).unwrap().state.add_number.0, 2); +// assert_eq!(tree.get(grandchild2).unwrap().state.add_number.0, 3); +// } + +// #[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); + +// 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(0) +// } +// } +// impl DownwardPass for AddPass { +// fn pass(&self, node: &mut i32, parent: Option<&mut i32>, _: &SendAnyMap) -> PassReturn { +// if let Some(parent) = parent { +// *node += *parent; +// } else { +// } +// 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(0) +// } +// } +// impl DownwardPass for SubtractPass { +// fn pass(&self, node: &mut i32, parent: Option<&mut i32>, _: &SendAnyMap) -> PassReturn { +// if let Some(parent) = parent { +// *node -= *parent; +// } else { +// } +// PassReturn { +// progress: true, +// mark_dirty: true, +// } +// } +// } + +// 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, SendAnyMap::new()); + +// // 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}; +// // 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 Pass for AddPass { +// fn pass_id(&self) -> PassId { +// PassId(0) +// } + +// fn dependancies(&self) -> &'static [PassId] { +// &[] +// } + +// fn dependants(&self) -> &'static [PassId] { +// &[] +// } + +// fn mask(&self) -> MemberMask { +// MemberMask(0) +// } +// } +// impl UpwardPass for AddPass { +// fn pass<'a>( +// &self, +// node: &mut i32, +// children: &mut dyn Iterator, +// _: &SendAnyMap, +// ) -> PassReturn { +// *node += children.map(|i| *i).sum::(); +// PassReturn { +// progress: true, +// mark_dirty: true, +// } +// } +// } + +// 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, SendAnyMap::new()); + +// 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); +// } + +// #[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(0) +// } +// } +// impl UpwardPass for AddPass { +// fn pass<'a>( +// &self, +// node: &mut i32, +// children: &mut dyn Iterator, +// _: &SendAnyMap, +// ) -> 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(0) +// } +// } +// impl UpwardPass for SubtractPass { +// fn pass<'a>( +// &self, +// node: &mut i32, +// children: &mut dyn Iterator, +// _: &SendAnyMap, +// ) -> PassReturn { +// *node -= children.map(|i| *i).sum::(); +// PassReturn { +// progress: true, +// mark_dirty: true, +// } +// } +// } + +// 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, SendAnyMap::new()); + +// // 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); +// } diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index dba68d618..338fc4c52 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -1,26 +1,17 @@ use dioxus_core::{ElementId, Mutations, TemplateNode}; use rustc_hash::{FxHashMap, FxHashSet}; +use std::any::TypeId; use std::fmt::Debug; -use std::ops::{Deref, DerefMut, Index, IndexMut}; +use std::ops::{Index, IndexMut}; -use crate::node::{Node, NodeType, OwnedAttributeDiscription, OwnedAttributeValue}; +use crate::node::{ + ElementNode, Node, NodeData, NodeType, OwnedAttributeDiscription, OwnedAttributeValue, +}; use crate::node_ref::{AttributeMask, NodeMask}; use crate::passes::{resolve_passes, DirtyNodeStates, TypeErasedPass}; use crate::state::State; -use crate::tree::{NodeId, Tree, TreeLike, TreeView}; -use crate::{FxDashSet, RealNodeId, SendAnyMap}; - -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); - } -} +use crate::tree::{NodeId, Tree, TreeLike, TreeView, TreeViewMut}; +use crate::{FxDashSet, SendAnyMap}; /// 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. @@ -28,12 +19,15 @@ fn mark_dirty( pub struct RealDom { pub tree: Tree>, /// a map from element id to real node id - node_id_mapping: Vec>, - nodes_listening: FxHashMap>, - stack: Vec, - templates: FxHashMap>, - root_initialized: bool, + node_id_mapping: Vec>, + nodes_listening: FxHashMap>, + stack: Vec, + templates: FxHashMap>, pub(crate) passes: Box<[TypeErasedPass]>, + pub(crate) nodes_updated: FxHashMap, + passes_updated: FxHashMap>, + parent_changed_nodes: FxHashSet, + child_changed_nodes: FxHashSet, } impl Debug for RealDom { @@ -44,7 +38,6 @@ impl Debug for RealDom { .field("nodes_listening", &self.nodes_listening) .field("stack", &self.stack) .field("templates", &self.templates) - .field("root_initialized", &self.root_initialized) .finish() } } @@ -57,18 +50,34 @@ impl Default for RealDom { impl RealDom { pub fn new() -> RealDom { - let mut root = Node::new(NodeType::Element { + let mut root = Node::new(NodeType::Element(ElementNode { tag: "Root".to_string(), namespace: Some("Root".to_string()), attributes: FxHashMap::default(), listeners: FxHashSet::default(), - }); + })); root.node_data.element_id = Some(ElementId(0)); let mut tree = Tree::new(root); let root_id = tree.root(); tree.get_mut(root_id).unwrap().node_data.node_id = root_id; - let passes = S::create_passes(); + let mut passes = S::create_passes(); + + // resolve dependants for each pass + for i in 1..passes.len() { + let (before, after) = passes.split_at_mut(i); + let (current, before) = before.split_last_mut().unwrap(); + for pass in before.iter_mut().chain(after.iter_mut()) { + for dependancy in ¤t.combined_dependancy_type_ids { + if pass.this_type_id == *dependancy { + pass.dependants.insert(current.this_type_id); + } + } + } + } + let mut nodes_updated = FxHashMap::default(); + let root_id = NodeId(0); + nodes_updated.insert(root_id, NodeMask::ALL); RealDom { tree, @@ -76,12 +85,31 @@ impl RealDom { nodes_listening: FxHashMap::default(), stack: vec![root_id], templates: FxHashMap::default(), - root_initialized: false, passes, + nodes_updated, + passes_updated: FxHashMap::default(), + parent_changed_nodes: FxHashSet::default(), + child_changed_nodes: FxHashSet::default(), } } - pub fn element_to_node_id(&self, element_id: ElementId) -> RealNodeId { + fn mark_dirty(&mut self, node_id: NodeId, mask: NodeMask) { + if let Some(node) = self.nodes_updated.get_mut(&node_id) { + *node = node.union(&mask); + } else { + self.nodes_updated.insert(node_id, mask); + } + } + + fn mark_parent_added_or_removed(&mut self, node_id: NodeId) { + self.parent_changed_nodes.insert(node_id); + } + + fn mark_child_changed(&mut self, node_id: NodeId) { + self.child_changed_nodes.insert(node_id); + } + + pub fn element_to_node_id(&self, element_id: ElementId) -> NodeId { self.node_id_mapping.get(element_id.0).unwrap().unwrap() } @@ -99,7 +127,7 @@ impl RealDom { self.node_id_mapping[element_id.0] = Some(node_id); } - fn load_child(&self, path: &[u8]) -> RealNodeId { + fn load_child(&self, path: &[u8]) -> NodeId { let mut current = *self.stack.last().unwrap(); for i in path { current = self.tree.children_ids(current).unwrap()[*i as usize]; @@ -107,18 +135,21 @@ impl RealDom { current } - fn create_node(&mut self, node: Node) -> RealNodeId { + fn create_node(&mut self, node: Node, mark_dirty: bool) -> NodeId { let node_id = self.tree.create_node(node); let node = self.tree.get_mut(node_id).unwrap(); node.node_data.node_id = node_id; + if mark_dirty { + self.nodes_updated.insert(node_id, NodeMask::ALL); + } node_id } - fn add_child(&mut self, node_id: RealNodeId, child_id: RealNodeId) { + fn add_child(&mut self, node_id: NodeId, child_id: NodeId) { self.tree.add_child(node_id, child_id); } - fn create_template_node(&mut self, node: &TemplateNode) -> RealNodeId { + fn create_template_node(&mut self, node: &TemplateNode) -> NodeId { match node { TemplateNode::Element { tag, @@ -126,7 +157,7 @@ impl RealDom { attrs, children, } => { - let node = Node::new(NodeType::Element { + let node = Node::new(NodeType::Element(ElementNode { tag: tag.to_string(), namespace: namespace.map(|s| s.to_string()), attributes: attrs @@ -148,30 +179,28 @@ impl RealDom { }) .collect(), listeners: FxHashSet::default(), - }); - let node_id = self.create_node(node); + })); + let node_id = self.create_node(node, true); for child in *children { let child_id = self.create_template_node(child); self.add_child(node_id, child_id); } 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(), - })), + TemplateNode::Text { text } => { + self.create_node(Node::new(NodeType::Text(text.to_string())), true) + } + TemplateNode::Dynamic { .. } => { + self.create_node(Node::new(NodeType::Placeholder), true) + } + TemplateNode::DynamicText { .. } => { + self.create_node(Node::new(NodeType::Text(String::new())), true) + } } } /// 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, FxHashMap) { - let mut nodes_updated: FxHashMap = FxHashMap::default(); + pub fn apply_mutations(&mut self, mutations: Mutations) { for template in mutations.templates { let mut template_root_ids = Vec::new(); for root in template.roots { @@ -181,11 +210,7 @@ impl RealDom { 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 { @@ -194,7 +219,6 @@ impl RealDom { let parent = self.element_to_node_id(id); for child in children { self.add_child(parent, child); - mark_dirty(child, NodeMask::ALL, &mut nodes_updated); } } AssignId { path, id } => { @@ -202,38 +226,32 @@ impl RealDom { } CreatePlaceholder { id } => { let node = Node::new(NodeType::Placeholder); - let node_id = self.create_node(node); + let node_id = self.create_node(node, true); 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 = Node::new(NodeType::Text(value.to_string())); + let node_id = self.create_node(node, true); 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 { + if let NodeType::Text(text) = &mut node.node_data.node_type { *text = value.to_string(); } else { - node.node_data.node_type = NodeType::Text { - text: value.to_string(), - }; + node.node_data.node_type = NodeType::Text(value.to_string()); } - mark_dirty(node_id, NodeMask::new().with_text(), &mut nodes_updated); + self.mark_dirty(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, &mut nodes_updated); + let clone_id = self.clone_node(template_id); self.set_element_id(clone_id, id); self.stack.push(clone_id); } @@ -242,7 +260,6 @@ 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); - mark_dirty(new, NodeMask::ALL, &mut nodes_updated); } self.tree.remove(old_node_id); } @@ -251,7 +268,6 @@ impl RealDom { let old_node_id = self.load_child(path); for new in new_nodes { self.tree.insert_before(old_node_id, new); - mark_dirty(new, NodeMask::ALL, &mut nodes_updated); } self.tree.remove(old_node_id); } @@ -260,7 +276,6 @@ impl RealDom { let old_node_id = self.element_to_node_id(id); for new in new_nodes.into_iter().rev() { self.tree.insert_after(old_node_id, new); - mark_dirty(new, NodeMask::ALL, &mut nodes_updated); } } InsertBefore { id, m } => { @@ -268,7 +283,6 @@ 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); - mark_dirty(new, NodeMask::ALL, &mut nodes_updated); } } SetAttribute { @@ -278,9 +292,9 @@ impl RealDom { 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( + let mut node = self.get_mut(node_id).unwrap(); + if let NodeTypeMut::Element(mut element) = node.node_type_mut() { + element.set_attribute( OwnedAttributeDiscription { name: name.to_string(), namespace: ns.map(|s| s.to_string()), @@ -288,18 +302,13 @@ impl RealDom { }, 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( + let mut node = self.get_mut(node_id).unwrap(); + if let NodeTypeMut::Element(mut element) = node.node_type_mut() { + element.set_attribute( OwnedAttributeDiscription { name: name.to_string(), namespace: None, @@ -307,25 +316,21 @@ impl RealDom { }, 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 { + let mut node = self.get_mut(node_id).unwrap(); + if let NodeTypeMut::Text(text) = node.node_type_mut() { *text = value.to_string(); } - mark_dirty(node_id, NodeMask::new().with_text(), &mut nodes_updated); } 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 { + let mut node = self.get_mut(node_id).unwrap(); + if let NodeTypeMut::Element(mut element) = node.node_type_mut() { + element.listeners_mut().insert(name.to_string()); + drop(node); match self.nodes_listening.get_mut(name) { Some(hs) => { hs.insert(node_id); @@ -336,14 +341,15 @@ impl RealDom { self.nodes_listening.insert(name.to_string(), hs); } } - 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); + { + let mut node = self.get_mut(node_id).unwrap(); + if let NodeTypeMut::Element(mut element) = node.node_type_mut() { + element.listeners_mut().remove(name); + } } self.nodes_listening.get_mut(name).unwrap().remove(&node_id); } @@ -357,8 +363,17 @@ 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, + ctx: SendAnyMap, + ) -> (FxDashSet, FxHashMap) { + let tree = &mut self.tree; + let passes = &self.passes; let dirty_nodes = DirtyNodeStates::default(); + let nodes_updated = std::mem::take(&mut self.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) { @@ -370,18 +385,11 @@ impl RealDom { } } - (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. - pub fn update_state( - &mut self, - nodes_updated: DirtyNodeStates, - ctx: SendAnyMap, - ) -> FxDashSet { - let tree = &mut self.tree; - let passes = &self.passes; - resolve_passes(tree, nodes_updated, passes, ctx) + todo!("incorporate other update members"); + ( + resolve_passes(tree, dirty_nodes, passes, ctx), + nodes_updated, + ) } /// Find all nodes that are listening for an event, sorted by there height in the dom progressing starting at the bottom and progressing up. @@ -411,39 +419,135 @@ impl RealDom { self.tree.root() } - fn clone_node( - &mut self, - node_id: NodeId, - nodes_updated: &mut FxHashMap, - ) -> RealNodeId { + fn clone_node(&mut self, node_id: NodeId) -> NodeId { 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 new_node = Node { + state: node.state.clone_or_default(), + node_data: node.node_data.clone(), + }; + let new_id = self.create_node(new_node, false); + if let Some(passes) = S::non_clone_nodes() { + for pass_id in &*passes { + self.passes_updated + .entry(node_id) + .or_default() + .insert(*pass_id); + } + } + 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, nodes_updated); + let child_id = self_mut.clone_node(*child); self_mut.add_child(new_id, child_id); } } new_id } -} -impl Deref for RealDom { - type Target = Tree>; + pub fn get_mut(&mut self, id: NodeId) -> Option> { + self.tree.get_mut(id).map(|node| NodeMut { + node: &mut node.node_data, + dirty: NodeMask::new(), + nodes_updated: &mut self.nodes_updated, + }) + } - fn deref(&self) -> &Self::Target { - &self.tree + /// WARNING: This escapes the reactive system that the real dom uses. Any changes made with this method will not trigger updates in states when [RealDom::update_state] is called. + pub fn get_mut_raw(&mut self, id: NodeId) -> Option<&mut Node> { + self.tree.get_mut(id) } } -impl DerefMut for RealDom { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.tree +impl TreeView> for RealDom { + type Iterator<'a> = > as TreeView>>::Iterator<'a>; + + fn root(&self) -> NodeId { + self.tree.root() + } + + fn get(&self, id: NodeId) -> Option<&Node> { + self.tree.get(id) + } + + fn children(&self, id: NodeId) -> Option> { + self.tree.children(id) + } + + fn children_ids(&self, id: NodeId) -> Option<&[NodeId]> { + self.tree.children_ids(id) + } + + fn parent(&self, id: NodeId) -> Option<&Node> { + self.tree.parent(id) + } + + fn parent_id(&self, id: NodeId) -> Option { + self.tree.parent_id(id) + } + + fn height(&self, id: NodeId) -> Option { + self.tree.height(id) + } + + fn size(&self) -> usize { + self.tree.size() + } +} + +impl TreeLike> for RealDom { + fn create_node(&mut self, node: Node) -> NodeId { + let mask = NodeMask::ALL; + let id = self.tree.create_node(node); + self.tree.get_mut(id).unwrap().node_data.node_id = id; + self.mark_dirty(id, mask); + id + } + + fn add_child(&mut self, parent: NodeId, child: NodeId) { + // mark the parent's children changed + self.mark_child_changed(parent); + // mark the child's parent changed + self.mark_parent_added_or_removed(child); + self.tree.add_child(parent, child); + } + + fn remove(&mut self, id: NodeId) -> Option> { + if let Some(parent_id) = self.tree.parent_id(id) { + self.mark_child_changed(parent_id); + } + self.tree.remove(id) + } + + fn remove_all_children(&mut self, id: NodeId) -> Vec> { + self.mark_child_changed(id); + self.tree.remove_all_children(id) + } + + fn replace(&mut self, old: NodeId, new: NodeId) { + if let Some(parent_id) = self.tree.parent_id(old) { + self.mark_child_changed(parent_id); + self.mark_parent_added_or_removed(new); + } + self.tree.replace(old, new); + } + + fn insert_before(&mut self, id: NodeId, new: NodeId) { + if let Some(parent_id) = self.tree.parent_id(id) { + self.mark_child_changed(parent_id); + self.mark_parent_added_or_removed(new); + } + self.tree.insert_before(id, new); + } + + fn insert_after(&mut self, id: NodeId, new: NodeId) { + if let Some(parent_id) = self.tree.parent_id(id) { + self.mark_child_changed(parent_id); + self.mark_parent_added_or_removed(new); + } + self.tree.insert_after(id, new); } } @@ -455,10 +559,10 @@ impl Index for RealDom { } } -impl Index for RealDom { +impl Index for RealDom { type Output = Node; - fn index(&self, idx: RealNodeId) -> &Self::Output { + fn index(&self, idx: NodeId) -> &Self::Output { self.tree.get(idx).unwrap() } } @@ -469,8 +573,114 @@ impl IndexMut for RealDom { } } -impl IndexMut for RealDom { - fn index_mut(&mut self, idx: RealNodeId) -> &mut Self::Output { +impl IndexMut for RealDom { + fn index_mut(&mut self, idx: NodeId) -> &mut Self::Output { self.tree.get_mut(idx).unwrap() } } + +pub struct NodeMut<'a> { + node: &'a mut NodeData, + dirty: NodeMask, + nodes_updated: &'a mut FxHashMap, +} + +impl<'a> NodeMut<'a> { + pub fn node_type(&self) -> &NodeType { + &self.node.node_type + } + + pub fn node_type_mut(&mut self) -> NodeTypeMut<'_> { + match &mut self.node.node_type { + NodeType::Element(element) => NodeTypeMut::Element(ElementNodeMut { + element, + dirty: &mut self.dirty, + }), + NodeType::Text(text) => { + self.dirty.set_text(); + NodeTypeMut::Text(text) + } + NodeType::Placeholder => NodeTypeMut::Placeholder, + } + } +} + +impl Drop for NodeMut<'_> { + fn drop(&mut self) { + let node_id = self.node.node_id; + let mask = std::mem::take(&mut self.dirty); + if let Some(node) = self.nodes_updated.get_mut(&node_id) { + *node = node.union(&mask); + } else { + self.nodes_updated.insert(node_id, mask); + } + } +} + +pub enum NodeTypeMut<'a> { + Element(ElementNodeMut<'a>), + Text(&'a mut String), + Placeholder, +} + +pub struct ElementNodeMut<'a> { + element: &'a mut ElementNode, + dirty: &'a mut NodeMask, +} + +impl ElementNodeMut<'_> { + pub fn tag(&self) -> &str { + &self.element.tag + } + + pub fn tag_mut(&mut self) -> &mut String { + self.dirty.set_tag(); + &mut self.element.tag + } + + pub fn namespace(&self) -> Option<&str> { + self.element.namespace.as_deref() + } + + pub fn namespace_mut(&mut self) -> &mut Option { + self.dirty.set_namespace(); + &mut self.element.namespace + } + + pub fn attributes(&self) -> &FxHashMap { + &self.element.attributes + } + + pub fn attributes_mut( + &mut self, + ) -> &mut FxHashMap { + self.dirty.add_attributes(AttributeMask::All); + &mut self.element.attributes + } + + pub fn set_attribute( + &mut self, + name: OwnedAttributeDiscription, + value: OwnedAttributeValue, + ) -> Option { + self.dirty.add_attributes(AttributeMask::single(&name.name)); + self.element.attributes.insert(name, value) + } + + pub fn get_attribute_mut( + &mut self, + name: &OwnedAttributeDiscription, + ) -> Option<&mut OwnedAttributeValue> { + self.dirty.add_attributes(AttributeMask::single(&name.name)); + self.element.attributes.get_mut(name) + } + + pub fn listeners(&self) -> &FxHashSet { + &self.element.listeners + } + + pub fn listeners_mut(&mut self) -> &mut FxHashSet { + self.dirty.set_listeners(); + &mut self.element.listeners + } +} diff --git a/packages/native-core/src/state.rs b/packages/native-core/src/state.rs index beabb54c2..8fbdae6cb 100644 --- a/packages/native-core/src/state.rs +++ b/packages/native-core/src/state.rs @@ -1,10 +1,7 @@ -use std::cmp::Ordering; - -use crate::node::Node; use crate::node_ref::{NodeMask, NodeView}; -use crate::passes::{resolve_passes, AnyMapLike, DirtyNodeStates, Pass, TypeErasedPass}; -use crate::tree::{Tree, TreeView}; -use crate::{FxDashSet, RealNodeId, SendAnyMap}; +use crate::passes::{AnyMapLike, TypeErasedPass}; +use std::any::TypeId; +use std::cmp::Ordering; /// Join two sorted iterators pub(crate) fn union_ordered_iter<'a>( @@ -213,6 +210,16 @@ pub trait NodeDepState { pub trait State: Default + Clone + AnyMapLike + 'static { #[doc(hidden)] fn create_passes() -> Box<[TypeErasedPass]>; + + #[doc(hidden)] + fn clone_or_default(&self) -> Self { + self.clone() + } + + #[doc(hidden)] + fn non_clone_nodes() -> Option> { + None + } } impl ChildDepState for () { diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index 0824e0b51..5c14d159f 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -25,6 +25,17 @@ pub struct Tree { } impl Tree { + pub fn new(root: T) -> Self { + let mut nodes = Slab::default(); + let root = NodeId(nodes.insert(Node { + value: root, + parent: None, + children: Vec::new(), + height: 0, + })); + Self { nodes, root } + } + fn try_remove(&mut self, id: NodeId) -> Option> { self.nodes.try_remove(id.0).map(|node| { if let Some(parent) = node.parent { @@ -63,10 +74,6 @@ impl Tree { pub trait TreeView: Sized { type Iterator<'a>: Iterator - where - T: 'a, - Self: 'a; - type IteratorMut<'a>: Iterator where T: 'a, Self: 'a; @@ -83,69 +90,16 @@ pub trait TreeView: Sized { 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(&self, id: NodeId) -> Option>; - fn children_mut(&mut self, id: NodeId) -> Option>; - - 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. - (*mut_ptr).get_mut(id).and_then(|parent| { - (*mut_ptr) - .children_mut(id) - .map(|children| (parent, children)) - }) - } - } - fn children_ids(&self, id: NodeId) -> Option<&[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, Option<&mut T>)> { - let mut_ptr: *mut Self = self; - unsafe { - // Safety: No node has itself as a parent. - (*mut_ptr) - .get_mut(id) - .map(|node| (node, (*mut_ptr).parent_mut(id))) - } - } - - fn node_parent_children_mut( - &mut self, - id: NodeId, - ) -> Option<(&mut T, Option<&mut T>, Option>)> { - let mut_ptr: *mut Self = self; - unsafe { - // Safety: No node has itself as a parent or child. - (*mut_ptr) - .get_mut(id) - .map(|node| (node, (*mut_ptr).parent_mut(id), (*mut_ptr).children_mut(id))) - } - } - 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, - map_mut: FMut, - ) -> TreeMap { - TreeMap::new(self, map, map_mut) - } - fn size(&self) -> usize; fn traverse_depth_first(&self, mut f: impl FnMut(&T)) { @@ -160,18 +114,6 @@ pub trait TreeView: Sized { } } - 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()); @@ -186,6 +128,61 @@ pub trait TreeView: Sized { } } } +} + +pub trait TreeViewMut: Sized + TreeView { + type IteratorMut<'a>: Iterator + where + T: 'a, + Self: 'a; + + 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_mut(&mut self, id: NodeId) -> Option<>::IteratorMut<'_>>; + + fn parent_child_mut( + &mut self, + id: NodeId, + ) -> Option<(&mut T, >::IteratorMut<'_>)> { + 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 parent_mut(&mut self, id: NodeId) -> Option<&mut T>; + + fn node_parent_mut(&mut self, id: NodeId) -> Option<(&mut T, Option<&mut T>)>; + + fn node_parent_children_mut( + &mut self, + id: NodeId, + ) -> Option<( + &mut T, + Option<&mut T>, + Option<>::IteratorMut<'_>>, + )>; + + 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_mut(&mut self, mut f: impl FnMut(&mut T)) { let mut queue = VecDeque::new(); @@ -203,9 +200,7 @@ pub trait TreeView: Sized { } } -pub trait TreeLike: TreeView { - fn new(root: T) -> Self; - +pub trait TreeLike { fn create_node(&mut self, value: T) -> NodeId; fn add_child(&mut self, parent: NodeId, child: NodeId); @@ -239,22 +234,22 @@ impl<'a, T: 'a, Tr: TreeView> Iterator for ChildNodeIterator<'a, T, Tr> { } } -pub struct ChildNodeIteratorMut<'a, T, Tr: TreeView + 'a> { +pub struct ChildNodeIteratorMut<'a, T, Tr: TreeViewMut + '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> {} +unsafe impl<'a, T, Tr: TreeViewMut + 'a> Sync for ChildNodeIteratorMut<'a, T, Tr> {} -impl<'a, T, Tr: TreeView> ChildNodeIteratorMut<'a, T, Tr> { +impl<'a, T, Tr: TreeViewMut> 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> { +impl<'a, T: 'a, Tr: TreeViewMut> Iterator for ChildNodeIteratorMut<'a, T, Tr> { type Item = &'a mut T; fn next(&mut self) -> Option { @@ -272,7 +267,6 @@ impl<'a, T: 'a, Tr: TreeView> Iterator for ChildNodeIteratorMut<'a, T, Tr> { 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 @@ -282,10 +276,6 @@ impl TreeView for Tree { 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(&self, id: NodeId) -> Option> { self.children_ids(id).map(|children_ids| ChildNodeIterator { tree: self, @@ -295,20 +285,6 @@ impl TreeView for Tree { }) } - 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 - self.children_ids(id) - .map(|children_ids| ChildNodeIteratorMut { - tree: &mut *raw_ptr, - children_ids, - index: 0, - node_type: PhantomData, - }) - } - } - fn children_ids(&self, id: NodeId) -> Option<&[NodeId]> { self.nodes.get(id.0).map(|node| node.children.as_slice()) } @@ -320,6 +296,30 @@ impl TreeView for Tree { .map(|node| &node.value) } + fn parent_id(&self, id: NodeId) -> Option { + 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 } + } + + fn size(&self) -> usize { + self.nodes.len() + } +} + +impl TreeViewMut for Tree { + type IteratorMut<'a> = ChildNodeIteratorMut<'a, T, Tree> where T: 'a; + + fn get_mut(&mut self, id: NodeId) -> Option<&mut T> { + self.nodes.get_mut(id.0).map(|node| &mut node.value) + } + fn parent_mut(&mut self, id: NodeId) -> Option<&mut T> { let self_ptr = self as *mut Self; unsafe { @@ -334,39 +334,52 @@ impl TreeView for Tree { } } - fn parent_id(&self, id: NodeId) -> Option { - 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 } + 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 + self.children_ids(id) + .map(|children_ids| ChildNodeIteratorMut { + tree: &mut *raw_ptr, + children_ids, + index: 0, + node_type: PhantomData, + }) + } } 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() + fn node_parent_mut(&mut self, id: NodeId) -> Option<(&mut T, Option<&mut T>)> { + if let Some(parent_id) = self.parent_id(id) { + self.nodes + .get2_mut(id.0, parent_id.0) + .map(|(node, parent)| (&mut node.value, Some(&mut parent.value))) + } else { + self.nodes.get_mut(id.0).map(|node| (&mut node.value, None)) + } + } + + fn node_parent_children_mut( + &mut self, + id: NodeId, + ) -> Option<( + &mut T, + Option<&mut T>, + Option<>::IteratorMut<'_>>, + )> { + // Safety: No node has itself as a parent. + let unbounded_self = unsafe { &mut *(self as *mut Self) }; + self.node_parent_mut(id).map(move |(node, parent)| { + let children = unbounded_self.children_mut(id); + (node, parent, children) + }) } } 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(), - height: 0, - })); - Self { nodes, root } - } - fn create_node(&mut self, value: T) -> NodeId { NodeId(self.nodes.insert(Node { value, @@ -448,248 +461,6 @@ impl TreeLike for Tree { self.set_height(new, height); } } - -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(&self, id: NodeId) -> Option> { - self.children_ids(id).map(|children_ids| ChildNodeIterator { - tree: self, - children_ids, - index: 0, - node_type: PhantomData, - }) - } - - fn children_mut(&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(&self, id: NodeId) -> Option<&[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 height(&self, id: NodeId) -> Option { - self.tree.height(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 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(&self, id: NodeId) -> Option> { - self.with_node(id, |t| t.children(id)) - } - - fn children_mut(&mut self, id: NodeId) -> Option> { - self.with_node(id, |t| t.children_mut(id)) - } - - fn children_ids(&self, id: NodeId) -> Option<&[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 height(&self, id: NodeId) -> Option { - unsafe { (*self.tree.get()).height(id) } - } - - fn size(&self) -> usize { - unsafe { (*self.tree.get()).size() } - } -} - #[test] fn creation() { let mut tree = Tree::new(1); @@ -798,60 +569,6 @@ fn deletion() { 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.create_node(0); - tree.add_child(parent, child); - - 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.create_node(Value::new(0)); - tree.add_child(parent, child); - - 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); diff --git a/packages/native-core/src/utils/persistant_iterator.rs b/packages/native-core/src/utils/persistant_iterator.rs index 6db60f086..dfbcb4638 100644 --- a/packages/native-core/src/utils/persistant_iterator.rs +++ b/packages/native-core/src/utils/persistant_iterator.rs @@ -1,10 +1,10 @@ use crate::{ - node::NodeType, + node::{ElementNode, NodeType}, passes::{AnyMapLike, TypeErasedPass}, real_dom::RealDom, state::State, tree::TreeView, - NodeId, RealNodeId, + NodeId, }; use dioxus_core::{Mutation, Mutations}; use std::fmt::Debug; @@ -12,12 +12,12 @@ 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), + Progressed(NodeId), /// The iterator reached the end of the tree and looped back to the root - Looped(RealNodeId), + Looped(NodeId), } impl ElementProduced { - pub fn id(&self) -> RealNodeId { + pub fn id(&self) -> NodeId { match self { ElementProduced::Progressed(id) => *id, ElementProduced::Looped(id) => *id, @@ -56,7 +56,7 @@ impl NodePosition { /// 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]>, + stack: smallvec::SmallVec<[(NodeId, NodePosition); 5]>, } impl Default for PersistantElementIter { @@ -141,7 +141,7 @@ impl PersistantElementIter { let (last, old_child_idx) = self.stack.last_mut().unwrap(); let node = &rdom[*last]; match &node.node_data.node_type { - NodeType::Element { .. } => { + NodeType::Element(ElementNode { .. }) => { let children = rdom.tree.children_ids(*last).unwrap(); *old_child_idx = old_child_idx.map(|i| i + 1); // if we have children, go to the next child @@ -151,7 +151,8 @@ impl PersistantElementIter { self.next(rdom) } else { let id = children[child_idx]; - if let NodeType::Element { .. } = &rdom[id].node_data.node_type { + if let NodeType::Element(ElementNode { .. }) = &rdom[id].node_data.node_type + { self.stack.push((id, NodePosition::AtNode)); } ElementProduced::Progressed(id) @@ -170,12 +171,12 @@ impl PersistantElementIter { 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, + stack: &mut smallvec::SmallVec<[(NodeId, NodePosition); 5]>, + new_node: NodeId, rdom: &RealDom, - ) -> RealNodeId { + ) -> NodeId { match &rdom[new_node].node_data.node_type { - NodeType::Element { .. } => { + NodeType::Element(ElementNode { .. }) => { let children = rdom.tree.children_ids(new_node).unwrap(); if children.is_empty() { new_node @@ -194,7 +195,7 @@ impl PersistantElementIter { let (last, old_child_idx) = self.stack.last_mut().unwrap(); let node = &rdom[*last]; match &node.node_data.node_type { - NodeType::Element { .. } => { + NodeType::Element(ElementNode { .. }) => { let children = rdom.tree.children_ids(*last).unwrap(); // if we have children, go to the next child if let NodePosition::InChild(0) = old_child_idx { @@ -228,7 +229,7 @@ impl PersistantElementIter { } } - fn pop(&mut self) -> RealNodeId { + fn pop(&mut self) -> NodeId { self.stack.pop().unwrap().0 } } @@ -280,68 +281,68 @@ fn traverse() { let div_tag = "div".to_string(); assert!(matches!( &rdom[iter.next(&rdom).id()].node_data.node_type, - NodeType::Element { tag: div_tag, .. } + NodeType::Element(ElementNode { tag: div_tag, .. }) )); assert!(matches!( &rdom[iter.next(&rdom).id()].node_data.node_type, - NodeType::Element { tag: div_tag, .. } + NodeType::Element(ElementNode { tag: div_tag, .. }) )); let text1 = "hello".to_string(); assert!(matches!( &rdom[iter.next(&rdom).id()].node_data.node_type, - NodeType::Text { text: text1, .. } + NodeType::Text(text1) )); let p_tag = "p".to_string(); assert!(matches!( &rdom[iter.next(&rdom).id()].node_data.node_type, - NodeType::Element { tag: p_tag, .. } + NodeType::Element(ElementNode { tag: p_tag, .. }) )); let text2 = "world".to_string(); assert!(matches!( &rdom[iter.next(&rdom).id()].node_data.node_type, - NodeType::Text { text: text2, .. } + NodeType::Text(text2) )); let text3 = "hello world".to_string(); assert!(matches!( &rdom[iter.next(&rdom).id()].node_data.node_type, - NodeType::Text { text: text3, .. } + NodeType::Text(text3) )); assert!(matches!( &rdom[iter.next(&rdom).id()].node_data.node_type, - NodeType::Element { tag: div_tag, .. } + NodeType::Element(ElementNode { tag: div_tag, .. }) )); assert!(matches!( &rdom[iter.prev(&rdom).id()].node_data.node_type, - NodeType::Text { text: text3, .. } + NodeType::Text(text3) )); assert!(matches!( &rdom[iter.prev(&rdom).id()].node_data.node_type, - NodeType::Text { text: text2, .. } + NodeType::Text(text2) )); assert!(matches!( &rdom[iter.prev(&rdom).id()].node_data.node_type, - NodeType::Element { tag: p_tag, .. } + NodeType::Element(ElementNode { tag: p_tag, .. }) )); assert!(matches!( &rdom[iter.prev(&rdom).id()].node_data.node_type, - NodeType::Text { text: text1, .. } + NodeType::Text(text1) )); assert!(matches!( &rdom[iter.prev(&rdom).id()].node_data.node_type, - NodeType::Element { tag: div_tag, .. } + NodeType::Element(ElementNode { tag: div_tag, .. }) )); assert!(matches!( &rdom[iter.prev(&rdom).id()].node_data.node_type, - NodeType::Element { tag: div_tag, .. } + NodeType::Element(ElementNode { tag: div_tag, .. }) )); assert!(matches!( &rdom[iter.prev(&rdom).id()].node_data.node_type, - NodeType::Element { tag: div_tag, .. } + NodeType::Element(ElementNode { tag: div_tag, .. }) )); assert!(matches!( &rdom[iter.prev(&rdom).id()].node_data.node_type, - NodeType::Text { text: text3, .. } + NodeType::Text(text3) )); } @@ -416,14 +417,14 @@ fn persist_removes() { dbg!(&rdom[idx].node_data.node_type); assert!(matches!( &rdom[idx].node_data.node_type, - NodeType::Element { tag: root_tag, .. } + NodeType::Element(ElementNode { tag: root_tag, .. }) )); let idx = iter2.next(&rdom).id(); dbg!(&rdom[idx].node_data.node_type); assert!(matches!( &rdom[idx].node_data.node_type, - NodeType::Element { tag: root_tag, .. } + NodeType::Element(ElementNode { tag: root_tag, .. }) )); } @@ -479,7 +480,7 @@ fn persist_instertions_before() { let idx = iter.next(&rdom).id(); assert!(matches!( &rdom[idx].node_data.node_type, - NodeType::Element { tag: p_tag, .. } + NodeType::Element(ElementNode { tag: p_tag, .. }) )); } @@ -534,12 +535,12 @@ fn persist_instertions_after() { let idx = iter.next(&rdom).id(); assert!(matches!( &rdom[idx].node_data.node_type, - NodeType::Element { tag: p_tag, .. } + NodeType::Element(ElementNode { 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, .. } + NodeType::Text(text) )); } diff --git a/packages/tui/src/focus.rs b/packages/tui/src/focus.rs index 90d9e9da0..a77eef0a2 100644 --- a/packages/tui/src/focus.rs +++ b/packages/tui/src/focus.rs @@ -3,7 +3,7 @@ use crate::{node::PreventDefault, TuiDom}; use dioxus_native_core::{ tree::TreeView, utils::{ElementProduced, PersistantElementIter}, - RealNodeId, + NodeId, }; use dioxus_native_core_macro::sorted_str_slice; @@ -120,7 +120,7 @@ const FOCUS_ATTRIBUTES: &[&str] = &sorted_str_slice!(["tabindex"]); #[derive(Default)] pub(crate) struct FocusState { pub(crate) focus_iter: PersistantElementIter, - pub(crate) last_focused_id: Option, + pub(crate) last_focused_id: Option, pub(crate) focus_level: FocusLevel, pub(crate) dirty: bool, } @@ -236,11 +236,7 @@ impl FocusState { } pub(crate) fn prune(&mut self, mutations: &dioxus_core::Mutations, rdom: &TuiDom) { - fn remove_children( - to_prune: &mut [&mut Option], - rdom: &TuiDom, - removed: RealNodeId, - ) { + fn remove_children(to_prune: &mut [&mut Option], rdom: &TuiDom, removed: NodeId) { for opt in to_prune.iter_mut() { if let Some(id) = opt { if *id == removed { @@ -274,7 +270,7 @@ impl FocusState { } } - pub(crate) fn set_focus(&mut self, rdom: &mut TuiDom, id: RealNodeId) { + pub(crate) fn set_focus(&mut self, rdom: &mut TuiDom, id: NodeId) { if let Some(old) = self.last_focused_id.replace(id) { rdom[old].state.focused = false; } From bc2925240e96580abcc1633e1e07b31a34ddddd9 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 1 Jan 2023 08:52:59 -0600 Subject: [PATCH 003/103] implement macro and update tui --- packages/native-core-macro/src/lib.rs | 594 +++++--------------------- packages/native-core/src/lib.rs | 4 +- packages/native-core/src/passes.rs | 17 +- packages/native-core/src/real_dom.rs | 13 +- packages/native-core/src/state.rs | 240 +---------- packages/tui/src/focus.rs | 35 +- packages/tui/src/layout.rs | 54 ++- packages/tui/src/lib.rs | 9 +- packages/tui/src/node.rs | 33 +- packages/tui/src/render.rs | 2 +- packages/tui/src/style_attributes.rs | 32 +- 11 files changed, 233 insertions(+), 800 deletions(-) diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index 5bf50afcc..51a8464bc 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -3,16 +3,9 @@ extern crate proc_macro; mod sorted_slice; use proc_macro::TokenStream; -use quote::{quote, ToTokens, __private::Span}; +use quote::quote; use sorted_slice::StrSlice; -use syn::parenthesized; -use syn::parse::ParseBuffer; -use syn::punctuated::Punctuated; -use syn::{ - self, - parse::{Parse, ParseStream, Result}, - parse_macro_input, parse_quote, Error, Field, Ident, Token, Type, -}; +use syn::{self, parse_macro_input}; /// Sorts a slice of string literals at compile time. #[proc_macro] @@ -22,112 +15,8 @@ pub fn sorted_str_slice(input: TokenStream) -> TokenStream { quote!([#(#strings, )*]).into() } -#[derive(PartialEq, Debug, Clone)] -enum DependencyKind { - Node, - Child, - Parent, -} - -/// Derive's the state from any elements that have a node_dep_state, child_dep_state, parent_dep_state, or state attribute. -/// -/// # Declaring elements -/// Each of the attributes require specifying the members of the struct it depends on to allow the macro to find the optimal resultion order. -/// These dependencies should match the types declared in the trait the member implements. -/// -/// # The node_dep_state attribute -/// The node_dep_state attribute declares a member that implements the NodeDepState trait. -/// ```rust, ignore -/// #[derive(State)] -/// struct MyStruct { -/// // MyDependency implements ChildDepState<()> -/// #[node_dep_state()] -/// my_dependency_1: MyDependency, -/// // MyDependency2 implements ChildDepState<(MyDependency,)> -/// #[node_dep_state(my_dependency_1)] -/// my_dependency_2: MyDependency2, -/// } -/// // or -/// #[derive(State)] -/// struct MyStruct { -/// // MyDependnancy implements NodeDepState<()> -/// #[node_dep_state()] -/// my_dependency_1: MyDependency, -/// // MyDependency2 implements NodeDepState<()> -/// #[node_dep_state()] -/// my_dependency_2: MyDependency2, -/// // MyDependency3 implements NodeDepState<(MyDependency, MyDependency2)> with Ctx = f32 -/// #[node_dep_state((my_dependency_1, my_dependency_2), f32)] -/// my_dependency_3: MyDependency2, -/// } -/// ``` -/// # The child_dep_state attribute -/// The child_dep_state attribute declares a member that implements the ChildDepState trait. -/// ```rust, ignore -/// #[derive(State)] -/// struct MyStruct { -/// // MyDependnacy implements ChildDepState with DepState = Self -/// #[child_dep_state(my_dependency_1)] -/// my_dependency_1: MyDependency, -/// } -/// // or -/// #[derive(State)] -/// struct MyStruct { -/// // MyDependnacy implements ChildDepState with DepState = Self -/// #[child_dep_state(my_dependency_1)] -/// my_dependency_1: MyDependency, -/// // MyDependnacy2 implements ChildDepState with DepState = MyDependency and Ctx = f32 -/// #[child_dep_state(my_dependency_1, f32)] -/// my_dependency_2: MyDependency2, -/// } -/// ``` -/// # The parent_dep_state attribute -/// The parent_dep_state attribute declares a member that implements the ParentDepState trait. -/// The parent_dep_state attribute can be called in the forms: -/// ```rust, ignore -/// #[derive(State)] -/// struct MyStruct { -/// // MyDependnacy implements ParentDepState with DepState = Self -/// #[parent_dep_state(my_dependency_1)] -/// my_dependency_1: MyDependency, -/// } -/// // or -/// #[derive(State)] -/// struct MyStruct { -/// // MyDependnacy implements ParentDepState with DepState = Self -/// #[parent_dep_state(my_dependency_1)] -/// my_dependency_1: MyDependency, -/// // MyDependnacy2 implements ParentDepState with DepState = MyDependency and Ctx = f32 -/// #[parent_dep_state(my_dependency_1, f32)] -/// my_dependency_2: MyDependency2, -/// } -/// ``` -/// -/// # Combining dependancies -/// The node_dep_state, parent_dep_state, and child_dep_state attributes can be combined to allow for more complex dependancies. -/// For example if we wanted to combine the font that is passed from the parent to the child and the layout of the size children to find the size of the current node we could do: -/// ```rust, ignore -/// #[derive(State)] -/// struct MyStruct { -/// // ChildrenSize implements ChildDepState with DepState = Size -/// #[child_dep_state(size)] -/// children_size: ChildrenSize, -/// // FontSize implements ParentDepState with DepState = Self -/// #[parent_dep_state(font_size)] -/// font_size: FontSize, -/// // Size implements NodeDepState<(ChildrenSize, FontSize)> -/// #[parent_dep_state((children_size, font_size))] -/// size: Size, -/// } -/// ``` -/// -/// # The state attribute -/// The state macro declares a member that implements the State trait. This allows you to organize your state into multiple isolated components. -/// Unlike the other attributes, the state attribute does not accept any arguments, because a nested state cannot depend on any other part of the state. -#[proc_macro_derive( - State, - attributes(node_dep_state, child_dep_state, parent_dep_state, state) -)] +/// Derive's the state from any members that implement the Pass trait +#[proc_macro_derive(State, attributes(skip, skip_clone))] pub fn state_macro_derive(input: TokenStream) -> TokenStream { let ast = syn::parse(input).unwrap(); impl_derive_macro(&ast) @@ -145,376 +34,119 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { .collect(), _ => unimplemented!(), }; - let strct = Struct::new(type_name.clone(), &fields); - match StateStruct::parse(&fields, &strct) { - Ok(state_strct) => { - 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() - .map(|m| m.impl_pass(state_strct.ty)); - let gen = quote! { - #(#impl_members)* - impl State for #type_name { - const PASSES: &'static [dioxus_native_core::AnyPass>] = &[ - #(#passes),* - ]; - const MASKS: &'static [dioxus_native_core::NodeMask] = &[#(#member_types::NODE_MASK),*]; - } - }; - gen.into() - } - Err(e) => e.into_compile_error().into(), - } -} - -struct Struct { - name: Ident, - members: Vec, -} - -impl Struct { - fn new(name: Ident, fields: &[&Field]) -> Self { - let members = fields + let clone_or_default = fields.iter().map(|field| { + let field_name = field.ident.as_ref().unwrap(); + let skip_clone = field + .attrs .iter() - .enumerate() - .filter_map(|(i, f)| Member::parse(&name, f, i as u64)) - .collect(); - Self { name, members } - } -} - -struct StateStruct<'a> { - state_members: Vec>, - #[allow(unused)] - child_states: Vec<&'a Member>, - ty: &'a Ident, -} - -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 state_members: Vec<_> = strct - .members - .iter() - .zip(fields.iter()) - .filter_map(|(m, f)| match StateMember::parse(f, m, strct) { - Ok(m) => m, - Err(err) => { - parse_err = Err(err); - None - } - }) - .collect(); - parse_err?; - for i in 0..state_members.len() { - 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); - } - } - - let child_states = strct - .members - .iter() - .zip(fields.iter()) - .filter(|(_, f)| { - f.attrs.iter().any(|a| { - a.path - .get_ident() - .filter(|i| i.to_string().as_str() == "state") - .is_some() - }) - }) - .map(|(m, _)| m); - - // 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 try_parenthesized(input: ParseStream) -> Result { - let inside; - parenthesized!(inside in input); - Ok(inside) -} - -struct Dependency { - ctx_ty: Option, - deps: Vec, -} - -impl Parse for Dependency { - fn parse(input: ParseStream) -> Result { - let deps: Option> = { - try_parenthesized(input) - .ok() - .and_then(|inside| inside.parse_terminated(Ident::parse).ok()) - }; - let deps: Vec<_> = deps - .map(|deps| deps.into_iter().collect()) - .or_else(|| { - input - .parse::() - .ok() - .filter(|i: &Ident| format!("{}", i) != "NONE") - .map(|i| vec![i]) - }) - .unwrap_or_default(); - let comma: Option = input.parse().ok(); - let ctx_ty = input.parse().ok(); - Ok(Self { - ctx_ty: comma.and(ctx_ty), - deps, - }) - } -} - -/// The type of the member and the ident of the member -#[derive(PartialEq, Debug)] -struct Member { - id: u64, - ty: Type, - unit_type: Ident, - ident: Ident, -} - -impl Member { - fn parse(parent: &Ident, field: &Field, id: u64) -> Option { - Some(Self { - id, - ty: field.ty.clone(), - unit_type: Ident::new( - ("_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(), - ), - ident: field.ident.as_ref()?.clone(), - }) - } -} - -#[derive(Debug, Clone)] -struct StateMember<'a> { - mem: &'a Member, - // the kind of dependncies this state has - dep_kind: DependencyKind, - // the depenancy and if it is satified - dep_mems: Vec<&'a Member>, - // any members that depend on this member - dependant_mems: Vec, - // the context this state requires - ctx_ty: Option, -} - -impl<'a> StateMember<'a> { - fn parse( - field: &Field, - mem: &'a Member, - parent: &'a Struct, - ) -> Result>> { - let mut err = Ok(()); - let member = field.attrs.iter().find_map(|a| { - let dep_kind = a - .path - .get_ident() - .and_then(|i| match i.to_string().as_str() { - "node_dep_state" => Some(DependencyKind::Node), - "child_dep_state" => Some(DependencyKind::Child), - "parent_dep_state" => Some(DependencyKind::Parent), - _ => None, - })?; - match a.parse_args::() { - Ok(dependency) => { - let dep_mems = dependency - .deps - .iter() - .filter_map(|name| { - if let Some(found) = parent.members.iter().find(|m| &m.ident == name) { - Some(found) - } else { - err = Err(Error::new( - name.span(), - format!("{} not found in {}", name, parent.name), - )); - None - } - }) - .collect(); - Some(Self { - mem, - dep_kind, - dep_mems, - dependant_mems: Vec::new(), - ctx_ty: dependency.ctx_ty, - }) - } - Err(e) => { - err = Err(e); - None - } - } - }); - err?; - Ok(member) - } - - /// 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: &Ident) -> quote::__private::TokenStream { - let ident = &self.mem.ident; - let get_ctx = if let Some(ctx_ty) = &self.ctx_ty { - if ctx_ty == &parse_quote!(()) { - quote! {&()} - } else { - let msg = ctx_ty.to_token_stream().to_string() + " not found in context"; - quote! {ctx.get().expect(#msg)} + .any(|attr| attr.path.is_ident("skip_clone")); + if skip_clone { + quote! { + Default::default() } } else { - quote! {&()} - }; - - 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 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) - } - } - } - } - DependencyKind::Child => { - let update = if self.dep_mems.iter().any(|m| m.id == self.mem.id) { - quote! { - if update { - dioxus_native_core::PassReturn{ - progress: true, - mark_dirty: true, - } - } else { - dioxus_native_core::PassReturn{ - progress: false, - mark_dirty: false, - } - } - } - } else { - quote! { - if update { - dioxus_native_core::PassReturn{ - progress: false, - mark_dirty: true, - } - } else { - dioxus_native_core::PassReturn{ - progress: false, - mark_dirty: false, - } - } - } - }; - quote!( - impl dioxus_native_core::UpwardPass> for #unit_type{ - fn pass<'a>( - &self, - 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 - } - } - ) - } - DependencyKind::Parent => { - let update = if self.dep_mems.iter().any(|m| m.id == self.mem.id) { - quote! { - if update { - dioxus_native_core::PassReturn{ - progress: true, - mark_dirty: true, - } - } else { - dioxus_native_core::PassReturn{ - progress: false, - mark_dirty: false, - } - } - } - } else { - quote! { - if update { - dioxus_native_core::PassReturn{ - progress: false, - mark_dirty: true, - } - } else { - dioxus_native_core::PassReturn{ - progress: false, - mark_dirty: false, - } - } - } - }; - quote!( - 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 - } - } - ) - } - }; - let pass_id = self.mem.id; - let depenancies = self.dep_mems.iter().map(|m| m.id); - let dependants = &self.dependant_mems; - let mask = self - .dep_mems - .iter() - .map(|m| 1u64 << m.id) - .fold(1 << self.mem.id, |a, b| a | b); - quote! { - #[derive(Clone, Copy)] - struct #unit_type; - #impl_specific - 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 [dioxus_native_core::PassId] { - &[#(dioxus_native_core::PassId(#depenancies)),*] - } - fn dependants(&self) -> &'static [dioxus_native_core::PassId] { - &[#(dioxus_native_core::PassId(#dependants)),*] - } - fn mask(&self) -> dioxus_native_core::MemberMask { - dioxus_native_core::MemberMask(#mask) - } + quote! { + self.#field_name.clone() } } - } + }); + + let non_clone_types = fields + .iter() + .filter(|field| { + field + .attrs + .iter() + .any(|attr| attr.path.is_ident("skip_clone")) + }) + .map(|field| &field.ty); + + let names = fields.iter().map(|field| field.ident.as_ref().unwrap()); + + let types = fields + .iter() + .filter(|field| !field.attrs.iter().any(|attr| attr.path.is_ident("skip"))) + .map(|field| &field.ty); + + let gen = quote! { + impl dioxus_native_core::State for #type_name { + fn create_passes() -> Box<[dioxus_native_core::TypeErasedPass]> { + Box::new([ + #( + <#types as dioxus_native_core::Pass>::to_type_erased() + ),* + ]) + } + + fn clone_or_default(&self) -> Self { + Self { + #( + #names: #clone_or_default + ),* + } + } + + fn non_clone_members() -> Box<[std::any::TypeId]> { + Box::new([ + #( + std::any::TypeId::of::<#non_clone_types>() + ),* + ]) + } + } + }; + + gen.into() +} + +/// Derive's the state from any elements that have a node_dep_state, child_dep_state, parent_dep_state, or state attribute. +#[proc_macro_derive(AnyMapLike)] +pub fn anymap_like_macro_derive(input: TokenStream) -> TokenStream { + let ast = syn::parse(input).unwrap(); + impl_anymap_like_derive_macro(&ast) +} + +fn impl_anymap_like_derive_macro(ast: &syn::DeriveInput) -> TokenStream { + let type_name = &ast.ident; + let fields: Vec<_> = match &ast.data { + syn::Data::Struct(data) => match &data.fields { + syn::Fields::Named(e) => &e.named, + syn::Fields::Unnamed(_) => todo!("unnamed fields"), + syn::Fields::Unit => todo!("unit structs"), + } + .iter() + .collect(), + _ => unimplemented!(), + }; + + let names: Vec<_> = fields + .iter() + .map(|field| field.ident.as_ref().unwrap()) + .collect(); + let types: Vec<_> = fields.iter().map(|field| &field.ty).collect(); + + let gen = quote! { + impl dioxus_native_core::AnyMapLike for #type_name { + fn get(&self) -> Option<&T> { + #( + if std::any::TypeId::of::() == std::any::TypeId::of::<#types>() { + return unsafe { Some(&*(&self.#names as *const _ as *const T)) } + } + )* + None + } + + fn get_mut(&mut self) -> Option<&mut T> { + #( + if std::any::TypeId::of::() == std::any::TypeId::of::<#types>() { + return unsafe { Some(&mut *(&mut self.#names as *mut _ as *mut T)) } + } + )* + None + } + } + }; + + gen.into() } diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 6e2f7a166..de398c7db 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -1,8 +1,10 @@ use std::hash::BuildHasherDefault; pub use node_ref::NodeMask; -pub use passes::Pass; +pub use passes::AnyMapLike; +pub use passes::{Dependancy, Pass, TypeErasedPass}; use rustc_hash::FxHasher; +pub use state::State; pub use tree::NodeId; pub mod layout_attributes; diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index 8640e8fc4..3f0ac6e58 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -1,5 +1,4 @@ use anymap::AnyMap; -use rustc_hash::FxHashMap; use rustc_hash::FxHashSet; use std::any::{Any, TypeId}; use std::collections::BTreeMap; @@ -22,7 +21,7 @@ pub trait Pass: Any { type NodeDependencies: Dependancy; /// This is a tuple of (T: Any, ..) type Ctx: Dependancy; - const MASK: NodeMask; + const NODE_MASK: NodeMask; fn pass<'a>( &mut self, @@ -70,7 +69,7 @@ pub trait Pass: Any { this_type_id: TypeId::of::(), combined_dependancy_type_ids: Self::all_dependanices().into_iter().collect(), dependants: FxHashSet::default(), - mask: Self::MASK, + mask: Self::NODE_MASK, pass_direction: Self::pass_direction(), pass: Box::new( |node_id: NodeId, any_map: &mut Tree>, context: &SendAnyMap| { @@ -100,7 +99,7 @@ pub trait Pass: Any { let context = Self::Ctx::borrow_elements_from(context) .expect("tried to get a pass that does not exist"); myself.pass( - NodeView::new(¤t_node.node_data, Self::MASK), + NodeView::new(¤t_node.node_data, Self::NODE_MASK), node, parent, children, @@ -350,16 +349,6 @@ pub struct DirtyNodeStates { } impl DirtyNodeStates { - pub 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 - } - pub fn insert(&self, pass_id: TypeId, node_id: NodeId) { if let Some(mut dirty) = self.dirty.get_mut(&node_id) { dirty.insert(pass_id); diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 338fc4c52..a106bc1ab 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -426,13 +426,12 @@ impl RealDom { node_data: node.node_data.clone(), }; let new_id = self.create_node(new_node, false); - if let Some(passes) = S::non_clone_nodes() { - for pass_id in &*passes { - self.passes_updated - .entry(node_id) - .or_default() - .insert(*pass_id); - } + let passes = S::non_clone_members(); + for pass_id in &*passes { + self.passes_updated + .entry(node_id) + .or_default() + .insert(*pass_id); } let self_ptr = self as *mut Self; diff --git a/packages/native-core/src/state.rs b/packages/native-core/src/state.rs index 8fbdae6cb..a284bbdc4 100644 --- a/packages/native-core/src/state.rs +++ b/packages/native-core/src/state.rs @@ -1,4 +1,3 @@ -use crate::node_ref::{NodeMask, NodeView}; use crate::passes::{AnyMapLike, TypeErasedPass}; use std::any::TypeId; use std::cmp::Ordering; @@ -38,174 +37,6 @@ pub(crate) fn union_ordered_iter<'a>( v } -/// This state is derived from children. For example a node's size could be derived from the size of children. -/// Called when the current node's node properties are modified, a child's [ChildDepState] is modified or a child is removed. -/// Called at most once per update. -/// ```rust -/// # use dioxus_native_core::node_ref::NodeView; -/// # use dioxus_native_core::state::ChildDepState; -/// #[derive(Clone, Copy, PartialEq, Default)] -/// struct Layout { -/// width: u32, -/// height: u32, -/// } -/// -/// impl ChildDepState for Layout { -/// type Ctx = (); -/// // The layout depends on the layout of the children. -/// type DepState = (Layout,); -/// fn reduce<'a>( -/// &mut self, -/// _node: NodeView, -/// 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.map(|(&c,)|c).reduce(|c1, c2| Layout{ -/// width: c1.width + c2.width, -/// height: c1.height.max(c2.height) -/// }).unwrap_or_default(); -/// let changed = new != *self; -/// *self = new; -/// changed -/// } -/// } -/// ``` -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. - /// 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::ElementBorrowed<'a>>, - ctx: &Self::Ctx, - ) -> bool - where - Self::DepState: 'a; -} - -/// This state that is passed down to children. For example text properties (`` `` ``) would be passed to children. -/// Called when the current node's node properties are modified or a parrent's [ParentDepState] is modified. -/// Called at most once per update. -/// ```rust -/// use dioxus_native_core::node_ref::{NodeMask, AttributeMask, NodeView}; -/// use dioxus_native_core::state::*; -/// -/// #[derive(Clone, Copy, PartialEq)] -/// struct FontSize(usize); -/// -/// impl ParentDepState for FontSize { -/// type Ctx = (); -/// // The font size depends on the font size of the parent element. -/// type DepState = (Self,); -/// const NODE_MASK: NodeMask = -/// NodeMask::new_with_attrs(AttributeMask::Static(&[ -/// "font-size" -/// ])); -/// fn reduce<'a>( -/// &mut self, -/// node: NodeView, -/// 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 { -/// *self = *parent; -/// } -/// // If the current node overrides the font size, use that size insead. -/// for attr in node.attributes().unwrap() { -/// match attr.attribute.name.as_str() { -/// "font-size" => { -/// self.0 = attr.value.as_text().unwrap().parse().unwrap(); -/// } -/// // font-size is the only attribute we specified in the mask, so it is the only one we can see -/// _ => unreachable!(), -/// } -/// } -/// old != *self -/// } -/// } -/// ``` -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. - /// 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<::ElementBorrowed<'a>>, - ctx: &Self::Ctx, - ) -> bool; -} - -/// This state that is upadated lazily. For example any propertys that do not effect other parts of the dom like bg-color. -/// Called when the current node's node properties are modified or a one of its dependanices are modified. -/// Called at most once per update. -/// NodeDepState is the only state that can accept multiple dependancies, but only from the current node. -/// ```rust -/// use dioxus_native_core::node_ref::{NodeMask, AttributeMask, NodeView}; -/// use dioxus_native_core::state::*; -/// -/// #[derive(Clone, Copy, PartialEq)] -/// struct TabIndex(usize); -/// -/// impl NodeDepState for TabIndex { -/// type Ctx = (); -/// type DepState = (); -/// const NODE_MASK: NodeMask = -/// NodeMask::new_with_attrs(AttributeMask::Static(&[ -/// "tabindex" -/// ])); -/// fn reduce( -/// &mut self, -/// node: NodeView, -/// siblings: (), -/// ctx: &(), -/// ) -> bool { -/// let old = self.clone(); -/// for attr in node.attributes().unwrap() { -/// match attr.attribute.name.as_str() { -/// "tabindex" => { -/// self.0 = attr.value.as_text().unwrap().parse().unwrap(); -/// } -/// // tabindex is the only attribute we specified in the mask, so it is the only one we can see -/// _ => unreachable!(), -/// } -/// } -/// old != *self -/// } -/// } -/// ``` -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<'a>( - &mut self, - node: NodeView, - 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 + AnyMapLike + 'static { #[doc(hidden)] @@ -217,74 +48,7 @@ pub trait State: Default + Clone + AnyMapLike + 'static { } #[doc(hidden)] - fn non_clone_nodes() -> Option> { - None + fn non_clone_members() -> Box<[TypeId]> { + Box::new([]) } } - -impl ChildDepState for () { - type Ctx = (); - type DepState = (); - fn reduce<'a>(&mut self, _: NodeView, _: impl Iterator, _: &Self::Ctx) -> bool - where - Self::DepState: 'a, - { - false - } -} - -impl ParentDepState for () { - type Ctx = (); - type DepState = (); - fn reduce<'a>(&mut self, _: NodeView, _: Option<()>, _: &Self::Ctx) -> bool { - false - } -} - -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/tui/src/focus.rs b/packages/tui/src/focus.rs index a77eef0a2..6e403f986 100644 --- a/packages/tui/src/focus.rs +++ b/packages/tui/src/focus.rs @@ -3,16 +3,13 @@ use crate::{node::PreventDefault, TuiDom}; use dioxus_native_core::{ tree::TreeView, utils::{ElementProduced, PersistantElementIter}, - NodeId, + NodeId, Pass, }; use dioxus_native_core_macro::sorted_str_slice; use std::{cmp::Ordering, num::NonZeroU16}; -use dioxus_native_core::{ - node_ref::{AttributeMask, NodeMask, NodeView}, - state::NodeDepState, -}; +use dioxus_native_core::node_ref::{AttributeMask, NodeMask, NodeView}; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub(crate) enum FocusLevel { @@ -64,15 +61,33 @@ pub(crate) struct Focus { pub level: FocusLevel, } -impl NodeDepState for Focus { - type DepState = (); +impl Pass for Focus { type Ctx = (); const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::Static(FOCUS_ATTRIBUTES)).with_listeners(); - fn reduce(&mut self, node: NodeView<'_>, _sibling: (), _: &Self::Ctx) -> bool { + type ParentDependencies = (); + type ChildDependencies = (); + type NodeDependencies = (); + + fn pass<'a>( + &mut self, + node_view: NodeView, + node: ::ElementBorrowed<'a>, + parent: Option< + ::ElementBorrowed<'a>, + >, + children: Option< + impl Iterator< + Item = ::ElementBorrowed< + 'a, + >, + >, + >, + context: ::ElementBorrowed<'a>, + ) -> bool { let new = Focus { - level: if let Some(a) = node + level: if let Some(a) = node_view .attributes() .and_then(|mut a| a.find(|a| a.attribute.name == "tabindex")) { @@ -91,7 +106,7 @@ impl NodeDepState for Focus { } else { FocusLevel::Unfocusable } - } else if node + } else if node_view .listeners() .and_then(|mut listeners| { listeners diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index ba50b59a6..ad709f6e6 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -3,7 +3,7 @@ use std::sync::{Arc, Mutex}; 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::state::ChildDepState; +use dioxus_native_core::Pass; use dioxus_native_core_macro::sorted_str_slice; use taffy::prelude::*; @@ -40,29 +40,35 @@ pub(crate) struct TaffyLayout { pub node: PossiblyUninitalized, } -impl ChildDepState for TaffyLayout { - 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)) - .with_text() - .with_tag(); +impl Pass for TaffyLayout { + type Ctx = (Arc>,); + type ChildDependencies = (Self,); + type ParentDependencies = (); + type NodeDependencies = (); - /// Setup the layout - fn reduce<'a>( + const NODE_MASK: NodeMask = + NodeMask::new_with_attrs(AttributeMask::Static(SORTED_LAYOUT_ATTRS)).with_text(); + + fn pass<'a>( &mut self, - node: NodeView, - children: impl Iterator, - ctx: &Self::Ctx, - ) -> bool - where - Self::DepState: 'a, - { + node_view: NodeView, + node: ::ElementBorrowed<'a>, + parent: Option< + ::ElementBorrowed<'a>, + >, + children: Option< + impl Iterator< + Item = ::ElementBorrowed< + 'a, + >, + >, + >, + (taffy,): ::ElementBorrowed<'a>, + ) -> bool { let mut changed = false; - let mut taffy = ctx.lock().expect("poisoned taffy"); + let mut taffy = taffy.lock().expect("poisoned taffy"); let mut style = Style::default(); - if let Some(text) = node.text() { + if let Some(text) = node_view.text() { let char_len = text.chars().count(); style = Style { @@ -85,7 +91,7 @@ impl ChildDepState for TaffyLayout { } } else { // gather up all the styles from the attribute list - if let Some(attributes) = node.attributes() { + if let Some(attributes) = node_view.attributes() { for OwnedAttributeView { attribute, value, .. } in attributes @@ -101,8 +107,10 @@ impl ChildDepState for TaffyLayout { // Set all direct nodes as our children let mut child_layout = vec![]; - for (l,) in children { - child_layout.push(l.node.unwrap()); + if let Some(children) = children { + for (l,) in children { + child_layout.push(l.node.unwrap()); + } } fn scale_dimention(d: Dimension) -> Dimension { diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 4c8d8e739..c90893bf9 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -113,10 +113,10 @@ pub fn launch_cfg_with_props(app: Component, props: Props { let mut rdom = rdom.borrow_mut(); let mutations = dom.rebuild(); - let (to_update, _) = rdom.apply_mutations(mutations); + 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); + let _ = rdom.update_state(any_map); } render_vdom( @@ -271,11 +271,12 @@ fn render_vdom( let mutations = vdom.render_immediate(); handler.prune(&mutations, &rdom); // updates the dom's nodes - let (to_update, dirty) = rdom.apply_mutations(mutations); + 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); + let (new_to_rerender, dirty) = rdom.update_state(any_map); + to_rerender = new_to_rerender; 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 26ea4412e..1321f6040 100644 --- a/packages/tui/src/node.rs +++ b/packages/tui/src/node.rs @@ -1,22 +1,20 @@ use crate::focus::Focus; use crate::layout::TaffyLayout; use crate::style_attributes::StyleModifier; -use dioxus_native_core::{real_dom::RealDom, state::*}; -use dioxus_native_core_macro::{sorted_str_slice, State}; +use dioxus_native_core::{real_dom::RealDom, Dependancy, Pass}; +use dioxus_native_core_macro::{sorted_str_slice, AnyMapLike, State}; pub(crate) type TuiDom = RealDom; pub(crate) type TuiNode = dioxus_native_core::node::Node; -#[derive(Debug, Clone, State, Default)] +#[derive(Debug, Clone, State, AnyMapLike, Default)] pub(crate) struct NodeState { - #[child_dep_state(layout, Mutex)] + #[skip_clone] pub layout: TaffyLayout, - #[parent_dep_state(style)] pub style: StyleModifier, - #[node_dep_state()] pub prevent_default: PreventDefault, - #[node_dep_state()] pub focus: Focus, + #[skip] pub focused: bool, } @@ -45,8 +43,11 @@ impl Default for PreventDefault { } } -impl NodeDepState for PreventDefault { - type DepState = (); +impl Pass for PreventDefault { + type ParentDependencies = (); + type ChildDependencies = (); + type NodeDependencies = (); + type Ctx = (); const NODE_MASK: dioxus_native_core::node_ref::NodeMask = @@ -57,13 +58,17 @@ impl NodeDepState for PreventDefault { ) .with_listeners(); - fn reduce( + fn pass<'a>( &mut self, - node: dioxus_native_core::node_ref::NodeView, - _sibling: (), - _ctx: &Self::Ctx, + node_view: dioxus_native_core::node_ref::NodeView, + node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + children: Option< + impl Iterator::ElementBorrowed<'a>>, + >, + context: ::ElementBorrowed<'a>, ) -> bool { - let new = match node.attributes().and_then(|mut attrs| { + let new = match node_view.attributes().and_then(|mut attrs| { attrs .find(|a| a.attribute.name == "dioxus-prevent-default") .and_then(|a| a.value.as_text()) diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index e7de5eaac..c6d5c6923 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -45,7 +45,7 @@ pub(crate) fn render_vnode( let height = layout_to_screen_space(fy + height).round() as u16 - y; match &node.node_data.node_type { - NodeType::Text { text } => { + NodeType::Text(text) => { #[derive(Default)] struct Label<'a> { text: &'a str, diff --git a/packages/tui/src/style_attributes.rs b/packages/tui/src/style_attributes.rs index 9d1a61d12..484b1c52c 100644 --- a/packages/tui/src/style_attributes.rs +++ b/packages/tui/src/style_attributes.rs @@ -33,7 +33,7 @@ use dioxus_native_core::{ layout_attributes::parse_value, node::OwnedAttributeView, node_ref::{AttributeMask, NodeMask, NodeView}, - state::ParentDepState, + Pass, }; use dioxus_native_core_macro::sorted_str_slice; use taffy::prelude::*; @@ -46,22 +46,40 @@ pub struct StyleModifier { pub modifier: TuiModifier, } -impl ParentDepState for StyleModifier { +impl Pass for StyleModifier { type Ctx = (); - type DepState = (Self,); + type ParentDependencies = (Self,); + type ChildDependencies = (); + type NodeDependencies = (); + // 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,)>, _: &Self::Ctx) -> bool { + fn pass<'a>( + &mut self, + node_view: NodeView, + node: ::ElementBorrowed<'a>, + parent: Option< + ::ElementBorrowed<'a>, + >, + children: Option< + impl Iterator< + Item = ::ElementBorrowed< + 'a, + >, + >, + >, + context: ::ElementBorrowed<'a>, + ) -> bool { let mut new = StyleModifier::default(); if parent.is_some() { new.core.fg = None; } // handle text modifier elements - if node.namespace().is_none() { - if let Some(tag) = node.tag() { + if node_view.namespace().is_none() { + if let Some(tag) = node_view.tag() { match tag { "b" => apply_style_attributes("font-weight", "bold", &mut new), "strong" => apply_style_attributes("font-weight", "bold", &mut new), @@ -79,7 +97,7 @@ impl ParentDepState for StyleModifier { } // gather up all the styles from the attribute list - if let Some(attrs) = node.attributes() { + if let Some(attrs) = node_view.attributes() { for OwnedAttributeView { attribute, value, .. } in attrs From 3c507479cba99238d39b9ee6489043900df473b4 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 1 Jan 2023 12:10:19 -0600 Subject: [PATCH 004/103] update the update_state method --- packages/native-core/src/passes.rs | 41 ++++++++---------- packages/native-core/src/real_dom.rs | 62 +++++++++++++++++++++------- packages/native-core/src/tree.rs | 5 --- packages/tui/src/focus.rs | 11 +++-- packages/tui/src/layout.rs | 12 +++--- packages/tui/src/node.rs | 6 +-- packages/tui/src/style_attributes.rs | 9 ++-- 7 files changed, 83 insertions(+), 63 deletions(-) diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index 3f0ac6e58..8dd275575 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -19,8 +19,6 @@ pub trait Pass: Any { type ChildDependencies: Dependancy; /// This is a tuple of (T: Any, ..) type NodeDependencies: Dependancy; - /// This is a tuple of (T: Any, ..) - type Ctx: Dependancy; const NODE_MASK: NodeMask; fn pass<'a>( @@ -31,22 +29,22 @@ pub trait Pass: Any { children: Option< impl Iterator::ElementBorrowed<'a>>, >, - context: ::ElementBorrowed<'a>, + context: &SendAnyMap, ) -> bool; - fn is_valid(&self) -> bool { + fn validate() { // no type can be a child and parent dependency for type_id in Self::parent_type_ids() { for type_id2 in Self::child_type_ids() { if type_id == type_id2 { - return false; + panic!("type cannot be both a parent and child dependency"); } } } // this type should not be a node dependency for type_id in Self::node_type_ids() { if type_id == TypeId::of::() { - return false; + panic!("The current type cannot be a node dependency"); } } // no states have the same type id @@ -56,18 +54,20 @@ pub trait Pass: Any { .len() != Self::all_dependanices().len() { - return false; + panic!("all states must have unique type ids"); } - true } fn to_type_erased() -> TypeErasedPass where Self: Sized, { + Self::validate(); TypeErasedPass { this_type_id: TypeId::of::(), combined_dependancy_type_ids: Self::all_dependanices().into_iter().collect(), + parent_dependant: !Self::parent_type_ids().is_empty(), + child_dependant: !Self::child_type_ids().is_empty(), dependants: FxHashSet::default(), mask: Self::NODE_MASK, pass_direction: Self::pass_direction(), @@ -96,8 +96,6 @@ pub trait Pass: Any { .get_mut() .expect("tried to get a pass that does not exist") }; - let context = Self::Ctx::borrow_elements_from(context) - .expect("tried to get a pass that does not exist"); myself.pass( NodeView::new(¤t_node.node_data, Self::NODE_MASK), node, @@ -149,11 +147,13 @@ pub trait Pass: Any { pub struct TypeErasedPass { pub(crate) this_type_id: TypeId, + pub(crate) parent_dependant: bool, + pub(crate) child_dependant: bool, pub(crate) combined_dependancy_type_ids: FxHashSet, pub(crate) dependants: FxHashSet, pub(crate) mask: NodeMask, pass: PassCallback, - pass_direction: PassDirection, + pub(crate) pass_direction: PassDirection, } impl TypeErasedPass { @@ -183,6 +183,7 @@ impl TypeErasedPass { } PassDirection::ChildToParent => { while let Some(id) = dirty.pop_back() { + println!("running pass {:?} on {:?}", self.this_type_id, id); if (self.pass)(id, tree, ctx) { nodes_updated.insert(id); if let Some(id) = tree.parent_id(id) { @@ -210,6 +211,7 @@ impl TypeErasedPass { } } +#[derive(Debug)] pub enum PassDirection { ParentToChild, ChildToParent, @@ -248,7 +250,6 @@ pub trait Dependancy { where Self: 'a; - fn borrow_elements(&self) -> Self::ElementBorrowed<'_>; fn borrow_elements_from(map: &T) -> Option>; fn type_ids() -> Vec; } @@ -258,12 +259,6 @@ macro_rules! impl_dependancy { impl< $($t: Any),* > Dependancy 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,)*) - } - #[allow(unused_variables, clippy::unused_unit, non_snake_case)] fn borrow_elements_from(map: &T) -> Option> { Some(($(map.get::<$t>()?,)*)) @@ -391,15 +386,13 @@ pub fn resolve_passes( let pass = &passes[passes_idx]; let pass_id = pass.this_type_id; // check if the pass is ready to be run - if pass - .combined_dependancy_type_ids - .iter() - .all(|d| resolved_passes.contains(d) || *d == pass_id) - { + if pass.combined_dependancy_type_ids.iter().all(|d| { + (resolved_passes.contains(d) || *d == pass_id) && !currently_in_use.contains(d) + }) { pass_indexes_remaining.remove(i); resolving.push(pass_id); currently_in_use.insert(pass.this_type_id); - // 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 + // this is safe because each pass only has mutable access to the member associated with this_type_id and we have verified that the pass does not overlap with any other pass currently running let tree_unbounded_mut = unsafe { &mut *(tree as *mut _) }; let dirty_states = dirty_states.clone(); let nodes_updated = nodes_updated.clone(); diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index a106bc1ab..2093a4359 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -1,6 +1,5 @@ use dioxus_core::{ElementId, Mutations, TemplateNode}; use rustc_hash::{FxHashMap, FxHashSet}; -use std::any::TypeId; use std::fmt::Debug; use std::ops::{Index, IndexMut}; @@ -25,9 +24,10 @@ pub struct RealDom { templates: FxHashMap>, pub(crate) passes: Box<[TypeErasedPass]>, pub(crate) nodes_updated: FxHashMap, - passes_updated: FxHashMap>, + passes_updated: DirtyNodeStates, parent_changed_nodes: FxHashSet, child_changed_nodes: FxHashSet, + nodes_created: FxHashSet, } impl Debug for RealDom { @@ -75,6 +75,14 @@ impl RealDom { } } } + + for pass in passes.iter_mut() { + println!("{:?}: {:?}", pass.this_type_id, pass.dependants); + println!("{:?}", pass.child_dependant); + println!("{:?}", pass.parent_dependant); + println!("{:?}", pass.pass_direction); + } + let mut nodes_updated = FxHashMap::default(); let root_id = NodeId(0); nodes_updated.insert(root_id, NodeMask::ALL); @@ -87,9 +95,10 @@ impl RealDom { templates: FxHashMap::default(), passes, nodes_updated, - passes_updated: FxHashMap::default(), + passes_updated: DirtyNodeStates::default(), parent_changed_nodes: FxHashSet::default(), child_changed_nodes: FxHashSet::default(), + nodes_created: FxHashSet::default(), } } @@ -140,7 +149,7 @@ impl RealDom { let node = self.tree.get_mut(node_id).unwrap(); node.node_data.node_id = node_id; if mark_dirty { - self.nodes_updated.insert(node_id, NodeMask::ALL); + self.nodes_created.insert(node_id); } node_id } @@ -370,22 +379,50 @@ impl RealDom { &mut self, ctx: SendAnyMap, ) -> (FxDashSet, FxHashMap) { - let tree = &mut self.tree; let passes = &self.passes; - let dirty_nodes = DirtyNodeStates::default(); + let dirty_nodes = std::mem::take(&mut self.passes_updated); let nodes_updated = std::mem::take(&mut self.nodes_updated); - for (&n, mask) in &nodes_updated { + for (&node, 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) { + if self.tree.contains(node) { for pass in &*self.passes { if mask.overlaps(&pass.mask) { - dirty_nodes.insert(pass.this_type_id, n); + dirty_nodes.insert(pass.this_type_id, node); } } } } + for node in std::mem::take(&mut self.child_changed_nodes) { + // remove any nodes that were created and then removed in the same mutations from the dirty nodes list + if self.tree.contains(node) { + for pass in &*self.passes { + if pass.child_dependant { + dirty_nodes.insert(pass.this_type_id, node); + } + } + } + } + for node in std::mem::take(&mut self.parent_changed_nodes) { + // remove any nodes that were created and then removed in the same mutations from the dirty nodes list + if self.tree.contains(node) { + for pass in &*self.passes { + if pass.parent_dependant { + dirty_nodes.insert(pass.this_type_id, node); + } + } + } + } + for node in std::mem::take(&mut self.nodes_created) { + // remove any nodes that were created and then removed in the same mutations from the dirty nodes list + if self.tree.contains(node) { + for pass in &*self.passes { + dirty_nodes.insert(pass.this_type_id, node); + } + } + } + + let tree = &mut self.tree; - todo!("incorporate other update members"); ( resolve_passes(tree, dirty_nodes, passes, ctx), nodes_updated, @@ -428,10 +465,7 @@ impl RealDom { let new_id = self.create_node(new_node, false); let passes = S::non_clone_members(); for pass_id in &*passes { - self.passes_updated - .entry(node_id) - .or_default() - .insert(*pass_id); + self.passes_updated.insert(*pass_id, node_id); } let self_ptr = self as *mut Self; diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index 5c14d159f..0d965ae09 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -1,11 +1,6 @@ -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(Hash, PartialEq, Eq, Clone, Copy, Debug, PartialOrd, Ord)] pub struct NodeId(pub usize); diff --git a/packages/tui/src/focus.rs b/packages/tui/src/focus.rs index 6e403f986..1529c61da 100644 --- a/packages/tui/src/focus.rs +++ b/packages/tui/src/focus.rs @@ -3,7 +3,7 @@ use crate::{node::PreventDefault, TuiDom}; use dioxus_native_core::{ tree::TreeView, utils::{ElementProduced, PersistantElementIter}, - NodeId, Pass, + NodeId, Pass, SendAnyMap, }; use dioxus_native_core_macro::sorted_str_slice; @@ -62,7 +62,6 @@ pub(crate) struct Focus { } impl Pass for Focus { - type Ctx = (); const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::Static(FOCUS_ATTRIBUTES)).with_listeners(); @@ -73,18 +72,18 @@ impl Pass for Focus { fn pass<'a>( &mut self, node_view: NodeView, - node: ::ElementBorrowed<'a>, - parent: Option< + _: ::ElementBorrowed<'a>, + _: Option< ::ElementBorrowed<'a>, >, - children: Option< + _: Option< impl Iterator< Item = ::ElementBorrowed< 'a, >, >, >, - context: ::ElementBorrowed<'a>, + _: &SendAnyMap, ) -> bool { let new = Focus { level: if let Some(a) = node_view diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index ad709f6e6..eaa7b6139 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -3,7 +3,7 @@ use std::sync::{Arc, Mutex}; 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::Pass; +use dioxus_native_core::{Pass, SendAnyMap}; use dioxus_native_core_macro::sorted_str_slice; use taffy::prelude::*; @@ -14,6 +14,7 @@ pub(crate) enum PossiblyUninitalized { Uninitalized, Initialized(T), } + impl PossiblyUninitalized { pub fn unwrap(self) -> T { match self { @@ -41,7 +42,6 @@ pub(crate) struct TaffyLayout { } impl Pass for TaffyLayout { - type Ctx = (Arc>,); type ChildDependencies = (Self,); type ParentDependencies = (); type NodeDependencies = (); @@ -52,8 +52,8 @@ impl Pass for TaffyLayout { fn pass<'a>( &mut self, node_view: NodeView, - node: ::ElementBorrowed<'a>, - parent: Option< + _: ::ElementBorrowed<'a>, + _: Option< ::ElementBorrowed<'a>, >, children: Option< @@ -63,9 +63,11 @@ impl Pass for TaffyLayout { >, >, >, - (taffy,): ::ElementBorrowed<'a>, + ctx: &SendAnyMap, ) -> bool { + println!("running layout pass on node: \n{:#?}", node_view); let mut changed = false; + let taffy: &Arc> = ctx.get().unwrap(); let mut taffy = taffy.lock().expect("poisoned taffy"); let mut style = Style::default(); if let Some(text) = node_view.text() { diff --git a/packages/tui/src/node.rs b/packages/tui/src/node.rs index 1321f6040..759424d62 100644 --- a/packages/tui/src/node.rs +++ b/packages/tui/src/node.rs @@ -1,7 +1,7 @@ use crate::focus::Focus; use crate::layout::TaffyLayout; use crate::style_attributes::StyleModifier; -use dioxus_native_core::{real_dom::RealDom, Dependancy, Pass}; +use dioxus_native_core::{real_dom::RealDom, Dependancy, Pass, SendAnyMap}; use dioxus_native_core_macro::{sorted_str_slice, AnyMapLike, State}; pub(crate) type TuiDom = RealDom; @@ -48,8 +48,6 @@ impl Pass for PreventDefault { type ChildDependencies = (); type NodeDependencies = (); - type Ctx = (); - const NODE_MASK: dioxus_native_core::node_ref::NodeMask = dioxus_native_core::node_ref::NodeMask::new_with_attrs( dioxus_native_core::node_ref::AttributeMask::Static(&sorted_str_slice!([ @@ -66,7 +64,7 @@ impl Pass for PreventDefault { children: Option< impl Iterator::ElementBorrowed<'a>>, >, - context: ::ElementBorrowed<'a>, + context: &SendAnyMap, ) -> bool { let new = match node_view.attributes().and_then(|mut attrs| { attrs diff --git a/packages/tui/src/style_attributes.rs b/packages/tui/src/style_attributes.rs index 484b1c52c..e6f5d5527 100644 --- a/packages/tui/src/style_attributes.rs +++ b/packages/tui/src/style_attributes.rs @@ -33,7 +33,7 @@ use dioxus_native_core::{ layout_attributes::parse_value, node::OwnedAttributeView, node_ref::{AttributeMask, NodeMask, NodeView}, - Pass, + Pass, SendAnyMap, }; use dioxus_native_core_macro::sorted_str_slice; use taffy::prelude::*; @@ -47,7 +47,6 @@ pub struct StyleModifier { } impl Pass for StyleModifier { - type Ctx = (); type ParentDependencies = (Self,); type ChildDependencies = (); type NodeDependencies = (); @@ -59,18 +58,18 @@ impl Pass for StyleModifier { fn pass<'a>( &mut self, node_view: NodeView, - node: ::ElementBorrowed<'a>, + _: ::ElementBorrowed<'a>, parent: Option< ::ElementBorrowed<'a>, >, - children: Option< + _: Option< impl Iterator< Item = ::ElementBorrowed< 'a, >, >, >, - context: ::ElementBorrowed<'a>, + _: &SendAnyMap, ) -> bool { let mut new = StyleModifier::default(); if parent.is_some() { From 4db3d8ff40d05f4b450cc3d231c0017d80bde556 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 1 Jan 2023 17:21:34 -0600 Subject: [PATCH 005/103] fix state --- packages/native-core/src/passes.rs | 1 - packages/native-core/src/real_dom.rs | 16 ++-------------- packages/tui/src/layout.rs | 1 - 3 files changed, 2 insertions(+), 16 deletions(-) diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index 8dd275575..ba26d0605 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -183,7 +183,6 @@ impl TypeErasedPass { } PassDirection::ChildToParent => { while let Some(id) = dirty.pop_back() { - println!("running pass {:?} on {:?}", self.this_type_id, id); if (self.pass)(id, tree, ctx) { nodes_updated.insert(id); if let Some(id) = tree.parent_id(id) { diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 2093a4359..82a1c4e14 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -76,13 +76,6 @@ impl RealDom { } } - for pass in passes.iter_mut() { - println!("{:?}: {:?}", pass.this_type_id, pass.dependants); - println!("{:?}", pass.child_dependant); - println!("{:?}", pass.parent_dependant); - println!("{:?}", pass.pass_direction); - } - let mut nodes_updated = FxHashMap::default(); let root_id = NodeId(0); nodes_updated.insert(root_id, NodeMask::ALL); @@ -462,11 +455,7 @@ impl RealDom { state: node.state.clone_or_default(), node_data: node.node_data.clone(), }; - let new_id = self.create_node(new_node, false); - let passes = S::non_clone_members(); - for pass_id in &*passes { - self.passes_updated.insert(*pass_id, node_id); - } + let new_id = self.create_node(new_node, true); let self_ptr = self as *mut Self; for child in self.tree.children_ids(node_id).unwrap() { @@ -532,10 +521,9 @@ impl TreeView> for RealDom { impl TreeLike> for RealDom { fn create_node(&mut self, node: Node) -> NodeId { - let mask = NodeMask::ALL; let id = self.tree.create_node(node); self.tree.get_mut(id).unwrap().node_data.node_id = id; - self.mark_dirty(id, mask); + self.nodes_created.insert(id); id } diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index eaa7b6139..58c8649d6 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -65,7 +65,6 @@ impl Pass for TaffyLayout { >, ctx: &SendAnyMap, ) -> bool { - println!("running layout pass on node: \n{:#?}", node_view); let mut changed = false; let taffy: &Arc> = ctx.get().unwrap(); let mut taffy = taffy.lock().expect("poisoned taffy"); From 6f6f6c258f892841a9e22a6a97e224f4b1ac9f51 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 1 Jan 2023 18:56:39 -0600 Subject: [PATCH 006/103] add allow --- packages/native-core/src/tree.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index 0d965ae09..27cfdd212 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -158,6 +158,7 @@ pub trait TreeViewMut: Sized + TreeView { fn node_parent_mut(&mut self, id: NodeId) -> Option<(&mut T, Option<&mut T>)>; + #[allow(clippy::type_complexity)] fn node_parent_children_mut( &mut self, id: NodeId, From 56154d90be9305e71d79e879ee419e63b68b175a Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 1 Jan 2023 19:00:52 -0600 Subject: [PATCH 007/103] clean up unused code --- packages/native-core-macro/src/lib.rs | 45 --------------------------- packages/native-core/src/real_dom.rs | 2 +- packages/native-core/src/state.rs | 11 ------- packages/tui/src/node.rs | 9 +++--- 4 files changed, 5 insertions(+), 62 deletions(-) diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index 51a8464bc..22532d144 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -35,35 +35,6 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { _ => unimplemented!(), }; - let clone_or_default = fields.iter().map(|field| { - let field_name = field.ident.as_ref().unwrap(); - let skip_clone = field - .attrs - .iter() - .any(|attr| attr.path.is_ident("skip_clone")); - if skip_clone { - quote! { - Default::default() - } - } else { - quote! { - self.#field_name.clone() - } - } - }); - - let non_clone_types = fields - .iter() - .filter(|field| { - field - .attrs - .iter() - .any(|attr| attr.path.is_ident("skip_clone")) - }) - .map(|field| &field.ty); - - let names = fields.iter().map(|field| field.ident.as_ref().unwrap()); - let types = fields .iter() .filter(|field| !field.attrs.iter().any(|attr| attr.path.is_ident("skip"))) @@ -78,22 +49,6 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { ),* ]) } - - fn clone_or_default(&self) -> Self { - Self { - #( - #names: #clone_or_default - ),* - } - } - - fn non_clone_members() -> Box<[std::any::TypeId]> { - Box::new([ - #( - std::any::TypeId::of::<#non_clone_types>() - ),* - ]) - } } }; diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 82a1c4e14..39a40d95f 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -452,7 +452,7 @@ impl RealDom { fn clone_node(&mut self, node_id: NodeId) -> NodeId { let node = self.tree.get(node_id).unwrap(); let new_node = Node { - state: node.state.clone_or_default(), + state: node.state.clone(), node_data: node.node_data.clone(), }; let new_id = self.create_node(new_node, true); diff --git a/packages/native-core/src/state.rs b/packages/native-core/src/state.rs index a284bbdc4..ef4155e40 100644 --- a/packages/native-core/src/state.rs +++ b/packages/native-core/src/state.rs @@ -1,5 +1,4 @@ use crate::passes::{AnyMapLike, TypeErasedPass}; -use std::any::TypeId; use std::cmp::Ordering; /// Join two sorted iterators @@ -41,14 +40,4 @@ pub(crate) fn union_ordered_iter<'a>( pub trait State: Default + Clone + AnyMapLike + 'static { #[doc(hidden)] fn create_passes() -> Box<[TypeErasedPass]>; - - #[doc(hidden)] - fn clone_or_default(&self) -> Self { - self.clone() - } - - #[doc(hidden)] - fn non_clone_members() -> Box<[TypeId]> { - Box::new([]) - } } diff --git a/packages/tui/src/node.rs b/packages/tui/src/node.rs index 759424d62..8dc418b24 100644 --- a/packages/tui/src/node.rs +++ b/packages/tui/src/node.rs @@ -9,7 +9,6 @@ pub(crate) type TuiNode = dioxus_native_core::node::Node; #[derive(Debug, Clone, State, AnyMapLike, Default)] pub(crate) struct NodeState { - #[skip_clone] pub layout: TaffyLayout, pub style: StyleModifier, pub prevent_default: PreventDefault, @@ -59,12 +58,12 @@ impl Pass for PreventDefault { fn pass<'a>( &mut self, node_view: dioxus_native_core::node_ref::NodeView, - node: ::ElementBorrowed<'a>, - parent: Option<::ElementBorrowed<'a>>, - children: Option< + _: ::ElementBorrowed<'a>, + _: Option<::ElementBorrowed<'a>>, + _: Option< impl Iterator::ElementBorrowed<'a>>, >, - context: &SendAnyMap, + _: &SendAnyMap, ) -> bool { let new = match node_view.attributes().and_then(|mut attrs| { attrs From 8adee11fe7be7fed08065e3dbda3570ed18308eb Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 8 Jan 2023 14:04:48 -0600 Subject: [PATCH 008/103] fix custom type --- packages/native-core-macro/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index c8b58efba..5ab2c8388 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -68,7 +68,7 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { .map(|field| &field.ty); let gen = quote! { - impl dioxus_native_core::State for #type_name { + impl dioxus_native_core::State<#custom_type> for #type_name { fn create_passes() -> Box<[dioxus_native_core::TypeErasedPass]> { Box::new([ #( From 85fdc778806ba2411d322dc8e876f46b0d795705 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 8 Jan 2023 15:26:30 -0600 Subject: [PATCH 009/103] fix tui --- packages/native-core/src/lib.rs | 3 ++- packages/native-core/src/passes.rs | 2 +- packages/tui/src/hooks.rs | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index f136fcfa5..c264155af 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -5,7 +5,8 @@ pub use node_ref::NodeMask; pub use passes::AnyMapLike; pub use passes::{Dependancy, Pass, TypeErasedPass}; use rustc_hash::FxHasher; -use tree::NodeId; +pub use state::State; +pub use tree::NodeId; pub mod layout_attributes; pub mod node; diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index c5c055aef..ee43310c9 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -12,7 +12,7 @@ use crate::tree::{Tree, TreeView}; use crate::{FxDashMap, FxDashSet, SendAnyMap}; use crate::{NodeId, NodeMask}; -pub trait Pass: Any { +pub trait Pass: Any { /// This is a tuple of (T: Any, ..) type ParentDependencies: Dependancy; /// This is a tuple of (T: Any, ..) diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index d627ac3e0..ab5064bc7 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -200,7 +200,7 @@ impl InnerInputState { if old_focus != self.focus_state.last_focused_id { // elements with listeners will always have a element id if let Some(id) = self.focus_state.last_focused_id { - let element = dom.tree.get(id).unwrap(); + let element = dom.get(id).unwrap(); if let Some(id) = element.node_data.element_id { resolved_events.push(Event { name: "focus", @@ -217,7 +217,7 @@ impl InnerInputState { } } if let Some(id) = old_focus { - let element = dom.tree.get(id).unwrap(); + let element = dom.get(id).unwrap(); if let Some(id) = element.node_data.element_id { resolved_events.push(Event { name: "focusout", From 91fc594708fbaf436d2e2de899448bd355fbc117 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 16 Jan 2023 14:33:11 -0600 Subject: [PATCH 010/103] ECS based tree data struture --- packages/native-core/Cargo.toml | 2 + packages/native-core/src/tree.rs | 1067 +++++++++++++++++++----------- 2 files changed, 687 insertions(+), 382 deletions(-) diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index dd3ca1027..86efbd18a 100644 --- a/packages/native-core/Cargo.toml +++ b/packages/native-core/Cargo.toml @@ -23,6 +23,8 @@ slab = "0.4" parking_lot = "0.12.1" crossbeam-deque = "0.8.2" dashmap = "5.4.0" +hashbrown = { version = "0.13.2", features = ["raw"] } +parking_lot = "0.12.1" # for parsing attributes lightningcss = "1.0.0-alpha.39" diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index 8aa9a8750..e2996dd3c 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -1,100 +1,271 @@ +use parking_lot::{ + MappedRwLockReadGuard, MappedRwLockWriteGuard, RwLock, RwLockReadGuard, RwLockWriteGuard, +}; +use rustc_hash::FxHasher; +use std::any::{Any, TypeId}; use std::collections::VecDeque; -use std::marker::PhantomData; - -#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug, PartialOrd, Ord)] -pub struct NodeId(pub usize); +use std::fmt::Debug; +use std::hash::BuildHasherDefault; #[derive(PartialEq, Eq, Clone, Debug)] -pub struct Node { - value: T, - parent: Option, - children: Vec, +pub struct Node { + parent: Option, + children: Vec, height: u16, } -#[derive(Debug)] -pub struct Tree { - nodes: Slab>, - root: NodeId, +pub struct NodeView<'a> { + tree: &'a Tree, + id: usize, } -impl Tree { - pub fn new(root: T) -> Self { - let mut nodes = Slab::default(); - let root = NodeId(nodes.insert(Node { - value: root, - parent: None, - children: Vec::new(), - height: 0, - })); - Self { nodes, root } +impl NodeView<'_> { + pub fn insert(&self, data: T) + where + T: Any, + { + self.tree.nodes.get_slab_mut::().insert(self.id, data); } - 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 + pub fn get(&self) -> MappedRwLockReadGuard<'_, T> + where + T: Any, + { + MappedRwLockReadGuard::map(self.tree.nodes.get_slab::(), |slab| { + slab.get(self.id).unwrap() }) } - fn remove_recursive(&mut self, node: NodeId) { - let node = self.nodes.remove(node.0); - for child in node.children { - self.remove_recursive(child); + pub fn get_mut(&self) -> MappedRwLockWriteGuard<'_, T> + where + T: Any, + { + MappedRwLockWriteGuard::map(self.tree.nodes.get_slab_mut::(), |slab| { + slab.get_mut(self.id).unwrap() + }) + } + + pub fn height(&self) -> u16 { + self.get::().height + } + + pub fn children_ids(&self) -> Vec { + self.get::().children.clone() + } + + pub fn parent_id(&self) -> Option { + self.get::().parent + } + + pub fn id(&self) -> usize { + self.id + } +} + +#[derive(Debug)] +pub struct Tree { + nodes: AnySlab, + root: usize, +} + +impl Tree { + pub fn new() -> Self { + let mut nodes = AnySlab::default(); + let mut node = nodes.insert(); + node.insert(Node { + parent: None, + children: Vec::new(), + height: 0, + }); + let root = node.id(); + Self { nodes, root } + } + + fn node_slab(&self) -> MappedRwLockReadGuard<'_, SlabStorage> { + self.nodes.get_slab() + } + + pub fn get_node_data(&self, id: usize) -> MappedRwLockReadGuard<'_, Node> { + MappedRwLockReadGuard::map(self.node_slab(), |slab| slab.get(id).unwrap()) + } + + fn node_slab_mut(&self) -> MappedRwLockWriteGuard<'_, SlabStorage> { + self.nodes.get_slab_mut() + } + + pub fn get_node_data_mut(&self, id: usize) -> MappedRwLockWriteGuard<'_, Node> { + MappedRwLockWriteGuard::map(self.node_slab_mut(), |slab| slab.get_mut(id).unwrap()) + } + + pub fn remove(&mut self, id: usize) { + fn recurse(tree: &mut Tree, id: usize) { + let children = { tree.get_node_data(id).children.clone() }; + for child in children { + recurse(tree, child); + } + + tree.nodes.remove(id); + } + { + let mut node_data_mut = self.node_slab_mut(); + if let Some(parent) = node_data_mut.get(id).unwrap().parent { + let parent = node_data_mut.get_mut(parent).unwrap(); + parent.children.retain(|&child| child != id); + } + } + + recurse(self, id); + } + + fn set_height(&self, node: usize, height: u16) { + let children = { + let mut node = self.get_node_data_mut(node); + node.height = height; + node.children.clone() + }; + for child in children { + self.set_height(child, height + 1); } } - 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 fn create_node(&mut self) -> Entry<'_> { + let mut node = self.nodes.insert(); + node.insert(Node { + parent: None, + children: Vec::new(), + height: 0, + }); + node + } + + pub fn add_child(&mut self, parent: usize, new: usize) { + let mut node_state = self.node_slab_mut(); + node_state.get_mut(new).unwrap().parent = Some(parent); + let parent = node_state.get_mut(parent).unwrap(); + parent.children.push(new); + let height = parent.height + 1; + drop(node_state); + self.set_height(new, height); + } + + pub fn replace(&mut self, old_id: usize, new_id: usize) { + { + let mut node_state = self.node_slab_mut(); + // update the parent's link to the child + if let Some(parent_id) = node_state.get(old_id).unwrap().parent { + let parent = node_state.get_mut(parent_id).unwrap(); + for id in &mut parent.children { + if *id == old_id { + *id = new_id; + break; + } + } + let height = parent.height + 1; + drop(node_state); + self.set_height(new_id, height); } } + // remove the old node + self.remove(old_id); + } + + pub fn insert_before(&mut self, old_id: usize, new_id: usize) { + let mut node_state = self.node_slab_mut(); + let old_node = node_state.get(old_id).unwrap(); + let parent_id = old_node.parent.expect("tried to insert before root"); + node_state.get_mut(new_id).unwrap().parent = Some(parent_id); + let parent = node_state.get_mut(parent_id).unwrap(); + let index = parent + .children + .iter() + .position(|child| *child == old_id) + .unwrap(); + parent.children.insert(index, new_id); + let height = parent.height + 1; + drop(node_state); + self.set_height(new_id, height); + } + + pub fn insert_after(&mut self, old_id: usize, new_id: usize) { + let mut node_state = self.node_slab_mut(); + let old_node = node_state.get(old_id).unwrap(); + let parent_id = old_node.parent.expect("tried to insert before root"); + node_state.get_mut(new_id).unwrap().parent = Some(parent_id); + let parent = node_state.get_mut(parent_id).unwrap(); + let index = parent + .children + .iter() + .position(|child| *child == old_id) + .unwrap(); + parent.children.insert(index + 1, new_id); + let height = parent.height + 1; + drop(node_state); + self.set_height(new_id, height); + } + + pub fn insert(&mut self, id: usize, value: T) { + self.nodes.add(id, value); + } + + pub fn size(&self) -> usize { + self.nodes.len() + } + + pub fn state_view(&self) -> TreeStateView<'_, T> { + TreeStateView { + nodes_data: self.node_slab(), + nodes: self.nodes.get_slab(), + root: self.root, + } + } + + pub fn state_view_mut(&mut self) -> TreeStateViewMut<'_, T> { + TreeStateViewMut { + nodes_data: self.node_slab(), + nodes: self.nodes.get_slab_mut(), + root: self.root, + } } } -pub trait TreeView: Sized { - type Iterator<'a>: Iterator - where - T: 'a, - Self: 'a; - - fn root(&self) -> NodeId; - - fn contains(&self, id: NodeId) -> bool { - self.get(id).is_some() +impl NodeDataView for Tree { + fn root(&self) -> usize { + self.root } - fn get(&self, id: NodeId) -> Option<&T>; + fn parent_id(&self, id: usize) -> Option { + self.get_node_data(id).parent + } - fn get_unchecked(&self, id: NodeId) -> &T { + fn children_ids(&self, id: usize) -> Option> { + Some(self.get_node_data(id).children.clone()) + } + + fn height(&self, id: usize) -> Option { + Some(self.get_node_data(id).height) + } +} + +pub trait NodeDataView { + fn root(&self) -> usize; + + fn parent_id(&self, id: usize) -> Option; + + fn children_ids(&self, id: usize) -> Option>; + + fn height(&self, id: usize) -> Option; +} + +pub trait TreeView: Sized + NodeDataView { + fn get(&self, id: usize) -> Option<&T>; + + fn get_unchecked(&self, id: usize) -> &T { unsafe { self.get(id).unwrap_unchecked() } } - fn children(&self, id: NodeId) -> Option>; + fn children(&self, id: usize) -> Option>; - fn children_ids(&self, id: NodeId) -> Option<&[NodeId]>; - - fn parent(&self, id: NodeId) -> Option<&T>; - - fn parent_id(&self, id: NodeId) -> Option; - - fn height(&self, id: NodeId) -> Option; - - fn size(&self) -> usize; + fn parent(&self, id: usize) -> Option<&T>; fn traverse_depth_first(&self, mut f: impl FnMut(&T)) { let mut stack = vec![self.root()]; @@ -116,7 +287,7 @@ pub trait TreeView: Sized { f(node); if let Some(children) = self.children_ids(id) { for id in children { - queue.push_back(*id); + queue.push_back(id); } } } @@ -125,23 +296,15 @@ pub trait TreeView: Sized { } pub trait TreeViewMut: Sized + TreeView { - type IteratorMut<'a>: Iterator - where - T: 'a, - Self: 'a; + fn get_mut(&mut self, id: usize) -> Option<&mut T>; - fn get_mut(&mut self, id: NodeId) -> Option<&mut T>; - - fn get_mut_unchecked(&mut self, id: NodeId) -> &mut T { + unsafe fn get_mut_unchecked(&mut self, id: usize) -> &mut T { unsafe { self.get_mut(id).unwrap_unchecked() } } - fn children_mut(&mut self, id: NodeId) -> Option<>::IteratorMut<'_>>; + fn children_mut(&mut self, id: usize) -> Option>; - fn parent_child_mut( - &mut self, - id: NodeId, - ) -> Option<(&mut T, >::IteratorMut<'_>)> { + fn parent_child_mut(&mut self, id: usize) -> Option<(&mut T, Vec<&mut T>)> { let mut_ptr: *mut Self = self; unsafe { // Safety: No node has itself as a child. @@ -153,19 +316,15 @@ pub trait TreeViewMut: Sized + TreeView { } } - fn parent_mut(&mut self, id: NodeId) -> Option<&mut T>; + fn parent_mut(&mut self, id: usize) -> Option<&mut T>; - fn node_parent_mut(&mut self, id: NodeId) -> Option<(&mut T, Option<&mut T>)>; + fn node_parent_mut(&mut self, id: usize) -> Option<(&mut T, Option<&mut T>)>; #[allow(clippy::type_complexity)] fn node_parent_children_mut( &mut self, - id: NodeId, - ) -> Option<( - &mut T, - Option<&mut T>, - Option<>::IteratorMut<'_>>, - )>; + id: usize, + ) -> Option<(&mut T, Option<&mut T>, Option>)>; fn traverse_depth_first_mut(&mut self, mut f: impl FnMut(&mut T)) { let mut stack = vec![self.root()]; @@ -187,7 +346,7 @@ pub trait TreeViewMut: Sized + TreeView { f(node); if let Some(children) = self.children_ids(id) { for id in children { - queue.push_back(*id); + queue.push_back(id); } } } @@ -195,160 +354,150 @@ pub trait TreeViewMut: Sized + TreeView { } } -pub trait TreeLike { - fn create_node(&mut self, value: T) -> NodeId; +pub trait TreeLike { + fn create_node(&mut self) -> Entry; - fn add_child(&mut self, parent: NodeId, child: NodeId); + fn add_child(&mut self, parent: usize, child: usize); - fn remove(&mut self, id: NodeId) -> Option; + fn remove(&mut self, id: usize); - fn remove_all_children(&mut self, id: NodeId) -> Vec; + fn replace(&mut self, old: usize, new: usize); - fn replace(&mut self, old: NodeId, new: NodeId); + fn insert_before(&mut self, id: usize, new: usize); - fn insert_before(&mut self, id: NodeId, new: NodeId); - - fn insert_after(&mut self, id: NodeId, new: NodeId); + fn insert_after(&mut self, id: usize, new: usize); } -pub struct ChildNodeIterator<'a, T> { - nodes: &'a Slab>, - children_ids: Vec, - index: usize, - node_type: PhantomData, +pub struct TreeStateView<'a, T> { + nodes_data: MappedRwLockReadGuard<'a, SlabStorage>, + nodes: MappedRwLockReadGuard<'a, SlabStorage>, + root: usize, } -impl<'a, T: 'a> Iterator for ChildNodeIterator<'a, T> { - type Item = &'a T; - - fn next(&mut self) -> Option { - self.children_ids.get(self.index).map(|id| { - self.index += 1; - &self.nodes.get(id.0).unwrap().value - }) - } +pub struct TreeStateViewMut<'a, T> { + nodes_data: MappedRwLockReadGuard<'a, SlabStorage>, + nodes: MappedRwLockWriteGuard<'a, SlabStorage>, + root: usize, } -pub struct ChildNodeIteratorMut<'a, T> { - children: Vec<&'a mut Node>, -} - -impl<'a, T> Iterator for ChildNodeIteratorMut<'a, T> { - type Item = &'a mut T; - - fn next(&mut self) -> Option { - self.children.pop().map(|node| &mut node.value) - } -} - -impl TreeView for Tree { - type Iterator<'a> = ChildNodeIterator<'a, T> where T: 'a; - - fn root(&self) -> NodeId { +impl<'a, T> NodeDataView for TreeStateView<'a, T> { + fn root(&self) -> usize { self.root } - fn get(&self, id: NodeId) -> Option<&T> { - self.nodes.get(id.0).map(|node| &node.value) + fn children_ids(&self, id: usize) -> Option> { + self.nodes_data.get(id).map(|node| node.children.clone()) } - fn children(&self, id: NodeId) -> Option> { - self.children_ids(id).map(|children_ids| ChildNodeIterator { - nodes: &self.nodes, - children_ids: children_ids.to_vec(), - index: 0, - node_type: PhantomData, - }) + fn parent_id(&self, id: usize) -> Option { + self.nodes_data.get(id).and_then(|node| node.parent) } - fn children_ids(&self, id: NodeId) -> Option<&[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_id(&self, id: NodeId) -> Option { - 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 } - } - - fn size(&self) -> usize { - self.nodes.len() + fn height(&self, id: usize) -> Option { + self.nodes_data.get(id).map(|n| n.height) } } -impl TreeViewMut for Tree { - type IteratorMut<'a> = ChildNodeIteratorMut<'a, T> where T: 'a; - - fn get_mut(&mut self, id: NodeId) -> Option<&mut T> { - self.nodes.get_mut(id.0).map(|node| &mut node.value) +impl<'a, T> TreeView for TreeStateView<'a, T> { + fn get(&self, id: usize) -> Option<&T> { + self.nodes.get(id) } - 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 children(&self, id: usize) -> Option> { + let ids = self.children_ids(id); + ids.map(|ids| ids.iter().map(|id| self.get_unchecked(*id)).collect()) } - fn children_mut(&mut self, id: NodeId) -> Option> { + fn parent(&self, id: usize) -> Option<&T> { + self.nodes_data + .get(id) + .and_then(|node| node.parent.map(|id| self.nodes.get(id).unwrap())) + } + + fn get_unchecked(&self, id: usize) -> &T { + unsafe { &self.nodes.get_unchecked(id) } + } +} + +impl<'a, T> NodeDataView for TreeStateViewMut<'a, T> { + fn root(&self) -> usize { + self.root + } + + fn children_ids(&self, id: usize) -> Option> { + self.nodes_data.get(id).map(|node| node.children.clone()) + } + + fn parent_id(&self, id: usize) -> Option { + self.nodes_data.get(id).and_then(|node| node.parent) + } + + fn height(&self, id: usize) -> Option { + self.nodes_data.get(id).map(|n| n.height) + } +} + +impl<'a, T> TreeView for TreeStateViewMut<'a, T> { + fn get(&self, id: usize) -> Option<&T> { + self.nodes.get(id) + } + + fn children(&self, id: usize) -> Option> { + let ids = self.children_ids(id); + ids.map(|ids| ids.iter().map(|id| self.get_unchecked(*id)).collect()) + } + fn parent(&self, id: usize) -> Option<&T> { + self.nodes_data + .get(id) + .and_then(|node| node.parent.map(|id| self.nodes.get(id).unwrap())) + } + + fn get_unchecked(&self, id: usize) -> &T { + unsafe { &self.nodes.get_unchecked(id) } + } +} + +impl<'a, T> TreeViewMut for TreeStateViewMut<'a, T> { + fn get_mut(&mut self, id: usize) -> Option<&mut T> { + self.nodes.get_mut(id) + } + + fn parent_mut(&mut self, id: usize) -> Option<&mut T> { + let parent_id = self.parent_id(id)?; + unsafe { Some(self.get_mut_unchecked(parent_id)) } + } + + fn children_mut(&mut self, id: usize) -> Option> { // Safety: No node has itself as a parent. if let Some(children_ids) = self.children_ids(id) { let children_ids = children_ids.to_vec(); - let children_ids = children_ids.to_vec(); - Some(ChildNodeIteratorMut { - children: unsafe { - self.nodes - .get_many_mut_unchecked(children_ids.into_iter().rev().map(|id| id.0)) - .unwrap() - }, - }) + unsafe { + self.nodes + .get_many_mut_unchecked(children_ids.into_iter().rev().map(|id| id)) + } } else { None } } - fn get_mut_unchecked(&mut self, id: NodeId) -> &mut T { - unsafe { &mut self.nodes.get_unchecked_mut(id.0).value } + unsafe fn get_mut_unchecked(&mut self, id: usize) -> &mut T { + unsafe { self.nodes.get_unchecked_mut(id) } } - fn node_parent_mut(&mut self, id: NodeId) -> Option<(&mut T, Option<&mut T>)> { + fn node_parent_mut(&mut self, id: usize) -> Option<(&mut T, Option<&mut T>)> { if let Some(parent_id) = self.parent_id(id) { self.nodes - .get2_mut(id.0, parent_id.0) - .map(|(node, parent)| (&mut node.value, Some(&mut parent.value))) + .get2_mut(id, parent_id) + .map(|(node, parent)| (node, Some(parent))) } else { - self.nodes.get_mut(id.0).map(|node| (&mut node.value, None)) + self.nodes.get_mut(id).map(|node| (node, None)) } } fn node_parent_children_mut( &mut self, - id: NodeId, - ) -> Option<( - &mut T, - Option<&mut T>, - Option<>::IteratorMut<'_>>, - )> { + id: usize, + ) -> Option<(&mut T, Option<&mut T>, Option>)> { // Safety: No node has itself as a parent. let unbounded_self = unsafe { &mut *(self as *mut Self) }; self.node_parent_mut(id).map(move |(node, parent)| { @@ -357,7 +506,7 @@ impl TreeViewMut for Tree { }) } - fn parent_child_mut(&mut self, id: NodeId) -> Option<(&mut T, Self::IteratorMut<'_>)> { + fn parent_child_mut(&mut self, id: usize) -> Option<(&mut T, Vec<&mut T>)> { // Safety: No node will appear as a child twice if let Some(children_ids) = self.children_ids(id) { debug_assert!(!children_ids.iter().any(|child_id| *child_id == id)); @@ -365,131 +514,57 @@ impl TreeViewMut for Tree { let as_vec = children_ids.to_vec(); self.nodes .get_many_mut_unchecked( - as_vec - .into_iter() - .rev() - .map(|id| id.0) - .chain(std::iter::once(id.0)), + as_vec.into_iter().map(|id| id).chain(std::iter::once(id)), ) .unwrap() }; - let node = &mut borrowed.pop().unwrap().value; - Some((node, ChildNodeIteratorMut { children: borrowed })) + let node = borrowed.pop().unwrap(); + Some((node, borrowed)) } else { None } } } -impl TreeLike for Tree { - fn create_node(&mut self, value: T) -> NodeId { - NodeId(self.nodes.insert(Node { - value, - parent: None, - children: Vec::new(), - height: 0, - })) - } - - fn add_child(&mut self, parent: NodeId, new: NodeId) { - 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 { - 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()); - } - } - children - } - - fn replace(&mut self, old_id: NodeId, new_id: NodeId) { - // remove the old node - let old = self - .try_remove(old_id) - .expect("tried to replace a node that doesn't exist"); - // 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; - } - } - let height = parent.height + 1; - self.set_height(new_id, height); - } - } - - 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"); - 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); - let height = parent.height + 1; - self.set_height(new, height); - } - - 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"); - 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); - let height = parent.height + 1; - self.set_height(new, height); - } -} #[test] fn creation() { - let mut tree = Tree::new(1); - let parent = tree.root(); - let child = tree.create_node(0); - tree.add_child(parent, child); + let mut tree = Tree::new(); + let parent_id = tree.root; + tree.insert(parent_id, 1i32); + let mut child = tree.create_node(); + child.insert(0i32); + let child_id = child.id(); + + tree.add_child(parent_id, child_id); 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); - assert_eq!(tree.parent_id(child).unwrap(), parent); - assert_eq!(tree.children_ids(parent).unwrap(), &[child]); + assert_eq!(tree.height(parent_id), Some(0)); + assert_eq!(tree.height(child_id), Some(1)); + assert_eq!(tree.parent_id(parent_id), None); + assert_eq!(tree.parent_id(child_id).unwrap(), parent_id); + assert_eq!(tree.children_ids(parent_id).unwrap(), &[child_id]); + let view = tree.state_view::(); + assert_eq!(*view.get(parent_id).unwrap(), 1); + assert_eq!(*view.get(child_id).unwrap(), 0); } #[test] fn insertion() { - let mut tree = Tree::new(0); + let mut tree = Tree::new(); let parent = tree.root(); - let child = tree.create_node(2); + tree.insert(parent, 0); + let mut child = tree.create_node(); + child.insert(2); + let child = child.id(); tree.add_child(parent, child); - let before = tree.create_node(1); + let mut before = tree.create_node(); + before.insert(1); + let before = before.id(); tree.insert_before(child, before); - let after = tree.create_node(3); + let mut after = tree.create_node(); + after.insert(3); + let after = after.id(); tree.insert_after(child, after); println!("Tree: {:#?}", tree); @@ -498,25 +573,33 @@ fn insertion() { 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); - 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]); + let view = tree.state_view::(); + assert_eq!(*view.get(parent).unwrap(), 0); + assert_eq!(*view.get(before).unwrap(), 1); + assert_eq!(*view.get(child).unwrap(), 2); + assert_eq!(*view.get(after).unwrap(), 3); } #[test] fn deletion() { - let mut tree = Tree::new(0); + let mut tree = Tree::new(); let parent = tree.root(); - let child = tree.create_node(2); + tree.insert(parent, 0); + let mut child = tree.create_node(); + child.insert(2); + let child = child.id(); tree.add_child(parent, child); - let before = tree.create_node(1); + let mut before = tree.create_node(); + before.insert(1); + let before = before.id(); tree.insert_before(child, before); - let after = tree.create_node(3); + let mut after = tree.create_node(); + after.insert(3); + let after = after.id(); tree.insert_after(child, after); println!("Tree: {:#?}", tree); @@ -525,14 +608,17 @@ fn deletion() { 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); - 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]); + { + let view = tree.state_view::(); + assert_eq!(*view.get(parent).unwrap(), 0); + assert_eq!(*view.get(before).unwrap(), 1); + assert_eq!(*view.get(child).unwrap(), 2); + assert_eq!(*view.get(after).unwrap(), 3); + } tree.remove(child); @@ -541,13 +627,16 @@ fn deletion() { 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); - 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]); + { + let view = tree.state_view::(); + assert_eq!(*view.get(parent).unwrap(), 0); + assert_eq!(*view.get(before).unwrap(), 1); + assert_eq!(view.get(child), None); + assert_eq!(*view.get(after).unwrap(), 3); + } tree.remove(before); @@ -555,37 +644,53 @@ fn deletion() { 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); assert_eq!(tree.parent_id(after).unwrap(), parent); assert_eq!(tree.children_ids(parent).unwrap(), &[after]); + { + let view = tree.state_view::(); + assert_eq!(*view.get(parent).unwrap(), 0); + assert_eq!(view.get(before), None); + assert_eq!(*view.get(after).unwrap(), 3); + } tree.remove(after); 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(), &[]); + { + let view = tree.state_view::(); + assert_eq!(*view.get(parent).unwrap(), 0); + assert_eq!(view.get(after), None); + } } #[test] fn traverse_depth_first() { - let mut tree = Tree::new(0); + let mut tree = Tree::new(); let parent = tree.root(); - let child1 = tree.create_node(1); + tree.insert(parent, 0); + let mut child1 = tree.create_node(); + child1.insert(1); + let child1 = child1.id(); tree.add_child(parent, child1); - let grandchild1 = tree.create_node(2); + let mut grandchild1 = tree.create_node(); + grandchild1.insert(2); + let grandchild1 = grandchild1.id(); tree.add_child(child1, grandchild1); - let child2 = tree.create_node(3); + let mut child2 = tree.create_node(); + child2.insert(3); + let child2 = child2.id(); tree.add_child(parent, child2); - let grandchild2 = tree.create_node(4); + let mut grandchild2 = tree.create_node(); + grandchild2.insert(4); + let grandchild2 = grandchild2.id(); tree.add_child(child2, grandchild2); + let view = tree.state_view::(); let mut node_count = 0; - tree.traverse_depth_first(move |node| { + view.traverse_depth_first(move |node| { assert_eq!(*node, node_count); node_count += 1; }); @@ -593,60 +698,55 @@ fn traverse_depth_first() { #[test] fn get_node_children_mut() { - let mut tree = Tree::new(0); + let mut tree = Tree::new(); let parent = tree.root(); - let child1 = tree.create_node(1); + tree.insert(parent, 0); + let mut child1 = tree.create_node(); + child1.insert(1); + let child1 = child1.id(); tree.add_child(parent, child1); - let child2 = tree.create_node(2); + let mut child2 = tree.create_node(); + child2.insert(2); + let child2 = child2.id(); tree.add_child(parent, child2); - let child3 = tree.create_node(3); + let mut child3 = tree.create_node(); + child3.insert(3); + let child3 = child3.id(); tree.add_child(parent, child3); - let (parent, children) = tree.parent_child_mut(parent).unwrap(); - for (i, child) in children.enumerate() { - assert_eq!(*child, i + 1); - } + let mut view = tree.state_view_mut::(); + let (parent, children) = view.parent_child_mut(parent).unwrap(); println!("Parent: {:#?}", parent); -} - -#[test] -fn get_many_mut_unchecked() { - let mut slab = Slab::new(); - let parent = slab.insert(0); - let child = slab.insert(1); - let grandchild = slab.insert(2); - - let all = - unsafe { slab.get_many_mut_unchecked([parent, child, grandchild].into_iter()) }.unwrap(); - println!("All: {:#?}", all); + println!("Children: {:#?}", children); + for (i, child) in children.into_iter().enumerate() { + assert_eq!(*child, i as i32 + 1); + } } #[derive(Debug)] -struct Slab { +struct SlabStorage { data: Vec>, - free: VecDeque, } -impl Default for Slab { +impl Default for SlabStorage { fn default() -> Self { - Self::new() + Self { data: Vec::new() } } } -impl Slab { - fn new() -> Self { - Self { - data: Vec::new(), - free: VecDeque::new(), - } - } +trait AnySlabStorageImpl: Any { + fn remove(&mut self, id: usize); + fn as_any(&self) -> &dyn Any; + fn as_any_mut(&mut self) -> &mut dyn Any; +} +impl SlabStorage { fn get(&self, id: usize) -> Option<&T> { self.data.get(id).and_then(|x| x.as_ref()) } unsafe fn get_unchecked(&self, id: usize) -> &T { - self.data.get_unchecked(id).as_ref().unwrap() + self.data.get_unchecked(id).as_ref().unwrap_unchecked() } fn get_mut(&mut self, id: usize) -> Option<&mut T> { @@ -654,7 +754,14 @@ impl Slab { } unsafe fn get_unchecked_mut(&mut self, id: usize) -> &mut T { - self.data.get_unchecked_mut(id).as_mut().unwrap() + self.data.get_unchecked_mut(id).as_mut().unwrap_unchecked() + } + + fn insert(&mut self, id: usize, value: T) { + if id >= self.data.len() { + self.data.resize_with(id + 1, || None); + } + self.data[id] = Some(value); } fn get2_mut(&mut self, id1: usize, id2: usize) -> Option<(&mut T, &mut T)> { @@ -676,6 +783,9 @@ impl Slab { let ptr = self.data.as_mut_ptr(); let mut result = Vec::new(); for id in ids { + if id >= self.data.len() { + return None; + } let item = unsafe { &mut *ptr.add(id) }; if let Some(item) = item { result.push(item); @@ -685,29 +795,222 @@ impl Slab { } Some(result) } +} - fn insert(&mut self, value: T) -> usize { - if let Some(id) = self.free.pop_front() { - self.data[id] = Some(value); - id - } else { - self.data.push(Some(value)); - self.data.len() - 1 +impl AnySlabStorageImpl for SlabStorage { + fn remove(&mut self, id: usize) { + self.data[id].take(); + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } +} + +struct AnySlab { + data: hashbrown::HashMap< + TypeId, + RwLock>, + BuildHasherDefault, + >, + free: VecDeque, + len: usize, +} + +impl Debug for AnySlab { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AnySlab") + .field("data", &self.data.keys().collect::>()) + .field("free", &self.free) + .field("len", &self.len) + .finish() + } +} + +impl Default for AnySlab { + fn default() -> Self { + Self::new() + } +} + +impl AnySlab { + fn new() -> Self { + Self { + data: Default::default(), + free: VecDeque::new(), + len: 0, } } - fn try_remove(&mut self, id: usize) -> Option { - self.data.get_mut(id).and_then(|x| { - self.free.push_back(id); - x.take() - }) + fn try_get_slab(&self) -> Option>> { + self.data + .get(&TypeId::of::()) + .map(|x| RwLockReadGuard::map(x.read(), |x| x.as_any().downcast_ref().unwrap())) } - fn remove(&mut self, id: usize) -> T { - self.try_remove(id).unwrap() + fn get_slab(&self) -> MappedRwLockReadGuard<'_, SlabStorage> { + self.try_get_slab().unwrap() + } + + fn try_get_slab_mut(&self) -> Option>> { + self.data + .get(&TypeId::of::()) + .map(|x| RwLockWriteGuard::map(x.write(), |x| x.as_any_mut().downcast_mut().unwrap())) + } + + fn get_slab_mut(&self) -> MappedRwLockWriteGuard<'_, SlabStorage> { + self.try_get_slab_mut().unwrap() + } + + fn get_or_insert_slab_mut(&mut self) -> MappedRwLockWriteGuard<'_, SlabStorage> { + RwLockWriteGuard::map( + self.data + .entry(TypeId::of::()) + .or_insert_with(|| RwLock::new(Box::new(SlabStorage::::default()))) + .write(), + |x| x.as_any_mut().downcast_mut().unwrap(), + ) + } + + fn insert(&mut self) -> Entry<'_> { + let id = if let Some(id) = self.free.pop_front() { + id + } else { + let id = self.len; + self.len += 1; + id + }; + Entry { id, inner: self } + } + + fn add(&mut self, id: usize, value: T) { + self.get_or_insert_slab_mut().insert(id, value); + } + + fn remove(&mut self, id: usize) { + for slab in self.data.values_mut() { + slab.write().remove(id); + } + self.free.push_back(id); } fn len(&self) -> usize { - self.data.len() - self.free.len() + self.len - self.free.len() } } + +pub struct Entry<'a> { + id: usize, + inner: &'a mut AnySlab, +} + +impl Entry<'_> { + pub fn insert(&mut self, value: T) { + self.inner + .get_or_insert_slab_mut::() + .insert(self.id, value); + } + + pub fn remove(self) { + self.inner.remove(self.id); + } + + pub fn id(&self) -> usize { + self.id + } +} + +#[test] +fn remove() { + let mut slab = AnySlab::new(); + let mut node1 = slab.insert(); + node1.insert(0i32); + let node1_id = node1.id(); + let mut node2 = slab.insert(); + node2.insert(0i32); + + assert_eq!(slab.len(), 2); + + slab.remove(node1_id); + + assert!(slab.get_slab::().get(node1_id).is_none()); + assert_eq!(slab.len(), 1); +} + +#[test] +fn get_many_mut_unchecked() { + let mut slab = AnySlab::new(); + let mut parent = slab.insert(); + parent.insert(0i32); + let parent = parent.id(); + let mut child = slab.insert(); + child.insert(1i32); + let child = child.id(); + let mut grandchild = slab.insert(); + grandchild.insert(2i32); + let grandchild = grandchild.id(); + assert_eq!(slab.len(), 3); + println!("ids: {:#?}", [parent, child, grandchild]); + + { + let mut i32_slab = slab.get_slab_mut::(); + let all = + unsafe { i32_slab.get_many_mut_unchecked([parent, child, grandchild].into_iter()) } + .unwrap(); + assert_eq!(all, [&mut 0, &mut 1, &mut 2]); + } + + { + let mut i32_slab = slab.get_slab_mut::(); + assert!(unsafe { i32_slab.get_many_mut_unchecked([3, 100].into_iter()) }.is_none()) + } +} + +#[test] +fn get_many_many_mut_unchecked() { + let mut slab = AnySlab::new(); + let mut parent = slab.insert(); + parent.insert(0i32); + parent.insert("0"); + let parent = parent.id(); + let mut child = slab.insert(); + child.insert(1i32); + child.insert("1"); + let child = child.id(); + let mut grandchild = slab.insert(); + grandchild.insert(2i32); + grandchild.insert("2"); + let grandchild = grandchild.id(); + println!("ids: {:#?}", [parent, child, grandchild]); + + println!("slab: {:#?}", slab); + + let mut num_entries = slab.get_slab_mut::(); + let mut str_entries = slab.get_slab_mut::<&str>(); + + let all_num = unsafe { + num_entries + .as_any_mut() + .downcast_mut::>() + .unwrap() + .get_many_mut_unchecked([parent, child, grandchild].into_iter()) + } + .unwrap(); + + assert_eq!(all_num, [&mut 0, &mut 1, &mut 2]); + + let all_str = unsafe { + str_entries + .as_any_mut() + .downcast_mut::>() + .unwrap() + .get_many_mut_unchecked([parent, child, grandchild].into_iter()) + } + .unwrap(); + + assert_eq!(all_str, [&mut "0", &mut "1", &mut "2"]); +} From e36874d86e98428c8b948a63184f46ca552c1a27 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 18 Jan 2023 12:14:59 -0600 Subject: [PATCH 011/103] use node id --- packages/native-core/Cargo.toml | 1 - packages/native-core/src/tree.rs | 200 ++++++++++++++++--------------- 2 files changed, 106 insertions(+), 95 deletions(-) diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index 86efbd18a..5a837e33a 100644 --- a/packages/native-core/Cargo.toml +++ b/packages/native-core/Cargo.toml @@ -24,7 +24,6 @@ parking_lot = "0.12.1" crossbeam-deque = "0.8.2" dashmap = "5.4.0" hashbrown = { version = "0.13.2", features = ["raw"] } -parking_lot = "0.12.1" # for parsing attributes lightningcss = "1.0.0-alpha.39" diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index e2996dd3c..26b89a580 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -7,16 +7,19 @@ use std::collections::VecDeque; use std::fmt::Debug; use std::hash::BuildHasherDefault; +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct NodeId(pub usize); + #[derive(PartialEq, Eq, Clone, Debug)] pub struct Node { - parent: Option, - children: Vec, + parent: Option, + children: Vec, height: u16, } pub struct NodeView<'a> { tree: &'a Tree, - id: usize, + id: NodeId, } impl NodeView<'_> { @@ -49,15 +52,15 @@ impl NodeView<'_> { self.get::().height } - pub fn children_ids(&self) -> Vec { + pub fn children_ids(&self) -> Vec { self.get::().children.clone() } - pub fn parent_id(&self) -> Option { + pub fn parent_id(&self) -> Option { self.get::().parent } - pub fn id(&self) -> usize { + pub fn id(&self) -> NodeId { self.id } } @@ -65,7 +68,7 @@ impl NodeView<'_> { #[derive(Debug)] pub struct Tree { nodes: AnySlab, - root: usize, + root: NodeId, } impl Tree { @@ -85,7 +88,7 @@ impl Tree { self.nodes.get_slab() } - pub fn get_node_data(&self, id: usize) -> MappedRwLockReadGuard<'_, Node> { + pub fn get_node_data(&self, id: NodeId) -> MappedRwLockReadGuard<'_, Node> { MappedRwLockReadGuard::map(self.node_slab(), |slab| slab.get(id).unwrap()) } @@ -93,12 +96,12 @@ impl Tree { self.nodes.get_slab_mut() } - pub fn get_node_data_mut(&self, id: usize) -> MappedRwLockWriteGuard<'_, Node> { + pub fn get_node_data_mut(&self, id: NodeId) -> MappedRwLockWriteGuard<'_, Node> { MappedRwLockWriteGuard::map(self.node_slab_mut(), |slab| slab.get_mut(id).unwrap()) } - pub fn remove(&mut self, id: usize) { - fn recurse(tree: &mut Tree, id: usize) { + pub fn remove(&mut self, id: NodeId) { + fn recurse(tree: &mut Tree, id: NodeId) { let children = { tree.get_node_data(id).children.clone() }; for child in children { recurse(tree, child); @@ -117,7 +120,7 @@ impl Tree { recurse(self, id); } - fn set_height(&self, node: usize, height: u16) { + fn set_height(&self, node: NodeId, height: u16) { let children = { let mut node = self.get_node_data_mut(node); node.height = height; @@ -138,7 +141,7 @@ impl Tree { node } - pub fn add_child(&mut self, parent: usize, new: usize) { + pub fn add_child(&mut self, parent: NodeId, new: NodeId) { let mut node_state = self.node_slab_mut(); node_state.get_mut(new).unwrap().parent = Some(parent); let parent = node_state.get_mut(parent).unwrap(); @@ -148,7 +151,7 @@ impl Tree { self.set_height(new, height); } - pub fn replace(&mut self, old_id: usize, new_id: usize) { + pub fn replace(&mut self, old_id: NodeId, new_id: NodeId) { { let mut node_state = self.node_slab_mut(); // update the parent's link to the child @@ -169,7 +172,7 @@ impl Tree { self.remove(old_id); } - pub fn insert_before(&mut self, old_id: usize, new_id: usize) { + pub fn insert_before(&mut self, old_id: NodeId, new_id: NodeId) { let mut node_state = self.node_slab_mut(); let old_node = node_state.get(old_id).unwrap(); let parent_id = old_node.parent.expect("tried to insert before root"); @@ -186,7 +189,7 @@ impl Tree { self.set_height(new_id, height); } - pub fn insert_after(&mut self, old_id: usize, new_id: usize) { + pub fn insert_after(&mut self, old_id: NodeId, new_id: NodeId) { let mut node_state = self.node_slab_mut(); let old_node = node_state.get(old_id).unwrap(); let parent_id = old_node.parent.expect("tried to insert before root"); @@ -203,7 +206,7 @@ impl Tree { self.set_height(new_id, height); } - pub fn insert(&mut self, id: usize, value: T) { + pub fn insert(&mut self, id: NodeId, value: T) { self.nodes.add(id, value); } @@ -229,43 +232,43 @@ impl Tree { } impl NodeDataView for Tree { - fn root(&self) -> usize { + fn root(&self) -> NodeId { self.root } - fn parent_id(&self, id: usize) -> Option { + fn parent_id(&self, id: NodeId) -> Option { self.get_node_data(id).parent } - fn children_ids(&self, id: usize) -> Option> { + fn children_ids(&self, id: NodeId) -> Option> { Some(self.get_node_data(id).children.clone()) } - fn height(&self, id: usize) -> Option { + fn height(&self, id: NodeId) -> Option { Some(self.get_node_data(id).height) } } pub trait NodeDataView { - fn root(&self) -> usize; + fn root(&self) -> NodeId; - fn parent_id(&self, id: usize) -> Option; + fn parent_id(&self, id: NodeId) -> Option; - fn children_ids(&self, id: usize) -> Option>; + fn children_ids(&self, id: NodeId) -> Option>; - fn height(&self, id: usize) -> Option; + fn height(&self, id: NodeId) -> Option; } pub trait TreeView: Sized + NodeDataView { - fn get(&self, id: usize) -> Option<&T>; + fn get(&self, id: NodeId) -> Option<&T>; - fn get_unchecked(&self, id: usize) -> &T { + fn get_unchecked(&self, id: NodeId) -> &T { unsafe { self.get(id).unwrap_unchecked() } } - fn children(&self, id: usize) -> Option>; + fn children(&self, id: NodeId) -> Option>; - fn parent(&self, id: usize) -> Option<&T>; + fn parent(&self, id: NodeId) -> Option<&T>; fn traverse_depth_first(&self, mut f: impl FnMut(&T)) { let mut stack = vec![self.root()]; @@ -296,15 +299,15 @@ pub trait TreeView: Sized + NodeDataView { } pub trait TreeViewMut: Sized + TreeView { - fn get_mut(&mut self, id: usize) -> Option<&mut T>; + fn get_mut(&mut self, id: NodeId) -> Option<&mut T>; - unsafe fn get_mut_unchecked(&mut self, id: usize) -> &mut T { + unsafe fn get_mut_unchecked(&mut self, id: NodeId) -> &mut T { unsafe { self.get_mut(id).unwrap_unchecked() } } - fn children_mut(&mut self, id: usize) -> Option>; + fn children_mut(&mut self, id: NodeId) -> Option>; - fn parent_child_mut(&mut self, id: usize) -> Option<(&mut T, Vec<&mut T>)> { + fn parent_child_mut(&mut self, id: NodeId) -> Option<(&mut T, Vec<&mut T>)> { let mut_ptr: *mut Self = self; unsafe { // Safety: No node has itself as a child. @@ -316,14 +319,14 @@ pub trait TreeViewMut: Sized + TreeView { } } - fn parent_mut(&mut self, id: usize) -> Option<&mut T>; + fn parent_mut(&mut self, id: NodeId) -> Option<&mut T>; - fn node_parent_mut(&mut self, id: usize) -> Option<(&mut T, Option<&mut T>)>; + fn node_parent_mut(&mut self, id: NodeId) -> Option<(&mut T, Option<&mut T>)>; #[allow(clippy::type_complexity)] fn node_parent_children_mut( &mut self, - id: usize, + id: NodeId, ) -> Option<(&mut T, Option<&mut T>, Option>)>; fn traverse_depth_first_mut(&mut self, mut f: impl FnMut(&mut T)) { @@ -357,117 +360,117 @@ pub trait TreeViewMut: Sized + TreeView { pub trait TreeLike { fn create_node(&mut self) -> Entry; - fn add_child(&mut self, parent: usize, child: usize); + fn add_child(&mut self, parent: NodeId, child: NodeId); - fn remove(&mut self, id: usize); + fn remove(&mut self, id: NodeId); - fn replace(&mut self, old: usize, new: usize); + fn replace(&mut self, old: NodeId, new: NodeId); - fn insert_before(&mut self, id: usize, new: usize); + fn insert_before(&mut self, id: NodeId, new: NodeId); - fn insert_after(&mut self, id: usize, new: usize); + fn insert_after(&mut self, id: NodeId, new: NodeId); } pub struct TreeStateView<'a, T> { nodes_data: MappedRwLockReadGuard<'a, SlabStorage>, nodes: MappedRwLockReadGuard<'a, SlabStorage>, - root: usize, + root: NodeId, } pub struct TreeStateViewMut<'a, T> { nodes_data: MappedRwLockReadGuard<'a, SlabStorage>, nodes: MappedRwLockWriteGuard<'a, SlabStorage>, - root: usize, + root: NodeId, } impl<'a, T> NodeDataView for TreeStateView<'a, T> { - fn root(&self) -> usize { + fn root(&self) -> NodeId { self.root } - fn children_ids(&self, id: usize) -> Option> { + fn children_ids(&self, id: NodeId) -> Option> { self.nodes_data.get(id).map(|node| node.children.clone()) } - fn parent_id(&self, id: usize) -> Option { + fn parent_id(&self, id: NodeId) -> Option { self.nodes_data.get(id).and_then(|node| node.parent) } - fn height(&self, id: usize) -> Option { + fn height(&self, id: NodeId) -> Option { self.nodes_data.get(id).map(|n| n.height) } } impl<'a, T> TreeView for TreeStateView<'a, T> { - fn get(&self, id: usize) -> Option<&T> { + fn get(&self, id: NodeId) -> Option<&T> { self.nodes.get(id) } - fn children(&self, id: usize) -> Option> { + fn children(&self, id: NodeId) -> Option> { let ids = self.children_ids(id); ids.map(|ids| ids.iter().map(|id| self.get_unchecked(*id)).collect()) } - fn parent(&self, id: usize) -> Option<&T> { + fn parent(&self, id: NodeId) -> Option<&T> { self.nodes_data .get(id) .and_then(|node| node.parent.map(|id| self.nodes.get(id).unwrap())) } - fn get_unchecked(&self, id: usize) -> &T { + fn get_unchecked(&self, id: NodeId) -> &T { unsafe { &self.nodes.get_unchecked(id) } } } impl<'a, T> NodeDataView for TreeStateViewMut<'a, T> { - fn root(&self) -> usize { + fn root(&self) -> NodeId { self.root } - fn children_ids(&self, id: usize) -> Option> { + fn children_ids(&self, id: NodeId) -> Option> { self.nodes_data.get(id).map(|node| node.children.clone()) } - fn parent_id(&self, id: usize) -> Option { + fn parent_id(&self, id: NodeId) -> Option { self.nodes_data.get(id).and_then(|node| node.parent) } - fn height(&self, id: usize) -> Option { + fn height(&self, id: NodeId) -> Option { self.nodes_data.get(id).map(|n| n.height) } } impl<'a, T> TreeView for TreeStateViewMut<'a, T> { - fn get(&self, id: usize) -> Option<&T> { + fn get(&self, id: NodeId) -> Option<&T> { self.nodes.get(id) } - fn children(&self, id: usize) -> Option> { + fn children(&self, id: NodeId) -> Option> { let ids = self.children_ids(id); ids.map(|ids| ids.iter().map(|id| self.get_unchecked(*id)).collect()) } - fn parent(&self, id: usize) -> Option<&T> { + fn parent(&self, id: NodeId) -> Option<&T> { self.nodes_data .get(id) .and_then(|node| node.parent.map(|id| self.nodes.get(id).unwrap())) } - fn get_unchecked(&self, id: usize) -> &T { + fn get_unchecked(&self, id: NodeId) -> &T { unsafe { &self.nodes.get_unchecked(id) } } } impl<'a, T> TreeViewMut for TreeStateViewMut<'a, T> { - fn get_mut(&mut self, id: usize) -> Option<&mut T> { + fn get_mut(&mut self, id: NodeId) -> Option<&mut T> { self.nodes.get_mut(id) } - fn parent_mut(&mut self, id: usize) -> Option<&mut T> { + fn parent_mut(&mut self, id: NodeId) -> Option<&mut T> { let parent_id = self.parent_id(id)?; unsafe { Some(self.get_mut_unchecked(parent_id)) } } - fn children_mut(&mut self, id: usize) -> Option> { + fn children_mut(&mut self, id: NodeId) -> Option> { // Safety: No node has itself as a parent. if let Some(children_ids) = self.children_ids(id) { let children_ids = children_ids.to_vec(); @@ -480,11 +483,11 @@ impl<'a, T> TreeViewMut for TreeStateViewMut<'a, T> { } } - unsafe fn get_mut_unchecked(&mut self, id: usize) -> &mut T { + unsafe fn get_mut_unchecked(&mut self, id: NodeId) -> &mut T { unsafe { self.nodes.get_unchecked_mut(id) } } - fn node_parent_mut(&mut self, id: usize) -> Option<(&mut T, Option<&mut T>)> { + fn node_parent_mut(&mut self, id: NodeId) -> Option<(&mut T, Option<&mut T>)> { if let Some(parent_id) = self.parent_id(id) { self.nodes .get2_mut(id, parent_id) @@ -496,7 +499,7 @@ impl<'a, T> TreeViewMut for TreeStateViewMut<'a, T> { fn node_parent_children_mut( &mut self, - id: usize, + id: NodeId, ) -> Option<(&mut T, Option<&mut T>, Option>)> { // Safety: No node has itself as a parent. let unbounded_self = unsafe { &mut *(self as *mut Self) }; @@ -506,7 +509,7 @@ impl<'a, T> TreeViewMut for TreeStateViewMut<'a, T> { }) } - fn parent_child_mut(&mut self, id: usize) -> Option<(&mut T, Vec<&mut T>)> { + fn parent_child_mut(&mut self, id: NodeId) -> Option<(&mut T, Vec<&mut T>)> { // Safety: No node will appear as a child twice if let Some(children_ids) = self.children_ids(id) { debug_assert!(!children_ids.iter().any(|child_id| *child_id == id)); @@ -735,40 +738,45 @@ impl Default for SlabStorage { } trait AnySlabStorageImpl: Any { - fn remove(&mut self, id: usize); + fn remove(&mut self, id: NodeId); fn as_any(&self) -> &dyn Any; fn as_any_mut(&mut self) -> &mut dyn Any; } impl SlabStorage { - fn get(&self, id: usize) -> Option<&T> { - self.data.get(id).and_then(|x| x.as_ref()) + fn get(&self, id: NodeId) -> Option<&T> { + self.data.get(id.0).and_then(|x| x.as_ref()) } - unsafe fn get_unchecked(&self, id: usize) -> &T { - self.data.get_unchecked(id).as_ref().unwrap_unchecked() + unsafe fn get_unchecked(&self, id: NodeId) -> &T { + self.data.get_unchecked(id.0).as_ref().unwrap_unchecked() } - fn get_mut(&mut self, id: usize) -> Option<&mut T> { - self.data.get_mut(id).and_then(|x| x.as_mut()) + fn get_mut(&mut self, id: NodeId) -> Option<&mut T> { + self.data.get_mut(id.0).and_then(|x| x.as_mut()) } - unsafe fn get_unchecked_mut(&mut self, id: usize) -> &mut T { - self.data.get_unchecked_mut(id).as_mut().unwrap_unchecked() + unsafe fn get_unchecked_mut(&mut self, id: NodeId) -> &mut T { + self.data + .get_unchecked_mut(id.0) + .as_mut() + .unwrap_unchecked() } - fn insert(&mut self, id: usize, value: T) { - if id >= self.data.len() { - self.data.resize_with(id + 1, || None); + fn insert(&mut self, id: NodeId, value: T) { + let idx = id.0; + if idx >= self.data.len() { + self.data.resize_with(idx + 1, || None); } - self.data[id] = Some(value); + self.data[idx] = Some(value); } - fn get2_mut(&mut self, id1: usize, id2: usize) -> Option<(&mut T, &mut T)> { + fn get2_mut(&mut self, id1: NodeId, id2: NodeId) -> Option<(&mut T, &mut T)> { assert!(id1 != id2); + let (idx1, idx2) = (id1.0, id2.0); let ptr = self.data.as_mut_ptr(); - let first = unsafe { &mut *ptr.add(id1) }; - let second = unsafe { &mut *ptr.add(id2) }; + let first = unsafe { &mut *ptr.add(idx1) }; + let second = unsafe { &mut *ptr.add(idx2) }; if let (Some(first), Some(second)) = (first, second) { Some((first, second)) } else { @@ -778,15 +786,16 @@ impl SlabStorage { unsafe fn get_many_mut_unchecked( &mut self, - ids: impl Iterator, + ids: impl Iterator, ) -> Option> { let ptr = self.data.as_mut_ptr(); let mut result = Vec::new(); for id in ids { - if id >= self.data.len() { + let idx = id.0; + if idx >= self.data.len() { return None; } - let item = unsafe { &mut *ptr.add(id) }; + let item = unsafe { &mut *ptr.add(idx) }; if let Some(item) = item { result.push(item); } else { @@ -798,8 +807,8 @@ impl SlabStorage { } impl AnySlabStorageImpl for SlabStorage { - fn remove(&mut self, id: usize) { - self.data[id].take(); + fn remove(&mut self, id: NodeId) { + self.data[id.0].take(); } fn as_any(&self) -> &dyn Any { @@ -817,7 +826,7 @@ struct AnySlab { RwLock>, BuildHasherDefault, >, - free: VecDeque, + free: VecDeque, len: usize, } @@ -882,16 +891,16 @@ impl AnySlab { } else { let id = self.len; self.len += 1; - id + NodeId(id) }; Entry { id, inner: self } } - fn add(&mut self, id: usize, value: T) { + fn add(&mut self, id: NodeId, value: T) { self.get_or_insert_slab_mut().insert(id, value); } - fn remove(&mut self, id: usize) { + fn remove(&mut self, id: NodeId) { for slab in self.data.values_mut() { slab.write().remove(id); } @@ -904,7 +913,7 @@ impl AnySlab { } pub struct Entry<'a> { - id: usize, + id: NodeId, inner: &'a mut AnySlab, } @@ -919,7 +928,7 @@ impl Entry<'_> { self.inner.remove(self.id); } - pub fn id(&self) -> usize { + pub fn id(&self) -> NodeId { self.id } } @@ -966,7 +975,10 @@ fn get_many_mut_unchecked() { { let mut i32_slab = slab.get_slab_mut::(); - assert!(unsafe { i32_slab.get_many_mut_unchecked([3, 100].into_iter()) }.is_none()) + assert!( + unsafe { i32_slab.get_many_mut_unchecked([NodeId(3), NodeId(100)].into_iter()) } + .is_none() + ) } } From 1587b41829b0db4edc4e50f1008ee85272db420e Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 19 Jan 2023 14:40:56 -0600 Subject: [PATCH 012/103] WIP make ECS work with reactive system --- packages/native-core/Cargo.toml | 2 +- packages/native-core/src/lib.rs | 2 - packages/native-core/src/node.rs | 26 +- packages/native-core/src/node_ref.rs | 38 +- packages/native-core/src/passes.rs | 366 ++++++++-------- packages/native-core/src/real_dom.rs | 368 ++++++++-------- packages/native-core/src/state.rs | 46 -- packages/native-core/src/tree.rs | 395 ++++++++---------- packages/native-core/src/utils/mod.rs | 4 +- .../src/utils/persistant_iterator.rs | 2 - 10 files changed, 564 insertions(+), 685 deletions(-) delete mode 100644 packages/native-core/src/state.rs diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index 5a837e33a..bf1f9ba92 100644 --- a/packages/native-core/Cargo.toml +++ b/packages/native-core/Cargo.toml @@ -20,7 +20,7 @@ smallvec = "1.6" rustc-hash = "1.1.0" anymap = "1.0.0-beta.2" slab = "0.4" -parking_lot = "0.12.1" +parking_lot = { version = "0.12.1", features = ["send_guard"] } crossbeam-deque = "0.8.2" dashmap = "5.4.0" hashbrown = { version = "0.13.2", features = ["raw"] } diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index c264155af..051a8731d 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -5,7 +5,6 @@ pub use node_ref::NodeMask; pub use passes::AnyMapLike; pub use passes::{Dependancy, Pass, TypeErasedPass}; use rustc_hash::FxHasher; -pub use state::State; pub use tree::NodeId; pub mod layout_attributes; @@ -13,7 +12,6 @@ pub mod node; pub mod node_ref; mod passes; pub mod real_dom; -pub mod state; pub mod tree; pub mod utils; diff --git a/packages/native-core/src/node.rs b/packages/native-core/src/node.rs index 137221864..7eb0fb31e 100644 --- a/packages/native-core/src/node.rs +++ b/packages/native-core/src/node.rs @@ -1,17 +1,8 @@ -use crate::{state::State, tree::NodeId}; +use crate::tree::NodeId; use dioxus_core::{AnyValue, BorrowedAttributeValue, ElementId}; use rustc_hash::{FxHashMap, FxHashSet}; use std::fmt::Debug; -/// The node is stored client side and stores only basic data about the node. -#[derive(Debug, Clone)] -pub struct Node, V: FromAnyValue = ()> { - /// 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 @@ -38,21 +29,18 @@ pub enum NodeType { Placeholder, } -impl, V: FromAnyValue> Node { +impl NodeData { pub(crate) fn new(node_type: NodeType) -> Self { - Node { - state: S::default(), - node_data: NodeData { - element_id: None, - node_type, - node_id: NodeId(0), - }, + NodeData { + element_id: None, + node_type, + node_id: NodeId(0), } } /// get the mounted id of the node pub fn mounted_id(&self) -> Option { - self.node_data.element_id + self.element_id } } diff --git a/packages/native-core/src/node_ref.rs b/packages/native-core/src/node_ref.rs index 6530d8dd3..a62a97d24 100644 --- a/packages/native-core/src/node_ref.rs +++ b/packages/native-core/src/node_ref.rs @@ -1,11 +1,47 @@ +use std::cmp::Ordering; + use dioxus_core::ElementId; use crate::{ node::{ElementNode, FromAnyValue, NodeData, NodeType, OwnedAttributeView}, - state::union_ordered_iter, NodeId, }; +/// Join two sorted iterators +pub(crate) fn union_ordered_iter<'a>( + s_iter: impl Iterator, + o_iter: impl Iterator, + new_len_guess: usize, +) -> Vec { + let mut s_peekable = s_iter.peekable(); + let mut o_peekable = o_iter.peekable(); + let mut v = Vec::with_capacity(new_len_guess); + while let Some(s_i) = s_peekable.peek() { + while let Some(o_i) = o_peekable.peek() { + match o_i.cmp(s_i) { + Ordering::Greater => { + break; + } + Ordering::Less => { + v.push(o_peekable.next().unwrap().to_string()); + } + Ordering::Equal => { + o_peekable.next(); + break; + } + } + } + v.push(s_peekable.next().unwrap().to_string()); + } + for o_i in o_peekable { + v.push(o_i.to_string()); + } + for w in v.windows(2) { + debug_assert!(w[1] > w[0]); + } + v +} + /// A view into a [VNode] with limited access. #[derive(Debug)] pub struct NodeView<'a, V: FromAnyValue = ()> { diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index e89a95ebf..b6a173b2b 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -1,18 +1,115 @@ use anymap::AnyMap; +use parking_lot::RwLock; use rustc_hash::FxHashSet; -use std::any::{Any, TypeId}; +use std::any::{self, Any, TypeId}; use std::collections::BTreeMap; +use std::marker::PhantomData; use std::sync::Arc; -use crate::node::{FromAnyValue, Node}; +use crate::node::{FromAnyValue, NodeData}; use crate::node_ref::NodeView; -use crate::state::State; -use crate::tree::TreeViewMut; -use crate::tree::{Tree, TreeView}; +use crate::real_dom::RealDom; +use crate::tree::{Node, TreeStateView}; use crate::{FxDashMap, FxDashSet, SendAnyMap}; use crate::{NodeId, NodeMask}; -pub trait Pass: Any { +#[derive(Default)] +struct DirtyNodes { + passes_dirty: Vec, +} + +impl DirtyNodes { + pub fn add_node(&mut self, node_id: NodeId) { + let node_id = node_id.0; + let index = node_id / 64; + let bit = node_id % 64; + let encoded = 1 << bit; + if let Some(passes) = self.passes_dirty.get_mut(index) { + *passes |= encoded; + } else { + self.passes_dirty.resize(index + 1, 0); + self.passes_dirty[index] |= encoded; + } + } + + pub fn is_empty(&self) -> bool { + self.passes_dirty.iter().all(|dirty| *dirty == 0) + } + + pub fn pop(&mut self) -> Option { + let index = self.passes_dirty.iter().position(|dirty| *dirty != 0)?; + let passes = self.passes_dirty[index]; + let node_id = passes.trailing_zeros(); + let encoded = 1 << node_id; + self.passes_dirty[index] &= !encoded; + Some(NodeId((index * 64) + node_id as usize)) + } +} + +#[derive(Default, Clone)] +pub struct DirtyNodeStates { + dirty: Arc>>>, +} + +impl DirtyNodeStates { + pub fn insert(&self, pass_id: TypeId, node_id: NodeId, height: u16) { + let mut dirty = self.dirty.write(); + if let Some(dirty) = dirty.get_mut(&height) { + if let Some(mut entry) = dirty.get_mut(&pass_id) { + entry.add_node(node_id); + } else { + let mut entry = DirtyNodes::default(); + entry.add_node(node_id); + dirty.insert(pass_id, entry); + } + } else { + let mut entry = DirtyNodes::default(); + entry.add_node(node_id); + let hm = FxDashMap::default(); + hm.insert(pass_id, entry); + dirty.insert(height, hm); + } + } + + fn pop_front(&self, pass_id: TypeId) -> Option<(u16, NodeId)> { + let dirty_read = self.dirty.read(); + let (&height, values) = dirty_read + .iter() + .find(|(_, values)| values.contains_key(&pass_id))?; + let mut dirty = values.get_mut(&pass_id)?; + let node_id = dirty.pop()?; + if dirty.is_empty() { + values.remove(&pass_id); + } + if values.is_empty() { + let mut dirty_write = self.dirty.write(); + dirty_write.remove(&height); + } + + Some((height, node_id)) + } + + fn pop_back(&self, pass_id: TypeId) -> Option<(u16, NodeId)> { + let dirty_read = self.dirty.read(); + let (&height, values) = dirty_read + .iter() + .rev() + .find(|(_, values)| values.contains_key(&pass_id))?; + let mut dirty = values.get_mut(&pass_id)?; + let node_id = dirty.pop()?; + if dirty.is_empty() { + values.remove(&pass_id); + } + if values.is_empty() { + let mut dirty_write = self.dirty.write(); + dirty_write.remove(&height); + } + + Some((height, node_id)) + } +} + +pub trait Pass: Any { /// This is a tuple of (T: Any, ..) type ParentDependencies: Dependancy; /// This is a tuple of (T: Any, ..) @@ -26,23 +123,21 @@ pub trait Pass: Any { node_view: NodeView, node: ::ElementBorrowed<'a>, parent: Option<::ElementBorrowed<'a>>, - children: Option< - impl Iterator::ElementBorrowed<'a>>, - >, + children: Option::ElementBorrowed<'a>>>, context: &SendAnyMap, ) -> bool; fn validate() { // no type can be a child and parent dependency - for type_id in Self::parent_type_ids() { - for type_id2 in Self::child_type_ids() { + for type_id in Self::parent_type_ids().into_iter().copied() { + for type_id2 in Self::child_type_ids().into_iter().copied() { if type_id == type_id2 { panic!("type cannot be both a parent and child dependency"); } } } // this type should not be a node dependency - for type_id in Self::node_type_ids() { + for type_id in Self::node_type_ids().into_iter().copied() { if type_id == TypeId::of::() { panic!("The current type cannot be a node dependency"); } @@ -58,85 +153,74 @@ pub trait Pass: Any { } } - fn to_type_erased>() -> TypeErasedPass + fn to_type_erased() -> TypeErasedPass where Self: Sized, { Self::validate(); TypeErasedPass { this_type_id: TypeId::of::(), - combined_dependancy_type_ids: Self::all_dependanices().into_iter().collect(), + combined_dependancy_type_ids: Self::all_dependanices().into_iter().copied().collect(), parent_dependant: !Self::parent_type_ids().is_empty(), child_dependant: !Self::child_type_ids().is_empty(), dependants: FxHashSet::default(), mask: Self::NODE_MASK, pass_direction: Self::pass_direction(), pass: Box::new( - |node_id: NodeId, any_map: &mut Tree>, context: &SendAnyMap| { - let (current_node, parent, children) = any_map - .node_parent_children_mut(node_id) - .expect("tried to run pass on node that does not exist"); - let current_node_raw = current_node as *mut Node; - let node = Self::NodeDependencies::borrow_elements_from(¤t_node.state) - .expect("tried to get a pass that does not exist"); - let parent = parent.map(|parent| { - Self::ParentDependencies::borrow_elements_from(&parent.state) - .expect("tried to get a pass that does not exist") - }); - let children = children.map(|children| { - children.map(|child| { - Self::ChildDependencies::borrow_elements_from(&child.state) - .expect("tried to get a pass that does not exist") - }) - }); - // safety: we have varified the pass is valid in the is_valid function - let myself: &mut Self = unsafe { - (*current_node_raw) - .state - .get_mut() - .expect("tried to get a pass that does not exist") - }; + |node_id: NodeId, tree: &mut TreeStateView, context: &SendAnyMap| { + debug_assert!(!Self::NodeDependencies::type_ids() + .into_iter() + .any(|id| *id == TypeId::of::())); + // get all of the states from the tree view + // Safety: No node has itself as a parent or child. + let node_raw = tree.get_mut::(node_id).unwrap() as *mut Self; + let node_data = tree.get_single::>(node_id).unwrap(); + let node = tree.get::(node_id).unwrap(); + let children = tree.children::(node_id); + let parent = tree.parent::(node_id); + let myself = unsafe { &mut *node_raw }; + myself.pass( - NodeView::new(¤t_node.node_data, Self::NODE_MASK), + NodeView::new(&node_data, Self::NODE_MASK), node, parent, children, context, ) }, - ) - as Box>, &SendAnyMap) -> bool + Send + Sync>, + ) as PassCallback, + phantom: PhantomData, } } - fn parent_type_ids() -> Vec { + fn parent_type_ids() -> Box<[TypeId]> { Self::ParentDependencies::type_ids() } - fn child_type_ids() -> Vec { + fn child_type_ids() -> Box<[TypeId]> { Self::ChildDependencies::type_ids() } - fn node_type_ids() -> Vec { + fn node_type_ids() -> Box<[TypeId]> { Self::NodeDependencies::type_ids() } - fn all_dependanices() -> Vec { - let mut dependencies = Self::parent_type_ids(); - dependencies.extend(Self::child_type_ids()); - dependencies.extend(Self::node_type_ids()); - dependencies + fn all_dependanices() -> Box<[TypeId]> { + let mut dependencies = Self::parent_type_ids().to_vec(); + dependencies.extend(Self::child_type_ids().into_iter()); + dependencies.extend(Self::node_type_ids().into_iter()); + dependencies.into_boxed_slice() } fn pass_direction() -> PassDirection { if Self::child_type_ids() .into_iter() - .any(|type_id| type_id == TypeId::of::()) + .any(|type_id| *type_id == TypeId::of::()) { PassDirection::ChildToParent } else if Self::parent_type_ids() .into_iter() - .any(|type_id| type_id == TypeId::of::()) + .any(|type_id| *type_id == TypeId::of::()) { PassDirection::ParentToChild } else { @@ -145,63 +229,57 @@ pub trait Pass: Any { } } -pub struct TypeErasedPass, V: FromAnyValue = ()> { +pub struct TypeErasedPass { pub(crate) this_type_id: TypeId, pub(crate) parent_dependant: bool, pub(crate) child_dependant: bool, pub(crate) combined_dependancy_type_ids: FxHashSet, pub(crate) dependants: FxHashSet, pub(crate) mask: NodeMask, - pass: PassCallback, + pass: PassCallback, pub(crate) pass_direction: PassDirection, + phantom: PhantomData, } -impl, V: FromAnyValue> TypeErasedPass { +impl TypeErasedPass { fn resolve( &self, - tree: &mut Tree>, - mut dirty: DirtyNodes, - dirty_states: &DirtyNodeStates, + mut tree: TreeStateView, + dirty: &DirtyNodeStates, nodes_updated: &FxDashSet, ctx: &SendAnyMap, ) { match self.pass_direction { PassDirection::ParentToChild => { - while let Some(id) = dirty.pop_front() { - if (self.pass)(id, tree, ctx) { + while let Some((height, id)) = dirty.pop_front(self.this_type_id) { + if (self.pass)(id, &mut tree, ctx) { nodes_updated.insert(id); for id in tree.children_ids(id).unwrap() { for dependant in &self.dependants { - dirty_states.insert(*dependant, *id); + dirty.insert(*dependant, id, height + 1); } - - let height = tree.height(*id).unwrap(); - dirty.insert(height, *id); } } } } PassDirection::ChildToParent => { - while let Some(id) = dirty.pop_back() { - if (self.pass)(id, tree, ctx) { + while let Some((height, id)) = dirty.pop_back(self.this_type_id) { + if (self.pass)(id, &mut tree, ctx) { nodes_updated.insert(id); if let Some(id) = tree.parent_id(id) { for dependant in &self.dependants { - dirty_states.insert(*dependant, id); + dirty.insert(*dependant, id, height - 1); } - - let height = tree.height(id).unwrap(); - dirty.insert(height, id); } } } } PassDirection::AnyOrder => { - while let Some(id) = dirty.pop_back() { - if (self.pass)(id, tree, ctx) { + while let Some((height, id)) = dirty.pop_back(self.this_type_id) { + if (self.pass)(id, &mut tree, ctx) { nodes_updated.insert(id); for dependant in &self.dependants { - dirty_states.insert(*dependant, id); + dirty.insert(*dependant, id, height); } } } @@ -217,30 +295,20 @@ pub enum PassDirection { AnyOrder, } -type PassCallback = - Box>, &SendAnyMap) -> bool + Send + Sync>; +type PassCallback = Box bool + Send + Sync>; -pub trait AnyMapLike { - fn get(&self) -> Option<&T>; - fn get_mut(&mut self) -> Option<&mut T>; +pub trait AnyMapLike<'a> { + fn get(self) -> Option<&'a T>; } -impl AnyMapLike for AnyMap { - fn get(&self) -> Option<&T> { +impl<'a> AnyMapLike<'a> for &'a AnyMap { + fn get(self) -> Option<&'a T> { self.get() } - - fn get_mut(&mut self) -> Option<&mut T> { - self.get_mut() - } } -impl AnyMapLike for SendAnyMap { - fn get(&self) -> Option<&T> { - todo!() - } - - fn get_mut(&mut self) -> Option<&mut T> { +impl<'a> AnyMapLike<'a> for &'a SendAnyMap { + fn get(self) -> Option<&'a T> { todo!() } } @@ -250,8 +318,10 @@ pub trait Dependancy { where Self: 'a; - fn borrow_elements_from(map: &T) -> Option>; - fn type_ids() -> Vec; + fn borrow_elements_from<'a, T: AnyMapLike<'a> + Copy>( + map: T, + ) -> Option>; + fn type_ids() -> Box<[TypeId]>; } macro_rules! impl_dependancy { @@ -260,12 +330,12 @@ macro_rules! impl_dependancy { type ElementBorrowed<'a> = ($(&'a $t,)*) where Self: 'a; #[allow(unused_variables, clippy::unused_unit, non_snake_case)] - fn borrow_elements_from(map: &T) -> Option> { + fn borrow_elements_from<'a, T: AnyMapLike<'a> + Copy>(map: T) -> Option> { Some(($(map.get::<$t>()?,)*)) } - fn type_ids() -> Vec { - vec![$(TypeId::of::<$t>()),*] + fn type_ids() -> Box<[TypeId]> { + Box::new([$(TypeId::of::<$t>()),*]) } } }; @@ -289,96 +359,12 @@ impl_dependancy!(A, B, C, D, E, F, G, H, I, J, K, L, M, N); impl_dependancy!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O); impl_dependancy!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P); -#[derive(Debug, Clone, PartialEq, Eq, Default)] -pub struct DirtyNodes { - map: BTreeMap>, -} - -impl DirtyNodes { - fn add_node(&mut self, node_id: NodeId) { - let node_id = node_id.0; - let index = node_id / 64; - let bit = node_id % 64; - let encoded = 1 << bit; - if let Some(passes) = self.passes_dirty.get_mut(index) { - *passes |= encoded; - } else { - self.passes_dirty.resize(index + 1, 0); - self.passes_dirty[index] |= encoded; - } - } - - fn is_empty(&self) -> bool { - self.passes_dirty.iter().all(|dirty| *dirty == 0) - } - - fn pop(&mut self) -> Option { - let index = self.passes_dirty.iter().position(|dirty| *dirty != 0)?; - let passes = self.passes_dirty[index]; - let node_id = passes.trailing_zeros(); - let encoded = 1 << node_id; - self.passes_dirty[index] &= !encoded; - Some(NodeId((index * 64) + node_id as usize)) - } -} - -#[derive(Default)] -pub struct DirtyNodeStates { - dirty: FxDashMap>, -} - -impl DirtyNodeStates { - pub fn insert(&self, pass_id: TypeId, node_id: NodeId) { - if let Some(mut dirty) = self.dirty.get_mut(&node_id) { - dirty.insert(pass_id); - } else { - let mut v = FxHashSet::default(); - v.insert(pass_id); - self.dirty.insert(node_id, v); - } - } - - fn all_dirty(&self, pass_id: TypeId, dirty_nodes: &mut DirtyNodes, tree: &impl TreeView) { - for entry in self.dirty.iter() { - let node_id = entry.key(); - let dirty = entry.value(); - if dirty.contains(&pass_id) { - dirty_nodes.insert(tree.height(*node_id).unwrap(), *node_id); - } - } - if values.is_empty() { - self.dirty.remove(&height); - } - - Some((height, node_id)) - } - - fn pop_back(&mut self, pass_id: PassId) -> Option<(u16, NodeId)> { - let (&height, values) = self - .dirty - .iter_mut() - .rev() - .find(|(_, values)| values.contains_key(&pass_id))?; - let dirty = values.get_mut(&pass_id)?; - let node_id = dirty.pop()?; - if dirty.is_empty() { - values.remove(&pass_id); - } - if values.is_empty() { - self.dirty.remove(&height); - } - - Some((height, node_id)) - } -} - -pub fn resolve_passes + Send, V: FromAnyValue + Send>( - tree: &mut Tree>, +pub fn resolve_passes( + tree: &mut RealDom, dirty_nodes: DirtyNodeStates, - passes: &[TypeErasedPass], ctx: SendAnyMap, ) -> FxDashSet { - let dirty_states = Arc::new(dirty_nodes); + let passes = &tree.passes; let mut resolved_passes: FxHashSet = FxHashSet::default(); let mut resolving = Vec::new(); let nodes_updated = Arc::new(FxDashSet::default()); @@ -399,21 +385,15 @@ pub fn resolve_passes + Send, V: FromAnyValue + Send>( pass_indexes_remaining.remove(i); resolving.push(pass_id); currently_in_use.insert(pass.this_type_id); - // this is safe because each pass only has mutable access to the member associated with this_type_id and we have verified that the pass does not overlap with any other pass currently running - let tree_unbounded_mut = unsafe { &mut *(tree as *mut _) }; - let dirty_states = dirty_states.clone(); + let tree_view = tree.tree.state_views( + [pass.this_type_id], + pass.combined_dependancy_type_ids.iter().copied(), + ); + let dirty_nodes = dirty_nodes.clone(); let nodes_updated = nodes_updated.clone(); let ctx = ctx.clone(); s.spawn(move || { - let mut dirty = DirtyNodes::default(); - dirty_states.all_dirty(pass_id, &mut dirty, tree_unbounded_mut); - pass.resolve( - tree_unbounded_mut, - dirty, - &dirty_states, - &nodes_updated, - &ctx, - ); + pass.resolve(tree_view, &dirty_nodes, &nodes_updated, &ctx); }); } else { i += 1; diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 9e7a909fb..4574d23c6 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -1,15 +1,14 @@ use dioxus_core::{BorrowedAttributeValue, ElementId, Mutations, TemplateNode}; +use parking_lot::{MappedRwLockReadGuard, MappedRwLockWriteGuard}; use rustc_hash::{FxHashMap, FxHashSet}; -use std::ops::{Index, IndexMut}; +use std::any::Any; use crate::node::{ - ElementNode, FromAnyValue, Node, NodeData, NodeType, OwnedAttributeDiscription, - OwnedAttributeValue, + ElementNode, FromAnyValue, NodeData, NodeType, OwnedAttributeDiscription, OwnedAttributeValue, }; use crate::node_ref::{AttributeMask, NodeMask}; use crate::passes::{resolve_passes, DirtyNodeStates, TypeErasedPass}; -use crate::state::State; -use crate::tree::{NodeId, Tree, TreeLike, TreeView, TreeViewMut}; +use crate::tree::{EntryBuilder, NodeId, Tree}; use crate::{FxDashSet, SendAnyMap}; /// A Dom that can sync with the VirtualDom mutations intended for use in lazy renderers. @@ -18,41 +17,36 @@ use crate::{FxDashSet, SendAnyMap}; /// /// # Custom values /// To allow custom values to be passed into attributes implement FromAnyValue on a type that can represent your custom value and specify the V generic to be that type. If you have many different custom values, it can be useful to use a enum type to represent the varients. -pub struct RealDom, V: FromAnyValue = ()> { - tree: Tree>, +pub struct RealDom { + pub(crate) tree: Tree, /// a map from element id to real node id node_id_mapping: Vec>, nodes_listening: FxHashMap>, stack: Vec, templates: FxHashMap>, - pub(crate) passes: Box<[TypeErasedPass]>, + pub(crate) passes: Box<[TypeErasedPass]>, pub(crate) nodes_updated: FxHashMap, passes_updated: DirtyNodeStates, parent_changed_nodes: FxHashSet, child_changed_nodes: FxHashSet, nodes_created: FxHashSet, + phantom: std::marker::PhantomData, } -impl, V: FromAnyValue> Default for RealDom { - fn default() -> Self { - Self::new() - } -} - -impl, V: FromAnyValue> RealDom { - pub fn new() -> RealDom { - let mut root = Node::new(NodeType::Element(ElementNode { +impl RealDom { + pub fn new(mut passes: Box<[TypeErasedPass]>) -> RealDom { + let tree = Tree::new(); + let root_id = tree.root(); + let root = tree.get_node(root_id); + let mut root_node: NodeData = NodeData::new(NodeType::Element(ElementNode { tag: "Root".to_string(), namespace: Some("Root".to_string()), attributes: FxHashMap::default(), listeners: FxHashSet::default(), })); - root.node_data.element_id = Some(ElementId(0)); - let mut tree = Tree::new(root); - let root_id = tree.root(); - tree.get_mut(root_id).unwrap().node_data.node_id = root_id; - - let mut passes = S::create_passes(); + root_node.element_id = Some(ElementId(0)); + root_node.node_id = root_id; + root.insert(root_node); // resolve dependants for each pass for i in 1..passes.len() { @@ -83,6 +77,7 @@ impl, V: FromAnyValue> RealDom { parent_changed_nodes: FxHashSet::default(), child_changed_nodes: FxHashSet::default(), nodes_created: FxHashSet::default(), + phantom: std::marker::PhantomData, } } @@ -107,9 +102,9 @@ impl, V: FromAnyValue> RealDom { } 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); + let mut node = self.tree.write::(node_id).unwrap(); + let node_id = node.node_id; + node.element_id = Some(element_id); if self.node_id_mapping.len() <= element_id.0 { self.node_id_mapping.resize(element_id.0 + 1, None); } @@ -124,14 +119,21 @@ impl, V: FromAnyValue> RealDom { current } - fn create_node(&mut self, node: Node, mark_dirty: bool) -> NodeId { - let node_id = self.tree.create_node(node); - let node = self.tree.get_mut(node_id).unwrap(); - node.node_data.node_id = node_id; + fn create_node( + &mut self, + mut node: NodeData, + id: Option, + mark_dirty: bool, + ) -> EntryBuilder<'_> { + let mut node_entry = self.tree.create_node(); + let node_id = node_entry.id(); + node.node_id = node_id; + node.element_id = id; + node_entry.insert(node); if mark_dirty { self.nodes_created.insert(node_id); } - node_id + node_entry } fn add_child(&mut self, node_id: NodeId, child_id: NodeId) { @@ -146,7 +148,7 @@ impl, V: FromAnyValue> RealDom { attrs, children, } => { - let node = Node::new(NodeType::Element(ElementNode { + let node = NodeData::new(NodeType::Element(ElementNode { tag: tag.to_string(), namespace: namespace.map(|s| s.to_string()), attributes: attrs @@ -169,22 +171,22 @@ impl, V: FromAnyValue> RealDom { .collect(), listeners: FxHashSet::default(), })); - let node_id = self.create_node(node, true); + let node_id = self.create_node(node, None, true).id(); for child in *children { let child_id = self.create_template_node(child); self.add_child(node_id, child_id); } node_id } - TemplateNode::Text { text } => { - self.create_node(Node::new(NodeType::Text(text.to_string())), true) - } - TemplateNode::Dynamic { .. } => { - self.create_node(Node::new(NodeType::Placeholder), true) - } - TemplateNode::DynamicText { .. } => { - self.create_node(Node::new(NodeType::Text(String::new())), true) - } + TemplateNode::Text { text } => self + .create_node(NodeData::new(NodeType::Text(text.to_string())), None, true) + .id(), + TemplateNode::Dynamic { .. } => self + .create_node(NodeData::new(NodeType::Placeholder), None, true) + .id(), + TemplateNode::DynamicText { .. } => self + .create_node(NodeData::new(NodeType::Text(String::new())), None, true) + .id(), } } @@ -214,29 +216,26 @@ impl, V: FromAnyValue> RealDom { self.set_element_id(self.load_child(path), id); } CreatePlaceholder { id } => { - let node = Node::new(NodeType::Placeholder); - let node_id = self.create_node(node, true); + let node = NodeData::new(NodeType::Placeholder); + let node_id = self.create_node(node, None, true).id(); self.set_element_id(node_id, id); self.stack.push(node_id); } CreateTextNode { value, id } => { - let node = Node::new(NodeType::Text(value.to_string())); - let node_id = self.create_node(node, true); - let node = self.tree.get_mut(node_id).unwrap(); - node.node_data.element_id = Some(id); + let node_data = NodeData::new(NodeType::Text(value.to_string())); + let node_id = self.create_node(node_data, None, true).id(); + self.set_element_id(node_id, id); self.stack.push(node_id); } 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 { + let mut node = self.get_mut(node_id).unwrap(); + if let NodeTypeMut::Text(text) = node.node_type_mut() { *text = value.to_string(); } else { - node.node_data.node_type = NodeType::Text(value.to_string()); + node.set_type(NodeType::Text(value.to_string())); } - - self.mark_dirty(node_id, NodeMask::new().with_text()); } LoadTemplate { name, index, id } => { let template_id = self.templates[name][index]; @@ -350,15 +349,14 @@ impl, V: FromAnyValue> RealDom { /// 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> { + pub fn get_listening_sorted(&self, event: &'static str) -> Vec { if let Some(nodes) = self.nodes_listening.get(event) { - let mut listening: Vec<_> = nodes.iter().map(|id| self.get(*id).unwrap()).collect(); - listening.sort_by(|n1, n2| { - (self.tree.height(n1.node_data.node_id)) - .cmp(&self.tree.height(n2.node_data.node_id)) - .reverse() - }); - listening + let mut listening: Vec<_> = nodes + .iter() + .map(|id| (*id, self.tree.height(*id).unwrap())) + .collect(); + listening.sort_by(|(_, h1), (_, h2)| h1.cmp(h2).reverse()); + listening.into_iter().map(|(id, _)| id).collect() } else { Vec::new() } @@ -376,160 +374,94 @@ impl, V: FromAnyValue> RealDom { } fn clone_node(&mut self, node_id: NodeId) -> NodeId { - let node = self.tree.get(node_id).unwrap(); - let new_node = Node { - state: node.state.clone(), - node_data: node.node_data.clone(), - }; - let new_id = self.create_node(new_node, true); + let node = self.get(node_id).unwrap(); + let new_node = node.node_data().clone(); + let new_id = self.create_node(new_node, None, true).id(); - 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.add_child(new_id, child_id); - } + let child_id = self.clone_node(child); + self.add_child(new_id, child_id); } new_id } + fn root(&self) -> NodeId { + self.tree.root() + } + + fn get(&self, id: NodeId) -> Option> { + self.tree.contains(id).then(|| NodeRef { id, dom: &self }) + } + pub fn get_mut(&mut self, id: NodeId) -> Option> { - self.tree.get_mut(id).map(|node| NodeMut { - node: &mut node.node_data, + self.tree.contains(id).then(|| NodeMut { + id, dirty: NodeMask::new(), - nodes_updated: &mut self.nodes_updated, + dom: self, }) } /// WARNING: This escapes the reactive system that the real dom uses. Any changes made with this method will not trigger updates in states when [RealDom::update_state] is called. - pub fn get_mut_raw(&mut self, id: NodeId) -> Option<&mut Node> { - self.tree.get_mut(id) + pub fn get_mut_raw(&mut self, id: NodeId) -> Option<&mut NodeData> { + self.tree.write(id) } -} -impl + Send, V: FromAnyValue + Send> 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, ctx: SendAnyMap, ) -> (FxDashSet, FxHashMap) { - let passes = &self.passes; let dirty_nodes = std::mem::take(&mut self.passes_updated); let nodes_updated = std::mem::take(&mut self.nodes_updated); for (&node, 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(node) { + if let Some(height) = self.tree.height(node) { for pass in &*self.passes { if mask.overlaps(&pass.mask) { - dirty_nodes.insert(pass.this_type_id, node); + dirty_nodes.insert(pass.this_type_id, node, height); } } } } for node in std::mem::take(&mut self.child_changed_nodes) { // remove any nodes that were created and then removed in the same mutations from the dirty nodes list - if self.tree.contains(node) { + if let Some(height) = self.tree.height(node) { for pass in &*self.passes { if pass.child_dependant { - dirty_nodes.insert(pass.this_type_id, node); + dirty_nodes.insert(pass.this_type_id, node, height); } } } } for node in std::mem::take(&mut self.parent_changed_nodes) { // remove any nodes that were created and then removed in the same mutations from the dirty nodes list - if self.tree.contains(node) { + if let Some(height) = self.tree.height(node) { for pass in &*self.passes { if pass.parent_dependant { - dirty_nodes.insert(pass.this_type_id, node); + dirty_nodes.insert(pass.this_type_id, node, height); } } } } for node in std::mem::take(&mut self.nodes_created) { // remove any nodes that were created and then removed in the same mutations from the dirty nodes list - if self.tree.contains(node) { + if let Some(height) = self.tree.height(node) { for pass in &*self.passes { - dirty_nodes.insert(pass.this_type_id, node); + dirty_nodes.insert(pass.this_type_id, node, height); } } } - let tree = &mut self.tree; - - ( - resolve_passes(tree, dirty_nodes, passes, ctx), - nodes_updated, - ) - } -} - -impl, V: FromAnyValue> TreeView> for RealDom { - type Iterator<'a> = > as TreeView>>::Iterator<'a>; - - fn root(&self) -> NodeId { - self.tree.root() + (resolve_passes(self, dirty_nodes, ctx), nodes_updated) } - fn get(&self, id: NodeId) -> Option<&Node> { - self.tree.get(id) - } - - fn children(&self, id: NodeId) -> Option> { - self.tree.children(id) - } - - fn children_ids(&self, id: NodeId) -> Option<&[NodeId]> { - self.tree.children_ids(id) - } - - fn parent(&self, id: NodeId) -> Option<&Node> { - self.tree.parent(id) - } - - fn parent_id(&self, id: NodeId) -> Option { - self.tree.parent_id(id) - } - - fn height(&self, id: NodeId) -> Option { - self.tree.height(id) - } - - fn size(&self) -> usize { - self.tree.size() - } -} - -impl, V: FromAnyValue> TreeLike> for RealDom { - fn create_node(&mut self, node: Node) -> NodeId { - let id = self.tree.create_node(node); - self.tree.get_mut(id).unwrap().node_data.node_id = id; - self.nodes_created.insert(id); - id - } - - fn add_child(&mut self, parent: NodeId, child: NodeId) { - // mark the parent's children changed - self.mark_child_changed(parent); - // mark the child's parent changed - self.mark_parent_added_or_removed(child); - self.tree.add_child(parent, child); - } - - fn remove(&mut self, id: NodeId) -> Option> { + fn remove(&mut self, id: NodeId) { if let Some(parent_id) = self.tree.parent_id(id) { self.mark_child_changed(parent_id); } self.tree.remove(id) } - fn remove_all_children(&mut self, id: NodeId) -> Vec> { - self.mark_child_changed(id); - self.tree.remove_all_children(id) - } - fn replace(&mut self, old: NodeId, new: NodeId) { if let Some(parent_id) = self.tree.parent_id(old) { self.mark_child_changed(parent_id); @@ -555,68 +487,108 @@ impl, V: FromAnyValue> TreeLike> for RealDom { } } -impl + Send, V: FromAnyValue> Index for RealDom { - type Output = Node; +// impl Index for RealDom { +// type Output = Node; - fn index(&self, id: ElementId) -> &Self::Output { - self.tree.get(self.element_to_node_id(id)).unwrap() +// fn index(&self, id: ElementId) -> &Self::Output { +// self.tree.get(self.element_to_node_id(id)).unwrap() +// } +// } + +// impl Index for RealDom { +// type Output = Node; + +// fn index(&self, idx: NodeId) -> &Self::Output { +// self.tree.get(idx).unwrap() +// } +// } + +// impl IndexMut for RealDom { +// 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: NodeId) -> &mut Self::Output { +// self.tree.get_mut(idx).unwrap() +// } +// } + +pub struct NodeRef<'a, V: FromAnyValue + Send = ()> { + id: NodeId, + dom: &'a RealDom, +} + +impl<'a, V: FromAnyValue + Send> NodeRef<'a, V> { + pub fn node_data(&self) -> &NodeData { + self.read().unwrap() } -} -impl + Send, V: FromAnyValue> Index for RealDom { - type Output = Node; - - fn index(&self, idx: NodeId) -> &Self::Output { - self.tree.get(idx).unwrap() - } -} - -impl + Send, V: FromAnyValue> IndexMut for RealDom { - fn index_mut(&mut self, id: ElementId) -> &mut Self::Output { - self.tree.get_mut(self.element_to_node_id(id)).unwrap() - } -} - -impl + Send, V: FromAnyValue> IndexMut for RealDom { - fn index_mut(&mut self, idx: NodeId) -> &mut Self::Output { - self.tree.get_mut(idx).unwrap() - } -} - -pub struct NodeMut<'a, V: FromAnyValue = ()> { - node: &'a mut NodeData, - dirty: NodeMask, - nodes_updated: &'a mut FxHashMap, -} - -impl<'a, V: FromAnyValue> NodeMut<'a, V> { pub fn node_type(&self) -> &NodeType { - &self.node.node_type + &self.node_data().node_type + } + + pub fn read(&self) -> Option<&T> { + self.dom.tree.read(self.id) + } +} + +pub struct NodeMut<'a, V: FromAnyValue + Send = ()> { + id: NodeId, + dom: &'a mut RealDom, + dirty: NodeMask, +} + +impl<'a, V: FromAnyValue + Send> NodeMut<'a, V> { + fn node_data(&self) -> &NodeData { + self.read().unwrap() + } + + fn node_data_mut(&mut self) -> &mut NodeData { + self.dom.tree.write(self.id).unwrap() + } + + pub fn node_type(&self) -> &NodeType { + &self.node_data().node_type } pub fn node_type_mut(&mut self) -> NodeTypeMut<'_, V> { - match &mut self.node.node_type { - NodeType::Element(element) => NodeTypeMut::Element(ElementNodeMut { - element, - dirty: &mut self.dirty, - }), + let Self { id, dom, dirty } = self; + let node_type = &mut dom.tree.write::>(*id).unwrap().node_type; + match node_type { + NodeType::Element(element) => NodeTypeMut::Element(ElementNodeMut { element, dirty }), NodeType::Text(text) => { - self.dirty.set_text(); + dirty.set_text(); NodeTypeMut::Text(text) } NodeType::Placeholder => NodeTypeMut::Placeholder, } } + + pub fn set_type(&mut self, new: NodeType) { + self.node_data_mut().node_type = new; + self.dirty = NodeMask::ALL; + } + + pub fn read(&self) -> Option<&T> { + self.dom.tree.read(self.id) + } + + pub fn write(&mut self) -> Option<&T> { + todo!("get_mut with mark as dirty") + } } -impl Drop for NodeMut<'_, V> { +impl Drop for NodeMut<'_, V> { fn drop(&mut self) { - let node_id = self.node.node_id; + let node_id = self.node_data().node_id; let mask = std::mem::take(&mut self.dirty); - if let Some(node) = self.nodes_updated.get_mut(&node_id) { + let nodes_updated = &mut self.dom.nodes_updated; + if let Some(node) = nodes_updated.get_mut(&node_id) { *node = node.union(&mask); } else { - self.nodes_updated.insert(node_id, mask); + nodes_updated.insert(node_id, mask); } } } diff --git a/packages/native-core/src/state.rs b/packages/native-core/src/state.rs deleted file mode 100644 index ef30a7fb6..000000000 --- a/packages/native-core/src/state.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::{ - node::FromAnyValue, - passes::{AnyMapLike, TypeErasedPass}, -}; -use std::cmp::Ordering; - -/// Join two sorted iterators -pub(crate) fn union_ordered_iter<'a>( - s_iter: impl Iterator, - o_iter: impl Iterator, - new_len_guess: usize, -) -> Vec { - let mut s_peekable = s_iter.peekable(); - let mut o_peekable = o_iter.peekable(); - let mut v = Vec::with_capacity(new_len_guess); - while let Some(s_i) = s_peekable.peek() { - while let Some(o_i) = o_peekable.peek() { - match o_i.cmp(s_i) { - Ordering::Greater => { - break; - } - Ordering::Less => { - v.push(o_peekable.next().unwrap().to_string()); - } - Ordering::Equal => { - o_peekable.next(); - break; - } - } - } - v.push(s_peekable.next().unwrap().to_string()); - } - for o_i in o_peekable { - v.push(o_i.to_string()); - } - for w in v.windows(2) { - debug_assert!(w[1] > w[0]); - } - v -} - -/// Do not implement this trait. It is only meant to be derived and used through [crate::real_dom::RealDom]. -pub trait State: Default + Clone + AnyMapLike + 'static { - #[doc(hidden)] - fn create_passes() -> Box<[TypeErasedPass]>; -} diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index 26b89a580..a6ad60a9a 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -7,17 +7,19 @@ use std::collections::VecDeque; use std::fmt::Debug; use std::hash::BuildHasherDefault; -#[derive(PartialEq, Eq, Clone, Copy, Debug)] +use crate::{AnyMapLike, Dependancy}; + +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Hash)] pub struct NodeId(pub usize); #[derive(PartialEq, Eq, Clone, Debug)] -pub struct Node { +pub(crate) struct Node { parent: Option, children: Vec, height: u16, } -pub struct NodeView<'a> { +pub(crate) struct NodeView<'a> { tree: &'a Tree, id: NodeId, } @@ -27,25 +29,21 @@ impl NodeView<'_> { where T: Any, { - self.tree.nodes.get_slab_mut::().insert(self.id, data); + self.tree.nodes.write_slab::().insert(self.id, data); } - pub fn get(&self) -> MappedRwLockReadGuard<'_, T> + pub fn get(&self) -> &T where T: Any, { - MappedRwLockReadGuard::map(self.tree.nodes.get_slab::(), |slab| { - slab.get(self.id).unwrap() - }) + self.tree.nodes.read_slab::().get(self.id).unwrap() } - pub fn get_mut(&self) -> MappedRwLockWriteGuard<'_, T> + pub fn get_mut(&self) -> &mut T where T: Any, { - MappedRwLockWriteGuard::map(self.tree.nodes.get_slab_mut::(), |slab| { - slab.get_mut(self.id).unwrap() - }) + self.tree.nodes.write_slab::().get_mut(self.id).unwrap() } pub fn height(&self) -> u16 { @@ -66,7 +64,7 @@ impl NodeView<'_> { } #[derive(Debug)] -pub struct Tree { +pub(crate) struct Tree { nodes: AnySlab, root: NodeId, } @@ -84,20 +82,20 @@ impl Tree { Self { nodes, root } } - fn node_slab(&self) -> MappedRwLockReadGuard<'_, SlabStorage> { - self.nodes.get_slab() + fn node_slab(&self) -> &SlabStorage { + self.nodes.read_slab() } - pub fn get_node_data(&self, id: NodeId) -> MappedRwLockReadGuard<'_, Node> { - MappedRwLockReadGuard::map(self.node_slab(), |slab| slab.get(id).unwrap()) + pub fn get_node_data(&self, id: NodeId) -> &Node { + self.node_slab().get(id).unwrap() } - fn node_slab_mut(&self) -> MappedRwLockWriteGuard<'_, SlabStorage> { - self.nodes.get_slab_mut() + fn node_slab_mut(&self) -> &mut SlabStorage { + self.nodes.write_slab() } - pub fn get_node_data_mut(&self, id: NodeId) -> MappedRwLockWriteGuard<'_, Node> { - MappedRwLockWriteGuard::map(self.node_slab_mut(), |slab| slab.get_mut(id).unwrap()) + pub fn get_node_data_mut(&self, id: NodeId) -> &mut Node { + self.node_slab_mut().get_mut(id).unwrap() } pub fn remove(&mut self, id: NodeId) { @@ -131,7 +129,7 @@ impl Tree { } } - pub fn create_node(&mut self) -> Entry<'_> { + pub fn create_node(&mut self) -> EntryBuilder<'_> { let mut node = self.nodes.insert(); node.insert(Node { parent: None, @@ -206,7 +204,7 @@ impl Tree { self.set_height(new_id, height); } - pub fn insert(&mut self, id: NodeId, value: T) { + pub fn insert(&mut self, id: NodeId, value: T) { self.nodes.add(id, value); } @@ -214,42 +212,60 @@ impl Tree { self.nodes.len() } - pub fn state_view(&self) -> TreeStateView<'_, T> { + pub fn state_views( + &self, + immutable: impl IntoIterator, + mutable: impl IntoIterator, + ) -> TreeStateView<'_> { TreeStateView { nodes_data: self.node_slab(), - nodes: self.nodes.get_slab(), + nodes: immutable + .into_iter() + .map(|id| (id, MaybeRead::Read(self.nodes.data.get(&id).unwrap()))) + .chain( + mutable + .into_iter() + .map(|id| (id, MaybeRead::Write(self.nodes.data.get_mut(&id).unwrap()))), + ) + .collect(), root: self.root, } } - pub fn state_view_mut(&mut self) -> TreeStateViewMut<'_, T> { - TreeStateViewMut { - nodes_data: self.node_slab(), - nodes: self.nodes.get_slab_mut(), - root: self.root, - } + pub fn read(&self, id: NodeId) -> Option<&T> { + self.nodes.read_slab().get(id) } -} -impl NodeDataView for Tree { - fn root(&self) -> NodeId { + pub fn write(&mut self, id: NodeId) -> Option<&mut T> { + self.nodes.write_slab().get_mut(id) + } + + pub fn get_node(&self, id: NodeId) -> NodeView<'_> { + NodeView { tree: self, id } + } + + pub fn contains(&self, id: NodeId) -> bool { + self.nodes.contains(id) + } + + pub fn root(&self) -> NodeId { self.root } - fn parent_id(&self, id: NodeId) -> Option { + pub fn parent_id(&self, id: NodeId) -> Option { self.get_node_data(id).parent } - fn children_ids(&self, id: NodeId) -> Option> { + pub fn children_ids(&self, id: NodeId) -> Option> { Some(self.get_node_data(id).children.clone()) } - fn height(&self, id: NodeId) -> Option { + pub fn height(&self, id: NodeId) -> Option { Some(self.get_node_data(id).height) } } -pub trait NodeDataView { +pub(crate) trait NodeDataView { fn root(&self) -> NodeId; fn parent_id(&self, id: NodeId) -> Option; @@ -259,7 +275,7 @@ pub trait NodeDataView { fn height(&self, id: NodeId) -> Option; } -pub trait TreeView: Sized + NodeDataView { +trait TreeView: Sized + NodeDataView { fn get(&self, id: NodeId) -> Option<&T>; fn get_unchecked(&self, id: NodeId) -> &T { @@ -298,7 +314,7 @@ pub trait TreeView: Sized + NodeDataView { } } -pub trait TreeViewMut: Sized + TreeView { +trait TreeViewMut: Sized + TreeView { fn get_mut(&mut self, id: NodeId) -> Option<&mut T>; unsafe fn get_mut_unchecked(&mut self, id: NodeId) -> &mut T { @@ -357,175 +373,91 @@ pub trait TreeViewMut: Sized + TreeView { } } -pub trait TreeLike { - fn create_node(&mut self) -> Entry; - - fn add_child(&mut self, parent: NodeId, child: NodeId); - - fn remove(&mut self, id: NodeId); - - fn replace(&mut self, old: NodeId, new: NodeId); - - fn insert_before(&mut self, id: NodeId, new: NodeId); - - fn insert_after(&mut self, id: NodeId, new: NodeId); +enum MaybeRead<'a> { + Read(&'a Box), + Write(&'a mut Box), } -pub struct TreeStateView<'a, T> { - nodes_data: MappedRwLockReadGuard<'a, SlabStorage>, - nodes: MappedRwLockReadGuard<'a, SlabStorage>, +#[derive(Clone, Copy)] +struct TreeStateViewEntry<'a> { + view: &'a TreeStateView<'a>, + id: NodeId, +} + +impl<'a> AnyMapLike<'a> for TreeStateViewEntry<'a> { + fn get(self) -> Option<&'a T> { + self.view.get_slab().and_then(|slab| slab.get(self.id)) + } +} + +pub struct TreeStateView<'a> { + nodes_data: &'a SlabStorage, + nodes: hashbrown::HashMap, BuildHasherDefault>, root: NodeId, } -pub struct TreeStateViewMut<'a, T> { - nodes_data: MappedRwLockReadGuard<'a, SlabStorage>, - nodes: MappedRwLockWriteGuard<'a, SlabStorage>, - root: NodeId, -} +impl<'a> TreeStateView<'a> { + fn get_slab(&self) -> Option<&SlabStorage> { + self.nodes + .get(&TypeId::of::()) + .and_then(|gaurd| match gaurd { + MaybeRead::Read(gaurd) => gaurd.as_any().downcast_ref::>(), + MaybeRead::Write(gaurd) => gaurd.as_any().downcast_ref::>(), + }) + } + + fn get_slab_mut(&mut self) -> Option<&mut SlabStorage> { + self.nodes + .get_mut(&TypeId::of::()) + .and_then(|gaurd| match gaurd { + MaybeRead::Read(gaurd) => None, + MaybeRead::Write(gaurd) => gaurd.as_any_mut().downcast_mut::>(), + }) + } -impl<'a, T> NodeDataView for TreeStateView<'a, T> { fn root(&self) -> NodeId { self.root } - fn children_ids(&self, id: NodeId) -> Option> { + pub fn children_ids(&self, id: NodeId) -> Option> { self.nodes_data.get(id).map(|node| node.children.clone()) } - fn parent_id(&self, id: NodeId) -> Option { + pub fn parent_id(&self, id: NodeId) -> Option { self.nodes_data.get(id).and_then(|node| node.parent) } - fn height(&self, id: NodeId) -> Option { + pub fn height(&self, id: NodeId) -> Option { self.nodes_data.get(id).map(|n| n.height) } -} -impl<'a, T> TreeView for TreeStateView<'a, T> { - fn get(&self, id: NodeId) -> Option<&T> { - self.nodes.get(id) + pub fn get(&self, id: NodeId) -> Option> { + T::borrow_elements_from(self.entry(id)) } - fn children(&self, id: NodeId) -> Option> { + pub fn get_single(&self, id: NodeId) -> Option<&T> { + self.get_slab().and_then(|slab| slab.get(id)) + } + + pub fn get_mut(&mut self, id: NodeId) -> Option<&mut T> { + self.get_slab_mut().and_then(|slab| slab.get_mut(id)) + } + + fn entry(&self, id: NodeId) -> TreeStateViewEntry<'_> { + TreeStateViewEntry { view: self, id } + } + + pub fn children(&self, id: NodeId) -> Option>> { let ids = self.children_ids(id); - ids.map(|ids| ids.iter().map(|id| self.get_unchecked(*id)).collect()) - } - - fn parent(&self, id: NodeId) -> Option<&T> { - self.nodes_data - .get(id) - .and_then(|node| node.parent.map(|id| self.nodes.get(id).unwrap())) - } - - fn get_unchecked(&self, id: NodeId) -> &T { - unsafe { &self.nodes.get_unchecked(id) } - } -} - -impl<'a, T> NodeDataView for TreeStateViewMut<'a, T> { - fn root(&self) -> NodeId { - self.root - } - - fn children_ids(&self, id: NodeId) -> Option> { - self.nodes_data.get(id).map(|node| node.children.clone()) - } - - fn parent_id(&self, id: NodeId) -> Option { - self.nodes_data.get(id).and_then(|node| node.parent) - } - - fn height(&self, id: NodeId) -> Option { - self.nodes_data.get(id).map(|n| n.height) - } -} - -impl<'a, T> TreeView for TreeStateViewMut<'a, T> { - fn get(&self, id: NodeId) -> Option<&T> { - self.nodes.get(id) - } - - fn children(&self, id: NodeId) -> Option> { - let ids = self.children_ids(id); - ids.map(|ids| ids.iter().map(|id| self.get_unchecked(*id)).collect()) - } - fn parent(&self, id: NodeId) -> Option<&T> { - self.nodes_data - .get(id) - .and_then(|node| node.parent.map(|id| self.nodes.get(id).unwrap())) - } - - fn get_unchecked(&self, id: NodeId) -> &T { - unsafe { &self.nodes.get_unchecked(id) } - } -} - -impl<'a, T> TreeViewMut for TreeStateViewMut<'a, T> { - fn get_mut(&mut self, id: NodeId) -> Option<&mut T> { - self.nodes.get_mut(id) - } - - fn parent_mut(&mut self, id: NodeId) -> Option<&mut T> { - let parent_id = self.parent_id(id)?; - unsafe { Some(self.get_mut_unchecked(parent_id)) } - } - - fn children_mut(&mut self, id: NodeId) -> Option> { - // Safety: No node has itself as a parent. - if let Some(children_ids) = self.children_ids(id) { - let children_ids = children_ids.to_vec(); - unsafe { - self.nodes - .get_many_mut_unchecked(children_ids.into_iter().rev().map(|id| id)) - } - } else { - None - } - } - - unsafe fn get_mut_unchecked(&mut self, id: NodeId) -> &mut T { - unsafe { self.nodes.get_unchecked_mut(id) } - } - - fn node_parent_mut(&mut self, id: NodeId) -> Option<(&mut T, Option<&mut T>)> { - if let Some(parent_id) = self.parent_id(id) { - self.nodes - .get2_mut(id, parent_id) - .map(|(node, parent)| (node, Some(parent))) - } else { - self.nodes.get_mut(id).map(|node| (node, None)) - } - } - - fn node_parent_children_mut( - &mut self, - id: NodeId, - ) -> Option<(&mut T, Option<&mut T>, Option>)> { - // Safety: No node has itself as a parent. - let unbounded_self = unsafe { &mut *(self as *mut Self) }; - self.node_parent_mut(id).map(move |(node, parent)| { - let children = unbounded_self.children_mut(id); - (node, parent, children) + ids.and_then(|ids| { + ids.iter() + .map(|id| T::borrow_elements_from(self.entry(*id))) + .collect() }) } - fn parent_child_mut(&mut self, id: NodeId) -> Option<(&mut T, Vec<&mut T>)> { - // Safety: No node will appear as a child twice - if let Some(children_ids) = self.children_ids(id) { - debug_assert!(!children_ids.iter().any(|child_id| *child_id == id)); - let mut borrowed = unsafe { - let as_vec = children_ids.to_vec(); - self.nodes - .get_many_mut_unchecked( - as_vec.into_iter().map(|id| id).chain(std::iter::once(id)), - ) - .unwrap() - }; - let node = borrowed.pop().unwrap(); - Some((node, borrowed)) - } else { - None - } + pub fn parent(&self, id: NodeId) -> Option> { + T::borrow_elements_from(self.entry(id)) } } @@ -727,7 +659,7 @@ fn get_node_children_mut() { } #[derive(Debug)] -struct SlabStorage { +pub(crate) struct SlabStorage { data: Vec>, } @@ -737,7 +669,7 @@ impl Default for SlabStorage { } } -trait AnySlabStorageImpl: Any { +trait AnySlabStorageImpl: Any + Send + Sync { fn remove(&mut self, id: NodeId); fn as_any(&self) -> &dyn Any; fn as_any_mut(&mut self) -> &mut dyn Any; @@ -806,7 +738,7 @@ impl SlabStorage { } } -impl AnySlabStorageImpl for SlabStorage { +impl AnySlabStorageImpl for SlabStorage { fn remove(&mut self, id: NodeId) { self.data[id.0].take(); } @@ -820,12 +752,9 @@ impl AnySlabStorageImpl for SlabStorage { } } -struct AnySlab { - data: hashbrown::HashMap< - TypeId, - RwLock>, - BuildHasherDefault, - >, +pub(crate) struct AnySlab { + data: hashbrown::HashMap, BuildHasherDefault>, + filled: Vec, free: VecDeque, len: usize, } @@ -850,80 +779,104 @@ impl AnySlab { fn new() -> Self { Self { data: Default::default(), + filled: Default::default(), free: VecDeque::new(), len: 0, } } - fn try_get_slab(&self) -> Option>> { + fn try_read_slab(&self) -> Option<&SlabStorage> { self.data .get(&TypeId::of::()) - .map(|x| RwLockReadGuard::map(x.read(), |x| x.as_any().downcast_ref().unwrap())) + .map(|x| x.as_any().downcast_ref().unwrap()) } - fn get_slab(&self) -> MappedRwLockReadGuard<'_, SlabStorage> { - self.try_get_slab().unwrap() + fn read_slab(&self) -> &SlabStorage { + self.try_read_slab().unwrap() } - fn try_get_slab_mut(&self) -> Option>> { + fn try_write_slab(&self) -> Option<&mut SlabStorage> { self.data .get(&TypeId::of::()) - .map(|x| RwLockWriteGuard::map(x.write(), |x| x.as_any_mut().downcast_mut().unwrap())) + .map(|x| x.as_any_mut().downcast_mut().unwrap()) } - fn get_slab_mut(&self) -> MappedRwLockWriteGuard<'_, SlabStorage> { - self.try_get_slab_mut().unwrap() + fn write_slab(&self) -> &mut SlabStorage { + self.try_write_slab().unwrap() } - fn get_or_insert_slab_mut(&mut self) -> MappedRwLockWriteGuard<'_, SlabStorage> { - RwLockWriteGuard::map( - self.data - .entry(TypeId::of::()) - .or_insert_with(|| RwLock::new(Box::new(SlabStorage::::default()))) - .write(), - |x| x.as_any_mut().downcast_mut().unwrap(), - ) + fn get_slab_mut_borrow(&mut self) -> &mut SlabStorage { + self.data + .get_mut(&TypeId::of::()) + .unwrap() + .as_any_mut() + .downcast_mut() + .unwrap() } - fn insert(&mut self) -> Entry<'_> { + fn get_or_insert_slab_mut(&mut self) -> &mut SlabStorage { + self.data + .entry(TypeId::of::()) + .or_insert_with(|| Box::new(SlabStorage::::default())) + .as_any_mut() + .downcast_mut() + .unwrap() + } + + fn insert(&mut self) -> EntryBuilder<'_> { let id = if let Some(id) = self.free.pop_front() { + self.filled[id.0] = true; id } else { let id = self.len; + self.filled.push(true); self.len += 1; NodeId(id) }; - Entry { id, inner: self } + EntryBuilder { id, inner: self } } - fn add(&mut self, id: NodeId, value: T) { + fn add(&mut self, id: NodeId, value: T) { self.get_or_insert_slab_mut().insert(id, value); } fn remove(&mut self, id: NodeId) { for slab in self.data.values_mut() { - slab.write().remove(id); + slab.remove(id); } + self.filled[id.0] = true; self.free.push_back(id); } fn len(&self) -> usize { self.len - self.free.len() } + + fn contains(&self, id: NodeId) -> bool { + self.filled.get(id.0).copied().unwrap_or_default() + } } -pub struct Entry<'a> { +pub struct EntryBuilder<'a> { id: NodeId, inner: &'a mut AnySlab, } -impl Entry<'_> { - pub fn insert(&mut self, value: T) { +impl EntryBuilder<'_> { + pub fn insert(&mut self, value: T) { self.inner .get_or_insert_slab_mut::() .insert(self.id, value); } + pub fn get(&self) -> Option<&T> { + self.inner.read_slab().get(self.id) + } + + pub fn get_mut(&mut self) -> Option<&mut T> { + self.inner.write_slab().get_mut(self.id) + } + pub fn remove(self) { self.inner.remove(self.id); } @@ -946,7 +899,7 @@ fn remove() { slab.remove(node1_id); - assert!(slab.get_slab::().get(node1_id).is_none()); + assert!(slab.read_slab::().get(node1_id).is_none()); assert_eq!(slab.len(), 1); } @@ -966,7 +919,7 @@ fn get_many_mut_unchecked() { println!("ids: {:#?}", [parent, child, grandchild]); { - let mut i32_slab = slab.get_slab_mut::(); + let mut i32_slab = slab.write_slab::(); let all = unsafe { i32_slab.get_many_mut_unchecked([parent, child, grandchild].into_iter()) } .unwrap(); @@ -974,7 +927,7 @@ fn get_many_mut_unchecked() { } { - let mut i32_slab = slab.get_slab_mut::(); + let mut i32_slab = slab.write_slab::(); assert!( unsafe { i32_slab.get_many_mut_unchecked([NodeId(3), NodeId(100)].into_iter()) } .is_none() @@ -1001,8 +954,8 @@ fn get_many_many_mut_unchecked() { println!("slab: {:#?}", slab); - let mut num_entries = slab.get_slab_mut::(); - let mut str_entries = slab.get_slab_mut::<&str>(); + let mut num_entries = slab.write_slab::(); + let mut str_entries = slab.write_slab::<&str>(); let all_num = unsafe { num_entries diff --git a/packages/native-core/src/utils/mod.rs b/packages/native-core/src/utils/mod.rs index 58e98b0fa..9ddb1a1c7 100644 --- a/packages/native-core/src/utils/mod.rs +++ b/packages/native-core/src/utils/mod.rs @@ -1,3 +1,3 @@ -mod persistant_iterator; -pub use persistant_iterator::*; +// mod persistant_iterator; +// pub use persistant_iterator::*; pub mod cursor; diff --git a/packages/native-core/src/utils/persistant_iterator.rs b/packages/native-core/src/utils/persistant_iterator.rs index 0ca13492e..f5423cb76 100644 --- a/packages/native-core/src/utils/persistant_iterator.rs +++ b/packages/native-core/src/utils/persistant_iterator.rs @@ -2,8 +2,6 @@ use crate::{ node::{ElementNode, FromAnyValue, NodeType}, passes::{AnyMapLike, TypeErasedPass}, real_dom::RealDom, - state::State, - tree::TreeView, NodeId, }; use dioxus_core::{Mutation, Mutations}; From ebb6300fc7be0c675534442385abaac8a0a092ef Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 20 Jan 2023 13:23:26 -0600 Subject: [PATCH 013/103] clean up the API --- packages/native-core/src/passes.rs | 21 +- packages/native-core/src/real_dom.rs | 4 +- packages/native-core/src/tree.rs | 294 ++++++++++------------ packages/native-core/tests/miri_native.rs | 228 ++++++++--------- 4 files changed, 255 insertions(+), 292 deletions(-) diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index b6a173b2b..fc464b722 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -9,7 +9,7 @@ use std::sync::Arc; use crate::node::{FromAnyValue, NodeData}; use crate::node_ref::NodeView; use crate::real_dom::RealDom; -use crate::tree::{Node, TreeStateView}; +use crate::tree::{self, Node, TreeStateView}; use crate::{FxDashMap, FxDashSet, SendAnyMap}; use crate::{NodeId, NodeMask}; @@ -374,6 +374,7 @@ pub fn resolve_passes( let mut currently_in_use = FxHashSet::::default(); std::thread::scope(|s| { let mut i = 0; + let dynamically_borrowed_tree = tree.tree.dynamically_borrowed(); while i < pass_indexes_remaining.len() { let passes_idx = pass_indexes_remaining[i]; let pass = &passes[passes_idx]; @@ -385,16 +386,18 @@ pub fn resolve_passes( pass_indexes_remaining.remove(i); resolving.push(pass_id); currently_in_use.insert(pass.this_type_id); - let tree_view = tree.tree.state_views( - [pass.this_type_id], + dynamically_borrowed_tree.with_view( pass.combined_dependancy_type_ids.iter().copied(), + [pass.this_type_id], + |tree_view| { + let dirty_nodes = dirty_nodes.clone(); + let nodes_updated = nodes_updated.clone(); + let ctx = ctx.clone(); + // s.spawn(move || { + pass.resolve(tree_view, &dirty_nodes, &nodes_updated, &ctx); + // }); + }, ); - let dirty_nodes = dirty_nodes.clone(); - let nodes_updated = nodes_updated.clone(); - let ctx = ctx.clone(); - s.spawn(move || { - pass.resolve(tree_view, &dirty_nodes, &nodes_updated, &ctx); - }); } else { i += 1; } diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 4574d23c6..3a8d9adff 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -35,9 +35,9 @@ pub struct RealDom { impl RealDom { pub fn new(mut passes: Box<[TypeErasedPass]>) -> RealDom { - let tree = Tree::new(); + let mut tree = Tree::new(); let root_id = tree.root(); - let root = tree.get_node(root_id); + let mut root = tree.get_node(root_id); let mut root_node: NodeData = NodeData::new(NodeType::Element(ElementNode { tag: "Root".to_string(), namespace: Some("Root".to_string()), diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index a6ad60a9a..86a03d93d 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -1,12 +1,13 @@ use parking_lot::{ MappedRwLockReadGuard, MappedRwLockWriteGuard, RwLock, RwLockReadGuard, RwLockWriteGuard, }; -use rustc_hash::FxHasher; +use rustc_hash::{FxHashMap, FxHasher}; use std::any::{Any, TypeId}; use std::collections::VecDeque; use std::fmt::Debug; use std::hash::BuildHasherDefault; +use crate::node::NodeData; use crate::{AnyMapLike, Dependancy}; #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Hash)] @@ -20,12 +21,12 @@ pub(crate) struct Node { } pub(crate) struct NodeView<'a> { - tree: &'a Tree, + tree: &'a mut Tree, id: NodeId, } impl NodeView<'_> { - pub fn insert(&self, data: T) + pub fn insert(&mut self, data: T) where T: Any, { @@ -39,7 +40,7 @@ impl NodeView<'_> { self.tree.nodes.read_slab::().get(self.id).unwrap() } - pub fn get_mut(&self) -> &mut T + pub fn get_mut(&mut self) -> &mut T where T: Any, { @@ -90,11 +91,11 @@ impl Tree { self.node_slab().get(id).unwrap() } - fn node_slab_mut(&self) -> &mut SlabStorage { + fn node_slab_mut(&mut self) -> &mut SlabStorage { self.nodes.write_slab() } - pub fn get_node_data_mut(&self, id: NodeId) -> &mut Node { + pub fn get_node_data_mut(&mut self, id: NodeId) -> &mut Node { self.node_slab_mut().get_mut(id).unwrap() } @@ -118,7 +119,7 @@ impl Tree { recurse(self, id); } - fn set_height(&self, node: NodeId, height: u16) { + fn set_height(&mut self, node: NodeId, height: u16) { let children = { let mut node = self.get_node_data_mut(node); node.height = height; @@ -145,7 +146,6 @@ impl Tree { let parent = node_state.get_mut(parent).unwrap(); parent.children.push(new); let height = parent.height + 1; - drop(node_state); self.set_height(new, height); } @@ -162,7 +162,6 @@ impl Tree { } } let height = parent.height + 1; - drop(node_state); self.set_height(new_id, height); } } @@ -183,7 +182,6 @@ impl Tree { .unwrap(); parent.children.insert(index, new_id); let height = parent.height + 1; - drop(node_state); self.set_height(new_id, height); } @@ -200,7 +198,6 @@ impl Tree { .unwrap(); parent.children.insert(index + 1, new_id); let height = parent.height + 1; - drop(node_state); self.set_height(new_id, height); } @@ -212,22 +209,9 @@ impl Tree { self.nodes.len() } - pub fn state_views( - &self, - immutable: impl IntoIterator, - mutable: impl IntoIterator, - ) -> TreeStateView<'_> { - TreeStateView { - nodes_data: self.node_slab(), - nodes: immutable - .into_iter() - .map(|id| (id, MaybeRead::Read(self.nodes.data.get(&id).unwrap()))) - .chain( - mutable - .into_iter() - .map(|id| (id, MaybeRead::Write(self.nodes.data.get_mut(&id).unwrap()))), - ) - .collect(), + pub fn dynamically_borrowed(&mut self) -> DynamicallyBorrowedTree<'_> { + DynamicallyBorrowedTree { + nodes: self.nodes.dynamically_borrowed(), root: self.root, } } @@ -240,7 +224,7 @@ impl Tree { self.nodes.write_slab().get_mut(id) } - pub fn get_node(&self, id: NodeId) -> NodeView<'_> { + pub fn get_node(&mut self, id: NodeId) -> NodeView<'_> { NodeView { tree: self, id } } @@ -265,111 +249,34 @@ impl Tree { } } -pub(crate) trait NodeDataView { - fn root(&self) -> NodeId; - - fn parent_id(&self, id: NodeId) -> Option; - - fn children_ids(&self, id: NodeId) -> Option>; - - fn height(&self, id: NodeId) -> Option; +pub(crate) struct DynamicallyBorrowedTree<'a> { + nodes: DynamiclyBorrowedAnySlabView<'a>, + root: NodeId, } -trait TreeView: Sized + NodeDataView { - fn get(&self, id: NodeId) -> Option<&T>; - - fn get_unchecked(&self, id: NodeId) -> &T { - unsafe { self.get(id).unwrap_unchecked() } - } - - fn children(&self, id: NodeId) -> Option>; - - fn parent(&self, id: NodeId) -> Option<&T>; - - 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()); - } - } +impl<'a> DynamicallyBorrowedTree<'a> { + pub fn with_view( + &self, + immutable: impl IntoIterator, + mutable: impl IntoIterator, + mut f: impl FnMut(TreeStateView), + ) { + let node_data = self.node_slab(); + let nodes = FxHashMap::default(); + { + let mut view = TreeStateView { + root: self.root, + nodes_data: &*node_data, + nodes, + }; + f(view) } } - 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); - } - } - } - } - } -} - -trait TreeViewMut: Sized + TreeView { - fn get_mut(&mut self, id: NodeId) -> Option<&mut T>; - - unsafe fn get_mut_unchecked(&mut self, id: NodeId) -> &mut T { - unsafe { self.get_mut(id).unwrap_unchecked() } - } - - fn children_mut(&mut self, id: NodeId) -> Option>; - - fn parent_child_mut(&mut self, id: NodeId) -> Option<(&mut T, Vec<&mut T>)> { - 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 parent_mut(&mut self, id: NodeId) -> Option<&mut T>; - - fn node_parent_mut(&mut self, id: NodeId) -> Option<(&mut T, Option<&mut T>)>; - - #[allow(clippy::type_complexity)] - fn node_parent_children_mut( - &mut self, - id: NodeId, - ) -> Option<(&mut T, Option<&mut T>, Option>)>; - - 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_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); - } - } - } - } + fn node_slab(&self) -> MappedRwLockReadGuard<'_, SlabStorage> { + RwLockReadGuard::map(self.nodes.get_slab(TypeId::of::()).unwrap(), |slab| { + slab.as_any().downcast_ref().unwrap() + }) } } @@ -379,7 +286,7 @@ enum MaybeRead<'a> { } #[derive(Clone, Copy)] -struct TreeStateViewEntry<'a> { +pub struct TreeStateViewEntry<'a> { view: &'a TreeStateView<'a>, id: NodeId, } @@ -392,7 +299,7 @@ impl<'a> AnyMapLike<'a> for TreeStateViewEntry<'a> { pub struct TreeStateView<'a> { nodes_data: &'a SlabStorage, - nodes: hashbrown::HashMap, BuildHasherDefault>, + nodes: FxHashMap>, root: NodeId, } @@ -443,7 +350,7 @@ impl<'a> TreeStateView<'a> { self.get_slab_mut().and_then(|slab| slab.get_mut(id)) } - fn entry(&self, id: NodeId) -> TreeStateViewEntry<'_> { + pub fn entry(&self, id: NodeId) -> TreeStateViewEntry<'_> { TreeStateViewEntry { view: self, id } } @@ -459,6 +366,33 @@ impl<'a> TreeStateView<'a> { pub fn parent(&self, id: NodeId) -> Option> { T::borrow_elements_from(self.entry(id)) } + + fn traverse_depth_first(&self, mut f: impl FnMut(T::ElementBorrowed<'_>)) { + 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_breadth_first(&self, mut f: impl FnMut(T::ElementBorrowed<'_>)) { + 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); + } + } + } + } + } } #[test] @@ -479,9 +413,9 @@ fn creation() { assert_eq!(tree.parent_id(parent_id), None); assert_eq!(tree.parent_id(child_id).unwrap(), parent_id); assert_eq!(tree.children_ids(parent_id).unwrap(), &[child_id]); - let view = tree.state_view::(); - assert_eq!(*view.get(parent_id).unwrap(), 1); - assert_eq!(*view.get(child_id).unwrap(), 0); + + assert_eq!(*tree.read::(parent_id).unwrap(), 1); + assert_eq!(*tree.read::(child_id).unwrap(), 0); } #[test] @@ -512,11 +446,11 @@ fn insertion() { 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]); - let view = tree.state_view::(); - assert_eq!(*view.get(parent).unwrap(), 0); - assert_eq!(*view.get(before).unwrap(), 1); - assert_eq!(*view.get(child).unwrap(), 2); - assert_eq!(*view.get(after).unwrap(), 3); + + assert_eq!(*tree.read::(parent).unwrap(), 0); + assert_eq!(*tree.read::(before).unwrap(), 1); + assert_eq!(*tree.read::(child).unwrap(), 2); + assert_eq!(*tree.read::(after).unwrap(), 3); } #[test] @@ -547,13 +481,11 @@ fn deletion() { 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]); - { - let view = tree.state_view::(); - assert_eq!(*view.get(parent).unwrap(), 0); - assert_eq!(*view.get(before).unwrap(), 1); - assert_eq!(*view.get(child).unwrap(), 2); - assert_eq!(*view.get(after).unwrap(), 3); - } + + assert_eq!(*tree.read::(parent).unwrap(), 0); + assert_eq!(*tree.read::(before).unwrap(), 1); + assert_eq!(*tree.read::(child).unwrap(), 2); + assert_eq!(*tree.read::(after).unwrap(), 3); tree.remove(child); @@ -565,13 +497,11 @@ fn deletion() { 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]); - { - let view = tree.state_view::(); - assert_eq!(*view.get(parent).unwrap(), 0); - assert_eq!(*view.get(before).unwrap(), 1); - assert_eq!(view.get(child), None); - assert_eq!(*view.get(after).unwrap(), 3); - } + + assert_eq!(*tree.read::(parent).unwrap(), 0); + assert_eq!(*tree.read::(before).unwrap(), 1); + assert_eq!(tree.read::(child), None); + assert_eq!(*tree.read::(after).unwrap(), 3); tree.remove(before); @@ -581,12 +511,10 @@ fn deletion() { assert_eq!(tree.height(after), Some(1)); assert_eq!(tree.parent_id(after).unwrap(), parent); assert_eq!(tree.children_ids(parent).unwrap(), &[after]); - { - let view = tree.state_view::(); - assert_eq!(*view.get(parent).unwrap(), 0); - assert_eq!(view.get(before), None); - assert_eq!(*view.get(after).unwrap(), 3); - } + + assert_eq!(*tree.read::(parent).unwrap(), 0); + assert_eq!(tree.read::(before), None); + assert_eq!(*tree.read::(after).unwrap(), 3); tree.remove(after); @@ -594,11 +522,9 @@ fn deletion() { assert_eq!(tree.size(), 1); assert_eq!(tree.height(parent), Some(0)); assert_eq!(tree.children_ids(parent).unwrap(), &[]); - { - let view = tree.state_view::(); - assert_eq!(*view.get(parent).unwrap(), 0); - assert_eq!(view.get(after), None); - } + + assert_eq!(*tree.read::(parent).unwrap(), 0); + assert_eq!(tree.read::(after), None); } #[test] @@ -752,6 +678,30 @@ impl AnySlabStorageImpl for SlabStorage { } } +pub(crate) struct DynamiclyBorrowedAnySlabView<'a> { + data: hashbrown::HashMap< + TypeId, + RwLock<&'a mut dyn AnySlabStorageImpl>, + BuildHasherDefault, + >, +} + +impl<'a> DynamiclyBorrowedAnySlabView<'a> { + fn get_slab( + &self, + type_id: TypeId, + ) -> Option> { + self.data.get(&type_id).map(|x| x.read()) + } + + fn get_slab_mut( + &self, + type_id: TypeId, + ) -> Option> { + self.data.get(&type_id).map(|x| x.write()) + } +} + pub(crate) struct AnySlab { data: hashbrown::HashMap, BuildHasherDefault>, filled: Vec, @@ -795,13 +745,13 @@ impl AnySlab { self.try_read_slab().unwrap() } - fn try_write_slab(&self) -> Option<&mut SlabStorage> { + fn try_write_slab(&mut self) -> Option<&mut SlabStorage> { self.data - .get(&TypeId::of::()) + .get_mut(&TypeId::of::()) .map(|x| x.as_any_mut().downcast_mut().unwrap()) } - fn write_slab(&self) -> &mut SlabStorage { + fn write_slab(&mut self) -> &mut SlabStorage { self.try_write_slab().unwrap() } @@ -855,6 +805,16 @@ impl AnySlab { fn contains(&self, id: NodeId) -> bool { self.filled.get(id.0).copied().unwrap_or_default() } + + fn dynamically_borrowed(&mut self) -> DynamiclyBorrowedAnySlabView<'_> { + DynamiclyBorrowedAnySlabView { + data: self + .data + .iter_mut() + .map(|(k, v)| (*k, RwLock::new(&mut **v))) + .collect(), + } + } } pub struct EntryBuilder<'a> { diff --git a/packages/native-core/tests/miri_native.rs b/packages/native-core/tests/miri_native.rs index 338f86915..674f69aa7 100644 --- a/packages/native-core/tests/miri_native.rs +++ b/packages/native-core/tests/miri_native.rs @@ -1,140 +1,140 @@ -use dioxus::prelude::*; -use dioxus_native_core::{ - node_ref::{AttributeMask, NodeView}, - real_dom::RealDom, - state::{ParentDepState, State}, - NodeMask, SendAnyMap, -}; -use dioxus_native_core_macro::{sorted_str_slice, State}; -use std::sync::{Arc, Mutex}; -use tokio::time::sleep; +// use dioxus::prelude::*; +// use dioxus_native_core::{ +// node_ref::{AttributeMask, NodeView}, +// real_dom::RealDom, +// state::{ParentDepState, State}, +// NodeMask, SendAnyMap, +// }; +// use dioxus_native_core_macro::{sorted_str_slice, State}; +// use std::sync::{Arc, Mutex}; +// use tokio::time::sleep; -#[derive(Debug, Clone, PartialEq, Eq, Default)] -pub struct BlablaState {} +// #[derive(Debug, Clone, PartialEq, Eq, Default)] +// pub struct BlablaState {} -/// Font style are inherited by default if not specified otherwise by some of the supported attributes. -impl ParentDepState for BlablaState { - type Ctx = (); - type DepState = (Self,); +// /// Font style are inherited by default if not specified otherwise by some of the supported attributes. +// impl ParentDepState for BlablaState { +// type Ctx = (); +// type DepState = (Self,); - const NODE_MASK: NodeMask = - NodeMask::new_with_attrs(AttributeMask::Static(&sorted_str_slice!(["blabla",]))); +// const NODE_MASK: NodeMask = +// NodeMask::new_with_attrs(AttributeMask::Static(&sorted_str_slice!(["blabla",]))); - fn reduce<'a>( - &mut self, - _node: NodeView, - _parent: Option<(&'a Self,)>, - _ctx: &Self::Ctx, - ) -> bool { - false - } -} +// fn reduce<'a>( +// &mut self, +// _node: NodeView, +// _parent: Option<(&'a Self,)>, +// _ctx: &Self::Ctx, +// ) -> bool { +// false +// } +// } -#[derive(Clone, State, Default, Debug)] -pub struct NodeState { - #[parent_dep_state(blabla)] - blabla: BlablaState, -} +// #[derive(Clone, State, Default, Debug)] +// pub struct NodeState { +// #[parent_dep_state(blabla)] +// blabla: BlablaState, +// } -mod dioxus_elements { - macro_rules! builder_constructors { - ( - $( - $(#[$attr:meta])* - $name:ident { - $( - $(#[$attr_method:meta])* - $fil:ident: $vil:ident, - )* - }; - )* - ) => { - $( - #[allow(non_camel_case_types)] - $(#[$attr])* - pub struct $name; +// mod dioxus_elements { +// macro_rules! builder_constructors { +// ( +// $( +// $(#[$attr:meta])* +// $name:ident { +// $( +// $(#[$attr_method:meta])* +// $fil:ident: $vil:ident, +// )* +// }; +// )* +// ) => { +// $( +// #[allow(non_camel_case_types)] +// $(#[$attr])* +// pub struct $name; - impl $name { - pub const TAG_NAME: &'static str = stringify!($name); - pub const NAME_SPACE: Option<&'static str> = None; +// impl $name { +// pub const TAG_NAME: &'static str = stringify!($name); +// pub const NAME_SPACE: Option<&'static str> = None; - $( - pub const $fil: (&'static str, Option<&'static str>, bool) = (stringify!($fil), None, false); - )* - } +// $( +// pub const $fil: (&'static str, Option<&'static str>, bool) = (stringify!($fil), None, false); +// )* +// } - impl GlobalAttributes for $name {} - )* - } - } +// impl GlobalAttributes for $name {} +// )* +// } +// } - pub trait GlobalAttributes {} +// pub trait GlobalAttributes {} - pub trait SvgAttributes {} +// pub trait SvgAttributes {} - builder_constructors! { - blabla { +// builder_constructors! { +// blabla { - }; - } -} +// }; +// } +// } -#[test] -fn native_core_is_okay() { - use std::time::Duration; +// #[test] +// fn native_core_is_okay() { +// use std::time::Duration; - fn app(cx: Scope) -> Element { - let colors = use_state(cx, || vec!["green", "blue", "red"]); - let padding = use_state(cx, || 10); +// fn app(cx: Scope) -> Element { +// let colors = use_state(cx, || vec!["green", "blue", "red"]); +// let padding = use_state(cx, || 10); - use_effect(cx, colors, |colors| async move { - sleep(Duration::from_millis(1000)).await; - colors.with_mut(|colors| colors.reverse()); - }); +// use_effect(cx, colors, |colors| async move { +// sleep(Duration::from_millis(1000)).await; +// colors.with_mut(|colors| colors.reverse()); +// }); - use_effect(cx, padding, |padding| async move { - sleep(Duration::from_millis(10)).await; - padding.with_mut(|padding| { - if *padding < 65 { - *padding += 1; - } else { - *padding = 5; - } - }); - }); +// use_effect(cx, padding, |padding| async move { +// sleep(Duration::from_millis(10)).await; +// padding.with_mut(|padding| { +// if *padding < 65 { +// *padding += 1; +// } else { +// *padding = 5; +// } +// }); +// }); - let _big = colors[0]; - let _mid = colors[1]; - let _small = colors[2]; +// let _big = colors[0]; +// let _mid = colors[1]; +// let _small = colors[2]; - cx.render(rsx! { - blabla {} - }) - } +// cx.render(rsx! { +// blabla {} +// }) +// } - let rt = tokio::runtime::Builder::new_current_thread() - .enable_time() - .build() - .unwrap(); +// let rt = tokio::runtime::Builder::new_current_thread() +// .enable_time() +// .build() +// .unwrap(); - rt.block_on(async { - let rdom = Arc::new(Mutex::new(RealDom::::new())); - let mut dom = VirtualDom::new(app); +// rt.block_on(async { +// let rdom = Arc::new(Mutex::new(RealDom::::new())); +// let mut dom = VirtualDom::new(app); - let muts = dom.rebuild(); - let (to_update, _diff) = rdom.lock().unwrap().apply_mutations(muts); +// let muts = dom.rebuild(); +// let (to_update, _diff) = rdom.lock().unwrap().apply_mutations(muts); - let ctx = SendAnyMap::new(); - rdom.lock().unwrap().update_state(to_update, ctx); +// let ctx = SendAnyMap::new(); +// rdom.lock().unwrap().update_state(to_update, ctx); - for _ in 0..10 { - dom.wait_for_work().await; +// for _ in 0..10 { +// dom.wait_for_work().await; - let mutations = dom.render_immediate(); - let (to_update, _diff) = rdom.lock().unwrap().apply_mutations(mutations); +// let mutations = dom.render_immediate(); +// let (to_update, _diff) = rdom.lock().unwrap().apply_mutations(mutations); - let ctx = SendAnyMap::new(); - rdom.lock().unwrap().update_state(to_update, ctx); - } - }); -} +// let ctx = SendAnyMap::new(); +// rdom.lock().unwrap().update_state(to_update, ctx); +// } +// }); +// } From e81f05fe9dfdf2aa3fb7ab3a8bee02180b1dbf00 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 20 Jan 2023 20:24:44 -0600 Subject: [PATCH 014/103] make native core compile --- packages/native-core/src/passes.rs | 2 +- packages/native-core/src/tree.rs | 132 ++++++++++++----------------- 2 files changed, 57 insertions(+), 77 deletions(-) diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index fc464b722..44377f4cd 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -372,7 +372,7 @@ pub fn resolve_passes( let mut pass_indexes_remaining: Vec<_> = (0..passes.len()).collect::>(); while !pass_indexes_remaining.is_empty() { let mut currently_in_use = FxHashSet::::default(); - std::thread::scope(|s| { + std::thread::scope(|_| { let mut i = 0; let dynamically_borrowed_tree = tree.tree.dynamically_borrowed(); while i < pass_indexes_remaining.len() { diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index 86a03d93d..21098d058 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -261,49 +261,56 @@ impl<'a> DynamicallyBorrowedTree<'a> { mutable: impl IntoIterator, mut f: impl FnMut(TreeStateView), ) { - let node_data = self.node_slab(); - let nodes = FxHashMap::default(); + let nodes_data = self.node_slab(); + let nodes_data: &SlabStorage = &*nodes_data; + let mut nodes = FxHashMap::default(); + for id in immutable { + nodes.insert(id, MaybeRead::Read(self.nodes.get_slab(id).unwrap())); + } + for id in mutable { + nodes.insert(id, MaybeRead::Write(self.nodes.get_slab_mut(id).unwrap())); + } { let mut view = TreeStateView { root: self.root, - nodes_data: &*node_data, + nodes_data, nodes, }; f(view) } } - fn node_slab(&self) -> MappedRwLockReadGuard<'_, SlabStorage> { + fn node_slab<'b>(&'b self) -> MappedRwLockReadGuard<'b, SlabStorage> { RwLockReadGuard::map(self.nodes.get_slab(TypeId::of::()).unwrap(), |slab| { slab.as_any().downcast_ref().unwrap() }) } } -enum MaybeRead<'a> { - Read(&'a Box), - Write(&'a mut Box), +enum MaybeRead<'a, 'b> { + Read(RwLockReadGuard<'a, &'b mut dyn AnySlabStorageImpl>), + Write(RwLockWriteGuard<'a, &'b mut dyn AnySlabStorageImpl>), } #[derive(Clone, Copy)] -pub struct TreeStateViewEntry<'a> { - view: &'a TreeStateView<'a>, +pub struct TreeStateViewEntry<'a, 'b> { + view: &'a TreeStateView<'a, 'b>, id: NodeId, } -impl<'a> AnyMapLike<'a> for TreeStateViewEntry<'a> { +impl<'a, 'b> AnyMapLike<'a> for TreeStateViewEntry<'a, 'b> { fn get(self) -> Option<&'a T> { self.view.get_slab().and_then(|slab| slab.get(self.id)) } } -pub struct TreeStateView<'a> { +pub struct TreeStateView<'a, 'b> { nodes_data: &'a SlabStorage, - nodes: FxHashMap>, + nodes: FxHashMap>, root: NodeId, } -impl<'a> TreeStateView<'a> { +impl<'a, 'b> TreeStateView<'a, 'b> { fn get_slab(&self) -> Option<&SlabStorage> { self.nodes .get(&TypeId::of::()) @@ -350,7 +357,7 @@ impl<'a> TreeStateView<'a> { self.get_slab_mut().and_then(|slab| slab.get_mut(id)) } - pub fn entry(&self, id: NodeId) -> TreeStateViewEntry<'_> { + pub fn entry(&self, id: NodeId) -> TreeStateViewEntry<'_, 'b> { TreeStateViewEntry { view: self, id } } @@ -527,62 +534,35 @@ fn deletion() { assert_eq!(tree.read::(after), None); } -#[test] -fn traverse_depth_first() { - let mut tree = Tree::new(); - let parent = tree.root(); - tree.insert(parent, 0); - let mut child1 = tree.create_node(); - child1.insert(1); - let child1 = child1.id(); - tree.add_child(parent, child1); - let mut grandchild1 = tree.create_node(); - grandchild1.insert(2); - let grandchild1 = grandchild1.id(); - tree.add_child(child1, grandchild1); - let mut child2 = tree.create_node(); - child2.insert(3); - let child2 = child2.id(); - tree.add_child(parent, child2); - let mut grandchild2 = tree.create_node(); - grandchild2.insert(4); - let grandchild2 = grandchild2.id(); - tree.add_child(child2, grandchild2); +// #[test] +// fn traverse_depth_first() { +// let mut tree = Tree::new(); +// let parent = tree.root(); +// tree.insert(parent, 0); +// let mut child1 = tree.create_node(); +// child1.insert(1); +// let child1 = child1.id(); +// tree.add_child(parent, child1); +// let mut grandchild1 = tree.create_node(); +// grandchild1.insert(2); +// let grandchild1 = grandchild1.id(); +// tree.add_child(child1, grandchild1); +// let mut child2 = tree.create_node(); +// child2.insert(3); +// let child2 = child2.id(); +// tree.add_child(parent, child2); +// let mut grandchild2 = tree.create_node(); +// grandchild2.insert(4); +// let grandchild2 = grandchild2.id(); +// tree.add_child(child2, grandchild2); - let view = tree.state_view::(); - let mut node_count = 0; - view.traverse_depth_first(move |node| { - assert_eq!(*node, node_count); - node_count += 1; - }); -} - -#[test] -fn get_node_children_mut() { - let mut tree = Tree::new(); - let parent = tree.root(); - tree.insert(parent, 0); - let mut child1 = tree.create_node(); - child1.insert(1); - let child1 = child1.id(); - tree.add_child(parent, child1); - let mut child2 = tree.create_node(); - child2.insert(2); - let child2 = child2.id(); - tree.add_child(parent, child2); - let mut child3 = tree.create_node(); - child3.insert(3); - let child3 = child3.id(); - tree.add_child(parent, child3); - - let mut view = tree.state_view_mut::(); - let (parent, children) = view.parent_child_mut(parent).unwrap(); - println!("Parent: {:#?}", parent); - println!("Children: {:#?}", children); - for (i, child) in children.into_iter().enumerate() { - assert_eq!(*child, i as i32 + 1); - } -} +// let view = tree.state_view::(); +// let mut node_count = 0; +// view.traverse_depth_first(move |node| { +// assert_eq!(*node, node_count); +// node_count += 1; +// }); +// } #[derive(Debug)] pub(crate) struct SlabStorage { @@ -687,17 +667,17 @@ pub(crate) struct DynamiclyBorrowedAnySlabView<'a> { } impl<'a> DynamiclyBorrowedAnySlabView<'a> { - fn get_slab( - &self, + fn get_slab<'b>( + &'b self, type_id: TypeId, - ) -> Option> { + ) -> Option> { self.data.get(&type_id).map(|x| x.read()) } - fn get_slab_mut( - &self, + fn get_slab_mut<'b>( + &'b self, type_id: TypeId, - ) -> Option> { + ) -> Option> { self.data.get(&type_id).map(|x| x.write()) } } @@ -915,7 +895,6 @@ fn get_many_many_mut_unchecked() { println!("slab: {:#?}", slab); let mut num_entries = slab.write_slab::(); - let mut str_entries = slab.write_slab::<&str>(); let all_num = unsafe { num_entries @@ -927,6 +906,7 @@ fn get_many_many_mut_unchecked() { .unwrap(); assert_eq!(all_num, [&mut 0, &mut 1, &mut 2]); + let mut str_entries = slab.write_slab::<&str>(); let all_str = unsafe { str_entries From adff97036dc7c6dde3a72af9b71af0e05f4cb89d Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 21 Jan 2023 07:44:38 -0600 Subject: [PATCH 015/103] clean up some unused code + clippy --- packages/native-core/src/passes.rs | 26 +++++++++++++------------- packages/native-core/src/real_dom.rs | 28 ++++++++++------------------ packages/native-core/src/tree.rs | 27 ++++++++++++--------------- 3 files changed, 35 insertions(+), 46 deletions(-) diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index 44377f4cd..854acfea6 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -1,7 +1,7 @@ use anymap::AnyMap; use parking_lot::RwLock; use rustc_hash::FxHashSet; -use std::any::{self, Any, TypeId}; +use std::any::{Any, TypeId}; use std::collections::BTreeMap; use std::marker::PhantomData; use std::sync::Arc; @@ -9,7 +9,7 @@ use std::sync::Arc; use crate::node::{FromAnyValue, NodeData}; use crate::node_ref::NodeView; use crate::real_dom::RealDom; -use crate::tree::{self, Node, TreeStateView}; +use crate::tree::TreeStateView; use crate::{FxDashMap, FxDashSet, SendAnyMap}; use crate::{NodeId, NodeMask}; @@ -129,22 +129,22 @@ pub trait Pass: Any { fn validate() { // no type can be a child and parent dependency - for type_id in Self::parent_type_ids().into_iter().copied() { - for type_id2 in Self::child_type_ids().into_iter().copied() { + for type_id in Self::parent_type_ids().iter().copied() { + for type_id2 in Self::child_type_ids().iter().copied() { if type_id == type_id2 { panic!("type cannot be both a parent and child dependency"); } } } // this type should not be a node dependency - for type_id in Self::node_type_ids().into_iter().copied() { + for type_id in Self::node_type_ids().iter().copied() { if type_id == TypeId::of::() { panic!("The current type cannot be a node dependency"); } } // no states have the same type id if Self::all_dependanices() - .into_iter() + .iter() .collect::>() .len() != Self::all_dependanices().len() @@ -160,7 +160,7 @@ pub trait Pass: Any { Self::validate(); TypeErasedPass { this_type_id: TypeId::of::(), - combined_dependancy_type_ids: Self::all_dependanices().into_iter().copied().collect(), + combined_dependancy_type_ids: Self::all_dependanices().iter().copied().collect(), parent_dependant: !Self::parent_type_ids().is_empty(), child_dependant: !Self::child_type_ids().is_empty(), dependants: FxHashSet::default(), @@ -169,7 +169,7 @@ pub trait Pass: Any { pass: Box::new( |node_id: NodeId, tree: &mut TreeStateView, context: &SendAnyMap| { debug_assert!(!Self::NodeDependencies::type_ids() - .into_iter() + .iter() .any(|id| *id == TypeId::of::())); // get all of the states from the tree view // Safety: No node has itself as a parent or child. @@ -181,7 +181,7 @@ pub trait Pass: Any { let myself = unsafe { &mut *node_raw }; myself.pass( - NodeView::new(&node_data, Self::NODE_MASK), + NodeView::new(node_data, Self::NODE_MASK), node, parent, children, @@ -207,19 +207,19 @@ pub trait Pass: Any { fn all_dependanices() -> Box<[TypeId]> { let mut dependencies = Self::parent_type_ids().to_vec(); - dependencies.extend(Self::child_type_ids().into_iter()); - dependencies.extend(Self::node_type_ids().into_iter()); + dependencies.extend(Self::child_type_ids().iter()); + dependencies.extend(Self::node_type_ids().iter()); dependencies.into_boxed_slice() } fn pass_direction() -> PassDirection { if Self::child_type_ids() - .into_iter() + .iter() .any(|type_id| *type_id == TypeId::of::()) { PassDirection::ChildToParent } else if Self::parent_type_ids() - .into_iter() + .iter() .any(|type_id| *type_id == TypeId::of::()) { PassDirection::ParentToChild diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 3a8d9adff..7febed2bb 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -1,5 +1,5 @@ use dioxus_core::{BorrowedAttributeValue, ElementId, Mutations, TemplateNode}; -use parking_lot::{MappedRwLockReadGuard, MappedRwLockWriteGuard}; + use rustc_hash::{FxHashMap, FxHashSet}; use std::any::Any; @@ -81,14 +81,6 @@ impl RealDom { } } - fn mark_dirty(&mut self, node_id: NodeId, mask: NodeMask) { - if let Some(node) = self.nodes_updated.get_mut(&node_id) { - *node = node.union(&mask); - } else { - self.nodes_updated.insert(node_id, mask); - } - } - fn mark_parent_added_or_removed(&mut self, node_id: NodeId) { self.parent_changed_nodes.insert(node_id); } @@ -247,7 +239,7 @@ impl RealDom { let new_nodes = self.stack.split_off(self.stack.len() - m); let old_node_id = self.element_to_node_id(id); for new in new_nodes { - self.tree.insert_before(old_node_id, new); + self.insert_before(old_node_id, new); } self.remove(old_node_id); } @@ -255,7 +247,7 @@ impl RealDom { 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_before(old_node_id, new); + self.insert_before(old_node_id, new); } self.remove(old_node_id); } @@ -263,7 +255,7 @@ impl RealDom { let new_nodes = self.stack.split_off(self.stack.len() - m); let old_node_id = self.element_to_node_id(id); for new in new_nodes.into_iter().rev() { - self.tree.insert_after(old_node_id, new); + self.insert_after(old_node_id, new); } } InsertBefore { id, m } => { @@ -385,12 +377,12 @@ impl RealDom { new_id } - fn root(&self) -> NodeId { + pub fn root(&self) -> NodeId { self.tree.root() } - fn get(&self, id: NodeId) -> Option> { - self.tree.contains(id).then(|| NodeRef { id, dom: &self }) + pub fn get(&self, id: NodeId) -> Option> { + self.tree.contains(id).then_some(NodeRef { id, dom: self }) } pub fn get_mut(&mut self, id: NodeId) -> Option> { @@ -462,7 +454,7 @@ impl RealDom { self.tree.remove(id) } - fn replace(&mut self, old: NodeId, new: NodeId) { + pub fn replace(&mut self, old: NodeId, new: NodeId) { if let Some(parent_id) = self.tree.parent_id(old) { self.mark_child_changed(parent_id); self.mark_parent_added_or_removed(new); @@ -470,7 +462,7 @@ impl RealDom { self.tree.replace(old, new); } - fn insert_before(&mut self, id: NodeId, new: NodeId) { + pub fn insert_before(&mut self, id: NodeId, new: NodeId) { if let Some(parent_id) = self.tree.parent_id(id) { self.mark_child_changed(parent_id); self.mark_parent_added_or_removed(new); @@ -478,7 +470,7 @@ impl RealDom { self.tree.insert_before(id, new); } - fn insert_after(&mut self, id: NodeId, new: NodeId) { + pub fn insert_after(&mut self, id: NodeId, new: NodeId) { if let Some(parent_id) = self.tree.parent_id(id) { self.mark_child_changed(parent_id); self.mark_parent_added_or_removed(new); diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index 21098d058..babdaded7 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -1,13 +1,10 @@ -use parking_lot::{ - MappedRwLockReadGuard, MappedRwLockWriteGuard, RwLock, RwLockReadGuard, RwLockWriteGuard, -}; +use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard, RwLockWriteGuard}; use rustc_hash::{FxHashMap, FxHasher}; use std::any::{Any, TypeId}; use std::collections::VecDeque; use std::fmt::Debug; use std::hash::BuildHasherDefault; -use crate::node::NodeData; use crate::{AnyMapLike, Dependancy}; #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Hash)] @@ -109,7 +106,7 @@ impl Tree { tree.nodes.remove(id); } { - let mut node_data_mut = self.node_slab_mut(); + let node_data_mut = self.node_slab_mut(); if let Some(parent) = node_data_mut.get(id).unwrap().parent { let parent = node_data_mut.get_mut(parent).unwrap(); parent.children.retain(|&child| child != id); @@ -141,7 +138,7 @@ impl Tree { } pub fn add_child(&mut self, parent: NodeId, new: NodeId) { - let mut node_state = self.node_slab_mut(); + let node_state = self.node_slab_mut(); node_state.get_mut(new).unwrap().parent = Some(parent); let parent = node_state.get_mut(parent).unwrap(); parent.children.push(new); @@ -151,7 +148,7 @@ impl Tree { pub fn replace(&mut self, old_id: NodeId, new_id: NodeId) { { - let mut node_state = self.node_slab_mut(); + let node_state = self.node_slab_mut(); // update the parent's link to the child if let Some(parent_id) = node_state.get(old_id).unwrap().parent { let parent = node_state.get_mut(parent_id).unwrap(); @@ -170,7 +167,7 @@ impl Tree { } pub fn insert_before(&mut self, old_id: NodeId, new_id: NodeId) { - let mut node_state = self.node_slab_mut(); + let node_state = self.node_slab_mut(); let old_node = node_state.get(old_id).unwrap(); let parent_id = old_node.parent.expect("tried to insert before root"); node_state.get_mut(new_id).unwrap().parent = Some(parent_id); @@ -186,7 +183,7 @@ impl Tree { } pub fn insert_after(&mut self, old_id: NodeId, new_id: NodeId) { - let mut node_state = self.node_slab_mut(); + let node_state = self.node_slab_mut(); let old_node = node_state.get(old_id).unwrap(); let parent_id = old_node.parent.expect("tried to insert before root"); node_state.get_mut(new_id).unwrap().parent = Some(parent_id); @@ -271,7 +268,7 @@ impl<'a> DynamicallyBorrowedTree<'a> { nodes.insert(id, MaybeRead::Write(self.nodes.get_slab_mut(id).unwrap())); } { - let mut view = TreeStateView { + let view = TreeStateView { root: self.root, nodes_data, nodes, @@ -324,7 +321,7 @@ impl<'a, 'b> TreeStateView<'a, 'b> { self.nodes .get_mut(&TypeId::of::()) .and_then(|gaurd| match gaurd { - MaybeRead::Read(gaurd) => None, + MaybeRead::Read(_gaurd) => None, MaybeRead::Write(gaurd) => gaurd.as_any_mut().downcast_mut::>(), }) } @@ -859,7 +856,7 @@ fn get_many_mut_unchecked() { println!("ids: {:#?}", [parent, child, grandchild]); { - let mut i32_slab = slab.write_slab::(); + let i32_slab = slab.write_slab::(); let all = unsafe { i32_slab.get_many_mut_unchecked([parent, child, grandchild].into_iter()) } .unwrap(); @@ -867,7 +864,7 @@ fn get_many_mut_unchecked() { } { - let mut i32_slab = slab.write_slab::(); + let i32_slab = slab.write_slab::(); assert!( unsafe { i32_slab.get_many_mut_unchecked([NodeId(3), NodeId(100)].into_iter()) } .is_none() @@ -894,7 +891,7 @@ fn get_many_many_mut_unchecked() { println!("slab: {:#?}", slab); - let mut num_entries = slab.write_slab::(); + let num_entries = slab.write_slab::(); let all_num = unsafe { num_entries @@ -906,7 +903,7 @@ fn get_many_many_mut_unchecked() { .unwrap(); assert_eq!(all_num, [&mut 0, &mut 1, &mut 2]); - let mut str_entries = slab.write_slab::<&str>(); + let str_entries = slab.write_slab::<&str>(); let all_str = unsafe { str_entries From 0a5499508a43c39271c035c2b578b6b026cbd5f9 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 21 Jan 2023 18:30:42 -0600 Subject: [PATCH 016/103] simplify api and pass clippy --- packages/native-core/src/real_dom.rs | 60 ++++++++++- packages/native-core/src/tree.rs | 156 +-------------------------- 2 files changed, 62 insertions(+), 154 deletions(-) diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 7febed2bb..b856c0d6c 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -2,6 +2,7 @@ use dioxus_core::{BorrowedAttributeValue, ElementId, Mutations, TemplateNode}; use rustc_hash::{FxHashMap, FxHashSet}; use std::any::Any; +use std::collections::VecDeque; use crate::node::{ ElementNode, FromAnyValue, NodeData, NodeType, OwnedAttributeDiscription, OwnedAttributeValue, @@ -37,7 +38,7 @@ impl RealDom { pub fn new(mut passes: Box<[TypeErasedPass]>) -> RealDom { let mut tree = Tree::new(); let root_id = tree.root(); - let mut root = tree.get_node(root_id); + let root: &mut NodeData = tree.write(root_id).unwrap(); let mut root_node: NodeData = NodeData::new(NodeType::Element(ElementNode { tag: "Root".to_string(), namespace: Some("Root".to_string()), @@ -46,7 +47,7 @@ impl RealDom { })); root_node.element_id = Some(ElementId(0)); root_node.node_id = root_id; - root.insert(root_node); + *root = root_node; // resolve dependants for each pass for i in 1..passes.len() { @@ -477,6 +478,61 @@ impl RealDom { } self.tree.insert_after(id, new); } + + pub fn traverse_depth_first(&self, mut f: impl FnMut(NodeRef)) { + 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.tree.children_ids(id) { + stack.extend(children.iter().copied().rev()); + } + } + } + } + + pub fn traverse_breadth_first(&self, mut f: impl FnMut(NodeRef)) { + 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.tree.children_ids(id) { + for id in children { + queue.push_back(id); + } + } + } + } + } + + pub fn traverse_depth_first_mut(&mut self, mut f: impl FnMut(NodeMut)) { + let mut stack = vec![self.root()]; + while let Some(id) = stack.pop() { + if let Some(children) = self.tree.children_ids(id) { + if let Some(node) = self.get_mut(id) { + let node = node; + f(node); + stack.extend(children.iter().copied().rev()); + } + } + } + } + + pub fn traverse_breadth_first_mut(&mut self, mut f: impl FnMut(NodeMut)) { + let mut queue = VecDeque::new(); + queue.push_back(self.root()); + while let Some(id) = queue.pop_front() { + if let Some(children) = self.tree.children_ids(id) { + if let Some(node) = self.get_mut(id) { + f(node); + for id in children { + queue.push_back(id); + } + } + } + } + } } // impl Index for RealDom { diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index babdaded7..bb550c5c4 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -17,50 +17,6 @@ pub(crate) struct Node { height: u16, } -pub(crate) struct NodeView<'a> { - tree: &'a mut Tree, - id: NodeId, -} - -impl NodeView<'_> { - pub fn insert(&mut self, data: T) - where - T: Any, - { - self.tree.nodes.write_slab::().insert(self.id, data); - } - - pub fn get(&self) -> &T - where - T: Any, - { - self.tree.nodes.read_slab::().get(self.id).unwrap() - } - - pub fn get_mut(&mut self) -> &mut T - where - T: Any, - { - self.tree.nodes.write_slab::().get_mut(self.id).unwrap() - } - - pub fn height(&self) -> u16 { - self.get::().height - } - - pub fn children_ids(&self) -> Vec { - self.get::().children.clone() - } - - pub fn parent_id(&self) -> Option { - self.get::().parent - } - - pub fn id(&self) -> NodeId { - self.id - } -} - #[derive(Debug)] pub(crate) struct Tree { nodes: AnySlab, @@ -198,10 +154,6 @@ impl Tree { self.set_height(new_id, height); } - pub fn insert(&mut self, id: NodeId, value: T) { - self.nodes.add(id, value); - } - pub fn size(&self) -> usize { self.nodes.len() } @@ -209,7 +161,6 @@ impl Tree { pub fn dynamically_borrowed(&mut self) -> DynamicallyBorrowedTree<'_> { DynamicallyBorrowedTree { nodes: self.nodes.dynamically_borrowed(), - root: self.root, } } @@ -221,10 +172,6 @@ impl Tree { self.nodes.write_slab().get_mut(id) } - pub fn get_node(&mut self, id: NodeId) -> NodeView<'_> { - NodeView { tree: self, id } - } - pub fn contains(&self, id: NodeId) -> bool { self.nodes.contains(id) } @@ -248,7 +195,6 @@ impl Tree { pub(crate) struct DynamicallyBorrowedTree<'a> { nodes: DynamiclyBorrowedAnySlabView<'a>, - root: NodeId, } impl<'a> DynamicallyBorrowedTree<'a> { @@ -259,7 +205,7 @@ impl<'a> DynamicallyBorrowedTree<'a> { mut f: impl FnMut(TreeStateView), ) { let nodes_data = self.node_slab(); - let nodes_data: &SlabStorage = &*nodes_data; + let nodes_data: &SlabStorage = &nodes_data; let mut nodes = FxHashMap::default(); for id in immutable { nodes.insert(id, MaybeRead::Read(self.nodes.get_slab(id).unwrap())); @@ -268,16 +214,12 @@ impl<'a> DynamicallyBorrowedTree<'a> { nodes.insert(id, MaybeRead::Write(self.nodes.get_slab_mut(id).unwrap())); } { - let view = TreeStateView { - root: self.root, - nodes_data, - nodes, - }; + let view = TreeStateView { nodes_data, nodes }; f(view) } } - fn node_slab<'b>(&'b self) -> MappedRwLockReadGuard<'b, SlabStorage> { + fn node_slab(&self) -> MappedRwLockReadGuard> { RwLockReadGuard::map(self.nodes.get_slab(TypeId::of::()).unwrap(), |slab| { slab.as_any().downcast_ref().unwrap() }) @@ -304,7 +246,6 @@ impl<'a, 'b> AnyMapLike<'a> for TreeStateViewEntry<'a, 'b> { pub struct TreeStateView<'a, 'b> { nodes_data: &'a SlabStorage, nodes: FxHashMap>, - root: NodeId, } impl<'a, 'b> TreeStateView<'a, 'b> { @@ -326,10 +267,6 @@ impl<'a, 'b> TreeStateView<'a, 'b> { }) } - fn root(&self) -> NodeId { - self.root - } - pub fn children_ids(&self, id: NodeId) -> Option> { self.nodes_data.get(id).map(|node| node.children.clone()) } @@ -370,33 +307,6 @@ impl<'a, 'b> TreeStateView<'a, 'b> { pub fn parent(&self, id: NodeId) -> Option> { T::borrow_elements_from(self.entry(id)) } - - fn traverse_depth_first(&self, mut f: impl FnMut(T::ElementBorrowed<'_>)) { - 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_breadth_first(&self, mut f: impl FnMut(T::ElementBorrowed<'_>)) { - 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); - } - } - } - } - } } #[test] @@ -583,21 +493,10 @@ impl SlabStorage { self.data.get(id.0).and_then(|x| x.as_ref()) } - unsafe fn get_unchecked(&self, id: NodeId) -> &T { - self.data.get_unchecked(id.0).as_ref().unwrap_unchecked() - } - fn get_mut(&mut self, id: NodeId) -> Option<&mut T> { self.data.get_mut(id.0).and_then(|x| x.as_mut()) } - unsafe fn get_unchecked_mut(&mut self, id: NodeId) -> &mut T { - self.data - .get_unchecked_mut(id.0) - .as_mut() - .unwrap_unchecked() - } - fn insert(&mut self, id: NodeId, value: T) { let idx = id.0; if idx >= self.data.len() { @@ -605,40 +504,6 @@ impl SlabStorage { } self.data[idx] = Some(value); } - - fn get2_mut(&mut self, id1: NodeId, id2: NodeId) -> Option<(&mut T, &mut T)> { - assert!(id1 != id2); - let (idx1, idx2) = (id1.0, id2.0); - let ptr = self.data.as_mut_ptr(); - let first = unsafe { &mut *ptr.add(idx1) }; - let second = unsafe { &mut *ptr.add(idx2) }; - if let (Some(first), Some(second)) = (first, second) { - Some((first, second)) - } else { - None - } - } - - unsafe fn get_many_mut_unchecked( - &mut self, - ids: impl Iterator, - ) -> Option> { - let ptr = self.data.as_mut_ptr(); - let mut result = Vec::new(); - for id in ids { - let idx = id.0; - if idx >= self.data.len() { - return None; - } - let item = unsafe { &mut *ptr.add(idx) }; - if let Some(item) = item { - result.push(item); - } else { - return None; - } - } - Some(result) - } } impl AnySlabStorageImpl for SlabStorage { @@ -732,19 +597,10 @@ impl AnySlab { self.try_write_slab().unwrap() } - fn get_slab_mut_borrow(&mut self) -> &mut SlabStorage { - self.data - .get_mut(&TypeId::of::()) - .unwrap() - .as_any_mut() - .downcast_mut() - .unwrap() - } - fn get_or_insert_slab_mut(&mut self) -> &mut SlabStorage { self.data .entry(TypeId::of::()) - .or_insert_with(|| Box::new(SlabStorage::::default())) + .or_insert_with(|| Box::>::default()) .as_any_mut() .downcast_mut() .unwrap() @@ -763,10 +619,6 @@ impl AnySlab { EntryBuilder { id, inner: self } } - fn add(&mut self, id: NodeId, value: T) { - self.get_or_insert_slab_mut().insert(id, value); - } - fn remove(&mut self, id: NodeId) { for slab in self.data.values_mut() { slab.remove(id); From 47383529558eef499df01901e4cb5bcec8729e2c Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 22 Jan 2023 16:41:15 -0600 Subject: [PATCH 017/103] remove native-core-macro --- Cargo.toml | 1 - packages/native-core-macro/.gitignore | 2 - packages/native-core-macro/Cargo.toml | 26 -- packages/native-core-macro/README.md | 43 -- packages/native-core-macro/src/lib.rs | 134 ------ .../native-core-macro/src/sorted_slice.rs | 29 -- .../tests/called_minimally_on_build.rs | 244 ---------- .../native-core-macro/tests/update_state.rs | 421 ------------------ packages/native-core/Cargo.toml | 1 - packages/native-core/src/node_ref.rs | 280 +++++------- packages/native-core/src/passes.rs | 9 +- packages/native-core/src/real_dom.rs | 8 +- packages/tui/Cargo.toml | 1 - 13 files changed, 118 insertions(+), 1081 deletions(-) delete mode 100644 packages/native-core-macro/.gitignore delete mode 100644 packages/native-core-macro/Cargo.toml delete mode 100644 packages/native-core-macro/README.md delete mode 100644 packages/native-core-macro/src/lib.rs delete mode 100644 packages/native-core-macro/src/sorted_slice.rs delete mode 100644 packages/native-core-macro/tests/called_minimally_on_build.rs delete mode 100644 packages/native-core-macro/tests/update_state.rs diff --git a/Cargo.toml b/Cargo.toml index 7d8cc6871..6e376d917 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,6 @@ members = [ "packages/rsx", "packages/tui", "packages/native-core", - "packages/native-core-macro", "packages/rsx-rosetta", "packages/signals", "docs/guide", diff --git a/packages/native-core-macro/.gitignore b/packages/native-core-macro/.gitignore deleted file mode 100644 index 96ef6c0b9..000000000 --- a/packages/native-core-macro/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/target -Cargo.lock diff --git a/packages/native-core-macro/Cargo.toml b/packages/native-core-macro/Cargo.toml deleted file mode 100644 index 6a37f75b2..000000000 --- a/packages/native-core-macro/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "dioxus-native-core-macro" -version = "0.2.0" -edition = "2021" -description = "Build natively rendered apps with Dioxus" -license = "MIT/Apache-2.0" -repository = "https://github.com/DioxusLabs/dioxus/" -homepage = "https://dioxuslabs.com" -documentation = "https://dioxuslabs.com" -keywords = ["dom", "ui", "gui", "react"] - - -[lib] -proc-macro = true - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[dependencies] -syn = { version = "1.0.11", features = ["extra-traits"] } -quote = "1.0" -dioxus-native-core = { path = "../native-core", version = "^0.2.0" } -dioxus = { path = "../dioxus" } - -[dev-dependencies] -smallvec = "1.6" -rustc-hash = "1.1.0" -anymap = "0.12.1" diff --git a/packages/native-core-macro/README.md b/packages/native-core-macro/README.md deleted file mode 100644 index 90d7123b1..000000000 --- a/packages/native-core-macro/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# Dioxus Native Core Macro - -[![Crates.io][crates-badge]][crates-url] -[![MIT licensed][mit-badge]][mit-url] -[![Build Status][actions-badge]][actions-url] -[![Discord chat][discord-badge]][discord-url] - -[crates-badge]: https://img.shields.io/crates/v/dioxus-native-core-macro.svg -[crates-url]: https://crates.io/crates/dioxus-native-core-macro - -[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg -[mit-url]: https://github.com/dioxuslabs/dioxus/blob/master/LICENSE - -[actions-badge]: https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg -[actions-url]: https://github.com/dioxuslabs/dioxus/actions?query=workflow%3ACI+branch%3Amaster - -[discord-badge]: https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square -[discord-url]: https://discord.gg/XgGxMSkvUM - -[Website](https://dioxuslabs.com) | -[Guides](https://dioxuslabs.com/guide/) | -[API Docs](https://docs.rs/dioxus-native-core-macro/latest/dioxus_native_core_macro) | -[Chat](https://discord.gg/XgGxMSkvUM) - - -## Overview - -`dioxus-native-core-macro` provides a handful of macros used by native-core for native renderers like TUI, Blitz, and Freya to derive their state. - - -## Contributing - -- Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues). -- Join the discord and ask questions! - -## License -This project is licensed under the [MIT license]. - -[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT - -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in Dioxus by you shall be licensed as MIT without any additional -terms or conditions. diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs deleted file mode 100644 index 5ab2c8388..000000000 --- a/packages/native-core-macro/src/lib.rs +++ /dev/null @@ -1,134 +0,0 @@ -extern crate proc_macro; - -mod sorted_slice; - -use proc_macro::TokenStream; -use quote::quote; -use sorted_slice::StrSlice; -use syn::{self, parse_macro_input}; - -/// Sorts a slice of string literals at compile time. -#[proc_macro] -pub fn sorted_str_slice(input: TokenStream) -> TokenStream { - let slice: StrSlice = parse_macro_input!(input as StrSlice); - let strings = slice.map.values(); - quote!([#(#strings, )*]).into() -} - -/// Derive's the state from any members that implement the Pass trait -#[proc_macro_derive(State, attributes(skip, skip_clone))] -pub fn state_macro_derive(input: TokenStream) -> TokenStream { - let ast = syn::parse(input).unwrap(); - impl_derive_macro(&ast) -} - -fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { - let custom_type = ast - .attrs - .iter() - .find(|a| a.path.is_ident("state")) - .and_then(|attr| { - // parse custom_type = "MyType" - let assignment = attr.parse_args::().unwrap(); - if let syn::Expr::Assign(assign) = assignment { - let (left, right) = (&*assign.left, &*assign.right); - if let syn::Expr::Path(e) = left { - let path = &e.path; - if let Some(ident) = path.get_ident() { - if ident == "custom_value" { - return match right { - syn::Expr::Path(e) => { - let path = &e.path; - Some(quote! {#path}) - } - _ => None, - }; - } - } - } - } - None - }) - .unwrap_or(quote! {()}); - let type_name = &ast.ident; - let fields: Vec<_> = match &ast.data { - syn::Data::Struct(data) => match &data.fields { - syn::Fields::Named(e) => &e.named, - syn::Fields::Unnamed(_) => todo!("unnamed fields"), - syn::Fields::Unit => todo!("unit structs"), - } - .iter() - .collect(), - _ => unimplemented!(), - }; - - let types = fields - .iter() - .filter(|field| !field.attrs.iter().any(|attr| attr.path.is_ident("skip"))) - .map(|field| &field.ty); - - let gen = quote! { - impl dioxus_native_core::State<#custom_type> for #type_name { - fn create_passes() -> Box<[dioxus_native_core::TypeErasedPass]> { - Box::new([ - #( - <#types as dioxus_native_core::Pass>::to_type_erased() - ),* - ]) - } - } - }; - - gen.into() -} - -/// Derive's the state from any elements that have a node_dep_state, child_dep_state, parent_dep_state, or state attribute. -#[proc_macro_derive(AnyMapLike)] -pub fn anymap_like_macro_derive(input: TokenStream) -> TokenStream { - let ast = syn::parse(input).unwrap(); - impl_anymap_like_derive_macro(&ast) -} - -fn impl_anymap_like_derive_macro(ast: &syn::DeriveInput) -> TokenStream { - let type_name = &ast.ident; - let fields: Vec<_> = match &ast.data { - syn::Data::Struct(data) => match &data.fields { - syn::Fields::Named(e) => &e.named, - syn::Fields::Unnamed(_) => todo!("unnamed fields"), - syn::Fields::Unit => todo!("unit structs"), - } - .iter() - .collect(), - _ => unimplemented!(), - }; - - let names: Vec<_> = fields - .iter() - .map(|field| field.ident.as_ref().unwrap()) - .collect(); - let types: Vec<_> = fields.iter().map(|field| &field.ty).collect(); - - let gen = quote! { - impl dioxus_native_core::AnyMapLike for #type_name { - fn get(&self) -> Option<&T> { - #( - if std::any::TypeId::of::() == std::any::TypeId::of::<#types>() { - return unsafe { Some(&*(&self.#names as *const _ as *const T)) } - } - )* - None - } - - fn get_mut(&mut self) -> Option<&mut T> { - #( - if std::any::TypeId::of::() == std::any::TypeId::of::<#types>() { - return unsafe { Some(&mut *(&mut self.#names as *mut _ as *mut T)) } - } - )* - None - } - } - }; - - gen.into() -} diff --git a/packages/native-core-macro/src/sorted_slice.rs b/packages/native-core-macro/src/sorted_slice.rs deleted file mode 100644 index aba2b8b65..000000000 --- a/packages/native-core-macro/src/sorted_slice.rs +++ /dev/null @@ -1,29 +0,0 @@ -extern crate proc_macro; - -use std::collections::BTreeMap; - -use syn::{ - self, bracketed, - parse::{Parse, ParseStream, Result}, - LitStr, Token, -}; - -pub struct StrSlice { - pub map: BTreeMap, -} - -impl Parse for StrSlice { - fn parse(input: ParseStream) -> Result { - let content; - bracketed!(content in input); - let mut map = BTreeMap::new(); - while let Ok(s) = content.parse::() { - map.insert(s.value(), s); - #[allow(unused_must_use)] - { - content.parse::(); - } - } - Ok(StrSlice { map }) - } -} diff --git a/packages/native-core-macro/tests/called_minimally_on_build.rs b/packages/native-core-macro/tests/called_minimally_on_build.rs deleted file mode 100644 index 69454dc43..000000000 --- a/packages/native-core-macro/tests/called_minimally_on_build.rs +++ /dev/null @@ -1,244 +0,0 @@ -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::SendAnyMap; -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::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<::ElementBorrowed<'_>>, - _: &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" - } - } - } - } - } - - let mut vdom = VirtualDom::new(Base); - - let mutations = vdom.rebuild(); - - let mut dom: RealDom<$s> = RealDom::new(); - - let (nodes_updated, _) = dom.apply_mutations(mutations); - let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::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); - )* - }); - } - } -} - -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 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, - } - - 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,))); - - #[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, 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)); -} - -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 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, - } - - 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,))); - - #[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, - } - - 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, ())); - - #[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, 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)); -} diff --git a/packages/native-core-macro/tests/update_state.rs b/packages/native-core-macro/tests/update_state.rs deleted file mode 100644 index fc36c42ed..000000000 --- a/packages/native-core-macro/tests/update_state.rs +++ /dev/null @@ -1,421 +0,0 @@ -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, SendAnyMap}; -use dioxus_native_core_macro::State; - -#[derive(Debug, Clone, Default, State)] -struct CallCounterState { - #[child_dep_state(child_counter)] - child_counter: ChildDepCallCounter, - #[parent_dep_state(parent_counter)] - parent_counter: ParentDepCallCounter, - #[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 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 = (); - 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 - } -} - -#[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; - 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, -} - -#[test] -fn state_initial() { - #[allow(non_snake_case)] - fn Base(cx: Scope) -> Element { - render! { - div { - p{ - color: "red" - } - h1{} - } - } - } - - let mut vdom = VirtualDom::new(Base); - - let mutations = vdom.rebuild(); - - let mut dom: RealDom = RealDom::new(); - - 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_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_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_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 { - let width = if cx.generation() == 0 { "100%" } else { "99%" }; - cx.render(rsx! { - div { - width: "{width}", - div{ - div{ - p{} - } - p{ - "hello" - } - div{ - h1{} - } - p{ - "world" - } - } - } - }) - } - - let mut vdom = VirtualDom::new(Base); - - let mut dom: RealDom = RealDom::new(); - - let (nodes_updated, _) = dom.apply_mutations(vdom.rebuild()); - let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::new()); - 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| { - if is_root { - is_root = false; - assert_eq!(n.state.parent_counter.0, 1); - } else { - 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! { - // 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" - } - } - } - }) - } - - let mut vdom = VirtualDom::new(Base); - - let mut dom: RealDom = RealDom::new(); - - let (nodes_updated, _) = dom.apply_mutations(vdom.rebuild()); - let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::new()); - 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.child_counter.0, { - if traverse_count > 4 { - 1 - } else { - 2 - } - }); - traverse_count += 1; - }); -} - -#[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 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, 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 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 - } -} - -#[test] -fn dependancies_order_independant() { - #[allow(non_snake_case)] - fn Base(cx: Scope) -> Element { - render!(div { - width: "100%", - p{ - "hello" - } - }) - } - - let mut vdom = VirtualDom::new(Base); - - let mut dom: RealDom = RealDom::new(); - - let mutations = vdom.rebuild(); - let (nodes_updated, _) = dom.apply_mutations(mutations); - let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::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); - }); -} diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index bf1f9ba92..e6b50b8a4 100644 --- a/packages/native-core/Cargo.toml +++ b/packages/native-core/Cargo.toml @@ -31,5 +31,4 @@ lightningcss = "1.0.0-alpha.39" [dev-dependencies] rand = "0.8.5" dioxus = { path = "../dioxus", version = "^0.3.0" } -dioxus-native-core-macro = { path = "../native-core-macro" } tokio = { version = "*", features = ["full"] } diff --git a/packages/native-core/src/node_ref.rs b/packages/native-core/src/node_ref.rs index a62a97d24..14145b5cc 100644 --- a/packages/native-core/src/node_ref.rs +++ b/packages/native-core/src/node_ref.rs @@ -1,47 +1,11 @@ -use std::cmp::Ordering; - use dioxus_core::ElementId; +use rustc_hash::FxHashSet; use crate::{ node::{ElementNode, FromAnyValue, NodeData, NodeType, OwnedAttributeView}, NodeId, }; -/// Join two sorted iterators -pub(crate) fn union_ordered_iter<'a>( - s_iter: impl Iterator, - o_iter: impl Iterator, - new_len_guess: usize, -) -> Vec { - let mut s_peekable = s_iter.peekable(); - let mut o_peekable = o_iter.peekable(); - let mut v = Vec::with_capacity(new_len_guess); - while let Some(s_i) = s_peekable.peek() { - while let Some(o_i) = o_peekable.peek() { - match o_i.cmp(s_i) { - Ordering::Greater => { - break; - } - Ordering::Less => { - v.push(o_peekable.next().unwrap().to_string()); - } - Ordering::Equal => { - o_peekable.next(); - break; - } - } - } - v.push(s_peekable.next().unwrap().to_string()); - } - for o_i in o_peekable { - v.push(o_i.to_string()); - } - for w in v.windows(2) { - debug_assert!(w[1] > w[0]); - } - v -} - /// A view into a [VNode] with limited access. #[derive(Debug)] pub struct NodeView<'a, V: FromAnyValue = ()> { @@ -138,124 +102,50 @@ impl<'a, V: FromAnyValue> NodeView<'a, V> { #[derive(PartialEq, Eq, Clone, Debug)] pub enum AttributeMask { All, - /// A list of attribute names that are visible, this list must be sorted - Dynamic(Vec), - /// A list of attribute names that are visible, this list must be sorted - Static(&'static [&'static str]), + Some(FxHashSet>), } impl AttributeMask { - /// A empty attribute mask - pub const NONE: Self = Self::Static(&[]); - fn contains_attribute(&self, attr: &str) -> bool { match self { AttributeMask::All => true, - AttributeMask::Dynamic(l) => l.binary_search_by_key(&attr, |s| s.as_str()).is_ok(), - AttributeMask::Static(l) => l.binary_search(&attr).is_ok(), + AttributeMask::Some(attrs) => attrs.contains(attr), } } /// Create a new dynamic attribute mask with a single attribute pub fn single(new: &str) -> Self { - Self::Dynamic(vec![new.to_string()]) - } - - /// Ensure the attribute list is sorted. - pub fn verify(&self) { - match &self { - AttributeMask::Static(attrs) => debug_assert!( - attrs.windows(2).all(|w| w[0] < w[1]), - "attritutes must be increasing" - ), - AttributeMask::Dynamic(attrs) => debug_assert!( - attrs.windows(2).all(|w| w[0] < w[1]), - "attritutes must be increasing" - ), - _ => (), - } + let mut set = FxHashSet::default(); + set.insert(new.into()); + Self::Some(set) } /// 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().map(|s| s.as_str()), - o.iter().map(|s| s.as_str()), - s.len() + o.len(), - )) + match (self, other) { + (AttributeMask::Some(s), AttributeMask::Some(o)) => { + AttributeMask::Some(s.intersection(o).cloned().collect()) } - (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()), - ), _ => AttributeMask::All, - }; - new.verify(); - new + } } /// Check if two attribute masks overlap fn overlaps(&self, other: &Self) -> bool { - 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 { - while other_attr < self_attr { - if let Some(attr) = other_iter.next() { - other_attr = attr; - } else { - return false; - } - } - if other_attr == self_attr { - return true; - } - } - } - false - } match (self, other) { - (AttributeMask::All, AttributeMask::All) => true, - (AttributeMask::All, AttributeMask::Dynamic(v)) => !v.is_empty(), - (AttributeMask::All, AttributeMask::Static(s)) => !s.is_empty(), - (AttributeMask::Dynamic(v), AttributeMask::All) => !v.is_empty(), - (AttributeMask::Static(s), AttributeMask::All) => !s.is_empty(), - (AttributeMask::Dynamic(v1), AttributeMask::Dynamic(v2)) => { - overlaps_iter(v1.iter().map(|s| s.as_str()), v2.iter().map(|s| s.as_str())) - } - (AttributeMask::Dynamic(v), AttributeMask::Static(s)) => { - overlaps_iter(v.iter().map(|s| s.as_str()), s.iter().copied()) - } - (AttributeMask::Static(s), AttributeMask::Dynamic(v)) => { - 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()) + (AttributeMask::All, AttributeMask::Some(attrs)) => !attrs.is_empty(), + (AttributeMask::Some(attrs), AttributeMask::All) => !attrs.is_empty(), + (AttributeMask::Some(attrs1), AttributeMask::Some(attrs2)) => { + !attrs1.is_disjoint(attrs2) } + _ => true, } } } impl Default for AttributeMask { fn default() -> Self { - AttributeMask::Static(&[]) + AttributeMask::Some(FxHashSet::default()) } } @@ -270,33 +160,33 @@ pub struct NodeMask { } impl NodeMask { - /// A node mask with no parts visible. - pub const NONE: Self = Self::new(); - /// A node mask with every part visible. - pub const ALL: Self = Self::new_with_attrs(AttributeMask::All) - .with_text() - .with_element() - .with_listeners(); - pub fn from_node(node: &NodeType) -> Self { match node { - NodeType::Text { .. } => Self::new().with_text(), + NodeType::Text { .. } => NodeMaskBuilder::new().with_text().build(), NodeType::Element(ElementNode { tag: _, namespace: _, attributes, listeners, - }) => { - let mut mask = Self::new_with_attrs(AttributeMask::Dynamic( - attributes.keys().map(|key| key.name.clone()).collect(), - )) - .with_element(); - if !listeners.is_empty() { - mask = mask.with_listeners(); - } - mask - } - NodeType::Placeholder => Self::new().with_element(), + }) => Self { + attritutes: AttributeMask::Some( + attributes + .keys() + .map(|key| key.name.as_str().into()) + .collect(), + ), + namespace: true, + tag: true, + text: false, + listeners: !listeners.is_empty(), + }, + NodeType::Placeholder => Self { + attritutes: AttributeMask::default(), + tag: true, + namespace: true, + text: false, + listeners: false, + }, } } @@ -320,8 +210,65 @@ impl NodeMask { } } + /// Allow the mask to view the given attributes + pub fn add_attributes(&mut self, attributes: AttributeMask) { + self.attritutes = self.attritutes.union(&attributes); + } + + /// Set the mask to view the tag + pub fn set_tag(&mut self) { + self.tag = true; + } + + /// Set the mask to view the namespace + pub fn set_namespace(&mut self) { + self.namespace = true; + } + + /// Set the mask to view the text + pub fn set_text(&mut self) { + self.text = true; + } + + /// Set the mask to view the listeners + pub fn set_listeners(&mut self) { + self.listeners = true; + } +} + +#[derive(PartialEq, Eq, Clone, Debug)] +pub enum AttributeMaskBuilder { + All, + Some(&'static [&'static str]), +} + +impl Default for AttributeMaskBuilder { + fn default() -> Self { + AttributeMaskBuilder::Some(&[]) + } +} + +/// A mask that limits what parts of a node a dependency can see. +#[derive(Default, PartialEq, Eq, Clone, Debug)] +pub struct NodeMaskBuilder { + attritutes: AttributeMaskBuilder, + tag: bool, + namespace: bool, + text: bool, + listeners: bool, +} + +impl NodeMaskBuilder { + /// A node mask with no parts visible. + pub const NONE: Self = Self::new(); + /// A node mask with every part visible. + pub const ALL: Self = Self::new_with_attrs(AttributeMaskBuilder::All) + .with_text() + .with_element() + .with_listeners(); + /// Create a new node mask with the given attributes - pub const fn new_with_attrs(attritutes: AttributeMask) -> Self { + pub const fn new_with_attrs(attritutes: AttributeMaskBuilder) -> Self { Self { attritutes, tag: false, @@ -333,7 +280,7 @@ impl NodeMask { /// Create a empty node mask pub const fn new() -> Self { - Self::new_with_attrs(AttributeMask::NONE) + Self::new_with_attrs(AttributeMaskBuilder::Some(&[])) } /// Allow the mask to view the tag @@ -342,22 +289,12 @@ impl NodeMask { self } - /// Set the mask to view the tag - pub fn set_tag(&mut self) { - self.tag = true; - } - /// Allow the mask to view the namespace pub const fn with_namespace(mut self) -> Self { self.namespace = true; self } - /// Set the mask to view the namespace - pub fn set_namespace(&mut self) { - self.namespace = true; - } - /// Allow the mask to view the namespace and tag pub const fn with_element(self) -> Self { self.with_namespace().with_tag() @@ -369,24 +306,25 @@ impl NodeMask { self } - /// Set the mask to view the text - pub fn set_text(&mut self) { - self.text = true; - } - /// Allow the mask to view the listeners pub const fn with_listeners(mut self) -> Self { self.listeners = true; self } - /// Set the mask to view the listeners - pub fn set_listeners(&mut self) { - self.listeners = true; - } - - /// Allow the mask to view the given attributes - pub fn add_attributes(&mut self, attributes: AttributeMask) { - self.attritutes = self.attritutes.union(&attributes); + /// Build the mask + pub fn build(self) -> NodeMask { + NodeMask { + attritutes: match self.attritutes { + AttributeMaskBuilder::All => AttributeMask::All, + AttributeMaskBuilder::Some(attrs) => { + AttributeMask::Some(attrs.iter().map(|s| (*s).into()).collect()) + } + }, + tag: self.tag, + namespace: self.namespace, + text: self.text, + listeners: self.listeners, + } } } diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index 854acfea6..cb21cc696 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -7,7 +7,7 @@ use std::marker::PhantomData; use std::sync::Arc; use crate::node::{FromAnyValue, NodeData}; -use crate::node_ref::NodeView; +use crate::node_ref::{NodeMaskBuilder, NodeView}; use crate::real_dom::RealDom; use crate::tree::TreeStateView; use crate::{FxDashMap, FxDashSet, SendAnyMap}; @@ -116,7 +116,7 @@ pub trait Pass: Any { type ChildDependencies: Dependancy; /// This is a tuple of (T: Any, ..) type NodeDependencies: Dependancy; - const NODE_MASK: NodeMask; + const NODE_MASK: NodeMaskBuilder; fn pass<'a>( &mut self, @@ -158,13 +158,14 @@ pub trait Pass: Any { Self: Sized, { Self::validate(); + let node_mask = Self::NODE_MASK.build(); TypeErasedPass { this_type_id: TypeId::of::(), combined_dependancy_type_ids: Self::all_dependanices().iter().copied().collect(), parent_dependant: !Self::parent_type_ids().is_empty(), child_dependant: !Self::child_type_ids().is_empty(), dependants: FxHashSet::default(), - mask: Self::NODE_MASK, + mask: node_mask, pass_direction: Self::pass_direction(), pass: Box::new( |node_id: NodeId, tree: &mut TreeStateView, context: &SendAnyMap| { @@ -181,7 +182,7 @@ pub trait Pass: Any { let myself = unsafe { &mut *node_raw }; myself.pass( - NodeView::new(node_data, Self::NODE_MASK), + NodeView::new(node_data, Self::NODE_MASK.build()), node, parent, children, diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index b856c0d6c..367a11eb7 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -7,7 +7,7 @@ use std::collections::VecDeque; use crate::node::{ ElementNode, FromAnyValue, NodeData, NodeType, OwnedAttributeDiscription, OwnedAttributeValue, }; -use crate::node_ref::{AttributeMask, NodeMask}; +use crate::node_ref::{AttributeMask, NodeMask, NodeMaskBuilder}; use crate::passes::{resolve_passes, DirtyNodeStates, TypeErasedPass}; use crate::tree::{EntryBuilder, NodeId, Tree}; use crate::{FxDashSet, SendAnyMap}; @@ -64,7 +64,7 @@ impl RealDom { let mut nodes_updated = FxHashMap::default(); let root_id = NodeId(0); - nodes_updated.insert(root_id, NodeMask::ALL); + nodes_updated.insert(root_id, NodeMaskBuilder::ALL.build()); RealDom { tree, @@ -389,7 +389,7 @@ impl RealDom { pub fn get_mut(&mut self, id: NodeId) -> Option> { self.tree.contains(id).then(|| NodeMut { id, - dirty: NodeMask::new(), + dirty: NodeMask::default(), dom: self, }) } @@ -616,7 +616,7 @@ impl<'a, V: FromAnyValue + Send> NodeMut<'a, V> { pub fn set_type(&mut self, new: NodeType) { self.node_data_mut().node_type = new; - self.dirty = NodeMask::ALL; + self.dirty = NodeMaskBuilder::ALL.build(); } pub fn read(&self) -> Option<&T> { diff --git a/packages/tui/Cargo.toml b/packages/tui/Cargo.toml index 5341d40f4..8d50b7bc6 100644 --- a/packages/tui/Cargo.toml +++ b/packages/tui/Cargo.toml @@ -17,7 +17,6 @@ dioxus = { path = "../dioxus", version = "^0.3.0" } dioxus-core = { path = "../core", version = "^0.3.0" } dioxus-html = { path = "../html", version = "^0.3.0" } dioxus-native-core = { path = "../native-core", version = "^0.2.0" } -dioxus-native-core-macro = { path = "../native-core-macro", version = "^0.2.0" } tui = "0.17.0" crossterm = "0.23.0" From 32790685697f5c49f95df80daf22d9f949b86785 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 22 Jan 2023 21:06:59 -0600 Subject: [PATCH 018/103] clean up view code --- packages/native-core/src/passes.rs | 18 ++++++------ packages/native-core/src/tree.rs | 44 ++++-------------------------- 2 files changed, 13 insertions(+), 49 deletions(-) diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index cb21cc696..b8cf5031f 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -374,8 +374,8 @@ pub fn resolve_passes( while !pass_indexes_remaining.is_empty() { let mut currently_in_use = FxHashSet::::default(); std::thread::scope(|_| { - let mut i = 0; let dynamically_borrowed_tree = tree.tree.dynamically_borrowed(); + let mut i = 0; while i < pass_indexes_remaining.len() { let passes_idx = pass_indexes_remaining[i]; let pass = &passes[passes_idx]; @@ -387,18 +387,16 @@ pub fn resolve_passes( pass_indexes_remaining.remove(i); resolving.push(pass_id); currently_in_use.insert(pass.this_type_id); - dynamically_borrowed_tree.with_view( + let tree_view = dynamically_borrowed_tree.view( pass.combined_dependancy_type_ids.iter().copied(), [pass.this_type_id], - |tree_view| { - let dirty_nodes = dirty_nodes.clone(); - let nodes_updated = nodes_updated.clone(); - let ctx = ctx.clone(); - // s.spawn(move || { - pass.resolve(tree_view, &dirty_nodes, &nodes_updated, &ctx); - // }); - }, ); + let dirty_nodes = dirty_nodes.clone(); + let nodes_updated = nodes_updated.clone(); + let ctx = ctx.clone(); + // s.spawn(move || { + pass.resolve(tree_view, &dirty_nodes, &nodes_updated, &ctx); + // }); } else { i += 1; } diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index bb550c5c4..a5bef626c 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -198,14 +198,12 @@ pub(crate) struct DynamicallyBorrowedTree<'a> { } impl<'a> DynamicallyBorrowedTree<'a> { - pub fn with_view( + pub fn view( &self, immutable: impl IntoIterator, mutable: impl IntoIterator, - mut f: impl FnMut(TreeStateView), - ) { + ) -> TreeStateView<'_, 'a> { let nodes_data = self.node_slab(); - let nodes_data: &SlabStorage = &nodes_data; let mut nodes = FxHashMap::default(); for id in immutable { nodes.insert(id, MaybeRead::Read(self.nodes.get_slab(id).unwrap())); @@ -213,10 +211,8 @@ impl<'a> DynamicallyBorrowedTree<'a> { for id in mutable { nodes.insert(id, MaybeRead::Write(self.nodes.get_slab_mut(id).unwrap())); } - { - let view = TreeStateView { nodes_data, nodes }; - f(view) - } + + TreeStateView { nodes_data, nodes } } fn node_slab(&self) -> MappedRwLockReadGuard> { @@ -244,7 +240,7 @@ impl<'a, 'b> AnyMapLike<'a> for TreeStateViewEntry<'a, 'b> { } pub struct TreeStateView<'a, 'b> { - nodes_data: &'a SlabStorage, + nodes_data: MappedRwLockReadGuard<'a, SlabStorage>, nodes: FxHashMap>, } @@ -441,36 +437,6 @@ fn deletion() { assert_eq!(tree.read::(after), None); } -// #[test] -// fn traverse_depth_first() { -// let mut tree = Tree::new(); -// let parent = tree.root(); -// tree.insert(parent, 0); -// let mut child1 = tree.create_node(); -// child1.insert(1); -// let child1 = child1.id(); -// tree.add_child(parent, child1); -// let mut grandchild1 = tree.create_node(); -// grandchild1.insert(2); -// let grandchild1 = grandchild1.id(); -// tree.add_child(child1, grandchild1); -// let mut child2 = tree.create_node(); -// child2.insert(3); -// let child2 = child2.id(); -// tree.add_child(parent, child2); -// let mut grandchild2 = tree.create_node(); -// grandchild2.insert(4); -// let grandchild2 = grandchild2.id(); -// tree.add_child(child2, grandchild2); - -// let view = tree.state_view::(); -// let mut node_count = 0; -// view.traverse_depth_first(move |node| { -// assert_eq!(*node, node_count); -// node_count += 1; -// }); -// } - #[derive(Debug)] pub(crate) struct SlabStorage { data: Vec>, From ce59ccfd447c0786fafc1b7a0e4fac6799eda999 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 23 Jan 2023 21:03:28 -0600 Subject: [PATCH 019/103] enable multithreading --- packages/native-core/src/passes.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index b8cf5031f..57a4b8609 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -373,8 +373,8 @@ pub fn resolve_passes( let mut pass_indexes_remaining: Vec<_> = (0..passes.len()).collect::>(); while !pass_indexes_remaining.is_empty() { let mut currently_in_use = FxHashSet::::default(); - std::thread::scope(|_| { - let dynamically_borrowed_tree = tree.tree.dynamically_borrowed(); + let dynamically_borrowed_tree = tree.tree.dynamically_borrowed(); + std::thread::scope(|s| { let mut i = 0; while i < pass_indexes_remaining.len() { let passes_idx = pass_indexes_remaining[i]; @@ -394,9 +394,9 @@ pub fn resolve_passes( let dirty_nodes = dirty_nodes.clone(); let nodes_updated = nodes_updated.clone(); let ctx = ctx.clone(); - // s.spawn(move || { - pass.resolve(tree_view, &dirty_nodes, &nodes_updated, &ctx); - // }); + s.spawn(move || { + pass.resolve(tree_view, &dirty_nodes, &nodes_updated, &ctx); + }); } else { i += 1; } From 9efff74c0c30ac1533fc681f9b39f7d2238e2a5c Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 28 Jan 2023 14:51:05 -0600 Subject: [PATCH 020/103] update TUI renderer to new approach --- packages/native-core/src/lib.rs | 1 + packages/native-core/src/node_ref.rs | 16 +- packages/native-core/src/real_dom.rs | 195 ++++++++++++------ packages/native-core/src/tree.rs | 8 +- packages/native-core/src/utils/mod.rs | 4 +- .../src/utils/persistant_iterator.rs | 62 ++---- packages/tui/src/focus.rs | 79 +++---- packages/tui/src/hooks.rs | 54 ++--- packages/tui/src/layout.rs | 30 +-- packages/tui/src/lib.rs | 23 ++- packages/tui/src/node.rs | 36 ++-- packages/tui/src/query.rs | 19 +- packages/tui/src/render.rs | 50 +++-- packages/tui/src/style_attributes.rs | 32 ++- 14 files changed, 339 insertions(+), 270 deletions(-) diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 051a8731d..c54daab10 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -4,6 +4,7 @@ use std::hash::BuildHasherDefault; pub use node_ref::NodeMask; pub use passes::AnyMapLike; pub use passes::{Dependancy, Pass, TypeErasedPass}; +pub use real_dom::{NodeMut, NodeRef, RealDom}; use rustc_hash::FxHasher; pub use tree::NodeId; diff --git a/packages/native-core/src/node_ref.rs b/packages/native-core/src/node_ref.rs index 14145b5cc..beb8519dc 100644 --- a/packages/native-core/src/node_ref.rs +++ b/packages/native-core/src/node_ref.rs @@ -262,15 +262,16 @@ impl NodeMaskBuilder { /// A node mask with no parts visible. pub const NONE: Self = Self::new(); /// A node mask with every part visible. - pub const ALL: Self = Self::new_with_attrs(AttributeMaskBuilder::All) + pub const ALL: Self = Self::new() + .with_attrs(AttributeMaskBuilder::All) .with_text() .with_element() .with_listeners(); - /// Create a new node mask with the given attributes - pub const fn new_with_attrs(attritutes: AttributeMaskBuilder) -> Self { + /// Create a empty node mask + pub const fn new() -> Self { Self { - attritutes, + attritutes: AttributeMaskBuilder::Some(&[]), tag: false, namespace: false, text: false, @@ -278,9 +279,10 @@ impl NodeMaskBuilder { } } - /// Create a empty node mask - pub const fn new() -> Self { - Self::new_with_attrs(AttributeMaskBuilder::Some(&[])) + /// Allow the mask to view the given attributes + pub const fn with_attrs(mut self, attritutes: AttributeMaskBuilder) -> Self { + self.attritutes = attritutes; + self } /// Allow the mask to view the tag diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 367a11eb7..9a874ea99 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -18,7 +18,7 @@ use crate::{FxDashSet, SendAnyMap}; /// /// # Custom values /// To allow custom values to be passed into attributes implement FromAnyValue on a type that can represent your custom value and specify the V generic to be that type. If you have many different custom values, it can be useful to use a enum type to represent the varients. -pub struct RealDom { +pub struct RealDom { pub(crate) tree: Tree, /// a map from element id to real node id node_id_mapping: Vec>, @@ -38,7 +38,7 @@ impl RealDom { pub fn new(mut passes: Box<[TypeErasedPass]>) -> RealDom { let mut tree = Tree::new(); let root_id = tree.root(); - let root: &mut NodeData = tree.write(root_id).unwrap(); + let root: &mut NodeData = tree.get_mut(root_id).unwrap(); let mut root_node: NodeData = NodeData::new(NodeType::Element(ElementNode { tag: "Root".to_string(), namespace: Some("Root".to_string()), @@ -95,7 +95,7 @@ impl RealDom { } fn set_element_id(&mut self, node_id: NodeId, element_id: ElementId) { - let mut node = self.tree.write::(node_id).unwrap(); + let mut node = self.tree.get_mut::(node_id).unwrap(); let node_id = node.node_id; node.element_id = Some(element_id); if self.node_id_mapping.len() <= element_id.0 { @@ -342,14 +342,17 @@ impl RealDom { /// 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 { + pub fn get_listening_sorted(&self, event: &'static str) -> Vec> { if let Some(nodes) = self.nodes_listening.get(event) { let mut listening: Vec<_> = nodes .iter() .map(|id| (*id, self.tree.height(*id).unwrap())) .collect(); listening.sort_by(|(_, h1), (_, h2)| h1.cmp(h2).reverse()); - listening.into_iter().map(|(id, _)| id).collect() + listening + .into_iter() + .map(|(id, _)| NodeRef { id, dom: self }) + .collect() } else { Vec::new() } @@ -371,7 +374,8 @@ impl RealDom { let new_node = node.node_data().clone(); let new_id = self.create_node(new_node, None, true).id(); - for child in self.tree.children_ids(node_id).unwrap() { + let children = self.tree.children_ids(node_id).unwrap().to_vec(); + for child in children { let child_id = self.clone_node(child); self.add_child(new_id, child_id); } @@ -382,11 +386,13 @@ impl RealDom { self.tree.root() } - pub fn get(&self, id: NodeId) -> Option> { + pub fn get(&self, id: impl IntoNodeId) -> Option> { + let id = id.into_node_id(self); self.tree.contains(id).then_some(NodeRef { id, dom: self }) } - pub fn get_mut(&mut self, id: NodeId) -> Option> { + pub fn get_mut(&mut self, id: impl IntoNodeId) -> Option> { + let id = id.into_node_id(self); self.tree.contains(id).then(|| NodeMut { id, dirty: NodeMask::default(), @@ -395,8 +401,11 @@ impl RealDom { } /// WARNING: This escapes the reactive system that the real dom uses. Any changes made with this method will not trigger updates in states when [RealDom::update_state] is called. - pub fn get_mut_raw(&mut self, id: NodeId) -> Option<&mut NodeData> { - self.tree.write(id) + pub fn get_mut_raw(&mut self, id: impl IntoNodeId) -> Option> { + let id = id.into_node_id(self); + self.tree + .contains(id) + .then_some(NodeMutRaw { id, dom: self }) } /// 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. @@ -499,7 +508,7 @@ impl RealDom { f(node); if let Some(children) = self.tree.children_ids(id) { for id in children { - queue.push_back(id); + queue.push_back(*id); } } } @@ -510,10 +519,11 @@ impl RealDom { let mut stack = vec![self.root()]; while let Some(id) = stack.pop() { if let Some(children) = self.tree.children_ids(id) { + let children = children.iter().copied().rev().collect::>(); if let Some(node) = self.get_mut(id) { let node = node; f(node); - stack.extend(children.iter().copied().rev()); + stack.extend(children.iter()); } } } @@ -524,6 +534,7 @@ impl RealDom { queue.push_back(self.root()); while let Some(id) = queue.pop_front() { if let Some(children) = self.tree.children_ids(id) { + let children = children.to_vec(); if let Some(node) = self.get_mut(id) { f(node); for id in children { @@ -535,75 +546,116 @@ impl RealDom { } } -// impl Index for RealDom { -// type Output = Node; +pub trait IntoNodeId { + fn into_node_id(self, rdom: &RealDom) -> NodeId; +} -// fn index(&self, id: ElementId) -> &Self::Output { -// self.tree.get(self.element_to_node_id(id)).unwrap() -// } -// } +impl IntoNodeId for NodeId { + fn into_node_id(self, _rdom: &RealDom) -> NodeId { + self + } +} -// impl Index for RealDom { -// type Output = Node; +impl IntoNodeId for ElementId { + fn into_node_id(self, rdom: &RealDom) -> NodeId { + rdom.element_to_node_id(self) + } +} -// fn index(&self, idx: NodeId) -> &Self::Output { -// self.tree.get(idx).unwrap() -// } -// } +pub trait NodeImmutable: Sized { + fn real_dom(&self) -> &RealDom; -// impl IndexMut for RealDom { -// fn index_mut(&mut self, id: ElementId) -> &mut Self::Output { -// self.tree.get_mut(self.element_to_node_id(id)).unwrap() -// } -// } + fn id(&self) -> NodeId; -// impl IndexMut for RealDom { -// fn index_mut(&mut self, idx: NodeId) -> &mut Self::Output { -// self.tree.get_mut(idx).unwrap() -// } -// } + fn mounted_id(&self) -> Option { + self.node_data().mounted_id() + } -pub struct NodeRef<'a, V: FromAnyValue + Send = ()> { + fn node_data(&self) -> &NodeData { + self.get().unwrap() + } + + fn node_type(&self) -> &NodeType { + &self.node_data().node_type + } + + fn get(&self) -> Option<&T> { + self.real_dom().tree.get(self.id()) + } + + fn child_ids(&self) -> Option<&[NodeId]> { + self.real_dom().tree.children_ids(self.id()) + } + + fn children(&self) -> Option>> { + self.child_ids().map(|ids| { + ids.iter() + .map(|id| NodeRef { + id: *id, + dom: self.real_dom(), + }) + .collect() + }) + } + + fn parent_id(&self) -> Option { + self.real_dom().tree.parent_id(self.id()) + } + + fn parent(&self) -> Option> { + self.parent_id().map(|id| NodeRef { + id, + dom: self.real_dom(), + }) + } +} + +pub trait NodeMutable: Sized + NodeImmutable { + fn get_mut(&mut self) -> Option<&mut T>; +} + +#[derive(Clone, Copy)] +pub struct NodeRef<'a, V: FromAnyValue + Send + Sync = ()> { id: NodeId, dom: &'a RealDom, } -impl<'a, V: FromAnyValue + Send> NodeRef<'a, V> { - pub fn node_data(&self) -> &NodeData { - self.read().unwrap() +impl<'a, V: FromAnyValue + Send + Sync> NodeImmutable for NodeRef<'a, V> { + fn real_dom(&self) -> &RealDom { + self.dom } - pub fn node_type(&self) -> &NodeType { - &self.node_data().node_type - } - - pub fn read(&self) -> Option<&T> { - self.dom.tree.read(self.id) + fn id(&self) -> NodeId { + self.id } } -pub struct NodeMut<'a, V: FromAnyValue + Send = ()> { +pub struct NodeMut<'a, V: FromAnyValue + Send + Sync = ()> { id: NodeId, dom: &'a mut RealDom, dirty: NodeMask, } -impl<'a, V: FromAnyValue + Send> NodeMut<'a, V> { - fn node_data(&self) -> &NodeData { - self.read().unwrap() +impl<'a, V: FromAnyValue + Send + Sync> NodeImmutable for NodeMut<'a, V> { + fn real_dom(&self) -> &RealDom { + self.dom } - fn node_data_mut(&mut self) -> &mut NodeData { - self.dom.tree.write(self.id).unwrap() + fn id(&self) -> NodeId { + self.id } +} - pub fn node_type(&self) -> &NodeType { - &self.node_data().node_type +impl<'a, V: FromAnyValue + Send + Sync> NodeMutable for NodeMut<'a, V> { + fn get_mut(&mut self) -> Option<&mut T> { + todo!("get_mut with mark as dirty") } +} +impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { pub fn node_type_mut(&mut self) -> NodeTypeMut<'_, V> { let Self { id, dom, dirty } = self; - let node_type = &mut dom.tree.write::>(*id).unwrap().node_type; + let node_type = &mut dom.tree.get_mut::>(*id).unwrap().node_type; match node_type { NodeType::Element(element) => NodeTypeMut::Element(ElementNodeMut { element, dirty }), NodeType::Text(text) => { @@ -615,20 +667,16 @@ impl<'a, V: FromAnyValue + Send> NodeMut<'a, V> { } pub fn set_type(&mut self, new: NodeType) { - self.node_data_mut().node_type = new; + self.dom + .tree + .get_mut::>(self.id) + .unwrap() + .node_type = new; self.dirty = NodeMaskBuilder::ALL.build(); } - - pub fn read(&self) -> Option<&T> { - self.dom.tree.read(self.id) - } - - pub fn write(&mut self) -> Option<&T> { - todo!("get_mut with mark as dirty") - } } -impl Drop for NodeMut<'_, V> { +impl Drop for NodeMut<'_, V> { fn drop(&mut self) { let node_id = self.node_data().node_id; let mask = std::mem::take(&mut self.dirty); @@ -716,3 +764,24 @@ impl ElementNodeMut<'_, V> { &mut self.element.listeners } } + +pub struct NodeMutRaw<'a, V: FromAnyValue + Send + Sync = ()> { + id: NodeId, + dom: &'a mut RealDom, +} + +impl<'a, V: FromAnyValue + Send + Sync> NodeImmutable for NodeMutRaw<'a, V> { + fn real_dom(&self) -> &RealDom { + self.dom + } + + fn id(&self) -> NodeId { + self.id + } +} + +impl<'a, V: FromAnyValue + Send + Sync> NodeMutable for NodeMutRaw<'a, V> { + fn get_mut(&mut self) -> Option<&mut T> { + self.dom.tree.get_mut::(self.id) + } +} diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index a5bef626c..f4d43e89f 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -164,11 +164,11 @@ impl Tree { } } - pub fn read(&self, id: NodeId) -> Option<&T> { + pub fn get(&self, id: NodeId) -> Option<&T> { self.nodes.read_slab().get(id) } - pub fn write(&mut self, id: NodeId) -> Option<&mut T> { + pub fn get_mut(&mut self, id: NodeId) -> Option<&mut T> { self.nodes.write_slab().get_mut(id) } @@ -184,8 +184,8 @@ impl Tree { self.get_node_data(id).parent } - pub fn children_ids(&self, id: NodeId) -> Option> { - Some(self.get_node_data(id).children.clone()) + pub fn children_ids(&self, id: NodeId) -> Option<&[NodeId]> { + Some(&self.get_node_data(id).children) } pub fn height(&self, id: NodeId) -> Option { diff --git a/packages/native-core/src/utils/mod.rs b/packages/native-core/src/utils/mod.rs index 9ddb1a1c7..58e98b0fa 100644 --- a/packages/native-core/src/utils/mod.rs +++ b/packages/native-core/src/utils/mod.rs @@ -1,3 +1,3 @@ -// mod persistant_iterator; -// pub use persistant_iterator::*; +mod persistant_iterator; +pub use persistant_iterator::*; pub mod cursor; diff --git a/packages/native-core/src/utils/persistant_iterator.rs b/packages/native-core/src/utils/persistant_iterator.rs index f5423cb76..7263619a1 100644 --- a/packages/native-core/src/utils/persistant_iterator.rs +++ b/packages/native-core/src/utils/persistant_iterator.rs @@ -1,7 +1,6 @@ use crate::{ node::{ElementNode, FromAnyValue, NodeType}, - passes::{AnyMapLike, TypeErasedPass}, - real_dom::RealDom, + real_dom::{NodeImmutable, RealDom}, NodeId, }; use dioxus_core::{Mutation, Mutations}; @@ -72,10 +71,10 @@ impl PersistantElementIter { /// remove stale element refreneces /// returns true if the focused element is removed - pub fn prune, V: FromAnyValue>( + pub fn prune( &mut self, mutations: &Mutations, - rdom: &RealDom, + rdom: &RealDom, ) -> bool { let mut changed = false; let ids_removed: Vec<_> = mutations @@ -102,7 +101,7 @@ impl PersistantElementIter { // 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.children_ids(*el_id) { + if let Some(children) = &rdom.get(*el_id).unwrap().child_ids() { for m in &mutations.edits { match m { Mutation::Remove { id } => { @@ -133,7 +132,7 @@ impl PersistantElementIter { } /// get the next element - pub fn next, V: FromAnyValue>(&mut self, rdom: &RealDom) -> ElementProduced { + pub fn next(&mut self, rdom: &RealDom) -> ElementProduced { if self.stack.is_empty() { let id = NodeId(0); let new = (id, NodePosition::AtNode); @@ -142,9 +141,9 @@ impl PersistantElementIter { } else { let (last, old_child_idx) = self.stack.last_mut().unwrap(); let node = rdom.get(*last).unwrap(); - match &node.node_data.node_type { + match &node.node_data().node_type { NodeType::Element(ElementNode { .. }) => { - let children = rdom.children_ids(*last).unwrap(); + let children = node.child_ids().unwrap(); *old_child_idx = old_child_idx.map(|i| i + 1); // if we have children, go to the next child let child_idx = old_child_idx.get_or_insert(0); @@ -154,7 +153,7 @@ impl PersistantElementIter { } else { let id = children[child_idx]; if let NodeType::Element(ElementNode { .. }) = - rdom.get(id).unwrap().node_data.node_type + rdom.get(id).unwrap().node_data().node_type { self.stack.push((id, NodePosition::AtNode)); } @@ -171,19 +170,17 @@ impl PersistantElementIter { } /// get the previous element - pub fn prev + Send, V: FromAnyValue>( - &mut self, - rdom: &RealDom, - ) -> ElementProduced { + pub fn prev(&mut self, rdom: &RealDom) -> ElementProduced { // recursively add the last child element to the stack - fn push_back + Send, V: FromAnyValue>( + fn push_back( stack: &mut smallvec::SmallVec<[(NodeId, NodePosition); 5]>, new_node: NodeId, - rdom: &RealDom, + rdom: &RealDom, ) -> NodeId { - match &rdom[new_node].node_data.node_type { + let node = rdom.get(new_node).unwrap(); + match &node.node_data().node_type { NodeType::Element(ElementNode { .. }) => { - let children = rdom.children_ids(new_node).unwrap(); + let children = node.child_ids().unwrap(); if children.is_empty() { new_node } else { @@ -199,10 +196,10 @@ impl PersistantElementIter { ElementProduced::Looped(push_back(&mut self.stack, new_node, rdom)) } else { let (last, old_child_idx) = self.stack.last_mut().unwrap(); - let node = &rdom[*last]; - match &node.node_data.node_type { + let node = rdom.get(*last).unwrap(); + match &node.node_data().node_type { NodeType::Element(ElementNode { .. }) => { - let children = rdom.children_ids(*last).unwrap(); + let children = node.child_ids().unwrap(); // if we have children, go to the next child if let NodePosition::InChild(0) = old_child_idx { ElementProduced::Progressed(self.pop()) @@ -240,23 +237,6 @@ impl PersistantElementIter { } } -#[derive(Default, Clone, Debug)] -struct Empty {} -impl AnyMapLike for Empty { - fn get(&self) -> Option<&T> { - None - } - - fn get_mut(&mut self) -> Option<&mut T> { - None - } -} -impl State for Empty { - fn create_passes() -> Box<[TypeErasedPass]> { - Box::new([]) - } -} - #[test] #[allow(unused_variables)] fn traverse() { @@ -279,7 +259,7 @@ fn traverse() { let mut vdom = VirtualDom::new(Base); let mutations = vdom.rebuild(); - let mut rdom: RealDom = RealDom::new(); + let mut rdom: RealDom = RealDom::new(Box::new([])); let _to_update = rdom.apply_mutations(mutations); @@ -378,7 +358,7 @@ fn persist_removes() { } let mut vdom = VirtualDom::new(Base); - let mut rdom: RealDom = RealDom::new(); + let mut rdom: RealDom = RealDom::new(Box::new([])); let build = vdom.rebuild(); println!("{:#?}", build); @@ -460,7 +440,7 @@ fn persist_instertions_before() { } let mut vdom = VirtualDom::new(Base); - let mut rdom: RealDom = RealDom::new(); + let mut rdom: RealDom = RealDom::new(Box::new([])); let build = vdom.rebuild(); let _to_update = rdom.apply_mutations(build); @@ -516,7 +496,7 @@ fn persist_instertions_after() { } let mut vdom = VirtualDom::new(Base); - let mut rdom: RealDom = RealDom::new(); + let mut rdom: RealDom = RealDom::new(Box::new([])); let build = vdom.rebuild(); let _to_update = rdom.apply_mutations(build); diff --git a/packages/tui/src/focus.rs b/packages/tui/src/focus.rs index 6d7f89154..4441f44f3 100644 --- a/packages/tui/src/focus.rs +++ b/packages/tui/src/focus.rs @@ -1,15 +1,17 @@ -use crate::{node::PreventDefault, TuiDom}; +use crate::node::PreventDefault; use dioxus_native_core::{ - tree::TreeView, + node_ref::{AttributeMaskBuilder, NodeMaskBuilder}, + real_dom::{NodeImmutable, NodeMutable}, utils::{ElementProduced, PersistantElementIter}, - NodeId, Pass, SendAnyMap, + Dependancy, NodeId, Pass, RealDom, SendAnyMap, }; -use dioxus_native_core_macro::sorted_str_slice; use std::{cmp::Ordering, num::NonZeroU16}; -use dioxus_native_core::node_ref::{AttributeMask, NodeMask, NodeView}; +use dioxus_native_core::node_ref::NodeView; + +pub struct Focused(pub bool); #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub(crate) enum FocusLevel { @@ -62,8 +64,9 @@ pub(crate) struct Focus { } impl Pass for Focus { - const NODE_MASK: NodeMask = - NodeMask::new_with_attrs(AttributeMask::Static(FOCUS_ATTRIBUTES)).with_listeners(); + const NODE_MASK: NodeMaskBuilder = NodeMaskBuilder::new() + .with_attrs(AttributeMaskBuilder::Some(FOCUS_ATTRIBUTES)) + .with_listeners(); type ParentDependencies = (); type ChildDependencies = (); @@ -72,18 +75,10 @@ impl Pass for Focus { fn pass<'a>( &mut self, node_view: NodeView, - _: ::ElementBorrowed<'a>, - _: Option< - ::ElementBorrowed<'a>, - >, - _: Option< - impl Iterator< - Item = ::ElementBorrowed< - 'a, - >, - >, - >, - _: &SendAnyMap, + node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + children: Option::ElementBorrowed<'a>>>, + context: &SendAnyMap, ) -> bool { let new = Focus { level: if let Some(a) = node_view @@ -128,8 +123,8 @@ impl Pass for Focus { } } -const FOCUS_EVENTS: &[&str] = &sorted_str_slice!(["keydown", "keypress", "keyup"]); -const FOCUS_ATTRIBUTES: &[&str] = &sorted_str_slice!(["tabindex"]); +const FOCUS_EVENTS: &[&str] = &["keydown", "keypress", "keyup"]; +const FOCUS_ATTRIBUTES: &[&str] = &["tabindex"]; #[derive(Default)] pub(crate) struct FocusState { @@ -141,9 +136,11 @@ pub(crate) struct FocusState { impl FocusState { /// Returns true if the focus has changed. - pub fn progress(&mut self, rdom: &mut TuiDom, forward: bool) -> bool { + pub fn progress(&mut self, rdom: &mut RealDom, forward: bool) -> bool { if let Some(last) = self.last_focused_id { - if rdom[last].state.prevent_default == PreventDefault::KeyDown { + if rdom.get(last).unwrap().get::().copied() + == Some(PreventDefault::KeyDown) + { return false; } } @@ -165,7 +162,7 @@ impl FocusState { if forward { // find the closest focusable element after the current level rdom.traverse_depth_first(|n| { - let node_level = n.state.focus.level; + let node_level = n.get::().unwrap().level; if node_level != *focus_level && node_level.focusable() && node_level > *focus_level @@ -182,7 +179,7 @@ impl FocusState { } else { // find the closest focusable element before the current level rdom.traverse_depth_first(|n| { - let node_level = n.state.focus.level; + let node_level = n.get::().unwrap().level; if node_level != *focus_level && node_level.focusable() && node_level < *focus_level @@ -219,7 +216,7 @@ impl FocusState { loop_marker_id = Some(new_id); } - let current_level = rdom[new_id].state.focus.level; + let current_level = rdom.get(new_id).unwrap().get::().unwrap().level; let after_previous_focused = if forward { current_level >= *focus_level } else { @@ -233,12 +230,16 @@ impl FocusState { } if let Some(id) = next_focus { - if !rdom[id].state.focus.level.focusable() { + let mut node = rdom.get_mut_raw(id).unwrap(); + if !node.get::().unwrap().level.focusable() { panic!() } - rdom[id].state.focused = true; + let focused = node.get_mut::().unwrap(); + focused.0 = true; if let Some(old) = self.last_focused_id.replace(id) { - rdom[old].state.focused = false; + let mut old = rdom.get_mut_raw(old).unwrap(); + let focused = old.get_mut::().unwrap(); + focused.0 = false; } // reset the position to the currently focused element while self.focus_iter.next(rdom).id() != id {} @@ -249,8 +250,8 @@ impl FocusState { false } - pub(crate) fn prune(&mut self, mutations: &dioxus_core::Mutations, rdom: &TuiDom) { - fn remove_children(to_prune: &mut [&mut Option], rdom: &TuiDom, removed: NodeId) { + pub(crate) fn prune(&mut self, mutations: &dioxus_core::Mutations, rdom: &RealDom) { + fn remove_children(to_prune: &mut [&mut Option], rdom: &RealDom, removed: NodeId) { for opt in to_prune.iter_mut() { if let Some(id) = opt { if *id == removed { @@ -258,8 +259,9 @@ impl FocusState { } } } - if let Some(children) = &rdom.children_ids(removed) { - for child in *children { + let node = rdom.get(removed).unwrap(); + if let Some(children) = node.child_ids() { + for child in children { remove_children(to_prune, rdom, *child); } } @@ -284,13 +286,14 @@ impl FocusState { } } - pub(crate) fn set_focus(&mut self, rdom: &mut TuiDom, id: NodeId) { + pub(crate) fn set_focus(&mut self, rdom: &mut RealDom, id: NodeId) { if let Some(old) = self.last_focused_id.replace(id) { - rdom[old].state.focused = false; + let mut node = rdom.get_mut_raw(old).unwrap(); + node.get_mut::().unwrap().0 = false; } - let state = &mut rdom[id].state; - state.focused = true; - self.focus_level = state.focus.level; + let mut node = &mut rdom.get_mut_raw(id).unwrap(); + node.get_mut::().unwrap().0 = true; + self.focus_level = node.get::().unwrap().level; // reset the position to the currently focused element while self.focus_iter.next(rdom).id() != id {} self.dirty = true; diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index ab5064bc7..6f043d522 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -2,8 +2,8 @@ use crossterm::event::{ Event as TermEvent, KeyCode as TermKeyCode, KeyModifiers, MouseButton, MouseEventKind, }; use dioxus_core::*; -use dioxus_native_core::tree::TreeView; -use dioxus_native_core::NodeId; +use dioxus_native_core::real_dom::NodeImmutable; +use dioxus_native_core::{NodeId, NodeRef, RealDom}; use rustc_hash::{FxHashMap, FxHashSet}; use dioxus_html::geometry::euclid::{Point2D, Rect, Size2D}; @@ -23,8 +23,9 @@ use std::{ use taffy::geometry::{Point, Size}; use taffy::{prelude::Layout, Taffy}; +use crate::focus::{Focus, Focused}; +use crate::layout::TaffyLayout; use crate::{layout_to_screen_space, FocusState}; -use crate::{TuiDom, TuiNode}; pub(crate) struct Event { pub id: ElementId, @@ -173,7 +174,7 @@ impl InnerInputState { evts: &mut Vec, resolved_events: &mut Vec, layout: &Taffy, - dom: &mut TuiDom, + dom: &mut RealDom, ) { let previous_mouse = self.mouse.clone(); @@ -201,7 +202,7 @@ impl InnerInputState { // elements with listeners will always have a element id if let Some(id) = self.focus_state.last_focused_id { let element = dom.get(id).unwrap(); - if let Some(id) = element.node_data.element_id { + if let Some(id) = element.node_data().element_id { resolved_events.push(Event { name: "focus", id, @@ -218,7 +219,7 @@ impl InnerInputState { } if let Some(id) = old_focus { let element = dom.get(id).unwrap(); - if let Some(id) = element.node_data.element_id { + if let Some(id) = element.node_data().element_id { resolved_events.push(Event { name: "focusout", id, @@ -239,7 +240,7 @@ impl InnerInputState { previous_mouse: Option, resolved_events: &mut Vec, layout: &Taffy, - dom: &mut TuiDom, + dom: &mut RealDom, ) { fn layout_contains_point(layout: &Layout, point: ScreenPoint) -> bool { let Point { x, y } = layout.location; @@ -262,17 +263,17 @@ impl InnerInputState { data: Rc, will_bubble: &mut FxHashSet, resolved_events: &mut Vec, - node: &TuiNode, - dom: &TuiDom, + node: NodeRef, + dom: &RealDom, ) { // only trigger event if the event was not triggered already by a child - let id = node.node_data.node_id; + let id = node.node_data().node_id; if will_bubble.insert(id) { - let mut parent = dom.parent(id); + let mut parent = Some(node); while let Some(current_parent) = parent { - let parent_id = current_parent.node_data.node_id; + let parent_id = current_parent.node_data().node_id; will_bubble.insert(parent_id); - parent = dom.parent(parent_id); + parent = current_parent.parent_id().and_then(|id| dom.get(id)); } if let Some(id) = node.mounted_id() { resolved_events.push(Event { @@ -564,11 +565,13 @@ impl InnerInputState { if was_released { let mut focus_id = None; dom.traverse_depth_first(|node| { - let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); + let node_layout = layout + .layout(node.get::().unwrap().node.unwrap()) + .unwrap(); let currently_contains = layout_contains_point(node_layout, new_pos); - if currently_contains && node.state.focus.level.focusable() { - focus_id = Some(node.node_data.node_id); + if currently_contains && node.get::().unwrap().level.focusable() { + focus_id = Some(node.node_data().node_id); } }); if let Some(id) = focus_id { @@ -583,13 +586,18 @@ impl InnerInputState { // } } -fn get_abs_layout(node: &TuiNode, dom: &TuiDom, taffy: &Taffy) -> Layout { - let mut node_layout = *taffy.layout(node.state.layout.node.unwrap()).unwrap(); +fn get_abs_layout(node: NodeRef, dom: &RealDom, taffy: &Taffy) -> Layout { + let mut node_layout = *taffy + .layout(node.get::().unwrap().node.unwrap()) + .unwrap(); let mut current = node; - while let Some(parent) = dom.parent(current.node_data.node_id) { + while let Some(parent) = current.parent_id() { + let parent = dom.get(parent).unwrap(); current = parent; - let parent_layout = taffy.layout(parent.state.layout.node.unwrap()).unwrap(); + let parent_layout = taffy + .layout(parent.get::().unwrap().node.unwrap()) + .unwrap(); node_layout.location.x += parent_layout.location.x; node_layout.location.y += parent_layout.location.y; } @@ -632,11 +640,11 @@ impl RinkInputHandler { ) } - pub(crate) fn prune(&self, mutations: &dioxus_core::Mutations, rdom: &TuiDom) { + pub(crate) fn prune(&self, mutations: &dioxus_core::Mutations, rdom: &RealDom) { self.state.borrow_mut().focus_state.prune(mutations, rdom); } - pub(crate) fn get_events(&self, layout: &Taffy, dom: &mut TuiDom) -> Vec { + pub(crate) fn get_events(&self, layout: &Taffy, dom: &mut RealDom) -> Vec { let mut resolved_events = Vec::new(); (*self.state).borrow_mut().update( @@ -680,7 +688,7 @@ impl RinkInputHandler { for (event, datas) in hm { for node in dom.get_listening_sorted(event) { for data in &datas { - if node.state.focused { + if node.get::().unwrap().0 { if let Some(id) = node.mounted_id() { resolved_events.push(Event { name: event, diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index 5d539e7e0..87241a287 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -4,9 +4,8 @@ use dioxus_native_core::layout_attributes::{ apply_layout_attributes_cfg, BorderWidths, LayoutConfigeration, }; use dioxus_native_core::node::OwnedAttributeView; -use dioxus_native_core::node_ref::{AttributeMask, NodeMask, NodeView}; -use dioxus_native_core::{Pass, SendAnyMap}; -use dioxus_native_core_macro::sorted_str_slice; +use dioxus_native_core::node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}; +use dioxus_native_core::{Dependancy, Pass, SendAnyMap}; use taffy::prelude::*; use crate::{screen_to_layout_space, unit_to_layout_space}; @@ -48,23 +47,16 @@ impl Pass for TaffyLayout { type ParentDependencies = (); type NodeDependencies = (); - const NODE_MASK: NodeMask = - NodeMask::new_with_attrs(AttributeMask::Static(SORTED_LAYOUT_ATTRS)).with_text(); + const NODE_MASK: NodeMaskBuilder = NodeMaskBuilder::new() + .with_attrs(AttributeMaskBuilder::Some(SORTED_LAYOUT_ATTRS)) + .with_text(); fn pass<'a>( &mut self, node_view: NodeView, - _: ::ElementBorrowed<'a>, - _: Option< - ::ElementBorrowed<'a>, - >, - children: Option< - impl Iterator< - Item = ::ElementBorrowed< - 'a, - >, - >, - >, + node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + children: Option::ElementBorrowed<'a>>>, ctx: &SendAnyMap, ) -> bool { let mut changed = false; @@ -203,7 +195,7 @@ impl Pass for TaffyLayout { } // these are the attributes in layout_attiributes in native-core -const SORTED_LAYOUT_ATTRS: &[&str] = &sorted_str_slice!([ +const SORTED_LAYOUT_ATTRS: &[&str] = &[ "align-content", "align-items", "align-self", @@ -355,5 +347,5 @@ const SORTED_LAYOUT_ATTRS: &[&str] = &sorted_str_slice!([ "word-break", "word-spacing", "word-wrap", - "z-index" -]); + "z-index", +]; diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index c90893bf9..99d531215 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -6,6 +6,7 @@ use crossterm::{ terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; use dioxus_core::*; +use dioxus_native_core::{node_ref::NodeMaskBuilder, real_dom::NodeImmutable, Pass}; use dioxus_native_core::{real_dom::RealDom, FxDashSet, NodeId, NodeMask, SendAnyMap}; use focus::FocusState; use futures::{ @@ -13,6 +14,7 @@ use futures::{ pin_mut, StreamExt, }; use futures_channel::mpsc::unbounded; +use layout::TaffyLayout; use query::Query; use std::rc::Rc; use std::{ @@ -101,7 +103,9 @@ pub fn launch_cfg_with_props(app: Component, props: Props } let cx = dom.base_scope(); - let rdom = Rc::new(RefCell::new(RealDom::new())); + let rdom = Rc::new(RefCell::new(RealDom::new(Box::new([ + TaffyLayout::to_type_erased(), + ])))); let taffy = Arc::new(Mutex::new(Taffy::new())); cx.provide_context(state); cx.provide_context(TuiContext { tx: event_tx_clone }); @@ -136,7 +140,7 @@ fn render_vdom( mut event_reciever: UnboundedReceiver, handler: RinkInputHandler, cfg: Config, - rdom: Rc>, + rdom: Rc>, taffy: Arc>, mut register_event: impl FnMut(crossterm::event::Event), ) -> Result<()> { @@ -177,10 +181,16 @@ fn render_vdom( if !to_rerender.is_empty() || updated { updated = false; - fn resize(dims: Rect, taffy: &mut Taffy, rdom: &TuiDom) { + fn resize(dims: Rect, taffy: &mut Taffy, rdom: &RealDom) { 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(); + let root_node = rdom + .get(NodeId(0)) + .unwrap() + .get::() + .unwrap() + .node + .unwrap(); // the root node fills the entire area @@ -204,7 +214,7 @@ fn render_vdom( 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)]; + let root = rdom.get(NodeId(0)).unwrap(); render::render_vnode(frame, &taffy, &rdom, root, cfg, Point::ZERO); })?; execute!(terminal.backend_mut(), RestorePosition, Show).unwrap(); @@ -277,8 +287,9 @@ fn render_vdom( any_map.insert(taffy.clone()); let (new_to_rerender, dirty) = rdom.update_state(any_map); to_rerender = new_to_rerender; + let text_mask = NodeMaskBuilder::new().with_text().build(); for (id, mask) in dirty { - if mask.overlaps(&NodeMask::new().with_text()) { + if mask.overlaps(&text_mask) { to_rerender.insert(id); } } diff --git a/packages/tui/src/node.rs b/packages/tui/src/node.rs index 8dc418b24..3d38d1cd7 100644 --- a/packages/tui/src/node.rs +++ b/packages/tui/src/node.rs @@ -1,23 +1,18 @@ use crate::focus::Focus; use crate::layout::TaffyLayout; use crate::style_attributes::StyleModifier; -use dioxus_native_core::{real_dom::RealDom, Dependancy, Pass, SendAnyMap}; -use dioxus_native_core_macro::{sorted_str_slice, AnyMapLike, State}; +use dioxus_native_core::{node_ref::NodeView, Dependancy, Pass, SendAnyMap}; -pub(crate) type TuiDom = RealDom; -pub(crate) type TuiNode = dioxus_native_core::node::Node; - -#[derive(Debug, Clone, State, AnyMapLike, Default)] +#[derive(Debug, Clone, Default)] pub(crate) struct NodeState { pub layout: TaffyLayout, pub style: StyleModifier, pub prevent_default: PreventDefault, pub focus: Focus, - #[skip] pub focused: bool, } -#[derive(PartialEq, Debug, Clone)] +#[derive(PartialEq, Debug, Clone, Copy)] pub(crate) enum PreventDefault { Focus, KeyPress, @@ -47,23 +42,20 @@ impl Pass for PreventDefault { type ChildDependencies = (); type NodeDependencies = (); - const NODE_MASK: dioxus_native_core::node_ref::NodeMask = - dioxus_native_core::node_ref::NodeMask::new_with_attrs( - dioxus_native_core::node_ref::AttributeMask::Static(&sorted_str_slice!([ - "dioxus-prevent-default" - ])), - ) - .with_listeners(); + const NODE_MASK: dioxus_native_core::node_ref::NodeMaskBuilder = + dioxus_native_core::node_ref::NodeMaskBuilder::new() + .with_attrs(dioxus_native_core::node_ref::AttributeMaskBuilder::Some(&[ + "dioxus-prevent-default", + ])) + .with_listeners(); fn pass<'a>( &mut self, - node_view: dioxus_native_core::node_ref::NodeView, - _: ::ElementBorrowed<'a>, - _: Option<::ElementBorrowed<'a>>, - _: Option< - impl Iterator::ElementBorrowed<'a>>, - >, - _: &SendAnyMap, + node_view: NodeView, + node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + children: Option::ElementBorrowed<'a>>>, + context: &SendAnyMap, ) -> bool { let new = match node_view.attributes().and_then(|mut attrs| { attrs diff --git a/packages/tui/src/query.rs b/packages/tui/src/query.rs index c052ad9ea..df1f69fbd 100644 --- a/packages/tui/src/query.rs +++ b/packages/tui/src/query.rs @@ -5,13 +5,14 @@ use std::{ }; use dioxus_core::ElementId; +use dioxus_native_core::{real_dom::NodeImmutable, RealDom}; use taffy::{ geometry::Point, prelude::{Layout, Size}, Taffy, }; -use crate::{layout_to_screen_space, TuiDom}; +use crate::{layout::TaffyLayout, layout_to_screen_space}; /// 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. @@ -46,7 +47,7 @@ use crate::{layout_to_screen_space, TuiDom}; /// ``` #[derive(Clone)] pub struct Query { - pub(crate) rdom: Rc>, + pub(crate) rdom: Rc>, pub(crate) stretch: Arc>, } @@ -61,13 +62,13 @@ impl Query { } pub struct ElementRef<'a> { - inner: Ref<'a, TuiDom>, + inner: Ref<'a, RealDom>, stretch: MutexGuard<'a, Taffy>, id: ElementId, } impl<'a> ElementRef<'a> { - fn new(inner: Ref<'a, TuiDom>, stretch: MutexGuard<'a, Taffy>, id: ElementId) -> Self { + fn new(inner: Ref<'a, RealDom>, stretch: MutexGuard<'a, Taffy>, id: ElementId) -> Self { Self { inner, stretch, id } } @@ -85,7 +86,15 @@ impl<'a> ElementRef<'a> { pub fn layout(&self) -> Option { let layout = self .stretch - .layout(self.inner[self.id].state.layout.node.ok()?) + .layout( + self.inner + .get(self.id) + .unwrap() + .get::() + .unwrap() + .node + .ok()?, + ) .ok(); layout.map(|layout| Layout { order: layout.order, diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index c6d5c6923..372d26183 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -1,18 +1,24 @@ -use dioxus_native_core::tree::TreeView; +use dioxus_native_core::{real_dom::NodeImmutable, NodeRef, RealDom}; use std::io::Stdout; use taffy::{ geometry::Point, prelude::{Dimension, Layout, Size}, Taffy, }; -use tui::{backend::CrosstermBackend, layout::Rect, style::Color}; +use tui::{ + backend::CrosstermBackend, + layout::Rect, + style::{Color, Style}, +}; use crate::{ + focus::Focused, + layout::TaffyLayout, layout_to_screen_space, style::{RinkColor, RinkStyle}, - style_attributes::{BorderEdge, BorderStyle}, + style_attributes::{BorderEdge, BorderStyle, StyleModifier}, widget::{RinkBuffer, RinkCell, RinkWidget, WidgetWithContext}, - Config, TuiDom, TuiNode, + Config, }; const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5]; @@ -20,20 +26,22 @@ const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5]; pub(crate) fn render_vnode( frame: &mut tui::Frame>, layout: &Taffy, - rdom: &TuiDom, - node: &TuiNode, + rdom: &RealDom, + node: NodeRef, cfg: Config, parent_location: Point, ) { use dioxus_native_core::node::NodeType; - if let NodeType::Placeholder = &node.node_data.node_type { + if let NodeType::Placeholder = &node.node_type() { return; } let Layout { mut location, size, .. - } = layout.layout(node.state.layout.node.unwrap()).unwrap(); + } = layout + .layout(node.get::().unwrap().node.unwrap()) + .unwrap(); location.x += parent_location.x; location.y += parent_location.y; @@ -44,7 +52,7 @@ pub(crate) fn render_vnode( 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 { + match node.node_type() { NodeType::Text(text) => { #[derive(Default)] struct Label<'a> { @@ -65,7 +73,7 @@ pub(crate) fn render_vnode( let label = Label { text, - style: node.state.style.core, + style: node.get::().unwrap().core, }; let area = Rect::new(x, y, width, height); @@ -82,15 +90,15 @@ pub(crate) fn render_vnode( frame.render_widget(WidgetWithContext::new(node, cfg), area); } - for c in rdom.children_ids(node.node_data.node_id).unwrap() { - render_vnode(frame, layout, rdom, &rdom[*c], cfg, location); + for c in node.children().unwrap() { + render_vnode(frame, layout, rdom, c, cfg, location); } } NodeType::Placeholder => unreachable!(), } } -impl RinkWidget for &TuiNode { +impl RinkWidget for NodeRef<'_> { fn render(self, area: Rect, mut buf: RinkBuffer<'_>) { use tui::symbols::line::*; @@ -275,10 +283,10 @@ impl RinkWidget for &TuiNode { for x in area.left()..area.right() { for y in area.top()..area.bottom() { let mut new_cell = RinkCell::default(); - if let Some(c) = self.state.style.core.bg { + if let Some(c) = self.get::().unwrap().core.bg { new_cell.bg = c; } - if self.state.focused { + if self.get::().unwrap().0 { new_cell.bg.alpha = 100; new_cell.bg.color = new_cell.bg.blend(Color::White); } @@ -286,7 +294,9 @@ impl RinkWidget for &TuiNode { } } - let borders = &self.state.style.modifier.borders; + let style = self.get::().unwrap(); + + let borders = &style.modifier.borders; let last_edge = &borders.left; let current_edge = &borders.top; @@ -303,7 +313,7 @@ impl RinkWidget for &TuiNode { (last_r * RADIUS_MULTIPLIER[0]) as u16, (last_r * RADIUS_MULTIPLIER[1]) as u16, ]; - let color = current_edge.color.or(self.state.style.core.fg); + let color = current_edge.color.or(style.core.fg); let mut new_cell = RinkCell::default(); if let Some(c) = color { new_cell.fg = c; @@ -338,7 +348,7 @@ impl RinkWidget for &TuiNode { (last_r * RADIUS_MULTIPLIER[0]) as u16, (last_r * RADIUS_MULTIPLIER[1]) as u16, ]; - let color = current_edge.color.or(self.state.style.core.fg); + let color = current_edge.color.or(style.core.fg); let mut new_cell = RinkCell::default(); if let Some(c) = color { new_cell.fg = c; @@ -373,7 +383,7 @@ impl RinkWidget for &TuiNode { (last_r * RADIUS_MULTIPLIER[0]) as u16, (last_r * RADIUS_MULTIPLIER[1]) as u16, ]; - let color = current_edge.color.or(self.state.style.core.fg); + let color = current_edge.color.or(style.core.fg); let mut new_cell = RinkCell::default(); if let Some(c) = color { new_cell.fg = c; @@ -408,7 +418,7 @@ impl RinkWidget for &TuiNode { (last_r * RADIUS_MULTIPLIER[0]) as u16, (last_r * RADIUS_MULTIPLIER[1]) as u16, ]; - let color = current_edge.color.or(self.state.style.core.fg); + let color = current_edge.color.or(style.core.fg); let mut new_cell = RinkCell::default(); if let Some(c) = color { new_cell.fg = c; diff --git a/packages/tui/src/style_attributes.rs b/packages/tui/src/style_attributes.rs index e6f5d5527..75b956ecd 100644 --- a/packages/tui/src/style_attributes.rs +++ b/packages/tui/src/style_attributes.rs @@ -32,10 +32,9 @@ use dioxus_native_core::{ layout_attributes::parse_value, node::OwnedAttributeView, - node_ref::{AttributeMask, NodeMask, NodeView}, - Pass, SendAnyMap, + node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}, + Dependancy, Pass, SendAnyMap, }; -use dioxus_native_core_macro::sorted_str_slice; use taffy::prelude::*; use crate::style::{RinkColor, RinkStyle}; @@ -52,24 +51,17 @@ impl Pass for StyleModifier { type NodeDependencies = (); // todo: seperate each attribute into it's own class - const NODE_MASK: NodeMask = - NodeMask::new_with_attrs(AttributeMask::Static(SORTED_STYLE_ATTRS)).with_element(); + const NODE_MASK: NodeMaskBuilder = NodeMaskBuilder::new() + .with_attrs(AttributeMaskBuilder::Some(SORTED_STYLE_ATTRS)) + .with_element(); fn pass<'a>( &mut self, node_view: NodeView, - _: ::ElementBorrowed<'a>, - parent: Option< - ::ElementBorrowed<'a>, - >, - _: Option< - impl Iterator< - Item = ::ElementBorrowed< - 'a, - >, - >, - >, - _: &SendAnyMap, + node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + children: Option::ElementBorrowed<'a>>>, + context: &SendAnyMap, ) -> bool { let mut new = StyleModifier::default(); if parent.is_some() { @@ -615,7 +607,7 @@ fn apply_transition(_name: &str, _value: &str, _style: &mut StyleModifier) { todo!() } -const SORTED_STYLE_ATTRS: &[&str] = &sorted_str_slice!([ +const SORTED_STYLE_ATTRS: &[&str] = &[ "animation", "animation-delay", "animation-direction", @@ -816,5 +808,5 @@ const SORTED_STYLE_ATTRS: &[&str] = &sorted_str_slice!([ "text-justify", "text-overflow", "text-shadow", - "text-transform" -]); + "text-transform", +]; From ec31014966a07e0c4dbfe6fe768b73d7fa4b5b5d Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 28 Jan 2023 15:06:03 -0600 Subject: [PATCH 021/103] fix clippy in TUI --- packages/tui/src/focus.rs | 12 ++++----- packages/tui/src/layout.rs | 4 +-- packages/tui/src/lib.rs | 7 +++-- .../tui/src/{node.rs => prevent_default.rs} | 20 +++----------- packages/tui/src/render.rs | 26 +++++-------------- packages/tui/src/style_attributes.rs | 6 ++--- 6 files changed, 24 insertions(+), 51 deletions(-) rename packages/tui/src/{node.rs => prevent_default.rs} (78%) diff --git a/packages/tui/src/focus.rs b/packages/tui/src/focus.rs index 4441f44f3..430964637 100644 --- a/packages/tui/src/focus.rs +++ b/packages/tui/src/focus.rs @@ -1,4 +1,4 @@ -use crate::node::PreventDefault; +use crate::prevent_default::PreventDefault; use dioxus_native_core::{ node_ref::{AttributeMaskBuilder, NodeMaskBuilder}, @@ -75,10 +75,10 @@ impl Pass for Focus { fn pass<'a>( &mut self, node_view: NodeView, - node: ::ElementBorrowed<'a>, - parent: Option<::ElementBorrowed<'a>>, - children: Option::ElementBorrowed<'a>>>, - context: &SendAnyMap, + _: ::ElementBorrowed<'a>, + _: Option<::ElementBorrowed<'a>>, + _: Option::ElementBorrowed<'a>>>, + _: &SendAnyMap, ) -> bool { let new = Focus { level: if let Some(a) = node_view @@ -291,7 +291,7 @@ impl FocusState { let mut node = rdom.get_mut_raw(old).unwrap(); node.get_mut::().unwrap().0 = false; } - let mut node = &mut rdom.get_mut_raw(id).unwrap(); + let mut node = rdom.get_mut_raw(id).unwrap(); node.get_mut::().unwrap().0 = true; self.focus_level = node.get::().unwrap().level; // reset the position to the currently focused element diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index 87241a287..5115de1e9 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -54,8 +54,8 @@ impl Pass for TaffyLayout { fn pass<'a>( &mut self, node_view: NodeView, - node: ::ElementBorrowed<'a>, - parent: Option<::ElementBorrowed<'a>>, + _: ::ElementBorrowed<'a>, + _: Option<::ElementBorrowed<'a>>, children: Option::ElementBorrowed<'a>>>, ctx: &SendAnyMap, ) -> bool { diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 99d531215..295722d93 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -7,7 +7,7 @@ use crossterm::{ }; use dioxus_core::*; use dioxus_native_core::{node_ref::NodeMaskBuilder, real_dom::NodeImmutable, Pass}; -use dioxus_native_core::{real_dom::RealDom, FxDashSet, NodeId, NodeMask, SendAnyMap}; +use dioxus_native_core::{real_dom::RealDom, FxDashSet, NodeId, SendAnyMap}; use focus::FocusState; use futures::{ channel::mpsc::{UnboundedReceiver, UnboundedSender}, @@ -30,8 +30,8 @@ mod config; mod focus; mod hooks; mod layout; -mod node; pub mod prelude; +mod prevent_default; pub mod query; mod render; mod style; @@ -41,7 +41,6 @@ mod widgets; 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 { @@ -215,7 +214,7 @@ fn render_vdom( // size is guaranteed to not change when rendering resize(frame.size(), &mut taffy, &rdom); let root = rdom.get(NodeId(0)).unwrap(); - render::render_vnode(frame, &taffy, &rdom, root, cfg, Point::ZERO); + render::render_vnode(frame, &taffy, root, cfg, Point::ZERO); })?; execute!(terminal.backend_mut(), RestorePosition, Show).unwrap(); } else { diff --git a/packages/tui/src/node.rs b/packages/tui/src/prevent_default.rs similarity index 78% rename from packages/tui/src/node.rs rename to packages/tui/src/prevent_default.rs index 3d38d1cd7..7755e610c 100644 --- a/packages/tui/src/node.rs +++ b/packages/tui/src/prevent_default.rs @@ -1,17 +1,5 @@ -use crate::focus::Focus; -use crate::layout::TaffyLayout; -use crate::style_attributes::StyleModifier; use dioxus_native_core::{node_ref::NodeView, Dependancy, Pass, SendAnyMap}; -#[derive(Debug, Clone, Default)] -pub(crate) struct NodeState { - pub layout: TaffyLayout, - pub style: StyleModifier, - pub prevent_default: PreventDefault, - pub focus: Focus, - pub focused: bool, -} - #[derive(PartialEq, Debug, Clone, Copy)] pub(crate) enum PreventDefault { Focus, @@ -52,10 +40,10 @@ impl Pass for PreventDefault { fn pass<'a>( &mut self, node_view: NodeView, - node: ::ElementBorrowed<'a>, - parent: Option<::ElementBorrowed<'a>>, - children: Option::ElementBorrowed<'a>>>, - context: &SendAnyMap, + _: ::ElementBorrowed<'a>, + _: Option<::ElementBorrowed<'a>>, + _: Option::ElementBorrowed<'a>>>, + _: &SendAnyMap, ) -> bool { let new = match node_view.attributes().and_then(|mut attrs| { attrs diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index 372d26183..17355b5b5 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -1,15 +1,11 @@ -use dioxus_native_core::{real_dom::NodeImmutable, NodeRef, RealDom}; +use dioxus_native_core::{real_dom::NodeImmutable, NodeRef}; use std::io::Stdout; use taffy::{ geometry::Point, prelude::{Dimension, Layout, Size}, Taffy, }; -use tui::{ - backend::CrosstermBackend, - layout::Rect, - style::{Color, Style}, -}; +use tui::{backend::CrosstermBackend, layout::Rect, style::Color}; use crate::{ focus::Focused, @@ -26,7 +22,6 @@ const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5]; pub(crate) fn render_vnode( frame: &mut tui::Frame>, layout: &Taffy, - rdom: &RealDom, node: NodeRef, cfg: Config, parent_location: Point, @@ -91,7 +86,7 @@ pub(crate) fn render_vnode( } for c in node.children().unwrap() { - render_vnode(frame, layout, rdom, c, cfg, location); + render_vnode(frame, layout, c, cfg, location); } } NodeType::Placeholder => unreachable!(), @@ -123,10 +118,7 @@ impl RinkWidget for NodeRef<'_> { [0, 1] => Direction::Down, [0, -1] => Direction::Up, [a, b] => { - panic!( - "draw({:?} {:?} {:?}) {}, {} no cell adjacent", - before, current, after, a, b - ) + panic!("draw({before:?} {current:?} {after:?}) {a}, {b} no cell adjacent") } }; let end_dir = match [after[0] - current[0], after[1] - current[1]] { @@ -135,10 +127,7 @@ impl RinkWidget for NodeRef<'_> { [0, 1] => Direction::Down, [0, -1] => Direction::Up, [a, b] => { - panic!( - "draw({:?} {:?} {:?}) {}, {} no cell adjacent", - before, current, after, a, b - ) + panic!("draw({before:?} {current:?} {after:?}) {a}, {b} no cell adjacent") } }; @@ -159,10 +148,7 @@ impl RinkWidget for NodeRef<'_> { [Direction::Left, Direction::Up] => symbols.bottom_right, [Direction::Left, Direction::Right] => symbols.horizontal, [Direction::Left, Direction::Down] => symbols.top_right, - _ => panic!( - "{:?} {:?} {:?} cannont connect cell to itself", - before, current, after - ), + _ => panic!("{before:?} {current:?} {after:?} cannont connect cell to itself"), } .to_string(); buf.set( diff --git a/packages/tui/src/style_attributes.rs b/packages/tui/src/style_attributes.rs index 75b956ecd..ada2ace9a 100644 --- a/packages/tui/src/style_attributes.rs +++ b/packages/tui/src/style_attributes.rs @@ -58,10 +58,10 @@ impl Pass for StyleModifier { fn pass<'a>( &mut self, node_view: NodeView, - node: ::ElementBorrowed<'a>, + _: ::ElementBorrowed<'a>, parent: Option<::ElementBorrowed<'a>>, - children: Option::ElementBorrowed<'a>>>, - context: &SendAnyMap, + _: Option::ElementBorrowed<'a>>>, + _: &SendAnyMap, ) -> bool { let mut new = StyleModifier::default(); if parent.is_some() { From a74a3b1925172e092050cfca4b6194c2cf110d4e Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 28 Jan 2023 15:54:02 -0600 Subject: [PATCH 022/103] add remaining passes --- packages/tui/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 295722d93..9f14d49fd 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -1,3 +1,4 @@ +use crate::focus::Focus; use anyhow::Result; use crossterm::{ cursor::{MoveTo, RestorePosition, SavePosition, Show}, @@ -15,6 +16,7 @@ use futures::{ }; use futures_channel::mpsc::unbounded; use layout::TaffyLayout; +use prevent_default::PreventDefault; use query::Query; use std::rc::Rc; use std::{ @@ -22,6 +24,7 @@ use std::{ sync::{Arc, Mutex}, }; use std::{io, time::Duration}; +use style_attributes::StyleModifier; use taffy::Taffy; pub use taffy::{geometry::Point, prelude::*}; use tui::{backend::CrosstermBackend, layout::Rect, Terminal}; @@ -104,6 +107,9 @@ pub fn launch_cfg_with_props(app: Component, props: Props let cx = dom.base_scope(); let rdom = Rc::new(RefCell::new(RealDom::new(Box::new([ TaffyLayout::to_type_erased(), + Focus::to_type_erased(), + StyleModifier::to_type_erased(), + PreventDefault::to_type_erased(), ])))); let taffy = Arc::new(Mutex::new(Taffy::new())); cx.provide_context(state); From 2c205f8ca56f350d5d8a52cd0a5b2e276f721cda Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 29 Jan 2023 09:17:06 -0600 Subject: [PATCH 023/103] fix some type bounds --- packages/native-core/src/passes.rs | 26 ++++++++++++------------ packages/native-core/src/real_dom.rs | 9 +++++---- packages/native-core/src/tree.rs | 30 ++++++++++++++++------------ 3 files changed, 35 insertions(+), 30 deletions(-) diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index 57a4b8609..525e22461 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -109,7 +109,7 @@ impl DirtyNodeStates { } } -pub trait Pass: Any { +pub trait Pass: Any + Send + Sync { /// This is a tuple of (T: Any, ..) type ParentDependencies: Dependancy; /// This is a tuple of (T: Any, ..) @@ -299,17 +299,17 @@ pub enum PassDirection { type PassCallback = Box bool + Send + Sync>; pub trait AnyMapLike<'a> { - fn get(self) -> Option<&'a T>; + fn get(self) -> Option<&'a T>; } impl<'a> AnyMapLike<'a> for &'a AnyMap { - fn get(self) -> Option<&'a T> { + fn get(self) -> Option<&'a T> { self.get() } } impl<'a> AnyMapLike<'a> for &'a SendAnyMap { - fn get(self) -> Option<&'a T> { + fn get(self) -> Option<&'a T> { todo!() } } @@ -327,7 +327,7 @@ pub trait Dependancy { macro_rules! impl_dependancy { ($($t:ident),*) => { - impl< $($t: Any),* > Dependancy for ($($t,)*) { + impl< $($t: Any + Send + Sync),* > Dependancy for ($($t,)*) { type ElementBorrowed<'a> = ($(&'a $t,)*) where Self: 'a; #[allow(unused_variables, clippy::unused_unit, non_snake_case)] @@ -423,7 +423,7 @@ pub fn resolve_passes( // } // impl AnyMapLike for Number { -// fn get(&self) -> Option<&T> { +// fn get(&self) -> Option<&T> { // if TypeId::of::() == TypeId::of::() { // Some(unsafe { &*(self as *const Self as *const T) }) // } else { @@ -431,7 +431,7 @@ pub fn resolve_passes( // } // } -// fn get_mut(&mut self) -> Option<&mut T> { +// fn get_mut(&mut self) -> Option<&mut T> { // if TypeId::of::() == TypeId::of::() { // Some(unsafe { &mut *(self as *mut Self as *mut T) }) // } else { @@ -530,7 +530,7 @@ pub fn resolve_passes( // } // impl AnyMapLike for NumberState { -// fn get(&self) -> Option<&T> { +// fn get(&self) -> Option<&T> { // if TypeId::of::() == TypeId::of::() { // Some(unsafe { &*(&self.add_number as *const AddNumber as *const T) }) // } else if TypeId::of::() == TypeId::of::() { @@ -540,7 +540,7 @@ pub fn resolve_passes( // } // } -// fn get_mut(&mut self) -> Option<&mut T> { +// fn get_mut(&mut self) -> Option<&mut T> { // if TypeId::of::() == TypeId::of::() { // Some(unsafe { &mut *(&mut self.add_number as *mut AddNumber as *mut T) }) // } else if TypeId::of::() == TypeId::of::() { @@ -636,7 +636,7 @@ pub fn resolve_passes( // } // impl AnyMapLike for NumberState { -// fn get(&self) -> Option<&T> { +// fn get(&self) -> Option<&T> { // if TypeId::of::() == TypeId::of::() { // Some(unsafe { &*(&self.add_number as *const AddNumber as *const T) }) // } else if TypeId::of::() == TypeId::of::() { @@ -646,7 +646,7 @@ pub fn resolve_passes( // } // } -// fn get_mut(&mut self) -> Option<&mut T> { +// fn get_mut(&mut self) -> Option<&mut T> { // if TypeId::of::() == TypeId::of::() { // Some(unsafe { &mut *(&mut self.add_number as *mut AddNumber as *mut T) }) // } else if TypeId::of::() == TypeId::of::() { @@ -718,7 +718,7 @@ pub fn resolve_passes( // } // impl AnyMapLike for NumberState { -// fn get(&self) -> Option<&T> { +// fn get(&self) -> Option<&T> { // if TypeId::of::() == TypeId::of::() { // Some(unsafe { &*(&self.add_number as *const AddNumber as *const T) }) // } else { @@ -726,7 +726,7 @@ pub fn resolve_passes( // } // } -// fn get_mut(&mut self) -> Option<&mut T> { +// fn get_mut(&mut self) -> Option<&mut T> { // if TypeId::of::() == TypeId::of::() { // Some(unsafe { &mut *(&mut self.add_number as *mut AddNumber as *mut T) }) // } else { diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 9a874ea99..5a7b34d46 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -37,6 +37,7 @@ pub struct RealDom { impl RealDom { pub fn new(mut passes: Box<[TypeErasedPass]>) -> RealDom { let mut tree = Tree::new(); + tree.insert_slab::>(); let root_id = tree.root(); let root: &mut NodeData = tree.get_mut(root_id).unwrap(); let mut root_node: NodeData = NodeData::new(NodeType::Element(ElementNode { @@ -579,7 +580,7 @@ pub trait NodeImmutable: Sized { &self.node_data().node_type } - fn get(&self) -> Option<&T> { + fn get(&self) -> Option<&T> { self.real_dom().tree.get(self.id()) } @@ -611,7 +612,7 @@ pub trait NodeImmutable: Sized { } pub trait NodeMutable: Sized + NodeImmutable { - fn get_mut(&mut self) -> Option<&mut T>; + fn get_mut(&mut self) -> Option<&mut T>; } #[derive(Clone, Copy)] @@ -647,7 +648,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeImmutable for NodeMut<'a, V> { } impl<'a, V: FromAnyValue + Send + Sync> NodeMutable for NodeMut<'a, V> { - fn get_mut(&mut self) -> Option<&mut T> { + fn get_mut(&mut self) -> Option<&mut T> { todo!("get_mut with mark as dirty") } } @@ -781,7 +782,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeImmutable for NodeMutRaw<'a, V> { } impl<'a, V: FromAnyValue + Send + Sync> NodeMutable for NodeMutRaw<'a, V> { - fn get_mut(&mut self) -> Option<&mut T> { + fn get_mut(&mut self) -> Option<&mut T> { self.dom.tree.get_mut::(self.id) } } diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index 9c6f73457..0e80aa04c 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -36,6 +36,10 @@ impl Tree { Self { nodes, root } } + pub(crate) fn insert_slab(&mut self) { + self.nodes.get_or_insert_slab_mut::(); + } + fn node_slab(&self) -> &SlabStorage { self.nodes.read_slab() } @@ -164,11 +168,11 @@ impl Tree { } } - pub fn get(&self, id: NodeId) -> Option<&T> { + pub fn get(&self, id: NodeId) -> Option<&T> { self.nodes.read_slab().get(id) } - pub fn get_mut(&mut self, id: NodeId) -> Option<&mut T> { + pub fn get_mut(&mut self, id: NodeId) -> Option<&mut T> { self.nodes.write_slab().get_mut(id) } @@ -234,7 +238,7 @@ pub struct TreeStateViewEntry<'a, 'b> { } impl<'a, 'b> AnyMapLike<'a> for TreeStateViewEntry<'a, 'b> { - fn get(self) -> Option<&'a T> { + fn get(self) -> Option<&'a T> { self.view.get_slab().and_then(|slab| slab.get(self.id)) } } @@ -245,7 +249,7 @@ pub struct TreeStateView<'a, 'b> { } impl<'a, 'b> TreeStateView<'a, 'b> { - fn get_slab(&self) -> Option<&SlabStorage> { + fn get_slab(&self) -> Option<&SlabStorage> { self.nodes .get(&TypeId::of::()) .and_then(|gaurd| match gaurd { @@ -254,7 +258,7 @@ impl<'a, 'b> TreeStateView<'a, 'b> { }) } - fn get_slab_mut(&mut self) -> Option<&mut SlabStorage> { + fn get_slab_mut(&mut self) -> Option<&mut SlabStorage> { self.nodes .get_mut(&TypeId::of::()) .and_then(|gaurd| match gaurd { @@ -279,11 +283,11 @@ impl<'a, 'b> TreeStateView<'a, 'b> { T::borrow_elements_from(self.entry(id)) } - pub fn get_single(&self, id: NodeId) -> Option<&T> { + pub fn get_single(&self, id: NodeId) -> Option<&T> { self.get_slab().and_then(|slab| slab.get(id)) } - pub fn get_mut(&mut self, id: NodeId) -> Option<&mut T> { + pub fn get_mut(&mut self, id: NodeId) -> Option<&mut T> { self.get_slab_mut().and_then(|slab| slab.get_mut(id)) } @@ -543,23 +547,23 @@ impl AnySlab { } } - fn try_read_slab(&self) -> Option<&SlabStorage> { + fn try_read_slab(&self) -> Option<&SlabStorage> { self.data .get(&TypeId::of::()) .map(|x| x.as_any().downcast_ref().unwrap()) } - fn read_slab(&self) -> &SlabStorage { + fn read_slab(&self) -> &SlabStorage { self.try_read_slab().unwrap() } - fn try_write_slab(&mut self) -> Option<&mut SlabStorage> { + fn try_write_slab(&mut self) -> Option<&mut SlabStorage> { self.data .get_mut(&TypeId::of::()) .map(|x| x.as_any_mut().downcast_mut().unwrap()) } - fn write_slab(&mut self) -> &mut SlabStorage { + fn write_slab(&mut self) -> &mut SlabStorage { self.try_write_slab().unwrap() } @@ -624,11 +628,11 @@ impl EntryBuilder<'_> { .insert(self.id, value); } - pub fn get(&self) -> Option<&T> { + pub fn get(&self) -> Option<&T> { self.inner.read_slab().get(self.id) } - pub fn get_mut(&mut self) -> Option<&mut T> { + pub fn get_mut(&mut self) -> Option<&mut T> { self.inner.write_slab().get_mut(self.id) } From 0058c7ed321373e460771d275244c4cebccfbb09 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 29 Jan 2023 20:28:29 -0600 Subject: [PATCH 024/103] create a way to create states --- packages/native-core/src/passes.rs | 64 ++++++++++++---- packages/native-core/src/real_dom.rs | 108 +++++++++++++-------------- packages/native-core/src/tree.rs | 43 ++++++++++- packages/tui/src/focus.rs | 12 +++ packages/tui/src/layout.rs | 12 +++ packages/tui/src/prevent_default.rs | 12 +++ packages/tui/src/style_attributes.rs | 12 +++ 7 files changed, 186 insertions(+), 77 deletions(-) diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index 525e22461..269c21fc7 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -9,7 +9,7 @@ use std::sync::Arc; use crate::node::{FromAnyValue, NodeData}; use crate::node_ref::{NodeMaskBuilder, NodeView}; use crate::real_dom::RealDom; -use crate::tree::TreeStateView; +use crate::tree::{SlabEntry, Tree, TreeStateView}; use crate::{FxDashMap, FxDashSet, SendAnyMap}; use crate::{NodeId, NodeMask}; @@ -76,12 +76,16 @@ impl DirtyNodeStates { let (&height, values) = dirty_read .iter() .find(|(_, values)| values.contains_key(&pass_id))?; - let mut dirty = values.get_mut(&pass_id)?; - let node_id = dirty.pop()?; - if dirty.is_empty() { - values.remove(&pass_id); - } + let node_id = { + let mut dirty = values.get_mut(&pass_id)?; + let node_id = dirty.pop()?; + if dirty.is_empty() { + values.remove(&pass_id); + } + node_id + }; if values.is_empty() { + drop(dirty_read); let mut dirty_write = self.dirty.write(); dirty_write.remove(&height); } @@ -127,6 +131,14 @@ pub trait Pass: Any + Send + Sync { context: &SendAnyMap, ) -> bool; + fn create<'a>( + node_view: NodeView, + node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + children: Option::ElementBorrowed<'a>>>, + context: &SendAnyMap, + ) -> Self; + fn validate() { // no type can be a child and parent dependency for type_id in Self::parent_type_ids().iter().copied() { @@ -172,24 +184,38 @@ pub trait Pass: Any + Send + Sync { debug_assert!(!Self::NodeDependencies::type_ids() .iter() .any(|id| *id == TypeId::of::())); + println!("passing: {node_id:?}"); // get all of the states from the tree view // Safety: No node has itself as a parent or child. - let node_raw = tree.get_mut::(node_id).unwrap() as *mut Self; + let myself: SlabEntry<'static, Self> = unsafe { + std::mem::transmute(tree.get_slab_mut::().unwrap().entry(node_id)) + }; let node_data = tree.get_single::>(node_id).unwrap(); let node = tree.get::(node_id).unwrap(); let children = tree.children::(node_id); let parent = tree.parent::(node_id); - let myself = unsafe { &mut *node_raw }; - myself.pass( - NodeView::new(node_data, Self::NODE_MASK.build()), - node, - parent, - children, - context, - ) + if myself.value.is_none() { + *myself.value = Some(Self::create( + NodeView::new(node_data, Self::NODE_MASK.build()), + node, + parent, + children, + context, + )); + true + } else { + myself.value.as_mut().unwrap().pass( + NodeView::new(node_data, Self::NODE_MASK.build()), + node, + parent, + children, + context, + ) + } }, ) as PassCallback, + create: Box::new(|tree: &mut Tree| tree.insert_slab::()) as CreatePassCallback, phantom: PhantomData, } } @@ -238,6 +264,7 @@ pub struct TypeErasedPass { pub(crate) dependants: FxHashSet, pub(crate) mask: NodeMask, pass: PassCallback, + pub(crate) create: CreatePassCallback, pub(crate) pass_direction: PassDirection, phantom: PhantomData, } @@ -297,6 +324,7 @@ pub enum PassDirection { } type PassCallback = Box bool + Send + Sync>; +type CreatePassCallback = Box; pub trait AnyMapLike<'a> { fn get(self) -> Option<&'a T>; @@ -388,7 +416,11 @@ pub fn resolve_passes( resolving.push(pass_id); currently_in_use.insert(pass.this_type_id); let tree_view = dynamically_borrowed_tree.view( - pass.combined_dependancy_type_ids.iter().copied(), + pass.combined_dependancy_type_ids + .iter() + .filter(|id| **id != pass.this_type_id) + .copied() + .chain(std::iter::once(TypeId::of::>())), [pass.this_type_id], ); let dirty_nodes = dirty_nodes.clone(); diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 5a7b34d46..fd3a0cac2 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -1,7 +1,7 @@ use dioxus_core::{BorrowedAttributeValue, ElementId, Mutations, TemplateNode}; use rustc_hash::{FxHashMap, FxHashSet}; -use std::any::Any; +use std::any::{Any, TypeId}; use std::collections::VecDeque; use crate::node::{ @@ -26,11 +26,8 @@ pub struct RealDom { stack: Vec, templates: FxHashMap>, pub(crate) passes: Box<[TypeErasedPass]>, - pub(crate) nodes_updated: FxHashMap, - passes_updated: DirtyNodeStates, - parent_changed_nodes: FxHashSet, - child_changed_nodes: FxHashSet, - nodes_created: FxHashSet, + passes_updated: FxHashMap>, + nodes_updated: FxHashMap, phantom: std::marker::PhantomData, } @@ -38,8 +35,10 @@ impl RealDom { pub fn new(mut passes: Box<[TypeErasedPass]>) -> RealDom { let mut tree = Tree::new(); tree.insert_slab::>(); + for pass in passes.iter() { + (pass.create)(&mut tree); + } let root_id = tree.root(); - let root: &mut NodeData = tree.get_mut(root_id).unwrap(); let mut root_node: NodeData = NodeData::new(NodeType::Element(ElementNode { tag: "Root".to_string(), namespace: Some("Root".to_string()), @@ -48,7 +47,7 @@ impl RealDom { })); root_node.element_id = Some(ElementId(0)); root_node.node_id = root_id; - *root = root_node; + tree.insert(root_id, root_node); // resolve dependants for each pass for i in 1..passes.len() { @@ -63,8 +62,11 @@ impl RealDom { } } + let mut passes_updated = FxHashMap::default(); let mut nodes_updated = FxHashMap::default(); + let root_id = NodeId(0); + passes_updated.insert(root_id, passes.iter().map(|x| x.this_type_id).collect()); nodes_updated.insert(root_id, NodeMaskBuilder::ALL.build()); RealDom { @@ -74,21 +76,42 @@ impl RealDom { stack: vec![root_id], templates: FxHashMap::default(), passes, + passes_updated, nodes_updated, - passes_updated: DirtyNodeStates::default(), - parent_changed_nodes: FxHashSet::default(), - child_changed_nodes: FxHashSet::default(), - nodes_created: FxHashSet::default(), phantom: std::marker::PhantomData, } } + fn mark_dirty(&mut self, node_id: NodeId, mask: NodeMask) { + self.passes_updated.entry(node_id).or_default().extend( + self.passes + .iter() + .filter_map(|x| x.mask.overlaps(&mask).then_some(x.this_type_id)), + ); + let nodes_updated = &mut self.nodes_updated; + if let Some(node) = nodes_updated.get_mut(&node_id) { + *node = node.union(&mask); + } else { + nodes_updated.insert(node_id, mask); + } + } + fn mark_parent_added_or_removed(&mut self, node_id: NodeId) { - self.parent_changed_nodes.insert(node_id); + let hm = self.passes_updated.entry(node_id).or_default(); + for pass in &*self.passes { + if pass.parent_dependant { + hm.insert(pass.this_type_id); + } + } } fn mark_child_changed(&mut self, node_id: NodeId) { - self.child_changed_nodes.insert(node_id); + let hm = self.passes_updated.entry(node_id).or_default(); + for pass in &*self.passes { + if pass.child_dependant { + hm.insert(pass.this_type_id); + } + } } pub fn element_to_node_id(&self, element_id: ElementId) -> NodeId { @@ -121,12 +144,15 @@ impl RealDom { ) -> EntryBuilder<'_> { let mut node_entry = self.tree.create_node(); let node_id = node_entry.id(); + if mark_dirty { + self.passes_updated + .entry(node_id) + .or_default() + .extend(self.passes.iter().map(|x| x.this_type_id)); + } node.node_id = node_id; node.element_id = id; node_entry.insert(node); - if mark_dirty { - self.nodes_created.insert(node_id); - } node_entry } @@ -414,43 +440,14 @@ impl RealDom { &mut self, ctx: SendAnyMap, ) -> (FxDashSet, FxHashMap) { - let dirty_nodes = std::mem::take(&mut self.passes_updated); + let passes = std::mem::take(&mut self.passes_updated); let nodes_updated = std::mem::take(&mut self.nodes_updated); - for (&node, mask) in &nodes_updated { + let dirty_nodes = DirtyNodeStates::default(); + for (node_id, passes) in passes { // remove any nodes that were created and then removed in the same mutations from the dirty nodes list - if let Some(height) = self.tree.height(node) { - for pass in &*self.passes { - if mask.overlaps(&pass.mask) { - dirty_nodes.insert(pass.this_type_id, node, height); - } - } - } - } - for node in std::mem::take(&mut self.child_changed_nodes) { - // remove any nodes that were created and then removed in the same mutations from the dirty nodes list - if let Some(height) = self.tree.height(node) { - for pass in &*self.passes { - if pass.child_dependant { - dirty_nodes.insert(pass.this_type_id, node, height); - } - } - } - } - for node in std::mem::take(&mut self.parent_changed_nodes) { - // remove any nodes that were created and then removed in the same mutations from the dirty nodes list - if let Some(height) = self.tree.height(node) { - for pass in &*self.passes { - if pass.parent_dependant { - dirty_nodes.insert(pass.this_type_id, node, height); - } - } - } - } - for node in std::mem::take(&mut self.nodes_created) { - // remove any nodes that were created and then removed in the same mutations from the dirty nodes list - if let Some(height) = self.tree.height(node) { - for pass in &*self.passes { - dirty_nodes.insert(pass.this_type_id, node, height); + if let Some(height) = self.tree.height(node_id) { + for pass in passes { + dirty_nodes.insert(pass, node_id, height); } } } @@ -681,12 +678,7 @@ impl Drop for NodeMut<'_, V> { fn drop(&mut self) { let node_id = self.node_data().node_id; let mask = std::mem::take(&mut self.dirty); - let nodes_updated = &mut self.dom.nodes_updated; - if let Some(node) = nodes_updated.get_mut(&node_id) { - *node = node.union(&mask); - } else { - nodes_updated.insert(node_id, mask); - } + self.dom.mark_dirty(node_id, mask); } } diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index 0e80aa04c..9cf59dfcc 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -176,6 +176,10 @@ impl Tree { self.nodes.write_slab().get_mut(id) } + pub fn insert(&mut self, id: NodeId, value: T) { + self.nodes.write_slab().insert(id, value); + } + pub fn contains(&self, id: NodeId) -> bool { self.nodes.contains(id) } @@ -210,9 +214,11 @@ impl<'a> DynamicallyBorrowedTree<'a> { let nodes_data = self.node_slab(); let mut nodes = FxHashMap::default(); for id in immutable { + println!("reading {id:?}"); nodes.insert(id, MaybeRead::Read(self.nodes.get_slab(id).unwrap())); } for id in mutable { + println!("writing {id:?}"); nodes.insert(id, MaybeRead::Write(self.nodes.get_slab_mut(id).unwrap())); } @@ -249,7 +255,7 @@ pub struct TreeStateView<'a, 'b> { } impl<'a, 'b> TreeStateView<'a, 'b> { - fn get_slab(&self) -> Option<&SlabStorage> { + pub fn get_slab(&self) -> Option<&SlabStorage> { self.nodes .get(&TypeId::of::()) .and_then(|gaurd| match gaurd { @@ -258,7 +264,7 @@ impl<'a, 'b> TreeStateView<'a, 'b> { }) } - fn get_slab_mut(&mut self) -> Option<&mut SlabStorage> { + pub fn get_slab_mut(&mut self) -> Option<&mut SlabStorage> { self.nodes .get_mut(&TypeId::of::()) .and_then(|gaurd| match gaurd { @@ -442,7 +448,7 @@ fn deletion() { } #[derive(Debug)] -pub(crate) struct SlabStorage { +pub struct SlabStorage { data: Vec>, } @@ -467,6 +473,16 @@ impl SlabStorage { self.data.get_mut(id.0).and_then(|x| x.as_mut()) } + pub fn entry(&mut self, id: NodeId) -> SlabEntry<'_, T> { + let idx = id.0; + if idx >= self.data.len() { + self.data.resize_with(idx + 1, || None); + } + SlabEntry { + value: &mut self.data[idx], + } + } + fn insert(&mut self, id: NodeId, value: T) { let idx = id.0; if idx >= self.data.len() { @@ -476,6 +492,27 @@ impl SlabStorage { } } +pub struct SlabEntry<'a, T> { + pub value: &'a mut Option, +} + +impl<'a, T> SlabEntry<'a, T> { + pub fn or_insert_with T>(self, f: F) -> &'a mut T { + self.value.get_or_insert_with(f) + } + + pub fn or_insert(self, value: T) -> &'a mut T { + self.value.get_or_insert(value) + } + + pub fn or_default(self) -> &'a mut T + where + T: Default, + { + self.value.get_or_insert_with(Default::default) + } +} + impl AnySlabStorageImpl for SlabStorage { fn remove(&mut self, id: NodeId) { self.data[id.0].take(); diff --git a/packages/tui/src/focus.rs b/packages/tui/src/focus.rs index 430964637..c263bc996 100644 --- a/packages/tui/src/focus.rs +++ b/packages/tui/src/focus.rs @@ -121,6 +121,18 @@ impl Pass for Focus { false } } + + fn create<'a>( + node_view: NodeView<()>, + node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + children: Option::ElementBorrowed<'a>>>, + context: &SendAnyMap, + ) -> Self { + let mut myself = Self::default(); + myself.pass(node_view, node, parent, children, context); + myself + } } const FOCUS_EVENTS: &[&str] = &["keydown", "keypress", "keyup"]; diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index 5115de1e9..53287cc8b 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -192,6 +192,18 @@ impl Pass for TaffyLayout { } changed } + + fn create<'a>( + node_view: NodeView<()>, + node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + children: Option::ElementBorrowed<'a>>>, + context: &SendAnyMap, + ) -> Self { + let mut myself = Self::default(); + myself.pass(node_view, node, parent, children, context); + myself + } } // these are the attributes in layout_attiributes in native-core diff --git a/packages/tui/src/prevent_default.rs b/packages/tui/src/prevent_default.rs index 7755e610c..e527b099b 100644 --- a/packages/tui/src/prevent_default.rs +++ b/packages/tui/src/prevent_default.rs @@ -73,4 +73,16 @@ impl Pass for PreventDefault { true } } + + fn create<'a>( + node_view: NodeView<()>, + node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + children: Option::ElementBorrowed<'a>>>, + context: &SendAnyMap, + ) -> Self { + let mut myself = Self::default(); + myself.pass(node_view, node, parent, children, context); + myself + } } diff --git a/packages/tui/src/style_attributes.rs b/packages/tui/src/style_attributes.rs index ada2ace9a..27c262ff1 100644 --- a/packages/tui/src/style_attributes.rs +++ b/packages/tui/src/style_attributes.rs @@ -112,6 +112,18 @@ impl Pass for StyleModifier { false } } + + fn create<'a>( + node_view: NodeView<()>, + node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + children: Option::ElementBorrowed<'a>>>, + context: &SendAnyMap, + ) -> Self { + let mut myself = Self::default(); + myself.pass(node_view, node, parent, children, context); + myself + } } #[derive(Default, Clone, PartialEq, Debug)] From 5eb4ca677d5c8e803857c957a60536ec759ad42d Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 1 Feb 2023 13:59:08 -0600 Subject: [PATCH 025/103] don't panic when trying to get information about a node that does not exist --- packages/native-core/src/tree.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index 9cf59dfcc..2ea71c3eb 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -48,6 +48,10 @@ impl Tree { self.node_slab().get(id).unwrap() } + pub fn try_get_node_data(&self, id: NodeId) -> Option<&Node> { + self.node_slab().get(id) + } + fn node_slab_mut(&mut self) -> &mut SlabStorage { self.nodes.write_slab() } @@ -189,15 +193,15 @@ impl Tree { } pub fn parent_id(&self, id: NodeId) -> Option { - self.get_node_data(id).parent + self.try_get_node_data(id).and_then(|node| node.parent) } pub fn children_ids(&self, id: NodeId) -> Option<&[NodeId]> { - Some(&self.get_node_data(id).children) + self.try_get_node_data(id).map(|node| &*node.children) } pub fn height(&self, id: NodeId) -> Option { - Some(self.get_node_data(id).height) + self.try_get_node_data(id).map(|node| node.height) } } @@ -515,7 +519,9 @@ impl<'a, T> SlabEntry<'a, T> { impl AnySlabStorageImpl for SlabStorage { fn remove(&mut self, id: NodeId) { - self.data[id.0].take(); + if let Some(entry) = self.data.get_mut(id.0) { + let _ = entry.take(); + } } fn as_any(&self) -> &dyn Any { From 39a3db68bdb48b5d2e83569823b8b652b3b18537 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 1 Feb 2023 14:15:17 -0600 Subject: [PATCH 026/103] fix deadlock --- packages/native-core/src/passes.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index 269c21fc7..d8961068f 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -1,4 +1,5 @@ use anymap::AnyMap; +use core::panic; use parking_lot::RwLock; use rustc_hash::FxHashSet; use std::any::{Any, TypeId}; @@ -80,6 +81,7 @@ impl DirtyNodeStates { let mut dirty = values.get_mut(&pass_id)?; let node_id = dirty.pop()?; if dirty.is_empty() { + drop(dirty); values.remove(&pass_id); } node_id @@ -99,12 +101,17 @@ impl DirtyNodeStates { .iter() .rev() .find(|(_, values)| values.contains_key(&pass_id))?; - let mut dirty = values.get_mut(&pass_id)?; - let node_id = dirty.pop()?; - if dirty.is_empty() { - values.remove(&pass_id); - } + let node_id = { + let mut dirty = values.get_mut(&pass_id)?; + let node_id = dirty.pop()?; + if dirty.is_empty() { + drop(dirty); + values.remove(&pass_id); + } + node_id + }; if values.is_empty() { + drop(dirty_read); let mut dirty_write = self.dirty.write(); dirty_write.remove(&height); } From 71a0bdf71d3c67a39a636ec998f01b5e19578b1d Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 1 Feb 2023 14:36:56 -0600 Subject: [PATCH 027/103] TUI rendering mostly working --- packages/native-core/src/node_ref.rs | 30 ---------------------------- packages/native-core/src/passes.rs | 1 - packages/native-core/src/real_dom.rs | 9 +++++++++ packages/native-core/src/tree.rs | 10 +++++----- packages/tui/src/focus.rs | 5 ++--- packages/tui/src/hooks.rs | 3 ++- packages/tui/src/layout.rs | 3 --- packages/tui/src/render.rs | 3 ++- 8 files changed, 20 insertions(+), 44 deletions(-) diff --git a/packages/native-core/src/node_ref.rs b/packages/native-core/src/node_ref.rs index beb8519dc..12ac47f2b 100644 --- a/packages/native-core/src/node_ref.rs +++ b/packages/native-core/src/node_ref.rs @@ -160,36 +160,6 @@ pub struct NodeMask { } impl NodeMask { - pub fn from_node(node: &NodeType) -> Self { - match node { - NodeType::Text { .. } => NodeMaskBuilder::new().with_text().build(), - NodeType::Element(ElementNode { - tag: _, - namespace: _, - attributes, - listeners, - }) => Self { - attritutes: AttributeMask::Some( - attributes - .keys() - .map(|key| key.name.as_str().into()) - .collect(), - ), - namespace: true, - tag: true, - text: false, - listeners: !listeners.is_empty(), - }, - NodeType::Placeholder => Self { - attritutes: AttributeMask::default(), - tag: true, - namespace: true, - text: false, - listeners: false, - }, - } - } - /// Check if two masks overlap pub fn overlaps(&self, other: &Self) -> bool { (self.tag && other.tag) diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index d8961068f..2ca7eab47 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -191,7 +191,6 @@ pub trait Pass: Any + Send + Sync { debug_assert!(!Self::NodeDependencies::type_ids() .iter() .any(|id| *id == TypeId::of::())); - println!("passing: {node_id:?}"); // get all of the states from the tree view // Safety: No node has itself as a parent or child. let myself: SlabEntry<'static, Self> = unsafe { diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index fd3a0cac2..71dbf668e 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -610,6 +610,7 @@ pub trait NodeImmutable: Sized { pub trait NodeMutable: Sized + NodeImmutable { fn get_mut(&mut self) -> Option<&mut T>; + fn insert(&mut self, value: T); } #[derive(Clone, Copy)] @@ -648,6 +649,10 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMutable for NodeMut<'a, V> { fn get_mut(&mut self) -> Option<&mut T> { todo!("get_mut with mark as dirty") } + + fn insert(&mut self, _: T) { + todo!("insert with mark as dirty") + } } impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { @@ -777,4 +782,8 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMutable for NodeMutRaw<'a, V> { fn get_mut(&mut self) -> Option<&mut T> { self.dom.tree.get_mut::(self.id) } + + fn insert(&mut self, value: T) { + self.dom.tree.insert(self.id, value); + } } diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index 2ea71c3eb..ee2b03690 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -173,15 +173,17 @@ impl Tree { } pub fn get(&self, id: NodeId) -> Option<&T> { - self.nodes.read_slab().get(id) + self.nodes.try_read_slab().and_then(|slab| slab.get(id)) } pub fn get_mut(&mut self, id: NodeId) -> Option<&mut T> { - self.nodes.write_slab().get_mut(id) + self.nodes + .try_write_slab() + .and_then(|slab| slab.get_mut(id)) } pub fn insert(&mut self, id: NodeId, value: T) { - self.nodes.write_slab().insert(id, value); + self.nodes.get_or_insert_slab_mut().insert(id, value); } pub fn contains(&self, id: NodeId) -> bool { @@ -218,11 +220,9 @@ impl<'a> DynamicallyBorrowedTree<'a> { let nodes_data = self.node_slab(); let mut nodes = FxHashMap::default(); for id in immutable { - println!("reading {id:?}"); nodes.insert(id, MaybeRead::Read(self.nodes.get_slab(id).unwrap())); } for id in mutable { - println!("writing {id:?}"); nodes.insert(id, MaybeRead::Write(self.nodes.get_slab_mut(id).unwrap())); } diff --git a/packages/tui/src/focus.rs b/packages/tui/src/focus.rs index c263bc996..05ae2019c 100644 --- a/packages/tui/src/focus.rs +++ b/packages/tui/src/focus.rs @@ -246,8 +246,7 @@ impl FocusState { if !node.get::().unwrap().level.focusable() { panic!() } - let focused = node.get_mut::().unwrap(); - focused.0 = true; + node.insert(Focused(true)); if let Some(old) = self.last_focused_id.replace(id) { let mut old = rdom.get_mut_raw(old).unwrap(); let focused = old.get_mut::().unwrap(); @@ -304,7 +303,7 @@ impl FocusState { node.get_mut::().unwrap().0 = false; } let mut node = rdom.get_mut_raw(id).unwrap(); - node.get_mut::().unwrap().0 = true; + node.insert(Focused(true)); self.focus_level = node.get::().unwrap().level; // reset the position to the currently focused element while self.focus_iter.next(rdom).id() != id {} diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 6f043d522..feec8686c 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -688,7 +688,8 @@ impl RinkInputHandler { for (event, datas) in hm { for node in dom.get_listening_sorted(event) { for data in &datas { - if node.get::().unwrap().0 { + let focused = node.get::(); + if focused.is_some() && focused.unwrap().0 { if let Some(id) = node.mounted_id() { resolved_events.push(Event { name: event, diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index 53287cc8b..6e34ba0c9 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -91,9 +91,6 @@ impl Pass for TaffyLayout { attribute, value, .. } in attributes { - assert!(SORTED_LAYOUT_ATTRS - .binary_search(&attribute.name.as_ref()) - .is_ok()); if let Some(text) = value.as_text() { apply_layout_attributes_cfg( &attribute.name, diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index 17355b5b5..fe6600ade 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -272,7 +272,8 @@ impl RinkWidget for NodeRef<'_> { if let Some(c) = self.get::().unwrap().core.bg { new_cell.bg = c; } - if self.get::().unwrap().0 { + let focused = self.get::(); + if focused.is_some() && focused.unwrap().0 { new_cell.bg.alpha = 100; new_cell.bg.color = new_cell.bg.blend(Color::White); } From d67b21c0e9439f860ed42bba0737b1c429ded5d3 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 1 Feb 2023 20:45:11 -0600 Subject: [PATCH 028/103] improve performance --- packages/native-core/src/node_ref.rs | 4 +- packages/native-core/src/passes.rs | 98 ++++++++++++---------------- packages/native-core/src/real_dom.rs | 2 +- packages/native-core/src/tree.rs | 4 +- 4 files changed, 45 insertions(+), 63 deletions(-) diff --git a/packages/native-core/src/node_ref.rs b/packages/native-core/src/node_ref.rs index 12ac47f2b..f0e094cdd 100644 --- a/packages/native-core/src/node_ref.rs +++ b/packages/native-core/src/node_ref.rs @@ -10,12 +10,12 @@ use crate::{ #[derive(Debug)] pub struct NodeView<'a, V: FromAnyValue = ()> { inner: &'a NodeData, - mask: NodeMask, + mask: &'a NodeMask, } impl<'a, V: FromAnyValue> NodeView<'a, V> { /// Create a new NodeView from a VNode, and mask. - pub fn new(node: &'a NodeData, view: NodeMask) -> Self { + pub fn new(node: &'a NodeData, view: &'a NodeMask) -> Self { Self { inner: node, mask: view, diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index 2ca7eab47..c5de9f965 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -1,7 +1,7 @@ use anymap::AnyMap; use core::panic; use parking_lot::RwLock; -use rustc_hash::FxHashSet; +use rustc_hash::{FxHashMap, FxHashSet}; use std::any::{Any, TypeId}; use std::collections::BTreeMap; use std::marker::PhantomData; @@ -11,7 +11,7 @@ use crate::node::{FromAnyValue, NodeData}; use crate::node_ref::{NodeMaskBuilder, NodeView}; use crate::real_dom::RealDom; use crate::tree::{SlabEntry, Tree, TreeStateView}; -use crate::{FxDashMap, FxDashSet, SendAnyMap}; +use crate::{FxDashSet, SendAnyMap}; use crate::{NodeId, NodeMask}; #[derive(Default)] @@ -47,76 +47,58 @@ impl DirtyNodes { } } -#[derive(Default, Clone)] +#[derive(Clone)] pub struct DirtyNodeStates { - dirty: Arc>>>, + dirty: Arc>>>, } impl DirtyNodeStates { + pub fn with_passes(passes: impl Iterator) -> Self { + let mut dirty = FxHashMap::default(); + for pass in passes { + dirty.insert(pass, RwLock::new(BTreeMap::new())); + } + Self { + dirty: Arc::new(dirty), + } + } + pub fn insert(&self, pass_id: TypeId, node_id: NodeId, height: u16) { - let mut dirty = self.dirty.write(); - if let Some(dirty) = dirty.get_mut(&height) { - if let Some(mut entry) = dirty.get_mut(&pass_id) { - entry.add_node(node_id); - } else { - let mut entry = DirtyNodes::default(); - entry.add_node(node_id); - dirty.insert(pass_id, entry); - } + let btree = self.dirty.get(&pass_id).unwrap(); + let mut write = btree.write(); + if let Some(entry) = write.get_mut(&height) { + entry.add_node(node_id); } else { let mut entry = DirtyNodes::default(); entry.add_node(node_id); - let hm = FxDashMap::default(); - hm.insert(pass_id, entry); - dirty.insert(height, hm); + write.insert(height, entry); } } fn pop_front(&self, pass_id: TypeId) -> Option<(u16, NodeId)> { - let dirty_read = self.dirty.read(); - let (&height, values) = dirty_read - .iter() - .find(|(_, values)| values.contains_key(&pass_id))?; - let node_id = { - let mut dirty = values.get_mut(&pass_id)?; - let node_id = dirty.pop()?; - if dirty.is_empty() { - drop(dirty); - values.remove(&pass_id); - } - node_id - }; - if values.is_empty() { - drop(dirty_read); - let mut dirty_write = self.dirty.write(); - dirty_write.remove(&height); + let mut values = self.dirty.get(&pass_id)?.write(); + let mut value = values.first_entry()?; + let height = *value.key(); + let ids = value.get_mut(); + let id = ids.pop()?; + if ids.is_empty() { + value.remove_entry(); } - Some((height, node_id)) + Some((height, id)) } fn pop_back(&self, pass_id: TypeId) -> Option<(u16, NodeId)> { - let dirty_read = self.dirty.read(); - let (&height, values) = dirty_read - .iter() - .rev() - .find(|(_, values)| values.contains_key(&pass_id))?; - let node_id = { - let mut dirty = values.get_mut(&pass_id)?; - let node_id = dirty.pop()?; - if dirty.is_empty() { - drop(dirty); - values.remove(&pass_id); - } - node_id - }; - if values.is_empty() { - drop(dirty_read); - let mut dirty_write = self.dirty.write(); - dirty_write.remove(&height); + let mut values = self.dirty.get(&pass_id)?.write(); + let mut value = values.last_entry()?; + let height = *value.key(); + let ids = value.get_mut(); + let id = ids.pop()?; + if ids.is_empty() { + value.remove_entry(); } - Some((height, node_id)) + Some((height, id)) } } @@ -184,10 +166,10 @@ pub trait Pass: Any + Send + Sync { parent_dependant: !Self::parent_type_ids().is_empty(), child_dependant: !Self::child_type_ids().is_empty(), dependants: FxHashSet::default(), - mask: node_mask, + mask: node_mask.clone(), pass_direction: Self::pass_direction(), pass: Box::new( - |node_id: NodeId, tree: &mut TreeStateView, context: &SendAnyMap| { + move |node_id: NodeId, tree: &mut TreeStateView, context: &SendAnyMap| { debug_assert!(!Self::NodeDependencies::type_ids() .iter() .any(|id| *id == TypeId::of::())); @@ -203,7 +185,7 @@ pub trait Pass: Any + Send + Sync { if myself.value.is_none() { *myself.value = Some(Self::create( - NodeView::new(node_data, Self::NODE_MASK.build()), + NodeView::new(node_data, &node_mask), node, parent, children, @@ -212,7 +194,7 @@ pub trait Pass: Any + Send + Sync { true } else { myself.value.as_mut().unwrap().pass( - NodeView::new(node_data, Self::NODE_MASK.build()), + NodeView::new(node_data, &node_mask), node, parent, children, @@ -290,7 +272,7 @@ impl TypeErasedPass { nodes_updated.insert(id); for id in tree.children_ids(id).unwrap() { for dependant in &self.dependants { - dirty.insert(*dependant, id, height + 1); + dirty.insert(*dependant, *id, height + 1); } } } diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 71dbf668e..b06119ff6 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -442,7 +442,7 @@ impl RealDom { ) -> (FxDashSet, FxHashMap) { let passes = std::mem::take(&mut self.passes_updated); let nodes_updated = std::mem::take(&mut self.nodes_updated); - let dirty_nodes = DirtyNodeStates::default(); + let dirty_nodes = DirtyNodeStates::with_passes(self.passes.iter().map(|p| p.this_type_id)); for (node_id, passes) in passes { // remove any nodes that were created and then removed in the same mutations from the dirty nodes list if let Some(height) = self.tree.height(node_id) { diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index ee2b03690..6d24ba83e 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -277,8 +277,8 @@ impl<'a, 'b> TreeStateView<'a, 'b> { }) } - pub fn children_ids(&self, id: NodeId) -> Option> { - self.nodes_data.get(id).map(|node| node.children.clone()) + pub fn children_ids(&self, id: NodeId) -> Option<&[NodeId]> { + self.nodes_data.get(id).map(|node| &*node.children) } pub fn parent_id(&self, id: NodeId) -> Option { From 687cdf2cac8932910183b155904915f9d3aa6363 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 2 Feb 2023 07:08:29 -0600 Subject: [PATCH 029/103] use thread pool --- packages/native-core/Cargo.toml | 2 ++ packages/native-core/src/passes.rs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index e6b50b8a4..370e85d36 100644 --- a/packages/native-core/Cargo.toml +++ b/packages/native-core/Cargo.toml @@ -28,6 +28,8 @@ hashbrown = { version = "0.13.2", features = ["raw"] } # for parsing attributes lightningcss = "1.0.0-alpha.39" +rayon = "1.6.1" + [dev-dependencies] rand = "0.8.5" dioxus = { path = "../dioxus", version = "^0.3.0" } diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index c5de9f965..c6c212f5f 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -390,7 +390,7 @@ pub fn resolve_passes( while !pass_indexes_remaining.is_empty() { let mut currently_in_use = FxHashSet::::default(); let dynamically_borrowed_tree = tree.tree.dynamically_borrowed(); - std::thread::scope(|s| { + rayon::in_place_scope(|s| { let mut i = 0; while i < pass_indexes_remaining.len() { let passes_idx = pass_indexes_remaining[i]; @@ -414,7 +414,7 @@ pub fn resolve_passes( let dirty_nodes = dirty_nodes.clone(); let nodes_updated = nodes_updated.clone(); let ctx = ctx.clone(); - s.spawn(move || { + s.spawn(move |_| { pass.resolve(tree_view, &dirty_nodes, &nodes_updated, &ctx); }); } else { From 2f6b8393f8b1cf6e91bc1ec3cde14a0f6e36921b Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 2 Feb 2023 08:57:23 -0600 Subject: [PATCH 030/103] make parallelism optional --- packages/native-core/src/passes.rs | 9 +++++++-- packages/native-core/src/real_dom.rs | 6 +++++- packages/tui/src/lib.rs | 4 ++-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index c6c212f5f..b7c3de826 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -380,6 +380,7 @@ pub fn resolve_passes( tree: &mut RealDom, dirty_nodes: DirtyNodeStates, ctx: SendAnyMap, + parallel: bool, ) -> FxDashSet { let passes = &tree.passes; let mut resolved_passes: FxHashSet = FxHashSet::default(); @@ -414,9 +415,13 @@ pub fn resolve_passes( let dirty_nodes = dirty_nodes.clone(); let nodes_updated = nodes_updated.clone(); let ctx = ctx.clone(); - s.spawn(move |_| { + if parallel { + s.spawn(move |_| { + pass.resolve(tree_view, &dirty_nodes, &nodes_updated, &ctx); + }); + } else { pass.resolve(tree_view, &dirty_nodes, &nodes_updated, &ctx); - }); + } } else { i += 1; } diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index b06119ff6..49f0e03c9 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -439,6 +439,7 @@ impl RealDom { pub fn update_state( &mut self, ctx: SendAnyMap, + parallel: bool, ) -> (FxDashSet, FxHashMap) { let passes = std::mem::take(&mut self.passes_updated); let nodes_updated = std::mem::take(&mut self.nodes_updated); @@ -452,7 +453,10 @@ impl RealDom { } } - (resolve_passes(self, dirty_nodes, ctx), nodes_updated) + ( + resolve_passes(self, dirty_nodes, ctx, parallel), + nodes_updated, + ) } fn remove(&mut self, id: NodeId) { diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 562cd39ad..a26cfe547 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -126,7 +126,7 @@ pub fn launch_cfg_with_props(app: Component, props: Props rdom.apply_mutations(mutations); let mut any_map = SendAnyMap::new(); any_map.insert(taffy.clone()); - let _ = rdom.update_state(any_map); + let _ = rdom.update_state(any_map, false); } render_vdom( @@ -320,7 +320,7 @@ fn render_vdom( // update the style and layout let mut any_map = SendAnyMap::new(); any_map.insert(taffy.clone()); - let (new_to_rerender, dirty) = rdom.update_state(any_map); + let (new_to_rerender, dirty) = rdom.update_state(any_map, false); to_rerender = new_to_rerender; let text_mask = NodeMaskBuilder::new().with_text().build(); for (id, mask) in dirty { From 6cf0274a5b72043d7e3450f5d28b3fa8f52d1e3e Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 2 Feb 2023 14:26:47 -0600 Subject: [PATCH 031/103] expose listener methods --- packages/native-core/src/real_dom.rs | 54 ++++++++++++++++------------ 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 49f0e03c9..5db372e06 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -329,31 +329,11 @@ impl RealDom { } NewEventListener { name, id } => { let node_id = self.element_to_node_id(id); - let mut node = self.get_mut(node_id).unwrap(); - if let NodeTypeMut::Element(mut element) = node.node_type_mut() { - element.listeners_mut().insert(name.to_string()); - drop(node); - 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); - } - } - } + self.add_event_listener(node_id, name); } RemoveEventListener { id, name } => { let node_id = self.element_to_node_id(id); - { - let mut node = self.get_mut(node_id).unwrap(); - if let NodeTypeMut::Element(mut element) = node.node_type_mut() { - element.listeners_mut().remove(name); - } - } - self.nodes_listening.get_mut(name).unwrap().remove(&node_id); + self.remove_event_listener(node_id, name); } Remove { id } => { let node_id = self.element_to_node_id(id); @@ -369,7 +349,7 @@ impl RealDom { /// 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> { + pub fn get_listening_sorted(&self, event: &str) -> Vec> { if let Some(nodes) = self.nodes_listening.get(event) { let mut listening: Vec<_> = nodes .iter() @@ -490,6 +470,34 @@ impl RealDom { self.tree.insert_after(id, new); } + pub fn add_event_listener(&mut self, id: NodeId, event: &str) { + let mut node = self.get_mut(id).unwrap(); + if let NodeTypeMut::Element(mut element) = node.node_type_mut() { + element.listeners_mut().insert(event.to_string()); + drop(node); + match self.nodes_listening.get_mut(event) { + Some(hs) => { + hs.insert(id); + } + None => { + let mut hs = FxHashSet::default(); + hs.insert(id); + self.nodes_listening.insert(event.to_string(), hs); + } + } + } + } + + pub fn remove_event_listener(&mut self, id: NodeId, event: &str) { + { + let mut node = self.get_mut(id).unwrap(); + if let NodeTypeMut::Element(mut element) = node.node_type_mut() { + element.listeners_mut().remove(event); + } + } + self.nodes_listening.get_mut(event).unwrap().remove(&id); + } + pub fn traverse_depth_first(&self, mut f: impl FnMut(NodeRef)) { let mut stack = vec![self.root()]; while let Some(id) = stack.pop() { From c8eb5c7dd7195ff04504e5dee5f16a15c0b243ee Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 2 Feb 2023 14:26:59 -0600 Subject: [PATCH 032/103] update native core tests --- packages/native-core/tests/fuzzing.rs | 77 ++++--- packages/native-core/tests/miri_native.rs | 246 ++++++++++++---------- 2 files changed, 180 insertions(+), 143 deletions(-) diff --git a/packages/native-core/tests/fuzzing.rs b/packages/native-core/tests/fuzzing.rs index d35a98288..615371dcb 100644 --- a/packages/native-core/tests/fuzzing.rs +++ b/packages/native-core/tests/fuzzing.rs @@ -1,12 +1,10 @@ use dioxus::prelude::Props; use dioxus_core::*; use dioxus_native_core::{ - node_ref::{AttributeMask, NodeView}, + node_ref::{AttributeMask, AttributeMaskBuilder, NodeMaskBuilder, NodeView}, real_dom::RealDom, - state::{ParentDepState, State}, - NodeMask, SendAnyMap, + Dependancy, NodeMask, Pass, SendAnyMap, }; -use dioxus_native_core_macro::{sorted_str_slice, State}; use std::cell::Cell; fn random_ns() -> Option<&'static str> { @@ -295,25 +293,46 @@ fn create_random_element(cx: Scope) -> Element { } #[derive(Debug, Clone, PartialEq, Eq, Default)] -pub struct BlablaState {} - -/// Font style are inherited by default if not specified otherwise by some of the supported attributes. -impl ParentDepState for BlablaState { - type Ctx = (); - type DepState = (Self,); - - const NODE_MASK: NodeMask = - NodeMask::new_with_attrs(AttributeMask::Static(&sorted_str_slice!(["blabla",]))); - - fn reduce(&mut self, _node: NodeView, _parent: Option<(&Self,)>, _ctx: &Self::Ctx) -> bool { - false - } +pub struct BlablaState { + count: usize, } -#[derive(Clone, State, Default, Debug)] -pub struct NodeState { - #[parent_dep_state(blabla)] - blabla: BlablaState, +impl Pass for BlablaState { + type ParentDependencies = (Self,); + type ChildDependencies = (); + type NodeDependencies = (); + + const NODE_MASK: NodeMaskBuilder = NodeMaskBuilder::new() + .with_attrs(AttributeMaskBuilder::Some(&["blabla"])) + .with_element(); + + fn pass<'a>( + &mut self, + node_view: NodeView, + _: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + _: Option::ElementBorrowed<'a>>>, + _: &SendAnyMap, + ) -> bool { + if let Some((parent,)) = parent { + if parent.count != 0 { + self.count += 1; + } + } + true + } + + fn create<'a>( + node_view: NodeView<()>, + node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + children: Option::ElementBorrowed<'a>>>, + context: &SendAnyMap, + ) -> Self { + let mut myself = Self::default(); + myself.pass(node_view, node, parent, children, context); + myself + } } // test for panics when creating random nodes and templates @@ -328,11 +347,11 @@ fn create() { }, ); let mutations = vdom.rebuild(); - let mut rdom: RealDom = RealDom::new(); - let (to_update, _diff) = rdom.apply_mutations(mutations); + let mut rdom: RealDom = RealDom::new(Box::new([BlablaState::to_type_erased()])); + rdom.apply_mutations(mutations); let ctx = SendAnyMap::new(); - rdom.update_state(to_update, ctx); + rdom.update_state(ctx, false); } } @@ -349,17 +368,17 @@ fn diff() { }, ); let mutations = vdom.rebuild(); - let mut rdom: RealDom = RealDom::new(); - let (to_update, _diff) = rdom.apply_mutations(mutations); + let mut rdom: RealDom = RealDom::new(Box::new([BlablaState::to_type_erased()])); + rdom.apply_mutations(mutations); let ctx = SendAnyMap::new(); - rdom.update_state(to_update, ctx); + rdom.update_state(ctx, false); for _ in 0..10 { let mutations = vdom.render_immediate(); - let (to_update, _diff) = rdom.apply_mutations(mutations); + rdom.apply_mutations(mutations); let ctx = SendAnyMap::new(); - rdom.update_state(to_update, ctx); + rdom.update_state(ctx, false); } } } diff --git a/packages/native-core/tests/miri_native.rs b/packages/native-core/tests/miri_native.rs index 674f69aa7..9b5c995fa 100644 --- a/packages/native-core/tests/miri_native.rs +++ b/packages/native-core/tests/miri_native.rs @@ -1,140 +1,158 @@ -// use dioxus::prelude::*; -// use dioxus_native_core::{ -// node_ref::{AttributeMask, NodeView}, -// real_dom::RealDom, -// state::{ParentDepState, State}, -// NodeMask, SendAnyMap, -// }; -// use dioxus_native_core_macro::{sorted_str_slice, State}; -// use std::sync::{Arc, Mutex}; -// use tokio::time::sleep; +use dioxus::prelude::*; +use dioxus_core::*; +use dioxus_native_core::{ + node_ref::{AttributeMask, AttributeMaskBuilder, NodeMaskBuilder, NodeView}, + real_dom::RealDom, + Dependancy, NodeMask, Pass, SendAnyMap, +}; +use std::cell::Cell; +use std::sync::{Arc, Mutex}; +use tokio::time::sleep; -// #[derive(Debug, Clone, PartialEq, Eq, Default)] -// pub struct BlablaState {} +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct BlablaState { + count: usize, +} -// /// Font style are inherited by default if not specified otherwise by some of the supported attributes. -// impl ParentDepState for BlablaState { -// type Ctx = (); -// type DepState = (Self,); +impl Pass for BlablaState { + type ParentDependencies = (Self,); + type ChildDependencies = (); + type NodeDependencies = (); -// const NODE_MASK: NodeMask = -// NodeMask::new_with_attrs(AttributeMask::Static(&sorted_str_slice!(["blabla",]))); + const NODE_MASK: NodeMaskBuilder = NodeMaskBuilder::new() + .with_attrs(AttributeMaskBuilder::Some(&["blabla"])) + .with_element(); -// fn reduce<'a>( -// &mut self, -// _node: NodeView, -// _parent: Option<(&'a Self,)>, -// _ctx: &Self::Ctx, -// ) -> bool { -// false -// } -// } + fn pass<'a>( + &mut self, + node_view: NodeView, + _: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + _: Option::ElementBorrowed<'a>>>, + _: &SendAnyMap, + ) -> bool { + if let Some((parent,)) = parent { + if parent.count != 0 { + self.count += 1; + } + } + true + } -// #[derive(Clone, State, Default, Debug)] -// pub struct NodeState { -// #[parent_dep_state(blabla)] -// blabla: BlablaState, -// } + fn create<'a>( + node_view: NodeView<()>, + node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + children: Option::ElementBorrowed<'a>>>, + context: &SendAnyMap, + ) -> Self { + let mut myself = Self::default(); + myself.pass(node_view, node, parent, children, context); + myself + } +} -// mod dioxus_elements { -// macro_rules! builder_constructors { -// ( -// $( -// $(#[$attr:meta])* -// $name:ident { -// $( -// $(#[$attr_method:meta])* -// $fil:ident: $vil:ident, -// )* -// }; -// )* -// ) => { -// $( -// #[allow(non_camel_case_types)] -// $(#[$attr])* -// pub struct $name; +mod dioxus_elements { + macro_rules! builder_constructors { + ( + $( + $(#[$attr:meta])* + $name:ident { + $( + $(#[$attr_method:meta])* + $fil:ident: $vil:ident, + )* + }; + )* + ) => { + $( + #[allow(non_camel_case_types)] + $(#[$attr])* + pub struct $name; -// impl $name { -// pub const TAG_NAME: &'static str = stringify!($name); -// pub const NAME_SPACE: Option<&'static str> = None; + impl $name { + pub const TAG_NAME: &'static str = stringify!($name); + pub const NAME_SPACE: Option<&'static str> = None; -// $( -// pub const $fil: (&'static str, Option<&'static str>, bool) = (stringify!($fil), None, false); -// )* -// } + $( + pub const $fil: (&'static str, Option<&'static str>, bool) = (stringify!($fil), None, false); + )* + } -// impl GlobalAttributes for $name {} -// )* -// } -// } + impl GlobalAttributes for $name {} + )* + } + } -// pub trait GlobalAttributes {} + pub trait GlobalAttributes {} -// pub trait SvgAttributes {} + pub trait SvgAttributes {} -// builder_constructors! { -// blabla { + builder_constructors! { + blabla { -// }; -// } -// } + }; + } +} -// #[test] -// fn native_core_is_okay() { -// use std::time::Duration; +#[test] +fn native_core_is_okay() { + use std::time::Duration; -// fn app(cx: Scope) -> Element { -// let colors = use_state(cx, || vec!["green", "blue", "red"]); -// let padding = use_state(cx, || 10); + fn app(cx: Scope) -> Element { + let colors = use_state(cx, || vec!["green", "blue", "red"]); + let padding = use_state(cx, || 10); -// use_effect(cx, colors, |colors| async move { -// sleep(Duration::from_millis(1000)).await; -// colors.with_mut(|colors| colors.reverse()); -// }); + use_effect(cx, colors, |colors| async move { + sleep(Duration::from_millis(1000)).await; + colors.with_mut(|colors| colors.reverse()); + }); -// use_effect(cx, padding, |padding| async move { -// sleep(Duration::from_millis(10)).await; -// padding.with_mut(|padding| { -// if *padding < 65 { -// *padding += 1; -// } else { -// *padding = 5; -// } -// }); -// }); + use_effect(cx, padding, |padding| async move { + sleep(Duration::from_millis(10)).await; + padding.with_mut(|padding| { + if *padding < 65 { + *padding += 1; + } else { + *padding = 5; + } + }); + }); -// let _big = colors[0]; -// let _mid = colors[1]; -// let _small = colors[2]; + let _big = colors[0]; + let _mid = colors[1]; + let _small = colors[2]; -// cx.render(rsx! { -// blabla {} -// }) -// } + cx.render(rsx! { + blabla {} + }) + } -// let rt = tokio::runtime::Builder::new_current_thread() -// .enable_time() -// .build() -// .unwrap(); + let rt = tokio::runtime::Builder::new_current_thread() + .enable_time() + .build() + .unwrap(); -// rt.block_on(async { -// let rdom = Arc::new(Mutex::new(RealDom::::new())); -// let mut dom = VirtualDom::new(app); + rt.block_on(async { + let rdom = Arc::new(Mutex::new(RealDom::new(Box::new([ + BlablaState::to_type_erased(), + ])))); + let mut dom = VirtualDom::new(app); -// let muts = dom.rebuild(); -// let (to_update, _diff) = rdom.lock().unwrap().apply_mutations(muts); + let muts = dom.rebuild(); + rdom.lock().unwrap().apply_mutations(muts); -// let ctx = SendAnyMap::new(); -// rdom.lock().unwrap().update_state(to_update, ctx); + let ctx = SendAnyMap::new(); + rdom.lock().unwrap().update_state(ctx, false); -// for _ in 0..10 { -// dom.wait_for_work().await; + for _ in 0..10 { + dom.wait_for_work().await; -// let mutations = dom.render_immediate(); -// let (to_update, _diff) = rdom.lock().unwrap().apply_mutations(mutations); + let mutations = dom.render_immediate(); + rdom.lock().unwrap().apply_mutations(mutations); -// let ctx = SendAnyMap::new(); -// rdom.lock().unwrap().update_state(to_update, ctx); -// } -// }); -// } + let ctx = SendAnyMap::new(); + rdom.lock().unwrap().update_state(ctx, false); + } + }); +} From 8dde4e492f16934dbd4c543c35112d32b703e7eb Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 2 Feb 2023 14:48:02 -0600 Subject: [PATCH 033/103] finish migrating tests --- packages/native-core/src/tree.rs | 115 +++--------------- .../src/utils/persistant_iterator.rs | 104 ++++++++++++---- packages/native-core/tests/fuzzing.rs | 6 +- packages/native-core/tests/miri_native.rs | 8 +- 4 files changed, 107 insertions(+), 126 deletions(-) diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index 6d24ba83e..f2d1a07e6 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -338,8 +338,8 @@ fn creation() { assert_eq!(tree.parent_id(child_id).unwrap(), parent_id); assert_eq!(tree.children_ids(parent_id).unwrap(), &[child_id]); - assert_eq!(*tree.read::(parent_id).unwrap(), 1); - assert_eq!(*tree.read::(child_id).unwrap(), 0); + assert_eq!(*tree.get::(parent_id).unwrap(), 1); + assert_eq!(*tree.get::(child_id).unwrap(), 0); } #[test] @@ -371,10 +371,10 @@ fn insertion() { assert_eq!(tree.parent_id(after).unwrap(), parent); assert_eq!(tree.children_ids(parent).unwrap(), &[before, child, after]); - assert_eq!(*tree.read::(parent).unwrap(), 0); - assert_eq!(*tree.read::(before).unwrap(), 1); - assert_eq!(*tree.read::(child).unwrap(), 2); - assert_eq!(*tree.read::(after).unwrap(), 3); + 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); } #[test] @@ -406,10 +406,10 @@ fn deletion() { assert_eq!(tree.parent_id(after).unwrap(), parent); assert_eq!(tree.children_ids(parent).unwrap(), &[before, child, after]); - assert_eq!(*tree.read::(parent).unwrap(), 0); - assert_eq!(*tree.read::(before).unwrap(), 1); - assert_eq!(*tree.read::(child).unwrap(), 2); - assert_eq!(*tree.read::(after).unwrap(), 3); + 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); tree.remove(child); @@ -422,10 +422,10 @@ fn deletion() { assert_eq!(tree.parent_id(after).unwrap(), parent); assert_eq!(tree.children_ids(parent).unwrap(), &[before, after]); - assert_eq!(*tree.read::(parent).unwrap(), 0); - assert_eq!(*tree.read::(before).unwrap(), 1); - assert_eq!(tree.read::(child), None); - assert_eq!(*tree.read::(after).unwrap(), 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); tree.remove(before); @@ -436,9 +436,9 @@ fn deletion() { assert_eq!(tree.parent_id(after).unwrap(), parent); assert_eq!(tree.children_ids(parent).unwrap(), &[after]); - assert_eq!(*tree.read::(parent).unwrap(), 0); - assert_eq!(tree.read::(before), None); - assert_eq!(*tree.read::(after).unwrap(), 3); + assert_eq!(*tree.get::(parent).unwrap(), 0); + assert_eq!(tree.get::(before), None); + assert_eq!(*tree.get::(after).unwrap(), 3); tree.remove(after); @@ -447,8 +447,8 @@ fn deletion() { assert_eq!(tree.height(parent), Some(0)); assert_eq!(tree.children_ids(parent).unwrap(), &[]); - assert_eq!(*tree.read::(parent).unwrap(), 0); - assert_eq!(tree.read::(after), None); + assert_eq!(*tree.get::(parent).unwrap(), 0); + assert_eq!(tree.get::(after), None); } #[derive(Debug)] @@ -704,80 +704,3 @@ fn remove() { assert!(slab.read_slab::().get(node1_id).is_none()); assert_eq!(slab.len(), 1); } - -#[test] -fn get_many_mut_unchecked() { - let mut slab = AnySlab::new(); - let mut parent = slab.insert(); - parent.insert(0i32); - let parent = parent.id(); - let mut child = slab.insert(); - child.insert(1i32); - let child = child.id(); - let mut grandchild = slab.insert(); - grandchild.insert(2i32); - let grandchild = grandchild.id(); - assert_eq!(slab.len(), 3); - println!("ids: {:#?}", [parent, child, grandchild]); - - { - let i32_slab = slab.write_slab::(); - let all = - unsafe { i32_slab.get_many_mut_unchecked([parent, child, grandchild].into_iter()) } - .unwrap(); - assert_eq!(all, [&mut 0, &mut 1, &mut 2]); - } - - { - let i32_slab = slab.write_slab::(); - assert!( - unsafe { i32_slab.get_many_mut_unchecked([NodeId(3), NodeId(100)].into_iter()) } - .is_none() - ) - } -} - -#[test] -fn get_many_many_mut_unchecked() { - let mut slab = AnySlab::new(); - let mut parent = slab.insert(); - parent.insert(0i32); - parent.insert("0"); - let parent = parent.id(); - let mut child = slab.insert(); - child.insert(1i32); - child.insert("1"); - let child = child.id(); - let mut grandchild = slab.insert(); - grandchild.insert(2i32); - grandchild.insert("2"); - let grandchild = grandchild.id(); - println!("ids: {:#?}", [parent, child, grandchild]); - - println!("slab: {:#?}", slab); - - let num_entries = slab.write_slab::(); - - let all_num = unsafe { - num_entries - .as_any_mut() - .downcast_mut::>() - .unwrap() - .get_many_mut_unchecked([parent, child, grandchild].into_iter()) - } - .unwrap(); - - assert_eq!(all_num, [&mut 0, &mut 1, &mut 2]); - let str_entries = slab.write_slab::<&str>(); - - let all_str = unsafe { - str_entries - .as_any_mut() - .downcast_mut::>() - .unwrap() - .get_many_mut_unchecked([parent, child, grandchild].into_iter()) - } - .unwrap(); - - assert_eq!(all_str, [&mut "0", &mut "1", &mut "2"]); -} diff --git a/packages/native-core/src/utils/persistant_iterator.rs b/packages/native-core/src/utils/persistant_iterator.rs index 44e8c4a76..2fffe14d1 100644 --- a/packages/native-core/src/utils/persistant_iterator.rs +++ b/packages/native-core/src/utils/persistant_iterator.rs @@ -266,68 +266,128 @@ fn traverse() { let mut iter = PersistantElementIter::new(); let div_tag = "div".to_string(); assert!(matches!( - &rdom[iter.next(&rdom).id()].node_data.node_type, + &rdom + .get(iter.next(&rdom).id()) + .unwrap() + .node_data() + .node_type, NodeType::Element(ElementNode { tag: div_tag, .. }) )); assert!(matches!( - &rdom[iter.next(&rdom).id()].node_data.node_type, + &rdom + .get(iter.next(&rdom).id()) + .unwrap() + .node_data() + .node_type, NodeType::Element(ElementNode { tag: div_tag, .. }) )); let text1 = "hello".to_string(); assert!(matches!( - &rdom[iter.next(&rdom).id()].node_data.node_type, + &rdom + .get(iter.next(&rdom).id()) + .unwrap() + .node_data() + .node_type, NodeType::Text(text1) )); let p_tag = "p".to_string(); assert!(matches!( - &rdom[iter.next(&rdom).id()].node_data.node_type, + &rdom + .get(iter.next(&rdom).id()) + .unwrap() + .node_data() + .node_type, NodeType::Element(ElementNode { tag: p_tag, .. }) )); let text2 = "world".to_string(); assert!(matches!( - &rdom[iter.next(&rdom).id()].node_data.node_type, + &rdom + .get(iter.next(&rdom).id()) + .unwrap() + .node_data() + .node_type, NodeType::Text(text2) )); let text3 = "hello world".to_string(); assert!(matches!( - &rdom[iter.next(&rdom).id()].node_data.node_type, + &rdom + .get(iter.next(&rdom).id()) + .unwrap() + .node_data() + .node_type, NodeType::Text(text3) )); assert!(matches!( - &rdom[iter.next(&rdom).id()].node_data.node_type, + &rdom + .get(iter.next(&rdom).id()) + .unwrap() + .node_data() + .node_type, NodeType::Element(ElementNode { tag: div_tag, .. }) )); assert!(matches!( - &rdom[iter.prev(&rdom).id()].node_data.node_type, + &rdom + .get(iter.prev(&rdom).id()) + .unwrap() + .node_data() + .node_type, NodeType::Text(text3) )); assert!(matches!( - &rdom[iter.prev(&rdom).id()].node_data.node_type, + &rdom + .get(iter.prev(&rdom).id()) + .unwrap() + .node_data() + .node_type, NodeType::Text(text2) )); assert!(matches!( - &rdom[iter.prev(&rdom).id()].node_data.node_type, + &rdom + .get(iter.prev(&rdom).id()) + .unwrap() + .node_data() + .node_type, NodeType::Element(ElementNode { tag: p_tag, .. }) )); assert!(matches!( - &rdom[iter.prev(&rdom).id()].node_data.node_type, + &rdom + .get(iter.prev(&rdom).id()) + .unwrap() + .node_data() + .node_type, NodeType::Text(text1) )); assert!(matches!( - &rdom[iter.prev(&rdom).id()].node_data.node_type, + &rdom + .get(iter.prev(&rdom).id()) + .unwrap() + .node_data() + .node_type, NodeType::Element(ElementNode { tag: div_tag, .. }) )); assert!(matches!( - &rdom[iter.prev(&rdom).id()].node_data.node_type, + &rdom + .get(iter.prev(&rdom).id()) + .unwrap() + .node_data() + .node_type, NodeType::Element(ElementNode { tag: div_tag, .. }) )); assert!(matches!( - &rdom[iter.prev(&rdom).id()].node_data.node_type, + &rdom + .get(iter.prev(&rdom).id()) + .unwrap() + .node_data() + .node_type, NodeType::Element(ElementNode { tag: div_tag, .. }) )); assert!(matches!( - &rdom[iter.prev(&rdom).id()].node_data.node_type, + &rdom + .get(iter.prev(&rdom).id()) + .unwrap() + .node_data() + .node_type, NodeType::Text(text3) )); } @@ -400,16 +460,16 @@ fn persist_removes() { let root_tag = "Root".to_string(); let idx = iter1.next(&rdom).id(); - dbg!(&rdom[idx].node_data.node_type); + dbg!(&rdom.get(idx).unwrap().node_data().node_type); assert!(matches!( - &rdom[idx].node_data.node_type, + &rdom.get(idx).unwrap().node_data().node_type, NodeType::Element(ElementNode { tag: root_tag, .. }) )); let idx = iter2.next(&rdom).id(); - dbg!(&rdom[idx].node_data.node_type); + dbg!(&rdom.get(idx).unwrap().node_data().node_type); assert!(matches!( - &rdom[idx].node_data.node_type, + &rdom.get(idx).unwrap().node_data().node_type, NodeType::Element(ElementNode { tag: root_tag, .. }) )); } @@ -465,7 +525,7 @@ fn persist_instertions_before() { let p_tag = "div".to_string(); let idx = iter.next(&rdom).id(); assert!(matches!( - &rdom[idx].node_data.node_type, + &rdom.get(idx).unwrap().node_data().node_type, NodeType::Element(ElementNode { tag: p_tag, .. }) )); } @@ -520,13 +580,13 @@ fn persist_instertions_after() { let p_tag = "p".to_string(); let idx = iter.next(&rdom).id(); assert!(matches!( - &rdom[idx].node_data.node_type, + &rdom.get(idx).unwrap().node_data().node_type, NodeType::Element(ElementNode { tag: p_tag, .. }) )); let text = "hello world".to_string(); let idx = iter.next(&rdom).id(); assert!(matches!( - &rdom[idx].node_data.node_type, + &rdom.get(idx).unwrap().node_data().node_type, NodeType::Text(text) )); } diff --git a/packages/native-core/tests/fuzzing.rs b/packages/native-core/tests/fuzzing.rs index 615371dcb..a32fb849f 100644 --- a/packages/native-core/tests/fuzzing.rs +++ b/packages/native-core/tests/fuzzing.rs @@ -1,9 +1,9 @@ use dioxus::prelude::Props; use dioxus_core::*; use dioxus_native_core::{ - node_ref::{AttributeMask, AttributeMaskBuilder, NodeMaskBuilder, NodeView}, + node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}, real_dom::RealDom, - Dependancy, NodeMask, Pass, SendAnyMap, + Dependancy, Pass, SendAnyMap, }; use std::cell::Cell; @@ -308,7 +308,7 @@ impl Pass for BlablaState { fn pass<'a>( &mut self, - node_view: NodeView, + _: NodeView, _: ::ElementBorrowed<'a>, parent: Option<::ElementBorrowed<'a>>, _: Option::ElementBorrowed<'a>>>, diff --git a/packages/native-core/tests/miri_native.rs b/packages/native-core/tests/miri_native.rs index 9b5c995fa..23ca8a0e7 100644 --- a/packages/native-core/tests/miri_native.rs +++ b/packages/native-core/tests/miri_native.rs @@ -1,11 +1,9 @@ use dioxus::prelude::*; -use dioxus_core::*; use dioxus_native_core::{ - node_ref::{AttributeMask, AttributeMaskBuilder, NodeMaskBuilder, NodeView}, + node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}, real_dom::RealDom, - Dependancy, NodeMask, Pass, SendAnyMap, + Dependancy, Pass, SendAnyMap, }; -use std::cell::Cell; use std::sync::{Arc, Mutex}; use tokio::time::sleep; @@ -25,7 +23,7 @@ impl Pass for BlablaState { fn pass<'a>( &mut self, - node_view: NodeView, + _: NodeView, _: ::ElementBorrowed<'a>, parent: Option<::ElementBorrowed<'a>>, _: Option::ElementBorrowed<'a>>>, From 6fe00c4f78145dfccae7ab186894d7efca55d17d Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 4 Feb 2023 17:56:30 -0600 Subject: [PATCH 034/103] make dioxus dependancy optional --- packages/native-core/Cargo.toml | 10 +- packages/native-core/src/dioxus.rs | 254 +++++++ packages/native-core/src/lib.rs | 38 +- packages/native-core/src/node.rs | 52 +- packages/native-core/src/node_ref.rs | 26 +- packages/native-core/src/passes.rs | 717 +----------------- packages/native-core/src/real_dom.rs | 491 ++++-------- packages/native-core/src/tree.rs | 14 +- packages/native-core/src/utils/cursor.rs | 112 +-- packages/native-core/src/utils/mod.rs | 4 +- .../src/utils/persistant_iterator.rs | 9 +- packages/native-core/tests/passes.rs | 620 +++++++++++++++ 12 files changed, 1152 insertions(+), 1195 deletions(-) create mode 100644 packages/native-core/src/dioxus.rs create mode 100644 packages/native-core/tests/passes.rs diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index 370e85d36..ee2a3b912 100644 --- a/packages/native-core/Cargo.toml +++ b/packages/native-core/Cargo.toml @@ -11,10 +11,9 @@ keywords = ["dom", "ui", "gui", "react"] [dependencies] -dioxus-core = { path = "../core", version = "^0.3.0" } -dioxus-html = { path = "../html", version = "^0.3.0" } -dioxus-core-macro = { path = "../core-macro", version = "^0.3.0" } +dioxus-core = { path = "../core", version = "^0.3.0", optional = true } +keyboard-types = "0.6.2" taffy = "0.2.1" smallvec = "1.6" rustc-hash = "1.1.0" @@ -34,3 +33,8 @@ rayon = "1.6.1" rand = "0.8.5" dioxus = { path = "../dioxus", version = "^0.3.0" } tokio = { version = "*", features = ["full"] } +dioxus-native-core = { path = ".", features = ["dioxus"] } + +[features] +default = ["dioxus"] +dioxus = ["dioxus-core"] \ No newline at end of file diff --git a/packages/native-core/src/dioxus.rs b/packages/native-core/src/dioxus.rs new file mode 100644 index 000000000..6170be0d2 --- /dev/null +++ b/packages/native-core/src/dioxus.rs @@ -0,0 +1,254 @@ +use dioxus_core::{BorrowedAttributeValue, ElementId, Mutations, TemplateNode}; +use rustc_hash::{FxHashMap, FxHashSet}; + +use crate::{ + node::{ElementNode, FromAnyValue, NodeType, OwnedAttributeDiscription, OwnedAttributeValue}, + prelude::{NodeImmutable, NodeMutable}, + real_dom::NodeTypeMut, + NodeId, NodeMut, RealDom, +}; + +pub struct DioxusState { + templates: FxHashMap>, + stack: Vec, + node_id_mapping: Vec>, +} + +impl DioxusState { + pub fn new(rdom: &mut RealDom) -> Self { + rdom.insert_slab::(); + let root_id = rdom.root_id(); + let mut root = rdom.get_mut(root_id).unwrap(); + root.insert(ElementId(0)); + Self { + templates: FxHashMap::default(), + stack: vec![root_id], + node_id_mapping: vec![Some(root_id)], + } + } + + pub fn element_to_node_id(&self, element_id: ElementId) -> NodeId { + self.node_id_mapping.get(element_id.0).unwrap().unwrap() + } + + fn set_element_id(&mut self, mut node: NodeMut, element_id: ElementId) { + let node_id = node.id(); + node.insert(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); + } + + pub fn load_child(&self, rdom: &RealDom, path: &[u8]) -> NodeId { + let mut current = rdom.get(*self.stack.last().unwrap()).unwrap(); + for i in path { + let new_id = current.child_ids().unwrap()[*i as usize]; + current = rdom.get(new_id).unwrap(); + } + current.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(&mut self, rdom: &mut RealDom, mutations: Mutations) { + for template in mutations.templates { + let mut template_root_ids = Vec::new(); + for root in template.roots { + let id = create_template_node(rdom, root); + 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(self.stack.len() - m); + let parent = self.element_to_node_id(id); + for child in children { + rdom.add_child(parent, child); + } + } + AssignId { path, id } => { + let node_id = self.load_child(rdom, path); + self.set_element_id(rdom.get_mut(node_id).unwrap(), id); + } + CreatePlaceholder { id } => { + let node = NodeType::Placeholder; + let node = rdom.create_node(node, true); + let node_id = node.id(); + self.set_element_id(node, id); + self.stack.push(node_id); + } + CreateTextNode { value, id } => { + let node_data = NodeType::Text(value.to_string()); + let node = rdom.create_node(node_data, true); + let node_id = node.id(); + self.set_element_id(node, id); + self.stack.push(node_id); + } + HydrateText { path, value, id } => { + let node_id = self.load_child(rdom, path); + let node = rdom.get_mut(node_id).unwrap(); + self.set_element_id(node, id); + let mut node = rdom.get_mut(node_id).unwrap(); + if let NodeTypeMut::Text(text) = node.node_type_mut() { + *text = value.to_string(); + } else { + node.set_type(NodeType::Text(value.to_string())); + } + } + LoadTemplate { name, index, id } => { + let template_id = self.templates[name][index]; + let clone_id = rdom.clone_node(template_id); + let clone = rdom.get_mut(clone_id).unwrap(); + self.set_element_id(clone, id); + self.stack.push(clone_id); + } + ReplaceWith { id, m } => { + let new_nodes = self.stack.split_off(self.stack.len() - m); + let old_node_id = self.element_to_node_id(id); + for new in new_nodes { + let mut node = rdom.get_mut(new).unwrap(); + node.insert_before(old_node_id); + } + rdom.remove(old_node_id); + } + ReplacePlaceholder { path, m } => { + let new_nodes = self.stack.split_off(self.stack.len() - m); + let old_node_id = self.load_child(rdom, path); + for new in new_nodes { + let mut node = rdom.get_mut(new).unwrap(); + node.insert_before(old_node_id); + } + rdom.remove(old_node_id); + } + InsertAfter { id, m } => { + let new_nodes = self.stack.split_off(self.stack.len() - m); + let old_node_id = self.element_to_node_id(id); + for new in new_nodes.into_iter().rev() { + let mut node = rdom.get_mut(new).unwrap(); + node.insert_after(old_node_id); + } + } + InsertBefore { id, m } => { + let new_nodes = self.stack.split_off(self.stack.len() - m); + let old_node_id = self.element_to_node_id(id); + for new in new_nodes { + rdom.tree.insert_before(old_node_id, new); + } + } + SetAttribute { + name, + value, + id, + ns, + } => { + let node_id = self.element_to_node_id(id); + let mut node = rdom.get_mut(node_id).unwrap(); + if let NodeTypeMut::Element(element) = &mut node.node_type_mut() { + if let BorrowedAttributeValue::None = &value { + element.remove_attributes(&OwnedAttributeDiscription { + name: name.to_string(), + namespace: ns.map(|s| s.to_string()), + volatile: false, + }); + } else { + element.set_attribute( + OwnedAttributeDiscription { + name: name.to_string(), + namespace: ns.map(|s| s.to_string()), + volatile: false, + }, + OwnedAttributeValue::from(value), + ); + } + } + } + SetText { value, id } => { + let node_id = self.element_to_node_id(id); + let mut node = rdom.get_mut(node_id).unwrap(); + if let NodeTypeMut::Text(text) = node.node_type_mut() { + *text = value.to_string(); + } + } + NewEventListener { name, id } => { + let node_id = self.element_to_node_id(id); + let mut node = rdom.get_mut(node_id).unwrap(); + node.add_event_listener(name); + } + RemoveEventListener { id, name } => { + let node_id = self.element_to_node_id(id); + let mut node = rdom.get_mut(node_id).unwrap(); + node.remove_event_listener(name); + } + Remove { id } => { + let node_id = self.element_to_node_id(id); + rdom.remove(node_id); + } + PushRoot { id } => { + let node_id = self.element_to_node_id(id); + self.stack.push(node_id); + } + } + } + } +} + +fn create_template_node(rdom: &mut RealDom, node: &TemplateNode) -> NodeId { + match node { + TemplateNode::Element { + tag, + namespace, + attrs, + children, + } => { + let node = NodeType::Element(ElementNode { + 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 = rdom.create_node(node, true).id(); + for child in *children { + let child_id = create_template_node(rdom, child); + rdom.add_child(node_id, child_id); + } + node_id + } + TemplateNode::Text { text } => rdom + .create_node(NodeType::Text(text.to_string()), true) + .id(), + TemplateNode::Dynamic { .. } => rdom.create_node(NodeType::Placeholder, true).id(), + TemplateNode::DynamicText { .. } => { + rdom.create_node(NodeType::Text(String::new()), true).id() + } + } +} + +trait NodeImmutableDioxusExt: NodeImmutable { + fn mounted_id(&self) -> Option { + self.get().copied() + } +} + +impl, V: FromAnyValue + Send + Sync> NodeImmutableDioxusExt for T {} diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index c54daab10..5e07f68aa 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -8,6 +8,8 @@ pub use real_dom::{NodeMut, NodeRef, RealDom}; use rustc_hash::FxHasher; pub use tree::NodeId; +#[cfg(feature = "dioxus")] +pub mod dioxus; pub mod layout_attributes; pub mod node; pub mod node_ref; @@ -16,33 +18,15 @@ pub mod real_dom; pub mod tree; pub mod utils; +pub mod prelude { + pub use crate::node::{ElementNode, FromAnyValue, NodeType, OwnedAttributeView}; + pub use crate::node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}; + pub use crate::passes::{Dependancy, Pass}; + pub use crate::real_dom::{NodeImmutable, NodeMut, NodeMutRaw, NodeMutable, NodeRef, RealDom}; + pub use crate::tree::NodeId; + pub use crate::SendAnyMap; +} + pub type FxDashMap = dashmap::DashMap>; pub type FxDashSet = dashmap::DashSet>; pub type SendAnyMap = anymap::Map; - -/// Used in derived state macros -#[derive(Eq, PartialEq)] -#[doc(hidden)] -pub struct HeightOrdering { - pub height: u16, - pub id: NodeId, -} - -impl HeightOrdering { - pub fn new(height: u16, id: NodeId) -> Self { - HeightOrdering { height, id } - } -} - -// not the ordering after height is just for deduplication it can be any ordering as long as it is consistent -impl Ord for HeightOrdering { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.height.cmp(&other.height).then(self.id.cmp(&other.id)) - } -} - -impl PartialOrd for HeightOrdering { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} diff --git a/packages/native-core/src/node.rs b/packages/native-core/src/node.rs index 7eb0fb31e..f0fe7073a 100644 --- a/packages/native-core/src/node.rs +++ b/packages/native-core/src/node.rs @@ -1,20 +1,8 @@ -use crate::tree::NodeId; -use dioxus_core::{AnyValue, BorrowedAttributeValue, ElementId}; use rustc_hash::{FxHashMap, FxHashSet}; -use std::fmt::Debug; +use std::{any::Any, fmt::Debug}; #[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, -} - -#[derive(Debug, Clone)] -pub struct ElementNode { +pub struct ElementNode { pub tag: String, pub namespace: Option, pub attributes: FxHashMap>, @@ -29,21 +17,6 @@ pub enum NodeType { Placeholder, } -impl NodeData { - pub(crate) fn new(node_type: NodeType) -> Self { - NodeData { - element_id: None, - node_type, - node_id: NodeId(0), - } - } - - /// get the mounted id of the node - pub fn mounted_id(&self) -> Option { - self.element_id - } -} - #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct OwnedAttributeDiscription { pub name: String, @@ -72,11 +45,11 @@ pub enum OwnedAttributeValue { } pub trait FromAnyValue: Clone + 'static { - fn from_any_value(value: &dyn AnyValue) -> Self; + fn from_any_value(value: &dyn Any) -> Self; } impl FromAnyValue for () { - fn from_any_value(_: &dyn AnyValue) -> Self {} + fn from_any_value(_: &dyn Any) -> Self {} } impl Debug for OwnedAttributeValue { @@ -91,15 +64,16 @@ impl Debug for OwnedAttributeValue { } } -impl From> for OwnedAttributeValue { - fn from(value: BorrowedAttributeValue<'_>) -> Self { +#[cfg(feature = "dioxus")] +impl From> for OwnedAttributeValue { + fn from(value: dioxus_core::BorrowedAttributeValue<'_>) -> Self { match value { - BorrowedAttributeValue::Text(text) => Self::Text(text.to_string()), - BorrowedAttributeValue::Float(float) => Self::Float(float), - BorrowedAttributeValue::Int(int) => Self::Int(int), - BorrowedAttributeValue::Bool(bool) => Self::Bool(bool), - BorrowedAttributeValue::Any(any) => Self::Custom(V::from_any_value(&*any)), - BorrowedAttributeValue::None => panic!("None attribute values result in removing the attribute, not converting it to a None value.") + dioxus_core::BorrowedAttributeValue::Text(text) => Self::Text(text.to_string()), + dioxus_core::BorrowedAttributeValue::Float(float) => Self::Float(float), + dioxus_core::BorrowedAttributeValue::Int(int) => Self::Int(int), + dioxus_core::BorrowedAttributeValue::Bool(bool) => Self::Bool(bool), + dioxus_core::BorrowedAttributeValue::Any(any) => Self::Custom(V::from_any_value(any.as_any())), + dioxus_core::BorrowedAttributeValue::None => panic!("None attribute values result in removing the attribute, not converting it to a None value.") } } } diff --git a/packages/native-core/src/node_ref.rs b/packages/native-core/src/node_ref.rs index f0e094cdd..d8fc309be 100644 --- a/packages/native-core/src/node_ref.rs +++ b/packages/native-core/src/node_ref.rs @@ -1,42 +1,38 @@ -use dioxus_core::ElementId; use rustc_hash::FxHashSet; use crate::{ - node::{ElementNode, FromAnyValue, NodeData, NodeType, OwnedAttributeView}, + node::{ElementNode, FromAnyValue, NodeType, OwnedAttributeView}, NodeId, }; /// A view into a [VNode] with limited access. #[derive(Debug)] pub struct NodeView<'a, V: FromAnyValue = ()> { - inner: &'a NodeData, + id: NodeId, + inner: &'a NodeType, mask: &'a NodeMask, } impl<'a, V: FromAnyValue> NodeView<'a, V> { /// Create a new NodeView from a VNode, and mask. - pub fn new(node: &'a NodeData, view: &'a NodeMask) -> Self { + pub fn new(id: NodeId, node: &'a NodeType, view: &'a NodeMask) -> Self { Self { inner: node, mask: view, + id, } } - /// Get the id of the node - pub fn id(&self) -> Option { - self.inner.element_id - } - /// Get the node id of the node pub fn node_id(&self) -> NodeId { - self.inner.node_id + self.id } /// Get the tag of the node if the tag is enabled in the mask pub fn tag(&self) -> Option<&'a str> { self.mask .tag - .then_some(match &self.inner.node_type { + .then_some(match &self.inner { NodeType::Element(ElementNode { tag, .. }) => Some(&**tag), _ => None, }) @@ -47,7 +43,7 @@ impl<'a, V: FromAnyValue> NodeView<'a, V> { pub fn namespace(&self) -> Option<&'a str> { self.mask .namespace - .then_some(match &self.inner.node_type { + .then_some(match &self.inner { NodeType::Element(ElementNode { namespace, .. }) => namespace.as_deref(), _ => None, }) @@ -58,7 +54,7 @@ impl<'a, V: FromAnyValue> NodeView<'a, V> { pub fn attributes<'b>( &'b self, ) -> Option> + 'b> { - match &self.inner.node_type { + match &self.inner { NodeType::Element(ElementNode { attributes, .. }) => Some( attributes .iter() @@ -76,7 +72,7 @@ impl<'a, V: FromAnyValue> NodeView<'a, V> { pub fn text(&self) -> Option<&str> { self.mask .text - .then_some(match &self.inner.node_type { + .then_some(match &self.inner { NodeType::Text(text) => Some(&**text), _ => None, }) @@ -86,7 +82,7 @@ impl<'a, V: FromAnyValue> NodeView<'a, V> { /// Get the listeners if it is enabled in the mask pub fn listeners(&self) -> Option + '_> { if self.mask.listeners { - match &self.inner.node_type { + match &self.inner { NodeType::Element(ElementNode { listeners, .. }) => { Some(listeners.iter().map(|l| &**l)) } diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index b7c3de826..f5a66aaad 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -7,7 +7,7 @@ use std::collections::BTreeMap; use std::marker::PhantomData; use std::sync::Arc; -use crate::node::{FromAnyValue, NodeData}; +use crate::node::{FromAnyValue, NodeType}; use crate::node_ref::{NodeMaskBuilder, NodeView}; use crate::real_dom::RealDom; use crate::tree::{SlabEntry, Tree, TreeStateView}; @@ -129,26 +129,35 @@ pub trait Pass: Any + Send + Sync { ) -> Self; fn validate() { - // no type can be a child and parent dependency - for type_id in Self::parent_type_ids().iter().copied() { - for type_id2 in Self::child_type_ids().iter().copied() { - if type_id == type_id2 { - panic!("type cannot be both a parent and child dependency"); - } - } - } // this type should not be a node dependency for type_id in Self::node_type_ids().iter().copied() { if type_id == TypeId::of::() { panic!("The current type cannot be a node dependency"); } } + // this type cannot be both a parent and child dependency + assert!( + Self::parent_type_ids() + .iter() + .any(|type_id| *type_id != TypeId::of::()) + && Self::child_type_ids() + .iter() + .any(|type_id| *type_id != TypeId::of::()), + "The current type cannot be a parent and child dependency" + ); // no states have the same type id - if Self::all_dependanices() + if Self::child_type_ids() .iter() - .collect::>() + .collect::>() .len() - != Self::all_dependanices().len() + != Self::child_type_ids().len() + || Self::parent_type_ids() + .iter() + .collect::>() + .len() + != Self::parent_type_ids().len() + || Self::node_type_ids().iter().collect::>().len() + != Self::node_type_ids().len() { panic!("all states must have unique type ids"); } @@ -178,28 +187,21 @@ pub trait Pass: Any + Send + Sync { let myself: SlabEntry<'static, Self> = unsafe { std::mem::transmute(tree.get_slab_mut::().unwrap().entry(node_id)) }; - let node_data = tree.get_single::>(node_id).unwrap(); + let node_data = tree.get_single::>(node_id).unwrap(); let node = tree.get::(node_id).unwrap(); let children = tree.children::(node_id); let parent = tree.parent::(node_id); + let view = NodeView::new(node_id, node_data, &node_mask); if myself.value.is_none() { - *myself.value = Some(Self::create( - NodeView::new(node_data, &node_mask), - node, - parent, - children, - context, - )); + *myself.value = Some(Self::create(view, node, parent, children, context)); true } else { - myself.value.as_mut().unwrap().pass( - NodeView::new(node_data, &node_mask), - node, - parent, - children, - context, - ) + myself + .value + .as_mut() + .unwrap() + .pass(view, node, parent, children, context) } }, ) as PassCallback, @@ -409,7 +411,7 @@ pub fn resolve_passes( .iter() .filter(|id| **id != pass.this_type_id) .copied() - .chain(std::iter::once(TypeId::of::>())), + .chain(std::iter::once(TypeId::of::>())), [pass.this_type_id], ); let dirty_nodes = dirty_nodes.clone(); @@ -432,662 +434,3 @@ pub fn resolve_passes( } std::sync::Arc::try_unwrap(nodes_updated).unwrap() } - -// #[test] -// fn node_pass() { -// use crate::real_dom::RealDom; -// use crate::tree::{Tree, TreeLike}; - -// #[derive(Debug, Default, Clone, PartialEq)] -// struct Number(i32); - -// impl State for Number { -// fn create_passes() -> Box<[TypeErasedPass]> { -// Box::new([Number::to_type_erased()]) -// } -// } - -// impl AnyMapLike for Number { -// fn get(&self) -> Option<&T> { -// if TypeId::of::() == TypeId::of::() { -// Some(unsafe { &*(self as *const Self as *const T) }) -// } else { -// None -// } -// } - -// fn get_mut(&mut self) -> Option<&mut T> { -// if TypeId::of::() == TypeId::of::() { -// Some(unsafe { &mut *(self as *mut Self as *mut T) }) -// } else { -// None -// } -// } -// } - -// impl Pass for Number { -// type ChildDependencies = (); -// type NodeDependencies = (); -// type ParentDependencies = (); -// type Ctx = (); -// const MASK: NodeMask = NodeMask::new(); - -// fn pass<'a>( -// &mut self, -// node_view: NodeView, -// node: ::ElementBorrowed<'a>, -// parent: Option<::ElementBorrowed<'a>>, -// children: Option< -// impl Iterator::ElementBorrowed<'a>>, -// >, -// context: ::ElementBorrowed<'a>, -// ) -> bool { -// self.0 += 1; -// true -// } -// } - -// let mut tree: RealDom = RealDom::new(); -// tree.dirty_nodes.insert(TypeId::of::(), NodeId(0)); -// tree.update_state(SendAnyMap::new()); - -// assert_eq!(tree.get(tree.root()).unwrap().state.0, 1); -// } - -// #[test] -// fn dependant_node_pass() { -// use crate::real_dom::RealDom; - -// #[derive(Debug, Default, Clone, PartialEq)] -// struct AddNumber(i32); - -// impl Pass for AddNumber { -// type ChildDependencies = (); -// type NodeDependencies = (SubtractNumber,); -// type ParentDependencies = (); -// type Ctx = (); -// const MASK: NodeMask = NodeMask::new(); - -// fn pass<'a>( -// &mut self, -// node_view: NodeView, -// node: ::ElementBorrowed<'a>, -// parent: Option<::ElementBorrowed<'a>>, -// children: Option< -// impl Iterator::ElementBorrowed<'a>>, -// >, -// context: ::ElementBorrowed<'a>, -// ) -> bool { -// self.0 += 1; -// true -// } -// } - -// #[derive(Debug, Default, Clone, PartialEq)] -// struct SubtractNumber(i32); - -// impl Pass for SubtractNumber { -// type ChildDependencies = (); -// type NodeDependencies = (); -// type ParentDependencies = (); -// type Ctx = (); -// const MASK: NodeMask = NodeMask::new(); - -// fn pass<'a>( -// &mut self, -// node_view: NodeView, -// node: ::ElementBorrowed<'a>, -// parent: Option<::ElementBorrowed<'a>>, -// children: Option< -// impl Iterator::ElementBorrowed<'a>>, -// >, -// context: ::ElementBorrowed<'a>, -// ) -> bool { -// self.0 -= 1; -// true -// } -// } - -// #[derive(Debug, Default, Clone, PartialEq)] -// struct NumberState { -// add_number: AddNumber, -// subtract_number: SubtractNumber, -// } - -// impl AnyMapLike for NumberState { -// fn get(&self) -> Option<&T> { -// if TypeId::of::() == TypeId::of::() { -// Some(unsafe { &*(&self.add_number as *const AddNumber as *const T) }) -// } else if TypeId::of::() == TypeId::of::() { -// Some(unsafe { &*(&self.subtract_number as *const SubtractNumber as *const T) }) -// } else { -// None -// } -// } - -// fn get_mut(&mut self) -> Option<&mut T> { -// if TypeId::of::() == TypeId::of::() { -// Some(unsafe { &mut *(&mut self.add_number as *mut AddNumber as *mut T) }) -// } else if TypeId::of::() == TypeId::of::() { -// Some(unsafe { &mut *(&mut self.subtract_number as *mut SubtractNumber as *mut T) }) -// } else { -// None -// } -// } -// } - -// impl State for NumberState { -// fn create_passes() -> Box<[TypeErasedPass]> { -// Box::new([ -// AddNumber::to_type_erased(), -// SubtractNumber::to_type_erased(), -// ]) -// } -// } - -// let mut tree: RealDom = RealDom::new(); -// tree.dirty_nodes -// .insert(TypeId::of::(), NodeId(0)); -// tree.update_state(dirty_nodes, SendAnyMap::new()); - -// assert_eq!( -// tree.get(tree.root()).unwrap().state, -// NumberState { -// add_number: AddNumber(1), -// subtract_number: SubtractNumber(-1) -// } -// ); -// } - -// #[test] -// fn independant_node_pass() { -// use crate::real_dom::RealDom; -// use crate::tree::{Tree, TreeLike}; - -// #[derive(Debug, Default, Clone, PartialEq)] -// struct AddNumber(i32); - -// impl Pass for AddNumber { -// type ChildDependencies = (); -// type NodeDependencies = (); -// type ParentDependencies = (); -// type Ctx = (); -// const MASK: NodeMask = NodeMask::new(); - -// fn pass<'a>( -// &mut self, -// node_view: NodeView, -// node: ::ElementBorrowed<'a>, -// parent: Option<::ElementBorrowed<'a>>, -// children: Option< -// impl Iterator::ElementBorrowed<'a>>, -// >, -// context: ::ElementBorrowed<'a>, -// ) -> bool { -// self.0 += 1; -// true -// } -// } - -// #[derive(Debug, Default, Clone, PartialEq)] -// struct SubtractNumber(i32); - -// impl Pass for SubtractNumber { -// type ChildDependencies = (); -// type NodeDependencies = (); -// type ParentDependencies = (); -// type Ctx = (); -// const MASK: NodeMask = NodeMask::new(); - -// fn pass<'a>( -// &mut self, -// node_view: NodeView, -// node: ::ElementBorrowed<'a>, -// parent: Option<::ElementBorrowed<'a>>, -// children: Option< -// impl Iterator::ElementBorrowed<'a>>, -// >, -// context: ::ElementBorrowed<'a>, -// ) -> bool { -// self.0 -= 1; -// true -// } -// } - -// #[derive(Debug, Default, Clone, PartialEq)] -// struct NumberState { -// add_number: AddNumber, -// subtract_number: SubtractNumber, -// } - -// impl AnyMapLike for NumberState { -// fn get(&self) -> Option<&T> { -// if TypeId::of::() == TypeId::of::() { -// Some(unsafe { &*(&self.add_number as *const AddNumber as *const T) }) -// } else if TypeId::of::() == TypeId::of::() { -// Some(unsafe { &*(&self.subtract_number as *const SubtractNumber as *const T) }) -// } else { -// None -// } -// } - -// fn get_mut(&mut self) -> Option<&mut T> { -// if TypeId::of::() == TypeId::of::() { -// Some(unsafe { &mut *(&mut self.add_number as *mut AddNumber as *mut T) }) -// } else if TypeId::of::() == TypeId::of::() { -// Some(unsafe { &mut *(&mut self.subtract_number as *mut SubtractNumber as *mut T) }) -// } else { -// None -// } -// } -// } - -// impl State for NumberState { -// fn create_passes() -> Box<[TypeErasedPass]> { -// Box::new([ -// AddNumber::to_type_erased(), -// SubtractNumber::to_type_erased(), -// ]) -// } -// } - -// let mut tree: RealDom = RealDom::new(); -// tree.dirty_nodes -// .insert(TypeId::of::(), NodeId(0)); -// tree.update_state(SendAnyMap::new()); - -// assert_eq!( -// tree.get(tree.root()).unwrap().state, -// NumberState { -// add_number: AddNumber(0), -// subtract_number: SubtractNumber(-1) -// } -// ); -// } - -// #[test] -// fn down_pass() { -// use crate::real_dom::RealDom; -// use crate::tree::{Tree, TreeLike}; - -// #[derive(Debug, Default, Clone, PartialEq)] -// struct AddNumber(i32); - -// impl Pass for AddNumber { -// type ChildDependencies = (); -// type NodeDependencies = (); -// type ParentDependencies = (AddNumber,); -// type Ctx = (); -// const MASK: NodeMask = NodeMask::new(); - -// fn pass<'a>( -// &mut self, -// node_view: NodeView, -// node: ::ElementBorrowed<'a>, -// parent: Option<::ElementBorrowed<'a>>, -// children: Option< -// impl Iterator::ElementBorrowed<'a>>, -// >, -// context: ::ElementBorrowed<'a>, -// ) -> bool { -// if let Some((parent,)) = parent { -// *self.0 += *parent.0; -// } -// true -// } -// } - -// #[derive(Debug, Default, Clone, PartialEq)] -// struct NumberState { -// add_number: AddNumber, -// } - -// impl AnyMapLike for NumberState { -// fn get(&self) -> Option<&T> { -// if TypeId::of::() == TypeId::of::() { -// Some(unsafe { &*(&self.add_number as *const AddNumber as *const T) }) -// } else { -// None -// } -// } - -// fn get_mut(&mut self) -> Option<&mut T> { -// if TypeId::of::() == TypeId::of::() { -// Some(unsafe { &mut *(&mut self.add_number as *mut AddNumber as *mut T) }) -// } else { -// None -// } -// } -// } - -// impl State for NumberState { -// fn create_passes() -> Box<[TypeErasedPass]> { -// Box::new([AddNumber::to_type_erased()]) -// } -// } - -// let mut tree: RealDom = RealDom::new(); -// 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); - -// tree.dirty_nodes -// .insert(TypeId::of::(), NodeId(0)); -// tree.update_state(SendAnyMap::new()); - -// assert_eq!(tree.get(tree.root()).unwrap().state.add_number.0, 1); -// assert_eq!(tree.get(child1).unwrap().state.add_number.0, 2); -// assert_eq!(tree.get(grandchild1).unwrap().state.add_number.0, 3); -// assert_eq!(tree.get(child2).unwrap().state.add_number.0, 2); -// assert_eq!(tree.get(grandchild2).unwrap().state.add_number.0, 3); -// } - -// #[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); - -// 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(0) -// } -// } -// impl DownwardPass for AddPass { -// fn pass(&self, node: &mut i32, parent: Option<&mut i32>, _: &SendAnyMap) -> PassReturn { -// if let Some(parent) = parent { -// *node += *parent; -// } else { -// } -// 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(0) -// } -// } -// impl DownwardPass for SubtractPass { -// fn pass(&self, node: &mut i32, parent: Option<&mut i32>, _: &SendAnyMap) -> PassReturn { -// if let Some(parent) = parent { -// *node -= *parent; -// } else { -// } -// PassReturn { -// progress: true, -// mark_dirty: true, -// } -// } -// } - -// 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, SendAnyMap::new()); - -// // 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}; -// // 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 Pass for AddPass { -// fn pass_id(&self) -> PassId { -// PassId(0) -// } - -// fn dependancies(&self) -> &'static [PassId] { -// &[] -// } - -// fn dependants(&self) -> &'static [PassId] { -// &[] -// } - -// fn mask(&self) -> MemberMask { -// MemberMask(0) -// } -// } -// impl UpwardPass for AddPass { -// fn pass<'a>( -// &self, -// node: &mut i32, -// children: &mut dyn Iterator, -// _: &SendAnyMap, -// ) -> PassReturn { -// *node += children.map(|i| *i).sum::(); -// PassReturn { -// progress: true, -// mark_dirty: true, -// } -// } -// } - -// 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, SendAnyMap::new()); - -// 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); -// } - -// #[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(0) -// } -// } -// impl UpwardPass for AddPass { -// fn pass<'a>( -// &self, -// node: &mut i32, -// children: &mut dyn Iterator, -// _: &SendAnyMap, -// ) -> 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(0) -// } -// } -// impl UpwardPass for SubtractPass { -// fn pass<'a>( -// &self, -// node: &mut i32, -// children: &mut dyn Iterator, -// _: &SendAnyMap, -// ) -> PassReturn { -// *node -= children.map(|i| *i).sum::(); -// PassReturn { -// progress: true, -// mark_dirty: true, -// } -// } -// } - -// 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, SendAnyMap::new()); - -// // 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); -// } diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 5db372e06..bb7d1ec99 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -1,15 +1,13 @@ -use dioxus_core::{BorrowedAttributeValue, ElementId, Mutations, TemplateNode}; - use rustc_hash::{FxHashMap, FxHashSet}; use std::any::{Any, TypeId}; use std::collections::VecDeque; use crate::node::{ - ElementNode, FromAnyValue, NodeData, NodeType, OwnedAttributeDiscription, OwnedAttributeValue, + ElementNode, FromAnyValue, NodeType, OwnedAttributeDiscription, OwnedAttributeValue, }; use crate::node_ref::{AttributeMask, NodeMask, NodeMaskBuilder}; use crate::passes::{resolve_passes, DirtyNodeStates, TypeErasedPass}; -use crate::tree::{EntryBuilder, NodeId, Tree}; +use crate::tree::{NodeId, Tree}; use crate::{FxDashSet, SendAnyMap}; /// A Dom that can sync with the VirtualDom mutations intended for use in lazy renderers. @@ -20,11 +18,7 @@ use crate::{FxDashSet, SendAnyMap}; /// To allow custom values to be passed into attributes implement FromAnyValue on a type that can represent your custom value and specify the V generic to be that type. If you have many different custom values, it can be useful to use a enum type to represent the varients. pub struct RealDom { pub(crate) tree: Tree, - /// a map from element id to real node id - node_id_mapping: Vec>, nodes_listening: FxHashMap>, - stack: Vec, - templates: FxHashMap>, pub(crate) passes: Box<[TypeErasedPass]>, passes_updated: FxHashMap>, nodes_updated: FxHashMap, @@ -34,19 +28,17 @@ pub struct RealDom { impl RealDom { pub fn new(mut passes: Box<[TypeErasedPass]>) -> RealDom { let mut tree = Tree::new(); - tree.insert_slab::>(); + tree.insert_slab::>(); for pass in passes.iter() { (pass.create)(&mut tree); } let root_id = tree.root(); - let mut root_node: NodeData = NodeData::new(NodeType::Element(ElementNode { + let root_node: NodeType = NodeType::Element(ElementNode { tag: "Root".to_string(), namespace: Some("Root".to_string()), attributes: FxHashMap::default(), listeners: FxHashSet::default(), - })); - root_node.element_id = Some(ElementId(0)); - root_node.node_id = root_id; + }); tree.insert(root_id, root_node); // resolve dependants for each pass @@ -71,10 +63,7 @@ impl RealDom { RealDom { tree, - node_id_mapping: vec![Some(root_id)], nodes_listening: FxHashMap::default(), - stack: vec![root_id], - templates: FxHashMap::default(), passes, passes_updated, nodes_updated, @@ -114,239 +103,23 @@ impl RealDom { } } - pub fn element_to_node_id(&self, element_id: ElementId) -> NodeId { - self.node_id_mapping.get(element_id.0).unwrap().unwrap() - } - - fn set_element_id(&mut self, node_id: NodeId, element_id: ElementId) { - let mut node = self.tree.get_mut::(node_id).unwrap(); - let node_id = node.node_id; - node.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]) -> NodeId { - 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, - mut node: NodeData, - id: Option, - mark_dirty: bool, - ) -> EntryBuilder<'_> { + pub fn create_node(&mut self, node: NodeType, mark_dirty: bool) -> NodeMut<'_, V> { let mut node_entry = self.tree.create_node(); - let node_id = node_entry.id(); + let id = node_entry.id(); if mark_dirty { self.passes_updated - .entry(node_id) + .entry(id) .or_default() .extend(self.passes.iter().map(|x| x.this_type_id)); } - node.node_id = node_id; - node.element_id = id; node_entry.insert(node); - node_entry + NodeMut::new(id, self) } - fn add_child(&mut self, node_id: NodeId, child_id: NodeId) { + pub fn add_child(&mut self, node_id: NodeId, child_id: NodeId) { self.tree.add_child(node_id, child_id); } - fn create_template_node(&mut self, node: &TemplateNode) -> NodeId { - match node { - TemplateNode::Element { - tag, - namespace, - attrs, - children, - } => { - let node = NodeData::new(NodeType::Element(ElementNode { - 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, None, true).id(); - for child in *children { - let child_id = self.create_template_node(child); - self.add_child(node_id, child_id); - } - node_id - } - TemplateNode::Text { text } => self - .create_node(NodeData::new(NodeType::Text(text.to_string())), None, true) - .id(), - TemplateNode::Dynamic { .. } => self - .create_node(NodeData::new(NodeType::Placeholder), None, true) - .id(), - TemplateNode::DynamicText { .. } => self - .create_node(NodeData::new(NodeType::Text(String::new())), None, true) - .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(&mut self, mutations: Mutations) { - for template in mutations.templates { - let mut template_root_ids = Vec::new(); - for root in template.roots { - let id = self.create_template_node(root); - 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(self.stack.len() - m); - let parent = self.element_to_node_id(id); - for child in children { - self.add_child(parent, child); - } - } - AssignId { path, id } => { - self.set_element_id(self.load_child(path), id); - } - CreatePlaceholder { id } => { - let node = NodeData::new(NodeType::Placeholder); - let node_id = self.create_node(node, None, true).id(); - self.set_element_id(node_id, id); - self.stack.push(node_id); - } - CreateTextNode { value, id } => { - let node_data = NodeData::new(NodeType::Text(value.to_string())); - let node_id = self.create_node(node_data, None, true).id(); - self.set_element_id(node_id, id); - self.stack.push(node_id); - } - HydrateText { path, value, id } => { - let node_id = self.load_child(path); - self.set_element_id(node_id, id); - let mut node = self.get_mut(node_id).unwrap(); - if let NodeTypeMut::Text(text) = node.node_type_mut() { - *text = value.to_string(); - } else { - node.set_type(NodeType::Text(value.to_string())); - } - } - 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); - self.stack.push(clone_id); - } - ReplaceWith { id, m } => { - let new_nodes = self.stack.split_off(self.stack.len() - m); - let old_node_id = self.element_to_node_id(id); - for new in new_nodes { - self.insert_before(old_node_id, new); - } - self.remove(old_node_id); - } - ReplacePlaceholder { path, 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.insert_before(old_node_id, new); - } - self.remove(old_node_id); - } - InsertAfter { id, m } => { - let new_nodes = self.stack.split_off(self.stack.len() - m); - let old_node_id = self.element_to_node_id(id); - for new in new_nodes.into_iter().rev() { - self.insert_after(old_node_id, new); - } - } - InsertBefore { id, m } => { - let new_nodes = self.stack.split_off(self.stack.len() - m); - let old_node_id = self.element_to_node_id(id); - for new in new_nodes { - self.tree.insert_before(old_node_id, new); - } - } - SetAttribute { - name, - value, - id, - ns, - } => { - let node_id = self.element_to_node_id(id); - let mut node = self.get_mut(node_id).unwrap(); - if let NodeTypeMut::Element(element) = &mut node.node_type_mut() { - if let BorrowedAttributeValue::None = &value { - element.remove_attributes(&OwnedAttributeDiscription { - name: name.to_string(), - namespace: ns.map(|s| s.to_string()), - volatile: false, - }); - } else { - element.set_attribute( - OwnedAttributeDiscription { - name: name.to_string(), - namespace: ns.map(|s| s.to_string()), - volatile: false, - }, - OwnedAttributeValue::from(value), - ); - } - } - } - SetText { value, id } => { - let node_id = self.element_to_node_id(id); - let mut node = self.get_mut(node_id).unwrap(); - if let NodeTypeMut::Text(text) = node.node_type_mut() { - *text = value.to_string(); - } - } - NewEventListener { name, id } => { - let node_id = self.element_to_node_id(id); - self.add_event_listener(node_id, name); - } - RemoveEventListener { id, name } => { - let node_id = self.element_to_node_id(id); - self.remove_event_listener(node_id, name); - } - Remove { id } => { - let node_id = self.element_to_node_id(id); - self.remove(node_id); - } - PushRoot { id } => { - let node_id = self.element_to_node_id(id); - self.stack.push(node_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: &str) -> Vec> { @@ -376,10 +149,10 @@ impl RealDom { self.tree.root() } - fn clone_node(&mut self, node_id: NodeId) -> NodeId { + pub fn clone_node(&mut self, node_id: NodeId) -> NodeId { let node = self.get(node_id).unwrap(); - let new_node = node.node_data().clone(); - let new_id = self.create_node(new_node, None, true).id(); + let new_node = node.node_type().clone(); + let new_id = self.create_node(new_node, true).id(); let children = self.tree.children_ids(node_id).unwrap().to_vec(); for child in children { @@ -389,26 +162,18 @@ impl RealDom { new_id } - pub fn root(&self) -> NodeId { - self.tree.root() - } - - pub fn get(&self, id: impl IntoNodeId) -> Option> { + pub fn get(&self, id: NodeId) -> Option> { let id = id.into_node_id(self); self.tree.contains(id).then_some(NodeRef { id, dom: self }) } - pub fn get_mut(&mut self, id: impl IntoNodeId) -> Option> { + pub fn get_mut(&mut self, id: NodeId) -> Option> { let id = id.into_node_id(self); - self.tree.contains(id).then(|| NodeMut { - id, - dirty: NodeMask::default(), - dom: self, - }) + self.tree.contains(id).then(|| NodeMut::new(id, self)) } /// WARNING: This escapes the reactive system that the real dom uses. Any changes made with this method will not trigger updates in states when [RealDom::update_state] is called. - pub fn get_mut_raw(&mut self, id: impl IntoNodeId) -> Option> { + pub fn get_mut_raw(&mut self, id: NodeId) -> Option> { let id = id.into_node_id(self); self.tree .contains(id) @@ -439,7 +204,7 @@ impl RealDom { ) } - fn remove(&mut self, id: NodeId) { + pub fn remove(&mut self, id: NodeId) { if let Some(parent_id) = self.tree.parent_id(id) { self.mark_child_changed(parent_id); } @@ -454,52 +219,8 @@ impl RealDom { self.tree.replace(old, new); } - pub fn insert_before(&mut self, id: NodeId, new: NodeId) { - if let Some(parent_id) = self.tree.parent_id(id) { - self.mark_child_changed(parent_id); - self.mark_parent_added_or_removed(new); - } - self.tree.insert_before(id, new); - } - - pub fn insert_after(&mut self, id: NodeId, new: NodeId) { - if let Some(parent_id) = self.tree.parent_id(id) { - self.mark_child_changed(parent_id); - self.mark_parent_added_or_removed(new); - } - self.tree.insert_after(id, new); - } - - pub fn add_event_listener(&mut self, id: NodeId, event: &str) { - let mut node = self.get_mut(id).unwrap(); - if let NodeTypeMut::Element(mut element) = node.node_type_mut() { - element.listeners_mut().insert(event.to_string()); - drop(node); - match self.nodes_listening.get_mut(event) { - Some(hs) => { - hs.insert(id); - } - None => { - let mut hs = FxHashSet::default(); - hs.insert(id); - self.nodes_listening.insert(event.to_string(), hs); - } - } - } - } - - pub fn remove_event_listener(&mut self, id: NodeId, event: &str) { - { - let mut node = self.get_mut(id).unwrap(); - if let NodeTypeMut::Element(mut element) = node.node_type_mut() { - element.listeners_mut().remove(event); - } - } - self.nodes_listening.get_mut(event).unwrap().remove(&id); - } - pub fn traverse_depth_first(&self, mut f: impl FnMut(NodeRef)) { - let mut stack = vec![self.root()]; + let mut stack = vec![self.root_id()]; while let Some(id) = stack.pop() { if let Some(node) = self.get(id) { f(node); @@ -512,7 +233,7 @@ impl RealDom { pub fn traverse_breadth_first(&self, mut f: impl FnMut(NodeRef)) { let mut queue = VecDeque::new(); - queue.push_back(self.root()); + queue.push_back(self.root_id()); while let Some(id) = queue.pop_front() { if let Some(node) = self.get(id) { f(node); @@ -526,7 +247,7 @@ impl RealDom { } pub fn traverse_depth_first_mut(&mut self, mut f: impl FnMut(NodeMut)) { - let mut stack = vec![self.root()]; + let mut stack = vec![self.root_id()]; while let Some(id) = stack.pop() { if let Some(children) = self.tree.children_ids(id) { let children = children.iter().copied().rev().collect::>(); @@ -541,7 +262,7 @@ impl RealDom { pub fn traverse_breadth_first_mut(&mut self, mut f: impl FnMut(NodeMut)) { let mut queue = VecDeque::new(); - queue.push_back(self.root()); + queue.push_back(self.root_id()); while let Some(id) = queue.pop_front() { if let Some(children) = self.tree.children_ids(id) { let children = children.to_vec(); @@ -554,6 +275,10 @@ impl RealDom { } } } + + pub fn insert_slab(&mut self) { + self.tree.insert_slab::(); + } } pub trait IntoNodeId { @@ -566,27 +291,13 @@ impl IntoNodeId for NodeId { } } -impl IntoNodeId for ElementId { - fn into_node_id(self, rdom: &RealDom) -> NodeId { - rdom.element_to_node_id(self) - } -} - pub trait NodeImmutable: Sized { fn real_dom(&self) -> &RealDom; fn id(&self) -> NodeId; - fn mounted_id(&self) -> Option { - self.node_data().mounted_id() - } - - fn node_data(&self) -> &NodeData { - self.get().unwrap() - } - fn node_type(&self) -> &NodeType { - &self.node_data().node_type + self.get().unwrap() } fn get(&self) -> Option<&T> { @@ -621,8 +332,29 @@ pub trait NodeImmutable: Sized { } pub trait NodeMutable: Sized + NodeImmutable { - fn get_mut(&mut self) -> Option<&mut T>; - fn insert(&mut self, value: T); + fn real_dom_mut(&mut self) -> &mut RealDom; + fn get_mut(&mut self) -> Option<&mut T> { + let id = self.id(); + self.real_dom_mut().tree.get_mut(id) + } + fn insert(&mut self, value: T) { + let id = self.id(); + self.real_dom_mut().tree.insert(id, value); + } + fn add_child(&mut self, child: NodeId) { + let id = self.id(); + self.real_dom_mut().tree.add_child(id, child); + } + fn insert_after(&mut self, old: NodeId) { + let id = self.id(); + self.real_dom_mut().tree.insert_after(old, id); + } + fn insert_before(&mut self, old: NodeId) { + let id = self.id(); + self.real_dom_mut().tree.insert_before(old, id); + } + fn add_event_listener(&mut self, event: &str); + fn remove_event_listener(&mut self, event: &str); } #[derive(Clone, Copy)] @@ -647,6 +379,16 @@ pub struct NodeMut<'a, V: FromAnyValue + Send + Sync = ()> { dirty: NodeMask, } +impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { + pub fn new(id: NodeId, dom: &'a mut RealDom) -> Self { + Self { + id, + dom, + dirty: NodeMask::default(), + } + } +} + impl<'a, V: FromAnyValue + Send + Sync> NodeImmutable for NodeMut<'a, V> { fn real_dom(&self) -> &RealDom { self.dom @@ -658,19 +400,78 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeImmutable for NodeMut<'a, V> { } impl<'a, V: FromAnyValue + Send + Sync> NodeMutable for NodeMut<'a, V> { - fn get_mut(&mut self) -> Option<&mut T> { - todo!("get_mut with mark as dirty") + fn real_dom_mut(&mut self) -> &mut RealDom { + self.dom } - fn insert(&mut self, _: T) { - todo!("insert with mark as dirty") + fn get_mut(&mut self) -> Option<&mut T> { + // mark the node state as dirty + self.dom + .passes_updated + .entry(self.id) + .or_default() + .insert(TypeId::of::()); + self.dom.tree.get_mut(self.id) + } + + fn insert(&mut self, value: T) { + // mark the node state as dirty + self.dom + .passes_updated + .entry(self.id) + .or_default() + .insert(TypeId::of::()); + self.dom.tree.insert(self.id, value); + } + + fn insert_after(&mut self, old: NodeId) { + let id = self.id(); + if let Some(parent_id) = self.dom.tree.parent_id(old) { + self.dom.mark_child_changed(parent_id); + self.dom.mark_parent_added_or_removed(id); + } + self.dom.tree.insert_after(old, id); + } + + fn insert_before(&mut self, old: NodeId) { + let id = self.id(); + if let Some(parent_id) = self.dom.tree.parent_id(old) { + self.dom.mark_child_changed(parent_id); + self.dom.mark_parent_added_or_removed(id); + } + self.dom.tree.insert_before(old, id); + } + + fn add_event_listener(&mut self, event: &str) { + let id = self.id(); + if let NodeTypeMut::Element(mut element) = self.node_type_mut() { + element.listeners_mut().insert(event.to_string()); + match self.dom.nodes_listening.get_mut(event) { + Some(hs) => { + hs.insert(id); + } + None => { + let mut hs = FxHashSet::default(); + hs.insert(id); + self.dom.nodes_listening.insert(event.to_string(), hs); + } + } + } + } + + fn remove_event_listener(&mut self, event: &str) { + let id = self.id(); + if let NodeTypeMut::Element(mut element) = self.node_type_mut() { + element.listeners_mut().remove(event); + } + self.dom.nodes_listening.get_mut(event).unwrap().remove(&id); } } impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { pub fn node_type_mut(&mut self) -> NodeTypeMut<'_, V> { let Self { id, dom, dirty } = self; - let node_type = &mut dom.tree.get_mut::>(*id).unwrap().node_type; + let node_type = dom.tree.get_mut::>(*id).unwrap(); match node_type { NodeType::Element(element) => NodeTypeMut::Element(ElementNodeMut { element, dirty }), NodeType::Text(text) => { @@ -682,20 +483,15 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { } pub fn set_type(&mut self, new: NodeType) { - self.dom - .tree - .get_mut::>(self.id) - .unwrap() - .node_type = new; + *self.dom.tree.get_mut::>(self.id).unwrap() = new; self.dirty = NodeMaskBuilder::ALL.build(); } } impl Drop for NodeMut<'_, V> { fn drop(&mut self) { - let node_id = self.node_data().node_id; let mask = std::mem::take(&mut self.dirty); - self.dom.mark_dirty(node_id, mask); + self.dom.mark_dirty(self.id, mask); } } @@ -780,6 +576,12 @@ pub struct NodeMutRaw<'a, V: FromAnyValue + Send + Sync = ()> { dom: &'a mut RealDom, } +impl<'a, V: FromAnyValue + Send + Sync> NodeMutRaw<'a, V> { + fn node_type_mut(&mut self) -> &mut NodeType { + self.dom.tree.get_mut::>(self.id).unwrap() + } +} + impl<'a, V: FromAnyValue + Send + Sync> NodeImmutable for NodeMutRaw<'a, V> { fn real_dom(&self) -> &RealDom { self.dom @@ -791,11 +593,32 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeImmutable for NodeMutRaw<'a, V> { } impl<'a, V: FromAnyValue + Send + Sync> NodeMutable for NodeMutRaw<'a, V> { - fn get_mut(&mut self) -> Option<&mut T> { - self.dom.tree.get_mut::(self.id) + fn real_dom_mut(&mut self) -> &mut RealDom { + self.dom } - fn insert(&mut self, value: T) { - self.dom.tree.insert(self.id, value); + fn add_event_listener(&mut self, event: &str) { + let id = self.id(); + if let NodeType::Element(element) = self.node_type_mut() { + element.listeners.insert(event.to_string()); + match self.dom.nodes_listening.get_mut(event) { + Some(hs) => { + hs.insert(id); + } + None => { + let mut hs = FxHashSet::default(); + hs.insert(id); + self.dom.nodes_listening.insert(event.to_string(), hs); + } + } + } + } + + fn remove_event_listener(&mut self, event: &str) { + let id = self.id(); + if let NodeType::Element(element) = self.node_type_mut() { + element.listeners.remove(event); + } + self.dom.nodes_listening.get_mut(event).unwrap().remove(&id); } } diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index f2d1a07e6..cbb59dab8 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -659,7 +659,7 @@ impl AnySlab { } } -pub struct EntryBuilder<'a> { +pub(crate) struct EntryBuilder<'a> { id: NodeId, inner: &'a mut AnySlab, } @@ -671,18 +671,6 @@ impl EntryBuilder<'_> { .insert(self.id, value); } - pub fn get(&self) -> Option<&T> { - self.inner.read_slab().get(self.id) - } - - pub fn get_mut(&mut self) -> Option<&mut T> { - self.inner.write_slab().get_mut(self.id) - } - - pub fn remove(self) { - self.inner.remove(self.id); - } - pub fn id(&self) -> NodeId { self.id } diff --git a/packages/native-core/src/utils/cursor.rs b/packages/native-core/src/utils/cursor.rs index eaded383e..18fbbb991 100644 --- a/packages/native-core/src/utils/cursor.rs +++ b/packages/native-core/src/utils/cursor.rs @@ -1,6 +1,6 @@ use std::{cmp::Ordering, ops::Range}; -use dioxus_html::input_data::keyboard_types::{Code, Key, Modifiers}; +use keyboard_types::{Code, Key, Modifiers}; /// This contains the information about the text that is used by the cursor to handle navigation. pub trait Text { @@ -221,26 +221,28 @@ impl Cursor { /// Handle moving the cursor with the given key. pub fn handle_input( &mut self, - data: &dioxus_html::KeyboardData, + code: &Code, + key: &Key, + modifiers: &Modifiers, text: &mut impl TextEditable, max_text_length: usize, ) { use Code::*; - match data.code() { + match code { ArrowUp => { self.move_cursor( |c| c.up(text.as_ref()), - data.modifiers().contains(Modifiers::SHIFT), + modifiers.contains(Modifiers::SHIFT), ); } ArrowDown => { self.move_cursor( |c| c.down(text.as_ref()), - data.modifiers().contains(Modifiers::SHIFT), + modifiers.contains(Modifiers::SHIFT), ); } ArrowRight => { - if data.modifiers().contains(Modifiers::CONTROL) { + if modifiers.contains(Modifiers::CONTROL) { self.move_cursor( |c| { let mut change = 1; @@ -255,17 +257,17 @@ impl Cursor { } c.move_col(change as i32, text.as_ref()); }, - data.modifiers().contains(Modifiers::SHIFT), + modifiers.contains(Modifiers::SHIFT), ); } else { self.move_cursor( |c| c.right(text.as_ref()), - data.modifiers().contains(Modifiers::SHIFT), + modifiers.contains(Modifiers::SHIFT), ); } } ArrowLeft => { - if data.modifiers().contains(Modifiers::CONTROL) { + if modifiers.contains(Modifiers::CONTROL) { self.move_cursor( |c| { let mut change = -1; @@ -279,23 +281,23 @@ impl Cursor { } c.move_col(change, text.as_ref()); }, - data.modifiers().contains(Modifiers::SHIFT), + modifiers.contains(Modifiers::SHIFT), ); } else { self.move_cursor( |c| c.left(text.as_ref()), - data.modifiers().contains(Modifiers::SHIFT), + modifiers.contains(Modifiers::SHIFT), ); } } End => { self.move_cursor( |c| c.col = c.len_line(text.as_ref()), - data.modifiers().contains(Modifiers::SHIFT), + modifiers.contains(Modifiers::SHIFT), ); } Home => { - self.move_cursor(|c| c.col = 0, data.modifiers().contains(Modifiers::SHIFT)); + self.move_cursor(|c| c.col = 0, modifiers.contains(Modifiers::SHIFT)); } Backspace => { self.start.realize_col(text.as_ref()); @@ -305,7 +307,7 @@ impl Cursor { } else if start_idx > 0 { self.start.left(text.as_ref()); text.delete_range(start_idx - 1..start_idx); - if data.modifiers().contains(Modifiers::CONTROL) { + if modifiers.contains(Modifiers::CONTROL) { start_idx = self.start.idx(text.as_ref()); while start_idx > 0 && text @@ -340,7 +342,7 @@ impl Cursor { } _ => { self.start.realize_col(text.as_ref()); - if let Key::Character(character) = data.key() { + if let Key::Character(character) = key { if text.as_ref().length() + 1 - self.selection_len(text.as_ref()) <= max_text_length { @@ -460,13 +462,9 @@ fn cursor_input() { 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(), - ), + &keyboard_types::Code::ArrowRight, + &keyboard_types::Key::ArrowRight, + &Modifiers::empty(), &mut text, 10, ); @@ -474,13 +472,9 @@ fn cursor_input() { 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(), - ), + &keyboard_types::Code::Backspace, + &keyboard_types::Key::Backspace, + &Modifiers::empty(), &mut text, 10, ); @@ -491,61 +485,41 @@ fn cursor_input() { 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(), - ), + &keyboard_types::Code::KeyH, + &keyboard_types::Key::Character("h".to_string()), + &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(), - ), + &keyboard_types::Code::KeyE, + &keyboard_types::Key::Character("e".to_string()), + &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(), - ), + &keyboard_types::Code::KeyL, + &keyboard_types::Key::Character("l".to_string()), + &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(), - ), + &keyboard_types::Code::KeyL, + &keyboard_types::Key::Character("l".to_string()), + &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(), - ), + &keyboard_types::Code::KeyO, + &keyboard_types::Key::Character("o".to_string()), + &Modifiers::empty(), &mut text, max_width, ); @@ -553,13 +527,9 @@ fn cursor_input() { // 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(), - ), + &keyboard_types::Code::KeyO, + &keyboard_types::Key::Character("o".to_string()), + &Modifiers::empty(), &mut text, max_width, ); diff --git a/packages/native-core/src/utils/mod.rs b/packages/native-core/src/utils/mod.rs index 58e98b0fa..9ddb1a1c7 100644 --- a/packages/native-core/src/utils/mod.rs +++ b/packages/native-core/src/utils/mod.rs @@ -1,3 +1,3 @@ -mod persistant_iterator; -pub use persistant_iterator::*; +// mod persistant_iterator; +// pub use persistant_iterator::*; pub mod cursor; diff --git a/packages/native-core/src/utils/persistant_iterator.rs b/packages/native-core/src/utils/persistant_iterator.rs index 2fffe14d1..eb2cd1269 100644 --- a/packages/native-core/src/utils/persistant_iterator.rs +++ b/packages/native-core/src/utils/persistant_iterator.rs @@ -13,6 +13,7 @@ pub enum ElementProduced { /// The iterator reached the end of the tree and looped back to the root Looped(NodeId), } + impl ElementProduced { pub fn id(&self) -> NodeId { match self { @@ -141,7 +142,7 @@ impl PersistantElementIter { } else { let (last, old_child_idx) = self.stack.last_mut().unwrap(); let node = rdom.get(*last).unwrap(); - match &node.node_data().node_type { + match node.node_type() { NodeType::Element(ElementNode { .. }) => { let children = node.child_ids().unwrap(); *old_child_idx = old_child_idx.map(|i| i + 1); @@ -153,7 +154,7 @@ impl PersistantElementIter { } else { let id = children[child_idx]; if let NodeType::Element(ElementNode { .. }) = - rdom.get(id).unwrap().node_data().node_type + rdom.get(id).unwrap().node_type() { self.stack.push((id, NodePosition::AtNode)); } @@ -178,7 +179,7 @@ impl PersistantElementIter { rdom: &RealDom, ) -> NodeId { let node = rdom.get(new_node).unwrap(); - match &node.node_data().node_type { + match node.node_type() { NodeType::Element(ElementNode { .. }) => { let children = node.child_ids().unwrap(); if children.is_empty() { @@ -197,7 +198,7 @@ impl PersistantElementIter { } else { let (last, old_child_idx) = self.stack.last_mut().unwrap(); let node = rdom.get(*last).unwrap(); - match &node.node_data().node_type { + match node.node_type() { NodeType::Element(ElementNode { .. }) => { let children = node.child_ids().unwrap(); // if we have children, go to the next child diff --git a/packages/native-core/tests/passes.rs b/packages/native-core/tests/passes.rs new file mode 100644 index 000000000..c9d6b089f --- /dev/null +++ b/packages/native-core/tests/passes.rs @@ -0,0 +1,620 @@ +use dioxus::prelude::*; +use dioxus_native_core::node::NodeType; +use dioxus_native_core::prelude::*; +use std::any::TypeId; +use std::sync::{Arc, Mutex}; + +fn create_blank_element() -> NodeType { + NodeType::Element(ElementNode { + tag: "div".to_owned(), + namespace: None, + attributes: HashMap::new(), + listeners: HashMap::new(), + }) +} + +#[test] +fn node_pass() { + #[derive(Debug, Default, Clone, PartialEq)] + struct Number(i32); + + impl Pass for Number { + type ChildDependencies = (); + type NodeDependencies = (); + type ParentDependencies = (); + const NODE_MASK: NodeMaskBuilder = NodeMaskBuilder::new(); + + fn pass<'a>( + &mut self, + node_view: NodeView, + node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + children: Option::ElementBorrowed<'a>>>, + context: &SendAnyMap, + ) -> bool { + self.0 += 1; + true + } + + fn create<'a>( + node_view: NodeView<()>, + node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + children: Option::ElementBorrowed<'a>>>, + context: &SendAnyMap, + ) -> Self { + let mut myself = Self::default(); + myself.pass(node_view, node, parent, children, context); + myself + } + } + + let mut tree: RealDom = RealDom::new(Box::new([Number::to_type_erased()])); + tree.update_state(SendAnyMap::new(), false); + + assert_eq!(tree.get(tree.root_id()).unwrap().get(), Some(&Number(1))); + + // mark the node as dirty + tree.get_mut(tree.root_id()).unwrap().get_mut::(); + + tree.update_state(SendAnyMap::new(), false); + assert_eq!(tree.get(tree.root_id()).unwrap().get(), Some(&Number(2))); +} + +#[test] +fn dependant_node_pass() { + #[derive(Debug, Default, Clone, PartialEq)] + struct AddNumber(i32); + + impl Pass for AddNumber { + type ChildDependencies = (); + type NodeDependencies = (SubtractNumber,); + type ParentDependencies = (); + const NODE_MASK: NodeMaskBuilder = NodeMaskBuilder::new(); + + fn pass<'a>( + &mut self, + node_view: NodeView, + node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + children: Option::ElementBorrowed<'a>>>, + context: &SendAnyMap, + ) -> bool { + self.0 += 1; + true + } + + fn create<'a>( + node_view: NodeView<()>, + node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + children: Option::ElementBorrowed<'a>>>, + context: &SendAnyMap, + ) -> Self { + let mut myself = Self::default(); + myself.pass(node_view, node, parent, children, context); + myself + } + } + + #[derive(Debug, Default, Clone, PartialEq)] + struct SubtractNumber(i32); + + impl Pass for SubtractNumber { + type ChildDependencies = (); + type NodeDependencies = (); + type ParentDependencies = (); + const NODE_MASK: NodeMaskBuilder = NodeMaskBuilder::new(); + + fn pass<'a>( + &mut self, + node_view: NodeView, + node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + children: Option::ElementBorrowed<'a>>>, + context: &SendAnyMap, + ) -> bool { + self.0 -= 1; + true + } + + fn create<'a>( + node_view: NodeView<()>, + node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + children: Option::ElementBorrowed<'a>>>, + context: &SendAnyMap, + ) -> Self { + let mut myself = Self::default(); + myself.pass(node_view, node, parent, children, context); + myself + } + } + + let mut tree: RealDom = RealDom::new(Box::new([ + AddNumber::to_type_erased(), + SubtractNumber::to_type_erased(), + ])); + tree.update_state(SendAnyMap::new(), false); + + let root = tree.get(tree.root_id()).unwrap(); + assert_eq!(root.get(), Some(&AddNumber(1))); + assert_eq!(root.get(), Some(&SubtractNumber(-1))); + + // mark the subtract state as dirty, it should update the add state + tree.get_mut(tree.root_id()) + .unwrap() + .get_mut::(); + + tree.update_state(SendAnyMap::new(), false); + let root = tree.get(tree.root_id()).unwrap(); + assert_eq!(root.get(), Some(&AddNumber(2))); + assert_eq!(root.get(), Some(&SubtractNumber(-2))); + + // mark the add state as dirty, it should ~not~ update the subtract state + tree.get_mut(tree.root_id()).unwrap().get_mut::(); + + tree.update_state(SendAnyMap::new(), false); + let root = tree.get(tree.root_id()).unwrap(); + assert_eq!(root.get(), Some(&AddNumber(3))); + assert_eq!(root.get(), Some(&SubtractNumber(-2))); +} + +#[test] +fn independant_node_pass() { + #[derive(Debug, Default, Clone, PartialEq)] + struct AddNumber(i32); + + impl Pass for AddNumber { + type ChildDependencies = (); + type NodeDependencies = (); + type ParentDependencies = (); + + const NODE_MASK: NodeMaskBuilder = NodeMaskBuilder::new(); + + fn pass<'a>( + &mut self, + node_view: NodeView, + node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + children: Option::ElementBorrowed<'a>>>, + context: &SendAnyMap, + ) -> bool { + self.0 += 1; + true + } + + fn create<'a>( + node_view: NodeView<()>, + node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + children: Option::ElementBorrowed<'a>>>, + context: &SendAnyMap, + ) -> Self { + let mut myself = Self::default(); + myself.pass(node_view, node, parent, children, context); + myself + } + } + + #[derive(Debug, Default, Clone, PartialEq)] + struct SubtractNumber(i32); + + impl Pass for SubtractNumber { + type ChildDependencies = (); + type NodeDependencies = (); + type ParentDependencies = (); + + const NODE_MASK: NodeMaskBuilder = NodeMaskBuilder::new(); + + fn pass<'a>( + &mut self, + node_view: NodeView, + node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + children: Option::ElementBorrowed<'a>>>, + context: &SendAnyMap, + ) -> bool { + self.0 -= 1; + true + } + + fn create<'a>( + node_view: NodeView<()>, + node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + children: Option::ElementBorrowed<'a>>>, + context: &SendAnyMap, + ) -> Self { + let mut myself = Self::default(); + myself.pass(node_view, node, parent, children, context); + myself + } + } + + let mut tree: RealDom = RealDom::new(Box::new([ + AddNumber::to_type_erased(), + SubtractNumber::to_type_erased(), + ])); + tree.update_state(SendAnyMap::new(), false); + + let root = tree.get(tree.root_id()).unwrap(); + assert_eq!(root.get(), Some(&AddNumber(1))); + assert_eq!(root.get(), Some(&SubtractNumber(-1))); + + // mark the subtract state as dirty, it should ~not~ update the add state + tree.get_mut(tree.root_id()) + .unwrap() + .get_mut::(); + + tree.update_state(SendAnyMap::new(), false); + + let root = tree.get(tree.root_id()).unwrap(); + assert_eq!(root.get(), Some(&AddNumber(1))); + assert_eq!(root.get(), Some(&SubtractNumber(-2))); + + // mark the add state as dirty, it should ~not~ update the subtract state + tree.get_mut(tree.root_id()).unwrap().get_mut::(); + + tree.update_state(SendAnyMap::new(), false); + + let root = tree.get(tree.root_id()).unwrap(); + assert_eq!(root.get(), Some(&AddNumber(2))); + assert_eq!(root.get(), Some(&SubtractNumber(-2))); +} + +#[test] +fn down_pass() { + #[derive(Debug, Clone, PartialEq)] + struct AddNumber(i32); + + impl Default for AddNumber { + fn default() -> Self { + Self(1) + } + } + + impl Pass for AddNumber { + type ChildDependencies = (); + type NodeDependencies = (); + type ParentDependencies = (AddNumber,); + + const NODE_MASK: NodeMaskBuilder = NodeMaskBuilder::new(); + + fn pass<'a>( + &mut self, + node_view: NodeView, + node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + children: Option::ElementBorrowed<'a>>>, + context: &SendAnyMap, + ) -> bool { + if let Some((parent,)) = parent { + *self.0 += *parent.0; + } + true + } + } + + let mut tree: RealDom = RealDom::new(Box::new([AddNumber::to_type_erased()])); + let parent = tree.root_id(); + let child1 = tree.create_node(create_blank_element(), true); + tree.add_child(parent, child1); + let grandchild1 = tree.create_node(create_blank_element(), true); + tree.add_child(child1, grandchild1); + let child2 = tree.create_node(create_blank_element(), true); + tree.add_child(parent, child2); + let grandchild2 = tree.create_node(create_blank_element(), true); + tree.add_child(child2, grandchild2); + + tree.dirty_nodes + .insert(TypeId::of::(), NodeId(0)); + tree.update_state(SendAnyMap::new(), false); + + assert_eq!(tree.get(tree.root_id()).unwrap().state.add_number.0, 1); + assert_eq!(tree.get(child1).unwrap().state.add_number.0, 2); + assert_eq!(tree.get(grandchild1).unwrap().state.add_number.0, 3); + assert_eq!(tree.get(child2).unwrap().state.add_number.0, 2); + assert_eq!(tree.get(grandchild2).unwrap().state.add_number.0, 3); +} + +// #[test] +// fn dependant_down_pass() { +// // 0 +// let mut tree = Tree::new(1); +// let parent = tree.root_id(); +// // 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); + +// 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(0) +// } +// } +// impl DownwardPass for AddPass { +// fn pass(&self, node: &mut i32, parent: Option<&mut i32>, _: &SendAnyMap) -> PassReturn { +// if let Some(parent) = parent { +// *node += *parent; +// } else { +// } +// 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(0) +// } +// } +// impl DownwardPass for SubtractPass { +// fn pass(&self, node: &mut i32, parent: Option<&mut i32>, _: &SendAnyMap) -> PassReturn { +// if let Some(parent) = parent { +// *node -= *parent; +// } else { +// } +// PassReturn { +// progress: true, +// mark_dirty: true, +// } +// } +// } + +// 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_id()); +// resolve_passes(&mut tree, dirty_nodes, passes, SendAnyMap::new()); + +// // 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_id()).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() { +// // Tree before: +// // 0=\ +// // 0=\ +// // 1 +// // 0=\ +// // 1 +// // Tree after: +// // 2=\ +// // 1=\ +// // 1 +// // 1=\ +// // 1 +// let mut tree = Tree::new(0); +// let parent = tree.root_id(); +// 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 Pass for AddPass { +// fn pass_id(&self) -> PassId { +// PassId(0) +// } + +// fn dependancies(&self) -> &'static [PassId] { +// &[] +// } + +// fn dependants(&self) -> &'static [PassId] { +// &[] +// } + +// fn mask(&self) -> MemberMask { +// MemberMask(0) +// } +// } +// impl UpwardPass for AddPass { +// fn pass<'a>( +// &self, +// node: &mut i32, +// children: &mut dyn Iterator, +// _: &SendAnyMap, +// ) -> PassReturn { +// *node += children.map(|i| *i).sum::(); +// PassReturn { +// progress: true, +// mark_dirty: true, +// } +// } +// } + +// 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, SendAnyMap::new()); + +// assert_eq!(tree.get(tree.root_id()).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); +// } + +// #[test] +// fn dependant_up_pass() { +// // 0 +// let mut tree = Tree::new(0); +// let parent = tree.root_id(); +// // 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(0) +// } +// } +// impl UpwardPass for AddPass { +// fn pass<'a>( +// &self, +// node: &mut i32, +// children: &mut dyn Iterator, +// _: &SendAnyMap, +// ) -> 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(0) +// } +// } +// impl UpwardPass for SubtractPass { +// fn pass<'a>( +// &self, +// node: &mut i32, +// children: &mut dyn Iterator, +// _: &SendAnyMap, +// ) -> PassReturn { +// *node -= children.map(|i| *i).sum::(); +// PassReturn { +// progress: true, +// mark_dirty: true, +// } +// } +// } + +// 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, SendAnyMap::new()); + +// // 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_id()).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 e4c7218fe8e7645c54bc1b2fcdff60dc66af4a3f Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 4 Feb 2023 20:45:29 -0600 Subject: [PATCH 035/103] make dioxus an optional dependancy --- packages/native-core/Cargo.toml | 2 +- packages/native-core/src/dioxus.rs | 2 +- packages/native-core/src/node_ref.rs | 14 +- packages/native-core/src/passes.rs | 25 +- packages/native-core/src/real_dom.rs | 214 ++++++----- packages/native-core/src/tree.rs | 10 +- packages/native-core/tests/fuzzing.rs | 11 +- packages/native-core/tests/miri_native.rs | 15 +- packages/native-core/tests/passes.rs | 439 +++++++--------------- 9 files changed, 300 insertions(+), 432 deletions(-) diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index ee2a3b912..2e748563c 100644 --- a/packages/native-core/Cargo.toml +++ b/packages/native-core/Cargo.toml @@ -36,5 +36,5 @@ tokio = { version = "*", features = ["full"] } dioxus-native-core = { path = ".", features = ["dioxus"] } [features] -default = ["dioxus"] +default = [] dioxus = ["dioxus-core"] \ No newline at end of file diff --git a/packages/native-core/src/dioxus.rs b/packages/native-core/src/dioxus.rs index 6170be0d2..8d67d9e2c 100644 --- a/packages/native-core/src/dioxus.rs +++ b/packages/native-core/src/dioxus.rs @@ -15,7 +15,7 @@ pub struct DioxusState { } impl DioxusState { - pub fn new(rdom: &mut RealDom) -> Self { + pub fn create(rdom: &mut RealDom) -> Self { rdom.insert_slab::(); let root_id = rdom.root_id(); let mut root = rdom.get_mut(root_id).unwrap(); diff --git a/packages/native-core/src/node_ref.rs b/packages/native-core/src/node_ref.rs index d8fc309be..99b4c53e2 100644 --- a/packages/native-core/src/node_ref.rs +++ b/packages/native-core/src/node_ref.rs @@ -203,12 +203,12 @@ impl NodeMask { } #[derive(PartialEq, Eq, Clone, Debug)] -pub enum AttributeMaskBuilder { +pub enum AttributeMaskBuilder<'a> { All, - Some(&'static [&'static str]), + Some(&'a [&'a str]), } -impl Default for AttributeMaskBuilder { +impl Default for AttributeMaskBuilder<'_> { fn default() -> Self { AttributeMaskBuilder::Some(&[]) } @@ -216,15 +216,15 @@ impl Default for AttributeMaskBuilder { /// A mask that limits what parts of a node a dependency can see. #[derive(Default, PartialEq, Eq, Clone, Debug)] -pub struct NodeMaskBuilder { - attritutes: AttributeMaskBuilder, +pub struct NodeMaskBuilder<'a> { + attritutes: AttributeMaskBuilder<'a>, tag: bool, namespace: bool, text: bool, listeners: bool, } -impl NodeMaskBuilder { +impl<'a> NodeMaskBuilder<'a> { /// A node mask with no parts visible. pub const NONE: Self = Self::new(); /// A node mask with every part visible. @@ -246,7 +246,7 @@ impl NodeMaskBuilder { } /// Allow the mask to view the given attributes - pub const fn with_attrs(mut self, attritutes: AttributeMaskBuilder) -> Self { + pub const fn with_attrs(mut self, attritutes: AttributeMaskBuilder<'a>) -> Self { self.attritutes = attritutes; self } diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index f5a66aaad..86059508a 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -64,14 +64,15 @@ impl DirtyNodeStates { } pub fn insert(&self, pass_id: TypeId, node_id: NodeId, height: u16) { - let btree = self.dirty.get(&pass_id).unwrap(); - let mut write = btree.write(); - if let Some(entry) = write.get_mut(&height) { - entry.add_node(node_id); - } else { - let mut entry = DirtyNodes::default(); - entry.add_node(node_id); - write.insert(height, entry); + if let Some(btree) = self.dirty.get(&pass_id) { + let mut write = btree.write(); + if let Some(entry) = write.get_mut(&height) { + entry.add_node(node_id); + } else { + let mut entry = DirtyNodes::default(); + entry.add_node(node_id); + write.insert(height, entry); + } } } @@ -109,7 +110,7 @@ pub trait Pass: Any + Send + Sync { type ChildDependencies: Dependancy; /// This is a tuple of (T: Any, ..) type NodeDependencies: Dependancy; - const NODE_MASK: NodeMaskBuilder; + const NODE_MASK: NodeMaskBuilder<'static>; fn pass<'a>( &mut self, @@ -137,12 +138,12 @@ pub trait Pass: Any + Send + Sync { } // this type cannot be both a parent and child dependency assert!( - Self::parent_type_ids() + !(Self::parent_type_ids() .iter() - .any(|type_id| *type_id != TypeId::of::()) + .any(|type_id| *type_id == TypeId::of::()) && Self::child_type_ids() .iter() - .any(|type_id| *type_id != TypeId::of::()), + .any(|type_id| *type_id == TypeId::of::())), "The current type cannot be a parent and child dependency" ); // no states have the same type id diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index bb7d1ec99..8cc4d29de 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -1,15 +1,57 @@ use rustc_hash::{FxHashMap, FxHashSet}; use std::any::{Any, TypeId}; use std::collections::VecDeque; +use std::rc::Rc; use crate::node::{ ElementNode, FromAnyValue, NodeType, OwnedAttributeDiscription, OwnedAttributeValue, }; -use crate::node_ref::{AttributeMask, NodeMask, NodeMaskBuilder}; +use crate::node_ref::{NodeMask, NodeMaskBuilder}; use crate::passes::{resolve_passes, DirtyNodeStates, TypeErasedPass}; +use crate::prelude::AttributeMaskBuilder; use crate::tree::{NodeId, Tree}; use crate::{FxDashSet, SendAnyMap}; +struct NodesDirty { + passes_updated: FxHashMap>, + nodes_updated: FxHashMap, + passes: Rc]>>, +} + +impl NodesDirty { + fn mark_dirty(&mut self, node_id: NodeId, mask: NodeMask) { + self.passes_updated.entry(node_id).or_default().extend( + self.passes + .iter() + .filter_map(|x| x.mask.overlaps(&mask).then_some(x.this_type_id)), + ); + let nodes_updated = &mut self.nodes_updated; + if let Some(node) = nodes_updated.get_mut(&node_id) { + *node = node.union(&mask); + } else { + nodes_updated.insert(node_id, mask); + } + } + + fn mark_parent_added_or_removed(&mut self, node_id: NodeId) { + let hm = self.passes_updated.entry(node_id).or_default(); + for pass in &**self.passes { + if pass.parent_dependant { + hm.insert(pass.this_type_id); + } + } + } + + fn mark_child_changed(&mut self, node_id: NodeId) { + let hm = self.passes_updated.entry(node_id).or_default(); + for pass in &**self.passes { + if pass.child_dependant { + hm.insert(pass.this_type_id); + } + } + } +} + /// 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. @@ -19,9 +61,8 @@ use crate::{FxDashSet, SendAnyMap}; pub struct RealDom { pub(crate) tree: Tree, nodes_listening: FxHashMap>, - pub(crate) passes: Box<[TypeErasedPass]>, - passes_updated: FxHashMap>, - nodes_updated: FxHashMap, + pub(crate) passes: Rc]>>, + dirty_nodes: NodesDirty, phantom: std::marker::PhantomData, } @@ -53,6 +94,7 @@ impl RealDom { } } } + let passes = Rc::new(passes); let mut passes_updated = FxHashMap::default(); let mut nodes_updated = FxHashMap::default(); @@ -64,50 +106,22 @@ impl RealDom { RealDom { tree, nodes_listening: FxHashMap::default(), - passes, - passes_updated, - nodes_updated, + passes: passes.clone(), + dirty_nodes: NodesDirty { + passes_updated, + nodes_updated, + passes, + }, phantom: std::marker::PhantomData, } } - fn mark_dirty(&mut self, node_id: NodeId, mask: NodeMask) { - self.passes_updated.entry(node_id).or_default().extend( - self.passes - .iter() - .filter_map(|x| x.mask.overlaps(&mask).then_some(x.this_type_id)), - ); - let nodes_updated = &mut self.nodes_updated; - if let Some(node) = nodes_updated.get_mut(&node_id) { - *node = node.union(&mask); - } else { - nodes_updated.insert(node_id, mask); - } - } - - fn mark_parent_added_or_removed(&mut self, node_id: NodeId) { - let hm = self.passes_updated.entry(node_id).or_default(); - for pass in &*self.passes { - if pass.parent_dependant { - hm.insert(pass.this_type_id); - } - } - } - - fn mark_child_changed(&mut self, node_id: NodeId) { - let hm = self.passes_updated.entry(node_id).or_default(); - for pass in &*self.passes { - if pass.child_dependant { - hm.insert(pass.this_type_id); - } - } - } - pub fn create_node(&mut self, node: NodeType, mark_dirty: bool) -> NodeMut<'_, V> { let mut node_entry = self.tree.create_node(); let id = node_entry.id(); if mark_dirty { - self.passes_updated + self.dirty_nodes + .passes_updated .entry(id) .or_default() .extend(self.passes.iter().map(|x| x.this_type_id)); @@ -186,8 +200,8 @@ impl RealDom { ctx: SendAnyMap, parallel: bool, ) -> (FxDashSet, FxHashMap) { - let passes = std::mem::take(&mut self.passes_updated); - let nodes_updated = std::mem::take(&mut self.nodes_updated); + let passes = std::mem::take(&mut self.dirty_nodes.passes_updated); + let nodes_updated = std::mem::take(&mut self.dirty_nodes.nodes_updated); let dirty_nodes = DirtyNodeStates::with_passes(self.passes.iter().map(|p| p.this_type_id)); for (node_id, passes) in passes { // remove any nodes that were created and then removed in the same mutations from the dirty nodes list @@ -206,15 +220,15 @@ impl RealDom { pub fn remove(&mut self, id: NodeId) { if let Some(parent_id) = self.tree.parent_id(id) { - self.mark_child_changed(parent_id); + self.dirty_nodes.mark_child_changed(parent_id); } self.tree.remove(id) } pub fn replace(&mut self, old: NodeId, new: NodeId) { if let Some(parent_id) = self.tree.parent_id(old) { - self.mark_child_changed(parent_id); - self.mark_parent_added_or_removed(new); + self.dirty_nodes.mark_child_changed(parent_id); + self.dirty_nodes.mark_parent_added_or_removed(new); } self.tree.replace(old, new); } @@ -376,16 +390,11 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeImmutable for NodeRef<'a, V> { pub struct NodeMut<'a, V: FromAnyValue + Send + Sync = ()> { id: NodeId, dom: &'a mut RealDom, - dirty: NodeMask, } impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { pub fn new(id: NodeId, dom: &'a mut RealDom) -> Self { - Self { - id, - dom, - dirty: NodeMask::default(), - } + Self { id, dom } } } @@ -407,6 +416,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMutable for NodeMut<'a, V> { fn get_mut(&mut self) -> Option<&mut T> { // mark the node state as dirty self.dom + .dirty_nodes .passes_updated .entry(self.id) .or_default() @@ -417,6 +427,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMutable for NodeMut<'a, V> { fn insert(&mut self, value: T) { // mark the node state as dirty self.dom + .dirty_nodes .passes_updated .entry(self.id) .or_default() @@ -427,8 +438,8 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMutable for NodeMut<'a, V> { fn insert_after(&mut self, old: NodeId) { let id = self.id(); if let Some(parent_id) = self.dom.tree.parent_id(old) { - self.dom.mark_child_changed(parent_id); - self.dom.mark_parent_added_or_removed(id); + self.dom.dirty_nodes.mark_child_changed(parent_id); + self.dom.dirty_nodes.mark_parent_added_or_removed(id); } self.dom.tree.insert_after(old, id); } @@ -436,16 +447,20 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMutable for NodeMut<'a, V> { fn insert_before(&mut self, old: NodeId) { let id = self.id(); if let Some(parent_id) = self.dom.tree.parent_id(old) { - self.dom.mark_child_changed(parent_id); - self.dom.mark_parent_added_or_removed(id); + self.dom.dirty_nodes.mark_child_changed(parent_id); + self.dom.dirty_nodes.mark_parent_added_or_removed(id); } self.dom.tree.insert_before(old, id); } fn add_event_listener(&mut self, event: &str) { let id = self.id(); - if let NodeTypeMut::Element(mut element) = self.node_type_mut() { - element.listeners_mut().insert(event.to_string()); + let node_type: &mut NodeType = self.dom.tree.get_mut(self.id).unwrap(); + if let NodeType::Element(ElementNode { listeners, .. }) = node_type { + self.dom + .dirty_nodes + .mark_dirty(self.id, NodeMaskBuilder::new().with_listeners().build()); + listeners.insert(event.to_string()); match self.dom.nodes_listening.get_mut(event) { Some(hs) => { hs.insert(id); @@ -461,21 +476,33 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMutable for NodeMut<'a, V> { fn remove_event_listener(&mut self, event: &str) { let id = self.id(); - if let NodeTypeMut::Element(mut element) = self.node_type_mut() { - element.listeners_mut().remove(event); + let node_type: &mut NodeType = self.dom.tree.get_mut(self.id).unwrap(); + if let NodeType::Element(ElementNode { listeners, .. }) = node_type { + self.dom + .dirty_nodes + .mark_dirty(self.id, NodeMaskBuilder::new().with_listeners().build()); + listeners.remove(event); + + self.dom.nodes_listening.get_mut(event).unwrap().remove(&id); } - self.dom.nodes_listening.get_mut(event).unwrap().remove(&id); } } impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { pub fn node_type_mut(&mut self) -> NodeTypeMut<'_, V> { - let Self { id, dom, dirty } = self; - let node_type = dom.tree.get_mut::>(*id).unwrap(); + let Self { id, dom } = self; + let RealDom { + dirty_nodes, tree, .. + } = dom; + let node_type = tree.get_mut(*id).unwrap(); match node_type { - NodeType::Element(element) => NodeTypeMut::Element(ElementNodeMut { element, dirty }), + NodeType::Element(element) => NodeTypeMut::Element(ElementNodeMut { + id: *id, + element, + dirty_nodes, + }), NodeType::Text(text) => { - dirty.set_text(); + dirty_nodes.mark_dirty(self.id, NodeMaskBuilder::new().with_text().build()); NodeTypeMut::Text(text) } NodeType::Placeholder => NodeTypeMut::Placeholder, @@ -484,35 +511,32 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { pub fn set_type(&mut self, new: NodeType) { *self.dom.tree.get_mut::>(self.id).unwrap() = new; - self.dirty = NodeMaskBuilder::ALL.build(); + self.dom + .dirty_nodes + .mark_dirty(self.id, NodeMaskBuilder::ALL.build()) } } -impl Drop for NodeMut<'_, V> { - fn drop(&mut self) { - let mask = std::mem::take(&mut self.dirty); - self.dom.mark_dirty(self.id, mask); - } -} - -pub enum NodeTypeMut<'a, V: FromAnyValue = ()> { +pub enum NodeTypeMut<'a, V: FromAnyValue + Send + Sync = ()> { Element(ElementNodeMut<'a, V>), Text(&'a mut String), Placeholder, } -pub struct ElementNodeMut<'a, V: FromAnyValue = ()> { +pub struct ElementNodeMut<'a, V: FromAnyValue + Send + Sync = ()> { + id: NodeId, element: &'a mut ElementNode, - dirty: &'a mut NodeMask, + dirty_nodes: &'a mut NodesDirty, } -impl ElementNodeMut<'_, V> { +impl ElementNodeMut<'_, V> { pub fn tag(&self) -> &str { &self.element.tag } pub fn tag_mut(&mut self) -> &mut String { - self.dirty.set_tag(); + self.dirty_nodes + .mark_dirty(self.id, NodeMaskBuilder::new().with_tag().build()); &mut self.element.tag } @@ -521,7 +545,8 @@ impl ElementNodeMut<'_, V> { } pub fn namespace_mut(&mut self) -> &mut Option { - self.dirty.set_namespace(); + self.dirty_nodes + .mark_dirty(self.id, NodeMaskBuilder::new().with_namespace().build()); &mut self.element.namespace } @@ -532,7 +557,12 @@ impl ElementNodeMut<'_, V> { pub fn attributes_mut( &mut self, ) -> &mut FxHashMap> { - self.dirty.add_attributes(AttributeMask::All); + self.dirty_nodes.mark_dirty( + self.id, + NodeMaskBuilder::new() + .with_attrs(AttributeMaskBuilder::All) + .build(), + ); &mut self.element.attributes } @@ -541,7 +571,12 @@ impl ElementNodeMut<'_, V> { name: OwnedAttributeDiscription, value: OwnedAttributeValue, ) -> Option> { - self.dirty.add_attributes(AttributeMask::single(&name.name)); + self.dirty_nodes.mark_dirty( + self.id, + NodeMaskBuilder::new() + .with_attrs(AttributeMaskBuilder::Some(&[&name.name])) + .build(), + ); self.element.attributes.insert(name, value) } @@ -549,7 +584,12 @@ impl ElementNodeMut<'_, V> { &mut self, name: &OwnedAttributeDiscription, ) -> Option> { - self.dirty.add_attributes(AttributeMask::single(&name.name)); + self.dirty_nodes.mark_dirty( + self.id, + NodeMaskBuilder::new() + .with_attrs(AttributeMaskBuilder::Some(&[&name.name])) + .build(), + ); self.element.attributes.remove(name) } @@ -557,18 +597,18 @@ impl ElementNodeMut<'_, V> { &mut self, name: &OwnedAttributeDiscription, ) -> Option<&mut OwnedAttributeValue> { - self.dirty.add_attributes(AttributeMask::single(&name.name)); + self.dirty_nodes.mark_dirty( + self.id, + NodeMaskBuilder::new() + .with_attrs(AttributeMaskBuilder::Some(&[&name.name])) + .build(), + ); self.element.attributes.get_mut(name) } pub fn listeners(&self) -> &FxHashSet { &self.element.listeners } - - pub fn listeners_mut(&mut self) -> &mut FxHashSet { - self.dirty.set_listeners(); - &mut self.element.listeners - } } pub struct NodeMutRaw<'a, V: FromAnyValue + Send + Sync = ()> { diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index cbb59dab8..390ac42b2 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -249,7 +249,13 @@ pub struct TreeStateViewEntry<'a, 'b> { impl<'a, 'b> AnyMapLike<'a> for TreeStateViewEntry<'a, 'b> { fn get(self) -> Option<&'a T> { - self.view.get_slab().and_then(|slab| slab.get(self.id)) + let slab = self.view.get_slab(); + dbg!(slab.is_some()); + slab.and_then(|slab| { + let r = slab.get(self.id); + dbg!(r.is_some()); + r + }) } } @@ -315,7 +321,7 @@ impl<'a, 'b> TreeStateView<'a, 'b> { } pub fn parent(&self, id: NodeId) -> Option> { - T::borrow_elements_from(self.entry(id)) + T::borrow_elements_from(self.entry(self.parent_id(id)?)) } } diff --git a/packages/native-core/tests/fuzzing.rs b/packages/native-core/tests/fuzzing.rs index a32fb849f..49e9656a8 100644 --- a/packages/native-core/tests/fuzzing.rs +++ b/packages/native-core/tests/fuzzing.rs @@ -1,6 +1,7 @@ use dioxus::prelude::Props; use dioxus_core::*; use dioxus_native_core::{ + dioxus::DioxusState, node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}, real_dom::RealDom, Dependancy, Pass, SendAnyMap, @@ -302,7 +303,7 @@ impl Pass for BlablaState { type ChildDependencies = (); type NodeDependencies = (); - const NODE_MASK: NodeMaskBuilder = NodeMaskBuilder::new() + const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new() .with_attrs(AttributeMaskBuilder::Some(&["blabla"])) .with_element(); @@ -348,7 +349,8 @@ fn create() { ); let mutations = vdom.rebuild(); let mut rdom: RealDom = RealDom::new(Box::new([BlablaState::to_type_erased()])); - rdom.apply_mutations(mutations); + let mut dioxus_state = DioxusState::create(&mut rdom); + dioxus_state.apply_mutations(&mut rdom, mutations); let ctx = SendAnyMap::new(); rdom.update_state(ctx, false); @@ -369,13 +371,14 @@ fn diff() { ); let mutations = vdom.rebuild(); let mut rdom: RealDom = RealDom::new(Box::new([BlablaState::to_type_erased()])); - rdom.apply_mutations(mutations); + let mut dioxus_state = DioxusState::create(&mut rdom); + dioxus_state.apply_mutations(&mut rdom, mutations); let ctx = SendAnyMap::new(); rdom.update_state(ctx, false); for _ in 0..10 { let mutations = vdom.render_immediate(); - rdom.apply_mutations(mutations); + dioxus_state.apply_mutations(&mut rdom, mutations); let ctx = SendAnyMap::new(); rdom.update_state(ctx, false); diff --git a/packages/native-core/tests/miri_native.rs b/packages/native-core/tests/miri_native.rs index 23ca8a0e7..ead73b84d 100644 --- a/packages/native-core/tests/miri_native.rs +++ b/packages/native-core/tests/miri_native.rs @@ -1,10 +1,14 @@ use dioxus::prelude::*; use dioxus_native_core::{ + dioxus::DioxusState, node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}, real_dom::RealDom, Dependancy, Pass, SendAnyMap, }; -use std::sync::{Arc, Mutex}; +use std::{ + borrow::BorrowMut, + sync::{Arc, Mutex}, +}; use tokio::time::sleep; #[derive(Debug, Clone, PartialEq, Eq, Default)] @@ -17,7 +21,7 @@ impl Pass for BlablaState { type ChildDependencies = (); type NodeDependencies = (); - const NODE_MASK: NodeMaskBuilder = NodeMaskBuilder::new() + const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new() .with_attrs(AttributeMaskBuilder::Some(&["blabla"])) .with_element(); @@ -135,10 +139,11 @@ fn native_core_is_okay() { let rdom = Arc::new(Mutex::new(RealDom::new(Box::new([ BlablaState::to_type_erased(), ])))); + let mut dioxus_state = DioxusState::create(&mut *rdom.lock().unwrap()); let mut dom = VirtualDom::new(app); - let muts = dom.rebuild(); - rdom.lock().unwrap().apply_mutations(muts); + let mutations = dom.rebuild(); + dioxus_state.apply_mutations(&mut *rdom.lock().unwrap(), mutations); let ctx = SendAnyMap::new(); rdom.lock().unwrap().update_state(ctx, false); @@ -147,7 +152,7 @@ fn native_core_is_okay() { dom.wait_for_work().await; let mutations = dom.render_immediate(); - rdom.lock().unwrap().apply_mutations(mutations); + dioxus_state.apply_mutations(&mut *rdom.lock().unwrap(), mutations); let ctx = SendAnyMap::new(); rdom.lock().unwrap().update_state(ctx, false); diff --git a/packages/native-core/tests/passes.rs b/packages/native-core/tests/passes.rs index c9d6b089f..ab104f5e5 100644 --- a/packages/native-core/tests/passes.rs +++ b/packages/native-core/tests/passes.rs @@ -1,6 +1,7 @@ use dioxus::prelude::*; use dioxus_native_core::node::NodeType; use dioxus_native_core::prelude::*; +use rustc_hash::{FxHashMap, FxHashSet}; use std::any::TypeId; use std::sync::{Arc, Mutex}; @@ -8,8 +9,8 @@ fn create_blank_element() -> NodeType { NodeType::Element(ElementNode { tag: "div".to_owned(), namespace: None, - attributes: HashMap::new(), - listeners: HashMap::new(), + attributes: FxHashMap::default(), + listeners: FxHashSet::default(), }) } @@ -22,7 +23,7 @@ fn node_pass() { type ChildDependencies = (); type NodeDependencies = (); type ParentDependencies = (); - const NODE_MASK: NodeMaskBuilder = NodeMaskBuilder::new(); + const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new(); fn pass<'a>( &mut self, @@ -70,7 +71,7 @@ fn dependant_node_pass() { type ChildDependencies = (); type NodeDependencies = (SubtractNumber,); type ParentDependencies = (); - const NODE_MASK: NodeMaskBuilder = NodeMaskBuilder::new(); + const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new(); fn pass<'a>( &mut self, @@ -104,7 +105,7 @@ fn dependant_node_pass() { type ChildDependencies = (); type NodeDependencies = (); type ParentDependencies = (); - const NODE_MASK: NodeMaskBuilder = NodeMaskBuilder::new(); + const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new(); fn pass<'a>( &mut self, @@ -170,7 +171,7 @@ fn independant_node_pass() { type NodeDependencies = (); type ParentDependencies = (); - const NODE_MASK: NodeMaskBuilder = NodeMaskBuilder::new(); + const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new(); fn pass<'a>( &mut self, @@ -205,7 +206,7 @@ fn independant_node_pass() { type NodeDependencies = (); type ParentDependencies = (); - const NODE_MASK: NodeMaskBuilder = NodeMaskBuilder::new(); + const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new(); fn pass<'a>( &mut self, @@ -279,7 +280,7 @@ fn down_pass() { type NodeDependencies = (); type ParentDependencies = (AddNumber,); - const NODE_MASK: NodeMaskBuilder = NodeMaskBuilder::new(); + const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new(); fn pass<'a>( &mut self, @@ -289,332 +290,144 @@ fn down_pass() { children: Option::ElementBorrowed<'a>>>, context: &SendAnyMap, ) -> bool { + dbg!(parent); + dbg!(node_view); if let Some((parent,)) = parent { - *self.0 += *parent.0; + self.0 += parent.0; } true } + + fn create<'a>( + node_view: NodeView<()>, + node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + children: Option::ElementBorrowed<'a>>>, + context: &SendAnyMap, + ) -> Self { + let mut myself = Self::default(); + myself.pass(node_view, node, parent, children, context); + myself + } } let mut tree: RealDom = RealDom::new(Box::new([AddNumber::to_type_erased()])); - let parent = tree.root_id(); - let child1 = tree.create_node(create_blank_element(), true); - tree.add_child(parent, child1); - let grandchild1 = tree.create_node(create_blank_element(), true); - tree.add_child(child1, grandchild1); - let child2 = tree.create_node(create_blank_element(), true); - tree.add_child(parent, child2); - let grandchild2 = tree.create_node(create_blank_element(), true); - tree.add_child(child2, grandchild2); + let mut grandchild1 = tree.create_node(create_blank_element(), true); + let grandchild1 = grandchild1.id(); + let mut child1 = tree.create_node(create_blank_element(), true); + child1.add_child(grandchild1); + let child1 = child1.id(); + let mut grandchild2 = tree.create_node(create_blank_element(), true); + let grandchild2 = grandchild2.id(); + let mut child2 = tree.create_node(create_blank_element(), true); + child2.add_child(grandchild2); + let child2 = child2.id(); + let mut parent = tree.get_mut(tree.root_id()).unwrap(); + parent.add_child(child1); + parent.add_child(child2); - tree.dirty_nodes - .insert(TypeId::of::(), NodeId(0)); tree.update_state(SendAnyMap::new(), false); - assert_eq!(tree.get(tree.root_id()).unwrap().state.add_number.0, 1); - assert_eq!(tree.get(child1).unwrap().state.add_number.0, 2); - assert_eq!(tree.get(grandchild1).unwrap().state.add_number.0, 3); - assert_eq!(tree.get(child2).unwrap().state.add_number.0, 2); - assert_eq!(tree.get(grandchild2).unwrap().state.add_number.0, 3); + let root = tree.get(tree.root_id()).unwrap(); + dbg!(root.id()); + assert_eq!(root.get(), Some(&AddNumber(1))); + + let child1 = tree.get(child1).unwrap(); + dbg!(child1.id()); + dbg!(child1.parent().unwrap().get::()); + assert_eq!(child1.get(), Some(&AddNumber(2))); + + let grandchild1 = tree.get(grandchild1).unwrap(); + assert_eq!(grandchild1.get(), Some(&AddNumber(3))); + + let child2 = tree.get(child2).unwrap(); + assert_eq!(child2.get(), Some(&AddNumber(2))); + + let grandchild2 = tree.get(grandchild2).unwrap(); + assert_eq!(grandchild2.get(), Some(&AddNumber(3))); } -// #[test] -// fn dependant_down_pass() { -// // 0 -// let mut tree = Tree::new(1); -// let parent = tree.root_id(); -// // 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); +#[test] +fn up_pass() { + // Tree before: + // 1=\ + // 1=\ + // 1 + // 1=\ + // 1 + // Tree after: + // 4=\ + // 2=\ + // 1 + // 2=\ + // 1 -// struct AddPass; -// impl Pass for AddPass { -// fn pass_id(&self) -> PassId { -// PassId(0) -// } + #[derive(Debug, Clone, PartialEq)] + struct AddNumber(i32); -// fn dependancies(&self) -> &'static [PassId] { -// &[PassId(1)] -// } + impl Pass for AddNumber { + type ChildDependencies = (AddNumber,); + type NodeDependencies = (); + type ParentDependencies = (); -// fn dependants(&self) -> &'static [PassId] { -// &[] -// } + const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new(); -// fn mask(&self) -> MemberMask { -// MemberMask(0) -// } -// } -// impl DownwardPass for AddPass { -// fn pass(&self, node: &mut i32, parent: Option<&mut i32>, _: &SendAnyMap) -> PassReturn { -// if let Some(parent) = parent { -// *node += *parent; -// } else { -// } -// PassReturn { -// progress: true, -// mark_dirty: true, -// } -// } -// } + fn pass<'a>( + &mut self, + node_view: NodeView, + node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + children: Option::ElementBorrowed<'a>>>, + context: &SendAnyMap, + ) -> bool { + if let Some(children) = children { + self.0 += children.iter().map(|(i,)| i.0).sum::(); + } + true + } -// struct SubtractPass; -// impl Pass for SubtractPass { -// fn pass_id(&self) -> PassId { -// PassId(1) -// } + fn create<'a>( + node_view: NodeView<()>, + node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + children: Option::ElementBorrowed<'a>>>, + context: &SendAnyMap, + ) -> Self { + let mut myself = Self(1); + myself.pass(node_view, node, parent, children, context); + myself + } + } -// fn dependancies(&self) -> &'static [PassId] { -// &[] -// } + let mut tree: RealDom = RealDom::new(Box::new([AddNumber::to_type_erased()])); + let mut grandchild1 = tree.create_node(create_blank_element(), true); + let grandchild1 = grandchild1.id(); + let mut child1 = tree.create_node(create_blank_element(), true); + child1.add_child(grandchild1); + let child1 = child1.id(); + let mut grandchild2 = tree.create_node(create_blank_element(), true); + let grandchild2 = grandchild2.id(); + let mut child2 = tree.create_node(create_blank_element(), true); + child2.add_child(grandchild2); + let child2 = child2.id(); + let mut parent = tree.get_mut(tree.root_id()).unwrap(); + parent.add_child(child1); + parent.add_child(child2); -// fn dependants(&self) -> &'static [PassId] { -// &[PassId(0)] -// } + tree.update_state(SendAnyMap::new(), false); -// fn mask(&self) -> MemberMask { -// MemberMask(0) -// } -// } -// impl DownwardPass for SubtractPass { -// fn pass(&self, node: &mut i32, parent: Option<&mut i32>, _: &SendAnyMap) -> PassReturn { -// if let Some(parent) = parent { -// *node -= *parent; -// } else { -// } -// PassReturn { -// progress: true, -// mark_dirty: true, -// } -// } -// } + let root = tree.get(tree.root_id()).unwrap(); + assert_eq!(root.get(), Some(&AddNumber(5))); -// 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_id()); -// resolve_passes(&mut tree, dirty_nodes, passes, SendAnyMap::new()); + let child1 = tree.get(child1).unwrap(); + assert_eq!(child1.get(), Some(&AddNumber(2))); -// // 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_id()).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); -// } + let grandchild1 = tree.get(grandchild1).unwrap(); + assert_eq!(grandchild1.get(), Some(&AddNumber(1))); -// #[test] -// fn up_pass() { -// // Tree before: -// // 0=\ -// // 0=\ -// // 1 -// // 0=\ -// // 1 -// // Tree after: -// // 2=\ -// // 1=\ -// // 1 -// // 1=\ -// // 1 -// let mut tree = Tree::new(0); -// let parent = tree.root_id(); -// 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); + let child2 = tree.get(child2).unwrap(); + assert_eq!(child2.get(), Some(&AddNumber(2))); -// struct AddPass; -// impl Pass for AddPass { -// fn pass_id(&self) -> PassId { -// PassId(0) -// } - -// fn dependancies(&self) -> &'static [PassId] { -// &[] -// } - -// fn dependants(&self) -> &'static [PassId] { -// &[] -// } - -// fn mask(&self) -> MemberMask { -// MemberMask(0) -// } -// } -// impl UpwardPass for AddPass { -// fn pass<'a>( -// &self, -// node: &mut i32, -// children: &mut dyn Iterator, -// _: &SendAnyMap, -// ) -> PassReturn { -// *node += children.map(|i| *i).sum::(); -// PassReturn { -// progress: true, -// mark_dirty: true, -// } -// } -// } - -// 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, SendAnyMap::new()); - -// assert_eq!(tree.get(tree.root_id()).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); -// } - -// #[test] -// fn dependant_up_pass() { -// // 0 -// let mut tree = Tree::new(0); -// let parent = tree.root_id(); -// // 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(0) -// } -// } -// impl UpwardPass for AddPass { -// fn pass<'a>( -// &self, -// node: &mut i32, -// children: &mut dyn Iterator, -// _: &SendAnyMap, -// ) -> 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(0) -// } -// } -// impl UpwardPass for SubtractPass { -// fn pass<'a>( -// &self, -// node: &mut i32, -// children: &mut dyn Iterator, -// _: &SendAnyMap, -// ) -> PassReturn { -// *node -= children.map(|i| *i).sum::(); -// PassReturn { -// progress: true, -// mark_dirty: true, -// } -// } -// } - -// 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, SendAnyMap::new()); - -// // 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_id()).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); -// } + let grandchild2 = tree.get(grandchild2).unwrap(); + assert_eq!(grandchild2.get(), Some(&AddNumber(1))); +} From 9dc63e67c9100a2d8d25afe5dd03350699db0d5f Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 5 Feb 2023 09:35:32 -0600 Subject: [PATCH 036/103] add minimally called test --- .../tests/called_minimally_on_build.rs | 242 ++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 packages/native-core/tests/called_minimally_on_build.rs diff --git a/packages/native-core/tests/called_minimally_on_build.rs b/packages/native-core/tests/called_minimally_on_build.rs new file mode 100644 index 000000000..da0675688 --- /dev/null +++ b/packages/native-core/tests/called_minimally_on_build.rs @@ -0,0 +1,242 @@ +use dioxus::prelude::*; +use dioxus_core::*; +use dioxus_native_core::prelude::*; +use dioxus_native_core::{ + dioxus::DioxusState, + node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}, + real_dom::RealDom, + Dependancy, Pass, SendAnyMap, +}; +use rustc_hash::{FxHashMap, FxHashSet}; +use std::any::TypeId; +use std::sync::{Arc, Mutex}; + +macro_rules! dep { + ( child( $name:ty, $dep:ty ) ) => { + impl Pass for $name { + type ParentDependencies = (); + type ChildDependencies = $dep; + type NodeDependencies = (); + + const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::ALL; + + fn pass<'a>( + &mut self, + _: NodeView, + _: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + _: Option::ElementBorrowed<'a>>>, + _: &SendAnyMap, + ) -> bool { + self.0 += 1; + true + } + + fn create<'a>( + node_view: NodeView<()>, + node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + children: Option::ElementBorrowed<'a>>>, + context: &SendAnyMap, + ) -> Self { + let mut myself = Self::default(); + myself.pass(node_view, node, parent, children, context); + myself + } + } + }; + + ( parent( $name:ty, $dep:ty ) ) => { + impl Pass for $name { + type ParentDependencies = $dep; + type ChildDependencies = (); + type NodeDependencies = (); + + const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::ALL; + + fn pass<'a>( + &mut self, + _: NodeView, + _: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + _: Option::ElementBorrowed<'a>>>, + _: &SendAnyMap, + ) -> bool { + self.0 += 1; + true + } + + fn create<'a>( + node_view: NodeView<()>, + node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + children: Option::ElementBorrowed<'a>>>, + context: &SendAnyMap, + ) -> Self { + let mut myself = Self::default(); + myself.pass(node_view, node, parent, children, context); + myself + } + } + }; + + ( node( $name:ty, $dep:ty ) ) => { + impl Pass for $name { + type ParentDependencies = $dep; + type ChildDependencies = (); + type NodeDependencies = (); + + const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::ALL; + + fn pass<'a>( + &mut self, + _: NodeView, + _: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + _: Option::ElementBorrowed<'a>>>, + _: &SendAnyMap, + ) -> bool { + self.0 += 1; + true + } + + fn create<'a>( + node_view: NodeView<()>, + node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + children: Option::ElementBorrowed<'a>>>, + context: &SendAnyMap, + ) -> Self { + let mut myself = Self::default(); + myself.pass(node_view, node, parent, children, context); + myself + } + } + }; +} + +macro_rules! test_state{ + ( state: ( $( $state:ty ),* ) ) => { + #[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 mut vdom = VirtualDom::new(Base); + + let mutations = vdom.rebuild(); + + let mut dom: RealDom = RealDom::new(Box::new([$( <$state>::to_type_erased() ),*])); + let mut dioxus_state = DioxusState::create(&mut dom); + + dioxus_state.apply_mutations(&mut dom, mutations); + dom.update_state(SendAnyMap::new(), false); + + dom.traverse_depth_first(|n| { + $( + assert_eq!(n.get::<$state>().unwrap().0, 1); + )* + }); + } + } +} + +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 Parent(i32); + dep!(parent(Parent, (Parent,))); + + test_state!(state: (Child, 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 Parent(i32); + dep!(parent(Parent, (Parent,))); + + test_state!(state: (Child, Node, Parent)); +} + +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 Parent(i32); + dep!(parent(Parent, (Node,))); + + test_state!(state: (Child, Node, Parent)); +} + +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, ())); + + test_state!(state: (Node1, Node2)); +} + +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 Parent(i32); + dep!(parent(Parent, (Parent,))); + + test_state!(state: (Child, Node, Parent)); +} From 93f64d91c9c4ad851ccdee51b8d25d4b436beda4 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 5 Feb 2023 09:39:55 -0600 Subject: [PATCH 037/103] fix some lints in tests --- .../tests/called_minimally_on_build.rs | 12 +-- packages/native-core/tests/miri_native.rs | 5 +- packages/native-core/tests/passes.rs | 79 +++++++++---------- 3 files changed, 42 insertions(+), 54 deletions(-) diff --git a/packages/native-core/tests/called_minimally_on_build.rs b/packages/native-core/tests/called_minimally_on_build.rs index da0675688..44b8a43f9 100644 --- a/packages/native-core/tests/called_minimally_on_build.rs +++ b/packages/native-core/tests/called_minimally_on_build.rs @@ -1,15 +1,11 @@ use dioxus::prelude::*; -use dioxus_core::*; use dioxus_native_core::prelude::*; use dioxus_native_core::{ dioxus::DioxusState, - node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}, + node_ref::{NodeMaskBuilder, NodeView}, real_dom::RealDom, Dependancy, Pass, SendAnyMap, }; -use rustc_hash::{FxHashMap, FxHashSet}; -use std::any::TypeId; -use std::sync::{Arc, Mutex}; macro_rules! dep { ( child( $name:ty, $dep:ty ) ) => { @@ -24,7 +20,7 @@ macro_rules! dep { &mut self, _: NodeView, _: ::ElementBorrowed<'a>, - parent: Option<::ElementBorrowed<'a>>, + _: Option<::ElementBorrowed<'a>>, _: Option::ElementBorrowed<'a>>>, _: &SendAnyMap, ) -> bool { @@ -58,7 +54,7 @@ macro_rules! dep { &mut self, _: NodeView, _: ::ElementBorrowed<'a>, - parent: Option<::ElementBorrowed<'a>>, + _: Option<::ElementBorrowed<'a>>, _: Option::ElementBorrowed<'a>>>, _: &SendAnyMap, ) -> bool { @@ -92,7 +88,7 @@ macro_rules! dep { &mut self, _: NodeView, _: ::ElementBorrowed<'a>, - parent: Option<::ElementBorrowed<'a>>, + _: Option<::ElementBorrowed<'a>>, _: Option::ElementBorrowed<'a>>>, _: &SendAnyMap, ) -> bool { diff --git a/packages/native-core/tests/miri_native.rs b/packages/native-core/tests/miri_native.rs index ead73b84d..384078672 100644 --- a/packages/native-core/tests/miri_native.rs +++ b/packages/native-core/tests/miri_native.rs @@ -5,10 +5,6 @@ use dioxus_native_core::{ real_dom::RealDom, Dependancy, Pass, SendAnyMap, }; -use std::{ - borrow::BorrowMut, - sync::{Arc, Mutex}, -}; use tokio::time::sleep; #[derive(Debug, Clone, PartialEq, Eq, Default)] @@ -99,6 +95,7 @@ mod dioxus_elements { #[test] fn native_core_is_okay() { + use std::sync::{Arc, Mutex}; use std::time::Duration; fn app(cx: Scope) -> Element { diff --git a/packages/native-core/tests/passes.rs b/packages/native-core/tests/passes.rs index ab104f5e5..f0023ae35 100644 --- a/packages/native-core/tests/passes.rs +++ b/packages/native-core/tests/passes.rs @@ -1,9 +1,6 @@ -use dioxus::prelude::*; use dioxus_native_core::node::NodeType; use dioxus_native_core::prelude::*; use rustc_hash::{FxHashMap, FxHashSet}; -use std::any::TypeId; -use std::sync::{Arc, Mutex}; fn create_blank_element() -> NodeType { NodeType::Element(ElementNode { @@ -27,11 +24,11 @@ fn node_pass() { fn pass<'a>( &mut self, - node_view: NodeView, - node: ::ElementBorrowed<'a>, - parent: Option<::ElementBorrowed<'a>>, - children: Option::ElementBorrowed<'a>>>, - context: &SendAnyMap, + _: NodeView, + _: ::ElementBorrowed<'a>, + _: Option<::ElementBorrowed<'a>>, + _: Option::ElementBorrowed<'a>>>, + _: &SendAnyMap, ) -> bool { self.0 += 1; true @@ -75,11 +72,11 @@ fn dependant_node_pass() { fn pass<'a>( &mut self, - node_view: NodeView, - node: ::ElementBorrowed<'a>, - parent: Option<::ElementBorrowed<'a>>, - children: Option::ElementBorrowed<'a>>>, - context: &SendAnyMap, + _: NodeView, + _: ::ElementBorrowed<'a>, + _: Option<::ElementBorrowed<'a>>, + _: Option::ElementBorrowed<'a>>>, + _: &SendAnyMap, ) -> bool { self.0 += 1; true @@ -109,11 +106,11 @@ fn dependant_node_pass() { fn pass<'a>( &mut self, - node_view: NodeView, - node: ::ElementBorrowed<'a>, - parent: Option<::ElementBorrowed<'a>>, - children: Option::ElementBorrowed<'a>>>, - context: &SendAnyMap, + _: NodeView, + _: ::ElementBorrowed<'a>, + _: Option<::ElementBorrowed<'a>>, + _: Option::ElementBorrowed<'a>>>, + _: &SendAnyMap, ) -> bool { self.0 -= 1; true @@ -175,11 +172,11 @@ fn independant_node_pass() { fn pass<'a>( &mut self, - node_view: NodeView, - node: ::ElementBorrowed<'a>, - parent: Option<::ElementBorrowed<'a>>, - children: Option::ElementBorrowed<'a>>>, - context: &SendAnyMap, + _: NodeView, + _: ::ElementBorrowed<'a>, + _: Option<::ElementBorrowed<'a>>, + _: Option::ElementBorrowed<'a>>>, + _: &SendAnyMap, ) -> bool { self.0 += 1; true @@ -210,11 +207,11 @@ fn independant_node_pass() { fn pass<'a>( &mut self, - node_view: NodeView, - node: ::ElementBorrowed<'a>, - parent: Option<::ElementBorrowed<'a>>, - children: Option::ElementBorrowed<'a>>>, - context: &SendAnyMap, + _: NodeView, + _: ::ElementBorrowed<'a>, + _: Option<::ElementBorrowed<'a>>, + _: Option::ElementBorrowed<'a>>>, + _: &SendAnyMap, ) -> bool { self.0 -= 1; true @@ -284,14 +281,12 @@ fn down_pass() { fn pass<'a>( &mut self, - node_view: NodeView, - node: ::ElementBorrowed<'a>, + _: NodeView, + _: ::ElementBorrowed<'a>, parent: Option<::ElementBorrowed<'a>>, - children: Option::ElementBorrowed<'a>>>, - context: &SendAnyMap, + _: Option::ElementBorrowed<'a>>>, + _: &SendAnyMap, ) -> bool { - dbg!(parent); - dbg!(node_view); if let Some((parent,)) = parent { self.0 += parent.0; } @@ -312,12 +307,12 @@ fn down_pass() { } let mut tree: RealDom = RealDom::new(Box::new([AddNumber::to_type_erased()])); - let mut grandchild1 = tree.create_node(create_blank_element(), true); + let grandchild1 = tree.create_node(create_blank_element(), true); let grandchild1 = grandchild1.id(); let mut child1 = tree.create_node(create_blank_element(), true); child1.add_child(grandchild1); let child1 = child1.id(); - let mut grandchild2 = tree.create_node(create_blank_element(), true); + let grandchild2 = tree.create_node(create_blank_element(), true); let grandchild2 = grandchild2.id(); let mut child2 = tree.create_node(create_blank_element(), true); child2.add_child(grandchild2); @@ -374,11 +369,11 @@ fn up_pass() { fn pass<'a>( &mut self, - node_view: NodeView, - node: ::ElementBorrowed<'a>, - parent: Option<::ElementBorrowed<'a>>, + _: NodeView, + _: ::ElementBorrowed<'a>, + _: Option<::ElementBorrowed<'a>>, children: Option::ElementBorrowed<'a>>>, - context: &SendAnyMap, + _: &SendAnyMap, ) -> bool { if let Some(children) = children { self.0 += children.iter().map(|(i,)| i.0).sum::(); @@ -400,12 +395,12 @@ fn up_pass() { } let mut tree: RealDom = RealDom::new(Box::new([AddNumber::to_type_erased()])); - let mut grandchild1 = tree.create_node(create_blank_element(), true); + let grandchild1 = tree.create_node(create_blank_element(), true); let grandchild1 = grandchild1.id(); let mut child1 = tree.create_node(create_blank_element(), true); child1.add_child(grandchild1); let child1 = child1.id(); - let mut grandchild2 = tree.create_node(create_blank_element(), true); + let grandchild2 = tree.create_node(create_blank_element(), true); let grandchild2 = grandchild2.id(); let mut child2 = tree.create_node(create_blank_element(), true); child2.add_child(grandchild2); From bd07d7754c0de5e7ca366bca9b0fdcdbdb642818 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 6 Feb 2023 13:40:22 -0600 Subject: [PATCH 038/103] create node watcher API --- packages/native-core/src/dioxus.rs | 16 +- packages/native-core/src/lib.rs | 3 +- packages/native-core/src/node_watcher.rs | 15 + packages/native-core/src/passes.rs | 6 +- packages/native-core/src/real_dom.rs | 298 +++++++++--------- packages/native-core/src/tree.rs | 12 + packages/native-core/src/utils/mod.rs | 4 +- .../src/utils/persistant_iterator.rs | 263 ++++++---------- packages/native-core/tests/passes.rs | 16 +- 9 files changed, 294 insertions(+), 339 deletions(-) create mode 100644 packages/native-core/src/node_watcher.rs diff --git a/packages/native-core/src/dioxus.rs b/packages/native-core/src/dioxus.rs index 8d67d9e2c..d5dc59e2b 100644 --- a/packages/native-core/src/dioxus.rs +++ b/packages/native-core/src/dioxus.rs @@ -77,14 +77,14 @@ impl DioxusState { } CreatePlaceholder { id } => { let node = NodeType::Placeholder; - let node = rdom.create_node(node, true); + let node = rdom.create_node(node); let node_id = node.id(); self.set_element_id(node, id); self.stack.push(node_id); } CreateTextNode { value, id } => { let node_data = NodeType::Text(value.to_string()); - let node = rdom.create_node(node_data, true); + let node = rdom.create_node(node_data); let node_id = node.id(); self.set_element_id(node, id); self.stack.push(node_id); @@ -228,20 +228,16 @@ fn create_template_node(rdom: &mut RealDom, node: &TemplateNode) -> NodeId { .collect(), listeners: FxHashSet::default(), }); - let node_id = rdom.create_node(node, true).id(); + let node_id = rdom.create_node(node).id(); for child in *children { let child_id = create_template_node(rdom, child); rdom.add_child(node_id, child_id); } node_id } - TemplateNode::Text { text } => rdom - .create_node(NodeType::Text(text.to_string()), true) - .id(), - TemplateNode::Dynamic { .. } => rdom.create_node(NodeType::Placeholder, true).id(), - TemplateNode::DynamicText { .. } => { - rdom.create_node(NodeType::Text(String::new()), true).id() - } + TemplateNode::Text { text } => rdom.create_node(NodeType::Text(text.to_string())).id(), + TemplateNode::Dynamic { .. } => rdom.create_node(NodeType::Placeholder).id(), + TemplateNode::DynamicText { .. } => rdom.create_node(NodeType::Text(String::new())).id(), } } diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 5e07f68aa..05e9a2c93 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -13,6 +13,7 @@ pub mod dioxus; pub mod layout_attributes; pub mod node; pub mod node_ref; +pub mod node_watcher; mod passes; pub mod real_dom; pub mod tree; @@ -22,7 +23,7 @@ pub mod prelude { pub use crate::node::{ElementNode, FromAnyValue, NodeType, OwnedAttributeView}; pub use crate::node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}; pub use crate::passes::{Dependancy, Pass}; - pub use crate::real_dom::{NodeImmutable, NodeMut, NodeMutRaw, NodeMutable, NodeRef, RealDom}; + pub use crate::real_dom::{NodeImmutable, NodeMut, NodeRef, RealDom}; pub use crate::tree::NodeId; pub use crate::SendAnyMap; } diff --git a/packages/native-core/src/node_watcher.rs b/packages/native-core/src/node_watcher.rs new file mode 100644 index 000000000..b9b79ebab --- /dev/null +++ b/packages/native-core/src/node_watcher.rs @@ -0,0 +1,15 @@ +use crate::{node::FromAnyValue, NodeMut}; + +/// A trait for watching for changes in the DOM tree. +pub trait NodeWatcher { + /// Called after a node is added to the tree. + fn on_node_added(&self, _node: NodeMut) {} + /// Called before a node is removed from the tree. + fn on_node_removed(&self, _node: NodeMut) {} + /// Called after a node is moved to a new parent. + fn on_node_moved(&self, _node: NodeMut) {} + // /// Called after the text content of a node is changed. + // fn on_text_changed(&self, _node: NodeMut) {} + // /// Called after an attribute of an element is changed. + // fn on_attribute_changed(&self, _node: NodeMut, attribute: &str) {} +} diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index 86059508a..761e4a72a 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -380,12 +380,12 @@ impl_dependancy!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O); impl_dependancy!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P); pub fn resolve_passes( - tree: &mut RealDom, + dom: &mut RealDom, dirty_nodes: DirtyNodeStates, ctx: SendAnyMap, parallel: bool, ) -> FxDashSet { - let passes = &tree.passes; + let passes = &dom.dirty_nodes.passes; let mut resolved_passes: FxHashSet = FxHashSet::default(); let mut resolving = Vec::new(); let nodes_updated = Arc::new(FxDashSet::default()); @@ -393,7 +393,7 @@ pub fn resolve_passes( let mut pass_indexes_remaining: Vec<_> = (0..passes.len()).collect::>(); while !pass_indexes_remaining.is_empty() { let mut currently_in_use = FxHashSet::::default(); - let dynamically_borrowed_tree = tree.tree.dynamically_borrowed(); + let dynamically_borrowed_tree = dom.tree.dynamically_borrowed(); rayon::in_place_scope(|s| { let mut i = 0; while i < pass_indexes_remaining.len() { diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 8cc4d29de..f466d2803 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -2,20 +2,22 @@ use rustc_hash::{FxHashMap, FxHashSet}; use std::any::{Any, TypeId}; use std::collections::VecDeque; use std::rc::Rc; +use std::sync::RwLock; use crate::node::{ ElementNode, FromAnyValue, NodeType, OwnedAttributeDiscription, OwnedAttributeValue, }; use crate::node_ref::{NodeMask, NodeMaskBuilder}; +use crate::node_watcher::NodeWatcher; use crate::passes::{resolve_passes, DirtyNodeStates, TypeErasedPass}; use crate::prelude::AttributeMaskBuilder; use crate::tree::{NodeId, Tree}; use crate::{FxDashSet, SendAnyMap}; -struct NodesDirty { +pub(crate) struct NodesDirty { passes_updated: FxHashMap>, nodes_updated: FxHashMap, - passes: Rc]>>, + pub(crate) passes: Box<[TypeErasedPass]>, } impl NodesDirty { @@ -35,7 +37,7 @@ impl NodesDirty { fn mark_parent_added_or_removed(&mut self, node_id: NodeId) { let hm = self.passes_updated.entry(node_id).or_default(); - for pass in &**self.passes { + for pass in &*self.passes { if pass.parent_dependant { hm.insert(pass.this_type_id); } @@ -44,7 +46,7 @@ impl NodesDirty { fn mark_child_changed(&mut self, node_id: NodeId) { let hm = self.passes_updated.entry(node_id).or_default(); - for pass in &**self.passes { + for pass in &*self.passes { if pass.child_dependant { hm.insert(pass.this_type_id); } @@ -52,6 +54,8 @@ impl NodesDirty { } } +type NodeWatchers = Rc>>>>; + /// 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. @@ -61,8 +65,8 @@ impl NodesDirty { pub struct RealDom { pub(crate) tree: Tree, nodes_listening: FxHashMap>, - pub(crate) passes: Rc]>>, - dirty_nodes: NodesDirty, + pub(crate) dirty_nodes: NodesDirty, + node_watchers: NodeWatchers, phantom: std::marker::PhantomData, } @@ -94,7 +98,6 @@ impl RealDom { } } } - let passes = Rc::new(passes); let mut passes_updated = FxHashMap::default(); let mut nodes_updated = FxHashMap::default(); @@ -106,34 +109,32 @@ impl RealDom { RealDom { tree, nodes_listening: FxHashMap::default(), - passes: passes.clone(), dirty_nodes: NodesDirty { passes_updated, nodes_updated, passes, }, + node_watchers: Default::default(), phantom: std::marker::PhantomData, } } - pub fn create_node(&mut self, node: NodeType, mark_dirty: bool) -> NodeMut<'_, V> { + pub fn create_node(&mut self, node: NodeType) -> NodeMut<'_, V> { let mut node_entry = self.tree.create_node(); let id = node_entry.id(); - if mark_dirty { - self.dirty_nodes - .passes_updated - .entry(id) - .or_default() - .extend(self.passes.iter().map(|x| x.this_type_id)); - } + self.dirty_nodes + .passes_updated + .entry(id) + .or_default() + .extend(self.dirty_nodes.passes.iter().map(|x| x.this_type_id)); node_entry.insert(node); + let watchers = self.node_watchers.clone(); + for watcher in &*watchers.read().unwrap() { + watcher.on_node_added(NodeMut::new(id, self)); + } NodeMut::new(id, self) } - pub fn add_child(&mut self, node_id: NodeId, child_id: NodeId) { - self.tree.add_child(node_id, child_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: &str) -> Vec> { @@ -166,12 +167,12 @@ impl RealDom { pub fn clone_node(&mut self, node_id: NodeId) -> NodeId { let node = self.get(node_id).unwrap(); let new_node = node.node_type().clone(); - let new_id = self.create_node(new_node, true).id(); + let new_id = self.create_node(new_node).id(); let children = self.tree.children_ids(node_id).unwrap().to_vec(); for child in children { let child_id = self.clone_node(child); - self.add_child(new_id, child_id); + self.get_mut(new_id).unwrap().add_child(child_id); } new_id } @@ -187,11 +188,9 @@ impl RealDom { } /// WARNING: This escapes the reactive system that the real dom uses. Any changes made with this method will not trigger updates in states when [RealDom::update_state] is called. - pub fn get_mut_raw(&mut self, id: NodeId) -> Option> { + pub fn get_state_mut_raw(&mut self, id: NodeId) -> Option<&mut T> { let id = id.into_node_id(self); - self.tree - .contains(id) - .then_some(NodeMutRaw { id, dom: self }) + self.tree.get_mut(id) } /// 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. @@ -202,7 +201,8 @@ impl RealDom { ) -> (FxDashSet, FxHashMap) { let passes = std::mem::take(&mut self.dirty_nodes.passes_updated); let nodes_updated = std::mem::take(&mut self.dirty_nodes.nodes_updated); - let dirty_nodes = DirtyNodeStates::with_passes(self.passes.iter().map(|p| p.this_type_id)); + let dirty_nodes = + DirtyNodeStates::with_passes(self.dirty_nodes.passes.iter().map(|p| p.this_type_id)); for (node_id, passes) in passes { // remove any nodes that were created and then removed in the same mutations from the dirty nodes list if let Some(height) = self.tree.height(node_id) { @@ -218,21 +218,6 @@ impl RealDom { ) } - pub fn remove(&mut self, id: NodeId) { - if let Some(parent_id) = self.tree.parent_id(id) { - self.dirty_nodes.mark_child_changed(parent_id); - } - self.tree.remove(id) - } - - pub fn replace(&mut self, old: NodeId, new: NodeId) { - if let Some(parent_id) = self.tree.parent_id(old) { - self.dirty_nodes.mark_child_changed(parent_id); - self.dirty_nodes.mark_parent_added_or_removed(new); - } - self.tree.replace(old, new); - } - pub fn traverse_depth_first(&self, mut f: impl FnMut(NodeRef)) { let mut stack = vec![self.root_id()]; while let Some(id) = stack.pop() { @@ -293,6 +278,10 @@ impl RealDom { pub fn insert_slab(&mut self) { self.tree.insert_slab::(); } + + pub fn add_node_watcher(&mut self, watcher: impl NodeWatcher + 'static) { + self.node_watchers.write().unwrap().push(Box::new(watcher)); + } } pub trait IntoNodeId { @@ -343,40 +332,52 @@ pub trait NodeImmutable: Sized { dom: self.real_dom(), }) } + + fn next(&self) -> Option> { + let parent = self.parent_id()?; + let children = self.real_dom().tree.children_ids(parent)?; + let index = children.iter().position(|id| *id == self.id())?; + if index + 1 < children.len() { + Some(NodeRef { + id: children[index + 1], + dom: self.real_dom(), + }) + } else { + None + } + } + + fn prev(&self) -> Option> { + let parent = self.parent_id()?; + let children = self.real_dom().tree.children_ids(parent)?; + let index = children.iter().position(|id| *id == self.id())?; + if index > 0 { + Some(NodeRef { + id: children[index - 1], + dom: self.real_dom(), + }) + } else { + None + } + } } -pub trait NodeMutable: Sized + NodeImmutable { - fn real_dom_mut(&mut self) -> &mut RealDom; - fn get_mut(&mut self) -> Option<&mut T> { - let id = self.id(); - self.real_dom_mut().tree.get_mut(id) - } - fn insert(&mut self, value: T) { - let id = self.id(); - self.real_dom_mut().tree.insert(id, value); - } - fn add_child(&mut self, child: NodeId) { - let id = self.id(); - self.real_dom_mut().tree.add_child(id, child); - } - fn insert_after(&mut self, old: NodeId) { - let id = self.id(); - self.real_dom_mut().tree.insert_after(old, id); - } - fn insert_before(&mut self, old: NodeId) { - let id = self.id(); - self.real_dom_mut().tree.insert_before(old, id); - } - fn add_event_listener(&mut self, event: &str); - fn remove_event_listener(&mut self, event: &str); -} - -#[derive(Clone, Copy)] pub struct NodeRef<'a, V: FromAnyValue + Send + Sync = ()> { id: NodeId, dom: &'a RealDom, } +impl<'a, V: FromAnyValue + Send + Sync> Clone for NodeRef<'a, V> { + fn clone(&self) -> Self { + Self { + id: self.id, + dom: self.dom, + } + } +} + +impl<'a, V: FromAnyValue + Send + Sync> Copy for NodeRef<'a, V> {} + impl<'a, V: FromAnyValue + Send + Sync> NodeImmutable for NodeRef<'a, V> { fn real_dom(&self) -> &RealDom { self.dom @@ -408,12 +409,12 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeImmutable for NodeMut<'a, V> { } } -impl<'a, V: FromAnyValue + Send + Sync> NodeMutable for NodeMut<'a, V> { - fn real_dom_mut(&mut self) -> &mut RealDom { +impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { + pub fn real_dom_mut(&mut self) -> &mut RealDom { self.dom } - fn get_mut(&mut self) -> Option<&mut T> { + pub fn get_mut(&mut self) -> Option<&mut T> { // mark the node state as dirty self.dom .dirty_nodes @@ -424,7 +425,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMutable for NodeMut<'a, V> { self.dom.tree.get_mut(self.id) } - fn insert(&mut self, value: T) { + pub fn insert(&mut self, value: T) { // mark the node state as dirty self.dom .dirty_nodes @@ -435,25 +436,87 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMutable for NodeMut<'a, V> { self.dom.tree.insert(self.id, value); } - fn insert_after(&mut self, old: NodeId) { + pub fn next_mut(self) -> Option> { + let parent = self.parent_id()?; + let children = self.dom.tree.children_ids(parent)?; + let index = children.iter().position(|id| *id == self.id)?; + if index + 1 < children.len() { + Some(NodeMut::new(children[index + 1], self.dom)) + } else { + None + } + } + + pub fn prev_mut(self) -> Option> { + let parent = self.parent_id()?; + let children = self.dom.tree.children_ids(parent)?; + let index = children.iter().position(|id| *id == self.id)?; + if index > 0 { + Some(NodeMut::new(children[index - 1], self.dom)) + } else { + None + } + } + + pub fn add_child(&mut self, child: NodeId) { + self.dom.dirty_nodes.mark_child_changed(self.id); + self.dom.dirty_nodes.mark_parent_added_or_removed(child); + self.dom.tree.add_child(self.id, child); + NodeMut::new(child, self.dom).mark_moved(); + } + + pub fn insert_after(&mut self, old: NodeId) { let id = self.id(); if let Some(parent_id) = self.dom.tree.parent_id(old) { self.dom.dirty_nodes.mark_child_changed(parent_id); self.dom.dirty_nodes.mark_parent_added_or_removed(id); } self.dom.tree.insert_after(old, id); + self.mark_moved(); } - fn insert_before(&mut self, old: NodeId) { + pub fn insert_before(&mut self, old: NodeId) { let id = self.id(); if let Some(parent_id) = self.dom.tree.parent_id(old) { self.dom.dirty_nodes.mark_child_changed(parent_id); self.dom.dirty_nodes.mark_parent_added_or_removed(id); } self.dom.tree.insert_before(old, id); + self.mark_moved(); } - fn add_event_listener(&mut self, event: &str) { + pub fn remove(&mut self) { + let id = self.id(); + self.mark_removed(); + if let Some(parent_id) = self.real_dom_mut().tree.parent_id(id) { + self.real_dom_mut() + .dirty_nodes + .mark_child_changed(parent_id); + } + if let Some(children_ids) = self.child_ids() { + let children_ids_vec = children_ids.to_vec(); + for child in children_ids_vec { + self.dom.get_mut(child).unwrap().remove(); + } + } + self.dom.tree.remove_single(id); + } + + pub fn replace(&mut self, new: NodeId) { + self.mark_removed(); + if let Some(parent_id) = self.parent_id() { + self.real_dom_mut() + .dirty_nodes + .mark_child_changed(parent_id); + self.real_dom_mut() + .dirty_nodes + .mark_parent_added_or_removed(new); + } + let id = self.id(); + self.dom.tree.replace(id, new); + } + + pub fn add_event_listener(&mut self, event: &str) { let id = self.id(); let node_type: &mut NodeType = self.dom.tree.get_mut(self.id).unwrap(); if let NodeType::Element(ElementNode { listeners, .. }) = node_type { @@ -474,7 +537,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMutable for NodeMut<'a, V> { } } - fn remove_event_listener(&mut self, event: &str) { + pub fn remove_event_listener(&mut self, event: &str) { let id = self.id(); let node_type: &mut NodeType = self.dom.tree.get_mut(self.id).unwrap(); if let NodeType::Element(ElementNode { listeners, .. }) = node_type { @@ -486,9 +549,21 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMutable for NodeMut<'a, V> { self.dom.nodes_listening.get_mut(event).unwrap().remove(&id); } } -} -impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { + fn mark_removed(&mut self) { + let watchers = self.dom.node_watchers.clone(); + for watcher in &*watchers.read().unwrap() { + watcher.on_node_removed(NodeMut::new(self.id(), self.dom)); + } + } + + fn mark_moved(&mut self) { + let watchers = self.dom.node_watchers.clone(); + for watcher in &*watchers.read().unwrap() { + watcher.on_node_moved(NodeMut::new(self.id(), self.dom)); + } + } + pub fn node_type_mut(&mut self) -> NodeTypeMut<'_, V> { let Self { id, dom } = self; let RealDom { @@ -503,6 +578,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { }), NodeType::Text(text) => { dirty_nodes.mark_dirty(self.id, NodeMaskBuilder::new().with_text().build()); + NodeTypeMut::Text(text) } NodeType::Placeholder => NodeTypeMut::Placeholder, @@ -554,18 +630,6 @@ impl ElementNodeMut<'_, V> { &self.element.attributes } - pub fn attributes_mut( - &mut self, - ) -> &mut FxHashMap> { - self.dirty_nodes.mark_dirty( - self.id, - NodeMaskBuilder::new() - .with_attrs(AttributeMaskBuilder::All) - .build(), - ); - &mut self.element.attributes - } - pub fn set_attribute( &mut self, name: OwnedAttributeDiscription, @@ -610,55 +674,3 @@ impl ElementNodeMut<'_, V> { &self.element.listeners } } - -pub struct NodeMutRaw<'a, V: FromAnyValue + Send + Sync = ()> { - id: NodeId, - dom: &'a mut RealDom, -} - -impl<'a, V: FromAnyValue + Send + Sync> NodeMutRaw<'a, V> { - fn node_type_mut(&mut self) -> &mut NodeType { - self.dom.tree.get_mut::>(self.id).unwrap() - } -} - -impl<'a, V: FromAnyValue + Send + Sync> NodeImmutable for NodeMutRaw<'a, V> { - fn real_dom(&self) -> &RealDom { - self.dom - } - - fn id(&self) -> NodeId { - self.id - } -} - -impl<'a, V: FromAnyValue + Send + Sync> NodeMutable for NodeMutRaw<'a, V> { - fn real_dom_mut(&mut self) -> &mut RealDom { - self.dom - } - - fn add_event_listener(&mut self, event: &str) { - let id = self.id(); - if let NodeType::Element(element) = self.node_type_mut() { - element.listeners.insert(event.to_string()); - match self.dom.nodes_listening.get_mut(event) { - Some(hs) => { - hs.insert(id); - } - None => { - let mut hs = FxHashSet::default(); - hs.insert(id); - self.dom.nodes_listening.insert(event.to_string(), hs); - } - } - } - } - - fn remove_event_listener(&mut self, event: &str) { - let id = self.id(); - if let NodeType::Element(element) = self.node_type_mut() { - element.listeners.remove(event); - } - self.dom.nodes_listening.get_mut(event).unwrap().remove(&id); - } -} diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index 390ac42b2..580dd4848 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -80,6 +80,18 @@ impl Tree { recurse(self, id); } + pub fn remove_single(&mut self, id: NodeId) { + { + let node_data_mut = self.node_slab_mut(); + if let Some(parent) = node_data_mut.get(id).unwrap().parent { + let parent = node_data_mut.get_mut(parent).unwrap(); + parent.children.retain(|&child| child != id); + } + } + + self.nodes.remove(id); + } + fn set_height(&mut self, node: NodeId, height: u16) { let children = { let mut node = self.get_node_data_mut(node); diff --git a/packages/native-core/src/utils/mod.rs b/packages/native-core/src/utils/mod.rs index 9ddb1a1c7..58e98b0fa 100644 --- a/packages/native-core/src/utils/mod.rs +++ b/packages/native-core/src/utils/mod.rs @@ -1,3 +1,3 @@ -// mod persistant_iterator; -// pub use persistant_iterator::*; +mod persistant_iterator; +pub use persistant_iterator::*; pub mod cursor; diff --git a/packages/native-core/src/utils/persistant_iterator.rs b/packages/native-core/src/utils/persistant_iterator.rs index eb2cd1269..b922d0f56 100644 --- a/packages/native-core/src/utils/persistant_iterator.rs +++ b/packages/native-core/src/utils/persistant_iterator.rs @@ -1,10 +1,15 @@ +use smallvec::SmallVec; + use crate::{ - node::{ElementNode, FromAnyValue, NodeType}, + node::FromAnyValue, + node_watcher::NodeWatcher, real_dom::{NodeImmutable, RealDom}, - NodeId, + NodeId, NodeRef, +}; +use std::{ + fmt::Debug, + sync::{Arc, Mutex}, }; -use dioxus_core::{Mutation, Mutations}; -use std::fmt::Debug; #[derive(Debug)] pub enum ElementProduced { @@ -23,27 +28,36 @@ impl ElementProduced { } } -#[derive(Debug)] -enum NodePosition { - AtNode, - InChild(usize), +struct PersistantElementIterUpdater { + stack: Arc>>, + phantom: std::marker::PhantomData, } -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 NodeWatcher for PersistantElementIterUpdater { + fn on_node_moved(&self, node: crate::NodeMut) { + // if any element is moved, update its parents in the stack + let mut stack = self.stack.lock().unwrap(); + let moved = node.id(); + let rdom = node.real_dom(); + if let Some(r) = stack.iter().position(|el_id| *el_id == moved) { + let back = &stack[r..]; + let mut new = SmallVec::new(); + let mut parent = node.parent_id(); + while let Some(p) = parent.and_then(|id| rdom.get(id)) { + new.push(p.id()); + parent = p.parent_id(); + } + new.extend(back.iter().copied()); + *stack = new; } } - 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 on_node_removed(&self, node: crate::NodeMut) { + // if any element is removed in the chain, remove it and its children from the stack + let mut stack = self.stack.lock().unwrap(); + let removed = node.id(); + if let Some(r) = stack.iter().position(|el_id| *el_id == removed) { + stack.truncate(r); } } } @@ -53,118 +67,47 @@ impl NodePosition { /// 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<[(NodeId, NodePosition); 5]>, -} - -impl Default for PersistantElementIter { - fn default() -> Self { - PersistantElementIter { - stack: smallvec::smallvec![(NodeId(0), NodePosition::AtNode)], - } - } + // stack of elements and fragments, the last element is the last element that was yielded + stack: Arc>>, } impl PersistantElementIter { - pub fn new() -> Self { - Self::default() - } + pub fn create(rdom: &mut RealDom) -> Self { + let inner = Arc::new(Mutex::new(smallvec::smallvec![NodeId(0)])); - /// 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 removed - match m { - Mutation::Remove { id } => Some(rdom.element_to_node_id(*id)), - Mutation::ReplaceWith { id, .. } => Some(rdom.element_to_node_id(*id)), - _ => 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.get(*el_id).unwrap().child_ids() { - 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 + rdom.add_node_watcher(PersistantElementIterUpdater { + stack: inner.clone(), + phantom: std::marker::PhantomData, + }); + + PersistantElementIter { stack: inner } } /// get the next element pub fn next(&mut self, rdom: &RealDom) -> ElementProduced { - if self.stack.is_empty() { + let mut stack = self.stack.lock().unwrap(); + if stack.is_empty() { let id = NodeId(0); - let new = (id, NodePosition::AtNode); - self.stack.push(new); + let new = id; + stack.push(new); ElementProduced::Looped(id) } else { - let (last, old_child_idx) = self.stack.last_mut().unwrap(); - let node = rdom.get(*last).unwrap(); - match node.node_type() { - NodeType::Element(ElementNode { .. }) => { - let children = node.child_ids().unwrap(); - *old_child_idx = old_child_idx.map(|i| i + 1); - // if we have children, go to the next child - let child_idx = old_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(ElementNode { .. }) = - rdom.get(id).unwrap().node_type() - { - self.stack.push((id, NodePosition::AtNode)); - } - ElementProduced::Progressed(id) + loop { + let last = stack.pop().unwrap(); + if let Some(current) = rdom.get(last) { + if let Some(new) = current.next() { + // the next element exists, add it to the stack and return it + let new = new.id(); + stack.push(new); + return ElementProduced::Progressed(new); } - } - - NodeType::Text { .. } | NodeType::Placeholder { .. } => { - // we are at a leaf, so we are done - ElementProduced::Progressed(self.pop()) + // otherwise, continue the loop and go to the parent + } else { + // if there is no parent, loop back to the root + let new = NodeId(0); + stack.clear(); + stack.push(new); + return ElementProduced::Looped(new); } } } @@ -174,68 +117,44 @@ impl PersistantElementIter { pub fn prev(&mut self, rdom: &RealDom) -> ElementProduced { // recursively add the last child element to the stack fn push_back( - stack: &mut smallvec::SmallVec<[(NodeId, NodePosition); 5]>, - new_node: NodeId, - rdom: &RealDom, + stack: &mut smallvec::SmallVec<[NodeId; 5]>, + node: NodeRef, ) -> NodeId { - let node = rdom.get(new_node).unwrap(); - match node.node_type() { - NodeType::Element(ElementNode { .. }) => { - let children = node.child_ids().unwrap(); - if children.is_empty() { - new_node - } else { - stack.push((new_node, NodePosition::InChild(children.len() - 1))); - push_back(stack, *children.last().unwrap(), rdom) - } + stack.push(node.id()); + if let Some(children) = node.children() { + if let Some(last) = children.last() { + push_back(stack, *last) + } else { + node.id() } - _ => new_node, + } else { + node.id() } } - if self.stack.is_empty() { - let new_node = NodeId(0); - ElementProduced::Looped(push_back(&mut self.stack, new_node, rdom)) + let mut stack = self.stack.lock().unwrap(); + if stack.is_empty() { + let id = NodeId(0); + let last = push_back(&mut stack, rdom.get(id).unwrap()); + ElementProduced::Looped(last) } else { - let (last, old_child_idx) = self.stack.last_mut().unwrap(); - let node = rdom.get(*last).unwrap(); - match node.node_type() { - NodeType::Element(ElementNode { .. }) => { - let children = node.child_ids().unwrap(); - // if we have children, go to the next child - if let NodePosition::InChild(0) = old_child_idx { - ElementProduced::Progressed(self.pop()) - } else { - *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) - } else { - let new_node = children[*child_idx]; - ElementProduced::Progressed(push_back( - &mut self.stack, - new_node, - rdom, - )) - } - } else { - self.pop(); - self.prev(rdom) - } + loop { + let last = stack.pop().unwrap(); + if let Some(current) = rdom.get(last) { + if let Some(new) = current.prev() { + // the next element exists, add it to the stack and return it + let new = push_back(&mut stack, new); + return ElementProduced::Progressed(new); } - } - - NodeType::Text { .. } | NodeType::Placeholder { .. } => { - // we are at a leaf, so we are done - ElementProduced::Progressed(self.pop()) + // otherwise, continue the loop and go to the parent + } else { + // if there is no parent, loop back to the root + let id = NodeId(0); + let last = push_back(&mut stack, rdom.get(id).unwrap()); + return ElementProduced::Looped(last); } } } } - - fn pop(&mut self) -> NodeId { - self.stack.pop().unwrap().0 - } } #[test] diff --git a/packages/native-core/tests/passes.rs b/packages/native-core/tests/passes.rs index f0023ae35..40fe58717 100644 --- a/packages/native-core/tests/passes.rs +++ b/packages/native-core/tests/passes.rs @@ -307,14 +307,14 @@ fn down_pass() { } let mut tree: RealDom = RealDom::new(Box::new([AddNumber::to_type_erased()])); - let grandchild1 = tree.create_node(create_blank_element(), true); + let grandchild1 = tree.create_node(create_blank_element()); let grandchild1 = grandchild1.id(); - let mut child1 = tree.create_node(create_blank_element(), true); + let mut child1 = tree.create_node(create_blank_element()); child1.add_child(grandchild1); let child1 = child1.id(); - let grandchild2 = tree.create_node(create_blank_element(), true); + let grandchild2 = tree.create_node(create_blank_element()); let grandchild2 = grandchild2.id(); - let mut child2 = tree.create_node(create_blank_element(), true); + let mut child2 = tree.create_node(create_blank_element()); child2.add_child(grandchild2); let child2 = child2.id(); let mut parent = tree.get_mut(tree.root_id()).unwrap(); @@ -395,14 +395,14 @@ fn up_pass() { } let mut tree: RealDom = RealDom::new(Box::new([AddNumber::to_type_erased()])); - let grandchild1 = tree.create_node(create_blank_element(), true); + let grandchild1 = tree.create_node(create_blank_element()); let grandchild1 = grandchild1.id(); - let mut child1 = tree.create_node(create_blank_element(), true); + let mut child1 = tree.create_node(create_blank_element()); child1.add_child(grandchild1); let child1 = child1.id(); - let grandchild2 = tree.create_node(create_blank_element(), true); + let grandchild2 = tree.create_node(create_blank_element()); let grandchild2 = grandchild2.id(); - let mut child2 = tree.create_node(create_blank_element(), true); + let mut child2 = tree.create_node(create_blank_element()); child2.add_child(grandchild2); let child2 = child2.id(); let mut parent = tree.get_mut(tree.root_id()).unwrap(); From 05b968e8e4422514f9d22a01e74e420849d56a8a Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 6 Feb 2023 14:52:15 -0600 Subject: [PATCH 039/103] update persistant iterator to work with new system --- packages/native-core/src/dioxus.rs | 12 +- .../src/utils/persistant_iterator.rs | 196 +++++++----------- 2 files changed, 85 insertions(+), 123 deletions(-) diff --git a/packages/native-core/src/dioxus.rs b/packages/native-core/src/dioxus.rs index d5dc59e2b..1c688b864 100644 --- a/packages/native-core/src/dioxus.rs +++ b/packages/native-core/src/dioxus.rs @@ -3,7 +3,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; use crate::{ node::{ElementNode, FromAnyValue, NodeType, OwnedAttributeDiscription, OwnedAttributeValue}, - prelude::{NodeImmutable, NodeMutable}, + prelude::NodeImmutable, real_dom::NodeTypeMut, NodeId, NodeMut, RealDom, }; @@ -68,7 +68,7 @@ impl DioxusState { let children = self.stack.split_off(self.stack.len() - m); let parent = self.element_to_node_id(id); for child in children { - rdom.add_child(parent, child); + rdom.get_mut(parent).unwrap().add_child(child); } } AssignId { path, id } => { @@ -114,7 +114,7 @@ impl DioxusState { let mut node = rdom.get_mut(new).unwrap(); node.insert_before(old_node_id); } - rdom.remove(old_node_id); + rdom.get_mut(old_node_id).unwrap().remove(); } ReplacePlaceholder { path, m } => { let new_nodes = self.stack.split_off(self.stack.len() - m); @@ -123,7 +123,7 @@ impl DioxusState { let mut node = rdom.get_mut(new).unwrap(); node.insert_before(old_node_id); } - rdom.remove(old_node_id); + rdom.get_mut(old_node_id).unwrap().remove(); } InsertAfter { id, m } => { let new_nodes = self.stack.split_off(self.stack.len() - m); @@ -186,7 +186,7 @@ impl DioxusState { } Remove { id } => { let node_id = self.element_to_node_id(id); - rdom.remove(node_id); + rdom.get_mut(node_id).unwrap().remove(); } PushRoot { id } => { let node_id = self.element_to_node_id(id); @@ -231,7 +231,7 @@ fn create_template_node(rdom: &mut RealDom, node: &TemplateNode) -> NodeId { let node_id = rdom.create_node(node).id(); for child in *children { let child_id = create_template_node(rdom, child); - rdom.add_child(node_id, child_id); + rdom.get_mut(node_id).unwrap().add_child(child_id); } node_id } diff --git a/packages/native-core/src/utils/persistant_iterator.rs b/packages/native-core/src/utils/persistant_iterator.rs index b922d0f56..d290caaa4 100644 --- a/packages/native-core/src/utils/persistant_iterator.rs +++ b/packages/native-core/src/utils/persistant_iterator.rs @@ -92,9 +92,20 @@ impl PersistantElementIter { stack.push(new); ElementProduced::Looped(id) } else { + let mut look_in_children = true; loop { - let last = stack.pop().unwrap(); - if let Some(current) = rdom.get(last) { + if let Some(current) = stack.last().and_then(|last| rdom.get(*last)) { + // if the current element has children, add the first child to the stack and return it + if look_in_children { + if let Some(children) = current.children() { + if let Some(first) = children.first() { + let new = first.id(); + stack.push(new); + return ElementProduced::Progressed(new); + } + } + } + stack.pop(); if let Some(new) = current.next() { // the next element exists, add it to the stack and return it let new = new.id(); @@ -109,6 +120,7 @@ impl PersistantElementIter { stack.push(new); return ElementProduced::Looped(new); } + look_in_children = false; } } } @@ -136,23 +148,27 @@ impl PersistantElementIter { let id = NodeId(0); let last = push_back(&mut stack, rdom.get(id).unwrap()); ElementProduced::Looped(last) - } else { - loop { - let last = stack.pop().unwrap(); - if let Some(current) = rdom.get(last) { - if let Some(new) = current.prev() { - // the next element exists, add it to the stack and return it - let new = push_back(&mut stack, new); - return ElementProduced::Progressed(new); - } - // otherwise, continue the loop and go to the parent - } else { - // if there is no parent, loop back to the root - let id = NodeId(0); - let last = push_back(&mut stack, rdom.get(id).unwrap()); - return ElementProduced::Looped(last); - } + } else if let Some(current) = stack.pop().and_then(|last| rdom.get(last)) { + if let Some(new) = current.prev() { + // the next element exists, add it to the stack and return it + let new = push_back(&mut stack, new); + ElementProduced::Progressed(new) } + // otherwise, yeild the parent + else if let Some(parent) = stack.last() { + // if there is a parent, return it + ElementProduced::Progressed(*parent) + } else { + // if there is no parent, loop back to the root + let id = NodeId(0); + let last = push_back(&mut stack, rdom.get(id).unwrap()); + ElementProduced::Looped(last) + } + } else { + // if there is no parent, loop back to the root + let id = NodeId(0); + let last = push_back(&mut stack, rdom.get(id).unwrap()); + ElementProduced::Looped(last) } } } @@ -160,6 +176,8 @@ impl PersistantElementIter { #[test] #[allow(unused_variables)] fn traverse() { + use crate::dioxus::DioxusState; + use crate::prelude::*; use dioxus::prelude::*; #[allow(non_snake_case)] fn Base(cx: Scope) -> Element { @@ -181,133 +199,74 @@ fn traverse() { let mut rdom: RealDom = RealDom::new(Box::new([])); - let _to_update = rdom.apply_mutations(mutations); + let mut iter = PersistantElementIter::create(&mut rdom); + let mut dioxus_state = DioxusState::create(&mut rdom); + dioxus_state.apply_mutations(&mut rdom, mutations); - let mut iter = PersistantElementIter::new(); let div_tag = "div".to_string(); assert!(matches!( - &rdom - .get(iter.next(&rdom).id()) - .unwrap() - .node_data() - .node_type, + &rdom.get(iter.next(&rdom).id()).unwrap().node_type(), NodeType::Element(ElementNode { tag: div_tag, .. }) )); assert!(matches!( - &rdom - .get(iter.next(&rdom).id()) - .unwrap() - .node_data() - .node_type, + &rdom.get(iter.next(&rdom).id()).unwrap().node_type(), NodeType::Element(ElementNode { tag: div_tag, .. }) )); let text1 = "hello".to_string(); assert!(matches!( - &rdom - .get(iter.next(&rdom).id()) - .unwrap() - .node_data() - .node_type, + &rdom.get(iter.next(&rdom).id()).unwrap().node_type(), NodeType::Text(text1) )); let p_tag = "p".to_string(); assert!(matches!( - &rdom - .get(iter.next(&rdom).id()) - .unwrap() - .node_data() - .node_type, + &rdom.get(iter.next(&rdom).id()).unwrap().node_type(), NodeType::Element(ElementNode { tag: p_tag, .. }) )); let text2 = "world".to_string(); assert!(matches!( - &rdom - .get(iter.next(&rdom).id()) - .unwrap() - .node_data() - .node_type, + &rdom.get(iter.next(&rdom).id()).unwrap().node_type(), NodeType::Text(text2) )); let text3 = "hello world".to_string(); assert!(matches!( - &rdom - .get(iter.next(&rdom).id()) - .unwrap() - .node_data() - .node_type, + &rdom.get(iter.next(&rdom).id()).unwrap().node_type(), NodeType::Text(text3) )); assert!(matches!( - &rdom - .get(iter.next(&rdom).id()) - .unwrap() - .node_data() - .node_type, + &rdom.get(iter.next(&rdom).id()).unwrap().node_type(), NodeType::Element(ElementNode { tag: div_tag, .. }) )); assert!(matches!( - &rdom - .get(iter.prev(&rdom).id()) - .unwrap() - .node_data() - .node_type, + &rdom.get(iter.prev(&rdom).id()).unwrap().node_type(), NodeType::Text(text3) )); assert!(matches!( - &rdom - .get(iter.prev(&rdom).id()) - .unwrap() - .node_data() - .node_type, + &rdom.get(iter.prev(&rdom).id()).unwrap().node_type(), NodeType::Text(text2) )); assert!(matches!( - &rdom - .get(iter.prev(&rdom).id()) - .unwrap() - .node_data() - .node_type, + &rdom.get(iter.prev(&rdom).id()).unwrap().node_type(), NodeType::Element(ElementNode { tag: p_tag, .. }) )); assert!(matches!( - &rdom - .get(iter.prev(&rdom).id()) - .unwrap() - .node_data() - .node_type, + &rdom.get(iter.prev(&rdom).id()).unwrap().node_type(), NodeType::Text(text1) )); assert!(matches!( - &rdom - .get(iter.prev(&rdom).id()) - .unwrap() - .node_data() - .node_type, + &rdom.get(iter.prev(&rdom).id()).unwrap().node_type(), NodeType::Element(ElementNode { tag: div_tag, .. }) )); assert!(matches!( - &rdom - .get(iter.prev(&rdom).id()) - .unwrap() - .node_data() - .node_type, + &rdom.get(iter.prev(&rdom).id()).unwrap().node_type(), NodeType::Element(ElementNode { tag: div_tag, .. }) )); assert!(matches!( - &rdom - .get(iter.prev(&rdom).id()) - .unwrap() - .node_data() - .node_type, + &rdom.get(iter.prev(&rdom).id()).unwrap().node_type(), NodeType::Element(ElementNode { tag: div_tag, .. }) )); assert!(matches!( - &rdom - .get(iter.prev(&rdom).id()) - .unwrap() - .node_data() - .node_type, + &rdom.get(iter.prev(&rdom).id()).unwrap().node_type(), NodeType::Text(text3) )); } @@ -315,6 +274,8 @@ fn traverse() { #[test] #[allow(unused_variables)] fn persist_removes() { + use crate::dioxus::DioxusState; + use crate::prelude::*; use dioxus::prelude::*; #[allow(non_snake_case)] fn Base(cx: Scope) -> Element { @@ -342,12 +303,13 @@ fn persist_removes() { let build = vdom.rebuild(); println!("{build:#?}"); - let _to_update = rdom.apply_mutations(build); - // this will end on the node that is removed - let mut iter1 = PersistantElementIter::new(); + let mut iter1 = PersistantElementIter::create(&mut rdom); // this will end on the after node that is removed - let mut iter2 = PersistantElementIter::new(); + let mut iter2 = PersistantElementIter::create(&mut rdom); + let mut dioxus_state = DioxusState::create(&mut rdom); + dioxus_state.apply_mutations(&mut rdom, build); + // root iter1.next(&rdom).id(); iter2.next(&rdom).id(); @@ -374,22 +336,18 @@ fn persist_removes() { vdom.mark_dirty(ScopeId(0)); let update = vdom.render_immediate(); println!("{update:#?}"); - iter1.prune(&update, &rdom); - iter2.prune(&update, &rdom); - let _to_update = rdom.apply_mutations(update); + dioxus_state.apply_mutations(&mut rdom, update); let root_tag = "Root".to_string(); let idx = iter1.next(&rdom).id(); - dbg!(&rdom.get(idx).unwrap().node_data().node_type); assert!(matches!( - &rdom.get(idx).unwrap().node_data().node_type, + &rdom.get(idx).unwrap().node_type(), NodeType::Element(ElementNode { tag: root_tag, .. }) )); let idx = iter2.next(&rdom).id(); - dbg!(&rdom.get(idx).unwrap().node_data().node_type); assert!(matches!( - &rdom.get(idx).unwrap().node_data().node_type, + &rdom.get(idx).unwrap().node_type(), NodeType::Element(ElementNode { tag: root_tag, .. }) )); } @@ -397,6 +355,8 @@ fn persist_removes() { #[test] #[allow(unused_variables)] fn persist_instertions_before() { + use crate::dioxus::DioxusState; + use crate::prelude::*; use dioxus::prelude::*; #[allow(non_snake_case)] fn Base(cx: Scope) -> Element { @@ -421,11 +381,12 @@ fn persist_instertions_before() { let mut vdom = VirtualDom::new(Base); let mut rdom: RealDom = RealDom::new(Box::new([])); + let mut dioxus_state = DioxusState::create(&mut rdom); let build = vdom.rebuild(); - let _to_update = rdom.apply_mutations(build); + dioxus_state.apply_mutations(&mut rdom, build); - let mut iter = PersistantElementIter::new(); + let mut iter = PersistantElementIter::create(&mut rdom); // div iter.next(&rdom).id(); // p @@ -439,13 +400,12 @@ fn persist_instertions_before() { vdom.mark_dirty(ScopeId(0)); let update = vdom.render_immediate(); - iter.prune(&update, &rdom); - let _to_update = rdom.apply_mutations(update); + dioxus_state.apply_mutations(&mut rdom, update); let p_tag = "div".to_string(); let idx = iter.next(&rdom).id(); assert!(matches!( - &rdom.get(idx).unwrap().node_data().node_type, + &rdom.get(idx).unwrap().node_type(), NodeType::Element(ElementNode { tag: p_tag, .. }) )); } @@ -453,6 +413,8 @@ fn persist_instertions_before() { #[test] #[allow(unused_variables)] fn persist_instertions_after() { + use crate::dioxus::DioxusState; + use crate::prelude::*; use dioxus::prelude::*; #[allow(non_snake_case)] fn Base(cx: Scope) -> Element { @@ -477,11 +439,12 @@ fn persist_instertions_after() { let mut vdom = VirtualDom::new(Base); let mut rdom: RealDom = RealDom::new(Box::new([])); + let mut iter = PersistantElementIter::create(&mut rdom); + let mut dioxus_state = DioxusState::create(&mut rdom); let build = vdom.rebuild(); - let _to_update = rdom.apply_mutations(build); + dioxus_state.apply_mutations(&mut rdom, build); - let mut iter = PersistantElementIter::new(); // div iter.next(&rdom).id(); // p @@ -494,19 +457,18 @@ fn persist_instertions_after() { iter.next(&rdom).id(); let update = vdom.rebuild(); - iter.prune(&update, &rdom); - let _to_update = rdom.apply_mutations(update); + dioxus_state.apply_mutations(&mut rdom, update); let p_tag = "p".to_string(); let idx = iter.next(&rdom).id(); assert!(matches!( - &rdom.get(idx).unwrap().node_data().node_type, + &rdom.get(idx).unwrap().node_type(), NodeType::Element(ElementNode { tag: p_tag, .. }) )); let text = "hello world".to_string(); let idx = iter.next(&rdom).id(); assert!(matches!( - &rdom.get(idx).unwrap().node_data().node_type, + &rdom.get(idx).unwrap().node_type(), NodeType::Text(text) )); } From c805bc25af5c9a08dd6a79f4993ad98d5c3a940c Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 6 Feb 2023 21:28:48 -0600 Subject: [PATCH 040/103] make tui agnostic over framework --- packages/native-core/src/dioxus.rs | 2 +- packages/native-core/src/lib.rs | 11 ++ packages/tui/Cargo.toml | 7 +- packages/tui/src/dioxus.rs | 66 ++++++++++ packages/tui/src/focus.rs | 65 +++------- packages/tui/src/hooks.rs | 172 +++++++++++++-------------- packages/tui/src/layout.rs | 2 +- packages/tui/src/lib.rs | 155 +++++++++--------------- packages/tui/src/prelude/mod.rs | 2 +- packages/tui/src/prevent_default.rs | 2 +- packages/tui/src/query.rs | 9 +- packages/tui/src/style_attributes.rs | 2 +- packages/tui/src/widgets/button.rs | 4 +- packages/tui/src/widgets/checkbox.rs | 4 +- packages/tui/src/widgets/number.rs | 4 +- packages/tui/src/widgets/password.rs | 4 +- packages/tui/src/widgets/slider.rs | 4 +- packages/tui/src/widgets/textbox.rs | 4 +- 18 files changed, 262 insertions(+), 257 deletions(-) create mode 100644 packages/tui/src/dioxus.rs diff --git a/packages/native-core/src/dioxus.rs b/packages/native-core/src/dioxus.rs index 1c688b864..f524035a4 100644 --- a/packages/native-core/src/dioxus.rs +++ b/packages/native-core/src/dioxus.rs @@ -241,7 +241,7 @@ fn create_template_node(rdom: &mut RealDom, node: &TemplateNode) -> NodeId { } } -trait NodeImmutableDioxusExt: NodeImmutable { +pub trait NodeImmutableDioxusExt: NodeImmutable { fn mounted_id(&self) -> Option { self.get().copied() } diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 05e9a2c93..eeb3b86e2 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -1,9 +1,12 @@ use std::any::Any; +use std::future::Future; use std::hash::BuildHasherDefault; +use std::pin::Pin; pub use node_ref::NodeMask; pub use passes::AnyMapLike; pub use passes::{Dependancy, Pass, TypeErasedPass}; +use prelude::FromAnyValue; pub use real_dom::{NodeMut, NodeRef, RealDom}; use rustc_hash::FxHasher; pub use tree::NodeId; @@ -31,3 +34,11 @@ pub mod prelude { pub type FxDashMap = dashmap::DashMap>; pub type FxDashSet = dashmap::DashSet>; pub type SendAnyMap = anymap::Map; + +pub trait Renderer { + fn render(&mut self, root: NodeMut); + fn handle_event(&mut self, node: NodeMut, event: &str, value: E); + fn poll_async(&mut self) -> Pin + Send>> { + Box::pin(async {}) + } +} diff --git a/packages/tui/Cargo.toml b/packages/tui/Cargo.toml index eb0ccf721..46a868301 100644 --- a/packages/tui/Cargo.toml +++ b/packages/tui/Cargo.toml @@ -13,8 +13,8 @@ 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.3.0" } -dioxus-core = { path = "../core", version = "^0.3.0", features = ["serialize"] } +dioxus = { path = "../dioxus", version = "^0.3.0", optional = true } +dioxus-core = { path = "../core", version = "^0.3.0", features = ["serialize"], optional = true } dioxus-html = { path = "../html", version = "^0.3.0" } dioxus-native-core = { path = "../native-core", version = "^0.2.0" } dioxus-hot-reload = { path = "../hot-reload", optional = true } @@ -40,5 +40,6 @@ name = "update" harness = false [features] -default = ["hot-reload"] +default = ["hot-reload", "dioxus-bindings"] hot-reload = ["dioxus-hot-reload"] +dioxus-bindings = ["dioxus", "dioxus-core", "dioxus-native-core/dioxus"] \ No newline at end of file diff --git a/packages/tui/src/dioxus.rs b/packages/tui/src/dioxus.rs new file mode 100644 index 000000000..c45918493 --- /dev/null +++ b/packages/tui/src/dioxus.rs @@ -0,0 +1,66 @@ +pub fn launch(app: Component<()>) { + launch_cfg(app, Config::default()) +} + +pub fn launch_cfg(app: Component<()>, cfg: Config) { + launch_cfg_with_props(app, (), cfg); +} + +pub fn launch_cfg_with_props(app: Component, props: Props, cfg: Config) { + let mut dom = VirtualDom::new_with_props(app, props); + let mut rdom = RealDom::new(Box::new([ + TaffyLayout::to_type_erased(), + Focus::to_type_erased(), + StyleModifier::to_type_erased(), + PreventDefault::to_type_erased(), + ])); + + let (handler, state, register_event) = RinkInputHandler::craete(&mut rdom); + + // Setup input handling + let (event_tx, event_rx) = unbounded(); + let event_tx_clone = event_tx.clone(); + if !cfg.headless { + std::thread::spawn(move || { + let tick_rate = Duration::from_millis(1000); + loop { + if crossterm::event::poll(tick_rate).unwrap() { + let evt = crossterm::event::read().unwrap(); + if event_tx.unbounded_send(InputEvent::UserInput(evt)).is_err() { + break; + } + } + } + }); + } + + let cx = dom.base_scope(); + let rdom = Rc::new(RefCell::new(rdom)); + 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(), + }); + + { + let mut rdom = rdom.borrow_mut(); + let mutations = dom.rebuild(); + rdom.apply_mutations(mutations); + let mut any_map = SendAnyMap::new(); + any_map.insert(taffy.clone()); + let _ = rdom.update_state(any_map, false); + } + + render_vdom( + &mut dom, + event_rx, + handler, + cfg, + rdom, + taffy, + register_event, + ) + .unwrap(); +} diff --git a/packages/tui/src/focus.rs b/packages/tui/src/focus.rs index 05ae2019c..e2958df61 100644 --- a/packages/tui/src/focus.rs +++ b/packages/tui/src/focus.rs @@ -2,7 +2,7 @@ use crate::prevent_default::PreventDefault; use dioxus_native_core::{ node_ref::{AttributeMaskBuilder, NodeMaskBuilder}, - real_dom::{NodeImmutable, NodeMutable}, + real_dom::NodeImmutable, utils::{ElementProduced, PersistantElementIter}, Dependancy, NodeId, Pass, RealDom, SendAnyMap, }; @@ -64,7 +64,7 @@ pub(crate) struct Focus { } impl Pass for Focus { - const NODE_MASK: NodeMaskBuilder = NodeMaskBuilder::new() + const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new() .with_attrs(AttributeMaskBuilder::Some(FOCUS_ATTRIBUTES)) .with_listeners(); @@ -138,7 +138,6 @@ impl Pass for Focus { const FOCUS_EVENTS: &[&str] = &["keydown", "keypress", "keyup"]; const FOCUS_ATTRIBUTES: &[&str] = &["tabindex"]; -#[derive(Default)] pub(crate) struct FocusState { pub(crate) focus_iter: PersistantElementIter, pub(crate) last_focused_id: Option, @@ -147,6 +146,16 @@ pub(crate) struct FocusState { } impl FocusState { + pub fn create(rdom: &mut RealDom) -> Self { + let mut focus_iter = PersistantElementIter::create(rdom); + Self { + focus_iter, + last_focused_id: Default::default(), + focus_level: Default::default(), + dirty: Default::default(), + } + } + /// Returns true if the focus has changed. pub fn progress(&mut self, rdom: &mut RealDom, forward: bool) -> bool { if let Some(last) = self.last_focused_id { @@ -242,14 +251,13 @@ impl FocusState { } if let Some(id) = next_focus { - let mut node = rdom.get_mut_raw(id).unwrap(); + let mut node = rdom.get_mut(id).unwrap(); if !node.get::().unwrap().level.focusable() { panic!() } node.insert(Focused(true)); if let Some(old) = self.last_focused_id.replace(id) { - let mut old = rdom.get_mut_raw(old).unwrap(); - let focused = old.get_mut::().unwrap(); + let focused = rdom.get_state_mut_raw::(old).unwrap(); focused.0 = false; } // reset the position to the currently focused element @@ -261,49 +269,14 @@ impl FocusState { false } - pub(crate) fn prune(&mut self, mutations: &dioxus_core::Mutations, rdom: &RealDom) { - fn remove_children(to_prune: &mut [&mut Option], rdom: &RealDom, removed: NodeId) { - for opt in to_prune.iter_mut() { - if let Some(id) = opt { - if *id == removed { - **opt = None; - } - } - } - let node = rdom.get(removed).unwrap(); - if let Some(children) = node.child_ids() { - for child in children { - remove_children(to_prune, rdom, *child); - } - } - } - if self.focus_iter.prune(mutations, rdom) { - self.dirty = true; - } - for m in &mutations.edits { - match m { - dioxus_core::Mutation::ReplaceWith { id, .. } => remove_children( - &mut [&mut self.last_focused_id], - rdom, - rdom.element_to_node_id(*id), - ), - dioxus_core::Mutation::Remove { id } => remove_children( - &mut [&mut self.last_focused_id], - rdom, - rdom.element_to_node_id(*id), - ), - _ => (), - } - } - } - pub(crate) fn set_focus(&mut self, rdom: &mut RealDom, id: NodeId) { if let Some(old) = self.last_focused_id.replace(id) { - let mut node = rdom.get_mut_raw(old).unwrap(); - node.get_mut::().unwrap().0 = false; + let mut focused = rdom.get_state_mut_raw::(old).unwrap(); + *focused = Focused(false); } - let mut node = rdom.get_mut_raw(id).unwrap(); - node.insert(Focused(true)); + let mut focused = rdom.get_state_mut_raw::(id).unwrap(); + *focused = Focused(true); + let mut node = rdom.get(id).unwrap(); self.focus_level = node.get::().unwrap().level; // reset the position to the currently focused element while self.focus_iter.next(rdom).id() != id {} diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index feec8686c..31b466816 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -1,7 +1,6 @@ use crossterm::event::{ Event as TermEvent, KeyCode as TermKeyCode, KeyModifiers, MouseButton, MouseEventKind, }; -use dioxus_core::*; use dioxus_native_core::real_dom::NodeImmutable; use dioxus_native_core::{NodeId, NodeRef, RealDom}; use rustc_hash::{FxHashMap, FxHashSet}; @@ -13,9 +12,8 @@ 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, FocusData, KeyboardData, MouseData, WheelData}; +use dioxus_html::{event_bubbles, EventData, FocusData, KeyboardData, MouseData, WheelData}; use std::{ - any::Any, cell::{RefCell, RefMut}, rc::Rc, time::{Duration, Instant}, @@ -28,9 +26,9 @@ use crate::layout::TaffyLayout; use crate::{layout_to_screen_space, FocusState}; pub(crate) struct Event { - pub id: ElementId, + pub id: NodeId, pub name: &'static str, - pub data: Rc, + pub data: Rc, pub bubbles: bool, } @@ -71,24 +69,6 @@ pub(crate) struct Event { type EventCore = (&'static str, EventData); -#[derive(Debug)] -enum EventData { - Mouse(MouseData), - Wheel(WheelData), - Screen((u16, u16)), - Keyboard(KeyboardData), -} -impl EventData { - fn into_any(self) -> Rc { - match self { - Self::Mouse(m) => Rc::new(m), - Self::Wheel(w) => Rc::new(w), - Self::Screen(s) => Rc::new(s), - Self::Keyboard(k) => Rc::new(k), - } - } -} - const MAX_REPEAT_TIME: Duration = Duration::from_millis(100); pub struct InnerInputState { @@ -101,14 +81,14 @@ pub struct InnerInputState { } impl InnerInputState { - fn new() -> Self { + fn create(rdom: &mut RealDom) -> Self { Self { mouse: None, wheel: None, last_key_pressed: None, screen: None, // subscribers: Vec::new(), - focus_state: FocusState::default(), + focus_state: FocusState::create(rdom), } } @@ -149,7 +129,6 @@ impl InnerInputState { *m = new_mouse_data; } EventData::Wheel(ref w) => self.wheel = Some(w.clone()), - EventData::Screen(ref s) => self.screen = Some(*s), EventData::Keyboard(ref mut k) => { let is_repeating = self .last_key_pressed @@ -166,6 +145,7 @@ impl InnerInputState { self.last_key_pressed = Some((k.clone(), Instant::now())); } + _ => {} } } @@ -202,31 +182,27 @@ impl InnerInputState { // elements with listeners will always have a element id if let Some(id) = self.focus_state.last_focused_id { let element = dom.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"), - }); - } + resolved_events.push(Event { + name: "focus", + id, + data: Rc::new(EventData::Focus(FocusData {})), + bubbles: event_bubbles("focus"), + }); + resolved_events.push(Event { + name: "focusin", + id, + data: Rc::new(EventData::Focus(FocusData {})), + bubbles: event_bubbles("focusin"), + }); } if let Some(id) = old_focus { let element = dom.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"), - }); - } + resolved_events.push(Event { + name: "focusout", + id, + data: Rc::new(EventData::Focus(FocusData {})), + bubbles: event_bubbles("focusout"), + }); } } @@ -260,29 +236,27 @@ impl InnerInputState { fn try_create_event( name: &'static str, - data: Rc, + data: Rc, will_bubble: &mut FxHashSet, resolved_events: &mut Vec, node: NodeRef, dom: &RealDom, ) { // only trigger event if the event was not triggered already by a child - let id = node.node_data().node_id; + let id = node.id(); if will_bubble.insert(id) { let mut parent = Some(node); while let Some(current_parent) = parent { - let parent_id = current_parent.node_data().node_id; + let parent_id = current_parent.id(); will_bubble.insert(parent_id); parent = current_parent.parent_id().and_then(|id| dom.get(id)); } - if let Some(id) = node.mounted_id() { - resolved_events.push(Event { - name, - id, - data, - bubbles: event_bubbles(name), - }) - } + resolved_events.push(Event { + name, + id, + data, + bubbles: event_bubbles(name), + }) } } @@ -346,7 +320,10 @@ impl InnerInputState { if currently_contains && previously_contained { try_create_event( "mousemove", - Rc::new(prepare_mouse_data(mouse_data, &node_layout)), + Rc::new(EventData::Mouse(prepare_mouse_data( + mouse_data, + &node_layout, + ))), &mut will_bubble, resolved_events, node, @@ -370,7 +347,7 @@ impl InnerInputState { if currently_contains && !previously_contained { try_create_event( "mouseenter", - Rc::new(mouse_data.clone()), + Rc::new(dioxus_html::EventData::Mouse(mouse_data.clone())), &mut will_bubble, resolved_events, node, @@ -393,7 +370,10 @@ impl InnerInputState { if currently_contains && !previously_contained { try_create_event( "mouseover", - Rc::new(prepare_mouse_data(mouse_data, &node_layout)), + Rc::new(EventData::Mouse(prepare_mouse_data( + mouse_data, + &node_layout, + ))), &mut will_bubble, resolved_events, node, @@ -413,7 +393,10 @@ impl InnerInputState { if currently_contains { try_create_event( "mousedown", - Rc::new(prepare_mouse_data(mouse_data, &node_layout)), + Rc::new(EventData::Mouse(prepare_mouse_data( + mouse_data, + &node_layout, + ))), &mut will_bubble, resolved_events, node, @@ -434,7 +417,10 @@ impl InnerInputState { if currently_contains { try_create_event( "mouseup", - Rc::new(prepare_mouse_data(mouse_data, &node_layout)), + Rc::new(EventData::Mouse(prepare_mouse_data( + mouse_data, + &node_layout, + ))), &mut will_bubble, resolved_events, node, @@ -456,7 +442,10 @@ impl InnerInputState { if currently_contains { try_create_event( "click", - Rc::new(prepare_mouse_data(mouse_data, &node_layout)), + Rc::new(EventData::Mouse(prepare_mouse_data( + mouse_data, + &node_layout, + ))), &mut will_bubble, resolved_events, node, @@ -479,7 +468,10 @@ impl InnerInputState { if currently_contains { try_create_event( "contextmenu", - Rc::new(prepare_mouse_data(mouse_data, &node_layout)), + Rc::new(EventData::Mouse(prepare_mouse_data( + mouse_data, + &node_layout, + ))), &mut will_bubble, resolved_events, node, @@ -503,7 +495,7 @@ impl InnerInputState { if currently_contains { try_create_event( "wheel", - Rc::new(w.clone()), + Rc::new(EventData::Wheel(w.clone())), &mut will_bubble, resolved_events, node, @@ -528,7 +520,10 @@ impl InnerInputState { if !currently_contains && previously_contained { try_create_event( "mouseleave", - Rc::new(prepare_mouse_data(mouse_data, &node_layout)), + Rc::new(EventData::Mouse(prepare_mouse_data( + mouse_data, + &node_layout, + ))), &mut will_bubble, resolved_events, node, @@ -551,7 +546,10 @@ impl InnerInputState { if !currently_contains && previously_contained { try_create_event( "mouseout", - Rc::new(prepare_mouse_data(mouse_data, &node_layout)), + Rc::new(EventData::Mouse(prepare_mouse_data( + mouse_data, + &node_layout, + ))), &mut will_bubble, resolved_events, node, @@ -571,7 +569,7 @@ impl InnerInputState { let currently_contains = layout_contains_point(node_layout, new_pos); if currently_contains && node.get::().unwrap().level.focusable() { - focus_id = Some(node.node_data().node_id); + focus_id = Some(node.id()); } }); if let Some(id) = focus_id { @@ -612,7 +610,9 @@ pub struct RinkInputHandler { impl RinkInputHandler { /// global context that handles events /// limitations: GUI key modifier is never detected, key up events are not detected, and only two mouse buttons may be pressed at once - pub fn new() -> ( + pub fn craete( + rdom: &mut RealDom, + ) -> ( Self, Rc>, impl FnMut(crossterm::event::Event), @@ -628,7 +628,7 @@ impl RinkInputHandler { } }; - let state = Rc::new(RefCell::new(InnerInputState::new())); + let state = Rc::new(RefCell::new(InnerInputState::create(rdom))); ( Self { @@ -640,10 +640,6 @@ impl RinkInputHandler { ) } - pub(crate) fn prune(&self, mutations: &dioxus_core::Mutations, rdom: &RealDom) { - self.state.borrow_mut().focus_state.prune(mutations, rdom); - } - pub(crate) fn get_events(&self, layout: &Taffy, dom: &mut RealDom) -> Vec { let mut resolved_events = Vec::new(); @@ -675,14 +671,14 @@ impl RinkInputHandler { ] .contains(&e.0) }) - .map(|evt| (evt.0, evt.1.into_any())); + .map(|evt| (evt.0, evt.1)); - 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); + v.push(Rc::new(data)); } else { - hm.insert(event, vec![data]); + hm.insert(event, vec![Rc::new(data)]); } } for (event, datas) in hm { @@ -690,14 +686,12 @@ impl RinkInputHandler { for data in &datas { let focused = node.get::(); if focused.is_some() && focused.unwrap().0 { - if let Some(id) = node.mounted_id() { - resolved_events.push(Event { - name: event, - id, - data: data.clone(), - bubbles: event_bubbles(event), - }); - } + resolved_events.push(Event { + name: event, + id: node.id(), + data: data.clone(), + bubbles: event_bubbles(event), + }); } } } @@ -779,7 +773,7 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> { MouseEventKind::ScrollUp => ("wheel", get_wheel_data(true)), } } - TermEvent::Resize(x, y) => ("resize", EventData::Screen((x, y))), + _ => return None, }; Some((name, data)) diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index 6e34ba0c9..5dfbd6c0b 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -47,7 +47,7 @@ impl Pass for TaffyLayout { type ParentDependencies = (); type NodeDependencies = (); - const NODE_MASK: NodeMaskBuilder = NodeMaskBuilder::new() + const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new() .with_attrs(AttributeMaskBuilder::Some(SORTED_LAYOUT_ATTRS)) .with_text(); diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index a26cfe547..f39564a4c 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -6,24 +6,17 @@ use crossterm::{ execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; -use dioxus_core::*; -use dioxus_native_core::{node_ref::NodeMaskBuilder, real_dom::NodeImmutable, Pass}; +use dioxus_html::EventData; +use dioxus_native_core::{node_ref::NodeMaskBuilder, real_dom::NodeImmutable, Pass, Renderer}; use dioxus_native_core::{real_dom::RealDom, FxDashSet, NodeId, SendAnyMap}; use focus::FocusState; -use futures::{ - channel::mpsc::{UnboundedReceiver, UnboundedSender}, - pin_mut, StreamExt, -}; +use futures::{channel::mpsc::UnboundedSender, pin_mut, StreamExt}; use futures_channel::mpsc::unbounded; use layout::TaffyLayout; use prevent_default::PreventDefault; -use query::Query; -use std::rc::Rc; -use std::{ - cell::RefCell, - sync::{Arc, Mutex}, -}; +use std::sync::{Arc, Mutex}; use std::{io, time::Duration}; +use std::{rc::Rc, sync::RwLock}; use style_attributes::StyleModifier; use taffy::Taffy; pub use taffy::{geometry::Point, prelude::*}; @@ -41,7 +34,7 @@ mod render; mod style; mod style_attributes; mod widget; -mod widgets; +// mod widgets; pub use config::*; pub use hooks::*; @@ -75,21 +68,21 @@ impl TuiContext { } } -pub fn launch(app: Component<()>) { - launch_cfg(app, Config::default()) -} +pub fn render>>( + cfg: Config, + f: impl FnOnce(&Arc>, &Arc>, UnboundedSender) -> R, +) -> Result<()> { + let mut rdom = RealDom::new(Box::new([ + TaffyLayout::to_type_erased(), + Focus::to_type_erased(), + StyleModifier::to_type_erased(), + PreventDefault::to_type_erased(), + ])); -pub fn launch_cfg(app: Component<()>, cfg: Config) { - launch_cfg_with_props(app, (), cfg); -} - -pub fn launch_cfg_with_props(app: Component, props: Props, cfg: Config) { - let mut dom = VirtualDom::new_with_props(app, props); - - let (handler, state, register_event) = RinkInputHandler::new(); + let (handler, state, mut register_event) = RinkInputHandler::craete(&mut rdom); // Setup input handling - let (event_tx, event_rx) = unbounded(); + let (event_tx, mut event_reciever) = unbounded(); let event_tx_clone = event_tx.clone(); if !cfg.headless { std::thread::spawn(move || { @@ -105,51 +98,19 @@ pub fn launch_cfg_with_props(app: Component, props: Props }); } - let cx = dom.base_scope(); - let rdom = Rc::new(RefCell::new(RealDom::new(Box::new([ - TaffyLayout::to_type_erased(), - Focus::to_type_erased(), - StyleModifier::to_type_erased(), - PreventDefault::to_type_erased(), - ])))); + let rdom = Arc::new(RwLock::new(rdom)); 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(), - }); + let mut renderer = f(&rdom, &taffy, event_tx_clone); { - let mut rdom = rdom.borrow_mut(); - let mutations = dom.rebuild(); - rdom.apply_mutations(mutations); + let mut rdom = rdom.write().unwrap(); + let root_id = rdom.root_id(); + renderer.render(rdom.get_mut(root_id).unwrap()); let mut any_map = SendAnyMap::new(); any_map.insert(taffy.clone()); let _ = rdom.update_state(any_map, false); } - render_vdom( - &mut dom, - event_rx, - handler, - cfg, - rdom, - taffy, - register_event, - ) - .unwrap(); -} - -fn render_vdom( - vdom: &mut VirtualDom, - mut event_reciever: UnboundedReceiver, - handler: RinkInputHandler, - cfg: Config, - rdom: Rc>, - taffy: Arc>, - mut register_event: impl FnMut(crossterm::event::Event), -) -> Result<()> { tokio::runtime::Builder::new_current_thread() .enable_all() .build()? @@ -225,16 +186,16 @@ fn render_vdom( if let Some(terminal) = &mut terminal { execute!(terminal.backend_mut(), SavePosition).unwrap(); terminal.draw(|frame| { - let rdom = rdom.borrow(); + let rdom = rdom.write().unwrap(); 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.get(NodeId(0)).unwrap(); + let root = rdom.get(rdom.root_id()).unwrap(); render::render_vnode(frame, &taffy, root, cfg, Point::ZERO); })?; execute!(terminal.backend_mut(), RestorePosition, Show).unwrap(); } else { - let rdom = rdom.borrow(); + let rdom = rdom.write().unwrap(); resize( Rect { x: 0, @@ -248,13 +209,13 @@ fn render_vdom( } } - let mut hot_reload_msg = None; + // let mut hot_reload_msg = None; { - let wait = vdom.wait_for_work(); - #[cfg(all(feature = "hot-reload", debug_assertions))] - let hot_reload_wait = hot_reload_rx.recv(); - #[cfg(not(all(feature = "hot-reload", debug_assertions)))] - let hot_reload_wait = std::future::pending(); + let wait = renderer.poll_async(); + // #[cfg(all(feature = "hot-reload", debug_assertions))] + // let hot_reload_wait = hot_reload_rx.recv(); + // #[cfg(not(all(feature = "hot-reload", debug_assertions)))] + // let hot_reload_wait = std::future::pending(); pin_mut!(wait); @@ -283,40 +244,40 @@ fn render_vdom( register_event(evt); } }, - Some(msg) = hot_reload_wait => { - hot_reload_msg = Some(msg); - } + // Some(msg) = hot_reload_wait => { + // hot_reload_msg = Some(msg); + // } } } - // if we have a new template, replace the old one - if let Some(msg) = hot_reload_msg { - match msg { - dioxus_hot_reload::HotReloadMsg::UpdateTemplate(template) => { - vdom.replace_template(template); - } - dioxus_hot_reload::HotReloadMsg::Shutdown => { - break; - } - } - } + // // if we have a new template, replace the old one + // if let Some(msg) = hot_reload_msg { + // match msg { + // dioxus_hot_reload::HotReloadMsg::UpdateTemplate(template) => { + // vdom.replace_template(template); + // } + // dioxus_hot_reload::HotReloadMsg::Shutdown => { + // break; + // } + // } + // } { - let evts = { - let mut rdom = rdom.borrow_mut(); - handler.get_events(&taffy.lock().expect("taffy lock poisoned"), &mut rdom) - }; { + let mut rdom = rdom.write().unwrap(); + let evts = handler + .get_events(&taffy.lock().expect("taffy lock poisoned"), &mut rdom); updated |= handler.state().focus_state.clean(); + + for e in evts { + let node = rdom.get_mut(e.id).unwrap(); + renderer.handle_event(node, e.name, e.data); + } } - for e in evts { - vdom.handle_event(e.name, e.data, e.id, e.bubbles) - } - let mut rdom = rdom.borrow_mut(); - let mutations = vdom.render_immediate(); - handler.prune(&mutations, &rdom); + let mut rdom = rdom.write().unwrap(); // updates the dom's nodes - rdom.apply_mutations(mutations); + let root_id = rdom.root_id(); + renderer.render(rdom.get_mut(root_id).unwrap()); // update the style and layout let mut any_map = SendAnyMap::new(); any_map.insert(taffy.clone()); @@ -346,7 +307,7 @@ fn render_vdom( } #[derive(Debug)] -enum InputEvent { +pub enum InputEvent { UserInput(TermEvent), Close, } diff --git a/packages/tui/src/prelude/mod.rs b/packages/tui/src/prelude/mod.rs index 2155f7707..2d5b029a0 100644 --- a/packages/tui/src/prelude/mod.rs +++ b/packages/tui/src/prelude/mod.rs @@ -1 +1 @@ -pub use crate::widgets::*; +// pub use crate::widgets::*; diff --git a/packages/tui/src/prevent_default.rs b/packages/tui/src/prevent_default.rs index e527b099b..0fda912c9 100644 --- a/packages/tui/src/prevent_default.rs +++ b/packages/tui/src/prevent_default.rs @@ -30,7 +30,7 @@ impl Pass for PreventDefault { type ChildDependencies = (); type NodeDependencies = (); - const NODE_MASK: dioxus_native_core::node_ref::NodeMaskBuilder = + const NODE_MASK: dioxus_native_core::node_ref::NodeMaskBuilder<'static> = dioxus_native_core::node_ref::NodeMaskBuilder::new() .with_attrs(dioxus_native_core::node_ref::AttributeMaskBuilder::Some(&[ "dioxus-prevent-default", diff --git a/packages/tui/src/query.rs b/packages/tui/src/query.rs index df1f69fbd..4bf89516d 100644 --- a/packages/tui/src/query.rs +++ b/packages/tui/src/query.rs @@ -4,8 +4,7 @@ use std::{ sync::{Arc, Mutex, MutexGuard}, }; -use dioxus_core::ElementId; -use dioxus_native_core::{real_dom::NodeImmutable, RealDom}; +use dioxus_native_core::{real_dom::NodeImmutable, NodeId, RealDom}; use taffy::{ geometry::Point, prelude::{Layout, Size}, @@ -52,7 +51,7 @@ pub struct Query { } impl Query { - pub fn get(&self, id: ElementId) -> ElementRef { + pub fn get(&self, id: NodeId) -> ElementRef { ElementRef::new( self.rdom.borrow(), self.stretch.lock().expect("taffy lock poisoned"), @@ -64,11 +63,11 @@ impl Query { pub struct ElementRef<'a> { inner: Ref<'a, RealDom>, stretch: MutexGuard<'a, Taffy>, - id: ElementId, + id: NodeId, } impl<'a> ElementRef<'a> { - fn new(inner: Ref<'a, RealDom>, stretch: MutexGuard<'a, Taffy>, id: ElementId) -> Self { + fn new(inner: Ref<'a, RealDom>, stretch: MutexGuard<'a, Taffy>, id: NodeId) -> Self { Self { inner, stretch, id } } diff --git a/packages/tui/src/style_attributes.rs b/packages/tui/src/style_attributes.rs index 27c262ff1..30b1ef2a1 100644 --- a/packages/tui/src/style_attributes.rs +++ b/packages/tui/src/style_attributes.rs @@ -51,7 +51,7 @@ impl Pass for StyleModifier { type NodeDependencies = (); // todo: seperate each attribute into it's own class - const NODE_MASK: NodeMaskBuilder = NodeMaskBuilder::new() + const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new() .with_attrs(AttributeMaskBuilder::Some(SORTED_STYLE_ATTRS)) .with_element(); diff --git a/packages/tui/src/widgets/button.rs b/packages/tui/src/widgets/button.rs index f3ebc7901..a6578905e 100644 --- a/packages/tui/src/widgets/button.rs +++ b/packages/tui/src/widgets/button.rs @@ -37,7 +37,7 @@ pub(crate) fn Button<'a>(cx: Scope<'a, ButtonProps>) -> Element<'a> { } state.set(new_state); }; - cx.render(rsx! { + render! { div{ width: "{width}", height: "{height}", @@ -55,5 +55,5 @@ pub(crate) fn Button<'a>(cx: Scope<'a, ButtonProps>) -> Element<'a> { }, "{text}" } - }) + } } diff --git a/packages/tui/src/widgets/checkbox.rs b/packages/tui/src/widgets/checkbox.rs index 483117212..0b5ebf671 100644 --- a/packages/tui/src/widgets/checkbox.rs +++ b/packages/tui/src/widgets/checkbox.rs @@ -61,7 +61,7 @@ pub(crate) fn CheckBox<'a>(cx: Scope<'a, CheckBoxProps>) -> Element<'a> { } state.set(new_state); }; - cx.render(rsx! { + render! { div { width: "{width}", height: "{height}", @@ -78,5 +78,5 @@ pub(crate) fn CheckBox<'a>(cx: Scope<'a, CheckBoxProps>) -> Element<'a> { }, "{text}" } - }) + } } diff --git a/packages/tui/src/widgets/number.rs b/packages/tui/src/widgets/number.rs index f34618f60..48e198ebd 100644 --- a/packages/tui/src/widgets/number.rs +++ b/packages/tui/src/widgets/number.rs @@ -99,7 +99,7 @@ pub(crate) fn NumbericInput<'a>(cx: Scope<'a, NumbericInputProps>) -> Element<'a update(text.clone()); }; - cx.render(rsx! { + render! { div{ width: "{width}", height: "{height}", @@ -205,5 +205,5 @@ pub(crate) fn NumbericInput<'a>(cx: Scope<'a, NumbericInputProps>) -> Element<'a "{text_after_second_cursor}" } - }) + } } diff --git a/packages/tui/src/widgets/password.rs b/packages/tui/src/widgets/password.rs index 6574ab4f9..714a5ad93 100644 --- a/packages/tui/src/widgets/password.rs +++ b/packages/tui/src/widgets/password.rs @@ -114,7 +114,7 @@ pub(crate) fn Password<'a>(cx: Scope<'a, PasswordProps>) -> Element<'a> { } }; - cx.render(rsx! { + render! { div { width: "{width}", height: "{height}", @@ -187,5 +187,5 @@ pub(crate) fn Password<'a>(cx: Scope<'a, PasswordProps>) -> Element<'a> { "{text_after_second_cursor}" } - }) + } } diff --git a/packages/tui/src/widgets/slider.rs b/packages/tui/src/widgets/slider.rs index 0d5dbca13..32de32ce2 100644 --- a/packages/tui/src/widgets/slider.rs +++ b/packages/tui/src/widgets/slider.rs @@ -62,7 +62,7 @@ pub(crate) fn Slider<'a>(cx: Scope<'a, SliderProps>) -> Element<'a> { } }; - cx.render(rsx! { + render! { div{ width: "{width}", height: "{height}", @@ -103,5 +103,5 @@ pub(crate) fn Slider<'a>(cx: Scope<'a, SliderProps>) -> Element<'a> { background_color: "rgba(10,10,10,0.5)", } } - }) + } } diff --git a/packages/tui/src/widgets/textbox.rs b/packages/tui/src/widgets/textbox.rs index 5e3cc693a..ad48f0800 100644 --- a/packages/tui/src/widgets/textbox.rs +++ b/packages/tui/src/widgets/textbox.rs @@ -79,7 +79,7 @@ pub(crate) fn TextBox<'a>(cx: Scope<'a, TextBoxProps>) -> Element<'a> { "solid" }; - cx.render(rsx! { + render! { div{ width: "{width}", height: "{height}", @@ -178,5 +178,5 @@ pub(crate) fn TextBox<'a>(cx: Scope<'a, TextBoxProps>) -> Element<'a> { "{text_after_second_cursor}" } - }) + } } From 71e34452dae596b6362b3c1048081259c1535683 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 7 Feb 2023 14:14:04 -0600 Subject: [PATCH 041/103] create driven example for tui renderer --- packages/native-core/src/dioxus.rs | 29 ++-- packages/native-core/src/lib.rs | 8 +- packages/native-core/src/node.rs | 20 ++- packages/native-core/src/node_ref.rs | 3 +- packages/native-core/src/real_dom.rs | 25 +++- packages/native-core/src/tree.rs | 7 +- packages/tui/examples/driven.rs | 208 +++++++++++++++++++++++++++ packages/tui/src/hooks.rs | 4 - packages/tui/src/lib.rs | 4 +- packages/tui/src/render.rs | 2 +- 10 files changed, 276 insertions(+), 34 deletions(-) create mode 100644 packages/tui/examples/driven.rs diff --git a/packages/native-core/src/dioxus.rs b/packages/native-core/src/dioxus.rs index f524035a4..fa7ca4802 100644 --- a/packages/native-core/src/dioxus.rs +++ b/packages/native-core/src/dioxus.rs @@ -2,7 +2,10 @@ use dioxus_core::{BorrowedAttributeValue, ElementId, Mutations, TemplateNode}; use rustc_hash::{FxHashMap, FxHashSet}; use crate::{ - node::{ElementNode, FromAnyValue, NodeType, OwnedAttributeDiscription, OwnedAttributeValue}, + node::{ + ElementNode, FromAnyValue, NodeType, OwnedAttributeDiscription, OwnedAttributeValue, + TextNode, + }, prelude::NodeImmutable, real_dom::NodeTypeMut, NodeId, NodeMut, RealDom, @@ -83,7 +86,10 @@ impl DioxusState { self.stack.push(node_id); } CreateTextNode { value, id } => { - let node_data = NodeType::Text(value.to_string()); + let node_data = NodeType::Text(TextNode { + listeners: FxHashSet::default(), + text: value.to_string(), + }); let node = rdom.create_node(node_data); let node_id = node.id(); self.set_element_id(node, id); @@ -97,7 +103,10 @@ impl DioxusState { if let NodeTypeMut::Text(text) = node.node_type_mut() { *text = value.to_string(); } else { - node.set_type(NodeType::Text(value.to_string())); + node.set_type(NodeType::Text(TextNode { + text: value.to_string(), + listeners: FxHashSet::default(), + })); } } LoadTemplate { name, index, id } => { @@ -153,14 +162,12 @@ impl DioxusState { element.remove_attributes(&OwnedAttributeDiscription { name: name.to_string(), namespace: ns.map(|s| s.to_string()), - volatile: false, }); } else { element.set_attribute( OwnedAttributeDiscription { name: name.to_string(), namespace: ns.map(|s| s.to_string()), - volatile: false, }, OwnedAttributeValue::from(value), ); @@ -219,7 +226,6 @@ fn create_template_node(rdom: &mut RealDom, node: &TemplateNode) -> NodeId { OwnedAttributeDiscription { namespace: namespace.map(|s| s.to_string()), name: name.to_string(), - volatile: false, }, OwnedAttributeValue::Text(value.to_string()), )), @@ -235,9 +241,16 @@ fn create_template_node(rdom: &mut RealDom, node: &TemplateNode) -> NodeId { } node_id } - TemplateNode::Text { text } => rdom.create_node(NodeType::Text(text.to_string())).id(), + TemplateNode::Text { text } => rdom + .create_node(NodeType::Text(TextNode { + text: text.to_string(), + ..Default::default() + })) + .id(), TemplateNode::Dynamic { .. } => rdom.create_node(NodeType::Placeholder).id(), - TemplateNode::DynamicText { .. } => rdom.create_node(NodeType::Text(String::new())).id(), + TemplateNode::DynamicText { .. } => { + rdom.create_node(NodeType::Text(TextNode::default())).id() + } } } diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index eeb3b86e2..f5d3bab56 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -35,10 +35,8 @@ pub type FxDashMap = dashmap::DashMap>; pub type FxDashSet = dashmap::DashSet>; pub type SendAnyMap = anymap::Map; -pub trait Renderer { +pub trait Renderer { fn render(&mut self, root: NodeMut); - fn handle_event(&mut self, node: NodeMut, event: &str, value: E); - fn poll_async(&mut self) -> Pin + Send>> { - Box::pin(async {}) - } + fn handle_event(&mut self, node: NodeMut, event: &str, value: E, bubbles: bool); + fn poll_async(&mut self) -> Pin + Send>>; } diff --git a/packages/native-core/src/node.rs b/packages/native-core/src/node.rs index f0fe7073a..51e096a97 100644 --- a/packages/native-core/src/node.rs +++ b/packages/native-core/src/node.rs @@ -1,7 +1,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; use std::{any::Any, fmt::Debug}; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct ElementNode { pub tag: String, pub namespace: Option, @@ -9,10 +9,25 @@ pub struct ElementNode { pub listeners: FxHashSet, } +#[derive(Debug, Clone, Default)] +pub struct TextNode { + pub text: String, + pub listeners: FxHashSet, +} + +impl TextNode { + pub fn new(text: String) -> Self { + Self { + text, + listeners: Default::default(), + } + } +} + /// 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(String), + Text(TextNode), Element(ElementNode), Placeholder, } @@ -21,7 +36,6 @@ pub enum NodeType { pub struct OwnedAttributeDiscription { pub name: String, pub namespace: Option, - pub volatile: bool, } /// An attribute on a DOM node, such as `id="my-thing"` or diff --git a/packages/native-core/src/node_ref.rs b/packages/native-core/src/node_ref.rs index 99b4c53e2..e50374c0b 100644 --- a/packages/native-core/src/node_ref.rs +++ b/packages/native-core/src/node_ref.rs @@ -73,10 +73,11 @@ impl<'a, V: FromAnyValue> NodeView<'a, V> { self.mask .text .then_some(match &self.inner { - NodeType::Text(text) => Some(&**text), + NodeType::Text(text) => Some(&text.text), _ => None, }) .flatten() + .map(|x| &**x) } /// Get the listeners if it is enabled in the mask diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index f466d2803..9ca94bb4c 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -5,7 +5,7 @@ use std::rc::Rc; use std::sync::RwLock; use crate::node::{ - ElementNode, FromAnyValue, NodeType, OwnedAttributeDiscription, OwnedAttributeValue, + ElementNode, FromAnyValue, NodeType, OwnedAttributeDiscription, OwnedAttributeValue, TextNode, }; use crate::node_ref::{NodeMask, NodeMaskBuilder}; use crate::node_watcher::NodeWatcher; @@ -487,6 +487,19 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { pub fn remove(&mut self) { let id = self.id(); + if let NodeType::Element(ElementNode { listeners, .. }) + | NodeType::Text(TextNode { listeners, .. }) = + self.dom.get_state_mut_raw::>(id).unwrap() + { + let listeners = std::mem::take(listeners); + for event in listeners { + self.dom + .nodes_listening + .get_mut(&event) + .unwrap() + .remove(&id); + } + } self.mark_removed(); if let Some(parent_id) = self.real_dom_mut().tree.parent_id(id) { self.real_dom_mut() @@ -519,7 +532,9 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { pub fn add_event_listener(&mut self, event: &str) { let id = self.id(); let node_type: &mut NodeType = self.dom.tree.get_mut(self.id).unwrap(); - if let NodeType::Element(ElementNode { listeners, .. }) = node_type { + if let NodeType::Element(ElementNode { listeners, .. }) + | NodeType::Text(TextNode { listeners, .. }) = node_type + { self.dom .dirty_nodes .mark_dirty(self.id, NodeMaskBuilder::new().with_listeners().build()); @@ -540,7 +555,9 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { pub fn remove_event_listener(&mut self, event: &str) { let id = self.id(); let node_type: &mut NodeType = self.dom.tree.get_mut(self.id).unwrap(); - if let NodeType::Element(ElementNode { listeners, .. }) = node_type { + if let NodeType::Element(ElementNode { listeners, .. }) + | NodeType::Text(TextNode { listeners, .. }) = node_type + { self.dom .dirty_nodes .mark_dirty(self.id, NodeMaskBuilder::new().with_listeners().build()); @@ -579,7 +596,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { NodeType::Text(text) => { dirty_nodes.mark_dirty(self.id, NodeMaskBuilder::new().with_text().build()); - NodeTypeMut::Text(text) + NodeTypeMut::Text(&mut text.text) } NodeType::Placeholder => NodeTypeMut::Placeholder, } diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index 580dd4848..1ba4f0bda 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -262,12 +262,7 @@ pub struct TreeStateViewEntry<'a, 'b> { impl<'a, 'b> AnyMapLike<'a> for TreeStateViewEntry<'a, 'b> { fn get(self) -> Option<&'a T> { let slab = self.view.get_slab(); - dbg!(slab.is_some()); - slab.and_then(|slab| { - let r = slab.get(self.id); - dbg!(r.is_some()); - r - }) + slab.and_then(|slab| slab.get(self.id)) } } diff --git a/packages/tui/examples/driven.rs b/packages/tui/examples/driven.rs new file mode 100644 index 000000000..de2ef0025 --- /dev/null +++ b/packages/tui/examples/driven.rs @@ -0,0 +1,208 @@ +use dioxus_html::EventData; +use dioxus_native_core::{ + node::{OwnedAttributeDiscription, OwnedAttributeValue, TextNode}, + prelude::*, + real_dom::{NodeImmutable, NodeTypeMut}, + NodeId, Renderer, +}; +use dioxus_tui::{self, render, Config}; +use std::sync::{Arc, RwLock}; +use std::{rc::Rc, sync::Mutex}; +use taffy::Taffy; + +struct Test([[usize; 10]; 10]); + +impl Renderer> for Test { + fn render(&mut self, mut root: dioxus_native_core::NodeMut) { + // Set the root node to be a flexbox with a column direction. + if let NodeTypeMut::Element(mut el) = root.node_type_mut() { + el.set_attribute( + OwnedAttributeDiscription { + name: "display".into(), + namespace: None, + }, + OwnedAttributeValue::Text("flex".into()), + ); + el.set_attribute( + OwnedAttributeDiscription { + name: "flex-direction".into(), + namespace: None, + }, + OwnedAttributeValue::Text("column".into()), + ); + el.set_attribute( + OwnedAttributeDiscription { + name: "width".into(), + namespace: None, + }, + OwnedAttributeValue::Text("100%".into()), + ); + el.set_attribute( + OwnedAttributeDiscription { + name: "height".into(), + namespace: None, + }, + OwnedAttributeValue::Text("100%".into()), + ); + } + + let root_id = root.id(); + // Remove old grid. Frameworks should retain the grid and only update the values. + let children_ids = root.child_ids().map(|ids| ids.to_vec()); + let rdom = root.real_dom_mut(); + if let Some(children) = children_ids { + for child in children { + rdom.get_mut(child).unwrap().remove(); + } + } + + // create the grid + for (x, row) in self.0.iter().copied().enumerate() { + let row_node = rdom + .create_node(NodeType::Element(ElementNode { + tag: "div".to_string(), + attributes: [ + ( + OwnedAttributeDiscription { + name: "display".into(), + namespace: None, + }, + OwnedAttributeValue::Text("flex".into()), + ), + ( + OwnedAttributeDiscription { + name: "flex-direction".into(), + namespace: None, + }, + OwnedAttributeValue::Text("row".into()), + ), + ( + OwnedAttributeDiscription { + name: "width".into(), + namespace: None, + }, + OwnedAttributeValue::Text("100%".into()), + ), + ( + OwnedAttributeDiscription { + name: "height".into(), + namespace: None, + }, + OwnedAttributeValue::Text("100%".into()), + ), + ] + .into_iter() + .collect(), + ..Default::default() + })) + .id(); + for (y, count) in row.iter().copied().enumerate() { + let node = rdom + .create_node(NodeType::Text(TextNode::new(count.to_string()))) + .id(); + let mut button = rdom.create_node(NodeType::Element(ElementNode { + tag: "div".to_string(), + attributes: [ + ( + OwnedAttributeDiscription { + name: "background-color".into(), + namespace: None, + }, + OwnedAttributeValue::Text(format!( + "rgb({}, {}, {})", + count * 10, + 0, + (x + y) * 10, + )), + ), + ( + OwnedAttributeDiscription { + name: "width".into(), + namespace: None, + }, + OwnedAttributeValue::Text("100%".into()), + ), + ( + OwnedAttributeDiscription { + name: "height".into(), + namespace: None, + }, + OwnedAttributeValue::Text("100%".into()), + ), + ( + OwnedAttributeDiscription { + name: "display".into(), + namespace: None, + }, + OwnedAttributeValue::Text("flex".into()), + ), + ( + OwnedAttributeDiscription { + name: "flex-direction".into(), + namespace: None, + }, + OwnedAttributeValue::Text("row".into()), + ), + ( + OwnedAttributeDiscription { + name: "justify-content".into(), + namespace: None, + }, + OwnedAttributeValue::Text("center".into()), + ), + ( + OwnedAttributeDiscription { + name: "align-items".into(), + namespace: None, + }, + OwnedAttributeValue::Text("center".into()), + ), + ] + .into_iter() + .collect(), + ..Default::default() + })); + button.add_event_listener("click"); + button.add_event_listener("wheel"); + button.add_child(node); + let button_id = button.id(); + rdom.get_mut(row_node).unwrap().add_child(button_id); + } + rdom.get_mut(root_id).unwrap().add_child(row_node); + } + } + + fn handle_event( + &mut self, + node: dioxus_native_core::NodeMut<()>, + event: &str, + value: Rc, + bubbles: bool, + ) { + if let Some(parent) = node.parent() { + let child_number = parent + .child_ids() + .unwrap() + .iter() + .position(|id| *id == node.id()) + .unwrap(); + if let Some(parents_parent) = parent.parent() { + let parents_child_number = parents_parent + .child_ids() + .unwrap() + .iter() + .position(|id| *id == parent.id()) + .unwrap(); + self.0[parents_child_number][child_number] += 1; + } + } + } + + fn poll_async(&mut self) -> std::pin::Pin + Send>> { + Box::pin(async move { tokio::time::sleep(std::time::Duration::from_millis(1000)).await }) + } +} + +fn main() { + render(Config::new(), |_, _, _| Test(Default::default())).unwrap(); +} diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 31b466816..8c509f592 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -75,7 +75,6 @@ pub struct InnerInputState { mouse: Option, wheel: Option, last_key_pressed: Option<(KeyboardData, Instant)>, - screen: Option<(u16, u16)>, pub(crate) focus_state: FocusState, // subscribers: Vec>, } @@ -86,7 +85,6 @@ impl InnerInputState { mouse: None, wheel: None, last_key_pressed: None, - screen: None, // subscribers: Vec::new(), focus_state: FocusState::create(rdom), } @@ -181,7 +179,6 @@ impl InnerInputState { if old_focus != self.focus_state.last_focused_id { // elements with listeners will always have a element id if let Some(id) = self.focus_state.last_focused_id { - let element = dom.get(id).unwrap(); resolved_events.push(Event { name: "focus", id, @@ -196,7 +193,6 @@ impl InnerInputState { }); } if let Some(id) = old_focus { - let element = dom.get(id).unwrap(); resolved_events.push(Event { name: "focusout", id, diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index f39564a4c..30a207c23 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -68,7 +68,7 @@ impl TuiContext { } } -pub fn render>>( +pub fn render>>( cfg: Config, f: impl FnOnce(&Arc>, &Arc>, UnboundedSender) -> R, ) -> Result<()> { @@ -271,7 +271,7 @@ pub fn render>>( for e in evts { let node = rdom.get_mut(e.id).unwrap(); - renderer.handle_event(node, e.name, e.data); + renderer.handle_event(node, e.name, e.data, e.bubbles); } } let mut rdom = rdom.write().unwrap(); diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index fe6600ade..c10c52252 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -67,7 +67,7 @@ pub(crate) fn render_vnode( } let label = Label { - text, + text: &text.text, style: node.get::().unwrap().core, }; let area = Rect::new(x, y, width, height); From 4ee8b8b676a9e0356e468c36b78c19fb73249511 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 7 Feb 2023 14:51:30 -0600 Subject: [PATCH 042/103] create dioxus handler for TUI --- packages/native-core/src/lib.rs | 2 +- packages/tui/src/dioxus.rs | 142 +++++++++++++++++++++----------- packages/tui/src/focus.rs | 8 +- packages/tui/src/hooks.rs | 46 +---------- packages/tui/src/lib.rs | 46 ++--------- packages/tui/src/query.rs | 18 ++-- 6 files changed, 117 insertions(+), 145 deletions(-) diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index f5d3bab56..9e8dbe7a1 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -38,5 +38,5 @@ pub type SendAnyMap = anymap::Map; pub trait Renderer { fn render(&mut self, root: NodeMut); fn handle_event(&mut self, node: NodeMut, event: &str, value: E, bubbles: bool); - fn poll_async(&mut self) -> Pin + Send>>; + fn poll_async(&mut self) -> Pin + '_>>; } diff --git a/packages/tui/src/dioxus.rs b/packages/tui/src/dioxus.rs index c45918493..cd4eccf1f 100644 --- a/packages/tui/src/dioxus.rs +++ b/packages/tui/src/dioxus.rs @@ -1,3 +1,15 @@ +use std::{ops::Deref, rc::Rc}; + +use dioxus_core::{Component, VirtualDom}; +use dioxus_html::EventData; +use dioxus_native_core::{ + dioxus::{DioxusState, NodeImmutableDioxusExt}, + Renderer, +}; +use tokio::select; + +use crate::{query::Query, render, Config, TuiContext}; + pub fn launch(app: Component<()>) { launch_cfg(app, Config::default()) } @@ -7,60 +19,92 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) { } pub fn launch_cfg_with_props(app: Component, props: Props, cfg: Config) { - let mut dom = VirtualDom::new_with_props(app, props); - let mut rdom = RealDom::new(Box::new([ - TaffyLayout::to_type_erased(), - Focus::to_type_erased(), - StyleModifier::to_type_erased(), - PreventDefault::to_type_erased(), - ])); + render(cfg, |rdom, taffy, event_tx| { + let mut vdom = VirtualDom::new_with_props(app, props) + .with_root_context(TuiContext { tx: event_tx }) + .with_root_context(Query { + rdom: rdom.clone(), + stretch: taffy.clone(), + }); + let muts = vdom.rebuild(); + let mut rdom = rdom.write().unwrap(); + let mut dioxus_state = DioxusState::create(&mut rdom); + dioxus_state.apply_mutations(&mut rdom, muts); + DioxusRenderer { + vdom, + dioxus_state, + #[cfg(all(feature = "hot-reload", debug_assertions))] + hot_reload_rx: { + let (hot_reload_tx, hot_reload_rx) = + tokio::sync::mpsc::unbounded_channel::(); + dioxus_hot_reload::connect(move |msg| { + let _ = hot_reload_tx.send(msg); + }); + hot_reload_rx + }, + } + }) + .unwrap(); +} - let (handler, state, register_event) = RinkInputHandler::craete(&mut rdom); +struct DioxusRenderer { + vdom: VirtualDom, + dioxus_state: DioxusState, + #[cfg(all(feature = "hot-reload", debug_assertions))] + hot_reload_rx: tokio::sync::mpsc::UnboundedReceiver, +} - // Setup input handling - let (event_tx, event_rx) = unbounded(); - let event_tx_clone = event_tx.clone(); - if !cfg.headless { - std::thread::spawn(move || { - let tick_rate = Duration::from_millis(1000); - loop { - if crossterm::event::poll(tick_rate).unwrap() { - let evt = crossterm::event::read().unwrap(); - if event_tx.unbounded_send(InputEvent::UserInput(evt)).is_err() { - break; +impl Renderer> for DioxusRenderer { + fn render(&mut self, mut root: dioxus_native_core::NodeMut<()>) { + let rdom = root.real_dom_mut(); + let muts = self.vdom.render_immediate(); + self.dioxus_state.apply_mutations(rdom, muts); + } + + fn handle_event( + &mut self, + node: dioxus_native_core::NodeMut<()>, + event: &str, + value: Rc, + bubbles: bool, + ) { + if let Some(id) = node.mounted_id() { + self.vdom + .handle_event(event, value.deref().clone().into_any(), id, bubbles); + } + } + + fn poll_async(&mut self) -> std::pin::Pin + '_>> { + #[cfg(all(feature = "hot-reload", debug_assertions))] + return Box::pin(async { + let hot_reload_wait = self.hot_reload_rx.recv(); + let mut hot_reload_msg = None; + let wait_for_work = self.vdom.wait_for_work(); + select! { + Some(msg) = hot_reload_wait => { + #[cfg(all(feature = "hot-reload", debug_assertions))] + { + hot_reload_msg = Some(msg); + } + #[cfg(not(all(feature = "hot-reload", debug_assertions)))] + let () = msg; + } + _ = wait_for_work => {} + } + // if we have a new template, replace the old one + if let Some(msg) = hot_reload_msg { + match msg { + dioxus_hot_reload::HotReloadMsg::UpdateTemplate(template) => { + self.vdom.replace_template(template); + } + dioxus_hot_reload::HotReloadMsg::Shutdown => { + std::process::exit(0); } } } }); + + #[cfg(not(all(feature = "hot-reload", debug_assertions)))] + Box::pin(self.vdom.wait_for_work()) } - - let cx = dom.base_scope(); - let rdom = Rc::new(RefCell::new(rdom)); - 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(), - }); - - { - let mut rdom = rdom.borrow_mut(); - let mutations = dom.rebuild(); - rdom.apply_mutations(mutations); - let mut any_map = SendAnyMap::new(); - any_map.insert(taffy.clone()); - let _ = rdom.update_state(any_map, false); - } - - render_vdom( - &mut dom, - event_rx, - handler, - cfg, - rdom, - taffy, - register_event, - ) - .unwrap(); } diff --git a/packages/tui/src/focus.rs b/packages/tui/src/focus.rs index e2958df61..8379444d8 100644 --- a/packages/tui/src/focus.rs +++ b/packages/tui/src/focus.rs @@ -147,7 +147,7 @@ pub(crate) struct FocusState { impl FocusState { pub fn create(rdom: &mut RealDom) -> Self { - let mut focus_iter = PersistantElementIter::create(rdom); + let focus_iter = PersistantElementIter::create(rdom); Self { focus_iter, last_focused_id: Default::default(), @@ -271,12 +271,12 @@ impl FocusState { pub(crate) fn set_focus(&mut self, rdom: &mut RealDom, id: NodeId) { if let Some(old) = self.last_focused_id.replace(id) { - let mut focused = rdom.get_state_mut_raw::(old).unwrap(); + let focused = rdom.get_state_mut_raw::(old).unwrap(); *focused = Focused(false); } - let mut focused = rdom.get_state_mut_raw::(id).unwrap(); + let focused = rdom.get_state_mut_raw::(id).unwrap(); *focused = Focused(true); - let mut node = rdom.get(id).unwrap(); + let node = rdom.get(id).unwrap(); self.focus_level = node.get::().unwrap().level; // reset the position to the currently focused element while self.focus_iter.next(rdom).id() != id {} diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 8c509f592..73d101bd5 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -32,41 +32,6 @@ pub(crate) struct Event { pub bubbles: bool, } -// a wrapper around the input state for easier access -// todo: fix loop -// pub struct InputState(Rc>>); -// impl InputState { -// pub fn get(cx: &ScopeState) -> InputState { -// let inner = cx -// .consume_context::>>() -// .expect("Rink InputState can only be used in Rink apps!"); -// (**inner).borrow_mut().subscribe(cx.schedule_update()); -// InputState(inner) -// } - -// pub fn mouse(&self) -> Option { -// let data = (**self.0).borrow(); -// mouse.as_ref().map(|m| m.clone()) -// } - -// pub fn wheel(&self) -> Option { -// let data = (**self.0).borrow(); -// wheel.as_ref().map(|w| w.clone()) -// } - -// pub fn screen(&self) -> Option<(u16, u16)> { -// let data = (**self.0).borrow(); -// screen.as_ref().map(|m| m.clone()) -// } - -// pub fn last_key_pressed(&self) -> Option { -// let data = (**self.0).borrow(); -// last_key_pressed -// .as_ref() -// .map(|k| &k.0.clone()) -// } -// } - type EventCore = (&'static str, EventData); const MAX_REPEAT_TIME: Duration = Duration::from_millis(100); @@ -606,13 +571,7 @@ pub struct RinkInputHandler { impl RinkInputHandler { /// global context that handles events /// limitations: GUI key modifier is never detected, key up events are not detected, and only two mouse buttons may be pressed at once - pub fn craete( - rdom: &mut RealDom, - ) -> ( - Self, - Rc>, - impl FnMut(crossterm::event::Event), - ) { + pub fn create(rdom: &mut RealDom) -> (Self, impl FnMut(crossterm::event::Event)) { let queued_events = Rc::new(RefCell::new(Vec::new())); let queued_events2 = Rc::downgrade(&queued_events); @@ -628,10 +587,9 @@ impl RinkInputHandler { ( Self { - state: state.clone(), + state, queued_events, }, - state, regester_event, ) } diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index efcd4ed64..1de52893b 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -24,6 +24,8 @@ use tokio::select; use tui::{backend::CrosstermBackend, layout::Rect, Terminal}; mod config; +#[cfg(feature = "dioxus")] +pub mod dioxus; mod focus; mod hooks; mod layout; @@ -36,6 +38,8 @@ mod style_attributes; mod widget; // mod widgets; +#[cfg(feature = "dioxus")] +pub use crate::dioxus::*; pub use config::*; pub use hooks::*; @@ -56,6 +60,7 @@ pub(crate) fn layout_to_screen_space(layout: f32) -> f32 { pub struct TuiContext { tx: UnboundedSender, } + impl TuiContext { pub fn quit(&self) { self.tx.unbounded_send(InputEvent::Close).unwrap(); @@ -79,7 +84,7 @@ pub fn render>>( PreventDefault::to_type_erased(), ])); - let (handler, state, mut register_event) = RinkInputHandler::craete(&mut rdom); + let (handler, mut register_event) = RinkInputHandler::create(&mut rdom); // Setup input handling let (event_tx, mut event_reciever) = unbounded(); @@ -115,15 +120,6 @@ pub fn render>>( .enable_all() .build()? .block_on(async { - #[cfg(all(feature = "hot-reload", debug_assertions))] - let mut hot_reload_rx = { - let (hot_reload_tx, hot_reload_rx) = - tokio::sync::mpsc::unbounded_channel::(); - dioxus_hot_reload::connect(move |msg| { - let _ = hot_reload_tx.send(msg); - }); - hot_reload_rx - }; let mut terminal = (!cfg.headless).then(|| { enable_raw_mode().unwrap(); let mut stdout = std::io::stdout(); @@ -209,13 +205,8 @@ pub fn render>>( } } - // let mut hot_reload_msg = None; { let wait = renderer.poll_async(); - // #[cfg(all(feature = "hot-reload", debug_assertions))] - // let hot_reload_wait = hot_reload_rx.recv(); - // #[cfg(not(all(feature = "hot-reload", debug_assertions)))] - // let hot_reload_wait: std::future::Pending> = std::future::pending(); pin_mut!(wait); @@ -228,8 +219,8 @@ pub fn render>>( InputEvent::UserInput(event) => match event { TermEvent::Key(key) => { if matches!(key.code, KeyCode::Char('C' | 'c')) - && key.modifiers.contains(KeyModifiers::CONTROL) - && cfg.ctrl_c_quit + && key.modifiers.contains(KeyModifiers::CONTROL) + && cfg.ctrl_c_quit { break; } @@ -244,30 +235,9 @@ pub fn render>>( register_event(evt); } }, - // Some(msg) = hot_reload_wait => { - // #[cfg(all(feature = "hot-reload", debug_assertions))] - // { - // hot_reload_msg = Some(msg); - // } - // #[cfg(not(all(feature = "hot-reload", debug_assertions)))] - // let () = msg; - // } } } - // // if we have a new template, replace the old one - // #[cfg(all(feature = "hot-reload", debug_assertions))] - // if let Some(msg) = hot_reload_msg { - // match msg { - // dioxus_hot_reload::HotReloadMsg::UpdateTemplate(template) => { - // vdom.replace_template(template); - // } - // dioxus_hot_reload::HotReloadMsg::Shutdown => { - // break; - // } - // } - // } - { { let mut rdom = rdom.write().unwrap(); diff --git a/packages/tui/src/query.rs b/packages/tui/src/query.rs index 4bf89516d..40606e743 100644 --- a/packages/tui/src/query.rs +++ b/packages/tui/src/query.rs @@ -1,8 +1,4 @@ -use std::{ - cell::{Ref, RefCell}, - rc::Rc, - sync::{Arc, Mutex, MutexGuard}, -}; +use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard}; use dioxus_native_core::{real_dom::NodeImmutable, NodeId, RealDom}; use taffy::{ @@ -46,14 +42,14 @@ use crate::{layout::TaffyLayout, layout_to_screen_space}; /// ``` #[derive(Clone)] pub struct Query { - pub(crate) rdom: Rc>, + pub(crate) rdom: Arc>, pub(crate) stretch: Arc>, } impl Query { pub fn get(&self, id: NodeId) -> ElementRef { ElementRef::new( - self.rdom.borrow(), + self.rdom.read().expect("rdom lock poisoned"), self.stretch.lock().expect("taffy lock poisoned"), id, ) @@ -61,13 +57,17 @@ impl Query { } pub struct ElementRef<'a> { - inner: Ref<'a, RealDom>, + inner: RwLockReadGuard<'a, RealDom>, stretch: MutexGuard<'a, Taffy>, id: NodeId, } impl<'a> ElementRef<'a> { - fn new(inner: Ref<'a, RealDom>, stretch: MutexGuard<'a, Taffy>, id: NodeId) -> Self { + fn new( + inner: RwLockReadGuard<'a, RealDom>, + stretch: MutexGuard<'a, Taffy>, + id: NodeId, + ) -> Self { Self { inner, stretch, id } } From 9702ee3e19b0ba160df99e5eac56932e6ce7e76b Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 7 Feb 2023 14:58:38 -0600 Subject: [PATCH 043/103] remove some unused items and inline common methods --- packages/native-core/src/real_dom.rs | 37 ++++++++++++++++++---------- packages/tui/src/dioxus.rs | 3 +-- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 9ca94bb4c..976c5c708 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -178,18 +178,15 @@ impl RealDom { } pub fn get(&self, id: NodeId) -> Option> { - let id = id.into_node_id(self); self.tree.contains(id).then_some(NodeRef { id, dom: self }) } pub fn get_mut(&mut self, id: NodeId) -> Option> { - let id = id.into_node_id(self); self.tree.contains(id).then(|| NodeMut::new(id, self)) } /// WARNING: This escapes the reactive system that the real dom uses. Any changes made with this method will not trigger updates in states when [RealDom::update_state] is called. pub fn get_state_mut_raw(&mut self, id: NodeId) -> Option<&mut T> { - let id = id.into_node_id(self); self.tree.get_mut(id) } @@ -284,33 +281,27 @@ impl RealDom { } } -pub trait IntoNodeId { - fn into_node_id(self, rdom: &RealDom) -> NodeId; -} - -impl IntoNodeId for NodeId { - fn into_node_id(self, _rdom: &RealDom) -> NodeId { - self - } -} - pub trait NodeImmutable: Sized { fn real_dom(&self) -> &RealDom; fn id(&self) -> NodeId; + #[inline] fn node_type(&self) -> &NodeType { self.get().unwrap() } + #[inline] fn get(&self) -> Option<&T> { self.real_dom().tree.get(self.id()) } + #[inline] fn child_ids(&self) -> Option<&[NodeId]> { self.real_dom().tree.children_ids(self.id()) } + #[inline] fn children(&self) -> Option>> { self.child_ids().map(|ids| { ids.iter() @@ -322,10 +313,12 @@ pub trait NodeImmutable: Sized { }) } + #[inline] fn parent_id(&self) -> Option { self.real_dom().tree.parent_id(self.id()) } + #[inline] fn parent(&self) -> Option> { self.parent_id().map(|id| NodeRef { id, @@ -333,6 +326,7 @@ pub trait NodeImmutable: Sized { }) } + #[inline] fn next(&self) -> Option> { let parent = self.parent_id()?; let children = self.real_dom().tree.children_ids(parent)?; @@ -347,6 +341,7 @@ pub trait NodeImmutable: Sized { } } + #[inline] fn prev(&self) -> Option> { let parent = self.parent_id()?; let children = self.real_dom().tree.children_ids(parent)?; @@ -379,10 +374,12 @@ impl<'a, V: FromAnyValue + Send + Sync> Clone for NodeRef<'a, V> { impl<'a, V: FromAnyValue + Send + Sync> Copy for NodeRef<'a, V> {} impl<'a, V: FromAnyValue + Send + Sync> NodeImmutable for NodeRef<'a, V> { + #[inline(always)] fn real_dom(&self) -> &RealDom { self.dom } + #[inline(always)] fn id(&self) -> NodeId { self.id } @@ -400,20 +397,24 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { } impl<'a, V: FromAnyValue + Send + Sync> NodeImmutable for NodeMut<'a, V> { + #[inline(always)] fn real_dom(&self) -> &RealDom { self.dom } + #[inline(always)] fn id(&self) -> NodeId { self.id } } impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { + #[inline(always)] pub fn real_dom_mut(&mut self) -> &mut RealDom { self.dom } + #[inline] pub fn get_mut(&mut self) -> Option<&mut T> { // mark the node state as dirty self.dom @@ -425,6 +426,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { self.dom.tree.get_mut(self.id) } + #[inline] pub fn insert(&mut self, value: T) { // mark the node state as dirty self.dom @@ -436,6 +438,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { self.dom.tree.insert(self.id, value); } + #[inline] pub fn next_mut(self) -> Option> { let parent = self.parent_id()?; let children = self.dom.tree.children_ids(parent)?; @@ -447,6 +450,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { } } + #[inline] pub fn prev_mut(self) -> Option> { let parent = self.parent_id()?; let children = self.dom.tree.children_ids(parent)?; @@ -458,6 +462,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { } } + #[inline] pub fn add_child(&mut self, child: NodeId) { self.dom.dirty_nodes.mark_child_changed(self.id); self.dom.dirty_nodes.mark_parent_added_or_removed(child); @@ -465,6 +470,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { NodeMut::new(child, self.dom).mark_moved(); } + #[inline] pub fn insert_after(&mut self, old: NodeId) { let id = self.id(); if let Some(parent_id) = self.dom.tree.parent_id(old) { @@ -475,6 +481,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { self.mark_moved(); } + #[inline] pub fn insert_before(&mut self, old: NodeId) { let id = self.id(); if let Some(parent_id) = self.dom.tree.parent_id(old) { @@ -485,6 +492,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { self.mark_moved(); } + #[inline] pub fn remove(&mut self) { let id = self.id(); if let NodeType::Element(ElementNode { listeners, .. }) @@ -515,6 +523,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { self.dom.tree.remove_single(id); } + #[inline] pub fn replace(&mut self, new: NodeId) { self.mark_removed(); if let Some(parent_id) = self.parent_id() { @@ -529,6 +538,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { self.dom.tree.replace(id, new); } + #[inline] pub fn add_event_listener(&mut self, event: &str) { let id = self.id(); let node_type: &mut NodeType = self.dom.tree.get_mut(self.id).unwrap(); @@ -552,6 +562,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { } } + #[inline] pub fn remove_event_listener(&mut self, event: &str) { let id = self.id(); let node_type: &mut NodeType = self.dom.tree.get_mut(self.id).unwrap(); diff --git a/packages/tui/src/dioxus.rs b/packages/tui/src/dioxus.rs index cd4eccf1f..32b5d4941 100644 --- a/packages/tui/src/dioxus.rs +++ b/packages/tui/src/dioxus.rs @@ -6,7 +6,6 @@ use dioxus_native_core::{ dioxus::{DioxusState, NodeImmutableDioxusExt}, Renderer, }; -use tokio::select; use crate::{query::Query, render, Config, TuiContext}; @@ -80,7 +79,7 @@ impl Renderer> for DioxusRenderer { let hot_reload_wait = self.hot_reload_rx.recv(); let mut hot_reload_msg = None; let wait_for_work = self.vdom.wait_for_work(); - select! { + tokio::select! { Some(msg) = hot_reload_wait => { #[cfg(all(feature = "hot-reload", debug_assertions))] { From a7205070948319b87b580a39051282e83b037b99 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 7 Feb 2023 20:55:01 -0600 Subject: [PATCH 044/103] improve performance for TUI --- packages/tui/examples/stress.rs | 114 ++++++++++++++++++++++++++++++++ packages/tui/src/lib.rs | 8 ++- 2 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 packages/tui/examples/stress.rs diff --git a/packages/tui/examples/stress.rs b/packages/tui/examples/stress.rs new file mode 100644 index 000000000..4e794ec30 --- /dev/null +++ b/packages/tui/examples/stress.rs @@ -0,0 +1,114 @@ +use dioxus::prelude::*; +use dioxus_tui::{Config, TuiContext}; + +/// This benchmarks the cache performance of the TUI for small edits by changing one box at a time. +fn main() { + for size in 1..=8usize { + for _ in 0..10 { + dioxus_tui::launch_cfg_with_props(app, size, Config::default().with_headless()) + } + } +} + +#[derive(Props, PartialEq)] +struct BoxProps { + x: usize, + y: usize, + hue: f32, + alpha: f32, +} +#[allow(non_snake_case)] +fn Box(cx: Scope) -> Element { + let count = use_state(cx, || 0); + + let x = cx.props.x * 2; + let y = cx.props.y * 2; + let hue = cx.props.hue; + let display_hue = cx.props.hue as u32 / 10; + let count = count.get(); + let alpha = cx.props.alpha + (count % 100) as f32; + + cx.render(rsx! { + div { + left: "{x}%", + top: "{y}%", + width: "100%", + height: "100%", + background_color: "hsl({hue}, 100%, 50%, {alpha}%)", + align_items: "center", + p{"{display_hue:03}"} + } + }) +} + +#[derive(Props, PartialEq)] +struct GridProps { + size: usize, +} +#[allow(non_snake_case)] +fn Grid(cx: Scope) -> Element { + let size = cx.props.size; + let count = use_state(cx, || 0); + let counts = use_ref(cx, || vec![0; size * size]); + + let ctx: TuiContext = cx.consume_context().unwrap(); + if *count.get() + 1 >= (size * size) { + ctx.quit(); + } else { + counts.with_mut(|c| { + let i = *count.current(); + c[i] += 1; + c[i] %= 360; + }); + count.with_mut(|i| { + *i += 1; + *i %= size * size; + }); + } + + render! { + div{ + width: "100%", + height: "100%", + flex_direction: "column", + (0..size).map(|x| + { + rsx! { + div{ + width: "100%", + height: "100%", + flex_direction: "row", + (0..size).map(|y| + { + let alpha = y as f32*100.0/size as f32 + counts.read()[x*size + y] as f32; + let key = format!("{}-{}", x, y); + rsx! { + Box{ + x: x, + y: y, + alpha: 100.0, + hue: alpha, + key: "{key}", + } + } + } + ) + } + } + } + ) + } + } +} + +fn app(cx: Scope) -> Element { + cx.render(rsx! { + div{ + width: "100%", + height: "100%", + Grid{ + size: *cx.props, + } + } + }) +} diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 1de52893b..ff0d85cfa 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -165,13 +165,15 @@ pub fn render>>( .unwrap(); // the root node fills the entire area - let mut style = *taffy.style(root_node).unwrap(); - style.size = Size { + let new_size = Size { width: Dimension::Points(width), height: Dimension::Points(height), }; - taffy.set_style(root_node, style).unwrap(); + if style.size != new_size { + style.size = new_size; + taffy.set_style(root_node, style).unwrap(); + } let size = Size { width: AvailableSpace::Definite(width), From 997986ecf6da6481f64fad162bbb5ca31ca4b8fc Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 7 Feb 2023 21:14:58 -0600 Subject: [PATCH 045/103] clean up passes code --- packages/native-core/src/passes.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index 761e4a72a..74c5dbacd 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -54,12 +54,12 @@ pub struct DirtyNodeStates { impl DirtyNodeStates { pub fn with_passes(passes: impl Iterator) -> Self { - let mut dirty = FxHashMap::default(); - for pass in passes { - dirty.insert(pass, RwLock::new(BTreeMap::new())); - } Self { - dirty: Arc::new(dirty), + dirty: Arc::new( + passes + .map(|pass| (pass, RwLock::new(BTreeMap::new()))) + .collect(), + ), } } From f03e08fc3bdcd999eb2bde6d2209a5800264d055 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 7 Feb 2023 21:15:09 -0600 Subject: [PATCH 046/103] fix tui driven example --- packages/tui/examples/driven.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tui/examples/driven.rs b/packages/tui/examples/driven.rs index de2ef0025..a4f37d136 100644 --- a/packages/tui/examples/driven.rs +++ b/packages/tui/examples/driven.rs @@ -198,7 +198,7 @@ impl Renderer> for Test { } } - fn poll_async(&mut self) -> std::pin::Pin + Send>> { + fn poll_async(&mut self) -> std::pin::Pin + '_>> { Box::pin(async move { tokio::time::sleep(std::time::Duration::from_millis(1000)).await }) } } From 0e7ee238eacf7fb328849ec14c9817945b8f87a6 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 8 Feb 2023 07:57:00 -0600 Subject: [PATCH 047/103] fix benchmark --- packages/tui/benches/update.rs | 4 ++-- packages/tui/examples/stress.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/tui/benches/update.rs b/packages/tui/benches/update.rs index c1fa44875..ea687e72a 100644 --- a/packages/tui/benches/update.rs +++ b/packages/tui/benches/update.rs @@ -10,8 +10,8 @@ fn tui_update(c: &mut Criterion) { let mut group = c.benchmark_group("Update boxes"); // We can also use loops to define multiple benchmarks, even over multiple dimensions. - for size in 1..=8usize { - let parameter_string = format!("{}", (3 * size).pow(2)); + for size in 1..=20usize { + let parameter_string = format!("{}", (size).pow(2)); group.bench_with_input( BenchmarkId::new("size", parameter_string), &size, diff --git a/packages/tui/examples/stress.rs b/packages/tui/examples/stress.rs index 4e794ec30..0668077cd 100644 --- a/packages/tui/examples/stress.rs +++ b/packages/tui/examples/stress.rs @@ -3,7 +3,7 @@ use dioxus_tui::{Config, TuiContext}; /// This benchmarks the cache performance of the TUI for small edits by changing one box at a time. fn main() { - for size in 1..=8usize { + for size in 1..=20usize { for _ in 0..10 { dioxus_tui::launch_cfg_with_props(app, size, Config::default().with_headless()) } From 0f6eb4fca25bc4f78a8981a3c8083f76e25a2382 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 8 Feb 2023 14:59:47 -0600 Subject: [PATCH 048/103] clean up node api --- packages/native-core/src/lib.rs | 2 +- packages/native-core/src/real_dom.rs | 32 +++++++++++-------- .../src/utils/persistant_iterator.rs | 18 ++++------- packages/tui/src/render.rs | 2 +- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 9e8dbe7a1..2e2ad14d8 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -23,7 +23,7 @@ pub mod tree; pub mod utils; pub mod prelude { - pub use crate::node::{ElementNode, FromAnyValue, NodeType, OwnedAttributeView}; + pub use crate::node::{ElementNode, FromAnyValue, NodeType, OwnedAttributeView, TextNode}; pub use crate::node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}; pub use crate::passes::{Dependancy, Pass}; pub use crate::real_dom::{NodeImmutable, NodeMut, NodeRef, RealDom}; diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 976c5c708..b14461e10 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -1,8 +1,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; use std::any::{Any, TypeId}; use std::collections::VecDeque; -use std::rc::Rc; -use std::sync::RwLock; +use std::sync::{Arc, RwLock}; use crate::node::{ ElementNode, FromAnyValue, NodeType, OwnedAttributeDiscription, OwnedAttributeValue, TextNode, @@ -54,7 +53,7 @@ impl NodesDirty { } } -type NodeWatchers = Rc>>>>; +type NodeWatchers = Arc + Send + Sync>>>>; /// 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. @@ -276,7 +275,7 @@ impl RealDom { self.tree.insert_slab::(); } - pub fn add_node_watcher(&mut self, watcher: impl NodeWatcher + 'static) { + pub fn add_node_watcher(&mut self, watcher: impl NodeWatcher + 'static + Send + Sync) { self.node_watchers.write().unwrap().push(Box::new(watcher)); } } @@ -302,15 +301,17 @@ pub trait NodeImmutable: Sized { } #[inline] - fn children(&self) -> Option>> { - self.child_ids().map(|ids| { - ids.iter() - .map(|id| NodeRef { - id: *id, - dom: self.real_dom(), - }) - .collect() - }) + fn children(&self) -> Vec> { + self.child_ids() + .map(|ids| { + ids.iter() + .map(|id| NodeRef { + id: *id, + dom: self.real_dom(), + }) + .collect() + }) + .unwrap_or_default() } #[inline] @@ -355,6 +356,11 @@ pub trait NodeImmutable: Sized { None } } + + #[inline] + fn height(&self) -> u16 { + self.real_dom().tree.height(self.id()).unwrap() + } } pub struct NodeRef<'a, V: FromAnyValue + Send + Sync = ()> { diff --git a/packages/native-core/src/utils/persistant_iterator.rs b/packages/native-core/src/utils/persistant_iterator.rs index d290caaa4..0e87c03f2 100644 --- a/packages/native-core/src/utils/persistant_iterator.rs +++ b/packages/native-core/src/utils/persistant_iterator.rs @@ -97,12 +97,10 @@ impl PersistantElementIter { if let Some(current) = stack.last().and_then(|last| rdom.get(*last)) { // if the current element has children, add the first child to the stack and return it if look_in_children { - if let Some(children) = current.children() { - if let Some(first) = children.first() { - let new = first.id(); - stack.push(new); - return ElementProduced::Progressed(new); - } + if let Some(first) = current.children().first() { + let new = first.id(); + stack.push(new); + return ElementProduced::Progressed(new); } } stack.pop(); @@ -133,12 +131,8 @@ impl PersistantElementIter { node: NodeRef, ) -> NodeId { stack.push(node.id()); - if let Some(children) = node.children() { - if let Some(last) = children.last() { - push_back(stack, *last) - } else { - node.id() - } + if let Some(last) = node.children().last() { + push_back(stack, *last) } else { node.id() } diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index c10c52252..a739d2621 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -85,7 +85,7 @@ pub(crate) fn render_vnode( frame.render_widget(WidgetWithContext::new(node, cfg), area); } - for c in node.children().unwrap() { + for c in node.children() { render_vnode(frame, layout, c, cfg, location); } } From 5b822201f72287ff5e6ba797ecb9fe6a8dfe4692 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 9 Feb 2023 13:24:45 -0600 Subject: [PATCH 049/103] clean up node mut api --- packages/native-core/src/real_dom.rs | 5 +++++ packages/tui/src/lib.rs | 8 ++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index b14461e10..6cbedd90e 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -420,6 +420,11 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { self.dom } + #[inline] + pub fn parent_mut(&mut self) -> Option> { + self.parent_id().map(|id| NodeMut { id, dom: self.dom }) + } + #[inline] pub fn get_mut(&mut self) -> Option<&mut T> { // mark the node state as dirty diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index ff0d85cfa..9f316ed58 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -75,7 +75,11 @@ impl TuiContext { pub fn render>>( cfg: Config, - f: impl FnOnce(&Arc>, &Arc>, UnboundedSender) -> R, + create_renderer: impl FnOnce( + &Arc>, + &Arc>, + UnboundedSender, + ) -> R, ) -> Result<()> { let mut rdom = RealDom::new(Box::new([ TaffyLayout::to_type_erased(), @@ -105,7 +109,7 @@ pub fn render>>( let rdom = Arc::new(RwLock::new(rdom)); let taffy = Arc::new(Mutex::new(Taffy::new())); - let mut renderer = f(&rdom, &taffy, event_tx_clone); + let mut renderer = create_renderer(&rdom, &taffy, event_tx_clone); { let mut rdom = rdom.write().unwrap(); From 26d1cc289196248058b4ab3a054d38e0ef72468d Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 9 Feb 2023 13:43:40 -0600 Subject: [PATCH 050/103] make tui driven example retained --- packages/tui/examples/driven.rs | 76 +++++++++++++++++++++++++++------ 1 file changed, 62 insertions(+), 14 deletions(-) diff --git a/packages/tui/examples/driven.rs b/packages/tui/examples/driven.rs index a4f37d136..11ff7a6a8 100644 --- a/packages/tui/examples/driven.rs +++ b/packages/tui/examples/driven.rs @@ -2,18 +2,35 @@ use dioxus_html::EventData; use dioxus_native_core::{ node::{OwnedAttributeDiscription, OwnedAttributeValue, TextNode}, prelude::*, - real_dom::{NodeImmutable, NodeTypeMut}, + real_dom::{ElementNodeMut, NodeImmutable, NodeTypeMut}, NodeId, Renderer, }; use dioxus_tui::{self, render, Config}; +use rustc_hash::FxHashSet; use std::sync::{Arc, RwLock}; use std::{rc::Rc, sync::Mutex}; use taffy::Taffy; -struct Test([[usize; 10]; 10]); +const SIZE: usize = 10; + +struct Test { + node_states: [[usize; SIZE]; SIZE], + dirty: FxHashSet<(usize, usize)>, +} + +impl Default for Test { + fn default() -> Self { + Self { + node_states: [[0; SIZE]; SIZE], + dirty: FxHashSet::default(), + } + } +} + +impl Test { + fn create(mut root: NodeMut) -> Self { + let myself = Self::default(); -impl Renderer> for Test { - fn render(&mut self, mut root: dioxus_native_core::NodeMut) { // Set the root node to be a flexbox with a column direction. if let NodeTypeMut::Element(mut el) = root.node_type_mut() { el.set_attribute( @@ -47,17 +64,10 @@ impl Renderer> for Test { } let root_id = root.id(); - // Remove old grid. Frameworks should retain the grid and only update the values. - let children_ids = root.child_ids().map(|ids| ids.to_vec()); let rdom = root.real_dom_mut(); - if let Some(children) = children_ids { - for child in children { - rdom.get_mut(child).unwrap().remove(); - } - } // create the grid - for (x, row) in self.0.iter().copied().enumerate() { + for (x, row) in myself.node_states.iter().copied().enumerate() { let row_node = rdom .create_node(NodeType::Element(ElementNode { tag: "div".to_string(), @@ -170,6 +180,38 @@ impl Renderer> for Test { } rdom.get_mut(root_id).unwrap().add_child(row_node); } + myself + } +} + +impl Renderer> for Test { + fn render(&mut self, mut root: dioxus_native_core::NodeMut) { + for (x, y) in self.dirty.drain() { + let row_id = root.child_ids().unwrap()[x]; + let mut rdom = root.real_dom_mut(); + let row = rdom.get(row_id).unwrap(); + let node_id = row.child_ids().unwrap()[y]; + let mut node = rdom.get_mut(node_id).unwrap(); + if let NodeTypeMut::Element(mut el) = node.node_type_mut() { + el.set_attribute( + OwnedAttributeDiscription { + name: "background-color".into(), + namespace: None, + }, + OwnedAttributeValue::Text(format!( + "rgb({}, {}, {})", + self.node_states[x][y] * 10, + 0, + (x + y) * 10, + )), + ); + } + let text_id = *node.child_ids().unwrap().first().unwrap(); + let mut text = rdom.get_mut(text_id).unwrap(); + if let NodeTypeMut::Text(text) = text.node_type_mut() { + *text = self.node_states[x][y].to_string(); + } + } } fn handle_event( @@ -193,7 +235,8 @@ impl Renderer> for Test { .iter() .position(|id| *id == parent.id()) .unwrap(); - self.0[parents_child_number][child_number] += 1; + self.node_states[parents_child_number][child_number] += 1; + self.dirty.insert((parents_child_number, child_number)); } } } @@ -204,5 +247,10 @@ impl Renderer> for Test { } fn main() { - render(Config::new(), |_, _, _| Test(Default::default())).unwrap(); + render(Config::new(), |rdom, _, _| { + let mut rdom = rdom.write().unwrap(); + let root = rdom.root_id(); + Test::create(rdom.get_mut(root).unwrap()) + }) + .unwrap(); } From da62e857313f5d0f693735823af2ceb89d4f963e Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 9 Feb 2023 13:55:45 -0600 Subject: [PATCH 051/103] move clone method to nodemut --- packages/native-core/src/dioxus.rs | 2 +- packages/native-core/src/real_dom.rs | 30 ++++++++++++++++------------ 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/packages/native-core/src/dioxus.rs b/packages/native-core/src/dioxus.rs index fa7ca4802..5760d9d2e 100644 --- a/packages/native-core/src/dioxus.rs +++ b/packages/native-core/src/dioxus.rs @@ -111,7 +111,7 @@ impl DioxusState { } LoadTemplate { name, index, id } => { let template_id = self.templates[name][index]; - let clone_id = rdom.clone_node(template_id); + let clone_id = rdom.get_mut(template_id).unwrap().clone_node(); let clone = rdom.get_mut(clone_id).unwrap(); self.set_element_id(clone, id); self.stack.push(clone_id); diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 6cbedd90e..ac9d5385f 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -163,19 +163,6 @@ impl RealDom { self.tree.root() } - pub fn clone_node(&mut self, node_id: NodeId) -> NodeId { - let node = self.get(node_id).unwrap(); - let new_node = node.node_type().clone(); - let new_id = self.create_node(new_node).id(); - - let children = self.tree.children_ids(node_id).unwrap().to_vec(); - for child in children { - let child_id = self.clone_node(child); - self.get_mut(new_id).unwrap().add_child(child_id); - } - new_id - } - pub fn get(&self, id: NodeId) -> Option> { self.tree.contains(id).then_some(NodeRef { id, dom: self }) } @@ -630,6 +617,23 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { .dirty_nodes .mark_dirty(self.id, NodeMaskBuilder::ALL.build()) } + + #[inline] + pub fn clone_node(&mut self) -> NodeId { + let new_node = self.node_type().clone(); + let rdom = self.real_dom_mut(); + let new_id = rdom.create_node(new_node).id(); + + if let Some(children) = self.child_ids() { + let children = children.to_vec(); + let rdom = self.real_dom_mut(); + for child in children { + let child_id = rdom.get_mut(child).unwrap().clone_node(); + rdom.get_mut(new_id).unwrap().add_child(child_id); + } + } + new_id + } } pub enum NodeTypeMut<'a, V: FromAnyValue + Send + Sync = ()> { From f6feb3f3f0b5c2730800d6c670be25021d214838 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 9 Feb 2023 21:34:47 -0600 Subject: [PATCH 052/103] fix tui widgets --- packages/native-core/src/dioxus.rs | 6 +++++- packages/tui/src/dioxus.rs | 31 +++++++++++++++++++++++----- packages/tui/src/focus.rs | 9 ++++---- packages/tui/src/lib.rs | 10 ++++++--- packages/tui/src/prelude/mod.rs | 3 ++- packages/tui/src/widgets/mod.rs | 10 ++++++--- packages/tui/src/widgets/number.rs | 2 +- packages/tui/src/widgets/password.rs | 4 +++- packages/tui/src/widgets/textbox.rs | 2 +- 9 files changed, 56 insertions(+), 21 deletions(-) diff --git a/packages/native-core/src/dioxus.rs b/packages/native-core/src/dioxus.rs index 5760d9d2e..f7426f1d6 100644 --- a/packages/native-core/src/dioxus.rs +++ b/packages/native-core/src/dioxus.rs @@ -31,7 +31,11 @@ impl DioxusState { } pub fn element_to_node_id(&self, element_id: ElementId) -> NodeId { - self.node_id_mapping.get(element_id.0).unwrap().unwrap() + self.try_element_to_node_id(element_id).unwrap() + } + + pub fn try_element_to_node_id(&self, element_id: ElementId) -> Option { + self.node_id_mapping.get(element_id.0).copied().flatten() } fn set_element_id(&mut self, mut node: NodeMut, element_id: ElementId) { diff --git a/packages/tui/src/dioxus.rs b/packages/tui/src/dioxus.rs index 32b5d4941..8c58d35bf 100644 --- a/packages/tui/src/dioxus.rs +++ b/packages/tui/src/dioxus.rs @@ -1,10 +1,10 @@ -use std::{ops::Deref, rc::Rc}; +use std::{ops::Deref, rc::Rc, sync::RwLock}; -use dioxus_core::{Component, VirtualDom}; +use dioxus_core::{Component, ElementId, VirtualDom}; use dioxus_html::EventData; use dioxus_native_core::{ dioxus::{DioxusState, NodeImmutableDioxusExt}, - Renderer, + NodeId, Renderer, }; use crate::{query::Query, render, Config, TuiContext}; @@ -29,6 +29,10 @@ pub fn launch_cfg_with_props(app: Component, props: Props let mut rdom = rdom.write().unwrap(); let mut dioxus_state = DioxusState::create(&mut rdom); dioxus_state.apply_mutations(&mut rdom, muts); + let dioxus_state = Rc::new(RwLock::new(dioxus_state)); + vdom = vdom.with_root_context(DioxusElementToNodeId { + mapping: dioxus_state.clone(), + }); DioxusRenderer { vdom, dioxus_state, @@ -48,7 +52,7 @@ pub fn launch_cfg_with_props(app: Component, props: Props struct DioxusRenderer { vdom: VirtualDom, - dioxus_state: DioxusState, + dioxus_state: Rc>, #[cfg(all(feature = "hot-reload", debug_assertions))] hot_reload_rx: tokio::sync::mpsc::UnboundedReceiver, } @@ -57,7 +61,10 @@ impl Renderer> for DioxusRenderer { fn render(&mut self, mut root: dioxus_native_core::NodeMut<()>) { let rdom = root.real_dom_mut(); let muts = self.vdom.render_immediate(); - self.dioxus_state.apply_mutations(rdom, muts); + self.dioxus_state + .write() + .unwrap() + .apply_mutations(rdom, muts); } fn handle_event( @@ -107,3 +114,17 @@ impl Renderer> for DioxusRenderer { Box::pin(self.vdom.wait_for_work()) } } + +#[derive(Clone)] +pub struct DioxusElementToNodeId { + mapping: Rc>, +} + +impl DioxusElementToNodeId { + pub fn get_node_id(&self, element_id: ElementId) -> Option { + self.mapping + .read() + .unwrap() + .try_element_to_node_id(element_id) + } +} diff --git a/packages/tui/src/focus.rs b/packages/tui/src/focus.rs index 8379444d8..37d27b0e5 100644 --- a/packages/tui/src/focus.rs +++ b/packages/tui/src/focus.rs @@ -271,12 +271,11 @@ impl FocusState { pub(crate) fn set_focus(&mut self, rdom: &mut RealDom, id: NodeId) { if let Some(old) = self.last_focused_id.replace(id) { - let focused = rdom.get_state_mut_raw::(old).unwrap(); - *focused = Focused(false); + let mut node = rdom.get_mut(old).unwrap(); + node.insert(Focused(false)); } - let focused = rdom.get_state_mut_raw::(id).unwrap(); - *focused = Focused(true); - let node = rdom.get(id).unwrap(); + let mut node = rdom.get_mut(id).unwrap(); + node.insert(Focused(true)); self.focus_level = node.get::().unwrap().level; // reset the position to the currently focused element while self.focus_iter.next(rdom).id() != id {} diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 9f316ed58..49b4d3555 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -24,7 +24,7 @@ use tokio::select; use tui::{backend::CrosstermBackend, layout::Rect, Terminal}; mod config; -#[cfg(feature = "dioxus")] +#[cfg(feature = "dioxus-bindings")] pub mod dioxus; mod focus; mod hooks; @@ -36,12 +36,16 @@ mod render; mod style; mod style_attributes; mod widget; -// mod widgets; +#[cfg(feature = "dioxus-bindings")] +mod widgets; -#[cfg(feature = "dioxus")] +#[cfg(feature = "dioxus-bindings")] pub use crate::dioxus::*; pub use config::*; pub use hooks::*; +pub use query::Query; +#[cfg(feature = "dioxus-bindings")] +pub use widgets::*; // the layout space has a multiplier of 10 to minimize rounding errors pub(crate) fn screen_to_layout_space(screen: u16) -> f32 { diff --git a/packages/tui/src/prelude/mod.rs b/packages/tui/src/prelude/mod.rs index 2d5b029a0..f7bbb3696 100644 --- a/packages/tui/src/prelude/mod.rs +++ b/packages/tui/src/prelude/mod.rs @@ -1 +1,2 @@ -// pub use crate::widgets::*; +#[cfg(feature = "dioxus-bindings")] +pub use crate::widgets::*; diff --git a/packages/tui/src/widgets/mod.rs b/packages/tui/src/widgets/mod.rs index cbf3d6d50..18e3b12b4 100644 --- a/packages/tui/src/widgets/mod.rs +++ b/packages/tui/src/widgets/mod.rs @@ -6,12 +6,16 @@ mod password; mod slider; mod textbox; -use dioxus_core::{ElementId, RenderReturn, Scope}; +use dioxus_core::{RenderReturn, Scope}; +use dioxus_native_core::NodeId; pub use input::*; -pub(crate) fn get_root_id(cx: Scope) -> Option { +use crate::DioxusElementToNodeId; + +pub(crate) fn get_root_id(cx: Scope) -> Option { if let RenderReturn::Ready(sync) = cx.root_node() { - sync.root_ids.get(0) + let mapping: DioxusElementToNodeId = cx.consume_context()?; + mapping.get_node_id(sync.root_ids.get(0)?) } else { None } diff --git a/packages/tui/src/widgets/number.rs b/packages/tui/src/widgets/number.rs index 48e198ebd..3e9d80d2d 100644 --- a/packages/tui/src/widgets/number.rs +++ b/packages/tui/src/widgets/number.rs @@ -113,7 +113,7 @@ pub(crate) fn NumbericInput<'a>(cx: Scope<'a, NumbericInputProps>) -> Element<'a }; if is_text{ let mut text = text_ref.write(); - cursor.write().handle_input(&k, &mut *text, max_len); + cursor.write().handle_input(&k.code(), &k.key(), &k.modifiers(), &mut *text, max_len); update(text.clone()); let node = tui_query.get(get_root_id(cx).unwrap()); diff --git a/packages/tui/src/widgets/password.rs b/packages/tui/src/widgets/password.rs index 714a5ad93..5e74e8b09 100644 --- a/packages/tui/src/widgets/password.rs +++ b/packages/tui/src/widgets/password.rs @@ -88,7 +88,9 @@ pub(crate) fn Password<'a>(cx: Scope<'a, PasswordProps>) -> Element<'a> { return; } let mut text = text_ref.write(); - cursor.write().handle_input(&k, &mut *text, max_len); + cursor + .write() + .handle_input(&k.code(), &k.key(), &k.modifiers(), &mut *text, max_len); if let Some(input_handler) = &cx.props.raw_oninput { input_handler.call(FormData { value: text.clone(), diff --git a/packages/tui/src/widgets/textbox.rs b/packages/tui/src/widgets/textbox.rs index ad48f0800..6c378cd72 100644 --- a/packages/tui/src/widgets/textbox.rs +++ b/packages/tui/src/widgets/textbox.rs @@ -90,7 +90,7 @@ pub(crate) fn TextBox<'a>(cx: Scope<'a, TextBoxProps>) -> Element<'a> { return; } let mut text = text_ref.write(); - cursor.write().handle_input(&k, &mut *text, max_len); + cursor.write().handle_input(&k.code(), &k.key(), &k.modifiers(), &mut *text, max_len); if let Some(input_handler) = &cx.props.raw_oninput{ input_handler.call(FormData{ value: text.clone(), From db04550fc8874e0e35328da5404e70f53e4fad7e Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 10 Feb 2023 10:56:24 -0600 Subject: [PATCH 053/103] fix widgets in tui --- packages/native-core/src/lib.rs | 9 ------- packages/tui/examples/driven.rs | 29 +++++++++++--------- packages/tui/src/dioxus.rs | 32 +++++++++++++--------- packages/tui/src/lib.rs | 48 ++++++++++++++++++++++----------- packages/tui/src/query.rs | 6 +++-- 5 files changed, 73 insertions(+), 51 deletions(-) diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 2e2ad14d8..a0e68e7ad 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -1,12 +1,9 @@ use std::any::Any; -use std::future::Future; use std::hash::BuildHasherDefault; -use std::pin::Pin; pub use node_ref::NodeMask; pub use passes::AnyMapLike; pub use passes::{Dependancy, Pass, TypeErasedPass}; -use prelude::FromAnyValue; pub use real_dom::{NodeMut, NodeRef, RealDom}; use rustc_hash::FxHasher; pub use tree::NodeId; @@ -34,9 +31,3 @@ pub mod prelude { pub type FxDashMap = dashmap::DashMap>; pub type FxDashSet = dashmap::DashSet>; pub type SendAnyMap = anymap::Map; - -pub trait Renderer { - fn render(&mut self, root: NodeMut); - fn handle_event(&mut self, node: NodeMut, event: &str, value: E, bubbles: bool); - fn poll_async(&mut self) -> Pin + '_>>; -} diff --git a/packages/tui/examples/driven.rs b/packages/tui/examples/driven.rs index 11ff7a6a8..1a258c3f6 100644 --- a/packages/tui/examples/driven.rs +++ b/packages/tui/examples/driven.rs @@ -2,14 +2,13 @@ use dioxus_html::EventData; use dioxus_native_core::{ node::{OwnedAttributeDiscription, OwnedAttributeValue, TextNode}, prelude::*, - real_dom::{ElementNodeMut, NodeImmutable, NodeTypeMut}, - NodeId, Renderer, + real_dom::{NodeImmutable, NodeTypeMut}, + NodeId, }; -use dioxus_tui::{self, render, Config}; +use dioxus_tui::{self, render, Config, Renderer}; use rustc_hash::FxHashSet; +use std::rc::Rc; use std::sync::{Arc, RwLock}; -use std::{rc::Rc, sync::Mutex}; -use taffy::Taffy; const SIZE: usize = 10; @@ -184,11 +183,14 @@ impl Test { } } -impl Renderer> for Test { - fn render(&mut self, mut root: dioxus_native_core::NodeMut) { +impl Renderer for Test { + fn render(&mut self, rdom: &Arc>) { + let mut rdom = rdom.write().unwrap(); + let root_id = rdom.root_id(); + let mut root = rdom.get_mut(root_id).unwrap(); for (x, y) in self.dirty.drain() { let row_id = root.child_ids().unwrap()[x]; - let mut rdom = root.real_dom_mut(); + let rdom = root.real_dom_mut(); let row = rdom.get(row_id).unwrap(); let node_id = row.child_ids().unwrap()[y]; let mut node = rdom.get_mut(node_id).unwrap(); @@ -216,11 +218,14 @@ impl Renderer> for Test { fn handle_event( &mut self, - node: dioxus_native_core::NodeMut<()>, - event: &str, - value: Rc, - bubbles: bool, + rdom: &Arc>, + id: NodeId, + _: &str, + _: Rc, + _: bool, ) { + let rdom = rdom.read().unwrap(); + let node = rdom.get(id).unwrap(); if let Some(parent) = node.parent() { let child_number = parent .child_ids() diff --git a/packages/tui/src/dioxus.rs b/packages/tui/src/dioxus.rs index 8c58d35bf..da83ce799 100644 --- a/packages/tui/src/dioxus.rs +++ b/packages/tui/src/dioxus.rs @@ -1,13 +1,17 @@ -use std::{ops::Deref, rc::Rc, sync::RwLock}; +use std::{ + ops::Deref, + rc::Rc, + sync::{Arc, RwLock}, +}; use dioxus_core::{Component, ElementId, VirtualDom}; use dioxus_html::EventData; use dioxus_native_core::{ dioxus::{DioxusState, NodeImmutableDioxusExt}, - NodeId, Renderer, + NodeId, RealDom, }; -use crate::{query::Query, render, Config, TuiContext}; +use crate::{query::Query, render, Config, Renderer, TuiContext}; pub fn launch(app: Component<()>) { launch_cfg(app, Config::default()) @@ -57,24 +61,28 @@ struct DioxusRenderer { hot_reload_rx: tokio::sync::mpsc::UnboundedReceiver, } -impl Renderer> for DioxusRenderer { - fn render(&mut self, mut root: dioxus_native_core::NodeMut<()>) { - let rdom = root.real_dom_mut(); +impl Renderer for DioxusRenderer { + fn render(&mut self, rdom: &Arc>) { let muts = self.vdom.render_immediate(); - self.dioxus_state - .write() - .unwrap() - .apply_mutations(rdom, muts); + { + let mut rdom = rdom.write().unwrap(); + self.dioxus_state + .write() + .unwrap() + .apply_mutations(&mut rdom, muts); + } } fn handle_event( &mut self, - node: dioxus_native_core::NodeMut<()>, + rdom: &Arc>, + id: NodeId, event: &str, value: Rc, bubbles: bool, ) { - if let Some(id) = node.mounted_id() { + let id = { rdom.read().unwrap().get(id).unwrap().mounted_id() }; + if let Some(id) = id { self.vdom .handle_event(event, value.deref().clone().into_any(), id, bubbles); } diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 49b4d3555..815264241 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -7,15 +7,18 @@ use crossterm::{ terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; use dioxus_html::EventData; -use dioxus_native_core::{node_ref::NodeMaskBuilder, real_dom::NodeImmutable, Pass, Renderer}; +use dioxus_native_core::{node_ref::NodeMaskBuilder, real_dom::NodeImmutable, Pass}; use dioxus_native_core::{real_dom::RealDom, FxDashSet, NodeId, SendAnyMap}; use focus::FocusState; -use futures::{channel::mpsc::UnboundedSender, pin_mut, StreamExt}; +use futures::{channel::mpsc::UnboundedSender, pin_mut, Future, StreamExt}; use futures_channel::mpsc::unbounded; use layout::TaffyLayout; use prevent_default::PreventDefault; -use std::sync::{Arc, Mutex}; use std::{io, time::Duration}; +use std::{ + pin::Pin, + sync::{Arc, Mutex}, +}; use std::{rc::Rc, sync::RwLock}; use style_attributes::StyleModifier; use taffy::Taffy; @@ -77,7 +80,7 @@ impl TuiContext { } } -pub fn render>>( +pub fn render( cfg: Config, create_renderer: impl FnOnce( &Arc>, @@ -116,11 +119,10 @@ pub fn render>>( let mut renderer = create_renderer(&rdom, &taffy, event_tx_clone); { - let mut rdom = rdom.write().unwrap(); - let root_id = rdom.root_id(); - renderer.render(rdom.get_mut(root_id).unwrap()); + renderer.render(&rdom); let mut any_map = SendAnyMap::new(); any_map.insert(taffy.clone()); + let mut rdom = rdom.write().unwrap(); let _ = rdom.update_state(any_map, false); } @@ -201,7 +203,7 @@ pub fn render>>( })?; execute!(terminal.backend_mut(), RestorePosition, Show).unwrap(); } else { - let rdom = rdom.write().unwrap(); + let rdom = rdom.read().unwrap(); resize( Rect { x: 0, @@ -250,21 +252,22 @@ pub fn render>>( { { - let mut rdom = rdom.write().unwrap(); - let evts = handler - .get_events(&taffy.lock().expect("taffy lock poisoned"), &mut rdom); + let evts = { + handler.get_events( + &taffy.lock().expect("taffy lock poisoned"), + &mut rdom.write().unwrap(), + ) + }; updated |= handler.state().focus_state.clean(); for e in evts { - let node = rdom.get_mut(e.id).unwrap(); - renderer.handle_event(node, e.name, e.data, e.bubbles); + renderer.handle_event(&rdom, e.id, e.name, e.data, e.bubbles); } } - let mut rdom = rdom.write().unwrap(); // updates the dom's nodes - let root_id = rdom.root_id(); - renderer.render(rdom.get_mut(root_id).unwrap()); + renderer.render(&rdom); // update the style and layout + let mut rdom = rdom.write().unwrap(); let mut any_map = SendAnyMap::new(); any_map.insert(taffy.clone()); let (new_to_rerender, dirty) = rdom.update_state(any_map, false); @@ -297,3 +300,16 @@ pub enum InputEvent { UserInput(TermEvent), Close, } + +pub trait Renderer { + fn render(&mut self, rdom: &Arc>); + fn handle_event( + &mut self, + rdom: &Arc>, + id: NodeId, + event: &str, + value: Rc, + bubbles: bool, + ); + fn poll_async(&mut self) -> Pin + '_>>; +} diff --git a/packages/tui/src/query.rs b/packages/tui/src/query.rs index 40606e743..3a11d0f2d 100644 --- a/packages/tui/src/query.rs +++ b/packages/tui/src/query.rs @@ -48,9 +48,11 @@ pub struct Query { impl Query { pub fn get(&self, id: NodeId) -> ElementRef { + let rdom = self.rdom.read(); + let stretch = self.stretch.lock(); ElementRef::new( - self.rdom.read().expect("rdom lock poisoned"), - self.stretch.lock().expect("taffy lock poisoned"), + rdom.expect("rdom lock poisoned"), + stretch.expect("taffy lock poisoned"), id, ) } From d8da8e42757ea2e9efd2a5e975f4e25b686ade71 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 10 Feb 2023 11:01:24 -0600 Subject: [PATCH 054/103] fix color picker example --- packages/tui/examples/colorpicker.rs | 4 +++- packages/tui/src/dioxus.rs | 18 ++++++++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/tui/examples/colorpicker.rs b/packages/tui/examples/colorpicker.rs index f3598af4f..7b7730173 100644 --- a/packages/tui/examples/colorpicker.rs +++ b/packages/tui/examples/colorpicker.rs @@ -1,6 +1,7 @@ use dioxus::core::RenderReturn; use dioxus::prelude::*; use dioxus_tui::query::Query; +use dioxus_tui::DioxusElementToNodeId; use dioxus_tui::Size; fn main() { @@ -11,6 +12,7 @@ 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 mapping: DioxusElementToNodeId = cx.consume_context().unwrap(); // disable templates so that every node has an id and can be queried cx.render(rsx! { div{ @@ -19,7 +21,7 @@ fn app(cx: Scope) -> Element { onmousemove: move |evt| { if let RenderReturn::Ready(node) = cx.root_node() { if let Some(id) = node.root_ids.get(0){ - let node = tui_query.get(id); + let node = tui_query.get(mapping.get_node_id(id).unwrap()); let Size{width, height} = node.size().unwrap(); let pos = evt.inner().element_coordinates(); hue.set((pos.x as f32/width as f32)*255.0); diff --git a/packages/tui/src/dioxus.rs b/packages/tui/src/dioxus.rs index da83ce799..c07d138d2 100644 --- a/packages/tui/src/dioxus.rs +++ b/packages/tui/src/dioxus.rs @@ -23,20 +23,26 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) { pub fn launch_cfg_with_props(app: Component, props: Props, cfg: Config) { render(cfg, |rdom, taffy, event_tx| { + let dioxus_state = { + let mut rdom = rdom.write().unwrap(); + DioxusState::create(&mut rdom) + }; + let dioxus_state = Rc::new(RwLock::new(dioxus_state)); let mut vdom = VirtualDom::new_with_props(app, props) .with_root_context(TuiContext { tx: event_tx }) .with_root_context(Query { rdom: rdom.clone(), stretch: taffy.clone(), + }) + .with_root_context(DioxusElementToNodeId { + mapping: dioxus_state.clone(), }); let muts = vdom.rebuild(); let mut rdom = rdom.write().unwrap(); - let mut dioxus_state = DioxusState::create(&mut rdom); - dioxus_state.apply_mutations(&mut rdom, muts); - let dioxus_state = Rc::new(RwLock::new(dioxus_state)); - vdom = vdom.with_root_context(DioxusElementToNodeId { - mapping: dioxus_state.clone(), - }); + dioxus_state + .write() + .unwrap() + .apply_mutations(&mut rdom, muts); DioxusRenderer { vdom, dioxus_state, From fe3ad8ebe92246153bfa49e4666b82341f6cdcb1 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 11 Feb 2023 17:43:08 -0600 Subject: [PATCH 055/103] document state trait --- packages/native-core/src/lib.rs | 4 +- packages/native-core/src/passes.rs | 15 ++++--- .../tests/called_minimally_on_build.rs | 2 +- packages/native-core/tests/fuzzing.rs | 8 ++-- packages/native-core/tests/miri_native.rs | 8 ++-- packages/native-core/tests/passes.rs | 42 +++++++++---------- packages/tui/src/focus.rs | 8 ++-- packages/tui/src/layout.rs | 8 ++-- packages/tui/src/lib.rs | 2 +- packages/tui/src/prevent_default.rs | 8 ++-- packages/tui/src/style_attributes.rs | 8 ++-- 11 files changed, 58 insertions(+), 55 deletions(-) diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index a0e68e7ad..ee65c35a4 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -3,7 +3,7 @@ use std::hash::BuildHasherDefault; pub use node_ref::NodeMask; pub use passes::AnyMapLike; -pub use passes::{Dependancy, Pass, TypeErasedPass}; +pub use passes::{Dependancy, State, TypeErasedPass}; pub use real_dom::{NodeMut, NodeRef, RealDom}; use rustc_hash::FxHasher; pub use tree::NodeId; @@ -22,7 +22,7 @@ pub mod utils; pub mod prelude { pub use crate::node::{ElementNode, FromAnyValue, NodeType, OwnedAttributeView, TextNode}; pub use crate::node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}; - pub use crate::passes::{Dependancy, Pass}; + pub use crate::passes::{Dependancy, State}; pub use crate::real_dom::{NodeImmutable, NodeMut, NodeRef, RealDom}; pub use crate::tree::NodeId; pub use crate::SendAnyMap; diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index 74c5dbacd..5a25c0424 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -103,16 +103,18 @@ impl DirtyNodeStates { } } -pub trait Pass: Any + Send + Sync { - /// This is a tuple of (T: Any, ..) +pub trait State: Any + Send + Sync { + /// This is a tuple of (T: Pass, ..) of states read from the parent required to run this pass type ParentDependencies: Dependancy; - /// This is a tuple of (T: Any, ..) + /// This is a tuple of (T: Pass, ..) of states read from the children required to run this pass type ChildDependencies: Dependancy; - /// This is a tuple of (T: Any, ..) + /// This is a tuple of (T: Pass, ..) of states read from the node required to run this pass type NodeDependencies: Dependancy; + /// This is a mask of what aspects of the node are required to run this pass const NODE_MASK: NodeMaskBuilder<'static>; - fn pass<'a>( + /// Update this state in a node, returns if the state was updated + fn update<'a>( &mut self, node_view: NodeView, node: ::ElementBorrowed<'a>, @@ -121,6 +123,7 @@ pub trait Pass: Any + Send + Sync { context: &SendAnyMap, ) -> bool; + /// Create a new instance of this state fn create<'a>( node_view: NodeView, node: ::ElementBorrowed<'a>, @@ -202,7 +205,7 @@ pub trait Pass: Any + Send + Sync { .value .as_mut() .unwrap() - .pass(view, node, parent, children, context) + .update(view, node, parent, children, context) } }, ) as PassCallback, diff --git a/packages/native-core/tests/called_minimally_on_build.rs b/packages/native-core/tests/called_minimally_on_build.rs index 44b8a43f9..db0bda59c 100644 --- a/packages/native-core/tests/called_minimally_on_build.rs +++ b/packages/native-core/tests/called_minimally_on_build.rs @@ -4,7 +4,7 @@ use dioxus_native_core::{ dioxus::DioxusState, node_ref::{NodeMaskBuilder, NodeView}, real_dom::RealDom, - Dependancy, Pass, SendAnyMap, + Dependancy, SendAnyMap, State, }; macro_rules! dep { diff --git a/packages/native-core/tests/fuzzing.rs b/packages/native-core/tests/fuzzing.rs index 49e9656a8..8a449c52a 100644 --- a/packages/native-core/tests/fuzzing.rs +++ b/packages/native-core/tests/fuzzing.rs @@ -4,7 +4,7 @@ use dioxus_native_core::{ dioxus::DioxusState, node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}, real_dom::RealDom, - Dependancy, Pass, SendAnyMap, + Dependancy, SendAnyMap, State, }; use std::cell::Cell; @@ -298,7 +298,7 @@ pub struct BlablaState { count: usize, } -impl Pass for BlablaState { +impl State for BlablaState { type ParentDependencies = (Self,); type ChildDependencies = (); type NodeDependencies = (); @@ -307,7 +307,7 @@ impl Pass for BlablaState { .with_attrs(AttributeMaskBuilder::Some(&["blabla"])) .with_element(); - fn pass<'a>( + fn update<'a>( &mut self, _: NodeView, _: ::ElementBorrowed<'a>, @@ -331,7 +331,7 @@ impl Pass for BlablaState { context: &SendAnyMap, ) -> Self { let mut myself = Self::default(); - myself.pass(node_view, node, parent, children, context); + myself.update(node_view, node, parent, children, context); myself } } diff --git a/packages/native-core/tests/miri_native.rs b/packages/native-core/tests/miri_native.rs index 384078672..342445ae7 100644 --- a/packages/native-core/tests/miri_native.rs +++ b/packages/native-core/tests/miri_native.rs @@ -3,7 +3,7 @@ use dioxus_native_core::{ dioxus::DioxusState, node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}, real_dom::RealDom, - Dependancy, Pass, SendAnyMap, + Dependancy, SendAnyMap, State, }; use tokio::time::sleep; @@ -12,7 +12,7 @@ pub struct BlablaState { count: usize, } -impl Pass for BlablaState { +impl State for BlablaState { type ParentDependencies = (Self,); type ChildDependencies = (); type NodeDependencies = (); @@ -21,7 +21,7 @@ impl Pass for BlablaState { .with_attrs(AttributeMaskBuilder::Some(&["blabla"])) .with_element(); - fn pass<'a>( + fn update<'a>( &mut self, _: NodeView, _: ::ElementBorrowed<'a>, @@ -45,7 +45,7 @@ impl Pass for BlablaState { context: &SendAnyMap, ) -> Self { let mut myself = Self::default(); - myself.pass(node_view, node, parent, children, context); + myself.update(node_view, node, parent, children, context); myself } } diff --git a/packages/native-core/tests/passes.rs b/packages/native-core/tests/passes.rs index 40fe58717..6afe24bf6 100644 --- a/packages/native-core/tests/passes.rs +++ b/packages/native-core/tests/passes.rs @@ -16,13 +16,13 @@ fn node_pass() { #[derive(Debug, Default, Clone, PartialEq)] struct Number(i32); - impl Pass for Number { + impl State for Number { type ChildDependencies = (); type NodeDependencies = (); type ParentDependencies = (); const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new(); - fn pass<'a>( + fn update<'a>( &mut self, _: NodeView, _: ::ElementBorrowed<'a>, @@ -42,7 +42,7 @@ fn node_pass() { context: &SendAnyMap, ) -> Self { let mut myself = Self::default(); - myself.pass(node_view, node, parent, children, context); + myself.update(node_view, node, parent, children, context); myself } } @@ -64,13 +64,13 @@ fn dependant_node_pass() { #[derive(Debug, Default, Clone, PartialEq)] struct AddNumber(i32); - impl Pass for AddNumber { + impl State for AddNumber { type ChildDependencies = (); type NodeDependencies = (SubtractNumber,); type ParentDependencies = (); const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new(); - fn pass<'a>( + fn update<'a>( &mut self, _: NodeView, _: ::ElementBorrowed<'a>, @@ -90,7 +90,7 @@ fn dependant_node_pass() { context: &SendAnyMap, ) -> Self { let mut myself = Self::default(); - myself.pass(node_view, node, parent, children, context); + myself.update(node_view, node, parent, children, context); myself } } @@ -98,13 +98,13 @@ fn dependant_node_pass() { #[derive(Debug, Default, Clone, PartialEq)] struct SubtractNumber(i32); - impl Pass for SubtractNumber { + impl State for SubtractNumber { type ChildDependencies = (); type NodeDependencies = (); type ParentDependencies = (); const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new(); - fn pass<'a>( + fn update<'a>( &mut self, _: NodeView, _: ::ElementBorrowed<'a>, @@ -124,7 +124,7 @@ fn dependant_node_pass() { context: &SendAnyMap, ) -> Self { let mut myself = Self::default(); - myself.pass(node_view, node, parent, children, context); + myself.update(node_view, node, parent, children, context); myself } } @@ -163,14 +163,14 @@ fn independant_node_pass() { #[derive(Debug, Default, Clone, PartialEq)] struct AddNumber(i32); - impl Pass for AddNumber { + impl State for AddNumber { type ChildDependencies = (); type NodeDependencies = (); type ParentDependencies = (); const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new(); - fn pass<'a>( + fn update<'a>( &mut self, _: NodeView, _: ::ElementBorrowed<'a>, @@ -190,7 +190,7 @@ fn independant_node_pass() { context: &SendAnyMap, ) -> Self { let mut myself = Self::default(); - myself.pass(node_view, node, parent, children, context); + myself.update(node_view, node, parent, children, context); myself } } @@ -198,14 +198,14 @@ fn independant_node_pass() { #[derive(Debug, Default, Clone, PartialEq)] struct SubtractNumber(i32); - impl Pass for SubtractNumber { + impl State for SubtractNumber { type ChildDependencies = (); type NodeDependencies = (); type ParentDependencies = (); const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new(); - fn pass<'a>( + fn update<'a>( &mut self, _: NodeView, _: ::ElementBorrowed<'a>, @@ -225,7 +225,7 @@ fn independant_node_pass() { context: &SendAnyMap, ) -> Self { let mut myself = Self::default(); - myself.pass(node_view, node, parent, children, context); + myself.update(node_view, node, parent, children, context); myself } } @@ -272,14 +272,14 @@ fn down_pass() { } } - impl Pass for AddNumber { + impl State for AddNumber { type ChildDependencies = (); type NodeDependencies = (); type ParentDependencies = (AddNumber,); const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new(); - fn pass<'a>( + fn update<'a>( &mut self, _: NodeView, _: ::ElementBorrowed<'a>, @@ -301,7 +301,7 @@ fn down_pass() { context: &SendAnyMap, ) -> Self { let mut myself = Self::default(); - myself.pass(node_view, node, parent, children, context); + myself.update(node_view, node, parent, children, context); myself } } @@ -360,14 +360,14 @@ fn up_pass() { #[derive(Debug, Clone, PartialEq)] struct AddNumber(i32); - impl Pass for AddNumber { + impl State for AddNumber { type ChildDependencies = (AddNumber,); type NodeDependencies = (); type ParentDependencies = (); const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new(); - fn pass<'a>( + fn update<'a>( &mut self, _: NodeView, _: ::ElementBorrowed<'a>, @@ -389,7 +389,7 @@ fn up_pass() { context: &SendAnyMap, ) -> Self { let mut myself = Self(1); - myself.pass(node_view, node, parent, children, context); + myself.update(node_view, node, parent, children, context); myself } } diff --git a/packages/tui/src/focus.rs b/packages/tui/src/focus.rs index 37d27b0e5..dc845c8aa 100644 --- a/packages/tui/src/focus.rs +++ b/packages/tui/src/focus.rs @@ -4,7 +4,7 @@ use dioxus_native_core::{ node_ref::{AttributeMaskBuilder, NodeMaskBuilder}, real_dom::NodeImmutable, utils::{ElementProduced, PersistantElementIter}, - Dependancy, NodeId, Pass, RealDom, SendAnyMap, + Dependancy, NodeId, RealDom, SendAnyMap, State, }; use std::{cmp::Ordering, num::NonZeroU16}; @@ -63,7 +63,7 @@ pub(crate) struct Focus { pub level: FocusLevel, } -impl Pass for Focus { +impl State for Focus { const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new() .with_attrs(AttributeMaskBuilder::Some(FOCUS_ATTRIBUTES)) .with_listeners(); @@ -72,7 +72,7 @@ impl Pass for Focus { type ChildDependencies = (); type NodeDependencies = (); - fn pass<'a>( + fn update<'a>( &mut self, node_view: NodeView, _: ::ElementBorrowed<'a>, @@ -130,7 +130,7 @@ impl Pass for Focus { context: &SendAnyMap, ) -> Self { let mut myself = Self::default(); - myself.pass(node_view, node, parent, children, context); + myself.update(node_view, node, parent, children, context); myself } } diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index 5dfbd6c0b..5c334b5c8 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -5,7 +5,7 @@ use dioxus_native_core::layout_attributes::{ }; use dioxus_native_core::node::OwnedAttributeView; use dioxus_native_core::node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}; -use dioxus_native_core::{Dependancy, Pass, SendAnyMap}; +use dioxus_native_core::{Dependancy, SendAnyMap, State}; use taffy::prelude::*; use crate::{screen_to_layout_space, unit_to_layout_space}; @@ -42,7 +42,7 @@ pub(crate) struct TaffyLayout { pub node: PossiblyUninitalized, } -impl Pass for TaffyLayout { +impl State for TaffyLayout { type ChildDependencies = (Self,); type ParentDependencies = (); type NodeDependencies = (); @@ -51,7 +51,7 @@ impl Pass for TaffyLayout { .with_attrs(AttributeMaskBuilder::Some(SORTED_LAYOUT_ATTRS)) .with_text(); - fn pass<'a>( + fn update<'a>( &mut self, node_view: NodeView, _: ::ElementBorrowed<'a>, @@ -198,7 +198,7 @@ impl Pass for TaffyLayout { context: &SendAnyMap, ) -> Self { let mut myself = Self::default(); - myself.pass(node_view, node, parent, children, context); + myself.update(node_view, node, parent, children, context); myself } } diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 815264241..864d92864 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -7,7 +7,7 @@ use crossterm::{ terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; use dioxus_html::EventData; -use dioxus_native_core::{node_ref::NodeMaskBuilder, real_dom::NodeImmutable, Pass}; +use dioxus_native_core::{node_ref::NodeMaskBuilder, real_dom::NodeImmutable, State}; use dioxus_native_core::{real_dom::RealDom, FxDashSet, NodeId, SendAnyMap}; use focus::FocusState; use futures::{channel::mpsc::UnboundedSender, pin_mut, Future, StreamExt}; diff --git a/packages/tui/src/prevent_default.rs b/packages/tui/src/prevent_default.rs index 0fda912c9..e16a5c0d7 100644 --- a/packages/tui/src/prevent_default.rs +++ b/packages/tui/src/prevent_default.rs @@ -1,4 +1,4 @@ -use dioxus_native_core::{node_ref::NodeView, Dependancy, Pass, SendAnyMap}; +use dioxus_native_core::{node_ref::NodeView, Dependancy, SendAnyMap, State}; #[derive(PartialEq, Debug, Clone, Copy)] pub(crate) enum PreventDefault { @@ -25,7 +25,7 @@ impl Default for PreventDefault { } } -impl Pass for PreventDefault { +impl State for PreventDefault { type ParentDependencies = (); type ChildDependencies = (); type NodeDependencies = (); @@ -37,7 +37,7 @@ impl Pass for PreventDefault { ])) .with_listeners(); - fn pass<'a>( + fn update<'a>( &mut self, node_view: NodeView, _: ::ElementBorrowed<'a>, @@ -82,7 +82,7 @@ impl Pass for PreventDefault { context: &SendAnyMap, ) -> Self { let mut myself = Self::default(); - myself.pass(node_view, node, parent, children, context); + myself.update(node_view, node, parent, children, context); myself } } diff --git a/packages/tui/src/style_attributes.rs b/packages/tui/src/style_attributes.rs index 30b1ef2a1..b03dcc6a4 100644 --- a/packages/tui/src/style_attributes.rs +++ b/packages/tui/src/style_attributes.rs @@ -33,7 +33,7 @@ use dioxus_native_core::{ layout_attributes::parse_value, node::OwnedAttributeView, node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}, - Dependancy, Pass, SendAnyMap, + Dependancy, SendAnyMap, State, }; use taffy::prelude::*; @@ -45,7 +45,7 @@ pub struct StyleModifier { pub modifier: TuiModifier, } -impl Pass for StyleModifier { +impl State for StyleModifier { type ParentDependencies = (Self,); type ChildDependencies = (); type NodeDependencies = (); @@ -55,7 +55,7 @@ impl Pass for StyleModifier { .with_attrs(AttributeMaskBuilder::Some(SORTED_STYLE_ATTRS)) .with_element(); - fn pass<'a>( + fn update<'a>( &mut self, node_view: NodeView, _: ::ElementBorrowed<'a>, @@ -121,7 +121,7 @@ impl Pass for StyleModifier { context: &SendAnyMap, ) -> Self { let mut myself = Self::default(); - myself.pass(node_view, node, parent, children, context); + myself.update(node_view, node, parent, children, context); myself } } From b91e010ac7de7ab06d6ad3a5f5f662f764d38201 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 28 Feb 2023 13:26:49 -0600 Subject: [PATCH 056/103] WIP: use shipyard ecs --- packages/native-core-macro/.gitignore | 2 + packages/native-core-macro/Cargo.toml | 26 + packages/native-core-macro/README.md | 43 ++ packages/native-core-macro/src/lib.rs | 22 + packages/native-core/Cargo.toml | 4 +- packages/native-core/src/dioxus.rs | 26 +- packages/native-core/src/lib.rs | 4 +- packages/native-core/src/node.rs | 3 +- packages/native-core/src/passes.rs | 353 ++++++------ packages/native-core/src/real_dom.rs | 298 +++++++--- packages/native-core/src/tree.rs | 512 +++--------------- .../src/utils/persistant_iterator.rs | 12 +- 12 files changed, 567 insertions(+), 738 deletions(-) create mode 100644 packages/native-core-macro/.gitignore create mode 100644 packages/native-core-macro/Cargo.toml create mode 100644 packages/native-core-macro/README.md create mode 100644 packages/native-core-macro/src/lib.rs 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/Cargo.toml b/packages/native-core-macro/Cargo.toml new file mode 100644 index 000000000..ed8648f93 --- /dev/null +++ b/packages/native-core-macro/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "dioxus-native-core-macro" +version = "0.3.0" +edition = "2021" +description = "Build natively rendered apps with Dioxus" +license = "MIT/Apache-2.0" +repository = "https://github.com/DioxusLabs/dioxus/" +homepage = "https://dioxuslabs.com" +documentation = "https://dioxuslabs.com" +keywords = ["dom", "ui", "gui", "react"] + + +[lib] +proc-macro = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[dependencies] +syn = { version = "1.0.11", features = ["extra-traits", "full"] } +quote = "1.0" + +[dev-dependencies] +smallvec = "1.6" +rustc-hash = "1.1.0" +anymap = "0.12.1" +dioxus = { path = "../dioxus" } +dioxus-native-core = { path = "../native-core" } diff --git a/packages/native-core-macro/README.md b/packages/native-core-macro/README.md new file mode 100644 index 000000000..6b8c13590 --- /dev/null +++ b/packages/native-core-macro/README.md @@ -0,0 +1,43 @@ +# Dioxus Native Core Macro + +[![Crates.io][crates-badge]][crates-url] +[![MIT licensed][mit-badge]][mit-url] +[![Build Status][actions-badge]][actions-url] +[![Discord chat][discord-badge]][discord-url] + +[crates-badge]: https://img.shields.io/crates/v/dioxus-native-core-macro.svg +[crates-url]: https://crates.io/crates/dioxus-native-core-macro + +[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg +[mit-url]: https://github.com/dioxuslabs/dioxus/blob/master/LICENSE + +[actions-badge]: https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg +[actions-url]: https://github.com/dioxuslabs/dioxus/actions?query=workflow%3ACI+branch%3Amaster + +[discord-badge]: https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square +[discord-url]: https://discord.gg/XgGxMSkvUM + +[Website](https://dioxuslabs.com) | +[Guides](https://dioxuslabs.com/docs/0.3/guide/en/) | +[API Docs](https://docs.rs/dioxus-native-core-macro/latest/dioxus_native_core_macro) | +[Chat](https://discord.gg/XgGxMSkvUM) + + +## Overview + +`dioxus-native-core-macro` provides a handful of macros used by native-core for native renderers like TUI, Blitz, and Freya to derive their state. + + +## Contributing + +- Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues). +- Join the discord and ask questions! + +## License +This project is licensed under the [MIT license]. + +[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in Dioxus by you shall be licensed as MIT without any additional +terms or conditions. diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs new file mode 100644 index 000000000..21fbb7e89 --- /dev/null +++ b/packages/native-core-macro/src/lib.rs @@ -0,0 +1,22 @@ +extern crate proc_macro; + +mod sorted_slice; + +use proc_macro::TokenStream; +use quote::{quote, ToTokens, __private::Span}; +use sorted_slice::StrSlice; +use syn::parenthesized; +use syn::parse::ParseBuffer; +use syn::punctuated::Punctuated; +use syn::{ + self, + parse::{Parse, ParseStream, Result}, + parse_macro_input, parse_quote, Error, Field, Ident, Token, Type, +}; + +/// A helper attribute for deriving `State` for a struct. +#[proc_macro_attribute] +pub fn my_attribute(_: TokenStream, input: TokenStream) -> TokenStream { + let impl_block: syn::ItemImpl = syn::parse(input).unwrap(); + quote!(#impl_block).into() +} diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index 2e748563c..d5e724e86 100644 --- a/packages/native-core/Cargo.toml +++ b/packages/native-core/Cargo.toml @@ -28,6 +28,8 @@ hashbrown = { version = "0.13.2", features = ["raw"] } lightningcss = "1.0.0-alpha.39" rayon = "1.6.1" +shipyard = "0.6.2" + [dev-dependencies] rand = "0.8.5" @@ -37,4 +39,4 @@ dioxus-native-core = { path = ".", features = ["dioxus"] } [features] default = [] -dioxus = ["dioxus-core"] \ No newline at end of file +dioxus = ["dioxus-core"] diff --git a/packages/native-core/src/dioxus.rs b/packages/native-core/src/dioxus.rs index f7426f1d6..ffc80c1c7 100644 --- a/packages/native-core/src/dioxus.rs +++ b/packages/native-core/src/dioxus.rs @@ -1,5 +1,6 @@ use dioxus_core::{BorrowedAttributeValue, ElementId, Mutations, TemplateNode}; use rustc_hash::{FxHashMap, FxHashSet}; +use shipyard::Component; use crate::{ node::{ @@ -11,6 +12,9 @@ use crate::{ NodeId, NodeMut, RealDom, }; +#[derive(Component)] +struct ElementIdComponent(ElementId); + pub struct DioxusState { templates: FxHashMap>, stack: Vec, @@ -19,10 +23,9 @@ pub struct DioxusState { impl DioxusState { pub fn create(rdom: &mut RealDom) -> Self { - rdom.insert_slab::(); let root_id = rdom.root_id(); let mut root = rdom.get_mut(root_id).unwrap(); - root.insert(ElementId(0)); + root.insert(ElementIdComponent(ElementId(0))); Self { templates: FxHashMap::default(), stack: vec![root_id], @@ -40,7 +43,7 @@ impl DioxusState { fn set_element_id(&mut self, mut node: NodeMut, element_id: ElementId) { let node_id = node.id(); - node.insert(element_id); + node.insert(ElementIdComponent(element_id)); if self.node_id_mapping.len() <= element_id.0 { self.node_id_mapping.resize(element_id.0 + 1, None); } @@ -104,9 +107,11 @@ impl DioxusState { let node = rdom.get_mut(node_id).unwrap(); self.set_element_id(node, id); let mut node = rdom.get_mut(node_id).unwrap(); - if let NodeTypeMut::Text(text) = node.node_type_mut() { - *text = value.to_string(); + let node_type_mut = node.node_type_mut(); + if let NodeTypeMut::Text(mut text) = node_type_mut { + *text.text_mut() = value.to_string(); } else { + drop(node_type_mut); node.set_type(NodeType::Text(TextNode { text: value.to_string(), listeners: FxHashSet::default(), @@ -161,7 +166,8 @@ impl DioxusState { } => { let node_id = self.element_to_node_id(id); let mut node = rdom.get_mut(node_id).unwrap(); - if let NodeTypeMut::Element(element) = &mut node.node_type_mut() { + let mut node_type_mut = node.node_type_mut(); + if let NodeTypeMut::Element(element) = &mut node_type_mut { if let BorrowedAttributeValue::None = &value { element.remove_attributes(&OwnedAttributeDiscription { name: name.to_string(), @@ -181,8 +187,9 @@ impl DioxusState { SetText { value, id } => { let node_id = self.element_to_node_id(id); let mut node = rdom.get_mut(node_id).unwrap(); - if let NodeTypeMut::Text(text) = node.node_type_mut() { - *text = value.to_string(); + let node_type_mut = node.node_type_mut(); + if let NodeTypeMut::Text(mut text) = node_type_mut { + *text.text_mut() = value.to_string(); } } NewEventListener { name, id } => { @@ -260,7 +267,8 @@ fn create_template_node(rdom: &mut RealDom, node: &TemplateNode) -> NodeId { pub trait NodeImmutableDioxusExt: NodeImmutable { fn mounted_id(&self) -> Option { - self.get().copied() + let id = self.get::(); + id.map(|id| id.0) } } diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index ee65c35a4..74c5e947c 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -6,7 +6,6 @@ pub use passes::AnyMapLike; pub use passes::{Dependancy, State, TypeErasedPass}; pub use real_dom::{NodeMut, NodeRef, RealDom}; use rustc_hash::FxHasher; -pub use tree::NodeId; #[cfg(feature = "dioxus")] pub mod dioxus; @@ -18,13 +17,14 @@ mod passes; pub mod real_dom; pub mod tree; pub mod utils; +pub use shipyard::EntityId as NodeId; pub mod prelude { pub use crate::node::{ElementNode, FromAnyValue, NodeType, OwnedAttributeView, TextNode}; pub use crate::node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}; pub use crate::passes::{Dependancy, State}; pub use crate::real_dom::{NodeImmutable, NodeMut, NodeRef, RealDom}; - pub use crate::tree::NodeId; + pub use crate::NodeId; pub use crate::SendAnyMap; } diff --git a/packages/native-core/src/node.rs b/packages/native-core/src/node.rs index 51e096a97..1e6331c86 100644 --- a/packages/native-core/src/node.rs +++ b/packages/native-core/src/node.rs @@ -1,4 +1,5 @@ use rustc_hash::{FxHashMap, FxHashSet}; +use shipyard::Component; use std::{any::Any, fmt::Debug}; #[derive(Debug, Clone, Default)] @@ -25,7 +26,7 @@ impl TextNode { } /// A type of node with data specific to the node type. The types are a subset of the [VNode] types. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Component)] pub enum NodeType { Text(TextNode), Element(ElementNode), diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index 5a25c0424..31b873dde 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -1,17 +1,15 @@ use anymap::AnyMap; -use core::panic; use parking_lot::RwLock; use rustc_hash::{FxHashMap, FxHashSet}; +use shipyard::{Component, IntoBorrow, Unique, View, WorkloadSystem}; use std::any::{Any, TypeId}; use std::collections::BTreeMap; use std::marker::PhantomData; use std::sync::Arc; -use crate::node::{FromAnyValue, NodeType}; +use crate::node::FromAnyValue; use crate::node_ref::{NodeMaskBuilder, NodeView}; -use crate::real_dom::RealDom; -use crate::tree::{SlabEntry, Tree, TreeStateView}; -use crate::{FxDashSet, SendAnyMap}; +use crate::SendAnyMap; use crate::{NodeId, NodeMask}; #[derive(Default)] @@ -21,7 +19,7 @@ struct DirtyNodes { impl DirtyNodes { pub fn add_node(&mut self, node_id: NodeId) { - let node_id = node_id.0; + let node_id = node_id.uindex(); let index = node_id / 64; let bit = node_id % 64; let encoded = 1 << bit; @@ -37,17 +35,17 @@ impl DirtyNodes { self.passes_dirty.iter().all(|dirty| *dirty == 0) } - pub fn pop(&mut self) -> Option { + pub fn pop(&mut self) -> Option { let index = self.passes_dirty.iter().position(|dirty| *dirty != 0)?; let passes = self.passes_dirty[index]; let node_id = passes.trailing_zeros(); let encoded = 1 << node_id; self.passes_dirty[index] &= !encoded; - Some(NodeId((index * 64) + node_id as usize)) + Some((index * 64) + node_id as usize) } } -#[derive(Clone)] +#[derive(Clone, Unique)] pub struct DirtyNodeStates { dirty: Arc>>>, } @@ -76,7 +74,7 @@ impl DirtyNodeStates { } } - fn pop_front(&self, pass_id: TypeId) -> Option<(u16, NodeId)> { + fn pop_front(&self, pass_id: TypeId) -> Option<(u16, usize)> { let mut values = self.dirty.get(&pass_id)?.write(); let mut value = values.first_entry()?; let height = *value.key(); @@ -89,7 +87,7 @@ impl DirtyNodeStates { Some((height, id)) } - fn pop_back(&self, pass_id: TypeId) -> Option<(u16, NodeId)> { + fn pop_back(&self, pass_id: TypeId) -> Option<(u16, usize)> { let mut values = self.dirty.get(&pass_id)?.write(); let mut value = values.last_entry()?; let height = *value.key(); @@ -110,6 +108,7 @@ pub trait State: Any + Send + Sync { type ChildDependencies: Dependancy; /// This is a tuple of (T: Pass, ..) of states read from the node required to run this pass type NodeDependencies: Dependancy; + type CombinedDependencies: Dependancy; /// This is a mask of what aspects of the node are required to run this pass const NODE_MASK: NodeMaskBuilder<'static>; @@ -131,47 +130,43 @@ pub trait State: Any + Send + Sync { children: Option::ElementBorrowed<'a>>>, context: &SendAnyMap, ) -> Self; +} - fn validate() { - // this type should not be a node dependency - for type_id in Self::node_type_ids().iter().copied() { - if type_id == TypeId::of::() { - panic!("The current type cannot be a node dependency"); - } - } - // this type cannot be both a parent and child dependency - assert!( - !(Self::parent_type_ids() - .iter() - .any(|type_id| *type_id == TypeId::of::()) - && Self::child_type_ids() - .iter() - .any(|type_id| *type_id == TypeId::of::())), - "The current type cannot be a parent and child dependency" - ); - // no states have the same type id - if Self::child_type_ids() - .iter() - .collect::>() - .len() - != Self::child_type_ids().len() - || Self::parent_type_ids() - .iter() - .collect::>() - .len() - != Self::parent_type_ids().len() - || Self::node_type_ids().iter().collect::>().len() - != Self::node_type_ids().len() - { - panic!("all states must have unique type ids"); - } - } +pub trait AnyState: State { + fn workload_system() -> WorkloadSystem; + // Box::new( + // move |node_id: NodeId, tree: &mut TreeStateView, context: &SendAnyMap| { + // debug_assert!(!Self::NodeDependencies::type_ids() + // .iter() + // .any(|id| *id == TypeId::of::())); + // // get all of the states from the tree view + // // Safety: No node has itself as a parent or child. + // let myself: SlabEntry<'static, Self> = unsafe { + // std::mem::transmute(tree.get_slab_mut::().unwrap().entry(node_id)) + // }; + // let node_data = tree.get_single::>(node_id).unwrap(); + // let node = tree.get::(node_id).unwrap(); + // let children = tree.children::(node_id); + // let parent = tree.parent::(node_id); + + // let view = NodeView::new(node_id, node_data, &node_mask); + // if myself.value.is_none() { + // *myself.value = Some(Self::create(view, node, parent, children, context)); + // true + // } else { + // myself + // .value + // .as_mut() + // .unwrap() + // .update(view, node, parent, children, context) + // } + // }, + // ) as PassCallback fn to_type_erased() -> TypeErasedPass where Self: Sized, { - Self::validate(); let node_mask = Self::NODE_MASK.build(); TypeErasedPass { this_type_id: TypeId::of::(), @@ -181,35 +176,7 @@ pub trait State: Any + Send + Sync { dependants: FxHashSet::default(), mask: node_mask.clone(), pass_direction: Self::pass_direction(), - pass: Box::new( - move |node_id: NodeId, tree: &mut TreeStateView, context: &SendAnyMap| { - debug_assert!(!Self::NodeDependencies::type_ids() - .iter() - .any(|id| *id == TypeId::of::())); - // get all of the states from the tree view - // Safety: No node has itself as a parent or child. - let myself: SlabEntry<'static, Self> = unsafe { - std::mem::transmute(tree.get_slab_mut::().unwrap().entry(node_id)) - }; - let node_data = tree.get_single::>(node_id).unwrap(); - let node = tree.get::(node_id).unwrap(); - let children = tree.children::(node_id); - let parent = tree.parent::(node_id); - - let view = NodeView::new(node_id, node_data, &node_mask); - if myself.value.is_none() { - *myself.value = Some(Self::create(view, node, parent, children, context)); - true - } else { - myself - .value - .as_mut() - .unwrap() - .update(view, node, parent, children, context) - } - }, - ) as PassCallback, - create: Box::new(|tree: &mut Tree| tree.insert_slab::()) as CreatePassCallback, + workload: Some(Self::workload_system()), phantom: PhantomData, } } @@ -257,58 +224,57 @@ pub struct TypeErasedPass { pub(crate) combined_dependancy_type_ids: FxHashSet, pub(crate) dependants: FxHashSet, pub(crate) mask: NodeMask, - pass: PassCallback, - pub(crate) create: CreatePassCallback, + pub(crate) workload: Option, pub(crate) pass_direction: PassDirection, phantom: PhantomData, } -impl TypeErasedPass { - fn resolve( - &self, - mut tree: TreeStateView, - dirty: &DirtyNodeStates, - nodes_updated: &FxDashSet, - ctx: &SendAnyMap, - ) { - match self.pass_direction { - PassDirection::ParentToChild => { - while let Some((height, id)) = dirty.pop_front(self.this_type_id) { - if (self.pass)(id, &mut tree, ctx) { - nodes_updated.insert(id); - for id in tree.children_ids(id).unwrap() { - for dependant in &self.dependants { - dirty.insert(*dependant, *id, height + 1); - } - } - } - } - } - PassDirection::ChildToParent => { - while let Some((height, id)) = dirty.pop_back(self.this_type_id) { - if (self.pass)(id, &mut tree, ctx) { - nodes_updated.insert(id); - if let Some(id) = tree.parent_id(id) { - for dependant in &self.dependants { - dirty.insert(*dependant, id, height - 1); - } - } - } - } - } - PassDirection::AnyOrder => { - while let Some((height, id)) = dirty.pop_back(self.this_type_id) { - if (self.pass)(id, &mut tree, ctx) { - nodes_updated.insert(id); - for dependant in &self.dependants { - dirty.insert(*dependant, id, height); - } - } - } - } - } - } -} +// impl TypeErasedPass { +// fn resolve( +// &self, +// mut tree: TreeStateView, +// dirty: &DirtyNodeStates, +// nodes_updated: &FxDashSet, +// ctx: &SendAnyMap, +// ) { +// match self.pass_direction { +// PassDirection::ParentToChild => { +// while let Some((height, id)) = dirty.pop_front(self.this_type_id) { +// if (self.pass)(id, &mut tree, ctx) { +// nodes_updated.insert(id); +// for id in tree.children_ids(id).unwrap() { +// for dependant in &self.dependants { +// dirty.insert(*dependant, *id, height + 1); +// } +// } +// } +// } +// } +// PassDirection::ChildToParent => { +// while let Some((height, id)) = dirty.pop_back(self.this_type_id) { +// if (self.pass)(id, &mut tree, ctx) { +// nodes_updated.insert(id); +// if let Some(id) = tree.parent_id(id) { +// for dependant in &self.dependants { +// dirty.insert(*dependant, id, height - 1); +// } +// } +// } +// } +// } +// PassDirection::AnyOrder => { +// while let Some((height, id)) = dirty.pop_back(self.this_type_id) { +// if (self.pass)(id, &mut tree, ctx) { +// nodes_updated.insert(id); +// for dependant in &self.dependants { +// dirty.insert(*dependant, id, height); +// } +// } +// } +// } +// } +// } +// } #[derive(Debug)] pub enum PassDirection { @@ -317,9 +283,6 @@ pub enum PassDirection { AnyOrder, } -type PassCallback = Box bool + Send + Sync>; -type CreatePassCallback = Box; - pub trait AnyMapLike<'a> { fn get(self) -> Option<&'a T>; } @@ -337,25 +300,17 @@ impl<'a> AnyMapLike<'a> for &'a SendAnyMap { } pub trait Dependancy { - type ElementBorrowed<'a> - where - Self: 'a; + type ElementBorrowed<'a>: IntoBorrow; - fn borrow_elements_from<'a, T: AnyMapLike<'a> + Copy>( - map: T, - ) -> Option>; - fn type_ids() -> Box<[TypeId]>; + fn type_ids() -> Box<[TypeId]> { + Box::new([]) + } } macro_rules! impl_dependancy { ($($t:ident),*) => { - impl< $($t: Any + Send + Sync),* > Dependancy for ($($t,)*) { - type ElementBorrowed<'a> = ($(&'a $t,)*) where Self: 'a; - - #[allow(unused_variables, clippy::unused_unit, non_snake_case)] - fn borrow_elements_from<'a, T: AnyMapLike<'a> + Copy>(map: T) -> Option> { - Some(($(map.get::<$t>()?,)*)) - } + impl< $($t: Send + Sync + Component),* > Dependancy for ($($t,)*) { + type ElementBorrowed<'a> = ($(View<'a, $t>,)*); fn type_ids() -> Box<[TypeId]> { Box::new([$(TypeId::of::<$t>()),*]) @@ -375,66 +330,60 @@ impl_dependancy!(A, B, C, D, E, F, G); impl_dependancy!(A, B, C, D, E, F, G, H); impl_dependancy!(A, B, C, D, E, F, G, H, I); impl_dependancy!(A, B, C, D, E, F, G, H, I, J); -impl_dependancy!(A, B, C, D, E, F, G, H, I, J, K); -impl_dependancy!(A, B, C, D, E, F, G, H, I, J, K, L); -impl_dependancy!(A, B, C, D, E, F, G, H, I, J, K, L, M); -impl_dependancy!(A, B, C, D, E, F, G, H, I, J, K, L, M, N); -impl_dependancy!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O); -impl_dependancy!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P); -pub fn resolve_passes( - dom: &mut RealDom, - dirty_nodes: DirtyNodeStates, - ctx: SendAnyMap, - parallel: bool, -) -> FxDashSet { - let passes = &dom.dirty_nodes.passes; - let mut resolved_passes: FxHashSet = FxHashSet::default(); - let mut resolving = Vec::new(); - let nodes_updated = Arc::new(FxDashSet::default()); - let ctx = Arc::new(ctx); - let mut pass_indexes_remaining: Vec<_> = (0..passes.len()).collect::>(); - while !pass_indexes_remaining.is_empty() { - let mut currently_in_use = FxHashSet::::default(); - let dynamically_borrowed_tree = dom.tree.dynamically_borrowed(); - rayon::in_place_scope(|s| { - let mut i = 0; - while i < pass_indexes_remaining.len() { - let passes_idx = pass_indexes_remaining[i]; - let pass = &passes[passes_idx]; - let pass_id = pass.this_type_id; - // check if the pass is ready to be run - if pass.combined_dependancy_type_ids.iter().all(|d| { - (resolved_passes.contains(d) || *d == pass_id) && !currently_in_use.contains(d) - }) { - pass_indexes_remaining.remove(i); - resolving.push(pass_id); - currently_in_use.insert(pass.this_type_id); - let tree_view = dynamically_borrowed_tree.view( - pass.combined_dependancy_type_ids - .iter() - .filter(|id| **id != pass.this_type_id) - .copied() - .chain(std::iter::once(TypeId::of::>())), - [pass.this_type_id], - ); - let dirty_nodes = dirty_nodes.clone(); - let nodes_updated = nodes_updated.clone(); - let ctx = ctx.clone(); - if parallel { - s.spawn(move |_| { - pass.resolve(tree_view, &dirty_nodes, &nodes_updated, &ctx); - }); - } else { - pass.resolve(tree_view, &dirty_nodes, &nodes_updated, &ctx); - } - } else { - i += 1; - } - } - }); - resolved_passes.extend(resolving.iter().copied()); - resolving.clear() - } - std::sync::Arc::try_unwrap(nodes_updated).unwrap() -} +// pub fn resolve_passes( +// dom: &mut RealDom, +// dirty_nodes: DirtyNodeStates, +// ctx: SendAnyMap, +// parallel: bool, +// ) -> FxDashSet { +// let passes = &dom.dirty_nodes.passes; +// let mut resolved_passes: FxHashSet = FxHashSet::default(); +// let mut resolving = Vec::new(); +// let nodes_updated = Arc::new(FxDashSet::default()); +// let ctx = Arc::new(ctx); +// let mut pass_indexes_remaining: Vec<_> = (0..passes.len()).collect::>(); +// while !pass_indexes_remaining.is_empty() { +// let mut currently_in_use = FxHashSet::::default(); +// let dynamically_borrowed_tree = dom.tree.dynamically_borrowed(); +// rayon::in_place_scope(|s| { +// let mut i = 0; +// while i < pass_indexes_remaining.len() { +// let passes_idx = pass_indexes_remaining[i]; +// let pass = &passes[passes_idx]; +// let pass_id = pass.this_type_id; +// // check if the pass is ready to be run +// if pass.combined_dependancy_type_ids.iter().all(|d| { +// (resolved_passes.contains(d) || *d == pass_id) && !currently_in_use.contains(d) +// }) { +// pass_indexes_remaining.remove(i); +// resolving.push(pass_id); +// currently_in_use.insert(pass.this_type_id); +// let tree_view = dynamically_borrowed_tree.view( +// pass.combined_dependancy_type_ids +// .iter() +// .filter(|id| **id != pass.this_type_id) +// .copied() +// .chain(std::iter::once(TypeId::of::>())), +// [pass.this_type_id], +// ); +// let dirty_nodes = dirty_nodes.clone(); +// let nodes_updated = nodes_updated.clone(); +// let ctx = ctx.clone(); +// if parallel { +// s.spawn(move |_| { +// pass.resolve(tree_view, &dirty_nodes, &nodes_updated, &ctx); +// }); +// } else { +// pass.resolve(tree_view, &dirty_nodes, &nodes_updated, &ctx); +// } +// } else { +// i += 1; +// } +// } +// }); +// resolved_passes.extend(resolving.iter().copied()); +// resolving.clear() +// } +// std::sync::Arc::try_unwrap(nodes_updated).unwrap() +// } diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index ac9d5385f..5908f8e4b 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -1,6 +1,13 @@ use rustc_hash::{FxHashMap, FxHashSet}; -use std::any::{Any, TypeId}; +use shipyard::error::GetStorage; +use shipyard::track::Untracked; +use shipyard::{ + Component, Get, IntoBorrow, ScheduledWorkload, System, Unique, View, ViewMut, Workload, + WorkloadSystem, +}; +use std::any::TypeId; use std::collections::VecDeque; +use std::ops::{Deref, DerefMut}; use std::sync::{Arc, RwLock}; use crate::node::{ @@ -8,11 +15,18 @@ use crate::node::{ }; use crate::node_ref::{NodeMask, NodeMaskBuilder}; use crate::node_watcher::NodeWatcher; -use crate::passes::{resolve_passes, DirtyNodeStates, TypeErasedPass}; +use crate::passes::{DirtyNodeStates, TypeErasedPass}; use crate::prelude::AttributeMaskBuilder; -use crate::tree::{NodeId, Tree}; +use crate::tree::Tree; +use crate::NodeId; use crate::{FxDashSet, SendAnyMap}; +#[derive(Unique)] +struct SendAnyMapWrapper(SendAnyMap); + +#[derive(Unique, Default)] +struct DirtyNodes(FxDashSet); + pub(crate) struct NodesDirty { passes_updated: FxHashMap>, nodes_updated: FxHashMap, @@ -66,16 +80,19 @@ pub struct RealDom { nodes_listening: FxHashMap>, pub(crate) dirty_nodes: NodesDirty, node_watchers: NodeWatchers, + workload: ScheduledWorkload, phantom: std::marker::PhantomData, } impl RealDom { pub fn new(mut passes: Box<[TypeErasedPass]>) -> RealDom { let mut tree = Tree::new(); - tree.insert_slab::>(); - for pass in passes.iter() { - (pass.create)(&mut tree); + let mut workload = Workload::new("Main Workload"); + for pass in &mut *passes { + let system = pass.workload.take().unwrap(); + workload = workload.with_system(system); } + let (workload, _) = workload.build().unwrap(); let root_id = tree.root(); let root_node: NodeType = NodeType::Element(ElementNode { tag: "Root".to_string(), @@ -101,7 +118,6 @@ impl RealDom { let mut passes_updated = FxHashMap::default(); let mut nodes_updated = FxHashMap::default(); - let root_id = NodeId(0); passes_updated.insert(root_id, passes.iter().map(|x| x.this_type_id).collect()); nodes_updated.insert(root_id, NodeMaskBuilder::ALL.build()); @@ -114,6 +130,7 @@ impl RealDom { passes, }, node_watchers: Default::default(), + workload, phantom: std::marker::PhantomData, } } @@ -152,12 +169,6 @@ impl RealDom { } } - /// Return the number of nodes in the dom. - pub fn size(&self) -> usize { - // The dom has a root node, ignore it. - self.tree.size() - 1 - } - /// Returns the id of the root node. pub fn root_id(&self) -> NodeId { self.tree.root() @@ -171,16 +182,21 @@ impl RealDom { self.tree.contains(id).then(|| NodeMut::new(id, self)) } - /// WARNING: This escapes the reactive system that the real dom uses. Any changes made with this method will not trigger updates in states when [RealDom::update_state] is called. - pub fn get_state_mut_raw(&mut self, id: NodeId) -> Option<&mut T> { - self.tree.get_mut(id) + fn borrow_raw<'a, B: IntoBorrow>(&'a self) -> Result + where + B::Borrow: shipyard::Borrow<'a, View = B>, + { + self.tree.nodes.borrow() + } + + fn borrow_node_type_mut(&self) -> Result>, GetStorage> { + self.tree.nodes.borrow() } /// 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, ctx: SendAnyMap, - parallel: bool, ) -> (FxDashSet, FxHashMap) { let passes = std::mem::take(&mut self.dirty_nodes.passes_updated); let nodes_updated = std::mem::take(&mut self.dirty_nodes.nodes_updated); @@ -195,10 +211,17 @@ impl RealDom { } } - ( - resolve_passes(self, dirty_nodes, ctx, parallel), - nodes_updated, - ) + let _ = self.tree.remove_unique::(); + let _ = self.tree.remove_unique::(); + self.tree.add_unique(dirty_nodes); + self.tree.add_unique(SendAnyMapWrapper(ctx)); + self.tree.add_unique(DirtyNodes::default()); + + self.workload.run_with_world(&self.tree.nodes).unwrap(); + + let dirty = self.tree.remove_unique::().unwrap(); + + (dirty.0, nodes_updated) } pub fn traverse_depth_first(&self, mut f: impl FnMut(NodeRef)) { @@ -221,7 +244,7 @@ impl RealDom { f(node); if let Some(children) = self.tree.children_ids(id) { for id in children { - queue.push_back(*id); + queue.push_back(id); } } } @@ -258,32 +281,74 @@ impl RealDom { } } - pub fn insert_slab(&mut self) { - self.tree.insert_slab::(); - } - pub fn add_node_watcher(&mut self, watcher: impl NodeWatcher + 'static + Send + Sync) { self.node_watchers.write().unwrap().push(Box::new(watcher)); } } +pub struct ViewEntry<'a, V: Component + Send + Sync> { + view: View<'a, V>, + id: NodeId, +} + +impl<'a, V: Component + Send + Sync> ViewEntry<'a, V> { + fn new(view: View<'a, V>, id: NodeId) -> Self { + Self { view, id } + } +} + +impl<'a, V: Component + Send + Sync> Deref for ViewEntry<'a, V> { + type Target = V; + + fn deref(&self) -> &Self::Target { + &self.view[self.id] + } +} + +pub struct ViewEntryMut<'a, V: Component + Send + Sync> { + view: ViewMut<'a, V, Untracked>, + id: NodeId, +} + +impl<'a, V: Component + Send + Sync> ViewEntryMut<'a, V> { + fn new(view: ViewMut<'a, V, Untracked>, id: NodeId) -> Self { + Self { view, id } + } +} + +impl<'a, V: Component + Send + Sync> Deref for ViewEntryMut<'a, V> { + type Target = V; + + fn deref(&self) -> &Self::Target { + self.view.get(self.id).unwrap() + } +} + +impl<'a, V: Component + Send + Sync> DerefMut for ViewEntryMut<'a, V> { + fn deref_mut(&mut self) -> &mut Self::Target { + (&mut self.view).get(self.id).unwrap() + } +} + pub trait NodeImmutable: Sized { fn real_dom(&self) -> &RealDom; fn id(&self) -> NodeId; #[inline] - fn node_type(&self) -> &NodeType { + fn node_type(&self) -> ViewEntry> { self.get().unwrap() } #[inline] - fn get(&self) -> Option<&T> { - self.real_dom().tree.get(self.id()) + fn get<'a, T: Component + Sync + Send>(&'a self) -> Option> { + // self.real_dom().tree.get(self.id()) + let view: View<'a, T> = self.real_dom().borrow_raw().ok()?; + Some(ViewEntry::new(view, self.id())) } #[inline] - fn child_ids(&self) -> Option<&[NodeId]> { + fn child_ids(&self) -> Option> { self.real_dom().tree.children_ids(self.id()) } @@ -413,7 +478,9 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { } #[inline] - pub fn get_mut(&mut self) -> Option<&mut T> { + pub fn get_mut + Sync + Send>( + &mut self, + ) -> Option> { // mark the node state as dirty self.dom .dirty_nodes @@ -421,11 +488,12 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { .entry(self.id) .or_default() .insert(TypeId::of::()); - self.dom.tree.get_mut(self.id) + let view_mut: ViewMut = self.dom.borrow_raw().ok()?; + Some(ViewEntryMut::new(view_mut, self.id)) } #[inline] - pub fn insert(&mut self, value: T) { + pub fn insert(&mut self, value: T) { // mark the node state as dirty self.dom .dirty_nodes @@ -493,17 +561,20 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { #[inline] pub fn remove(&mut self) { let id = self.id(); - if let NodeType::Element(ElementNode { listeners, .. }) - | NodeType::Text(TextNode { listeners, .. }) = - self.dom.get_state_mut_raw::>(id).unwrap() { - let listeners = std::mem::take(listeners); - for event in listeners { - self.dom - .nodes_listening - .get_mut(&event) - .unwrap() - .remove(&id); + let RealDom { + tree, + nodes_listening, + .. + } = &mut self.dom; + let mut view: ViewMut> = tree.nodes.borrow().unwrap(); + if let NodeType::Element(ElementNode { listeners, .. }) + | NodeType::Text(TextNode { listeners, .. }) = (&mut view).get(id).unwrap() + { + let listeners = std::mem::take(listeners); + for event in listeners { + nodes_listening.get_mut(&event).unwrap().remove(&id); + } } } self.mark_removed(); @@ -539,22 +610,27 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { #[inline] pub fn add_event_listener(&mut self, event: &str) { let id = self.id(); - let node_type: &mut NodeType = self.dom.tree.get_mut(self.id).unwrap(); + let RealDom { + tree, + dirty_nodes, + nodes_listening, + .. + } = &mut self.dom; + let mut view: ViewMut> = tree.nodes.borrow().unwrap(); + let node_type: &mut NodeType = (&mut view).get(self.id).unwrap(); if let NodeType::Element(ElementNode { listeners, .. }) | NodeType::Text(TextNode { listeners, .. }) = node_type { - self.dom - .dirty_nodes - .mark_dirty(self.id, NodeMaskBuilder::new().with_listeners().build()); + dirty_nodes.mark_dirty(self.id, NodeMaskBuilder::new().with_listeners().build()); listeners.insert(event.to_string()); - match self.dom.nodes_listening.get_mut(event) { + match nodes_listening.get_mut(event) { Some(hs) => { hs.insert(id); } None => { let mut hs = FxHashSet::default(); hs.insert(id); - self.dom.nodes_listening.insert(event.to_string(), hs); + nodes_listening.insert(event.to_string(), hs); } } } @@ -563,16 +639,21 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { #[inline] pub fn remove_event_listener(&mut self, event: &str) { let id = self.id(); - let node_type: &mut NodeType = self.dom.tree.get_mut(self.id).unwrap(); + let RealDom { + tree, + dirty_nodes, + nodes_listening, + .. + } = &mut self.dom; + let mut view: ViewMut> = tree.nodes.borrow().unwrap(); + let node_type: &mut NodeType = (&mut view).get(self.id).unwrap(); if let NodeType::Element(ElementNode { listeners, .. }) | NodeType::Text(TextNode { listeners, .. }) = node_type { - self.dom - .dirty_nodes - .mark_dirty(self.id, NodeMaskBuilder::new().with_listeners().build()); + dirty_nodes.mark_dirty(self.id, NodeMaskBuilder::new().with_listeners().build()); listeners.remove(event); - self.dom.nodes_listening.get_mut(event).unwrap().remove(&id); + nodes_listening.get_mut(event).unwrap().remove(&id); } } @@ -591,28 +672,32 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { } pub fn node_type_mut(&mut self) -> NodeTypeMut<'_, V> { - let Self { id, dom } = self; + let id = self.id(); let RealDom { - dirty_nodes, tree, .. - } = dom; - let node_type = tree.get_mut(*id).unwrap(); - match node_type { - NodeType::Element(element) => NodeTypeMut::Element(ElementNodeMut { - id: *id, - element, + tree, dirty_nodes, .. + } = &mut self.dom; + let view: ViewMut> = tree.nodes.borrow().unwrap(); + let node_type = ViewEntryMut::new(view, id); + match &*node_type { + NodeType::Element(_) => NodeTypeMut::Element(ElementNodeMut { + id, + element: node_type, + dirty_nodes, + }), + NodeType::Text(_) => NodeTypeMut::Text(TextNodeMut { + id, + text: node_type, dirty_nodes, }), - NodeType::Text(text) => { - dirty_nodes.mark_dirty(self.id, NodeMaskBuilder::new().with_text().build()); - - NodeTypeMut::Text(&mut text.text) - } NodeType::Placeholder => NodeTypeMut::Placeholder, } } pub fn set_type(&mut self, new: NodeType) { - *self.dom.tree.get_mut::>(self.id).unwrap() = new; + { + let mut view: ViewMut> = self.dom.borrow_node_type_mut().unwrap(); + *(&mut view).get(self.id).unwrap() = new; + } self.dom .dirty_nodes .mark_dirty(self.id, NodeMaskBuilder::ALL.build()) @@ -638,39 +723,94 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { pub enum NodeTypeMut<'a, V: FromAnyValue + Send + Sync = ()> { Element(ElementNodeMut<'a, V>), - Text(&'a mut String), + Text(TextNodeMut<'a, V>), Placeholder, } +pub struct TextNodeMut<'a, V: FromAnyValue + Send + Sync = ()> { + id: NodeId, + text: ViewEntryMut<'a, NodeType>, + dirty_nodes: &'a mut NodesDirty, +} + +impl TextNodeMut<'_, V> { + pub fn text(&self) -> &str { + match &*self.text { + NodeType::Text(text) => &text.text, + _ => unreachable!(), + } + } + + pub fn text_mut(&mut self) -> &mut String { + self.dirty_nodes + .mark_dirty(self.id, NodeMaskBuilder::new().with_text().build()); + match &mut *self.text { + NodeType::Text(text) => &mut text.text, + _ => unreachable!(), + } + } +} + +impl Deref for TextNodeMut<'_, V> { + type Target = String; + + fn deref(&self) -> &Self::Target { + match &*self.text { + NodeType::Text(text) => &text.text, + _ => unreachable!(), + } + } +} + +impl DerefMut for TextNodeMut<'_, V> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.text_mut() + } +} + pub struct ElementNodeMut<'a, V: FromAnyValue + Send + Sync = ()> { id: NodeId, - element: &'a mut ElementNode, + element: ViewEntryMut<'a, NodeType>, dirty_nodes: &'a mut NodesDirty, } impl ElementNodeMut<'_, V> { + fn element(&self) -> &ElementNode { + match &*self.element { + NodeType::Element(element) => element, + _ => unreachable!(), + } + } + + fn element_mut(&mut self) -> &mut ElementNode { + match &mut *self.element { + NodeType::Element(element) => element, + _ => unreachable!(), + } + } + pub fn tag(&self) -> &str { - &self.element.tag + &self.element().tag } pub fn tag_mut(&mut self) -> &mut String { self.dirty_nodes .mark_dirty(self.id, NodeMaskBuilder::new().with_tag().build()); - &mut self.element.tag + &mut self.element_mut().tag } pub fn namespace(&self) -> Option<&str> { - self.element.namespace.as_deref() + self.element().namespace.as_deref() } pub fn namespace_mut(&mut self) -> &mut Option { self.dirty_nodes .mark_dirty(self.id, NodeMaskBuilder::new().with_namespace().build()); - &mut self.element.namespace + &mut self.element_mut().namespace } pub fn attributes(&self) -> &FxHashMap> { - &self.element.attributes + &self.element().attributes } pub fn set_attribute( @@ -684,7 +824,7 @@ impl ElementNodeMut<'_, V> { .with_attrs(AttributeMaskBuilder::Some(&[&name.name])) .build(), ); - self.element.attributes.insert(name, value) + self.element_mut().attributes.insert(name, value) } pub fn remove_attributes( @@ -697,7 +837,7 @@ impl ElementNodeMut<'_, V> { .with_attrs(AttributeMaskBuilder::Some(&[&name.name])) .build(), ); - self.element.attributes.remove(name) + self.element_mut().attributes.remove(name) } pub fn get_attribute_mut( @@ -710,10 +850,10 @@ impl ElementNodeMut<'_, V> { .with_attrs(AttributeMaskBuilder::Some(&[&name.name])) .build(), ); - self.element.attributes.get_mut(name) + self.element_mut().attributes.get_mut(name) } pub fn listeners(&self) -> &FxHashSet { - &self.element.listeners + &self.element().listeners } } diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index 1ba4f0bda..0d8bab7c9 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -1,16 +1,8 @@ -use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard, RwLockWriteGuard}; -use rustc_hash::{FxHashMap, FxHasher}; -use std::any::{Any, TypeId}; -use std::collections::VecDeque; +use crate::NodeId; +use shipyard::{Component, EntitiesView, EntityId, Get, Unique, View, ViewMut, World}; use std::fmt::Debug; -use std::hash::BuildHasherDefault; -use crate::{AnyMapLike, Dependancy}; - -#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Hash)] -pub struct NodeId(pub usize); - -#[derive(PartialEq, Eq, Clone, Debug)] +#[derive(PartialEq, Eq, Clone, Debug, Component)] pub(crate) struct Node { parent: Option, children: Vec, @@ -19,60 +11,55 @@ pub(crate) struct Node { #[derive(Debug)] pub(crate) struct Tree { - nodes: AnySlab, + pub(crate) nodes: World, root: NodeId, } impl Tree { pub fn new() -> Self { - let mut nodes = AnySlab::default(); - let mut node = nodes.insert(); - node.insert(Node { + let mut nodes = World::default(); + let node = Node { parent: None, children: Vec::new(), height: 0, - }); - let root = node.id(); + }; + let root = nodes.add_entity((node,)); Self { nodes, root } } - pub(crate) fn insert_slab(&mut self) { - self.nodes.get_or_insert_slab_mut::(); + pub fn add_unique(&mut self, data: T) { + self.nodes.add_unique(data); } - fn node_slab(&self) -> &SlabStorage { - self.nodes.read_slab() + pub fn remove_unique( + &mut self, + ) -> Result { + self.nodes.remove_unique::() } - pub fn get_node_data(&self, id: NodeId) -> &Node { - self.node_slab().get(id).unwrap() + fn node_data(&self) -> View { + self.nodes.borrow().unwrap() } - pub fn try_get_node_data(&self, id: NodeId) -> Option<&Node> { - self.node_slab().get(id) - } - - fn node_slab_mut(&mut self) -> &mut SlabStorage { - self.nodes.write_slab() - } - - pub fn get_node_data_mut(&mut self, id: NodeId) -> &mut Node { - self.node_slab_mut().get_mut(id).unwrap() + fn node_data_mut(&self) -> ViewMut { + self.nodes.borrow().unwrap() } pub fn remove(&mut self, id: NodeId) { fn recurse(tree: &mut Tree, id: NodeId) { - let children = { tree.get_node_data(id).children.clone() }; - for child in children { - recurse(tree, child); + let children = tree.children_ids(id); + if let Some(children) = children { + for child in children { + recurse(tree, child); + } } - tree.nodes.remove(id); + tree.nodes.delete_entity(id); } { - let node_data_mut = self.node_slab_mut(); + let mut node_data_mut = self.node_data_mut(); if let Some(parent) = node_data_mut.get(id).unwrap().parent { - let parent = node_data_mut.get_mut(parent).unwrap(); + let parent = (&mut node_data_mut).get(parent).unwrap(); parent.children.retain(|&child| child != id); } } @@ -82,19 +69,20 @@ impl Tree { pub fn remove_single(&mut self, id: NodeId) { { - let node_data_mut = self.node_slab_mut(); + let mut node_data_mut = self.node_data_mut(); if let Some(parent) = node_data_mut.get(id).unwrap().parent { - let parent = node_data_mut.get_mut(parent).unwrap(); + let parent = (&mut node_data_mut).get(parent).unwrap(); parent.children.retain(|&child| child != id); } } - self.nodes.remove(id); + self.nodes.delete_entity(id); } fn set_height(&mut self, node: NodeId, height: u16) { let children = { - let mut node = self.get_node_data_mut(node); + let mut node_data_mut = self.node_data_mut(); + let mut node = (&mut node_data_mut).get(node).unwrap(); node.height = height; node.children.clone() }; @@ -103,31 +91,33 @@ impl Tree { } } - pub fn create_node(&mut self) -> EntryBuilder<'_> { - let mut node = self.nodes.insert(); - node.insert(Node { + pub fn create_node(&mut self) -> NodeBuilder<'_> { + let node = self.nodes.add_entity((Node { parent: None, children: Vec::new(), height: 0, - }); - node + },)); + NodeBuilder { tree: self, node } } pub fn add_child(&mut self, parent: NodeId, new: NodeId) { - let node_state = self.node_slab_mut(); - node_state.get_mut(new).unwrap().parent = Some(parent); - let parent = node_state.get_mut(parent).unwrap(); - parent.children.push(new); - let height = parent.height + 1; + let height; + { + let mut node_state = self.node_data_mut(); + (&mut node_state).get(new).unwrap().parent = Some(parent); + let parent = (&mut node_state).get(parent).unwrap(); + parent.children.push(new); + height = parent.height + 1; + } self.set_height(new, height); } pub fn replace(&mut self, old_id: NodeId, new_id: NodeId) { { - let node_state = self.node_slab_mut(); + let mut node_state = self.node_data_mut(); // update the parent's link to the child if let Some(parent_id) = node_state.get(old_id).unwrap().parent { - let parent = node_state.get_mut(parent_id).unwrap(); + let parent = (&mut node_state).get(parent_id).unwrap(); for id in &mut parent.children { if *id == old_id { *id = new_id; @@ -135,6 +125,7 @@ impl Tree { } } let height = parent.height + 1; + drop(node_state); self.set_height(new_id, height); } } @@ -143,11 +134,11 @@ impl Tree { } pub fn insert_before(&mut self, old_id: NodeId, new_id: NodeId) { - let node_state = self.node_slab_mut(); + let mut node_state = self.node_data_mut(); let old_node = node_state.get(old_id).unwrap(); let parent_id = old_node.parent.expect("tried to insert before root"); - node_state.get_mut(new_id).unwrap().parent = Some(parent_id); - let parent = node_state.get_mut(parent_id).unwrap(); + (&mut node_state).get(new_id).unwrap().parent = Some(parent_id); + let parent = (&mut node_state).get(parent_id).unwrap(); let index = parent .children .iter() @@ -155,15 +146,16 @@ impl Tree { .unwrap(); parent.children.insert(index, new_id); let height = parent.height + 1; + drop(node_state); self.set_height(new_id, height); } pub fn insert_after(&mut self, old_id: NodeId, new_id: NodeId) { - let node_state = self.node_slab_mut(); + let mut node_state = self.node_data_mut(); let old_node = node_state.get(old_id).unwrap(); let parent_id = old_node.parent.expect("tried to insert before root"); - node_state.get_mut(new_id).unwrap().parent = Some(parent_id); - let parent = node_state.get_mut(parent_id).unwrap(); + (&mut node_state).get(new_id).unwrap().parent = Some(parent_id); + let parent = (&mut node_state).get(parent_id).unwrap(); let index = parent .children .iter() @@ -171,35 +163,16 @@ impl Tree { .unwrap(); parent.children.insert(index + 1, new_id); let height = parent.height + 1; + drop(node_state); self.set_height(new_id, height); } - pub fn size(&self) -> usize { - self.nodes.len() - } - - pub fn dynamically_borrowed(&mut self) -> DynamicallyBorrowedTree<'_> { - DynamicallyBorrowedTree { - nodes: self.nodes.dynamically_borrowed(), - } - } - - pub fn get(&self, id: NodeId) -> Option<&T> { - self.nodes.try_read_slab().and_then(|slab| slab.get(id)) - } - - pub fn get_mut(&mut self, id: NodeId) -> Option<&mut T> { - self.nodes - .try_write_slab() - .and_then(|slab| slab.get_mut(id)) - } - - pub fn insert(&mut self, id: NodeId, value: T) { - self.nodes.get_or_insert_slab_mut().insert(id, value); + pub fn insert(&mut self, id: NodeId, value: T) { + self.nodes.add_component(id, value); } pub fn contains(&self, id: NodeId) -> bool { - self.nodes.contains(id) + self.nodes.borrow::().unwrap().is_alive(id) } pub fn root(&self) -> NodeId { @@ -207,128 +180,18 @@ impl Tree { } pub fn parent_id(&self, id: NodeId) -> Option { - self.try_get_node_data(id).and_then(|node| node.parent) + let node_data = self.node_data(); + node_data.get(id).unwrap().parent } - pub fn children_ids(&self, id: NodeId) -> Option<&[NodeId]> { - self.try_get_node_data(id).map(|node| &*node.children) + pub fn children_ids(&self, id: NodeId) -> Option> { + let node_data = self.node_data(); + node_data.get(id).map(|node| node.children.clone()).ok() } pub fn height(&self, id: NodeId) -> Option { - self.try_get_node_data(id).map(|node| node.height) - } -} - -pub(crate) struct DynamicallyBorrowedTree<'a> { - nodes: DynamiclyBorrowedAnySlabView<'a>, -} - -impl<'a> DynamicallyBorrowedTree<'a> { - pub fn view( - &self, - immutable: impl IntoIterator, - mutable: impl IntoIterator, - ) -> TreeStateView<'_, 'a> { - let nodes_data = self.node_slab(); - let mut nodes = FxHashMap::default(); - for id in immutable { - nodes.insert(id, MaybeRead::Read(self.nodes.get_slab(id).unwrap())); - } - for id in mutable { - nodes.insert(id, MaybeRead::Write(self.nodes.get_slab_mut(id).unwrap())); - } - - TreeStateView { nodes_data, nodes } - } - - fn node_slab(&self) -> MappedRwLockReadGuard> { - RwLockReadGuard::map(self.nodes.get_slab(TypeId::of::()).unwrap(), |slab| { - slab.as_any().downcast_ref().unwrap() - }) - } -} - -enum MaybeRead<'a, 'b> { - Read(RwLockReadGuard<'a, &'b mut dyn AnySlabStorageImpl>), - Write(RwLockWriteGuard<'a, &'b mut dyn AnySlabStorageImpl>), -} - -#[derive(Clone, Copy)] -pub struct TreeStateViewEntry<'a, 'b> { - view: &'a TreeStateView<'a, 'b>, - id: NodeId, -} - -impl<'a, 'b> AnyMapLike<'a> for TreeStateViewEntry<'a, 'b> { - fn get(self) -> Option<&'a T> { - let slab = self.view.get_slab(); - slab.and_then(|slab| slab.get(self.id)) - } -} - -pub struct TreeStateView<'a, 'b> { - nodes_data: MappedRwLockReadGuard<'a, SlabStorage>, - nodes: FxHashMap>, -} - -impl<'a, 'b> TreeStateView<'a, 'b> { - pub fn get_slab(&self) -> Option<&SlabStorage> { - self.nodes - .get(&TypeId::of::()) - .and_then(|gaurd| match gaurd { - MaybeRead::Read(gaurd) => gaurd.as_any().downcast_ref::>(), - MaybeRead::Write(gaurd) => gaurd.as_any().downcast_ref::>(), - }) - } - - pub fn get_slab_mut(&mut self) -> Option<&mut SlabStorage> { - self.nodes - .get_mut(&TypeId::of::()) - .and_then(|gaurd| match gaurd { - MaybeRead::Read(_gaurd) => None, - MaybeRead::Write(gaurd) => gaurd.as_any_mut().downcast_mut::>(), - }) - } - - pub fn children_ids(&self, id: NodeId) -> Option<&[NodeId]> { - self.nodes_data.get(id).map(|node| &*node.children) - } - - pub fn parent_id(&self, id: NodeId) -> Option { - self.nodes_data.get(id).and_then(|node| node.parent) - } - - pub fn height(&self, id: NodeId) -> Option { - self.nodes_data.get(id).map(|n| n.height) - } - - pub fn get(&self, id: NodeId) -> Option> { - T::borrow_elements_from(self.entry(id)) - } - - pub fn get_single(&self, id: NodeId) -> Option<&T> { - self.get_slab().and_then(|slab| slab.get(id)) - } - - pub fn get_mut(&mut self, id: NodeId) -> Option<&mut T> { - self.get_slab_mut().and_then(|slab| slab.get_mut(id)) - } - - pub fn entry(&self, id: NodeId) -> TreeStateViewEntry<'_, 'b> { - TreeStateViewEntry { view: self, id } - } - - pub fn children(&self, id: NodeId) -> Option>> { - let ids = self.children_ids(id); - ids.and_then(|ids| { - ids.iter() - .map(|id| T::borrow_elements_from(self.entry(*id))) - .collect() - }) - } - - pub fn parent(&self, id: NodeId) -> Option> { - T::borrow_elements_from(self.entry(self.parent_id(id)?)) + let node_data = self.node_data(); + node_data.get(id).map(|node| node.height).ok() } } @@ -464,244 +327,17 @@ fn deletion() { assert_eq!(tree.get::(after), None); } -#[derive(Debug)] -pub struct SlabStorage { - data: Vec>, +pub struct NodeBuilder<'a> { + tree: &'a mut Tree, + node: EntityId, } -impl Default for SlabStorage { - fn default() -> Self { - Self { data: Vec::new() } +impl<'a> NodeBuilder<'a> { + pub fn insert(&mut self, component: T) { + self.tree.insert(self.node, component); + } + + pub fn id(&self) -> EntityId { + self.node } } - -trait AnySlabStorageImpl: Any + Send + Sync { - fn remove(&mut self, id: NodeId); - fn as_any(&self) -> &dyn Any; - fn as_any_mut(&mut self) -> &mut dyn Any; -} - -impl SlabStorage { - fn get(&self, id: NodeId) -> Option<&T> { - self.data.get(id.0).and_then(|x| x.as_ref()) - } - - fn get_mut(&mut self, id: NodeId) -> Option<&mut T> { - self.data.get_mut(id.0).and_then(|x| x.as_mut()) - } - - pub fn entry(&mut self, id: NodeId) -> SlabEntry<'_, T> { - let idx = id.0; - if idx >= self.data.len() { - self.data.resize_with(idx + 1, || None); - } - SlabEntry { - value: &mut self.data[idx], - } - } - - fn insert(&mut self, id: NodeId, value: T) { - let idx = id.0; - if idx >= self.data.len() { - self.data.resize_with(idx + 1, || None); - } - self.data[idx] = Some(value); - } -} - -pub struct SlabEntry<'a, T> { - pub value: &'a mut Option, -} - -impl<'a, T> SlabEntry<'a, T> { - pub fn or_insert_with T>(self, f: F) -> &'a mut T { - self.value.get_or_insert_with(f) - } - - pub fn or_insert(self, value: T) -> &'a mut T { - self.value.get_or_insert(value) - } - - pub fn or_default(self) -> &'a mut T - where - T: Default, - { - self.value.get_or_insert_with(Default::default) - } -} - -impl AnySlabStorageImpl for SlabStorage { - fn remove(&mut self, id: NodeId) { - if let Some(entry) = self.data.get_mut(id.0) { - let _ = entry.take(); - } - } - - fn as_any(&self) -> &dyn Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } -} - -pub(crate) struct DynamiclyBorrowedAnySlabView<'a> { - data: hashbrown::HashMap< - TypeId, - RwLock<&'a mut dyn AnySlabStorageImpl>, - BuildHasherDefault, - >, -} - -impl<'a> DynamiclyBorrowedAnySlabView<'a> { - fn get_slab<'b>( - &'b self, - type_id: TypeId, - ) -> Option> { - self.data.get(&type_id).map(|x| x.read()) - } - - fn get_slab_mut<'b>( - &'b self, - type_id: TypeId, - ) -> Option> { - self.data.get(&type_id).map(|x| x.write()) - } -} - -pub(crate) struct AnySlab { - data: hashbrown::HashMap, BuildHasherDefault>, - filled: Vec, - free: VecDeque, - len: usize, -} - -impl Debug for AnySlab { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("AnySlab") - .field("data", &self.data.keys().collect::>()) - .field("free", &self.free) - .field("len", &self.len) - .finish() - } -} - -impl Default for AnySlab { - fn default() -> Self { - Self::new() - } -} - -impl AnySlab { - fn new() -> Self { - Self { - data: Default::default(), - filled: Default::default(), - free: VecDeque::new(), - len: 0, - } - } - - fn try_read_slab(&self) -> Option<&SlabStorage> { - self.data - .get(&TypeId::of::()) - .map(|x| x.as_any().downcast_ref().unwrap()) - } - - fn read_slab(&self) -> &SlabStorage { - self.try_read_slab().unwrap() - } - - fn try_write_slab(&mut self) -> Option<&mut SlabStorage> { - self.data - .get_mut(&TypeId::of::()) - .map(|x| x.as_any_mut().downcast_mut().unwrap()) - } - - fn write_slab(&mut self) -> &mut SlabStorage { - self.try_write_slab().unwrap() - } - - fn get_or_insert_slab_mut(&mut self) -> &mut SlabStorage { - self.data - .entry(TypeId::of::()) - .or_insert_with(|| Box::>::default()) - .as_any_mut() - .downcast_mut() - .unwrap() - } - - fn insert(&mut self) -> EntryBuilder<'_> { - let id = if let Some(id) = self.free.pop_front() { - self.filled[id.0] = true; - id - } else { - let id = self.len; - self.filled.push(true); - self.len += 1; - NodeId(id) - }; - EntryBuilder { id, inner: self } - } - - fn remove(&mut self, id: NodeId) { - for slab in self.data.values_mut() { - slab.remove(id); - } - self.filled[id.0] = true; - self.free.push_back(id); - } - - fn len(&self) -> usize { - self.len - self.free.len() - } - - fn contains(&self, id: NodeId) -> bool { - self.filled.get(id.0).copied().unwrap_or_default() - } - - fn dynamically_borrowed(&mut self) -> DynamiclyBorrowedAnySlabView<'_> { - DynamiclyBorrowedAnySlabView { - data: self - .data - .iter_mut() - .map(|(k, v)| (*k, RwLock::new(&mut **v))) - .collect(), - } - } -} - -pub(crate) struct EntryBuilder<'a> { - id: NodeId, - inner: &'a mut AnySlab, -} - -impl EntryBuilder<'_> { - pub fn insert(&mut self, value: T) { - self.inner - .get_or_insert_slab_mut::() - .insert(self.id, value); - } - - pub fn id(&self) -> NodeId { - self.id - } -} - -#[test] -fn remove() { - let mut slab = AnySlab::new(); - let mut node1 = slab.insert(); - node1.insert(0i32); - let node1_id = node1.id(); - let mut node2 = slab.insert(); - node2.insert(0i32); - - assert_eq!(slab.len(), 2); - - slab.remove(node1_id); - - assert!(slab.read_slab::().get(node1_id).is_none()); - assert_eq!(slab.len(), 1); -} diff --git a/packages/native-core/src/utils/persistant_iterator.rs b/packages/native-core/src/utils/persistant_iterator.rs index 0e87c03f2..2469e3ce4 100644 --- a/packages/native-core/src/utils/persistant_iterator.rs +++ b/packages/native-core/src/utils/persistant_iterator.rs @@ -73,7 +73,7 @@ pub struct PersistantElementIter { impl PersistantElementIter { pub fn create(rdom: &mut RealDom) -> Self { - let inner = Arc::new(Mutex::new(smallvec::smallvec![NodeId(0)])); + let inner = Arc::new(Mutex::new(smallvec::smallvec![rdom.root_id()])); rdom.add_node_watcher(PersistantElementIterUpdater { stack: inner.clone(), @@ -87,7 +87,7 @@ impl PersistantElementIter { pub fn next(&mut self, rdom: &RealDom) -> ElementProduced { let mut stack = self.stack.lock().unwrap(); if stack.is_empty() { - let id = NodeId(0); + let id = rdom.root_id(); let new = id; stack.push(new); ElementProduced::Looped(id) @@ -113,7 +113,7 @@ impl PersistantElementIter { // otherwise, continue the loop and go to the parent } else { // if there is no parent, loop back to the root - let new = NodeId(0); + let new = rdom.root_id(); stack.clear(); stack.push(new); return ElementProduced::Looped(new); @@ -139,7 +139,7 @@ impl PersistantElementIter { } let mut stack = self.stack.lock().unwrap(); if stack.is_empty() { - let id = NodeId(0); + let id = rdom.root_id(); let last = push_back(&mut stack, rdom.get(id).unwrap()); ElementProduced::Looped(last) } else if let Some(current) = stack.pop().and_then(|last| rdom.get(last)) { @@ -154,13 +154,13 @@ impl PersistantElementIter { ElementProduced::Progressed(*parent) } else { // if there is no parent, loop back to the root - let id = NodeId(0); + let id = rdom.root_id(); let last = push_back(&mut stack, rdom.get(id).unwrap()); ElementProduced::Looped(last) } } else { // if there is no parent, loop back to the root - let id = NodeId(0); + let id = rdom.root_id(); let last = push_back(&mut stack, rdom.get(id).unwrap()); ElementProduced::Looped(last) } From a8566b4e71bd3d77b9054ce22eb2a4bea018ab28 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 7 Mar 2023 16:00:43 -0600 Subject: [PATCH 057/103] finish shipyard migration in native-core --- Cargo.toml | 1 + packages/native-core-macro/src/lib.rs | 128 ++++++++++++-- packages/native-core/Cargo.toml | 1 + packages/native-core/src/dioxus.rs | 3 +- packages/native-core/src/passes.rs | 236 +++++++++++--------------- packages/native-core/src/real_dom.rs | 194 ++++++++++++++------- packages/native-core/src/tree.rs | 158 ++++++++--------- 7 files changed, 414 insertions(+), 307 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index af0a1b916..e32940e18 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ members = [ "packages/rsx", "packages/tui", "packages/native-core", + "packages/native-core-macro", "packages/rsx-rosetta", "packages/signals", "packages/hot-reload", diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index 21fbb7e89..098b4c8c6 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -1,22 +1,126 @@ extern crate proc_macro; -mod sorted_slice; +use std::collections::HashSet; use proc_macro::TokenStream; -use quote::{quote, ToTokens, __private::Span}; -use sorted_slice::StrSlice; -use syn::parenthesized; -use syn::parse::ParseBuffer; -use syn::punctuated::Punctuated; -use syn::{ - self, - parse::{Parse, ParseStream, Result}, - parse_macro_input, parse_quote, Error, Field, Ident, Token, Type, -}; +use quote::quote; +use syn::Type; /// A helper attribute for deriving `State` for a struct. #[proc_macro_attribute] -pub fn my_attribute(_: TokenStream, input: TokenStream) -> TokenStream { +pub fn partial_derive_pass(_: TokenStream, input: TokenStream) -> TokenStream { let impl_block: syn::ItemImpl = syn::parse(input).unwrap(); + + let parent_dependencies = impl_block + .items + .iter() + .find_map(|item| { + if let syn::ImplItem::Type(syn::ImplItemType { + attrs, ident, ty, .. + }) = item + { + (ident == "ParentDependencies").then_some(ty) + } else { + None + } + }) + .unwrap(); + let child_dependencies = impl_block + .items + .iter() + .find_map(|item| { + if let syn::ImplItem::Type(syn::ImplItemType { + attrs, ident, ty, .. + }) = item + { + (ident == "ChildDependencies").then_some(ty) + } else { + None + } + }) + .unwrap(); + let node_dependencies = impl_block + .items + .iter() + .find_map(|item| { + if let syn::ImplItem::Type(syn::ImplItemType { + attrs, ident, ty, .. + }) = item + { + (ident == "NodeDependencies").then_some(ty) + } else { + None + } + }) + .unwrap(); + + let this_type = &impl_block.self_ty; + + let mut combined_dependencies = HashSet::new(); + + match parent_dependencies { + Type::Tuple(tuple) => { + for type_ in &tuple.elems { + combined_dependencies.insert(type_.clone()); + } + } + _ => panic!("ParentDependencies must be a tuple"), + } + match child_dependencies { + Type::Tuple(tuple) => { + for type_ in &tuple.elems { + combined_dependencies.insert(type_.clone()); + } + } + _ => panic!("ChildDependencies must be a tuple"), + } + match node_dependencies { + Type::Tuple(tuple) => { + for type_ in &tuple.elems { + combined_dependencies.insert(type_.clone()); + } + } + _ => panic!("NodeDependencies must be a tuple"), + } + combined_dependencies.insert(*this_type.clone()); + + let combined_dependencies = combined_dependencies.into_iter(); + let combined_dependencies = quote!((#(#combined_dependencies),*)); + quote!(#impl_block).into() } + +// pub trait State: Any + Send + Sync { +// /// This is a tuple of (T: Pass, ..) of states read from the parent required to run this pass +// type ParentDependencies: Dependancy; +// /// This is a tuple of (T: Pass, ..) of states read from the children required to run this pass +// type ChildDependencies: Dependancy; +// /// This is a tuple of (T: Pass, ..) of states read from the node required to run this pass +// type NodeDependencies: Dependancy; +// /// A tuple of all the dependencies combined +// type CombinedDependencies: Dependancy; +// /// This is a mask of what aspects of the node are required to run this pass +// const NODE_MASK: NodeMaskBuilder<'static>; +// +// /// Update this state in a node, returns if the state was updated +// fn update<'a>( +// &mut self, +// node_view: NodeView, +// node: ::ElementBorrowed<'a>, +// parent: Option<::ElementBorrowed<'a>>, +// children: Option::ElementBorrowed<'a>>>, +// context: &SendAnyMap, +// ) -> bool; +// +// /// Create a new instance of this state +// fn create<'a>( +// node_view: NodeView, +// node: ::ElementBorrowed<'a>, +// parent: Option<::ElementBorrowed<'a>>, +// children: Option::ElementBorrowed<'a>>>, +// context: &SendAnyMap, +// ) -> Self; +// +// /// Create a workload system for this state +// fn workload_system(dependants: FxHashSet) -> WorkloadSystem; +// } diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index d5e724e86..5b5868de9 100644 --- a/packages/native-core/Cargo.toml +++ b/packages/native-core/Cargo.toml @@ -29,6 +29,7 @@ lightningcss = "1.0.0-alpha.39" rayon = "1.6.1" shipyard = "0.6.2" +shipyard_hierarchy = "0.6.0" [dev-dependencies] diff --git a/packages/native-core/src/dioxus.rs b/packages/native-core/src/dioxus.rs index ffc80c1c7..4deada6ab 100644 --- a/packages/native-core/src/dioxus.rs +++ b/packages/native-core/src/dioxus.rs @@ -1,3 +1,4 @@ +use crate::tree::TreeMut; use dioxus_core::{BorrowedAttributeValue, ElementId, Mutations, TemplateNode}; use rustc_hash::{FxHashMap, FxHashSet}; use shipyard::Component; @@ -155,7 +156,7 @@ impl DioxusState { let new_nodes = self.stack.split_off(self.stack.len() - m); let old_node_id = self.element_to_node_id(id); for new in new_nodes { - rdom.tree.insert_before(old_node_id, new); + rdom.tree_mut().insert_before(old_node_id, new); } } SetAttribute { diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index 31b873dde..b93ee074f 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -1,7 +1,10 @@ use anymap::AnyMap; use parking_lot::RwLock; use rustc_hash::{FxHashMap, FxHashSet}; -use shipyard::{Component, IntoBorrow, Unique, View, WorkloadSystem}; +use shipyard::{ + BorrowInfo, Component, IntoBorrow, IntoWorkloadSystem, Unique, UniqueView, View, + WorkloadSystem, World, +}; use std::any::{Any, TypeId}; use std::collections::BTreeMap; use std::marker::PhantomData; @@ -9,7 +12,9 @@ use std::sync::Arc; use crate::node::FromAnyValue; use crate::node_ref::{NodeMaskBuilder, NodeView}; -use crate::SendAnyMap; +use crate::real_dom::{DirtyNodesResult, SendAnyMapWrapper}; +use crate::tree::{TreeMut, TreeRef, TreeRefView}; +use crate::{FxDashSet, SendAnyMap}; use crate::{NodeId, NodeMask}; #[derive(Default)] @@ -108,6 +113,7 @@ pub trait State: Any + Send + Sync { type ChildDependencies: Dependancy; /// This is a tuple of (T: Pass, ..) of states read from the node required to run this pass type NodeDependencies: Dependancy; + /// A tuple of all the dependencies combined type CombinedDependencies: Dependancy; /// This is a mask of what aspects of the node are required to run this pass const NODE_MASK: NodeMaskBuilder<'static>; @@ -130,39 +136,93 @@ pub trait State: Any + Send + Sync { children: Option::ElementBorrowed<'a>>>, context: &SendAnyMap, ) -> Self; + + /// Create a workload system for this state + fn workload_system(dependants: FxHashSet) -> WorkloadSystem; +} + +pub struct RunPassView<'a> { + type_id: TypeId, + dependants: FxHashSet, + pass_direction: PassDirection, + tree: TreeRefView<'a>, + nodes_updated: UniqueView<'a, DirtyNodesResult>, + dirty: UniqueView<'a, DirtyNodeStates>, + ctx: UniqueView<'a, SendAnyMapWrapper>, +} + +impl<'a> RunPassView<'a> { + pub fn borrow( + type_id: TypeId, + dependants: FxHashSet, + pass_direction: PassDirection, + world: &'a World, + ) -> Self { + Self { + type_id, + dependants, + pass_direction, + tree: world.borrow().unwrap(), + nodes_updated: world.borrow().unwrap(), + dirty: world.borrow().unwrap(), + ctx: world.borrow().unwrap(), + } + } +} + +pub fn run_pass(view: RunPassView, mut update_node: impl FnMut(NodeId, &SendAnyMap) -> bool) { + let RunPassView { + type_id, + dependants, + pass_direction, + tree, + nodes_updated, + dirty, + ctx, + } = view; + let ctx = ctx.as_ref(); + match pass_direction { + PassDirection::ParentToChild => { + while let Some((height, id)) = dirty.pop_front(type_id) { + let id = tree.id_at(id).unwrap(); + if (update_node)(id, ctx) { + nodes_updated.insert(id); + for id in tree.children_ids(id).unwrap() { + for dependant in &dependants { + dirty.insert(*dependant, id, height + 1); + } + } + } + } + } + PassDirection::ChildToParent => { + while let Some((height, id)) = dirty.pop_back(type_id) { + let id = tree.id_at(id).unwrap(); + if (update_node)(id, ctx) { + nodes_updated.insert(id); + if let Some(id) = tree.parent_id(id) { + for dependant in &dependants { + dirty.insert(*dependant, id, height - 1); + } + } + } + } + } + PassDirection::AnyOrder => { + while let Some((height, id)) = dirty.pop_back(type_id) { + let id = tree.id_at(id).unwrap(); + if (update_node)(id, ctx) { + nodes_updated.insert(id); + for dependant in &dependants { + dirty.insert(*dependant, id, height); + } + } + } + } + } } pub trait AnyState: State { - fn workload_system() -> WorkloadSystem; - // Box::new( - // move |node_id: NodeId, tree: &mut TreeStateView, context: &SendAnyMap| { - // debug_assert!(!Self::NodeDependencies::type_ids() - // .iter() - // .any(|id| *id == TypeId::of::())); - // // get all of the states from the tree view - // // Safety: No node has itself as a parent or child. - // let myself: SlabEntry<'static, Self> = unsafe { - // std::mem::transmute(tree.get_slab_mut::().unwrap().entry(node_id)) - // }; - // let node_data = tree.get_single::>(node_id).unwrap(); - // let node = tree.get::(node_id).unwrap(); - // let children = tree.children::(node_id); - // let parent = tree.parent::(node_id); - - // let view = NodeView::new(node_id, node_data, &node_mask); - // if myself.value.is_none() { - // *myself.value = Some(Self::create(view, node, parent, children, context)); - // true - // } else { - // myself - // .value - // .as_mut() - // .unwrap() - // .update(view, node, parent, children, context) - // } - // }, - // ) as PassCallback - fn to_type_erased() -> TypeErasedPass where Self: Sized, @@ -174,9 +234,9 @@ pub trait AnyState: State { parent_dependant: !Self::parent_type_ids().is_empty(), child_dependant: !Self::child_type_ids().is_empty(), dependants: FxHashSet::default(), - mask: node_mask.clone(), + mask: node_mask, pass_direction: Self::pass_direction(), - workload: Some(Self::workload_system()), + workload: Self::workload_system, phantom: PhantomData, } } @@ -224,58 +284,11 @@ pub struct TypeErasedPass { pub(crate) combined_dependancy_type_ids: FxHashSet, pub(crate) dependants: FxHashSet, pub(crate) mask: NodeMask, - pub(crate) workload: Option, + pub(crate) workload: fn(FxHashSet) -> WorkloadSystem, pub(crate) pass_direction: PassDirection, phantom: PhantomData, } -// impl TypeErasedPass { -// fn resolve( -// &self, -// mut tree: TreeStateView, -// dirty: &DirtyNodeStates, -// nodes_updated: &FxDashSet, -// ctx: &SendAnyMap, -// ) { -// match self.pass_direction { -// PassDirection::ParentToChild => { -// while let Some((height, id)) = dirty.pop_front(self.this_type_id) { -// if (self.pass)(id, &mut tree, ctx) { -// nodes_updated.insert(id); -// for id in tree.children_ids(id).unwrap() { -// for dependant in &self.dependants { -// dirty.insert(*dependant, *id, height + 1); -// } -// } -// } -// } -// } -// PassDirection::ChildToParent => { -// while let Some((height, id)) = dirty.pop_back(self.this_type_id) { -// if (self.pass)(id, &mut tree, ctx) { -// nodes_updated.insert(id); -// if let Some(id) = tree.parent_id(id) { -// for dependant in &self.dependants { -// dirty.insert(*dependant, id, height - 1); -// } -// } -// } -// } -// } -// PassDirection::AnyOrder => { -// while let Some((height, id)) = dirty.pop_back(self.this_type_id) { -// if (self.pass)(id, &mut tree, ctx) { -// nodes_updated.insert(id); -// for dependant in &self.dependants { -// dirty.insert(*dependant, id, height); -// } -// } -// } -// } -// } -// } -// } - #[derive(Debug)] pub enum PassDirection { ParentToChild, @@ -300,7 +313,7 @@ impl<'a> AnyMapLike<'a> for &'a SendAnyMap { } pub trait Dependancy { - type ElementBorrowed<'a>: IntoBorrow; + type ElementBorrowed<'a>: IntoBorrow + BorrowInfo; fn type_ids() -> Box<[TypeId]> { Box::new([]) @@ -330,60 +343,3 @@ impl_dependancy!(A, B, C, D, E, F, G); impl_dependancy!(A, B, C, D, E, F, G, H); impl_dependancy!(A, B, C, D, E, F, G, H, I); impl_dependancy!(A, B, C, D, E, F, G, H, I, J); - -// pub fn resolve_passes( -// dom: &mut RealDom, -// dirty_nodes: DirtyNodeStates, -// ctx: SendAnyMap, -// parallel: bool, -// ) -> FxDashSet { -// let passes = &dom.dirty_nodes.passes; -// let mut resolved_passes: FxHashSet = FxHashSet::default(); -// let mut resolving = Vec::new(); -// let nodes_updated = Arc::new(FxDashSet::default()); -// let ctx = Arc::new(ctx); -// let mut pass_indexes_remaining: Vec<_> = (0..passes.len()).collect::>(); -// while !pass_indexes_remaining.is_empty() { -// let mut currently_in_use = FxHashSet::::default(); -// let dynamically_borrowed_tree = dom.tree.dynamically_borrowed(); -// rayon::in_place_scope(|s| { -// let mut i = 0; -// while i < pass_indexes_remaining.len() { -// let passes_idx = pass_indexes_remaining[i]; -// let pass = &passes[passes_idx]; -// let pass_id = pass.this_type_id; -// // check if the pass is ready to be run -// if pass.combined_dependancy_type_ids.iter().all(|d| { -// (resolved_passes.contains(d) || *d == pass_id) && !currently_in_use.contains(d) -// }) { -// pass_indexes_remaining.remove(i); -// resolving.push(pass_id); -// currently_in_use.insert(pass.this_type_id); -// let tree_view = dynamically_borrowed_tree.view( -// pass.combined_dependancy_type_ids -// .iter() -// .filter(|id| **id != pass.this_type_id) -// .copied() -// .chain(std::iter::once(TypeId::of::>())), -// [pass.this_type_id], -// ); -// let dirty_nodes = dirty_nodes.clone(); -// let nodes_updated = nodes_updated.clone(); -// let ctx = ctx.clone(); -// if parallel { -// s.spawn(move |_| { -// pass.resolve(tree_view, &dirty_nodes, &nodes_updated, &ctx); -// }); -// } else { -// pass.resolve(tree_view, &dirty_nodes, &nodes_updated, &ctx); -// } -// } else { -// i += 1; -// } -// } -// }); -// resolved_passes.extend(resolving.iter().copied()); -// resolving.clear() -// } -// std::sync::Arc::try_unwrap(nodes_updated).unwrap() -// } diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 5908f8e4b..c87564dab 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -1,10 +1,8 @@ use rustc_hash::{FxHashMap, FxHashSet}; use shipyard::error::GetStorage; use shipyard::track::Untracked; -use shipyard::{ - Component, Get, IntoBorrow, ScheduledWorkload, System, Unique, View, ViewMut, Workload, - WorkloadSystem, -}; +use shipyard::{Component, Get, IntoBorrow, ScheduledWorkload, Unique, View, ViewMut, Workload}; +use shipyard::{SystemModificator, World}; use std::any::TypeId; use std::collections::VecDeque; use std::ops::{Deref, DerefMut}; @@ -17,17 +15,33 @@ use crate::node_ref::{NodeMask, NodeMaskBuilder}; use crate::node_watcher::NodeWatcher; use crate::passes::{DirtyNodeStates, TypeErasedPass}; use crate::prelude::AttributeMaskBuilder; -use crate::tree::Tree; +use crate::tree::{TreeMut, TreeMutView, TreeRef, TreeRefView}; use crate::NodeId; use crate::{FxDashSet, SendAnyMap}; #[derive(Unique)] -struct SendAnyMapWrapper(SendAnyMap); +pub struct SendAnyMapWrapper(SendAnyMap); + +impl Deref for SendAnyMapWrapper { + type Target = SendAnyMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} #[derive(Unique, Default)] -struct DirtyNodes(FxDashSet); +pub struct DirtyNodesResult(FxDashSet); -pub(crate) struct NodesDirty { +impl Deref for DirtyNodesResult { + type Target = FxDashSet; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +pub struct NodesDirty { passes_updated: FxHashMap>, nodes_updated: FxHashMap, pub(crate) passes: Box<[TypeErasedPass]>, @@ -76,31 +90,27 @@ type NodeWatchers = Arc + Send + Sync>>>>; /// # Custom values /// To allow custom values to be passed into attributes implement FromAnyValue on a type that can represent your custom value and specify the V generic to be that type. If you have many different custom values, it can be useful to use a enum type to represent the varients. pub struct RealDom { - pub(crate) tree: Tree, + pub(crate) world: World, nodes_listening: FxHashMap>, pub(crate) dirty_nodes: NodesDirty, node_watchers: NodeWatchers, workload: ScheduledWorkload, + root_id: NodeId, phantom: std::marker::PhantomData, } impl RealDom { pub fn new(mut passes: Box<[TypeErasedPass]>) -> RealDom { - let mut tree = Tree::new(); - let mut workload = Workload::new("Main Workload"); - for pass in &mut *passes { - let system = pass.workload.take().unwrap(); - workload = workload.with_system(system); - } + let workload = construct_workload(&mut passes); let (workload, _) = workload.build().unwrap(); - let root_id = tree.root(); + let mut world = World::new(); let root_node: NodeType = NodeType::Element(ElementNode { tag: "Root".to_string(), namespace: Some("Root".to_string()), attributes: FxHashMap::default(), listeners: FxHashSet::default(), }); - tree.insert(root_id, root_node); + let root_id = world.add_entity(root_node); // resolve dependants for each pass for i in 1..passes.len() { @@ -122,7 +132,7 @@ impl RealDom { nodes_updated.insert(root_id, NodeMaskBuilder::ALL.build()); RealDom { - tree, + world, nodes_listening: FxHashMap::default(), dirty_nodes: NodesDirty { passes_updated, @@ -131,19 +141,27 @@ impl RealDom { }, node_watchers: Default::default(), workload, + root_id, phantom: std::marker::PhantomData, } } + pub fn tree_mut(&self) -> TreeMutView { + self.world.borrow().unwrap() + } + + pub fn tree_ref(&self) -> TreeRefView { + self.world.borrow().unwrap() + } + pub fn create_node(&mut self, node: NodeType) -> NodeMut<'_, V> { - let mut node_entry = self.tree.create_node(); - let id = node_entry.id(); + let id = self.world.add_entity(node); + self.tree_mut().create_node(id); self.dirty_nodes .passes_updated .entry(id) .or_default() .extend(self.dirty_nodes.passes.iter().map(|x| x.this_type_id)); - node_entry.insert(node); let watchers = self.node_watchers.clone(); for watcher in &*watchers.read().unwrap() { watcher.on_node_added(NodeMut::new(id, self)); @@ -157,7 +175,7 @@ impl RealDom { if let Some(nodes) = self.nodes_listening.get(event) { let mut listening: Vec<_> = nodes .iter() - .map(|id| (*id, self.tree.height(*id).unwrap())) + .map(|id| (*id, self.tree_ref().height(*id).unwrap())) .collect(); listening.sort_by(|(_, h1), (_, h2)| h1.cmp(h2).reverse()); listening @@ -171,26 +189,29 @@ impl RealDom { /// Returns the id of the root node. pub fn root_id(&self) -> NodeId { - self.tree.root() + self.root_id } pub fn get(&self, id: NodeId) -> Option> { - self.tree.contains(id).then_some(NodeRef { id, dom: self }) + self.tree_ref() + .contains(id) + .then_some(NodeRef { id, dom: self }) } pub fn get_mut(&mut self, id: NodeId) -> Option> { - self.tree.contains(id).then(|| NodeMut::new(id, self)) + let contains = { self.tree_ref().contains(id) }; + contains.then(|| NodeMut::new(id, self)) } fn borrow_raw<'a, B: IntoBorrow>(&'a self) -> Result where B::Borrow: shipyard::Borrow<'a, View = B>, { - self.tree.nodes.borrow() + self.world.borrow() } fn borrow_node_type_mut(&self) -> Result>, GetStorage> { - self.tree.nodes.borrow() + self.world.borrow() } /// 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. @@ -202,34 +223,36 @@ impl RealDom { let nodes_updated = std::mem::take(&mut self.dirty_nodes.nodes_updated); let dirty_nodes = DirtyNodeStates::with_passes(self.dirty_nodes.passes.iter().map(|p| p.this_type_id)); + let tree = self.tree_ref(); for (node_id, passes) in passes { // remove any nodes that were created and then removed in the same mutations from the dirty nodes list - if let Some(height) = self.tree.height(node_id) { + if let Some(height) = tree.height(node_id) { for pass in passes { dirty_nodes.insert(pass, node_id, height); } } } - let _ = self.tree.remove_unique::(); - let _ = self.tree.remove_unique::(); - self.tree.add_unique(dirty_nodes); - self.tree.add_unique(SendAnyMapWrapper(ctx)); - self.tree.add_unique(DirtyNodes::default()); + let _ = self.world.remove_unique::(); + let _ = self.world.remove_unique::(); + self.world.add_unique(dirty_nodes); + self.world.add_unique(SendAnyMapWrapper(ctx)); + self.world.add_unique(DirtyNodesResult::default()); - self.workload.run_with_world(&self.tree.nodes).unwrap(); + self.workload.run_with_world(&self.world).unwrap(); - let dirty = self.tree.remove_unique::().unwrap(); + let dirty = self.world.remove_unique::().unwrap(); (dirty.0, nodes_updated) } pub fn traverse_depth_first(&self, mut f: impl FnMut(NodeRef)) { let mut stack = vec![self.root_id()]; + let tree = self.tree_ref(); while let Some(id) = stack.pop() { if let Some(node) = self.get(id) { f(node); - if let Some(children) = self.tree.children_ids(id) { + if let Some(children) = tree.children_ids(id) { stack.extend(children.iter().copied().rev()); } } @@ -239,10 +262,11 @@ impl RealDom { pub fn traverse_breadth_first(&self, mut f: impl FnMut(NodeRef)) { let mut queue = VecDeque::new(); queue.push_back(self.root_id()); + let tree = self.tree_ref(); while let Some(id) = queue.pop_front() { if let Some(node) = self.get(id) { f(node); - if let Some(children) = self.tree.children_ids(id) { + if let Some(children) = tree.children_ids(id) { for id in children { queue.push_back(id); } @@ -254,8 +278,10 @@ impl RealDom { pub fn traverse_depth_first_mut(&mut self, mut f: impl FnMut(NodeMut)) { let mut stack = vec![self.root_id()]; while let Some(id) = stack.pop() { - if let Some(children) = self.tree.children_ids(id) { - let children = children.iter().copied().rev().collect::>(); + let tree = self.tree_ref(); + if let Some(mut children) = tree.children_ids(id) { + drop(tree); + children.reverse(); if let Some(node) = self.get_mut(id) { let node = node; f(node); @@ -269,8 +295,9 @@ impl RealDom { let mut queue = VecDeque::new(); queue.push_back(self.root_id()); while let Some(id) = queue.pop_front() { - if let Some(children) = self.tree.children_ids(id) { - let children = children.to_vec(); + let tree = self.tree_ref(); + if let Some(children) = tree.children_ids(id) { + drop(tree); if let Some(node) = self.get_mut(id) { f(node); for id in children { @@ -349,7 +376,7 @@ pub trait NodeImmutable: Sized { #[inline] fn child_ids(&self) -> Option> { - self.real_dom().tree.children_ids(self.id()) + self.real_dom().tree_ref().children_ids(self.id()) } #[inline] @@ -368,7 +395,7 @@ pub trait NodeImmutable: Sized { #[inline] fn parent_id(&self) -> Option { - self.real_dom().tree.parent_id(self.id()) + self.real_dom().tree_ref().parent_id(self.id()) } #[inline] @@ -382,7 +409,7 @@ pub trait NodeImmutable: Sized { #[inline] fn next(&self) -> Option> { let parent = self.parent_id()?; - let children = self.real_dom().tree.children_ids(parent)?; + let children = self.real_dom().tree_ref().children_ids(parent)?; let index = children.iter().position(|id| *id == self.id())?; if index + 1 < children.len() { Some(NodeRef { @@ -397,7 +424,7 @@ pub trait NodeImmutable: Sized { #[inline] fn prev(&self) -> Option> { let parent = self.parent_id()?; - let children = self.real_dom().tree.children_ids(parent)?; + let children = self.real_dom().tree_ref().children_ids(parent)?; let index = children.iter().position(|id| *id == self.id())?; if index > 0 { Some(NodeRef { @@ -411,7 +438,7 @@ pub trait NodeImmutable: Sized { #[inline] fn height(&self) -> u16 { - self.real_dom().tree.height(self.id()).unwrap() + self.real_dom().tree_ref().height(self.id()).unwrap() } } @@ -501,13 +528,13 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { .entry(self.id) .or_default() .insert(TypeId::of::()); - self.dom.tree.insert(self.id, value); + self.dom.world.add_component(self.id, value); } #[inline] pub fn next_mut(self) -> Option> { let parent = self.parent_id()?; - let children = self.dom.tree.children_ids(parent)?; + let children = self.dom.tree_mut().children_ids(parent)?; let index = children.iter().position(|id| *id == self.id)?; if index + 1 < children.len() { Some(NodeMut::new(children[index + 1], self.dom)) @@ -519,7 +546,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { #[inline] pub fn prev_mut(self) -> Option> { let parent = self.parent_id()?; - let children = self.dom.tree.children_ids(parent)?; + let children = self.dom.tree_ref().children_ids(parent)?; let index = children.iter().position(|id| *id == self.id)?; if index > 0 { Some(NodeMut::new(children[index - 1], self.dom)) @@ -532,29 +559,31 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { pub fn add_child(&mut self, child: NodeId) { self.dom.dirty_nodes.mark_child_changed(self.id); self.dom.dirty_nodes.mark_parent_added_or_removed(child); - self.dom.tree.add_child(self.id, child); + self.dom.tree_mut().add_child(self.id, child); NodeMut::new(child, self.dom).mark_moved(); } #[inline] pub fn insert_after(&mut self, old: NodeId) { let id = self.id(); - if let Some(parent_id) = self.dom.tree.parent_id(old) { + let parent_id = { self.dom.tree_ref().parent_id(old) }; + if let Some(parent_id) = parent_id { self.dom.dirty_nodes.mark_child_changed(parent_id); self.dom.dirty_nodes.mark_parent_added_or_removed(id); } - self.dom.tree.insert_after(old, id); + self.dom.tree_mut().insert_after(old, id); self.mark_moved(); } #[inline] pub fn insert_before(&mut self, old: NodeId) { let id = self.id(); - if let Some(parent_id) = self.dom.tree.parent_id(old) { + let parent_id = { self.dom.tree_ref().parent_id(old) }; + if let Some(parent_id) = parent_id { self.dom.dirty_nodes.mark_child_changed(parent_id); self.dom.dirty_nodes.mark_parent_added_or_removed(id); } - self.dom.tree.insert_before(old, id); + self.dom.tree_mut().insert_before(old, id); self.mark_moved(); } @@ -563,11 +592,11 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { let id = self.id(); { let RealDom { - tree, + world, nodes_listening, .. } = &mut self.dom; - let mut view: ViewMut> = tree.nodes.borrow().unwrap(); + let mut view: ViewMut> = world.borrow().unwrap(); if let NodeType::Element(ElementNode { listeners, .. }) | NodeType::Text(TextNode { listeners, .. }) = (&mut view).get(id).unwrap() { @@ -578,7 +607,8 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { } } self.mark_removed(); - if let Some(parent_id) = self.real_dom_mut().tree.parent_id(id) { + let parent_id = { self.dom.tree_ref().parent_id(id) }; + if let Some(parent_id) = parent_id { self.real_dom_mut() .dirty_nodes .mark_child_changed(parent_id); @@ -589,7 +619,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { self.dom.get_mut(child).unwrap().remove(); } } - self.dom.tree.remove_single(id); + self.dom.tree_mut().remove_single(id); } #[inline] @@ -604,19 +634,19 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { .mark_parent_added_or_removed(new); } let id = self.id(); - self.dom.tree.replace(id, new); + self.dom.tree_mut().replace(id, new); } #[inline] pub fn add_event_listener(&mut self, event: &str) { let id = self.id(); let RealDom { - tree, + world, dirty_nodes, nodes_listening, .. } = &mut self.dom; - let mut view: ViewMut> = tree.nodes.borrow().unwrap(); + let mut view: ViewMut> = world.borrow().unwrap(); let node_type: &mut NodeType = (&mut view).get(self.id).unwrap(); if let NodeType::Element(ElementNode { listeners, .. }) | NodeType::Text(TextNode { listeners, .. }) = node_type @@ -640,12 +670,12 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { pub fn remove_event_listener(&mut self, event: &str) { let id = self.id(); let RealDom { - tree, + world, dirty_nodes, nodes_listening, .. } = &mut self.dom; - let mut view: ViewMut> = tree.nodes.borrow().unwrap(); + let mut view: ViewMut> = world.borrow().unwrap(); let node_type: &mut NodeType = (&mut view).get(self.id).unwrap(); if let NodeType::Element(ElementNode { listeners, .. }) | NodeType::Text(TextNode { listeners, .. }) = node_type @@ -674,9 +704,9 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { pub fn node_type_mut(&mut self) -> NodeTypeMut<'_, V> { let id = self.id(); let RealDom { - tree, dirty_nodes, .. + world, dirty_nodes, .. } = &mut self.dom; - let view: ViewMut> = tree.nodes.borrow().unwrap(); + let view: ViewMut> = world.borrow().unwrap(); let node_type = ViewEntryMut::new(view, id); match &*node_type { NodeType::Element(_) => NodeTypeMut::Element(ElementNodeMut { @@ -857,3 +887,37 @@ impl ElementNodeMut<'_, V> { &self.element().listeners } } + +fn construct_workload(passes: &mut [TypeErasedPass]) -> Workload { + let mut workload = Workload::new("Main Workload"); + let mut unresloved_workloads = passes + .iter_mut() + .enumerate() + .map(|(i, pass)| { + let workload = Some((pass.workload)(pass.dependants.clone())); + (i, pass, workload) + }) + .collect::>(); + // set all the labels + for (id, _, workload) in &mut unresloved_workloads { + *workload = Some(workload.take().unwrap().tag(id.to_string())); + } + // mark any dependancies + for i in 0..unresloved_workloads.len() { + let (_, pass, _) = &unresloved_workloads[i]; + for ty_id in pass.combined_dependancy_type_ids.clone() { + let &(dependancy_id, _, _) = unresloved_workloads + .iter() + .find(|(_, pass, _)| pass.this_type_id == ty_id) + .unwrap(); + let (_, _, workload) = &mut unresloved_workloads[i]; + *workload = workload + .take() + .map(|workload| workload.tag(dependancy_id.to_string())); + } + } + for (_, _, mut workload_system) in unresloved_workloads { + workload = workload.with_system(workload_system.take().unwrap()); + } + workload +} diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index 0d8bab7c9..f1c542a3c 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -1,63 +1,65 @@ use crate::NodeId; -use shipyard::{Component, EntitiesView, EntityId, Get, Unique, View, ViewMut, World}; +use shipyard::{Component, EntitiesViewMut, Get, View, ViewMut}; use std::fmt::Debug; #[derive(PartialEq, Eq, Clone, Debug, Component)] -pub(crate) struct Node { +pub struct Node { parent: Option, children: Vec, height: u16, } -#[derive(Debug)] -pub(crate) struct Tree { - pub(crate) nodes: World, - root: NodeId, +pub type TreeRefView<'a> = View<'a, Node>; +pub type TreeMutView<'a> = (EntitiesViewMut<'a>, ViewMut<'a, Node>); + +pub trait TreeRef { + fn parent_id(&self, id: NodeId) -> Option; + fn children_ids(&self, id: NodeId) -> Option>; + fn height(&self, id: NodeId) -> Option; + fn contains(&self, id: NodeId) -> bool; } -impl Tree { - pub fn new() -> Self { - let mut nodes = World::default(); - let node = Node { - parent: None, - children: Vec::new(), - height: 0, - }; - let root = nodes.add_entity((node,)); - Self { nodes, root } +pub trait TreeMut: TreeRef { + fn remove(&mut self, id: NodeId); + fn remove_single(&mut self, id: NodeId); + fn set_height(&mut self, node: NodeId, height: u16); + fn create_node(&mut self, id: NodeId); + fn add_child(&mut self, parent: NodeId, new: NodeId); + fn replace(&mut self, old_id: NodeId, new_id: NodeId); + fn insert_before(&mut self, old_id: NodeId, new_id: NodeId); + fn insert_after(&mut self, old_id: NodeId, new_id: NodeId); +} + +impl<'a> TreeRef for TreeRefView<'a> { + fn parent_id(&self, id: NodeId) -> Option { + self.get(id).unwrap().parent } - pub fn add_unique(&mut self, data: T) { - self.nodes.add_unique(data); + fn children_ids(&self, id: NodeId) -> Option> { + Some(self.get(id).unwrap().children.clone()) } - pub fn remove_unique( - &mut self, - ) -> Result { - self.nodes.remove_unique::() + fn height(&self, id: NodeId) -> Option { + Some(self.get(id).unwrap().height) } - fn node_data(&self) -> View { - self.nodes.borrow().unwrap() + fn contains(&self, id: NodeId) -> bool { + self.get(id).is_ok() } +} - fn node_data_mut(&self) -> ViewMut { - self.nodes.borrow().unwrap() - } - - pub fn remove(&mut self, id: NodeId) { - fn recurse(tree: &mut Tree, id: NodeId) { +impl<'a> TreeMut for TreeMutView<'a> { + fn remove(&mut self, id: NodeId) { + fn recurse<'a>(tree: &mut TreeMutView<'a>, id: NodeId) { let children = tree.children_ids(id); if let Some(children) = children { for child in children { recurse(tree, child); } } - - tree.nodes.delete_entity(id); } { - let mut node_data_mut = self.node_data_mut(); + let mut node_data_mut = &mut self.1; if let Some(parent) = node_data_mut.get(id).unwrap().parent { let parent = (&mut node_data_mut).get(parent).unwrap(); parent.children.retain(|&child| child != id); @@ -67,21 +69,19 @@ impl Tree { recurse(self, id); } - pub fn remove_single(&mut self, id: NodeId) { + fn remove_single(&mut self, id: NodeId) { { - let mut node_data_mut = self.node_data_mut(); + let mut node_data_mut = &mut self.1; if let Some(parent) = node_data_mut.get(id).unwrap().parent { let parent = (&mut node_data_mut).get(parent).unwrap(); parent.children.retain(|&child| child != id); } } - - self.nodes.delete_entity(id); } fn set_height(&mut self, node: NodeId, height: u16) { let children = { - let mut node_data_mut = self.node_data_mut(); + let mut node_data_mut = &mut self.1; let mut node = (&mut node_data_mut).get(node).unwrap(); node.height = height; node.children.clone() @@ -91,19 +91,23 @@ impl Tree { } } - pub fn create_node(&mut self) -> NodeBuilder<'_> { - let node = self.nodes.add_entity((Node { - parent: None, - children: Vec::new(), - height: 0, - },)); - NodeBuilder { tree: self, node } + fn create_node(&mut self, id: NodeId) { + let (entities, node_data_mut) = self; + entities.add_component( + id, + node_data_mut, + Node { + parent: None, + children: Vec::new(), + height: 0, + }, + ); } - pub fn add_child(&mut self, parent: NodeId, new: NodeId) { + fn add_child(&mut self, parent: NodeId, new: NodeId) { let height; { - let mut node_state = self.node_data_mut(); + let mut node_state = &mut self.1; (&mut node_state).get(new).unwrap().parent = Some(parent); let parent = (&mut node_state).get(parent).unwrap(); parent.children.push(new); @@ -112,9 +116,9 @@ impl Tree { self.set_height(new, height); } - pub fn replace(&mut self, old_id: NodeId, new_id: NodeId) { + fn replace(&mut self, old_id: NodeId, new_id: NodeId) { { - let mut node_state = self.node_data_mut(); + let mut node_state = &mut self.1; // update the parent's link to the child if let Some(parent_id) = node_state.get(old_id).unwrap().parent { let parent = (&mut node_state).get(parent_id).unwrap(); @@ -125,7 +129,6 @@ impl Tree { } } let height = parent.height + 1; - drop(node_state); self.set_height(new_id, height); } } @@ -133,8 +136,8 @@ impl Tree { self.remove(old_id); } - pub fn insert_before(&mut self, old_id: NodeId, new_id: NodeId) { - let mut node_state = self.node_data_mut(); + fn insert_before(&mut self, old_id: NodeId, new_id: NodeId) { + let mut node_state = &mut self.1; let old_node = node_state.get(old_id).unwrap(); let parent_id = old_node.parent.expect("tried to insert before root"); (&mut node_state).get(new_id).unwrap().parent = Some(parent_id); @@ -146,12 +149,11 @@ impl Tree { .unwrap(); parent.children.insert(index, new_id); let height = parent.height + 1; - drop(node_state); self.set_height(new_id, height); } - pub fn insert_after(&mut self, old_id: NodeId, new_id: NodeId) { - let mut node_state = self.node_data_mut(); + fn insert_after(&mut self, old_id: NodeId, new_id: NodeId) { + let mut node_state = &mut self.1; let old_node = node_state.get(old_id).unwrap(); let parent_id = old_node.parent.expect("tried to insert before root"); (&mut node_state).get(new_id).unwrap().parent = Some(parent_id); @@ -163,36 +165,29 @@ impl Tree { .unwrap(); parent.children.insert(index + 1, new_id); let height = parent.height + 1; - drop(node_state); self.set_height(new_id, height); } +} - pub fn insert(&mut self, id: NodeId, value: T) { - self.nodes.add_component(id, value); - } - - pub fn contains(&self, id: NodeId) -> bool { - self.nodes.borrow::().unwrap().is_alive(id) - } - - pub fn root(&self) -> NodeId { - self.root - } - - pub fn parent_id(&self, id: NodeId) -> Option { - let node_data = self.node_data(); +impl<'a> TreeRef for TreeMutView<'a> { + fn parent_id(&self, id: NodeId) -> Option { + let node_data = &self.1; node_data.get(id).unwrap().parent } - pub fn children_ids(&self, id: NodeId) -> Option> { - let node_data = self.node_data(); + fn children_ids(&self, id: NodeId) -> Option> { + let node_data = &self.1; node_data.get(id).map(|node| node.children.clone()).ok() } - pub fn height(&self, id: NodeId) -> Option { - let node_data = self.node_data(); + fn height(&self, id: NodeId) -> Option { + let node_data = &self.1; node_data.get(id).map(|node| node.height).ok() } + + fn contains(&self, id: NodeId) -> bool { + self.1.get(id).is_ok() + } } #[test] @@ -326,18 +321,3 @@ fn deletion() { assert_eq!(*tree.get::(parent).unwrap(), 0); assert_eq!(tree.get::(after), None); } - -pub struct NodeBuilder<'a> { - tree: &'a mut Tree, - node: EntityId, -} - -impl<'a> NodeBuilder<'a> { - pub fn insert(&mut self, component: T) { - self.tree.insert(self.node, component); - } - - pub fn id(&self) -> EntityId { - self.node - } -} From bab3c8d5840703c59cfdbd16e6ee90f7e8224e52 Mon Sep 17 00:00:00 2001 From: = Date: Wed, 8 Mar 2023 09:06:21 -0600 Subject: [PATCH 058/103] WIP: derive macro --- packages/native-core-macro/src/lib.rs | 107 +++++++++++++++++++++++--- packages/native-core/src/lib.rs | 1 + packages/native-core/src/passes.rs | 72 +++++++++-------- 3 files changed, 138 insertions(+), 42 deletions(-) diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index 098b4c8c6..42c76cb04 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -4,7 +4,7 @@ use std::collections::HashSet; use proc_macro::TokenStream; use quote::quote; -use syn::Type; +use syn::{ItemImpl, Type}; /// A helper attribute for deriving `State` for a struct. #[proc_macro_attribute] @@ -58,36 +58,125 @@ pub fn partial_derive_pass(_: TokenStream, input: TokenStream) -> TokenStream { let mut combined_dependencies = HashSet::new(); - match parent_dependencies { + let parent_dependencies = match parent_dependencies { Type::Tuple(tuple) => { + let mut parent_dependencies = Vec::new(); for type_ in &tuple.elems { combined_dependencies.insert(type_.clone()); + parent_dependencies.push(type_.clone()); } + parent_dependencies } _ => panic!("ParentDependencies must be a tuple"), - } - match child_dependencies { + }; + let child_dependencies = match child_dependencies { Type::Tuple(tuple) => { + let mut child_dependencies = Vec::new(); for type_ in &tuple.elems { combined_dependencies.insert(type_.clone()); + child_dependencies.push(type_.clone()); } + child_dependencies } _ => panic!("ChildDependencies must be a tuple"), - } - match node_dependencies { + }; + let node_dependencies = match node_dependencies { Type::Tuple(tuple) => { + let mut node_dependencies = Vec::new(); for type_ in &tuple.elems { combined_dependencies.insert(type_.clone()); + node_dependencies.push(type_.clone()); } + node_dependencies } _ => panic!("NodeDependencies must be a tuple"), - } + }; combined_dependencies.insert(*this_type.clone()); - let combined_dependencies = combined_dependencies.into_iter(); + let combined_dependencies: Vec<_> = combined_dependencies.into_iter().collect(); + let parent_dependancies_idxes: Vec<_> = combined_dependencies + .iter() + .enumerate() + .filter_map(|(i, ident)| parent_dependencies.contains(ident).then_some(i)) + .collect(); + let child_dependencies_idxes: Vec<_> = combined_dependencies + .iter() + .enumerate() + .filter_map(|(i, ident)| child_dependencies.contains(ident).then_some(i)) + .collect(); + let node_dependencies_idxes: Vec<_> = combined_dependencies + .iter() + .enumerate() + .filter_map(|(i, ident)| node_dependencies.contains(ident).then_some(i)) + .collect(); + let this_type_idx = node_dependencies + .iter() + .enumerate() + .find_map(|(i, ident)| (&**this_type == ident).then_some(i)) + .unwrap(); + + let combined_dependencies = combined_dependencies.iter().map(|ident| { + if ident == &**this_type { + quote! {shipyard::ViewMut<#ident>} + } else { + quote! {shipyard::View<#ident>} + } + }); let combined_dependencies = quote!((#(#combined_dependencies),*)); - quote!(#impl_block).into() + let ItemImpl { + attrs, + defaultness, + unsafety, + impl_token, + generics, + trait_, + self_ty, + items, + .. + } = impl_block; + let for_ = trait_.as_ref().map(|t| t.2); + let trait_ = trait_.map(|t| t.1); + + // if the mutable borrow on the current type overlaps with the child or parent dependancies we need to use apply + + quote!( + #(#attrs)* + #defaultness #unsafety #impl_token #impl_token #generics #trait_ #for_ #self_ty { + #(#items)* + + fn workload_system(type_id: TypeId, dependants: FxHashSet, pass_direction: PassDirection) -> WorkloadSystem { + use shipyard::IntoWorkloadSystem; + + move |data: #combined_dependencies, run_view: RunPassView| { + shipyard::run(type_id, dependants, pass_direction, run_view, |id: NodeId, ctx: &SendAnyMap| { + // get all of the states from the tree view + // Safety: No node has itself as a parent or child. + let myself: SlabEntry<'static, Self> = unsafe { + std::mem::transmute(tree.get_slab_mut::().unwrap().entry(node_id)) + }; + let node_data = tree.get_single::>(node_id).unwrap(); + let node = tree.get::(node_id).unwrap(); + let children = tree.children::(node_id); + let parent = tree.parent::(node_id); + + let view = NodeView::new(node_id, node_data, &node_mask); + if myself.value.is_none() { + *myself.value = Some(Self::create(view, node, parent, children, context)); + true + } else { + myself + .value + .as_mut() + .unwrap() + .update(view, node, parent, children, context) + } + }) + }.into_workload_system() + } + } + ) + .into() } // pub trait State: Any + Send + Sync { diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 74c5e947c..777d61760 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -17,6 +17,7 @@ mod passes; pub mod real_dom; pub mod tree; pub mod utils; +pub use passes::run_pass; pub use shipyard::EntityId as NodeId; pub mod prelude { diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index b93ee074f..99553b124 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -1,10 +1,7 @@ use anymap::AnyMap; use parking_lot::RwLock; use rustc_hash::{FxHashMap, FxHashSet}; -use shipyard::{ - BorrowInfo, Component, IntoBorrow, IntoWorkloadSystem, Unique, UniqueView, View, - WorkloadSystem, World, -}; +use shipyard::{BorrowInfo, Component, IntoBorrow, Unique, UniqueView, View, WorkloadSystem}; use std::any::{Any, TypeId}; use std::collections::BTreeMap; use std::marker::PhantomData; @@ -13,8 +10,8 @@ use std::sync::Arc; use crate::node::FromAnyValue; use crate::node_ref::{NodeMaskBuilder, NodeView}; use crate::real_dom::{DirtyNodesResult, SendAnyMapWrapper}; -use crate::tree::{TreeMut, TreeRef, TreeRefView}; -use crate::{FxDashSet, SendAnyMap}; +use crate::tree::{TreeRef, TreeRefView}; +use crate::SendAnyMap; use crate::{NodeId, NodeMask}; #[derive(Default)] @@ -113,8 +110,6 @@ pub trait State: Any + Send + Sync { type ChildDependencies: Dependancy; /// This is a tuple of (T: Pass, ..) of states read from the node required to run this pass type NodeDependencies: Dependancy; - /// A tuple of all the dependencies combined - type CombinedDependencies: Dependancy; /// This is a mask of what aspects of the node are required to run this pass const NODE_MASK: NodeMaskBuilder<'static>; @@ -138,43 +133,54 @@ pub trait State: Any + Send + Sync { ) -> Self; /// Create a workload system for this state - fn workload_system(dependants: FxHashSet) -> WorkloadSystem; + fn workload_system( + type_id: TypeId, + dependants: FxHashSet, + pass_direction: PassDirection, + ) -> WorkloadSystem; } pub struct RunPassView<'a> { - type_id: TypeId, - dependants: FxHashSet, - pass_direction: PassDirection, tree: TreeRefView<'a>, nodes_updated: UniqueView<'a, DirtyNodesResult>, dirty: UniqueView<'a, DirtyNodeStates>, ctx: UniqueView<'a, SendAnyMapWrapper>, } -impl<'a> RunPassView<'a> { - pub fn borrow( - type_id: TypeId, - dependants: FxHashSet, - pass_direction: PassDirection, - world: &'a World, - ) -> Self { - Self { - type_id, - dependants, - pass_direction, - tree: world.borrow().unwrap(), - nodes_updated: world.borrow().unwrap(), - dirty: world.borrow().unwrap(), - ctx: world.borrow().unwrap(), - } +impl<'v> shipyard::Borrow<'v> for RunPassView<'v> { + type View = RunPassView<'v>; + + fn borrow( + world: &'v shipyard::World, + last_run: Option, + current: u32, + ) -> Result { + Ok(RunPassView { + tree: as shipyard::IntoBorrow>::Borrow::borrow( + world, last_run, current, + )?, + nodes_updated: + as shipyard::IntoBorrow>::Borrow::borrow( + world, last_run, current, + )?, + dirty: as shipyard::IntoBorrow>::Borrow::borrow( + world, last_run, current, + )?, + ctx: as shipyard::IntoBorrow>::Borrow::borrow( + world, last_run, current, + )?, + }) } } -pub fn run_pass(view: RunPassView, mut update_node: impl FnMut(NodeId, &SendAnyMap) -> bool) { +pub fn run_pass( + type_id: TypeId, + dependants: FxHashSet, + pass_direction: PassDirection, + view: RunPassView, + mut update_node: impl FnMut(NodeId, &SendAnyMap) -> bool, +) { let RunPassView { - type_id, - dependants, - pass_direction, tree, nodes_updated, dirty, @@ -284,7 +290,7 @@ pub struct TypeErasedPass { pub(crate) combined_dependancy_type_ids: FxHashSet, pub(crate) dependants: FxHashSet, pub(crate) mask: NodeMask, - pub(crate) workload: fn(FxHashSet) -> WorkloadSystem, + pub(crate) workload: fn(TypeId, FxHashSet, PassDirection) -> WorkloadSystem, pub(crate) pass_direction: PassDirection, phantom: PhantomData, } From ec3e02e4bbbe3912767ee532ac641e7f1428edbd Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 12 Mar 2023 08:26:16 -0500 Subject: [PATCH 059/103] finish macro --- packages/native-core-macro/src/lib.rs | 269 ++++++++++++++++---------- packages/native-core/src/lib.rs | 12 +- 2 files changed, 176 insertions(+), 105 deletions(-) diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index 42c76cb04..4343743d1 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -3,126 +3,119 @@ extern crate proc_macro; use std::collections::HashSet; use proc_macro::TokenStream; -use quote::quote; -use syn::{ItemImpl, Type}; +use quote::{format_ident, quote}; +use syn::{parse_macro_input, ItemImpl, Type, TypeTuple, TypePath}; /// A helper attribute for deriving `State` for a struct. #[proc_macro_attribute] -pub fn partial_derive_pass(_: TokenStream, input: TokenStream) -> TokenStream { - let impl_block: syn::ItemImpl = syn::parse(input).unwrap(); +pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream { + let impl_block: syn::ItemImpl = parse_macro_input!(input as syn::ItemImpl); let parent_dependencies = impl_block .items .iter() .find_map(|item| { - if let syn::ImplItem::Type(syn::ImplItemType { - attrs, ident, ty, .. - }) = item - { + if let syn::ImplItem::Type(syn::ImplItemType { ident, ty, .. }) = item { (ident == "ParentDependencies").then_some(ty) } else { None } }) - .unwrap(); + .expect("ParentDependencies must be defined"); let child_dependencies = impl_block .items .iter() .find_map(|item| { - if let syn::ImplItem::Type(syn::ImplItemType { - attrs, ident, ty, .. - }) = item - { + if let syn::ImplItem::Type(syn::ImplItemType { ident, ty, .. }) = item { (ident == "ChildDependencies").then_some(ty) } else { None } }) - .unwrap(); + .expect("ChildDependencies must be defined"); let node_dependencies = impl_block .items .iter() .find_map(|item| { - if let syn::ImplItem::Type(syn::ImplItemType { - attrs, ident, ty, .. - }) = item - { + if let syn::ImplItem::Type(syn::ImplItemType { ident, ty, .. }) = item { (ident == "NodeDependencies").then_some(ty) } else { None } }) - .unwrap(); + .expect("NodeDependencies must be defined"); let this_type = &impl_block.self_ty; + let this_type = extract_type_path(this_type).unwrap_or_else(|| panic!("Self must be a type path, found {}", quote!(#this_type))); let mut combined_dependencies = HashSet::new(); - let parent_dependencies = match parent_dependencies { - Type::Tuple(tuple) => { + let parent_dependencies = match extract_tuple(parent_dependencies) { + Some(tuple) => { let mut parent_dependencies = Vec::new(); for type_ in &tuple.elems { + let type_ = extract_type_path(type_).unwrap_or_else(|| panic!("ParentDependencies must be a tuple of type paths, found {}", quote!(#type_))); combined_dependencies.insert(type_.clone()); - parent_dependencies.push(type_.clone()); + parent_dependencies.push(type_); } parent_dependencies } - _ => panic!("ParentDependencies must be a tuple"), + _ => panic!("ParentDependencies must be a tuple, found {}", quote!(#parent_dependencies)), }; - let child_dependencies = match child_dependencies { - Type::Tuple(tuple) => { + let child_dependencies = match extract_tuple(child_dependencies) { + Some(tuple) => { let mut child_dependencies = Vec::new(); for type_ in &tuple.elems { + let type_ = extract_type_path(type_).unwrap_or_else(|| panic!("ChildDependencies must be a tuple of type paths, found {}", quote!(#type_))); combined_dependencies.insert(type_.clone()); - child_dependencies.push(type_.clone()); + child_dependencies.push(type_); } child_dependencies } - _ => panic!("ChildDependencies must be a tuple"), + _ => panic!("ChildDependencies must be a tuple, found {}", quote!(#child_dependencies)), }; - let node_dependencies = match node_dependencies { - Type::Tuple(tuple) => { + let node_dependencies = match extract_tuple(node_dependencies) { + Some(tuple) => { let mut node_dependencies = Vec::new(); for type_ in &tuple.elems { + let type_ = extract_type_path(type_).unwrap_or_else(|| panic!("NodeDependencies must be a tuple of type paths, found {}", quote!(#type_))); combined_dependencies.insert(type_.clone()); - node_dependencies.push(type_.clone()); + node_dependencies.push(type_); } node_dependencies } - _ => panic!("NodeDependencies must be a tuple"), + _ => panic!("NodeDependencies must be a tuple, found {}", quote!(#node_dependencies)), }; - combined_dependencies.insert(*this_type.clone()); + combined_dependencies.insert(this_type.clone()); let combined_dependencies: Vec<_> = combined_dependencies.into_iter().collect(); - let parent_dependancies_idxes: Vec<_> = combined_dependencies + let parent_dependancies_idxes: Vec<_> = parent_dependencies .iter() - .enumerate() - .filter_map(|(i, ident)| parent_dependencies.contains(ident).then_some(i)) + .filter_map(|ident| combined_dependencies.iter().position(|i| i == ident)) .collect(); - let child_dependencies_idxes: Vec<_> = combined_dependencies + let child_dependencies_idxes: Vec<_> = child_dependencies .iter() - .enumerate() - .filter_map(|(i, ident)| child_dependencies.contains(ident).then_some(i)) + .filter_map(|ident| combined_dependencies.iter().position(|i| i == ident)) .collect(); - let node_dependencies_idxes: Vec<_> = combined_dependencies + let node_dependencies_idxes: Vec<_> = node_dependencies .iter() - .enumerate() - .filter_map(|(i, ident)| node_dependencies.contains(ident).then_some(i)) + .filter_map(|ident| combined_dependencies.iter().position(|i| i == ident)) .collect(); - let this_type_idx = node_dependencies + let this_type_idx = combined_dependencies .iter() .enumerate() - .find_map(|(i, ident)| (&**this_type == ident).then_some(i)) + .find_map(|(i, ident)| (this_type == *ident).then_some(i)) .unwrap(); + let this_view = format_ident!("__data{}", this_type_idx); - let combined_dependencies = combined_dependencies.iter().map(|ident| { - if ident == &**this_type { + let combined_dependencies_quote = combined_dependencies.iter().map(|ident| { + if ident == &this_type { quote! {shipyard::ViewMut<#ident>} } else { quote! {shipyard::View<#ident>} } }); - let combined_dependencies = quote!((#(#combined_dependencies),*)); + let combined_dependencies_quote = quote!((#(#combined_dependencies_quote,)*)); let ItemImpl { attrs, @@ -138,78 +131,150 @@ pub fn partial_derive_pass(_: TokenStream, input: TokenStream) -> TokenStream { let for_ = trait_.as_ref().map(|t| t.2); let trait_ = trait_.map(|t| t.1); - // if the mutable borrow on the current type overlaps with the child or parent dependancies we need to use apply + let split_views: Vec<_> = (0..combined_dependencies.len()) + .map(|i| { + let ident = format_ident!("__data{}", i); + if i == this_type_idx { + quote!{mut #ident} + } else { + quote!{#ident} + } + }) + .collect(); + + let node_view = node_dependencies_idxes + .iter() + .map(|i| format_ident!("__data{}", i)) + .collect::>(); + let get_node_view = { + if node_dependencies.is_empty() { + quote! { + let raw_node = (); + } + } else { + quote! { + let raw_node: (#(*const #node_dependencies,)*) = (#(&#node_view,)*).get(id).unwrap(); + } + } + }; + let parent_view = parent_dependancies_idxes + .iter() + .map(|i| format_ident!("__data{}", i)) + .collect::>(); + let get_parent_view = { + if parent_dependencies.is_empty() { + quote! { + let raw_parent = tree.parent_id(id).map(|_| ()); + } + } else { + let temps = (0..parent_dependencies.len()) + .map(|i| format_ident!("__temp{}", i)) + .collect::>(); + quote! { + let raw_parent = tree.parent_id(id).and_then(|parent_id| { + let raw_parent: Option<(#(*const #parent_dependencies,)*)> = (#(&#parent_view,)*).get(parent_id).ok().map(|c| { + let (#(#temps,)*) = c; + (#(#temps as *const _,)*) + }); + raw_parent + }); + } + } + }; + let child_view = child_dependencies_idxes + .iter() + .map(|i| format_ident!("__data{}", i)) + .collect::>(); +let get_child_view = { + if child_dependencies.is_empty() { + quote! { + let raw_children: Vec<_> = tree.children_ids(id).into_iter().map(|_| ()).collect(); + } + } else { + let temps = (0..child_dependencies.len()) + .map(|i| format_ident!("__temp{}", i)) + .collect::>(); + quote! { + let raw_children: Vec<_> = tree.children_ids(id).into_iter().filter_map(|id| { + let raw_children: Option<(#(*const #child_dependencies,)*)> = (#(&#child_view,)*).get(id).ok().map(|c| { + let (#(#temps,)*) = c; + (#(#temps as *const _,)*) + }); + raw_children + }).collect(); + } + } + }; + + let trait_generics = trait_ + .as_ref() + .unwrap() + .segments + .last() + .unwrap() + .arguments + .clone(); quote!( #(#attrs)* - #defaultness #unsafety #impl_token #impl_token #generics #trait_ #for_ #self_ty { + #defaultness #unsafety #impl_token #generics #trait_ #for_ #self_ty { #(#items)* - fn workload_system(type_id: TypeId, dependants: FxHashSet, pass_direction: PassDirection) -> WorkloadSystem { - use shipyard::IntoWorkloadSystem; + fn workload_system(type_id: std::any::TypeId, dependants: dioxus_native_core::exports::FxHashSet, pass_direction: dioxus_native_core::PassDirection) -> dioxus_native_core::exports::shipyard::WorkloadSystem { + use dioxus_native_core::exports::shipyard::{IntoWorkloadSystem, Get, AddComponent}; + use dioxus_native_core::tree::TreeRef; - move |data: #combined_dependencies, run_view: RunPassView| { - shipyard::run(type_id, dependants, pass_direction, run_view, |id: NodeId, ctx: &SendAnyMap| { + (move |data: #combined_dependencies_quote, run_view: dioxus_native_core::RunPassView #trait_generics| { + let (#(#split_views,)*) = data; + let (tree, types, _, _, _) = &run_view; + let tree = tree.clone(); + let node_mask = Self::NODE_MASK.build(); + let node_types = types.clone(); + dioxus_native_core::run_pass(type_id, dependants.clone(), pass_direction, run_view, |id, context| { + let node_data: &NodeType<_> = node_types.get(id).unwrap(); // get all of the states from the tree view // Safety: No node has itself as a parent or child. - let myself: SlabEntry<'static, Self> = unsafe { - std::mem::transmute(tree.get_slab_mut::().unwrap().entry(node_id)) - }; - let node_data = tree.get_single::>(node_id).unwrap(); - let node = tree.get::(node_id).unwrap(); - let children = tree.children::(node_id); - let parent = tree.parent::(node_id); + let raw_myself: Option<*mut Self> = (&mut #this_view).get(id).ok().map(|c| c as *mut _); + #get_node_view + #get_parent_view + #get_child_view - let view = NodeView::new(node_id, node_data, &node_mask); - if myself.value.is_none() { - *myself.value = Some(Self::create(view, node, parent, children, context)); - true - } else { + let myself: Option<&mut Self> = unsafe { raw_myself.map(|val| std::mem::transmute(val)) }; + let node = unsafe { std::mem::transmute(raw_node) }; + let parent = unsafe { raw_parent.map(|val| std::mem::transmute(val)) }; + let children = unsafe { std::mem::transmute(raw_children) }; + + let view = NodeView::new(id, node_data, &node_mask); + if let Some(myself) = myself { myself - .value - .as_mut() - .unwrap() .update(view, node, parent, children, context) } + else { + (&mut #this_view).add_component_unchecked( + id, + Self::create(view, node, parent, children, context)); + true + } }) - }.into_workload_system() + }).into_workload_system().unwrap() } } ) .into() } -// pub trait State: Any + Send + Sync { -// /// This is a tuple of (T: Pass, ..) of states read from the parent required to run this pass -// type ParentDependencies: Dependancy; -// /// This is a tuple of (T: Pass, ..) of states read from the children required to run this pass -// type ChildDependencies: Dependancy; -// /// This is a tuple of (T: Pass, ..) of states read from the node required to run this pass -// type NodeDependencies: Dependancy; -// /// A tuple of all the dependencies combined -// type CombinedDependencies: Dependancy; -// /// This is a mask of what aspects of the node are required to run this pass -// const NODE_MASK: NodeMaskBuilder<'static>; -// -// /// Update this state in a node, returns if the state was updated -// fn update<'a>( -// &mut self, -// node_view: NodeView, -// node: ::ElementBorrowed<'a>, -// parent: Option<::ElementBorrowed<'a>>, -// children: Option::ElementBorrowed<'a>>>, -// context: &SendAnyMap, -// ) -> bool; -// -// /// Create a new instance of this state -// fn create<'a>( -// node_view: NodeView, -// node: ::ElementBorrowed<'a>, -// parent: Option<::ElementBorrowed<'a>>, -// children: Option::ElementBorrowed<'a>>>, -// context: &SendAnyMap, -// ) -> Self; -// -// /// Create a workload system for this state -// fn workload_system(dependants: FxHashSet) -> WorkloadSystem; -// } +fn extract_tuple(ty: &Type) -> Option { + match ty { + Type::Tuple(tuple) => Some(tuple.clone()), + Type::Group(group) => extract_tuple(&group.elem), + _ => None, + } +} + +fn extract_type_path(ty: &Type) -> Option { + match ty { + Type::Path(path) => Some(path.clone()), + Type::Group(group) => extract_type_path(&group.elem), + _ => None, + } +} \ No newline at end of file diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 777d61760..34a2332e9 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -3,7 +3,7 @@ use std::hash::BuildHasherDefault; pub use node_ref::NodeMask; pub use passes::AnyMapLike; -pub use passes::{Dependancy, State, TypeErasedPass}; +pub use passes::{run_pass, Dependancy, PassDirection, RunPassView, State, TypeErasedPass}; pub use real_dom::{NodeMut, NodeRef, RealDom}; use rustc_hash::FxHasher; @@ -17,13 +17,19 @@ mod passes; pub mod real_dom; pub mod tree; pub mod utils; -pub use passes::run_pass; pub use shipyard::EntityId as NodeId; +// exported for the macro +pub mod exports { + #[doc(hidden)] + pub use rustc_hash::FxHashSet; + pub use shipyard; +} + pub mod prelude { pub use crate::node::{ElementNode, FromAnyValue, NodeType, OwnedAttributeView, TextNode}; pub use crate::node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}; - pub use crate::passes::{Dependancy, State}; + pub use crate::passes::{AnyState, Dependancy, State}; pub use crate::real_dom::{NodeImmutable, NodeMut, NodeRef, RealDom}; pub use crate::NodeId; pub use crate::SendAnyMap; From 865c5aa9579a800744c0acc1a9b4755461cd6112 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 12 Mar 2023 15:35:48 -0500 Subject: [PATCH 060/103] all native-core tests passing --- packages/native-core-macro/src/lib.rs | 28 +++- packages/native-core/Cargo.toml | 1 + packages/native-core/src/dioxus.rs | 2 +- packages/native-core/src/passes.rs | 109 +++++++------ packages/native-core/src/real_dom.rs | 119 +++++++------- packages/native-core/src/tree.rs | 148 ++++++++---------- .../src/utils/persistant_iterator.rs | 40 ++--- .../tests/called_minimally_on_build.rs | 65 ++++---- packages/native-core/tests/fuzzing.rs | 18 ++- packages/native-core/tests/miri_native.rs | 14 +- packages/native-core/tests/passes.rs | 130 ++++++++------- 11 files changed, 350 insertions(+), 324 deletions(-) diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index 4343743d1..e1e104bf3 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -50,11 +50,16 @@ pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream { let mut combined_dependencies = HashSet::new(); + let self_path: TypePath = syn::parse_quote!(Self); + let parent_dependencies = match extract_tuple(parent_dependencies) { Some(tuple) => { let mut parent_dependencies = Vec::new(); for type_ in &tuple.elems { - let type_ = extract_type_path(type_).unwrap_or_else(|| panic!("ParentDependencies must be a tuple of type paths, found {}", quote!(#type_))); + let mut type_ = extract_type_path(type_).unwrap_or_else(|| panic!("ParentDependencies must be a tuple of type paths, found {}", quote!(#type_))); + if type_ == self_path { + type_ = this_type.clone(); + } combined_dependencies.insert(type_.clone()); parent_dependencies.push(type_); } @@ -66,7 +71,10 @@ pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream { Some(tuple) => { let mut child_dependencies = Vec::new(); for type_ in &tuple.elems { - let type_ = extract_type_path(type_).unwrap_or_else(|| panic!("ChildDependencies must be a tuple of type paths, found {}", quote!(#type_))); + let mut type_ = extract_type_path(type_).unwrap_or_else(|| panic!("ChildDependencies must be a tuple of type paths, found {}", quote!(#type_))); + if type_ == self_path { + type_ = this_type.clone(); + } combined_dependencies.insert(type_.clone()); child_dependencies.push(type_); } @@ -78,7 +86,10 @@ pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream { Some(tuple) => { let mut node_dependencies = Vec::new(); for type_ in &tuple.elems { - let type_ = extract_type_path(type_).unwrap_or_else(|| panic!("NodeDependencies must be a tuple of type paths, found {}", quote!(#type_))); + let mut type_ = extract_type_path(type_).unwrap_or_else(|| panic!("NodeDependencies must be a tuple of type paths, found {}", quote!(#type_))); + if type_ == self_path { + type_ = this_type.clone(); + } combined_dependencies.insert(type_.clone()); node_dependencies.push(type_); } @@ -152,8 +163,14 @@ pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream { let raw_node = (); } } else { + let temps = (0..node_dependencies.len()) + .map(|i| format_ident!("__temp{}", i)) + .collect::>(); quote! { - let raw_node: (#(*const #node_dependencies,)*) = (#(&#node_view,)*).get(id).unwrap(); + let raw_node: (#(*const #node_dependencies,)*) = { + let (#(#temps,)*) = (#(&#node_view,)*).get(id).unwrap_or_else(|err| panic!("Failed to get node view {:?}", err)); + (#(#temps as *const _,)*) + }; } } }; @@ -225,13 +242,14 @@ let get_child_view = { use dioxus_native_core::tree::TreeRef; (move |data: #combined_dependencies_quote, run_view: dioxus_native_core::RunPassView #trait_generics| { + println!("Running system for {:?}", type_id); let (#(#split_views,)*) = data; let (tree, types, _, _, _) = &run_view; let tree = tree.clone(); let node_mask = Self::NODE_MASK.build(); let node_types = types.clone(); dioxus_native_core::run_pass(type_id, dependants.clone(), pass_direction, run_view, |id, context| { - let node_data: &NodeType<_> = node_types.get(id).unwrap(); + let node_data: &NodeType<_> = node_types.get(id).unwrap_or_else(|err| panic!("Failed to get node type {:?}", err)); // get all of the states from the tree view // Safety: No node has itself as a parent or child. let raw_myself: Option<*mut Self> = (&mut #this_view).get(id).ok().map(|c| c as *mut _); diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index 5b5868de9..2b08d2c04 100644 --- a/packages/native-core/Cargo.toml +++ b/packages/native-core/Cargo.toml @@ -37,6 +37,7 @@ rand = "0.8.5" dioxus = { path = "../dioxus", version = "^0.3.0" } tokio = { version = "*", features = ["full"] } dioxus-native-core = { path = ".", features = ["dioxus"] } +dioxus-native-core-macro = { path = "../native-core-macro" } [features] default = [] diff --git a/packages/native-core/src/dioxus.rs b/packages/native-core/src/dioxus.rs index 4deada6ab..938ed16c4 100644 --- a/packages/native-core/src/dioxus.rs +++ b/packages/native-core/src/dioxus.rs @@ -54,7 +54,7 @@ impl DioxusState { pub fn load_child(&self, rdom: &RealDom, path: &[u8]) -> NodeId { let mut current = rdom.get(*self.stack.last().unwrap()).unwrap(); for i in path { - let new_id = current.child_ids().unwrap()[*i as usize]; + let new_id = current.child_ids()[*i as usize]; current = rdom.get(new_id).unwrap(); } current.id() diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index 99553b124..ff55f65f2 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -1,13 +1,14 @@ use anymap::AnyMap; use parking_lot::RwLock; use rustc_hash::{FxHashMap, FxHashSet}; -use shipyard::{BorrowInfo, Component, IntoBorrow, Unique, UniqueView, View, WorkloadSystem}; +use shipyard::{Component, Unique, UniqueView, View, WorkloadSystem}; use std::any::{Any, TypeId}; use std::collections::BTreeMap; use std::marker::PhantomData; +use std::ops::Deref; use std::sync::Arc; -use crate::node::FromAnyValue; +use crate::node::{FromAnyValue, NodeType}; use crate::node_ref::{NodeMaskBuilder, NodeView}; use crate::real_dom::{DirtyNodesResult, SendAnyMapWrapper}; use crate::tree::{TreeRef, TreeRefView}; @@ -104,11 +105,11 @@ impl DirtyNodeStates { } pub trait State: Any + Send + Sync { - /// This is a tuple of (T: Pass, ..) of states read from the parent required to run this pass + /// This is a tuple of (T: State, ..) of states read from the parent required to run this pass type ParentDependencies: Dependancy; - /// This is a tuple of (T: Pass, ..) of states read from the children required to run this pass + /// This is a tuple of (T: State, ..) of states read from the children required to run this pass type ChildDependencies: Dependancy; - /// This is a tuple of (T: Pass, ..) of states read from the node required to run this pass + /// This is a tuple of (T: State, ..) of states read from the node required to run this pass type NodeDependencies: Dependancy; /// This is a mask of what aspects of the node are required to run this pass const NODE_MASK: NodeMaskBuilder<'static>; @@ -119,7 +120,7 @@ pub trait State: Any + Send + Sync { node_view: NodeView, node: ::ElementBorrowed<'a>, parent: Option<::ElementBorrowed<'a>>, - children: Option::ElementBorrowed<'a>>>, + children: Vec<::ElementBorrowed<'a>>, context: &SendAnyMap, ) -> bool; @@ -128,7 +129,7 @@ pub trait State: Any + Send + Sync { node_view: NodeView, node: ::ElementBorrowed<'a>, parent: Option<::ElementBorrowed<'a>>, - children: Option::ElementBorrowed<'a>>>, + children: Vec<::ElementBorrowed<'a>>, context: &SendAnyMap, ) -> Self; @@ -140,52 +141,22 @@ pub trait State: Any + Send + Sync { ) -> WorkloadSystem; } -pub struct RunPassView<'a> { - tree: TreeRefView<'a>, - nodes_updated: UniqueView<'a, DirtyNodesResult>, - dirty: UniqueView<'a, DirtyNodeStates>, - ctx: UniqueView<'a, SendAnyMapWrapper>, -} +pub type RunPassView<'a, V = ()> = ( + TreeRefView<'a>, + View<'a, NodeType>, + UniqueView<'a, DirtyNodesResult>, + UniqueView<'a, DirtyNodeStates>, + UniqueView<'a, SendAnyMapWrapper>, +); -impl<'v> shipyard::Borrow<'v> for RunPassView<'v> { - type View = RunPassView<'v>; - - fn borrow( - world: &'v shipyard::World, - last_run: Option, - current: u32, - ) -> Result { - Ok(RunPassView { - tree: as shipyard::IntoBorrow>::Borrow::borrow( - world, last_run, current, - )?, - nodes_updated: - as shipyard::IntoBorrow>::Borrow::borrow( - world, last_run, current, - )?, - dirty: as shipyard::IntoBorrow>::Borrow::borrow( - world, last_run, current, - )?, - ctx: as shipyard::IntoBorrow>::Borrow::borrow( - world, last_run, current, - )?, - }) - } -} - -pub fn run_pass( +pub fn run_pass( type_id: TypeId, dependants: FxHashSet, pass_direction: PassDirection, - view: RunPassView, + view: RunPassView, mut update_node: impl FnMut(NodeId, &SendAnyMap) -> bool, ) { - let RunPassView { - tree, - nodes_updated, - dirty, - ctx, - } = view; + let (tree, _, nodes_updated, dirty, ctx, ..) = view; let ctx = ctx.as_ref(); match pass_direction { PassDirection::ParentToChild => { @@ -193,7 +164,7 @@ pub fn run_pass( let id = tree.id_at(id).unwrap(); if (update_node)(id, ctx) { nodes_updated.insert(id); - for id in tree.children_ids(id).unwrap() { + for id in tree.children_ids(id) { for dependant in &dependants { dirty.insert(*dependant, id, height + 1); } @@ -283,6 +254,8 @@ pub trait AnyState: State { } } +impl> AnyState for S {} + pub struct TypeErasedPass { pub(crate) this_type_id: TypeId, pub(crate) parent_dependant: bool, @@ -295,7 +268,17 @@ pub struct TypeErasedPass { phantom: PhantomData, } -#[derive(Debug)] +impl TypeErasedPass { + pub(crate) fn create_workload(&self) -> WorkloadSystem { + (self.workload)( + self.this_type_id, + self.dependants.clone(), + self.pass_direction, + ) + } +} + +#[derive(Debug, Clone, Copy)] pub enum PassDirection { ParentToChild, ChildToParent, @@ -319,7 +302,7 @@ impl<'a> AnyMapLike<'a> for &'a SendAnyMap { } pub trait Dependancy { - type ElementBorrowed<'a>: IntoBorrow + BorrowInfo; + type ElementBorrowed<'a>; fn type_ids() -> Box<[TypeId]> { Box::new([]) @@ -328,8 +311,8 @@ pub trait Dependancy { macro_rules! impl_dependancy { ($($t:ident),*) => { - impl< $($t: Send + Sync + Component),* > Dependancy for ($($t,)*) { - type ElementBorrowed<'a> = ($(View<'a, $t>,)*); + impl< $($t: Send + Sync + Component + State),* > Dependancy for ($($t,)*) { + type ElementBorrowed<'a> = ($(DependancyView<'a, $t>,)*); fn type_ids() -> Box<[TypeId]> { Box::new([$(TypeId::of::<$t>()),*]) @@ -338,6 +321,28 @@ macro_rules! impl_dependancy { }; } +// TODO: track what components are actually read to update subscriptions +// making this a wrapper makes it possible to implement that optimization without a breaking change +pub struct DependancyView<'a, T> { + inner: &'a T, +} + +impl<'a, T> DependancyView<'a, T> { + // This should only be used in the macro. This is not a public API or stable + #[doc(hidden)] + pub fn new(inner: &'a T) -> Self { + Self { inner } + } +} + +impl<'a, T> Deref for DependancyView<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.inner + } +} + impl_dependancy!(); impl_dependancy!(A); impl_dependancy!(A, B); diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index c87564dab..fc077d143 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -101,6 +101,19 @@ pub struct RealDom { impl RealDom { pub fn new(mut passes: Box<[TypeErasedPass]>) -> RealDom { + // resolve dependants for each pass + for i in 1..passes.len() { + let (before, after) = passes.split_at_mut(i); + let (current, before) = before.split_last_mut().unwrap(); + for pass in before.iter_mut().chain(after.iter_mut()) { + if current + .combined_dependancy_type_ids + .contains(&pass.this_type_id) + { + pass.dependants.insert(current.this_type_id); + } + } + } let workload = construct_workload(&mut passes); let (workload, _) = workload.build().unwrap(); let mut world = World::new(); @@ -111,18 +124,9 @@ impl RealDom { listeners: FxHashSet::default(), }); let root_id = world.add_entity(root_node); - - // resolve dependants for each pass - for i in 1..passes.len() { - let (before, after) = passes.split_at_mut(i); - let (current, before) = before.split_last_mut().unwrap(); - for pass in before.iter_mut().chain(after.iter_mut()) { - for dependancy in ¤t.combined_dependancy_type_ids { - if pass.this_type_id == *dependancy { - pass.dependants.insert(current.this_type_id); - } - } - } + { + let mut tree: TreeMutView = world.borrow().unwrap(); + tree.create_node(root_id); } let mut passes_updated = FxHashMap::default(); @@ -199,7 +203,7 @@ impl RealDom { } pub fn get_mut(&mut self, id: NodeId) -> Option> { - let contains = { self.tree_ref().contains(id) }; + let contains = self.tree_ref().contains(id); contains.then(|| NodeMut::new(id, self)) } @@ -252,9 +256,8 @@ impl RealDom { while let Some(id) = stack.pop() { if let Some(node) = self.get(id) { f(node); - if let Some(children) = tree.children_ids(id) { - stack.extend(children.iter().copied().rev()); - } + let children = tree.children_ids(id); + stack.extend(children.iter().copied().rev()); } } } @@ -266,10 +269,9 @@ impl RealDom { while let Some(id) = queue.pop_front() { if let Some(node) = self.get(id) { f(node); - if let Some(children) = tree.children_ids(id) { - for id in children { - queue.push_back(id); - } + let children = tree.children_ids(id); + for id in children { + queue.push_back(id); } } } @@ -279,14 +281,13 @@ impl RealDom { let mut stack = vec![self.root_id()]; while let Some(id) = stack.pop() { let tree = self.tree_ref(); - if let Some(mut children) = tree.children_ids(id) { - drop(tree); - children.reverse(); - if let Some(node) = self.get_mut(id) { - let node = node; - f(node); - stack.extend(children.iter()); - } + let mut children = tree.children_ids(id); + drop(tree); + children.reverse(); + if let Some(node) = self.get_mut(id) { + let node = node; + f(node); + stack.extend(children.iter()); } } } @@ -296,13 +297,12 @@ impl RealDom { queue.push_back(self.root_id()); while let Some(id) = queue.pop_front() { let tree = self.tree_ref(); - if let Some(children) = tree.children_ids(id) { - drop(tree); - if let Some(node) = self.get_mut(id) { - f(node); - for id in children { - queue.push_back(id); - } + let children = tree.children_ids(id); + drop(tree); + if let Some(node) = self.get_mut(id) { + f(node); + for id in children { + queue.push_back(id); } } } @@ -375,22 +375,19 @@ pub trait NodeImmutable: Sized { } #[inline] - fn child_ids(&self) -> Option> { + fn child_ids(&self) -> Vec { self.real_dom().tree_ref().children_ids(self.id()) } #[inline] fn children(&self) -> Vec> { self.child_ids() - .map(|ids| { - ids.iter() - .map(|id| NodeRef { - id: *id, - dom: self.real_dom(), - }) - .collect() + .iter() + .map(|id| NodeRef { + id: *id, + dom: self.real_dom(), }) - .unwrap_or_default() + .collect() } #[inline] @@ -409,7 +406,7 @@ pub trait NodeImmutable: Sized { #[inline] fn next(&self) -> Option> { let parent = self.parent_id()?; - let children = self.real_dom().tree_ref().children_ids(parent)?; + let children = self.real_dom().tree_ref().children_ids(parent); let index = children.iter().position(|id| *id == self.id())?; if index + 1 < children.len() { Some(NodeRef { @@ -424,7 +421,7 @@ pub trait NodeImmutable: Sized { #[inline] fn prev(&self) -> Option> { let parent = self.parent_id()?; - let children = self.real_dom().tree_ref().children_ids(parent)?; + let children = self.real_dom().tree_ref().children_ids(parent); let index = children.iter().position(|id| *id == self.id())?; if index > 0 { Some(NodeRef { @@ -534,7 +531,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { #[inline] pub fn next_mut(self) -> Option> { let parent = self.parent_id()?; - let children = self.dom.tree_mut().children_ids(parent)?; + let children = self.dom.tree_mut().children_ids(parent); let index = children.iter().position(|id| *id == self.id)?; if index + 1 < children.len() { Some(NodeMut::new(children[index + 1], self.dom)) @@ -546,7 +543,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { #[inline] pub fn prev_mut(self) -> Option> { let parent = self.parent_id()?; - let children = self.dom.tree_ref().children_ids(parent)?; + let children = self.dom.tree_ref().children_ids(parent); let index = children.iter().position(|id| *id == self.id)?; if index > 0 { Some(NodeMut::new(children[index - 1], self.dom)) @@ -613,11 +610,10 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { .dirty_nodes .mark_child_changed(parent_id); } - if let Some(children_ids) = self.child_ids() { - let children_ids_vec = children_ids.to_vec(); - for child in children_ids_vec { - self.dom.get_mut(child).unwrap().remove(); - } + let children_ids = self.child_ids(); + let children_ids_vec = children_ids.to_vec(); + for child in children_ids_vec { + self.dom.get_mut(child).unwrap().remove(); } self.dom.tree_mut().remove_single(id); } @@ -739,13 +735,12 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { let rdom = self.real_dom_mut(); let new_id = rdom.create_node(new_node).id(); - if let Some(children) = self.child_ids() { - let children = children.to_vec(); - let rdom = self.real_dom_mut(); - for child in children { - let child_id = rdom.get_mut(child).unwrap().clone_node(); - rdom.get_mut(new_id).unwrap().add_child(child_id); - } + let children = self.child_ids(); + let children = children.to_vec(); + let rdom = self.real_dom_mut(); + for child in children { + let child_id = rdom.get_mut(child).unwrap().clone_node(); + rdom.get_mut(new_id).unwrap().add_child(child_id); } new_id } @@ -894,7 +889,7 @@ fn construct_workload(passes: &mut [TypeErasedPas .iter_mut() .enumerate() .map(|(i, pass)| { - let workload = Some((pass.workload)(pass.dependants.clone())); + let workload = Some(pass.create_workload()); (i, pass, workload) }) .collect::>(); @@ -913,7 +908,7 @@ fn construct_workload(passes: &mut [TypeErasedPas let (_, _, workload) = &mut unresloved_workloads[i]; *workload = workload .take() - .map(|workload| workload.tag(dependancy_id.to_string())); + .map(|workload| workload.after_all(dependancy_id.to_string())); } } for (_, _, mut workload_system) in unresloved_workloads { diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index f1c542a3c..ec5e02895 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -14,7 +14,7 @@ pub type TreeMutView<'a> = (EntitiesViewMut<'a>, ViewMut<'a, Node>); pub trait TreeRef { fn parent_id(&self, id: NodeId) -> Option; - fn children_ids(&self, id: NodeId) -> Option>; + fn children_ids(&self, id: NodeId) -> Vec; fn height(&self, id: NodeId) -> Option; fn contains(&self, id: NodeId) -> bool; } @@ -35,8 +35,10 @@ impl<'a> TreeRef for TreeRefView<'a> { self.get(id).unwrap().parent } - fn children_ids(&self, id: NodeId) -> Option> { - Some(self.get(id).unwrap().children.clone()) + fn children_ids(&self, id: NodeId) -> Vec { + self.get(id) + .map(|node| node.children.clone()) + .unwrap_or_default() } fn height(&self, id: NodeId) -> Option { @@ -50,12 +52,10 @@ impl<'a> TreeRef for TreeRefView<'a> { impl<'a> TreeMut for TreeMutView<'a> { fn remove(&mut self, id: NodeId) { - fn recurse<'a>(tree: &mut TreeMutView<'a>, id: NodeId) { + fn recurse(tree: &mut TreeMutView<'_>, id: NodeId) { let children = tree.children_ids(id); - if let Some(children) = children { - for child in children { - recurse(tree, child); - } + for child in children { + recurse(tree, child); } } { @@ -175,9 +175,12 @@ impl<'a> TreeRef for TreeMutView<'a> { node_data.get(id).unwrap().parent } - fn children_ids(&self, id: NodeId) -> Option> { + fn children_ids(&self, id: NodeId) -> Vec { let node_data = &self.1; - node_data.get(id).map(|node| node.children.clone()).ok() + node_data + .get(id) + .map(|node| node.children.clone()) + .unwrap_or_default() } fn height(&self, id: NodeId) -> Option { @@ -192,47 +195,51 @@ impl<'a> TreeRef for TreeMutView<'a> { #[test] fn creation() { - let mut tree = Tree::new(); - let parent_id = tree.root; - tree.insert(parent_id, 1i32); - let mut child = tree.create_node(); - child.insert(0i32); - let child_id = child.id(); + use shipyard::World; + #[derive(Component)] + struct Num(i32); + + let mut world = World::new(); + let parent_id = world.add_entity(Num(1i32)); + let child_id = world.add_entity(Num(0i32)); + + let mut tree = world.borrow::().unwrap(); + + tree.create_node(parent_id); + tree.create_node(child_id); tree.add_child(parent_id, child_id); - println!("Tree: {tree:#?}"); - assert_eq!(tree.size(), 2); assert_eq!(tree.height(parent_id), Some(0)); assert_eq!(tree.height(child_id), Some(1)); assert_eq!(tree.parent_id(parent_id), None); assert_eq!(tree.parent_id(child_id).unwrap(), parent_id); - assert_eq!(tree.children_ids(parent_id).unwrap(), &[child_id]); - - assert_eq!(*tree.get::(parent_id).unwrap(), 1); - assert_eq!(*tree.get::(child_id).unwrap(), 0); + assert_eq!(tree.children_ids(parent_id), &[child_id]); } #[test] fn insertion() { - let mut tree = Tree::new(); - let parent = tree.root(); - tree.insert(parent, 0); - let mut child = tree.create_node(); - child.insert(2); - let child = child.id(); + use shipyard::World; + #[derive(Component)] + struct Num(i32); + + let mut world = World::new(); + let parent = world.add_entity(Num(0)); + let child = world.add_entity(Num(2)); + let before = world.add_entity(Num(1)); + let after = world.add_entity(Num(3)); + + let mut tree = world.borrow::().unwrap(); + + tree.create_node(parent); + tree.create_node(child); + tree.create_node(before); + tree.create_node(after); + tree.add_child(parent, child); - let mut before = tree.create_node(); - before.insert(1); - let before = before.id(); tree.insert_before(child, before); - let mut after = tree.create_node(); - after.insert(3); - let after = after.id(); tree.insert_after(child, after); - 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)); @@ -240,34 +247,32 @@ fn insertion() { 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]); - - 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.children_ids(parent), &[before, child, after]); } #[test] fn deletion() { - let mut tree = Tree::new(); - let parent = tree.root(); - tree.insert(parent, 0); - let mut child = tree.create_node(); - child.insert(2); - let child = child.id(); + use shipyard::World; + #[derive(Component)] + struct Num(i32); + + let mut world = World::new(); + let parent = world.add_entity(Num(0)); + let child = world.add_entity(Num(2)); + let before = world.add_entity(Num(1)); + let after = world.add_entity(Num(3)); + + let mut tree = world.borrow::().unwrap(); + + tree.create_node(parent); + tree.create_node(child); + tree.create_node(before); + tree.create_node(after); + tree.add_child(parent, child); - let mut before = tree.create_node(); - before.insert(1); - let before = before.id(); tree.insert_before(child, before); - let mut after = tree.create_node(); - after.insert(3); - let after = after.id(); tree.insert_after(child, after); - 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)); @@ -275,49 +280,26 @@ fn deletion() { 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]); - - 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.children_ids(parent), &[before, child, after]); tree.remove(child); - 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.parent_id(before).unwrap(), parent); assert_eq!(tree.parent_id(after).unwrap(), parent); - assert_eq!(tree.children_ids(parent).unwrap(), &[before, after]); - - 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.children_ids(parent), &[before, after]); tree.remove(before); - 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.parent_id(after).unwrap(), parent); - assert_eq!(tree.children_ids(parent).unwrap(), &[after]); - - assert_eq!(*tree.get::(parent).unwrap(), 0); - assert_eq!(tree.get::(before), None); - assert_eq!(*tree.get::(after).unwrap(), 3); + assert_eq!(tree.children_ids(parent), &[after]); tree.remove(after); - println!("Tree: {tree:#?}"); - assert_eq!(tree.size(), 1); assert_eq!(tree.height(parent), Some(0)); - assert_eq!(tree.children_ids(parent).unwrap(), &[]); - - assert_eq!(*tree.get::(parent).unwrap(), 0); - assert_eq!(tree.get::(after), None); + assert_eq!(tree.children_ids(parent), &[]); } diff --git a/packages/native-core/src/utils/persistant_iterator.rs b/packages/native-core/src/utils/persistant_iterator.rs index 2469e3ce4..155909373 100644 --- a/packages/native-core/src/utils/persistant_iterator.rs +++ b/packages/native-core/src/utils/persistant_iterator.rs @@ -199,68 +199,68 @@ fn traverse() { let div_tag = "div".to_string(); assert!(matches!( - &rdom.get(iter.next(&rdom).id()).unwrap().node_type(), + &*rdom.get(iter.next(&rdom).id()).unwrap().node_type(), NodeType::Element(ElementNode { tag: div_tag, .. }) )); assert!(matches!( - &rdom.get(iter.next(&rdom).id()).unwrap().node_type(), + &*rdom.get(iter.next(&rdom).id()).unwrap().node_type(), NodeType::Element(ElementNode { tag: div_tag, .. }) )); let text1 = "hello".to_string(); assert!(matches!( - &rdom.get(iter.next(&rdom).id()).unwrap().node_type(), + &*rdom.get(iter.next(&rdom).id()).unwrap().node_type(), NodeType::Text(text1) )); let p_tag = "p".to_string(); assert!(matches!( - &rdom.get(iter.next(&rdom).id()).unwrap().node_type(), + &*rdom.get(iter.next(&rdom).id()).unwrap().node_type(), NodeType::Element(ElementNode { tag: p_tag, .. }) )); let text2 = "world".to_string(); assert!(matches!( - &rdom.get(iter.next(&rdom).id()).unwrap().node_type(), + &*rdom.get(iter.next(&rdom).id()).unwrap().node_type(), NodeType::Text(text2) )); let text3 = "hello world".to_string(); assert!(matches!( - &rdom.get(iter.next(&rdom).id()).unwrap().node_type(), + &*rdom.get(iter.next(&rdom).id()).unwrap().node_type(), NodeType::Text(text3) )); assert!(matches!( - &rdom.get(iter.next(&rdom).id()).unwrap().node_type(), + &*rdom.get(iter.next(&rdom).id()).unwrap().node_type(), NodeType::Element(ElementNode { tag: div_tag, .. }) )); assert!(matches!( - &rdom.get(iter.prev(&rdom).id()).unwrap().node_type(), + &*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(), NodeType::Text(text3) )); assert!(matches!( - &rdom.get(iter.prev(&rdom).id()).unwrap().node_type(), + &*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(), NodeType::Text(text2) )); assert!(matches!( - &rdom.get(iter.prev(&rdom).id()).unwrap().node_type(), + &*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(), NodeType::Element(ElementNode { tag: p_tag, .. }) )); assert!(matches!( - &rdom.get(iter.prev(&rdom).id()).unwrap().node_type(), + &*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(), NodeType::Text(text1) )); assert!(matches!( - &rdom.get(iter.prev(&rdom).id()).unwrap().node_type(), + &*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(), NodeType::Element(ElementNode { tag: div_tag, .. }) )); assert!(matches!( - &rdom.get(iter.prev(&rdom).id()).unwrap().node_type(), + &*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(), NodeType::Element(ElementNode { tag: div_tag, .. }) )); assert!(matches!( - &rdom.get(iter.prev(&rdom).id()).unwrap().node_type(), + &*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(), NodeType::Element(ElementNode { tag: div_tag, .. }) )); assert!(matches!( - &rdom.get(iter.prev(&rdom).id()).unwrap().node_type(), + &*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(), NodeType::Text(text3) )); } @@ -335,13 +335,13 @@ fn persist_removes() { let root_tag = "Root".to_string(); let idx = iter1.next(&rdom).id(); assert!(matches!( - &rdom.get(idx).unwrap().node_type(), + &*rdom.get(idx).unwrap().node_type(), NodeType::Element(ElementNode { tag: root_tag, .. }) )); let idx = iter2.next(&rdom).id(); assert!(matches!( - &rdom.get(idx).unwrap().node_type(), + &*rdom.get(idx).unwrap().node_type(), NodeType::Element(ElementNode { tag: root_tag, .. }) )); } @@ -399,7 +399,7 @@ fn persist_instertions_before() { let p_tag = "div".to_string(); let idx = iter.next(&rdom).id(); assert!(matches!( - &rdom.get(idx).unwrap().node_type(), + &*rdom.get(idx).unwrap().node_type(), NodeType::Element(ElementNode { tag: p_tag, .. }) )); } @@ -456,13 +456,13 @@ fn persist_instertions_after() { let p_tag = "p".to_string(); let idx = iter.next(&rdom).id(); assert!(matches!( - &rdom.get(idx).unwrap().node_type(), + &*rdom.get(idx).unwrap().node_type(), NodeType::Element(ElementNode { tag: p_tag, .. }) )); let text = "hello world".to_string(); let idx = iter.next(&rdom).id(); assert!(matches!( - &rdom.get(idx).unwrap().node_type(), + &*rdom.get(idx).unwrap().node_type(), NodeType::Text(text) )); } diff --git a/packages/native-core/tests/called_minimally_on_build.rs b/packages/native-core/tests/called_minimally_on_build.rs index db0bda59c..feebc1ea6 100644 --- a/packages/native-core/tests/called_minimally_on_build.rs +++ b/packages/native-core/tests/called_minimally_on_build.rs @@ -6,22 +6,25 @@ use dioxus_native_core::{ real_dom::RealDom, Dependancy, SendAnyMap, State, }; +use dioxus_native_core_macro::partial_derive_state; +use shipyard::Component; macro_rules! dep { ( child( $name:ty, $dep:ty ) ) => { - impl Pass for $name { + #[partial_derive_state] + impl State for $name { type ParentDependencies = (); type ChildDependencies = $dep; type NodeDependencies = (); const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::ALL; - fn pass<'a>( + fn update<'a>( &mut self, _: NodeView, _: ::ElementBorrowed<'a>, _: Option<::ElementBorrowed<'a>>, - _: Option::ElementBorrowed<'a>>>, + _: Vec<::ElementBorrowed<'a>>, _: &SendAnyMap, ) -> bool { self.0 += 1; @@ -32,30 +35,31 @@ macro_rules! dep { node_view: NodeView<()>, node: ::ElementBorrowed<'a>, parent: Option<::ElementBorrowed<'a>>, - children: Option::ElementBorrowed<'a>>>, + children: Vec<::ElementBorrowed<'a>>, context: &SendAnyMap, ) -> Self { let mut myself = Self::default(); - myself.pass(node_view, node, parent, children, context); + myself.update(node_view, node, parent, children, context); myself } } }; ( parent( $name:ty, $dep:ty ) ) => { - impl Pass for $name { + #[partial_derive_state] + impl State for $name { type ParentDependencies = $dep; type ChildDependencies = (); type NodeDependencies = (); const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::ALL; - fn pass<'a>( + fn update<'a>( &mut self, _: NodeView, _: ::ElementBorrowed<'a>, _: Option<::ElementBorrowed<'a>>, - _: Option::ElementBorrowed<'a>>>, + _: Vec<::ElementBorrowed<'a>>, _: &SendAnyMap, ) -> bool { self.0 += 1; @@ -66,30 +70,31 @@ macro_rules! dep { node_view: NodeView<()>, node: ::ElementBorrowed<'a>, parent: Option<::ElementBorrowed<'a>>, - children: Option::ElementBorrowed<'a>>>, + children: Vec<::ElementBorrowed<'a>>, context: &SendAnyMap, ) -> Self { let mut myself = Self::default(); - myself.pass(node_view, node, parent, children, context); + myself.update(node_view, node, parent, children, context); myself } } }; ( node( $name:ty, $dep:ty ) ) => { - impl Pass for $name { + #[partial_derive_state] + impl State for $name { type ParentDependencies = $dep; type ChildDependencies = (); type NodeDependencies = (); const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::ALL; - fn pass<'a>( + fn update<'a>( &mut self, _: NodeView, _: ::ElementBorrowed<'a>, _: Option<::ElementBorrowed<'a>>, - _: Option::ElementBorrowed<'a>>>, + _: Vec<::ElementBorrowed<'a>>, _: &SendAnyMap, ) -> bool { self.0 += 1; @@ -100,11 +105,11 @@ macro_rules! dep { node_view: NodeView<()>, node: ::ElementBorrowed<'a>, parent: Option<::ElementBorrowed<'a>>, - children: Option::ElementBorrowed<'a>>>, + children: Vec<::ElementBorrowed<'a>>, context: &SendAnyMap, ) -> Self { let mut myself = Self::default(); - myself.pass(node_view, node, parent, children, context); + myself.update(node_view, node, parent, children, context); myself } } @@ -145,7 +150,7 @@ macro_rules! test_state{ let mut dioxus_state = DioxusState::create(&mut dom); dioxus_state.apply_mutations(&mut dom, mutations); - dom.update_state(SendAnyMap::new(), false); + dom.update_state(SendAnyMap::new()); dom.traverse_depth_first(|n| { $( @@ -158,15 +163,15 @@ macro_rules! test_state{ mod node_depends_on_child_and_parent { use super::*; - #[derive(Debug, Clone, Default, PartialEq)] + #[derive(Debug, Clone, Default, PartialEq, Component)] struct Node(i32); dep!(node(Node, (Child, Parent))); - #[derive(Debug, Clone, Default, PartialEq)] + #[derive(Debug, Clone, Default, PartialEq, Component)] struct Child(i32); dep!(child(Child, (Child,))); - #[derive(Debug, Clone, Default, PartialEq)] + #[derive(Debug, Clone, Default, PartialEq, Component)] struct Parent(i32); dep!(parent(Parent, (Parent,))); @@ -175,15 +180,15 @@ mod node_depends_on_child_and_parent { mod child_depends_on_node_that_depends_on_parent { use super::*; - #[derive(Debug, Clone, Default, PartialEq)] + #[derive(Debug, Clone, Default, PartialEq, Component)] struct Node(i32); dep!(node(Node, (Parent,))); - #[derive(Debug, Clone, Default, PartialEq)] + #[derive(Debug, Clone, Default, PartialEq, Component)] struct Child(i32); dep!(child(Child, (Node,))); - #[derive(Debug, Clone, Default, PartialEq)] + #[derive(Debug, Clone, Default, PartialEq, Component)] struct Parent(i32); dep!(parent(Parent, (Parent,))); @@ -192,15 +197,15 @@ mod child_depends_on_node_that_depends_on_parent { mod parent_depends_on_node_that_depends_on_child { use super::*; - #[derive(Debug, Clone, Default, PartialEq)] + #[derive(Debug, Clone, Default, PartialEq, Component)] struct Node(i32); dep!(node(Node, (Child,))); - #[derive(Debug, Clone, Default, PartialEq)] + #[derive(Debug, Clone, Default, PartialEq, Component)] struct Child(i32); dep!(child(Child, (Child,))); - #[derive(Debug, Clone, Default, PartialEq)] + #[derive(Debug, Clone, Default, PartialEq, Component)] struct Parent(i32); dep!(parent(Parent, (Node,))); @@ -209,11 +214,11 @@ mod parent_depends_on_node_that_depends_on_child { mod node_depends_on_other_node_state { use super::*; - #[derive(Debug, Clone, Default, PartialEq)] + #[derive(Debug, Clone, Default, PartialEq, Component)] struct Node1(i32); dep!(node(Node1, (Node2,))); - #[derive(Debug, Clone, Default, PartialEq)] + #[derive(Debug, Clone, Default, PartialEq, Component)] struct Node2(i32); dep!(node(Node2, ())); @@ -222,15 +227,15 @@ mod node_depends_on_other_node_state { mod node_child_and_parent_state_depends_on_self { use super::*; - #[derive(Debug, Clone, Default, PartialEq)] + #[derive(Debug, Clone, Default, PartialEq, Component)] struct Node(i32); dep!(node(Node, ())); - #[derive(Debug, Clone, Default, PartialEq)] + #[derive(Debug, Clone, Default, PartialEq, Component)] struct Child(i32); dep!(child(Child, (Child,))); - #[derive(Debug, Clone, Default, PartialEq)] + #[derive(Debug, Clone, Default, PartialEq, Component)] struct Parent(i32); dep!(parent(Parent, (Parent,))); diff --git a/packages/native-core/tests/fuzzing.rs b/packages/native-core/tests/fuzzing.rs index 8a449c52a..38b23587a 100644 --- a/packages/native-core/tests/fuzzing.rs +++ b/packages/native-core/tests/fuzzing.rs @@ -3,9 +3,12 @@ use dioxus_core::*; use dioxus_native_core::{ dioxus::DioxusState, node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}, + prelude::*, real_dom::RealDom, Dependancy, SendAnyMap, State, }; +use dioxus_native_core_macro::partial_derive_state; +use shipyard::Component; use std::cell::Cell; fn random_ns() -> Option<&'static str> { @@ -231,7 +234,7 @@ fn create_random_dynamic_attr(cx: &ScopeState) -> Attribute { static mut TEMPLATE_COUNT: usize = 0; -#[derive(PartialEq, Props)] +#[derive(PartialEq, Props, Component)] struct DepthProps { depth: usize, root: bool, @@ -293,11 +296,12 @@ fn create_random_element(cx: Scope) -> Element { node } -#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[derive(Debug, Clone, PartialEq, Eq, Default, Component)] pub struct BlablaState { count: usize, } +#[partial_derive_state] impl State for BlablaState { type ParentDependencies = (Self,); type ChildDependencies = (); @@ -312,7 +316,7 @@ impl State for BlablaState { _: NodeView, _: ::ElementBorrowed<'a>, parent: Option<::ElementBorrowed<'a>>, - _: Option::ElementBorrowed<'a>>>, + _: Vec<::ElementBorrowed<'a>>, _: &SendAnyMap, ) -> bool { if let Some((parent,)) = parent { @@ -327,7 +331,7 @@ impl State for BlablaState { node_view: NodeView<()>, node: ::ElementBorrowed<'a>, parent: Option<::ElementBorrowed<'a>>, - children: Option::ElementBorrowed<'a>>>, + children: Vec<::ElementBorrowed<'a>>, context: &SendAnyMap, ) -> Self { let mut myself = Self::default(); @@ -353,7 +357,7 @@ fn create() { dioxus_state.apply_mutations(&mut rdom, mutations); let ctx = SendAnyMap::new(); - rdom.update_state(ctx, false); + rdom.update_state(ctx); } } @@ -375,13 +379,13 @@ fn diff() { dioxus_state.apply_mutations(&mut rdom, mutations); let ctx = SendAnyMap::new(); - rdom.update_state(ctx, false); + rdom.update_state(ctx); for _ in 0..10 { let mutations = vdom.render_immediate(); dioxus_state.apply_mutations(&mut rdom, mutations); let ctx = SendAnyMap::new(); - rdom.update_state(ctx, false); + rdom.update_state(ctx); } } } diff --git a/packages/native-core/tests/miri_native.rs b/packages/native-core/tests/miri_native.rs index 342445ae7..8e05fe934 100644 --- a/packages/native-core/tests/miri_native.rs +++ b/packages/native-core/tests/miri_native.rs @@ -2,16 +2,20 @@ use dioxus::prelude::*; use dioxus_native_core::{ dioxus::DioxusState, node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}, + prelude::*, real_dom::RealDom, Dependancy, SendAnyMap, State, }; +use dioxus_native_core_macro::partial_derive_state; +use shipyard::Component; use tokio::time::sleep; -#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[derive(Debug, Clone, PartialEq, Eq, Default, Component)] pub struct BlablaState { count: usize, } +#[partial_derive_state] impl State for BlablaState { type ParentDependencies = (Self,); type ChildDependencies = (); @@ -26,7 +30,7 @@ impl State for BlablaState { _: NodeView, _: ::ElementBorrowed<'a>, parent: Option<::ElementBorrowed<'a>>, - _: Option::ElementBorrowed<'a>>>, + _: Vec<::ElementBorrowed<'a>>, _: &SendAnyMap, ) -> bool { if let Some((parent,)) = parent { @@ -41,7 +45,7 @@ impl State for BlablaState { node_view: NodeView<()>, node: ::ElementBorrowed<'a>, parent: Option<::ElementBorrowed<'a>>, - children: Option::ElementBorrowed<'a>>>, + children: Vec<::ElementBorrowed<'a>>, context: &SendAnyMap, ) -> Self { let mut myself = Self::default(); @@ -143,7 +147,7 @@ fn native_core_is_okay() { dioxus_state.apply_mutations(&mut *rdom.lock().unwrap(), mutations); let ctx = SendAnyMap::new(); - rdom.lock().unwrap().update_state(ctx, false); + rdom.lock().unwrap().update_state(ctx); for _ in 0..10 { dom.wait_for_work().await; @@ -152,7 +156,7 @@ fn native_core_is_okay() { dioxus_state.apply_mutations(&mut *rdom.lock().unwrap(), mutations); let ctx = SendAnyMap::new(); - rdom.lock().unwrap().update_state(ctx, false); + rdom.lock().unwrap().update_state(ctx); } }); } diff --git a/packages/native-core/tests/passes.rs b/packages/native-core/tests/passes.rs index 6afe24bf6..7f69b8968 100644 --- a/packages/native-core/tests/passes.rs +++ b/packages/native-core/tests/passes.rs @@ -1,6 +1,8 @@ use dioxus_native_core::node::NodeType; use dioxus_native_core::prelude::*; +use dioxus_native_core_macro::partial_derive_state; use rustc_hash::{FxHashMap, FxHashSet}; +use shipyard::Component; fn create_blank_element() -> NodeType { NodeType::Element(ElementNode { @@ -13,9 +15,10 @@ fn create_blank_element() -> NodeType { #[test] fn node_pass() { - #[derive(Debug, Default, Clone, PartialEq)] + #[derive(Debug, Default, Clone, PartialEq, Component)] struct Number(i32); + #[partial_derive_state] impl State for Number { type ChildDependencies = (); type NodeDependencies = (); @@ -27,7 +30,7 @@ fn node_pass() { _: NodeView, _: ::ElementBorrowed<'a>, _: Option<::ElementBorrowed<'a>>, - _: Option::ElementBorrowed<'a>>>, + _: Vec<::ElementBorrowed<'a>>, _: &SendAnyMap, ) -> bool { self.0 += 1; @@ -38,7 +41,7 @@ fn node_pass() { node_view: NodeView<()>, node: ::ElementBorrowed<'a>, parent: Option<::ElementBorrowed<'a>>, - children: Option::ElementBorrowed<'a>>>, + children: Vec<::ElementBorrowed<'a>>, context: &SendAnyMap, ) -> Self { let mut myself = Self::default(); @@ -48,22 +51,29 @@ fn node_pass() { } let mut tree: RealDom = RealDom::new(Box::new([Number::to_type_erased()])); - tree.update_state(SendAnyMap::new(), false); + tree.update_state(SendAnyMap::new()); - assert_eq!(tree.get(tree.root_id()).unwrap().get(), Some(&Number(1))); + assert_eq!( + tree.get(tree.root_id()).unwrap().get().as_deref(), + Some(&Number(1)) + ); // mark the node as dirty tree.get_mut(tree.root_id()).unwrap().get_mut::(); - tree.update_state(SendAnyMap::new(), false); - assert_eq!(tree.get(tree.root_id()).unwrap().get(), Some(&Number(2))); + tree.update_state(SendAnyMap::new()); + assert_eq!( + tree.get(tree.root_id()).unwrap().get().as_deref(), + Some(&Number(2)) + ); } #[test] fn dependant_node_pass() { - #[derive(Debug, Default, Clone, PartialEq)] + #[derive(Debug, Default, Clone, PartialEq, Component)] struct AddNumber(i32); + #[partial_derive_state] impl State for AddNumber { type ChildDependencies = (); type NodeDependencies = (SubtractNumber,); @@ -75,7 +85,7 @@ fn dependant_node_pass() { _: NodeView, _: ::ElementBorrowed<'a>, _: Option<::ElementBorrowed<'a>>, - _: Option::ElementBorrowed<'a>>>, + _: Vec<::ElementBorrowed<'a>>, _: &SendAnyMap, ) -> bool { self.0 += 1; @@ -86,7 +96,7 @@ fn dependant_node_pass() { node_view: NodeView<()>, node: ::ElementBorrowed<'a>, parent: Option<::ElementBorrowed<'a>>, - children: Option::ElementBorrowed<'a>>>, + children: Vec<::ElementBorrowed<'a>>, context: &SendAnyMap, ) -> Self { let mut myself = Self::default(); @@ -95,9 +105,10 @@ fn dependant_node_pass() { } } - #[derive(Debug, Default, Clone, PartialEq)] + #[derive(Debug, Default, Clone, PartialEq, Component)] struct SubtractNumber(i32); + #[partial_derive_state] impl State for SubtractNumber { type ChildDependencies = (); type NodeDependencies = (); @@ -109,7 +120,7 @@ fn dependant_node_pass() { _: NodeView, _: ::ElementBorrowed<'a>, _: Option<::ElementBorrowed<'a>>, - _: Option::ElementBorrowed<'a>>>, + _: Vec<::ElementBorrowed<'a>>, _: &SendAnyMap, ) -> bool { self.0 -= 1; @@ -120,7 +131,7 @@ fn dependant_node_pass() { node_view: NodeView<()>, node: ::ElementBorrowed<'a>, parent: Option<::ElementBorrowed<'a>>, - children: Option::ElementBorrowed<'a>>>, + children: Vec<::ElementBorrowed<'a>>, context: &SendAnyMap, ) -> Self { let mut myself = Self::default(); @@ -133,36 +144,37 @@ fn dependant_node_pass() { AddNumber::to_type_erased(), SubtractNumber::to_type_erased(), ])); - tree.update_state(SendAnyMap::new(), false); + tree.update_state(SendAnyMap::new()); let root = tree.get(tree.root_id()).unwrap(); - assert_eq!(root.get(), Some(&AddNumber(1))); - assert_eq!(root.get(), Some(&SubtractNumber(-1))); + assert_eq!(root.get().as_deref(), Some(&AddNumber(1))); + assert_eq!(root.get().as_deref(), Some(&SubtractNumber(-1))); // mark the subtract state as dirty, it should update the add state tree.get_mut(tree.root_id()) .unwrap() .get_mut::(); - tree.update_state(SendAnyMap::new(), false); + tree.update_state(SendAnyMap::new()); let root = tree.get(tree.root_id()).unwrap(); - assert_eq!(root.get(), Some(&AddNumber(2))); - assert_eq!(root.get(), Some(&SubtractNumber(-2))); + assert_eq!(root.get().as_deref(), Some(&AddNumber(2))); + assert_eq!(root.get().as_deref(), Some(&SubtractNumber(-2))); // mark the add state as dirty, it should ~not~ update the subtract state tree.get_mut(tree.root_id()).unwrap().get_mut::(); - tree.update_state(SendAnyMap::new(), false); + tree.update_state(SendAnyMap::new()); let root = tree.get(tree.root_id()).unwrap(); - assert_eq!(root.get(), Some(&AddNumber(3))); - assert_eq!(root.get(), Some(&SubtractNumber(-2))); + assert_eq!(root.get().as_deref(), Some(&AddNumber(3))); + assert_eq!(root.get().as_deref(), Some(&SubtractNumber(-2))); } #[test] fn independant_node_pass() { - #[derive(Debug, Default, Clone, PartialEq)] + #[derive(Debug, Default, Clone, PartialEq, Component)] struct AddNumber(i32); + #[partial_derive_state] impl State for AddNumber { type ChildDependencies = (); type NodeDependencies = (); @@ -175,7 +187,7 @@ fn independant_node_pass() { _: NodeView, _: ::ElementBorrowed<'a>, _: Option<::ElementBorrowed<'a>>, - _: Option::ElementBorrowed<'a>>>, + _: Vec<::ElementBorrowed<'a>>, _: &SendAnyMap, ) -> bool { self.0 += 1; @@ -186,7 +198,7 @@ fn independant_node_pass() { node_view: NodeView<()>, node: ::ElementBorrowed<'a>, parent: Option<::ElementBorrowed<'a>>, - children: Option::ElementBorrowed<'a>>>, + children: Vec<::ElementBorrowed<'a>>, context: &SendAnyMap, ) -> Self { let mut myself = Self::default(); @@ -195,9 +207,10 @@ fn independant_node_pass() { } } - #[derive(Debug, Default, Clone, PartialEq)] + #[derive(Debug, Default, Clone, PartialEq, Component)] struct SubtractNumber(i32); + #[partial_derive_state] impl State for SubtractNumber { type ChildDependencies = (); type NodeDependencies = (); @@ -210,7 +223,7 @@ fn independant_node_pass() { _: NodeView, _: ::ElementBorrowed<'a>, _: Option<::ElementBorrowed<'a>>, - _: Option::ElementBorrowed<'a>>>, + _: Vec<::ElementBorrowed<'a>>, _: &SendAnyMap, ) -> bool { self.0 -= 1; @@ -221,7 +234,7 @@ fn independant_node_pass() { node_view: NodeView<()>, node: ::ElementBorrowed<'a>, parent: Option<::ElementBorrowed<'a>>, - children: Option::ElementBorrowed<'a>>>, + children: Vec<::ElementBorrowed<'a>>, context: &SendAnyMap, ) -> Self { let mut myself = Self::default(); @@ -234,36 +247,36 @@ fn independant_node_pass() { AddNumber::to_type_erased(), SubtractNumber::to_type_erased(), ])); - tree.update_state(SendAnyMap::new(), false); + tree.update_state(SendAnyMap::new()); let root = tree.get(tree.root_id()).unwrap(); - assert_eq!(root.get(), Some(&AddNumber(1))); - assert_eq!(root.get(), Some(&SubtractNumber(-1))); + assert_eq!(root.get().as_deref(), Some(&AddNumber(1))); + assert_eq!(root.get().as_deref(), Some(&SubtractNumber(-1))); // mark the subtract state as dirty, it should ~not~ update the add state tree.get_mut(tree.root_id()) .unwrap() .get_mut::(); - tree.update_state(SendAnyMap::new(), false); + tree.update_state(SendAnyMap::new()); let root = tree.get(tree.root_id()).unwrap(); - assert_eq!(root.get(), Some(&AddNumber(1))); - assert_eq!(root.get(), Some(&SubtractNumber(-2))); + assert_eq!(root.get().as_deref(), Some(&AddNumber(1))); + assert_eq!(root.get().as_deref(), Some(&SubtractNumber(-2))); // mark the add state as dirty, it should ~not~ update the subtract state tree.get_mut(tree.root_id()).unwrap().get_mut::(); - tree.update_state(SendAnyMap::new(), false); + tree.update_state(SendAnyMap::new()); let root = tree.get(tree.root_id()).unwrap(); - assert_eq!(root.get(), Some(&AddNumber(2))); - assert_eq!(root.get(), Some(&SubtractNumber(-2))); + assert_eq!(root.get().as_deref(), Some(&AddNumber(2))); + assert_eq!(root.get().as_deref(), Some(&SubtractNumber(-2))); } #[test] fn down_pass() { - #[derive(Debug, Clone, PartialEq)] + #[derive(Debug, Clone, PartialEq, Component)] struct AddNumber(i32); impl Default for AddNumber { @@ -272,6 +285,7 @@ fn down_pass() { } } + #[partial_derive_state] impl State for AddNumber { type ChildDependencies = (); type NodeDependencies = (); @@ -284,7 +298,7 @@ fn down_pass() { _: NodeView, _: ::ElementBorrowed<'a>, parent: Option<::ElementBorrowed<'a>>, - _: Option::ElementBorrowed<'a>>>, + _: Vec<::ElementBorrowed<'a>>, _: &SendAnyMap, ) -> bool { if let Some((parent,)) = parent { @@ -297,7 +311,7 @@ fn down_pass() { node_view: NodeView<()>, node: ::ElementBorrowed<'a>, parent: Option<::ElementBorrowed<'a>>, - children: Option::ElementBorrowed<'a>>>, + children: Vec<::ElementBorrowed<'a>>, context: &SendAnyMap, ) -> Self { let mut myself = Self::default(); @@ -321,25 +335,24 @@ fn down_pass() { parent.add_child(child1); parent.add_child(child2); - tree.update_state(SendAnyMap::new(), false); + tree.update_state(SendAnyMap::new()); let root = tree.get(tree.root_id()).unwrap(); dbg!(root.id()); - assert_eq!(root.get(), Some(&AddNumber(1))); + assert_eq!(root.get().as_deref(), Some(&AddNumber(1))); let child1 = tree.get(child1).unwrap(); dbg!(child1.id()); - dbg!(child1.parent().unwrap().get::()); - assert_eq!(child1.get(), Some(&AddNumber(2))); + assert_eq!(child1.get().as_deref(), Some(&AddNumber(2))); let grandchild1 = tree.get(grandchild1).unwrap(); - assert_eq!(grandchild1.get(), Some(&AddNumber(3))); + assert_eq!(grandchild1.get().as_deref(), Some(&AddNumber(3))); let child2 = tree.get(child2).unwrap(); - assert_eq!(child2.get(), Some(&AddNumber(2))); + assert_eq!(child2.get().as_deref(), Some(&AddNumber(2))); let grandchild2 = tree.get(grandchild2).unwrap(); - assert_eq!(grandchild2.get(), Some(&AddNumber(3))); + assert_eq!(grandchild2.get().as_deref(), Some(&AddNumber(3))); } #[test] @@ -357,9 +370,10 @@ fn up_pass() { // 2=\ // 1 - #[derive(Debug, Clone, PartialEq)] + #[derive(Debug, Clone, PartialEq, Component)] struct AddNumber(i32); + #[partial_derive_state] impl State for AddNumber { type ChildDependencies = (AddNumber,); type NodeDependencies = (); @@ -372,12 +386,10 @@ fn up_pass() { _: NodeView, _: ::ElementBorrowed<'a>, _: Option<::ElementBorrowed<'a>>, - children: Option::ElementBorrowed<'a>>>, + children: Vec<::ElementBorrowed<'a>>, _: &SendAnyMap, ) -> bool { - if let Some(children) = children { - self.0 += children.iter().map(|(i,)| i.0).sum::(); - } + self.0 += children.iter().map(|(i,)| i.0).sum::(); true } @@ -385,7 +397,7 @@ fn up_pass() { node_view: NodeView<()>, node: ::ElementBorrowed<'a>, parent: Option<::ElementBorrowed<'a>>, - children: Option::ElementBorrowed<'a>>>, + children: Vec<::ElementBorrowed<'a>>, context: &SendAnyMap, ) -> Self { let mut myself = Self(1); @@ -409,20 +421,20 @@ fn up_pass() { parent.add_child(child1); parent.add_child(child2); - tree.update_state(SendAnyMap::new(), false); + tree.update_state(SendAnyMap::new()); let root = tree.get(tree.root_id()).unwrap(); - assert_eq!(root.get(), Some(&AddNumber(5))); + assert_eq!(root.get().as_deref(), Some(&AddNumber(5))); let child1 = tree.get(child1).unwrap(); - assert_eq!(child1.get(), Some(&AddNumber(2))); + assert_eq!(child1.get().as_deref(), Some(&AddNumber(2))); let grandchild1 = tree.get(grandchild1).unwrap(); - assert_eq!(grandchild1.get(), Some(&AddNumber(1))); + assert_eq!(grandchild1.get().as_deref(), Some(&AddNumber(1))); let child2 = tree.get(child2).unwrap(); - assert_eq!(child2.get(), Some(&AddNumber(2))); + assert_eq!(child2.get().as_deref(), Some(&AddNumber(2))); let grandchild2 = tree.get(grandchild2).unwrap(); - assert_eq!(grandchild2.get(), Some(&AddNumber(1))); + assert_eq!(grandchild2.get().as_deref(), Some(&AddNumber(1))); } From c4845d5c8d4e456bd5ea388e36cf06a68e653fa1 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 12 Mar 2023 16:20:01 -0500 Subject: [PATCH 061/103] update TUI to new native-core --- packages/native-core-macro/src/lib.rs | 2 +- packages/native-core/src/real_dom.rs | 13 ++++++++++++- packages/tui/Cargo.toml | 4 +++- packages/tui/src/focus.rs | 16 +++++++++++----- packages/tui/src/layout.rs | 15 ++++++++------- packages/tui/src/lib.rs | 10 +++++----- packages/tui/src/prevent_default.rs | 9 ++++++--- packages/tui/src/render.rs | 13 +++++++------ packages/tui/src/style_attributes.rs | 9 ++++++--- 9 files changed, 59 insertions(+), 32 deletions(-) diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index e1e104bf3..6beb56a2f 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -240,9 +240,9 @@ let get_child_view = { fn workload_system(type_id: std::any::TypeId, dependants: dioxus_native_core::exports::FxHashSet, pass_direction: dioxus_native_core::PassDirection) -> dioxus_native_core::exports::shipyard::WorkloadSystem { use dioxus_native_core::exports::shipyard::{IntoWorkloadSystem, Get, AddComponent}; use dioxus_native_core::tree::TreeRef; + use dioxus_native_core::prelude::{NodeType, NodeView}; (move |data: #combined_dependencies_quote, run_view: dioxus_native_core::RunPassView #trait_generics| { - println!("Running system for {:?}", type_id); let (#(#split_views,)*) = data; let (tree, types, _, _, _) = &run_view; let tree = tree.clone(); diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index fc077d143..d721428ea 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -311,6 +311,16 @@ impl RealDom { pub fn add_node_watcher(&mut self, watcher: impl NodeWatcher + 'static + Send + Sync) { self.node_watchers.write().unwrap().push(Box::new(watcher)); } + + /// Returns a reference to the underlying world. Any changes made to the world will not update the reactive system. + pub fn raw_world(&self) -> &World { + &self.world + } + + /// Returns a mutable reference to the underlying world. Any changes made to the world will not update the reactive system. + pub fn raw_world_mut(&mut self) -> &mut World { + &mut self.world + } } pub struct ViewEntry<'a, V: Component + Send + Sync> { @@ -371,7 +381,8 @@ pub trait NodeImmutable: Sized { fn get<'a, T: Component + Sync + Send>(&'a self) -> Option> { // self.real_dom().tree.get(self.id()) let view: View<'a, T> = self.real_dom().borrow_raw().ok()?; - Some(ViewEntry::new(view, self.id())) + view.contains(self.id()) + .then(|| ViewEntry::new(view, self.id())) } #[inline] diff --git a/packages/tui/Cargo.toml b/packages/tui/Cargo.toml index 46a868301..104701173 100644 --- a/packages/tui/Cargo.toml +++ b/packages/tui/Cargo.toml @@ -17,6 +17,7 @@ dioxus = { path = "../dioxus", version = "^0.3.0", optional = true } dioxus-core = { path = "../core", version = "^0.3.0", features = ["serialize"], optional = true } dioxus-html = { path = "../html", version = "^0.3.0" } dioxus-native-core = { path = "../native-core", version = "^0.2.0" } +dioxus-native-core-macro = { path = "../native-core-macro", version = "^0.3.0" } dioxus-hot-reload = { path = "../hot-reload", optional = true } tui = "0.17.0" @@ -29,6 +30,7 @@ smallvec = "1.6" rustc-hash = "1.1.0" anymap = "1.0.0-beta.2" futures-channel = "0.3.25" +shipyard = "0.6.2" [dev-dependencies] dioxus = { path = "../dioxus" } @@ -42,4 +44,4 @@ harness = false [features] default = ["hot-reload", "dioxus-bindings"] hot-reload = ["dioxus-hot-reload"] -dioxus-bindings = ["dioxus", "dioxus-core", "dioxus-native-core/dioxus"] \ No newline at end of file +dioxus-bindings = ["dioxus", "dioxus-core", "dioxus-native-core/dioxus"] diff --git a/packages/tui/src/focus.rs b/packages/tui/src/focus.rs index dc845c8aa..1f2cb973f 100644 --- a/packages/tui/src/focus.rs +++ b/packages/tui/src/focus.rs @@ -6,11 +6,15 @@ use dioxus_native_core::{ utils::{ElementProduced, PersistantElementIter}, Dependancy, NodeId, RealDom, SendAnyMap, State, }; +use dioxus_native_core_macro::partial_derive_state; +use shipyard::Component; +use shipyard::{Get, ViewMut}; use std::{cmp::Ordering, num::NonZeroU16}; use dioxus_native_core::node_ref::NodeView; +#[derive(Component)] pub struct Focused(pub bool); #[derive(Clone, Copy, PartialEq, Eq, Debug)] @@ -58,11 +62,12 @@ impl Default for FocusLevel { } } -#[derive(Clone, PartialEq, Debug, Default)] +#[derive(Clone, PartialEq, Debug, Default, Component)] pub(crate) struct Focus { pub level: FocusLevel, } +#[partial_derive_state] impl State for Focus { const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new() .with_attrs(AttributeMaskBuilder::Some(FOCUS_ATTRIBUTES)) @@ -77,7 +82,7 @@ impl State for Focus { node_view: NodeView, _: ::ElementBorrowed<'a>, _: Option<::ElementBorrowed<'a>>, - _: Option::ElementBorrowed<'a>>>, + _: Vec<::ElementBorrowed<'a>>, _: &SendAnyMap, ) -> bool { let new = Focus { @@ -126,7 +131,7 @@ impl State for Focus { node_view: NodeView<()>, node: ::ElementBorrowed<'a>, parent: Option<::ElementBorrowed<'a>>, - children: Option::ElementBorrowed<'a>>>, + children: Vec<::ElementBorrowed<'a>>, context: &SendAnyMap, ) -> Self { let mut myself = Self::default(); @@ -159,7 +164,7 @@ impl FocusState { /// Returns true if the focus has changed. pub fn progress(&mut self, rdom: &mut RealDom, forward: bool) -> bool { if let Some(last) = self.last_focused_id { - if rdom.get(last).unwrap().get::().copied() + if rdom.get(last).unwrap().get::().map(|p| *p) == Some(PreventDefault::KeyDown) { return false; @@ -257,7 +262,8 @@ impl FocusState { } node.insert(Focused(true)); if let Some(old) = self.last_focused_id.replace(id) { - let focused = rdom.get_state_mut_raw::(old).unwrap(); + let mut focused_borrow: ViewMut = rdom.raw_world().borrow().unwrap(); + let focused = (&mut focused_borrow).get(old).unwrap(); focused.0 = false; } // reset the position to the currently focused element diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index 5c334b5c8..286c129f1 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -1,11 +1,13 @@ use std::sync::{Arc, Mutex}; +use dioxus_native_core::exports::shipyard::Component; use dioxus_native_core::layout_attributes::{ apply_layout_attributes_cfg, BorderWidths, LayoutConfigeration, }; use dioxus_native_core::node::OwnedAttributeView; use dioxus_native_core::node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}; use dioxus_native_core::{Dependancy, SendAnyMap, State}; +use dioxus_native_core_macro::partial_derive_state; use taffy::prelude::*; use crate::{screen_to_layout_space, unit_to_layout_space}; @@ -36,12 +38,13 @@ impl Default for PossiblyUninitalized { } } -#[derive(Clone, PartialEq, Default, Debug)] +#[derive(Clone, PartialEq, Default, Debug, Component)] pub(crate) struct TaffyLayout { pub style: Style, pub node: PossiblyUninitalized, } +#[partial_derive_state] impl State for TaffyLayout { type ChildDependencies = (Self,); type ParentDependencies = (); @@ -56,7 +59,7 @@ impl State for TaffyLayout { node_view: NodeView, _: ::ElementBorrowed<'a>, _: Option<::ElementBorrowed<'a>>, - children: Option::ElementBorrowed<'a>>>, + children: Vec<::ElementBorrowed<'a>>, ctx: &SendAnyMap, ) -> bool { let mut changed = false; @@ -110,10 +113,8 @@ impl State for TaffyLayout { // Set all direct nodes as our children let mut child_layout = vec![]; - if let Some(children) = children { - for (l,) in children { - child_layout.push(l.node.unwrap()); - } + for (l,) in children { + child_layout.push(l.node.unwrap()); } fn scale_dimention(d: Dimension) -> Dimension { @@ -194,7 +195,7 @@ impl State for TaffyLayout { node_view: NodeView<()>, node: ::ElementBorrowed<'a>, parent: Option<::ElementBorrowed<'a>>, - children: Option::ElementBorrowed<'a>>>, + children: Vec<::ElementBorrowed<'a>>, context: &SendAnyMap, ) -> Self { let mut myself = Self::default(); diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 864d92864..6112f3bf5 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -7,7 +7,7 @@ use crossterm::{ terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; use dioxus_html::EventData; -use dioxus_native_core::{node_ref::NodeMaskBuilder, real_dom::NodeImmutable, State}; +use dioxus_native_core::{node_ref::NodeMaskBuilder, prelude::AnyState, real_dom::NodeImmutable}; use dioxus_native_core::{real_dom::RealDom, FxDashSet, NodeId, SendAnyMap}; use focus::FocusState; use futures::{channel::mpsc::UnboundedSender, pin_mut, Future, StreamExt}; @@ -123,7 +123,7 @@ pub fn render( let mut any_map = SendAnyMap::new(); any_map.insert(taffy.clone()); let mut rdom = rdom.write().unwrap(); - let _ = rdom.update_state(any_map, false); + let _ = rdom.update_state(any_map); } tokio::runtime::Builder::new_current_thread() @@ -148,7 +148,7 @@ pub fn render( } let mut to_rerender = FxDashSet::default(); - to_rerender.insert(NodeId(0)); + to_rerender.insert(rdom.read().unwrap().root_id()); let mut updated = true; loop { @@ -167,7 +167,7 @@ pub fn render( let width = screen_to_layout_space(dims.width); let height = screen_to_layout_space(dims.height); let root_node = rdom - .get(NodeId(0)) + .get(rdom.root_id()) .unwrap() .get::() .unwrap() @@ -270,7 +270,7 @@ pub fn render( let mut rdom = rdom.write().unwrap(); let mut any_map = SendAnyMap::new(); any_map.insert(taffy.clone()); - let (new_to_rerender, dirty) = rdom.update_state(any_map, false); + let (new_to_rerender, dirty) = rdom.update_state(any_map); to_rerender = new_to_rerender; let text_mask = NodeMaskBuilder::new().with_text().build(); for (id, mask) in dirty { diff --git a/packages/tui/src/prevent_default.rs b/packages/tui/src/prevent_default.rs index e16a5c0d7..8e1761c5f 100644 --- a/packages/tui/src/prevent_default.rs +++ b/packages/tui/src/prevent_default.rs @@ -1,6 +1,8 @@ use dioxus_native_core::{node_ref::NodeView, Dependancy, SendAnyMap, State}; +use dioxus_native_core_macro::partial_derive_state; +use shipyard::Component; -#[derive(PartialEq, Debug, Clone, Copy)] +#[derive(PartialEq, Debug, Clone, Copy, Component)] pub(crate) enum PreventDefault { Focus, KeyPress, @@ -25,6 +27,7 @@ impl Default for PreventDefault { } } +#[partial_derive_state] impl State for PreventDefault { type ParentDependencies = (); type ChildDependencies = (); @@ -42,7 +45,7 @@ impl State for PreventDefault { node_view: NodeView, _: ::ElementBorrowed<'a>, _: Option<::ElementBorrowed<'a>>, - _: Option::ElementBorrowed<'a>>>, + _: Vec<::ElementBorrowed<'a>>, _: &SendAnyMap, ) -> bool { let new = match node_view.attributes().and_then(|mut attrs| { @@ -78,7 +81,7 @@ impl State for PreventDefault { node_view: NodeView<()>, node: ::ElementBorrowed<'a>, parent: Option<::ElementBorrowed<'a>>, - children: Option::ElementBorrowed<'a>>>, + children: Vec<::ElementBorrowed<'a>>, context: &SendAnyMap, ) -> Self { let mut myself = Self::default(); diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index a739d2621..28471a17c 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -28,7 +28,7 @@ pub(crate) fn render_vnode( ) { use dioxus_native_core::node::NodeType; - if let NodeType::Placeholder = &node.node_type() { + if let NodeType::Placeholder = &*node.node_type() { return; } @@ -47,7 +47,7 @@ pub(crate) fn render_vnode( 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_type() { + match &*node.node_type() { NodeType::Text(text) => { #[derive(Default)] struct Label<'a> { @@ -272,10 +272,11 @@ impl RinkWidget for NodeRef<'_> { if let Some(c) = self.get::().unwrap().core.bg { new_cell.bg = c; } - let focused = self.get::(); - if focused.is_some() && focused.unwrap().0 { - new_cell.bg.alpha = 100; - new_cell.bg.color = new_cell.bg.blend(Color::White); + if let Some(focused) = self.get::() { + if focused.0 { + new_cell.bg.alpha = 100; + new_cell.bg.color = new_cell.bg.blend(Color::White); + } } buf.set(x, y, new_cell); } diff --git a/packages/tui/src/style_attributes.rs b/packages/tui/src/style_attributes.rs index b03dcc6a4..77fda7d91 100644 --- a/packages/tui/src/style_attributes.rs +++ b/packages/tui/src/style_attributes.rs @@ -35,16 +35,19 @@ use dioxus_native_core::{ node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}, Dependancy, SendAnyMap, State, }; +use dioxus_native_core_macro::partial_derive_state; +use shipyard::Component; use taffy::prelude::*; use crate::style::{RinkColor, RinkStyle}; -#[derive(Default, Clone, PartialEq, Debug)] +#[derive(Default, Clone, PartialEq, Debug, Component)] pub struct StyleModifier { pub core: RinkStyle, pub modifier: TuiModifier, } +#[partial_derive_state] impl State for StyleModifier { type ParentDependencies = (Self,); type ChildDependencies = (); @@ -60,7 +63,7 @@ impl State for StyleModifier { node_view: NodeView, _: ::ElementBorrowed<'a>, parent: Option<::ElementBorrowed<'a>>, - _: Option::ElementBorrowed<'a>>>, + _: Vec<::ElementBorrowed<'a>>, _: &SendAnyMap, ) -> bool { let mut new = StyleModifier::default(); @@ -117,7 +120,7 @@ impl State for StyleModifier { node_view: NodeView<()>, node: ::ElementBorrowed<'a>, parent: Option<::ElementBorrowed<'a>>, - children: Option::ElementBorrowed<'a>>>, + children: Vec<::ElementBorrowed<'a>>, context: &SendAnyMap, ) -> Self { let mut myself = Self::default(); From 0f5833d4c0bf864362058aafe433d3e55e1ca9ed Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 12 Mar 2023 16:56:02 -0500 Subject: [PATCH 062/103] Improve benchmarks to also test for updating many elements at once --- packages/tui/benches/update.rs | 87 ++++++++++++++++++++++++---------- 1 file changed, 62 insertions(+), 25 deletions(-) diff --git a/packages/tui/benches/update.rs b/packages/tui/benches/update.rs index ea687e72a..348277328 100644 --- a/packages/tui/benches/update.rs +++ b/packages/tui/benches/update.rs @@ -7,20 +7,53 @@ criterion_main!(mbenches); /// This benchmarks the cache performance of the TUI for small edits by changing one box at a time. fn tui_update(c: &mut Criterion) { - let mut group = c.benchmark_group("Update boxes"); + { + let mut group = c.benchmark_group("Update boxes"); - // We can also use loops to define multiple benchmarks, even over multiple dimensions. - for size in 1..=20usize { - let parameter_string = format!("{}", (size).pow(2)); - group.bench_with_input( - BenchmarkId::new("size", parameter_string), - &size, - |b, size| { - b.iter(|| { - dioxus_tui::launch_cfg_with_props(app, *size, Config::default().with_headless()) - }) - }, - ); + for size in 1..=20usize { + let parameter_string = format!("{}", (size).pow(2)); + group.bench_with_input( + BenchmarkId::new("size", parameter_string), + &size, + |b, size| { + b.iter(|| { + dioxus_tui::launch_cfg_with_props( + app, + GridProps { + size: *size, + update_count: 1, + }, + Config::default().with_headless(), + ) + }) + }, + ); + } + } + + { + let mut group = c.benchmark_group("Update many boxes"); + + for update_count in 1..=20usize { + let update_count = update_count * 20; + let parameter_string = update_count.to_string(); + group.bench_with_input( + BenchmarkId::new("update count", parameter_string), + &update_count, + |b, update_count| { + b.iter(|| { + dioxus_tui::launch_cfg_with_props( + app, + GridProps { + size: 20, + update_count: *update_count, + }, + Config::default().with_headless(), + ) + }) + }, + ); + } } } @@ -58,6 +91,7 @@ fn Box(cx: Scope) -> Element { #[derive(Props, PartialEq)] struct GridProps { size: usize, + update_count: usize, } #[allow(non_snake_case)] fn Grid(cx: Scope) -> Element { @@ -66,18 +100,20 @@ fn Grid(cx: Scope) -> Element { let counts = use_ref(cx, || vec![0; size * size]); let ctx: TuiContext = cx.consume_context().unwrap(); - if *count.get() + 1 >= (size * size) { + if *count.get() + cx.props.update_count >= (size * size) { ctx.quit(); } else { - counts.with_mut(|c| { - let i = *count.current(); - c[i] += 1; - c[i] %= 360; - }); - count.with_mut(|i| { - *i += 1; - *i %= size * size; - }); + for _ in 0..cx.props.update_count { + counts.with_mut(|c| { + let i = *count.current(); + c[i] += 1; + c[i] %= 360; + }); + count.with_mut(|i| { + *i += 1; + *i %= size * size; + }); + } } render! { @@ -115,13 +151,14 @@ fn Grid(cx: Scope) -> Element { } } -fn app(cx: Scope) -> Element { +fn app(cx: Scope) -> Element { cx.render(rsx! { div{ width: "100%", height: "100%", Grid{ - size: *cx.props, + size: cx.props.size, + update_count: cx.props.update_count, } } }) From ad374a8b9d41e79c6522882cd7f0d8484815b608 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 13 Mar 2023 10:03:45 -0500 Subject: [PATCH 063/103] fix formatting and lints --- packages/native-core-macro/src/lib.rs | 113 ++++++++++++++++++++------ packages/native-core/src/lib.rs | 2 +- 2 files changed, 89 insertions(+), 26 deletions(-) diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index 21f28623b..a5d6af001 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -4,7 +4,7 @@ use std::collections::HashSet; use proc_macro::TokenStream; use quote::{format_ident, quote}; -use syn::{parse_macro_input, ItemImpl, Type, TypeTuple, TypePath}; +use syn::{parse_macro_input, ItemImpl, Type, TypePath, TypeTuple}; /// A helper attribute for deriving `State` for a struct. #[proc_macro_attribute] @@ -46,7 +46,8 @@ pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream { .expect("NodeDependencies must be defined"); let this_type = &impl_block.self_ty; - let this_type = extract_type_path(this_type).unwrap_or_else(|| panic!("Self must be a type path, found {}", quote!(#this_type))); + let this_type = extract_type_path(this_type) + .unwrap_or_else(|| panic!("Self must be a type path, found {}", quote!(#this_type))); let mut combined_dependencies = HashSet::new(); @@ -56,7 +57,12 @@ pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream { Some(tuple) => { let mut parent_dependencies = Vec::new(); for type_ in &tuple.elems { - let mut type_ = extract_type_path(type_).unwrap_or_else(|| panic!("ParentDependencies must be a tuple of type paths, found {}", quote!(#type_))); + let mut type_ = extract_type_path(type_).unwrap_or_else(|| { + panic!( + "ParentDependencies must be a tuple of type paths, found {}", + quote!(#type_) + ) + }); if type_ == self_path { type_ = this_type.clone(); } @@ -65,13 +71,21 @@ pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream { } parent_dependencies } - _ => panic!("ParentDependencies must be a tuple, found {}", quote!(#parent_dependencies)), + _ => panic!( + "ParentDependencies must be a tuple, found {}", + quote!(#parent_dependencies) + ), }; let child_dependencies = match extract_tuple(child_dependencies) { Some(tuple) => { let mut child_dependencies = Vec::new(); for type_ in &tuple.elems { - let mut type_ = extract_type_path(type_).unwrap_or_else(|| panic!("ChildDependencies must be a tuple of type paths, found {}", quote!(#type_))); + let mut type_ = extract_type_path(type_).unwrap_or_else(|| { + panic!( + "ChildDependencies must be a tuple of type paths, found {}", + quote!(#type_) + ) + }); if type_ == self_path { type_ = this_type.clone(); } @@ -80,13 +94,21 @@ pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream { } child_dependencies } - _ => panic!("ChildDependencies must be a tuple, found {}", quote!(#child_dependencies)), + _ => panic!( + "ChildDependencies must be a tuple, found {}", + quote!(#child_dependencies) + ), }; let node_dependencies = match extract_tuple(node_dependencies) { Some(tuple) => { let mut node_dependencies = Vec::new(); for type_ in &tuple.elems { - let mut type_ = extract_type_path(type_).unwrap_or_else(|| panic!("NodeDependencies must be a tuple of type paths, found {}", quote!(#type_))); + let mut type_ = extract_type_path(type_).unwrap_or_else(|| { + panic!( + "NodeDependencies must be a tuple of type paths, found {}", + quote!(#type_) + ) + }); if type_ == self_path { type_ = this_type.clone(); } @@ -95,7 +117,10 @@ pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream { } node_dependencies } - _ => panic!("NodeDependencies must be a tuple, found {}", quote!(#node_dependencies)), + _ => panic!( + "NodeDependencies must be a tuple, found {}", + quote!(#node_dependencies) + ), }; combined_dependencies.insert(this_type.clone()); @@ -146,11 +171,11 @@ pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream { .map(|i| { let ident = format_ident!("__data{}", i); if i == this_type_idx { - quote!{mut #ident} + quote! {mut #ident} } else { - quote!{#ident} + quote! {#ident} } - }) + }) .collect(); let node_view = node_dependencies_idxes @@ -174,6 +199,19 @@ pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream { } } }; + let deref_node_view = { + if node_dependencies.is_empty() { + quote! { + let node = raw_node; + } + } else { + let indexes = (0..node_dependencies.len()).map(syn::Index::from); + quote! { + let node = unsafe { (#(dioxus_native_core::prelude::DependancyView::new(&*raw_node.#indexes),)*) }; + } + } + }; + let parent_view = parent_dependancies_idxes .iter() .map(|i| format_ident!("__data{}", i)) @@ -198,16 +236,29 @@ pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream { } } }; - let child_view = child_dependencies_idxes - .iter() - .map(|i| format_ident!("__data{}", i)) - .collect::>(); -let get_child_view = { - if child_dependencies.is_empty() { - quote! { - let raw_children: Vec<_> = tree.children_ids(id).into_iter().map(|_| ()).collect(); + let deref_parent_view = { + if parent_dependencies.is_empty() { + quote! { + let parent = raw_parent; + } + } else { + let indexes = (0..parent_dependencies.len()).map(syn::Index::from); + quote! { + let parent = unsafe { raw_parent.map(|raw_parent| (#(dioxus_native_core::prelude::DependancyView::new(&*raw_parent.#indexes),)*)) }; + } } - } else { + }; + + let child_view = child_dependencies_idxes + .iter() + .map(|i| format_ident!("__data{}", i)) + .collect::>(); + let get_child_view = { + if child_dependencies.is_empty() { + quote! { + let raw_children: Vec<_> = tree.children_ids(id).into_iter().map(|_| ()).collect(); + } + } else { let temps = (0..child_dependencies.len()) .map(|i| format_ident!("__temp{}", i)) .collect::>(); @@ -222,6 +273,18 @@ let get_child_view = { } } }; + let deref_child_view = { + if child_dependencies.is_empty() { + quote! { + let children = raw_children; + } + } else { + let indexes = (0..child_dependencies.len()).map(syn::Index::from); + quote! { + let children = unsafe { raw_children.iter().map(|raw_children| (#(dioxus_native_core::prelude::DependancyView::new(&*raw_children.#indexes),)*)).collect::>() }; + } + } + }; let trait_generics = trait_ .as_ref() @@ -258,10 +321,10 @@ let get_child_view = { #get_parent_view #get_child_view - let myself: Option<&mut Self> = unsafe { raw_myself.map(|val| std::mem::transmute(val)) }; - let node = unsafe { std::mem::transmute(raw_node) }; - let parent = unsafe { raw_parent.map(|val| std::mem::transmute(val)) }; - let children = unsafe { std::mem::transmute(raw_children) }; + let myself: Option<&mut Self> = unsafe { raw_myself.map(|val| &mut *val) }; + #deref_node_view + #deref_parent_view + #deref_child_view let view = NodeView::new(id, node_data, &node_mask); if let Some(myself) = myself { @@ -296,4 +359,4 @@ fn extract_type_path(ty: &Type) -> Option { Type::Group(group) => extract_type_path(&group.elem), _ => None, } -} \ No newline at end of file +} diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 34a2332e9..955e1ba6a 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -29,7 +29,7 @@ pub mod exports { pub mod prelude { pub use crate::node::{ElementNode, FromAnyValue, NodeType, OwnedAttributeView, TextNode}; pub use crate::node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}; - pub use crate::passes::{AnyState, Dependancy, State}; + pub use crate::passes::{AnyState, Dependancy, DependancyView, State}; pub use crate::real_dom::{NodeImmutable, NodeMut, NodeRef, RealDom}; pub use crate::NodeId; pub use crate::SendAnyMap; From 9121d68d84b82f6d6ebd2025927549ec6e37506c Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 13 Mar 2023 10:04:01 -0500 Subject: [PATCH 064/103] fix focus events subscriptions --- packages/tui/Cargo.toml | 1 + packages/tui/src/focus.rs | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/tui/Cargo.toml b/packages/tui/Cargo.toml index d7edc0e28..05d3a6e12 100644 --- a/packages/tui/Cargo.toml +++ b/packages/tui/Cargo.toml @@ -31,6 +31,7 @@ rustc-hash = "1.1.0" anymap = "1.0.0-beta.2" futures-channel = "0.3.25" shipyard = { version = "0.6.2", features = ["proc", "std"], default-features = false } +once_cell = "1.17.1" [dev-dependencies] dioxus = { path = "../dioxus" } diff --git a/packages/tui/src/focus.rs b/packages/tui/src/focus.rs index 1f2cb973f..919182e4f 100644 --- a/packages/tui/src/focus.rs +++ b/packages/tui/src/focus.rs @@ -7,6 +7,8 @@ use dioxus_native_core::{ Dependancy, NodeId, RealDom, SendAnyMap, State, }; use dioxus_native_core_macro::partial_derive_state; +use once_cell::sync::Lazy; +use rustc_hash::FxHashSet; use shipyard::Component; use shipyard::{Get, ViewMut}; @@ -108,9 +110,7 @@ impl State for Focus { } else if node_view .listeners() .and_then(|mut listeners| { - listeners - .any(|l| FOCUS_EVENTS.binary_search(&l).is_ok()) - .then_some(()) + listeners.any(|l| FOCUS_EVENTS.contains(&l)).then_some(()) }) .is_some() { @@ -140,7 +140,8 @@ impl State for Focus { } } -const FOCUS_EVENTS: &[&str] = &["keydown", "keypress", "keyup"]; +static FOCUS_EVENTS: Lazy> = + Lazy::new(|| ["keydown", "keypress", "keyup"].into_iter().collect()); const FOCUS_ATTRIBUTES: &[&str] = &["tabindex"]; pub(crate) struct FocusState { From 080a9bdba026847d61fe43b3c5570f87cf1450e9 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 13 Mar 2023 10:14:31 -0500 Subject: [PATCH 065/103] fix driven example --- packages/tui/examples/driven.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/tui/examples/driven.rs b/packages/tui/examples/driven.rs index 1a258c3f6..8d79e6224 100644 --- a/packages/tui/examples/driven.rs +++ b/packages/tui/examples/driven.rs @@ -10,7 +10,7 @@ use rustc_hash::FxHashSet; use std::rc::Rc; use std::sync::{Arc, RwLock}; -const SIZE: usize = 10; +const SIZE: usize = 200; struct Test { node_states: [[usize; SIZE]; SIZE], @@ -189,10 +189,10 @@ impl Renderer for Test { let root_id = rdom.root_id(); let mut root = rdom.get_mut(root_id).unwrap(); for (x, y) in self.dirty.drain() { - let row_id = root.child_ids().unwrap()[x]; + let row_id = root.child_ids()[x]; let rdom = root.real_dom_mut(); let row = rdom.get(row_id).unwrap(); - let node_id = row.child_ids().unwrap()[y]; + let node_id = row.child_ids()[y]; let mut node = rdom.get_mut(node_id).unwrap(); if let NodeTypeMut::Element(mut el) = node.node_type_mut() { el.set_attribute( @@ -208,9 +208,10 @@ impl Renderer for Test { )), ); } - let text_id = *node.child_ids().unwrap().first().unwrap(); + let text_id = *node.child_ids().first().unwrap(); let mut text = rdom.get_mut(text_id).unwrap(); - if let NodeTypeMut::Text(text) = text.node_type_mut() { + let type_mut = text.node_type_mut(); + if let NodeTypeMut::Text(mut text) = type_mut { *text = self.node_states[x][y].to_string(); } } @@ -229,14 +230,12 @@ impl Renderer for Test { if let Some(parent) = node.parent() { let child_number = parent .child_ids() - .unwrap() .iter() .position(|id| *id == node.id()) .unwrap(); if let Some(parents_parent) = parent.parent() { let parents_child_number = parents_parent .child_ids() - .unwrap() .iter() .position(|id| *id == parent.id()) .unwrap(); From 8e8c89c99e0f75534cad8da63adc47ff21d42f1b Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 13 Mar 2023 10:21:30 -0500 Subject: [PATCH 066/103] shink grid size in driven example --- packages/tui/examples/driven.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tui/examples/driven.rs b/packages/tui/examples/driven.rs index 8d79e6224..b5d231888 100644 --- a/packages/tui/examples/driven.rs +++ b/packages/tui/examples/driven.rs @@ -10,7 +10,7 @@ use rustc_hash::FxHashSet; use std::rc::Rc; use std::sync::{Arc, RwLock}; -const SIZE: usize = 200; +const SIZE: usize = 10; struct Test { node_states: [[usize; SIZE]; SIZE], From f34fa5d9744895b049c05160b8791355af87925a Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 13 Mar 2023 12:04:51 -0500 Subject: [PATCH 067/103] Move dioxus-specifc code into dioxus-tui crate and rename core crate to Rink --- Cargo.toml | 3 +- docs/guide/Cargo.toml | 2 +- packages/{tui => dioxus-tui}/.gitignore | 0 .../.vscode/spellright.dict | 0 packages/{tui => dioxus-tui}/Cargo.toml | 18 +--- packages/{tui => dioxus-tui}/README.md | 0 .../{tui => dioxus-tui}/benches/update.rs | 0 .../examples/all_events.rs | 0 .../{tui => dioxus-tui}/examples/border.rs | 0 .../{tui => dioxus-tui}/examples/buttons.rs | 0 .../examples/color_test.rs | 0 .../examples/colorpicker.rs | 0 .../examples/components.rs | 0 .../{tui => dioxus-tui}/examples/example.png | Bin packages/{tui => dioxus-tui}/examples/flex.rs | 0 .../{tui => dioxus-tui}/examples/hover.rs | 0 packages/{tui => dioxus-tui}/examples/list.rs | 0 .../{tui => dioxus-tui}/examples/margin.rs | 0 .../{tui => dioxus-tui}/examples/quadrants.rs | 0 .../{tui => dioxus-tui}/examples/readme.rs | 0 .../{tui => dioxus-tui}/examples/stress.rs | 0 packages/{tui => dioxus-tui}/examples/task.rs | 0 packages/{tui => dioxus-tui}/examples/text.rs | 0 .../{tui => dioxus-tui}/examples/widgets.rs | 1 - .../src/dioxus.rs => dioxus-tui/src/lib.rs} | 13 +-- packages/dioxus-tui/src/prelude/mod.rs | 2 + .../{tui => dioxus-tui}/src/widgets/button.rs | 0 .../src/widgets/checkbox.rs | 0 .../{tui => dioxus-tui}/src/widgets/input.rs | 0 .../{tui => dioxus-tui}/src/widgets/mod.rs | 0 .../{tui => dioxus-tui}/src/widgets/number.rs | 2 +- .../src/widgets/password.rs | 2 +- .../{tui => dioxus-tui}/src/widgets/slider.rs | 2 +- .../src/widgets/textbox.rs | 2 +- packages/{tui => dioxus-tui}/test.html | 0 packages/{tui => dioxus-tui}/tests/events.rs | 0 packages/rink/.gitignore | 2 + packages/rink/.vscode/spellright.dict | 2 + packages/rink/Cargo.toml | 39 ++++++++ packages/rink/README.md | 90 ++++++++++++++++++ packages/{tui => rink}/examples/driven.rs | 0 packages/rink/examples/example.png | Bin 0 -> 107349 bytes packages/{tui => rink}/src/config.rs | 9 +- packages/{tui => rink}/src/focus.rs | 9 +- packages/{tui => rink}/src/hooks.rs | 0 packages/{tui => rink}/src/layout.rs | 0 packages/{tui => rink}/src/lib.rs | 12 +-- packages/{tui => rink}/src/prelude/mod.rs | 0 packages/{tui => rink}/src/prevent_default.rs | 9 +- packages/{tui => rink}/src/query.rs | 4 + packages/{tui => rink}/src/render.rs | 0 packages/{tui => rink}/src/style.rs | 0 .../{tui => rink}/src/style_attributes.rs | 0 packages/{tui => rink}/src/widget.rs | 0 packages/rink/test.html | 73 ++++++++++++++ 55 files changed, 241 insertions(+), 55 deletions(-) rename packages/{tui => dioxus-tui}/.gitignore (100%) rename packages/{tui => dioxus-tui}/.vscode/spellright.dict (100%) rename packages/{tui => dioxus-tui}/Cargo.toml (70%) rename packages/{tui => dioxus-tui}/README.md (100%) rename packages/{tui => dioxus-tui}/benches/update.rs (100%) rename packages/{tui => dioxus-tui}/examples/all_events.rs (100%) rename packages/{tui => dioxus-tui}/examples/border.rs (100%) rename packages/{tui => dioxus-tui}/examples/buttons.rs (100%) rename packages/{tui => dioxus-tui}/examples/color_test.rs (100%) rename packages/{tui => dioxus-tui}/examples/colorpicker.rs (100%) rename packages/{tui => dioxus-tui}/examples/components.rs (100%) rename packages/{tui => dioxus-tui}/examples/example.png (100%) rename packages/{tui => dioxus-tui}/examples/flex.rs (100%) rename packages/{tui => dioxus-tui}/examples/hover.rs (100%) rename packages/{tui => dioxus-tui}/examples/list.rs (100%) rename packages/{tui => dioxus-tui}/examples/margin.rs (100%) rename packages/{tui => dioxus-tui}/examples/quadrants.rs (100%) rename packages/{tui => dioxus-tui}/examples/readme.rs (100%) rename packages/{tui => dioxus-tui}/examples/stress.rs (100%) rename packages/{tui => dioxus-tui}/examples/task.rs (100%) rename packages/{tui => dioxus-tui}/examples/text.rs (100%) rename packages/{tui => dioxus-tui}/examples/widgets.rs (99%) rename packages/{tui/src/dioxus.rs => dioxus-tui/src/lib.rs} (94%) create mode 100644 packages/dioxus-tui/src/prelude/mod.rs rename packages/{tui => dioxus-tui}/src/widgets/button.rs (100%) rename packages/{tui => dioxus-tui}/src/widgets/checkbox.rs (100%) rename packages/{tui => dioxus-tui}/src/widgets/input.rs (100%) rename packages/{tui => dioxus-tui}/src/widgets/mod.rs (100%) rename packages/{tui => dioxus-tui}/src/widgets/number.rs (99%) rename packages/{tui => dioxus-tui}/src/widgets/password.rs (99%) rename packages/{tui => dioxus-tui}/src/widgets/slider.rs (99%) rename packages/{tui => dioxus-tui}/src/widgets/textbox.rs (99%) rename packages/{tui => dioxus-tui}/test.html (100%) rename packages/{tui => dioxus-tui}/tests/events.rs (100%) create mode 100644 packages/rink/.gitignore create mode 100644 packages/rink/.vscode/spellright.dict create mode 100644 packages/rink/Cargo.toml create mode 100644 packages/rink/README.md rename packages/{tui => rink}/examples/driven.rs (100%) create mode 100644 packages/rink/examples/example.png rename packages/{tui => rink}/src/config.rs (91%) rename packages/{tui => rink}/src/focus.rs (98%) rename packages/{tui => rink}/src/hooks.rs (100%) rename packages/{tui => rink}/src/layout.rs (100%) rename packages/{tui => rink}/src/lib.rs (98%) rename packages/{tui => rink}/src/prelude/mod.rs (100%) rename packages/{tui => rink}/src/prevent_default.rs (94%) rename packages/{tui => rink}/src/query.rs (96%) rename packages/{tui => rink}/src/render.rs (100%) rename packages/{tui => rink}/src/style.rs (100%) rename packages/{tui => rink}/src/style_attributes.rs (100%) rename packages/{tui => rink}/src/widget.rs (100%) create mode 100644 packages/rink/test.html diff --git a/Cargo.toml b/Cargo.toml index e32940e18..7b6a08a39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,8 @@ members = [ "packages/liveview", "packages/autofmt", "packages/rsx", - "packages/tui", + "packages/dioxus-tui", + "packages/rink", "packages/native-core", "packages/native-core-macro", "packages/rsx-rosetta", diff --git a/docs/guide/Cargo.toml b/docs/guide/Cargo.toml index 87d5b9006..c9707e0d7 100644 --- a/docs/guide/Cargo.toml +++ b/docs/guide/Cargo.toml @@ -13,7 +13,7 @@ dioxus-web = { path = "../../packages/web" } dioxus-ssr = { path = "../../packages/ssr" } dioxus-router = { path = "../../packages/router" } dioxus-liveview = { path = "../../packages/liveview", features = ["axum"] } -dioxus-tui = { path = "../../packages/tui" } +dioxus-tui = { path = "../../packages/dioxus-tui" } fermi = { path = "../../packages/fermi" } diff --git a/packages/tui/.gitignore b/packages/dioxus-tui/.gitignore similarity index 100% rename from packages/tui/.gitignore rename to packages/dioxus-tui/.gitignore diff --git a/packages/tui/.vscode/spellright.dict b/packages/dioxus-tui/.vscode/spellright.dict similarity index 100% rename from packages/tui/.vscode/spellright.dict rename to packages/dioxus-tui/.vscode/spellright.dict diff --git a/packages/tui/Cargo.toml b/packages/dioxus-tui/Cargo.toml similarity index 70% rename from packages/tui/Cargo.toml rename to packages/dioxus-tui/Cargo.toml index 05d3a6e12..43e568b0f 100644 --- a/packages/tui/Cargo.toml +++ b/packages/dioxus-tui/Cargo.toml @@ -13,25 +13,18 @@ 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.3.0", optional = true } -dioxus-core = { path = "../core", version = "^0.3.0", features = ["serialize"], optional = true } +dioxus = { path = "../dioxus", version = "^0.3.0" } +dioxus-core = { path = "../core", version = "^0.3.0", features = ["serialize"] } dioxus-html = { path = "../html", version = "^0.3.0" } -dioxus-native-core = { path = "../native-core", version = "^0.2.0" } +dioxus-native-core = { path = "../native-core", version = "^0.2.0", features = ["dioxus"] } dioxus-native-core-macro = { path = "../native-core-macro", version = "^0.3.0" } dioxus-hot-reload = { path = "../hot-reload", optional = true } +rink = { path = "../rink" } -tui = "0.17.0" crossterm = "0.23.0" -anyhow = "1.0.42" tokio = { version = "1.15.0", features = ["full"] } futures = "0.3.19" taffy = "0.2.1" -smallvec = "1.6" -rustc-hash = "1.1.0" -anymap = "1.0.0-beta.2" -futures-channel = "0.3.25" -shipyard = { version = "0.6.2", features = ["proc", "std"], default-features = false } -once_cell = "1.17.1" [dev-dependencies] dioxus = { path = "../dioxus" } @@ -43,6 +36,5 @@ name = "update" harness = false [features] -default = ["hot-reload", "dioxus-bindings"] +default = ["hot-reload"] hot-reload = ["dioxus-hot-reload"] -dioxus-bindings = ["dioxus", "dioxus-core", "dioxus-native-core/dioxus"] diff --git a/packages/tui/README.md b/packages/dioxus-tui/README.md similarity index 100% rename from packages/tui/README.md rename to packages/dioxus-tui/README.md diff --git a/packages/tui/benches/update.rs b/packages/dioxus-tui/benches/update.rs similarity index 100% rename from packages/tui/benches/update.rs rename to packages/dioxus-tui/benches/update.rs diff --git a/packages/tui/examples/all_events.rs b/packages/dioxus-tui/examples/all_events.rs similarity index 100% rename from packages/tui/examples/all_events.rs rename to packages/dioxus-tui/examples/all_events.rs diff --git a/packages/tui/examples/border.rs b/packages/dioxus-tui/examples/border.rs similarity index 100% rename from packages/tui/examples/border.rs rename to packages/dioxus-tui/examples/border.rs diff --git a/packages/tui/examples/buttons.rs b/packages/dioxus-tui/examples/buttons.rs similarity index 100% rename from packages/tui/examples/buttons.rs rename to packages/dioxus-tui/examples/buttons.rs diff --git a/packages/tui/examples/color_test.rs b/packages/dioxus-tui/examples/color_test.rs similarity index 100% rename from packages/tui/examples/color_test.rs rename to packages/dioxus-tui/examples/color_test.rs diff --git a/packages/tui/examples/colorpicker.rs b/packages/dioxus-tui/examples/colorpicker.rs similarity index 100% rename from packages/tui/examples/colorpicker.rs rename to packages/dioxus-tui/examples/colorpicker.rs diff --git a/packages/tui/examples/components.rs b/packages/dioxus-tui/examples/components.rs similarity index 100% rename from packages/tui/examples/components.rs rename to packages/dioxus-tui/examples/components.rs diff --git a/packages/tui/examples/example.png b/packages/dioxus-tui/examples/example.png similarity index 100% rename from packages/tui/examples/example.png rename to packages/dioxus-tui/examples/example.png diff --git a/packages/tui/examples/flex.rs b/packages/dioxus-tui/examples/flex.rs similarity index 100% rename from packages/tui/examples/flex.rs rename to packages/dioxus-tui/examples/flex.rs diff --git a/packages/tui/examples/hover.rs b/packages/dioxus-tui/examples/hover.rs similarity index 100% rename from packages/tui/examples/hover.rs rename to packages/dioxus-tui/examples/hover.rs diff --git a/packages/tui/examples/list.rs b/packages/dioxus-tui/examples/list.rs similarity index 100% rename from packages/tui/examples/list.rs rename to packages/dioxus-tui/examples/list.rs diff --git a/packages/tui/examples/margin.rs b/packages/dioxus-tui/examples/margin.rs similarity index 100% rename from packages/tui/examples/margin.rs rename to packages/dioxus-tui/examples/margin.rs diff --git a/packages/tui/examples/quadrants.rs b/packages/dioxus-tui/examples/quadrants.rs similarity index 100% rename from packages/tui/examples/quadrants.rs rename to packages/dioxus-tui/examples/quadrants.rs diff --git a/packages/tui/examples/readme.rs b/packages/dioxus-tui/examples/readme.rs similarity index 100% rename from packages/tui/examples/readme.rs rename to packages/dioxus-tui/examples/readme.rs diff --git a/packages/tui/examples/stress.rs b/packages/dioxus-tui/examples/stress.rs similarity index 100% rename from packages/tui/examples/stress.rs rename to packages/dioxus-tui/examples/stress.rs diff --git a/packages/tui/examples/task.rs b/packages/dioxus-tui/examples/task.rs similarity index 100% rename from packages/tui/examples/task.rs rename to packages/dioxus-tui/examples/task.rs diff --git a/packages/tui/examples/text.rs b/packages/dioxus-tui/examples/text.rs similarity index 100% rename from packages/tui/examples/text.rs rename to packages/dioxus-tui/examples/text.rs diff --git a/packages/tui/examples/widgets.rs b/packages/dioxus-tui/examples/widgets.rs similarity index 99% rename from packages/tui/examples/widgets.rs rename to packages/dioxus-tui/examples/widgets.rs index 793057bc5..04690522c 100644 --- a/packages/tui/examples/widgets.rs +++ b/packages/dioxus-tui/examples/widgets.rs @@ -1,7 +1,6 @@ use dioxus::prelude::*; use dioxus_html::FormData; use dioxus_tui::prelude::*; -use dioxus_tui::Config; fn main() { dioxus_tui::launch_cfg(app, Config::new()); diff --git a/packages/tui/src/dioxus.rs b/packages/dioxus-tui/src/lib.rs similarity index 94% rename from packages/tui/src/dioxus.rs rename to packages/dioxus-tui/src/lib.rs index c07d138d2..99df66df9 100644 --- a/packages/tui/src/dioxus.rs +++ b/packages/dioxus-tui/src/lib.rs @@ -1,3 +1,6 @@ +pub mod prelude; +pub mod widgets; + use std::{ ops::Deref, rc::Rc, @@ -11,7 +14,8 @@ use dioxus_native_core::{ NodeId, RealDom, }; -use crate::{query::Query, render, Config, Renderer, TuiContext}; +pub use rink::Config; +use rink::{query::Query, render, Renderer, TuiContext}; pub fn launch(app: Component<()>) { launch_cfg(app, Config::default()) @@ -29,11 +33,8 @@ pub fn launch_cfg_with_props(app: Component, props: Props }; let dioxus_state = Rc::new(RwLock::new(dioxus_state)); let mut vdom = VirtualDom::new_with_props(app, props) - .with_root_context(TuiContext { tx: event_tx }) - .with_root_context(Query { - rdom: rdom.clone(), - stretch: taffy.clone(), - }) + .with_root_context(TuiContext::new(event_tx)) + .with_root_context(Query::new(rdom.clone(), taffy.clone())) .with_root_context(DioxusElementToNodeId { mapping: dioxus_state.clone(), }); diff --git a/packages/dioxus-tui/src/prelude/mod.rs b/packages/dioxus-tui/src/prelude/mod.rs new file mode 100644 index 000000000..cb4fdee65 --- /dev/null +++ b/packages/dioxus-tui/src/prelude/mod.rs @@ -0,0 +1,2 @@ +pub use crate::widgets::*; +pub use rink::Config; diff --git a/packages/tui/src/widgets/button.rs b/packages/dioxus-tui/src/widgets/button.rs similarity index 100% rename from packages/tui/src/widgets/button.rs rename to packages/dioxus-tui/src/widgets/button.rs diff --git a/packages/tui/src/widgets/checkbox.rs b/packages/dioxus-tui/src/widgets/checkbox.rs similarity index 100% rename from packages/tui/src/widgets/checkbox.rs rename to packages/dioxus-tui/src/widgets/checkbox.rs diff --git a/packages/tui/src/widgets/input.rs b/packages/dioxus-tui/src/widgets/input.rs similarity index 100% rename from packages/tui/src/widgets/input.rs rename to packages/dioxus-tui/src/widgets/input.rs diff --git a/packages/tui/src/widgets/mod.rs b/packages/dioxus-tui/src/widgets/mod.rs similarity index 100% rename from packages/tui/src/widgets/mod.rs rename to packages/dioxus-tui/src/widgets/mod.rs diff --git a/packages/tui/src/widgets/number.rs b/packages/dioxus-tui/src/widgets/number.rs similarity index 99% rename from packages/tui/src/widgets/number.rs rename to packages/dioxus-tui/src/widgets/number.rs index 3e9d80d2d..f82732981 100644 --- a/packages/tui/src/widgets/number.rs +++ b/packages/dioxus-tui/src/widgets/number.rs @@ -1,11 +1,11 @@ use crate::widgets::get_root_id; -use crate::Query; use crossterm::{cursor::MoveTo, 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 rink::Query; use std::{collections::HashMap, io::stdout}; use taffy::geometry::Point; diff --git a/packages/tui/src/widgets/password.rs b/packages/dioxus-tui/src/widgets/password.rs similarity index 99% rename from packages/tui/src/widgets/password.rs rename to packages/dioxus-tui/src/widgets/password.rs index 5e74e8b09..07491c140 100644 --- a/packages/tui/src/widgets/password.rs +++ b/packages/dioxus-tui/src/widgets/password.rs @@ -1,11 +1,11 @@ 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 rink::Query; use std::{collections::HashMap, io::stdout}; use taffy::geometry::Point; diff --git a/packages/tui/src/widgets/slider.rs b/packages/dioxus-tui/src/widgets/slider.rs similarity index 99% rename from packages/tui/src/widgets/slider.rs rename to packages/dioxus-tui/src/widgets/slider.rs index 32de32ce2..9e8d63cb9 100644 --- a/packages/tui/src/widgets/slider.rs +++ b/packages/dioxus-tui/src/widgets/slider.rs @@ -1,11 +1,11 @@ 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; +use rink::Query; #[derive(Props)] pub(crate) struct SliderProps<'a> { diff --git a/packages/tui/src/widgets/textbox.rs b/packages/dioxus-tui/src/widgets/textbox.rs similarity index 99% rename from packages/tui/src/widgets/textbox.rs rename to packages/dioxus-tui/src/widgets/textbox.rs index 6c378cd72..fcfaf0a90 100644 --- a/packages/tui/src/widgets/textbox.rs +++ b/packages/dioxus-tui/src/widgets/textbox.rs @@ -1,11 +1,11 @@ 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 rink::Query; use std::{collections::HashMap, io::stdout}; use taffy::geometry::Point; diff --git a/packages/tui/test.html b/packages/dioxus-tui/test.html similarity index 100% rename from packages/tui/test.html rename to packages/dioxus-tui/test.html diff --git a/packages/tui/tests/events.rs b/packages/dioxus-tui/tests/events.rs similarity index 100% rename from packages/tui/tests/events.rs rename to packages/dioxus-tui/tests/events.rs diff --git a/packages/rink/.gitignore b/packages/rink/.gitignore new file mode 100644 index 000000000..96ef6c0b9 --- /dev/null +++ b/packages/rink/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/packages/rink/.vscode/spellright.dict b/packages/rink/.vscode/spellright.dict new file mode 100644 index 000000000..c30e473fc --- /dev/null +++ b/packages/rink/.vscode/spellright.dict @@ -0,0 +1,2 @@ +esque +Tui diff --git a/packages/rink/Cargo.toml b/packages/rink/Cargo.toml new file mode 100644 index 000000000..2f08eb720 --- /dev/null +++ b/packages/rink/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "rink" +version = "0.2.2" +authors = ["Jonathan Kelley, @dementhos"] +edition = "2021" +description = "TUI-based renderer for Dioxus" +repository = "https://github.com/DioxusLabs/dioxus/" +homepage = "https://dioxuslabs.com" +documentation = "https://dioxuslabs.com" +keywords = ["dom", "ui", "gui", "react", "terminal"] +license = "MIT/Apache-2.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +dioxus-html = { path = "../html", version = "^0.3.0" } +dioxus-native-core = { path = "../native-core", version = "^0.2.0" } +dioxus-native-core-macro = { path = "../native-core-macro", version = "^0.3.0" } + +tui = "0.17.0" +crossterm = "0.23.0" +anyhow = "1.0.42" +tokio = { version = "1.15.0", features = ["full"] } +futures = "0.3.19" +taffy = "0.2.1" +smallvec = "1.6" +rustc-hash = "1.1.0" +anymap = "1.0.0-beta.2" +futures-channel = "0.3.25" +shipyard = { version = "0.6.2", features = ["proc", "std"], default-features = false } +once_cell = "1.17.1" + +[dev-dependencies] +tokio = { version = "1" } +criterion = "0.3.5" + +[features] +default = [] +parallel = ["shipyard/parallel"] \ No newline at end of file diff --git a/packages/rink/README.md b/packages/rink/README.md new file mode 100644 index 000000000..d021a728b --- /dev/null +++ b/packages/rink/README.md @@ -0,0 +1,90 @@ +
+

Rink

+

+ A beautiful terminal user interfaces library in Rust. +

+
+ +
+ + + Crates.io version + + + + Download + + + + docs.rs docs + + + + CI status + + + + Discord Link + +
+ +
+ +Leverage CSS, HTML, and Rust to build beautiful, portable, terminal user interfaces. + +```rust +fn app(cx: Scope) -> Element { + cx.render(rsx!{ + div { + width: "100%", + height: "10px", + background_color: "red", + justify_content: "center", + align_items: "center", + "Hello world!" + } + }) +} +``` + +![demo app](examples/example.png) + +## Background + +You can use Html-like semantics with inline styles, tree hierarchy, components, and more in your [`text-based user interface (TUI)`](https://en.wikipedia.org/wiki/Text-based_user_interface) application. + +Rink is essentially a port of [Ink](https://github.com/vadimdemedes/ink) but for [`Rust`](https://www.rust-lang.org/) and [`Dioxus`](https://dioxuslabs.com/). Rink doesn't depend on Node.js or any other JavaScript runtime, so your binaries are portable and beautiful. + +## Limitations + +- **Subset of Html** + Terminals can only render a subset of HTML. We support as much as we can. +- **Particular frontend design** + Terminals and browsers are and look different. Therefore, the same design might not be the best to cover both renderers. + +## Status + +**WARNING: Rink is currently under construction!** + +Rendering a VirtualDom works fine, but the ecosystem of hooks is not yet ready. Additionally, some bugs in the flexbox implementation might be quirky at times. + +## Features + +Rink features: + +- [x] Flexbox-based layout system +- [ ] CSS selectors +- [x] inline CSS support +- [x] Built-in focusing system + +* [x] Widgets1 +* [ ] Support for events, hooks, and callbacks2 +* [ ] Html tags3 + +1 Currently only a subset of the input element is implemented as a component (not an element). The `Input` component supports sliders, text, numbers, passwords, buttons, and checkboxes. +2 Basic keyboard, mouse, and focus events are implemented. +3 Currently, most HTML tags don't translate into any meaning inside of Dioxus TUI. So an `input` _element_ won't mean anything nor does it have any additional functionality. diff --git a/packages/tui/examples/driven.rs b/packages/rink/examples/driven.rs similarity index 100% rename from packages/tui/examples/driven.rs rename to packages/rink/examples/driven.rs diff --git a/packages/rink/examples/example.png b/packages/rink/examples/example.png new file mode 100644 index 0000000000000000000000000000000000000000..ba45e6e99c94aea47a7419536c2e9db11d5fac29 GIT binary patch literal 107349 zcmZU(1ymf((m#xQaCf)h79hal?yxv4?gR)BAjo3D-GjS(kOX&kmk=z$Ng(*L?EZP~ zz3=3zPt{b_uWGt`X3kXg#As_Oy~3izLO?)xrJ^jai-3T7h=71phk^DYiLr9q zL_olCbCQ$OR*{pV*M9GA@8oKSfS??cVTf*|H$hrpnwp#(K`1S)daRMGiNL|oim(`G z$;2EQL1-hZ-CP=n)nG2)TvE#z>xMd)Y%8``PPo z8a$al(Swdqk0((rM7M%gB(2M?7LQ?-s5)nvDvm%hAId{bD$#2;nV6J>sEzg;oaOrl zp`;t+WAOgj3)UCgAkBV+91VCmn6b z?wN??Zn)1yBK6%pEpI&ZK>&eLpH?dq8R40BSupNFEIA5BVQm;A&4noKLOvWCNT7w# zEO2Lqib|CJmC!oCk@pk%D6*09j^`mX0pOBcI5tHwT+MC2ZNoG>WebBT5 z@scp^1C=+T$95C9=fD$`$AQ;2H`Y4uDTj%p>*I#2oAH+zKBOG6DAc+s632-)BZ~+q zln4a7L=$~Vjm9Sd7^HJ!Yw2%gk&L7N9TABg{%$1v!;oDyQGW&f?a0yX7@pSv4>rqr zIwEaxp#HQp50aH*HmC3TH|^BavcpGuZX6Z;`Vo#rs9&4pJC5lj?Ne@syL2*^>b3pq zPaa-14_B^ER)H;?n%$yVWF(ET=pZC(1oQ|bR_*>2RMrqP`ga%3$69f2+dP373mdZMnN>;nFnz4LM#egtAh3ZV7=*+!w=7N?-c7p#`maeZ2U9-#(bF{;SSP&0KUwfEkF%P zWJNf_{Y%oqn1O3(fpH0v#V!zHvP7#3k(?f*@k0jV;|xj@Mkkk5w z_^8su-Z!Dtp}6#;HBq>tclArnBjAP;?c#jJ;0Zt5<@qj2v&(Xf_D7mGjGj3nk`JG0 zn7Uae58pT96AGi1GEQoA8PjKFTt*qf1qiXj%E5}_-LYV~>#VNXaxjV8iZo^c_> zQRybsfj>W6El<@<*-YuXhJ>aolN9S+vNAJMDiKErvC@7DmP&cqN?CrHP1)-*+(q

oPRG!R6c57GXb+sm49iEX_G7~ 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�&=*CxM7=N;Qn<8{39UQazNEy$W{smux7_m#G z_X8b=5st5@Wr_70ah!Ix5d;ODMAE|-hpO|fYpU}{SNckYoXP{EZ5UZS)@w_X)@y~w zSA1aRI6p6IPxfo6MM`vfpXy_rMvFz={)J-bq2&MOHJ%Z_4Yd2Wft1|e1Q)(8m5zpq z7S<+4Kd*?&!6x{rx5shfKA+34uiJDGm2P>WQjk$8AWE&{liC{%i<3ztu_6djC*}Wm zNqX$x?>CfNv+7*GpDL+FSY2Ca(2Ne+Qe74u+N@{d(n#t&B?zHw_51Ufp#ueWY`5D# zU8rO4u4LB&nEA%=*uqk?jL!!z+LZxn52oS+vTf}NP2d~nuMqU&{ju>t@y5ZQ%&*38 z3w!;Kg#of3$wB8CraqJYoNbmOzeABnD(LvPp*_l&bX`LUH>R ziiPCu=57GKHq8;Jd06F~9>L$wuyUo`&~;=LvJlGUe^6XF;EgL~gtizoec&$-1z^l_ z?;1?_+Sg;-hip#3@1T8R^gC#SjTH9ZY8_D}$GuTP->8jm?_M)c;FuE*TXGa(i5aU) zuM2X&x{nq}oLz?KLOoVlUP$iDcW&XL*+)g_cM@GoZ4Zg6V=u+F7TPse$3$y{$`3+IeyqvAz0gCa7uL{7^#{w% z5!Gz2dDlPLQ7>0|Baxr-!8C;S_@|S9C1qKGpVpVnw~5;JxP2#opHT78Uni7XwE?gH zdwsds$jK2~@ECRp%zw*EQleqb>~%?F(kfmDmtL8a;6EGl@-0ptI+#X%G)NhfU|f({ zL0)OF1usr8HSCex)@NUXBdW}Fvq6Ljn~mK}w#u+CRbRvhZ-J>x@LHnEBX$Ujo@Og3 zyL_i7?Np_v8iuECFUc>u3e1{2Zm?c;XS9aKcC&|9q73gqoZ|)OJ!n<8<|Lv#ei{vGunG{hA0#@WBXFfV3(U$**3KU+ul` zvZSzrlX<`UQj~QpW5jnnvZje2w4R3KWEM>XiyaRbpZBBW@|O`om#yBr?=_ zj>@NolTckpY1ed$>1KN^mMwd#i{J*P7pmzpzC%-dYM_!_&OS?|8sT#8ICQ&XL|6?;sd$mVR8I>0h+zLG{;Ky?n^mj zZf&B*;xYbNq0=ptn|@vLWR9(cvkudCLyJQ%xmh?DB>z0zB#|_KeCX{6yE?JJay@eE z{8_EUa+hlC$g1u;f^Bb#^$NA9rdj3_@6)h;bNEB|g%A6+t%)~Ze9#OHiIW7a*H#5E ziTruV?pCJln|<@l!S@8)r23%K`kK|%nS3ECkBLX~Fte3~S2Klchc)Ka>IVesgP!oK z2_ZIcqM@awOQDyDr9{!k$`4DN9R_0>UZR2sW zD_@9vqZ{W-6vLvNBKU`JD>j%u zeEJwzr4>u3wxNfw&f)9l5ieh-Ywx?-(m3PXNY|YM6X%kZkbUnk)h(7!LQEw=aVJh* zb$qnikg3k&-R~Z{Fn77VRE35%FF2$Aqip|zA_VE-yZ$A!dA$gzz7O*Jj`X1~r*HPE zd=t!&ulBCqNXl3_lk?5@R(a!II3*F@_=J9mYMFg*+A^h%X1h?RB-rft6n+a6FFK^G zpx3TS?)j|9kJJZ9I7?kOlEc)U`TL~%Dm^~(IGZuaYRgQK6W#;PAb2HlLfi>f;d;e0 zJ-_i!{b7IuQWwdqT*`7|Xyi4vXc?W=tI{A_)dvL}?ypC+P`*^|*^x5Q1Hy`xLRKTV znhlnD=w3%F)^7eI7wfmkXGteBL==0X&uS%Xl0I4MTb|Uu zKjeo@w5z-5opmxVZ7=5MSuM6xO;wEoe-b>Je2{yK9kz8iT4Ti*{@&>C}n&=Lusg4MFo;cU&_?jn91^9 zeth5Ou1{P0BAAuyjmAZg>UB=iR>7><`GaTYD%w*=J8BLyJjz7ko3q(#Q>o0{y!wgd z%#ZN7jx7Ibmq6WS+4#Ze*wkwFu&RSTm&a9dKo?b&a3`+^kItqdjXKto; z2D&QAzFXdm87|t~@Mz=aAscQkG+s}IBP*v3M+-F|@k#4uhKm{t1>EKQ?^WrD^%tmk z4lcV$3fnsVM0Woo$>i3FpB{4RNEB>J?mD;8ML>l)Z9wQ=4!%duaK*L&o8#mbUSTVbozqY%dDJ14unAj;1jcG_%M(?cKKL% zSR>LS3*l6pf525(4sx*w0;uh?g)6Xn+;gO@?@SiE)KLVnFuKq_u(b?#=UC>MEE_^# z7Lo$MwoHc5UKn9(R1tZg&w!wyc<&f_3-^O$=Q=)ve*m4ka5JiUVLEj)4PrIX$62ux zpa-jdoRYPdFV!-@s%WJAu3uaDkt43m-F?&MQ2)g~rPT+|+V`xPXBon3GjS4ytkmOv z?xhSPd7^^roOp77(IZV)ywT{QT}ho(JLDs$T@dP z*E=swByh`H&bA^(5DcFteYn#gL z(W^xeb!zh(@4643mV&TFa~+723|JzlpYR?D=#!;y~UpLqQ#G?V=r$RsG2e#!a=Xjw=! zj`?mq^?hjn(zC9LrN(-cXFF^l=NP#xptf{0g0z}e)i-STn?TFROc5G_t7W@0w`c>AjYbpiZ>8fGPS_noXzfv@|zO=*5g-8Y5!6-QM z3vH$3QC@-KwC4-Fqv;xS@r%@7EwX3V&UAFqJ!VPYVah!|-uS#Sq;gd0$W@JcIQ23Y zb8yPX4UJ0{Jwt?&OjRHBVDkT*)Jjnq@t&xmobvQWj%A7!bl4v0wi_7P@~YwBWacMyQ{Wz7#9 zw1~`o%}Z;G&I~znpLPVA*n2?SAZa?uw`&t%x~hWL5{qze#4#xes$z0ioo&A`nsLB1 zKe~Od-<_7@Y|}Tw;x1{QNj5;+3mqXx7Mtrk7TWa{Q%;ounEsFJRM9%nj!7$K&(^vH z=4kWor9>YQ>Lj6v7q2?I=XOK~f(|Dw$1I=aR^!358LkPi0?AUUwg%l;<83xw5x)@m zK9Q~?y0)yX%9mF?{En6DfzwlDRoa(t5m_~Tgat2|u3MqtRwV1YE0Z=cMzWeK@RB6h zy1rANWm}(D=_#G5zndHN$4`%lt=g}#`SrcgMID|=u{Ngm>`Ww5TX8OBWZLISJu#mN zfv{zmo-rMH<#{@yDpXs1?lH314^fXXIWUt>N6foB28gpdB$+%v;zTx>FGgQ>wJ%_+ z8v__~XunO}6|Hw|lS{8uro`PxiA4l&??iat1GKAyGl%HLxBY7OqC}2K1htwNs!w|P z{|zr3k5135T^*R4#wN`I>7#E*Ls)xg{vB!@w^D|KWBa0}hSPAqXyNuz0&9|1Iq^DA zez0w|_ryGh%xgB2uoh50IxF-vv^p&yj6&P$XQ}PO&3_tQU(7TA!$IyuDWoaODD*e&>#Qli#^xDKSABTD@_-w|ldH=lJv7w-P9_ zwYY#-iv77xXy2!0^Ep_gDJJf@%V6}-BkkV1;Ue@bpe4bFRAZ!MR<>#Z0-p4hPxaW; zAj)GaYiMez(UnRL$m&G(6&OTm{u?vZ9!;WB?3O-9qjS-VN-)$q%@DQ*k=cZ6zq_QDUT3PZ~JCcQ!5IA=fFVTyf^nh@1t1TGycjO)Qlya-0a4#HDR7PdGDb_tpgSh|V&z=Dy2;x{9O zLV~n!3sfXr4xN9wI!mC)XRtBglbKUl7@>L(e{DEO=>*4ak{bQpd-2IMc!T9(4&d;_ z>IKnK*f`m#m8+>T2zz&>V2p9)*c(aJ;m9Rd)bcM}cA^GZ!mu6?ODI21}?yP@%OThd&} z^%0aRI(TGaZ)WXaiPp)kGQKW_K(oWc;Z)jV=3r0MgNrIN zyT#X(uUuTCwj+feKe(EH@4i^3Y>{8GmBVC&B>O92>6SH^xkO;sx|+_No)0-JEO;!- z2s&cF!3oN?wNe?kaIFF8aHHg^@Hok8_ErT+sG`1LkHVHMvHrrG_95Qz&JP+odB!@K zddN@aI#ornPM=or3{J21gTBq5U+c_zs_YeNGi*%&{l@p|p35l!b;D(G!|6;@Gw=Ks#OA z!^lXo>1@N=JzgF4K}=H`B<>kg%6k`RtW7EyW}*0)E~UV|^sw8~$*;?RyZ_Ela&$+a z}n8DXNb`H)8aT1TQ zssj;sQrG^RHzW3x?mq#6Vr|V%)tRL znn6{ale#s8`7Mxm?q_hq0Vg@f=7pBA%>CbDR|cF>d6DV7kzGJ{Xea39TE7CLL&2|f zNXxNOeeAqSG1mf^KbUceQjJk8P;Y$Pxsv|O^^@_3vd|rV0`$Rv# zxcQl2L-?F!*nYb^5byN68{e-qDSL1CuX0THEDJLFdkfYQ%SK#=-Y0*JtsEanS??~Q z{s$E3C6;E6dwP#UoG+;u5amuX=f|rRhMdQ_tBzl!xgysrXDk|&X9mfid83vT0H%zd z%-B-hKrKu~Y|hC|FobCyRqE1JQ1*^6re84l^vh$MPX;>eMjORbmupGc>X*<8vcUNg zD8ie#)v=L3(9l|)_}$je=|$9$Y3MFBHH}PnZ%?p3BP9h*p`xNpoQDRs(*>LCID)%G zghK9?_s!>j0r_uIP~4ohnahhN8bs-!lwSKTHII^WMU=T;H!9g^j3m4C#J z!>tDMhd2e}r+EK(Jz`nwz0OmNLYx!SYDy@Jn6#ymz3L8i)ijE~%gL}(clR4NP+x~G zf^v^-McEQXR*)l`*SRna$@4oc1zZ}rbf1GEP=&9z6qnbv?KX$u0iV2si;tS-?m_nr z#;P;Fx0_n+F>rTiXHCMSzk%vLb}bfHqf@_0Uf5S4;@u#$xm%R=Y-@*S5qd8<6)`*W zzSbIoAwjM7m>1XtGrPSlsCTR<>QdF{KxZ2r%rCfmU=ZF9rgc=?*?%y(@O zM%fq7&FSoWv`D1jlgdEaoR&rjdboFp(3!fTf>}h*r5io1$I=C&FJwcLDS4IB8zkBm zdUl9wyiAxT;;t-1Iqs8Ud0cs(5sD=Svl)u!8WJt<)@g+->Cul&)Px9dCDKX|ycJE@ zT*u3@00!h8MB6kfG~mFs_lRBX>RV3b%u)ErE0^+8k|RsjGhHAx4M8z~c5VDXHyj~y zTaGMwp+|YUH1WAN4An3y?{kqJhPKNLLqQ^+{I|MLTb})EaVl_Y`TZ5>cZ@h$1ZHtE zrBE%`D&2fV=ax0%dsjrY25t38@5SH@ud6(?`+C-`9S<~zqfEn|%w-EU@mDq6Y zq1zFGVnNG3@y$7|cE7}YDQ)fgaU$Eg`^XuL`nOrlENcs4K33iLJ#QkFPX&dT^otz>wzq{>1gknjS619`92YKJB)IRAS|JB~Rl)TS}zufasj z6-QTCsU7JPaO7!f#m#Yhh6ZSkA0j>tQNsmeVqJYH|yCkE`n=e<9UR>`$F>J6~##~Fnj#I zZ0g)Rpz031esde=TA|AG;*R0%t$4|kdQ#5V2{~7^;kmDBY?=4$po`M`gqRnHE+_=t zaZ4B$Id0}KKl{46cY^wdoO(&jm`1NnvLPVd=4;X_?FE`|w8HYDnU9F?Xz4=G_b25( z{$ts>sxt;#O;f6Pj7@t0xdv4N8UtaNm8wH=(n?!EQDWK$$FCMp<}v$ft4und=Pi|I z$AL?xva8`5tHk#aJFKp3tY;Ed`esS1WW^9(BK$dN6}eu6IvgI!{9uTXm;>IGFiM0m zLoO5iA2&L!Uys%ClL?h2!7ml;0Wy@y9^z7OSx~y6>fn@T^P#dJQ6#NCK%L;(wbp)B zEE=76@XSa)s5H0F7GE~kXpk$kHRgUy6wIB+9DZWRKU*BtTuZ?8lO9TxX2*e)&PZKi zCiUU#g1rM0JZMRjGw+Gv9qV$yZT3Ls4a#*&NkRv(pf2+{GN|L^_GC!&6)(U;WR7$U z>T#C{K>;;K=D!&H(7LDHP;ChIrv@R@?yxAqX?&(sGDvn+8Qv|JtG4iZ(w$+p7 z%^dz@E|fcMh988T=EASkL)+AFi?rzwT#*nv3SYPlXSypF4abd64*8s4D~7v)p0kTp$kKAxsk;6_@FSKRK>g#dDD(7YTB|JipV+AccvB;h#|mwCv3)tAH#qsq>kA^Cg=R zF8GotUB8AiGz@KPgJq3l^l@n@Pic>qCcAKjqu}%HZ4BFtLPziI4xpgsHSX5pABi{D zO=WVIjwr@O{V~(PLlNuH{Y-k)e;%<_+>|$SoKKvskG9E<-UGatJz0~^*DAd%t}>YS zpyZEBaQFMZ)-znzqaIvpd09=`(E`+6xG2r%*UoJ7F3yp15YM};u3N|~cQkuu4h%O2 zIy1^2vMYO%WJNWo<9Kkg-~-r99D@3ZF&2J@yJvadh;hB>B(yrmqU7Q`_w^GMbW2bW z%*y1XUyxe9qL2<~i#@wQL9r`+1(pCBrqbX&c(IHRwKKQC%XLiV68yB2vT9zL8MJ8x zoxSr5T+<0TEtt_`UX5L*3D|-;*N;e+ICHARe4}590w!L_R`z{?Ij<8e)x&8k4zRAN z0HL8TA7*VWx*qQ@^Ak4wazBCqc8wsU`WC*mRJ<`T=NM7`$>=hdb6^j2(>+q75#o@l zsQg;eKbkLO#ICHoyoCWG9g$xBzE|t)A23oVaf&f^wp|t0t5&pdf}STj=|09JB3(^` zG4th;4HyG7(M-4UVTaAr$_697H7=yPwRy=?Q(*oSDUMuy1o70`MNr+P?!GSWZ;VlS zLORiQmGTaoi>HKqS{}FW-zj@=n{lI_eRb1TN^l+M4#^echJCp3d=5PTpmFMv%mL`l z$npB|yErq$uWjPh7>WuLLX)V{NZtX?ULn#9)nAVx?|pK%fPK|Orwt#}P@L}~+?QKCdmY^^Kr z!|&N#1~K_RZi>ARy~z3E3Jmy=R#X-sj)}9Kn_Vl-re37_*b#uvjzF*1 zs0kOq)k_w^sRaYL6=}q03L3AD>6_nZ;54Lvh(7%7~8nIN@kF_)gK8~L>?zH!+j?$MePCDI0S zZm{w;s!XzbU}yN50G>(HEZ)9@^hAvwn-)p=GqI6ll|;{0{n6KDdot)jF%Mx~kky z7N6LKG&zOfwKzG?77ZYjeb>kHJ+oSFIm3L@r?a?}+KmUhqDz%>4pQ{@1n0k&61F_; zw5$V`D{<)&RDa?=KY8?Xe{H+QmcP>Uj~A+ZDB$+Oz#FLmY9@zdJqp*Ty4Z7UJLw6$ z+_PL4!$0(>4nHLxY@@u0<4c=wbzShlwAByM@^PWEe${-h zwq}t5@;T-OY5nh1friPm1GoQ-yC(AieA?kyQk^C-hwuSXl!;G>1uYCVVx<6 z)?oX-5=WGrvqnsZXpYztFsHq<2B7!e-bk4p4H?;`EFNje)Ph1g1At*}DGjCysO;*G&oSmAj2B$-4%6yRAH_8ZlE$HAEOHI;(f zLja+5O7H69a4wc#ex9OTu`C8lX5{NdJ+-ZiT4X(C7&i=}wx*{>7qvwV&jYFHI3KN# z;r)&n9^;WVKLbI`Ov=)G-_D;jf*pD-bPkXlXhrK2m%=EzU%fUh zn8`FbbbCa|hV`LGHn(nDxaQYYYBV3nzZWKSE29Cb5kA{H6j5}xzpgn~iQynxr&W5K zSSR=-Kwg8{wtXYy>z0t@9)q_p%pS9)-}x>nm0>Cz@CGbY~#&4f?_vSCv* z*G!_51*3fHkBaEh_@}nc*xy=h`Y5y?wp+x1`s-S4Eg(Z0^qpUcqoCjlbydrV^)bP? zLe#zV7F+&pS<&F_A1{ljCDYm>TvI4Gf^e9Sskj-5A2l?ZtylyE6Dg{xIZo`^Ou|L@ zsem6}_pjUJ&M1|XR#WxlkepdSvR|uJ+9DpU_8lDD(Jx(H8Fx z>28`uf4fzZ@B@v6c0vX~;51m*5&Bpm>nO*!^=$SzzD(8|f2bmi*n4xOSfA~AFtVp+ z=Uc^sD66;K+O9{Y@#i=gM1Ve^W~Y>0AWPOI)+IZw_pX0hoLzNzu(>ZN($E9cZy1Sg z=q2ft!cP^%3X=wvN~|jU>^l5*UikL94lTcP`4stnM5yJ1^lOwO)0mEAKAuT0Y}A{& z3TECSbvkPB;}QVxO}4a9nL=KO-c?=ZAbPeMw=%NEj3tAiDo?v_6ZlC}&jS4Ayg8=h z?l719YW#b1i#CWA<^-Vl-_S3&xi5!S?C(J?j@9Pvf1olx=zZ9;b_FPm1HEFm^@o*; z?kUM$MC$)K>b$LqQN~M&uHpQTyu}{(4D8Bv>0KEyH=AdGN1tOM<*?|x=7K#LPo1fi z)%qVJR5{j?Ab=V_r10%`U>JvITer8B&nUHo+RNYlxGBnub;YinXUd|#mp7X(-9%w2 zkC26Vyc;1ad%%r`-`dqgMf`r8hWM5|>Ih`bzhZ<0HDdG*hsS6*78xUsCapKw8jivI ztn#`Eu|v$NfEu!;lSuKy_XaD%B|QQVTwgZ35$`AYE|MtlY84WHCao|HJaj=H)2C;1 z;HT^9U7LD&x&7+pcbkh{Di3^f&}7h08i;2{XQUsF)##!&)T%&gfrup>;+U%SKz&;p zVfN#6$zB1M$Cplw*77%NT1X&h`2BMfg<~kZnbZ0mjNVggu!x^bF5*IiC4%gHS#9`1 z!oKTgfehWj^Qr?gRx1G3PVHYJ%5c`~-uv>VN7y?~$VK-D$e8?*X}R;pgh9Ex>6WDb6LGG(cDt}ofcuh|N` zR4Lu%>IG<=0c95WfX&BOMV#&?ZzqnT<%!BV;dg**BxTCaFHE0}_QR)_#%k`^MUmZB z%K%40ijOH@Vj0Nr`3=D@V0Jz<2qc zljrHr;w%?kvo|N?m;4L)RVvH;d%*#9r7t=9hCTItNgOl}6=B@@d0PrO6zpL#tWX=4 zn{tLR*&L4vO)ABucs}pVcGuC3zvRFagsXygI$b(oam=Obb@F;}>h0NcdA>flln|Gb zkVCIuLF9V?Ew1AgNhb}eMediMAiIsXqsEjygbuD>SJ{R}bw8E38Ut1dr955=f|aic zgLy(eJlh)FRGwE;W#6oEyTD{Mm(&pl60Vtw*JD22gv9r;Q|7zQ-CQ?g@jaJh^Sjc= zaH$EVrL8Lr*QC&;f_&*gGrDgKJMA_^HDgII!c_`&(~AmM&(^hVVLb+Jfgm0+N{&?> z?(h>@O}ruMV?{B0bgEjsL8Q~ z2xk*knh(-0z?}djEx8T%#c*U z_Oim{yH01fo6IdZNo#3DyyFwygW1}- zJ<(J3?A$96h1<*VpTT-OLyUZz_aF1X8r$;Q_btipo~RvDOY@i0_wU(vFi->_qXabs zEfZwD`ai#*Wn@o1;qOzqA`%QURiu^;?Oe<*+^uy-eMKj&vTn4xvZqoT7qR>)o@ij_ z?VgS;D^stW{(fpOY|$*<4`Wrvf1^n1?%-zuy1UlElXuSdjU6&~&wkn$5Sx9qL0wRD zf9TBVG-<0#2Y)#DTzg@&ZPyf@>7_7CxLB=0z(zsm4I9&><@?6J<-?bEP^JSbJrQ8- zskY+3Wy4lD#B?s5e(mwsen;LP55URXB}W80_V|4+@NzJ)ce1MPwoX5cPtHq4eG2Q- zq1v4p=DwG6#S2|;=8H>s+?OY#D_7H4ClCePqNnQ!!h+P!s8yXBnH&?Z8j!i$g{ikrY&(jTY& z?0x0)9sRL^fPO5zG0_!5-E-{0c@dd`lo}hvuZ;!0#MwT5WsPN9cVkxb8!Ielk@b-4^{d);q{wn`7 zVGId|$@~?u1q^kY)g-Wa{#o z+(rk{!-<~g)}s#!HZfG~x#h1)=Ka32R4Skcr;)$RO9^N;5Bkdnz+?YvN&>~*&m~oz z{v{%VxE?xPNIcyun|qWjLT4q*(ySfNwLJN9uG;dtbc3Z?>hPwxO9YW_>>I7XY4rdr zGiZVt!yo<Gv#x z+t@iSJ6k{B|U zwcfCmBiDyyySy6}q0bgP+lXWsa)at4h$Wj|M({G z(-rbNPKP-oc1X%`IVf^25HEAG zVf}({n=I_8U3kA}XAxjJDX6fqp|LNY+7=`11yTv{1RYJ++T|18_dKqTVILf zH-C!UV)%3NbQc}FwOSstF^)ss7T_ST$QRr(cn?LRRVJ>iB$uN#w-s=N zW599#Z44b{{UhM#oL72hd#;(}&v5v^RtId7@BNe4Ju6O3LEZfAe+K;y&$){os-rNK z>Zv!E3P&-2x`_4c@jGO61EU^W=r8gDZ=N~({E%xN`^J4;sBWSfJzmys);;U6$JEHU zrTTX4{lmll`joOOFh$&O$pIL3FPyA~9UJ1Rgh{78kuODSxVKB$4PfI*=lT3yJn(K&M%?YKRuwT@;Wd#tUEO?=jw5hm#qKVM+&C|lA%i%6u%#= z$D4tiKY&WiEVE;bx#8n$a{_Nu%;<@zI9Lls;sE{V<$oJh&v!W@!B4jGbk!($kmF-L zGAot?FIv4{O7r@(WS{%Mzuoauo3`X$jFglua7}~4je(nP{y$HgfAqTTp^Nvbv5{O8 z7T-)~{iIP-CM!?5bz{Qy+?bDpOYbYJ4T>9UaNVML+w!woR4F)Vl~h&o6C%PR@eh4D za^Fg2w z9QM}kjfAKqC%hLz4Zne9N}jx0m#7%M=1{+0pu%PMLY5Z0_Rpd7^V2}^`3?zd#8iq@ z9X4#-eX&nZDcZLE+;ZU`*^M_n`MZmrDFD+zagVIJ>nKH~h5St5zGB)W_@oC9S*>oP zc-*QAHPkqUK0tr2{rsOzwf$)z@#j+tr8Y0_UwM$oT=>J;*^EbX@bg_Mo7+iu-|Esb zK51W7Yc7|>Hrag9T*z|L;8@AB7ay!wxDeZVkC$xsgos^p$w=o8T8nBhI{Yub$v9#k zD=W&6wBik3JI0My>bUzh$OANec|a|UTHs~;vYllfV*^|I=)+d$oiPEMb(fZWss&(F zNpl3@OpEsBzwP=p8jt-)S@i45dh8y?c~}#s9?SnEb!p(yA2LjdM*iS+=|7kM7-hHO z*;8vRl8`t48@AQ{P-r3`2;ylvpWgOwbKUEK*V2R5?n)$wVJekV@64?ov&tz`lWE5e z+kR@Km?hpq-;u&*Wz}O3me=6yu<0~&Gp$0byaPjkUKaADD~i72F-06#P+70C#h?9S zyCC>yp8_z1+8#l%7RlJib*Iecn@nkrwc|M*rK=6<`{ zMEayT#Y^lf#MDvB8nKcyRIQRBLJ|9x$bVuRkN>xgSe6ge;-CY{N#OObHQ%gyV{fiH z`Q2K4PzdWB(mmm7&NF69SYrFPuRnneNclEqJ{2ZZz?>2E_~?) zB6hOO(V3&D#TFbvxHt#oyyv(6EsXI$!+2>T<-wMVcnhZ5#k4{e#W9oQdu%9XHI-xW z#A|E-QI`G&q*3(RVQsL{@7q$pDloU)dSl?^6ypH>>iK`Wl%peb+c6V@3s2ajQqr}f zfk8S557+`Xs5a32%`@5UjxU5a1h|L>n5aL{PRPH6U4B!dSb#fM1|KhEV_nZT)%Tw7 z;lH^~CKvG8idf^hk(MVp|7#fR*pqd{9xLnm!RfH-Lt1|0`5OI4$O{hwTJ^0wd|mS6bu-)cNf_-7!);M*y;(dEZ!gIhXtHVHZOUkpdG=Fe z|6ihR+GN{n0bGI}4rrH0tGJ1xVr`n+lLA1#j-y>-+%OIbOmFRo0@2sLf2Ds=_t#wj zNRk>3pPMVSL788(9_?yB_Wv0B_IM`y|Nrjp4m#XPB~fvgBqSsxOlLPa+z2^sYBzFR zlH*Qy=Y!Z?lsPU5Ar@i@TPhYdOJOm#BAY|AxXjGhe%JckpZoj${Jzci`;W&SdpvB{ z`*pnz&)4($x?byTJGqmj8v$vVTdFMHdxzcX>y!C9yN5*G1jN?YMhl8p%+A{E?0!%#@%vJz^|5_1ba1)y>AaBR*VJHs>gANr-A`Msxu%AY6wCEIe@BZ7 zG|?tbS=%mB!?WB^HIjTx6EI4!N*_Ei{>?rGmlxT0&AYnHq9r`kPG=HNu#_W--ie?v zC%gp$;{;U#%qD(cp(s;;@n^H+Z@*mw;x#zy!kZgUFO0l@x3kgDtv0>TRc*TJ7WmxL zswbPPGd?|0bCPXWt{jZLH^9H zqHmqrD$h@WNtD8;do%ARechRv-U=>BB)orO8U?vITH>Y8=UJ*xg^FG>CTpwfVM^fY znv@<=U|x8pQsDYKlM5XO?fAg;RXr7NG2efrC+%o_mj9mIbjAgGrn$il=y-|VZvt1E zizZO}F?Vm%{n1k&NvO)M_fNnyepSyM7rhOusR^`SQ(hB-Btzll}({c zyeijAnuEd)pEwbXw>j}@ZX$9WPP}F7KRc9ua2JM;ECv^x7ThE$2g0xFO!%3`jPzNU z1QVpXzTdi3XB>MOnI(txeBahtvKViqoxe%eaaROtAN*&R?vFmwWpT0^S9jd)-8;k0 z@ZKHtzJ1fD_=J0x5<~BKpIZSjzis9X`-vtA-j_5$nQa1X@tj(pxwduZ&rJ)K<+Ov^ zR}-9efP)cLtye_n=lgdEXQ-K9GIl*HVmw@gNC=TQbeNKhzg#3#8{C?*r|G6S{n5P8 zs+lRHi^o?2@2PEYT3C68J|T?XgX#R@?`AX%c%RZxcY{eBbHD9*Uix-6xZz=hYO%G( z?SwG*)q(jw*4$$k!GV|`RjlvtqMaVxc~#H7OU2_~JCbEpSHZZT!+-I_eM z4cb)U)V)`2ylPDXrMvsSaTvGkbn7#C*%Q5|Go|?pE^nE|%h5?oYFuT;z7{wpeWfA9 zemLV%WVKF6{}&{Ou@OVBXE(j^EzUET^Cnyx`eIM4ap-lqAGi!BetXqL@ingD6KK$x~~4;I9t@`Z3;R5pHAmE8wrQElJnGKmqnz zbCc#;7Q#6&6%ef%In|GV8+rHfgpQiTO8;}5*AOp6USh%pvkFWyMuNkn>ch2J# zxJ+rZ?cd99e-JN!9^|YCkut$Zn)pV=?l*hfK;6*`Cn6$N+O2Qf(AvED%{qfA#~MBU zjy(2e{8lxt(+#glmm!0riNvKs@Q>fq#1hm*jwFUMNwoQ4AWiERc% z%Bpef$D-^JUg$kNzD}e6L)5sLpv!x6Tyk7n#$STvdMkvH^j@FP5Ky(|7Ht)n46g_r zJT;@xMqqaV`5Tsi-$Pmzm@igcn%IOF?M?sS6p|hQyW7(c0m9qn>CX`lxw%7w&YoXl z&)0c=d>;zV>F>${Z$zn}J^@HAE@6#{@TaP?4=>=Zf1_tXxO`;651+n?lXm#s#~abO z1n`T~+D0=fb60&Wu@5|78pnqQ>fhc2K=~5y57q)RDE@lo=?O0$eWBexQ_Aw>kmRx< zZl^1LxErA()x9NvFvD#V-Q~o_3+UO_VrCb%8dofAN=p~ zN3S2bp`CK>Ui~gwSc-i%IK-&a)9)dAkJ+jhG%GY*$cW4N8b4w&0FD2 zh#@+zKAhk8~%W$#!&SjDY9D<~^_T3y|Ar048} z*9uuY&93}^`+_uM+k{EyaxCu^s^bLI-BAKQuO*r{<(|JTvWD|h4-1^`?$_!PgNsKT zAEi4WT4s+%v1@WGlqKtr^z;Sgg{J^PNgOzNHIu>`mv@AMV+udh7zn z<#7GjX!+B+mwSv7)Pz+JKU%EkrtUI=v-c!$h%4yyTP8STBj|z%#CGv7YFym@fg65@ zjjmPdt>=~<8|P`7#pn(MCI}q=GU|Ghee;`U$8lufE7VDDYA_@(e2bdaLXOCgI|~my znf2svADUG?o0nVw{&G2-(CfJ>P&-(>*A=RP^ez0z8>oAGtP8>%FWX~82RwtYpy~jk z)#W%7#5DNZ5mZ*}RMfoYdaII$fA4}(;G1AzflWa|r>JlTE$oW@Z5Ll~zc1&t>oczg zY1_%^;iX!~V$?>uK8~Ha{KOl|HBbFUxc$YyduD)G6~taw=xD`0!J}t^KWMV^ig-=O z?>{Y2I%C$#{i(wmHs=A~y>e2|=rlAGwShCFLAWAvOe7_bW`3pYBG=`e_w9HNvGG_x z;_Ku4DttY+=!G#t69B&9UFttBz|S4zsL0dZ=K%W3UAoWY?Ekb79CPEt8Du9geM(oM;`4Hzrq67DQmmbK(%8GKTB zgkyH3=k3o>P2kv1{oA|$=&g&hSiiGP_p|@8jwcS)gE8fe!4Z{ir7ed!e+-pvYz^wB z`5LCtqu99L&Hwix*uXWr*KWfFL5sWd;kv2Vn`0fHZEj6!!q^V*Y;ny;$W0+xZE0d4 zd?w4Up9Jj)_%C#?g%hvdvLHhP_PXHg9t=C?%Xt9~OhpspvY;kd0b2Q(4s-}2-1W#V z53=HGM!44IqZR$E688;)59Kw-Bq6X@b6_W{(pso=3!E$Zv6F)DPFY^OwhFM*OnB5b zvaT)ZQbH>y3rS=34K3C3AIkp?_EAkeo##(-S&6O@mKW5MJYe_iPUKY|%L|W;0=IRo zZ#}+Y!4%E>-u>+HfYWqcp}%}FA%qOq1fNMPBpLMD;@y#U67M;ayoQ6;uhmN@u;xMH6hp6Lh9Hdw9eNjh9N38Jf0w@_SQd~f1% zNwMp=79mFeYcTbOs_SDHYD9#`t}NfzX(3b}SNDJkuZ2AkCig^nN;bb&_--G_JTH~S z7#MujK}_$Wj+rJj1dH#UL0B5JC7GIlL`YldOB% zBc9Wxb6)LB(&wPopon#e_O6Uc&%e_3viL{C3fN8av>V8wtY+dKc3Z1&CiR+SbfhvE zM!djyTKd|%^4S3ePI8g_oeRs1_rQsFRp@qEJDaZ2-OkIp4Mp$UNi3Yd6mR7 zbp(Kpiu>Xq@7*MqoKy=3hweWi*EWIBfTLNMNv&U(L^-hRe}l~6{X7Y7+&ArLIG?ia zeZDaVdL#Y$c-rPo}Rn z!JP13Dkj{ms}?L(RC%cq)B}%p-Ai8q6y7+old-+$)Sg@1%;-V_b+19YPvX_dS|+L_ z`bY)5ah+vyE^7+$8`u&PsO_IU!j7 z9uSn|@tM|}Ks%w&_%a>dIvMX0c)vyC3-lZG(cu$4%~b`?p4o<)VS(PFz9jaECr_Wv z803X#>nvz&Be}7?lmqkiUOdR%7$Uqx>#<4g^!GE;|E9CY{t0ffE_}qrKF!6jfs``n zRPVYGF0C={+KBB+y$Di-_Vm)o6@l)ms}?fH@49k^Y-OPKe4SiH%*@q1c0Rcp&q#|2 z3pDiqOSDXfD0*C9H+hc0^Al9>=C^xdzZ;^JLQxv>ZE#b`x03hQlLWn8D)r_<`ge}^ zbBde^c!Q5CTu4(qD3don0!_fY;5<(3_!n-Vc4xNz2o(BPm#K-(Ha(z{6m;x!zvu}% z^`^Agzjvdg4G!uY@+A2N@8Y*G$oSqtQ{}&~WJYpmv{?b%?d^ZvZ|g@iQsbDKup{2! zo&@e`F5TB7B3F3t8L$9Fmlc3J7*Cm2xJ|&COAA8P?~HLNk`@+P_)^!^rhVVdN$AL7 zWa`pKExtoO=-i1&C~WO)gL%)Mv^o!#3)n25x}6}*0XbJ|)N;aZY41;)ZY{8)_Q9U- z92_OlIR3G@woiX@^dKjJPPkVe%?T=qRkkgwF5M3lS)wUoP zt=W1!iQ+Irg|kB@7fd&DA`}c)t6FKlGv1{YIo_EY`0#dlk6jeIYsxLp;l$Y|I6(re z={~~r4)i>^;H1}SU9b=Qt`^YOa<~4xU=V!IIG|e4kv(wf$uwtJxp76z$leN>auWQGo;{l_g)@%GT4fh`V7Z%z50@0s> zj&JRAe0I;9wgD}yy1QdWS3A;OOTOyJ+Z3_Fy>P3b0N~Q&Qn#XirNte+c76de{o3CM;bAcG$~)wOY8{&S zfARDQ*QpEgA$ouF^e=vgu5HWm+ecdJTXphXXW5&hF4;DtFH&z~E6VED(>XsY})XyqJPg z0Xs$fL8dgm;{X5X?_c(5^06OpLT5VBH8R`q(@c__+b+)Y8ayViHO7H ze|=!7B$Y97RHp6kB6S*vjq+>_dS1Kk$CsR{6s|?1rif8hv6(*$i@T0Yh0HxNN0qvN z2-p7$chlXNqV!2?)gVhy1ADPQNG}Ul4Tnesop{9K_WyYUpvA8(pYuatc260<^lA-Ks#9=TA6AB|(}Mdn&A9n#bo-GE z&%DBGrZI#i#~ zZTz?w&xo@uHqnZbWWT1Kque|ZHqV^L@7pe!m>Gc7jV?c*<4Pb4{r&#X%W|nJjl*{1 zag=43FaOYEm$dJB>S2sc5F_hPghye)%SHCFp^;Hu^HH2;o^gY4cM?7LoYo-Pp->{E zoiAgTOXmtQKL`dY=VrpzuuKRm*Zd$3Kr`+qz4UCH8*B3t>PPEkt!_^r3i3o~9Fk-| z7R6q0MBCsQ!TRT5*7fy*BwAqP?79xDwKc1sjp_0Crw{}Ac-W>;1(xku0{W<_C-wpr z)y8tUEW-C+7S=99M{fdsGg++FWjQ+;bcaJ-XGz`#PtBi*i?aS283|d%J;Av7V-w^$ z1V{p;fvCBdCFA1zJ<#!NoMd%3@^hvJL6CpXBFeEK+^00A``~yD&K^zg?$XTkOqqCX z5=-Ntdi+7pvrenjTc)eP3{@}hH=@X`iBy#GXBUpiW#LAe?dkzjKVr_L`(Z+!ge1`9{=TMcVa1#^EEbwO-2Z`!5L7YR+2HbiD(c~Ra8i1Ld8AE{o@gXiV?j7}a z$(TDgtA>T{v1uO2Y(&}M`92R1i~Gzt-I~!=b@U8~RHG3UyMmk4uD}%SyFqV|%?e9f zDWcK><(=r#?qPRvQl4*1KPJxF9(o`>5X%Al1l|#dc>wM zjlMQo=RZ^8-!XPEdS$pO4F)Bb%t;REWua;V|AMR&ZzM7f%^iIE0IeTb75|ivN0cV} zPXN6(&xxE+zH2M@z$GziS}G+>tj9rRvx80f^9O#M82@}pR02Prk^<%zL5wj$_MU7; z4*c4-s9{-ACuZd+{LWly2dbu0^0lqKcQ5B!`j|6+;!~u>y-Lt{*G3%wBa8o&7F@PJ z4Fpw?XKT3H#)QGC337*8j{6(7u4CP&L9&f15dB2UjICKR5xR#7y9P}3eKaA07Pz3m z0!3@V;()DBb0VKE_%^6r*41Ir;c|0s%Nx>XYo^Jo*}&fke3e!gpfXB>=9MgF)Q!!| zgh9^5i9~qmu!mljo%aO=5n&{^ZLnSzjUr;e$jR1d_9!`=xpAg-5FSJ;_MZ1Sjhya; zB|r1xi_D|};ZSW|o_hqF0d3{BjboYE?i(6_-j^R{*F_hjSAn0+OA$LZizp58AKZki zc_Q`HVA(Ssx2zvzvbglI2_dZ5#Fpm&W1sz$y%;Q+8CZrHSK{mOja|Y# z>QXrV!D%Tsqu3>80LWP0h{6p^^4;HYUe7=pq{@Fz$4|ZCG&NN?^n9VhOlL@NNX(Z1 zIQBoGcP{c`*(GsX8^=I~SxaM1&sZkGmJAPeX~z>UG%OuWVs|_V?oG1}ju2}_wd}V| ziGSX_my1Z%$0G{DA$EFMA~Ppr*lK45A08zCaL^-BMbn46e{1!<&prZZ6njCh4VRIc z#w@W%`9KvqCvh)xym3xxD7r#Zit86$Khlj?qs8r2`*EU--+qyPS=>d14eI#*B37v( zeMG5~MUD;o_5TuxQL)d?QGD)o;K*bL=d<(Gzn$PxO&xk3-B(c5a((2vQ9lrvYs$La zQj~v5yvxdTF|hgrfae`8mG)LIGlk1kME73ezM(Pt$9pUM!a_+gm1%TQmQh`aiPl(O z|GZ6Z;?yit%Se;YwEuB>Sb{kgP!}Ne@IZ5Hu@mOoCpB{zWnGloSF^({2{tYWb<~OB zQi|r|z+NA8Q|>S?y2Es_tm3yaaB049f{B3Hj+e91kJ%ClHnat6wSp^iI&S-8*PFW~ zuOipDV>n96%X*KD3|W5%L6)`hwOwhySAMT#_HEcTK@aot%vrAMw)s<5f-3a7_gY3N zby-SWq5&Sa6%fVJ)N&OBVYY|dTHqzEY*jlgCLac70>W%JW+YA>eGIjiD1G$xaxRM* zt#LV<15b3mF+GR+MPzyXbydHJ<=PrVOmlj%|9=4C?~e27t2_uj;E+iXh*nVzD;X}F z=&yegNbik)o(^ZGh*$Re?t-0`j*N?E8hvb+hwYA(AT)7|SduGTIy#FM_Onc|vs#Td zKQ>DCA@H&xakSu_PcyEe>#$f_;tmrew^@S_zQUK> zm4VPXcicOR?67pIr(8;oCXYu~&sb?H*JUBpY1qd?K3?m*g6wq8S&lGi%3;TQBP~*Wt&P3|1?& zv6dAVp!bHX=`8Y8?kJi_?fq8f>{L~J5VF&`ac=vc*xk1g6c%P=AifVRnXQPW1YevFLT^}iRu%5oFR>d!??+E&3}p_}MM#w*ct-)rOfHq3j3F*@$5=CT=wp4F$l z6hyX<{Is`O`OT=bN=rSP5Bi;*=L*MDUn+H0!L3a%&3p|r0ZcH#ok>5ebuW9C)~w_j8DVj8Hx0)b;N{|2tTw~(65Das%$7n@ zZeWFY^*FrVrR4%8*Soxn)kUD3jGp!PyRU_%pI#WUgM}Y0E`bK_2~2GiSN73HowUyq z)(hDwDaFL)fY@>Ac+&NB5&BLo%?Y-B76(>N7R#uvbWBwJWGk@hh|`ZPyFW{w7Me$K z<9I}ZB;=cB74j*B`2evdza3R-BFjHu2V?J^l$_d5RSOJ1zfZa+<$6Dz9^8l;u~g9i zxvv15Davhmnz7;RWwqr1QG~`sBR}^+0R3!oXG(FJQ&@Go#@qgBlVW1ei{2w2T1_zf zn^VMK-W>aALb&c`-lQm`g1>hzUfu${VB6e?RnSxRCGqt`sB2@@?ihz*(+eC?&O}7P z#@BYpTFf(G(?zKXM4AW);ix&Y%i?)wy{v(#?f(?G|Ni2lsT`xYXkq6K!T*?b2G-1O z3dN#ZQ6FOKjOwVT{7mu0QH2g47oSJC>Rg1 z2%}09ZRJOIWo*pkS&8PXedmp+K4mjqd?YiT0kUOYuStrrVMKQ#>; zR8<`P-)pniJ*@4nz*HmB>i2frIRiq=8(K>0&s++^H!9a`1tO{|3^35TGNGnnp=)8g70Re5=4s?dU^4Qsdnte4bGx{X$U!2Fcf>v9Oa z`!3cjli?XlK=*k2a#`Qb_ZQn}_VEkd%puZV;~WK8#a`ZY0av>lQA@bd*$}qH?8E3= zA0<~p)KGn-qBEEjVSY2~h#8bVG~0F#lDpZ8d#_Uck^&3-3!UN8D_bm{Y`~x%RUeq$ zIjeVvaCyc&n|I*&Daz{6Dx1UN5U9EHWpRm#EFo?y7-GxE$EBJdv>L#&zAMT3#}P4e zOn&y=fvIAVRNO|oQ^fG?Pv`e8h9#hAw=K;|VFuxh+}YAQ(pTC8R=BL}n9g@Y9=M*?`k&2u<+6qiKjBN! zFQt%7uL({kw!ekPXnoMXgh)W9bk9Hz%S;e{W1poc%$$(RDgtx(k*0e;&f&R-<<-3e z{Lx!HgJktfpPe!6bE4DPWy-Han$0fmz2h@4{L6b-ABNB599aU8RA?}Y1&3?=m0hc4DPa!)vnDgZ( zSXDD_w4oXIdhNo@kf631g)Lpl_7!Fhw{)Uk6u-1K6&5?AAi%G9feIRXVdX4x6Q_tN z`{|t~E_bHx`F>BmERcs-CX1yNr6|CH?S_97977!COX6Nl+u(_$Md!-G$q0gV)MK?dg6ktB^>~z109NiJiHJ6%_IvPZNwO zvom4!f0S;seKox?%jmTzw^+??>7)&Tk+N~zt90*d2h*!%MHyvdNXuOOalNe0^9qWZ z`}f>f5S~kkPc;!=IBBdE7<@b+_jIBrkbSlbR@WU6^woBNylzH?%y4P?&FX%b6Tim! zex(=#1bUfq;W>466sr6GrTtc;QA$b#5}pzJ_)&H~P+L7~;dM&O#*)YzF}#deffh1< zu6Fb-C4O8#kJ^)X{FCj*Vl!Z>3L>55k5T)(>Xf^XSec8V7@^xZM`;<>{jux8ROI~E zJJkFHQw2}bHw7qGvjEop^vv06N$5!=Oe=x@%|?>AR*P<}|41v0hpurs3^E5%aqAoB zVwM6RPXRKluc8$MVU@g-CPJh>hs4`=bG*)KYK8Nu+ud=wN?isaT0bGjT8pcl`=?iw zMLRN9bmS6jGKqjU?3qzMp|)QWZKUa7{ydh zet=Cn?G3XaT>lgT(KRR>30g9&{upGOHE|Ovyfx!y1+-TEh25Kx(d8Sl)WOqn00{h)tI1n|Bkc;ZQ@frVVTxzJXX|?mS7hOrQkG^m?*z#^ zs+>~4nZ=VxXUw;Gy&d7-XzCuL4vuL(maOI?5D_1N)k>KQ2U~w{EBeLR&e3hN z`!H^s$z8=E=BjV0Z}jl6CEsvYC-`wJBmXV9I9D09e#*cNI zanrZWO=Z>&qWeycLM_~9G~?Rdp`Y&_I3}&KrTQ!0WvL?f z@|82|oS{d}s5kuRHCnIdckU?@!bI@*H@;E_lO$hDwBE)XN6&W0Mcn}#e1185-Wv{* zfvXjfw{>#zc4$a2v8SSGKA{sYh(aQ3oBV}T^k%~AsqU)X=7dJ{ZINEaIMcMPIxDe)W7(`s{b+&(0^_!VUD)rfg_{(G z6zzNGq7N2&n=7_x>gsM^W0~;{#<_;rR+km%YvU5KDY>lk%*wvv9b{FW`2aGIQqYf4<(a=jWyu!N|?tsF~UTj$e7Y_-Qd^ zyPVDjS!5c8*@%*!kaW-7uQ2*~N+a7%qW2hgY?62!=xH6VGO`M5Lv4nZTUApp*Y%%l z11X^aX?XFT$8b7`#~R=u4X z4X4(F7I>$?0^iY@bi&PCeC^&V9PXK?9p|X6&Sdmj&NaY~sx+2<2wh{h%a-2O9AXSJ zoPN4FiM90%eC_kWjBL6xW21g5FL ziNEcW^G2Xd2MuX}V^ni_tAfvJx+V%mvCZFX92x2fPsZUs$G#aN7#$iMyJ1^ya+pmY zfq|p2QIcowaLnW93V3pNd6{%E1D_lK#IZ#eB}!ZaKX88c)1evm>zNP-)>as)%?ydu z@P870BS30?Njw&(HEJ0jQIJx}nsDi5DO|8j+>Y^)?wVTr5=a z!G(~nn~Y%1C4kLC0nYglR(Vh|HEX-5cd%nUR!xlxf>5wY`ou{QcSb3|g0+7W97Irq zF@~`QdAl(x4{%<%PYv&d8y_1b+EwL-xKCzR4`JWv49@Kz_^i=}r#O`akndHBw-ki4 znfn@1CW@rv@BK39k&`*S%e}Sr5Z;N(^sq z+s@AFTjmLw+YK|c7+^!~qoAn8hstjim{b5Y#NM5Rl$G54uQVl^sUriPyS{8TjHyAp zJ#W|Nwb=HRNL;)rgnGWLxWBu_&76ZEe=TW7J&6XTnE28EL;KCYB=+(UqGfYUtQ&8U z7a$~08F%GazAjH_-4OWAFpnrrd~iP`OsI-{NMIaeD9ur~R|n-{x~VV zAdnU7$oM<(G7I(jAG_W+19=ru&m`JP4=e^e1B0Y-c33Gwf3;RwEG#)xFs4mZCkMln z2l6xQu1>-E^^9rz->~)j$0I?-qdWit>X$4NSiFKD1XXa*S|A^9unTr&NV1YXc)4u; z)E}cA>+=$I_}T#of6MCbViqJXCS}BOTQ2~Z_3^f>VZ?F}V2W;VGaniFA&y#fu}a>M zfcn;34v1AJh5FXEXhy})7`6jt^~WlIE3Ux>L>}s@xTuadxIAor%ae-dd`f~fgw=l> zlvKtL-r=Ahw(35sa5mEjxn57d-ql|RK_X#~$4M|F! z(G3=RfcRBI)JU;sF!|m~^}vW(ebsltb+eLo@e4-RfFK@Jpvi&8vb=5*9P?JuAcLhG zVVc4~EkSHvIcXWnNJGW2E(53$bAxtadK_GW;%ISpepzYRu(jf2;(R=6{^uA%;~bwJ zTo8UOOF?H1R;%eo)K@&iBCN>rcY=@2Q9`pz=xfpeGkW1-F@FD2AZd}b!k9cCDJe)A zj1qyZSg#3TJ}#7IaWeSF9LxUF?*;Jfi{`n#R|rtkxO!ZcL;qONO3q-U`U~8&pBJXw z!ggcgrcWS5zoJO|3i*E8NSp=$HXx6t97}(9e+?$O#oE6!QYL6h164#Y>jI^e0>JRO zAZ94+LUqud>8el~2%Ky0Zv~1!DR3`PuT-b;>aszI{T$u?8UCE}rl49nhH=!m1DY&~ z;4l2Sw*9dSXLipXQ12-M`%c?18Wna~(Y(d?kseC@oDEy2=D96+xi*mqBhvFy>2t(j zG@hM!zlm%595(^c`7oatRO^ZG!b;ufBXLpQm^riS`~@L9s3r=BN)zwZra3L+wVgLD zwMc$|cJ4%49VjGg(s-t-&y6&pY|%09UJLZiVOz~yek6kOwY;3etc;}@tu|B8{Dgl` z3#GO%;N&5GQ6rKz-Z<-bnQVC*?9-a(aHCt$2~}?e@S9s;X%#IUGj;nlV=XKQOMwEO z0xiTv2NV`$dm&(&5uw_`jpEv==w)LK=bVxmqK?+A{UCK%)^#B4*EkmS5jkiqRN2XZ zI`esx6&K8#23WnmPYDoE}u!@YxXe_)ziCfN5sxg40K>}ew-o?e)3u(r?x>xSu8q$XJA7(8l1h;fqz0T zyNB8?6;}Kv?K%tb?Lkim21jHdUbn>*O~;=jWjXYK^9ZjmD4+y&zw@(u6IVTzR>?$6estV*s>d^fl%&>H%lKgdM%ER{EC-PPrXOV;N}%<;PJ(pAPL-t5^LHy4 z6#1kk$3RtSPFxPiRB?{yLJ%>qny^)9j-xYiv%ulL{1@ z3xbUTfj|*u%r@+3vUr0cuI)R%il(A=4oW!C`jFzpYk=f-OBJ>kAV<0!LOF%DBsvps z*hmH*cO7438}sIF8A5%Sc~%;^5xB7j z#nuKPT=J7*)~L`p$BnV`<45M3`x`*vkflgC z)RQ+}GxDiCB}Nf_Tv!MaCiUlQ0C;(Y1Wg--D?=RqDN7uonxD3C?z#MmXeF z)y(0Z|BHMB60(3miQlY}fNSS8;PhD=Ouug6wJ@tR)3-7ECFOY^9euJJHVj~u%a9R zgjASa0K0@nlZ_8EWKLH<=XZ^ly7JNJFJ`YrU&jui_5qLo!aT_l^*~mP;MA!7PD=o$ zs(g|Zgu>*8qrvGL4=wMC?i}p+en(Xz)?Y+_^}BVB*$jQ`FPuT+^5_h8>RhMIT-*p?CW9a zmBj%K4IVgVQI^Vs;0Mq=B+|Os?ktRsxrL*4$G6kpk^KkvCgyk98nZTzft15Ba2&|e zNiWMyk#hAnW(I$87d$#t3*zq&&5+jl4a*g_fnrfw!k>Lb^ zWRP{mZ(9*bd!sK=3rEC07&paJ8pKc1=;stoBZAcmA|Hzp2l4kUA9XLiKKxi?59;7#0jxl{1iP-b_5~+P4p3 z+!Rridoi-#@iVf|@QA1*cdV5m9{z;#jtmkOsyt)dOhnjGJDK2IcHv@C)9W?~`K=$f&`4#T3JxOi z`RV;#LWvJZ;$O|%`Qx0K?IceNaG_lsjZ{CsFCN!pbBYl5&f>ajFu4mabIr*<nVH zci$OI+G}p9fmM@_v~#Nh$Ww|)+~OrQ%3K&o7218uMP%@Y+QrWq0S#*9#S>zPPr`cY z$=1SZpWt7C{%! zQ;5d7*S|tu3w!l!b*9u`2qf(@HEv0t^BeqZdlPOSlx@<&Hi&1ziHXd{IZRQS)5L-_ zO%aYF-Kd4NK^G+~N3X7?Kmy4jb^Yo6@ZII=H0I@=avl9NhCdL&H?gQ=WrK7wI82Zp zKps|P3XsnRX?+-_WDZGL$(5u`*N!@_q60ZdOm3Mp9bV=+7s^EaLb9~XR_4;8td+Qu zL~yPjCD~vFMn4>!9WqfI{ojQMa2{C;)IidGmyKxOK}^hUd82$~b#IZIdD3!Linylt zqpB^j|FUGZtXs1nyd0eD2nGohb=!$yA<-6U-AhdUaavRY2kYch>PlQwnbdaCqD3;9 z^|oK4{=H!9Yk$I@{=VM4`D*ngs*OLJf5I=jmH8$wqOOay zwO9Sv+MhC1-~0qC>Mry+daS8CGWd=GzSy~`c(hPO<;{wthjyx%@X#1WRd+9vaNu5+ zuZQI1Lyl9$_3CbD(R9(cRtwQHw|_S=JKNbkxXr60NYYme_!uV7Q%sMGd_J7CBk0bW z?JY-N;PoNvqpURx=q*jpYA~MpAG)XgF?|tnJib9)Oh?M1#Y(^a`J+ECbC*JbCg0m| zkPVw%o@k233)N`8`0~frRK6G!R@JOg!TU**9CpQ@>K~PhxOEQV@(5$gZx=M8$xQAh zqvQ`?CdJQUXR~pjR&aNV%E3iHEm`}6KO6OoPa0D~8|y14F~&q)U<3JPKtEyMr39Bx zF-h0Q9C$#t4gVT{v^@{KVhBmPK-I^SRDsmwq>%Y%Wq8u&nwnE4u=@Sme|QG)T)UkE z9tW_!Xui_^-C9Unz`x5zSu!Gi=jKY;bdY)MXeWRR8q7<_cwT;azaS-%M!vJIofdC^3# zqHU_~3M+je1YcjQM?@D$m+X{PzGY;XCNF~eAFT4n9QWbvg}DnB;%+Jm2d zUvh0FH!NBF;tUkW{N-be_oW2oLG+)oqc_MJ@2oikHNr-G#XiBu>BK7V??sDOZ3L|g z^KYO70kqdd1;PQcWyt-yzEDk1aC^5uFv?m2hfTMa@NuI6TCyXP; zob%_e{U~VU?>*grG4GNXs<+w3x&E)NfeSjL{Ot+C$1EVx7Iz}7Ot%4e&YV3T*Ozr! z92r2c&3{7HNv21mZf$dB$clk#C1_c1eVN+J z^rXEntO${B`4IaZz{IMb9D4TeQf$~GYT&_%1>yI-ob`7SeLL}9;*7pb9DkD)K-@o@ z-1?E&Q_Y*vL3!b-yBeGiFoo7!SG_EQ)Dy07Hn*quMVp}i|CB$jt>WS&R?w`Wd+reOuxFl~ABXh;k*R&E0Y7tcG<5j_XT0fEB@YD>E9gh6B zOygCLB0Omf>DuSV6^7)WU`W<`f&nz1UO5q9ldDf16Fyur?6XafhwO`-&-rAtEX1dJ zw!!C^mKYUxJV!J}w@VhUl6yGEPH%H?z;%Tl9&mBnWRUoyUR$g7KHPHmk8JeOy0^@z8)K(bVD}k3;Y5rdq+_4CH&$x?$cXR|vlPfm zO#+{++NHSCtT`4i6TtSQjkTO%E*ll@E}E+5jJ?0m%p1eMJ}&km*QI_FY;5QKW^0&C z4H=wFfSC|$cLi!MRqPLl2HtM&EwKR){DuA25_?` zhqdY(sKQ^dR9K|8l*>;%5L;>jK2hONreRZhIF|VPV1HE3FAfjJUr*Zf-}~kYhl)mO z@OD5`t&2unu@M3j#O!x7A3f8AQW?0R@K*iy{Z>&OlM}8E_fy-{VsBK9`KKSs!E>(1 zl}C&!O?IgqYJfOJoH(H$7`+V)JCJ#q^Vs{3>%KJHZjAZOK{@evR232F4>Cw?W7t7z z3QKFb5V1Q#^1@h>Ge+^P<{tSVZ&kc+$KoSPfzs0HV7s>Fh^D!g zjNOpyg6^%gi7UEU#x_1vYI@c|3M%s)cj+ zeHgSs^89jUu*l^B@7JYkBs}4>-~uj2B+|{q$l{7FFuTWHL=WPe=z|0wNf8VC*L!8_ zI%xEze{RINXgMYzoT@(N04|0O-|#T3%WXv$l7Uc3Y=5U(My)mLG)E*`1qlRDp8dmT z$-fx6pOVC1*bxy4bqE^^M*DcuM3_Z>|KvRj(jxHLxp_d$llH67WYMw}0P5fQ<>jlT z*H&{?OBwzqS_w#d`d~(b;qtMFUuOH#!9n^tGsJF#FEJ{>y^2vBY|&8|s{MU@+RoIZ zB|zgF&3_|>=haIh?*~Y~@`^6VPSy7P{A_X?y+!%4Q(WB{=>u*t;cd%<#nA&BI@V5)Ia`H_DI)W($~=hg~E10cBZaP{xmzn7-rX=y`|II z^uu|}zg|AXK$ieN&o9c@`0vZ!Z~fiDJ+MuvHJ^jEyc*F<0ISRKSI5Y1PJxzGnn+*q4`zNz-v+{07)+Yj36&m@Jm#;QRid+giYlAP zxdA?Cy6&muDu}Q82Aw$uPf7P?OyYtyl_w4HAQzaW9|&)(&s5?DWA**C|6TVjDqg;8 zoNEx|y#HA%?LCWq|9uwc0%O}6{RcLW6JEHS<46jprb#TV{CO98f3B}&?pMNzXzvF( z(s7}PHu3-qogV0(5+QEN*3{YNAS=&61MlTd*a*i*3IIJDk)<$m{Xb~yV%8B58B5>X z9F*G0Ky1%~Et_Iq^lNEu@1<;chIM&rE}Og~T@LT}@S>-a`^sE8LV9AC045pBLG-<9 zBaKi6W=Hq`hP+8vUaoDdzchoJ`GGuhZ{vx-zY4f1v z<3Fc=<+{F|`-XEF$rBLXBa__{nQ=)0QX(w%eVA0gAc>Ot-rv_^+D~+$v#6}#+4gS#Zsrz9Rlvh@(1-dx#wy@pBE2pJ~#gkb4RB{7mW zdq?ZWTB$bVK*4pJ?Pk*e)4a0M+70YvgJn)I-nxZ)*SVwJUpGoE{?=bE(#Ed@KsuCF4x>7uSTx;<4*C$RwAgB9I z(XK%Uy{wp3+^(7ibahU6druCDNDC=g@oyKGgZ4EC@Yrw2#NS!PnUuVvyP$cd7E+x-x(Ws_dP?Jwv>mB>yHvW2iyg}(X}Csh^)U; z9!qaLnvj+&^)IevHavvY*iP88FgfV=MMd285X)IDnUh|_I3gcQVF;b4*Zip)$NuSC z8ntNZCgEYYdBU)#Uq{W911`c<4})zD z3aGYbX39@h=WCo0JLHa^EM59%v#i&sV2XleGyWUo?X#AreBIr6FKNFrSJ-EEL$s=H z&F%fPC8N3gxZVpy!#^f}W%D{pcH-C)pqp{STc}>0_KOkrmvhtqB*L;C=l*J(LvF43 zT^i;F{OW6h$RghLRFZ`HI+fq+@~O4P+z=&DJn6C-*<&pG$=3eV`6bW5IwdhGni`t+ zOBkXQ=14!-7EJY0YIup%+4`ZuLhJ-j4=qU@$9Tloem09J<+imUq&Rxa~)|3k@<9(cUjEFFg`G3@Xc|4SB`2K0tuH|T<=$uZAR6|0- zsGL$1MUi!sM7F7988c%ll{QU_En+GmiOD))7All&vQCVdNiq|I!5CxA;`fY7=XB0= zzQ5(4-#>l$=k4{p&vQTbecjh}-C=!+uw$P)B)qM7bfh$Rq&rHze^cn*7()hRKSnV2 zc&J_Do~m@Va%AysH(3Jiy-e#IY@{$MtixX7`j>jg04q-`l5#Yvo!mg-{&DDno8N7M z`RgR5#>4$ViOIo~A`>!}3K8vkFG##Cp{QBil9m zQq_snnkA@%_1@C*+BA5!V>fF^FU8;{Ot?QC!NWwS;w6h_zsAMX?=YeHYL6|!ET%jk zN#~>vr5Y5l!Y2<<^Xg;A*i(GyLu)Y`QuvT88tE=cMm)5QbC7i>Ui=y|DRngER(A=v zuqt)Tu2*`b3r4{iZCStfIDPIk{wVxt7s*{>?X>qnEb+}hwF{ff zjE{lJw6x3v!aTKtip<4t2@g?)Z{b}x^QULV;of_v1xnkSp(lK}S({OPw{7$T9K0qn z-+AT_)T*`qkQU;|GT}fY&&x9PtmRYGr;5kr7l5xmfoPbr7OKB`1;srg}#?YclZ`8Bxf&)_D+y7Pgp$dbk)yT!9vxitse6@*L2AKWK5SknXGYx6MH{& z=5Nu3hEF*0bJ;MTv1=dRvg&2^k5Jfc-LU1T-ek#=sYOE=hyEdWQ&+7Y-V|T!E$X6! z_L41bK4sl|(M(M#0dHAjhHXw#o5eN~?!)w$`%7GA3T(GdY_9oB9l-_Un&KOUF{rAr zYWX;P5`D9;P^v?tNR%n+Z|Kiac}SD9*x!1kovmW&us_Vz31)^EQ}?Yrxyd6ylhKYj z`mh`&J%4H<>1vxbInhm)6jn@f`~K7`^qW_!mj*m|rj zOsC(^+M7o;ebqH4;mL9u*i!ZQRF+>LOMqXTXVQN@9kDa2;GRrEeLVCMX4&;1$wxnq zMhSbtzcf(DAJ*uH+ww1s;f4DIgH7f#gE3^~&4)-SqNOboeYqe#tnTq)QL$fDg-rwE zbT8P4FiB^+gGraJ73TApWtTDrPG~tfk$27C`Uij+5C8%*^+;uZu6(g1WO(K#?#{;*PO=hCqN)9Cu^VF9uc()7IC+}^+>eCTm+rtqOi zoQK)p|A-ZrScqW+zNK6zq|P=tu19=Z|~w5on6EzCHD zCF&gklOp5e@z0yY1x@{;@s%1Q^DMf$gHU}$X*f$u51W0s2ay16uhXoFF|uK~Vq94x z?;CH#poY5`(mN#<^Yw-|({c_c)NAubM0Oct;UCKi8ay%P7MLLnC-TwU@7L%2X#n#= zmk~|u$Fsy$#xz^bItJY+J9J*dbxU|OX)gZ=bT9~KCJlXic$xi8h8Vzqmq73S(RU#2 z#igm+N{N(bf=mpC0VfmJ*@lN|v2l_)8ERv-VZ5Q@4LvAPRhE(UXxms+yt3^1(lIW@ zSFZu4jgh!cX>Dm4-iEa+aXPuwuVKtzOFZTl8!heR_r0YoUngw)ob9xplaLAMHVhwR z(WQ3Bl(D(6lR(&FS(6sFJ6;weo!mZc`1(}8(9-nGTQq%iTXN5(XZY*U((!wRXi*_) zf{l3AFc@NHI-)$+hc@IejGyH?uvSo%)Fa>sK6w`KATMsK^q!y*Um1;5Fb`c4ZZMRtO9>8(6r~;M z6$PWrDFH==x9inhF(e0@BENi>7Ks`qz8)g!kqjW`dU<-%xyB66=%m?EgfX`uwoI@D zebU)Us=$%*nK=PYJlJ0<)m@fW|N98$ww;%X4_Lk z5ntSb<~rGqmgBD#gokFexXCoJmU|)2BU@CS;3xn=^__{D*<|+M;vlnY2hx4o$?hk{A$@^s zKMRj~^jRTM$DO_aomsuT83O~wJqQtZllGR0Y(L^R?d{qaYmJVgNyG9z0zZc5FF$n7 zL@e#;9Q|zJO_umbYP$k(ck4m=?r<`p+@ftCp=Zf-Q!mQat(&cClF2D^yY)tAQ(kvx zVcg=qJ&Np}_!vFmv|4Iwuu28O>!S!pgBz6MWzUivf@};kliAOvVN^rEgYk>kX&=|d zONujfa2o3?YeS&$jn^77pYNYn9~f?x+!$%ztMl4C#wXGnp1A{)XqH&eYL(Kf8E8}qFu1HOiwAVm30x8r@fr(Q@9YXn4RX{_`TVIkS z4I4fqJr}&UW)Y=^_YCr08W)iQ{{#A-@6}SKfpA>xTswn&8iixE(25Nd0%B zl7z_vZ_AWZ9T#-#Nl*O1Q!t)--F-hw@0_s2qRbV)S9e&5;TXlEJ za>kqm)u!AoI|E6l5`P3ax6#uSJ@JVI+h~J0*X%PpsG^5aJ=701=4juiTA%fJ$r`wL zq4AwmYs@hVjELhJPA=r38-}J)t-A5l>D`q=v+=Jaf+Tdl6HFcdTC}cS$yD(OJ!+CxcU2N6r;3;C_e9FH8$b|Tk z9n<)XSpTrJ%x>9TQ|QvGr0LzcI5G`U1j&~^aYqlX)W+o&{?ZF)Q@bimov4^obPd;NrG9}Gi^#Ozr% z=Bx(y2%UGvh+mj4>c?6%P{zbj>VhUs6WPgh#JlkNA+?^AtT8u8aMN&?PdZ|xBCQJ| zy)bs+v8Xf%b zodL(n6Nt{o#|DcK+VK28EZwrSU7U={Yb9DYndvvA@h)1~_;w^RR`9wxF(awM7{GEK zXEeR5>Bde@!O_KRbr3|H7ps0d!{=Wu02enD-_~rl4>K@LQ#uNPjUoEbxTwAq(~GUc z)?>eF-RgKs!OgX%ymECNtu*bz7T(Sv)iwx3y-QMubWIDzX$-FGfK5}*WN|U>bbki0 zFj!#MA<4riH^9;Ryvd@gC!aodd5CO*_N=lE80OcXZKz~O1}ts)_c--KnTtPRlBbD0 zK-^JXAIIWSxt8SL^(w4KY^`8k9>_3erLd=fM5S2O&V+PPJT6wUm-ng zRWn$bzA%OGaP-FQ2tjkIpzmvo04{VgJ}*oRJyzwXJ``I**>`N!BV_mATLk>6eC4Tp zzKhqcZ)MB8T@TdWje~tGyGt-Qk}XW#Jt@zxfrmI@0YRR;Ars=F;;lu>b+T59RaZKe zC7t%1=ayoYZbSNA)+%B!B|ZDAb!QSzB?s?U;x7gKw$ z_;B+nTAzv8W4lJO^nJSa9e61`W&nAt0ot^$FNgw_*#KhI*J58^V*V`*eMnvM`QqIM zDOlW4S`@jZvZakfmc41XPI1qu!o>n?s(I}TA<1@|P{TGr98vzlz#(Mxy?Gz`m}|1E zDmIn{$tO>(kdgB%IwKr39IjxLcZORj+P zqzlc>w3iuC67NjoD3Kl3wHXiZ1n`sXo&38h3f zlrKtO7TF710j%JP3v5Q)c?8VBz_6^%c>QP#Is9LQ8r309wK4C*v z<^CI!UL&p5URRXJyFjirLAgqN0g=Q?I3xBt`-e-*h7?~_i&_P7;e*A5DO=>LFDP*p-JMI+1iM1*>vy196={FO`>=`91!S}d~NmW zRJu8SPX`U)V-;sw)W>=bYo=eUCi>y|M@ATNwk?v}v5(Y`g?Zf=$*o2&l|W{yqmbUhabH^ap4<~N_I@= zhrW-dAG?Hfy+ow*#W-C|b9X*i*k1j|tSpr-ADoNXSePfWQ>zs>5LIw=*cZ?D3KXnG zRWdL#Sc}D|2FA+5(ZRH_8BzKSdTcb2#fBhy(f5I7^V#G5Evd!Pvs-xqb3v;ofmKE2 zPFhTMD#VqC&CH|gqZzE!$3hk3(E$VXYRo`v$_c?Ccx9Keu~C?)(Wi6r_nc8%eyQgl zbG;WriHcO>4!kgQ#V=XCoiG*Z$qy?Q1YZy?eSEU1Ti6Y;Oe*D^f)dJ?=64RS*7C?b zExHh$W5^XAtStXKRK7!rAL^ZXgD~G{g^??_ZpjT$f%jCDmqi`Ya@FeMy&YCpw(9CI zHB5;hT3he}AIGfhvN{d6{d8WSIwNff9y;W;(J#4+`0T1I@LXlt=fXqm<8ONTV*%dK z&t~0g_ffihGMR;$vv~fVP>7y}>~_A#=tkBHq@P4;OnV1_B{zsEgrwAd7rHS8iwKb} zQH`&}{5B-E|7bD|!*>+Zl&D1mZZav>aak&4PbAt@rpwB~%zdQ77x8w*<0g*9_7DB? z_J!bV!7kPAMrV~b!qln*GCwdHmAIyFFi6_a(noC8Q^(dH zH!GDG;&3W_m3tGo_6JzOxu$K_%3=5@_2VAC%u!`cO=4K06z1XOFW9fg>5ZKtTlkJf zWaF$82QdEB6bgk{flNIf*xtaRLm)%BISaRoTW@|UWIm@Nh6Vdi>3&NG&z1O31~JB> zGNIEwZG#p(IjF3UWLfs9&}iQ26G=s>)}~CqqvLPeV`Tbyxa)LLV|X3*jWu#3;~|%=wkY>zy_dm6P7~{V09Q zs=hrDL_Po?#^gIiBD6egy_-HCR7pi%Qsv#`?mI9!P$Pz?xr5mHsGQi>;HQISUk|`- z>9c_`CDmDn-N{io2bX~RWd6i`^6hT^_UE79HA~(+H9X;6t@S4l>PmiYcHf0pG^hKu z1WF9>vo*hkzd55@aMIh!(MB_2_Q~F%WxS3~{GFqUIVgE@4oY{o-|)JE*XS1SYR(Ij zq|5-7Z!JUOk^OsnHohRpjQ{KkDDybZTJ^P!XR!{}_qCB{t@5`<9w}0VKke2kh2QJw}aCkGe<-KHX8@vy-qjj&>r3<`#wb*N8 z>x(kuXysL~#Pcf@zVU_e9kx!Y!k<;gF@A7CFoT_Gpxl2uEST&SrtRin_vHJ}@c391PI`9Ui8UU(nNh;8pRJziD8R=-wAQ#!Rr9?e1@uh3$vM1!MZnqx@)9o@GCaON)xM%u#p($013P2vn}O)Stx0 z=7pNM_9U$W=y>r=l$Pzw(JL&ySaRzD@{OTlfg$v@QVL+B>txcaV1k76nVH3v{e|Wh zE!7?m6|XZ+zRv4Ck|o6s(4fk}p&4%lq#$ZQj(@W6W2Hgp0~dk%+u{hGz|h1KauhX{ z?+3ftucnRfi7ZIFqq|6FTx9(@2fzRtQm-BwuDS5ji5e6RLdx78jx?lgZ zQ(NWlPd>&}Pp2G%Dh)13;+_wP>g!EBViM7H=`o>_c{wmVn36LPc}eEsY#77?`o$#+ z_1M>EDUPGBP zVhWLh5@W-*a+O-xkXcmQU2J9d{ylLk~#V7q$c*s_EulWwE7+ z4_L^mw!|P@QL0EMR$KTGDI z!;A0vcAfm=#DH4pZ#WQbSFh-gV`oj!7x_u4gh+3{lRfH`LF8d4S^TA;YkWT~wC)7K zPkB{b@btgHp+9Vuao!*=I;T#z4B#_X_K%pG?$~3COz=Yfi$l-)dbgdWcDTZCgi#EX z_^YGr|H|dUJ!}K_!zk0P(%e{qhTKj0tH?wuet|bSr zcc+}7C6&bXlSoye$GEkJsqX60Ifa`Gf7N6ACCSgDO8}NA3EVV0@`nZZ{;T-crJLeR z`wp|daQrx*i6&e$UA@`qdU>a@$>xzgpGgdO|H)%n#_m=_fw=rAP{Mx<@HP;{Qv98v zxMnLHzZqbo&`y5<)-jHIeQ$3}5O}_PVG#d7F0zv^1DBec`r#9;JbJ5u;<%Jq`?1wp zLd<>weHonJxsqv>kJtlaH|N)S?i?Mc-@1Q8984%DeZg~&F$cb*(%=7rFF$QKBws9m zLMc}o9Yu6JEc`3ik2JNX?(st?7RH`bcjU6qxkQ2x8LX~5-bb<2Ot@^Lf95Ay$&0T= zkAwuP+>Pb5i+TcLRQh{uM*T~^)H}$FgX-+qc>d}Zg7T0kuooS4&osaodPg7VZ(Bzl z;Pew^k{;#rKHn&ZDXPd2a9b6nI7|M1@Ck!hh-@lIVdO|i=7m|5*J` zL^y+3Pi0Us7r^t07Sfhb)G^ zt@xy6bQUIi>Tg$V`I9GuO$F#4c35$89-znF53Z^iN@~0?tbdToX+A(dqm4%zpW?N( zk!tVedH4_h+6+}+u{R+hwjP`bT#O2Tg_)J&@3cn#osOXUu8H2zJ;Q_562k=r)6rX_ zw($?T%i;}U^j*#lA`^B>6F2JvHdxBQy8UaG%w)S@6{n9!XU@^6*2s;e#FV&Lr}O(N z5yf`TlV;OGFi1boqUV`0IahPNhUR4!5OyGHy{;v6S2vrg(yxi$rsVl+vGf04y#FKo z9FcRFvGp(=oY_c==b`87rIOl}uI)wmDEv&|PrL?y1AFiH%LIO(Bq%F$>Zm(X#Qijg zK!OI0XbpHY;wO>q$Y0axdmx=|R)Hb*AQxmXifdA+z}G~&J5l_rdZkYjs@kResbP6z zEX7e9zvcoW&0S$4^W1H`BwBkDDPs&>5D_1dGPf;ySeRtU;cTmdbzgTJARXp2SGWVJ zzFv+mi0#I>3!=B>(?A5Ng#)MF1wV}jpxSC`>PW)y)_EjHaHn^zzKsnb&HXt0!i8bS z%G3b&XK`4NOOFKgEpHQhmLW7fa{Wt*eGDeq5qhj|pq6gWE@)5~ZO)a1$t4ihN927k zjcTq@jb+Cwci!~ROb1)BY9sxR(Lq-Rw}sT&_}P-3Tx-euf)R_pY|QqBvfRpK3=P{BfdT(V5LQftUD^$Ij%_Kof?q z`da*8PqSXzs-XN9c5d`9Wc~M1bpyBVl?=&J-@ zBr(r-23)Tvnc%c8Q;!zy_TswGwXq*o!R_%vgTqdAbz{B?)R;fr=q1RZX}@J}V?v4p*!))dJkO=?j|O(B~}^ z4c&j7Ya8&oVKR`J-@C*K%q})f2Q`*?bD=BSJA+mAq|HEBMAF$ETWHifa2q7T^7Ogz zi4Lq+#>#!U$IhIKC@<*mPbHy#DZ$0DHD{EA8wLg&I3IU&{J5ne7^Vb z(zBQ-^Oz*F0N#Lk-{sGBsslyku}?@O18PUGci-~r*aNP2JNqX(o6x(bfBaw*PX|S3 zX}E}41?&AS`7&xIeU(1%GSAR9c%Jg704VUfptFMy#a$^>b#O_5|^`^=OX^~Z&hSshCW)TV9l-A~Fu1!gp z)BEGro2V|t3zlwLO{#G&CUA|<{EP;1&t?oe1oelXW27iwkjpHnxh87S zm`qc+GPC8CGbr>uK1yx$)t^<^c7~8#qkeHYUlh@Ix>ykVj%m%Wbyep|RWsf}Zw9T) z42pqitKIEX`}V5e23@ML@^b zU2Q(|zD@HQ>c>f>T9OI=zR;)U1grz(BKp5pr`_u2h1V>FVnqanI zDO4`749$6;e?HWt>bKBdT>4y%K<}63QxIxL!bMiUm_lw~sgc%>s@R6|&T&8*D9Dr# zO>l7k5msiafE`E+9|Af?vyss_#{t((faZE#l}tY@hot>nLh*Uhy1eA)TNmUo^d1M_tKys{ z;BkXAcIH&ld$*0s)hk71Y?U@AjQXRqEM8Y_G?nCn`{X_ty`l($Ic%WCPWj1`y}n`5 z2jy6FZE@#`P;2iU$b=`H0G}s*hH*3#^%ueZeLz#vy4e6R%SQuy^Kng$Wme171g*4h z8j}ADB-+lh<+a0>&EIQvDV#D<^5%5jzK)kOd% z_75#Zxz;i560g2FgoBl- zR}}Xsjy#rQb=}>ksgz)_;a>%Y58ZiVPGn9Wn6_gFa-&B>p^ja3g|Ek9lu_`!K56bp zVpNU;!Od0Z4*+Nlo&V8=B`?>$vGWtzDFe5&gQuM3JxfQ=?e<%UM&SDdJ+6MdSAj_ywlsTj|A}KFZ9X=KGD`sz5u|7#FOp7`%*F1@ar77Q6TM zs1_U9UZ{TLVd1VWW)fB96-V1;Rh}TrFV4LGHX(y1R+}xYdU0kbfiDdeagTTG1ZJcM z+yA3t$uCE_=-|^5=C)aC^V*A)XN0e4N6zcu-a4{^7t1i@BYJzQb#vUp8ZL+g4IGJX zK>-*|`2Ht|i`*W-6jMe_s(#5Yde6We(qdMv;yObD@|$=bhx!tb4Cc~gfIttAyeTM` z#5aKv=8bU>wqjTa7!G`+JmMzR{(D+|{d-V~eL<c#*D2XIH1;UhhY6ni_^FZ zo&-BqrLz{9<=Oj*e1Z<#hbJ(4(APxnx0K|rhL7Y;{#T%f_4Cf#7iQqTft_}im8&eN z4-17OtAfpjydpzfIxHKR%mmJWkzVjTw&gnf+cP6DI~I2LQypt<+}o``BoNYQ!Ff4J?~4MO_;KdIhhyKS)gam9`}#2s z_r)WL{Nd=r-Yp6xvn!u#oNvD%>Tng10K)CD0CBUHp34;W93|y@IoCF<3dde#(xuaJ zYuJ^#>_1^b2g{j~sz0D1(PQ=55CR-`F!XD@l$#H+B8LL!KFC!WZ9=X9!jh_8obK z#6`Y6^HpYk89-MmKY^|s<+U#Lm=@cb?bQZk<`)M=2YsCU^UdgBI)=kWjX-wtR4YxN zyPgB*Tsh1nAT!TDk&7Py+EySsp^7BW;vi@KD-fVa@=jvTpuV@g1+%!iv`~H9?iMEH z?BQ{hxz2W7uMl4#GY68Au}NW>ZeamufQF|mQZc&na0UPhfwa}cp{I2_DXDWlX_(r0 z8M{sIXvcZt+-IezwdZh4$}2eKYrpHvhc|jxE3KcPQU4>XTpH&}?D#4)-zOKECl!fz z&1LmIRV#|+7i6KF;N*RIx+QW@iLE$9zmS7e*XWt$M9@OkG+CtLO>{~}s;oO-q*`gR z`iebB;tjzhqEmyWkpj!?)$3hfdkg&ox^j~* zASeep&`S}6j(QpavDw}?bp)OG#nl;C-I!fJ2?=sETdrA7!WfVM>)8vQQTwM?mS?S~ zuy!zZ^pPD3-b4(pSQ^OP9l2qg!vW0Ma!!|mBAibLa^=@QEvNxj7btiEgaZJhYA@Om zy7$v6S9tiyg$EBvF*#jrOSRbU1v%@|Ivjdrx7id zJ8~%T!Qip_kvH#)Clv)ZHjI^x+%$}Tg3Zu_+ zYk6WDopbPu6B8n|xRHeHv;1I!CEV68H}6c2==C0e@UEKI(3q5De4<%+p#6;mwvB#l z{R*xpF+!EU@R;IwAwSh}x07Y?n_2g79CB$#CC54U2G$_Qh|>q4>+S^36<%@iD-ijv zK8K&H?~xZc1nIh|5~g8cNfz zUN!QD-o%xDp>B-Uh{6DWDd+TW!0qnIssE^09No!TakPe->3yr?gr^uv7fWyUn?wu* z?s;}oSk-r5L>vxcFLThQ07=+rSAcAp6CEHfzKRi-aSDrEpk#fb9pTc`XZ=)#*LgcI zE4Hw;Tex|>u!aQ8nCku}QQG8Py(C~lSn&@G2RspA12RT)9M!tB6nO}ry*Q;$@N!Ck zM|Zvzv9D!8HRlSHn;n)MLn0i1dHfTR>4B#*Geo9%K#kRMH^MPPq zn^)CA-#`YM9dp75MuT8RqfusOBk$^Otr-OpJ)?ECgc|>0XqiOkO}OZrZ0G1ye(g`X z*QN4S3NV=uO%3@iV|C~46wbRgq~vz-Z=rd+iCF`3IZX|JBr)DdPS>CvH5` z;?~Adk1ziRa{Fy>d>5{~dGZ_VJLf5sy_RVq1+A7ui7JLihAIj7fwha7&+blkFu z({;Pld?&9rEZllgKxSIR&&-^)64###k8=GoJh*2!|lbbXXeAxT8$q=0=SMG-QT1k zz`N@&=1jbhpCB~rL1D-O9Z;ptL}1SJuFds*Ji#i}f8*k($a+_~H`4)J1%!XjYr`wO zl3Kc$WQ1KdZn>5cwckurt?>DGhX40UX#A%QasndL2ROztfg-7b-;WwLteQ0vq=`0g zqZQ)ggzrptB9!@__EfsksHA%cT3n4YRM*&>0Nm05%0}?ZJr*_`QKF}CWhTp8O@E<+ zANoQCw|~$#Zp^egPuM`rjjaf!l&{ajCawh1n>t zZ#{uNZ*9O9e|yn}og>{EdXxhrQSXrssfA%hy`Xb76mQR$h_p%YzlIuj24WN0vTVw#8UK;h8Cg z!5sUAVy8_pEZhSoaw;ZJ%5CZ7A{1iMh9JKOuGBMET*aQ=ugnwUpAT$jPH%Q2kJF$# z0y^%-ly~Hv77_7r_Tt5Q>?zR`a=jn8cIQfVj`LJkeq}#U zjy(&#Aa=HAB9Q^!Iq9@G!0_AxD9}I0a^*H!Rlu9+I-S0pS;W7z;$u%J&Evq;o~qtN zA3c$hSCS15+8PscsFJIgCDXvC~JgFPNj)rmjpY91JSHDbwGF zjq%S$OcOTaBGxg4$fAJG061-}C=TTj(N_g)s=0>H{K=E$T7B8;?$em4f~d#~e1AZJ zzU%moxu#pxrt0l9j~$3r>Pt*0rXlVWRPRrE*;SO@l#t}$18gh=2-QQ>`JP$9& zBgGfxm)J>~7E-%E*H#X#%U4d>g#Mef5u2laxh8OU@ogwtj<2ehm5 z;P~EsOA0_*b$W3m^I_Al{yOTi{ikgcTSm>@opekKqng1c)Ccx?Kks7#=K4W^p9Rb? z72lZE4Df?xueF?o(Sv#q!3e!i(E~cRuls|TiLBcE=WX59)tB&YZ#ElNx1Lv4O zwRvZM5)$NGNX*CA031$m)yCQ95e|F)zJ;dCkvMybEYSzG$b@m`b5UUO?ikQd#Bpr3 zORqi-0P z+gt5^nsthNa9`GN5u1c@4G+=y^=;8*6KGDR4&{U%TW<;AW*aK*rjBb|zBrhs4Sq0rN;`7hMi|21wj7+0swIus^5JE5)~VRQm$r=N^xsT9vz)+5#8T zJA-fP1HAf7WN#SrO&nLb(nc2M6NGGp#HuDST(q##Cug}Vwn>we9CoUpdZ*-LTfd4XX1e^xk`}D_nyQ&*fAmA5FkM zzI*L0mt9JXPSiYK`0}jZ7cbM$w9UglLA}7FfkiKVwzi_QDekVoJfRb%->Z$7MgC=8;Mrd=%@ykS^JLpR$@ z9@~7Cn$Bp1$%;;kP~hgSbpkNKXjF0U40iC{hb#WUEd6@y0WAa|bhB#25X{E&h(q^I zQs6$cG4G=xM%R5z2l(Z%!KeK$euh^FwtnjMv4w$|{Cl^;h+C`{&1Ta~R290(iA>ub%Yt>^R-}0tJ|J8R+Y({PUF)wvRvZs@xuq6B%y-lNSFOFb!MKZo8D$7k2PfaB=tLzD{DM z7OOb{vZ!~?|Bj{U2@C3=xOY+e$T?=h zPJA~YNB#PfC(FrXm}7z$rWQ_>yGs#-wtc+48@=F*1StQx`5Tq^MHN$owKWW`@m?qP z8tbFI9d)yVg<&nvEHzMJ>X8>tq>xsm!Jz1CNnoDEFQoGrLu6RM^7-v4>;5@>woszJkx zIP~smT!0_U`KXd5wh+a?17rLKD@WoqkA)cVYT=w#iwCAZXa?^`D9$s0AaNF(TLr_m zR>3@R=`K6qE(6gKc}uceNp$DY9#GB08w;)59}?|qD%w1r#k=oAi!!G1y?>7j-ljMt zj3d~lr#}Kfct0c#DF5kCbr-~Q?*)WqPgGb=dOU{CZbs3;RaKyeNabtB;e4H+ ztMEAzP(l2v^V1?)?mTYN2inBou7BZw%XOjX3XQ&!ymFlvXq|2|D8D9(%Y1eX&Z#XW zVeIaMW5Hcq&1C|uOy?@L`v2s@0_EDpY5YoxxRgk4AZ~$f9`--im#CMI-$B~{2*PV3 zB{4GgN0N_2i;DTp{4?$rU%V9eDMl3|ewQN)O5IBQ#c_nwp$ONnQvd>0h+qhSzAGBR zm%Vr^mgX5@4DJ)emDdnp6?Q0I1z>*!di#Lem-NndWblq1{LS^xKEJc357cFn;H#hU zrVF+~9->yH-EEP>F|d8y;X!-G^ZY>jWl$l26ACvd?hg0Zf`oj+TFxPfxhasBBwiRo z?eKdXIdD+)`Dn4Dqzd`*AU|Ko`|QJc=f+U!NG<$k`v0(;c=10@HC}~pQJ#>jwRY8E z`>m{XYwVFOe5NUW2T}RjahF#n$LtgQpiVPC_=N9Y<_DbDkb=U3$nl%h@?@kczpqrG z*yh72IXonHE3*M^We{W96s;`;;8w;jJT|gC)#qXXGk-3B2Cyp=gkszuVt`#)SO6IY zN=#qpJ%IVJ{y(Y|a&sJB3ux_g&m(8DO`L_3jy_+7q8>%9SeVfv%`x(X;z@~!-HWT? zj@p#4(fgS4W_^INBq&B%fPQ%vn}d&|-E|0YT~aK*>s^LK98ctWWEG5(Zd9TJ@N_lz zv-gS{)HPa~<6ya}{CIE!mEBj|K?P5o!;Z!f^w@k`M8A5I&$C_WNK$`^^S#i_MR+7I zp*4I&B{V-UjTk_@7@NG(pRic$=%|X+RFnJpf}1$ym11VzIDZZ>^Nh!s3#!F~Bl_2Q zQ$KgsZ442fHJL21@-Y-3k6jrCp8qxTfGRLIfn=?NnKS4H2JATJ0i_grX({o)<{+T( zU`uPdzVY6RINoKrMW~MXjv1?0FGduMh=8aW#i|h92v1fwm z$Vv+Pie?L!8jq)K>YKga#6CAU#`hmd(hcPFZ-;XD-d9essK&_SAuYV+!38o zPVs$Qrf>~Ty_cs_`BA`hXK&f3K)xHZvNo#3`Djr=(2Ycq{c@7+GO=Fq@Ko|eUaw2@ zBB$W2P<8&3sG8`vDWCo#4GdVD zv`cKMT%BY5v^tMP$me46wTX#k`M4282Ly3=B&%TgYkIoAa#vMbQL|{@9#dD*!55NkKUjkge%6tFYzraN7~)SZ+Ud(I?&OCx8X%6R4f zt2qE<7Yh~`&C#dD2KJW+n6D%)P5j~WP|7wEb<6Q4b+x5F!<-rG|&Emy}O zO9xX54AGjjEm~eoR=hA@4e)(|Kl}1X`eEqx15i_MzpBh2FY&4f5*Im?}&~leQqRH;H_8&}J<7<42 za9%HtH?^Pta%7}!Aqo8(T(x+$KIc18wk1>z^KK45QF zm^O958-z|BCVf>R_5+j_Eo3t9$NP5cu`M;#YRdi#zm;=oHUo3UkHh*Agg(^Kh^@uU zz-5Pi7oS0L9QVs6#iuoq&$f2g*de^pOu=C8`O7Z?8ysn#h~`7P;k(+Rd&$mA#8%tHcqBIHu#3=P`>lT6v5 z=Za#)?Jt6F{m1u|k0L%l201^5(jHq6&@M10 zw~`36t~ECb7r4GiEhhxEd8~qhp`*ny%?z)#S0aZcvR*@Vd(F8c_G?+>LRV<*z+oxUTK=+=+WA&c@w<8|5#B z_R*vrP}5cKOv|cZR2cMi6-+nIR9yx(U;g_g|2`UR{>ZMW>?#-z=tebUM)0*?2IV>> zCkDZaD~>qp#^Em^U`~hD;nLW2rn`R-!I0niZ6X+I!N1O7F0q>GN|BKUc^$j7S=M>* z@hQ1fFeR7B#TYhwm%;AGt+C)_~Kedlv3by)WacK;xLcYvRVuq8Gz@Hlxc zUR*2A?mrcm;w!^~kwlcw3IG1dW=K>ggGtn!&2iWg)b)mn@IPT^Yp;;3qBbdX%F1zbFX3P&mn1+wUH{KE4cU^!SE=vnineSkbN`PX)C+_p0c6zp=(vGyawO3<}G04(rv>jx%*PPpXs>h@{~+ z1MU}P?#vfiT(C zA={`Ngnh$QzQSM?^!q6=juMwnx}|q#FeXL#j<~z~Hf=shVE5FO@43#DHjMW6=wpP7 z7F2Rhm`F6zc=)WGa-tjgZsEj;+rRx#Qakm8HfCUu7V^f<49Xu)%Hib8auPc-qxLg| z*{V;n^*x_u8D>AY#M65iY59RR}X@XAw* zz~Ot!t5^bI{rM+zGJj63m{F#imG&8##I!CY96)D1E%S>gwB}_zFb&Jja=$wgtuLVh zK0XbiF%Le5f{}zm(@nA!TW0)i4JLw234X(^QdH{4SO{jtc|_7V+}qIFs?R1y1?Tl{ zP{r<&hL=Jprp4_dPsawAwsW*W&~@sjo2=l1XsZr}_gd0_X7#_HTNuo(8Q40nx2pEM zk~GF$+lmd0Tv9Mhy_0QjUF$c+X+9;-XE*K^BRiy^JIo383SHaOl>3TM4C3pPsQpDF z3f|U#>eF)6CtfUwUIWc5(NO=3+~`` zfwKa4I1u&s_7=|?(sG5D1TDsWwdqhI=V&nA5t>{;$@~G^(j{-Q#Kz zaj0OaSfCQCM-h=CdO$@*_xr?bqCXEYPz|*; zDeyx1w&_Ep(u`#*1H)C}K+7_bXXlPvJjtC>Ns2m(!Y?gLpn4oP&(!B#qUm;DB&?&y z8E#*F?jD7(WI=NyYA8TC*b;HrEVMFlH>Mu-DmgOdSccD2$f>CQ{o+Q`Qu^X1Muj*B z1bnuyP@bU#<_CNx z9Gr6^WWK&csd4pgr);g#LtHCpOdRqKp+|L@bav`2M4xzd;=l+%8y@c*_Mk%mszzgR zm&bNpi1|eO>uY?KU&Bdy{?a3TN2%ik!t;TGauBt*5p+oiW0oWZW>#PIF&-mUTtA<4 zF^lT`N=rQ{F3xaBN9FVYZO-Z6peZhTE_66SvBRg`8jYhqa=jtq7(sXB2pV!? zRUsJC+FPqL>4fYcn%id7$bwdKPllDWBZ`@-ewF`-Ly&9TA2QvD$0w^Rq91a7fgaPr z-&@UjRJ-vc*avzKSJKu#2+R(l1SN< zl3%l^ANOOG7UkluiTQ4WjA{+zRd5t$cYCMjIt~?-{JQ-QrVoDi*UTm1j`Y;8^~+hJ z`V0?>^R*fJ!Z+R`4oTkW<|Lm84`7yEVk}a6^i$*h;g4T~h1KBevv_!Sx zEzNk;*-p%}>$@8#cmJ4#E9&1{x5}%&L)WD~6y&yrQmM_aqkN}EuzuBZ!HZd9a|pBw z^oe9y-$ROk!GGpI?v!_K=Adg(`t2fctS49=Tn^)9f?J^m2oMq>JLNFx5 zyFfGMfV>-M{lP7B1&h0mzv@lE!iPncd`1N+;=AYT_yxrI9ek$tP^eud7X?PbX)`qX zU@$2bv8S2EYicRzgJ&S`ZuU~|V2%o2FW#g_hmAM*l`*pfcT=mGhKV##T6o~BBGn_o zkov&<2BI{f;zn|(9JvQ}=hffDJ>i}ijdiOURl z32p3EYBVP|)u+G|m}ordE6llQI=vSi-U*V32c|vGhGMqTp7q*?i_Wo=5Os3qKOjJYNOmk#F+%((PZ}R(?bZ6OYh^%Hj+kl{H@JW_J90+AusnwXn zTGUSB`b?Au#rVDDU3re?XE(G%r>K>+G;T_ews&cXY1f1dXCOy+BDLLHf5=hGG?Cxz zf%>(Wmt<{Brg_}B_(AEq4HKN^2S&6r%Ksrp<)VT*$}ptYN=sW2Dw0{->Bm0cI(t=5 zMxQ?0xaczPF@+sYe|JWbt75h1>^GKvqEXunMIWwIx>a8v`k7#4z^6HF=>WZUh-rF` zN9S|9wOUlgb%ez-Ydh<13|VI2__`P)+2uzJNn%I%!*^8cz$fY|OdZC7BBI_Rv}bM8 zl-^XCZqxW1L2?sWq&wfY#$Dth`kB#pOw?t zxi;`tSEKY{8npS)Oj<;|YtYgrKz-;q?G`7&>k0&&h6@g1vpCK&`HIjsv~^U;%F%+# zDIL0~khg#3VCj+eWiHm)y0-D$SYs$o?g!%cA4u_={@6k|mD|M=rLza2<3N<}YSw1C z=)!q!N2&~(!IeGqH5WLqtaH=A{y=>=29goo1Ux|iw`u)`g%q<^cEIV;Npnv@-s7~y&kRX>bvddk>C%f}&cQ&V@Or9S<5FWxYk0$<}{ z7IgO&kqf);{}z#}+w?I&6q9Q?&CFC%*zwDK-j z2fqGV!2k1Z|MDZmb{06t5iH~HuvGOISURBl087V*X~~+F%4Eo?=Tq=i8yz+S#y!Lk zzo+qTJN$2W$HIH3*^C(`dv@*c`1V6v02AVcOu>|pKvQmy7*Q9L3ecg%LpRQI6;2jF z+Y%3Vi!By6%6I9?RIJP{QWI!>Mo{}omZK}u(=bmGbLm{2Hhz)NYdPB~fqT?E&f%XT zf&xw$I1_%R>Z;)tM@6OST?AZgQ{#K4wn_Tgd|;6(p`0gJ zN4ifqlBvJ`SU`n|0GOKB!{gU@;$0K-dca$t{_9V3n_@8mB!EmOF7PeI+#brwi~kuk z-SY=D<$Db-ejuk#=JSYc>?cmwzR{WVJ6_9=#s}y9AE@co6zPlcalY-4lYQ9!WhhY> zcX1i-P4zD-yAzA*cp)_FA;J)0we)<~jxDFbZOflvj!!nCzQNOeu0XJ*Zd2$~C}4S=5ot|7jsnQn z=aC9$_acaN@+U0NH$cIE*7 zs=wzn$-hgR;-<@xaO9UpY_t=HVbP$8R8Z&*OwyYIj z**%CT*u?5q2y?22j#?TVr7ZVDSDf6Fs zHD?HAUS^z`)Z_s;mmkjXo3AQy4yzAZ8{R{XRT(;NuY~ZadtJ6SBXyD0>%7~m;$g44 zH;})F#_5^peK)O3RvV-*B{~Q%=m`()lYo1?xI?OI%Dm&Il%T3R^XoN-+7x8JNZF&emPwOsM}-$-*{n%T{}a$u zm3b(ENZsNZ~PwDj!(mwT6JB)R_d zCBvs+^aNo1^JJiwR+~eWYRBi>r#ArZbA?urV=&mdThPcX)CFe4qFtp&2b-DElHV+9 z9Y->nZI8ySWfwe>wu-;!M8}K|zZ*GzwRrQI(6lcRELEX(5`fFF7PvV|mGf5|#unel z|DBQ^I81A)v?St7)HNNLImCQ|t{Huky;SjYolYW&Tl_}4#R)UsHT~s2<~mNfUJt?A zQ2&}la=8sxi7Z;-wIu$^R~M+zR?YNe_bK2b_>> z?@bf1DI08PzBL+OOcO7EZ@68;Rn>)BM`lx6n2hcZlr(q!#`&$`ys?cVZzu}`en45X zeFM%xpgl^(2CzRHkF-C_T=EAc{ptfHjo3vQIzAG+oMtp!urDRCAkgK<=bo!EvDEuF zYjj<3+FqBB8W7?)b|t`GAh$XBR8o?rD$`aLz4tx(-p7~@65}(jfS@wwUEbbiab~Su^Xs*VhO1=lPLdqOphSq9r03 Self { - RenderingMode::Rgb - } -} diff --git a/packages/tui/src/focus.rs b/packages/rink/src/focus.rs similarity index 98% rename from packages/tui/src/focus.rs rename to packages/rink/src/focus.rs index 919182e4f..b42e10b55 100644 --- a/packages/tui/src/focus.rs +++ b/packages/rink/src/focus.rs @@ -19,8 +19,9 @@ use dioxus_native_core::node_ref::NodeView; #[derive(Component)] pub struct Focused(pub bool); -#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] pub(crate) enum FocusLevel { + #[default] Unfocusable, Focusable, Ordered(std::num::NonZeroU16), @@ -58,12 +59,6 @@ impl Ord for FocusLevel { } } -impl Default for FocusLevel { - fn default() -> Self { - FocusLevel::Unfocusable - } -} - #[derive(Clone, PartialEq, Debug, Default, Component)] pub(crate) struct Focus { pub level: FocusLevel, diff --git a/packages/tui/src/hooks.rs b/packages/rink/src/hooks.rs similarity index 100% rename from packages/tui/src/hooks.rs rename to packages/rink/src/hooks.rs diff --git a/packages/tui/src/layout.rs b/packages/rink/src/layout.rs similarity index 100% rename from packages/tui/src/layout.rs rename to packages/rink/src/layout.rs diff --git a/packages/tui/src/lib.rs b/packages/rink/src/lib.rs similarity index 98% rename from packages/tui/src/lib.rs rename to packages/rink/src/lib.rs index 6112f3bf5..9e1a4dbf1 100644 --- a/packages/tui/src/lib.rs +++ b/packages/rink/src/lib.rs @@ -27,8 +27,6 @@ use tokio::select; use tui::{backend::CrosstermBackend, layout::Rect, Terminal}; mod config; -#[cfg(feature = "dioxus-bindings")] -pub mod dioxus; mod focus; mod hooks; mod layout; @@ -39,16 +37,10 @@ mod render; mod style; mod style_attributes; mod widget; -#[cfg(feature = "dioxus-bindings")] -mod widgets; -#[cfg(feature = "dioxus-bindings")] -pub use crate::dioxus::*; pub use config::*; pub use hooks::*; pub use query::Query; -#[cfg(feature = "dioxus-bindings")] -pub use widgets::*; // the layout space has a multiplier of 10 to minimize rounding errors pub(crate) fn screen_to_layout_space(screen: u16) -> f32 { @@ -69,6 +61,10 @@ pub struct TuiContext { } impl TuiContext { + pub fn new(tx: UnboundedSender) -> Self { + Self { tx } + } + pub fn quit(&self) { self.tx.unbounded_send(InputEvent::Close).unwrap(); } diff --git a/packages/tui/src/prelude/mod.rs b/packages/rink/src/prelude/mod.rs similarity index 100% rename from packages/tui/src/prelude/mod.rs rename to packages/rink/src/prelude/mod.rs diff --git a/packages/tui/src/prevent_default.rs b/packages/rink/src/prevent_default.rs similarity index 94% rename from packages/tui/src/prevent_default.rs rename to packages/rink/src/prevent_default.rs index 8e1761c5f..cadd262f2 100644 --- a/packages/tui/src/prevent_default.rs +++ b/packages/rink/src/prevent_default.rs @@ -2,7 +2,7 @@ use dioxus_native_core::{node_ref::NodeView, Dependancy, SendAnyMap, State}; use dioxus_native_core_macro::partial_derive_state; use shipyard::Component; -#[derive(PartialEq, Debug, Clone, Copy, Component)] +#[derive(PartialEq, Debug, Clone, Copy, Component, Default)] pub(crate) enum PreventDefault { Focus, KeyPress, @@ -14,6 +14,7 @@ pub(crate) enum PreventDefault { MouseEnter, MouseLeave, MouseOut, + #[default] Unknown, MouseOver, ContextMenu, @@ -21,12 +22,6 @@ pub(crate) enum PreventDefault { MouseUp, } -impl Default for PreventDefault { - fn default() -> Self { - PreventDefault::Unknown - } -} - #[partial_derive_state] impl State for PreventDefault { type ParentDependencies = (); diff --git a/packages/tui/src/query.rs b/packages/rink/src/query.rs similarity index 96% rename from packages/tui/src/query.rs rename to packages/rink/src/query.rs index 3a11d0f2d..f28c4978f 100644 --- a/packages/tui/src/query.rs +++ b/packages/rink/src/query.rs @@ -47,6 +47,10 @@ pub struct Query { } impl Query { + pub fn new(rdom: Arc>, stretch: Arc>) -> Self { + Self { rdom, stretch } + } + pub fn get(&self, id: NodeId) -> ElementRef { let rdom = self.rdom.read(); let stretch = self.stretch.lock(); diff --git a/packages/tui/src/render.rs b/packages/rink/src/render.rs similarity index 100% rename from packages/tui/src/render.rs rename to packages/rink/src/render.rs diff --git a/packages/tui/src/style.rs b/packages/rink/src/style.rs similarity index 100% rename from packages/tui/src/style.rs rename to packages/rink/src/style.rs diff --git a/packages/tui/src/style_attributes.rs b/packages/rink/src/style_attributes.rs similarity index 100% rename from packages/tui/src/style_attributes.rs rename to packages/rink/src/style_attributes.rs diff --git a/packages/tui/src/widget.rs b/packages/rink/src/widget.rs similarity index 100% rename from packages/tui/src/widget.rs rename to packages/rink/src/widget.rs diff --git a/packages/rink/test.html b/packages/rink/test.html new file mode 100644 index 000000000..5298c723e --- /dev/null +++ b/packages/rink/test.html @@ -0,0 +1,73 @@ + + + + + Test + + + + +

+
+
+

Hello World

+

This is a test

+
+
+
+ + + + \ No newline at end of file From daac9f540f1128ebd81fc9790665acb639754745 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 13 Mar 2023 17:49:37 -0500 Subject: [PATCH 068/103] document every item in native-core --- packages/dioxus-tui/src/lib.rs | 6 +- packages/native-core-macro/src/lib.rs | 11 +- packages/native-core/src/dioxus.rs | 17 +- packages/native-core/src/layout_attributes.rs | 4 + packages/native-core/src/lib.rs | 19 ++- packages/native-core/src/node.rs | 54 +++++- packages/native-core/src/node_ref.rs | 10 +- packages/native-core/src/node_watcher.rs | 4 +- packages/native-core/src/passes.rs | 161 ++++++++---------- packages/native-core/src/real_dom.rs | 130 +++++++++++--- packages/native-core/src/tree.rs | 52 ++++-- packages/native-core/src/utils/cursor.rs | 2 + packages/native-core/src/utils/mod.rs | 4 + .../src/utils/persistant_iterator.rs | 71 +++++--- packages/rink/examples/widgets.rs | 93 ++++++++++ packages/rink/src/focus.rs | 6 +- packages/rink/src/hooks.rs | 2 +- packages/rink/src/layout.rs | 2 +- packages/rink/src/lib.rs | 2 +- packages/rink/src/prevent_default.rs | 2 +- packages/rink/src/query.rs | 2 +- packages/rink/src/render.rs | 4 +- packages/rink/src/style_attributes.rs | 2 +- 23 files changed, 479 insertions(+), 181 deletions(-) create mode 100644 packages/rink/examples/widgets.rs diff --git a/packages/dioxus-tui/src/lib.rs b/packages/dioxus-tui/src/lib.rs index 99df66df9..ece97b673 100644 --- a/packages/dioxus-tui/src/lib.rs +++ b/packages/dioxus-tui/src/lib.rs @@ -9,10 +9,8 @@ use std::{ use dioxus_core::{Component, ElementId, VirtualDom}; use dioxus_html::EventData; -use dioxus_native_core::{ - dioxus::{DioxusState, NodeImmutableDioxusExt}, - NodeId, RealDom, -}; +use dioxus_native_core::dioxus::{DioxusState, NodeImmutableDioxusExt}; +use dioxus_native_core::prelude::*; pub use rink::Config; use rink::{query::Query, render, Renderer, TuiContext}; diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index a5d6af001..6132d898d 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -300,19 +300,18 @@ pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream { #defaultness #unsafety #impl_token #generics #trait_ #for_ #self_ty { #(#items)* - fn workload_system(type_id: std::any::TypeId, dependants: dioxus_native_core::exports::FxHashSet, pass_direction: dioxus_native_core::PassDirection) -> dioxus_native_core::exports::shipyard::WorkloadSystem { + fn workload_system(type_id: std::any::TypeId, dependants: dioxus_native_core::exports::FxHashSet, pass_direction: dioxus_native_core::prelude::PassDirection) -> dioxus_native_core::exports::shipyard::WorkloadSystem { use dioxus_native_core::exports::shipyard::{IntoWorkloadSystem, Get, AddComponent}; use dioxus_native_core::tree::TreeRef; use dioxus_native_core::prelude::{NodeType, NodeView}; let node_mask = Self::NODE_MASK.build(); - (move |data: #combined_dependencies_quote, run_view: dioxus_native_core::RunPassView #trait_generics| { + (move |data: #combined_dependencies_quote, run_view: dioxus_native_core::prelude::RunPassView #trait_generics| { let (#(#split_views,)*) = data; - let (tree, types, _, _, _) = &run_view; - let tree = tree.clone(); - let node_types = types.clone(); - dioxus_native_core::run_pass(type_id, dependants.clone(), pass_direction, run_view, |id, context| { + let tree = run_view.tree.clone(); + let node_types = run_view.node_type.clone(); + dioxus_native_core::prelude::run_pass(type_id, dependants.clone(), pass_direction, run_view, |id, context| { let node_data: &NodeType<_> = node_types.get(id).unwrap_or_else(|err| panic!("Failed to get node type {:?}", err)); // get all of the states from the tree view // Safety: No node has itself as a parent or child. diff --git a/packages/native-core/src/dioxus.rs b/packages/native-core/src/dioxus.rs index 938ed16c4..d288883b0 100644 --- a/packages/native-core/src/dioxus.rs +++ b/packages/native-core/src/dioxus.rs @@ -1,3 +1,5 @@ +//! Integration between Dioxus and the RealDom + use crate::tree::TreeMut; use dioxus_core::{BorrowedAttributeValue, ElementId, Mutations, TemplateNode}; use rustc_hash::{FxHashMap, FxHashSet}; @@ -8,14 +10,15 @@ use crate::{ ElementNode, FromAnyValue, NodeType, OwnedAttributeDiscription, OwnedAttributeValue, TextNode, }, - prelude::NodeImmutable, + prelude::*, real_dom::NodeTypeMut, - NodeId, NodeMut, RealDom, + NodeId, }; #[derive(Component)] struct ElementIdComponent(ElementId); +/// The state of the Dioxus integration with the RealDom pub struct DioxusState { templates: FxHashMap>, stack: Vec, @@ -23,6 +26,7 @@ pub struct DioxusState { } impl DioxusState { + /// Initialize the DioxusState in the RealDom pub fn create(rdom: &mut RealDom) -> Self { let root_id = rdom.root_id(); let mut root = rdom.get_mut(root_id).unwrap(); @@ -34,10 +38,12 @@ impl DioxusState { } } + /// Convert an ElementId to a NodeId pub fn element_to_node_id(&self, element_id: ElementId) -> NodeId { self.try_element_to_node_id(element_id).unwrap() } + /// Attempt to convert an ElementId to a NodeId. This will return None if the ElementId is not in the RealDom. pub fn try_element_to_node_id(&self, element_id: ElementId) -> Option { self.node_id_mapping.get(element_id.0).copied().flatten() } @@ -51,7 +57,7 @@ impl DioxusState { self.node_id_mapping[element_id.0] = Some(node_id); } - pub fn load_child(&self, rdom: &RealDom, path: &[u8]) -> NodeId { + fn load_child(&self, rdom: &RealDom, path: &[u8]) -> NodeId { let mut current = rdom.get(*self.stack.last().unwrap()).unwrap(); for i in path { let new_id = current.child_ids()[*i as usize]; @@ -170,7 +176,7 @@ impl DioxusState { let mut node_type_mut = node.node_type_mut(); if let NodeTypeMut::Element(element) = &mut node_type_mut { if let BorrowedAttributeValue::None = &value { - element.remove_attributes(&OwnedAttributeDiscription { + element.remove_attribute(&OwnedAttributeDiscription { name: name.to_string(), namespace: ns.map(|s| s.to_string()), }); @@ -266,7 +272,10 @@ fn create_template_node(rdom: &mut RealDom, node: &TemplateNode) -> NodeId { } } +/// A trait that extends the `NodeImmutable` trait with methods that are useful for dioxus. pub trait NodeImmutableDioxusExt: NodeImmutable { + /// Returns the id of the element that this node is mounted to. + /// Not all nodes are mounted to an element, only nodes with dynamic content that have been renderered will have an id. fn mounted_id(&self) -> Option { let id = self.get::(); id.map(|id| id.0) diff --git a/packages/native-core/src/layout_attributes.rs b/packages/native-core/src/layout_attributes.rs index 77926eb96..84fd61ac4 100644 --- a/packages/native-core/src/layout_attributes.rs +++ b/packages/native-core/src/layout_attributes.rs @@ -1,3 +1,5 @@ +//! Utility functions for applying layout attributes to taffy layout + /* - [ ] pub display: Display, ----> taffy doesnt support all display types - [x] pub position_type: PositionType, --> taffy doesnt support everything @@ -46,12 +48,14 @@ use taffy::{ style::{FlexDirection, PositionType}, }; +/// Default values for layout attributes #[derive(Default)] pub struct LayoutConfigeration { /// the default border widths to use pub border_widths: BorderWidths, } +/// Default border widths pub struct BorderWidths { /// the default border width to use for thin borders pub thin: f32, diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 955e1ba6a..b57cf57c9 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -1,10 +1,10 @@ +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + use std::any::Any; use std::hash::BuildHasherDefault; -pub use node_ref::NodeMask; -pub use passes::AnyMapLike; -pub use passes::{run_pass, Dependancy, PassDirection, RunPassView, State, TypeErasedPass}; -pub use real_dom::{NodeMut, NodeRef, RealDom}; +use node_ref::NodeMask; use rustc_hash::FxHasher; #[cfg(feature = "dioxus")] @@ -19,22 +19,29 @@ pub mod tree; pub mod utils; pub use shipyard::EntityId as NodeId; -// exported for the macro pub mod exports { + //! Important dependencies that are used by the rest of the library + //! Feel free to just add the dependencies in your own Crates.toml + // exported for the macro #[doc(hidden)] pub use rustc_hash::FxHashSet; pub use shipyard; } +/// A prelude of commonly used items pub mod prelude { pub use crate::node::{ElementNode, FromAnyValue, NodeType, OwnedAttributeView, TextNode}; pub use crate::node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}; - pub use crate::passes::{AnyState, Dependancy, DependancyView, State}; + pub use crate::passes::{run_pass, PassDirection, RunPassView, TypeErasedState}; + pub use crate::passes::{Dependancy, DependancyView, State}; pub use crate::real_dom::{NodeImmutable, NodeMut, NodeRef, RealDom}; pub use crate::NodeId; pub use crate::SendAnyMap; } +/// A map that can be sent between threads pub type FxDashMap = dashmap::DashMap>; +/// A set that can be sent between threads pub type FxDashSet = dashmap::DashSet>; +/// A map of types that can be sent between threads pub type SendAnyMap = anymap::Map; diff --git a/packages/native-core/src/node.rs b/packages/native-core/src/node.rs index 1e6331c86..a3362ada2 100644 --- a/packages/native-core/src/node.rs +++ b/packages/native-core/src/node.rs @@ -1,22 +1,36 @@ +//! Items related to Nodes in the RealDom + use rustc_hash::{FxHashMap, FxHashSet}; use shipyard::Component; -use std::{any::Any, fmt::Debug}; +use std::{ + any::Any, + fmt::{Debug, Display}, +}; +/// A element node in the RealDom #[derive(Debug, Clone, Default)] pub struct ElementNode { + /// The [tag](https://developer.mozilla.org/en-US/docs/Web/API/Element/tagName) of the element pub tag: String, + /// The [namespace](https://developer.mozilla.org/en-US/docs/Web/API/Element/namespaceURI) of the element pub namespace: Option, + /// The attributes of the element pub attributes: FxHashMap>, + /// The events the element is listening for pub listeners: FxHashSet, } +/// A text node in the RealDom #[derive(Debug, Clone, Default)] pub struct TextNode { + /// The text of the node pub text: String, + /// The events the node is listening for pub listeners: FxHashSet, } impl TextNode { + /// Create a new text node pub fn new(text: String) -> Self { Self { text, @@ -25,17 +39,26 @@ impl TextNode { } } -/// A type of node with data specific to the node type. The types are a subset of the [VNode] types. +/// A type of node with data specific to the node type. #[derive(Debug, Clone, Component)] pub enum NodeType { + /// A text node Text(TextNode), + /// An element node Element(ElementNode), + /// A placeholder node. This can be used as a cheaper placeholder for a node that will be created later Placeholder, } +/// A discription of an attribute on a DOM node, such as `id` or `href`. #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct OwnedAttributeDiscription { + /// The name of the attribute. pub name: String, + /// The namespace of the attribute used to identify what kind of attribute it is. + /// + /// For renderers that use HTML, this can be used to identify if the attribute is a style attribute. + /// Instead of parsing the style attribute every time a style is changed, you can set an attribute with the `style` namespace. pub namespace: Option, } @@ -50,16 +73,24 @@ pub struct OwnedAttributeView<'a, V: FromAnyValue = ()> { pub value: &'a OwnedAttributeValue, } +/// The value of an attribute on a DOM node. This contains non-text values to allow users to skip parsing attribute values in some cases. #[derive(Clone)] pub enum OwnedAttributeValue { + /// A string value. This is the most common type of attribute. Text(String), + /// A floating point value. Float(f64), + /// An integer value. Int(i64), + /// A boolean value. Bool(bool), + /// A custom value specific to the renderer Custom(V), } +/// Something that can be converted from a borrowed [Any] value. pub trait FromAnyValue: Clone + 'static { + /// Convert from an [Any] value. fn from_any_value(value: &dyn Any) -> Self; } @@ -79,6 +110,18 @@ impl Debug for OwnedAttributeValue { } } +impl Display for OwnedAttributeValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Text(arg0) => f.write_str(arg0), + Self::Float(arg0) => f.write_str(&arg0.to_string()), + Self::Int(arg0) => f.write_str(&arg0.to_string()), + Self::Bool(arg0) => f.write_str(&arg0.to_string()), + Self::Custom(arg0) => f.write_str(&arg0.to_string()), + } + } +} + #[cfg(feature = "dioxus")] impl From> for OwnedAttributeValue { fn from(value: dioxus_core::BorrowedAttributeValue<'_>) -> Self { @@ -94,6 +137,7 @@ impl From> for OwnedAtt } impl OwnedAttributeValue { + /// Attempt to convert the attribute value to a string. pub fn as_text(&self) -> Option<&str> { match self { OwnedAttributeValue::Text(text) => Some(text), @@ -101,20 +145,25 @@ impl OwnedAttributeValue { } } + /// Attempt to convert the attribute value to a float. pub fn as_float(&self) -> Option { match self { OwnedAttributeValue::Float(float) => Some(*float), + OwnedAttributeValue::Int(int) => Some(*int as f64), _ => None, } } + /// Attempt to convert the attribute value to an integer. pub fn as_int(&self) -> Option { match self { + OwnedAttributeValue::Float(float) => Some(*float as i64), OwnedAttributeValue::Int(int) => Some(*int), _ => None, } } + /// Attempt to convert the attribute value to a boolean. pub fn as_bool(&self) -> Option { match self { OwnedAttributeValue::Bool(bool) => Some(*bool), @@ -122,6 +171,7 @@ impl OwnedAttributeValue { } } + /// Attempt to convert the attribute value to a custom value. pub fn as_custom(&self) -> Option<&V> { match self { OwnedAttributeValue::Custom(custom) => Some(custom), diff --git a/packages/native-core/src/node_ref.rs b/packages/native-core/src/node_ref.rs index e50374c0b..22ab5d8f1 100644 --- a/packages/native-core/src/node_ref.rs +++ b/packages/native-core/src/node_ref.rs @@ -1,3 +1,5 @@ +//! Utilities that provide limited access to nodes + use rustc_hash::FxHashSet; use crate::{ @@ -5,7 +7,7 @@ use crate::{ NodeId, }; -/// A view into a [VNode] with limited access. +/// A view into a [NodeType] with a mask that determines what is visible. #[derive(Debug)] pub struct NodeView<'a, V: FromAnyValue = ()> { id: NodeId, @@ -98,11 +100,14 @@ impl<'a, V: FromAnyValue> NodeView<'a, V> { /// A mask that contains a list of attributes that are visible. #[derive(PartialEq, Eq, Clone, Debug)] pub enum AttributeMask { + /// All attributes are visible All, + /// Only the given attributes are visible Some(FxHashSet>), } impl AttributeMask { + /// Check if the given attribute is visible fn contains_attribute(&self, attr: &str) -> bool { match self { AttributeMask::All => true, @@ -203,9 +208,12 @@ impl NodeMask { } } +/// A builder for a mask that controls what attributes are visible. #[derive(PartialEq, Eq, Clone, Debug)] pub enum AttributeMaskBuilder<'a> { + /// All attributes are visible All, + /// Only the given attributes are visible Some(&'a [&'a str]), } diff --git a/packages/native-core/src/node_watcher.rs b/packages/native-core/src/node_watcher.rs index b9b79ebab..27a449ef9 100644 --- a/packages/native-core/src/node_watcher.rs +++ b/packages/native-core/src/node_watcher.rs @@ -1,4 +1,6 @@ -use crate::{node::FromAnyValue, NodeMut}; +//! Helpers for watching for changes in the DOM tree. + +use crate::{node::FromAnyValue, prelude::*}; /// A trait for watching for changes in the DOM tree. pub trait NodeWatcher { diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index ff55f65f2..849be75e5 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -1,7 +1,6 @@ -use anymap::AnyMap; use parking_lot::RwLock; use rustc_hash::{FxHashMap, FxHashSet}; -use shipyard::{Component, Unique, UniqueView, View, WorkloadSystem}; +use shipyard::{Borrow, BorrowInfo, Component, Unique, UniqueView, View, WorkloadSystem}; use std::any::{Any, TypeId}; use std::collections::BTreeMap; use std::marker::PhantomData; @@ -104,6 +103,7 @@ impl DirtyNodeStates { } } +/// A state that is automatically inserted in a node with dependencies. pub trait State: Any + Send + Sync { /// This is a tuple of (T: State, ..) of states read from the parent required to run this pass type ParentDependencies: Dependancy; @@ -139,24 +139,77 @@ pub trait State: Any + Send + Sync { dependants: FxHashSet, pass_direction: PassDirection, ) -> WorkloadSystem; + + /// Converts to a type erased version of the trait + fn to_type_erased() -> TypeErasedState + where + Self: Sized, + { + let node_mask = Self::NODE_MASK.build(); + TypeErasedState { + this_type_id: TypeId::of::(), + combined_dependancy_type_ids: all_dependanices::().iter().copied().collect(), + parent_dependant: !Self::ParentDependencies::type_ids().is_empty(), + child_dependant: !Self::ChildDependencies::type_ids().is_empty(), + dependants: FxHashSet::default(), + mask: node_mask, + pass_direction: pass_direction::(), + workload: Self::workload_system, + phantom: PhantomData, + } + } } -pub type RunPassView<'a, V = ()> = ( - TreeRefView<'a>, - View<'a, NodeType>, - UniqueView<'a, DirtyNodesResult>, - UniqueView<'a, DirtyNodeStates>, - UniqueView<'a, SendAnyMapWrapper>, -); +fn pass_direction>() -> PassDirection { + if S::ChildDependencies::type_ids() + .iter() + .any(|type_id| *type_id == TypeId::of::()) + { + PassDirection::ChildToParent + } else if S::ParentDependencies::type_ids() + .iter() + .any(|type_id| *type_id == TypeId::of::()) + { + PassDirection::ParentToChild + } else { + PassDirection::AnyOrder + } +} -pub fn run_pass( +fn all_dependanices>() -> Box<[TypeId]> { + let mut dependencies = S::ParentDependencies::type_ids().to_vec(); + dependencies.extend(S::ChildDependencies::type_ids().iter()); + dependencies.extend(S::NodeDependencies::type_ids().iter()); + dependencies.into_boxed_slice() +} + +#[doc(hidden)] +#[derive(Borrow, BorrowInfo)] +pub struct RunPassView<'a, V: FromAnyValue + Send + Sync = ()> { + pub tree: TreeRefView<'a>, + pub node_type: View<'a, NodeType>, + dirty_nodes_result: UniqueView<'a, DirtyNodesResult>, + node_states: UniqueView<'a, DirtyNodeStates>, + any_map: UniqueView<'a, SendAnyMapWrapper>, +} + +// This is used by the macro +/// Updates the given pass, marking any nodes that were changed +#[doc(hidden)] +pub fn run_pass( type_id: TypeId, dependants: FxHashSet, pass_direction: PassDirection, view: RunPassView, mut update_node: impl FnMut(NodeId, &SendAnyMap) -> bool, ) { - let (tree, _, nodes_updated, dirty, ctx, ..) = view; + let RunPassView { + tree, + dirty_nodes_result: nodes_updated, + node_states: dirty, + any_map: ctx, + .. + } = view; let ctx = ctx.as_ref(); match pass_direction { PassDirection::ParentToChild => { @@ -199,64 +252,8 @@ pub fn run_pass( } } -pub trait AnyState: State { - fn to_type_erased() -> TypeErasedPass - where - Self: Sized, - { - let node_mask = Self::NODE_MASK.build(); - TypeErasedPass { - this_type_id: TypeId::of::(), - combined_dependancy_type_ids: Self::all_dependanices().iter().copied().collect(), - parent_dependant: !Self::parent_type_ids().is_empty(), - child_dependant: !Self::child_type_ids().is_empty(), - dependants: FxHashSet::default(), - mask: node_mask, - pass_direction: Self::pass_direction(), - workload: Self::workload_system, - phantom: PhantomData, - } - } - - fn parent_type_ids() -> Box<[TypeId]> { - Self::ParentDependencies::type_ids() - } - - fn child_type_ids() -> Box<[TypeId]> { - Self::ChildDependencies::type_ids() - } - - fn node_type_ids() -> Box<[TypeId]> { - Self::NodeDependencies::type_ids() - } - - fn all_dependanices() -> Box<[TypeId]> { - let mut dependencies = Self::parent_type_ids().to_vec(); - dependencies.extend(Self::child_type_ids().iter()); - dependencies.extend(Self::node_type_ids().iter()); - dependencies.into_boxed_slice() - } - - fn pass_direction() -> PassDirection { - if Self::child_type_ids() - .iter() - .any(|type_id| *type_id == TypeId::of::()) - { - PassDirection::ChildToParent - } else if Self::parent_type_ids() - .iter() - .any(|type_id| *type_id == TypeId::of::()) - { - PassDirection::ParentToChild - } else { - PassDirection::AnyOrder - } - } -} - -impl> AnyState for S {} - -pub struct TypeErasedPass { +/// A type erased version of [`State`] that can be added to the [`crate::prelude::RealDom`] with [`crate::prelude::RealDom::new`] +pub struct TypeErasedState { pub(crate) this_type_id: TypeId, pub(crate) parent_dependant: bool, pub(crate) child_dependant: bool, @@ -268,7 +265,7 @@ pub struct TypeErasedPass { phantom: PhantomData, } -impl TypeErasedPass { +impl TypeErasedState { pub(crate) fn create_workload(&self) -> WorkloadSystem { (self.workload)( self.this_type_id, @@ -278,32 +275,23 @@ impl TypeErasedPass { } } +/// The direction that a pass should be run in #[derive(Debug, Clone, Copy)] pub enum PassDirection { + /// The pass should be run from the root to the leaves ParentToChild, + /// The pass should be run from the leaves to the root ChildToParent, + /// The pass can be run in any order AnyOrder, } -pub trait AnyMapLike<'a> { - fn get(self) -> Option<&'a T>; -} - -impl<'a> AnyMapLike<'a> for &'a AnyMap { - fn get(self) -> Option<&'a T> { - self.get() - } -} - -impl<'a> AnyMapLike<'a> for &'a SendAnyMap { - fn get(self) -> Option<&'a T> { - todo!() - } -} - +/// A trait that is implemented for all the dependancies of a [`State`] pub trait Dependancy { + /// A tuple with all the elements of the dependancy as [`DependancyView`] type ElementBorrowed<'a>; + /// Returns a list of all the [`TypeId`]s of the elements in the dependancy fn type_ids() -> Box<[TypeId]> { Box::new([]) } @@ -323,6 +311,7 @@ macro_rules! impl_dependancy { // TODO: track what components are actually read to update subscriptions // making this a wrapper makes it possible to implement that optimization without a breaking change +/// A immutable view of a [`State`] pub struct DependancyView<'a, T> { inner: &'a T, } diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index d721428ea..016633fc7 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -1,3 +1,5 @@ +//! A Dom that can sync with the VirtualDom mutations intended for use in lazy renderers. + use rustc_hash::{FxHashMap, FxHashSet}; use shipyard::error::GetStorage; use shipyard::track::Untracked; @@ -13,14 +15,15 @@ use crate::node::{ }; use crate::node_ref::{NodeMask, NodeMaskBuilder}; use crate::node_watcher::NodeWatcher; -use crate::passes::{DirtyNodeStates, TypeErasedPass}; +use crate::passes::{DirtyNodeStates, TypeErasedState}; use crate::prelude::AttributeMaskBuilder; use crate::tree::{TreeMut, TreeMutView, TreeRef, TreeRefView}; use crate::NodeId; use crate::{FxDashSet, SendAnyMap}; +/// The context passes can receive when they are executed #[derive(Unique)] -pub struct SendAnyMapWrapper(SendAnyMap); +pub(crate) struct SendAnyMapWrapper(SendAnyMap); impl Deref for SendAnyMapWrapper { type Target = SendAnyMap; @@ -30,8 +33,9 @@ impl Deref for SendAnyMapWrapper { } } +/// The nodes that were changed when updating the state of the RealDom #[derive(Unique, Default)] -pub struct DirtyNodesResult(FxDashSet); +pub(crate) struct DirtyNodesResult(FxDashSet); impl Deref for DirtyNodesResult { type Target = FxDashSet; @@ -41,13 +45,15 @@ impl Deref for DirtyNodesResult { } } -pub struct NodesDirty { +/// The nodes that have been marked as dirty in the RealDom +pub(crate) struct NodesDirty { passes_updated: FxHashMap>, nodes_updated: FxHashMap, - pub(crate) passes: Box<[TypeErasedPass]>, + pub(crate) passes: Box<[TypeErasedState]>, } impl NodesDirty { + /// Mark a node as dirty fn mark_dirty(&mut self, node_id: NodeId, mask: NodeMask) { self.passes_updated.entry(node_id).or_default().extend( self.passes @@ -62,6 +68,7 @@ impl NodesDirty { } } + /// Mark a node as added or removed from the tree fn mark_parent_added_or_removed(&mut self, node_id: NodeId) { let hm = self.passes_updated.entry(node_id).or_default(); for pass in &*self.passes { @@ -71,6 +78,7 @@ impl NodesDirty { } } + /// Mark a node as having a child added or removed fn mark_child_changed(&mut self, node_id: NodeId) { let hm = self.passes_updated.entry(node_id).or_default(); for pass in &*self.passes { @@ -85,9 +93,13 @@ type NodeWatchers = Arc + Send + Sync>>>>; /// 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. +/// To get started: +/// 1) Implement [crate::passes::State] for each part of your state that you want to compute incrementally +/// 2) Create a RealDom [RealDom::new], passing in each state you created +/// 3) Update the state of the RealDom by adding and modifying nodes +/// 4) Call [RealDom::update_state] to update the state of incrementally computed values on each node /// -/// # Custom values +/// # Custom attribute values /// To allow custom values to be passed into attributes implement FromAnyValue on a type that can represent your custom value and specify the V generic to be that type. If you have many different custom values, it can be useful to use a enum type to represent the varients. pub struct RealDom { pub(crate) world: World, @@ -100,10 +112,11 @@ pub struct RealDom { } impl RealDom { - pub fn new(mut passes: Box<[TypeErasedPass]>) -> RealDom { + /// Create a new RealDom with the given states that will be inserted and updated when needed + pub fn new(mut tracked_states: Box<[TypeErasedState]>) -> RealDom { // resolve dependants for each pass - for i in 1..passes.len() { - let (before, after) = passes.split_at_mut(i); + for i in 1..tracked_states.len() { + let (before, after) = tracked_states.split_at_mut(i); let (current, before) = before.split_last_mut().unwrap(); for pass in before.iter_mut().chain(after.iter_mut()) { if current @@ -114,7 +127,7 @@ impl RealDom { } } } - let workload = construct_workload(&mut passes); + let workload = construct_workload(&mut tracked_states); let (workload, _) = workload.build().unwrap(); let mut world = World::new(); let root_node: NodeType = NodeType::Element(ElementNode { @@ -132,7 +145,10 @@ impl RealDom { let mut passes_updated = FxHashMap::default(); let mut nodes_updated = FxHashMap::default(); - passes_updated.insert(root_id, passes.iter().map(|x| x.this_type_id).collect()); + passes_updated.insert( + root_id, + tracked_states.iter().map(|x| x.this_type_id).collect(), + ); nodes_updated.insert(root_id, NodeMaskBuilder::ALL.build()); RealDom { @@ -141,7 +157,7 @@ impl RealDom { dirty_nodes: NodesDirty { passes_updated, nodes_updated, - passes, + passes: tracked_states, }, node_watchers: Default::default(), workload, @@ -150,14 +166,17 @@ impl RealDom { } } - pub fn tree_mut(&self) -> TreeMutView { - self.world.borrow().unwrap() - } - + /// Get a reference to the tree. pub fn tree_ref(&self) -> TreeRefView { self.world.borrow().unwrap() } + /// Get a mutable reference to the tree. + pub fn tree_mut(&self) -> TreeMutView { + self.world.borrow().unwrap() + } + + /// Create a new node of the given type in the dom and return a mutable reference to it. pub fn create_node(&mut self, node: NodeType) -> NodeMut<'_, V> { let id = self.world.add_entity(node); self.tree_mut().create_node(id); @@ -196,17 +215,20 @@ impl RealDom { self.root_id } + /// Get a reference to a node. pub fn get(&self, id: NodeId) -> Option> { self.tree_ref() .contains(id) .then_some(NodeRef { id, dom: self }) } + /// Get a mutable reference to a node. pub fn get_mut(&mut self, id: NodeId) -> Option> { let contains = self.tree_ref().contains(id); contains.then(|| NodeMut::new(id, self)) } + /// Borrow a component from the world without updating the dirty nodes. fn borrow_raw<'a, B: IntoBorrow>(&'a self) -> Result where B::Borrow: shipyard::Borrow<'a, View = B>, @@ -214,6 +236,7 @@ impl RealDom { self.world.borrow() } + /// Borrow a component from the world without updating the dirty nodes. fn borrow_node_type_mut(&self) -> Result>, GetStorage> { self.world.borrow() } @@ -250,6 +273,7 @@ impl RealDom { (dirty.0, nodes_updated) } + /// Traverses the dom in a depth first manner, calling the provided function on each node. pub fn traverse_depth_first(&self, mut f: impl FnMut(NodeRef)) { let mut stack = vec![self.root_id()]; let tree = self.tree_ref(); @@ -262,6 +286,7 @@ impl RealDom { } } + /// Traverses the dom in a breadth first manner, calling the provided function on each node. pub fn traverse_breadth_first(&self, mut f: impl FnMut(NodeRef)) { let mut queue = VecDeque::new(); queue.push_back(self.root_id()); @@ -277,6 +302,7 @@ impl RealDom { } } + /// Traverses the dom in a depth first manner mutably, calling the provided function on each node. pub fn traverse_depth_first_mut(&mut self, mut f: impl FnMut(NodeMut)) { let mut stack = vec![self.root_id()]; while let Some(id) = stack.pop() { @@ -292,6 +318,7 @@ impl RealDom { } } + /// Traverses the dom in a breadth first manner mutably, calling the provided function on each node. pub fn traverse_breadth_first_mut(&mut self, mut f: impl FnMut(NodeMut)) { let mut queue = VecDeque::new(); queue.push_back(self.root_id()); @@ -308,6 +335,7 @@ impl RealDom { } } + /// Adds a [`NodeWatcher`] to the dom. Node watchers are called whenever a node is created or removed. pub fn add_node_watcher(&mut self, watcher: impl NodeWatcher + 'static + Send + Sync) { self.node_watchers.write().unwrap().push(Box::new(watcher)); } @@ -323,6 +351,7 @@ impl RealDom { } } +/// A reference to a tracked component in a node. pub struct ViewEntry<'a, V: Component + Send + Sync> { view: View<'a, V>, id: NodeId, @@ -342,6 +371,7 @@ impl<'a, V: Component + Send + Sync> Deref for ViewEntry<'a, V> { } } +/// A mutable reference to a tracked component in a node. pub struct ViewEntryMut<'a, V: Component + Send + Sync> { view: ViewMut<'a, V, Untracked>, id: NodeId, @@ -367,16 +397,21 @@ impl<'a, V: Component + Send + Sync> DerefMut for ViewEntr } } +/// A immutable view of a node pub trait NodeImmutable: Sized { + /// Get the real dom this node was created in fn real_dom(&self) -> &RealDom; + /// Get the id of the current node fn id(&self) -> NodeId; + /// Get the type of the current node #[inline] fn node_type(&self) -> ViewEntry> { self.get().unwrap() } + /// Get a component from the current node #[inline] fn get<'a, T: Component + Sync + Send>(&'a self) -> Option> { // self.real_dom().tree.get(self.id()) @@ -385,11 +420,13 @@ pub trait NodeImmutable: Sized { .then(|| ViewEntry::new(view, self.id())) } + /// Get the ids of the children of the current node #[inline] fn child_ids(&self) -> Vec { self.real_dom().tree_ref().children_ids(self.id()) } + /// Get the children of the current node #[inline] fn children(&self) -> Vec> { self.child_ids() @@ -401,11 +438,13 @@ pub trait NodeImmutable: Sized { .collect() } + /// Get the id of the parent of the current node #[inline] fn parent_id(&self) -> Option { self.real_dom().tree_ref().parent_id(self.id()) } + /// Get the parent of the current node #[inline] fn parent(&self) -> Option> { self.parent_id().map(|id| NodeRef { @@ -414,6 +453,7 @@ pub trait NodeImmutable: Sized { }) } + /// Get the node after the current node #[inline] fn next(&self) -> Option> { let parent = self.parent_id()?; @@ -429,6 +469,7 @@ pub trait NodeImmutable: Sized { } } + /// Get the node before the current node #[inline] fn prev(&self) -> Option> { let parent = self.parent_id()?; @@ -444,12 +485,14 @@ pub trait NodeImmutable: Sized { } } + /// Get the height of the current node in the tree (the number of nodes between the current node and the root) #[inline] fn height(&self) -> u16 { self.real_dom().tree_ref().height(self.id()).unwrap() } } +/// An immutable reference to a node in a RealDom pub struct NodeRef<'a, V: FromAnyValue + Send + Sync = ()> { id: NodeId, dom: &'a RealDom, @@ -478,12 +521,14 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeImmutable for NodeRef<'a, V> { } } +/// A mutable refrence to a node in the RealDom that tracks what States need to be updated pub struct NodeMut<'a, V: FromAnyValue + Send + Sync = ()> { id: NodeId, dom: &'a mut RealDom, } impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { + /// Create a new mutable refrence to a node in a RealDom pub fn new(id: NodeId, dom: &'a mut RealDom) -> Self { Self { id, dom } } @@ -502,16 +547,19 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeImmutable for NodeMut<'a, V> { } impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { + /// Get the real dom this node was created in mutably #[inline(always)] pub fn real_dom_mut(&mut self) -> &mut RealDom { self.dom } + /// Get the parent of this node mutably #[inline] pub fn parent_mut(&mut self) -> Option> { self.parent_id().map(|id| NodeMut { id, dom: self.dom }) } + /// Get a component from the current node mutably #[inline] pub fn get_mut + Sync + Send>( &mut self, @@ -527,6 +575,9 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { Some(ViewEntryMut::new(view_mut, self.id)) } + /// Insert a custom component into this node + /// + /// Note: Components that implement State and are added when the RealDom is created will automatically be created #[inline] pub fn insert(&mut self, value: T) { // mark the node state as dirty @@ -539,6 +590,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { self.dom.world.add_component(self.id, value); } + /// Get the next node #[inline] pub fn next_mut(self) -> Option> { let parent = self.parent_id()?; @@ -551,6 +603,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { } } + /// Get the previous node #[inline] pub fn prev_mut(self) -> Option> { let parent = self.parent_id()?; @@ -563,6 +616,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { } } + /// Add the given node to the end of this nodes children #[inline] pub fn add_child(&mut self, child: NodeId) { self.dom.dirty_nodes.mark_child_changed(self.id); @@ -571,6 +625,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { NodeMut::new(child, self.dom).mark_moved(); } + /// Insert this node after the given node #[inline] pub fn insert_after(&mut self, old: NodeId) { let id = self.id(); @@ -583,6 +638,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { self.mark_moved(); } + /// Insert this node before the given node #[inline] pub fn insert_before(&mut self, old: NodeId) { let id = self.id(); @@ -595,6 +651,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { self.mark_moved(); } + /// Remove this node from the RealDom #[inline] pub fn remove(&mut self) { let id = self.id(); @@ -629,8 +686,9 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { self.dom.tree_mut().remove_single(id); } + /// Replace this node with a different node #[inline] - pub fn replace(&mut self, new: NodeId) { + pub fn replace(mut self, new: NodeId) { self.mark_removed(); if let Some(parent_id) = self.parent_id() { self.real_dom_mut() @@ -644,6 +702,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { self.dom.tree_mut().replace(id, new); } + /// Add an event listener #[inline] pub fn add_event_listener(&mut self, event: &str) { let id = self.id(); @@ -673,6 +732,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { } } + /// Remove an event listener #[inline] pub fn remove_event_listener(&mut self, event: &str) { let id = self.id(); @@ -694,6 +754,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { } } + /// mark that this node was removed for the incremental system fn mark_removed(&mut self) { let watchers = self.dom.node_watchers.clone(); for watcher in &*watchers.read().unwrap() { @@ -701,6 +762,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { } } + /// mark that this node was moved for the incremental system fn mark_moved(&mut self) { let watchers = self.dom.node_watchers.clone(); for watcher in &*watchers.read().unwrap() { @@ -708,6 +770,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { } } + /// Get a mutable reference to the type of the current node pub fn node_type_mut(&mut self) -> NodeTypeMut<'_, V> { let id = self.id(); let RealDom { @@ -730,6 +793,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { } } + /// Set the type of the current node pub fn set_type(&mut self, new: NodeType) { { let mut view: ViewMut> = self.dom.borrow_node_type_mut().unwrap(); @@ -740,6 +804,8 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { .mark_dirty(self.id, NodeMaskBuilder::ALL.build()) } + /// Clone a node and it's children and returns the id of the new node. + /// This is more effecient than creating the node from scratch because it can pre-allocate the memory required. #[inline] pub fn clone_node(&mut self) -> NodeId { let new_node = self.node_type().clone(); @@ -757,12 +823,17 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { } } +/// A mutable refrence to the type of a node in the RealDom pub enum NodeTypeMut<'a, V: FromAnyValue + Send + Sync = ()> { + /// An element node Element(ElementNodeMut<'a, V>), + /// A text node Text(TextNodeMut<'a, V>), + /// A placeholder node Placeholder, } +/// A mutable refrence to a text node in the RealDom pub struct TextNodeMut<'a, V: FromAnyValue + Send + Sync = ()> { id: NodeId, text: ViewEntryMut<'a, NodeType>, @@ -770,6 +841,7 @@ pub struct TextNodeMut<'a, V: FromAnyValue + Send + Sync = ()> { } impl TextNodeMut<'_, V> { + /// Get the underlying test of the node pub fn text(&self) -> &str { match &*self.text { NodeType::Text(text) => &text.text, @@ -777,6 +849,7 @@ impl TextNodeMut<'_, V> { } } + /// Get the underlying text mutably pub fn text_mut(&mut self) -> &mut String { self.dirty_nodes .mark_dirty(self.id, NodeMaskBuilder::new().with_text().build()); @@ -804,6 +877,7 @@ impl DerefMut for TextNodeMut<'_, V> { } } +/// A mutable refrence to a text Element node in the RealDom pub struct ElementNodeMut<'a, V: FromAnyValue + Send + Sync = ()> { id: NodeId, element: ViewEntryMut<'a, NodeType>, @@ -811,6 +885,7 @@ pub struct ElementNodeMut<'a, V: FromAnyValue + Send + Sync = ()> { } impl ElementNodeMut<'_, V> { + /// Get the current element fn element(&self) -> &ElementNode { match &*self.element { NodeType::Element(element) => element, @@ -818,6 +893,7 @@ impl ElementNodeMut<'_, V> { } } + /// Get the current element mutably (does not mark anything as dirty) fn element_mut(&mut self) -> &mut ElementNode { match &mut *self.element { NodeType::Element(element) => element, @@ -825,30 +901,36 @@ impl ElementNodeMut<'_, V> { } } + /// Get the tag of the element pub fn tag(&self) -> &str { &self.element().tag } + /// Get a mutable reference to the tag of the element pub fn tag_mut(&mut self) -> &mut String { self.dirty_nodes .mark_dirty(self.id, NodeMaskBuilder::new().with_tag().build()); &mut self.element_mut().tag } + /// Get a reference to the namespace the element is in pub fn namespace(&self) -> Option<&str> { self.element().namespace.as_deref() } + /// Get a mutable reference to the namespace the element is in pub fn namespace_mut(&mut self) -> &mut Option { self.dirty_nodes .mark_dirty(self.id, NodeMaskBuilder::new().with_namespace().build()); &mut self.element_mut().namespace } + /// Get a reference to all of the attributes currently set on the element pub fn attributes(&self) -> &FxHashMap> { &self.element().attributes } + /// Set an attribute in the element pub fn set_attribute( &mut self, name: OwnedAttributeDiscription, @@ -863,7 +945,8 @@ impl ElementNodeMut<'_, V> { self.element_mut().attributes.insert(name, value) } - pub fn remove_attributes( + /// Remove an attribute from the element + pub fn remove_attribute( &mut self, name: &OwnedAttributeDiscription, ) -> Option> { @@ -876,6 +959,7 @@ impl ElementNodeMut<'_, V> { self.element_mut().attributes.remove(name) } + /// Get an attribute of the element pub fn get_attribute_mut( &mut self, name: &OwnedAttributeDiscription, @@ -889,13 +973,18 @@ impl ElementNodeMut<'_, V> { self.element_mut().attributes.get_mut(name) } + /// Get the set of all events the element is listening to pub fn listeners(&self) -> &FxHashSet { &self.element().listeners } } -fn construct_workload(passes: &mut [TypeErasedPass]) -> Workload { +// Create a workload from all of the passes. This orders the passes so that each pass will only run at most once. +fn construct_workload( + passes: &mut [TypeErasedState], +) -> Workload { let mut workload = Workload::new("Main Workload"); + // Assign a unique index to keep track of each pass let mut unresloved_workloads = passes .iter_mut() .enumerate() @@ -922,6 +1011,7 @@ fn construct_workload(passes: &mut [TypeErasedPas .map(|workload| workload.after_all(dependancy_id.to_string())); } } + // Add all of the passes for (_, _, mut workload_system) in unresloved_workloads { workload = workload.with_system(workload_system.take().unwrap()); } diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index ec5e02895..99ac07d16 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -1,7 +1,10 @@ +//! A tree of nodes intigated with shipyard + use crate::NodeId; use shipyard::{Component, EntitiesViewMut, Get, View, ViewMut}; use std::fmt::Debug; +/// A node in a tree. #[derive(PartialEq, Eq, Clone, Debug, Component)] pub struct Node { parent: Option, @@ -9,24 +12,38 @@ pub struct Node { height: u16, } +/// A view of a tree. pub type TreeRefView<'a> = View<'a, Node>; +/// A mutable view of a tree. pub type TreeMutView<'a> = (EntitiesViewMut<'a>, ViewMut<'a, Node>); +/// A immutable view of a tree. pub trait TreeRef { + /// The parent id of the node. fn parent_id(&self, id: NodeId) -> Option; + /// The children ids of the node. fn children_ids(&self, id: NodeId) -> Vec; + /// The height of the node. fn height(&self, id: NodeId) -> Option; + /// Returns true if the node exists. fn contains(&self, id: NodeId) -> bool; } +/// A mutable view of a tree. pub trait TreeMut: TreeRef { + /// Removes the node and all of its children. fn remove(&mut self, id: NodeId); + /// Removes the node and all of its children. fn remove_single(&mut self, id: NodeId); - fn set_height(&mut self, node: NodeId, height: u16); + /// Adds a new node to the tree. fn create_node(&mut self, id: NodeId); + /// Adds a child to the node. fn add_child(&mut self, parent: NodeId, new: NodeId); + /// Replaces the node with a new node. fn replace(&mut self, old_id: NodeId, new_id: NodeId); + /// Inserts a node before another node. fn insert_before(&mut self, old_id: NodeId, new_id: NodeId); + /// Inserts a node after another node. fn insert_after(&mut self, old_id: NodeId, new_id: NodeId); } @@ -79,18 +96,6 @@ impl<'a> TreeMut for TreeMutView<'a> { } } - fn set_height(&mut self, node: NodeId, height: u16) { - let children = { - let mut node_data_mut = &mut self.1; - let mut node = (&mut node_data_mut).get(node).unwrap(); - node.height = height; - node.children.clone() - }; - for child in children { - self.set_height(child, height + 1); - } - } - fn create_node(&mut self, id: NodeId) { let (entities, node_data_mut) = self; entities.add_component( @@ -113,7 +118,7 @@ impl<'a> TreeMut for TreeMutView<'a> { parent.children.push(new); height = parent.height + 1; } - self.set_height(new, height); + set_height(self, new, height); } fn replace(&mut self, old_id: NodeId, new_id: NodeId) { @@ -129,7 +134,7 @@ impl<'a> TreeMut for TreeMutView<'a> { } } let height = parent.height + 1; - self.set_height(new_id, height); + set_height(self, new_id, height); } } // remove the old node @@ -149,7 +154,7 @@ impl<'a> TreeMut for TreeMutView<'a> { .unwrap(); parent.children.insert(index, new_id); let height = parent.height + 1; - self.set_height(new_id, height); + set_height(self, new_id, height); } fn insert_after(&mut self, old_id: NodeId, new_id: NodeId) { @@ -165,7 +170,20 @@ impl<'a> TreeMut for TreeMutView<'a> { .unwrap(); parent.children.insert(index + 1, new_id); let height = parent.height + 1; - self.set_height(new_id, height); + set_height(self, new_id, height); + } +} + +/// Sets the height of a node and updates the height of all its children +fn set_height(tree: &mut TreeMutView<'_>, node: NodeId, height: u16) { + let children = { + let mut node_data_mut = &mut tree.1; + let mut node = (&mut node_data_mut).get(node).unwrap(); + node.height = height; + node.children.clone() + }; + for child in children { + set_height(tree, child, height + 1); } } diff --git a/packages/native-core/src/utils/cursor.rs b/packages/native-core/src/utils/cursor.rs index 18fbbb991..466b0e70c 100644 --- a/packages/native-core/src/utils/cursor.rs +++ b/packages/native-core/src/utils/cursor.rs @@ -1,3 +1,5 @@ +//! A cursor implementation that can be used to navigate and edit text. + use std::{cmp::Ordering, ops::Range}; use keyboard_types::{Code, Key, Modifiers}; diff --git a/packages/native-core/src/utils/mod.rs b/packages/native-core/src/utils/mod.rs index 58e98b0fa..a78b5310b 100644 --- a/packages/native-core/src/utils/mod.rs +++ b/packages/native-core/src/utils/mod.rs @@ -1,3 +1,7 @@ +//! # Utilities for renders using the RealDOM +//! +//! This includes an iterator that can be used to iterate over the children of a node that persists changes in the struture of the DOM, and a cursor for text editing. + mod persistant_iterator; pub use persistant_iterator::*; pub mod cursor; diff --git a/packages/native-core/src/utils/persistant_iterator.rs b/packages/native-core/src/utils/persistant_iterator.rs index 155909373..732789aac 100644 --- a/packages/native-core/src/utils/persistant_iterator.rs +++ b/packages/native-core/src/utils/persistant_iterator.rs @@ -3,27 +3,53 @@ use smallvec::SmallVec; use crate::{ node::FromAnyValue, node_watcher::NodeWatcher, + prelude::{NodeMut, NodeRef}, real_dom::{NodeImmutable, RealDom}, - NodeId, NodeRef, + NodeId, }; use std::{ fmt::Debug, sync::{Arc, Mutex}, }; -#[derive(Debug)] -pub enum ElementProduced { - /// The iterator produced an element by progressing to the next node in a depth first order. - Progressed(NodeId), +/// The element produced by the iterator +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct ElementProduced { + id: NodeId, + movement: IteratorMovement, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +/// The method by which the iterator produced an element +pub enum IteratorMovement { + /// The iterator produced an element by progressing to the next node + Progressed, /// The iterator reached the end of the tree and looped back to the root - Looped(NodeId), + Looped, } impl ElementProduced { + /// Get the id of the element produced pub fn id(&self) -> NodeId { - match self { - ElementProduced::Progressed(id) => *id, - ElementProduced::Looped(id) => *id, + self.id + } + + /// The movement the iterator made to produce the element + pub fn movement(&self) -> &IteratorMovement { + &self.movement + } + + fn looped(id: NodeId) -> Self { + Self { + id, + movement: IteratorMovement::Looped, + } + } + + fn progressed(id: NodeId) -> Self { + Self { + id, + movement: IteratorMovement::Progressed, } } } @@ -34,7 +60,7 @@ struct PersistantElementIterUpdater { } impl NodeWatcher for PersistantElementIterUpdater { - fn on_node_moved(&self, node: crate::NodeMut) { + fn on_node_moved(&self, node: NodeMut) { // if any element is moved, update its parents in the stack let mut stack = self.stack.lock().unwrap(); let moved = node.id(); @@ -52,7 +78,7 @@ impl NodeWatcher for PersistantElementIterUpda } } - fn on_node_removed(&self, node: crate::NodeMut) { + fn on_node_removed(&self, node: NodeMut) { // if any element is removed in the chain, remove it and its children from the stack let mut stack = self.stack.lock().unwrap(); let removed = node.id(); @@ -62,9 +88,9 @@ impl NodeWatcher for PersistantElementIterUpda } } -/// Focus systems need a iterator that can persist through changes in the [dioxus_core::VirtualDom]. +/// Focus systems need a iterator that can persist through changes in the [crate::prelude::RealDom] /// 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`]). +/// You can iterate through it with [PersistantElementIter::next] and [PersistantElementIter::prev]. /// The iterator loops around when it reaches the end or the beginning. pub struct PersistantElementIter { // stack of elements and fragments, the last element is the last element that was yielded @@ -72,6 +98,7 @@ pub struct PersistantElementIter { } impl PersistantElementIter { + /// Create a new iterator in the RealDom pub fn create(rdom: &mut RealDom) -> Self { let inner = Arc::new(Mutex::new(smallvec::smallvec![rdom.root_id()])); @@ -90,7 +117,7 @@ impl PersistantElementIter { let id = rdom.root_id(); let new = id; stack.push(new); - ElementProduced::Looped(id) + ElementProduced::looped(id) } else { let mut look_in_children = true; loop { @@ -100,7 +127,7 @@ impl PersistantElementIter { if let Some(first) = current.children().first() { let new = first.id(); stack.push(new); - return ElementProduced::Progressed(new); + return ElementProduced::progressed(new); } } stack.pop(); @@ -108,7 +135,7 @@ impl PersistantElementIter { // the next element exists, add it to the stack and return it let new = new.id(); stack.push(new); - return ElementProduced::Progressed(new); + return ElementProduced::progressed(new); } // otherwise, continue the loop and go to the parent } else { @@ -116,7 +143,7 @@ impl PersistantElementIter { let new = rdom.root_id(); stack.clear(); stack.push(new); - return ElementProduced::Looped(new); + return ElementProduced::looped(new); } look_in_children = false; } @@ -141,28 +168,28 @@ impl PersistantElementIter { if stack.is_empty() { let id = rdom.root_id(); let last = push_back(&mut stack, rdom.get(id).unwrap()); - ElementProduced::Looped(last) + ElementProduced::looped(last) } else if let Some(current) = stack.pop().and_then(|last| rdom.get(last)) { if let Some(new) = current.prev() { // the next element exists, add it to the stack and return it let new = push_back(&mut stack, new); - ElementProduced::Progressed(new) + ElementProduced::progressed(new) } // otherwise, yeild the parent else if let Some(parent) = stack.last() { // if there is a parent, return it - ElementProduced::Progressed(*parent) + ElementProduced::progressed(*parent) } else { // if there is no parent, loop back to the root let id = rdom.root_id(); let last = push_back(&mut stack, rdom.get(id).unwrap()); - ElementProduced::Looped(last) + ElementProduced::looped(last) } } else { // if there is no parent, loop back to the root let id = rdom.root_id(); let last = push_back(&mut stack, rdom.get(id).unwrap()); - ElementProduced::Looped(last) + ElementProduced::looped(last) } } } diff --git a/packages/rink/examples/widgets.rs b/packages/rink/examples/widgets.rs new file mode 100644 index 000000000..793057bc5 --- /dev/null +++ b/packages/rink/examples/widgets.rs @@ -0,0 +1,93 @@ +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", + 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/rink/src/focus.rs b/packages/rink/src/focus.rs index b42e10b55..e26f79b3d 100644 --- a/packages/rink/src/focus.rs +++ b/packages/rink/src/focus.rs @@ -2,9 +2,9 @@ use crate::prevent_default::PreventDefault; use dioxus_native_core::{ node_ref::{AttributeMaskBuilder, NodeMaskBuilder}, + prelude::*, real_dom::NodeImmutable, - utils::{ElementProduced, PersistantElementIter}, - Dependancy, NodeId, RealDom, SendAnyMap, State, + utils::{IteratorMovement, PersistantElementIter}, }; use dioxus_native_core_macro::partial_derive_state; use once_cell::sync::Lazy; @@ -178,7 +178,7 @@ impl FocusState { self.focus_iter.prev(rdom) }; let new_id = new.id(); - if let ElementProduced::Looped(_) = new { + if let IteratorMovement::Looped = new.movement() { let mut closest_level = None; if forward { diff --git a/packages/rink/src/hooks.rs b/packages/rink/src/hooks.rs index 73d101bd5..fe29b4120 100644 --- a/packages/rink/src/hooks.rs +++ b/packages/rink/src/hooks.rs @@ -1,8 +1,8 @@ use crossterm::event::{ Event as TermEvent, KeyCode as TermKeyCode, KeyModifiers, MouseButton, MouseEventKind, }; +use dioxus_native_core::prelude::*; use dioxus_native_core::real_dom::NodeImmutable; -use dioxus_native_core::{NodeId, NodeRef, RealDom}; use rustc_hash::{FxHashMap, FxHashSet}; use dioxus_html::geometry::euclid::{Point2D, Rect, Size2D}; diff --git a/packages/rink/src/layout.rs b/packages/rink/src/layout.rs index 286c129f1..b7eff57a6 100644 --- a/packages/rink/src/layout.rs +++ b/packages/rink/src/layout.rs @@ -6,7 +6,7 @@ use dioxus_native_core::layout_attributes::{ }; use dioxus_native_core::node::OwnedAttributeView; use dioxus_native_core::node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}; -use dioxus_native_core::{Dependancy, SendAnyMap, State}; +use dioxus_native_core::prelude::*; use dioxus_native_core_macro::partial_derive_state; use taffy::prelude::*; diff --git a/packages/rink/src/lib.rs b/packages/rink/src/lib.rs index 9e1a4dbf1..b8bcc1dac 100644 --- a/packages/rink/src/lib.rs +++ b/packages/rink/src/lib.rs @@ -7,7 +7,7 @@ use crossterm::{ terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; use dioxus_html::EventData; -use dioxus_native_core::{node_ref::NodeMaskBuilder, prelude::AnyState, real_dom::NodeImmutable}; +use dioxus_native_core::prelude::*; use dioxus_native_core::{real_dom::RealDom, FxDashSet, NodeId, SendAnyMap}; use focus::FocusState; use futures::{channel::mpsc::UnboundedSender, pin_mut, Future, StreamExt}; diff --git a/packages/rink/src/prevent_default.rs b/packages/rink/src/prevent_default.rs index cadd262f2..4ad5ad87f 100644 --- a/packages/rink/src/prevent_default.rs +++ b/packages/rink/src/prevent_default.rs @@ -1,4 +1,4 @@ -use dioxus_native_core::{node_ref::NodeView, Dependancy, SendAnyMap, State}; +use dioxus_native_core::prelude::*; use dioxus_native_core_macro::partial_derive_state; use shipyard::Component; diff --git a/packages/rink/src/query.rs b/packages/rink/src/query.rs index f28c4978f..1cf113310 100644 --- a/packages/rink/src/query.rs +++ b/packages/rink/src/query.rs @@ -1,6 +1,6 @@ use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard}; -use dioxus_native_core::{real_dom::NodeImmutable, NodeId, RealDom}; +use dioxus_native_core::prelude::*; use taffy::{ geometry::Point, prelude::{Layout, Size}, diff --git a/packages/rink/src/render.rs b/packages/rink/src/render.rs index 28471a17c..ef433976d 100644 --- a/packages/rink/src/render.rs +++ b/packages/rink/src/render.rs @@ -1,4 +1,4 @@ -use dioxus_native_core::{real_dom::NodeImmutable, NodeRef}; +use dioxus_native_core::prelude::*; use std::io::Stdout; use taffy::{ geometry::Point, @@ -26,8 +26,6 @@ pub(crate) fn render_vnode( cfg: Config, parent_location: Point, ) { - use dioxus_native_core::node::NodeType; - if let NodeType::Placeholder = &*node.node_type() { return; } diff --git a/packages/rink/src/style_attributes.rs b/packages/rink/src/style_attributes.rs index 77fda7d91..0c87e3f83 100644 --- a/packages/rink/src/style_attributes.rs +++ b/packages/rink/src/style_attributes.rs @@ -33,7 +33,7 @@ use dioxus_native_core::{ layout_attributes::parse_value, node::OwnedAttributeView, node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}, - Dependancy, SendAnyMap, State, + prelude::*, }; use dioxus_native_core_macro::partial_derive_state; use shipyard::Component; From b25580051132a3ea6e52d066115eed4b183e8320 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 13 Mar 2023 18:09:20 -0500 Subject: [PATCH 069/103] fix TUI and tests building --- packages/dioxus-tui/examples/colorpicker.rs | 2 +- packages/dioxus-tui/src/lib.rs | 4 ++-- packages/native-core/src/lib.rs | 2 ++ packages/native-core/tests/called_minimally_on_build.rs | 6 ------ packages/native-core/tests/fuzzing.rs | 8 +------- packages/native-core/tests/miri_native.rs | 8 +------- 6 files changed, 7 insertions(+), 23 deletions(-) diff --git a/packages/dioxus-tui/examples/colorpicker.rs b/packages/dioxus-tui/examples/colorpicker.rs index 7b7730173..1de6b8209 100644 --- a/packages/dioxus-tui/examples/colorpicker.rs +++ b/packages/dioxus-tui/examples/colorpicker.rs @@ -1,7 +1,7 @@ use dioxus::core::RenderReturn; use dioxus::prelude::*; -use dioxus_tui::query::Query; use dioxus_tui::DioxusElementToNodeId; +use dioxus_tui::Query; use dioxus_tui::Size; fn main() { diff --git a/packages/dioxus-tui/src/lib.rs b/packages/dioxus-tui/src/lib.rs index ece97b673..a0f016147 100644 --- a/packages/dioxus-tui/src/lib.rs +++ b/packages/dioxus-tui/src/lib.rs @@ -12,8 +12,8 @@ use dioxus_html::EventData; use dioxus_native_core::dioxus::{DioxusState, NodeImmutableDioxusExt}; use dioxus_native_core::prelude::*; -pub use rink::Config; -use rink::{query::Query, render, Renderer, TuiContext}; +pub use rink::{query::Query, Config, Size, TuiContext, RenderingMode}; +use rink::{render, Renderer}; pub fn launch(app: Component<()>) { launch_cfg(app, Config::default()) diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index b57cf57c9..ca8e8e5ee 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -30,6 +30,8 @@ pub mod exports { /// A prelude of commonly used items pub mod prelude { + #[cfg(feature = "dioxus")] + pub use crate::dioxus::*; pub use crate::node::{ElementNode, FromAnyValue, NodeType, OwnedAttributeView, TextNode}; pub use crate::node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}; pub use crate::passes::{run_pass, PassDirection, RunPassView, TypeErasedState}; diff --git a/packages/native-core/tests/called_minimally_on_build.rs b/packages/native-core/tests/called_minimally_on_build.rs index feebc1ea6..a55d8adfa 100644 --- a/packages/native-core/tests/called_minimally_on_build.rs +++ b/packages/native-core/tests/called_minimally_on_build.rs @@ -1,11 +1,5 @@ use dioxus::prelude::*; use dioxus_native_core::prelude::*; -use dioxus_native_core::{ - dioxus::DioxusState, - node_ref::{NodeMaskBuilder, NodeView}, - real_dom::RealDom, - Dependancy, SendAnyMap, State, -}; use dioxus_native_core_macro::partial_derive_state; use shipyard::Component; diff --git a/packages/native-core/tests/fuzzing.rs b/packages/native-core/tests/fuzzing.rs index 38b23587a..e34a953c7 100644 --- a/packages/native-core/tests/fuzzing.rs +++ b/packages/native-core/tests/fuzzing.rs @@ -1,12 +1,6 @@ use dioxus::prelude::Props; use dioxus_core::*; -use dioxus_native_core::{ - dioxus::DioxusState, - node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}, - prelude::*, - real_dom::RealDom, - Dependancy, SendAnyMap, State, -}; +use dioxus_native_core::prelude::*; use dioxus_native_core_macro::partial_derive_state; use shipyard::Component; use std::cell::Cell; diff --git a/packages/native-core/tests/miri_native.rs b/packages/native-core/tests/miri_native.rs index 8e05fe934..87888d188 100644 --- a/packages/native-core/tests/miri_native.rs +++ b/packages/native-core/tests/miri_native.rs @@ -1,11 +1,5 @@ use dioxus::prelude::*; -use dioxus_native_core::{ - dioxus::DioxusState, - node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}, - prelude::*, - real_dom::RealDom, - Dependancy, SendAnyMap, State, -}; +use dioxus_native_core::prelude::*; use dioxus_native_core_macro::partial_derive_state; use shipyard::Component; use tokio::time::sleep; From ae1300b015eee92ce3a635b382075299e59da969 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 13 Mar 2023 21:26:44 -0500 Subject: [PATCH 070/103] improve ergonomics of custom drivers and add a counter example to rink --- packages/dioxus-tui/src/lib.rs | 8 +- packages/native-core/src/node.rs | 48 +++++ packages/native-core/src/real_dom.rs | 6 +- packages/rink/examples/counter.rs | 104 +++++++++++ packages/rink/examples/driven.rs | 260 --------------------------- packages/rink/examples/grid.rs | 173 ++++++++++++++++++ packages/rink/examples/widgets.rs | 93 ---------- packages/rink/src/lib.rs | 10 +- 8 files changed, 338 insertions(+), 364 deletions(-) create mode 100644 packages/rink/examples/counter.rs delete mode 100644 packages/rink/examples/driven.rs create mode 100644 packages/rink/examples/grid.rs delete mode 100644 packages/rink/examples/widgets.rs diff --git a/packages/dioxus-tui/src/lib.rs b/packages/dioxus-tui/src/lib.rs index a0f016147..296fd4697 100644 --- a/packages/dioxus-tui/src/lib.rs +++ b/packages/dioxus-tui/src/lib.rs @@ -12,8 +12,8 @@ use dioxus_html::EventData; use dioxus_native_core::dioxus::{DioxusState, NodeImmutableDioxusExt}; use dioxus_native_core::prelude::*; -pub use rink::{query::Query, Config, Size, TuiContext, RenderingMode}; -use rink::{render, Renderer}; +pub use rink::{query::Query, Config, RenderingMode, Size, TuiContext}; +use rink::{render, Driver}; pub fn launch(app: Component<()>) { launch_cfg(app, Config::default()) @@ -66,8 +66,8 @@ struct DioxusRenderer { hot_reload_rx: tokio::sync::mpsc::UnboundedReceiver, } -impl Renderer for DioxusRenderer { - fn render(&mut self, rdom: &Arc>) { +impl Driver for DioxusRenderer { + fn update(&mut self, rdom: &Arc>) { let muts = self.vdom.render_immediate(); { let mut rdom = rdom.write().unwrap(); diff --git a/packages/native-core/src/node.rs b/packages/native-core/src/node.rs index a3362ada2..48f9397a4 100644 --- a/packages/native-core/src/node.rs +++ b/packages/native-core/src/node.rs @@ -62,6 +62,24 @@ pub struct OwnedAttributeDiscription { pub namespace: Option, } +impl From for OwnedAttributeDiscription { + fn from(name: String) -> Self { + Self { + name, + namespace: None, + } + } +} + +impl, N: Into> From<(S, N)> for OwnedAttributeDiscription { + fn from(name: (S, N)) -> Self { + Self { + name: name.0.into(), + namespace: Some(name.1.into()), + } + } +} + /// An attribute on a DOM node, such as `id="my-thing"` or /// `href="https://example.com"`. #[derive(Clone, Copy, Debug)] @@ -88,6 +106,36 @@ pub enum OwnedAttributeValue { Custom(V), } +impl From for OwnedAttributeValue { + fn from(value: String) -> Self { + Self::Text(value) + } +} + +impl From for OwnedAttributeValue { + fn from(value: f64) -> Self { + Self::Float(value) + } +} + +impl From for OwnedAttributeValue { + fn from(value: i64) -> Self { + Self::Int(value) + } +} + +impl From for OwnedAttributeValue { + fn from(value: bool) -> Self { + Self::Bool(value) + } +} + +impl From for OwnedAttributeValue { + fn from(value: V) -> Self { + Self::Custom(value) + } +} + /// Something that can be converted from a borrowed [Any] value. pub trait FromAnyValue: Clone + 'static { /// Convert from an [Any] value. diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 016633fc7..1ac34a421 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -933,9 +933,11 @@ impl ElementNodeMut<'_, V> { /// Set an attribute in the element pub fn set_attribute( &mut self, - name: OwnedAttributeDiscription, - value: OwnedAttributeValue, + name: impl Into, + value: impl Into>, ) -> Option> { + let name = name.into(); + let value = value.into(); self.dirty_nodes.mark_dirty( self.id, NodeMaskBuilder::new() diff --git a/packages/rink/examples/counter.rs b/packages/rink/examples/counter.rs new file mode 100644 index 000000000..4d2428d8a --- /dev/null +++ b/packages/rink/examples/counter.rs @@ -0,0 +1,104 @@ +use dioxus_html::EventData; +use dioxus_native_core::{ + node::TextNode, + prelude::*, + real_dom::{NodeImmutable, NodeTypeMut}, + NodeId, +}; +use rink::{render, Config, Driver}; +use std::rc::Rc; +use std::sync::{Arc, RwLock}; + +#[derive(Default)] +struct Counter { + count: usize, + counter_id: NodeId, + button_id: NodeId, +} + +impl Counter { + fn create(mut root: NodeMut) -> Self { + let mut myself = Self::default(); + + let root_id = root.id(); + let rdom = root.real_dom_mut(); + + // create the counter + let count = myself.count; + myself.counter_id = rdom + .create_node(NodeType::Text(TextNode::new(count.to_string()))) + .id(); + let mut button = rdom.create_node(NodeType::Element(ElementNode { + tag: "div".to_string(), + attributes: [ + ("display".to_string().into(), "flex".to_string().into()), + ( + ("background-color", "style").into(), + format!("rgb({}, {}, {})", count * 10, 0, 0,).into(), + ), + (("width", "style").into(), "100%".to_string().into()), + (("height", "style").into(), "100%".to_string().into()), + (("flex-direction", "style").into(), "row".to_string().into()), + ( + ("justify-content", "style").into(), + "center".to_string().into(), + ), + (("align-items", "style").into(), "center".to_string().into()), + ] + .into_iter() + .collect(), + ..Default::default() + })); + button.add_event_listener("click"); + button.add_event_listener("wheel"); + button.add_child(myself.counter_id); + myself.button_id = button.id(); + rdom.get_mut(root_id).unwrap().add_child(myself.button_id); + + myself + } +} + +impl Driver for Counter { + fn update(&mut self, rdom: &Arc>) { + // update the counter + let mut rdom = rdom.write().unwrap(); + let mut node = rdom.get_mut(self.button_id).unwrap(); + if let NodeTypeMut::Element(mut el) = node.node_type_mut() { + el.set_attribute( + ("background-color", "style"), + format!("rgb({}, {}, {})", self.count * 10, 0, 0,), + ); + } + let mut text = rdom.get_mut(self.counter_id).unwrap(); + let type_mut = text.node_type_mut(); + if let NodeTypeMut::Text(mut text) = type_mut { + *text = self.count.to_string(); + } + } + + fn handle_event( + &mut self, + _: &Arc>, + _: NodeId, + _: &str, + _: Rc, + _: bool, + ) { + // when a click or wheel event is fired, increment the counter + self.count += 1; + } + + fn poll_async(&mut self) -> std::pin::Pin + '_>> { + Box::pin(async move { tokio::time::sleep(std::time::Duration::from_millis(1000)).await }) + } +} + +fn main() { + render(Config::new(), |rdom, _, _| { + let mut rdom = rdom.write().unwrap(); + let root = rdom.root_id(); + Counter::create(rdom.get_mut(root).unwrap()) + }) + .unwrap(); +} diff --git a/packages/rink/examples/driven.rs b/packages/rink/examples/driven.rs deleted file mode 100644 index b5d231888..000000000 --- a/packages/rink/examples/driven.rs +++ /dev/null @@ -1,260 +0,0 @@ -use dioxus_html::EventData; -use dioxus_native_core::{ - node::{OwnedAttributeDiscription, OwnedAttributeValue, TextNode}, - prelude::*, - real_dom::{NodeImmutable, NodeTypeMut}, - NodeId, -}; -use dioxus_tui::{self, render, Config, Renderer}; -use rustc_hash::FxHashSet; -use std::rc::Rc; -use std::sync::{Arc, RwLock}; - -const SIZE: usize = 10; - -struct Test { - node_states: [[usize; SIZE]; SIZE], - dirty: FxHashSet<(usize, usize)>, -} - -impl Default for Test { - fn default() -> Self { - Self { - node_states: [[0; SIZE]; SIZE], - dirty: FxHashSet::default(), - } - } -} - -impl Test { - fn create(mut root: NodeMut) -> Self { - let myself = Self::default(); - - // Set the root node to be a flexbox with a column direction. - if let NodeTypeMut::Element(mut el) = root.node_type_mut() { - el.set_attribute( - OwnedAttributeDiscription { - name: "display".into(), - namespace: None, - }, - OwnedAttributeValue::Text("flex".into()), - ); - el.set_attribute( - OwnedAttributeDiscription { - name: "flex-direction".into(), - namespace: None, - }, - OwnedAttributeValue::Text("column".into()), - ); - el.set_attribute( - OwnedAttributeDiscription { - name: "width".into(), - namespace: None, - }, - OwnedAttributeValue::Text("100%".into()), - ); - el.set_attribute( - OwnedAttributeDiscription { - name: "height".into(), - namespace: None, - }, - OwnedAttributeValue::Text("100%".into()), - ); - } - - let root_id = root.id(); - let rdom = root.real_dom_mut(); - - // create the grid - for (x, row) in myself.node_states.iter().copied().enumerate() { - let row_node = rdom - .create_node(NodeType::Element(ElementNode { - tag: "div".to_string(), - attributes: [ - ( - OwnedAttributeDiscription { - name: "display".into(), - namespace: None, - }, - OwnedAttributeValue::Text("flex".into()), - ), - ( - OwnedAttributeDiscription { - name: "flex-direction".into(), - namespace: None, - }, - OwnedAttributeValue::Text("row".into()), - ), - ( - OwnedAttributeDiscription { - name: "width".into(), - namespace: None, - }, - OwnedAttributeValue::Text("100%".into()), - ), - ( - OwnedAttributeDiscription { - name: "height".into(), - namespace: None, - }, - OwnedAttributeValue::Text("100%".into()), - ), - ] - .into_iter() - .collect(), - ..Default::default() - })) - .id(); - for (y, count) in row.iter().copied().enumerate() { - let node = rdom - .create_node(NodeType::Text(TextNode::new(count.to_string()))) - .id(); - let mut button = rdom.create_node(NodeType::Element(ElementNode { - tag: "div".to_string(), - attributes: [ - ( - OwnedAttributeDiscription { - name: "background-color".into(), - namespace: None, - }, - OwnedAttributeValue::Text(format!( - "rgb({}, {}, {})", - count * 10, - 0, - (x + y) * 10, - )), - ), - ( - OwnedAttributeDiscription { - name: "width".into(), - namespace: None, - }, - OwnedAttributeValue::Text("100%".into()), - ), - ( - OwnedAttributeDiscription { - name: "height".into(), - namespace: None, - }, - OwnedAttributeValue::Text("100%".into()), - ), - ( - OwnedAttributeDiscription { - name: "display".into(), - namespace: None, - }, - OwnedAttributeValue::Text("flex".into()), - ), - ( - OwnedAttributeDiscription { - name: "flex-direction".into(), - namespace: None, - }, - OwnedAttributeValue::Text("row".into()), - ), - ( - OwnedAttributeDiscription { - name: "justify-content".into(), - namespace: None, - }, - OwnedAttributeValue::Text("center".into()), - ), - ( - OwnedAttributeDiscription { - name: "align-items".into(), - namespace: None, - }, - OwnedAttributeValue::Text("center".into()), - ), - ] - .into_iter() - .collect(), - ..Default::default() - })); - button.add_event_listener("click"); - button.add_event_listener("wheel"); - button.add_child(node); - let button_id = button.id(); - rdom.get_mut(row_node).unwrap().add_child(button_id); - } - rdom.get_mut(root_id).unwrap().add_child(row_node); - } - myself - } -} - -impl Renderer for Test { - fn render(&mut self, rdom: &Arc>) { - let mut rdom = rdom.write().unwrap(); - let root_id = rdom.root_id(); - let mut root = rdom.get_mut(root_id).unwrap(); - for (x, y) in self.dirty.drain() { - let row_id = root.child_ids()[x]; - let rdom = root.real_dom_mut(); - let row = rdom.get(row_id).unwrap(); - let node_id = row.child_ids()[y]; - let mut node = rdom.get_mut(node_id).unwrap(); - if let NodeTypeMut::Element(mut el) = node.node_type_mut() { - el.set_attribute( - OwnedAttributeDiscription { - name: "background-color".into(), - namespace: None, - }, - OwnedAttributeValue::Text(format!( - "rgb({}, {}, {})", - self.node_states[x][y] * 10, - 0, - (x + y) * 10, - )), - ); - } - let text_id = *node.child_ids().first().unwrap(); - let mut text = rdom.get_mut(text_id).unwrap(); - let type_mut = text.node_type_mut(); - if let NodeTypeMut::Text(mut text) = type_mut { - *text = self.node_states[x][y].to_string(); - } - } - } - - fn handle_event( - &mut self, - rdom: &Arc>, - id: NodeId, - _: &str, - _: Rc, - _: bool, - ) { - let rdom = rdom.read().unwrap(); - let node = rdom.get(id).unwrap(); - if let Some(parent) = node.parent() { - let child_number = parent - .child_ids() - .iter() - .position(|id| *id == node.id()) - .unwrap(); - if let Some(parents_parent) = parent.parent() { - let parents_child_number = parents_parent - .child_ids() - .iter() - .position(|id| *id == parent.id()) - .unwrap(); - self.node_states[parents_child_number][child_number] += 1; - self.dirty.insert((parents_child_number, child_number)); - } - } - } - - fn poll_async(&mut self) -> std::pin::Pin + '_>> { - Box::pin(async move { tokio::time::sleep(std::time::Duration::from_millis(1000)).await }) - } -} - -fn main() { - render(Config::new(), |rdom, _, _| { - let mut rdom = rdom.write().unwrap(); - let root = rdom.root_id(); - Test::create(rdom.get_mut(root).unwrap()) - }) - .unwrap(); -} diff --git a/packages/rink/examples/grid.rs b/packages/rink/examples/grid.rs new file mode 100644 index 000000000..7abc4669f --- /dev/null +++ b/packages/rink/examples/grid.rs @@ -0,0 +1,173 @@ +use dioxus_html::EventData; +use dioxus_native_core::{ + node::TextNode, + prelude::*, + real_dom::{NodeImmutable, NodeTypeMut}, + NodeId, +}; +use rink::{render, Config, Driver}; +use rustc_hash::FxHashSet; +use std::rc::Rc; +use std::sync::{Arc, RwLock}; + +const SIZE: usize = 20; + +#[derive(Default, Clone, Copy)] +struct Node { + container_id: Option, + text_id: Option, + count: usize, +} + +struct Test { + node_states: [[Node; SIZE]; SIZE], + dirty: FxHashSet<(usize, usize)>, +} + +impl Default for Test { + fn default() -> Self { + Self { + node_states: [[Node { + container_id: None, + text_id: None, + count: 0, + }; SIZE]; SIZE], + dirty: FxHashSet::default(), + } + } +} + +impl Test { + fn create(mut root: NodeMut) -> Self { + let mut myself = Self::default(); + + // Set the root node to be a flexbox with a column direction. + if let NodeTypeMut::Element(mut el) = root.node_type_mut() { + el.set_attribute("display".to_string(), "flex".to_string()); + el.set_attribute(("flex-direction", "style"), "column".to_string()); + el.set_attribute(("width", "style"), "100%".to_string()); + el.set_attribute(("height", "style"), "100%".to_string()); + } + + let root_id = root.id(); + let rdom = root.real_dom_mut(); + + // create the grid + for (x, row) in myself.node_states.iter_mut().enumerate() { + let row_node = rdom + .create_node(NodeType::Element(ElementNode { + tag: "div".to_string(), + attributes: [ + ("display".to_string().into(), "flex".to_string().into()), + (("flex-direction", "style").into(), "row".to_string().into()), + (("width", "style").into(), "100%".to_string().into()), + (("height", "style").into(), "100%".to_string().into()), + ] + .into_iter() + .collect(), + ..Default::default() + })) + .id(); + for (y, node) in row.iter_mut().enumerate() { + let count = node.count; + let id = rdom + .create_node(NodeType::Text(TextNode::new(count.to_string()))) + .id(); + let mut button = rdom.create_node(NodeType::Element(ElementNode { + tag: "div".to_string(), + attributes: [ + ("display".to_string().into(), "flex".to_string().into()), + ( + ("background-color", "style").into(), + format!("rgb({}, {}, {})", count * 10, 0, (x + y),).into(), + ), + (("width", "style").into(), "100%".to_string().into()), + (("height", "style").into(), "100%".to_string().into()), + (("flex-direction", "style").into(), "row".to_string().into()), + ( + ("justify-content", "style").into(), + "center".to_string().into(), + ), + (("align-items", "style").into(), "center".to_string().into()), + ] + .into_iter() + .collect(), + ..Default::default() + })); + button.add_event_listener("click"); + button.add_event_listener("wheel"); + button.add_child(id); + let button_id = button.id(); + rdom.get_mut(row_node).unwrap().add_child(button_id); + node.container_id = Some(button_id); + node.text_id = Some(id); + } + rdom.get_mut(root_id).unwrap().add_child(row_node); + } + myself + } +} + +impl Driver for Test { + fn update(&mut self, rdom: &Arc>) { + let mut rdom = rdom.write().unwrap(); + for (x, y) in self.dirty.drain() { + let node = self.node_states[x][y]; + let node_id = node.container_id.unwrap(); + let mut container = rdom.get_mut(node_id).unwrap(); + if let NodeTypeMut::Element(mut el) = container.node_type_mut() { + el.set_attribute( + ("background-color", "style"), + format!("rgb({}, {}, {})", node.count * 10, 0, (x + y),), + ); + } + let text_id = node.text_id.unwrap(); + let mut text = rdom.get_mut(text_id).unwrap(); + let type_mut = text.node_type_mut(); + if let NodeTypeMut::Text(mut text) = type_mut { + *text = node.count.to_string(); + } + } + } + + fn handle_event( + &mut self, + rdom: &Arc>, + id: NodeId, + _: &str, + _: Rc, + _: bool, + ) { + let rdom = rdom.read().unwrap(); + let node = rdom.get(id).unwrap(); + if let Some(parent) = node.parent() { + let child_number = parent + .child_ids() + .iter() + .position(|id| *id == node.id()) + .unwrap(); + if let Some(parents_parent) = parent.parent() { + let parents_child_number = parents_parent + .child_ids() + .iter() + .position(|id| *id == parent.id()) + .unwrap(); + self.node_states[parents_child_number][child_number].count += 1; + self.dirty.insert((parents_child_number, child_number)); + } + } + } + + fn poll_async(&mut self) -> std::pin::Pin + '_>> { + Box::pin(async move { tokio::time::sleep(std::time::Duration::from_millis(1000)).await }) + } +} + +fn main() { + render(Config::new(), |rdom, _, _| { + let mut rdom = rdom.write().unwrap(); + let root = rdom.root_id(); + Test::create(rdom.get_mut(root).unwrap()) + }) + .unwrap(); +} diff --git a/packages/rink/examples/widgets.rs b/packages/rink/examples/widgets.rs deleted file mode 100644 index 793057bc5..000000000 --- a/packages/rink/examples/widgets.rs +++ /dev/null @@ -1,93 +0,0 @@ -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", - 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/rink/src/lib.rs b/packages/rink/src/lib.rs index b8bcc1dac..1111d73dc 100644 --- a/packages/rink/src/lib.rs +++ b/packages/rink/src/lib.rs @@ -76,7 +76,7 @@ impl TuiContext { } } -pub fn render( +pub fn render( cfg: Config, create_renderer: impl FnOnce( &Arc>, @@ -115,7 +115,7 @@ pub fn render( let mut renderer = create_renderer(&rdom, &taffy, event_tx_clone); { - renderer.render(&rdom); + renderer.update(&rdom); let mut any_map = SendAnyMap::new(); any_map.insert(taffy.clone()); let mut rdom = rdom.write().unwrap(); @@ -261,7 +261,7 @@ pub fn render( } } // updates the dom's nodes - renderer.render(&rdom); + renderer.update(&rdom); // update the style and layout let mut rdom = rdom.write().unwrap(); let mut any_map = SendAnyMap::new(); @@ -297,8 +297,8 @@ pub enum InputEvent { Close, } -pub trait Renderer { - fn render(&mut self, rdom: &Arc>); +pub trait Driver { + fn update(&mut self, rdom: &Arc>); fn handle_event( &mut self, rdom: &Arc>, From 4b81cdbf1b77e47802333bfafbd07d1371d8f481 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 14 Mar 2023 16:06:32 -0500 Subject: [PATCH 071/103] Update custom renderer guide --- docs/guide/examples/custom_renderer.rs | 309 ++++++++++++++++++ docs/guide/src/en/custom_renderer/index.md | 229 ++----------- packages/native-core-macro/src/lib.rs | 25 ++ packages/native-core/examples/simple.rs | 222 +++++++++++++ .../native-core/examples/simple_dioxus.rs | 248 ++++++++++++++ packages/native-core/src/node.rs | 34 +- packages/native-core/src/passes.rs | 8 +- packages/native-core/src/real_dom.rs | 7 +- 8 files changed, 870 insertions(+), 212 deletions(-) create mode 100644 docs/guide/examples/custom_renderer.rs create mode 100644 packages/native-core/examples/simple.rs create mode 100644 packages/native-core/examples/simple_dioxus.rs diff --git a/docs/guide/examples/custom_renderer.rs b/docs/guide/examples/custom_renderer.rs new file mode 100644 index 000000000..17590f18c --- /dev/null +++ b/docs/guide/examples/custom_renderer.rs @@ -0,0 +1,309 @@ +use dioxus::prelude::*; +use dioxus_native_core::exports::shipyard::Component; +use dioxus_native_core::node_ref::*; +use dioxus_native_core::prelude::*; +use dioxus_native_core_macro::partial_derive_state; + +// ANCHOR: state_impl +struct FontSize(f64); + +// All states need to derive Component +#[derive(Default, Debug, Copy, Clone, Component)] +struct Size(f64, f64); + +/// Derive some of the boilerplate for the State implementation +#[partial_derive_state] +impl State for Size { + type ParentDependencies = (); + + // The size of the current node depends on the size of its children + type ChildDependencies = (Self,); + + type NodeDependencies = (); + + // Size only cares about the width, height, and text parts of the current node + const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new() + // Get access to the width and height attributes + .with_attrs(AttributeMaskBuilder::Some(&["width", "height"])) + // Get access to the text of the node + .with_text(); + + fn update<'a>( + &mut self, + node_view: NodeView<()>, + _node: ::ElementBorrowed<'a>, + _parent: Option<::ElementBorrowed<'a>>, + children: Vec<::ElementBorrowed<'a>>, + context: &SendAnyMap, + ) -> bool { + let font_size = context.get::().unwrap().0; + let mut width; + let mut height; + if let Some(text) = node_view.text() { + // if the node has text, use the text to size our object + width = text.len() as f64 * font_size; + height = font_size; + } else { + // otherwise, the size is the maximum size of the children + width = children + .iter() + .map(|(item,)| item.0) + .reduce(|accum, item| if accum >= item { accum } else { item }) + .unwrap_or(0.0); + + height = children + .iter() + .map(|(item,)| item.1) + .reduce(|accum, item| if accum >= item { accum } else { item }) + .unwrap_or(0.0); + } + // if the node contains a width or height attribute it overrides the other size + for a in node_view.attributes().into_iter().flatten() { + match &*a.attribute.name { + "width" => width = a.value.as_float().unwrap(), + "height" => height = a.value.as_float().unwrap(), + // because Size only depends on the width and height, no other attributes will be passed to the member + _ => panic!(), + } + } + // to determine what other parts of the dom need to be updated we return a boolean that marks if this member changed + let changed = (width != self.0) || (height != self.1); + *self = Self(width, height); + changed + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Default, Component)] +struct TextColor { + r: u8, + g: u8, + b: u8, +} + +#[partial_derive_state] +impl State for TextColor { + // TextColor depends on the TextColor part of the parent + type ParentDependencies = (Self,); + + type ChildDependencies = (); + + type NodeDependencies = (); + + // TextColor only cares about the color attribute of the current node + const NODE_MASK: NodeMaskBuilder<'static> = + // Get access to the color attribute + NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["color"])); + + fn update<'a>( + &mut self, + node_view: NodeView<()>, + _node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + _children: Vec<::ElementBorrowed<'a>>, + _context: &SendAnyMap, + ) -> bool { + // TextColor only depends on the color tag, so getting the first tag is equivilent to looking through all tags + let new = match node_view + .attributes() + .and_then(|mut attrs| attrs.next()) + .and_then(|attr| attr.value.as_text()) + { + // if there is a color tag, translate it + Some("red") => TextColor { r: 255, g: 0, b: 0 }, + Some("green") => TextColor { r: 0, g: 255, b: 0 }, + Some("blue") => TextColor { r: 0, g: 0, b: 255 }, + Some(color) => panic!("unknown color {color}"), + // otherwise check if the node has a parent and inherit that color + None => match parent { + Some((parent,)) => *parent, + None => Self::default(), + }, + }; + // check if the member has changed + let changed = new != *self; + *self = new; + changed + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Default, Component)] +struct Border(bool); + +#[partial_derive_state] +impl State for Border { + // TextColor depends on the TextColor part of the parent + type ParentDependencies = (Self,); + + type ChildDependencies = (); + + type NodeDependencies = (); + + // Border does not depended on any other member in the current node + const NODE_MASK: NodeMaskBuilder<'static> = + // Get access to the border attribute + NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["border"])); + + fn update<'a>( + &mut self, + node_view: NodeView<()>, + _node: ::ElementBorrowed<'a>, + _parent: Option<::ElementBorrowed<'a>>, + _children: Vec<::ElementBorrowed<'a>>, + _context: &SendAnyMap, + ) -> bool { + // check if the node contians a border attribute + let new = Self( + node_view + .attributes() + .and_then(|mut attrs| attrs.next().map(|a| a.attribute.name == "border")) + .is_some(), + ); + // check if the member has changed + let changed = new != *self; + *self = new; + changed + } +} +// ANCHOR_END: state_impl + +// ANCHOR: rendering +fn main() -> Result<(), Box> { + fn app(cx: Scope) -> Element { + let count = use_state(cx, || 0); + + use_future(cx, (count,), |(count,)| async move { + loop { + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + count.set(*count + 1); + } + }); + + cx.render(rsx! { + div{ + color: "red", + "{count}" + } + }) + } + + // create the vdom, the real_dom, and the binding layer between them + let mut vdom = VirtualDom::new(app); + let mut rdom: RealDom = RealDom::new([ + Border::to_type_erased(), + TextColor::to_type_erased(), + Size::to_type_erased(), + ]); + let mut dioxus_intigration_state = DioxusState::create(&mut rdom); + + let mutations = vdom.rebuild(); + // update the structure of the real_dom tree + dioxus_intigration_state.apply_mutations(&mut rdom, mutations); + let mut ctx = SendAnyMap::new(); + // set the font size to 3.3 + ctx.insert(FontSize(3.3)); + // update the State for nodes in the real_dom tree + let _to_rerender = rdom.update_state(ctx); + + // we need to run the vdom in a async runtime + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build()? + .block_on(async { + loop { + // wait for the vdom to update + vdom.wait_for_work().await; + + // get the mutations from the vdom + let mutations = vdom.render_immediate(); + + // update the structure of the real_dom tree + dioxus_intigration_state.apply_mutations(&mut rdom, mutations); + + // update the state of the real_dom tree + let mut ctx = SendAnyMap::new(); + // set the font size to 3.3 + ctx.insert(FontSize(3.3)); + let _to_rerender = rdom.update_state(ctx); + + // render... + rdom.traverse_depth_first(|node| { + let indent = " ".repeat(node.height() as usize); + let color = *node.get::().unwrap(); + let size = *node.get::().unwrap(); + let border = *node.get::().unwrap(); + let id = node.id(); + let node = node.node_type(); + let node_type = &*node; + println!("{indent}{id:?} {color:?} {size:?} {border:?} {node_type:?}"); + }); + } + }) +} +// ANCHOR_END: rendering + +// ANCHOR: derive_state +// All states must derive Component (https://docs.rs/shipyard/latest/shipyard/derive.Component.html) +// They also must implement Default or provide a custom implementation of create in the State trait +#[derive(Default, Component)] +struct MyState; + +/// Derive some of the boilerplate for the State implementation +#[partial_derive_state] +impl State for MyState { + // The states of the parent nodes this state depends on + type ParentDependencies = (); + + // The states of the child nodes this state depends on + type ChildDependencies = (Self,); + + // The states of the current node this state depends on + type NodeDependencies = (); + + // The parts of the current text, element, or placeholder node in the tree that this state depends on + const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new(); + + // How to update the state of the current node based on the state of the parent nodes, child nodes, and the current node + // Returns true if the node was updated and false if the node was not updated + fn update<'a>( + &mut self, + // The view of the current node limited to the parts this state depends on + _node_view: NodeView<()>, + // The state of the current node that this state depends on + _node: ::ElementBorrowed<'a>, + // The state of the parent nodes that this state depends on + _parent: Option<::ElementBorrowed<'a>>, + // The state of the child nodes that this state depends on + _children: Vec<::ElementBorrowed<'a>>, + // The context of the current node used to pass global state into the tree + _context: &SendAnyMap, + ) -> bool { + todo!() + } + + // partial_derive_state will generate a default implementation of all the other methods +} +// ANCHOR_END: derive_state + +// ANCHOR: cursor +fn text_editing() { + let mut cursor = Cursor::default(); + let mut text = String::new(); + + let keyboard_data = 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(), + ); + // handle keyboard input with a max text length of 10 + cursor.handle_input(&keyboard_data, &mut text, 10); + + // mannually select text between characters 0-5 on the first line (this could be from dragging with a mouse) + cursor.start = Pos::new(0, 0); + cursor.end = Some(Pos::new(5, 0)); + + // delete the selected text and move the cursor to the start of the selection + cursor.delete_selection(&mut text); +} +// ANCHOR_END: cursor diff --git a/docs/guide/src/en/custom_renderer/index.md b/docs/guide/src/en/custom_renderer/index.md index 7c2e89eac..2d4ddb759 100644 --- a/docs/guide/src/en/custom_renderer/index.md +++ b/docs/guide/src/en/custom_renderer/index.md @@ -99,13 +99,14 @@ Template { attr_paths: &'a [&'a [u8]], } ``` + > For more detailed docs about the struture of templates see the [Template api docs](https://docs.rs/dioxus-core/latest/dioxus_core/prelude/struct.Template.html) This template will be sent to the renderer in the [list of templates](https://docs.rs/dioxus-core/latest/dioxus_core/struct.Mutations.html#structfield.templates) supplied with the mutations the first time it is used. Any time the renderer encounters a [LoadTemplate](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.LoadTemplate) mutation after this, it should clone the template and store it in the given id. -For dynamic nodes and dynamic text nodes, a placeholder node should be created and inserted into the UI so that the node can be navigated to later. +For dynamic nodes and dynamic text nodes, a placeholder node should be created and inserted into the UI so that the node can be modified later. -In HTML renderers, this template could look like: +In HTML renderers, this template could look like this: ```html

""

@@ -206,7 +207,9 @@ nodes: [ "count: 0", ] ``` + Over time, our stack looked like this: + ```rust [Root] [Root,

""

] @@ -218,15 +221,15 @@ Conveniently, this approach completely separates the Virtual DOM and the Real DO Dioxus is also really fast. Because Dioxus splits the diff and patch phase, it's able to make all the edits to the RealDOM in a very short amount of time (less than a single frame) making rendering very snappy. It also allows Dioxus to cancel large diffing operations if higher priority work comes in while it's diffing. -This little demo serves to show exactly how a Renderer would need to process an edit stream to build UIs. +This little demo serves to show exactly how a Renderer would need to process a mutation stream to build UIs. ## Event loop -Like most GUIs, Dioxus relies on an event loop to progress the VirtualDOM. The VirtualDOM itself can produce events as well, so it's important that your custom renderer can handle those too. +Like most GUIs, Dioxus relies on an event loop to progress the VirtualDOM. The VirtualDOM itself can produce events as well, so it's important for your custom renderer can handle those too. The code for the WebSys implementation is straightforward, so we'll add it here to demonstrate how simple an event loop is: -```rust +```rust, ignore pub async fn run(&mut self) -> dioxus_core::error::Result<()> { // Push the body element onto the WebsysDom's stack machine let mut websys_dom = crate::new::WebsysDom::new(prepare_websys_dom()); @@ -254,9 +257,9 @@ pub async fn run(&mut self) -> dioxus_core::error::Result<()> { } ``` -It's important that you decode the real events from your event system into Dioxus' synthetic event system (synthetic meaning abstracted). This simply means matching your event type and creating a Dioxus `UserEvent` type. Right now, the VirtualEvent system is modeled almost entirely around the HTML spec, but we are interested in slimming it down. +It's important to decode what the real events are for your event system into Dioxus' synthetic event system (synthetic meaning abstracted). This simply means matching your event type and creating a Dioxus `UserEvent` type. Right now, the virtual event system is modeled almost entirely around the HTML spec, but we are interested in slimming it down. -```rust +```rust, ignore fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent { match event.type_().as_str() { "keydown" => { @@ -294,16 +297,17 @@ For more examples and information on how to create custom namespaces, see the [` # Native Core -If you are creating a renderer in rust, the [native-core](https://github.com/DioxusLabs/dioxus/tree/master/packages/native-core) crate provides some utilities to implement a renderer. It provides an abstraction over Mutations and Templates and contains helpers that can handle the layout, and text editing for you. +If you are creating a renderer in rust, the [native-core](https://github.com/DioxusLabs/dioxus/tree/master/packages/native-core) crate provides some utilities to implement a renderer. It provides an abstraction over Mutations and Templates and contains helpers that can handle the layout and text editing for you. ## The RealDom -The `RealDom` is a higher-level abstraction over updating the Dom. It updates with `Mutations` and provides a way to incrementally update the state of nodes based on attributes or other states that change. +The `RealDom` is a higher-level abstraction over updating the Dom. It uses an entity component system to manage the state of nodes. This system allows you to modify insert and modify arbitrary components on nodes. On top of this, the RealDom provides a way to manage a tree of nodes, and the State trait provides a way to automatically add and update these components when the tree is modified. It also provides a way to apply `Mutations` to the RealDom. ### Example Let's build a toy renderer with borders, size, and text color. Before we start let's take a look at an example element we can render: + ```rust cx.render(rsx!{ div{ @@ -372,188 +376,24 @@ In the following diagram arrows represent dataflow: [//]: # " end" [//]: # " end" -To help in building a Dom, native-core provides four traits: State, ChildDepState, ParentDepState, NodeDepState, and a RealDom struct. The ChildDepState, ParentDepState, and NodeDepState provide a way to describe how some information in a node relates to that of its relatives. By providing how to build a single node from its relations, native-core will derive a way to update the state of all nodes for you with ```#[derive(State)]```. Once you have a state you can provide it as a generic to RealDom. RealDom provides all of the methods to interact and update your new dom. +To help in building a Dom, native-core provides the State trait and a RealDom struct. The State trait provides a way to describe how states in a node depend on other states in its relatives. By describing how to update a single node from its relations, native-core will derive a way to update the states of all nodes for you. Once you have a state you can provide it as a generic to RealDom. RealDom provides all of the methods to interact and update your new dom. -```rust +Native Core cannot create all of the required methods for the State trait, but it can derive some of them. To implement the State trait, you must implement the following methods and let the `#[partial_derive_state]` macro handle the rest: -use dioxus_native_core::node_ref::*; -use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State}; -use dioxus_native_core_macro::{sorted_str_slice, State}; - -#[derive(Default, Copy, Clone)] -struct Size(f64, f64); -// Size only depends on the current node and its children, so it implements ChildDepState -impl ChildDepState for Size { - // Size accepts a font size context - type Ctx = f64; - // Size depends on the Size part of each child - type DepState = (Self,); - // Size only cares about the width, height, and text parts of the current node - const NODE_MASK: NodeMask = - NodeMask::new_with_attrs(AttributeMask::Static(&sorted_str_slice!([ - "width", "height" - ]))) - .with_text(); - fn reduce<'a>( - &mut self, - node: NodeView, - children: impl Iterator, - ctx: &Self::Ctx, - ) -> bool - where - Self::DepState: 'a, - { - let mut width; - let mut height; - if let Some(text) = node.text() { - // if the node has text, use the text to size our object - width = text.len() as f64 * ctx; - height = *ctx; - } else { - // otherwise, the size is the maximum size of the children - width = children - .by_ref() - .map(|(item,)| item.0) - .reduce(|accum, item| if accum >= item { accum } else { item }) - .unwrap_or(0.0); - - height = children - .map(|(item,)| item.1) - .reduce(|accum, item| if accum >= item { accum } else { item }) - .unwrap_or(0.0); - } - // if the node contains a width or height attribute it overrides the other size - for a in node.attributes().into_iter().flatten() { - match &*a.attribute.name { - "width" => width = a.value.as_float().unwrap(), - "height" => height = a.value.as_float().unwrap(), - // because Size only depends on the width and height, no other attributes will be passed to the member - _ => panic!(), - } - } - // to determine what other parts of the dom need to be updated we return a boolean that marks if this member changed - let changed = (width != self.0) || (height != self.1); - *self = Self(width, height); - changed - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Default)] -struct TextColor { - r: u8, - g: u8, - b: u8, -} -// TextColor only depends on the current node and its parent, so it implements ParentDepState -impl ParentDepState for TextColor { - type Ctx = (); - // TextColor depends on the TextColor part of the parent - type DepState = (Self,); - // TextColor only cares about the color attribute of the current node - const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::Static(&["color"])); - fn reduce(&mut self, node: NodeView, parent: Option<(&Self,)>, _ctx: &Self::Ctx) -> bool { - // TextColor only depends on the color tag, so getting the first tag is equivilent to looking through all tags - let new = match node - .attributes() - .and_then(|attrs| attrs.next()) - .map(|attr| attr.attribute.name.as_str()) - { - // if there is a color tag, translate it - Some("red") => TextColor { r: 255, g: 0, b: 0 }, - Some("green") => TextColor { r: 0, g: 255, b: 0 }, - Some("blue") => TextColor { r: 0, g: 0, b: 255 }, - Some(_) => panic!("unknown color"), - // otherwise check if the node has a parent and inherit that color - None => match parent { - Some((parent,)) => *parent, - None => Self::default(), - }, - }; - // check if the member has changed - let changed = new != *self; - *self = new; - changed - } -} - -#[derive(Debug, Clone, PartialEq, Default)] -struct Border(bool); -// TextColor only depends on the current node, so it implements NodeDepState -impl NodeDepState for Border { - type Ctx = (); - type DepState = (); - - // Border does not depended on any other member in the current node - const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::Static(&["border"])); - fn reduce(&mut self, node: NodeView, _sibling: (), _ctx: &Self::Ctx) -> bool { - // check if the node contians a border attribute - let new = Self( - node.attributes() - .and_then(|attrs| attrs.next().map(|a| a.attribute.name == "border")) - .is_some(), - ); - // check if the member has changed - let changed = new != *self; - *self = new; - changed - } -} - -// State provides a derive macro, but anotations on the members are needed in the form #[dep_type(dep_member, CtxType)] -#[derive(State, Default, Clone)] -struct ToyState { - // the color member of it's parent and no context - #[parent_dep_state(color)] - color: TextColor, - // depends on the node, and no context - #[node_dep_state()] - border: Border, - // depends on the layout_width member of children and f32 context (for text size) - #[child_dep_state(size, f32)] - size: Size, -} +```rust, ignore +{{#include ../../../examples/custom_renderer.rs:derive_state}} ``` -Now that we have our state, we can put it to use in our dom. We can update the dom with update_state to update the structure of the dom (adding, removing, and changing properties of nodes) and then apply_mutations to update the ToyState for each of the nodes that changed. +Lets take a look at how to implement the State trait for a simple renderer. + ```rust -fn main(){ - fn app(cx: Scope) -> Element { - cx.render(rsx!{ - div{ - color: "red", - "hello world" - } - }) - } - let vdom = VirtualDom::new(app); - let rdom: RealDom = RealDom::new(); +{{#include ../../../examples/custom_renderer.rs:state_impl}} +``` - let mutations = dom.rebuild(); - // update the structure of the real_dom tree - let to_update = rdom.apply_mutations(vec![mutations]); - let mut ctx = AnyMap::new(); - // set the font size to 3.3 - ctx.insert(3.3f64); - // update the ToyState for nodes in the real_dom tree - let _to_rerender = rdom.update_state(&dom, to_update, ctx).unwrap(); +Now that we have our state, we can put it to use in our RealDom. We can update the RealDom with apply_mutations to update the structure of the dom (adding, removing, and changing properties of nodes) and then update_state to update the States for each of the nodes that changed. - // we need to run the vdom in a async runtime - tokio::runtime::Builder::new_current_thread() - .enable_all() - .build()? - .block_on(async { - loop{ - let wait = vdom.wait_for_work(); - let mutations = vdom.work_with_deadline(|| false); - let to_update = rdom.apply_mutations(mutations); - let mut ctx = AnyMap::new(); - ctx.insert(3.3f64); - let _to_rerender = rdom.update_state(vdom, to_update, ctx).unwrap(); - - // render... - } - }) -} +```rust +{{#include ../../../examples/custom_renderer.rs:rendering}} ``` ## Layout @@ -565,26 +405,9 @@ For most platforms, the layout of the Elements will stay the same. The [layout_a To make it easier to implement text editing in rust renderers, `native-core` also contains a renderer-agnostic cursor system. The cursor can handle text editing, selection, and movement with common keyboard shortcuts integrated. ```rust -let mut cursor = Cursor::default(); -let mut text = String::new(); - -let keyboard_data = 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(), -); -// handle keyboard input with a max text length of 10 -cursor.handle_input(&keyboard_data, &mut text, 10); - -// mannually select text between characters 0-5 on the first line (this could be from dragging with a mouse) -cursor.start = Pos::new(0, 0); -cursor.end = Some(Pos::new(5, 0)); - -// delete the selected text and move the cursor to the start of the selection -cursor.delete_selection(&mut text); +{{#include ../../../examples/custom_renderer.rs:cursor}} ``` ## Conclusion + That should be it! You should have nearly all the knowledge required on how to implement your renderer. We're super interested in seeing Dioxus apps brought to custom desktop renderers, mobile renderers, video game UI, and even augmented reality! If you're interested in contributing to any of these projects, don't be afraid to reach out or join the [community](https://discord.gg/XgGxMSkvUM). diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index 6132d898d..159717c75 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -11,6 +11,11 @@ use syn::{parse_macro_input, ItemImpl, Type, TypePath, TypeTuple}; pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream { let impl_block: syn::ItemImpl = parse_macro_input!(input as syn::ItemImpl); + let has_create_fn = impl_block + .items + .iter() + .any(|item| matches!(item, syn::ImplItem::Method(method) if method.sig.ident == "create")); + let parent_dependencies = impl_block .items .iter() @@ -295,9 +300,29 @@ pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream { .arguments .clone(); + // if a create function is defined, we don't generate one + // otherwise we generate a default one that uses the update function and the default constructor + let create_fn = (!has_create_fn).then(||{ + quote!{ + fn create<'a>( + node_view: dioxus_native_core::prelude::NodeView<()>, + node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + children: Vec<::ElementBorrowed<'a>>, + context: &dioxus_native_core::prelude::SendAnyMap, + ) -> Self { + let mut myself = Self::default(); + myself.update(node_view, node, parent, children, context); + myself + } + } + }); + quote!( #(#attrs)* #defaultness #unsafety #impl_token #generics #trait_ #for_ #self_ty { + #create_fn + #(#items)* fn workload_system(type_id: std::any::TypeId, dependants: dioxus_native_core::exports::FxHashSet, pass_direction: dioxus_native_core::prelude::PassDirection) -> dioxus_native_core::exports::shipyard::WorkloadSystem { diff --git a/packages/native-core/examples/simple.rs b/packages/native-core/examples/simple.rs new file mode 100644 index 000000000..469147c35 --- /dev/null +++ b/packages/native-core/examples/simple.rs @@ -0,0 +1,222 @@ +use dioxus_native_core::exports::shipyard::Component; +use dioxus_native_core::node_ref::*; +use dioxus_native_core::prelude::*; +use dioxus_native_core::real_dom::NodeTypeMut; +use dioxus_native_core_macro::partial_derive_state; + +struct FontSize(f64); + +// All states need to derive Component +#[derive(Default, Debug, Copy, Clone, Component)] +struct Size(f64, f64); + +/// Derive some of the boilerplate for the State implementation +#[partial_derive_state] +impl State for Size { + type ParentDependencies = (); + + // The size of the current node depends on the size of its children + type ChildDependencies = (Self,); + + type NodeDependencies = (); + + // Size only cares about the width, height, and text parts of the current node + const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new() + // Get access to the width and height attributes + .with_attrs(AttributeMaskBuilder::Some(&["width", "height"])) + // Get access to the text of the node + .with_text(); + + fn update<'a>( + &mut self, + node_view: NodeView<()>, + _node: ::ElementBorrowed<'a>, + _parent: Option<::ElementBorrowed<'a>>, + children: Vec<::ElementBorrowed<'a>>, + context: &SendAnyMap, + ) -> bool { + let font_size = context.get::().unwrap().0; + let mut width; + let mut height; + if let Some(text) = node_view.text() { + // if the node has text, use the text to size our object + width = text.len() as f64 * font_size; + height = font_size; + } else { + // otherwise, the size is the maximum size of the children + width = children + .iter() + .map(|(item,)| item.0) + .reduce(|accum, item| if accum >= item { accum } else { item }) + .unwrap_or(0.0); + + height = children + .iter() + .map(|(item,)| item.1) + .reduce(|accum, item| if accum >= item { accum } else { item }) + .unwrap_or(0.0); + } + // if the node contains a width or height attribute it overrides the other size + for a in node_view.attributes().into_iter().flatten() { + match &*a.attribute.name { + "width" => width = a.value.as_float().unwrap(), + "height" => height = a.value.as_float().unwrap(), + // because Size only depends on the width and height, no other attributes will be passed to the member + _ => panic!(), + } + } + // to determine what other parts of the dom need to be updated we return a boolean that marks if this member changed + let changed = (width != self.0) || (height != self.1); + *self = Self(width, height); + changed + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Default, Component)] +struct TextColor { + r: u8, + g: u8, + b: u8, +} + +#[partial_derive_state] +impl State for TextColor { + // TextColor depends on the TextColor part of the parent + type ParentDependencies = (Self,); + + type ChildDependencies = (); + + type NodeDependencies = (); + + // TextColor only cares about the color attribute of the current node + const NODE_MASK: NodeMaskBuilder<'static> = + // Get access to the color attribute + NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["color"])); + + fn update<'a>( + &mut self, + node_view: NodeView<()>, + _node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + _children: Vec<::ElementBorrowed<'a>>, + _context: &SendAnyMap, + ) -> bool { + // TextColor only depends on the color tag, so getting the first tag is equivilent to looking through all tags + let new = match node_view + .attributes() + .and_then(|mut attrs| attrs.next()) + .and_then(|attr| attr.value.as_text()) + { + // if there is a color tag, translate it + Some("red") => TextColor { r: 255, g: 0, b: 0 }, + Some("green") => TextColor { r: 0, g: 255, b: 0 }, + Some("blue") => TextColor { r: 0, g: 0, b: 255 }, + Some(color) => panic!("unknown color {color}"), + // otherwise check if the node has a parent and inherit that color + None => match parent { + Some((parent,)) => *parent, + None => Self::default(), + }, + }; + // check if the member has changed + let changed = new != *self; + *self = new; + changed + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Default, Component)] +struct Border(bool); + +#[partial_derive_state] +impl State for Border { + // TextColor depends on the TextColor part of the parent + type ParentDependencies = (Self,); + + type ChildDependencies = (); + + type NodeDependencies = (); + + // Border does not depended on any other member in the current node + const NODE_MASK: NodeMaskBuilder<'static> = + // Get access to the border attribute + NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["border"])); + + fn update<'a>( + &mut self, + node_view: NodeView<()>, + _node: ::ElementBorrowed<'a>, + _parent: Option<::ElementBorrowed<'a>>, + _children: Vec<::ElementBorrowed<'a>>, + _context: &SendAnyMap, + ) -> bool { + // check if the node contians a border attribute + let new = Self( + node_view + .attributes() + .and_then(|mut attrs| attrs.next().map(|a| a.attribute.name == "border")) + .is_some(), + ); + // check if the member has changed + let changed = new != *self; + *self = new; + changed + } +} + +fn main() -> Result<(), Box> { + let mut rdom: RealDom = RealDom::new([ + Border::to_type_erased(), + TextColor::to_type_erased(), + Size::to_type_erased(), + ]); + + let mut count = 0; + + // intial render + let text_id = rdom.create_node(format!("Count: {count}")).id(); + let mut root = rdom.get_mut(rdom.root_id()).unwrap(); + // set the color to red + if let NodeTypeMut::Element(mut element) = root.node_type_mut() { + element.set_attribute(("color", "style"), "red".to_string()); + } + root.add_child(text_id); + + let mut ctx = SendAnyMap::new(); + // set the font size to 3.3 + ctx.insert(FontSize(3.3)); + // update the State for nodes in the real_dom tree + let _to_rerender = rdom.update_state(ctx); + + // we need to run the vdom in a async runtime + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build()? + .block_on(async { + loop { + // update the count + count += 1; + let mut text = rdom.get_mut(text_id).unwrap(); + if let NodeTypeMut::Text(mut text) = text.node_type_mut() { + *text = format!("Count: {count}"); + } + + let mut ctx = SendAnyMap::new(); + ctx.insert(FontSize(3.3)); + let _to_rerender = rdom.update_state(ctx); + + // render... + rdom.traverse_depth_first(|node| { + let indent = " ".repeat(node.height() as usize); + let color = *node.get::().unwrap(); + let size = *node.get::().unwrap(); + let border = *node.get::().unwrap(); + let id = node.id(); + println!("{indent}{id:?} {color:?} {size:?} {border:?}"); + }); + + // wait 1 second + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + } + }) +} diff --git a/packages/native-core/examples/simple_dioxus.rs b/packages/native-core/examples/simple_dioxus.rs new file mode 100644 index 000000000..b4c911cd4 --- /dev/null +++ b/packages/native-core/examples/simple_dioxus.rs @@ -0,0 +1,248 @@ +use dioxus::prelude::*; +use dioxus_native_core::exports::shipyard::Component; +use dioxus_native_core::node_ref::*; +use dioxus_native_core::prelude::*; +use dioxus_native_core_macro::partial_derive_state; + +struct FontSize(f64); + +// All states need to derive Component +#[derive(Default, Debug, Copy, Clone, Component)] +struct Size(f64, f64); + +/// Derive some of the boilerplate for the State implementation +#[partial_derive_state] +impl State for Size { + type ParentDependencies = (); + + // The size of the current node depends on the size of its children + type ChildDependencies = (Self,); + + type NodeDependencies = (); + + // Size only cares about the width, height, and text parts of the current node + const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new() + // Get access to the width and height attributes + .with_attrs(AttributeMaskBuilder::Some(&["width", "height"])) + // Get access to the text of the node + .with_text(); + + fn update<'a>( + &mut self, + node_view: NodeView<()>, + _node: ::ElementBorrowed<'a>, + _parent: Option<::ElementBorrowed<'a>>, + children: Vec<::ElementBorrowed<'a>>, + context: &SendAnyMap, + ) -> bool { + let font_size = context.get::().unwrap().0; + let mut width; + let mut height; + if let Some(text) = node_view.text() { + // if the node has text, use the text to size our object + width = text.len() as f64 * font_size; + height = font_size; + } else { + // otherwise, the size is the maximum size of the children + width = children + .iter() + .map(|(item,)| item.0) + .reduce(|accum, item| if accum >= item { accum } else { item }) + .unwrap_or(0.0); + + height = children + .iter() + .map(|(item,)| item.1) + .reduce(|accum, item| if accum >= item { accum } else { item }) + .unwrap_or(0.0); + } + // if the node contains a width or height attribute it overrides the other size + for a in node_view.attributes().into_iter().flatten() { + match &*a.attribute.name { + "width" => width = a.value.as_float().unwrap(), + "height" => height = a.value.as_float().unwrap(), + // because Size only depends on the width and height, no other attributes will be passed to the member + _ => panic!(), + } + } + // to determine what other parts of the dom need to be updated we return a boolean that marks if this member changed + let changed = (width != self.0) || (height != self.1); + *self = Self(width, height); + changed + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Default, Component)] +struct TextColor { + r: u8, + g: u8, + b: u8, +} + +#[partial_derive_state] +impl State for TextColor { + // TextColor depends on the TextColor part of the parent + type ParentDependencies = (Self,); + + type ChildDependencies = (); + + type NodeDependencies = (); + + // TextColor only cares about the color attribute of the current node + const NODE_MASK: NodeMaskBuilder<'static> = + // Get access to the color attribute + NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["color"])); + + fn update<'a>( + &mut self, + node_view: NodeView<()>, + _node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + _children: Vec<::ElementBorrowed<'a>>, + _context: &SendAnyMap, + ) -> bool { + // TextColor only depends on the color tag, so getting the first tag is equivilent to looking through all tags + let new = match node_view + .attributes() + .and_then(|mut attrs| attrs.next()) + .and_then(|attr| attr.value.as_text()) + { + // if there is a color tag, translate it + Some("red") => TextColor { r: 255, g: 0, b: 0 }, + Some("green") => TextColor { r: 0, g: 255, b: 0 }, + Some("blue") => TextColor { r: 0, g: 0, b: 255 }, + Some(color) => panic!("unknown color {color}"), + // otherwise check if the node has a parent and inherit that color + None => match parent { + Some((parent,)) => *parent, + None => Self::default(), + }, + }; + // check if the member has changed + let changed = new != *self; + *self = new; + changed + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Default, Component)] +struct Border(bool); + +#[partial_derive_state] +impl State for Border { + // TextColor depends on the TextColor part of the parent + type ParentDependencies = (Self,); + + type ChildDependencies = (); + + type NodeDependencies = (); + + // Border does not depended on any other member in the current node + const NODE_MASK: NodeMaskBuilder<'static> = + // Get access to the border attribute + NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["border"])); + + fn update<'a>( + &mut self, + node_view: NodeView<()>, + _node: ::ElementBorrowed<'a>, + _parent: Option<::ElementBorrowed<'a>>, + _children: Vec<::ElementBorrowed<'a>>, + _context: &SendAnyMap, + ) -> bool { + // check if the node contians a border attribute + let new = Self( + node_view + .attributes() + .and_then(|mut attrs| attrs.next().map(|a| a.attribute.name == "border")) + .is_some(), + ); + // check if the member has changed + let changed = new != *self; + *self = new; + changed + } +} + +fn main() -> Result<(), Box> { + fn app(cx: Scope) -> Element { + let count = use_state(cx, || 0); + + use_future(cx, (count,), |(count,)| async move { + loop { + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + count.set(*count + 1); + } + }); + + cx.render(rsx! { + div{ + color: "red", + "{count}", + Comp {} + } + }) + } + + fn Comp(cx: Scope) -> Element { + cx.render(rsx! { + div{ + border: "", + "hello world" + } + }) + } + + // create the vdom, the real_dom, and the binding layer between them + let mut vdom = VirtualDom::new(app); + let mut rdom: RealDom = RealDom::new([ + Border::to_type_erased(), + TextColor::to_type_erased(), + Size::to_type_erased(), + ]); + let mut dioxus_intigration_state = DioxusState::create(&mut rdom); + + let mutations = vdom.rebuild(); + // update the structure of the real_dom tree + dioxus_intigration_state.apply_mutations(&mut rdom, mutations); + let mut ctx = SendAnyMap::new(); + // set the font size to 3.3 + ctx.insert(FontSize(3.3)); + // update the State for nodes in the real_dom tree + let _to_rerender = rdom.update_state(ctx); + + // we need to run the vdom in a async runtime + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build()? + .block_on(async { + loop { + // wait for the vdom to update + vdom.wait_for_work().await; + + // get the mutations from the vdom + let mutations = vdom.render_immediate(); + + // update the structure of the real_dom tree + dioxus_intigration_state.apply_mutations(&mut rdom, mutations); + + // update the state of the real_dom tree + let mut ctx = SendAnyMap::new(); + // set the font size to 3.3 + ctx.insert(FontSize(3.3)); + let _to_rerender = rdom.update_state(ctx); + + // render... + rdom.traverse_depth_first(|node| { + let indent = " ".repeat(node.height() as usize); + let color = *node.get::().unwrap(); + let size = *node.get::().unwrap(); + let border = *node.get::().unwrap(); + let id = node.id(); + let node = node.node_type(); + let node_type = &*node; + println!("{indent}{id:?} {color:?} {size:?} {border:?} {node_type:?}"); + }); + } + }) +} diff --git a/packages/native-core/src/node.rs b/packages/native-core/src/node.rs index 48f9397a4..1cb39cff4 100644 --- a/packages/native-core/src/node.rs +++ b/packages/native-core/src/node.rs @@ -20,6 +20,18 @@ pub struct ElementNode { pub listeners: FxHashSet, } +impl ElementNode { + /// Create a new element node + pub fn new(tag: impl Into, namespace: impl Into>) -> Self { + Self { + tag: tag.into(), + namespace: namespace.into(), + attributes: Default::default(), + listeners: Default::default(), + } + } +} + /// A text node in the RealDom #[derive(Debug, Clone, Default)] pub struct TextNode { @@ -50,6 +62,24 @@ pub enum NodeType { Placeholder, } +impl> From for NodeType { + fn from(text: S) -> Self { + Self::Text(TextNode::new(text.into())) + } +} + +impl From for NodeType { + fn from(text: TextNode) -> Self { + Self::Text(text) + } +} + +impl From> for NodeType { + fn from(element: ElementNode) -> Self { + Self::Element(element) + } +} + /// A discription of an attribute on a DOM node, such as `id` or `href`. #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct OwnedAttributeDiscription { @@ -158,14 +188,14 @@ impl Debug for OwnedAttributeValue { } } -impl Display for OwnedAttributeValue { +impl Display for OwnedAttributeValue { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Text(arg0) => f.write_str(arg0), Self::Float(arg0) => f.write_str(&arg0.to_string()), Self::Int(arg0) => f.write_str(&arg0.to_string()), Self::Bool(arg0) => f.write_str(&arg0.to_string()), - Self::Custom(arg0) => f.write_str(&arg0.to_string()), + Self::Custom(_) => f.write_str("custom"), } } } diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index 849be75e5..d5c07f352 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -105,13 +105,13 @@ impl DirtyNodeStates { /// A state that is automatically inserted in a node with dependencies. pub trait State: Any + Send + Sync { - /// This is a tuple of (T: State, ..) of states read from the parent required to run this pass + /// This is a tuple of (T: State, ..) of states read from the parent required to update this state type ParentDependencies: Dependancy; - /// This is a tuple of (T: State, ..) of states read from the children required to run this pass + /// This is a tuple of (T: State, ..) of states read from the children required to update this state type ChildDependencies: Dependancy; - /// This is a tuple of (T: State, ..) of states read from the node required to run this pass + /// This is a tuple of (T: State, ..) of states read from the node required to update this state type NodeDependencies: Dependancy; - /// This is a mask of what aspects of the node are required to run this pass + /// This is a mask of what aspects of the node are required to update this state const NODE_MASK: NodeMaskBuilder<'static>; /// Update this state in a node, returns if the state was updated diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 1ac34a421..1deec7d64 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -113,7 +113,8 @@ pub struct RealDom { impl RealDom { /// Create a new RealDom with the given states that will be inserted and updated when needed - pub fn new(mut tracked_states: Box<[TypeErasedState]>) -> RealDom { + pub fn new(tracked_states: impl Into]>>) -> RealDom { + let mut tracked_states = tracked_states.into(); // resolve dependants for each pass for i in 1..tracked_states.len() { let (before, after) = tracked_states.split_at_mut(i); @@ -177,8 +178,8 @@ impl RealDom { } /// Create a new node of the given type in the dom and return a mutable reference to it. - pub fn create_node(&mut self, node: NodeType) -> NodeMut<'_, V> { - let id = self.world.add_entity(node); + pub fn create_node(&mut self, node: impl Into>) -> NodeMut<'_, V> { + let id = self.world.add_entity(node.into()); self.tree_mut().create_node(id); self.dirty_nodes .passes_updated From 41bf87e74f45e672cd4bb0364b27a4aca9561232 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 14 Mar 2023 16:21:39 -0500 Subject: [PATCH 072/103] Merge branch 'upstream' into simplify-native-core --- .github/workflows/docs stable.yml | 4 +- .github/workflows/docs.yml | 4 +- README.md | 2 +- docs/guide/examples/hooks_composed.rs | 2 +- docs/guide/src/en/getting_started/desktop.md | 2 +- .../src/en/interactivity/custom_hooks.md | 2 +- docs/posts/release.md | 4 +- docs/reference/src/README_pt-br.md | 2 +- docs/reference/src/platforms/desktop.md | 4 +- docs/router/src/README.md | 11 +- examples/shortcut.rs | 17 + notes/README/ZH_CN.md | 2 +- packages/autofmt/Cargo.toml | 2 +- packages/core-macro/Cargo.toml | 2 +- packages/core-macro/README.md | 2 +- packages/core-macro/src/inlineprops.rs | 21 +- packages/core/Cargo.toml | 2 +- packages/core/src/arena.rs | 6 +- packages/core/src/create.rs | 23 +- packages/core/src/diff.rs | 4 +- packages/core/src/scheduler/task.rs | 6 +- packages/core/src/scopes.rs | 7 +- packages/core/src/virtual_dom.rs | 2 +- packages/desktop/README.md | 4 +- packages/desktop/src/desktop_context.rs | 36 ++ packages/desktop/src/lib.rs | 9 + packages/desktop/src/readme.md | 4 +- packages/desktop/src/shortcut.rs | 332 ++++++++++++++++++ packages/dioxus/Cargo.toml | 4 +- packages/dioxus/README.md | 5 +- packages/fermi/src/root.rs | 4 +- packages/hooks/Cargo.toml | 1 + packages/hooks/README.md | 2 +- packages/hooks/src/use_shared_state.rs | 227 +++++++----- packages/hooks/src/usecoroutine.rs | 6 + packages/hooks/src/usestate.rs | 2 +- packages/hot-reload/Cargo.toml | 12 +- packages/hot-reload/README.md | 2 +- packages/hot-reload/src/lib.rs | 60 +++- packages/html/Cargo.toml | 4 +- packages/html/README.md | 2 +- packages/interpreter/Cargo.toml | 2 +- packages/interpreter/README.md | 2 +- packages/interpreter/src/interpreter.js | 14 +- packages/liveview/README.md | 2 +- packages/liveview/examples/axum.rs | 2 +- packages/liveview/examples/salvo.rs | 2 +- packages/liveview/src/pool.rs | 22 +- packages/mobile/README.md | 4 +- packages/router/README.md | 4 +- packages/rsx-rosetta/Cargo.toml | 2 +- packages/rsx-rosetta/README.md | 2 +- packages/rsx/Cargo.toml | 5 +- packages/rsx/README.md | 2 +- .../src/hot_reload/hot_reloading_file_map.rs | 104 +++++- packages/ssr/Cargo.toml | 1 + packages/ssr/README.md | 7 +- packages/ssr/src/cache.rs | 8 +- packages/ssr/src/renderer.rs | 17 +- packages/ssr/tests/simple.rs | 2 +- packages/web/Cargo.toml | 2 +- packages/web/README.md | 2 +- translations/pt-br/README.md | 6 +- 63 files changed, 818 insertions(+), 245 deletions(-) create mode 100644 examples/shortcut.rs create mode 100644 packages/desktop/src/shortcut.rs diff --git a/.github/workflows/docs stable.yml b/.github/workflows/docs stable.yml index 895e7e8d0..2790b1d51 100644 --- a/.github/workflows/docs stable.yml +++ b/.github/workflows/docs stable.yml @@ -24,7 +24,9 @@ jobs: - name: Build run: cd docs && cd guide && mdbook build -d ../nightly/guide && cd .. && - cd router && mdbook build -d ../nightly/router && cd .. + cd router && mdbook build -d ../nightly/router && cd .. + # cd reference && mdbook build -d ../nightly/reference && cd .. && + # cd fermi && mdbook build -d ../nightly/fermi && cd .. - name: Deploy 🚀 uses: JamesIves/github-pages-deploy-action@v4.4.1 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index f69053742..07a8b462b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -29,7 +29,9 @@ jobs: - name: Build run: cd docs && cd guide && mdbook build -d ../nightly/guide && cd .. && - cd router && mdbook build -d ../nightly/router && cd .. + cd router && mdbook build -d ../nightly/router && cd .. + # cd reference && mdbook build -d ../nightly/reference && cd .. && + # cd fermi && mdbook build -d ../nightly/fermi && cd .. - name: Deploy 🚀 uses: JamesIves/github-pages-deploy-action@v4.4.1 diff --git a/README.md b/README.md index 4ef6acfee..40981401e 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ | Examples | - Guide + Guide | 中文 | diff --git a/docs/guide/examples/hooks_composed.rs b/docs/guide/examples/hooks_composed.rs index 5b1f5ebfe..801474aa3 100644 --- a/docs/guide/examples/hooks_composed.rs +++ b/docs/guide/examples/hooks_composed.rs @@ -7,7 +7,7 @@ fn main() {} struct AppSettings {} // ANCHOR: wrap_context -fn use_settings(cx: &ScopeState) -> UseSharedState { +fn use_settings(cx: &ScopeState) -> &UseSharedState { use_shared_state::(cx).expect("App settings not provided") } // ANCHOR_END: wrap_context diff --git a/docs/guide/src/en/getting_started/desktop.md b/docs/guide/src/en/getting_started/desktop.md index c86d7760e..311ad2c9f 100644 --- a/docs/guide/src/en/getting_started/desktop.md +++ b/docs/guide/src/en/getting_started/desktop.md @@ -14,7 +14,7 @@ Examples: The desktop is a powerful target for Dioxus but is currently limited in capability when compared to the Web platform. Currently, desktop apps are rendered with the platform's WebView library, but your Rust code is running natively on a native thread. This means that browser APIs are *not* available, so rendering WebGL, Canvas, etc is not as easy as the Web. However, native system APIs *are* accessible, so streaming, WebSockets, filesystem, etc are all viable APIs. In the future, we plan to move to a custom web renderer-based DOM renderer with WGPU integrations. -Dioxus Desktop is built off [Tauri](https://tauri.app/). Right now there aren't any Dioxus abstractions over keyboard shortcuts, menubar, handling, etc, so you'll want to leverage Tauri – mostly [Wry](http://github.com/tauri-apps/wry/) and [Tao](http://github.com/tauri-apps/tao)) directly. +Dioxus Desktop is built off [Tauri](https://tauri.app/). Right now there aren't any Dioxus abstractions over the menubar, handling, etc, so you'll want to leverage Tauri – mostly [Wry](http://github.com/tauri-apps/wry/) and [Tao](http://github.com/tauri-apps/tao)) directly. # Getting started diff --git a/docs/guide/src/en/interactivity/custom_hooks.md b/docs/guide/src/en/interactivity/custom_hooks.md index 2f0155cb5..ebf4f94db 100644 --- a/docs/guide/src/en/interactivity/custom_hooks.md +++ b/docs/guide/src/en/interactivity/custom_hooks.md @@ -14,7 +14,7 @@ For example, if many components need to access an `AppSettings` struct, you can ## Custom Hook Logic -You can use [`cx.use_hook`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.Scope.html#method.use_hook) to build your own hooks. In fact, this is what all the standard hooks are built on! +You can use [`cx.use_hook`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.ScopeState.html#method.use_hook) to build your own hooks. In fact, this is what all the standard hooks are built on! `use_hook` accepts a single closure for initializing the hook. It will be only run the first time the component is rendered. The return value of that closure will be used as the value of the hook – Dioxus will take it, and store it for as long as the component is alive. On every render (not just the first one!), you will get a reference to this value. diff --git a/docs/posts/release.md b/docs/posts/release.md index b40b451cc..171664dd3 100644 --- a/docs/posts/release.md +++ b/docs/posts/release.md @@ -63,7 +63,7 @@ This very site is built with Dioxus, and the source code is available [here](htt To get started with Dioxus, check out any of the "Getting Started" guides for your platform of choice, or check out the GitHub Repository for more details. -- [Getting Started with Dioxus](https://dioxuslabs.com/guide) +- [Getting Started with Dioxus](https://dioxuslabs.com/guide/en) - [Getting Started with Web](https://dioxuslabs.com/reference/web) - [Getting Started with Desktop](https://dioxuslabs.com/reference/desktop) - [Getting Started with Mobile](https://dioxuslabs.com/reference/mobile) @@ -163,7 +163,7 @@ Today, to publish a Dioxus app, you don't need NPM/WebPack/Parcel/etc. Dioxus si ## Show me more -Here, we'll dive into some features of Dioxus and why it's so fun to use. The [guide](https://dioxuslabs.com/guide/) serves as a deeper and more comprehensive look at what Dioxus can do. +Here, we'll dive into some features of Dioxus and why it's so fun to use. The [guide](https://dioxuslabs.com/guide/en/) serves as a deeper and more comprehensive look at what Dioxus can do. ## Building a new project is simple diff --git a/docs/reference/src/README_pt-br.md b/docs/reference/src/README_pt-br.md index c345da6bd..e76f43516 100644 --- a/docs/reference/src/README_pt-br.md +++ b/docs/reference/src/README_pt-br.md @@ -4,7 +4,7 @@ **Dioxus** é um framework e ecossistema para desenvolver interfaces rápidas, escaláveis e robustas com a linguagem de Programação Rust. Este guia irá ajudar você a começar com o Dioxus para Web, Desktop, Móvel e mais. -> Este livro é a Referência e Guias Avançados para o framework Dioxus. Para um tutorial em como de fato _usar_ o Dioxus, procure o [guia oficial](https://dioxuslabs.com/guide/). +> Este livro é a Referência e Guias Avançados para o framework Dioxus. Para um tutorial em como de fato _usar_ o Dioxus, procure o [guia oficial](https://dioxuslabs.com/guide/en/). ## Guias e Referência diff --git a/docs/reference/src/platforms/desktop.md b/docs/reference/src/platforms/desktop.md index d444b90e4..dcdcca778 100644 --- a/docs/reference/src/platforms/desktop.md +++ b/docs/reference/src/platforms/desktop.md @@ -2,7 +2,7 @@ One of Dioxus' killer features is the ability to quickly build a native desktop app that looks and feels the same across platforms. Apps built with Dioxus are typically <5mb in size and use existing system resources, so they won't hog extreme amounts of RAM or memory. -Dioxus Desktop is built off Tauri. Right now there aren't any Dioxus abstractions over keyboard shortcuts, menubar, handling, etc, so you'll want to leverage Tauri - mostly [Wry](http://github.com/tauri-apps/wry/) and [Tao](http://github.com/tauri-apps/tao)) directly. The next major release of Dioxus-Desktop will include components and hooks for notifications, global shortcuts, menubar, etc. +Dioxus Desktop is built off Tauri. Right now there aren't any Dioxus abstractions over the menubar, handling, etc, so you'll want to leverage Tauri - mostly [Wry](http://github.com/tauri-apps/wry/) and [Tao](http://github.com/tauri-apps/tao)) directly. The next major release of Dioxus-Desktop will include components and hooks for notifications, global shortcuts, menubar, etc. ## Getting Set up @@ -42,4 +42,4 @@ To configure the webview, menubar, and other important desktop-specific features ## Future Steps -Make sure to read the [Dioxus Guide](https://dioxuslabs.com/guide) if you already haven't! +Make sure to read the [Dioxus Guide](https://dioxuslabs.com/guide/en) if you already haven't! diff --git a/docs/router/src/README.md b/docs/router/src/README.md index fcbd79aef..055c3009b 100644 --- a/docs/router/src/README.md +++ b/docs/router/src/README.md @@ -1,10 +1,11 @@ # Dioxus Router: Introduction + Whether or not you're building a website, desktop app, or mobile app, organizing your app's views into "pages" can be an effective method for organization and maintainability. -Dioxus comes with a router built-in. To start utilizing Dioxus Router, enable the ``router`` feature in your ``Cargo.toml`` file. -```toml -[dependencies] -dioxus = { version = "x.x.x", features = [.., "router"] } -``` +The `dioxus-router` crate contains the Router module. To add it to your project run: + + cargo add dioxus-router + +> **Be sure to include the `web` feature (`--feature web`) for deployment into a browser!** In this book you'll find a short [guide](./guide/index.md) to get up to speed with Dioxus Router, as well as the router's [reference](./reference/index.md). \ No newline at end of file diff --git a/examples/shortcut.rs b/examples/shortcut.rs new file mode 100644 index 000000000..d98a69ad6 --- /dev/null +++ b/examples/shortcut.rs @@ -0,0 +1,17 @@ +use dioxus::prelude::*; +use dioxus_desktop::tao::keyboard::ModifiersState; +use dioxus_desktop::use_global_shortcut; + +fn main() { + dioxus_desktop::launch(app); +} + +fn app(cx: Scope) -> Element { + let toggled = use_state(cx, || false); + use_global_shortcut(cx, KeyCode::S, ModifiersState::CONTROL, { + to_owned![toggled]; + move || toggled.modify(|t| !*t) + }); + + cx.render(rsx!("toggle: {toggled.get()}")) +} diff --git a/notes/README/ZH_CN.md b/notes/README/ZH_CN.md index 8b1e6185b..ca37f7be1 100644 --- a/notes/README/ZH_CN.md +++ b/notes/README/ZH_CN.md @@ -40,7 +40,7 @@ | 代码示例 | - 开发指南 + 开发指南 | English | diff --git a/packages/autofmt/Cargo.toml b/packages/autofmt/Cargo.toml index a1a4592d8..e409cfb2f 100644 --- a/packages/autofmt/Cargo.toml +++ b/packages/autofmt/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-rsx = { path = "../rsx", version = "0.0.2" } +dioxus-rsx = { path = "../rsx", version = "^0.0.3" } proc-macro2 = { version = "1.0.6", features = ["span-locations"] } quote = "1.0" syn = { version = "1.0.11", features = ["full", "extra-traits"] } diff --git a/packages/core-macro/Cargo.toml b/packages/core-macro/Cargo.toml index 5338a7acc..843470646 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", version = "0.0.2" } +dioxus-rsx = { path = "../rsx", version = "^0.0.3" } # testing [dev-dependencies] diff --git a/packages/core-macro/README.md b/packages/core-macro/README.md index 60654396e..bcf1572cf 100644 --- a/packages/core-macro/README.md +++ b/packages/core-macro/README.md @@ -19,7 +19,7 @@ [discord-url]: https://discord.gg/XgGxMSkvUM [Website](https://dioxuslabs.com) | -[Guides](https://dioxuslabs.com/guide/) | +[Guides](https://dioxuslabs.com/docs/0.3/guide/en/) | [API Docs](https://docs.rs/dioxus-core-macro/latest/dioxus_core_macro) | [Chat](https://discord.gg/XgGxMSkvUM) diff --git a/packages/core-macro/src/inlineprops.rs b/packages/core-macro/src/inlineprops.rs index 8eaadff32..814724b56 100644 --- a/packages/core-macro/src/inlineprops.rs +++ b/packages/core-macro/src/inlineprops.rs @@ -145,6 +145,25 @@ impl ToTokens for InlinePropsBody { (quote! { #lifetime, }, fn_generics, quote! { #generics }) }; + let generics_no_bounds = { + let mut generics = generics.clone(); + generics.params = generics + .params + .iter() + .map(|it| match it { + GenericParam::Type(tp) => { + let mut tp = tp.clone(); + tp.bounds.clear(); + + GenericParam::Type(tp) + } + _ => it.clone(), + }) + .collect(); + + generics + }; + out_tokens.append_all(quote! { #modifiers #[allow(non_camel_case_types)] @@ -155,7 +174,7 @@ impl ToTokens for InlinePropsBody { } #(#attrs)* - #maybe_async #vis fn #ident #fn_generics (#cx_token: Scope<#scope_lifetime #struct_name #generics>) #output + #maybe_async #vis fn #ident #fn_generics (#cx_token: Scope<#scope_lifetime #struct_name #generics_no_bounds>) #output #where_clause { let #struct_name { #(#field_names),* } = &#cx_token.props; diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 9f855e18e..b86466692 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-core" -version = "0.3.1" +version = "0.3.2" authors = ["Jonathan Kelley"] edition = "2018" description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences" diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index 7fbaf0e12..caa863adf 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -175,10 +175,10 @@ impl VirtualDom { } impl ElementPath { - pub(crate) fn is_ascendant(&self, big: &&[u8]) -> bool { + pub(crate) fn is_decendant(&self, small: &&[u8]) -> bool { match *self { - ElementPath::Deep(small) => small.len() <= big.len() && small == &big[..small.len()], - ElementPath::Root(r) => big.len() == 1 && big[0] == r as u8, + ElementPath::Deep(big) => small.len() <= big.len() && *small == &big[..small.len()], + ElementPath::Root(r) => small.len() == 1 && small[0] == r as u8, } } } diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index 12c11d1e9..113fa7ae1 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -25,8 +25,8 @@ fn sort_bfs(paths: &[&'static [u8]]) -> Vec<(usize, &'static [u8])> { } } // The shorter path goes first - (Some(_), None) => return std::cmp::Ordering::Less, - (None, Some(_)) => return std::cmp::Ordering::Greater, + (None, Some(_)) => return std::cmp::Ordering::Less, + (Some(_), None) => return std::cmp::Ordering::Greater, (None, None) => return std::cmp::Ordering::Equal, } } @@ -40,14 +40,25 @@ fn sorting() { (0, &[0, 1]), (1, &[0, 2]), (2, &[1, 0]), - (4, &[1, 1]), - (3, &[1, 2]), + (3, &[1, 0, 1]), + (4, &[1, 2]), ]; assert_eq!( - sort_bfs(&[&[0, 1,], &[0, 2,], &[1, 0,], &[1, 2,], &[1, 1,],]), + sort_bfs(&[&[0, 1,], &[0, 2,], &[1, 0,], &[1, 0, 1,], &[1, 2,],]), + r + ); + let r: [(usize, &[u8]); 6] = [ + (0, &[0]), + (1, &[0, 1]), + (2, &[0, 1, 2]), + (3, &[1]), + (4, &[1, 2]), + (5, &[2]), + ]; + assert_eq!( + sort_bfs(&[&[0], &[0, 1], &[0, 1, 2], &[1], &[1, 2], &[2],]), r ); - assert!(matches!(&[0], &[_, ..])) } impl<'b> VirtualDom { diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 37e9a0428..fdc1a03f1 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -717,7 +717,7 @@ impl<'b> VirtualDom { Fragment(nodes) => nodes .iter() .map(|node| self.push_all_real_nodes(node)) - .count(), + .sum(), Component(comp) => { let scope = comp.scope.get().unwrap(); @@ -729,7 +729,7 @@ impl<'b> VirtualDom { } } }) - .count() + .sum() } fn create_children(&mut self, nodes: impl IntoIterator>) -> usize { diff --git a/packages/core/src/scheduler/task.rs b/packages/core/src/scheduler/task.rs index 997953f8e..fc668b364 100644 --- a/packages/core/src/scheduler/task.rs +++ b/packages/core/src/scheduler/task.rs @@ -72,9 +72,9 @@ pub struct LocalTaskHandle { impl ArcWake for LocalTaskHandle { fn wake_by_ref(arc_self: &Arc) { - arc_self + // This can fail if the scheduler has been dropped while the application is shutting down + let _ = arc_self .tx - .unbounded_send(SchedulerMsg::TaskNotified(arc_self.id)) - .unwrap(); + .unbounded_send(SchedulerMsg::TaskNotified(arc_self.id)); } } diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index fceaefec6..5601d57fe 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -373,17 +373,12 @@ impl<'src> ScopeState { None } - /// Expose state to children further down the [`crate::VirtualDom`] Tree. Does not require `clone` on the context, - /// though we do recommend it. + /// Expose state to children further down the [`crate::VirtualDom`] Tree. Requires `Clone` on the context to allow getting values down the tree. /// /// This is a "fundamental" operation and should only be called during initialization of a hook. /// /// For a hook that provides the same functionality, use `use_provide_context` and `use_context` instead. /// - /// If a state is provided that already exists, the new value will not be inserted. Instead, this method will - /// return the existing value. This behavior is chosen so shared values do not need to be `Clone`. This particular - /// behavior might change in the future. - /// /// # Example /// /// ```rust, ignore diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index a29671f5d..2269815aa 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -396,7 +396,7 @@ impl VirtualDom { // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one if attr.name.trim_start_matches("on") == name - && target_path.is_ascendant(&this_path) + && target_path.is_decendant(&this_path) { listeners.push(&attr.value); diff --git a/packages/desktop/README.md b/packages/desktop/README.md index 9b1e7ad58..964f90495 100644 --- a/packages/desktop/README.md +++ b/packages/desktop/README.md @@ -19,7 +19,7 @@ [discord-url]: https://discord.gg/XgGxMSkvUM [Website](https://dioxuslabs.com) | -[Guides](https://dioxuslabs.com/guide/) | +[Guides](https://dioxuslabs.com/docs/0.3/guide/en/) | [API Docs](https://docs.rs/dioxus-desktop/latest/dioxus_desktop) | [Chat](https://discord.gg/XgGxMSkvUM) @@ -30,7 +30,7 @@ This requires that webview is installed on the target system. WebView is installed by default on macOS and iOS devices, but might not come preinstalled on Windows or Linux devices. To fix these issues, follow the [instructions in the guide](guide-url). -[guide-url]: https://dioxuslabs.com/guide/setup.html#platform-specific-dependencies +[guide-url]: https://dioxuslabs.com/docs/0.3/guide/en/getting_started/desktop.html diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index edddd7887..2ed4bbee9 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -5,6 +5,11 @@ use std::rc::Weak; use crate::create_new_window; use crate::eval::EvalResult; use crate::events::IpcMessage; +use crate::shortcut::IntoKeyCode; +use crate::shortcut::IntoModifersState; +use crate::shortcut::ShortcutId; +use crate::shortcut::ShortcutRegistry; +use crate::shortcut::ShortcutRegistryError; use crate::Config; use crate::WebviewHandler; use dioxus_core::ScopeState; @@ -63,6 +68,8 @@ pub struct DesktopContext { pub(crate) event_handlers: WindowEventHandlers, + pub(crate) shortcut_manager: ShortcutRegistry, + #[cfg(target_os = "ios")] pub(crate) views: Rc>>, } @@ -83,6 +90,7 @@ impl DesktopContext { event_loop: EventLoopWindowTarget, webviews: WebviewQueue, event_handlers: WindowEventHandlers, + shortcut_manager: ShortcutRegistry, ) -> Self { Self { webview, @@ -91,6 +99,7 @@ impl DesktopContext { eval: tokio::sync::broadcast::channel(8).0, pending_windows: webviews, event_handlers, + shortcut_manager, #[cfg(target_os = "ios")] views: Default::default(), } @@ -111,6 +120,7 @@ impl DesktopContext { dom, &self.pending_windows, &self.event_handlers, + self.shortcut_manager.clone(), ); let id = window.webview.window().id(); @@ -240,6 +250,32 @@ impl DesktopContext { self.event_handlers.remove(id) } + /// Create a global shortcut + /// + /// Linux: Only works on x11. See [this issue](https://github.com/tauri-apps/tao/issues/331) for more information. + pub fn create_shortcut( + &self, + key: impl IntoKeyCode, + modifiers: impl IntoModifersState, + callback: impl FnMut() + 'static, + ) -> Result { + self.shortcut_manager.add_shortcut( + modifiers.into_modifiers_state(), + key.into_key_code(), + Box::new(callback), + ) + } + + /// Remove a global shortcut + pub fn remove_shortcut(&self, id: ShortcutId) { + self.shortcut_manager.remove_shortcut(id) + } + + /// Remove all global shortcuts + pub fn remove_all_shortcuts(&self) { + self.shortcut_manager.remove_all() + } + /// Push an objc view to the window #[cfg(target_os = "ios")] pub fn push_view(&self, view: objc_id::ShareId) { diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 16f562464..75c67dd22 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -9,6 +9,7 @@ mod escape; mod eval; mod events; mod protocol; +mod shortcut; mod waker; mod webview; @@ -21,6 +22,8 @@ use dioxus_core::*; use dioxus_html::HtmlEvent; pub use eval::{use_eval, EvalResult}; use futures_util::{pin_mut, FutureExt}; +use shortcut::ShortcutRegistry; +pub use shortcut::{use_global_shortcut, ShortcutHandle, ShortcutId, ShortcutRegistryError}; use std::collections::HashMap; use std::rc::Rc; use std::task::Waker; @@ -139,6 +142,8 @@ pub fn launch_with_props(root: Component

, props: P, cfg: Config) let queue = WebviewQueue::default(); + let shortcut_manager = ShortcutRegistry::new(&event_loop); + // By default, we'll create a new window when the app starts queue.borrow_mut().push(create_new_window( cfg, @@ -147,6 +152,7 @@ pub fn launch_with_props(root: Component

, props: P, cfg: Config) VirtualDom::new_with_props(root, props), &queue, &event_handlers, + shortcut_manager.clone(), )); event_loop.run(move |window_event, event_loop, control_flow| { @@ -260,6 +266,7 @@ pub fn launch_with_props(root: Component

, props: P, cfg: Config) _ => {} }, + Event::GlobalShortcutEvent(id) => shortcut_manager.call_handlers(id), _ => {} } }) @@ -272,6 +279,7 @@ fn create_new_window( dom: VirtualDom, queue: &WebviewQueue, event_handlers: &WindowEventHandlers, + shortcut_manager: ShortcutRegistry, ) -> WebviewHandler { let webview = webview::build(&mut cfg, event_loop, proxy.clone()); @@ -281,6 +289,7 @@ fn create_new_window( event_loop.clone(), queue.clone(), event_handlers.clone(), + shortcut_manager, )); let id = webview.window().id(); diff --git a/packages/desktop/src/readme.md b/packages/desktop/src/readme.md index 567b05fcf..cd4dbe41a 100644 --- a/packages/desktop/src/readme.md +++ b/packages/desktop/src/readme.md @@ -6,7 +6,7 @@ Render the Dioxus VirtualDom using the platform's native WebView implementation. One of Dioxus' flagship features is the ability to quickly build a native desktop app that looks and feels the same across platforms. Apps built with Dioxus are typically <5mb in size and use existing system resources, so they won't hog extreme amounts of RAM or memory. -Dioxus Desktop is built off Tauri. Right now there aren't any Dioxus abstractions over keyboard shortcuts, menubar, handling, etc, so you'll want to leverage Tauri - mostly [Wry](http://github.com/tauri-apps/wry/) and [Tao](http://github.com/tauri-apps/tao) directly. An upcoming release of Dioxus-Desktop will include components and hooks for notifications, global shortcuts, menubar, etc. +Dioxus Desktop is built off Tauri. Right now there aren't any Dioxus abstractions over the menubar, handling, etc, so you'll want to leverage Tauri - mostly [Wry](http://github.com/tauri-apps/wry/) and [Tao](http://github.com/tauri-apps/tao) directly. An upcoming release of Dioxus-Desktop will include components and hooks for notifications, global shortcuts, menubar, etc. ## Getting Set up @@ -49,4 +49,4 @@ To configure the webview, menubar, and other important desktop-specific features ## Future Steps -Make sure to read the [Dioxus Guide](https://dioxuslabs.com/guide) if you already haven't! +Make sure to read the [Dioxus Guide](https://dioxuslabs.com/docs/0.3/guide/en) if you already haven't! diff --git a/packages/desktop/src/shortcut.rs b/packages/desktop/src/shortcut.rs new file mode 100644 index 000000000..5232bdb8b --- /dev/null +++ b/packages/desktop/src/shortcut.rs @@ -0,0 +1,332 @@ +use std::{cell::RefCell, collections::HashMap, rc::Rc}; + +use dioxus_core::ScopeState; +use dioxus_html::input_data::keyboard_types::Modifiers; +use slab::Slab; +use wry::application::{ + accelerator::{Accelerator, AcceleratorId}, + event_loop::EventLoopWindowTarget, + global_shortcut::{GlobalShortcut, ShortcutManager, ShortcutManagerError}, + keyboard::{KeyCode, ModifiersState}, +}; + +use crate::{use_window, DesktopContext}; + +#[derive(Clone)] +pub(crate) struct ShortcutRegistry { + manager: Rc>, + shortcuts: ShortcutMap, +} + +type ShortcutMap = Rc>>; + +struct Shortcut { + shortcut: GlobalShortcut, + callbacks: Slab>, +} + +impl Shortcut { + fn insert(&mut self, callback: Box) -> usize { + self.callbacks.insert(callback) + } + + fn remove(&mut self, id: usize) { + let _ = self.callbacks.remove(id); + } + + fn is_empty(&self) -> bool { + self.callbacks.is_empty() + } +} + +impl ShortcutRegistry { + pub fn new(target: &EventLoopWindowTarget) -> Self { + let myself = Self { + manager: Rc::new(RefCell::new(ShortcutManager::new(target))), + shortcuts: Rc::new(RefCell::new(HashMap::new())), + }; + // prevent CTRL+R from reloading the page which breaks apps + let _ = myself.add_shortcut( + Some(ModifiersState::CONTROL), + KeyCode::KeyR, + Box::new(|| {}), + ); + myself + } + + pub(crate) fn call_handlers(&self, id: AcceleratorId) { + if let Some(Shortcut { callbacks, .. }) = self.shortcuts.borrow_mut().get_mut(&id) { + for (_, callback) in callbacks.iter_mut() { + (callback)(); + } + } + } + + pub(crate) fn add_shortcut( + &self, + modifiers: impl Into>, + key: KeyCode, + callback: Box, + ) -> Result { + let accelerator = Accelerator::new(modifiers, key); + let accelerator_id = accelerator.clone().id(); + let mut shortcuts = self.shortcuts.borrow_mut(); + Ok( + if let Some(callbacks) = shortcuts.get_mut(&accelerator_id) { + let id = callbacks.insert(callback); + ShortcutId { + id: accelerator_id, + number: id, + } + } else { + match self.manager.borrow_mut().register(accelerator) { + Ok(global_shortcut) => { + let mut slab = Slab::new(); + let id = slab.insert(callback); + let shortcut = Shortcut { + shortcut: global_shortcut, + callbacks: slab, + }; + shortcuts.insert(accelerator_id, shortcut); + ShortcutId { + id: accelerator_id, + number: id, + } + } + Err(ShortcutManagerError::InvalidAccelerator(shortcut)) => { + return Err(ShortcutRegistryError::InvalidShortcut(shortcut)) + } + Err(err) => return Err(ShortcutRegistryError::Other(Box::new(err))), + } + }, + ) + } + + pub(crate) fn remove_shortcut(&self, id: ShortcutId) { + let mut shortcuts = self.shortcuts.borrow_mut(); + if let Some(callbacks) = shortcuts.get_mut(&id.id) { + callbacks.remove(id.number); + if callbacks.is_empty() { + if let Some(shortcut) = shortcuts.remove(&id.id) { + let _ = self.manager.borrow_mut().unregister(shortcut.shortcut); + } + } + } + } + + pub(crate) fn remove_all(&self) { + let mut shortcuts = self.shortcuts.borrow_mut(); + shortcuts.clear(); + let _ = self.manager.borrow_mut().unregister_all(); + // prevent CTRL+R from reloading the page which breaks apps + let _ = self.add_shortcut( + Some(ModifiersState::CONTROL), + KeyCode::KeyR, + Box::new(|| {}), + ); + } +} + +#[non_exhaustive] +#[derive(Debug)] +/// An error that can occur when registering a shortcut. +pub enum ShortcutRegistryError { + /// The shortcut is invalid. + InvalidShortcut(String), + /// An unknown error occurred. + Other(Box), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +/// An global id for a shortcut. +pub struct ShortcutId { + id: AcceleratorId, + number: usize, +} + +/// A global shortcut. This will be automatically removed when it is dropped. +pub struct ShortcutHandle { + desktop: DesktopContext, + /// The id of the shortcut + pub shortcut_id: ShortcutId, +} + +/// Get a closure that executes any JavaScript in the WebView context. +pub fn use_global_shortcut( + cx: &ScopeState, + key: impl IntoKeyCode, + modifiers: impl IntoModifersState, + handler: impl FnMut() + 'static, +) -> &Result { + let desktop = use_window(cx); + cx.use_hook(move || { + let desktop = desktop.clone(); + + let id = desktop.create_shortcut( + key.into_key_code(), + modifiers.into_modifiers_state(), + handler, + ); + + Ok(ShortcutHandle { + desktop, + shortcut_id: id?, + }) + }) +} + +impl ShortcutHandle { + /// Remove the shortcut. + pub fn remove(&self) { + self.desktop.remove_shortcut(self.shortcut_id); + } +} + +impl Drop for ShortcutHandle { + fn drop(&mut self) { + self.remove() + } +} + +pub trait IntoModifersState { + fn into_modifiers_state(self) -> ModifiersState; +} + +impl IntoModifersState for ModifiersState { + fn into_modifiers_state(self) -> ModifiersState { + self + } +} + +impl IntoModifersState for Modifiers { + fn into_modifiers_state(self) -> ModifiersState { + let mut state = ModifiersState::empty(); + if self.contains(Modifiers::SHIFT) { + state |= ModifiersState::SHIFT + } + if self.contains(Modifiers::CONTROL) { + state |= ModifiersState::CONTROL + } + if self.contains(Modifiers::ALT) { + state |= ModifiersState::ALT + } + if self.contains(Modifiers::META) || self.contains(Modifiers::SUPER) { + state |= ModifiersState::SUPER + } + state + } +} + +pub trait IntoKeyCode { + fn into_key_code(self) -> KeyCode; +} + +impl IntoKeyCode for KeyCode { + fn into_key_code(self) -> KeyCode { + self + } +} + +impl IntoKeyCode for dioxus_html::KeyCode { + fn into_key_code(self) -> KeyCode { + match self { + dioxus_html::KeyCode::Backspace => KeyCode::Backspace, + dioxus_html::KeyCode::Tab => KeyCode::Tab, + dioxus_html::KeyCode::Clear => KeyCode::NumpadClear, + dioxus_html::KeyCode::Enter => KeyCode::Enter, + dioxus_html::KeyCode::Shift => KeyCode::ShiftLeft, + dioxus_html::KeyCode::Ctrl => KeyCode::ControlLeft, + dioxus_html::KeyCode::Alt => KeyCode::AltLeft, + dioxus_html::KeyCode::Pause => KeyCode::Pause, + dioxus_html::KeyCode::CapsLock => KeyCode::CapsLock, + dioxus_html::KeyCode::Escape => KeyCode::Escape, + dioxus_html::KeyCode::Space => KeyCode::Space, + dioxus_html::KeyCode::PageUp => KeyCode::PageUp, + dioxus_html::KeyCode::PageDown => KeyCode::PageDown, + dioxus_html::KeyCode::End => KeyCode::End, + dioxus_html::KeyCode::Home => KeyCode::Home, + dioxus_html::KeyCode::LeftArrow => KeyCode::ArrowLeft, + dioxus_html::KeyCode::UpArrow => KeyCode::ArrowUp, + dioxus_html::KeyCode::RightArrow => KeyCode::ArrowRight, + dioxus_html::KeyCode::DownArrow => KeyCode::ArrowDown, + dioxus_html::KeyCode::Insert => KeyCode::Insert, + dioxus_html::KeyCode::Delete => KeyCode::Delete, + dioxus_html::KeyCode::Num0 => KeyCode::Numpad0, + dioxus_html::KeyCode::Num1 => KeyCode::Numpad1, + dioxus_html::KeyCode::Num2 => KeyCode::Numpad2, + dioxus_html::KeyCode::Num3 => KeyCode::Numpad3, + dioxus_html::KeyCode::Num4 => KeyCode::Numpad4, + dioxus_html::KeyCode::Num5 => KeyCode::Numpad5, + dioxus_html::KeyCode::Num6 => KeyCode::Numpad6, + dioxus_html::KeyCode::Num7 => KeyCode::Numpad7, + dioxus_html::KeyCode::Num8 => KeyCode::Numpad8, + dioxus_html::KeyCode::Num9 => KeyCode::Numpad9, + dioxus_html::KeyCode::A => KeyCode::KeyA, + dioxus_html::KeyCode::B => KeyCode::KeyB, + dioxus_html::KeyCode::C => KeyCode::KeyC, + dioxus_html::KeyCode::D => KeyCode::KeyD, + dioxus_html::KeyCode::E => KeyCode::KeyE, + dioxus_html::KeyCode::F => KeyCode::KeyF, + dioxus_html::KeyCode::G => KeyCode::KeyG, + dioxus_html::KeyCode::H => KeyCode::KeyH, + dioxus_html::KeyCode::I => KeyCode::KeyI, + dioxus_html::KeyCode::J => KeyCode::KeyJ, + dioxus_html::KeyCode::K => KeyCode::KeyK, + dioxus_html::KeyCode::L => KeyCode::KeyL, + dioxus_html::KeyCode::M => KeyCode::KeyM, + dioxus_html::KeyCode::N => KeyCode::KeyN, + dioxus_html::KeyCode::O => KeyCode::KeyO, + dioxus_html::KeyCode::P => KeyCode::KeyP, + dioxus_html::KeyCode::Q => KeyCode::KeyQ, + dioxus_html::KeyCode::R => KeyCode::KeyR, + dioxus_html::KeyCode::S => KeyCode::KeyS, + dioxus_html::KeyCode::T => KeyCode::KeyT, + dioxus_html::KeyCode::U => KeyCode::KeyU, + dioxus_html::KeyCode::V => KeyCode::KeyV, + dioxus_html::KeyCode::W => KeyCode::KeyW, + dioxus_html::KeyCode::X => KeyCode::KeyX, + dioxus_html::KeyCode::Y => KeyCode::KeyY, + dioxus_html::KeyCode::Z => KeyCode::KeyZ, + dioxus_html::KeyCode::Numpad0 => KeyCode::Numpad0, + dioxus_html::KeyCode::Numpad1 => KeyCode::Numpad1, + dioxus_html::KeyCode::Numpad2 => KeyCode::Numpad2, + dioxus_html::KeyCode::Numpad3 => KeyCode::Numpad3, + dioxus_html::KeyCode::Numpad4 => KeyCode::Numpad4, + dioxus_html::KeyCode::Numpad5 => KeyCode::Numpad5, + dioxus_html::KeyCode::Numpad6 => KeyCode::Numpad6, + dioxus_html::KeyCode::Numpad7 => KeyCode::Numpad7, + dioxus_html::KeyCode::Numpad8 => KeyCode::Numpad8, + dioxus_html::KeyCode::Numpad9 => KeyCode::Numpad9, + dioxus_html::KeyCode::Multiply => KeyCode::NumpadMultiply, + dioxus_html::KeyCode::Add => KeyCode::NumpadAdd, + dioxus_html::KeyCode::Subtract => KeyCode::NumpadSubtract, + dioxus_html::KeyCode::DecimalPoint => KeyCode::NumpadDecimal, + dioxus_html::KeyCode::Divide => KeyCode::NumpadDivide, + dioxus_html::KeyCode::F1 => KeyCode::F1, + dioxus_html::KeyCode::F2 => KeyCode::F2, + dioxus_html::KeyCode::F3 => KeyCode::F3, + dioxus_html::KeyCode::F4 => KeyCode::F4, + dioxus_html::KeyCode::F5 => KeyCode::F5, + dioxus_html::KeyCode::F6 => KeyCode::F6, + dioxus_html::KeyCode::F7 => KeyCode::F7, + dioxus_html::KeyCode::F8 => KeyCode::F8, + dioxus_html::KeyCode::F9 => KeyCode::F9, + dioxus_html::KeyCode::F10 => KeyCode::F10, + dioxus_html::KeyCode::F11 => KeyCode::F11, + dioxus_html::KeyCode::F12 => KeyCode::F12, + dioxus_html::KeyCode::NumLock => KeyCode::NumLock, + dioxus_html::KeyCode::ScrollLock => KeyCode::ScrollLock, + dioxus_html::KeyCode::Semicolon => KeyCode::Semicolon, + dioxus_html::KeyCode::EqualSign => KeyCode::Equal, + dioxus_html::KeyCode::Comma => KeyCode::Comma, + dioxus_html::KeyCode::Period => KeyCode::Period, + dioxus_html::KeyCode::ForwardSlash => KeyCode::Slash, + dioxus_html::KeyCode::GraveAccent => KeyCode::Backquote, + dioxus_html::KeyCode::OpenBracket => KeyCode::BracketLeft, + dioxus_html::KeyCode::BackSlash => KeyCode::Backslash, + dioxus_html::KeyCode::CloseBraket => KeyCode::BracketRight, + dioxus_html::KeyCode::SingleQuote => KeyCode::Quote, + key => panic!("Failed to convert {:?} to tao::keyboard::KeyCode, try using tao::keyboard::KeyCode directly", key), + } + } +} diff --git a/packages/dioxus/Cargo.toml b/packages/dioxus/Cargo.toml index afe0ea0c3..bc2098285 100644 --- a/packages/dioxus/Cargo.toml +++ b/packages/dioxus/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus" -version = "0.3.1" +version = "0.3.2" authors = ["Jonathan Kelley", "Dioxus Labs", "ealmloff"] edition = "2021" description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences" @@ -16,7 +16,7 @@ dioxus-core = { path = "../core", version = "^0.3.0" } dioxus-html = { path = "../html", version = "^0.3.0", optional = true } dioxus-core-macro = { path = "../core-macro", version = "^0.3.0", optional = true } dioxus-hooks = { path = "../hooks", version = "^0.3.0", optional = true } -dioxus-rsx = { path = "../rsx", version = "0.0.2", optional = true } +dioxus-rsx = { path = "../rsx", version = "^0.0.3", optional = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] dioxus-hot-reload = { path = "../hot-reload", version = "0.1.0", optional = true } diff --git a/packages/dioxus/README.md b/packages/dioxus/README.md index 4dc007096..31fff88fa 100644 --- a/packages/dioxus/README.md +++ b/packages/dioxus/README.md @@ -9,9 +9,8 @@ This overview provides a brief introduction to Dioxus. For a more in-depth guide, make sure to check out: -- [Getting Started](https://dioxuslabs.com/guide/setup.html) -- [Book](https://dioxuslabs.com/guide/) -- [Reference](https://dioxuslabs.com/reference) +- [Getting Started](https://dioxuslabs.com/docs/0.3/guide/en/getting_started/index.html) +- [Book](https://dioxuslabs.com/docs/0.3/guide/en/) - [Examples](https://github.com/DioxusLabs/example-projects) # Overview and Goals diff --git a/packages/fermi/src/root.rs b/packages/fermi/src/root.rs index 68a5d5d33..99e97bfbb 100644 --- a/packages/fermi/src/root.rs +++ b/packages/fermi/src/root.rs @@ -13,8 +13,8 @@ use crate::Readable; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct AtomId { - pub(crate) ptr: *const (), - pub(crate) type_id: TypeId, + pub ptr: *const (), + pub type_id: TypeId, } pub struct AtomRoot { diff --git a/packages/hooks/Cargo.toml b/packages/hooks/Cargo.toml index c1b31ea65..c0e1ea783 100644 --- a/packages/hooks/Cargo.toml +++ b/packages/hooks/Cargo.toml @@ -20,3 +20,4 @@ log = "0.4" [dev-dependencies] futures-util = { version = "0.3", default-features = false } dioxus-core = { path = "../../packages/core", version = "^0.3.0" } +dioxus = { path = "../../packages/dioxus", version = "^0.3.0" } diff --git a/packages/hooks/README.md b/packages/hooks/README.md index 6d6f56d50..6c77867de 100644 --- a/packages/hooks/README.md +++ b/packages/hooks/README.md @@ -19,7 +19,7 @@ [discord-url]: https://discord.gg/XgGxMSkvUM [Website](https://dioxuslabs.com) | -[Guides](https://dioxuslabs.com/guide/) | +[Guides](https://dioxuslabs.com/docs/0.3/guide/en/) | [API Docs](https://docs.rs/dioxus-hooks/latest/dioxus_hooks) | [Chat](https://discord.gg/XgGxMSkvUM) diff --git a/packages/hooks/src/use_shared_state.rs b/packages/hooks/src/use_shared_state.rs index a355ae42a..eb836a211 100644 --- a/packages/hooks/src/use_shared_state.rs +++ b/packages/hooks/src/use_shared_state.rs @@ -1,6 +1,6 @@ use dioxus_core::{ScopeId, ScopeState}; use std::{ - cell::{Cell, Ref, RefCell, RefMut}, + cell::{Ref, RefCell, RefMut}, collections::HashSet, rc::Rc, sync::Arc, @@ -9,8 +9,8 @@ use std::{ type ProvidedState = Rc>>; // Tracks all the subscribers to a shared State -pub struct ProvidedStateInner { - value: Rc>, +pub(crate) struct ProvidedStateInner { + value: T, notify_any: Arc, consumers: HashSet, } @@ -21,14 +21,6 @@ impl ProvidedStateInner { (self.notify_any)(*consumer); } } - - pub fn write(&self) -> RefMut { - self.value.borrow_mut() - } - - pub fn read(&self) -> Ref { - self.value.borrow() - } } /// This hook provides some relatively light ergonomics around shared state. @@ -38,18 +30,57 @@ impl ProvidedStateInner { /// /// # Example /// -/// ## Provider +/// ```rust +/// # use dioxus::prelude::*; +/// # +/// # fn app(cx: Scope) -> Element { +/// # render! { +/// # Parent{} +/// # } +/// # } /// -/// ```rust, ignore +/// #[derive(Clone, Copy)] +/// enum Theme { +/// Light, +/// Dark, +/// } /// +/// // Provider +/// fn Parent<'a>(cx: Scope<'a>) -> Element<'a> { +/// use_shared_state_provider(cx, || Theme::Dark); +/// let theme = use_shared_state::(cx).unwrap(); /// -/// ``` -/// -/// ## Consumer -/// -/// ```rust, ignore +/// render! { +/// button{ +/// onclick: move |_| { +/// let current_theme = *theme.read(); +/// *theme.write() = match current_theme { +/// Theme::Dark => Theme::Light, +/// Theme::Light => Theme::Dark, +/// }; +/// }, +/// "Change theme" +/// } +/// Child{} +/// } +/// } /// +/// // Consumer +/// fn Child<'a>(cx: Scope<'a>) -> Element<'a> { +/// let theme = use_shared_state::(cx).unwrap(); +/// let current_theme = *theme.read(); /// +/// render! { +/// match &*theme.read() { +/// Theme::Dark => { +/// "Dark mode" +/// } +/// Theme::Light => { +/// "Light mode" +/// } +/// } +/// } +/// } /// ``` /// /// # How it works @@ -57,118 +88,126 @@ impl ProvidedStateInner { /// Any time a component calls `write`, every consumer of the state will be notified - excluding the provider. /// /// Right now, there is not a distinction between read-only and write-only, so every consumer will be notified. -/// -/// -/// -pub fn use_shared_state(cx: &ScopeState) -> Option> { - let state = cx.use_hook(|| { +pub fn use_shared_state(cx: &ScopeState) -> Option<&UseSharedState> { + let state: &Option> = &*cx.use_hook(move || { let scope_id = cx.scope_id(); - let root = cx.consume_context::>(); + let root = cx.consume_context::>()?; - if let Some(root) = root.as_ref() { - root.borrow_mut().consumers.insert(scope_id); - } + root.borrow_mut().consumers.insert(scope_id); - let value = root.as_ref().map(|f| f.borrow().value.clone()); - SharedStateInner { - root, - value, - scope_id, - needs_notification: Cell::new(false), - } + let state = UseSharedState { inner: root }; + let owner = UseSharedStateOwner { state, scope_id }; + Some(owner) }); - - state.needs_notification.set(false); - match (&state.value, &state.root) { - (Some(value), Some(root)) => Some(UseSharedState { - cx, - value, - root, - needs_notification: &state.needs_notification, - }), - _ => None, - } + state.as_ref().map(|s| &s.state) } -struct SharedStateInner { - root: Option>, - value: Option>>, +/// This wrapper detects when the hook is dropped and will unsubscribe when the component is unmounted +struct UseSharedStateOwner { + state: UseSharedState, scope_id: ScopeId, - needs_notification: Cell, } -impl Drop for SharedStateInner { + +impl Drop for UseSharedStateOwner { fn drop(&mut self) { - // we need to unsubscribe when our component is unounted - if let Some(root) = &self.root { - let mut root = root.borrow_mut(); - root.consumers.remove(&self.scope_id); - } + // we need to unsubscribe when our component is unmounted + let mut root = self.state.inner.borrow_mut(); + root.consumers.remove(&self.scope_id); } } -pub struct UseSharedState<'a, T: 'static> { - pub(crate) cx: &'a ScopeState, - pub(crate) value: &'a Rc>, - pub(crate) root: &'a Rc>>, - pub(crate) needs_notification: &'a Cell, +/// State that is shared between components through the context system +pub struct UseSharedState { + pub(crate) inner: Rc>>, } -impl<'a, T: 'static> UseSharedState<'a, T> { +impl UseSharedState { + /// Notify all consumers of the state that it has changed. (This is called automatically when you call "write") + pub fn notify_consumers(&self) { + self.inner.borrow_mut().notify_consumers(); + } + + /// Read the shared value pub fn read(&self) -> Ref<'_, T> { - self.value.borrow() - } - - pub fn notify_consumers(self) { - if !self.needs_notification.get() { - self.root.borrow_mut().notify_consumers(); - self.needs_notification.set(true); - } - } - - pub fn read_write(&self) -> (Ref<'_, T>, &Self) { - (self.read(), self) + Ref::map(self.inner.borrow(), |inner| &inner.value) } /// Calling "write" will force the component to re-render /// /// - /// TODO: We prevent unncessary notifications only in the hook, but we should figure out some more global lock + // TODO: We prevent unncessary notifications only in the hook, but we should figure out some more global lock pub fn write(&self) -> RefMut<'_, T> { - self.cx.needs_update(); - self.notify_consumers(); - self.value.borrow_mut() + let mut value = self.inner.borrow_mut(); + value.notify_consumers(); + RefMut::map(value, |inner| &mut inner.value) } /// Allows the ability to write the value without forcing a re-render pub fn write_silent(&self) -> RefMut<'_, T> { - self.value.borrow_mut() - } - - pub fn inner(&self) -> Rc>> { - self.root.clone() + RefMut::map(self.inner.borrow_mut(), |inner| &mut inner.value) } } -impl Copy for UseSharedState<'_, T> {} -impl<'a, T> Clone for UseSharedState<'a, T> -where - T: 'static, -{ +impl Clone for UseSharedState { fn clone(&self) -> Self { - UseSharedState { - cx: self.cx, - value: self.value, - root: self.root, - needs_notification: self.needs_notification, + Self { + inner: self.inner.clone(), } } } -/// Provide some state for components down the hierarchy to consume without having to drill props. +impl PartialEq for UseSharedState { + fn eq(&self, other: &Self) -> bool { + let first = self.inner.borrow(); + let second = other.inner.borrow(); + first.value == second.value + } +} + +/// Provide some state for components down the hierarchy to consume without having to drill props. See [`use_shared_state`] to consume the state +/// +/// +/// # Example +/// +/// ```rust +/// # use dioxus::prelude::*; +/// # +/// # fn app(cx: Scope) -> Element { +/// # render! { +/// # Parent{} +/// # } +/// # } +/// +/// #[derive(Clone, Copy)] +/// enum Theme { +/// Light, +/// Dark, +/// } +/// +/// // Provider +/// fn Parent<'a>(cx: Scope<'a>) -> Element<'a> { +/// use_shared_state_provider(cx, || Theme::Dark); +/// let theme = use_shared_state::(cx).unwrap(); +/// +/// render! { +/// button{ +/// onclick: move |_| { +/// let current_theme = *theme.read(); +/// *theme.write() = match current_theme { +/// Theme::Dark => Theme::Light, +/// Theme::Light => Theme::Dark, +/// }; +/// }, +/// "Change theme" +/// } +/// // Children components that consume the state... +/// } +/// } +/// ``` pub fn use_shared_state_provider(cx: &ScopeState, f: impl FnOnce() -> T) { cx.use_hook(|| { let state: ProvidedState = Rc::new(RefCell::new(ProvidedStateInner { - value: Rc::new(RefCell::new(f())), + value: f(), notify_any: cx.schedule_update_any(), consumers: HashSet::new(), })); diff --git a/packages/hooks/src/usecoroutine.rs b/packages/hooks/src/usecoroutine.rs index 2f2442f87..bc97735b9 100644 --- a/packages/hooks/src/usecoroutine.rs +++ b/packages/hooks/src/usecoroutine.rs @@ -108,6 +108,12 @@ impl Coroutine { } } +impl PartialEq for Coroutine { + fn eq(&self, other: &Self) -> bool { + self.task == other.task + } +} + #[cfg(test)] mod tests { #![allow(unused)] diff --git a/packages/hooks/src/usestate.rs b/packages/hooks/src/usestate.rs index 2e1aa7295..d2b6db6fc 100644 --- a/packages/hooks/src/usestate.rs +++ b/packages/hooks/src/usestate.rs @@ -329,7 +329,7 @@ impl PartialEq for &UseState { } } -impl PartialEq> for UseState { +impl PartialEq> for UseState { fn eq(&self, other: &UseState) -> bool { Rc::ptr_eq(&self.current_val, &other.current_val) } diff --git a/packages/hot-reload/Cargo.toml b/packages/hot-reload/Cargo.toml index 99d5cb00d..2d88bf029 100644 --- a/packages/hot-reload/Cargo.toml +++ b/packages/hot-reload/Cargo.toml @@ -1,20 +1,20 @@ [package] name = "dioxus-hot-reload" -version = "0.1.0" +version = "0.1.1" edition = "2021" license = "MIT/Apache-2.0" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" description = "Hot reloading utilites for Dioxus" documentation = "https://dioxuslabs.com" -keywords = ["dom", "ui", "gui", "react", "hot-reloading", "watch"] +keywords = ["dom", "ui", "gui", "react", "hot-reloading"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-rsx = { path = "../rsx" } -dioxus-core = { path = "../core", features = ["serialize"] } -dioxus-html = { path = "../html", features = ["hot-reload-context"] } +dioxus-rsx = { path = "../rsx" , version = "^0.0.3" } +dioxus-core = { path = "../core", features = ["serialize"], version = "^0.3.2"} +dioxus-html = { path = "../html", features = ["hot-reload-context"], version = "^0.3.0" } interprocess = { version = "1.2.1" } notify = "5.0.0" @@ -23,4 +23,4 @@ serde_json = "1.0.91" serde = { version = "1", features = ["derive"] } execute = "0.2.11" once_cell = "1.17.0" -ignore = "0.4.19" \ No newline at end of file +ignore = "0.4.19" diff --git a/packages/hot-reload/README.md b/packages/hot-reload/README.md index 126c42103..60b7b9c87 100644 --- a/packages/hot-reload/README.md +++ b/packages/hot-reload/README.md @@ -19,7 +19,7 @@ [discord-url]: https://discord.gg/XgGxMSkvUM [Website](https://dioxuslabs.com) | -[Guides](https://dioxuslabs.com/guide/) | +[Guides](https://dioxuslabs.com/docs/0.3/guide/en) | [API Docs](https://docs.rs/dioxus-hot-reload/latest/dioxus_hot_reload) | [Chat](https://discord.gg/XgGxMSkvUM) diff --git a/packages/hot-reload/src/lib.rs b/packages/hot-reload/src/lib.rs index 1ddd32bb1..d144fccfc 100644 --- a/packages/hot-reload/src/lib.rs +++ b/packages/hot-reload/src/lib.rs @@ -7,7 +7,7 @@ use std::{ use dioxus_core::Template; use dioxus_rsx::{ - hot_reload::{FileMap, UpdateResult}, + hot_reload::{FileMap, FileMapBuildResult, UpdateResult}, HotReloadingContext, }; use interprocess::local_socket::{LocalSocketListener, LocalSocketStream}; @@ -129,10 +129,37 @@ pub fn init(cfg: Config) { } = cfg; if let Ok(crate_dir) = PathBuf::from_str(root_path) { - let temp_file = std::env::temp_dir().join("@dioxusin"); + // try to find the gitingore file + let gitignore_file_path = crate_dir.join(".gitignore"); + let (gitignore, _) = ignore::gitignore::Gitignore::new(gitignore_file_path); + + // convert the excluded paths to absolute paths + let excluded_paths = excluded_paths + .iter() + .map(|path| crate_dir.join(PathBuf::from(path))) + .collect::>(); + let channels = Arc::new(Mutex::new(Vec::new())); - let file_map = Arc::new(Mutex::new(FileMap::::new(crate_dir.clone()))); - if let Ok(local_socket_stream) = LocalSocketListener::bind(temp_file.as_path()) { + let FileMapBuildResult { + map: file_map, + errors, + } = FileMap::::create_with_filter(crate_dir.clone(), |path| { + // skip excluded paths + excluded_paths.iter().any(|p| path.starts_with(p)) || + // respect .gitignore + gitignore + .matched_path_or_any_parents(path, path.is_dir()) + .is_ignore() + }) + .unwrap(); + for err in errors { + if log { + println!("hot reloading failed to initialize:\n{err:?}"); + } + } + let file_map = Arc::new(Mutex::new(file_map)); + + if let Ok(local_socket_stream) = LocalSocketListener::bind("@dioxusin") { let aborted = Arc::new(Mutex::new(false)); // listen for connections @@ -177,10 +204,6 @@ pub fn init(cfg: Config) { // watch for changes std::thread::spawn(move || { - // try to find the gitingore file - let gitignore_file_path = crate_dir.join(".gitignore"); - let (gitignore, _) = ignore::gitignore::Gitignore::new(gitignore_file_path); - let mut last_update_time = chrono::Local::now().timestamp(); let (tx, rx) = std::sync::mpsc::channel(); @@ -198,11 +221,6 @@ pub fn init(cfg: Config) { } } - let excluded_paths = excluded_paths - .iter() - .map(|path| crate_dir.join(PathBuf::from(path))) - .collect::>(); - let mut rebuild = { let aborted = aborted.clone(); let channels = channels.clone(); @@ -242,7 +260,7 @@ pub fn init(cfg: Config) { matches!( path.extension().and_then(|p| p.to_str()), Some("rs" | "toml" | "css" | "html" | "js") - )&& + ) && // skip excluded paths !excluded_paths.iter().any(|p| path.starts_with(p)) && // respect .gitignore @@ -271,7 +289,7 @@ pub fn init(cfg: Config) { .unwrap() .update_rsx(path, crate_dir.as_path()) { - UpdateResult::UpdatedRsx(msgs) => { + Ok(UpdateResult::UpdatedRsx(msgs)) => { for msg in msgs { let mut i = 0; while i < channels.len() { @@ -287,13 +305,20 @@ pub fn init(cfg: Config) { } } } - UpdateResult::NeedsRebuild => { + Ok(UpdateResult::NeedsRebuild) => { drop(channels); if rebuild() { return; } break; } + Err(err) => { + if log { + println!( + "hot reloading failed to update rsx:\n{err:?}" + ); + } + } } } } @@ -322,8 +347,7 @@ fn send_msg(msg: HotReloadMsg, channel: &mut impl Write) -> bool { /// Connect to the hot reloading listener. The callback provided will be called every time a template change is detected pub fn connect(mut f: impl FnMut(HotReloadMsg) + Send + 'static) { std::thread::spawn(move || { - let temp_file = std::env::temp_dir().join("@dioxusin"); - if let Ok(socket) = LocalSocketStream::connect(temp_file.as_path()) { + if let Ok(socket) = LocalSocketStream::connect("@dioxusin") { let mut buf_reader = BufReader::new(socket); loop { let mut buf = String::new(); diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index 2159acb7b..e60fcd8e5 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-html" -version = "0.3.0" +version = "0.3.1" authors = ["Jonathan Kelley"] edition = "2018" description = "HTML Element pack for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences" @@ -12,7 +12,7 @@ keywords = ["dom", "ui", "gui", "react"] [dependencies] dioxus-core = { path = "../core", version = "^0.3.0" } -dioxus-rsx = { path = "../rsx", version = "0.0.2", optional = true } +dioxus-rsx = { path = "../rsx", version = "^0.0.3", optional = true } serde = { version = "1", features = ["derive"], optional = true } serde_repr = { version = "0.1", optional = true } wasm-bindgen = { version = "0.2.79", optional = true } diff --git a/packages/html/README.md b/packages/html/README.md index 7bae7e197..59ec2f9ff 100644 --- a/packages/html/README.md +++ b/packages/html/README.md @@ -19,7 +19,7 @@ [discord-url]: https://discord.gg/XgGxMSkvUM [Website](https://dioxuslabs.com) | -[Guides](https://dioxuslabs.com/guide/) | +[Guides](https://dioxuslabs.com/docs/0.3/guide/en/) | [API Docs](https://docs.rs/dioxus-html/latest/dioxus_html) | [Chat](https://discord.gg/XgGxMSkvUM) diff --git a/packages/interpreter/Cargo.toml b/packages/interpreter/Cargo.toml index a10377f34..7c7d79607 100644 --- a/packages/interpreter/Cargo.toml +++ b/packages/interpreter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-interpreter-js" -version = "0.3.0" +version = "0.3.1" edition = "2018" authors = ["Jonathan Kelley"] description = "JS Intepreter for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences" diff --git a/packages/interpreter/README.md b/packages/interpreter/README.md index cb01f2cc7..90e390f56 100644 --- a/packages/interpreter/README.md +++ b/packages/interpreter/README.md @@ -19,7 +19,7 @@ [discord-url]: https://discord.gg/XgGxMSkvUM [Website](https://dioxuslabs.com) | -[Guides](https://dioxuslabs.com/guide/) | +[Guides](https://dioxuslabs.com/docs/0.3/guide/en/) | [API Docs](https://docs.rs/dioxus-interpreter-js/latest/dioxus_interpreter_js) | [Chat](https://discord.gg/XgGxMSkvUM) diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index 06774e474..b921c7c22 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -116,9 +116,6 @@ class Interpreter { node.remove(); } } - CreateRawText(text) { - this.stack.push(document.createTextNode(text)); - } CreateTextNode(text, root) { const node = document.createTextNode(text); this.nodes[root] = node; @@ -306,7 +303,7 @@ class Interpreter { this.CreatePlaceholder(edit.id); break; case "CreateTextNode": - this.CreateTextNode(edit.value); + this.CreateTextNode(edit.value, edit.id); break; case "HydrateText": this.HydrateText(edit.path, edit.value, edit.id); @@ -360,10 +357,11 @@ class Interpreter { 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"); + let a_element = target.closest("a"); + if (a_element != null) { + event.preventDefault(); + if (shouldPreventDefault !== `onclick` && a_element.getAttribute(`dioxus-prevent-default`) !== `onclick`) { + const href = a_element.getAttribute("href"); if (href !== "" && href !== null && href !== undefined) { window.ipc.postMessage( serializeIpcMessage("browser_open", { href }) diff --git a/packages/liveview/README.md b/packages/liveview/README.md index 6ab109f8e..0a0e7de3a 100644 --- a/packages/liveview/README.md +++ b/packages/liveview/README.md @@ -19,7 +19,7 @@ [discord-url]: https://discord.gg/XgGxMSkvUM [Website](https://dioxuslabs.com) | -[Guides](https://dioxuslabs.com/guide/) | +[Guides](https://dioxuslabs.com/docs/0.3/guide/en/) | [API Docs](https://docs.rs/dioxus-liveview/latest/dioxus_liveview) | [Chat](https://discord.gg/XgGxMSkvUM) diff --git a/packages/liveview/examples/axum.rs b/packages/liveview/examples/axum.rs index df24de4aa..2e7f10ee7 100644 --- a/packages/liveview/examples/axum.rs +++ b/packages/liveview/examples/axum.rs @@ -28,7 +28,7 @@ async fn main() { r#" - Dioxus LiveView with Warp + Dioxus LiveView with axum

{glue} diff --git a/packages/liveview/examples/salvo.rs b/packages/liveview/examples/salvo.rs index 8cd2c0ca4..015892702 100644 --- a/packages/liveview/examples/salvo.rs +++ b/packages/liveview/examples/salvo.rs @@ -41,7 +41,7 @@ fn index(_depot: &mut Depot, res: &mut Response) { r#" - Dioxus LiveView with Warp + Dioxus LiveView with Salvo
{glue} diff --git a/packages/liveview/src/pool.rs b/packages/liveview/src/pool.rs index 022641d5c..e02c8195b 100644 --- a/packages/liveview/src/pool.rs +++ b/packages/liveview/src/pool.rs @@ -37,7 +37,16 @@ impl LiveViewPool { app: fn(Scope) -> Element, props: T, ) -> Result<(), LiveViewError> { - match self.pool.spawn_pinned(move || run(app, props, ws)).await { + self.launch_virtualdom(ws, move || VirtualDom::new_with_props(app, props)) + .await + } + + pub async fn launch_virtualdom VirtualDom + Send + 'static>( + &self, + ws: impl LiveViewSocket, + make_app: F, + ) -> Result<(), LiveViewError> { + match self.pool.spawn_pinned(move || run(make_app(), ws)).await { Ok(Ok(_)) => Ok(()), Ok(Err(e)) => Err(e), Err(_) => Err(LiveViewError::SendingFailed), @@ -95,14 +104,7 @@ impl LiveViewSocket for S where /// As long as your framework can provide a Sink and Stream of Strings, you can use this function. /// /// You might need to transform the error types of the web backend into the LiveView error type. -pub async fn run( - app: Component, - props: T, - ws: impl LiveViewSocket, -) -> Result<(), LiveViewError> -where - T: Send + 'static, -{ +pub async fn run(mut vdom: VirtualDom, ws: impl LiveViewSocket) -> Result<(), LiveViewError> { #[cfg(all(feature = "hot-reload", debug_assertions))] let mut hot_reload_rx = { let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); @@ -112,8 +114,6 @@ where rx }; - let mut vdom = VirtualDom::new_with_props(app, props); - // todo: use an efficient binary packed format for this let edits = serde_json::to_string(&vdom.rebuild()).unwrap(); diff --git a/packages/mobile/README.md b/packages/mobile/README.md index 7af09e970..46da9061a 100644 --- a/packages/mobile/README.md +++ b/packages/mobile/README.md @@ -18,7 +18,7 @@ [discord-url]: https://discord.gg/XgGxMSkvUM [Website](https://dioxuslabs.com) | -[Guides](https://dioxuslabs.com/guide/) | +[Guides](https://dioxuslabs.com/docs/0.3/guide/en/) | [API Docs](https://docs.rs/dioxus-mobile/latest/dioxus_mobile) | [Chat](https://discord.gg/XgGxMSkvUM) @@ -98,7 +98,7 @@ To configure the web view, menubar, and other important desktop-specific feature ## Future Steps -Make sure to read the [Dioxus Guide](https://dioxuslabs.com/guide) if you already haven't! +Make sure to read the [Dioxus Guide](https://dioxuslabs.com/docs/0.3/guide/en) if you already haven't! diff --git a/packages/router/README.md b/packages/router/README.md index 43bc2df2b..c7be4496d 100644 --- a/packages/router/README.md +++ b/packages/router/README.md @@ -1,4 +1,4 @@ -# Dioxus Native Core +# Dioxus Router [![Crates.io][crates-badge]][crates-url] [![MIT licensed][mit-badge]][mit-url] @@ -18,7 +18,7 @@ [discord-url]: https://discord.gg/XgGxMSkvUM [Website](https://dioxuslabs.com) | -[Guides](https://dioxuslabs.com/guide/) | +[Guides](https://dioxuslabs.com/docs/0.3/guide/en/) | [API Docs](https://docs.rs/dioxus-router/latest/dioxus_router) | [Chat](https://discord.gg/XgGxMSkvUM) diff --git a/packages/rsx-rosetta/Cargo.toml b/packages/rsx-rosetta/Cargo.toml index 2def4e7c8..a0a9763e1 100644 --- a/packages/rsx-rosetta/Cargo.toml +++ b/packages/rsx-rosetta/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" [dependencies] dioxus-autofmt = { path = "../autofmt" } -dioxus-rsx = { path = "../rsx" } +dioxus-rsx = { path = "../rsx" , version = "^0.0.3" } html_parser = "0.6.3" proc-macro2 = "1.0.49" quote = "1.0.23" diff --git a/packages/rsx-rosetta/README.md b/packages/rsx-rosetta/README.md index bf811a5fe..da3049d3c 100644 --- a/packages/rsx-rosetta/README.md +++ b/packages/rsx-rosetta/README.md @@ -20,7 +20,7 @@ [discord-url]: https://discord.gg/XgGxMSkvUM [Website](https://dioxuslabs.com) | -[Guides](https://dioxuslabs.com/guide/) | +[Guides](https://dioxuslabs.com/docs/0.3/guide/en/) | [API Docs](https://docs.rs/rsx-rosetta/latest/rsx-rosetta) | [Chat](https://discord.gg/XgGxMSkvUM) diff --git a/packages/rsx/Cargo.toml b/packages/rsx/Cargo.toml index 6f98b5f47..055a97132 100644 --- a/packages/rsx/Cargo.toml +++ b/packages/rsx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-rsx" -version = "0.0.2" +version = "0.0.3" edition = "2018" license = "MIT/Apache-2.0" description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences" @@ -13,8 +13,9 @@ keywords = ["dom", "ui", "gui", "react"] [dependencies] proc-macro2 = { version = "1.0", features = ["span-locations"] } -dioxus-core = { path = "../core", version = "0.3.0"} +dioxus-core = { path = "../core", version = "^0.3.0"} syn = { version = "1.0", features = ["full", "extra-traits"] } quote = { version = "1.0" } serde = { version = "1.0", features = ["derive"] } internment = "0.7.0" +krates = "0.12.6" \ No newline at end of file diff --git a/packages/rsx/README.md b/packages/rsx/README.md index 707416b98..c20737167 100644 --- a/packages/rsx/README.md +++ b/packages/rsx/README.md @@ -18,7 +18,7 @@ [discord-url]: https://discord.gg/XgGxMSkvUM [Website](https://dioxuslabs.com) | -[Guides](https://dioxuslabs.com/guide/) | +[Guides](https://dioxuslabs.com/docs/0.3/guide/en/) | [API Docs](https://docs.rs/dioxus-rsx/latest/dioxus_rsx) | [Chat](https://discord.gg/XgGxMSkvUM) diff --git a/packages/rsx/src/hot_reload/hot_reloading_file_map.rs b/packages/rsx/src/hot_reload/hot_reloading_file_map.rs index be4f5d828..2f0cc5246 100644 --- a/packages/rsx/src/hot_reload/hot_reloading_file_map.rs +++ b/packages/rsx/src/hot_reload/hot_reloading_file_map.rs @@ -1,5 +1,7 @@ use crate::{CallBody, HotReloadingContext}; use dioxus_core::Template; +use krates::cm::MetadataCommand; +use krates::Cmd; pub use proc_macro2::TokenStream; pub use std::collections::HashMap; use std::path::PathBuf; @@ -17,46 +19,82 @@ pub enum UpdateResult { NeedsRebuild, } +/// The result of building a FileMap +pub struct FileMapBuildResult { + /// The FileMap that was built + pub map: FileMap, + /// Any errors that occurred while building the FileMap that were not fatal + pub errors: Vec, +} + pub struct FileMap { pub map: HashMap>)>, + in_workspace: HashMap, phantom: std::marker::PhantomData, } impl FileMap { /// Create a new FileMap from a crate directory - pub fn new(path: PathBuf) -> Self { + pub fn create(path: PathBuf) -> io::Result> { + Self::create_with_filter(path, |_| false) + } + + /// Create a new FileMap from a crate directory + pub fn create_with_filter( + path: PathBuf, + mut filter: impl FnMut(&Path) -> bool, + ) -> io::Result> { + struct FileMapSearchResult { + map: HashMap>)>, + errors: Vec, + } fn find_rs_files( root: PathBuf, - ) -> io::Result>)>> { + filter: &mut impl FnMut(&Path) -> bool, + ) -> io::Result { let mut files = HashMap::new(); + let mut errors = Vec::new(); if root.is_dir() { for entry in (fs::read_dir(root)?).flatten() { let path = entry.path(); - files.extend(find_rs_files(path)?); + if !filter(&path) { + let FileMapSearchResult { + map, + errors: child_errors, + } = find_rs_files(path, filter)?; + errors.extend(child_errors); + files.extend(map); + } } } else if root.extension().and_then(|s| s.to_str()) == Some("rs") { if let Ok(mut file) = File::open(root.clone()) { let mut src = String::new(); - file.read_to_string(&mut src).expect("Unable to read file"); + file.read_to_string(&mut src)?; files.insert(root, (src, None)); } } - Ok(files) + Ok(FileMapSearchResult { map: files, errors }) } + let FileMapSearchResult { map, errors } = find_rs_files(path, &mut filter)?; let result = Self { - map: find_rs_files(path).unwrap(), + map, + in_workspace: HashMap::new(), phantom: std::marker::PhantomData, }; - result + Ok(FileMapBuildResult { + map: result, + errors, + }) } /// Try to update the rsx in a file - pub fn update_rsx(&mut self, file_path: &Path, crate_dir: &Path) -> UpdateResult { - let mut file = File::open(file_path).unwrap(); + pub fn update_rsx(&mut self, file_path: &Path, crate_dir: &Path) -> io::Result { + let mut file = File::open(file_path)?; let mut src = String::new(); - file.read_to_string(&mut src).expect("Unable to read file"); + file.read_to_string(&mut src)?; if let Ok(syntax) = syn::parse_file(&src) { + let in_workspace = self.child_in_workspace(crate_dir)?; if let Some((old_src, template_slot)) = self.map.get_mut(file_path) { if let Ok(old) = syn::parse_file(old_src) { match find_rsx(&syntax, &old) { @@ -72,7 +110,17 @@ impl FileMap { syn::parse2::(old.tokens), syn::parse2::(new), ) { - if let Ok(file) = file_path.strip_prefix(crate_dir) { + // if the file!() macro is invoked in a workspace, the path is relative to the workspace root, otherwise it's relative to the crate root + // we need to check if the file is in a workspace or not and strip the prefix accordingly + let prefix = if in_workspace { + crate_dir.parent().ok_or(io::Error::new( + io::ErrorKind::Other, + "Could not load workspace", + ))? + } else { + crate_dir + }; + if let Ok(file) = file_path.strip_prefix(prefix) { let line = old_start.line; let column = old_start.column + 1; let location = file.display().to_string() @@ -91,7 +139,7 @@ impl FileMap { { // dioxus cannot handle empty templates if template.roots.is_empty() { - return UpdateResult::NeedsRebuild; + return Ok(UpdateResult::NeedsRebuild); } else { // if the template is the same, don't send it if let Some(old_template) = template_slot { @@ -103,20 +151,44 @@ impl FileMap { messages.push(template); } } else { - return UpdateResult::NeedsRebuild; + return Ok(UpdateResult::NeedsRebuild); } } } } - return UpdateResult::UpdatedRsx(messages); + return Ok(UpdateResult::UpdatedRsx(messages)); } } } } else { // if this is a new file, rebuild the project - *self = FileMap::new(crate_dir.to_path_buf()); + let FileMapBuildResult { map, mut errors } = + FileMap::create(crate_dir.to_path_buf())?; + if let Some(err) = errors.pop() { + return Err(err); + } + *self = map; } } - UpdateResult::NeedsRebuild + Ok(UpdateResult::NeedsRebuild) + } + + fn child_in_workspace(&mut self, crate_dir: &Path) -> io::Result { + if let Some(in_workspace) = self.in_workspace.get(crate_dir) { + Ok(*in_workspace) + } else { + let mut cmd = Cmd::new(); + let manafest_path = crate_dir.join("Cargo.toml"); + cmd.manifest_path(&manafest_path); + let cmd: MetadataCommand = cmd.into(); + let metadata = cmd + .exec() + .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?; + + let in_workspace = metadata.workspace_root != crate_dir; + self.in_workspace + .insert(crate_dir.to_path_buf(), in_workspace); + Ok(in_workspace) + } } } diff --git a/packages/ssr/Cargo.toml b/packages/ssr/Cargo.toml index 0ce901b5a..02900ca0e 100644 --- a/packages/ssr/Cargo.toml +++ b/packages/ssr/Cargo.toml @@ -14,6 +14,7 @@ keywords = ["dom", "ui", "gui", "react", "ssr"] [dependencies] dioxus-core = { path = "../core", version = "^0.3.0", features = ["serialize"] } +askama_escape = "0.10.3" [dev-dependencies] dioxus = { path = "../dioxus", version = "0.3.0" } diff --git a/packages/ssr/README.md b/packages/ssr/README.md index ef5931874..c5b91b7b8 100644 --- a/packages/ssr/README.md +++ b/packages/ssr/README.md @@ -8,10 +8,9 @@ ## Resources This crate is a part of the broader Dioxus ecosystem. For more resources about Dioxus, check out: -- [Getting Started](https://dioxuslabs.com/getting-started) -- [Book](https://dioxuslabs.com/book) -- [Reference](https://dioxuslabs.com/reference) -- [Community Examples](https://github.com/DioxusLabs/community-examples) +- [Getting Started](https://dioxuslabs.com/docs/0.3/guide/en/getting_started/index.html) +- [Book](https://dioxuslabs.com/docs/0.3/guide/en/) +- [Examples](https://github.com/DioxusLabs/example-projects) ## Overview diff --git a/packages/ssr/src/cache.rs b/packages/ssr/src/cache.rs index 39bfb8671..8556682f1 100644 --- a/packages/ssr/src/cache.rs +++ b/packages/ssr/src/cache.rs @@ -82,7 +82,13 @@ impl StringCache { } cur_path.pop(); } - TemplateNode::Text { text } => write!(chain, "{text}")?, + TemplateNode::Text { text } => { + write!( + chain, + "{}", + askama_escape::escape(text, askama_escape::Html) + )?; + } TemplateNode::Dynamic { id: idx } | TemplateNode::DynamicText { id: idx } => { chain.segments.push(Segment::Node(*idx)) } diff --git a/packages/ssr/src/renderer.rs b/packages/ssr/src/renderer.rs index 8c828f01c..5b6fa8fb2 100644 --- a/packages/ssr/src/renderer.rs +++ b/packages/ssr/src/renderer.rs @@ -104,8 +104,11 @@ impl Renderer { write!(buf, "")?; } - // todo: escape the text - write!(buf, "{}", text.value)?; + write!( + buf, + "{}", + askama_escape::escape(text.value, askama_escape::Html) + )?; if self.pre_render { write!(buf, "")?; @@ -119,7 +122,7 @@ impl Renderer { DynamicNode::Placeholder(_el) => { if self.pre_render { - write!(buf, "
")?;
+                            write!(buf, "
")?;
                         }
                     }
                 },
@@ -138,7 +141,7 @@ fn to_string_works() {
 
     fn app(cx: Scope) -> Element {
         let dynamic = 123;
-        let dyn2 = ""; // todo: escape this
+        let dyn2 = ""; // this should be escaped
 
         render! {
             div { class: "asdasdasd", class: "asdasdasd", id: "id-{dynamic}",
@@ -165,10 +168,10 @@ fn to_string_works() {
                 vec![
                     PreRendered("
Hello world 1 -->".into(),), + PreRendered(">Hello world 1 -->".into(),), Node(0,), PreRendered( - "<-- Hello world 2
nest 1
nest 2
".into(), + "<-- Hello world 2
nest 1
nest 2
".into(), ), Node(1,), Node(2,), @@ -180,5 +183,5 @@ fn to_string_works() { use Segment::*; - assert_eq!(out, "
Hello world 1 -->123<-- Hello world 2
nest 1
nest 2
finalize 0
finalize 1
finalize 2
finalize 3
finalize 4
"); + assert_eq!(out, "
Hello world 1 -->123<-- Hello world 2
nest 1
nest 2
</diiiiiiiiv>
finalize 0
finalize 1
finalize 2
finalize 3
finalize 4
"); } diff --git a/packages/ssr/tests/simple.rs b/packages/ssr/tests/simple.rs index 63137aeae..395f801e1 100644 --- a/packages/ssr/tests/simple.rs +++ b/packages/ssr/tests/simple.rs @@ -38,7 +38,7 @@ fn dynamic() { dioxus_ssr::render_lazy(rsx! { div { "Hello world 1 -->" "{dynamic}" "<-- Hello world 2" } }), - "
Hello world 1 -->123<-- Hello world 2
" + "
Hello world 1 -->123<-- Hello world 2
" ); } diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index 805cacddd..7adfbf13a 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-web" -version = "0.3.0" +version = "0.3.1" authors = ["Jonathan Kelley"] edition = "2018" description = "Dioxus VirtualDOM renderer for the web browser using websys" diff --git a/packages/web/README.md b/packages/web/README.md index d3f4c1aeb..87265c926 100644 --- a/packages/web/README.md +++ b/packages/web/README.md @@ -18,7 +18,7 @@ [discord-url]: https://discord.gg/XgGxMSkvUM [Website](https://dioxuslabs.com) | -[Guides](https://dioxuslabs.com/guide/) | +[Guides](https://dioxuslabs.com/docs/0.3/guide/en/) | [API Docs](https://docs.rs/dioxus-web/latest/dioxus_web) | [Chat](https://discord.gg/XgGxMSkvUM) diff --git a/translations/pt-br/README.md b/translations/pt-br/README.md index ef14a1af9..a50727662 100644 --- a/translations/pt-br/README.md +++ b/translations/pt-br/README.md @@ -40,7 +40,7 @@ | Exemplos | - Guia + Guia | 中文 @@ -83,7 +83,7 @@ Se você conhece React, então você já conhece o Dioxus. - + @@ -159,7 +159,7 @@ Dioxus é único no ecossistema do Rust por suportar: - SSR com `hydration` feito pelo Cliente - Suporte à aplicação Desktop -Para mais informações sobre quais funções estão atualmente disponíveis e para o progresso futuro, veja [O Guia](https://dioxuslabs.com/guide/). +Para mais informações sobre quais funções estão atualmente disponíveis e para o progresso futuro, veja [O Guia](https://dioxuslabs.com/guide/pt-br/). ## Projeto dentro do ecossistema Dioxus From d1155d159c93036182c615c97d10bf8bf92b0422 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 14 Mar 2023 16:27:01 -0500 Subject: [PATCH 073/103] restore some unintended changes --- packages/autofmt/README.md | 2 +- packages/hooks/src/usefuture.rs | 23 +++++++++++------------ 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/autofmt/README.md b/packages/autofmt/README.md index b0a418ee6..bd934f2c7 100644 --- a/packages/autofmt/README.md +++ b/packages/autofmt/README.md @@ -19,7 +19,7 @@ [discord-url]: https://discord.gg/XgGxMSkvUM [Website](https://dioxuslabs.com) | -[Guides](https://dioxuslabs.com/guide/) | +[Guides](https://dioxuslabs.com/docs/0.3/guide/en/) | [API Docs](https://docs.rs/dioxus-autofmt/latest/dioxus_autofmt) | [Chat](https://discord.gg/XgGxMSkvUM) diff --git a/packages/hooks/src/usefuture.rs b/packages/hooks/src/usefuture.rs index df34a6433..f540e0a6e 100644 --- a/packages/hooks/src/usefuture.rs +++ b/packages/hooks/src/usefuture.rs @@ -143,22 +143,21 @@ impl UseFuture { self.task.get() } - /// Get the current stateof the future. + /// Get the current state of the future. pub fn state(&self) -> UseFutureState { - todo!() - // match (&self.task.get(), &self.value) { - // // If we have a task and an existing value, we're reloading - // (Some(_), Some(val)) => UseFutureState::Reloading(val), + match (&self.task.get(), &self.value()) { + // If we have a task and an existing value, we're reloading + (Some(_), Some(val)) => UseFutureState::Reloading(val), - // // no task, but value - we're done - // (None, Some(val)) => UseFutureState::Complete(val), + // no task, but value - we're done + (None, Some(val)) => UseFutureState::Complete(val), - // // no task, no value - something's wrong? return pending - // (None, None) => UseFutureState::Pending, + // no task, no value - something's wrong? return pending + (None, None) => UseFutureState::Pending, - // // Task, no value - we're still pending - // (Some(_), None) => UseFutureState::Pending, - // } + // Task, no value - we're still pending + (Some(_), None) => UseFutureState::Pending, + } } } From a37b29947b121521970ec457e6a77042f264d934 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 14 Mar 2023 16:35:11 -0500 Subject: [PATCH 074/103] Remove the box::new passed into RealDom::new --- packages/native-core/examples/simple_dioxus.rs | 1 + .../native-core/src/utils/persistant_iterator.rs | 8 ++++---- .../native-core/tests/called_minimally_on_build.rs | 2 +- packages/native-core/tests/fuzzing.rs | 4 ++-- packages/native-core/tests/miri_native.rs | 4 ++-- packages/native-core/tests/passes.rs | 14 +++++++------- packages/rink/src/lib.rs | 4 ++-- 7 files changed, 19 insertions(+), 18 deletions(-) diff --git a/packages/native-core/examples/simple_dioxus.rs b/packages/native-core/examples/simple_dioxus.rs index b4c911cd4..fa1b202dd 100644 --- a/packages/native-core/examples/simple_dioxus.rs +++ b/packages/native-core/examples/simple_dioxus.rs @@ -1,3 +1,4 @@ +#![allow(non_snake_case)] use dioxus::prelude::*; use dioxus_native_core::exports::shipyard::Component; use dioxus_native_core::node_ref::*; diff --git a/packages/native-core/src/utils/persistant_iterator.rs b/packages/native-core/src/utils/persistant_iterator.rs index 732789aac..96c2cffb7 100644 --- a/packages/native-core/src/utils/persistant_iterator.rs +++ b/packages/native-core/src/utils/persistant_iterator.rs @@ -218,7 +218,7 @@ fn traverse() { let mut vdom = VirtualDom::new(Base); let mutations = vdom.rebuild(); - let mut rdom: RealDom = RealDom::new(Box::new([])); + let mut rdom: RealDom = RealDom::new([]); let mut iter = PersistantElementIter::create(&mut rdom); let mut dioxus_state = DioxusState::create(&mut rdom); @@ -320,7 +320,7 @@ fn persist_removes() { } let mut vdom = VirtualDom::new(Base); - let mut rdom: RealDom = RealDom::new(Box::new([])); + let mut rdom: RealDom = RealDom::new([]); let build = vdom.rebuild(); println!("{build:#?}"); @@ -401,7 +401,7 @@ fn persist_instertions_before() { } let mut vdom = VirtualDom::new(Base); - let mut rdom: RealDom = RealDom::new(Box::new([])); + let mut rdom: RealDom = RealDom::new([]); let mut dioxus_state = DioxusState::create(&mut rdom); let build = vdom.rebuild(); @@ -459,7 +459,7 @@ fn persist_instertions_after() { } let mut vdom = VirtualDom::new(Base); - let mut rdom: RealDom = RealDom::new(Box::new([])); + let mut rdom: RealDom = RealDom::new([]); let mut iter = PersistantElementIter::create(&mut rdom); let mut dioxus_state = DioxusState::create(&mut rdom); diff --git a/packages/native-core/tests/called_minimally_on_build.rs b/packages/native-core/tests/called_minimally_on_build.rs index a55d8adfa..394cf464a 100644 --- a/packages/native-core/tests/called_minimally_on_build.rs +++ b/packages/native-core/tests/called_minimally_on_build.rs @@ -140,7 +140,7 @@ macro_rules! test_state{ let mutations = vdom.rebuild(); - let mut dom: RealDom = RealDom::new(Box::new([$( <$state>::to_type_erased() ),*])); + let mut dom: RealDom = RealDom::new([$( <$state>::to_type_erased() ),*]); let mut dioxus_state = DioxusState::create(&mut dom); dioxus_state.apply_mutations(&mut dom, mutations); diff --git a/packages/native-core/tests/fuzzing.rs b/packages/native-core/tests/fuzzing.rs index e34a953c7..5acdc33c0 100644 --- a/packages/native-core/tests/fuzzing.rs +++ b/packages/native-core/tests/fuzzing.rs @@ -346,7 +346,7 @@ fn create() { }, ); let mutations = vdom.rebuild(); - let mut rdom: RealDom = RealDom::new(Box::new([BlablaState::to_type_erased()])); + let mut rdom: RealDom = RealDom::new([BlablaState::to_type_erased()]); let mut dioxus_state = DioxusState::create(&mut rdom); dioxus_state.apply_mutations(&mut rdom, mutations); @@ -368,7 +368,7 @@ fn diff() { }, ); let mutations = vdom.rebuild(); - let mut rdom: RealDom = RealDom::new(Box::new([BlablaState::to_type_erased()])); + let mut rdom: RealDom = RealDom::new([BlablaState::to_type_erased()]); let mut dioxus_state = DioxusState::create(&mut rdom); dioxus_state.apply_mutations(&mut rdom, mutations); diff --git a/packages/native-core/tests/miri_native.rs b/packages/native-core/tests/miri_native.rs index 87888d188..75f5b6eb4 100644 --- a/packages/native-core/tests/miri_native.rs +++ b/packages/native-core/tests/miri_native.rs @@ -131,9 +131,9 @@ fn native_core_is_okay() { .unwrap(); rt.block_on(async { - let rdom = Arc::new(Mutex::new(RealDom::new(Box::new([ + let rdom = Arc::new(Mutex::new(RealDom::new([ BlablaState::to_type_erased(), - ])))); + ]))); let mut dioxus_state = DioxusState::create(&mut *rdom.lock().unwrap()); let mut dom = VirtualDom::new(app); diff --git a/packages/native-core/tests/passes.rs b/packages/native-core/tests/passes.rs index 7f69b8968..368333fb5 100644 --- a/packages/native-core/tests/passes.rs +++ b/packages/native-core/tests/passes.rs @@ -50,7 +50,7 @@ fn node_pass() { } } - let mut tree: RealDom = RealDom::new(Box::new([Number::to_type_erased()])); + let mut tree: RealDom = RealDom::new([Number::to_type_erased()]); tree.update_state(SendAnyMap::new()); assert_eq!( @@ -140,10 +140,10 @@ fn dependant_node_pass() { } } - let mut tree: RealDom = RealDom::new(Box::new([ + let mut tree: RealDom = RealDom::new([ AddNumber::to_type_erased(), SubtractNumber::to_type_erased(), - ])); + ]); tree.update_state(SendAnyMap::new()); let root = tree.get(tree.root_id()).unwrap(); @@ -243,10 +243,10 @@ fn independant_node_pass() { } } - let mut tree: RealDom = RealDom::new(Box::new([ + let mut tree: RealDom = RealDom::new([ AddNumber::to_type_erased(), SubtractNumber::to_type_erased(), - ])); + ]); tree.update_state(SendAnyMap::new()); let root = tree.get(tree.root_id()).unwrap(); @@ -320,7 +320,7 @@ fn down_pass() { } } - let mut tree: RealDom = RealDom::new(Box::new([AddNumber::to_type_erased()])); + let mut tree: RealDom = RealDom::new([AddNumber::to_type_erased()]); let grandchild1 = tree.create_node(create_blank_element()); let grandchild1 = grandchild1.id(); let mut child1 = tree.create_node(create_blank_element()); @@ -406,7 +406,7 @@ fn up_pass() { } } - let mut tree: RealDom = RealDom::new(Box::new([AddNumber::to_type_erased()])); + let mut tree: RealDom = RealDom::new([AddNumber::to_type_erased()]); let grandchild1 = tree.create_node(create_blank_element()); let grandchild1 = grandchild1.id(); let mut child1 = tree.create_node(create_blank_element()); diff --git a/packages/rink/src/lib.rs b/packages/rink/src/lib.rs index 1111d73dc..9fceb6b48 100644 --- a/packages/rink/src/lib.rs +++ b/packages/rink/src/lib.rs @@ -84,12 +84,12 @@ pub fn render( UnboundedSender, ) -> R, ) -> Result<()> { - let mut rdom = RealDom::new(Box::new([ + let mut rdom = RealDom::new([ TaffyLayout::to_type_erased(), Focus::to_type_erased(), StyleModifier::to_type_erased(), PreventDefault::to_type_erased(), - ])); + ]); let (handler, mut register_event) = RinkInputHandler::create(&mut rdom); From 8b5551e5d2d12adabdac8a66a4c086599e34858a Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 14 Mar 2023 17:26:59 -0500 Subject: [PATCH 075/103] fix formatting --- packages/native-core-macro/src/lib.rs | 10 +++++----- packages/native-core/tests/miri_native.rs | 4 +--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index 159717c75..43fed66ac 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -302,8 +302,8 @@ pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream { // if a create function is defined, we don't generate one // otherwise we generate a default one that uses the update function and the default constructor - let create_fn = (!has_create_fn).then(||{ - quote!{ + let create_fn = (!has_create_fn).then(|| { + quote! { fn create<'a>( node_view: dioxus_native_core::prelude::NodeView<()>, node: ::ElementBorrowed<'a>, @@ -329,7 +329,7 @@ pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream { use dioxus_native_core::exports::shipyard::{IntoWorkloadSystem, Get, AddComponent}; use dioxus_native_core::tree::TreeRef; use dioxus_native_core::prelude::{NodeType, NodeView}; - + let node_mask = Self::NODE_MASK.build(); (move |data: #combined_dependencies_quote, run_view: dioxus_native_core::prelude::RunPassView #trait_generics| { @@ -351,7 +351,7 @@ pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream { #deref_child_view let view = NodeView::new(id, node_data, &node_mask); - if let Some(myself) = myself { + if let Some(myself) = myself { myself .update(view, node, parent, children, context) } @@ -360,7 +360,7 @@ pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream { id, Self::create(view, node, parent, children, context)); true - } + } }) }).into_workload_system().unwrap() } diff --git a/packages/native-core/tests/miri_native.rs b/packages/native-core/tests/miri_native.rs index 75f5b6eb4..75caf40c8 100644 --- a/packages/native-core/tests/miri_native.rs +++ b/packages/native-core/tests/miri_native.rs @@ -131,9 +131,7 @@ fn native_core_is_okay() { .unwrap(); rt.block_on(async { - let rdom = Arc::new(Mutex::new(RealDom::new([ - BlablaState::to_type_erased(), - ]))); + let rdom = Arc::new(Mutex::new(RealDom::new([BlablaState::to_type_erased()]))); let mut dioxus_state = DioxusState::create(&mut *rdom.lock().unwrap()); let mut dom = VirtualDom::new(app); From ed6d813a9c5c75c207374e5951617beaa2333fcd Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 14 Mar 2023 17:31:21 -0500 Subject: [PATCH 076/103] update Rink and Dioxus-TUI readmes --- packages/dioxus-tui/README.md | 18 +++++++++--------- packages/rink/README.md | 32 ++++++++------------------------ 2 files changed, 17 insertions(+), 33 deletions(-) diff --git a/packages/dioxus-tui/README.md b/packages/dioxus-tui/README.md index aa0c4b3fc..cae1cecce 100644 --- a/packages/dioxus-tui/README.md +++ b/packages/dioxus-tui/README.md @@ -1,5 +1,5 @@
-

Rink

+

Dioxus TUI

Beautiful terminal user interfaces in Rust with Dioxus .

@@ -37,7 +37,6 @@
-
Leverage React-like patterns, CSS, HTML, and Rust to build beautiful, portable, terminal user interfaces with Dioxus. @@ -63,33 +62,34 @@ fn app(cx: Scope) -> Element { You can use Html-like semantics with inline styles, tree hierarchy, components, and more in your [`text-based user interface (TUI)`](https://en.wikipedia.org/wiki/Text-based_user_interface) application. -Rink is essentially a port of [Ink](https://github.com/vadimdemedes/ink) but for [`Rust`](https://www.rust-lang.org/) and [`Dioxus`](https://dioxuslabs.com/). Rink doesn't depend on Node.js or any other JavaScript runtime, so your binaries are portable and beautiful. +Dioxus TUI is essentially a port of [Ink](https://github.com/vadimdemedes/ink) but for [`Rust`](https://www.rust-lang.org/) and [`Dioxus`](https://dioxuslabs.com/). Dioxus TUI doesn't depend on Node.js or any other JavaScript runtime, so your binaries are portable and beautiful. ## Limitations - **Subset of Html** -Terminals can only render a subset of HTML. We support as much as we can. + Terminals can only render a subset of HTML. We support as much as we can. - **Particular frontend design** -Terminals and browsers are and look different. Therefore, the same design might not be the best to cover both renderers. - + Terminals and browsers are and look different. Therefore, the same design might not be the best to cover both renderers. ## Status -**WARNING: Rink is currently under construction!** +**WARNING: Dioxus TUI is currently under construction!** Rendering a VirtualDom works fine, but the ecosystem of hooks is not yet ready. Additionally, some bugs in the flexbox implementation might be quirky at times. ## Features -Rink features: +Dioxus TUI features: + - [x] Flexbox-based layout system - [ ] CSS selectors - [x] inline CSS support - [x] Built-in focusing system + * [x] Widgets1 * [ ] Support for events, hooks, and callbacks2 * [ ] Html tags3 1 Currently only a subset of the input element is implemented as a component (not an element). The `Input` component supports sliders, text, numbers, passwords, buttons, and checkboxes. 2 Basic keyboard, mouse, and focus events are implemented. -3 Currently, most HTML tags don't translate into any meaning inside of Dioxus TUI. So an `input` *element* won't mean anything nor does it have any additional functionality. +3 Currently, most HTML tags don't translate into any meaning inside of Dioxus TUI. So an `input` _element_ won't mean anything nor does it have any additional functionality. diff --git a/packages/rink/README.md b/packages/rink/README.md index d021a728b..0c327db13 100644 --- a/packages/rink/README.md +++ b/packages/rink/README.md @@ -34,22 +34,7 @@
-Leverage CSS, HTML, and Rust to build beautiful, portable, terminal user interfaces. - -```rust -fn app(cx: Scope) -> Element { - cx.render(rsx!{ - div { - width: "100%", - height: "10px", - background_color: "red", - justify_content: "center", - align_items: "center", - "Hello world!" - } - }) -} -``` +Leverage CSS, HTML, and Rust to build beautiful, portable, terminal user interfaces. Rink is the cross-framework library that powers [`Dioxus-TUI`](https://github.com/DioxusLabs/dioxus/tree/master/packages/dioxus-tui) ![demo app](examples/example.png) @@ -57,7 +42,7 @@ fn app(cx: Scope) -> Element { You can use Html-like semantics with inline styles, tree hierarchy, components, and more in your [`text-based user interface (TUI)`](https://en.wikipedia.org/wiki/Text-based_user_interface) application. -Rink is essentially a port of [Ink](https://github.com/vadimdemedes/ink) but for [`Rust`](https://www.rust-lang.org/) and [`Dioxus`](https://dioxuslabs.com/). Rink doesn't depend on Node.js or any other JavaScript runtime, so your binaries are portable and beautiful. +Rink is essentially a port of [Ink](https://github.com/vadimdemedes/ink) but for [`Rust`](https://www.rust-lang.org/). Rink doesn't depend on Node.js or any other JavaScript runtime, so your binaries are portable and beautiful. ## Limitations @@ -70,7 +55,7 @@ Rink is essentially a port of [Ink](https://github.com/vadimdemedes/ink) but for **WARNING: Rink is currently under construction!** -Rendering a VirtualDom works fine, but the ecosystem of hooks is not yet ready. Additionally, some bugs in the flexbox implementation might be quirky at times. +Rendering a Dom works fine, but the ecosystem of widgets is not ready yet. Additionally, some bugs in the flexbox implementation might be quirky at times. ## Features @@ -81,10 +66,9 @@ Rink features: - [x] inline CSS support - [x] Built-in focusing system -* [x] Widgets1 -* [ ] Support for events, hooks, and callbacks2 -* [ ] Html tags3 +* [ ] Widgets +* [ ] Support for events, hooks, and callbacks1 +* [ ] Html tags2 -1 Currently only a subset of the input element is implemented as a component (not an element). The `Input` component supports sliders, text, numbers, passwords, buttons, and checkboxes. -2 Basic keyboard, mouse, and focus events are implemented. -3 Currently, most HTML tags don't translate into any meaning inside of Dioxus TUI. So an `input` _element_ won't mean anything nor does it have any additional functionality. +1 Basic keyboard, mouse, and focus events are implemented. +2 Currently, most HTML tags don't translate into any meaning inside of Rink. So an `input` _element_ won't mean anything nor does it have any additional functionality. From 6ee1c5ea1683adb93e233eba706c9ed179626611 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 14 Mar 2023 17:58:42 -0500 Subject: [PATCH 077/103] fix custom renderer imports --- docs/guide/Cargo.toml | 3 +++ docs/guide/examples/custom_renderer.rs | 18 ++++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/docs/guide/Cargo.toml b/docs/guide/Cargo.toml index c9707e0d7..227bdfa35 100644 --- a/docs/guide/Cargo.toml +++ b/docs/guide/Cargo.toml @@ -11,10 +11,13 @@ dioxus = { path = "../../packages/dioxus" } dioxus-desktop = { path = "../../packages/desktop" } dioxus-web = { path = "../../packages/web" } dioxus-ssr = { path = "../../packages/ssr" } +dioxus-native-core = { path = "../../packages/native-core" } +dioxus-native-core-macro = { path = "../../packages/native-core-macro" } dioxus-router = { path = "../../packages/router" } dioxus-liveview = { path = "../../packages/liveview", features = ["axum"] } dioxus-tui = { path = "../../packages/dioxus-tui" } fermi = { path = "../../packages/fermi" } +shipyard = "0.6.2" # dioxus = { path = "../../packages/dioxus", features = ["desktop", "web", "ssr", "router", "fermi", "tui"] } diff --git a/docs/guide/examples/custom_renderer.rs b/docs/guide/examples/custom_renderer.rs index 17590f18c..13993fe7b 100644 --- a/docs/guide/examples/custom_renderer.rs +++ b/docs/guide/examples/custom_renderer.rs @@ -1,7 +1,9 @@ +use dioxus::html::input_data::keyboard_types::{Code, Key, Modifiers}; use dioxus::prelude::*; use dioxus_native_core::exports::shipyard::Component; use dioxus_native_core::node_ref::*; use dioxus_native_core::prelude::*; +use dioxus_native_core::utils::cursor::{Cursor, Pos}; use dioxus_native_core_macro::partial_derive_state; // ANCHOR: state_impl @@ -284,20 +286,20 @@ impl State for MyState { } // ANCHOR_END: derive_state +#[allow(unused)] // ANCHOR: cursor fn text_editing() { let mut cursor = Cursor::default(); let mut text = String::new(); - let keyboard_data = 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(), - ); // handle keyboard input with a max text length of 10 - cursor.handle_input(&keyboard_data, &mut text, 10); + cursor.handle_input( + &Code::ArrowRight, + &Key::ArrowRight, + &Modifiers::empty(), + &mut text, + 10, + ); // mannually select text between characters 0-5 on the first line (this could be from dragging with a mouse) cursor.start = Pos::new(0, 0); From a974fb3a8a2defb425a578ea6e09cd0e923856cb Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 14 Mar 2023 18:07:52 -0500 Subject: [PATCH 078/103] fix clippy lints in native-core tests --- packages/native-core/tests/miri_native.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/native-core/tests/miri_native.rs b/packages/native-core/tests/miri_native.rs index 75caf40c8..adcfe03f1 100644 --- a/packages/native-core/tests/miri_native.rs +++ b/packages/native-core/tests/miri_native.rs @@ -132,11 +132,11 @@ fn native_core_is_okay() { rt.block_on(async { let rdom = Arc::new(Mutex::new(RealDom::new([BlablaState::to_type_erased()]))); - let mut dioxus_state = DioxusState::create(&mut *rdom.lock().unwrap()); + let mut dioxus_state = DioxusState::create(&mut rdom.lock().unwrap()); let mut dom = VirtualDom::new(app); let mutations = dom.rebuild(); - dioxus_state.apply_mutations(&mut *rdom.lock().unwrap(), mutations); + dioxus_state.apply_mutations(&mut rdom.lock().unwrap(), mutations); let ctx = SendAnyMap::new(); rdom.lock().unwrap().update_state(ctx); @@ -145,7 +145,7 @@ fn native_core_is_okay() { dom.wait_for_work().await; let mutations = dom.render_immediate(); - dioxus_state.apply_mutations(&mut *rdom.lock().unwrap(), mutations); + dioxus_state.apply_mutations(&mut rdom.lock().unwrap(), mutations); let ctx = SendAnyMap::new(); rdom.lock().unwrap().update_state(ctx); From 9a16687bdcd3f569561caeb464b2295256774c48 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 18 Mar 2023 07:16:53 -0500 Subject: [PATCH 079/103] fix custom attribute values and add example --- packages/native-core-macro/src/lib.rs | 2 +- packages/native-core/examples/custom_attr.rs | 231 +++++++++++++++++++ packages/native-core/src/node.rs | 12 +- packages/native-core/src/passes.rs | 2 +- 4 files changed, 239 insertions(+), 8 deletions(-) create mode 100644 packages/native-core/examples/custom_attr.rs diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index 43fed66ac..fd25c3868 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -305,7 +305,7 @@ pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream { let create_fn = (!has_create_fn).then(|| { quote! { fn create<'a>( - node_view: dioxus_native_core::prelude::NodeView<()>, + node_view: dioxus_native_core::prelude::NodeView # trait_generics, node: ::ElementBorrowed<'a>, parent: Option<::ElementBorrowed<'a>>, children: Vec<::ElementBorrowed<'a>>, diff --git a/packages/native-core/examples/custom_attr.rs b/packages/native-core/examples/custom_attr.rs new file mode 100644 index 000000000..5a1fcfb8c --- /dev/null +++ b/packages/native-core/examples/custom_attr.rs @@ -0,0 +1,231 @@ +use dioxus_native_core::exports::shipyard::Component; +use dioxus_native_core::node_ref::*; +use dioxus_native_core::prelude::*; +use dioxus_native_core::real_dom::NodeTypeMut; +use dioxus_native_core_macro::partial_derive_state; + +struct FontSize(f64); + +// All states need to derive Component +#[derive(Default, Debug, Copy, Clone, Component)] +struct Size(f64, f64); + +#[derive(Default, Debug, Copy, Clone, Component)] +struct X; + +impl FromAnyValue for X { + fn from_any_value(_: &dyn std::any::Any) -> Self { + X + } +} + +/// Derive some of the boilerplate for the State implementation +#[partial_derive_state] +impl State for Size { + type ParentDependencies = (); + + // The size of the current node depends on the size of its children + type ChildDependencies = (Self,); + + type NodeDependencies = (); + + // Size only cares about the width, height, and text parts of the current node + const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new() + // Get access to the width and height attributes + .with_attrs(AttributeMaskBuilder::Some(&["width", "height"])) + // Get access to the text of the node + .with_text(); + + fn update<'a>( + &mut self, + node_view: NodeView, + _node: ::ElementBorrowed<'a>, + _parent: Option<::ElementBorrowed<'a>>, + children: Vec<::ElementBorrowed<'a>>, + context: &SendAnyMap, + ) -> bool { + let font_size = context.get::().unwrap().0; + let mut width; + let mut height; + if let Some(text) = node_view.text() { + // if the node has text, use the text to size our object + width = text.len() as f64 * font_size; + height = font_size; + } else { + // otherwise, the size is the maximum size of the children + width = children + .iter() + .map(|(item,)| item.0) + .reduce(|accum, item| if accum >= item { accum } else { item }) + .unwrap_or(0.0); + + height = children + .iter() + .map(|(item,)| item.1) + .reduce(|accum, item| if accum >= item { accum } else { item }) + .unwrap_or(0.0); + } + // if the node contains a width or height attribute it overrides the other size + for a in node_view.attributes().into_iter().flatten() { + match &*a.attribute.name { + "width" => width = a.value.as_float().unwrap(), + "height" => height = a.value.as_float().unwrap(), + // because Size only depends on the width and height, no other attributes will be passed to the member + _ => panic!(), + } + } + // to determine what other parts of the dom need to be updated we return a boolean that marks if this member changed + let changed = (width != self.0) || (height != self.1); + *self = Self(width, height); + changed + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Default, Component)] +struct TextColor { + r: u8, + g: u8, + b: u8, +} + +#[partial_derive_state] +impl State for TextColor { + // TextColor depends on the TextColor part of the parent + type ParentDependencies = (Self,); + + type ChildDependencies = (); + + type NodeDependencies = (); + + // TextColor only cares about the color attribute of the current node + const NODE_MASK: NodeMaskBuilder<'static> = + // Get access to the color attribute + NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["color"])); + + fn update<'a>( + &mut self, + node_view: NodeView, + _node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + _children: Vec<::ElementBorrowed<'a>>, + _context: &SendAnyMap, + ) -> bool { + // TextColor only depends on the color tag, so getting the first tag is equivilent to looking through all tags + let new = match node_view + .attributes() + .and_then(|mut attrs| attrs.next()) + .and_then(|attr| attr.value.as_text()) + { + // if there is a color tag, translate it + Some("red") => TextColor { r: 255, g: 0, b: 0 }, + Some("green") => TextColor { r: 0, g: 255, b: 0 }, + Some("blue") => TextColor { r: 0, g: 0, b: 255 }, + Some(color) => panic!("unknown color {color}"), + // otherwise check if the node has a parent and inherit that color + None => match parent { + Some((parent,)) => *parent, + None => Self::default(), + }, + }; + // check if the member has changed + let changed = new != *self; + *self = new; + changed + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Default, Component)] +struct Border(bool); + +#[partial_derive_state] +impl State for Border { + // TextColor depends on the TextColor part of the parent + type ParentDependencies = (Self,); + + type ChildDependencies = (); + + type NodeDependencies = (); + + // Border does not depended on any other member in the current node + const NODE_MASK: NodeMaskBuilder<'static> = + // Get access to the border attribute + NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["border"])); + + fn update<'a>( + &mut self, + node_view: NodeView, + _node: ::ElementBorrowed<'a>, + _parent: Option<::ElementBorrowed<'a>>, + _children: Vec<::ElementBorrowed<'a>>, + _context: &SendAnyMap, + ) -> bool { + // check if the node contians a border attribute + let new = Self( + node_view + .attributes() + .and_then(|mut attrs| attrs.next().map(|a| a.attribute.name == "border")) + .is_some(), + ); + // check if the member has changed + let changed = new != *self; + *self = new; + changed + } +} + +fn main() -> Result<(), Box> { + let mut rdom: RealDom = RealDom::new([ + Border::to_type_erased(), + TextColor::to_type_erased(), + Size::to_type_erased(), + ]); + + let mut count = 0; + + // intial render + let text_id = rdom.create_node(format!("Count: {count}")).id(); + let mut root = rdom.get_mut(rdom.root_id()).unwrap(); + // set the color to red + if let NodeTypeMut::Element(mut element) = root.node_type_mut() { + element.set_attribute(("color", "style"), "red".to_string()); + } + root.add_child(text_id); + + let mut ctx = SendAnyMap::new(); + // set the font size to 3.3 + ctx.insert(FontSize(3.3)); + // update the State for nodes in the real_dom tree + let _to_rerender = rdom.update_state(ctx); + + // we need to run the vdom in a async runtime + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build()? + .block_on(async { + loop { + // update the count + count += 1; + let mut text = rdom.get_mut(text_id).unwrap(); + if let NodeTypeMut::Text(mut text) = text.node_type_mut() { + *text = format!("Count: {count}"); + } + + let mut ctx = SendAnyMap::new(); + ctx.insert(FontSize(3.3)); + let _to_rerender = rdom.update_state(ctx); + + // render... + rdom.traverse_depth_first(|node| { + let indent = " ".repeat(node.height() as usize); + let color = *node.get::().unwrap(); + let size = *node.get::().unwrap(); + let border = *node.get::().unwrap(); + let id = node.id(); + println!("{indent}{id:?} {color:?} {size:?} {border:?}"); + }); + + // wait 1 second + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + } + }) +} diff --git a/packages/native-core/src/node.rs b/packages/native-core/src/node.rs index 1cb39cff4..42ff8229f 100644 --- a/packages/native-core/src/node.rs +++ b/packages/native-core/src/node.rs @@ -62,13 +62,13 @@ pub enum NodeType { Placeholder, } -impl> From for NodeType { +impl> From for NodeType { fn from(text: S) -> Self { Self::Text(TextNode::new(text.into())) } } -impl From for NodeType { +impl From for NodeType { fn from(text: TextNode) -> Self { Self::Text(text) } @@ -136,25 +136,25 @@ pub enum OwnedAttributeValue { Custom(V), } -impl From for OwnedAttributeValue { +impl From for OwnedAttributeValue { fn from(value: String) -> Self { Self::Text(value) } } -impl From for OwnedAttributeValue { +impl From for OwnedAttributeValue { fn from(value: f64) -> Self { Self::Float(value) } } -impl From for OwnedAttributeValue { +impl From for OwnedAttributeValue { fn from(value: i64) -> Self { Self::Int(value) } } -impl From for OwnedAttributeValue { +impl From for OwnedAttributeValue { fn from(value: bool) -> Self { Self::Bool(value) } diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index d5c07f352..70dfc0f08 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -299,7 +299,7 @@ pub trait Dependancy { macro_rules! impl_dependancy { ($($t:ident),*) => { - impl< $($t: Send + Sync + Component + State),* > Dependancy for ($($t,)*) { + impl< $($t: Send + Sync + Component),* > Dependancy for ($($t,)*) { type ElementBorrowed<'a> = ($(DependancyView<'a, $t>,)*); fn type_ids() -> Box<[TypeId]> { From f0bcb3c2c602907e6d7a8e6e90efeff364876dd0 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 19 Mar 2023 07:14:35 -0500 Subject: [PATCH 080/103] make dioxus intigration generic over custom value type --- packages/native-core/src/dioxus.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/native-core/src/dioxus.rs b/packages/native-core/src/dioxus.rs index d288883b0..3e9ff9ee0 100644 --- a/packages/native-core/src/dioxus.rs +++ b/packages/native-core/src/dioxus.rs @@ -27,7 +27,7 @@ pub struct DioxusState { impl DioxusState { /// Initialize the DioxusState in the RealDom - pub fn create(rdom: &mut RealDom) -> Self { + pub fn create(rdom: &mut RealDom) -> Self { let root_id = rdom.root_id(); let mut root = rdom.get_mut(root_id).unwrap(); root.insert(ElementIdComponent(ElementId(0))); @@ -48,7 +48,11 @@ impl DioxusState { self.node_id_mapping.get(element_id.0).copied().flatten() } - fn set_element_id(&mut self, mut node: NodeMut, element_id: ElementId) { + fn set_element_id( + &mut self, + mut node: NodeMut, + element_id: ElementId, + ) { let node_id = node.id(); node.insert(ElementIdComponent(element_id)); if self.node_id_mapping.len() <= element_id.0 { @@ -57,7 +61,7 @@ impl DioxusState { self.node_id_mapping[element_id.0] = Some(node_id); } - fn load_child(&self, rdom: &RealDom, path: &[u8]) -> NodeId { + fn load_child(&self, rdom: &RealDom, path: &[u8]) -> NodeId { let mut current = rdom.get(*self.stack.last().unwrap()).unwrap(); for i in path { let new_id = current.child_ids()[*i as usize]; @@ -67,7 +71,11 @@ impl DioxusState { } /// 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, rdom: &mut RealDom, mutations: Mutations) { + pub fn apply_mutations( + &mut self, + rdom: &mut RealDom, + mutations: Mutations, + ) { for template in mutations.templates { let mut template_root_ids = Vec::new(); for root in template.roots { @@ -222,7 +230,10 @@ impl DioxusState { } } -fn create_template_node(rdom: &mut RealDom, node: &TemplateNode) -> NodeId { +fn create_template_node( + rdom: &mut RealDom, + node: &TemplateNode, +) -> NodeId { match node { TemplateNode::Element { tag, From b19e7ce4550f714ce0c073a6390b87657f018002 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 22 Mar 2023 09:17:27 -0500 Subject: [PATCH 081/103] fix non-bubbling listener hydration --- packages/web/src/rehydrate.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/web/src/rehydrate.rs b/packages/web/src/rehydrate.rs index d6afffea1..9af23534b 100644 --- a/packages/web/src/rehydrate.rs +++ b/packages/web/src/rehydrate.rs @@ -128,10 +128,11 @@ impl WebsysDom { mounted_id = Some(id); let name = attribute.name; if let AttributeValue::Listener(_) = value { + let event_name = &name[2..]; self.interpreter.new_event_listener( - &name[2..], + event_name, id.0 as u32, - event_bubbles(name) as u8, + event_bubbles(event_name) as u8, ); } } From 468f684d7d8496fd20066f9839ff2a84c8289849 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 22 Mar 2023 10:10:18 -0500 Subject: [PATCH 082/103] fix style attributes in SSR --- packages/ssr/src/cache.rs | 41 ++++++++++++++++++++++++++--- packages/ssr/src/renderer.rs | 50 ++++++++++++++++++++++++++++++++---- packages/ssr/tests/styles.rs | 40 +++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 8 deletions(-) create mode 100644 packages/ssr/tests/styles.rs diff --git a/packages/ssr/src/cache.rs b/packages/ssr/src/cache.rs index 8556682f1..e261d3b65 100644 --- a/packages/ssr/src/cache.rs +++ b/packages/ssr/src/cache.rs @@ -17,6 +17,12 @@ pub enum Segment { Attr(usize), Node(usize), PreRendered(String), + /// A marker for where to insert a dynamic styles + StyleMarker { + // If the marker is inside a style tag or not + // This will be true if there are static styles + inside_style_tag: bool, + }, } impl std::fmt::Write for StringChain { @@ -61,16 +67,45 @@ impl StringCache { } => { cur_path.push(root_idx); write!(chain, "<{tag}")?; + // we need to collect the styles and write them at the end + let mut styles = Vec::new(); + let mut has_dynamic_attrs = false; for attr in *attrs { match attr { - TemplateAttribute::Static { name, value, .. } => { - write!(chain, " {name}=\"{value}\"")?; + TemplateAttribute::Static { + name, + value, + namespace, + } => { + if let Some("style") = namespace { + styles.push((name, value)); + } else { + write!(chain, " {name}=\"{value}\"")?; + } } TemplateAttribute::Dynamic { id: index } => { - chain.segments.push(Segment::Attr(*index)) + chain.segments.push(Segment::Attr(*index)); + has_dynamic_attrs = true; } } } + + // write the styles + if !styles.is_empty() { + write!(chain, " style=\"")?; + for (name, value) in styles { + write!(chain, "{name}:{value};")?; + } + chain.segments.push(Segment::StyleMarker { + inside_style_tag: true, + }); + write!(chain, "\"")?; + } else if has_dynamic_attrs { + chain.segments.push(Segment::StyleMarker { + inside_style_tag: false, + }); + } + if children.is_empty() && tag_is_self_closing(tag) { write!(chain, "/>")?; } else { diff --git a/packages/ssr/src/renderer.rs b/packages/ssr/src/renderer.rs index 5b6fa8fb2..be4025dba 100644 --- a/packages/ssr/src/renderer.rs +++ b/packages/ssr/src/renderer.rs @@ -70,15 +70,24 @@ impl Renderer { .or_insert_with(|| Rc::new(StringCache::from_template(template).unwrap())) .clone(); + // We need to keep track of the dynamic styles so we can insert them into the right place + let mut accumulated_dynamic_styles = Vec::new(); + for segment in entry.segments.iter() { match segment { Segment::Attr(idx) => { let attr = &template.dynamic_attrs[*idx]; - match attr.value { - AttributeValue::Text(value) => write!(buf, " {}=\"{}\"", attr.name, value)?, - AttributeValue::Bool(value) => write!(buf, " {}={}", attr.name, value)?, - _ => {} - }; + if attr.namespace == Some("style") { + accumulated_dynamic_styles.push(attr); + } else { + match attr.value { + AttributeValue::Text(value) => { + write!(buf, " {}=\"{}\"", attr.name, value)? + } + AttributeValue::Bool(value) => write!(buf, " {}={}", attr.name, value)?, + _ => {} + }; + } } Segment::Node(idx) => match &template.dynamic_nodes[*idx] { DynamicNode::Component(node) => { @@ -128,6 +137,34 @@ impl Renderer { }, Segment::PreRendered(contents) => write!(buf, "{contents}")?, + + Segment::StyleMarker { inside_style_tag } => { + if !accumulated_dynamic_styles.is_empty() { + // if we are inside a style tag, we don't need to write the style attribute + if !*inside_style_tag { + write!(buf, " style=\"")?; + } + for attr in &accumulated_dynamic_styles { + match attr.value { + AttributeValue::Text(value) => { + write!(buf, "{}:{};", attr.name, value)? + } + AttributeValue::Bool(value) => { + write!(buf, "{}:{};", attr.name, value)? + } + AttributeValue::Float(f) => write!(buf, "{}:{};", attr.name, f)?, + AttributeValue::Int(i) => write!(buf, "{}:{};", attr.name, i)?, + _ => {} + }; + } + if !*inside_style_tag { + write!(buf, "\"")?; + } + + // clear the accumulated styles + accumulated_dynamic_styles.clear(); + } + } } } @@ -168,6 +205,9 @@ fn to_string_works() { vec![ PreRendered("
Hello world 1 -->".into(),), Node(0,), PreRendered( diff --git a/packages/ssr/tests/styles.rs b/packages/ssr/tests/styles.rs new file mode 100644 index 000000000..de77e715c --- /dev/null +++ b/packages/ssr/tests/styles.rs @@ -0,0 +1,40 @@ +use dioxus::prelude::*; + +#[test] +fn static_styles() { + fn app(cx: Scope) -> Element { + render! { div { width: "100px" } } + } + + let mut dom = VirtualDom::new(app); + _ = dom.rebuild(); + + assert_eq!( + dioxus_ssr::render(&dom), + r#"
"# + ); +} + +#[test] +fn partially_dynamic_styles() { + let dynamic = 123; + + assert_eq!( + dioxus_ssr::render_lazy(rsx! { + div { width: "100px", height: "{dynamic}px" } + }), + r#"
"# + ); +} + +#[test] +fn dynamic_styles() { + let dynamic = 123; + + assert_eq!( + dioxus_ssr::render_lazy(rsx! { + div { width: "{dynamic}px" } + }), + r#"
"# + ); +} From c8880fac36ac76774f4752f217452655b18ff949 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 22 Mar 2023 13:53:20 -0500 Subject: [PATCH 083/103] expose data directory in desktop config --- packages/desktop/src/cfg.rs | 10 ++++++++++ packages/desktop/src/webview.rs | 7 +++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/desktop/src/cfg.rs b/packages/desktop/src/cfg.rs index 3127a7639..7b37b46d4 100644 --- a/packages/desktop/src/cfg.rs +++ b/packages/desktop/src/cfg.rs @@ -18,6 +18,7 @@ pub struct Config { pub(crate) pre_rendered: Option, pub(crate) disable_context_menu: bool, pub(crate) resource_dir: Option, + pub(crate) data_dir: Option, pub(crate) custom_head: Option, pub(crate) custom_index: Option, pub(crate) root_name: String, @@ -44,6 +45,7 @@ impl Config { pre_rendered: None, disable_context_menu: !cfg!(debug_assertions), resource_dir: None, + data_dir: None, custom_head: None, custom_index: None, root_name: "main".to_string(), @@ -56,6 +58,14 @@ impl Config { self } + /// set the directory where data will be stored in release mode. + /// + /// > Note: This **must** be set when bundling on Windows. + pub fn with_data_directory(mut self, path: impl Into) -> Self { + self.data_dir = Some(path.into()); + self + } + /// Set whether or not the right-click context menu should be disabled. pub fn with_disable_context_menu(mut self, disable: bool) -> Self { self.disable_context_menu = disable; diff --git a/packages/desktop/src/webview.rs b/packages/desktop/src/webview.rs index a8f437614..c5d8b4b42 100644 --- a/packages/desktop/src/webview.rs +++ b/packages/desktop/src/webview.rs @@ -7,7 +7,7 @@ use tao::event_loop::{EventLoopProxy, EventLoopWindowTarget}; pub use wry; pub use wry::application as tao; use wry::application::window::Window; -use wry::webview::{WebView, WebViewBuilder}; +use wry::webview::{WebContext, WebView, WebViewBuilder}; pub fn build( cfg: &mut Config, @@ -33,6 +33,8 @@ pub fn build( )); } + let mut web_context = WebContext::new(cfg.data_dir.clone()); + let mut webview = WebViewBuilder::new(window) .unwrap() .with_transparent(cfg.window.window.transparent) @@ -52,7 +54,8 @@ pub fn build( .as_ref() .map(|handler| handler(window, evet)) .unwrap_or_default() - }); + }) + .with_web_context(&mut web_context); for (name, handler) in cfg.protocols.drain(..) { webview = webview.with_custom_protocol(name, handler) From e0563bd63717d662556cc3d152c5ee799bf16cc0 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 23 Mar 2023 13:15:47 -0500 Subject: [PATCH 084/103] allow paths in format expressions --- packages/rsx/src/ifmt.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/rsx/src/ifmt.rs b/packages/rsx/src/ifmt.rs index c7a3c2e26..8146cea42 100644 --- a/packages/rsx/src/ifmt.rs +++ b/packages/rsx/src/ifmt.rs @@ -59,6 +59,11 @@ impl FromStr for IfmtInput { let mut current_captured = String::new(); while let Some(c) = chars.next() { if c == ':' { + // two :s in a row is a path, not a format arg + if chars.next_if(|c| *c == ':').is_some() { + current_captured.push_str("::"); + continue; + } let mut current_format_args = String::new(); for c in chars.by_ref() { if c == '}' { From d658f501415aefb9061a95aab653b24e9a603e86 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 24 Mar 2023 09:34:40 -0700 Subject: [PATCH 085/103] publish autofmt --- packages/autofmt/Cargo.toml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/autofmt/Cargo.toml b/packages/autofmt/Cargo.toml index e409cfb2f..ccb6fed24 100644 --- a/packages/autofmt/Cargo.toml +++ b/packages/autofmt/Cargo.toml @@ -2,7 +2,13 @@ name = "dioxus-autofmt" version = "0.3.0" edition = "2021" - +authors = ["Jonathan Kelley"] +description = "Autofomatter for Dioxus RSX" +license = "MIT/Apache-2.0" +repository = "https://github.com/DioxusLabs/dioxus/" +homepage = "https://dioxuslabs.com" +documentation = "https://dioxuslabs.com" +keywords = ["dom", "ui", "gui", "react"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] @@ -11,7 +17,7 @@ proc-macro2 = { version = "1.0.6", features = ["span-locations"] } quote = "1.0" syn = { version = "1.0.11", features = ["full", "extra-traits"] } serde = { version = "1.0.136", features = ["derive"] } -prettyplease = { git = "https://github.com/DioxusLabs/prettyplease-macro-fmt.git", features = [ +prettyplease = { package = "prettier-please", version = "0.1.16", features = [ "verbatim", ] } From b06228be7047b02b39562f9b96a1ce08cb8a60b3 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 24 Mar 2023 09:39:52 -0700 Subject: [PATCH 086/103] publish rosetta --- packages/rsx-rosetta/Cargo.toml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/rsx-rosetta/Cargo.toml b/packages/rsx-rosetta/Cargo.toml index a0a9763e1..d160d2bf9 100644 --- a/packages/rsx-rosetta/Cargo.toml +++ b/packages/rsx-rosetta/Cargo.toml @@ -1,12 +1,19 @@ [package] name = "rsx-rosetta" -version = "0.0.0" -edition = "2018" +version = "0.3.0" +edition = "2021" +authors = ["Jonathan Kelley"] +description = "Autofomatter for Dioxus RSX" +license = "MIT/Apache-2.0" +repository = "https://github.com/DioxusLabs/dioxus/" +homepage = "https://dioxuslabs.com" +documentation = "https://dioxuslabs.com" +keywords = ["dom", "ui", "gui", "react"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-autofmt = { path = "../autofmt" } +dioxus-autofmt = { path = "../autofmt", version = "0.3.0" } dioxus-rsx = { path = "../rsx" , version = "^0.0.3" } html_parser = "0.6.3" proc-macro2 = "1.0.49" From 6bc9bc6ef95d054e92c8accca4d52e2554c39a0f Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 24 Mar 2023 12:57:19 -0500 Subject: [PATCH 087/103] fix nodes not being marked as dirty with multiple mutations --- packages/native-core/src/node_ref.rs | 2 +- packages/native-core/src/real_dom.rs | 22 +++++++++++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/native-core/src/node_ref.rs b/packages/native-core/src/node_ref.rs index 22ab5d8f1..218348c90 100644 --- a/packages/native-core/src/node_ref.rs +++ b/packages/native-core/src/node_ref.rs @@ -126,7 +126,7 @@ impl AttributeMask { pub fn union(&self, other: &Self) -> Self { match (self, other) { (AttributeMask::Some(s), AttributeMask::Some(o)) => { - AttributeMask::Some(s.intersection(o).cloned().collect()) + AttributeMask::Some(s.union(o).cloned().collect()) } _ => AttributeMask::All, } diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 1deec7d64..25d1ed9fe 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -186,6 +186,8 @@ impl RealDom { .entry(id) .or_default() .extend(self.dirty_nodes.passes.iter().map(|x| x.this_type_id)); + self.dirty_nodes + .mark_dirty(id, NodeMaskBuilder::ALL.build()); let watchers = self.node_watchers.clone(); for watcher in &*watchers.read().unwrap() { watcher.on_node_added(NodeMut::new(id, self)); @@ -216,16 +218,19 @@ impl RealDom { self.root_id } + /// Check if a node exists in the dom. + pub fn contains(&self, id: NodeId) -> bool { + self.tree_ref().contains(id) + } + /// Get a reference to a node. pub fn get(&self, id: NodeId) -> Option> { - self.tree_ref() - .contains(id) - .then_some(NodeRef { id, dom: self }) + self.contains(id).then_some(NodeRef { id, dom: self }) } /// Get a mutable reference to a node. pub fn get_mut(&mut self, id: NodeId) -> Option> { - let contains = self.tree_ref().contains(id); + let contains = self.contains(id); contains.then(|| NodeMut::new(id, self)) } @@ -399,7 +404,7 @@ impl<'a, V: Component + Send + Sync> DerefMut for ViewEntr } /// A immutable view of a node -pub trait NodeImmutable: Sized { +pub trait NodeImmutable: Sized { /// Get the real dom this node was created in fn real_dom(&self) -> &RealDom; @@ -573,7 +578,9 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { .or_default() .insert(TypeId::of::()); let view_mut: ViewMut = self.dom.borrow_raw().ok()?; - Some(ViewEntryMut::new(view_mut, self.id)) + view_mut + .contains(self.id) + .then_some(ViewEntryMut::new(view_mut, self.id)) } /// Insert a custom component into this node @@ -684,7 +691,8 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { for child in children_ids_vec { self.dom.get_mut(child).unwrap().remove(); } - self.dom.tree_mut().remove_single(id); + self.dom.tree_mut().remove(id); + self.real_dom_mut().raw_world_mut().delete_entity(id); } /// Replace this node with a different node From e4cb8c3aa0a4fc2a5915e7003a3e6bb7a05b58dd Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 24 Mar 2023 13:41:00 -0500 Subject: [PATCH 088/103] fix Dirty Nodes for large NodeIds --- packages/native-core/src/passes.rs | 31 +++++++----------------------- packages/native-core/src/tree.rs | 4 ++-- 2 files changed, 9 insertions(+), 26 deletions(-) diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index 70dfc0f08..a98f81600 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -16,34 +16,20 @@ use crate::{NodeId, NodeMask}; #[derive(Default)] struct DirtyNodes { - passes_dirty: Vec, + nodes_dirty: Vec, } impl DirtyNodes { pub fn add_node(&mut self, node_id: NodeId) { - let node_id = node_id.uindex(); - let index = node_id / 64; - let bit = node_id % 64; - let encoded = 1 << bit; - if let Some(passes) = self.passes_dirty.get_mut(index) { - *passes |= encoded; - } else { - self.passes_dirty.resize(index + 1, 0); - self.passes_dirty[index] |= encoded; - } + self.nodes_dirty.push(node_id); } pub fn is_empty(&self) -> bool { - self.passes_dirty.iter().all(|dirty| *dirty == 0) + self.nodes_dirty.is_empty() } - pub fn pop(&mut self) -> Option { - let index = self.passes_dirty.iter().position(|dirty| *dirty != 0)?; - let passes = self.passes_dirty[index]; - let node_id = passes.trailing_zeros(); - let encoded = 1 << node_id; - self.passes_dirty[index] &= !encoded; - Some((index * 64) + node_id as usize) + pub fn pop(&mut self) -> Option { + self.nodes_dirty.pop() } } @@ -76,7 +62,7 @@ impl DirtyNodeStates { } } - fn pop_front(&self, pass_id: TypeId) -> Option<(u16, usize)> { + fn pop_front(&self, pass_id: TypeId) -> Option<(u16, NodeId)> { let mut values = self.dirty.get(&pass_id)?.write(); let mut value = values.first_entry()?; let height = *value.key(); @@ -89,7 +75,7 @@ impl DirtyNodeStates { Some((height, id)) } - fn pop_back(&self, pass_id: TypeId) -> Option<(u16, usize)> { + fn pop_back(&self, pass_id: TypeId) -> Option<(u16, NodeId)> { let mut values = self.dirty.get(&pass_id)?.write(); let mut value = values.last_entry()?; let height = *value.key(); @@ -214,7 +200,6 @@ pub fn run_pass( match pass_direction { PassDirection::ParentToChild => { while let Some((height, id)) = dirty.pop_front(type_id) { - let id = tree.id_at(id).unwrap(); if (update_node)(id, ctx) { nodes_updated.insert(id); for id in tree.children_ids(id) { @@ -227,7 +212,6 @@ pub fn run_pass( } PassDirection::ChildToParent => { while let Some((height, id)) = dirty.pop_back(type_id) { - let id = tree.id_at(id).unwrap(); if (update_node)(id, ctx) { nodes_updated.insert(id); if let Some(id) = tree.parent_id(id) { @@ -240,7 +224,6 @@ pub fn run_pass( } PassDirection::AnyOrder => { while let Some((height, id)) = dirty.pop_back(type_id) { - let id = tree.id_at(id).unwrap(); if (update_node)(id, ctx) { nodes_updated.insert(id); for dependant in &dependants { diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index 99ac07d16..df25deac3 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -49,7 +49,7 @@ pub trait TreeMut: TreeRef { impl<'a> TreeRef for TreeRefView<'a> { fn parent_id(&self, id: NodeId) -> Option { - self.get(id).unwrap().parent + self.get(id).ok()?.parent } fn children_ids(&self, id: NodeId) -> Vec { @@ -59,7 +59,7 @@ impl<'a> TreeRef for TreeRefView<'a> { } fn height(&self, id: NodeId) -> Option { - Some(self.get(id).unwrap().height) + Some(self.get(id).ok()?.height) } fn contains(&self, id: NodeId) -> bool { From ec8b77ab3200fef11df718956ec21e81c7c187e4 Mon Sep 17 00:00:00 2001 From: Arniu Tseng Date: Sat, 25 Mar 2023 18:20:01 +0800 Subject: [PATCH 089/103] Remove unused simple-logger --- packages/router/Cargo.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index d14d9ae19..e1f442ab4 100644 --- a/packages/router/Cargo.toml +++ b/packages/router/Cargo.toml @@ -36,7 +36,6 @@ 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"] @@ -46,7 +45,6 @@ wasm_test = [] [dev-dependencies] console_error_panic_hook = "0.1.7" -log = "0.4.14" wasm-logger = "0.2.0" wasm-bindgen-test = "0.3" gloo-utils = "0.1.2" From a9b5c92fbb608121f93db5c3458c6843efb464fb Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 28 Mar 2023 08:35:57 -0500 Subject: [PATCH 090/103] avoid duplicating dirty nodes in native core --- packages/native-core/src/passes.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index a98f81600..2a84e6df4 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -16,12 +16,12 @@ use crate::{NodeId, NodeMask}; #[derive(Default)] struct DirtyNodes { - nodes_dirty: Vec, + nodes_dirty: FxHashSet, } impl DirtyNodes { pub fn add_node(&mut self, node_id: NodeId) { - self.nodes_dirty.push(node_id); + self.nodes_dirty.insert(node_id); } pub fn is_empty(&self) -> bool { @@ -29,10 +29,14 @@ impl DirtyNodes { } pub fn pop(&mut self) -> Option { - self.nodes_dirty.pop() + self.nodes_dirty.iter().next().copied().map(|id| { + self.nodes_dirty.remove(&id); + id + }) } } +/// Tracks the dirty nodes sorted by height for each pass. We resolve passes based on the height of the node in order to avoid resolving any node twice in a pass. #[derive(Clone, Unique)] pub struct DirtyNodeStates { dirty: Arc>>>, From 204ac8b2ed33f92566c55b234e0dc86350f6c4ca Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 28 Mar 2023 11:58:50 -0500 Subject: [PATCH 091/103] clarify mutations docs --- packages/core/src/mutations.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 551069c45..27976c157 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -68,7 +68,7 @@ pub enum Mutation<'a> { /// The ID of the element being mounted to id: ElementId, - /// The number of nodes on the stack + /// The number of nodes on the stack to append to the target element m: usize, }, @@ -155,7 +155,7 @@ pub enum Mutation<'a> { /// The ID of the node we're going to replace with id: ElementId, - /// The number of nodes on the stack to use to replace + /// The number of nodes on the stack to replace the target element with m: usize, }, @@ -167,7 +167,7 @@ pub enum Mutation<'a> { /// `[0,1,2]` represents 1st child's 2nd child's 3rd child. path: &'static [u8], - /// The number of nodes on the stack to use to replace + /// The number of nodes on the stack to replace the target element with m: usize, }, @@ -176,7 +176,7 @@ pub enum Mutation<'a> { /// The ID of the node to insert after. id: ElementId, - /// The ids of the nodes to insert after the target node. + /// The number of nodes on the stack to insert after the target node. m: usize, }, @@ -185,7 +185,7 @@ pub enum Mutation<'a> { /// The ID of the node to insert before. id: ElementId, - /// The ids of the nodes to insert before the target node. + /// The number of nodes on the stack to insert before the target node. m: usize, }, From 3bb6c4bc300b4358f105c2d0d909dac793e2cad3 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 28 Mar 2023 13:34:37 -0500 Subject: [PATCH 092/103] don't add no op mutations --- packages/core/src/diff.rs | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index fdc1a03f1..6f84b21aa 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -628,10 +628,12 @@ impl<'b> VirtualDom { } let id = self.find_last_element(&new[last]); - self.mutations.push(Mutation::InsertAfter { - id, - m: nodes_created, - }); + if nodes_created > 0 { + self.mutations.push(Mutation::InsertAfter { + id, + m: nodes_created, + }) + } nodes_created = 0; } @@ -652,10 +654,12 @@ impl<'b> VirtualDom { } let id = self.find_first_element(&new[last]); - self.mutations.push(Mutation::InsertBefore { - id, - m: nodes_created, - }); + if nodes_created > 0 { + self.mutations.push(Mutation::InsertBefore { + id, + m: nodes_created, + }); + } nodes_created = 0; } @@ -676,10 +680,12 @@ impl<'b> VirtualDom { } let id = self.find_first_element(&new[first_lis]); - self.mutations.push(Mutation::InsertBefore { - id, - m: nodes_created, - }); + if nodes_created > 0 { + self.mutations.push(Mutation::InsertBefore { + id, + m: nodes_created, + }); + } } } From f9b33f12cbd5cb6509d12b43b32b54167bcb3a17 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 28 Mar 2023 15:39:52 -0500 Subject: [PATCH 093/103] fix more complex workspaces with hot reloading --- .../src/hot_reload/hot_reloading_file_map.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/rsx/src/hot_reload/hot_reloading_file_map.rs b/packages/rsx/src/hot_reload/hot_reloading_file_map.rs index 2f0cc5246..0393142b4 100644 --- a/packages/rsx/src/hot_reload/hot_reloading_file_map.rs +++ b/packages/rsx/src/hot_reload/hot_reloading_file_map.rs @@ -29,7 +29,7 @@ pub struct FileMapBuildResult { pub struct FileMap { pub map: HashMap>)>, - in_workspace: HashMap, + in_workspace: HashMap>, phantom: std::marker::PhantomData, } @@ -112,11 +112,8 @@ impl FileMap { ) { // if the file!() macro is invoked in a workspace, the path is relative to the workspace root, otherwise it's relative to the crate root // we need to check if the file is in a workspace or not and strip the prefix accordingly - let prefix = if in_workspace { - crate_dir.parent().ok_or(io::Error::new( - io::ErrorKind::Other, - "Could not load workspace", - ))? + let prefix = if let Some(workspace) = &in_workspace { + workspace } else { crate_dir }; @@ -173,9 +170,9 @@ impl FileMap { Ok(UpdateResult::NeedsRebuild) } - fn child_in_workspace(&mut self, crate_dir: &Path) -> io::Result { + fn child_in_workspace(&mut self, crate_dir: &Path) -> io::Result> { if let Some(in_workspace) = self.in_workspace.get(crate_dir) { - Ok(*in_workspace) + Ok(in_workspace.clone()) } else { let mut cmd = Cmd::new(); let manafest_path = crate_dir.join("Cargo.toml"); @@ -186,9 +183,10 @@ impl FileMap { .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?; let in_workspace = metadata.workspace_root != crate_dir; + let workspace_path = in_workspace.then(|| metadata.workspace_root.into()); self.in_workspace - .insert(crate_dir.to_path_buf(), in_workspace); - Ok(in_workspace) + .insert(crate_dir.to_path_buf(), workspace_path.clone()); + Ok(workspace_path) } } } From 5f7a6e2f3f75072e2dd76d5652597697e3a4593e Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 31 Mar 2023 07:55:39 -0500 Subject: [PATCH 094/103] fix docs for the web cache setting --- packages/web/src/cfg.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/web/src/cfg.rs b/packages/web/src/cfg.rs index 7e47ed461..7483a7a8b 100644 --- a/packages/web/src/cfg.rs +++ b/packages/web/src/cfg.rs @@ -49,15 +49,16 @@ impl Config { /// Set the name of the element that Dioxus will use as the root. /// - /// This is akint to calling React.render() on the element with the specified name. + /// This is akin to calling React.render() on the element with the specified name. pub fn rootname(mut self, name: impl Into) -> Self { self.rootname = name.into(); self } - /// Set the name of the element that Dioxus will use as the root. + /// Sets a string cache for wasm bindgen to [intern](https://docs.rs/wasm-bindgen/0.2.84/wasm_bindgen/fn.intern.html). This can help reduce the time it takes for wasm bindgen to pass + /// strings from rust to javascript. This can significantly improve pefromance when passing strings to javascript, but can have a negative impact on startup time. /// - /// This is akint to calling React.render() on the element with the specified name. + /// > Currently this cache is only used when creating static elements and attributes. pub fn with_string_cache(mut self, cache: Vec) -> Self { self.cached_strings = cache; self From 1cc4260f70c4ed0388f2e8f15ff3bd22d4159328 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 1 Apr 2023 20:15:10 -0500 Subject: [PATCH 095/103] more resilient keyboard event deserialization --- packages/html/src/events/keyboard.rs | 39 +++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/packages/html/src/events/keyboard.rs b/packages/html/src/events/keyboard.rs index ee803d948..d00dcf8d2 100644 --- a/packages/html/src/events/keyboard.rs +++ b/packages/html/src/events/keyboard.rs @@ -5,6 +5,16 @@ use std::convert::TryInto; use std::fmt::{Debug, Formatter}; use std::str::FromStr; +#[cfg(feature = "serialize")] +fn resilient_deserialize_code<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + use serde::Deserialize; + // If we fail to deserialize the code for any reason, just return Unidentified instead of failing. + Ok(Code::deserialize(deserializer).unwrap_or(Code::Unidentified)) +} + pub type KeyboardEvent = Event; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, PartialEq, Eq)] @@ -27,6 +37,10 @@ pub struct KeyboardData { pub key_code: KeyCode, /// the physical key on the keyboard + #[cfg_attr( + feature = "serialize", + serde(deserialize_with = "resilient_deserialize_code") + )] code: Code, /// Indicate if the `alt` modifier key was pressed during this keyboard event @@ -102,7 +116,7 @@ impl KeyboardData { /// The value of the key pressed by the user, taking into consideration the state of modifier keys such as Shift as well as the keyboard locale and layout. pub fn key(&self) -> Key { #[allow(deprecated)] - FromStr::from_str(&self.key).expect("could not parse") + FromStr::from_str(&self.key).unwrap_or(Key::Unidentified) } /// A physical key on the keyboard (as opposed to the character generated by pressing the key). In other words, this property returns a value that isn't altered by keyboard layout or the state of the modifier keys. @@ -158,10 +172,24 @@ impl Debug for KeyboardData { } } -#[cfg_attr( - feature = "serialize", - derive(serde_repr::Serialize_repr, serde_repr::Deserialize_repr) -)] +#[cfg(feature = "serialize")] +impl<'de> serde::Deserialize<'de> for KeyCode { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + // We could be deserializing a unicode character, so we need to use u64 even if the output only takes u8 + let value = u64::deserialize(deserializer)?; + + if let Ok(smaller_uint) = value.try_into() { + Ok(KeyCode::from_raw_code(smaller_uint)) + } else { + Ok(KeyCode::Unknown) + } + } +} + +#[cfg_attr(feature = "serialize", derive(serde_repr::Serialize_repr))] #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(u8)] pub enum KeyCode { @@ -525,7 +553,6 @@ pub enum KeyCode { // kanji, = 244 // unlock trackpad (Chrome/Edge), = 251 // toggle touchpad, = 255 - #[cfg_attr(feature = "serialize", serde(other))] Unknown, } From 3aa7349db76127774dd4bcc0ec0b896391b13d8b Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Mon, 3 Apr 2023 09:03:33 +0800 Subject: [PATCH 096/103] Add more features to todomvc (#928) - toggle-all button - double click modify todo item - filter state show mouse pointer - individual todo item remove button - correct active item count --- examples/todomvc.rs | 125 ++++++++++++++++++++++++++++++-------------- 1 file changed, 86 insertions(+), 39 deletions(-) diff --git a/examples/todomvc.rs b/examples/todomvc.rs index b5277133e..4b25507a5 100644 --- a/examples/todomvc.rs +++ b/examples/todomvc.rs @@ -7,7 +7,7 @@ fn main() { dioxus_desktop::launch(app); } -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Clone, Copy)] pub enum FilterState { All, Active, @@ -39,56 +39,99 @@ pub fn app(cx: Scope<()>) -> Element { .collect::>(); filtered_todos.sort_unstable(); - let show_clear_completed = todos.values().any(|todo| todo.checked); - let items_left = filtered_todos.len(); - let item_text = match items_left { + let active_todo_count = todos.values().filter(|item| !item.checked).count(); + let active_todo_text = match active_todo_count { 1 => "item", _ => "items", }; - cx.render(rsx!{ + let show_clear_completed = todos.values().any(|todo| todo.checked); + + let selected = |state| { + if *filter == state { + "selected" + } else { + "false" + } + }; + + cx.render(rsx! { section { class: "todoapp", style { include_str!("./assets/todomvc.css") } - div { - header { class: "header", - h1 {"todos"} - input { - class: "new-todo", - placeholder: "What needs to be done?", - value: "{draft}", - autofocus: "true", - oninput: move |evt| { - draft.set(evt.value.clone()); - }, - onkeydown: move |evt| { - if evt.key() == Key::Enter && !draft.is_empty() { - todos.make_mut().insert( - **todo_id, - TodoItem { - id: **todo_id, - checked: false, - contents: draft.to_string(), - }, - ); - *todo_id.make_mut() += 1; - draft.set("".to_string()); - } + header { class: "header", + h1 {"todos"} + input { + class: "new-todo", + placeholder: "What needs to be done?", + value: "{draft}", + autofocus: "true", + oninput: move |evt| { + draft.set(evt.value.clone()); + }, + onkeydown: move |evt| { + if evt.key() == Key::Enter && !draft.is_empty() { + todos.make_mut().insert( + **todo_id, + TodoItem { + id: **todo_id, + checked: false, + contents: draft.to_string(), + }, + ); + *todo_id.make_mut() += 1; + draft.set("".to_string()); } } } + } + section { + class: "main", + if !todos.is_empty() { + rsx! { + input { + id: "toggle-all", + class: "toggle-all", + r#type: "checkbox", + onchange: move |_| { + let check = active_todo_count != 0; + for (_, item) in todos.make_mut().iter_mut() { + item.checked = check; + } + }, + checked: if active_todo_count == 0 { "true" } else { "false" }, + } + label { r#for: "toggle-all" } + } + } ul { class: "todo-list", - filtered_todos.iter().map(|id| rsx!(TodoEntry { key: "{id}", id: *id, todos: todos })) + filtered_todos.iter().map(|id| rsx!(TodoEntry { + key: "{id}", + id: *id, + todos: todos, + })) } (!todos.is_empty()).then(|| rsx!( footer { class: "footer", span { class: "todo-count", - strong {"{items_left} "} - span {"{item_text} left"} + strong {"{active_todo_count} "} + span {"{active_todo_text} left"} } ul { class: "filters", - li { class: "All", a { onclick: move |_| filter.set(FilterState::All), "All" }} - li { class: "Active", a { onclick: move |_| filter.set(FilterState::Active), "Active" }} - li { class: "Completed", a { onclick: move |_| filter.set(FilterState::Completed), "Completed" }} + for (state, state_text, url) in [ + (FilterState::All, "All", "#/"), + (FilterState::Active, "Active", "#/active"), + (FilterState::Completed, "Completed", "#/completed"), + ] { + li { + a { + href: url, + class: selected(state), + onclick: move |_| filter.set(state), + prevent_default: "onclick", + state_text + } + } + } } show_clear_completed.then(|| rsx!( button { @@ -102,8 +145,8 @@ pub fn app(cx: Scope<()>) -> Element { } } footer { class: "info", - p {"Double-click to edit a todo"} - p { "Created by ", a { href: "http://github.com/jkelleyrtp/", "jkelleyrtp" }} + p { "Double-click to edit a todo" } + p { "Created by ", a { href: "http://github.com/jkelleyrtp/", "jkelleyrtp" }} p { "Part of ", a { href: "http://todomvc.com", "TodoMVC" }} } }) @@ -136,13 +179,17 @@ pub fn TodoEntry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element { cx.props.todos.make_mut()[&cx.props.id].checked = evt.value.parse().unwrap(); } } - label { r#for: "cbg-{todo.id}", - onclick: move |_| is_editing.set(true), + ondblclick: move |_| is_editing.set(true), prevent_default: "onclick", "{todo.contents}" } + button { + class: "destroy", + onclick: move |_| { cx.props.todos.make_mut().remove(&todo.id); }, + prevent_default: "onclick", + } } is_editing.then(|| rsx!{ input { From c2dd963d86ef745a260d311978866f8f0bdcbfcb Mon Sep 17 00:00:00 2001 From: Antonio Curavalea <112291302+arqalite@users.noreply.github.com> Date: Tue, 4 Apr 2023 05:12:52 +0300 Subject: [PATCH 097/103] doc: fermi: add counter example to readme (#931) * doc: add counter example to readme * doc: fermi: revert use_read --- packages/fermi/README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/fermi/README.md b/packages/fermi/README.md index bd7d38ac4..f04ec5961 100644 --- a/packages/fermi/README.md +++ b/packages/fermi/README.md @@ -63,6 +63,26 @@ fn NameCard(cx: Scope) -> Element { } ``` +If needed, we can update the atom's value, based on itself: + +```rust, ignore +static COUNT: Atom = |_| 0; + +fn Counter(cx: Scope) -> Element { + let mut count = use_atom_state(cx, COUNT); + + cx.render(rsx!{ + p { + "{count}" + } + button { + onclick: move |_| count += 1, + "Increment counter" + } + }) +} +``` + It's that simple! ## Installation From 297c9a822e22921577d29f099f7acd73498baa70 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 5 Apr 2023 12:34:17 -0500 Subject: [PATCH 098/103] always update root id node refs --- packages/core/src/create.rs | 1 + packages/core/src/diff.rs | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index 113fa7ae1..80561e937 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -35,6 +35,7 @@ fn sort_bfs(paths: &[&'static [u8]]) -> Vec<(usize, &'static [u8])> { } #[test] +#[cfg(debug_assertions)] fn sorting() { let r: [(usize, &[u8]); 5] = [ (0, &[0, 1]), diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 6f84b21aa..56a2d05de 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -148,6 +148,13 @@ impl<'b> VirtualDom { // Make sure the roots get transferred over while we're here right_template.root_ids.transfer(&left_template.root_ids); + + // Update the node refs + for i in 0..right_template.root_ids.len() { + if let Some(root_id) = right_template.root_ids.get(i) { + self.update_template(root_id, right_template); + } + } } fn diff_dynamic_node( From dc0df321b1caee0061d4867a20d56466395309a2 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 5 Apr 2023 13:00:27 -0700 Subject: [PATCH 099/103] Fix wry segfaulting by commenting out webcontext --- packages/desktop/src/webview.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/desktop/src/webview.rs b/packages/desktop/src/webview.rs index c5d8b4b42..a8e1d4995 100644 --- a/packages/desktop/src/webview.rs +++ b/packages/desktop/src/webview.rs @@ -33,8 +33,6 @@ pub fn build( )); } - let mut web_context = WebContext::new(cfg.data_dir.clone()); - let mut webview = WebViewBuilder::new(window) .unwrap() .with_transparent(cfg.window.window.transparent) @@ -54,8 +52,11 @@ pub fn build( .as_ref() .map(|handler| handler(window, evet)) .unwrap_or_default() - }) - .with_web_context(&mut web_context); + }); + + // These are commented out because wry is currently broken in wry + // let mut web_context = WebContext::new(cfg.data_dir.clone()); + // .with_web_context(&mut web_context); for (name, handler) in cfg.protocols.drain(..) { webview = webview.with_custom_protocol(name, handler) From 44c1e2e033344d970d5a14bcd17e742c3ae99871 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 5 Apr 2023 13:06:25 -0700 Subject: [PATCH 100/103] clippy --- packages/desktop/src/webview.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/desktop/src/webview.rs b/packages/desktop/src/webview.rs index a8e1d4995..099a2bd94 100644 --- a/packages/desktop/src/webview.rs +++ b/packages/desktop/src/webview.rs @@ -7,7 +7,7 @@ use tao::event_loop::{EventLoopProxy, EventLoopWindowTarget}; pub use wry; pub use wry::application as tao; use wry::application::window::Window; -use wry::webview::{WebContext, WebView, WebViewBuilder}; +use wry::webview::{WebView, WebViewBuilder}; pub fn build( cfg: &mut Config, From 073c4140d9547741f578ed999877ab25cc0c163a Mon Sep 17 00:00:00 2001 From: Andrew Collins <50530928+ProfXwing@users.noreply.github.com> Date: Wed, 5 Apr 2023 22:09:17 -0400 Subject: [PATCH 101/103] added web context back and updated wry to latest --- packages/desktop/Cargo.toml | 2 +- packages/desktop/src/cfg.rs | 5 +++-- packages/desktop/src/lib.rs | 6 ++++-- packages/desktop/src/protocol.rs | 13 ++++++++----- packages/desktop/src/webview.rs | 15 +++++++-------- 5 files changed, 23 insertions(+), 18 deletions(-) diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 7645e4de5..5722a42a2 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -21,7 +21,7 @@ serde = "1.0.136" serde_json = "1.0.79" thiserror = "1.0.30" log = "0.4.14" -wry = { version = "0.23.4" } +wry = { version = "0.27.2" } futures-channel = "0.3.21" tokio = { version = "1.16.1", features = [ "sync", diff --git a/packages/desktop/src/cfg.rs b/packages/desktop/src/cfg.rs index 7b37b46d4..d7f1b5dfa 100644 --- a/packages/desktop/src/cfg.rs +++ b/packages/desktop/src/cfg.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::path::PathBuf; use wry::application::window::Icon; @@ -28,7 +29,7 @@ type DropHandler = Box bool>; pub(crate) type WryProtocol = ( String, - Box>) -> WryResult>> + 'static>, + Box>) -> WryResult>> + 'static>, ); impl Config { @@ -98,7 +99,7 @@ impl Config { /// Set a custom protocol pub fn with_custom_protocol(mut self, name: String, handler: F) -> Self where - F: Fn(&HttpRequest>) -> WryResult>> + 'static, + F: Fn(&HttpRequest>) -> WryResult>> + 'static, { self.protocols.push((name, Box::new(handler))); self diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 75c67dd22..434cf3305 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -36,8 +36,8 @@ use tao::{ }; pub use wry; pub use wry::application as tao; -use wry::application::window::WindowId; use wry::webview::WebView; +use wry::{application::window::WindowId, webview::WebContext}; /// Launch the WebView and run the event loop. /// @@ -281,7 +281,7 @@ fn create_new_window( event_handlers: &WindowEventHandlers, shortcut_manager: ShortcutRegistry, ) -> WebviewHandler { - let webview = webview::build(&mut cfg, event_loop, proxy.clone()); + let (webview, web_context) = webview::build(&mut cfg, event_loop, proxy.clone()); dom.base_scope().provide_context(DesktopContext::new( webview.clone(), @@ -299,6 +299,7 @@ fn create_new_window( webview, dom, waker: waker::tao_waker(proxy, id), + web_context, } } @@ -306,6 +307,7 @@ struct WebviewHandler { dom: VirtualDom, webview: Rc, waker: Waker, + web_context: WebContext, } /// Poll the virtualdom until it's pending diff --git a/packages/desktop/src/protocol.rs b/packages/desktop/src/protocol.rs index f8c002a6a..efdc849a5 100644 --- a/packages/desktop/src/protocol.rs +++ b/packages/desktop/src/protocol.rs @@ -1,5 +1,8 @@ use dioxus_interpreter_js::INTERPRETER_JS; -use std::path::{Path, PathBuf}; +use std::{ + borrow::Cow, + path::{Path, PathBuf}, +}; use wry::{ http::{status::StatusCode, Request, Response}, Result, @@ -27,7 +30,7 @@ pub(super) fn desktop_handler( custom_head: Option, custom_index: Option, root_name: &str, -) -> Result>> { +) -> Result>> { // If the request is for the root, we'll serve the index.html file. if request.uri().path() == "/" { // If a custom index is provided, just defer to that, expecting the user to know what they're doing. @@ -53,7 +56,7 @@ pub(super) fn desktop_handler( return Response::builder() .header("Content-Type", "text/html") - .body(body) + .body(Cow::from(body)) .map_err(From::from); } @@ -72,13 +75,13 @@ pub(super) fn desktop_handler( if asset.exists() { return Response::builder() .header("Content-Type", get_mime_from_path(&asset)?) - .body(std::fs::read(asset)?) + .body(Cow::from(std::fs::read(asset)?)) .map_err(From::from); } Response::builder() .status(StatusCode::NOT_FOUND) - .body(String::from("Not Found").into_bytes()) + .body(Cow::from(String::from("Not Found").into_bytes())) .map_err(From::from) } diff --git a/packages/desktop/src/webview.rs b/packages/desktop/src/webview.rs index 099a2bd94..c6909972f 100644 --- a/packages/desktop/src/webview.rs +++ b/packages/desktop/src/webview.rs @@ -7,13 +7,13 @@ use tao::event_loop::{EventLoopProxy, EventLoopWindowTarget}; pub use wry; pub use wry::application as tao; use wry::application::window::Window; -use wry::webview::{WebView, WebViewBuilder}; +use wry::webview::{WebContext, WebView, WebViewBuilder}; pub fn build( cfg: &mut Config, event_loop: &EventLoopWindowTarget, proxy: EventLoopProxy, -) -> Rc { +) -> (Rc, WebContext) { let builder = cfg.window.clone(); let window = builder.build(event_loop).unwrap(); let file_handler = cfg.file_drop_handler.take(); @@ -33,6 +33,8 @@ pub fn build( )); } + let mut web_context = WebContext::new(cfg.data_dir.clone()); + let mut webview = WebViewBuilder::new(window) .unwrap() .with_transparent(cfg.window.window.transparent) @@ -52,11 +54,8 @@ pub fn build( .as_ref() .map(|handler| handler(window, evet)) .unwrap_or_default() - }); - - // These are commented out because wry is currently broken in wry - // let mut web_context = WebContext::new(cfg.data_dir.clone()); - // .with_web_context(&mut web_context); + }) + .with_web_context(&mut web_context); for (name, handler) in cfg.protocols.drain(..) { webview = webview.with_custom_protocol(name, handler) @@ -82,5 +81,5 @@ pub fn build( webview = webview.with_devtools(true); } - Rc::new(webview.build().unwrap()) + (Rc::new(webview.build().unwrap()), web_context) } From fe75138e429fe3db055ca029ba9aad924eeba682 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 7 Apr 2023 09:35:32 -0500 Subject: [PATCH 102/103] fix dangerous_inner_html with SSR --- packages/ssr/src/cache.rs | 16 +++++++++++++++- packages/ssr/src/renderer.rs | 23 +++++++++++++++++++++-- packages/ssr/tests/inner_html.rs | 26 ++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 packages/ssr/tests/inner_html.rs diff --git a/packages/ssr/src/cache.rs b/packages/ssr/src/cache.rs index e261d3b65..a67fd5f22 100644 --- a/packages/ssr/src/cache.rs +++ b/packages/ssr/src/cache.rs @@ -23,6 +23,8 @@ pub enum Segment { // This will be true if there are static styles inside_style_tag: bool, }, + /// A marker for where to insert a dynamic inner html + InnerHtmlMarker, } impl std::fmt::Write for StringChain { @@ -69,6 +71,9 @@ impl StringCache { write!(chain, "<{tag}")?; // we need to collect the styles and write them at the end let mut styles = Vec::new(); + // we need to collect the inner html and write it at the end + let mut inner_html = None; + // we need to keep track of if we have dynamic attrs to know if we need to insert a style and inner_html marker let mut has_dynamic_attrs = false; for attr in *attrs { match attr { @@ -77,7 +82,9 @@ impl StringCache { value, namespace, } => { - if let Some("style") = namespace { + if *name == "dangerous_inner_html" { + inner_html = Some(value); + } else if let Some("style") = namespace { styles.push((name, value)); } else { write!(chain, " {name}=\"{value}\"")?; @@ -110,6 +117,13 @@ impl StringCache { write!(chain, "/>")?; } else { write!(chain, ">")?; + // Write the static inner html, or insert a marker if dynamic inner html is possible + if let Some(inner_html) = inner_html { + chain.write_str(inner_html)?; + } else if has_dynamic_attrs { + chain.segments.push(Segment::InnerHtmlMarker); + } + for child in *children { Self::recurse(child, cur_path, root_idx, chain)?; } diff --git a/packages/ssr/src/renderer.rs b/packages/ssr/src/renderer.rs index be4025dba..cfa08aee4 100644 --- a/packages/ssr/src/renderer.rs +++ b/packages/ssr/src/renderer.rs @@ -70,6 +70,8 @@ impl Renderer { .or_insert_with(|| Rc::new(StringCache::from_template(template).unwrap())) .clone(); + let mut inner_html = None; + // We need to keep track of the dynamic styles so we can insert them into the right place let mut accumulated_dynamic_styles = Vec::new(); @@ -77,7 +79,9 @@ impl Renderer { match segment { Segment::Attr(idx) => { let attr = &template.dynamic_attrs[*idx]; - if attr.namespace == Some("style") { + if attr.name == "dangerous_inner_html" { + inner_html = Some(attr); + } else if attr.namespace == Some("style") { accumulated_dynamic_styles.push(attr); } else { match attr.value { @@ -165,6 +169,19 @@ impl Renderer { accumulated_dynamic_styles.clear(); } } + + Segment::InnerHtmlMarker => { + if let Some(inner_html) = inner_html.take() { + let inner_html = &inner_html.value; + match inner_html { + AttributeValue::Text(value) => write!(buf, "{}", value)?, + AttributeValue::Bool(value) => write!(buf, "{}", value)?, + AttributeValue::Float(f) => write!(buf, "{}", f)?, + AttributeValue::Int(i) => write!(buf, "{}", i)?, + _ => {} + } + } + } } } @@ -208,7 +225,9 @@ fn to_string_works() { StyleMarker { inside_style_tag: false, }, - PreRendered(">Hello world 1 -->".into(),), + PreRendered(">".into()), + InnerHtmlMarker, + PreRendered("Hello world 1 -->".into(),), Node(0,), PreRendered( "<-- Hello world 2
nest 1
nest 2
".into(), diff --git a/packages/ssr/tests/inner_html.rs b/packages/ssr/tests/inner_html.rs new file mode 100644 index 000000000..11fb94746 --- /dev/null +++ b/packages/ssr/tests/inner_html.rs @@ -0,0 +1,26 @@ +use dioxus::prelude::*; + +#[test] +fn static_inner_html() { + fn app(cx: Scope) -> Element { + render! { div { dangerous_inner_html: "
1234
" } } + } + + let mut dom = VirtualDom::new(app); + _ = dom.rebuild(); + + assert_eq!(dioxus_ssr::render(&dom), r#"
1234
"#); +} + +#[test] +fn dynamic_inner_html() { + fn app(cx: Scope) -> Element { + let inner_html = "
1234
"; + render! { div { dangerous_inner_html: "{inner_html}" } } + } + + let mut dom = VirtualDom::new(app); + _ = dom.rebuild(); + + assert_eq!(dioxus_ssr::render(&dom), r#"
1234
"#); +} From 9571adea3068bee5f62e2229004cb873e8dcabaa Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 9 Apr 2023 13:42:40 -0700 Subject: [PATCH 103/103] add counter example --- examples/counter.rs | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 examples/counter.rs diff --git a/examples/counter.rs b/examples/counter.rs new file mode 100644 index 000000000..f51083a95 --- /dev/null +++ b/examples/counter.rs @@ -0,0 +1,36 @@ +//! Comparison example with leptos' counter example +//! https://github.com/leptos-rs/leptos/blob/main/examples/counters/src/lib.rs + +use dioxus::prelude::*; + +fn main() { + dioxus_desktop::launch(app); +} + +fn app(cx: Scope) -> Element { + let counters = use_state(cx, || vec![0, 0, 0]); + let sum: usize = counters.iter().copied().sum(); + + render! { + div { + button { onclick: move |_| counters.make_mut().push(0), "Add counter" } + button { onclick: move |_| { counters.make_mut().pop(); }, "Remove counter" } + p { "Total: {sum}" } + for (i, counter) in counters.iter().enumerate() { + li { + button { onclick: move |_| counters.make_mut()[i] -= 1, "-1" } + input { + value: "{counter}", + oninput: move |e| { + if let Ok(value) = e.value.parse::() { + counters.make_mut()[i] = value; + } + } + } + button { onclick: move |_| counters.make_mut()[i] += 1, "+1" } + button { onclick: move |_| { counters.make_mut().remove(i); }, "x" } + } + } + } + } +}
TutorialTutorial Web Desktop SSR