From 611894329a01615d37eb66a7e1e0c762b91fbe69 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Sun, 30 Oct 2016 12:07:18 -0700 Subject: [PATCH] [ASStackLayoutSpec] Performance improvements (#2470) * Initial commit for ASStackLayoutSpec improvements * Remove the lock in ASStackLayoutSpec and make the ASStackLayoutSpecStyle const I think we don't need lock here as the style already has a lock while we set the value * Add ASStackLayoutSpecItem that replaces layout specific items * Prevent baseline pass if not needed * Update comments --- AsyncDisplayKit/Layout/ASStackLayoutSpec.mm | 76 ++++++++++----- .../Private/ASStackBaselinePositionedLayout.h | 12 ++- .../ASStackBaselinePositionedLayout.mm | 96 ++++++++++--------- .../Private/ASStackPositionedLayout.h | 2 +- .../Private/ASStackPositionedLayout.mm | 28 +++--- .../Private/ASStackUnpositionedLayout.h | 20 +++- .../Private/ASStackUnpositionedLayout.mm | 91 +++++++++--------- 7 files changed, 185 insertions(+), 140 deletions(-) diff --git a/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm b/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm index 2f261b681f..56a7a6d76c 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm @@ -13,6 +13,8 @@ #import "ASInternalHelpers.h" +#import "ASLayoutElement.h" +#import "ASLayoutElementStylePrivate.h" #import "ASLayoutSpecUtilities.h" #import "ASStackBaselinePositionedLayout.h" #import "ASThread.h" @@ -120,43 +122,65 @@ - (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize { - std::vector> stackChildren; - for (id child in self.children) { - stackChildren.push_back(child); - } - - if (stackChildren.empty()) { + NSArray *children = self.children; + if (children.count == 0) { return [ASLayout layoutWithLayoutElement:self size:constrainedSize.min]; } + + // Accessing the style and size property is pretty costly we create layout spec children we use to figure + // out the layout for each child + const auto stackChildren = AS::map(children, [&](const id child) -> ASStackLayoutSpecChild { + ASLayoutElementStyle *style = child.style; + return {child, style, style.size}; + }); - ASStackLayoutSpecStyle style = {.direction = _direction, .spacing = _spacing, .justifyContent = _justifyContent, .alignItems = _alignItems, .baselineRelativeArrangement = _baselineRelativeArrangement}; - BOOL needsBaselinePass = _baselineRelativeArrangement || _alignItems == ASStackLayoutAlignItemsBaselineFirst || _alignItems == ASStackLayoutAlignItemsBaselineLast; + const ASStackLayoutSpecStyle style = {.direction = _direction, .spacing = _spacing, .justifyContent = _justifyContent, .alignItems = _alignItems, .baselineRelativeArrangement = _baselineRelativeArrangement}; + // First pass is to get the children into a positioned state const auto unpositionedLayout = ASStackUnpositionedLayout::compute(stackChildren, style, constrainedSize); const auto positionedLayout = ASStackPositionedLayout::compute(unpositionedLayout, style, constrainedSize); - CGSize finalSize = CGSizeZero; - NSArray *sublayouts = nil; + // Figure out if a baseline pass is really needed + const BOOL directionIsVertical = (style.direction == ASStackLayoutDirectionVertical); + const BOOL needsBaselineAlignment = ASStackBaselinePositionedLayout::needsBaselineAlignment(style); + const BOOL needsBaselinePositioning = (directionIsVertical == NO || needsBaselineAlignment == YES); - // regardless of whether or not this stack aligns to baseline, we should let ASStackBaselinePositionedLayout::compute find the max ascender - // and min descender in case this spec is a child in another spec that wants to align to a baseline. - const auto baselinePositionedLayout = ASStackBaselinePositionedLayout::compute(positionedLayout, style, constrainedSize); - if (self.direction == ASStackLayoutDirectionVertical) { - ASDN::MutexLocker l(__instanceLock__); - self.style.ascender = stackChildren.front().style.ascender; - self.style.descender = stackChildren.back().style.descender; - } else { - ASDN::MutexLocker l(__instanceLock__); - self.style.ascender = baselinePositionedLayout.ascender; - self.style.descender = baselinePositionedLayout.descender; + NSMutableArray *sublayouts = [NSMutableArray array]; + CGSize finalSize = CGSizeZero; + if (needsBaselinePositioning) { + // All horizontal stacks, regardless of whether or not they are baseline aligned, should go through a baseline + // computation. They could be used in another horizontal stack that is baseline aligned and will need to have + // computed the proper ascender/descender. + + // Vertical stacks do not need to go through this computation since we can easily compute ascender/descender by + // looking at their first/last child's ascender/descender. + const auto baselinePositionedLayout = ASStackBaselinePositionedLayout::compute(positionedLayout, style, constrainedSize); + + if (directionIsVertical == NO) { + self.style.ascender = baselinePositionedLayout.ascender; + self.style.descender = baselinePositionedLayout.descender; + } + + if (needsBaselineAlignment == YES) { + finalSize = directionSize(style.direction, unpositionedLayout.stackDimensionSum, baselinePositionedLayout.crossSize); + + for (const auto &l : baselinePositionedLayout.items) { + [sublayouts addObject:l.layout]; + } + } } - if (needsBaselinePass) { - finalSize = directionSize(style.direction, unpositionedLayout.stackDimensionSum, baselinePositionedLayout.crossSize); - sublayouts = [NSArray arrayWithObjects:&baselinePositionedLayout.sublayouts[0] count:baselinePositionedLayout.sublayouts.size()]; - } else { + if (directionIsVertical == YES) { + self.style.ascender = stackChildren.front().style.ascender; + self.style.descender = stackChildren.back().style.descender; + } + + if (needsBaselineAlignment == NO) { finalSize = directionSize(style.direction, unpositionedLayout.stackDimensionSum, positionedLayout.crossSize); - sublayouts = [NSArray arrayWithObjects:&positionedLayout.sublayouts[0] count:positionedLayout.sublayouts.size()]; + + for (const auto &l : positionedLayout.items) { + [sublayouts addObject:l.layout]; + } } return [ASLayout layoutWithLayoutElement:self size:ASSizeRangeClamp(constrainedSize, finalSize) sublayouts:sublayouts]; diff --git a/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.h b/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.h index 9b23393e63..9764c25d12 100644 --- a/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.h +++ b/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.h @@ -13,13 +13,15 @@ #import "ASStackPositionedLayout.h" struct ASStackBaselinePositionedLayout { - const std::vector sublayouts; - const CGFloat crossSize; - const CGFloat ascender; - const CGFloat descender; - + const std::vector items; + const CGFloat crossSize; + const CGFloat ascender; + const CGFloat descender; + /** Given a positioned layout, computes each child position using baseline alignment. */ static ASStackBaselinePositionedLayout compute(const ASStackPositionedLayout &positionedLayout, const ASStackLayoutSpecStyle &style, const ASSizeRange &constrainedSize); + + static BOOL needsBaselineAlignment(const ASStackLayoutSpecStyle &style); }; diff --git a/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.mm b/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.mm index 081d7cf42c..3dad841291 100644 --- a/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.mm +++ b/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.mm @@ -13,31 +13,31 @@ #import "ASLayoutSpecUtilities.h" #import "ASLayoutSpec+Subclasses.h" +#import "ASLayoutElement.h" +#import "ASLayoutElementStylePrivate.h" + static CGFloat baselineForItem(const ASStackLayoutSpecStyle &style, - const ASLayout *layout) { - - __weak id child = layout.layoutElement; + const ASStackLayoutSpecItem &l) +{ switch (style.alignItems) { case ASStackLayoutAlignItemsBaselineFirst: - return child.style.ascender; + return l.child.style.ascender; case ASStackLayoutAlignItemsBaselineLast: - return layout.size.height + child.style.descender; + return l.layout.size.height + l.child.style.descender; default: return 0; } - } static CGFloat baselineOffset(const ASStackLayoutSpecStyle &style, - const ASLayout *l, + const ASStackLayoutSpecItem &l, const CGFloat maxAscender, const CGFloat maxBaseline) { if (style.direction == ASStackLayoutDirectionHorizontal) { - __weak id child = l.layoutElement; switch (style.alignItems) { case ASStackLayoutAlignItemsBaselineFirst: - return maxAscender - child.style.ascender; + return maxAscender - l.child.style.ascender; case ASStackLayoutAlignItemsBaselineLast: return maxBaseline - baselineForItem(style, l); @@ -48,19 +48,28 @@ static CGFloat baselineOffset(const ASStackLayoutSpecStyle &style, return 0; } -static CGFloat maxDimensionForLayout(const ASLayout *l, - const ASStackLayoutSpecStyle &style) +static CGFloat maxDimensionForItem(const ASStackLayoutSpecItem &l, + const ASStackLayoutSpecStyle &style) { - CGFloat maxDimension = crossDimension(style.direction, l.size); - style.direction == ASStackLayoutDirectionVertical ? maxDimension += l.position.x : maxDimension += l.position.y; + CGFloat maxDimension = crossDimension(style.direction, l.layout.size); + style.direction == ASStackLayoutDirectionVertical ? maxDimension += l.layout.position.x : maxDimension += l.layout.position.y; return maxDimension; } +BOOL ASStackBaselinePositionedLayout::needsBaselineAlignment(const ASStackLayoutSpecStyle &style) +{ + return style.baselineRelativeArrangement || + style.alignItems == ASStackLayoutAlignItemsBaselineFirst || + style.alignItems == ASStackLayoutAlignItemsBaselineLast; +} + ASStackBaselinePositionedLayout ASStackBaselinePositionedLayout::compute(const ASStackPositionedLayout &positionedLayout, const ASStackLayoutSpecStyle &style, const ASSizeRange &constrainedSize) { - /* Step 1: Look at each child and determine the distance from the top of the child node it's baseline. + const auto stackedChildren = positionedLayout.items; + + /* Step 1: Look at each child and determine the distance from the top of the child node to its baseline. For example, let's say we have the following two text nodes and want to align them to the first baseline: Hello! Why, hello there! How @@ -74,10 +83,10 @@ ASStackBaselinePositionedLayout ASStackBaselinePositionedLayout::compute(const A the font's descender (it's negative). In the case of the first node, which is only 1 line, this should be the same value as the ascender. The second node, however, has a larger height and there will have a larger baseline offset. */ - const auto baselineIt = std::max_element(positionedLayout.sublayouts.begin(), positionedLayout.sublayouts.end(), [&](const ASLayout *a, const ASLayout *b){ + const auto baselineIt = std::max_element(stackedChildren.begin(), stackedChildren.end(), [&](const ASStackLayoutSpecItem &a, const ASStackLayoutSpecItem &b){ return baselineForItem(style, a) < baselineForItem(style, b); }); - const CGFloat maxBaseline = baselineIt == positionedLayout.sublayouts.end() ? 0 : baselineForItem(style, *baselineIt); + const CGFloat maxBaseline = baselineIt == stackedChildren.end() ? 0 : baselineForItem(style, *baselineIt); /* Step 2: Find the max ascender for all of the children. @@ -89,10 +98,10 @@ ASStackBaselinePositionedLayout ASStackBaselinePositionedLayout::compute(const A Note: if we are aligning to the last baseline, then we don't need this value in our computation. However, we do want our layoutSpec to have it so that it can be baseline aligned with another text node or baseline layout spec. */ - const auto ascenderIt = std::max_element(positionedLayout.sublayouts.begin(), positionedLayout.sublayouts.end(), [&](const ASLayout *a, const ASLayout *b){ - return a.layoutElement.style.ascender < b.layoutElement.style.ascender; + const auto ascenderIt = std::max_element(stackedChildren.begin(), stackedChildren.end(), [&](const ASStackLayoutSpecItem &a, const ASStackLayoutSpecItem &b){ + return a.child.style.ascender < b.child.style.ascender; }); - const CGFloat maxAscender = ascenderIt == positionedLayout.sublayouts.end() ? 0 : (*ascenderIt).layoutElement.style.ascender; + const CGFloat maxAscender = ascenderIt == stackedChildren.end() ? 0 : (*ascenderIt).child.style.ascender; /* Step 3: Take each child and update its layout position based on the baseline offset. @@ -102,39 +111,34 @@ ASStackBaselinePositionedLayout ASStackBaselinePositionedLayout::compute(const A spacing between the two nodes is from the baseline, not the bounding box. */ - std::vector stackedChildren; + // Only change positions of layouts this stackSpec is aligning to a baseline. Otherwise we are only here to // compute the min/max descender/ascender for this stack spec. - if (style.baselineRelativeArrangement || style.alignItems == ASStackLayoutAlignItemsBaselineFirst || style.alignItems == ASStackLayoutAlignItemsBaselineLast) { + if (ASStackBaselinePositionedLayout::needsBaselineAlignment(style)) { + // Adjust the positioned layout items to be positioned based on the baseline CGPoint p = CGPointZero; BOOL first = YES; - stackedChildren = AS::map(positionedLayout.sublayouts, [&](ASLayout *l) -> ASLayout *{ - __weak id child = l.layoutElement; - p = p + directionPoint(style.direction, child.style.spacingBefore, 0); - if (first) { - // if this is the first item use the previously computed start point - p = l.position; - } else { - // otherwise add the stack spacing - p = p + directionPoint(style.direction, style.spacing, 0); - } + + for (const ASStackLayoutSpecItem &l : stackedChildren) { + ASLayoutElementStyle *layoutElementStyle = l.child.style; + + p = p + directionPoint(style.direction, layoutElementStyle.spacingBefore, 0); + + // if this is the first item use the previously computed start point otherwise add the stack spacing + p = first ? l.layout.position : p + directionPoint(style.direction, style.spacing, 0); first = NO; // Find the difference between this node's baseline and the max baseline of all the children. Add this difference to the child's y position. - l.position = p + CGPointMake(0, baselineOffset(style, l, maxAscender, maxBaseline)); + l.layout.position = p + CGPointMake(0, baselineOffset(style, l, maxAscender, maxBaseline)); // If we are a vertical stack, add the item's descender (it is negative) to the offset for the next node. This will ensure we are spacing // node from baselines and not bounding boxes. CGFloat spacingAfterBaseline = 0; if (style.direction == ASStackLayoutDirectionVertical) { - spacingAfterBaseline = child.style.descender; + spacingAfterBaseline = layoutElementStyle.descender; } - p = p + directionPoint(style.direction, stackDimension(style.direction, l.size) + child.style.spacingAfter + spacingAfterBaseline, 0); - - return l; - }); - } else { - stackedChildren = positionedLayout.sublayouts; + p = p + directionPoint(style.direction, stackDimension(style.direction, l.layout.size) + layoutElementStyle.spacingAfter + spacingAfterBaseline, 0); + } } /* @@ -147,10 +151,10 @@ ASStackBaselinePositionedLayout ASStackBaselinePositionedLayout::compute(const A */ const auto it = std::max_element(stackedChildren.begin(), stackedChildren.end(), - [&](ASLayout *a, ASLayout *b) { - return maxDimensionForLayout(a, style) < maxDimensionForLayout(b, style); + [&](const ASStackLayoutSpecItem &a, const ASStackLayoutSpecItem &b) { + return maxDimensionForItem(a, style) < maxDimensionForItem(b, style); }); - const auto largestChildCrossSize = it == stackedChildren.end() ? 0 : maxDimensionForLayout(*it, style); + const auto largestChildCrossSize = it == stackedChildren.end() ? 0 : maxDimensionForItem(*it, style); const auto minCrossSize = crossDimension(style.direction, constrainedSize.min); const auto maxCrossSize = crossDimension(style.direction, constrainedSize.max); const CGFloat crossSize = MIN(MAX(minCrossSize, largestChildCrossSize), maxCrossSize); @@ -159,10 +163,10 @@ ASStackBaselinePositionedLayout ASStackBaselinePositionedLayout::compute(const A Step 5: finally, we must find the smallest descender (descender is negative). This is since ASBaselineLayoutSpec implements ASLayoutElement and needs an ascender and descender to lay itself out properly. */ - const auto descenderIt = std::max_element(stackedChildren.begin(), stackedChildren.end(), [&](const ASLayout *a, const ASLayout *b){ - return a.position.y + a.size.height < b.position.y + b.size.height; + const auto descenderIt = std::max_element(stackedChildren.begin(), stackedChildren.end(), [&](const ASStackLayoutSpecItem &a, const ASStackLayoutSpecItem &b){ + return a.layout.position.y + a.layout.size.height < b.layout.position.y + b.layout.size.height; }); - const CGFloat minDescender = descenderIt == stackedChildren.end() ? 0 : (*descenderIt).layoutElement.style.descender; + const CGFloat minDescender = descenderIt == stackedChildren.end() ? 0 : (*descenderIt).child.style.descender; - return {stackedChildren, crossSize, maxAscender, minDescender}; + return {std::move(stackedChildren), crossSize, maxAscender, minDescender}; } diff --git a/AsyncDisplayKit/Private/ASStackPositionedLayout.h b/AsyncDisplayKit/Private/ASStackPositionedLayout.h index d28a97940b..a879763297 100644 --- a/AsyncDisplayKit/Private/ASStackPositionedLayout.h +++ b/AsyncDisplayKit/Private/ASStackPositionedLayout.h @@ -14,7 +14,7 @@ /** Represents a set of laid out and positioned stack layout children. */ struct ASStackPositionedLayout { - const std::vector sublayouts; + const std::vector items; const CGFloat crossSize; /** Given an unpositioned layout, computes the positions each child should be placed at. */ diff --git a/AsyncDisplayKit/Private/ASStackPositionedLayout.mm b/AsyncDisplayKit/Private/ASStackPositionedLayout.mm index f00478b67a..c38cf797bf 100644 --- a/AsyncDisplayKit/Private/ASStackPositionedLayout.mm +++ b/AsyncDisplayKit/Private/ASStackPositionedLayout.mm @@ -17,7 +17,7 @@ #import "ASLayoutSpec+Subclasses.h" static CGFloat crossOffset(const ASStackLayoutSpecStyle &style, - const ASStackUnpositionedItem &l, + const ASStackLayoutSpecItem &l, const CGFloat crossSize) { switch (alignment(l.child.style.alignSelf, style.alignItems)) { @@ -50,9 +50,10 @@ static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style const ASStackUnpositionedLayout &unpositionedLayout, const ASSizeRange &constrainedSize) { + // The cross dimension is the max of the childrens' cross dimensions (clamped to our constraint below). const auto it = std::max_element(unpositionedLayout.items.begin(), unpositionedLayout.items.end(), - [&](const ASStackUnpositionedItem &a, const ASStackUnpositionedItem &b){ + [&](const ASStackLayoutSpecItem &a, const ASStackLayoutSpecItem &b){ return compareCrossDimension(style.direction, a.layout.size, b.layout.size); }); const auto largestChildCrossSize = it == unpositionedLayout.items.end() ? 0 : crossDimension(style.direction, it->layout.size); @@ -64,9 +65,11 @@ static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style BOOL first = YES; const auto lastChild = unpositionedLayout.items.back().child; CGFloat offset = 0; - - auto stackedChildren = AS::map(unpositionedLayout.items, [&](const ASStackUnpositionedItem &l) -> ASLayout *{ - offset = (l.child == lastChild) ? lastChildOffset : 0; + + // Adjust the position of the unpositioned layouts to be positioned + const auto stackedChildren = unpositionedLayout.items; + for (const auto &l : stackedChildren) { + offset = (l.child.element == lastChild.element) ? lastChildOffset : 0; p = p + directionPoint(style.direction, l.child.style.spacingBefore + offset, 0); if (!first) { p = p + directionPoint(style.direction, style.spacing + extraSpacing, 0); @@ -75,9 +78,9 @@ static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style l.layout.position = p + directionPoint(style.direction, 0, crossOffset(style, l, crossSize)); p = p + directionPoint(style.direction, stackDimension(style.direction, l.layout.size) + l.child.style.spacingAfter, 0); - return l.layout; - }); - return {stackedChildren, crossSize}; + } + + return {std::move(stackedChildren), crossSize}; } static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style, @@ -105,12 +108,15 @@ ASStackPositionedLayout ASStackPositionedLayout::compute(const ASStackUnposition } switch (justifyContent) { - case ASStackLayoutJustifyContentStart: + case ASStackLayoutJustifyContentStart: { return stackedLayout(style, 0, unpositionedLayout, constrainedSize); - case ASStackLayoutJustifyContentCenter: + } + case ASStackLayoutJustifyContentCenter: { return stackedLayout(style, std::floor(violation / 2), unpositionedLayout, constrainedSize); - case ASStackLayoutJustifyContentEnd: + } + case ASStackLayoutJustifyContentEnd: { return stackedLayout(style, violation, unpositionedLayout, constrainedSize); + } case ASStackLayoutJustifyContentSpaceBetween: { const auto numOfSpacings = numOfItems - 1; return stackedLayout(style, 0, std::floor(violation / numOfSpacings), std::fmod(violation, numOfSpacings), unpositionedLayout, constrainedSize); diff --git a/AsyncDisplayKit/Private/ASStackUnpositionedLayout.h b/AsyncDisplayKit/Private/ASStackUnpositionedLayout.h index adfd5565f4..c1607c93cb 100644 --- a/AsyncDisplayKit/Private/ASStackUnpositionedLayout.h +++ b/AsyncDisplayKit/Private/ASStackUnpositionedLayout.h @@ -14,24 +14,34 @@ #import "ASStackLayoutSpecUtilities.h" #import "ASStackLayoutSpec.h" -struct ASStackUnpositionedItem { +struct ASStackLayoutSpecChild { /** The original source child. */ - id child; - /** The proposed layout. */ + id element; + /** Style object of element. */ + ASLayoutElementStyle *style; + /** Size object of the element */ + ASLayoutElementSize size; +}; + +struct ASStackLayoutSpecItem { + /** The original source child. */ + ASStackLayoutSpecChild child; + /** The proposed layout or nil if no is calculated yet. */ ASLayout *layout; }; + /** Represents a set of stack layout children that have their final layout computed, but are not yet positioned. */ struct ASStackUnpositionedLayout { /** A set of proposed child layouts, not yet positioned. */ - const std::vector items; + const std::vector items; /** The total size of the children in the stack dimension, including all spacing. */ const CGFloat stackDimensionSum; /** The amount by which stackDimensionSum violates constraints. If positive, less than min; negative, greater than max. */ const CGFloat violation; /** Given a set of children, computes the unpositioned layouts for those children. */ - static ASStackUnpositionedLayout compute(const std::vector> &children, + static ASStackUnpositionedLayout compute(const std::vector &children, const ASStackLayoutSpecStyle &style, const ASSizeRange &sizeRange); }; diff --git a/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm b/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm index ec72e49695..f86d5ef4ae 100644 --- a/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm +++ b/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm @@ -17,12 +17,11 @@ #import "ASLayoutElementStylePrivate.h" static CGFloat resolveCrossDimensionMaxForStretchChild(const ASStackLayoutSpecStyle &style, - const idchild, + const ASStackLayoutSpecChild &child, const CGFloat stackMax, const CGFloat crossMax) { // stretched children may have a cross direction max that is smaller than the minimum size constraint of the parent. - const CGFloat computedMax = (style.direction == ASStackLayoutDirectionVertical ? ASLayoutElementSizeResolve(child.style.size, ASLayoutElementParentSizeUndefined).max.width : ASLayoutElementSizeResolve(child.style.size, ASLayoutElementParentSizeUndefined).max.height); @@ -30,7 +29,7 @@ static CGFloat resolveCrossDimensionMaxForStretchChild(const ASStackLayoutSpecSt } static CGFloat resolveCrossDimensionMinForStretchChild(const ASStackLayoutSpecStyle &style, - const idchild, + const ASStackLayoutSpecChild &child, const CGFloat stackMax, const CGFloat crossMin) { @@ -44,8 +43,8 @@ static CGFloat resolveCrossDimensionMinForStretchChild(const ASStackLayoutSpecSt /** Sizes the child given the parameters specified, and returns the computed layout. */ -static ASLayout *crossChildLayout(const id child, - const ASStackLayoutSpecStyle style, +static ASLayout *crossChildLayout(const ASStackLayoutSpecChild &child, + const ASStackLayoutSpecStyle &style, const CGFloat stackMin, const CGFloat stackMax, const CGFloat crossMin, @@ -54,14 +53,16 @@ static ASLayout *crossChildLayout(const id child, { const ASStackLayoutAlignItems alignItems = alignment(child.style.alignSelf, style.alignItems); // stretched children will have a cross dimension of at least crossMin - const CGFloat childCrossMin = (alignItems == ASStackLayoutAlignItemsStretch ? resolveCrossDimensionMinForStretchChild(style, child, stackMax, crossMin) : 0); + const CGFloat childCrossMin = (alignItems == ASStackLayoutAlignItemsStretch ? + resolveCrossDimensionMinForStretchChild(style, child, stackMax, crossMin) : + 0); const CGFloat childCrossMax = (alignItems == ASStackLayoutAlignItemsStretch ? resolveCrossDimensionMaxForStretchChild(style, child, stackMax, crossMax) : crossMax); const ASSizeRange childSizeRange = directionSizeRange(style.direction, stackMin, stackMax, childCrossMin, childCrossMax); - ASLayout *layout = [child layoutThatFits:childSizeRange parentSize:size]; - ASDisplayNodeCAssertNotNil(layout, @"ASLayout returned from measureWithSizeRange: must not be nil: %@", child); - return layout ? : [ASLayout layoutWithLayoutElement:child size:{0, 0}]; + ASLayout *layout = [child.element layoutThatFits:childSizeRange parentSize:size]; + ASDisplayNodeCAssertNotNil(layout, @"ASLayout returned from measureWithSizeRange: must not be nil: %@", child.element); + return layout ? : [ASLayout layoutWithLayoutElement:child.element size:{0, 0}]; } /** @@ -96,13 +97,13 @@ static ASLayout *crossChildLayout(const id child, @param layouts pre-computed child layouts; modified in-place as needed @param style the layout style of the overall stack layout */ -static void stretchChildrenAlongCrossDimension(std::vector &layouts, +static void stretchChildrenAlongCrossDimension(std::vector &layouts, const ASStackLayoutSpecStyle &style, const CGSize size) { // Find the maximum cross dimension size among child layouts const auto it = std::max_element(layouts.begin(), layouts.end(), - [&](const ASStackUnpositionedItem &a, const ASStackUnpositionedItem &b) { + [&](const ASStackLayoutSpecItem &a, const ASStackLayoutSpecItem &b) { return compareCrossDimension(style.direction, a.layout.size, b.layout.size); }); @@ -129,18 +130,18 @@ static const CGFloat kViolationEpsilon = 0.01; Returns a lambda that computes the relevant flex factor based on the given violation. @param violation The amount that the stack layout violates its size range. See header for sign interpretation. */ -static std::function flexFactorInViolationDirection(const CGFloat violation) +static std::function flexFactorInViolationDirection(const CGFloat violation) { if (std::fabs(violation) < kViolationEpsilon) { - return [](const ASStackUnpositionedItem &item) { return 0.0; }; + return [](const ASStackLayoutSpecItem &item) { return 0.0; }; } else if (violation > 0) { - return [](const ASStackUnpositionedItem &item) { return item.child.style.flexGrow; }; + return [](const ASStackLayoutSpecItem &item) { return item.child.style.flexGrow; }; } else { - return [](const ASStackUnpositionedItem &item) { return item.child.style.flexShrink; }; + return [](const ASStackLayoutSpecItem &item) { return item.child.style.flexShrink; }; } } -static inline CGFloat scaledFlexShrinkFactor(const ASStackUnpositionedItem &item, +static inline CGFloat scaledFlexShrinkFactor(const ASStackLayoutSpecItem &item, const ASStackLayoutSpecStyle &style, const CGFloat flexFactorSum) { @@ -155,15 +156,15 @@ static inline CGFloat scaledFlexShrinkFactor(const ASStackUnpositionedItem &item @param flexFactorSum The sum of each item's flex factor as determined by the provided violation. @return A lambda capable of computing the flex shrink adjustment, if any, for a particular item. */ -static std::function flexShrinkAdjustment(const std::vector &items, +static std::function flexShrinkAdjustment(const std::vector &items, const ASStackLayoutSpecStyle &style, const CGFloat violation, const CGFloat flexFactorSum) { - const CGFloat scaledFlexShrinkFactorSum = std::accumulate(items.begin(), items.end(), 0.0, [&](CGFloat x, const ASStackUnpositionedItem &item) { + const CGFloat scaledFlexShrinkFactorSum = std::accumulate(items.begin(), items.end(), 0.0, [&](CGFloat x, const ASStackLayoutSpecItem &item) { return x + scaledFlexShrinkFactor(item, style, flexFactorSum); }); - return [style, scaledFlexShrinkFactorSum, violation, flexFactorSum](const ASStackUnpositionedItem &item) { + return [style, scaledFlexShrinkFactorSum, violation, flexFactorSum](const ASStackLayoutSpecItem &item) { const CGFloat scaledFlexShrinkFactorRatio = scaledFlexShrinkFactor(item, style, flexFactorSum) / scaledFlexShrinkFactorSum; // The item should shrink proportionally to the scaled flex shrink factor ratio computed above. // Unlike the flex grow adjustment the flex shrink adjustment needs to take the size of each item into account. @@ -178,12 +179,12 @@ static std::function flexShrinkAdjustm @param flexFactorSum The sum of each item's flex factor as determined by the provided violation. @return A lambda capable of computing the flex grow adjustment, if any, for a particular item. */ -static std::function flexGrowAdjustment(const std::vector &items, +static std::function flexGrowAdjustment(const std::vector &items, const CGFloat violation, const CGFloat flexFactorSum) { // To compute the flex grow adjustment distribute the violation proportionally based on each item's flex grow factor. - return [violation, flexFactorSum](const ASStackUnpositionedItem &item) { + return [violation, flexFactorSum](const ASStackLayoutSpecItem &item) { return std::floor(violation * (item.child.style.flexGrow / flexFactorSum)); }; } @@ -196,7 +197,7 @@ static std::function flexGrowAdjustmen @param flexFactorSum The sum of each item's flex factor as determined by the provided violation. @return A lambda capable of computing the flex adjustment for a particular item. */ -static std::function flexAdjustmentInViolationDirection(const std::vector &items, +static std::function flexAdjustmentInViolationDirection(const std::vector &items, const ASStackLayoutSpecStyle &style, const CGFloat violation, const CGFloat flexFactorSum) @@ -208,7 +209,7 @@ static std::function flexAdjustmentInV } } -ASDISPLAYNODE_INLINE BOOL isFlexibleInBothDirections(id child) +ASDISPLAYNODE_INLINE BOOL isFlexibleInBothDirections(const ASStackLayoutSpecChild &child) { return child.style.flexGrow > 0 && child.style.flexShrink > 0; } @@ -217,15 +218,14 @@ ASDISPLAYNODE_INLINE BOOL isFlexibleInBothDirections(id child) The flexible children may have been left not laid out in the initial layout pass, so we may have to go through and size these children at zero size so that the children layouts are at least present. */ -static void layoutFlexibleChildrenAtZeroSize(std::vector &items, +static void layoutFlexibleChildrenAtZeroSize(std::vector &items, const ASStackLayoutSpecStyle &style, const ASSizeRange &sizeRange, const CGSize size) { - for (ASStackUnpositionedItem &item : items) { - const id child = item.child; - if (isFlexibleInBothDirections(child)) { - item.layout = crossChildLayout(child, + for (ASStackLayoutSpecItem &item : items) { + if (isFlexibleInBothDirections(item.child)) { + item.layout = crossChildLayout(item.child, style, 0, 0, @@ -250,21 +250,20 @@ static void layoutFlexibleChildrenAtZeroSize(std::vector &children, +static CGFloat computeStackDimensionSum(const std::vector &children, const ASStackLayoutSpecStyle &style) { // Sum up the childrens' spacing const CGFloat childSpacingSum = std::accumulate(children.begin(), children.end(), // Start from default spacing between each child: children.empty() ? 0 : style.spacing * (children.size() - 1), - [&](CGFloat x, const ASStackUnpositionedItem &l) { - const id child = l.child; - return x + child.style.spacingBefore + child.style.spacingAfter; + [&](CGFloat x, const ASStackLayoutSpecItem &l) { + return x + l.child.style.spacingBefore + l.child.style.spacingAfter; }); // Sum up the childrens' dimensions (including spacing) in the stack direction. const CGFloat childStackDimensionSum = std::accumulate(children.begin(), children.end(), childSpacingSum, - [&](CGFloat x, const ASStackUnpositionedItem &l) { + [&](CGFloat x, const ASStackLayoutSpecItem &l) { return x + stackDimension(style.direction, l.layout.size); }); return childStackDimensionSum; @@ -319,7 +318,7 @@ static CGFloat computeViolation(const CGFloat stackDimensionSum, If we have a single flexible (both shrinkable and growable) child, and our allowed size range is set to a specific number then we may avoid the first "intrinsic" size calculation. */ -ASDISPLAYNODE_INLINE BOOL useOptimizedFlexing(const std::vector> &children, +ASDISPLAYNODE_INLINE BOOL useOptimizedFlexing(const std::vector &children, const ASStackLayoutSpecStyle &style, const ASSizeRange &sizeRange) { @@ -342,17 +341,17 @@ ASDISPLAYNODE_INLINE BOOL useOptimizedFlexing(const std::vector &items, +static void flexChildrenAlongStackDimension(std::vector &items, const ASStackLayoutSpecStyle &style, const ASSizeRange &sizeRange, const CGSize size, const BOOL useOptimizedFlexing) { const CGFloat violation = computeViolation(computeStackDimensionSum(items, style), style, sizeRange); - std::function flexFactor = flexFactorInViolationDirection(violation); + std::function flexFactor = flexFactorInViolationDirection(violation); // The flex factor sum is needed to determine if flexing is necessary. // This value is also needed if the violation is positive and flexible children need to grow, so keep it around. - const CGFloat flexFactorSum = std::accumulate(items.begin(), items.end(), 0.0, [&](CGFloat x, const ASStackUnpositionedItem &item) { + const CGFloat flexFactorSum = std::accumulate(items.begin(), items.end(), 0.0, [&](CGFloat x, const ASStackLayoutSpecItem &item) { return x + flexFactor(item); }); // If no children are able to flex then there is nothing left to do. Bail. @@ -363,17 +362,17 @@ static void flexChildrenAlongStackDimension(std::vector } return; } - std::function flexAdjustment = flexAdjustmentInViolationDirection(items, + std::function flexAdjustment = flexAdjustmentInViolationDirection(items, style, violation, flexFactorSum); // Compute any remaining violation to the first flexible child. - const CGFloat remainingViolation = std::accumulate(items.begin(), items.end(), violation, [&](CGFloat x, const ASStackUnpositionedItem &item) { + const CGFloat remainingViolation = std::accumulate(items.begin(), items.end(), violation, [&](CGFloat x, const ASStackLayoutSpecItem &item) { return x - flexAdjustment(item); }); BOOL isFirstFlex = YES; - for (ASStackUnpositionedItem &item : items) { + for (ASStackLayoutSpecItem &item : items) { const CGFloat currentFlexAdjustment = flexAdjustment(item); // Children are consider inflexible if they do not need to make a flex adjustment. if (currentFlexAdjustment != 0) { @@ -396,7 +395,7 @@ static void flexChildrenAlongStackDimension(std::vector Performs the first unconstrained layout of the children, generating the unpositioned items that are then flexed and stretched. */ -static std::vector layoutChildrenAlongUnconstrainedStackDimension(const std::vector> &children, +static std::vector layoutChildrenAlongUnconstrainedStackDimension(const std::vector &children, const ASStackLayoutSpecStyle &style, const ASSizeRange &sizeRange, const CGSize size, @@ -404,9 +403,9 @@ static std::vector layoutChildrenAlongUnconstrainedStac { const CGFloat minCrossDimension = crossDimension(style.direction, sizeRange.min); const CGFloat maxCrossDimension = crossDimension(style.direction, sizeRange.max); - return AS::map(children, [&](id child) -> ASStackUnpositionedItem { + return AS::map(children, [&](const ASStackLayoutSpecChild &child) -> ASStackLayoutSpecItem { if (useOptimizedFlexing && isFlexibleInBothDirections(child)) { - return { child, [ASLayout layoutWithLayoutElement:child size:{0, 0}] }; + return {child, [ASLayout layoutWithLayoutElement:child.element size:{0, 0}]}; } else { return { child, @@ -422,7 +421,7 @@ static std::vector layoutChildrenAlongUnconstrainedStac }); } -ASStackUnpositionedLayout ASStackUnpositionedLayout::compute(const std::vector> &children, +ASStackUnpositionedLayout ASStackUnpositionedLayout::compute(const std::vector &children, const ASStackLayoutSpecStyle &style, const ASSizeRange &sizeRange) { @@ -439,7 +438,7 @@ ASStackUnpositionedLayout ASStackUnpositionedLayout::compute(const std::vector items = layoutChildrenAlongUnconstrainedStackDimension(children, + std::vector items = layoutChildrenAlongUnconstrainedStackDimension(children, style, sizeRange, size, @@ -449,5 +448,5 @@ ASStackUnpositionedLayout ASStackUnpositionedLayout::compute(const std::vector