Skip to content

Commit

Permalink
Add fast path for simple stack layouts
Browse files Browse the repository at this point in the history
Change the initial line loop to opportunistically position children in
the in container with simple stacking params i.e. vertical/horizontal
stacking on non-flexible STRETCH/FLEX_START aligned. This allows us to
skip the main and cross axis loops (Loop C and D, respectively)
partially and even completely in many common scenarios.

In my benchamrks, this gives us about ~15% performance win in many
setups.
  • Loading branch information
lucasr committed Sep 10, 2015
1 parent 2d86948 commit 765ff84
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 24 deletions.
75 changes: 67 additions & 8 deletions src/Layout.c
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,8 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction

bool isNodeFlexWrap = isFlexWrap(node);

css_justify_t justifyContent = node->style.justify_content;

float leadingPaddingAndBorderMain = getLeadingPaddingAndBorder(node, mainAxis);
float leadingPaddingAndBorderCross = getLeadingPaddingAndBorder(node, crossAxis);
float paddingAndBorderAxisMain = getPaddingAndBorderAxis(node, mainAxis);
Expand Down Expand Up @@ -636,19 +638,41 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
float totalFlexible = 0;
int nonFlexibleChildrenCount = 0;

// Use the line loop to position children in the main axis for as long
// as they are using a simple stacking behaviour. Children that are
// immediately stacked in the initial loop will not be touched again
// in <Loop C>.
bool isSimpleStackMain =
(isMainDimDefined && justifyContent == CSS_JUSTIFY_FLEX_START) ||
(!isMainDimDefined && justifyContent != CSS_JUSTIFY_CENTER);
int firstComplexMain = (isSimpleStackMain ? childCount : startLine);

// Use the initial line loop to position children in the cross axis for
// as long as they are relatively positioned with alignment STRETCH or
// FLEX_START. Children that are immediately stacked in the initial loop
// will not be touched again in <Loop D>.
bool isSimpleStackCross = true;
int firstComplexCross = childCount;

css_node_t* firstFlexChild = NULL;
css_node_t* currentFlexChild = NULL;

float mainDim = leadingPaddingAndBorderMain;
float crossDim = 0;

