diff --git a/CHANGELOG.md b/CHANGELOG.md index eb280cddc8..e74bac89d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Clean up unused/unneeded header macros. [Adlai Holler](https://github.com/Adlai-Holler) - Clean up C-function `extern` decorators. [Adlai Holler](https://github.com/Adlai-Holler) - Add an experiment to reduce work involved in collection teardown. [Adlai Holler](https://github.com/Adlai-Holler) +- Optimize layout flattening, particularly reducing retain/release operations. [Adlai Holler](https://github.com/Adlai-Holler) ## 2.7 - Fix pager node for interface coalescing. [Max Wang](https://github.com/wsdwsd0829) [#877](https://github.com/TextureGroup/Texture/pull/877) diff --git a/Source/Base/ASBaseDefines.h b/Source/Base/ASBaseDefines.h index 4150b8aed2..3adcf3aad7 100755 --- a/Source/Base/ASBaseDefines.h +++ b/Source/Base/ASBaseDefines.h @@ -18,6 +18,7 @@ #import #define AS_EXTERN FOUNDATION_EXTERN +#define unowned __unsafe_unretained #ifdef __GNUC__ # define ASDISPLAYNODE_GNUC(major, minor) \ diff --git a/Source/Layout/ASLayout.mm b/Source/Layout/ASLayout.mm index 607a1c6483..de06f8567a 100644 --- a/Source/Layout/ASLayout.mm +++ b/Source/Layout/ASLayout.mm @@ -55,22 +55,6 @@ ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT BOOL ASLayoutIsDisplayNodeType(ASLayo return layout.type == ASLayoutElementTypeDisplayNode; } -ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT BOOL ASLayoutIsFlattened(ASLayout *layout) -{ - // A layout is flattened if its position is null, and all of its sublayouts are of type displaynode with no sublayouts. - if (! ASPointIsNull(layout.position)) { - return NO; - } - - for (ASLayout *sublayout in layout.sublayouts) { - if (ASLayoutIsDisplayNodeType(sublayout) == NO || sublayout.sublayouts.count > 0) { - return NO; - } - } - - return YES; -} - @interface ASLayout () { ASLayoutElementType _layoutElementType; @@ -216,9 +200,25 @@ static std::atomic_bool static_retainsSublayoutLayoutElements = ATOMIC_VAR_INIT( #pragma mark - Layout Flattening +- (BOOL)isFlattened +{ + // A layout is flattened if its position is null, and all of its sublayouts are of type displaynode with no sublayouts. + if (!ASPointIsNull(_position)) { + return NO; + } + + for (ASLayout *sublayout in _sublayouts) { + if (ASLayoutIsDisplayNodeType(sublayout) == NO || sublayout->_sublayouts.count > 0) { + return NO; + } + } + + return YES; +} + - (ASLayout *)filteredNodeLayoutTree NS_RETURNS_RETAINED { - if (ASLayoutIsFlattened(self)) { + if ([self isFlattened]) { // All flattened layouts must have this flag enabled // to ensure sublayout elements are retained until the layouts are applied. self.retainSublayoutLayoutElements = YES; @@ -226,42 +226,45 @@ static std::atomic_bool static_retainsSublayoutLayoutElements = ATOMIC_VAR_INIT( } struct Context { - ASLayout *layout; + unowned ASLayout *layout; CGPoint absolutePosition; }; // Queue used to keep track of sublayouts while traversing this layout in a DFS fashion. std::deque queue; - for (ASLayout *sublayout in self.sublayouts) { + for (ASLayout *sublayout in _sublayouts) { queue.push_back({sublayout, sublayout.position}); } - NSMutableArray *flattenedSublayouts = [[NSMutableArray alloc] init]; + auto flattenedSublayouts = [[NSMutableArray alloc] init]; while (!queue.empty()) { const Context context = std::move(queue.front()); queue.pop_front(); - ASLayout *layout = context.layout; - const NSArray *sublayouts = layout.sublayouts; - const NSUInteger sublayoutsCount = sublayouts.count; + unowned ASLayout *layout = context.layout; + // Direct ivar access to avoid retain/release, use existing +1. + const NSUInteger sublayoutsCount = layout->_sublayouts.count; const CGPoint absolutePosition = context.absolutePosition; if (ASLayoutIsDisplayNodeType(layout)) { if (sublayoutsCount > 0 || CGPointEqualToPoint(ASCeilPointValues(absolutePosition), layout.position) == NO) { // Only create a new layout if the existing one can't be reused, which means it has either some sublayouts or an invalid absolute position. - layout = [ASLayout layoutWithLayoutElement:layout.layoutElement - size:layout.size - position:absolutePosition - sublayouts:@[]]; + auto newLayout = [ASLayout layoutWithLayoutElement:layout->_layoutElement + size:layout.size + position:absolutePosition + sublayouts:@[]]; + [flattenedSublayouts addObject:newLayout]; + } else { + [flattenedSublayouts addObject:layout]; } - [flattenedSublayouts addObject:layout]; - } else if (sublayoutsCount > 0){ - std::vector sublayoutContexts; - for (ASLayout *sublayout in sublayouts) { - sublayoutContexts.push_back({sublayout, absolutePosition + sublayout.position}); + } else if (sublayoutsCount > 0) { + // Fast-reverse-enumerate the sublayouts array by copying it into a C-array and push_front'ing each into the queue. + unowned ASLayout *rawSublayouts[sublayoutsCount]; + [layout->_sublayouts getObjects:rawSublayouts range:NSMakeRange(0, sublayoutsCount)]; + for (NSInteger i = sublayoutsCount - 1; i >= 0; i--) { + queue.push_front({rawSublayouts[i], absolutePosition + rawSublayouts[i].position}); } - queue.insert(queue.cbegin(), sublayoutContexts.begin(), sublayoutContexts.end()); } }