From 6031fa21b5800de29385e8426daa4f4929754601 Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Thu, 12 Oct 2023 18:57:23 +0100 Subject: [PATCH] Refactor benchmarks (#472) * Refactor benchmarks: - Abstract tree creation behind a trait - Implement trait for Yoga and Taffy * Remove unused random_style module * Set super deep benchmarks back to branching factor of 1 * Feature flag small and large benchmarks * Split benchmark definitions into functions * Add deep(auto) benchmarks * Improve super-deep hierarchy benchmarks --- benches/Cargo.toml | 3 + benches/benches/flexbox.rs | 303 +++++++++++++++++-------------- benches/benches/tree_creation.rs | 6 +- benches/src/lib.rs | 150 ++++++++++++--- benches/src/random_style.rs | 53 ------ benches/src/taffy_helpers.rs | 55 ++++++ benches/src/yoga_helpers.rs | 108 +++++++++-- src/tree/taffy_tree/tree.rs | 5 + 8 files changed, 446 insertions(+), 237 deletions(-) delete mode 100644 benches/src/random_style.rs create mode 100644 benches/src/taffy_helpers.rs diff --git a/benches/Cargo.toml b/benches/Cargo.toml index d270b1326..b83939ce5 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -23,6 +23,9 @@ slotmap = { version = "1.0.6", optional = true } [features] yoga = ["dep:yoga", "dep:slotmap", "dep:ordered-float"] +yoga-super-deep = ["yoga"] +small = [] +large = [] [[bench]] name = "tree_creation" diff --git a/benches/benches/flexbox.rs b/benches/benches/flexbox.rs index 393bb996e..a2c532149 100644 --- a/benches/benches/flexbox.rs +++ b/benches/benches/flexbox.rs @@ -1,155 +1,90 @@ //! This file includes benchmarks for very large, pseudo-randomly generated trees use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; -use rand::prelude::*; -use rand_chacha::ChaCha8Rng; +use rand::Rng; use taffy::prelude::*; -use taffy::style::Style; -use taffy_benchmarks::{build_deep_tree, Randomizeable}; +use taffy::style::Dimension; +use taffy::style::Style as TaffyStyle; + +use taffy_benchmarks::{BuildTreeExt, FixedStyleGenerator, GenStyle, TaffyTreeBuilder}; -#[cfg(feature = "yoga")] -use slotmap::SlotMap; #[cfg(feature = "yoga")] use taffy_benchmarks::yoga_helpers; #[cfg(feature = "yoga")] -use yoga_helpers::yg; - -/// Build a random leaf node -fn build_random_leaf(taffy: &mut Taffy, rng: &mut ChaCha8Rng) -> NodeId { - taffy.new_with_children(Style::random(rng), &[]).unwrap() -} +use yoga_helpers::{yg, YogaTreeBuilder}; -/// A tree with many children that have shallow depth -fn build_taffy_flat_hierarchy(total_node_count: u32) -> (Taffy, NodeId) { - let mut taffy = Taffy::new(); - let mut rng = ChaCha8Rng::seed_from_u64(12345); - let mut children = Vec::new(); - let mut node_count = 0; - - while node_count < total_node_count { - let sub_children_count = rng.gen_range(1..=4); - let sub_children: Vec = - (0..sub_children_count).map(|_| build_random_leaf(&mut taffy, &mut rng)).collect(); - let node = taffy.new_with_children(Style::random(&mut rng), &sub_children).unwrap(); - - children.push(node); - node_count += 1 + sub_children_count; +fn random_dimension(rng: &mut impl Rng) -> Dimension { + match rng.gen_range(0.0..=1.0) { + rand if rand < 0.2 => Dimension::Auto, + rand if rand < 0.8 => Dimension::Length(rng.gen_range(0.0..500.0)), + _ => Dimension::Percent(rng.gen_range(0.0..1.0)), } - - let root = taffy.new_with_children(Style::DEFAULT, children.as_slice()).unwrap(); - (taffy, root) } - -#[cfg(feature = "yoga")] -/// A tree with many children that have shallow depth -fn build_yoga_flat_hierarchy(total_node_count: u32) -> (yg::YogaTree, yg::NodeId) { - let mut tree = SlotMap::new(); - let mut rng = ChaCha8Rng::seed_from_u64(12345); - let mut children = Vec::new(); - let mut node_count = 0; - - while node_count < total_node_count { - let sub_children_count = rng.gen_range(1..=4); - let sub_children: Vec = (0..sub_children_count) - .map(|_| yoga_helpers::new_with_children(&mut tree, &Style::random(&mut rng), vec![])) - .collect(); - let node = yoga_helpers::new_with_children(&mut tree, &Style::random(&mut rng), sub_children); - - children.push(node); - node_count += 1 + sub_children_count; +pub struct RandomStyleGenerator; +impl GenStyle for RandomStyleGenerator { + fn create_leaf_style(&mut self, rng: &mut impl Rng) -> TaffyStyle { + TaffyStyle { size: Size { width: random_dimension(rng), height: random_dimension(rng) }, ..Default::default() } + } + fn create_container_style(&mut self, rng: &mut impl Rng) -> TaffyStyle { + TaffyStyle { size: Size { width: random_dimension(rng), height: random_dimension(rng) }, ..Default::default() } } - - let root = yoga_helpers::new_with_children(&mut tree, &Style::DEFAULT, children); - (tree, root) } -/// A tree with a higher depth for a more realistic scenario -fn build_taffy_deep_hierarchy(node_count: u32, branching_factor: u32) -> (Taffy, NodeId) { - let mut rng = ChaCha8Rng::seed_from_u64(12345); - let mut build_leaf_node = |taffy: &mut Taffy| build_random_leaf(taffy, &mut rng); - let mut rng = ChaCha8Rng::seed_from_u64(12345); - let mut build_flex_node = - |taffy: &mut Taffy, children: Vec| taffy.new_with_children(Style::random(&mut rng), &children).unwrap(); - - let mut taffy = Taffy::new(); - let tree = build_deep_tree(&mut taffy, node_count, branching_factor, &mut build_leaf_node, &mut build_flex_node); - let root = taffy.new_with_children(Style::DEFAULT, &tree).unwrap(); - (taffy, root) +/// A deep tree that matches the shape and styling that yoga use on their benchmarks +fn build_flat_hierarchy, TreeBuilder: BuildTreeExt>( + target_node_count: u32, + style_generator: impl FnOnce() -> G, +) -> (TreeBuilder::Tree, TreeBuilder::Node) { + let tree_builder = TreeBuilder::new(style_generator()); + tree_builder.build_flat_hierarchy(target_node_count) } -#[cfg(feature = "yoga")] -/// A tree with a higher depth for a more realistic scenario -fn build_yoga_deep_hierarchy(node_count: u32, branching_factor: u32) -> (yg::YogaTree, yg::NodeId) { - let mut rng = ChaCha8Rng::seed_from_u64(12345); - let mut build_leaf_node = - |tree: &mut yg::YogaTree| yoga_helpers::new_with_children(tree, &Style::random(&mut rng), vec![]); - let mut rng = ChaCha8Rng::seed_from_u64(12345); - let mut build_flex_node = |tree: &mut yg::YogaTree, children: Vec| { - yoga_helpers::new_with_children(tree, &Style::random(&mut rng), children) - }; - - let mut tree = SlotMap::new(); - let children = build_deep_tree(&mut tree, node_count, branching_factor, &mut build_leaf_node, &mut build_flex_node); - let root = yoga_helpers::new_with_children(&mut tree, &Style::DEFAULT, children); - - (tree, root) +/// A deep tree that matches the shape and styling that yoga use on their benchmarks +fn build_deep_hierarchy, TreeBuilder: BuildTreeExt>( + node_count: u32, + branching_factor: u32, + style_generator: impl FnOnce() -> G, +) -> (TreeBuilder::Tree, TreeBuilder::Node) { + let tree_builder = TreeBuilder::new(style_generator()); + tree_builder.build_deep_hierarchy(node_count, branching_factor) } /// A deep tree that matches the shape and styling that yoga use on their benchmarks -fn build_taffy_huge_nested_hierarchy(node_count: u32, branching_factor: u32) -> (Taffy, NodeId) { - let style = Style { - size: Size { width: Dimension::Length(10.0), height: Dimension::Length(10.0) }, - flex_grow: 1.0, - ..Default::default() - }; - let mut build_leaf_node = |taffy: &mut Taffy| taffy.new_leaf(style.clone()).unwrap(); - let mut build_flex_node = - |taffy: &mut Taffy, children: Vec| taffy.new_with_children(style.clone(), &children).unwrap(); - - let mut taffy = Taffy::new(); - let tree = build_deep_tree(&mut taffy, node_count, branching_factor, &mut build_leaf_node, &mut build_flex_node); - let root = taffy.new_with_children(Style::DEFAULT, &tree).unwrap(); - (taffy, root) +fn build_super_deep_hierarchy, TreeBuilder: BuildTreeExt>( + depth: u32, + nodes_per_level: u32, + style_generator: impl FnOnce() -> G, +) -> (TreeBuilder::Tree, TreeBuilder::Node) { + let tree_builder = TreeBuilder::new(style_generator()); + tree_builder.build_super_deep_hierarchy(depth, nodes_per_level) } -#[cfg(feature = "yoga")] /// A deep tree that matches the shape and styling that yoga use on their benchmarks -fn build_yoga_huge_nested_hierarchy(node_count: u32, branching_factor: u32) -> (yg::YogaTree, yg::NodeId) { - let style = Style { - size: Size { width: Dimension::Length(10.0), height: Dimension::Length(10.0) }, - flex_grow: 1.0, - ..Default::default() - }; - let mut build_leaf_node = |tree: &mut yg::YogaTree| -> yg::NodeId { - let mut node = yg::Node::new(); - yoga_helpers::apply_taffy_style(&mut node, &style.clone()); - tree.insert(node) - }; - let mut build_flex_node = |tree: &mut yg::YogaTree, children: Vec| -> yg::NodeId { - let mut node = yg::Node::new(); - yoga_helpers::apply_taffy_style(&mut node, &style.clone()); - for (i, child) in children.into_iter().enumerate() { - node.insert_child(&mut tree[child], i as u32); - } - tree.insert(node) - }; - - let mut tree = SlotMap::new(); - let children = build_deep_tree(&mut tree, node_count, branching_factor, &mut build_leaf_node, &mut build_flex_node); - let mut root = yg::Node::new(); - for (i, child) in children.into_iter().enumerate() { - root.insert_child(&mut tree[child], i as u32); - } - let root = tree.insert(root); - (tree, root) +fn build_huge_nested_hierarchy, TreeBuilder: BuildTreeExt>( + node_count: u32, + branching_factor: u32, + style_generator: impl FnOnce() -> G, +) -> (TreeBuilder::Tree, TreeBuilder::Node) { + let tree_builder = TreeBuilder::new(style_generator()); + tree_builder.build_deep_hierarchy(node_count, branching_factor) } -fn taffy_benchmarks(c: &mut Criterion) { +fn huge_nested_benchmarks(c: &mut Criterion) { let mut group = c.benchmark_group("yoga 'huge nested'"); - for node_count in [1_000u32, 10_000, 100_000].iter() { + let style = Style { size: length(10.0), flex_grow: 1.0, ..Default::default() }; + let style_gen = || FixedStyleGenerator(style.clone()); + for node_count in [ + #[cfg(feature = "small")] + 1_000u32, + 10_000, + #[cfg(feature = "large")] + 100_000, + ] + .iter() + { #[cfg(feature = "yoga")] group.bench_with_input(BenchmarkId::new("Yoga", node_count), node_count, |b, &node_count| { b.iter_batched( - || build_yoga_huge_nested_hierarchy(node_count, 10), + || build_huge_nested_hierarchy::<_, YogaTreeBuilder<_, _>>(node_count, 10, style_gen), |(mut tree, root)| { tree[root].calculate_layout(f32::INFINITY, f32::INFINITY, yg::Direction::LTR); }, @@ -158,24 +93,34 @@ fn taffy_benchmarks(c: &mut Criterion) { }); group.bench_with_input(BenchmarkId::new("Taffy", node_count), node_count, |b, &node_count| { b.iter_batched( - || build_taffy_huge_nested_hierarchy(node_count, 10), + || build_huge_nested_hierarchy::<_, TaffyTreeBuilder<_, _>>(node_count, 10, style_gen), |(mut taffy, root)| taffy.compute_layout(root, Size::MAX_CONTENT).unwrap(), criterion::BatchSize::SmallInput, ) }); } group.finish(); +} +fn wide_benchmarks(c: &mut Criterion) { // Decrease sample size, because the tasks take longer - let mut group = c.benchmark_group("big trees (wide)"); + let mut group = c.benchmark_group("Wide tree"); group.sample_size(10); - for node_count in [1_000u32, 10_000, 100_000].iter() { + for node_count in [ + #[cfg(feature = "small")] + 1_000u32, + 10_000, + #[cfg(feature = "large")] + 100_000, + ] + .iter() + { #[cfg(feature = "yoga")] let benchmark_id = BenchmarkId::new(format!("Yoga (2-level hierarchy)"), node_count); #[cfg(feature = "yoga")] group.bench_with_input(benchmark_id, node_count, |b, &node_count| { b.iter_batched( - || build_yoga_flat_hierarchy(node_count), + || build_flat_hierarchy::<_, YogaTreeBuilder<_, _>>(node_count, || RandomStyleGenerator), |(mut tree, root)| { tree[root].calculate_layout(f32::INFINITY, f32::INFINITY, yg::Direction::LTR); }, @@ -185,23 +130,30 @@ fn taffy_benchmarks(c: &mut Criterion) { let benchmark_id = BenchmarkId::new(format!("Taffy (2-level hierarchy)"), node_count); group.bench_with_input(benchmark_id, node_count, |b, &node_count| { b.iter_batched( - || build_taffy_flat_hierarchy(node_count), + || build_flat_hierarchy::<_, TaffyTreeBuilder<_, _>>(node_count, || RandomStyleGenerator), |(mut taffy, root)| taffy.compute_layout(root, Size::MAX_CONTENT).unwrap(), criterion::BatchSize::SmallInput, ) }); } group.finish(); +} +fn deep_random_benchmarks(c: &mut Criterion) { // Decrease sample size, because the tasks take longer - let mut group = c.benchmark_group("big trees (deep)"); + let mut group = c.benchmark_group("Deep tree (random size)"); group.sample_size(10); - let benches = [(4000, "(12-level hierarchy)"), (10_000, "(14-level hierarchy)"), (100_000, "(17-level hierarchy)")]; + let benches = [ + (4000, "(12-level hierarchy)"), + (10_000, "(14-level hierarchy)"), + #[cfg(feature = "large")] + (100_000, "(17-level hierarchy)"), + ]; for (node_count, label) in benches.iter() { #[cfg(feature = "yoga")] group.bench_with_input(BenchmarkId::new(format!("Yoga {label}"), node_count), node_count, |b, &node_count| { b.iter_batched( - || build_yoga_deep_hierarchy(node_count, 2), + || build_deep_hierarchy::<_, YogaTreeBuilder<_, _>>(node_count, 2, || RandomStyleGenerator), |(mut tree, root)| { tree[root].calculate_layout(f32::INFINITY, f32::INFINITY, yg::Direction::LTR); }, @@ -210,30 +162,41 @@ fn taffy_benchmarks(c: &mut Criterion) { }); group.bench_with_input(BenchmarkId::new(format!("Taffy {label}"), node_count), node_count, |b, &node_count| { b.iter_batched( - || build_taffy_deep_hierarchy(node_count, 2), + || build_deep_hierarchy::<_, TaffyTreeBuilder<_, _>>(node_count, 2, || RandomStyleGenerator), |(mut taffy, root)| taffy.compute_layout(root, Size::MAX_CONTENT).unwrap(), criterion::BatchSize::SmallInput, ) }); } group.finish(); +} - let mut group = c.benchmark_group("super deep (1000-level hierarchy)"); +fn deep_auto_benchmarks(c: &mut Criterion) { + // Decrease sample size, because the tasks take longer + let mut group = c.benchmark_group("Deep tree (auto size)"); group.sample_size(10); - for node_count in [1000u32].iter() { + let style = Style { flex_grow: 1.0, margin: length(10.0), ..Default::default() }; + let style_gen = || FixedStyleGenerator(style.clone()); + let benches = [ + (4000, "(12-level hierarchy)"), + (10_000, "(14-level hierarchy)"), + #[cfg(feature = "large")] + (100_000, "(17-level hierarchy)"), + ]; + for (node_count, label) in benches.iter() { #[cfg(feature = "yoga")] - group.bench_with_input(BenchmarkId::new("Yoga", node_count), node_count, |b, &node_count| { + group.bench_with_input(BenchmarkId::new(format!("Yoga {label}"), node_count), node_count, |b, &node_count| { b.iter_batched( - || build_yoga_deep_hierarchy(node_count, 2), + || build_deep_hierarchy::<_, YogaTreeBuilder<_, _>>(node_count, 2, style_gen), |(mut tree, root)| { tree[root].calculate_layout(f32::INFINITY, f32::INFINITY, yg::Direction::LTR); }, criterion::BatchSize::SmallInput, ) }); - group.bench_with_input(BenchmarkId::new("Taffy", node_count), node_count, |b, &node_count| { + group.bench_with_input(BenchmarkId::new(format!("Taffy {label}"), node_count), node_count, |b, &node_count| { b.iter_batched( - || build_taffy_deep_hierarchy(node_count, 2), + || build_deep_hierarchy::<_, TaffyTreeBuilder<_, _>>(node_count, 2, style_gen), |(mut taffy, root)| taffy.compute_layout(root, Size::MAX_CONTENT).unwrap(), criterion::BatchSize::SmallInput, ) @@ -242,5 +205,65 @@ fn taffy_benchmarks(c: &mut Criterion) { group.finish(); } +fn super_deep_benchmarks(c: &mut Criterion) { + let mut group = c.benchmark_group("super deep"); + group.sample_size(10); + struct SuperDeepStyleGen; + impl GenStyle for SuperDeepStyleGen { + fn create_leaf_style(&mut self, _rng: &mut impl Rng) -> TaffyStyle { + // let flex_direction = if rng.gen::() < 0.5 { FlexDirection::Column } else { FlexDirection::Row }; + let flex_direction = FlexDirection::Row; + Style { flex_direction, flex_grow: 1.0, margin: length(10.0), ..Default::default() } + } + fn create_container_style(&mut self, rng: &mut impl Rng) -> TaffyStyle { + self.create_leaf_style(rng) + } + } + for depth in [ + #[cfg(feature = "small")] + 50u32, + 100, + #[cfg(feature = "large")] + 200, + ] + .iter() + { + // Yoga is particularly slow at these benchmarks, so we gate them behind a separate feature flag + #[cfg(all(feature = "yoga", feature = "yoga-super-deep"))] + group.bench_with_input(BenchmarkId::new("Yoga", depth), depth, |b, &depth| { + b.iter_batched( + || build_super_deep_hierarchy::<_, YogaTreeBuilder<_, _>>(depth, 3, || SuperDeepStyleGen), + |(mut tree, root)| { + tree[root].calculate_layout(800., 800., yg::Direction::LTR); + }, + criterion::BatchSize::SmallInput, + ) + }); + group.bench_with_input(BenchmarkId::new("Taffy", depth), depth, |b, &depth| { + b.iter_batched( + || build_super_deep_hierarchy::<_, TaffyTreeBuilder<_, _>>(depth, 3, || SuperDeepStyleGen), + |(mut taffy, root)| { + taffy + .compute_layout( + root, + Size { width: AvailableSpace::Definite(800.), height: AvailableSpace::Definite(800.) }, + ) + .unwrap() + }, + criterion::BatchSize::SmallInput, + ) + }); + } + group.finish(); +} + +fn taffy_benchmarks(c: &mut Criterion) { + huge_nested_benchmarks(c); + wide_benchmarks(c); + deep_auto_benchmarks(c); + deep_random_benchmarks(c); + super_deep_benchmarks(c); +} + criterion_group!(benches, taffy_benchmarks); criterion_main!(benches); diff --git a/benches/benches/tree_creation.rs b/benches/benches/tree_creation.rs index 0c2178d50..6f0c83dd1 100644 --- a/benches/benches/tree_creation.rs +++ b/benches/benches/tree_creation.rs @@ -48,14 +48,14 @@ fn build_yoga_flat_hierarchy(total_node_count: u32) -> (yg::YogaTree, yg::NodeId while node_count < total_node_count { let sub_children_count = rng.gen_range(1..=4); let sub_children: Vec = - (0..sub_children_count).map(|_| yoga_helpers::new_default_style_with_children(&mut tree, vec![])).collect(); - let node = yoga_helpers::new_default_style_with_children(&mut tree, sub_children); + (0..sub_children_count).map(|_| yoga_helpers::new_default_style_with_children(&mut tree, &[])).collect(); + let node = yoga_helpers::new_default_style_with_children(&mut tree, &sub_children); children.push(node); node_count += 1 + sub_children_count; } - let root = yoga_helpers::new_default_style_with_children(&mut tree, children); + let root = yoga_helpers::new_default_style_with_children(&mut tree, &children); (tree, root) } diff --git a/benches/src/lib.rs b/benches/src/lib.rs index 42c5a4662..6d67c6774 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -1,31 +1,127 @@ +// As each benchmark suite is compiled as a seperate crate and uses different helpers, we end up with a bunch +// of false positives for this lint. So let's just disable it for this code. +#![allow(dead_code)] + +pub mod taffy_helpers; +pub use taffy_helpers::TaffyTreeBuilder; + #[cfg(feature = "yoga")] pub mod yoga_helpers; +#[cfg(feature = "yoga")] +pub use yoga_helpers::YogaTreeBuilder; + +use rand::distributions::uniform::SampleRange; +use rand::{Rng, SeedableRng}; +use rand_chacha::ChaCha8Rng; +use taffy::style::Style as TaffyStyle; + +pub const STANDARD_RNG_SEED: u64 = 12345; + +pub trait GenStyle { + fn create_leaf_style(&mut self, rng: &mut impl Rng) -> Style; + fn create_container_style(&mut self, rng: &mut impl Rng) -> Style; + fn create_root_style(&mut self, _rng: &mut impl Rng) -> Style { + Default::default() + } +} + +pub struct FixedStyleGenerator(pub TaffyStyle); +impl GenStyle for FixedStyleGenerator { + fn create_leaf_style(&mut self, _rng: &mut impl Rng) -> TaffyStyle { + self.0.clone() + } + fn create_container_style(&mut self, _rng: &mut impl Rng) -> TaffyStyle { + self.0.clone() + } +} + +pub trait BuildTree>: Sized { + type Tree; + type Node: Clone; + + fn with_rng(rng: R, style_generator: G) -> Self; + + fn random_usize(&mut self, range: impl SampleRange) -> usize; + fn create_leaf_node(&mut self) -> Self::Node; + fn create_container_node(&mut self, children: &[Self::Node]) -> Self::Node; + fn set_root_children(&mut self, children: &[Self::Node]); + fn total_node_count(&mut self) -> usize; + fn into_tree_and_root(self) -> (Self::Tree, Self::Node); + + fn build_n_leaf_nodes(&mut self, n: usize) -> Vec { + (0..n).map(|_| self.create_leaf_node()).collect() + } -mod random_style; -pub use random_style::Randomizeable; - -/// A helper function to recursively construct a deep tree -#[allow(dead_code)] -pub fn build_deep_tree( - tree: &mut T, - max_nodes: u32, - branching_factor: u32, - create_leaf_node: &mut impl FnMut(&mut T) -> N, - create_container_node: &mut impl FnMut(&mut T, Vec) -> N, -) -> Vec { - if max_nodes <= branching_factor { - // Build leaf nodes - return (0..max_nodes).map(|_| create_leaf_node(tree)).collect(); - } - - // Add another layer to the tree - // Each child gets an equal amount of the remaining nodes - (0..branching_factor) - .map(|_| { - let max_nodes = (max_nodes - branching_factor) / branching_factor; - let sub_children = - build_deep_tree(tree, max_nodes, branching_factor, create_leaf_node, create_container_node); - create_container_node(tree, sub_children) - }) - .collect() + /// A helper function to recursively construct a deep tree + fn build_deep_tree(&mut self, max_nodes: u32, branching_factor: u32) -> Vec { + if max_nodes <= branching_factor { + // Build leaf nodes + return (0..max_nodes).map(|_| self.create_leaf_node()).collect(); + } + + // Add another layer to the tree + // Each child gets an equal amount of the remaining nodes + (0..branching_factor) + .map(|_| { + let max_nodes = (max_nodes - branching_factor) / branching_factor; + let children = self.build_deep_tree(max_nodes, branching_factor); + self.create_container_node(&children) + }) + .collect() + } + + /// A helper function to recursively construct a deep tree + fn build_super_deep_hierarchy(mut self, depth: u32, nodes_per_level: u32) -> (Self::Tree, Self::Node) { + let mut children = Vec::with_capacity(nodes_per_level as usize); + for _ in 0..depth { + let node_with_children = self.create_container_node(&children); + + children.clear(); + children.push(node_with_children); + for _ in 0..(nodes_per_level - 1) { + children.push(self.create_leaf_node()) + } + } + self.set_root_children(&children); + self.into_tree_and_root() + } + + /// A tree with a higher depth for a more realistic scenario + fn build_deep_hierarchy(mut self, node_count: u32, branching_factor: u32) -> (Self::Tree, Self::Node) { + let children = self.build_deep_tree(node_count, branching_factor); + self.set_root_children(&children); + self.into_tree_and_root() + } + + /// A tree with many children that have shallow depth + fn build_flat_hierarchy(mut self, target_node_count: u32) -> (Self::Tree, Self::Node) { + let mut children = Vec::new(); + + while self.total_node_count() < target_node_count as usize { + let count = self.random_usize(1..=4); + let sub_children = self.build_n_leaf_nodes(count); + let node = self.create_container_node(&sub_children); + children.push(node); + } + + self.set_root_children(&children); + self.into_tree_and_root() + } +} + +pub trait BuildTreeExt>: BuildTree { + fn with_seed(seed: u64, style_generator: G) -> Self + where + Self: Sized, + { + let rng = ChaCha8Rng::seed_from_u64(seed); + Self::with_rng(rng, style_generator) + } + + fn new(style_generator: G) -> Self + where + Self: Sized, + { + Self::with_seed(STANDARD_RNG_SEED, style_generator) + } } diff --git a/benches/src/random_style.rs b/benches/src/random_style.rs deleted file mode 100644 index f7ef6154c..000000000 --- a/benches/src/random_style.rs +++ /dev/null @@ -1,53 +0,0 @@ -//! Pseudo-random generation of data (e.g. UI nodes) to use in benchmarks. - -use rand::Rng; - -use taffy::geometry::Size; -use taffy::style::Dimension; -use taffy::style::Style; - -/// A trait for generating pseudo-random instances. -pub trait Randomizeable { - /// Generate a pseudo-random instance of this type. - /// - /// This can be useful for benchmarking. - fn random(rng: &mut R) -> Self - where - R: Rng; -} - -impl Randomizeable for Dimension { - fn random(rng: &mut R) -> Self - where - R: Rng, - { - let switch: f32 = rng.gen_range(0.0..=1.0); - - if switch < 0.2 { - Dimension::Auto - } else if switch < 0.8 { - Dimension::Length(rng.gen_range(0.0..500.0)) - } else { - Dimension::Percent(rng.gen_range(0.0..1.0)) - } - } -} - -impl Randomizeable for Size { - fn random(rng: &mut R) -> Self - where - R: Rng, - { - Size { width: Dimension::random(rng), height: Dimension::random(rng) } - } -} - -impl Randomizeable for Style { - fn random(rng: &mut R) -> Self - where - R: Rng, - { - // TODO: Add more attributes - Style { size: Size::::random(rng), ..Default::default() } - } -} diff --git a/benches/src/taffy_helpers.rs b/benches/src/taffy_helpers.rs new file mode 100644 index 000000000..e0fbb6b2a --- /dev/null +++ b/benches/src/taffy_helpers.rs @@ -0,0 +1,55 @@ +use rand::distributions::uniform::SampleRange; +use rand::Rng; +use rand_chacha::ChaCha8Rng; +use taffy::style::Style as TaffyStyle; +use taffy::tree::NodeId as TaffyNodeId; +use taffy::Taffy as TaffyTree; + +use super::{BuildTree, BuildTreeExt, GenStyle}; + +pub struct TaffyTreeBuilder> { + rng: R, + style_generator: G, + tree: TaffyTree, + root: TaffyNodeId, +} + +// Implement the BuildTree trait +impl> BuildTree for TaffyTreeBuilder { + type Tree = TaffyTree; + type Node = TaffyNodeId; + + fn with_rng(mut rng: R, mut style_generator: G) -> Self { + let mut tree = TaffyTree::new(); + let root = tree.new_leaf(style_generator.create_root_style(&mut rng)).unwrap(); + TaffyTreeBuilder { rng, style_generator, tree, root } + } + + fn random_usize(&mut self, range: impl SampleRange) -> usize { + self.rng.gen_range(range) + } + + fn create_leaf_node(&mut self) -> Self::Node { + let style = self.style_generator.create_leaf_style(&mut self.rng); + self.tree.new_leaf(style).unwrap() + } + + fn create_container_node(&mut self, children: &[Self::Node]) -> Self::Node { + let style = self.style_generator.create_container_style(&mut self.rng); + self.tree.new_with_children(style, children).unwrap() + } + + fn total_node_count(&mut self) -> usize { + self.tree.total_node_count() + } + + fn set_root_children(&mut self, children: &[Self::Node]) { + self.tree.set_children(self.root, children).unwrap(); + } + + fn into_tree_and_root(self) -> (Self::Tree, Self::Node) { + (self.tree, self.root) + } +} + +impl> BuildTreeExt for TaffyTreeBuilder {} diff --git a/benches/src/yoga_helpers.rs b/benches/src/yoga_helpers.rs index 3a5a96ba2..b05d7e61d 100644 --- a/benches/src/yoga_helpers.rs +++ b/benches/src/yoga_helpers.rs @@ -1,9 +1,15 @@ #![allow(dead_code)] + +use rand::distributions::uniform::SampleRange; +use rand::Rng; +use rand_chacha::ChaCha8Rng; use slotmap::{DefaultKey, SlotMap}; +use super::{BuildTree, BuildTreeExt, GenStyle}; + pub mod yg { pub use ordered_float::OrderedFloat; - use slotmap::{DefaultKey, SlotMap}; + pub use slotmap::{DefaultKey, SlotMap}; pub use yoga::types::*; pub use yoga::Node; @@ -13,31 +19,105 @@ pub mod yg { mod tf { pub use taffy::prelude::*; } +use tf::Style as TaffyStyle; + +pub struct YogaTreeBuilder> { + rng: R, + style_generator: G, + tree: yg::YogaTree, + root: yg::DefaultKey, +} + +// Implement the BuildTree trait +impl> BuildTree for YogaTreeBuilder { + type Tree = yg::YogaTree; + type Node = DefaultKey; + + fn with_rng(mut rng: R, mut style_generator: G) -> Self { + let mut tree = SlotMap::new(); + let root = create_yg_node(&mut tree, &style_generator.create_root_style(&mut rng), &[]); + YogaTreeBuilder { rng, style_generator, tree, root } + } + + fn random_usize(&mut self, range: impl SampleRange) -> usize { + self.rng.gen_range(range) + } -pub fn new_default_style_with_children( - tree: &mut SlotMap, - children: Vec, -) -> DefaultKey { + fn create_leaf_node(&mut self) -> Self::Node { + let style = self.style_generator.create_leaf_style(&mut self.rng); + create_yg_node(&mut self.tree, &style, &[]) + } + + fn create_container_node(&mut self, children: &[Self::Node]) -> Self::Node { + let style = self.style_generator.create_container_style(&mut self.rng); + create_yg_node(&mut self.tree, &style, &children) + } + + fn set_root_children(&mut self, children: &[Self::Node]) { + set_node_children(&mut self.tree, self.root, &children); + } + + fn total_node_count(&mut self) -> usize { + self.tree.len() + } + + fn into_tree_and_root(self) -> (Self::Tree, Self::Node) { + (self.tree, self.root) + } +} + +impl> BuildTreeExt for YogaTreeBuilder {} + +// impl> YogaTreeBuilder { +// /// Create a YogaTreeBuilder with a standard rng from a style generator +// fn new>(mut style_generator: NG) -> YogaTreeBuilder { +// let mut rng = ChaCha8Rng::seed_from_u64(STANDARD_RNG_SEED); +// let mut tree = SlotMap::new(); +// let root = create_yg_node(&mut tree, &style_generator.create_root_style(&mut rng), &[]); +// YogaTreeBuilder { rng, style_generator, tree, root } +// } + +// /// Create a YogaTreeBuilder with a standard rng from a style generator +// fn with_seed>(seed: u64, mut style_generator: NG) -> YogaTreeBuilder { +// let mut rng = ChaCha8Rng::seed_from_u64(seed); +// let mut tree = SlotMap::new(); +// let root = create_yg_node(&mut tree, &style_generator.create_root_style(&mut rng), &[]); +// YogaTreeBuilder { rng, style_generator, tree, root } +// } + +// /// Create a YogaTreeBuilder from a random number generator and a style generator +// fn with_rng>(mut rng: NR, mut style_generator: NG) -> YogaTreeBuilder { +// let mut tree = SlotMap::new(); +// let root = create_yg_node(&mut tree, &style_generator.create_root_style(&mut rng), &[]); +// YogaTreeBuilder { rng, style_generator, tree, root } +// } +// } + +fn create_yg_node(tree: &mut yg::YogaTree, style: &tf::Style, children: &[yg::DefaultKey]) -> yg::DefaultKey { let mut node = yg::Node::new(); + apply_taffy_style(&mut node, &style); for (i, child) in children.into_iter().enumerate() { - node.insert_child(&mut tree[child], i as u32); + node.insert_child(&mut tree[*child], i as u32); } tree.insert(node) } -pub fn new_with_children( - tree: &mut SlotMap, - style: &tf::Style, - children: Vec, -) -> DefaultKey { +pub fn new_default_style_with_children(tree: &mut yg::YogaTree, children: &[yg::DefaultKey]) -> yg::DefaultKey { let mut node = yg::Node::new(); - apply_taffy_style(&mut node, style); for (i, child) in children.into_iter().enumerate() { - node.insert_child(&mut tree[child], i as u32); + node.insert_child(&mut tree[*child], i as u32); } tree.insert(node) } +fn set_node_children(tree: &mut yg::YogaTree, node_id: yg::DefaultKey, children: &[yg::DefaultKey]) { + // TODO: clear existing children. + for (i, child_id) in children.into_iter().enumerate() { + let [node, child] = tree.get_disjoint_mut([node_id, *child_id]).unwrap(); + node.insert_child(child, i as u32); + } +} + fn into_yg_units(dim: impl Into) -> yg::StyleUnit { match dim.into() { tf::Dimension::Auto => yg::StyleUnit::Auto, @@ -93,7 +173,7 @@ fn content_into_justify(align: Option) -> yg::Justify { } } -pub fn apply_taffy_style(node: &mut yg::Node, style: &tf::Style) { +fn apply_taffy_style(node: &mut yg::Node, style: &tf::Style) { // display node.set_display(match style.display { tf::Display::None => yg::Display::None, diff --git a/src/tree/taffy_tree/tree.rs b/src/tree/taffy_tree/tree.rs index effd39000..6ead32954 100644 --- a/src/tree/taffy_tree/tree.rs +++ b/src/tree/taffy_tree/tree.rs @@ -400,6 +400,11 @@ impl Taffy { Ok(self.children[parent_key][child_index]) } + /// Returns the total number of nodes in the tree + pub fn total_node_count(&self) -> usize { + self.nodes.len() + } + /// Returns the number of children of the `parent` node pub fn child_count(&self, parent: NodeId) -> TaffyResult { Ok(self.children[parent.into()].len())