gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 1112HTML 1039Markdown 12TOML 94628 Definiciones

Rayon WIP

dde8c6aa4a2faa6faea600e24ecf2ce67923c37e · por Nico Burns · (hace 2 años) · padre 066949d8b · ver el árbol en este commit

Delta semántico · 49

src/compute/block.rs

src/compute/flexbox.rs

src/compute/grid/alignment.rs

src/compute/grid/mod.rs

src/compute/grid/track_sizing.rs

src/compute/grid/types/grid_item.rs

src/compute/mod.rs

src/tree/taffy_tree.rs

src/tree/traits.rs

tests/fixtures.rs

12 archivos · +92 −75

diff --git a/Cargo.toml b/Cargo.toml
index 64e68ee6b..1b461556d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -21,6 +21,7 @@ num-traits = { version = "0.2", default-features = false }
 serde = { version = "1.0", default-features = false, optional = true, features = ["serde_derive"] }
 slotmap = { version = "1.0.6", default-features = false, optional = true }
 grid = { version = "0.13.0", default-features = false, optional = true }
+rayon = "1.10.0"
 
 ### FEATURES #################################################################
 
diff --git a/src/compute/block.rs b/src/compute/block.rs
index 7e6d300be..5ff45891e 100644
--- a/src/compute/block.rs
+++ b/src/compute/block.rs
@@ -57,7 +57,7 @@ struct BlockItem {
 }
 
 /// Computes the layout of [`LayoutPartialTree`] according to the block layout algorithm
