Pārlūkot izejas kodu

add sync behind a feature flag

= 2 gadi atpakaļ
vecāks
revīzija
74e9d5977a

+ 1 - 0
packages/core/Cargo.toml

@@ -44,3 +44,4 @@ dioxus = { path = "../dioxus" }
 [features]
 default = []
 serialize = ["serde"]
+sync_attributes = []

+ 4 - 4
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

+ 49 - 19
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<dyn AnyValue>);
+#[cfg(not(feature = "sync_attributes"))]
+pub struct AnyValueContainer(pub Rc<dyn AnyValue>);
 
-impl PartialEq for AnyValueRc {
+#[derive(Clone)]
+#[cfg(feature = "sync_attributes")]
+pub struct AnyValueContainer(pub std::sync::Arc<dyn AnyValue>);
+
+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<T: AnyValueBounds>(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<T: Any + PartialEq>(&self) -> &T {
-        unsafe { &*(self.0.as_ref() as *const dyn AnyValue as *const T) }
+    pub unsafe fn downcast_ref_unchecked<T: AnyValueBounds>(&self) -> &T {
+        unsafe { &*(self.0.as_ref() as *const _ as *const T) }
     }
 
     /// Returns a reference to the inner value.
-    pub fn downcast_ref<T: Any + PartialEq>(&self) -> Option<&T> {
-        if self.0.type_id() == TypeId::of::<T>() {
+    pub fn downcast_ref<T: AnyValueBounds>(&self) -> Option<&T> {
+        if self.0.our_typeid() == TypeId::of::<T>() {
             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<T: Any + PartialEq>(&self) -> bool {
-        self.0.type_id() == TypeId::of::<T>()
+    pub fn is<T: AnyValueBounds>(&self) -> bool {
+        self.0.our_typeid() == TypeId::of::<T>()
     }
 }
 
 #[test]
 fn test_any_value_rc() {
-    let a = AnyValueRc(Rc::new(1i32));
-    assert!(a.is::<i32>());
-    assert!(!a.is::<i64>());
+    let a = AnyValueContainer::new(1i32);
     assert_eq!(a.downcast_ref::<i32>(), Some(&1i32));
     assert_eq!(a.downcast_ref::<i64>(), None);
+    assert!(a.is::<i32>());
+    assert!(!a.is::<i64>());
     unsafe {
         assert_eq!(a.downcast_ref_unchecked::<i32>(), &1i32);
     }
 }
 
 #[cfg(feature = "serialize")]
-impl serde::Serialize for AnyValueRc {
+impl serde::Serialize for AnyValueContainer {
     fn serialize<S>(&self, _: S) -> Result<S::Ok, S::Error>
     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>(_: D) -> Result<Self, D::Error>
     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<T: PartialEq + Any> AnyValue for T {
+impl<T: AnyValueBounds> 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)
     }

+ 5 - 1
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" }
+dioxus = { path = "../dioxus", version = "^0.2.1" }
+
+[features]
+default = ["parallel"]
+parallel = ["dioxus-core/sync_attributes"]

+ 2 - 2
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,
 }
 

+ 45 - 1
packages/native-core/src/passes.rs

@@ -314,7 +314,7 @@ struct RawPointer<T>(*mut T);
 unsafe impl<T> Send for RawPointer<T> {}
 unsafe impl<T> Sync for RawPointer<T> {}
 
-pub fn resolve_passes<T, Tr: TreeView<T>>(
+pub fn resolve_passes<T, Tr: TreeView<T> + Sync>(
     tree: &mut Tr,
     dirty_nodes: DirtyNodeStates,
     mut passes: Vec<&AnyPass<T>>,
@@ -368,6 +368,50 @@ pub fn resolve_passes<T, Tr: TreeView<T>>(
     std::sync::Arc::try_unwrap(nodes_updated).unwrap()
 }
 
+pub fn resolve_passes_single_threaded<T, Tr: TreeView<T>>(
+    tree: &mut Tr,
+    dirty_nodes: DirtyNodeStates,
+    mut passes: Vec<&AnyPass<T>>,
+    ctx: SendAnyMap,
+) -> FxDashSet<NodeId> {
+    let dirty_states = Arc::new(dirty_nodes);
+    let mut resolved_passes: FxHashSet<PassId> = 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};

+ 17 - 2
packages/native-core/src/real_dom.rs

@@ -339,12 +339,12 @@ impl<S: State> RealDom<S> {
     }
 
     /// 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<RealNodeId> {
-        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<S: State> RealDom<S> {
     }
 }
 
+impl<S: State + Sync> RealDom<S>
+where
+    Tree<Node<S>>: 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<RealNodeId> {
+        S::update(nodes_updated, &mut self.tree, ctx)
+    }
+}
+
 impl<S: State> Deref for RealDom<S> {
     type Target = Tree<Node<S>>;
 

+ 12 - 2
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<T: TreeView<Node<Self>>>(
+    fn update<T: TreeView<Node<Self>> + 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<T: TreeView<Node<Self>>>(
+        dirty: DirtyNodeStates,
+        tree: &mut T,
+        ctx: SendAnyMap,
+    ) -> FxDashSet<RealNodeId> {
+        let passes = Self::PASSES.iter().collect();
+        resolve_passes_single_threaded(tree, dirty, passes, ctx)
+    }
 }
 
 impl ChildDepState for () {

+ 2 - 2
packages/native-core/src/tree.rs

@@ -564,8 +564,8 @@ impl<'a, T, Tr: TreeView<T>> SharedView<'a, T, Tr> {
     }
 }
 
-unsafe impl<'a, T, Tr: TreeView<T>> Send for SharedView<'a, T, Tr> {}
-unsafe impl<'a, T, Tr: TreeView<T>> Sync for SharedView<'a, T, Tr> {}
+unsafe impl<'a, T, Tr: TreeView<T> + Send> Send for SharedView<'a, T, Tr> {}
+unsafe impl<'a, T, Tr: TreeView<T> + Sync> Sync for SharedView<'a, T, Tr> {}
 impl<'a, T, Tr: TreeView<T>> Clone for SharedView<'a, T, Tr> {
     fn clone(&self) -> Self {
         Self {