float maxWidth;
for (i = startLine; i < childCount; ++i) {
child = node->get_child(node->context, i);
child->line_index = linesCount;

child->next_absolute_child = NULL;
child->next_flex_child = NULL;

css_align_t alignItem = getAlignItem(node, child);

// 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 &&
if (alignItem == CSS_ALIGN_STRETCH &&
child->style.position_type == CSS_POSITION_RELATIVE &&
isCrossDimDefined &&
!isDimDefined(child, crossAxis)) {
Expand Down Expand Up @@ -753,6 +777,44 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
alreadyComputedNextLayout = 1;
break;
}

// Disable simple stacking in the main axis for the current line as
// we found a non-trivial child-> The remaining children will be laid out
// in <Loop C>.
if (isSimpleStackMain &&
(child->style.position_type != CSS_POSITION_RELATIVE || isFlex(child))) {
isSimpleStackMain = false;
firstComplexMain = i;
}

// Disable simple stacking in the cross axis for the current line as
// we found a non-trivial child-> The remaining children will be laid out
// in <Loop D>.
if (isSimpleStackCross &&
(child->style.position_type != CSS_POSITION_RELATIVE ||
(alignItem != CSS_ALIGN_STRETCH && alignItem != CSS_ALIGN_FLEX_START) ||
isUndefined(child->layout.dimensions[dim[crossAxis]]))) {
isSimpleStackCross = false;
firstComplexCross = i;
}

if (isSimpleStackMain) {
child->layout.position[pos[mainAxis]] += mainDim;
if (isMainDimDefined) {
setTrailingPosition(node, child, mainAxis);
}

mainDim += getDimWithMargin(child, mainAxis);
crossDim = fmaxf(crossDim, boundAxis(child, crossAxis, getDimWithMargin(child, crossAxis)));
}

if (isSimpleStackCross) {
child->layout.position[pos[crossAxis]] += linesCrossDim + leadingPaddingAndBorderCross;
if (isCrossDimDefined) {
setTrailingPosition(node, child, crossAxis);
}
}

alreadyComputedNextLayout = 0;
mainContentDim += nextContentDim;
endLine = i + 1;
Expand Down Expand Up @@ -833,8 +895,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction

// We use justifyContent to figure out how to allocate the remaining
// space available
} else if (node->style.justify_content != CSS_JUSTIFY_FLEX_START) {
css_justify_t justifyContent = node->style.justify_content;
} else if (justifyContent != CSS_JUSTIFY_FLEX_START) {
if (justifyContent == CSS_JUSTIFY_CENTER) {
leadingMainDim = remainingMainDim / 2;
} else if (justifyContent == CSS_JUSTIFY_FLEX_END) {
Expand All @@ -861,12 +922,10 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
// find their position. In order to do that, we accumulate data in
// variables that are also useful to compute the total dimensions of the
// container!
float crossDim = 0;
float mainDim = leadingMainDim + leadingPaddingAndBorderMain;
mainDim += leadingMainDim;

for (i = startLine; i < endLine; ++i) {
for (i = firstComplexMain; i < endLine; ++i) {
child = node->get_child(node->context, i);
child->line_index = linesCount;

if (child->style.position_type == CSS_POSITION_ABSOLUTE &&
isPosDefined(child, leading[mainAxis])) {
Expand Down Expand Up @@ -912,7 +971,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
}

// <Loop D> Position elements in the cross axis
for (i = startLine; i < endLine; ++i) {
for (i = firstComplexCross; i < endLine; ++i) {
child = node->get_child(node->context, i);

if (child->style.position_type == CSS_POSITION_ABSOLUTE &&
Expand Down
75 changes: 67 additions & 8 deletions src/Layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,8 @@ var computeLayout = (function() {

var/*bool*/ isNodeFlexWrap = isFlexWrap(node);

var/*css_justify_t*/ justifyContent = getJustifyContent(node);

var/*float*/ leadingPaddingAndBorderMain = getLeadingPaddingAndBorder(node, mainAxis);
var/*float*/ leadingPaddingAndBorderCross = getLeadingPaddingAndBorder(node, crossAxis);
var/*float*/ paddingAndBorderAxisMain = getPaddingAndBorderAxis(node, mainAxis);
Expand Down Expand Up @@ -504,19 +506,41 @@ var computeLayout = (function() {
var/*float*/ totalFlexible = 0;
var/*int*/ nonFlexibleChildrenCount = 0;

// Use the line loop to position children in the main axis for as long
// as they are using a simple stacking behaviour. Children that are
// immediately stacked in the initial loop will not be touched again
// in <Loop C>.
var/*bool*/ isSimpleStackMain =
(isMainDimDefined && justifyContent == CSS_JUSTIFY_FLEX_START) ||
(!isMainDimDefined && justifyContent != CSS_JUSTIFY_CENTER);
var/*int*/ firstComplexMain = (isSimpleStackMain ? childCount : startLine);

// Use the initial line loop to position children in the cross axis for
// as long as they are relatively positioned with alignment STRETCH or
// FLEX_START. Children that are immediately stacked in the initial loop
// will not be touched again in <Loop D>.
var/*bool*/ isSimpleStackCross = true;
var/*int*/ firstComplexCross = childCount;

var/*css_node_t**/ firstFlexChild = null;
var/*css_node_t**/ currentFlexChild = null;

var/*float*/ mainDim = leadingPaddingAndBorderMain;
var/*float*/ crossDim = 0;

var/*float*/ maxWidth;
for (i = startLine; i < childCount; ++i) {
child = node.children[i];
child.lineIndex = linesCount;

child.nextAbsoluteChild = null;
child.nextFlexChild = null;

var/*css_align_t*/ alignItem = getAlignItem(node, child);

// 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 &&
if (alignItem === CSS_ALIGN_STRETCH &&
getPositionType(child) === CSS_POSITION_RELATIVE &&
isCrossDimDefined &&
!isDimDefined(child, crossAxis)) {
Expand Down Expand Up @@ -621,6 +645,44 @@ var computeLayout = (function() {
alreadyComputedNextLayout = 1;
break;
}

// Disable simple stacking in the main axis for the current line as
// we found a non-trivial child. The remaining children will be laid out
// in <Loop C>.
if (isSimpleStackMain &&
(getPositionType(child) != CSS_POSITION_RELATIVE || isFlex(child))) {
isSimpleStackMain = false;
firstComplexMain = i;
}

// Disable simple stacking in the cross axis for the current line as
// we found a non-trivial child. The remaining children will be laid out
// in <Loop D>.
if (isSimpleStackCross &&
(getPositionType(child) != CSS_POSITION_RELATIVE ||
(alignItem !== CSS_ALIGN_STRETCH && alignItem != CSS_ALIGN_FLEX_START) ||
isUndefined(child.layout[dim[crossAxis]]))) {
isSimpleStackCross = false;
firstComplexCross = i;
}

if (isSimpleStackMain) {
child.layout[pos[mainAxis]] += mainDim;
if (isMainDimDefined) {
setTrailingPosition(node, child, mainAxis);
}

mainDim += getDimWithMargin(child, mainAxis);
crossDim = fmaxf(crossDim, boundAxis(child, crossAxis, getDimWithMargin(child, crossAxis)));
}

if (isSimpleStackCross) {
child.layout[pos[crossAxis]] += linesCrossDim + leadingPaddingAndBorderCross;
if (isCrossDimDefined) {
setTrailingPosition(node, child, crossAxis);
}
}

alreadyComputedNextLayout = 0;
mainContentDim += nextContentDim;
endLine = i + 1;
Expand Down Expand Up @@ -701,8 +763,7 @@ var computeLayout = (function() {

// We use justifyContent to figure out how to allocate the remaining
// space available
} else if (getJustifyContent(node) !== CSS_JUSTIFY_FLEX_START) {
var/*css_justify_t*/ justifyContent = getJustifyContent(node);
} else if (justifyContent !== CSS_JUSTIFY_FLEX_START) {
if (justifyContent === CSS_JUSTIFY_CENTER) {
leadingMainDim = remainingMainDim / 2;
} else if (justifyContent === CSS_JUSTIFY_FLEX_END) {
Expand All @@ -729,12 +790,10 @@ var computeLayout = (function() {
// find their position. In order to do that, we accumulate data in
// variables that are also useful to compute the total dimensions of the
// container!
var/*float*/ crossDim = 0;
var/*float*/ mainDim = leadingMainDim + leadingPaddingAndBorderMain;
mainDim += leadingMainDim;

for (i = startLine; i < endLine; ++i) {
for (i = firstComplexMain; i < endLine; ++i) {
child = node.children[i];
child.lineIndex = linesCount;

if (getPositionType(child) === CSS_POSITION_ABSOLUTE &&
isPosDefined(child, leading[mainAxis])) {
Expand Down Expand Up @@ -780,7 +839,7 @@ var computeLayout = (function() {
}

// <Loop D> Position elements in the cross axis
for (i = startLine; i < endLine; ++i) {
for (i = firstComplexCross; i < endLine; ++i) {
child = node.children[i];

if (getPositionType(child) === CSS_POSITION_ABSOLUTE &&
Expand Down
75 changes: 67 additions & 8 deletions src/java/src/com/facebook/csslayout/LayoutEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,8 @@ private static void layoutNodeImpl(

boolean isNodeFlexWrap = isFlexWrap(node);

CSSJustify justifyContent = node.style.justifyContent;

float leadingPaddingAndBorderMain = getLeadingPaddingAndBorder(node, mainAxis);
float leadingPaddingAndBorderCross = getLeadingPaddingAndBorder(node, crossAxis);
float paddingAndBorderAxisMain = getPaddingAndBorderAxis(node, mainAxis);
Expand Down Expand Up @@ -453,19 +455,41 @@ private static void layoutNodeImpl(
float totalFlexible = 0;
int nonFlexibleChildrenCount = 0;

// Use the line loop to position children in the main axis for as long
// as they are using a simple stacking behaviour. Children that are
// immediately stacked in the initial loop will not be touched again
// in <Loop C>.
boolean isSimpleStackMain =
(isMainDimDefined && justifyContent == CSSJustify.FLEX_START) ||
(!isMainDimDefined && justifyContent != CSSJustify.CENTER);
int firstComplexMain = (isSimpleStackMain ? childCount : startLine);

// Use the initial line loop to position children in the cross axis for
// as long as they are relatively positioned with alignment STRETCH or
// FLEX_START. Children that are immediately stacked in the initial loop
// will not be touched again in <Loop D>.
boolean isSimpleStackCross = true;
int firstComplexCross = childCount;

CSSNode firstFlexChild = null;
CSSNode currentFlexChild = null;

float mainDim = leadingPaddingAndBorderMain;
float crossDim = 0;

float maxWidth;
for (i = startLine; i < childCount; ++i) {
child = node.getChildAt(i);
child.lineIndex = linesCount;

child.nextAbsoluteChild = null;
child.nextFlexChild = null;

CSSAlign alignItem = getAlignItem(node, child);

// Pre-fill cross axis dimensions when the child is using stretch before
// we call the recursive layout pass
if (getAlignItem(node, child) == CSSAlign.STRETCH &&
if (alignItem == CSSAlign.STRETCH &&
child.style.positionType == CSSPositionType.RELATIVE &&
isCrossDimDefined &&
!isDimDefined(child, crossAxis)) {
Expand Down Expand Up @@ -570,6 +594,44 @@ private static void layoutNodeImpl(
alreadyComputedNextLayout = 1;
break;
}

// Disable simple stacking in the main axis for the current line as
// we found a non-trivial child. The remaining children will be laid out
// in <Loop C>.
if (isSimpleStackMain &&
(child.style.positionType != CSSPositionType.RELATIVE || isFlex(child))) {
isSimpleStackMain = false;
firstComplexMain = i;
}

// Disable simple stacking in the cross axis for the current line as
// we found a non-trivial child. The remaining children will be laid out
// in <Loop D>.
if (isSimpleStackCross &&
(child.style.positionType != CSSPositionType.RELATIVE ||
(alignItem != CSSAlign.STRETCH && alignItem != CSSAlign.FLEX_START) ||
isUndefined(child.layout.dimensions[dim[crossAxis]]))) {
isSimpleStackCross = false;
firstComplexCross = i;
}

if (isSimpleStackMain) {
child.layout.position[pos[mainAxis]] += mainDim;
if (isMainDimDefined) {
setTrailingPosition(node, child, mainAxis);
}

mainDim += getDimWithMargin(child, mainAxis);
crossDim = Math.max(crossDim, boundAxis(child, crossAxis, getDimWithMargin(child, crossAxis)));
}

if (isSimpleStackCross) {
child.layout.position[pos[crossAxis]] += linesCrossDim + leadingPaddingAndBorderCross;
if (isCrossDimDefined) {
setTrailingPosition(node, child, crossAxis);
}
}

alreadyComputedNextLayout = 0;
mainContentDim += nextContentDim;
endLine = i + 1;
Expand Down Expand Up @@ -650,8 +712,7 @@ private static void layoutNodeImpl(

// We use justifyContent to figure out how to allocate the remaining
// space available
} else if (node.style.justifyContent != CSSJustify.FLEX_START) {
CSSJustify justifyContent = node.style.justifyContent;
} else if (justifyContent != CSSJustify.FLEX_START) {
if (justifyContent == CSSJustify.CENTER) {
leadingMainDim = remainingMainDim / 2;
} else if (justifyContent == CSSJustify.FLEX_END) {
Expand All @@ -678,12 +739,10 @@ private static void layoutNodeImpl(
// find their position. In order to do that, we accumulate data in
// variables that are also useful to compute the total dimensions of the
// container!
float crossDim = 0;
float mainDim = leadingMainDim + leadingPaddingAndBorderMain;
mainDim += leadingMainDim;

for (i = startLine; i < endLine; ++i) {
for (i = firstComplexMain; i < endLine; ++i) {
child = node.getChildAt(i);
child.lineIndex = linesCount;

if (child.style.positionType == CSSPositionType.ABSOLUTE &&
isPosDefined(child, leading[mainAxis])) {
Expand Down Expand Up @@ -729,7 +788,7 @@ private static void layoutNodeImpl(
}

// <Loop D> Position elements in the cross axis
for (i = startLine; i < endLine; ++i) {
for (i = firstComplexCross; i < endLine; ++i) {
child = node.getChildAt(i);

if (child.style.positionType == CSSPositionType.ABSOLUTE &&
Expand Down

0 comments on commit 765ff84

Please sign in to comment.