Skip to content

Commit

Permalink
Improve separation of code for layout method types (#1305)
Browse files Browse the repository at this point in the history
* Improve separation of code for layout method types

* Address PR comments

- Delegate to layout spec engine if the node is a layout spec node but yoga engine was asked for calculate the layout
- Change ASLayoutType to ASLayoutEngineType
- Improve layout engine fall through code
  • Loading branch information
maicki committed Jan 28, 2019
1 parent f180138 commit d4efe95
Show file tree
Hide file tree
Showing 16 changed files with 440 additions and 281 deletions.
12 changes: 12 additions & 0 deletions AsyncDisplayKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,9 @@
9C8898BD1C738BB800D6B02E /* ASTextKitFontSizeAdjuster.h in Headers */ = {isa = PBXBuildFile; fileRef = A32FEDD31C501B6A004F642A /* ASTextKitFontSizeAdjuster.h */; settings = {ATTRIBUTES = (Private, ); }; };
9CC606651D24DF9E006581A0 /* NSIndexSet+ASHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC4981BB1D1C7F65004E13CC /* NSIndexSet+ASHelpers.mm */; };
9CDC18CD1B910E12004965E2 /* ASLayoutElementPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 9CDC18CB1B910E12004965E2 /* ASLayoutElementPrivate.h */; settings = {ATTRIBUTES = (Public, ); }; };
9D9AA56921E23EE200172C09 /* ASDisplayNode+LayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9D9AA56721E23EE200172C09 /* ASDisplayNode+LayoutSpec.mm */; };
9D9AA56B21E254B800172C09 /* ASDisplayNode+Yoga.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D9AA56A21E254B800172C09 /* ASDisplayNode+Yoga.h */; };
9D9AA56D21E2568500172C09 /* ASDisplayNode+LayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D9AA56C21E2568500172C09 /* ASDisplayNode+LayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; };
9F06E5CD1B4CAF4200F015D8 /* ASCollectionViewTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.mm */; };
9F98C0261DBE29E000476D92 /* ASControlTargetAction.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9F98C0241DBDF2A300476D92 /* ASControlTargetAction.mm */; };
9F98C0271DBE29FC00476D92 /* ASControlTargetAction.h in Headers */ = {isa = PBXBuildFile; fileRef = 9F98C0231DBDF2A300476D92 /* ASControlTargetAction.h */; settings = {ATTRIBUTES = (Private, ); }; };
Expand Down Expand Up @@ -774,6 +777,9 @@
9CDC18CB1B910E12004965E2 /* ASLayoutElementPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutElementPrivate.h; sourceTree = "<group>"; };
9CFFC6BF1CCAC73C006A6476 /* ASViewController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASViewController.mm; sourceTree = "<group>"; };
9CFFC6C11CCAC768006A6476 /* ASTableNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTableNode.mm; sourceTree = "<group>"; };
9D9AA56721E23EE200172C09 /* ASDisplayNode+LayoutSpec.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASDisplayNode+LayoutSpec.mm"; sourceTree = "<group>"; };
9D9AA56A21E254B800172C09 /* ASDisplayNode+Yoga.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+Yoga.h"; sourceTree = "<group>"; };
9D9AA56C21E2568500172C09 /* ASDisplayNode+LayoutSpec.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+LayoutSpec.h"; sourceTree = "<group>"; };
9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASCollectionViewTests.mm; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
9F98C0231DBDF2A300476D92 /* ASControlTargetAction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASControlTargetAction.h; sourceTree = "<group>"; };
9F98C0241DBDF2A300476D92 /* ASControlTargetAction.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASControlTargetAction.mm; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1209,6 +1215,9 @@
683F563620E409D600CEB7A3 /* ASDisplayNode+InterfaceState.h */,
69BCE3D71EC6513B007DCCAD /* ASDisplayNode+Layout.mm */,
058D09DA195D050800B7D73C /* ASDisplayNode+Subclasses.h */,
9D9AA56C21E2568500172C09 /* ASDisplayNode+LayoutSpec.h */,
9D9AA56721E23EE200172C09 /* ASDisplayNode+LayoutSpec.mm */,
9D9AA56A21E254B800172C09 /* ASDisplayNode+Yoga.h */,
90FC784E1E4BFE1B00383C5A /* ASDisplayNode+Yoga.mm */,
058D09DB195D050800B7D73C /* ASDisplayNodeExtras.h */,
058D09DC195D050800B7D73C /* ASDisplayNodeExtras.mm */,
Expand Down Expand Up @@ -1887,6 +1896,7 @@
CCBDDD0520C62A2D00CBA922 /* ASMainThreadDeallocation.h in Headers */,
CCAA0B7F206ADBF30057B336 /* ASRecursiveUnfairLock.h in Headers */,
E5E281741E71C833006B67C2 /* ASCollectionLayoutState.h in Headers */,
9D9AA56D21E2568500172C09 /* ASDisplayNode+LayoutSpec.h in Headers */,
E5B077FF1E69F4EB00C24B5B /* ASElementMap.h in Headers */,
CCCCCCE31EC3EF060087FE10 /* NSParagraphStyle+ASText.h in Headers */,
E58E9E441E941D74004CFC59 /* ASCollectionLayoutContext.h in Headers */,
Expand Down Expand Up @@ -2018,6 +2028,7 @@
B35062531B010EFD0018CF92 /* ASImageNode+CGExtras.h in Headers */,
E58E9E491E941DA5004CFC59 /* ASCollectionLayout.h in Headers */,
254C6B7F1BF94DF4003EC431 /* ASTextKitTruncating.h in Headers */,
9D9AA56B21E254B800172C09 /* ASDisplayNode+Yoga.h in Headers */,
CC58AA4B1E398E1D002C8CB4 /* ASBlockTypes.h in Headers */,
CCA282BC1E9EABDD0037E8B7 /* ASTipProvider.h in Headers */,
6977965F1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.h in Headers */,
Expand Down Expand Up @@ -2395,6 +2406,7 @@
CCDC9B4E200991D10063C1F8 /* ASGraphicsContext.mm in Sources */,
CCCCCCD81EC3EF060087FE10 /* ASTextInput.mm in Sources */,
34EFC7621B701CA400AD841F /* ASBackgroundLayoutSpec.mm in Sources */,
9D9AA56921E23EE200172C09 /* ASDisplayNode+LayoutSpec.mm in Sources */,
DE8BEAC41C2DF3FC00D57C12 /* ASDelegateProxy.mm in Sources */,
B35062141B010EFD0018CF92 /* ASBasicImageDownloader.mm in Sources */,
B35062161B010EFD0018CF92 /* ASBatchContext.mm in Sources */,
Expand Down
1 change: 0 additions & 1 deletion Source/ASCellNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
#import <AsyncDisplayKit/ASTableView+Undeprecated.h>
#import <AsyncDisplayKit/_ASDisplayView.h>
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
#import <AsyncDisplayKit/ASTextNode.h>
#import <AsyncDisplayKit/ASCollectionNode.h>
#import <AsyncDisplayKit/ASTableNode.h>
Expand Down
60 changes: 1 addition & 59 deletions Source/ASDisplayNode+Beta.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#if YOGA
#import YOGA_HEADER_PATH
#import <AsyncDisplayKit/ASYogaUtilities.h>
#import <AsyncDisplayKit/ASDisplayNode+Yoga.h>
#endif

