From 74e9d5977aee111d00ef37967f0c66f9f6068e42 Mon Sep 17 00:00:00 2001 From: = Date: Sun, 11 Dec 2022 17:31:15 -0600 Subject: [PATCH] add sync behind a feature flag --- packages/core/Cargo.toml | 1 + packages/core/src/lib.rs | 8 ++-- packages/core/src/nodes.rs | 68 ++++++++++++++++++++-------- packages/native-core/Cargo.toml | 6 ++- packages/native-core/src/node.rs | 4 +- packages/native-core/src/passes.rs | 46 ++++++++++++++++++- packages/native-core/src/real_dom.rs | 19 +++++++- packages/native-core/src/state.rs | 14 +++++- packages/native-core/src/tree.rs | 4 +- 9 files changed, 137 insertions(+), 33 deletions(-) diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 827968655..6d4ed51ef 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -44,3 +44,4 @@ dioxus = { path = "../dioxus" } [features] default = [] serialize = ["serde"] +sync_attributes = [] \ No newline at end of file diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index fd8a934f6..d15d7a032 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -70,10 +70,10 @@ pub(crate) mod innerlude { } pub use crate::innerlude::{ - fc_to_builder, AnyValueRc, Attribute, AttributeValue, Component, DynamicNode, Element, - ElementId, Event, Fragment, IntoAttributeValue, IntoDynNode, LazyNodes, Mutation, Mutations, - Properties, RenderReturn, Scope, ScopeId, ScopeState, Scoped, SuspenseContext, TaskId, - Template, TemplateAttribute, TemplateNode, VComponent, VNode, VText, VirtualDom, + fc_to_builder, AnyValue, AnyValueContainer, Attribute, AttributeValue, Component, DynamicNode, + Element, ElementId, Event, Fragment, IntoAttributeValue, IntoDynNode, LazyNodes, Mutation, + Mutations, Properties, RenderReturn, Scope, ScopeId, ScopeState, Scoped, SuspenseContext, + TaskId, Template, TemplateAttribute, TemplateNode, VComponent, VNode, VText, VirtualDom, }; /// The purpose of this module is to alleviate imports of many common types diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 3e8638a84..909ced495 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -338,7 +338,7 @@ pub enum AttributeValue<'a> { Listener(ListenerCb<'a>), /// An arbitrary value that implements PartialEq and is static - Any(AnyValueRc), + Any(AnyValueContainer), /// A "none" value, resulting in the removal of an attribute from the dom None, @@ -375,26 +375,38 @@ impl<'de, 'a> serde::Deserialize<'de> for ListenerCb<'a> { /// A boxed value that implements PartialEq and Any #[derive(Clone)] -pub struct AnyValueRc(pub Rc); +#[cfg(not(feature = "sync_attributes"))] +pub struct AnyValueContainer(pub Rc); -impl PartialEq for AnyValueRc { +#[derive(Clone)] +#[cfg(feature = "sync_attributes")] +pub struct AnyValueContainer(pub std::sync::Arc); + +impl PartialEq for AnyValueContainer { fn eq(&self, other: &Self) -> bool { self.0.any_cmp(other.0.as_ref()) } } -impl AnyValueRc { +impl AnyValueContainer { + pub fn new(value: T) -> Self { + #[cfg(feature = "sync_attributes")] + return Self(std::sync::Arc::new(value)); + #[cfg(not(feature = "sync_attributes"))] + return Self(Rc::new(value)); + } + /// Returns a reference to the inner value without checking the type. /// /// # Safety /// The caller must ensure that the type of the inner value is `T`. - pub unsafe fn downcast_ref_unchecked(&self) -> &T { - unsafe { &*(self.0.as_ref() as *const dyn AnyValue as *const T) } + pub unsafe fn downcast_ref_unchecked(&self) -> &T { + unsafe { &*(self.0.as_ref() as *const _ as *const T) } } /// Returns a reference to the inner value. - pub fn downcast_ref(&self) -> Option<&T> { - if self.0.type_id() == TypeId::of::() { + pub fn downcast_ref(&self) -> Option<&T> { + if self.0.our_typeid() == TypeId::of::() { Some(unsafe { self.downcast_ref_unchecked() }) } else { None @@ -402,25 +414,25 @@ impl AnyValueRc { } /// Checks if the inner value is of type `T`. - pub fn is(&self) -> bool { - self.0.type_id() == TypeId::of::() + pub fn is(&self) -> bool { + self.0.our_typeid() == TypeId::of::() } } #[test] fn test_any_value_rc() { - let a = AnyValueRc(Rc::new(1i32)); - assert!(a.is::()); - assert!(!a.is::()); + let a = AnyValueContainer::new(1i32); assert_eq!(a.downcast_ref::(), Some(&1i32)); assert_eq!(a.downcast_ref::(), None); + assert!(a.is::()); + assert!(!a.is::()); unsafe { assert_eq!(a.downcast_ref_unchecked::(), &1i32); } } #[cfg(feature = "serialize")] -impl serde::Serialize for AnyValueRc { +impl serde::Serialize for AnyValueContainer { fn serialize(&self, _: S) -> Result where S: serde::Serializer, @@ -430,7 +442,7 @@ impl serde::Serialize for AnyValueRc { } #[cfg(feature = "serialize")] -impl<'de> serde::Deserialize<'de> for AnyValueRc { +impl<'de> serde::Deserialize<'de> for AnyValueContainer { fn deserialize(_: D) -> Result where D: serde::Deserializer<'de>, @@ -467,19 +479,37 @@ impl<'a> PartialEq for AttributeValue<'a> { } } +#[cfg(feature = "sync_attributes")] +pub trait AnyValueBounds: Any + PartialEq + Sync {} +#[cfg(not(feature = "sync_attributes"))] +pub trait AnyValueBounds: Any + PartialEq {} + #[doc(hidden)] -pub trait AnyValue: Any { +#[cfg(feature = "sync_attributes")] +pub trait AnyValue: Sync { fn any_cmp(&self, other: &dyn AnyValue) -> bool; + fn our_typeid(&self) -> TypeId; } -impl AnyValue for T { +impl AnyValue for T { fn any_cmp(&self, other: &dyn AnyValue) -> bool { - if self.type_id() != other.type_id() { + if self.our_typeid() != other.our_typeid() { return false; } self == unsafe { &*(other as *const _ as *const T) } } + + fn our_typeid(&self) -> TypeId { + self.type_id() + } +} + +#[doc(hidden)] +#[cfg(not(feature = "sync_attributes"))] +pub trait AnyValue { + fn any_cmp(&self, other: &dyn AnyValue) -> bool; + fn our_typeid(&self) -> TypeId; } #[doc(hidden)] @@ -679,7 +709,7 @@ impl<'a> IntoAttributeValue<'a> for Arguments<'_> { } } -impl<'a> IntoAttributeValue<'a> for AnyValueRc { +impl<'a> IntoAttributeValue<'a> for AnyValueContainer { fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { AttributeValue::Any(self) } diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index 309fba781..057317779 100644 --- a/packages/native-core/Cargo.toml +++ b/packages/native-core/Cargo.toml @@ -26,4 +26,8 @@ dashmap = "5.4.0" [dev-dependencies] rand = "0.8.5" -dioxus = { path = "../dioxus", version = "^0.2.1" } \ No newline at end of file +dioxus = { path = "../dioxus", version = "^0.2.1" } + +[features] +default = ["parallel"] +parallel = ["dioxus-core/sync_attributes"] \ No newline at end of file diff --git a/packages/native-core/src/node.rs b/packages/native-core/src/node.rs index 6ee7d7184..c471dba11 100644 --- a/packages/native-core/src/node.rs +++ b/packages/native-core/src/node.rs @@ -1,5 +1,5 @@ use crate::{state::State, tree::NodeId}; -use dioxus_core::{AnyValueRc, AttributeValue, ElementId}; +use dioxus_core::{AnyValueContainer, AttributeValue, ElementId}; use rustc_hash::{FxHashMap, FxHashSet}; use std::fmt::Debug; @@ -79,7 +79,7 @@ pub enum OwnedAttributeValue { Float(f64), Int(i64), Bool(bool), - Any(AnyValueRc), + Any(AnyValueContainer), None, } diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index bdc7c7e5b..fd15e2ae6 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -314,7 +314,7 @@ struct RawPointer(*mut T); unsafe impl Send for RawPointer {} unsafe impl Sync for RawPointer {} -pub fn resolve_passes>( +pub fn resolve_passes + Sync>( tree: &mut Tr, dirty_nodes: DirtyNodeStates, mut passes: Vec<&AnyPass>, @@ -368,6 +368,50 @@ pub fn resolve_passes>( std::sync::Arc::try_unwrap(nodes_updated).unwrap() } +pub fn resolve_passes_single_threaded>( + tree: &mut Tr, + dirty_nodes: DirtyNodeStates, + mut passes: Vec<&AnyPass>, + ctx: SendAnyMap, +) -> FxDashSet { + let dirty_states = Arc::new(dirty_nodes); + let mut resolved_passes: FxHashSet = FxHashSet::default(); + let mut resolving = Vec::new(); + let nodes_updated = Arc::new(FxDashSet::default()); + let ctx = Arc::new(ctx); + while !passes.is_empty() { + let mut currently_borrowed = MemberMask::default(); + let mut i = 0; + while i < passes.len() { + let pass = &passes[i]; + let pass_id = pass.pass_id(); + let pass_mask = pass.mask(); + if pass + .dependancies() + .iter() + .all(|d| resolved_passes.contains(d) || *d == pass_id) + && !pass_mask.overlaps(currently_borrowed) + { + let pass = passes.remove(i); + resolving.push(pass_id); + currently_borrowed |= pass_mask; + let dirty_states = dirty_states.clone(); + let nodes_updated = nodes_updated.clone(); + let ctx = ctx.clone(); + // 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 mut dirty = DirtyNodes::default(); + dirty_states.all_dirty(pass_id, &mut dirty, tree); + pass.resolve(tree, dirty, &dirty_states, &nodes_updated, &ctx); + } else { + i += 1; + } + } + resolved_passes.extend(resolving.iter().copied()); + resolving.clear() + } + std::sync::Arc::try_unwrap(nodes_updated).unwrap() +} + #[test] fn node_pass() { use crate::tree::{Tree, TreeLike}; diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 637a8d7ce..5fe3113ce 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -339,12 +339,12 @@ 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( + pub fn update_state_single_threaded( &mut self, nodes_updated: DirtyNodeStates, ctx: SendAnyMap, ) -> FxDashSet { - S::update(nodes_updated, &mut self.tree, ctx) + S::update_single_threaded(nodes_updated, &mut self.tree, ctx) } /// Find all nodes that are listening for an event, sorted by there height in the dom progressing starting at the bottom and progressing up. @@ -396,6 +396,21 @@ impl RealDom { } } +impl RealDom +where + Tree>: Sync, +{ + /// 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. + /// This will resolve the state in parallel + pub fn update_state( + &mut self, + nodes_updated: DirtyNodeStates, + ctx: SendAnyMap, + ) -> FxDashSet { + S::update(nodes_updated, &mut self.tree, ctx) + } +} + impl Deref for RealDom { type Target = Tree>; diff --git a/packages/native-core/src/state.rs b/packages/native-core/src/state.rs index 7e6c5266a..54dad4997 100644 --- a/packages/native-core/src/state.rs +++ b/packages/native-core/src/state.rs @@ -2,7 +2,7 @@ use std::cmp::Ordering; use crate::node::Node; use crate::node_ref::{NodeMask, NodeView}; -use crate::passes::{resolve_passes, AnyPass, DirtyNodeStates}; +use crate::passes::{resolve_passes, resolve_passes_single_threaded, AnyPass, DirtyNodeStates}; use crate::tree::TreeView; use crate::{FxDashSet, RealNodeId, SendAnyMap}; @@ -217,7 +217,7 @@ pub trait State: Default + Clone + 'static { const MASKS: &'static [NodeMask]; #[doc(hidden)] - fn update>>( + fn update> + Sync>( dirty: DirtyNodeStates, tree: &mut T, ctx: SendAnyMap, @@ -225,6 +225,16 @@ pub trait State: Default + Clone + 'static { let passes = Self::PASSES.iter().collect(); resolve_passes(tree, dirty, passes, ctx) } + + #[doc(hidden)] + fn update_single_threaded>>( + dirty: DirtyNodeStates, + tree: &mut T, + ctx: SendAnyMap, + ) -> FxDashSet { + let passes = Self::PASSES.iter().collect(); + resolve_passes_single_threaded(tree, dirty, passes, ctx) + } } impl ChildDepState for () { diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index 0547fc3a9..c173824cb 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -564,8 +564,8 @@ impl<'a, T, Tr: TreeView> SharedView<'a, T, Tr> { } } -unsafe impl<'a, T, Tr: TreeView> Send for SharedView<'a, T, Tr> {} -unsafe impl<'a, T, Tr: TreeView> Sync for SharedView<'a, T, Tr> {} +unsafe impl<'a, T, Tr: TreeView + Send> Send for SharedView<'a, T, Tr> {} +unsafe impl<'a, T, Tr: TreeView + Sync> Sync for SharedView<'a, T, Tr> {} impl<'a, T, Tr: TreeView> Clone for SharedView<'a, T, Tr> { fn clone(&self) -> Self { Self {