Merge pull request #1735 from levi/betterFlatten3

[ASLayout] Clean up flattening process of ASLayout
This commit is contained in:
appleguy 2016-06-11 23:15:16 -07:00 committed by GitHub
commit c211c76c64
8 changed files with 152 additions and 114 deletions

View File

@ -62,7 +62,9 @@ NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp = @"AS
@implementation ASDisplayNode
// these dynamic properties all defined in ASLayoutOptionsPrivate.m
@dynamic spacingAfter, spacingBefore, flexGrow, flexShrink, flexBasis, alignSelf, ascender, descender, sizeRange, layoutPosition;
@dynamic spacingAfter, spacingBefore, flexGrow, flexShrink, flexBasis,
alignSelf, ascender, descender, sizeRange, layoutPosition, layoutableType;
@synthesize name = _name;
@synthesize preferredFrameSize = _preferredFrameSize;
@synthesize isFinalLayoutable = _isFinalLayoutable;
@ -659,6 +661,11 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
return _layout == nil || _layout.isDirty;
}
- (ASLayoutableType)layoutableType
{
return ASLayoutableTypeDisplayNode;
}
#pragma mark - Layout Transition
- (void)transitionLayoutWithAnimation:(BOOL)animated
@ -1916,13 +1923,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
ASLayoutableValidateLayout(layout);
#endif
}
return [layout flattenedLayoutUsingPredicateBlock:^BOOL(ASLayout *evaluatedLayout) {
if (self.usesImplicitHierarchyManagement) {
return ASObjectIsEqual(layout, evaluatedLayout) == NO && [evaluatedLayout.layoutableObject isKindOfClass:[ASDisplayNode class]];
} else {
return [_subnodes containsObject:evaluatedLayout.layoutableObject];
}
}];
return [layout filteredNodeLayoutTree];
} else {
// If neither -layoutSpecThatFits: nor -calculateSizeThatFits: is overridden by subclassses, preferredFrameSize should be used,
// assume that the default implementation of -calculateSizeThatFits: returns it.
@ -2415,7 +2416,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
- (void)__layoutSublayouts
{
for (ASLayout *subnodeLayout in _layout.immediateSublayouts) {
for (ASLayout *subnodeLayout in _layout.sublayouts) {
((ASDisplayNode *)subnodeLayout.layoutableObject).frame = [subnodeLayout frame];
}
}

View File

@ -31,6 +31,11 @@ extern BOOL CGPointIsNull(CGPoint point);
*/
@property (nonatomic, weak, readonly) id<ASLayoutable> layoutableObject;
/**
* The type of ASLayoutable that created this layout
*/
@property (nonatomic, readonly) ASLayoutableType type;
/**
* Size of the current layout
*/
@ -53,38 +58,39 @@ extern BOOL CGPointIsNull(CGPoint point);
*/
@property (nonatomic, readonly) NSArray<ASLayout *> *sublayouts;
/**
* A list of sublayouts that were not already flattened.
*/
@property (nonatomic, readonly) NSArray<ASLayout *> *immediateSublayouts;
/**
* Mark the layout dirty for future regeneration.
*/
@property (nonatomic, getter=isDirty) BOOL dirty;
/**
* A boolean describing if the current layout has been flattened.
* @abstract Returns a valid frame for the current layout computed with the size and position.
* @discussion Clamps the layout's origin or position to 0 if any of the calculated values are infinite.
*/
@property (nonatomic, readonly, getter=isFlattened) BOOL flattened;
@property (nonatomic, readonly) CGRect frame;
/**
* Initializer.
* Designated initializer
*/
- (instancetype)initWithLayoutableObject:(id<ASLayoutable>)layoutableObject
constrainedSizeRange:(ASSizeRange)sizeRange
size:(CGSize)size
position:(CGPoint)position
sublayouts:(NSArray *)sublayouts NS_DESIGNATED_INITIALIZER;
/**
* Convenience class initializer for layout construction.
*
* @param layoutableObject The backing ASLayoutable object.
*
* @param size The size of this layout.
*
* @param position The position of this layout within its parent (if available).
*
* @param sublayouts Sublayouts belong to the new layout.
* @param size The size of this layout.
* @param position The position of this layout within its parent (if available).
* @param sublayouts Sublayouts belong to the new layout.
*/
+ (instancetype)layoutWithLayoutableObject:(id<ASLayoutable>)layoutableObject
constrainedSizeRange:(ASSizeRange)sizeRange
size:(CGSize)size
position:(CGPoint)position
sublayouts:(nullable NSArray<ASLayout *> *)sublayouts
flattened:(BOOL)flattened;
sublayouts:(nullable NSArray<ASLayout *> *)sublayouts;
/**
* Convenience initializer that has CGPointNull position.
@ -93,9 +99,7 @@ extern BOOL CGPointIsNull(CGPoint point);
* or for creating a sublayout of which the position is yet to be determined.
*
* @param layoutableObject The backing ASLayoutable object.
*
* @param size The size of this layout.
*
* @param sublayouts Sublayouts belong to the new layout.
*/
+ (instancetype)layoutWithLayoutableObject:(id<ASLayoutable>)layoutableObject
@ -109,7 +113,6 @@ extern BOOL CGPointIsNull(CGPoint point);
* or a sublayout of which the position is yet to be determined.
*
* @param layoutableObject The backing ASLayoutable object.
*
* @param size The size of this layout.
*/
+ (instancetype)layoutWithLayoutableObject:(id<ASLayoutable>)layoutableObject
@ -120,9 +123,7 @@ extern BOOL CGPointIsNull(CGPoint point);
* Convenience initializer that is flattened and has CGPointNull position.
*
* @param layoutableObject The backing ASLayoutable object.
*
* @param size The size of this layout.
*
* @param sublayouts Sublayouts belong to the new layout.
*/
+ (instancetype)flattenedLayoutWithLayoutableObject:(id<ASLayoutable>)layoutableObject
@ -131,22 +132,16 @@ extern BOOL CGPointIsNull(CGPoint point);
sublayouts:(nullable NSArray<ASLayout *> *)sublayouts;
/**
* @abstract Evaluates a given predicate block against each object in the receiving layout tree
* and returns a new, 1-level deep layout containing the objects for which the predicate block returns true.
*
* @param predicateBlock The block is applied to a layout to be evaluated.
* The block takes 1 argument: evaluatedLayout - the layout to be evaluated.
* The block returns YES if evaluatedLayout evaluates to true, otherwise NO.
*
* @return A new, 1-level deep layout containing the layouts for which the predicate block returns true.
* Convenience initializer that creates a layout based on the values of the given layout, with a new position
* @param layout The layout to use to create the new layout
* @param position The position of the new layout
*/
- (ASLayout *)flattenedLayoutUsingPredicateBlock:(BOOL (^)(ASLayout *evaluatedLayout))predicateBlock;
+ (instancetype)layoutWithLayout:(ASLayout *)layout position:(CGPoint)position;
/**
* @abstract Returns a valid frame for the current layout computed with the size and position.
* @discussion Clamps the layout's origin or position to 0 if any of the calculated values are infinite.
* Traverses the existing layout tree and generates a new tree that represents only ASDisplayNode layouts
*/
- (CGRect)frame;
- (ASLayout *)filteredNodeLayoutTree;
@end

View File

@ -9,10 +9,12 @@
*/
#import "ASLayout.h"
#import "ASAssert.h"
#import "ASLayoutSpecUtilities.h"
#import "ASInternalHelpers.h"
#import "ASDimension.h"
#import "ASInternalHelpers.h"
#import "ASLayoutSpecUtilities.h"
#import <queue>
CGPoint const CGPointNull = {NAN, NAN};
@ -22,25 +24,35 @@ extern BOOL CGPointIsNull(CGPoint point)
return isnan(point.x) && isnan(point.y);
}
@interface ASLayout ()
/**
* A boolean describing if the current layout has been flattened.
*/
@property (nonatomic, getter=isFlattened) BOOL flattened;
@end
@implementation ASLayout
+ (instancetype)layoutWithLayoutableObject:(id<ASLayoutable>)layoutableObject
constrainedSizeRange:(ASSizeRange)sizeRange
size:(CGSize)size
position:(CGPoint)position
sublayouts:(NSArray *)sublayouts
flattened:(BOOL)flattened
@dynamic frame, type;
- (instancetype)initWithLayoutableObject:(id<ASLayoutable>)layoutableObject
constrainedSizeRange:(ASSizeRange)sizeRange
size:(CGSize)size
position:(CGPoint)position
sublayouts:(NSArray *)sublayouts
{
ASDisplayNodeAssert(layoutableObject, @"layoutableObject is required.");
self = [super init];
if (self) {
NSParameterAssert(layoutableObject);
#if DEBUG
for (ASLayout *sublayout in sublayouts) {
ASDisplayNodeAssert(!CGPointIsNull(sublayout.position), @"Invalid position is not allowed in sublayout.");
}
for (ASLayout *sublayout in sublayouts) {
ASDisplayNodeAssert(CGPointIsNull(sublayout.position) == NO, @"Invalid position is not allowed in sublayout.");
}
#endif
ASLayout *l = [super new];
if (l) {
l->_layoutableObject = layoutableObject;
_layoutableObject = layoutableObject;
if (!isValidForLayout(size.width) || !isValidForLayout(size.height)) {
ASDisplayNodeAssert(NO, @"layoutSize is invalid and unsafe to provide to Core Animation! Production will force to 0, 0. Size = %@, node = %@", NSStringFromCGSize(size), layoutableObject);
@ -48,27 +60,40 @@ extern BOOL CGPointIsNull(CGPoint point)
} else {
size = CGSizeMake(ASCeilPixelValue(size.width), ASCeilPixelValue(size.height));
}
l->_constrainedSizeRange = sizeRange;
l->_size = size;
l->_dirty = NO;
_constrainedSizeRange = sizeRange;
_size = size;
_dirty = NO;
if (CGPointIsNull(position) == NO) {
l->_position = CGPointMake(ASCeilPixelValue(position.x), ASCeilPixelValue(position.y));
_position = CGPointMake(ASCeilPixelValue(position.x), ASCeilPixelValue(position.y));
} else {
l->_position = position;
_position = position;
}
l->_sublayouts = [sublayouts copy];
l->_flattened = flattened;
NSMutableArray<ASLayout *> *result = [NSMutableArray array];
for (ASLayout *sublayout in l->_sublayouts) {
if (!sublayout.isFlattened) {
[result addObject:sublayout];
}
}
l->_immediateSublayouts = result;
_sublayouts = sublayouts != nil ? [sublayouts copy] : @[];
_flattened = NO;
}
return l;
return self;
}
- (instancetype)init
{
ASDisplayNodeAssert(NO, @"Use the designated initializer");
return [self init];
}
#pragma mark - Class Constructors
+ (instancetype)layoutWithLayoutableObject:(id<ASLayoutable>)layoutableObject
constrainedSizeRange:(ASSizeRange)sizeRange
size:(CGSize)size
position:(CGPoint)position
sublayouts:(NSArray *)sublayouts
{
return [[self alloc] initWithLayoutableObject:layoutableObject
constrainedSizeRange:sizeRange
size:size
position:position
sublayouts:sublayouts];
}
+ (instancetype)layoutWithLayoutableObject:(id<ASLayoutable>)layoutableObject
@ -80,8 +105,7 @@ extern BOOL CGPointIsNull(CGPoint point)
constrainedSizeRange:sizeRange
size:size
position:CGPointNull
sublayouts:sublayouts
flattened:NO];
sublayouts:sublayouts];
}
+ (instancetype)layoutWithLayoutableObject:(id<ASLayoutable>)layoutableObject
@ -103,53 +127,61 @@ extern BOOL CGPointIsNull(CGPoint point)
constrainedSizeRange:sizeRange
size:size
position:CGPointNull
sublayouts:sublayouts
flattened:YES];
sublayouts:sublayouts];
}
- (ASLayout *)flattenedLayoutUsingPredicateBlock:(BOOL (^)(ASLayout *))predicateBlock
+ (instancetype)layoutWithLayout:(ASLayout *)layout position:(CGPoint)position
{
return [self layoutWithLayoutableObject:layout.layoutableObject
constrainedSizeRange:layout.constrainedSizeRange
size:layout.size
position:position
sublayouts:layout.sublayouts];
}
#pragma mark - Layout Flattening
- (ASLayout *)filteredNodeLayoutTree
{
NSMutableArray *flattenedSublayouts = [NSMutableArray array];
struct Context {
ASLayout *layout;
CGPoint absolutePosition;
BOOL visited;
BOOL flattened;
};
// Queue used to keep track of sublayouts while traversing this layout in a BFS fashion.
std::queue<Context> queue;
queue.push({self, CGPointMake(0, 0), NO, NO});
queue.push({self, CGPointMake(0, 0)});
while (!queue.empty()) {
Context &context = queue.front();
if (context.visited) {
queue.pop();
} else {
context.visited = YES;
if (predicateBlock(context.layout)) {
[flattenedSublayouts addObject:[ASLayout layoutWithLayoutableObject:context.layout.layoutableObject
constrainedSizeRange:context.layout.constrainedSizeRange
size:context.layout.size
position:context.absolutePosition
sublayouts:nil
flattened:context.flattened]];
}
for (ASLayout *sublayout in context.layout.sublayouts) {
// Mark layout trees that have already been flattened for future identification of immediate sublayouts
BOOL flattened = context.flattened ? : context.layout.flattened;
queue.push({sublayout, context.absolutePosition + sublayout.position, NO, flattened});
Context context = queue.front();
queue.pop();
if (self != context.layout && context.layout.type == ASLayoutableTypeDisplayNode) {
ASLayout *layout = [ASLayout layoutWithLayout:context.layout position:context.absolutePosition];
layout.flattened = YES;
[flattenedSublayouts addObject:layout];
}
for (ASLayout *sublayout in context.layout.sublayouts) {
if (sublayout.isFlattened == NO) {
queue.push({sublayout, context.absolutePosition + sublayout.position});
}
}
}
return [ASLayout flattenedLayoutWithLayoutableObject:_layoutableObject
constrainedSizeRange:_constrainedSizeRange
size:_size
sublayouts:flattenedSublayouts];
return [ASLayout layoutWithLayoutableObject:_layoutableObject
constrainedSizeRange:_constrainedSizeRange
size:_size
sublayouts:flattenedSublayouts];
}
#pragma mark - Accessors
- (ASLayoutableType)type
{
return _layoutableObject.layoutableType;
}
- (CGRect)frame

View File

@ -34,7 +34,8 @@
@implementation ASLayoutSpec
// these dynamic properties all defined in ASLayoutOptionsPrivate.m
@dynamic spacingAfter, spacingBefore, flexGrow, flexShrink, flexBasis, alignSelf, ascender, descender, sizeRange, layoutPosition;
@dynamic spacingAfter, spacingBefore, flexGrow, flexShrink, flexBasis,
alignSelf, ascender, descender, sizeRange, layoutPosition, layoutableType;
@synthesize isFinalLayoutable = _isFinalLayoutable;
- (instancetype)init
@ -48,6 +49,11 @@
return self;
}
- (ASLayoutableType)layoutableType
{
return ASLayoutableTypeLayoutSpec;
}
#pragma mark - Layout
- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize

View File

@ -21,6 +21,11 @@
@class ASLayout;
@class ASLayoutSpec;
typedef NS_ENUM(NSUInteger, ASLayoutableType) {
ASLayoutableTypeLayoutSpec,
ASLayoutableTypeDisplayNode
};
NS_ASSUME_NONNULL_BEGIN
/**
@ -41,6 +46,8 @@ NS_ASSUME_NONNULL_BEGIN
*/
@protocol ASLayoutable <ASEnvironment, ASStackLayoutable, ASStaticLayoutable, ASLayoutablePrivate, ASLayoutableExtensibility>
@property (nonatomic, readonly) ASLayoutableType layoutableType;
/**
* @abstract Calculate a layout based on given size range.
*
@ -50,7 +57,6 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize;
#pragma mark - Layout options from the Layoutable Protocols
#pragma mark - ASStackLayoutable

View File

@ -67,7 +67,7 @@
}
if (_previousLayout) {
NSIndexSet *insertions, *deletions;
[_previousLayout.immediateSublayouts asdk_diffWithArray:_pendingLayout.immediateSublayouts
[_previousLayout.sublayouts asdk_diffWithArray:_pendingLayout.sublayouts
insertions:&insertions
deletions:&deletions
compareBlock:^BOOL(ASLayout *lhs, ASLayout *rhs) {
@ -80,7 +80,7 @@
&_removedSubnodes,
&_removedSubnodePositions);
} else {
NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [_pendingLayout.immediateSublayouts count])];
NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [_pendingLayout.sublayouts count])];
findNodesInLayoutAtIndexes(_pendingLayout, indexes, &_insertedSubnodes, &_insertedSubnodePositions);
_removedSubnodes = nil;
}
@ -160,7 +160,7 @@ static inline void findNodesInLayoutAtIndexesWithFilteredNodes(ASLayout *layout,
std::vector<NSUInteger> positions = std::vector<NSUInteger>();
NSUInteger idx = [indexes firstIndex];
while (idx != NSNotFound) {
ASDisplayNode *node = (ASDisplayNode *)layout.immediateSublayouts[idx].layoutableObject;
ASDisplayNode *node = (ASDisplayNode *)layout.sublayouts[idx].layoutableObject;
ASDisplayNodeCAssert(node, @"A flattened layout must consist exclusively of node sublayouts");
// Ignore the odd case in which a non-node sublayout is accessed and the type cast fails
if (node != nil) {

View File

@ -71,7 +71,7 @@ NSString * const ASTransitionContextToLayoutKey = @"org.asyncdisplaykit.ASTransi
- (NSArray<ASDisplayNode *> *)subnodesForKey:(NSString *)key
{
NSMutableArray<ASDisplayNode *> *subnodes = [NSMutableArray array];
for (ASLayout *sublayout in [self layoutForKey:key].immediateSublayouts) {
for (ASLayout *sublayout in [self layoutForKey:key].sublayouts) {
[subnodes addObject:(ASDisplayNode *)sublayout.layoutableObject];
}
return subnodes;

View File

@ -65,9 +65,7 @@
constrainedSizeRange:sizeRange
size:layout.size
sublayouts:@[layout]];
_layoutUnderTest = [layout flattenedLayoutUsingPredicateBlock:^BOOL(ASLayout *evaluatedLayout) {
return [self.subnodes containsObject:(ASDisplayNode *)evaluatedLayout.layoutableObject];
}];
_layoutUnderTest = [layout filteredNodeLayoutTree];
self.frame = CGRectMake(0, 0, _layoutUnderTest.size.width, _layoutUnderTest.size.height);
[self measure:_layoutUnderTest.size];
}