ASStackLayoutSpec utilizes multiple threads if it runs on main (#3116)

This commit is contained in:
Huy Nguyen 2017-03-02 18:12:53 +00:00 committed by Adlai Holler
parent bccfc6c779
commit 415005b6fc

View File

@ -13,8 +13,10 @@
#import <tgmath.h> #import <tgmath.h>
#import <numeric> #import <numeric>
#import <AsyncDisplayKit/ASDispatch.h>
#import <AsyncDisplayKit/ASLayoutSpecUtilities.h> #import <AsyncDisplayKit/ASLayoutSpecUtilities.h>
#import <AsyncDisplayKit/ASLayoutElementStylePrivate.h> #import <AsyncDisplayKit/ASLayoutElementStylePrivate.h>
#import <AsyncDisplayKit/ASThread.h>
CGFloat const kViolationEpsilon = 0.01; CGFloat const kViolationEpsilon = 0.01;
@ -67,6 +69,28 @@ static ASLayout *crossChildLayout(const ASStackLayoutSpecChild &child,
return layout ? : [ASLayout layoutWithLayoutElement:child.element size:{0, 0}]; return layout ? : [ASLayout layoutWithLayoutElement:child.element size:{0, 0}];
} }
static void dispatchApplyIfNeeded(size_t iterationCount, void(^work)(size_t i))
{
if (iterationCount == 0) {
return;
}
if (iterationCount == 1) {
work(0);
return;
}
if (ASDisplayNodeThreadIsMain() == NO) {
for (size_t i = 0; i < iterationCount; i++) {
work(i);
}
return;
}
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
ASDispatchApply(iterationCount, queue, 0, work);
}
/** /**
Computes the consumed cross dimension length for the given vector of lines and stacking style. Computes the consumed cross dimension length for the given vector of lines and stacking style.
@ -175,21 +199,20 @@ static void stretchItemsAlongCrossDimension(std::vector<ASStackLayoutSpecItem> &
const CGSize parentSize, const CGSize parentSize,
const CGFloat crossSize) const CGFloat crossSize)
{ {
for (auto &item : items) { dispatchApplyIfNeeded(items.size(), ^(size_t i) {
auto &item = items[i];
const ASStackLayoutAlignItems alignItems = alignment(item.child.style.alignSelf, style.alignItems); const ASStackLayoutAlignItems alignItems = alignment(item.child.style.alignSelf, style.alignItems);
if (alignItems != ASStackLayoutAlignItemsStretch) { if (alignItems == ASStackLayoutAlignItemsStretch) {
continue; const CGFloat cross = crossDimension(style.direction, item.layout.size);
const CGFloat stack = stackDimension(style.direction, item.layout.size);
const CGFloat violation = crossSize - cross;
// Only stretch if violation is positive. Compare against kViolationEpsilon here to avoid stretching against a tiny violation.
if (violation > kViolationEpsilon) {
item.layout = crossChildLayout(item.child, style, stack, stack, crossSize, crossSize, parentSize);
}
} }
});
const CGFloat cross = crossDimension(style.direction, item.layout.size);
const CGFloat stack = stackDimension(style.direction, item.layout.size);
const CGFloat violation = crossSize - cross;
// Only stretch if violation is positive. Compare against kViolationEpsilon here to avoid stretching against a tiny violation.
if (violation > kViolationEpsilon) {
item.layout = crossChildLayout(item.child, style, stack, stack, crossSize, crossSize, parentSize);
}
}
} }
/** /**
@ -405,7 +428,8 @@ static void layoutFlexibleChildrenAtZeroSize(std::vector<ASStackLayoutSpecItem>
const ASSizeRange &sizeRange, const ASSizeRange &sizeRange,
const CGSize parentSize) const CGSize parentSize)
{ {
for (ASStackLayoutSpecItem &item : items) { dispatchApplyIfNeeded(items.size(), ^(size_t i) {
auto &item = items[i];
if (isFlexibleInBothDirections(item.child)) { if (isFlexibleInBothDirections(item.child)) {
item.layout = crossChildLayout(item.child, item.layout = crossChildLayout(item.child,
style, style,
@ -415,7 +439,7 @@ static void layoutFlexibleChildrenAtZeroSize(std::vector<ASStackLayoutSpecItem>
crossDimension(style.direction, sizeRange.max), crossDimension(style.direction, sizeRange.max),
parentSize); parentSize);
} }
} });
} }
/** /**
@ -556,14 +580,25 @@ static void flexLinesAlongStackDimension(std::vector<ASStackUnpositionedLine> &l
const CGFloat remainingViolation = std::accumulate(items.begin(), items.end(), violation, [&](CGFloat x, const ASStackLayoutSpecItem &item) { const CGFloat remainingViolation = std::accumulate(items.begin(), items.end(), violation, [&](CGFloat x, const ASStackLayoutSpecItem &item) {
return x - flexAdjustment(item); return x - flexAdjustment(item);
}); });
BOOL isFirstFlex = YES;
for (ASStackLayoutSpecItem &item : items) { size_t firstFlexItem = -1;
for(size_t i = 0; i < items.size(); i++) {
// Children are consider inflexible if they do not need to make a flex adjustment.
if (flexAdjustment(items[i]) != 0) {
firstFlexItem = i;
break;
}
}
ASDisplayNodeCAssert(firstFlexItem != -1, @"At this point there must be at least 1 flexible item");
dispatchApplyIfNeeded(items.size(), ^(size_t i) {
auto &item = items[i];
const CGFloat currentFlexAdjustment = flexAdjustment(item); const CGFloat currentFlexAdjustment = flexAdjustment(item);
// Children are consider inflexible if they do not need to make a flex adjustment. // Children are consider inflexible if they do not need to make a flex adjustment.
if (currentFlexAdjustment != 0) { if (currentFlexAdjustment != 0) {
const CGFloat originalStackSize = stackDimension(style.direction, item.layout.size); const CGFloat originalStackSize = stackDimension(style.direction, item.layout.size);
// Only apply the remaining violation for the first flexible child that has a flex grow factor. // Only apply the remaining violation for the first flexible child that has a flex grow factor.
const CGFloat flexedStackSize = originalStackSize + currentFlexAdjustment + (isFirstFlex && item.child.style.flexGrow > 0 ? remainingViolation : 0); const CGFloat flexedStackSize = originalStackSize + currentFlexAdjustment + (i == firstFlexItem && item.child.style.flexGrow > 0 ? remainingViolation : 0);
item.layout = crossChildLayout(item.child, item.layout = crossChildLayout(item.child,
style, style,
MAX(flexedStackSize, 0), MAX(flexedStackSize, 0),
@ -571,9 +606,8 @@ static void flexLinesAlongStackDimension(std::vector<ASStackUnpositionedLine> &l
crossDimension(style.direction, sizeRange.min), crossDimension(style.direction, sizeRange.min),
crossDimension(style.direction, sizeRange.max), crossDimension(style.direction, sizeRange.max),
parentSize); parentSize);
isFirstFlex = NO;
} }
} });
} }
} }
@ -619,28 +653,27 @@ static std::vector<ASStackUnpositionedLine> collectChildrenIntoLines(const std::
Performs the first unconstrained layout of the children, generating the unpositioned items that are then flexed and Performs the first unconstrained layout of the children, generating the unpositioned items that are then flexed and
stretched. stretched.
*/ */
static std::vector<ASStackLayoutSpecItem> layoutChildrenAlongUnconstrainedStackDimension(const std::vector<ASStackLayoutSpecChild> &children, static void layoutItemsAlongUnconstrainedStackDimension(std::vector<ASStackLayoutSpecItem> &items,
const ASStackLayoutSpecStyle &style, const ASStackLayoutSpecStyle &style,
const ASSizeRange &sizeRange, const ASSizeRange &sizeRange,
const CGSize parentSize, const CGSize parentSize,
const BOOL useOptimizedFlexing) const BOOL useOptimizedFlexing)
{ {
const CGFloat minCrossDimension = crossDimension(style.direction, sizeRange.min); const CGFloat minCrossDimension = crossDimension(style.direction, sizeRange.min);
const CGFloat maxCrossDimension = crossDimension(style.direction, sizeRange.max); const CGFloat maxCrossDimension = crossDimension(style.direction, sizeRange.max);
return AS::map(children, [&](const ASStackLayoutSpecChild &child) -> ASStackLayoutSpecItem {
if (useOptimizedFlexing && isFlexibleInBothDirections(child)) { dispatchApplyIfNeeded(items.size(), ^(size_t i) {
return {child, [ASLayout layoutWithLayoutElement:child.element size:{0, 0}]}; auto &item = items[i];
if (useOptimizedFlexing && isFlexibleInBothDirections(item.child)) {
item.layout = [ASLayout layoutWithLayoutElement:item.child.element size:{0, 0}];
} else { } else {
return { item.layout = crossChildLayout(item.child,
child, style,
crossChildLayout(child, ASDimensionResolve(item.child.style.flexBasis, stackDimension(style.direction, parentSize), 0),
style, ASDimensionResolve(item.child.style.flexBasis, stackDimension(style.direction, parentSize), INFINITY),
ASDimensionResolve(child.style.flexBasis, stackDimension(style.direction, parentSize), 0), minCrossDimension,
ASDimensionResolve(child.style.flexBasis, stackDimension(style.direction, parentSize), INFINITY), maxCrossDimension,
minCrossDimension, parentSize);
maxCrossDimension,
parentSize)
};
} }
}); });
} }
@ -663,14 +696,18 @@ ASStackUnpositionedLayout ASStackUnpositionedLayout::compute(const std::vector<A
// We may be able to avoid some redundant layout passes // We may be able to avoid some redundant layout passes
const BOOL optimizedFlexing = useOptimizedFlexing(children, style, sizeRange); const BOOL optimizedFlexing = useOptimizedFlexing(children, style, sizeRange);
std::vector<ASStackLayoutSpecItem> items = AS::map(children, [&](const ASStackLayoutSpecChild &child) -> ASStackLayoutSpecItem {
return {child, nil};
});
// We do a first pass of all the children, generating an unpositioned layout for each with an unbounded range along // We do a first pass of all the children, generating an unpositioned layout for each with an unbounded range along
// the stack dimension. This allows us to compute the "intrinsic" size of each child and find the available violation // the stack dimension. This allows us to compute the "intrinsic" size of each child and find the available violation
// which determines whether we must grow or shrink the flexible children. // which determines whether we must grow or shrink the flexible children.
std::vector<ASStackLayoutSpecItem> items = layoutChildrenAlongUnconstrainedStackDimension(children, layoutItemsAlongUnconstrainedStackDimension(items,
style, style,
sizeRange, sizeRange,
parentSize, parentSize,
optimizedFlexing); optimizedFlexing);
// Collect items into lines (https://www.w3.org/TR/css-flexbox-1/#algo-line-break) // Collect items into lines (https://www.w3.org/TR/css-flexbox-1/#algo-line-break)
std::vector<ASStackUnpositionedLine> lines = collectChildrenIntoLines(items, style, sizeRange); std::vector<ASStackUnpositionedLine> lines = collectChildrenIntoLines(items, style, sizeRange);