-pub fn compute_block_layout(tree: &mut impl LayoutPartialTree, node_id: NodeId, inputs: LayoutInput) -> LayoutOutput {
+pub fn compute_block_layout(tree: &impl LayoutPartialTree, node_id: NodeId, inputs: LayoutInput) -> LayoutOutput {
     let LayoutInput { known_dimensions, parent_size, available_space, run_mode, .. } = inputs;
     let style = tree.get_style(node_id);
 
@@ -104,7 +104,7 @@ pub fn compute_block_layout(tree: &mut impl LayoutPartialTree, node_id: NodeId,
 }
 
 /// Computes the layout of [`LayoutPartialTree`] according to the block layout algorithm
-fn compute_inner(tree: &mut impl LayoutPartialTree, node_id: NodeId, inputs: LayoutInput) -> LayoutOutput {
+fn compute_inner(tree: &impl LayoutPartialTree, node_id: NodeId, inputs: LayoutInput) -> LayoutOutput {
     let LayoutInput {
         known_dimensions, parent_size, available_space, run_mode, vertical_margins_are_collapsible, ..
     } = inputs;
@@ -298,7 +298,7 @@ fn generate_item_list(
 /// Compute the content-based width in the case that the width of the container is not known
 #[inline]
 fn determine_content_based_container_width(
-    tree: &mut impl LayoutPartialTree,
+    tree: &impl LayoutPartialTree,
     items: &[BlockItem],
     available_width: AvailableSpace,
 ) -> f32 {
@@ -333,7 +333,7 @@ fn determine_content_based_container_width(
 /// Compute each child's final size and position
 #[inline]
 fn perform_final_layout_on_in_flow_children(
-    tree: &mut impl LayoutPartialTree,
+    tree: &impl LayoutPartialTree,
     items: &mut [BlockItem],
     container_outer_width: f32,
     content_box_inset: Rect<f32>,
@@ -488,7 +488,7 @@ fn perform_final_layout_on_in_flow_children(
 /// Perform absolute layout on all absolutely positioned children.
 #[inline]
 fn perform_absolute_layout_on_absolute_children(
-    tree: &mut impl LayoutPartialTree,
+    tree: &impl LayoutPartialTree,
     items: &[BlockItem],
     area_size: Size<f32>,
     area_offset: Point<f32>,
diff --git a/src/compute/flexbox.rs b/src/compute/flexbox.rs
index 143a1dea3..03bfc12ff 100644
--- a/src/compute/flexbox.rs
+++ b/src/compute/flexbox.rs
@@ -13,6 +13,7 @@ use crate::util::debug::debug_log;
 use crate::util::sys::{f32_max, new_vec_with_capacity, Vec};
 use crate::util::MaybeMath;
 use crate::util::{MaybeResolve, ResolveOrZero};
+use rayon::prelude::*;
 
 #[cfg(feature = "content_size")]
 use super::common::content_size::compute_content_size_contribution;
@@ -152,7 +153,7 @@ struct AlgoConstants {
 }
 
 /// Computes the layout of [`LayoutPartialTree`] according to the flexbox algorithm
-pub fn compute_flexbox_layout(tree: &mut impl LayoutPartialTree, node: NodeId, inputs: LayoutInput) -> LayoutOutput {
+pub fn compute_flexbox_layout(tree: &impl LayoutPartialTree, node: NodeId, inputs: LayoutInput) -> LayoutOutput {
     let LayoutInput { known_dimensions, parent_size, run_mode, .. } = inputs;
     let style = tree.get_style(node);
 
@@ -186,7 +187,7 @@ pub fn compute_flexbox_layout(tree: &mut impl LayoutPartialTree, node: NodeId, i
 }
 
 /// Compute a preliminary size for an item
-fn compute_preliminary(tree: &mut impl LayoutPartialTree, node: NodeId, inputs: LayoutInput) -> LayoutOutput {
+fn compute_preliminary(tree: &impl LayoutPartialTree, node: NodeId, inputs: LayoutInput) -> LayoutOutput {
     let LayoutInput { known_dimensions, parent_size, available_space, run_mode, .. } = inputs;
 
     // Define some general constants we will need for the remainder of the algorithm.
@@ -562,14 +563,15 @@ fn determine_available_space(
 ///     (For example, an item with a specified size of zero, positive padding, and box-sizing: border-box will have an outer flex base size of zero—and hence a negative inner flex base size.)
 #[inline]
 fn determine_flex_base_size(
-    tree: &mut impl LayoutPartialTree,
+    tree: &impl LayoutPartialTree,
     constants: &AlgoConstants,
     available_space: Size<AvailableSpace>,
     flex_items: &mut [FlexItem],
 ) {
     let dir = constants.dir;
 
-    for child in flex_items.iter_mut() {
+    let block_size = (flex_items.len() / 8).max(1);
+    flex_items.par_iter_mut().by_uniform_blocks(block_size).for_each(|child| {
         let child_style = tree.get_style(child.node);
 
         // Parent size for child sizing
@@ -717,7 +719,7 @@ fn determine_flex_base_size(
                 min_content_main_size.maybe_min(child.size.main(dir)).maybe_min(child.max_size.main(dir));
             clamped_min_content_size.maybe_max(padding_border_axes_sums.main(dir))
         });
-    }
+    });
 }
 
 /// Collect flex items into flex lines.
@@ -802,7 +804,7 @@ fn collect_flex_lines<'a>(
 
 /// Determine the container's main size (if not already known)
 fn determine_container_main_size(
-    tree: &mut impl LayoutPartialTree,
+    tree: &impl LayoutPartialTree,
     available_space: Size<AvailableSpace>,
     lines: &mut [FlexLine<'_>],
     constants: &mut AlgoConstants,
@@ -1208,7 +1210,7 @@ fn resolve_flexible_lengths(line: &mut FlexLine, constants: &AlgoConstants, orig
 ///     by performing layout with the used main size and the available space, treating auto as fit-content.
 #[inline]
 fn determine_hypothetical_cross_size(
-    tree: &mut impl LayoutPartialTree,
+    tree: &impl LayoutPartialTree,
     line: &mut FlexLine,
     constants: &AlgoConstants,
     available_space: Size<AvailableSpace>,
@@ -1258,7 +1260,7 @@ fn determine_hypothetical_cross_size(
 /// Calculate the base lines of the children.
 #[inline]
 fn calculate_children_base_lines(
-    tree: &mut impl LayoutPartialTree,
+    tree: &impl LayoutPartialTree,
     node_size: Size<Option<f32>>,
     available_space: Size<AvailableSpace>,
     flex_lines: &mut [FlexLine],
@@ -1722,7 +1724,7 @@ fn align_flex_lines_per_align_content(flex_lines: &mut [FlexLine], constants: &A
 /// Calculates the layout for a flex-item
 #[allow(clippy::too_many_arguments)]
 fn calculate_flex_item(
-    tree: &mut impl LayoutPartialTree,
+    tree: &impl LayoutPartialTree,
     item: &mut FlexItem,
     total_offset_main: &mut f32,
     total_offset_cross: f32,
@@ -1803,7 +1805,7 @@ fn calculate_flex_item(
 /// Calculates the layout line
 #[allow(clippy::too_many_arguments)]
 fn calculate_layout_line(
-    tree: &mut impl LayoutPartialTree,
+    tree: &impl LayoutPartialTree,
     line: &mut FlexLine,
     total_offset_cross: &mut f32,
     #[cfg(feature = "content_size")] content_size: &mut Size<f32>,
@@ -1815,8 +1817,11 @@ fn calculate_layout_line(
     let mut total_offset_main = padding_border.main_start(direction);
     let line_offset_cross = line.offset_cross;
 
+    let block_size = (line.items.len() / 8).max(1);
     if direction.is_reverse() {
-        for item in line.items.iter_mut().rev() {
+        line.items.par_iter_mut().rev().by_uniform_blocks(block_size).for_each(|item| {
+            let mut total_offset_main = 0.0;
+            let mut content_size = &mut Size::ZERO;
             calculate_flex_item(
                 tree,
                 item,
@@ -1829,9 +1834,11 @@ fn calculate_layout_line(
                 node_inner_size,
                 direction,
             );
-        }
+        });
     } else {
-        for item in line.items.iter_mut() {
+        line.items.par_iter_mut().by_uniform_blocks(block_size).for_each(|item| {
+            let mut total_offset_main = 0.0;
+            let mut content_size = &mut Size::ZERO;
             calculate_flex_item(
                 tree,
                 item,
@@ -1844,7 +1851,7 @@ fn calculate_layout_line(
                 node_inner_size,
                 direction,
             );
-        }
+        });
     }
 
     *total_offset_cross += line_offset_cross + line.cross_size;
@@ -1853,7 +1860,7 @@ fn calculate_layout_line(
 /// Do a final layout pass and collect the resulting layouts.
 #[inline]
 fn final_layout_pass(
-    tree: &mut impl LayoutPartialTree,
+    tree: &impl LayoutPartialTree,
     flex_lines: &mut [FlexLine],
     constants: &AlgoConstants,
 ) -> Size<f32> {
@@ -1898,7 +1905,7 @@ fn final_layout_pass(
 /// Perform absolute layout on all absolutely positioned children.
 #[inline]
 fn perform_absolute_layout_on_absolute_children(
-    tree: &mut impl LayoutPartialTree,
+    tree: &impl LayoutPartialTree,
     node: NodeId,
     constants: &AlgoConstants,
 ) -> Size<f32> {
diff --git a/src/compute/grid/alignment.rs b/src/compute/grid/alignment.rs
index 9a38c7ba6..723af8685 100644
--- a/src/compute/grid/alignment.rs
+++ b/src/compute/grid/alignment.rs
@@ -54,7 +54,7 @@ pub(super) fn align_tracks(
 
 /// Align and size a grid item into it's final position
 pub(super) fn align_and_position_item(
-    tree: &mut impl LayoutPartialTree,
+    tree: &impl LayoutPartialTree,
     node: NodeId,
     order: u32,
     grid_area: Rect<f32>,
diff --git a/src/compute/grid/mod.rs b/src/compute/grid/mod.rs
index cc755769e..ffc70aed8 100644
--- a/src/compute/grid/mod.rs
+++ b/src/compute/grid/mod.rs
@@ -35,7 +35,7 @@ mod util;
 ///   - Placing items (which also resolves the implicit grid)
 ///   - Track (row/column) sizing
 ///   - Alignment & Final item placement
-pub fn compute_grid_layout(tree: &mut impl LayoutPartialTree, node: NodeId, inputs: LayoutInput) -> LayoutOutput {
+pub fn compute_grid_layout(tree: &impl LayoutPartialTree, node: NodeId, inputs: LayoutInput) -> LayoutOutput {
     let LayoutInput { known_dimensions, parent_size, available_space, run_mode, .. } = inputs;
 
     let get_child_styles_iter = |node| tree.child_ids(node).map(|child_node: NodeId| tree.get_style(child_node));
diff --git a/src/compute/grid/track_sizing.rs b/src/compute/grid/track_sizing.rs
index 8e5c73c9d..9dd0a9d19 100644
--- a/src/compute/grid/track_sizing.rs
+++ b/src/compute/grid/track_sizing.rs
@@ -71,7 +71,7 @@ where
     EstimateFunction: Fn(&GridTrack, Option<f32>) -> Option<f32>,
 {
     /// The layout tree
-    tree: &'tree mut Tree,
+    tree: &'tree Tree,
     /// The tracks in the opposite axis to the one we are currently sizing
     other_axis_tracks: &'oat [GridTrack],
     /// A function that computes an estimate of an other-axis track's size which is passed to
@@ -261,7 +261,7 @@ pub(super) fn determine_if_item_crosses_flexible_or_intrinsic_tracks(
 /// Note: Gutters are treated as empty fixed-size tracks for the purpose of the track sizing algorithm.
 #[allow(clippy::too_many_arguments)]
 pub(super) fn track_sizing_algorithm<Tree: LayoutPartialTree>(
-    tree: &mut Tree,
+    tree: &Tree,
     axis: AbstractAxis,
     axis_min_size: Option<f32>,
     axis_max_size: Option<f32>,
@@ -435,7 +435,7 @@ fn initialize_track_sizes(axis_tracks: &mut [GridTrack], axis_inner_node_size: O
 
 /// 11.5.1 Shim baseline-aligned items so their intrinsic size contributions reflect their baseline alignment.
 fn resolve_item_baselines(
-    tree: &mut impl LayoutPartialTree,
+    tree: &impl LayoutPartialTree,
     axis: AbstractAxis,
     items: &mut [GridItem],
     inner_node_size: Size<Option<f32>>,
@@ -508,7 +508,7 @@ fn resolve_item_baselines(
 /// 11.5 Resolve Intrinsic Track Sizes
 #[allow(clippy::too_many_arguments)]
 fn resolve_intrinsic_track_sizes(
-    tree: &mut impl LayoutPartialTree,
+    tree: &impl LayoutPartialTree,
     axis: AbstractAxis,
     axis_tracks: &mut [GridTrack],
     other_axis_tracks: &[GridTrack],
@@ -1139,7 +1139,7 @@ fn maximise_tracks(
 #[allow(clippy::too_many_arguments)]
 #[inline(always)]
 fn expand_flexible_tracks(
-    tree: &mut impl LayoutPartialTree,
+    tree: &impl LayoutPartialTree,
     axis: AbstractAxis,
     axis_tracks: &mut [GridTrack],
     items: &mut [GridItem],
diff --git a/src/compute/grid/types/grid_item.rs b/src/compute/grid/types/grid_item.rs
index 5f0e7906b..98c83409d 100644
--- a/src/compute/grid/types/grid_item.rs
+++ b/src/compute/grid/types/grid_item.rs
@@ -340,7 +340,7 @@ impl GridItem {
     pub fn min_content_contribution(
         &self,
         axis: AbstractAxis,
-        tree: &mut impl LayoutPartialTree,
+        tree: &impl LayoutPartialTree,
         available_space: Size<Option<f32>>,
         inner_node_size: Size<Option<f32>>,
     ) -> f32 {
@@ -364,7 +364,7 @@ impl GridItem {
     pub fn min_content_contribution_cached(
         &mut self,
         axis: AbstractAxis,
-        tree: &mut impl LayoutPartialTree,
+        tree: &impl LayoutPartialTree,
         available_space: Size<Option<f32>>,
         inner_node_size: Size<Option<f32>>,
     ) -> f32 {
@@ -379,7 +379,7 @@ impl GridItem {
     pub fn max_content_contribution(
         &self,
         axis: AbstractAxis,
-        tree: &mut impl LayoutPartialTree,
+        tree: &impl LayoutPartialTree,
         available_space: Size<Option<f32>>,
         inner_node_size: Size<Option<f32>>,
     ) -> f32 {
@@ -403,7 +403,7 @@ impl GridItem {
     pub fn max_content_contribution_cached(
         &mut self,
         axis: AbstractAxis,
-        tree: &mut impl LayoutPartialTree,
+        tree: &impl LayoutPartialTree,
         available_space: Size<Option<f32>>,
         inner_node_size: Size<Option<f32>>,
     ) -> f32 {
@@ -423,7 +423,7 @@ impl GridItem {
     /// See: https://www.w3.org/TR/css-grid-1/#min-size-auto
     pub fn minimum_contribution(
         &mut self,
-        tree: &mut impl LayoutPartialTree,
+        tree: &impl LayoutPartialTree,
         axis: AbstractAxis,
         axis_tracks: &[GridTrack],
         known_dimensions: Size<Option<f32>>,
@@ -482,7 +482,7 @@ impl GridItem {
     #[inline(always)]
     pub fn minimum_contribution_cached(
         &mut self,
-        tree: &mut impl LayoutPartialTree,
+        tree: &impl LayoutPartialTree,
         axis: AbstractAxis,
         axis_tracks: &[GridTrack],
         known_dimensions: Size<Option<f32>>,
diff --git a/src/compute/mod.rs b/src/compute/mod.rs
index f78a22df9..beeea4d0d 100644
--- a/src/compute/mod.rs
+++ b/src/compute/mod.rs
@@ -2,7 +2,7 @@
 //!
 //! ### Layout functions
 //!
-//! The layout functions all take an [`&mut impl LayoutPartialTree`](crate::LayoutPartialTree) parameter, which represents a single container node and it's direct children.
+//! The layout functions all take an [`&impl LayoutPartialTree`](crate::LayoutPartialTree) parameter, which represents a single container node and it's direct children.
 //!
 //! | Function                          | Purpose                                                                                                                                                                                            |
 //! | ---                               | ---                                                                                                                                                                                                |
@@ -54,7 +54,7 @@ use crate::util::sys::round;
 use crate::util::ResolveOrZero;
 
 /// Compute layout for the root node in the tree
-pub fn compute_root_layout(tree: &mut impl LayoutPartialTree, root: NodeId, available_space: Size<AvailableSpace>) {
+pub fn compute_root_layout(tree: &impl LayoutPartialTree, root: NodeId, available_space: Size<AvailableSpace>) {
     // Recursively compute node layout
     let output = tree.perform_child_layout(
         root,
@@ -93,13 +93,13 @@ pub fn compute_root_layout(tree: &mut impl LayoutPartialTree, root: NodeId, avai
 /// Uses the provided closure to compute the layout (and then stores the result in the cache) if no cached layout is found.
 #[inline(always)]
 pub fn compute_cached_layout<Tree: LayoutPartialTree + ?Sized, ComputeFunction>(
-    tree: &mut Tree,
+    tree: &Tree,
     node: NodeId,
     inputs: LayoutInput,
     mut compute_uncached: ComputeFunction,
 ) -> LayoutOutput
 where
-    ComputeFunction: FnMut(&mut Tree, NodeId, LayoutInput) -> LayoutOutput,
+    ComputeFunction: FnMut(&Tree, NodeId, LayoutInput) -> LayoutOutput,
 {
     debug_push_node!(node);
     let LayoutInput { known_dimensions, available_space, run_mode, .. } = inputs;
@@ -194,7 +194,7 @@ pub fn round_layout(tree: &mut impl RoundTree, node_id: NodeId) {
 
 /// Creates a layout for this node and its children, recursively.
 /// Each hidden node has zero size and is placed at the origin
-pub fn compute_hidden_layout(tree: &mut impl LayoutPartialTree, node: NodeId) -> LayoutOutput {
+pub fn compute_hidden_layout(tree: &impl LayoutPartialTree, node: NodeId) -> LayoutOutput {
     // Clear cache and set zeroed-out layout for the node
     tree.get_cache_mut(node).clear();
     tree.set_unrounded_layout(node, &Layout::with_order(0));
diff --git a/src/lib.rs b/src/lib.rs
index 577abf778..4f4ce8e1e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -58,8 +58,6 @@
 //!   - [custom_tree_owned_unsafe](https://github.com/DioxusLabs/taffy/blob/main/examples/custom_tree_owned_unsafe.rs) which implements a custom Taffy tree using directly owned children with NodeId's being pointers.
 
 #![cfg_attr(not(feature = "std"), no_std)]
-#![deny(unsafe_code)]
-#![forbid(unsafe_code)]
 #![warn(missing_docs)]
 #![warn(clippy::missing_docs_in_private_items)]
 
diff --git a/src/tree/taffy_tree.rs b/src/tree/taffy_tree.rs
index b465bed5a..f1e71f83f 100644
--- a/src/tree/taffy_tree.rs
+++ b/src/tree/taffy_tree.rs
@@ -1,4 +1,6 @@
 //! Contains [TaffyTree](crate::tree::TaffyTree): the default implementation of [LayoutTree](crate::tree::LayoutTree), and the error type for Taffy.
+use core::cell::UnsafeCell;
+
 #[cfg(not(feature = "std"))]
 use slotmap::SecondaryMap;
 #[cfg(feature = "std")]
@@ -86,7 +88,7 @@ struct NodeData {
 
     /// The always unrounded results of the layout computation. We must store this separately from the rounded
     /// layout to avoid errors from rounding already-rounded values. See <https://github.com/DioxusLabs/taffy/issues/501>.
-    pub(crate) unrounded_layout: Layout,
+    pub(crate) unrounded_layout: UnsafeCell<Layout>,
 
     /// The final results of the layout computation.
     /// These may be rounded or unrounded depending on what the `use_rounding` config setting is set to.
@@ -96,17 +98,20 @@ struct NodeData {
     pub(crate) has_context: bool,
 
     /// The cached results of the layout computation
-    pub(crate) cache: Cache,
+    pub(crate) cache: UnsafeCell<Cache>,
 }
 
+unsafe impl Send for NodeData {}
+unsafe impl Sync for NodeData {}
+
 impl NodeData {
     /// Create the data for a new node
     #[must_use]
     pub const fn new(style: Style) -> Self {
         Self {
             style,
-            cache: Cache::new(),
-            unrounded_layout: Layout::new(),
+            cache: UnsafeCell::new(Cache::new()),
+            unrounded_layout: UnsafeCell::new(Layout::new()),
             final_layout: Layout::new(),
             has_context: false,
         }
@@ -116,15 +121,15 @@ impl NodeData {
     ///
     /// This clears any cached data and signals that the data must be recomputed.
     #[inline]
-    pub fn mark_dirty(&mut self) {
-        self.cache.clear()
+    pub fn mark_dirty(&self) {
+        unsafe { &mut *self.cache.get() }.clear()
     }
 }
 
 /// An entire tree of UI nodes. The entry point to Taffy's high-level API.
 ///
 /// Allows you to build a tree of UI nodes, run Taffy's layout algorithms over that tree, and then access the resultant layout.
-pub struct TaffyTree<NodeContext = ()> {
+pub struct TaffyTree<NodeContext: Send + Sync = ()> {
     /// The [`NodeData`] for each node stored in this tree
     nodes: SlotMap<DefaultKey, NodeData>,
 
@@ -162,7 +167,7 @@ impl<'a> Iterator for TaffyTreeChildIter<'a> {
 }
 
 // TraversePartialTree impl for TaffyTree
-impl<NodeContext> TraversePartialTree for TaffyTree<NodeContext> {
+impl<NodeContext: Send + Sync> TraversePartialTree for TaffyTree<NodeContext> {
     type ChildIter<'a> = TaffyTreeChildIter<'a> where Self: 'a;
 
     #[inline(always)]
@@ -182,10 +187,10 @@ impl<NodeContext> TraversePartialTree for TaffyTree<NodeContext> {
 }
 
 // TraverseTree impl for TaffyTree
-impl<NodeContext> TraverseTree for TaffyTree<NodeContext> {}
+impl<NodeContext: Send + Sync> TraverseTree for TaffyTree<NodeContext> {}
 
 // PrintTree impl for TaffyTree
-impl<NodeContext> PrintTree for TaffyTree<NodeContext> {
+impl<NodeContext: Send + Sync> PrintTree for TaffyTree<NodeContext> {
     #[inline(always)]
     fn get_debug_label(&self, node_id: NodeId) -> &'static str {
         let node = &self.nodes[node_id.into()];
@@ -221,7 +226,8 @@ impl<NodeContext> PrintTree for TaffyTree<NodeContext> {
 /// which makes the lifetimes of the context much more flexible.
 pub(crate) struct TaffyView<'t, NodeContext, MeasureFunction>
 where
-    MeasureFunction: FnMut(Size<Option<f32>>, Size<AvailableSpace>, NodeId, Option<&mut NodeContext>) -> Size<f32>,
+    MeasureFunction: Fn(Size<Option<f32>>, Size<AvailableSpace>, NodeId, Option<&NodeContext>) -> Size<f32> + Send + Sync,
+    NodeContext: Send + Sync,
 {
     /// A reference to the TaffyTree
     pub(crate) taffy: &'t mut TaffyTree<NodeContext>,
@@ -232,7 +238,8 @@ where
 // TraversePartialTree impl for TaffyView
 impl<'t, NodeContext, MeasureFunction> TraversePartialTree for TaffyView<'t, NodeContext, MeasureFunction>
 where
-    MeasureFunction: FnMut(Size<Option<f32>>, Size<AvailableSpace>, NodeId, Option<&mut NodeContext>) -> Size<f32>,
+    MeasureFunction: Fn(Size<Option<f32>>, Size<AvailableSpace>, NodeId, Option<&NodeContext>) -> Size<f32> + Send + Sync,
+    NodeContext: Send + Sync,
 {
     type ChildIter<'a> = TaffyTreeChildIter<'a> where Self: 'a;
 
@@ -254,14 +261,16 @@ where
 
 // TraverseTree impl for TaffyView
 impl<'t, NodeContext, MeasureFunction> TraverseTree for TaffyView<'t, NodeContext, MeasureFunction> where
-    MeasureFunction: FnMut(Size<Option<f32>>, Size<AvailableSpace>, NodeId, Option<&mut NodeContext>) -> Size<f32>
+    MeasureFunction: Fn(Size<Option<f32>>, Size<AvailableSpace>, NodeId, Option<&NodeContext>) -> Size<f32> + Send + Sync,
+    NodeContext: Send + Sync,
 {
 }
 
 // LayoutPartialTree impl for TaffyView
 impl<'t, NodeContext, MeasureFunction> LayoutPartialTree for TaffyView<'t, NodeContext, MeasureFunction>
 where
-    MeasureFunction: FnMut(Size<Option<f32>>, Size<AvailableSpace>, NodeId, Option<&mut NodeContext>) -> Size<f32>,
+    MeasureFunction: Fn(Size<Option<f32>>, Size<AvailableSpace>, NodeId, Option<&NodeContext>) -> Size<f32> + Send + Sync,
+    NodeContext: Send + Sync,
 {
     #[inline(always)]
     fn get_style(&self, node: NodeId) -> &Style {
@@ -269,17 +278,17 @@ where
     }
 
     #[inline(always)]
-    fn get_cache_mut(&mut self, node: NodeId) -> &mut Cache {
-        &mut self.taffy.nodes[node.into()].cache
+    fn get_cache_mut(&self, node: NodeId) -> &mut Cache {
+        unsafe { &mut*self.taffy.nodes[node.into()].cache.get() }
     }
 
     #[inline(always)]
-    fn set_unrounded_layout(&mut self, node_id: NodeId, layout: &Layout) {
-        self.taffy.nodes[node_id.into()].unrounded_layout = *layout;
+    fn set_unrounded_layout(&self, node_id: NodeId, layout: &Layout) {
+        unsafe { *self.taffy.nodes[node_id.into()].unrounded_layout.get() = *layout };
     }
 
     #[inline(always)]
-    fn compute_child_layout(&mut self, node: NodeId, inputs: LayoutInput) -> LayoutOutput {
+    fn compute_child_layout(&self, node: NodeId, inputs: LayoutInput) -> LayoutOutput {
         // If RunMode is PerformHiddenLayout then this indicates that an ancestor node is `Display::None`
         // and thus that we should lay out this node using hidden layout regardless of it's own display style.
         if inputs.run_mode == RunMode::PerformHiddenLayout {
@@ -318,7 +327,7 @@ where
                     let node_key = node.into();
                     let style = &tree.taffy.nodes[node_key].style;
                     let has_context = tree.taffy.nodes[node_key].has_context;
-                    let node_context = has_context.then(|| tree.taffy.node_context_data.get_mut(node_key)).flatten();
+                    let node_context = has_context.then(|| tree.taffy.node_context_data.get(node_key)).flatten();
                     let measure_function = |known_dimensions, available_space| {
                         (tree.measure_function)(known_dimensions, available_space, node, node_context)
                     };
@@ -332,11 +341,12 @@ where
 // RoundTree impl for TaffyView
 impl<'t, NodeContext, MeasureFunction> RoundTree for TaffyView<'t, NodeContext, MeasureFunction>
 where
-    MeasureFunction: FnMut(Size<Option<f32>>, Size<AvailableSpace>, NodeId, Option<&mut NodeContext>) -> Size<f32>,
+    MeasureFunction: Fn(Size<Option<f32>>, Size<AvailableSpace>, NodeId, Option<&NodeContext>) -> Size<f32> + Send + Sync,
+    NodeContext: Send + Sync,
 {
     #[inline(always)]
     fn get_unrounded_layout(&self, node: NodeId) -> &Layout {
-        &self.taffy.nodes[node.into()].unrounded_layout
+        unsafe { &*self.taffy.nodes[node.into()].unrounded_layout.get() }
     }
 
     #[inline(always)]
@@ -346,7 +356,7 @@ where
 }
 
 #[allow(clippy::iter_cloned_collect)] // due to no-std support, we need to use `iter_cloned` instead of `collect`
-impl<NodeContext> TaffyTree<NodeContext> {
+impl<NodeContext: Send + Sync> TaffyTree<NodeContext> {
     /// Creates a new [`TaffyTree`]
     ///
     /// The default capacity of a [`TaffyTree`] is 16 nodes.
@@ -631,7 +641,7 @@ impl<NodeContext> TaffyTree<NodeContext> {
         if self.config.use_rounding {
             Ok(&self.nodes[node.into()].final_layout)
         } else {
-            Ok(&self.nodes[node.into()].unrounded_layout)
+            Ok(unsafe { &*self.nodes[node.into()].unrounded_layout.get() })
         }
     }
 
@@ -661,7 +671,7 @@ impl<NodeContext> TaffyTree<NodeContext> {
 
     /// Indicates whether the layout of this node (and its children) need to be recomputed
     pub fn dirty(&self, node: NodeId) -> TaffyResult<bool> {
-        Ok(self.nodes[node.into()].cache.is_empty())
+        Ok(unsafe { &*self.nodes[node.into()].cache.get() }.is_empty())
     }
 
     /// Updates the stored layout of the provided `node` and its children
@@ -672,7 +682,8 @@ impl<NodeContext> TaffyTree<NodeContext> {
         measure_function: MeasureFunction,
     ) -> Result<(), TaffyError>
     where
-        MeasureFunction: FnMut(Size<Option<f32>>, Size<AvailableSpace>, NodeId, Option<&mut NodeContext>) -> Size<f32>,
+        MeasureFunction: Fn(Size<Option<f32>>, Size<AvailableSpace>, NodeId, Option<&NodeContext>) -> Size<f32> + Send + Sync,
+        NodeContext: Send + Sync,
     {
         let use_rounding = self.config.use_rounding;
         let mut taffy_view = TaffyView { taffy: self, measure_function };
@@ -713,7 +724,7 @@ mod tests {
         known_dimensions: Size<Option<f32>>,
         _available_space: Size<AvailableSpace>,
         _node_id: NodeId,
-        node_context: Option<&mut Size<f32>>,
+        node_context: Option<&Size<f32>>,
     ) -> Size<f32> {
         known_dimensions.unwrap_or(node_context.cloned().unwrap_or(Size::ZERO))
     }
diff --git a/src/tree/traits.rs b/src/tree/traits.rs
index ce6688294..31f8ab43f 100644
--- a/src/tree/traits.rs
+++ b/src/tree/traits.rs
@@ -156,18 +156,18 @@ pub trait TraverseTree: TraversePartialTree {}
 ///
 /// Note that this trait extends [`TraversePartialTree`] (not [`TraverseTree`]). Taffy's algorithm implementations have been designed such that they can be used for a laying out a single
 /// node that only has access to it's immediate children.
-pub trait LayoutPartialTree: TraversePartialTree {
+pub trait LayoutPartialTree: TraversePartialTree + Send + Sync {
     /// Get a reference to the [`Style`] for this node.
     fn get_style(&self, node_id: NodeId) -> &Style;
 
     /// Set the node's unrounded layout
-    fn set_unrounded_layout(&mut self, node_id: NodeId, layout: &Layout);
+    fn set_unrounded_layout(&self, node_id: NodeId, layout: &Layout);
 
     /// Get a mutable reference to the [`Cache`] for this node.
-    fn get_cache_mut(&mut self, node_id: NodeId) -> &mut Cache;
+    fn get_cache_mut(&self, node_id: NodeId) -> &mut Cache;
 
     /// Compute the specified node's size or full layout given the specified constraints
-    fn compute_child_layout(&mut self, node_id: NodeId, inputs: LayoutInput) -> LayoutOutput;
+    fn compute_child_layout(&self, node_id: NodeId, inputs: LayoutInput) -> LayoutOutput;
 }
 
 /// Trait used by the `round_layout` method which takes a tree of unrounded float-valued layouts and performs
@@ -200,7 +200,7 @@ pub(crate) trait LayoutPartialTreeExt: LayoutPartialTree {
     #[inline(always)]
     #[allow(clippy::too_many_arguments)]
     fn measure_child_size(
-        &mut self,
+        &self,
         node_id: NodeId,
         known_dimensions: Size<Option<f32>>,
         parent_size: Size<Option<f32>>,
@@ -228,7 +228,7 @@ pub(crate) trait LayoutPartialTreeExt: LayoutPartialTree {
     /// Perform a full layout on the node given the specified constraints
     #[inline(always)]
     fn perform_child_layout(
-        &mut self,
+        &self,
         node_id: NodeId,
         known_dimensions: Size<Option<f32>>,
         parent_size: Size<Option<f32>>,
diff --git a/tests/fixtures.rs b/tests/fixtures.rs
index e1f1b8851..92721dc76 100644
--- a/tests/fixtures.rs
+++ b/tests/fixtures.rs
@@ -22,7 +22,7 @@ fn test_measure_function(
     known_dimensions: taffy::geometry::Size<Option<f32>>,
     available_space: taffy::geometry::Size<taffy::style::AvailableSpace>,
     _node_id: taffy::tree::NodeId,
-    node_context: Option<&mut TextMeasure>,
+    node_context: Option<&TextMeasure>,
 ) -> taffy::geometry::Size<f32> {
     use taffy::geometry::AbsoluteAxis;
     use taffy::prelude::*;