Skip to content

Commit

Permalink
Reduce style accesses (DioxusLabs#386)
Browse files Browse the repository at this point in the history
* Use cached flex_grow and flex_shrink values when resolving flexible lengths

* Cache auto margins on FlexItem struct

* Use constants.is_wrap instead of re-resolving style

* Use cached align_content value rather than re-resolving style

* Cache justify_content style in AlgoConstants
  • Loading branch information
nicoburns authored Mar 9, 2023
1 parent 6f4bb57 commit 6be6a47
Showing 1 changed file with 42 additions and 68 deletions.
110 changes: 42 additions & 68 deletions src/compute/flexbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ struct FlexItem {
inset: Rect<Option<f32>>,
/// The margin of this item
margin: Rect<f32>,
/// Whether each margin is an auto margin or not
margin_is_auto: Rect<bool>,
/// The padding of this item
padding: Rect<f32>,
/// The border of this item
Expand Down Expand Up @@ -153,6 +155,8 @@ struct AlgoConstants {
align_items: AlignItems,
/// The align_content property of this node
align_content: AlignContent,
/// The justify_content property of this node
justify_content: Option<JustifyContent>,

/// The border-box size of the node being laid out (if known)
node_outer_size: Size<Option<f32>>,
Expand Down Expand Up @@ -283,7 +287,7 @@ fn compute_preliminary(
// 5. Collect flex items into flex lines.
#[cfg(feature = "debug")]
NODE_LOGGER.log("collect_flex_lines");
let mut flex_lines = collect_flex_lines(tree, node, &constants, available_space, &mut flex_items);
let mut flex_lines = collect_flex_lines(&constants, available_space, &mut flex_items);

// If container size is undefined, determine the container's main size
// and then re-resolve gaps based on newly determined size
Expand All @@ -309,7 +313,7 @@ fn compute_preliminary(
#[cfg(feature = "debug")]
NODE_LOGGER.log("resolve_flexible_lengths");
for line in &mut flex_lines {
resolve_flexible_lengths(tree, line, &constants, original_gap);
resolve_flexible_lengths(line, &constants, original_gap);
}

// 9.4. Cross Size Determination
Expand All @@ -330,7 +334,7 @@ fn compute_preliminary(
// 8. Calculate the cross size of each flex line.
#[cfg(feature = "debug")]
NODE_LOGGER.log("calculate_cross_size");
calculate_cross_size(tree, &mut flex_lines, known_dimensions, &constants);
calculate_cross_size(&mut flex_lines, known_dimensions, &constants);

// 9. Handle 'align-content: stretch'.
#[cfg(feature = "debug")]
Expand Down Expand Up @@ -362,14 +366,14 @@ fn compute_preliminary(
// 12. Distribute any remaining free space.
#[cfg(feature = "debug")]
NODE_LOGGER.log("distribute_remaining_free_space");
distribute_remaining_free_space(tree, &mut flex_lines, node, &constants);
distribute_remaining_free_space(&mut flex_lines, &constants);

// 9.6. Cross-Axis Alignment

// 13. Resolve cross-axis auto margins (also includes 14).
#[cfg(feature = "debug")]
NODE_LOGGER.log("resolve_cross_axis_auto_margins");
resolve_cross_axis_auto_margins(tree, &mut flex_lines, &constants);
resolve_cross_axis_auto_margins(&mut flex_lines, &constants);

// 15. Determine the flex container’s used cross size.
#[cfg(feature = "debug")]
Expand All @@ -385,7 +389,7 @@ fn compute_preliminary(
// 16. Align all flex lines per align-content.
#[cfg(feature = "debug")]
NODE_LOGGER.log("align_flex_lines_per_align_content");
align_flex_lines_per_align_content(tree, &mut flex_lines, node, &constants, total_line_cross_size);
align_flex_lines_per_align_content(&mut flex_lines, &constants, total_line_cross_size);

// Do a final layout pass and gather the resulting layouts
#[cfg(feature = "debug")]
Expand Down Expand Up @@ -450,8 +454,9 @@ fn compute_constants(
let margin = style.margin.resolve_or_zero(parent_size.width);
let padding = style.padding.resolve_or_zero(parent_size.width);
let border = style.border.resolve_or_zero(parent_size.width);
let align_items = style.align_items.unwrap_or(crate::style::AlignItems::Stretch);
let align_content = style.align_content.unwrap_or(crate::style::AlignContent::Stretch);
let align_items = style.align_items.unwrap_or(AlignItems::Stretch);
let align_content = style.align_content.unwrap_or(AlignContent::Stretch);
let justify_content = style.justify_content;

let padding_border = padding + border;

Expand All @@ -474,6 +479,7 @@ fn compute_constants(
padding_border,
align_items,
align_content,
justify_content,
node_outer_size,
node_inner_size,
container_size,
Expand Down Expand Up @@ -508,6 +514,7 @@ fn generate_anonymous_flex_items(tree: &impl LayoutTree, node: Node, constants:

inset: child_style.inset.zip_size(constants.node_inner_size, |p, s| p.maybe_resolve(s)),
margin: child_style.margin.resolve_or_zero(constants.node_inner_size.width),
margin_is_auto: child_style.margin.map(|m| m == LengthPercentageAuto::Auto),
padding: child_style.padding.resolve_or_zero(constants.node_inner_size.width),
border: child_style.border.resolve_or_zero(constants.node_inner_size.width),
align_self: child_style.align_self.unwrap_or(constants.align_items),
Expand Down Expand Up @@ -725,13 +732,11 @@ fn determine_flex_base_size(
/// **Note that the "collect as many" line will collect zero-sized flex items onto the end of the previous line even if the last non-zero item exactly "filled up" the line**.
#[inline]
fn collect_flex_lines<'a>(
tree: &impl LayoutTree,
node: Node,
constants: &AlgoConstants,
available_space: Size<AvailableSpace>,
flex_items: &'a mut Vec<FlexItem>,
) -> Vec<FlexLine<'a>> {
if tree.style(node).flex_wrap == FlexWrap::NoWrap {
if !constants.is_wrap {
let mut lines = new_vec_with_capacity(1);
lines.push(FlexLine { items: flex_items.as_mut_slice(), cross_size: 0.0, offset_cross: 0.0 });
lines
Expand Down Expand Up @@ -966,12 +971,7 @@ fn determine_container_main_size(
///
/// # [9.7. Resolving Flexible Lengths](https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths)
#[inline]
fn resolve_flexible_lengths(
tree: &mut impl LayoutTree,
line: &mut FlexLine,
constants: &AlgoConstants,
original_gap: Size<f32>,
) {
fn resolve_flexible_lengths(line: &mut FlexLine, constants: &AlgoConstants, original_gap: Size<f32>) {
let total_original_main_axis_gap = sum_axis_gaps(original_gap.main(constants.dir), line.items.len());
let total_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());

Expand All @@ -997,8 +997,7 @@ fn resolve_flexible_lengths(
let inner_target_size = child.hypothetical_inner_size.main(constants.dir);
child.target_size.set_main(constants.dir, inner_target_size);

let child_style = tree.style(child.node);
if (child_style.flex_grow == 0.0 && child_style.flex_shrink == 0.0)
if (child.flex_grow == 0.0 && child.flex_shrink == 0.0)
|| (growing && child.flex_basis > child.hypothetical_inner_size.main(constants.dir))
|| (shrinking && child.flex_basis < child.hypothetical_inner_size.main(constants.dir))
{
Expand Down Expand Up @@ -1054,8 +1053,7 @@ fn resolve_flexible_lengths(

let (sum_flex_grow, sum_flex_shrink): (f32, f32) =
unfrozen.iter().fold((0.0, 0.0), |(flex_grow, flex_shrink), item| {
let style = tree.style(item.node);
(flex_grow + style.flex_grow, flex_shrink + style.flex_shrink)
(flex_grow + item.flex_grow, flex_shrink + item.flex_shrink)
});

let free_space = if growing && sum_flex_grow < 1.0 {
Expand Down Expand Up @@ -1091,18 +1089,17 @@ fn resolve_flexible_lengths(
if free_space.is_normal() {
if growing && sum_flex_grow > 0.0 {
for child in &mut unfrozen {
child.target_size.set_main(
constants.dir,
child.flex_basis + free_space * (tree.style(child.node).flex_grow / sum_flex_grow),
);
child
.target_size
.set_main(constants.dir, child.flex_basis + free_space * (child.flex_grow / sum_flex_grow));
}
} else if shrinking && sum_flex_shrink > 0.0 {
let sum_scaled_shrink_factor: f32 =
unfrozen.iter().map(|child| child.inner_flex_basis * tree.style(child.node).flex_shrink).sum();
unfrozen.iter().map(|child| child.inner_flex_basis * child.flex_shrink).sum();

if sum_scaled_shrink_factor > 0.0 {
for child in &mut unfrozen {
let scaled_shrink_factor = child.inner_flex_basis * tree.style(child.node).flex_shrink;
let scaled_shrink_factor = child.inner_flex_basis * child.flex_shrink;
child.target_size.set_main(
constants.dir,
child.flex_basis + free_space * (scaled_shrink_factor / sum_scaled_shrink_factor),
Expand Down Expand Up @@ -1296,12 +1293,7 @@ fn calculate_children_base_lines(
/// If the flex container is single-line, then clamp the line’s cross-size to be within the container’s computed min and max cross sizes.
/// **Note that if CSS 2.1’s definition of min/max-width/height applied more generally, this behavior would fall out automatically**.
#[inline]
fn calculate_cross_size(
tree: &mut impl LayoutTree,
flex_lines: &mut [FlexLine],
node_size: Size<Option<f32>>,
constants: &AlgoConstants,
) {
fn calculate_cross_size(flex_lines: &mut [FlexLine], node_size: Size<Option<f32>>, constants: &AlgoConstants) {
// Note: AlignContent::SpaceEvenly and AlignContent::SpaceAround behave like AlignContent::Stretch when there is only
// a single flex line in the container. See: https://www.w3.org/TR/css-flexbox-1/#align-content-property
// Also: align_content is ignored entirely (and thus behaves like Stretch) when `flex_wrap` is set to `nowrap`.
Expand Down Expand Up @@ -1335,10 +1327,9 @@ fn calculate_cross_size(
.items
.iter()
.map(|child| {
let child_style = tree.style(child.node);
if child.align_self == AlignSelf::Baseline
&& child_style.margin.cross_start(constants.dir) != LengthPercentageAuto::Auto
&& child_style.margin.cross_end(constants.dir) != LengthPercentageAuto::Auto
&& !child.margin_is_auto.cross_start(constants.dir)
&& !child.margin_is_auto.cross_end(constants.dir)
{
max_baseline - child.baseline + child.hypothetical_outer_size.cross(constants.dir)
} else {
Expand Down Expand Up @@ -1395,8 +1386,8 @@ fn determine_used_cross_size(tree: &mut impl LayoutTree, flex_lines: &mut [FlexL
child.target_size.set_cross(
constants.dir,
if child.align_self == AlignSelf::Stretch
&& child_style.margin.cross_start(constants.dir) != LengthPercentageAuto::Auto
&& child_style.margin.cross_end(constants.dir) != LengthPercentageAuto::Auto
&& !child.margin_is_auto.cross_start(constants.dir)
&& !child.margin_is_auto.cross_end(constants.dir)
&& child_style.size.cross(constants.dir) == Dimension::Auto
{
// For some reason this particular usage of max_width is an exception to the rule that max_width's transfer
Expand Down Expand Up @@ -1432,12 +1423,7 @@ fn determine_used_cross_size(tree: &mut impl LayoutTree, flex_lines: &mut [FlexL
///
/// 2. Align the items along the main-axis per `justify-content`.
#[inline]
fn distribute_remaining_free_space(
tree: &mut impl LayoutTree,
flex_lines: &mut [FlexLine],
node: Node,
constants: &AlgoConstants,
) {
fn distribute_remaining_free_space(flex_lines: &mut [FlexLine], constants: &AlgoConstants) {
for line in flex_lines {
let total_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
let used_space: f32 = total_main_axis_gap
Expand All @@ -1446,11 +1432,10 @@ fn distribute_remaining_free_space(
let mut num_auto_margins = 0;

for child in line.items.iter_mut() {
let child_style = tree.style(child.node);
if child_style.margin.main_start(constants.dir) == LengthPercentageAuto::Auto {
if child.margin_is_auto.main_start(constants.dir) {
num_auto_margins += 1;
}
if child_style.margin.main_end(constants.dir) == LengthPercentageAuto::Auto {
if child.margin_is_auto.main_end(constants.dir) {
num_auto_margins += 1;
}
}
Expand All @@ -1459,15 +1444,14 @@ fn distribute_remaining_free_space(
let margin = free_space / num_auto_margins as f32;

for child in line.items.iter_mut() {
let child_style = tree.style(child.node);
if child_style.margin.main_start(constants.dir) == LengthPercentageAuto::Auto {
if child.margin_is_auto.main_start(constants.dir) {
if constants.is_row {
child.margin.left = margin;
} else {
child.margin.top = margin;
}
}
if child_style.margin.main_end(constants.dir) == LengthPercentageAuto::Auto {
if child.margin_is_auto.main_end(constants.dir) {
if constants.is_row {
child.margin.right = margin;
} else {
Expand All @@ -1479,8 +1463,7 @@ fn distribute_remaining_free_space(
let num_items = line.items.len();
let layout_reverse = constants.dir.is_reverse();
let gap = constants.gap.main(constants.dir);
let justify_content_mode: JustifyContent =
tree.style(node).justify_content.unwrap_or(JustifyContent::FlexStart);
let justify_content_mode = constants.justify_content.unwrap_or(JustifyContent::FlexStart);

let justify_item = |(i, child): (usize, &mut FlexItem)| {
child.offset_main =
Expand Down Expand Up @@ -1509,32 +1492,29 @@ fn distribute_remaining_free_space(
/// - Otherwise, if the block-start or inline-start margin (whichever is in the cross axis) is auto, set it to zero.
/// Set the opposite margin so that the outer cross size of the item equals the cross size of its flex line.
#[inline]
fn resolve_cross_axis_auto_margins(tree: &mut impl LayoutTree, flex_lines: &mut [FlexLine], constants: &AlgoConstants) {
fn resolve_cross_axis_auto_margins(flex_lines: &mut [FlexLine], constants: &AlgoConstants) {
for line in flex_lines {
let line_cross_size = line.cross_size;
let max_baseline: f32 = line.items.iter_mut().map(|child| child.baseline).fold(0.0, |acc, x| acc.max(x));

for child in line.items.iter_mut() {
let free_space = line_cross_size - child.outer_target_size.cross(constants.dir);
let child_style = tree.style(child.node);

if child_style.margin.cross_start(constants.dir) == LengthPercentageAuto::Auto
&& child_style.margin.cross_end(constants.dir) == LengthPercentageAuto::Auto
{
if child.margin_is_auto.cross_start(constants.dir) && child.margin_is_auto.cross_end(constants.dir) {
if constants.is_row {
child.margin.top = free_space / 2.0;
child.margin.bottom = free_space / 2.0;
} else {
child.margin.left = free_space / 2.0;
child.margin.right = free_space / 2.0;
}
} else if child_style.margin.cross_start(constants.dir) == LengthPercentageAuto::Auto {
} else if child.margin_is_auto.cross_start(constants.dir) {
if constants.is_row {
child.margin.top = free_space;
} else {
child.margin.left = free_space;
}
} else if child_style.margin.cross_end(constants.dir) == LengthPercentageAuto::Auto {
} else if child.margin_is_auto.cross_end(constants.dir) {
if constants.is_row {
child.margin.bottom = free_space;
} else {
Expand Down Expand Up @@ -1642,16 +1622,10 @@ fn determine_container_cross_size(
///
/// - [**Align all flex lines**](https://www.w3.org/TR/css-flexbox-1/#algo-line-align) per `align-content`.
#[inline]
fn align_flex_lines_per_align_content(
tree: &impl LayoutTree,
flex_lines: &mut [FlexLine],
node: Node,
constants: &AlgoConstants,
total_cross_size: f32,
) {
fn align_flex_lines_per_align_content(flex_lines: &mut [FlexLine], constants: &AlgoConstants, total_cross_size: f32) {
let num_lines = flex_lines.len();
let gap = constants.gap.cross(constants.dir);
let align_content_mode = tree.style(node).align_content.unwrap_or(AlignContent::Stretch);
let align_content_mode = constants.align_content;
let total_cross_axis_gap = sum_axis_gaps(gap, num_lines);
let free_space = constants.inner_container_size.cross(constants.dir) - total_cross_size - total_cross_axis_gap;

Expand Down Expand Up @@ -1927,7 +1901,7 @@ fn perform_absolute_layout_on_absolute_children(tree: &mut impl LayoutTree, node
} else {
// Stretch is an invalid value for justify_content in the flexbox algorithm, so we
// treat it as if it wasn't set (and thus we default to FlexStart behaviour)
match (tree.style(node).justify_content.unwrap_or(JustifyContent::Start), constants.is_wrap_reverse) {
match (constants.justify_content.unwrap_or(JustifyContent::Start), constants.is_wrap_reverse) {
(JustifyContent::SpaceBetween, _)
| (JustifyContent::Start, _)
| (JustifyContent::Stretch, false)
Expand Down

0 comments on commit 6be6a47

Please sign in to comment.