Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions benches/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,8 @@ harness = false
name = "mixed"
path = "benches/mixed.rs"
harness = false

[[bench]]
name = "display_contents"
path = "benches/display_contents.rs"
harness = false
171 changes: 171 additions & 0 deletions benches/benches/display_contents.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
//! Benchmarks for Display::Contents overhead
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use taffy::prelude::*;
use taffy::style::Style;

/// Build a flat flex tree with N children, no contents nodes.
fn build_flat_tree(n: usize) -> (TaffyTree<()>, NodeId) {
let mut taffy = TaffyTree::new();
let mut children = Vec::with_capacity(n);
for _ in 0..n {
let child = taffy
.new_leaf(Style {
size: Size { width: Dimension::from_length(30.0), height: auto() },
..Default::default()
})
.unwrap();
children.push(child);
}
let root = taffy
.new_with_children(
Style {
display: Display::Flex,
size: Size {
width: Dimension::from_length(1000.0),
height: Dimension::from_length(500.0),
},
..Default::default()
},
&children,
)
.unwrap();
(taffy, root)
}

/// Build a flex tree with N children, where every 5th child is wrapped in a
/// Display::Contents node (simulating GestureDetector-like wrappers).
fn build_tree_with_contents(n: usize) -> (TaffyTree<()>, NodeId) {
let mut taffy = TaffyTree::new();
let mut children = Vec::with_capacity(n);
for i in 0..n {
let leaf = taffy
.new_leaf(Style {
size: Size { width: Dimension::from_length(30.0), height: auto() },
..Default::default()
})
.unwrap();
if i % 5 == 0 {
// Wrap in contents node
let contents = taffy
.new_with_children(
Style { display: Display::Contents, ..Default::default() },
&[leaf],
)
.unwrap();
children.push(contents);
} else {
children.push(leaf);
}
}
let root = taffy
.new_with_children(
Style {
display: Display::Flex,
size: Size {
width: Dimension::from_length(1000.0),
height: Dimension::from_length(500.0),
},
..Default::default()
},
&children,
)
.unwrap();
(taffy, root)
}

/// Build a deep tree (10 levels) with N leaves at the bottom, no contents.
fn build_deep_tree(depth: usize, breadth: usize) -> (TaffyTree<()>, NodeId) {
let mut taffy = TaffyTree::new();
let root = build_deep_node(&mut taffy, depth, breadth);
(taffy, root)
}

fn build_deep_node(taffy: &mut TaffyTree<()>, depth: usize, breadth: usize) -> NodeId {
if depth == 0 {
return taffy
.new_leaf(Style {
size: Size { width: Dimension::from_length(10.0), height: Dimension::from_length(10.0) },
..Default::default()
})
.unwrap();
}
let mut children = Vec::with_capacity(breadth);
for _ in 0..breadth {
children.push(build_deep_node(taffy, depth - 1, breadth));
}
taffy
.new_with_children(
Style { display: Display::Flex, ..Default::default() },
&children,
)
.unwrap()
}

fn bench_no_contents(c: &mut Criterion) {
let mut group = c.benchmark_group("display_contents/no_contents");
for &n in &[10, 100, 1000] {
group.bench_with_input(BenchmarkId::new("flat", n), &n, |b, &n| {
b.iter_batched(
|| build_flat_tree(n),
|(mut taffy, root)| {
taffy.compute_layout(root, Size::MAX_CONTENT).unwrap();
},
criterion::BatchSize::SmallInput,
);
});
}
// Deep tree: 4^6 = 4096 nodes
group.bench_function("deep_4x6", |b| {
b.iter_batched(
|| build_deep_tree(6, 4),
|(mut taffy, root)| {
taffy.compute_layout(root, Size::MAX_CONTENT).unwrap();
},
criterion::BatchSize::SmallInput,
);
});
group.finish();
}

