Skip to content

Commit

Permalink
Add taffy_tree feature to opt-out of default Taffy tree (DioxusLabs…
Browse files Browse the repository at this point in the history
…#448)

* Quick first draft

* Create separate non-cached node computation

* Revert "Create separate non-cached node computation"

This reverts commit c974efb.

* Move Taffy-specific stuff into taffy_tree module

* Export leaf::compute()

* fmt and clippy

* Remove useless use_rounding method

* Fix broken doc reference

* Sync Measurable is the only Measurable

* Move computation back into compute mod

* Rename feature to taffy_tree

* Optional slotmap

* didn't check feature-gated deps properly

* Make debug not rely on slotmap

* Formatting
  • Loading branch information
martijnarts authored Apr 18, 2023
1 parent 6ce86a9 commit fda1f88
Show file tree
Hide file tree
Showing 13 changed files with 304 additions and 246 deletions.
5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ arrayvec = { version = "0.7", default-features = false }
num-traits = { version = "0.2", default-features = false }
rand = { version = "0.8.5", optional = true }
serde = { version = "1.0", optional = true, features = ["serde_derive"] }
slotmap = "1.0.6"
slotmap = { version = "1.0.6", optional = true }
grid = { version = "0.9.0", optional = true }

[features]
default = ["std", "flexbox", "grid"]
default = ["std", "flexbox", "grid", "taffy_tree"]
flexbox = []
grid = ["alloc", "dep:grid"]
alloc = []
Expand All @@ -32,6 +32,7 @@ random = ["dep:rand"]
yoga_benchmark = []
debug = []
profile = []
taffy_tree = ["dep:slotmap"]

[dev-dependencies]
criterion = "0.4"
Expand Down
30 changes: 13 additions & 17 deletions src/compute/leaf.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
//! Computes size using styles and measure functions

use crate::geometry::{Point, Size};
use crate::style::AvailableSpace;
use crate::tree::NodeId;
use crate::tree::Taffy;
use crate::style::{AvailableSpace, Style};
use crate::tree::Measurable;
use crate::tree::{SizeAndBaselines, SizingMode};
use crate::util::sys::f32_max;
use crate::util::MaybeMath;
Expand All @@ -14,40 +13,37 @@ use crate::util::debug::NODE_LOGGER;

/// Perform full layout on a leaf node
pub(crate) fn perform_layout(
tree: &mut Taffy,
node: NodeId,
style: &Style,
measurable: Option<&impl Measurable>,
known_dimensions: Size<Option<f32>>,
parent_size: Size<Option<f32>>,
available_space: Size<AvailableSpace>,
sizing_mode: SizingMode,
) -> SizeAndBaselines {
compute(tree, node, known_dimensions, parent_size, available_space, sizing_mode)
compute(style, measurable, known_dimensions, parent_size, available_space, sizing_mode)
}

/// Measure a leaf node's size
pub(crate) fn measure_size(
tree: &mut Taffy,
node: NodeId,
style: &Style,
measurable: Option<&impl Measurable>,
known_dimensions: Size<Option<f32>>,
parent_size: Size<Option<f32>>,
available_space: Size<AvailableSpace>,
sizing_mode: SizingMode,
) -> Size<f32> {
compute(tree, node, known_dimensions, parent_size, available_space, sizing_mode).size
compute(style, measurable, known_dimensions, parent_size, available_space, sizing_mode).size
}

/// Compute the size of a leaf node (node with no children)
pub(crate) fn compute(
tree: &mut Taffy,
node: NodeId,
pub fn compute(
style: &Style,
measurable: Option<&impl Measurable>,
known_dimensions: Size<Option<f32>>,
parent_size: Size<Option<f32>>,
available_space: Size<AvailableSpace>,
sizing_mode: SizingMode,
) -> SizeAndBaselines {
let key = node.into();
let style = tree.style(node).unwrap();

// Resolve node's preferred/min/max sizes (width/heights) against the available space (percentages resolve to pixel values)
// For ContentSize mode, we pretend that the node has no size styles as these should be ignored.
let (node_size, node_min_size, node_max_size, aspect_ratio) = match sizing_mode {
Expand Down Expand Up @@ -91,7 +87,7 @@ pub(crate) fn compute(
return SizeAndBaselines { size, first_baselines: Point::NONE };
};

if tree.nodes[key].needs_measure {
if let Some(measurable) = measurable {
// Compute available space
let available_space = Size {
width: available_space
Expand All @@ -107,7 +103,7 @@ pub(crate) fn compute(
};

// Measure node
let measured_size = tree.measure_funcs[key].measure(known_dimensions, available_space);
let measured_size = measurable.measure(known_dimensions, available_space);

let measured_size = Size {
width: measured_size.width,
Expand Down
199 changes: 8 additions & 191 deletions src/compute/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,30 @@
pub(crate) mod common;
pub(crate) mod leaf;

pub use leaf::compute;

#[cfg(feature = "flexbox")]
pub(crate) mod flexbox;

#[cfg(feature = "grid")]
pub(crate) mod grid;

use crate::geometry::{Point, Size};
use crate::style::{AvailableSpace, Display};
use crate::tree::{Layout, LayoutTree, NodeId, RunMode, SizeAndBaselines, SizingMode, Taffy, TaffyError};
use crate::util::sys::round;
use crate::style::AvailableSpace;
use crate::tree::{Layout, LayoutTree, NodeId, SizeAndBaselines, SizingMode};

#[cfg(feature = "flexbox")]
pub use self::flexbox::FlexboxAlgorithm;

#[cfg(feature = "grid")]
pub use self::grid::CssGridAlgorithm;

#[cfg(feature = "taffy_tree")]
pub(crate) mod taffy_tree;

#[cfg(any(feature = "debug", feature = "profile"))]
use crate::util::debug::NODE_LOGGER;

/// Updates the stored layout of the provided `node` and its children
pub(crate) fn compute_layout(
taffy: &mut Taffy,
root: NodeId,
available_space: Size<AvailableSpace>,
) -> Result<(), TaffyError> {
// Recursively compute node layout
let size_and_baselines = perform_node_layout(
taffy,
root,
Size::NONE,
available_space.into_options(),
available_space,
SizingMode::InherentSize,
);

let layout = Layout { order: 0, size: size_and_baselines.size, location: Point::ZERO };
*taffy.layout_mut(root) = layout;

// If rounding is enabled, recursively round the layout's of this node and all children
if taffy.config.use_rounding {
round_layout(taffy, root, 0.0, 0.0);
}

Ok(())
}

/// A common interface that all Taffy layout algorithms conform to
pub trait LayoutAlgorithm {
/// The name of the algorithm (mainly used for debug purposes)
Expand All @@ -76,142 +53,6 @@ pub trait LayoutAlgorithm {
) -> SizeAndBaselines;
}

/// Perform full layout on a node. Chooses which algorithm to use based on the `display` property.
pub(crate) fn perform_node_layout(
tree: &mut Taffy,
node: NodeId,
known_dimensions: Size<Option<f32>>,
parent_size: Size<Option<f32>>,
available_space: Size<AvailableSpace>,
sizing_mode: SizingMode,
) -> SizeAndBaselines {
compute_node_layout(tree, node, known_dimensions, parent_size, available_space, RunMode::PeformLayout, sizing_mode)
}

/// Measure a node's size. Chooses which algorithm to use based on the `display` property.
pub(crate) fn measure_node_size(
tree: &mut Taffy,
node: NodeId,
known_dimensions: Size<Option<f32>>,
parent_size: Size<Option<f32>>,
available_space: Size<AvailableSpace>,
sizing_mode: SizingMode,
) -> Size<f32> {
compute_node_layout(tree, node, known_dimensions, parent_size, available_space, RunMode::ComputeSize, sizing_mode)
.size
}

/// Updates the stored layout of the provided `node` and its children
fn compute_node_layout(
tree: &mut Taffy,
node: NodeId,
known_dimensions: Size<Option<f32>>,
parent_size: Size<Option<f32>>,
available_space: Size<AvailableSpace>,
run_mode: RunMode,
sizing_mode: SizingMode,
) -> SizeAndBaselines {
#[cfg(any(feature = "debug", feature = "profile"))]
NODE_LOGGER.push_node(node);
#[cfg(feature = "debug")]
println!();

let node_key = node.into();
let has_children = !tree.children[node_key].is_empty();

// First we check if we have a cached result for the given input
let cache_run_mode = if !has_children { RunMode::PeformLayout } else { run_mode };
if let Some(cached_size_and_baselines) =
tree.nodes[node_key].cache.get(known_dimensions, available_space, cache_run_mode)
{
#[cfg(feature = "debug")]
NODE_LOGGER.labelled_debug_log("CACHE", cached_size_and_baselines.size);
#[cfg(feature = "debug")]
debug_log_node(known_dimensions, parent_size, available_space, run_mode, sizing_mode);
#[cfg(any(feature = "debug", feature = "profile"))]
NODE_LOGGER.pop_node();
return cached_size_and_baselines;
}

#[cfg(feature = "debug")]
debug_log_node(known_dimensions, parent_size, available_space, run_mode, sizing_mode);

/// Inlined function generic over the LayoutAlgorithm to reduce code duplication
#[inline(always)]
fn perform_computations<Algorithm: LayoutAlgorithm>(
tree: &mut impl LayoutTree,
node: NodeId,
known_dimensions: Size<Option<f32>>,
parent_size: Size<Option<f32>>,
available_space: Size<AvailableSpace>,
run_mode: RunMode,
sizing_mode: SizingMode,
) -> SizeAndBaselines {
#[cfg(feature = "debug")]
NODE_LOGGER.log(Algorithm::NAME);

match run_mode {
RunMode::PeformLayout => {
Algorithm::perform_layout(tree, node, known_dimensions, parent_size, available_space, sizing_mode)
}
RunMode::ComputeSize => {
Algorithm::measure_size(tree, node, known_dimensions, parent_size, available_space, sizing_mode).into()
}
}
}

let display_mode = tree.nodes[node_key].style.display;
let computed_size_and_baselines = match (display_mode, has_children) {
(Display::None, _) => perform_computations::<HiddenAlgorithm>(
tree,
node,
known_dimensions,
parent_size,
available_space,
run_mode,
sizing_mode,
),
#[cfg(feature = "flexbox")]
(Display::Flex, true) => perform_computations::<FlexboxAlgorithm>(
tree,
node,
known_dimensions,
parent_size,
available_space,
run_mode,
sizing_mode,
),
#[cfg(feature = "grid")]
(Display::Grid, true) => perform_computations::<CssGridAlgorithm>(
tree,
node,
known_dimensions,
parent_size,
available_space,
run_mode,
sizing_mode,
),
(_, false) => match run_mode {
RunMode::PeformLayout => {
leaf::perform_layout(tree, node, known_dimensions, parent_size, available_space, sizing_mode)
}
RunMode::ComputeSize => {
leaf::measure_size(tree, node, known_dimensions, parent_size, available_space, sizing_mode).into()
}
},
};

// Cache result
tree.nodes[node_key].cache.store(known_dimensions, available_space, cache_run_mode, computed_size_and_baselines);

#[cfg(feature = "debug")]
NODE_LOGGER.labelled_debug_log("RESULT", computed_size_and_baselines.size);
#[cfg(any(feature = "debug", feature = "profile"))]
NODE_LOGGER.pop_node();

computed_size_and_baselines
}

#[cfg(feature = "debug")]
fn debug_log_node(
known_dimensions: Size<Option<f32>>,
Expand All @@ -228,7 +69,7 @@ fn debug_log_node(
}

/// The public interface to Taffy's hidden node algorithm implementation
struct HiddenAlgorithm;
pub struct HiddenAlgorithm;
impl LayoutAlgorithm for HiddenAlgorithm {
const NAME: &'static str = "NONE";

Expand Down Expand Up @@ -272,30 +113,6 @@ fn perform_hidden_layout(tree: &mut impl LayoutTree, node: NodeId) {
}
}

/// Rounds the calculated [`Layout`] to exact pixel values
/// In order to ensure that no gaps in the layout are introduced we:
/// - Always round based on the absolute coordinates rather than parent-relative coordinates
/// - Compute width/height by first rounding the top/bottom/left/right and then computing the difference
/// rather than rounding the width/height directly
///
/// See <https://github.com/facebook/yoga/commit/aa5b296ac78f7a22e1aeaf4891243c6bb76488e2> for more context
fn round_layout(tree: &mut impl LayoutTree, node: NodeId, abs_x: f32, abs_y: f32) {
let layout = tree.layout_mut(node);
let abs_x = abs_x + layout.location.x;
let abs_y = abs_y + layout.location.y;

layout.location.x = round(layout.location.x);
layout.location.y = round(layout.location.y);
layout.size.width = round(abs_x + layout.size.width) - round(abs_x);
layout.size.height = round(abs_y + layout.size.height) - round(abs_y);

let child_count = tree.child_count(node);
for index in 0..child_count {
let child = tree.child(node, index);
round_layout(tree, child, abs_x, abs_y);
}
}

#[cfg(test)]
mod tests {
use super::perform_hidden_layout;
Expand Down
Loading

0 comments on commit fda1f88

Please sign in to comment.