From 793220faf8641265c0383b8276074169ea2c0aaa Mon Sep 17 00:00:00 2001 From: Lucas Rocha Date: Fri, 4 Sep 2015 17:37:07 +0100 Subject: [PATCH] Skip final loop on absolute children, if possible There's no need to go through all absolute children at the end of the layout calculation if the node at hand doesn't have any. This also ensures only absolutely positioned children are traversed in the final loop. --- src/Layout.c | 77 +++++++++++-------- src/Layout.h | 7 +- src/Layout.js | 77 +++++++++++-------- .../src/com/facebook/csslayout/CSSNode.java | 2 + .../com/facebook/csslayout/LayoutEngine.java | 77 +++++++++++-------- src/transpile.js | 3 + 6 files changed, 148 insertions(+), 95 deletions(-) diff --git a/src/Layout.c b/src/Layout.c index be0d7d20..a59d7045 100644 --- a/src/Layout.c +++ b/src/Layout.c @@ -604,6 +604,9 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction css_node_t* child; css_flex_direction_t axis; + css_node_t* firstAbsoluteChild = NULL; + css_node_t* currentAbsoluteChild = NULL; + float definedMainDim = CSS_UNDEFINED; if (isMainDimDefined) { definedMainDim = node->layout.dimensions[dim[mainAxis]] - paddingAndBorderAxisMain; @@ -637,6 +640,8 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction for (i = startLine; i < childCount; ++i) { child = node->get_child(node->context, i); + child->next_absolute_child = NULL; + // Pre-fill cross axis dimensions when the child is using stretch before // we call the recursive layout pass if (getAlignItem(node, child) == CSS_ALIGN_STRETCH && @@ -650,6 +655,16 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction getPaddingAndBorderAxis(child, crossAxis) ); } else if (child->style.position_type == CSS_POSITION_ABSOLUTE) { + // Store a private linked list of absolutely positioned children + // so that we can efficiently traverse them later. + if (firstAbsoluteChild == NULL) { + firstAbsoluteChild = child; + } + if (currentAbsoluteChild != NULL) { + currentAbsoluteChild->next_absolute_child = child; + } + currentAbsoluteChild = child; + // Pre-fill dimensions when using absolute position and both offsets for the axis are defined (either both // left and right or top and bottom). for (ii = 0; ii < 2; ii++) { @@ -1075,40 +1090,40 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction } // Calculate dimensions for absolutely positioned elements - for (i = 0; i < childCount; ++i) { - child = node->get_child(node->context, i); - if (child->style.position_type == CSS_POSITION_ABSOLUTE) { - // Pre-fill dimensions when using absolute position and both offsets for the axis are defined (either both - // left and right or top and bottom). - for (ii = 0; ii < 2; ii++) { - axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN; - if (!isUndefined(node->layout.dimensions[dim[axis]]) && - !isDimDefined(child, axis) && - isPosDefined(child, leading[axis]) && - isPosDefined(child, trailing[axis])) { - child->layout.dimensions[dim[axis]] = fmaxf( - boundAxis(child, axis, node->layout.dimensions[dim[axis]] - - getBorderAxis(node, axis) - - getMarginAxis(child, axis) - - getPosition(child, leading[axis]) - - getPosition(child, trailing[axis]) - ), - // You never want to go smaller than padding - getPaddingAndBorderAxis(child, axis) - ); - } + currentAbsoluteChild = firstAbsoluteChild; + while (currentAbsoluteChild != NULL) { + // Pre-fill dimensions when using absolute position and both offsets for + // the axis are defined (either both left and right or top and bottom). + for (ii = 0; ii < 2; ii++) { + axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN; + if (!isUndefined(node->layout.dimensions[dim[axis]]) && + !isDimDefined(currentAbsoluteChild, axis) && + isPosDefined(currentAbsoluteChild, leading[axis]) && + isPosDefined(currentAbsoluteChild, trailing[axis])) { + currentAbsoluteChild->layout.dimensions[dim[axis]] = fmaxf( + boundAxis(currentAbsoluteChild, axis, node->layout.dimensions[dim[axis]] - + getBorderAxis(node, axis) - + getMarginAxis(currentAbsoluteChild, axis) - + getPosition(currentAbsoluteChild, leading[axis]) - + getPosition(currentAbsoluteChild, trailing[axis]) + ), + // You never want to go smaller than padding + getPaddingAndBorderAxis(currentAbsoluteChild, axis) + ); } - for (ii = 0; ii < 2; ii++) { - axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN; - if (isPosDefined(child, trailing[axis]) && - !isPosDefined(child, leading[axis])) { - child->layout.position[leading[axis]] = - node->layout.dimensions[dim[axis]] - - child->layout.dimensions[dim[axis]] - - getPosition(child, trailing[axis]); - } + + if (isPosDefined(currentAbsoluteChild, trailing[axis]) && + !isPosDefined(currentAbsoluteChild, leading[axis])) { + currentAbsoluteChild->layout.position[leading[axis]] = + node->layout.dimensions[dim[axis]] - + currentAbsoluteChild->layout.dimensions[dim[axis]] - + getPosition(currentAbsoluteChild, trailing[axis]); } } + + child = currentAbsoluteChild; + currentAbsoluteChild = currentAbsoluteChild->next_absolute_child; + child->next_absolute_child = NULL; } /** END_GENERATED **/ } diff --git a/src/Layout.h b/src/Layout.h index 54d31589..820f54c1 100644 --- a/src/Layout.h +++ b/src/Layout.h @@ -129,18 +129,21 @@ typedef struct { float maxDimensions[2]; } css_style_t; -typedef struct css_node { +typedef struct css_node css_node_t; +struct css_node { css_style_t style; css_layout_t layout; int children_count; int line_index; + css_node_t* next_absolute_child; + css_dim_t (*measure)(void *context, float width); void (*print)(void *context); struct css_node* (*get_child)(void *context, int i); bool (*is_dirty)(void *context); void *context; -} css_node_t; +}; // Lifecycle of nodes and children diff --git a/src/Layout.js b/src/Layout.js index 957e4455..ac111e3c 100755 --- a/src/Layout.js +++ b/src/Layout.js @@ -472,6 +472,9 @@ var computeLayout = (function() { var/*css_node_t**/ child; var/*(c)!css_flex_direction_t*//*(java)!int*/ axis; + var/*css_node_t**/ firstAbsoluteChild = null; + var/*css_node_t**/ currentAbsoluteChild = null; + var/*float*/ definedMainDim = CSS_UNDEFINED; if (isMainDimDefined) { definedMainDim = node.layout[dim[mainAxis]] - paddingAndBorderAxisMain; @@ -505,6 +508,8 @@ var computeLayout = (function() { for (i = startLine; i < childCount; ++i) { child = node.children[i]; + child.nextAbsoluteChild = null; + // Pre-fill cross axis dimensions when the child is using stretch before // we call the recursive layout pass if (getAlignItem(node, child) === CSS_ALIGN_STRETCH && @@ -518,6 +523,16 @@ var computeLayout = (function() { getPaddingAndBorderAxis(child, crossAxis) ); } else if (getPositionType(child) === CSS_POSITION_ABSOLUTE) { + // Store a private linked list of absolutely positioned children + // so that we can efficiently traverse them later. + if (firstAbsoluteChild === null) { + firstAbsoluteChild = child; + } + if (currentAbsoluteChild !== null) { + currentAbsoluteChild.nextAbsoluteChild = child; + } + currentAbsoluteChild = child; + // Pre-fill dimensions when using absolute position and both offsets for the axis are defined (either both // left and right or top and bottom). for (ii = 0; ii < 2; ii++) { @@ -943,40 +958,40 @@ var computeLayout = (function() { } // Calculate dimensions for absolutely positioned elements - for (i = 0; i < childCount; ++i) { - child = node.children[i]; - if (getPositionType(child) === CSS_POSITION_ABSOLUTE) { - // Pre-fill dimensions when using absolute position and both offsets for the axis are defined (either both - // left and right or top and bottom). - for (ii = 0; ii < 2; ii++) { - axis = (ii !== 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN; - if (!isUndefined(node.layout[dim[axis]]) && - !isDimDefined(child, axis) && - isPosDefined(child, leading[axis]) && - isPosDefined(child, trailing[axis])) { - child.layout[dim[axis]] = fmaxf( - boundAxis(child, axis, node.layout[dim[axis]] - - getBorderAxis(node, axis) - - getMarginAxis(child, axis) - - getPosition(child, leading[axis]) - - getPosition(child, trailing[axis]) - ), - // You never want to go smaller than padding - getPaddingAndBorderAxis(child, axis) - ); - } + currentAbsoluteChild = firstAbsoluteChild; + while (currentAbsoluteChild !== null) { + // Pre-fill dimensions when using absolute position and both offsets for + // the axis are defined (either both left and right or top and bottom). + for (ii = 0; ii < 2; ii++) { + axis = (ii !== 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN; + if (!isUndefined(node.layout[dim[axis]]) && + !isDimDefined(currentAbsoluteChild, axis) && + isPosDefined(currentAbsoluteChild, leading[axis]) && + isPosDefined(currentAbsoluteChild, trailing[axis])) { + currentAbsoluteChild.layout[dim[axis]] = fmaxf( + boundAxis(currentAbsoluteChild, axis, node.layout[dim[axis]] - + getBorderAxis(node, axis) - + getMarginAxis(currentAbsoluteChild, axis) - + getPosition(currentAbsoluteChild, leading[axis]) - + getPosition(currentAbsoluteChild, trailing[axis]) + ), + // You never want to go smaller than padding + getPaddingAndBorderAxis(currentAbsoluteChild, axis) + ); } - for (ii = 0; ii < 2; ii++) { - axis = (ii !== 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN; - if (isPosDefined(child, trailing[axis]) && - !isPosDefined(child, leading[axis])) { - child.layout[leading[axis]] = - node.layout[dim[axis]] - - child.layout[dim[axis]] - - getPosition(child, trailing[axis]); - } + + if (isPosDefined(currentAbsoluteChild, trailing[axis]) && + !isPosDefined(currentAbsoluteChild, leading[axis])) { + currentAbsoluteChild.layout[leading[axis]] = + node.layout[dim[axis]] - + currentAbsoluteChild.layout[dim[axis]] - + getPosition(currentAbsoluteChild, trailing[axis]); } } + + child = currentAbsoluteChild; + currentAbsoluteChild = currentAbsoluteChild.nextAbsoluteChild; + child.nextAbsoluteChild = null; } } diff --git a/src/java/src/com/facebook/csslayout/CSSNode.java b/src/java/src/com/facebook/csslayout/CSSNode.java index 91d7ed83..bd2ec4a3 100644 --- a/src/java/src/com/facebook/csslayout/CSSNode.java +++ b/src/java/src/com/facebook/csslayout/CSSNode.java @@ -63,6 +63,8 @@ public class CSSNode { public int lineIndex = 0; + CSSNode nextAbsoluteChild; + private @Nullable ArrayList mChildren; private @Nullable CSSNode mParent; private @Nullable MeasureFunction mMeasureFunction = null; diff --git a/src/java/src/com/facebook/csslayout/LayoutEngine.java b/src/java/src/com/facebook/csslayout/LayoutEngine.java index cc92377d..2f40c165 100644 --- a/src/java/src/com/facebook/csslayout/LayoutEngine.java +++ b/src/java/src/com/facebook/csslayout/LayoutEngine.java @@ -421,6 +421,9 @@ public class LayoutEngine { CSSNode child; int axis; + CSSNode firstAbsoluteChild = null; + CSSNode currentAbsoluteChild = null; + float definedMainDim = CSSConstants.UNDEFINED; if (isMainDimDefined) { definedMainDim = node.layout.dimensions[dim[mainAxis]] - paddingAndBorderAxisMain; @@ -454,6 +457,8 @@ public class LayoutEngine { for (i = startLine; i < childCount; ++i) { child = node.getChildAt(i); + child.nextAbsoluteChild = null; + // Pre-fill cross axis dimensions when the child is using stretch before // we call the recursive layout pass if (getAlignItem(node, child) == CSSAlign.STRETCH && @@ -467,6 +472,16 @@ public class LayoutEngine { getPaddingAndBorderAxis(child, crossAxis) ); } else if (child.style.positionType == CSSPositionType.ABSOLUTE) { + // Store a private linked list of absolutely positioned children + // so that we can efficiently traverse them later. + if (firstAbsoluteChild == null) { + firstAbsoluteChild = child; + } + if (currentAbsoluteChild != null) { + currentAbsoluteChild.nextAbsoluteChild = child; + } + currentAbsoluteChild = child; + // Pre-fill dimensions when using absolute position and both offsets for the axis are defined (either both // left and right or top and bottom). for (ii = 0; ii < 2; ii++) { @@ -892,40 +907,40 @@ public class LayoutEngine { } // Calculate dimensions for absolutely positioned elements - for (i = 0; i < childCount; ++i) { - child = node.getChildAt(i); - if (child.style.positionType == CSSPositionType.ABSOLUTE) { - // Pre-fill dimensions when using absolute position and both offsets for the axis are defined (either both - // left and right or top and bottom). - for (ii = 0; ii < 2; ii++) { - axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN; - if (!isUndefined(node.layout.dimensions[dim[axis]]) && - !isDimDefined(child, axis) && - isPosDefined(child, leading[axis]) && - isPosDefined(child, trailing[axis])) { - child.layout.dimensions[dim[axis]] = Math.max( - boundAxis(child, axis, node.layout.dimensions[dim[axis]] - - getBorderAxis(node, axis) - - getMarginAxis(child, axis) - - getPosition(child, leading[axis]) - - getPosition(child, trailing[axis]) - ), - // You never want to go smaller than padding - getPaddingAndBorderAxis(child, axis) - ); - } + currentAbsoluteChild = firstAbsoluteChild; + while (currentAbsoluteChild != null) { + // Pre-fill dimensions when using absolute position and both offsets for + // the axis are defined (either both left and right or top and bottom). + for (ii = 0; ii < 2; ii++) { + axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN; + if (!isUndefined(node.layout.dimensions[dim[axis]]) && + !isDimDefined(currentAbsoluteChild, axis) && + isPosDefined(currentAbsoluteChild, leading[axis]) && + isPosDefined(currentAbsoluteChild, trailing[axis])) { + currentAbsoluteChild.layout.dimensions[dim[axis]] = Math.max( + boundAxis(currentAbsoluteChild, axis, node.layout.dimensions[dim[axis]] - + getBorderAxis(node, axis) - + getMarginAxis(currentAbsoluteChild, axis) - + getPosition(currentAbsoluteChild, leading[axis]) - + getPosition(currentAbsoluteChild, trailing[axis]) + ), + // You never want to go smaller than padding + getPaddingAndBorderAxis(currentAbsoluteChild, axis) + ); } - for (ii = 0; ii < 2; ii++) { - axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN; - if (isPosDefined(child, trailing[axis]) && - !isPosDefined(child, leading[axis])) { - child.layout.position[leading[axis]] = - node.layout.dimensions[dim[axis]] - - child.layout.dimensions[dim[axis]] - - getPosition(child, trailing[axis]); - } + + if (isPosDefined(currentAbsoluteChild, trailing[axis]) && + !isPosDefined(currentAbsoluteChild, leading[axis])) { + currentAbsoluteChild.layout.position[leading[axis]] = + node.layout.dimensions[dim[axis]] - + currentAbsoluteChild.layout.dimensions[dim[axis]] - + getPosition(currentAbsoluteChild, trailing[axis]); } } + + child = currentAbsoluteChild; + currentAbsoluteChild = currentAbsoluteChild.nextAbsoluteChild; + child.nextAbsoluteChild = null; } } /** END_GENERATED **/ diff --git a/src/transpile.js b/src/transpile.js index 2f93d154..4666dbaf 100644 --- a/src/transpile.js +++ b/src/transpile.js @@ -243,6 +243,7 @@ function printLayout(test) { function transpileAnnotatedJStoC(jsCode) { return jsCode .replace('node.style.measure', 'node.measure') + .replace(/null/g, 'NULL') .replace(/\.children\.length/g, '.children_count') .replace(/\.width/g, '.dimensions[CSS_WIDTH]') .replace(/\.height/g, '.dimensions[CSS_HEIGHT]') @@ -251,6 +252,7 @@ function transpileAnnotatedJStoC(jsCode) { .replace(/\.minWidth/g, '.minDimensions[CSS_WIDTH]') .replace(/\.minHeight/g, '.minDimensions[CSS_HEIGHT]') .replace(/\.lineIndex/g, '.line_index') + .replace(/\.nextAbsoluteChild/g, '.next_absolute_child') .replace(/layout\[dim/g, 'layout.dimensions[dim') .replace(/layout\[pos/g, 'layout.position[pos') .replace(/layout\[leading/g, 'layout.position[leading') @@ -261,6 +263,7 @@ function transpileAnnotatedJStoC(jsCode) { .replace(/node\./g, 'node->') .replace(/child\./g, 'child->') .replace(/parent\./g, 'parent->') + .replace(/currentAbsoluteChild\./g, 'currentAbsoluteChild->') .replace(/getPositionType\((.+?)\)/g, '$1->style.position_type') .replace(/getJustifyContent\((.+?)\)/g, '$1->style.justify_content') .replace(/getAlignContent\((.+?)\)/g, '$1->style.align_content')