fn bench_with_contents(c: &mut Criterion) {
let mut group = c.benchmark_group("display_contents/with_contents");
for &n in &[10, 100, 1000] {
group.bench_with_input(BenchmarkId::new("flat_20pct_contents", n), &n, |b, &n| {
b.iter_batched(
|| build_tree_with_contents(n),
|(mut taffy, root)| {
taffy.compute_layout(root, Size::MAX_CONTENT).unwrap();
},
criterion::BatchSize::SmallInput,
);
});
}
group.finish();
}

fn bench_relayout(c: &mut Criterion) {
let mut group = c.benchmark_group("display_contents/relayout");
// Measure cost of repeated layouts (resolved_children recomputed each time)
for &n in &[100, 1000] {
group.bench_with_input(BenchmarkId::new("no_contents", n), &n, |b, &n| {
let (mut taffy, root) = build_flat_tree(n);
taffy.compute_layout(root, Size::MAX_CONTENT).unwrap();
b.iter(|| {
taffy.mark_dirty(root).unwrap();
taffy.compute_layout(root, Size::MAX_CONTENT).unwrap();
});
});
group.bench_with_input(BenchmarkId::new("with_contents", n), &n, |b, &n| {
let (mut taffy, root) = build_tree_with_contents(n);
taffy.compute_layout(root, Size::MAX_CONTENT).unwrap();
b.iter(|| {
taffy.mark_dirty(root).unwrap();
taffy.compute_layout(root, Size::MAX_CONTENT).unwrap();
});
});
}
group.finish();
}

criterion_group!(benches, bench_no_contents, bench_with_contents, bench_relayout);
criterion_main!(benches);
18 changes: 12 additions & 6 deletions src/style/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,16 @@ pub trait CheapCloneStr:
AsRef<str> + for<'a> From<&'a str> + From<String> + PartialEq + Eq + Clone + Default + Debug + 'static
{
}
#[cfg(any(feature = "alloc", feature = "std"))]
impl<T> CheapCloneStr for T where
T: AsRef<str> + for<'a> From<&'a str> + From<String> + PartialEq + Eq + Clone + Default + Debug + 'static
{
}

/// Trait that represents a cheaply clonable string. If you're unsure what to use here
/// consider `Arc<str>` or `string_cache::Atom`.
#[cfg(not(any(feature = "alloc", feature = "std")))]
pub trait CheapCloneStr {}
#[cfg(any(feature = "alloc", feature = "std"))]
impl<T> CheapCloneStr for T where
T: AsRef<str> + for<'a> From<&'a str> + From<String> + PartialEq + Eq + Clone + Default + Debug + 'static
{
}
#[cfg(not(any(feature = "alloc", feature = "std")))]
impl<T> CheapCloneStr for T {}

Expand Down Expand Up @@ -188,6 +188,10 @@ pub enum Display {
/// The children will follow the CSS Grid layout algorithm
#[cfg(feature = "grid")]
Grid,
/// The node is invisible to layout: it generates no box, and its children
/// are laid out as if they were direct children of this node's parent.
/// Equivalent to CSS `display: contents`.
Contents,
/// The node is hidden, and it's children will also be hidden
None,
}
Expand Down Expand Up @@ -219,6 +223,7 @@ impl Default for Display {
#[cfg(feature = "parse")]
crate::util::parse::impl_parse_for_keyword_enum!(Display,
"none" => None,
"contents" => Contents,
#[cfg(feature = "flexbox")]
"flex" => Flex,
#[cfg(feature = "grid")]
Expand All @@ -231,6 +236,7 @@ impl core::fmt::Display for Display {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Display::None => write!(f, "NONE"),
Display::Contents => write!(f, "CONTENTS"),
#[cfg(feature = "block_layout")]
Display::Block => write!(f, "BLOCK"),
#[cfg(feature = "flexbox")]
Expand Down Expand Up @@ -670,7 +676,7 @@ impl<S: CheapCloneStr> CoreStyle for Style<S> {
#[inline(always)]
fn box_generation_mode(&self) -> BoxGenerationMode {
match self.display {
Display::None => BoxGenerationMode::None,
Display::None | Display::Contents => BoxGenerationMode::None,
_ => BoxGenerationMode::Normal,
}
}
Expand Down
Loading
Loading