Skip to content

Commit

Permalink
Yoga integration improvements (#1187)
Browse files Browse the repository at this point in the history
* Thread safety for Yoga layout

* Support baseline alignments for ASYogaLayout

* Refactor ASLayoutElementYogaBaselineFunc to not require yogaParent (its parent style is set into a private var on ASLayoutElementStyle before layout instead)

* Only set the accessibility element if the view is loaded

* Add nodeWillCalculateLayout to ASNodeController

* Update Changelog

* Address first comments
  • Loading branch information
maicki authored Oct 24, 2018
1 parent dab3281 commit 0977903
Show file tree
Hide file tree
Showing 11 changed files with 112 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
- ASTableNode init method match checks from ASCollectionNode [Michael Schneider](https://github.com/maicki) [#1171]
- Add NSLocking conformance to ASNodeController [Michael Schneider](https://github.com/maicki)[#1179] (https://github.com/TextureGroup/Texture/pull/1179)
- Don’t handle touches on additional attributed message if passthrough is enabled [Michael Schneider](https://github.com/maicki)[#1184] (https://github.com/TextureGroup/Texture/pull/1184)
- Yoga integration improvements [Michael Schneider](https://github.com/maicki)[#1187] (https://github.com/TextureGroup/Texture/pull/1187)

## 2.7
- Fix pager node for interface coalescing. [Max Wang](https://github.com/wsdwsd0829) [#877](https://github.com/TextureGroup/Texture/pull/877)
Expand Down
3 changes: 3 additions & 0 deletions Source/ASDisplayNode+Beta.h
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,9 @@ AS_EXTERN void ASDisplayNodePerformBlockOnEveryYogaChild(ASDisplayNode * _Nullab
@property BOOL yogaLayoutInProgress;
@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;
Expand Down
11 changes: 11 additions & 0 deletions Source/ASDisplayNode+InterfaceState.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,15 @@ typedef NS_OPTIONS(NSUInteger, ASInterfaceState)
*/
- (void)hierarchyDisplayDidFinish;

@optional
/**
* @abstract Called when the node is about to calculate layout. This is only called before
* Yoga-driven layouts.
* @discussion Can be used for operations that are performed after the node's view is available.
* @note This method is guaranteed to be called on main, but implementations should be careful not
* to attempt to ascend the node tree when handling this, as the root node is locked when this is
* called.
*/
- (void)nodeWillCalculateLayout:(ASSizeRange)constrainedSize;

@end
6 changes: 6 additions & 0 deletions Source/ASDisplayNode+Layout.mm
Original file line number Diff line number Diff line change
Expand Up @@ -999,6 +999,12 @@ - (void)_pendingLayoutTransitionDidComplete
_pendingLayoutTransition = nil;
}

- (void)_setCalculatedDisplayNodeLayout:(const ASDisplayNodeLayout &)displayNodeLayout
{
ASDN::MutexLocker l(__instanceLock__);
[self _locked_setCalculatedDisplayNodeLayout:displayNodeLayout];
}

- (void)_locked_setCalculatedDisplayNodeLayout:(const ASDisplayNodeLayout &)displayNodeLayout
{
ASAssertLocked(__instanceLock__);
Expand Down
34 changes: 32 additions & 2 deletions Source/ASDisplayNode+Yoga.mm
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
#import <AsyncDisplayKit/ASLayout.h>
#import <AsyncDisplayKit/ASLayoutElementStylePrivate.h>

#define YOGA_LAYOUT_LOGGING 0

Expand All @@ -31,8 +32,19 @@ - (ASSizeRange)_locked_constrainedSizeForLayoutPass;

@implementation ASDisplayNode (Yoga)

- (ASDisplayNode *)yogaRoot
{
ASDisplayNode *yogaRoot = self;
ASDisplayNode *yogaParent = nil;
while ((yogaParent = yogaRoot.yogaParent)) {
yogaRoot = yogaParent;
}
return yogaRoot;
}

- (void)setYogaChildren:(NSArray *)yogaChildren
{
ASLockScope(self.yogaRoot);
for (ASDisplayNode *child in [_yogaChildren copy]) {
// Make sure to un-associate the YGNodeRef tree before replacing _yogaChildren
// If this becomes a performance bottleneck, it can be optimized by not doing the NSArray removals here.
Expand All @@ -51,11 +63,13 @@ - (NSArray *)yogaChildren

- (void)addYogaChild:(ASDisplayNode *)child
{
ASLockScope(self.yogaRoot);
[self insertYogaChild:child atIndex:_yogaChildren.count];
}

- (void)removeYogaChild:(ASDisplayNode *)child
{
ASLockScope(self.yogaRoot);
if (child == nil) {
return;
}
Expand All @@ -68,6 +82,7 @@ - (void)removeYogaChild:(ASDisplayNode *)child

- (void)insertYogaChild:(ASDisplayNode *)child atIndex:(NSUInteger)index
{
ASLockScope(self.yogaRoot);
if (child == nil) {
return;
}
Expand Down Expand Up @@ -216,6 +231,7 @@ - (void)setupYogaCalculatedLayout
- (BOOL)shouldHaveYogaMeasureFunc
{
// Size calculation via calculateSizeThatFits: or layoutSpecThatFits:
// For these nodes, we assume they may need custom Baseline calculation too.
// This will be used for ASTextNode, as well as any other node that has no Yoga children
BOOL isLeafNode = (self.yogaChildren.count == 0);
BOOL definesCustomLayout = [self implementsLayoutMethod];
Expand Down Expand Up @@ -261,11 +277,23 @@ - (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize
return;
}

[self enumerateInterfaceStateDelegates:^(id<ASInterfaceStateDelegate> _Nonnull delegate) {
if ([delegate respondsToSelector:@selector(nodeWillCalculateLayout:)]) {
[delegate nodeWillCalculateLayout:rootConstrainedSize];
}
}];

ASLockScopeSelf();

// Prepare all children for the layout pass with the current Yoga tree configuration.
ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) {
ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode *_Nonnull node) {
node.yogaLayoutInProgress = YES;
ASDisplayNode *yogaParent = node.yogaParent;
if (yogaParent) {
node.style.parentAlignStyle = yogaParent.style.alignItems;
} else {
node.style.parentAlignStyle = ASStackLayoutAlignItemsNotSet;
};
});

if (ASSizeRangeEqualToSizeRange(rootConstrainedSize, ASSizeRangeUnconstrained)) {
Expand Down Expand Up @@ -293,7 +321,9 @@ - (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize

// Reset accessible elements, since layout may have changed.
ASPerformBlockOnMainThread(^{
[(_ASDisplayView *)self.view setAccessibilityElements:nil];
if (self.nodeLoaded && !self.isSynchronous) {
[(_ASDisplayView *)self.view setAccessibilityElements:nil];
}
});

ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) {
Expand Down
3 changes: 3 additions & 0 deletions Source/ASNodeController+Beta.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
- (void)nodeDidLoad ASDISPLAYNODE_REQUIRES_SUPER;
- (void)nodeDidLayout ASDISPLAYNODE_REQUIRES_SUPER;

// This is only called during Yoga-driven layouts.
- (void)nodeWillCalculateLayout:(ASSizeRange)constrainedSize ASDISPLAYNODE_REQUIRES_SUPER;

- (void)didEnterVisibleState ASDISPLAYNODE_REQUIRES_SUPER;
- (void)didExitVisibleState ASDISPLAYNODE_REQUIRES_SUPER;

Expand Down
1 change: 1 addition & 0 deletions Source/ASNodeController+Beta.mm
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ - (void)setShouldInvertStrongReference:(BOOL)shouldInvertStrongReference
// subclass overrides
- (void)nodeDidLoad {}
- (void)nodeDidLayout {}
- (void)nodeWillCalculateLayout:(ASSizeRange)constrainedSize {}

- (void)didEnterVisibleState {}
- (void)didExitVisibleState {}
Expand Down
14 changes: 13 additions & 1 deletion Source/Layout/ASLayoutElement.mm
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ void ASLayoutElementPopContext()
@implementation ASLayoutElementStyle {
ASDN::RecursiveMutex __instanceLock__;
ASLayoutElementStyleExtensions _extensions;

std::atomic<ASLayoutElementSize> _size;
std::atomic<CGFloat> _spacingBefore;
std::atomic<CGFloat> _spacingAfter;
Expand All @@ -180,6 +180,7 @@ @implementation ASLayoutElementStyle {
std::atomic<ASEdgeInsets> _padding;
std::atomic<ASEdgeInsets> _border;
std::atomic<CGFloat> _aspectRatio;
ASStackLayoutAlignItems _parentAlignStyle;
#endif
}

Expand All @@ -202,6 +203,9 @@ - (instancetype)init
self = [super init];
if (self) {
_size = ASLayoutElementSizeMake();
#if YOGA
_parentAlignStyle = ASStackLayoutAlignItemsNotSet;
#endif
}
return self;
}
Expand Down Expand Up @@ -777,6 +781,10 @@ - (ASEdgeInsets)margin { return _margin.load(); }
- (ASEdgeInsets)padding { return _padding.load(); }
- (ASEdgeInsets)border { return _border.load(); }
- (CGFloat)aspectRatio { return _aspectRatio.load(); }
// private (ASLayoutElementStylePrivate.h)
- (ASStackLayoutAlignItems)parentAlignStyle {
return _parentAlignStyle;
}

- (void)setFlexWrap:(YGWrap)flexWrap {
_flexWrap.store(flexWrap);
Expand Down Expand Up @@ -822,6 +830,10 @@ - (void)setAspectRatio:(CGFloat)aspectRatio {
_aspectRatio.store(aspectRatio);
ASLayoutElementStyleCallDelegate(ASYogaAspectRatioProperty);
}
// private (ASLayoutElementStylePrivate.h)
- (void)setParentAlignStyle:(ASStackLayoutAlignItems)style {
_parentAlignStyle = style;
}

#endif /* YOGA */

Expand Down
6 changes: 4 additions & 2 deletions Source/Layout/ASYogaUtilities.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

@end

// pre-order, depth-first
AS_EXTERN void ASDisplayNodePerformBlockOnEveryYogaChild(ASDisplayNode *node, void(^block)(ASDisplayNode *node));

#pragma mark - Yoga Type Conversion Helpers
Expand All @@ -40,9 +41,10 @@ AS_EXTERN float yogaDimensionToPercent(ASDimension dimension);
AS_EXTERN ASDimension dimensionForEdgeWithEdgeInsets(YGEdge edge, ASEdgeInsets insets);

AS_EXTERN void ASLayoutElementYogaUpdateMeasureFunc(YGNodeRef yogaNode, id <ASLayoutElement> layoutElement);
AS_EXTERN float ASLayoutElementYogaBaselineFunc(YGNodeRef yogaNode, const float width, const float height);
AS_EXTERN YGSize ASLayoutElementYogaMeasureFunc(YGNodeRef yogaNode,
float width, YGMeasureMode widthMode,
float height, YGMeasureMode heightMode);
float width, YGMeasureMode widthMode,
float height, YGMeasureMode heightMode);

#pragma mark - Yoga Style Setter Helpers

Expand Down
42 changes: 35 additions & 7 deletions Source/Layout/ASYogaUtilities.mm
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
//

#import <AsyncDisplayKit/ASYogaUtilities.h>

#import <AsyncDisplayKit/ASLayoutElementStylePrivate.h>
#if YOGA /* YOGA */

@implementation ASDisplayNode (YogaHelpers)
Expand Down Expand Up @@ -143,23 +143,51 @@ void ASLayoutElementYogaUpdateMeasureFunc(YGNodeRef yogaNode, id <ASLayoutElemen
if (yogaNode == NULL) {
return;
}
BOOL hasMeasureFunc = (YGNodeGetMeasureFunc(yogaNode) != NULL);

if (layoutElement != nil && [layoutElement implementsLayoutMethod]) {
if (hasMeasureFunc == NO) {
BOOL shouldHaveMeasureFunc = [layoutElement implementsLayoutMethod];
// How expensive is it to set a baselineFunc on all (leaf) nodes?
BOOL shouldHaveBaselineFunc = YES;

if (layoutElement != nil) {
if (shouldHaveMeasureFunc || shouldHaveBaselineFunc) {
// Retain the Context object. This must be explicitly released with a
// __bridge_transfer - YGNodeFree() is not sufficient.
YGNodeSetContext(yogaNode, (__bridge_retained void *)layoutElement);
}
if (shouldHaveMeasureFunc) {
YGNodeSetMeasureFunc(yogaNode, &ASLayoutElementYogaMeasureFunc);
}
if (shouldHaveBaselineFunc) {
YGNodeSetBaselineFunc(yogaNode, &ASLayoutElementYogaBaselineFunc);
}
ASDisplayNodeCAssert(YGNodeGetContext(yogaNode) == (__bridge void *)layoutElement,
@"Yoga node context should contain layoutElement: %@", layoutElement);
} else if (hasMeasureFunc == YES) {
// If we lack any of the conditions above, and currently have a measure func, get rid of it.
} else {
// If we lack any of the conditions above, and currently have a measureFn/baselineFn/context,
// get rid of it.
// Release the __bridge_retained Context object.
__unused id <ASLayoutElement> element = (__bridge_transfer id)YGNodeGetContext(yogaNode);
__unused id<ASLayoutElement> element = (__bridge_transfer id)YGNodeGetContext(yogaNode);
YGNodeSetContext(yogaNode, NULL);
YGNodeSetMeasureFunc(yogaNode, NULL);
YGNodeSetBaselineFunc(yogaNode, NULL);
}
}

float ASLayoutElementYogaBaselineFunc(YGNodeRef yogaNode, const float width, const float height)
{
id<ASLayoutElement> layoutElement = (__bridge id<ASLayoutElement>)YGNodeGetContext(yogaNode);
ASDisplayNodeCAssert([layoutElement conformsToProtocol:@protocol(ASLayoutElement)],
@"Yoga context must be <ASLayoutElement>");

ASDisplayNode *displayNode = ASDynamicCast(layoutElement, ASDisplayNode);

switch (displayNode.style.parentAlignStyle) {
case ASStackLayoutAlignItemsBaselineFirst:
return layoutElement.style.ascender;
case ASStackLayoutAlignItemsBaselineLast:
return height + layoutElement.style.descender;
default:
return 0;
}
}

Expand Down
3 changes: 3 additions & 0 deletions Source/Private/Layout/ASLayoutElementStylePrivate.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#pragma once

#import <AsyncDisplayKit/ASLayoutElement.h>
#import <AsyncDisplayKit/ASObjectDescriptionHelpers.h>

@interface ASLayoutElementStyle () <ASDescriptionProvider>
Expand All @@ -25,4 +26,6 @@
*/
@property (nonatomic, readonly) ASLayoutElementSize size;

@property (nonatomic, assign) ASStackLayoutAlignItems parentAlignStyle;

@end

0 comments on commit 0977903

Please sign in to comment.