NS_ASSUME_NONNULL_BEGIN
Expand Down Expand Up @@ -180,63 +181,4 @@ typedef struct {

@end

#pragma mark - Yoga Layout Support

#if YOGA

AS_EXTERN void ASDisplayNodePerformBlockOnEveryYogaChild(ASDisplayNode * _Nullable node, void(^block)(ASDisplayNode *node));

@interface ASDisplayNode (Yoga)

@property (copy) NSArray *yogaChildren;

- (void)addYogaChild:(ASDisplayNode *)child;
- (void)removeYogaChild:(ASDisplayNode *)child;
- (void)insertYogaChild:(ASDisplayNode *)child atIndex:(NSUInteger)index;

- (void)semanticContentAttributeDidChange:(UISemanticContentAttribute)attribute;

@property BOOL yogaLayoutInProgress;
// TODO: Make this atomic (lock).
@property (nullable, nonatomic) ASLayout *yogaCalculatedLayout;

// Will walk up the Yoga tree and returns the root node
- (ASDisplayNode *)yogaRoot;

// These methods are intended to be used internally to Texture, and should not be called directly.
- (BOOL)shouldHaveYogaMeasureFunc;
- (void)invalidateCalculatedYogaLayout;
- (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize;

@end

@interface ASDisplayNode (YogaDebugging)

- (NSString *)yogaTreeDescription;

@end

@interface ASLayoutElementStyle (Yoga)

- (YGNodeRef)yogaNodeCreateIfNeeded;
- (void)destroyYogaNode;

@property (readonly) YGNodeRef yogaNode;

@property ASStackLayoutDirection flexDirection;
@property YGDirection direction;
@property ASStackLayoutJustifyContent justifyContent;
@property ASStackLayoutAlignItems alignItems;
@property YGPositionType positionType;
@property ASEdgeInsets position;
@property ASEdgeInsets margin;
@property ASEdgeInsets padding;
@property ASEdgeInsets border;
@property CGFloat aspectRatio;
@property YGWrap flexWrap;

@end

#endif

NS_ASSUME_NONNULL_END
21 changes: 11 additions & 10 deletions Source/ASDisplayNode+Layout.mm
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//

#import <AsyncDisplayKit/ASAvailability.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
Expand Down Expand Up @@ -143,19 +144,19 @@ - (NSString *)asciiArtName

@implementation ASDisplayNode (ASLayout)

- (void)setLayoutSpecBlock:(ASLayoutSpecBlock)layoutSpecBlock
- (ASLayoutEngineType)layoutEngineType
{
// For now there should never be an override of layoutSpecThatFits: and a layoutSpecBlock together.
ASDisplayNodeAssert(!(_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits),
@"Nodes with a .layoutSpecBlock must not also implement -layoutSpecThatFits:");
#if YOGA
ASDN::MutexLocker l(__instanceLock__);
_layoutSpecBlock = layoutSpecBlock;
}
YGNodeRef yogaNode = _style.yogaNode;
BOOL hasYogaParent = (_yogaParent != nil);
BOOL hasYogaChildren = (_yogaChildren.count > 0);
if (yogaNode != NULL && (hasYogaParent || hasYogaChildren)) {
return ASLayoutEngineTypeYoga;
}
#endif

- (ASLayoutSpecBlock)layoutSpecBlock
{
ASDN::MutexLocker l(__instanceLock__);
return _layoutSpecBlock;
return ASLayoutEngineTypeLayoutSpec;
}

- (ASLayout *)calculatedLayout
Expand Down
66 changes: 66 additions & 0 deletions Source/ASDisplayNode+LayoutSpec.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// ASDisplayNode+LayoutSpec.h
// Texture
//
// Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//

#import <AsyncDisplayKit/ASDisplayNode.h>
#import <AsyncDisplayKit/ASDimension.h>

@class ASLayout;

NS_ASSUME_NONNULL_BEGIN

@interface ASDisplayNode (ASLayoutSpec)

/**
* @abstract Provides a way to declare a block to provide an ASLayoutSpec without having to subclass ASDisplayNode and
* implement layoutSpecThatFits:
*
* @return A block that takes a constrainedSize ASSizeRange argument, and must return an ASLayoutSpec that includes all
* of the subnodes to position in the layout. This input-output relationship is identical to the subclass override
* method -layoutSpecThatFits:
*
* @warning Subclasses that implement -layoutSpecThatFits: must not also use .layoutSpecBlock. Doing so will trigger
* an exception. A future version of the framework may support using both, calling them serially, with the
* .layoutSpecBlock superseding any values set by the method override.
*
* @code ^ASLayoutSpec *(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {};
*/
@property (nullable) ASLayoutSpecBlock layoutSpecBlock;

@end

// These methods are intended to be used internally to Texture, and should not be called directly.
@interface ASDisplayNode (ASLayoutSpecPrivate)

/// For internal usage only
- (ASLayout *)calculateLayoutLayoutSpec:(ASSizeRange)constrainedSize;

@end

@interface ASDisplayNode (ASLayoutSpecSubclasses)

/**
* @abstract Return a layout spec that describes the layout of the receiver and its children.
*
* @param constrainedSize The minimum and maximum sizes the receiver should fit in.
*
* @discussion Subclasses that override should expect this method to be called on a non-main thread. The returned layout spec
* is used to calculate an ASLayout and cached by ASDisplayNode for quick access during -layout. Other expensive work that needs to
* be done before display can be performed here, and using ivars to cache any valuable intermediate results is
* encouraged.
*
* @note This method should not be called directly outside of ASDisplayNode; use -layoutThatFits: instead.
*
* @warning Subclasses that implement -layoutSpecThatFits: must not use .layoutSpecBlock. Doing so will trigger an
* exception. A future version of the framework may support using both, calling them serially, with the .layoutSpecBlock
* superseding any values set by the method override.
*/
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize;

@end

NS_ASSUME_NONNULL_END
146 changes: 146 additions & 0 deletions Source/ASDisplayNode+LayoutSpec.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
//
// ASDisplayNode+LayoutSpec.mm
// Texture
//
// Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//

#import <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASAvailability.h>

#import <AsyncDisplayKit/_ASScopeTimer.h>
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASLayout.h>
#import <AsyncDisplayKit/ASLayoutSpec+Subclasses.h>
#import <AsyncDisplayKit/ASLayoutSpecPrivate.h>
#import <AsyncDisplayKit/ASLog.h>
#import <AsyncDisplayKit/ASThread.h>


@implementation ASDisplayNode (ASLayoutSpec)

- (void)setLayoutSpecBlock:(ASLayoutSpecBlock)layoutSpecBlock
{
// For now there should never be an override of layoutSpecThatFits: and a layoutSpecBlock together.
ASDisplayNodeAssert(!(_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits),
@"Nodes with a .layoutSpecBlock must not also implement -layoutSpecThatFits:");
ASDN::MutexLocker l(__instanceLock__);
_layoutSpecBlock = layoutSpecBlock;
}

- (ASLayoutSpecBlock)layoutSpecBlock
{
ASDN::MutexLocker l(__instanceLock__);
return _layoutSpecBlock;
}

- (ASLayout *)calculateLayoutLayoutSpec:(ASSizeRange)constrainedSize
{
ASDN::UniqueLock l(__instanceLock__);

// Manual size calculation via calculateSizeThatFits:
if (_layoutSpecBlock == NULL && (_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits) == 0) {
CGSize size = [self calculateSizeThatFits:constrainedSize.max];
ASDisplayNodeLogEvent(self, @"calculatedSize: %@", NSStringFromCGSize(size));
return [ASLayout layoutWithLayoutElement:self size:ASSizeRangeClamp(constrainedSize, size) sublayouts:nil];
}

// Size calcualtion with layout elements
BOOL measureLayoutSpec = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutSpec;
if (measureLayoutSpec) {
_layoutSpecNumberOfPasses++;
}

// Get layout element from the node
id<ASLayoutElement> layoutElement = [self _locked_layoutElementThatFits:constrainedSize];
#if ASEnableVerboseLogging
for (NSString *asciiLine in [[layoutElement asciiArtString] componentsSeparatedByString:@"\n"]) {
as_log_verbose(ASLayoutLog(), "%@", asciiLine);
}
#endif


// Certain properties are necessary to set on an element of type ASLayoutSpec
if (layoutElement.layoutElementType == ASLayoutElementTypeLayoutSpec) {
ASLayoutSpec *layoutSpec = (ASLayoutSpec *)layoutElement;

#if AS_DEDUPE_LAYOUT_SPEC_TREE
NSHashTable *duplicateElements = [layoutSpec findDuplicatedElementsInSubtree];
if (duplicateElements.count > 0) {
ASDisplayNodeFailAssert(@"Node %@ returned a layout spec that contains the same elements in multiple positions. Elements: %@", self, duplicateElements);
// Use an empty layout spec to avoid crashes
layoutSpec = [[ASLayoutSpec alloc] init];
}
#endif

ASDisplayNodeAssert(layoutSpec.isMutable, @"Node %@ returned layout spec %@ that has already been used. Layout specs should always be regenerated.", self, layoutSpec);

layoutSpec.isMutable = NO;
}

// Manually propagate the trait collection here so that any layoutSpec children of layoutSpec will get a traitCollection
{
ASDN::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec);
ASTraitCollectionPropagateDown(layoutElement, self.primitiveTraitCollection);
}

BOOL measureLayoutComputation = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutComputation;
if (measureLayoutComputation) {
_layoutComputationNumberOfPasses++;
}

// Layout element layout creation
ASLayout *layout = ({
ASDN::SumScopeTimer t(_layoutComputationTotalTime, measureLayoutComputation);
[layoutElement layoutThatFits:constrainedSize];
});
ASDisplayNodeAssertNotNil(layout, @"[ASLayoutElement layoutThatFits:] should never return nil! %@, %@", self, layout);

// Make sure layoutElementObject of the root layout is `self`, so that the flattened layout will be structurally correct.
BOOL isFinalLayoutElement = (layout.layoutElement != self);
if (isFinalLayoutElement) {
layout.position = CGPointZero;
layout = [ASLayout layoutWithLayoutElement:self size:layout.size sublayouts:@[layout]];
}
ASDisplayNodeLogEvent(self, @"computedLayout: %@", layout);

// PR #1157: Reduces accuracy of _unflattenedLayout for debugging/Weaver
if ([ASDisplayNode shouldStoreUnflattenedLayouts]) {
_unflattenedLayout = layout;
}
layout = [layout filteredNodeLayoutTree];

return layout;
}

- (id<ASLayoutElement>)_locked_layoutElementThatFits:(ASSizeRange)constrainedSize
{
ASAssertLocked(__instanceLock__);

BOOL measureLayoutSpec = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutSpec;

if (_layoutSpecBlock != NULL) {
return ({
ASDN::MutexLocker l(__instanceLock__);
ASDN::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec);
_layoutSpecBlock(self, constrainedSize);
});
} else {
return ({
ASDN::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec);
[self layoutSpecThatFits:constrainedSize];
});
}
}

- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
__ASDisplayNodeCheckForLayoutMethodOverrides;

ASDisplayNodeAssert(NO, @"-[ASDisplayNode layoutSpecThatFits:] should never return an empty value. One way this is caused is by calling -[super layoutSpecThatFits:] which is not currently supported.");
return [[ASLayoutSpec alloc] init];
}

@end
Loading

0 comments on commit d4efe95

Please sign in to comment.