From d1a49a4f0bdaf16c9e5ac1267e22d9d5ba3f335d Mon Sep 17 00:00:00 2001 From: Lucas Rocha Date: Mon, 7 Sep 2015 13:54:27 +0100 Subject: [PATCH] Reduce search range of flexible children We were traversing all children to only perform calculations/changes to flexible children in order to avoid new allocations during layout. This diff ensures we only visit flexible children during layout calculations if any are present. We accomplish this by keeping a private linked list of flexible children. --- src/Layout.c | 84 +++++++++++-------- src/Layout.h | 1 + src/Layout.js | 84 +++++++++++-------- .../src/com/facebook/csslayout/CSSNode.java | 1 + .../com/facebook/csslayout/LayoutEngine.java | 84 +++++++++++-------- src/transpile.js | 2 + 6 files changed, 151 insertions(+), 105 deletions(-) diff --git a/src/Layout.c b/src/Layout.c index a59d7045..feb4f952 100644 --- a/src/Layout.c +++ b/src/Layout.c @@ -636,11 +636,15 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction float totalFlexible = 0; int nonFlexibleChildrenCount = 0; + css_node_t* firstFlexChild = NULL; + css_node_t* currentFlexChild = NULL; + float maxWidth; for (i = startLine; i < childCount; ++i) { child = node->get_child(node->context, i); child->next_absolute_child = NULL; + child->next_flex_child = NULL; // Pre-fill cross axis dimensions when the child is using stretch before // we call the recursive layout pass @@ -694,6 +698,16 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction flexibleChildrenCount++; totalFlexible += child->style.flex; + // Store a private linked list of flexible children so that we can + // efficiently traverse them later. + if (firstFlexChild == NULL) { + firstFlexChild = child; + } + if (currentFlexChild != NULL) { + currentFlexChild->next_flex_child = child; + } + currentFlexChild = child; + // Even if we don't know its exact size yet, we already know the padding, // border and margin. We'll use this partial information, which represents // the smallest possible size for the child, to compute the remaining @@ -767,21 +781,20 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction float baseMainDim; float boundMainDim; - // Iterate over every child in the axis. If the flex share of remaining - // space doesn't meet min/max bounds, remove this child from flex - // calculations. - for (i = startLine; i < endLine; ++i) { - child = node->get_child(node->context, i); - if (isFlex(child)) { - baseMainDim = flexibleMainDim * child->style.flex + - getPaddingAndBorderAxis(child, mainAxis); - boundMainDim = boundAxis(child, mainAxis, baseMainDim); + // If the flex share of remaining space doesn't meet min/max bounds, + // remove this child from flex calculations. + currentFlexChild = firstFlexChild; + while (currentFlexChild != NULL) { + baseMainDim = flexibleMainDim * currentFlexChild->style.flex + + getPaddingAndBorderAxis(currentFlexChild, mainAxis); + boundMainDim = boundAxis(currentFlexChild, mainAxis, baseMainDim); - if (baseMainDim != boundMainDim) { - remainingMainDim -= boundMainDim; - totalFlexible -= child->style.flex; - } + if (baseMainDim != boundMainDim) { + remainingMainDim -= boundMainDim; + totalFlexible -= currentFlexChild->style.flex; } + + currentFlexChild = currentFlexChild->next_flex_child; } flexibleMainDim = remainingMainDim / totalFlexible; @@ -790,31 +803,32 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction if (flexibleMainDim < 0) { flexibleMainDim = 0; } - // We iterate over the full array and only apply the action on flexible - // children. This is faster than actually allocating a new array that - // contains only flexible children. - for (i = startLine; i < endLine; ++i) { - child = node->get_child(node->context, i); - if (isFlex(child)) { - // At this point we know the final size of the element in the main - // dimension - child->layout.dimensions[dim[mainAxis]] = boundAxis(child, mainAxis, - flexibleMainDim * child->style.flex + getPaddingAndBorderAxis(child, mainAxis) - ); - maxWidth = CSS_UNDEFINED; - if (isDimDefined(node, resolvedRowAxis)) { - maxWidth = node->layout.dimensions[dim[resolvedRowAxis]] - - paddingAndBorderAxisResolvedRow; - } else if (!isMainRowDirection) { - maxWidth = parentMaxWidth - - getMarginAxis(node, resolvedRowAxis) - - paddingAndBorderAxisResolvedRow; - } + currentFlexChild = firstFlexChild; + while (currentFlexChild != NULL) { + // At this point we know the final size of the element in the main + // dimension + currentFlexChild->layout.dimensions[dim[mainAxis]] = boundAxis(currentFlexChild, mainAxis, + flexibleMainDim * currentFlexChild->style.flex + + getPaddingAndBorderAxis(currentFlexChild, mainAxis) + ); - // And we recursively call the layout algorithm for this child - layoutNode(child, maxWidth, direction); + maxWidth = CSS_UNDEFINED; + if (isDimDefined(node, resolvedRowAxis)) { + maxWidth = node->layout.dimensions[dim[resolvedRowAxis]] - + paddingAndBorderAxisResolvedRow; + } else if (!isMainRowDirection) { + maxWidth = parentMaxWidth - + getMarginAxis(node, resolvedRowAxis) - + paddingAndBorderAxisResolvedRow; } + + // And we recursively call the layout algorithm for this child + layoutNode(currentFlexChild, maxWidth, direction); + + child = currentFlexChild; + currentFlexChild = currentFlexChild->next_flex_child; + child->next_flex_child = NULL; } // We use justifyContent to figure out how to allocate the remaining diff --git a/src/Layout.h b/src/Layout.h index 820f54c1..535fa734 100644 --- a/src/Layout.h +++ b/src/Layout.h @@ -137,6 +137,7 @@ struct css_node { int line_index; css_node_t* next_absolute_child; + css_node_t* next_flex_child; css_dim_t (*measure)(void *context, float width); void (*print)(void *context); diff --git a/src/Layout.js b/src/Layout.js index ac111e3c..ead03eab 100755 --- a/src/Layout.js +++ b/src/Layout.js @@ -504,11 +504,15 @@ var computeLayout = (function() { var/*float*/ totalFlexible = 0; var/*int*/ nonFlexibleChildrenCount = 0; + var/*css_node_t**/ firstFlexChild = null; + var/*css_node_t**/ currentFlexChild = null; + var/*float*/ maxWidth; for (i = startLine; i < childCount; ++i) { child = node.children[i]; child.nextAbsoluteChild = null; + child.nextFlexChild = null; // Pre-fill cross axis dimensions when the child is using stretch before // we call the recursive layout pass @@ -562,6 +566,16 @@ var computeLayout = (function() { flexibleChildrenCount++; totalFlexible += child.style.flex; + // Store a private linked list of flexible children so that we can + // efficiently traverse them later. + if (firstFlexChild === null) { + firstFlexChild = child; + } + if (currentFlexChild !== null) { + currentFlexChild.nextFlexChild = child; + } + currentFlexChild = child; + // Even if we don't know its exact size yet, we already know the padding, // border and margin. We'll use this partial information, which represents // the smallest possible size for the child, to compute the remaining @@ -635,21 +649,20 @@ var computeLayout = (function() { var/*float*/ baseMainDim; var/*float*/ boundMainDim; - // Iterate over every child in the axis. If the flex share of remaining - // space doesn't meet min/max bounds, remove this child from flex - // calculations. - for (i = startLine; i < endLine; ++i) { - child = node.children[i]; - if (isFlex(child)) { - baseMainDim = flexibleMainDim * child.style.flex + - getPaddingAndBorderAxis(child, mainAxis); - boundMainDim = boundAxis(child, mainAxis, baseMainDim); + // If the flex share of remaining space doesn't meet min/max bounds, + // remove this child from flex calculations. + currentFlexChild = firstFlexChild; + while (currentFlexChild !== null) { + baseMainDim = flexibleMainDim * currentFlexChild.style.flex + + getPaddingAndBorderAxis(currentFlexChild, mainAxis); + boundMainDim = boundAxis(currentFlexChild, mainAxis, baseMainDim); - if (baseMainDim !== boundMainDim) { - remainingMainDim -= boundMainDim; - totalFlexible -= child.style.flex; - } + if (baseMainDim !== boundMainDim) { + remainingMainDim -= boundMainDim; + totalFlexible -= currentFlexChild.style.flex; } + + currentFlexChild = currentFlexChild.nextFlexChild; } flexibleMainDim = remainingMainDim / totalFlexible; @@ -658,31 +671,32 @@ var computeLayout = (function() { if (flexibleMainDim < 0) { flexibleMainDim = 0; } - // We iterate over the full array and only apply the action on flexible - // children. This is faster than actually allocating a new array that - // contains only flexible children. - for (i = startLine; i < endLine; ++i) { - child = node.children[i]; - if (isFlex(child)) { - // At this point we know the final size of the element in the main - // dimension - child.layout[dim[mainAxis]] = boundAxis(child, mainAxis, - flexibleMainDim * child.style.flex + getPaddingAndBorderAxis(child, mainAxis) - ); - maxWidth = CSS_UNDEFINED; - if (isDimDefined(node, resolvedRowAxis)) { - maxWidth = node.layout[dim[resolvedRowAxis]] - - paddingAndBorderAxisResolvedRow; - } else if (!isMainRowDirection) { - maxWidth = parentMaxWidth - - getMarginAxis(node, resolvedRowAxis) - - paddingAndBorderAxisResolvedRow; - } + currentFlexChild = firstFlexChild; + while (currentFlexChild !== null) { + // At this point we know the final size of the element in the main + // dimension + currentFlexChild.layout[dim[mainAxis]] = boundAxis(currentFlexChild, mainAxis, + flexibleMainDim * currentFlexChild.style.flex + + getPaddingAndBorderAxis(currentFlexChild, mainAxis) + ); - // And we recursively call the layout algorithm for this child - layoutNode(/*(java)!layoutContext, */child, maxWidth, direction); + maxWidth = CSS_UNDEFINED; + if (isDimDefined(node, resolvedRowAxis)) { + maxWidth = node.layout[dim[resolvedRowAxis]] - + paddingAndBorderAxisResolvedRow; + } else if (!isMainRowDirection) { + maxWidth = parentMaxWidth - + getMarginAxis(node, resolvedRowAxis) - + paddingAndBorderAxisResolvedRow; } + + // And we recursively call the layout algorithm for this child + layoutNode(/*(java)!layoutContext, */currentFlexChild, maxWidth, direction); + + child = currentFlexChild; + currentFlexChild = currentFlexChild.nextFlexChild; + child.nextFlexChild = null; } // We use justifyContent to figure out how to allocate the remaining diff --git a/src/java/src/com/facebook/csslayout/CSSNode.java b/src/java/src/com/facebook/csslayout/CSSNode.java index bd2ec4a3..85d165e8 100644 --- a/src/java/src/com/facebook/csslayout/CSSNode.java +++ b/src/java/src/com/facebook/csslayout/CSSNode.java @@ -64,6 +64,7 @@ public class CSSNode { public int lineIndex = 0; CSSNode nextAbsoluteChild; + CSSNode nextFlexChild; private @Nullable ArrayList mChildren; private @Nullable CSSNode mParent; diff --git a/src/java/src/com/facebook/csslayout/LayoutEngine.java b/src/java/src/com/facebook/csslayout/LayoutEngine.java index 2f40c165..1e867671 100644 --- a/src/java/src/com/facebook/csslayout/LayoutEngine.java +++ b/src/java/src/com/facebook/csslayout/LayoutEngine.java @@ -453,11 +453,15 @@ public class LayoutEngine { float totalFlexible = 0; int nonFlexibleChildrenCount = 0; + CSSNode firstFlexChild = null; + CSSNode currentFlexChild = null; + float maxWidth; for (i = startLine; i < childCount; ++i) { child = node.getChildAt(i); child.nextAbsoluteChild = null; + child.nextFlexChild = null; // Pre-fill cross axis dimensions when the child is using stretch before // we call the recursive layout pass @@ -511,6 +515,16 @@ public class LayoutEngine { flexibleChildrenCount++; totalFlexible += child.style.flex; + // Store a private linked list of flexible children so that we can + // efficiently traverse them later. + if (firstFlexChild == null) { + firstFlexChild = child; + } + if (currentFlexChild != null) { + currentFlexChild.nextFlexChild = child; + } + currentFlexChild = child; + // Even if we don't know its exact size yet, we already know the padding, // border and margin. We'll use this partial information, which represents // the smallest possible size for the child, to compute the remaining @@ -584,21 +598,20 @@ public class LayoutEngine { float baseMainDim; float boundMainDim; - // Iterate over every child in the axis. If the flex share of remaining - // space doesn't meet min/max bounds, remove this child from flex - // calculations. - for (i = startLine; i < endLine; ++i) { - child = node.getChildAt(i); - if (isFlex(child)) { - baseMainDim = flexibleMainDim * child.style.flex + - getPaddingAndBorderAxis(child, mainAxis); - boundMainDim = boundAxis(child, mainAxis, baseMainDim); + // If the flex share of remaining space doesn't meet min/max bounds, + // remove this child from flex calculations. + currentFlexChild = firstFlexChild; + while (currentFlexChild != null) { + baseMainDim = flexibleMainDim * currentFlexChild.style.flex + + getPaddingAndBorderAxis(currentFlexChild, mainAxis); + boundMainDim = boundAxis(currentFlexChild, mainAxis, baseMainDim); - if (baseMainDim != boundMainDim) { - remainingMainDim -= boundMainDim; - totalFlexible -= child.style.flex; - } + if (baseMainDim != boundMainDim) { + remainingMainDim -= boundMainDim; + totalFlexible -= currentFlexChild.style.flex; } + + currentFlexChild = currentFlexChild.nextFlexChild; } flexibleMainDim = remainingMainDim / totalFlexible; @@ -607,31 +620,32 @@ public class LayoutEngine { if (flexibleMainDim < 0) { flexibleMainDim = 0; } - // We iterate over the full array and only apply the action on flexible - // children. This is faster than actually allocating a new array that - // contains only flexible children. - for (i = startLine; i < endLine; ++i) { - child = node.getChildAt(i); - if (isFlex(child)) { - // At this point we know the final size of the element in the main - // dimension - child.layout.dimensions[dim[mainAxis]] = boundAxis(child, mainAxis, - flexibleMainDim * child.style.flex + getPaddingAndBorderAxis(child, mainAxis) - ); - maxWidth = CSSConstants.UNDEFINED; - if (isDimDefined(node, resolvedRowAxis)) { - maxWidth = node.layout.dimensions[dim[resolvedRowAxis]] - - paddingAndBorderAxisResolvedRow; - } else if (!isMainRowDirection) { - maxWidth = parentMaxWidth - - getMarginAxis(node, resolvedRowAxis) - - paddingAndBorderAxisResolvedRow; - } + currentFlexChild = firstFlexChild; + while (currentFlexChild != null) { + // At this point we know the final size of the element in the main + // dimension + currentFlexChild.layout.dimensions[dim[mainAxis]] = boundAxis(currentFlexChild, mainAxis, + flexibleMainDim * currentFlexChild.style.flex + + getPaddingAndBorderAxis(currentFlexChild, mainAxis) + ); - // And we recursively call the layout algorithm for this child - layoutNode(layoutContext, child, maxWidth, direction); + maxWidth = CSSConstants.UNDEFINED; + if (isDimDefined(node, resolvedRowAxis)) { + maxWidth = node.layout.dimensions[dim[resolvedRowAxis]] - + paddingAndBorderAxisResolvedRow; + } else if (!isMainRowDirection) { + maxWidth = parentMaxWidth - + getMarginAxis(node, resolvedRowAxis) - + paddingAndBorderAxisResolvedRow; } + + // And we recursively call the layout algorithm for this child + layoutNode(layoutContext, currentFlexChild, maxWidth, direction); + + child = currentFlexChild; + currentFlexChild = currentFlexChild.nextFlexChild; + child.nextFlexChild = null; } // We use justifyContent to figure out how to allocate the remaining diff --git a/src/transpile.js b/src/transpile.js index 4666dbaf..ff0f1922 100644 --- a/src/transpile.js +++ b/src/transpile.js @@ -253,6 +253,7 @@ function transpileAnnotatedJStoC(jsCode) { .replace(/\.minHeight/g, '.minDimensions[CSS_HEIGHT]') .replace(/\.lineIndex/g, '.line_index') .replace(/\.nextAbsoluteChild/g, '.next_absolute_child') + .replace(/\.nextFlexChild/g, '.next_flex_child') .replace(/layout\[dim/g, 'layout.dimensions[dim') .replace(/layout\[pos/g, 'layout.position[pos') .replace(/layout\[leading/g, 'layout.position[leading') @@ -264,6 +265,7 @@ function transpileAnnotatedJStoC(jsCode) { .replace(/child\./g, 'child->') .replace(/parent\./g, 'parent->') .replace(/currentAbsoluteChild\./g, 'currentAbsoluteChild->') + .replace(/currentFlexChild\./g, 'currentFlexChild->') .replace(/getPositionType\((.+?)\)/g, '$1->style.position_type') .replace(/getJustifyContent\((.+?)\)/g, '$1->style.justify_content') .replace(/getAlignContent\((.+?)\)/g, '$1->style.align_content')