From 9669f147ba9042ca6f58e185ddcc9ee9cfe1b9a0 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sun, 8 Nov 2015 17:04:13 -0800 Subject: [PATCH] Efficiency improvements to consolidate display events for the window-less preload range. --- AsyncDisplayKit/ASDisplayNode.mm | 69 +++++++++++++++---- .../Private/ASDisplayNode+AsyncDisplay.mm | 7 +- .../Private/ASDisplayNode+UIViewBridge.mm | 20 ++---- .../Private/ASDisplayNodeInternal.h | 2 + 4 files changed, 68 insertions(+), 30 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index f70d336b88..9baec3b662 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -192,6 +192,29 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return [_ASDisplayLayer class]; } ++ (void)scheduleNodeForDisplay:(ASDisplayNode *)node +{ + ASDisplayNodeAssertMainThread(); + static NSMutableSet *nodesToDisplay = nil; + static BOOL displayScheduled = NO; + if (!nodesToDisplay) { + nodesToDisplay = [[NSMutableSet alloc] init]; + } + [nodesToDisplay addObject:node]; + if (!displayScheduled) { + displayScheduled = YES; + // It's essenital that any layout pass that is scheduled during the current + // runloop has a chance to be applied / scheduled, so always perform this after the current runloop. + dispatch_async(dispatch_get_main_queue(), ^{ + displayScheduled = NO; + for (ASDisplayNode *node in nodesToDisplay) { + [node __recursivelyTriggerDisplayAndBlock:NO]; + } + nodesToDisplay = nil; + }); + } +} + #pragma mark - Lifecycle - (void)_staticInitialize @@ -705,6 +728,20 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) [self displayImmediately]; } +- (void)__setNeedsDisplay +{ + ASDisplayNode *rasterizedContainerNode = [self __rasterizedContainerNode]; + if (rasterizedContainerNode) { + [rasterizedContainerNode setNeedsDisplay]; + } else { + [_layer setNeedsDisplay]; + + if (_layer && !self.isSynchronous && self.displaysAsynchronously) { + [ASDisplayNode scheduleNodeForDisplay:self]; + } + } +} + - (void)__setNeedsLayout { ASDisplayNodeAssertThreadAffinity(self); @@ -1417,7 +1454,7 @@ static NSInteger incrementIfFound(NSInteger i) { [_placeholderLayer removeFromSuperlayer]; } -void recursivelyEnsureDisplayForLayer(CALayer *layer) +void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) { // This recursion must handle layers in various states: // 1. Just added to hierarchy, CA hasn't yet called -display @@ -1436,22 +1473,24 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer) // Kick off the recursion first, so that all necessary display calls are sent and the displayQueue is full of parallelizable work. for (CALayer *sublayer in layer.sublayers) { - recursivelyEnsureDisplayForLayer(sublayer); + recursivelyTriggerDisplayForLayer(sublayer, shouldBlock); } - // As the recursion unwinds, verify each transaction is complete and block if it is not. - // While blocking on one transaction, others may be completing concurrently, so it doesn't matter which blocks first. - BOOL waitUntilComplete = (!node.shouldBypassEnsureDisplay); - if (waitUntilComplete) { - for (_ASAsyncTransaction *transaction in [layer.asyncdisplaykit_asyncLayerTransactions copy]) { - // Even if none of the layers have had a chance to start display earlier, they will still be allowed to saturate a multicore CPU while blocking main. - // This significantly reduces time on the main thread relative to UIKit. - [transaction waitUntilComplete]; + if (shouldBlock) { + // As the recursion unwinds, verify each transaction is complete and block if it is not. + // While blocking on one transaction, others may be completing concurrently, so it doesn't matter which blocks first. + BOOL waitUntilComplete = (!node.shouldBypassEnsureDisplay); + if (waitUntilComplete) { + for (_ASAsyncTransaction *transaction in [layer.asyncdisplaykit_asyncLayerTransactions copy]) { + // Even if none of the layers have had a chance to start display earlier, they will still be allowed to saturate a multicore CPU while blocking main. + // This significantly reduces time on the main thread relative to UIKit. + [transaction waitUntilComplete]; + } } } } -- (void)recursivelyEnsureDisplay +- (void)__recursivelyTriggerDisplayAndBlock:(BOOL)shouldBlock { ASDisplayNodeAssertMainThread(); // ASDisplayNodeAssert(self.isNodeLoaded, @"Node must have layer or view loaded to use -recursivelyEnsureDisplay"); @@ -1464,7 +1503,12 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer) if ([layer needsLayout]) { [layer layoutIfNeeded]; } - recursivelyEnsureDisplayForLayer(layer); + recursivelyTriggerDisplayForLayer(layer, shouldBlock); +} + +- (void)recursivelyEnsureDisplay +{ + [self __recursivelyTriggerDisplayAndBlock:YES]; } - (void)setShouldBypassEnsureDisplay:(BOOL)shouldBypassEnsureDisplay @@ -1997,6 +2041,7 @@ static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer, return _replaceAsyncSentinel != nil; } +// FIXME: This method doesn't appear to be called, and should be removed. - (ASSentinel *)_asyncReplaceSentinel { ASDN::MutexLocker l(_propertyLock); diff --git a/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm b/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm index e74cd00e35..e53f30bccb 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm @@ -303,10 +303,9 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, // for async display, capture the current displaySentinel value to bail early when the job is executed if another is // enqueued // for sync display, just use nil for the displaySentinel and go - // - // REVIEW: what about the degenerate case where we are calling setNeedsDisplay faster than the jobs are dequeuing - // from the displayQueue? do we want to put in some kind of timer to not cancel early fails from displaySentinel - // changes? + + // FIXME: what about the degenerate case where we are calling setNeedsDisplay faster than the jobs are dequeuing + // from the displayQueue? Need to not cancel early fails from displaySentinel changes. ASSentinel *displaySentinel = (asynchronously ? _displaySentinel : nil); int64_t displaySentinelValue = [displaySentinel increment]; diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index 65ad2a63e6..6f0ecd9dac 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -218,20 +218,12 @@ - (void)setNeedsDisplay { - ASDisplayNode *rasterizedContainerNode = [self __rasterizedContainerNode]; - if (rasterizedContainerNode) { - [rasterizedContainerNode setNeedsDisplay]; - } else { - [_layer setNeedsDisplay]; - - if (_layer && !self.isSynchronous && self.displaysAsynchronously) { - // It's essenital that any layout pass that is scheduled during the current - // runloop has a chance to be applied / scheduled. - dispatch_async(dispatch_get_main_queue(), ^{ - [self recursivelyEnsureDisplay]; - }); - } - } + _bridge_prologue; + // Send the message to the layer first, as __setNeedsDisplay may call -displayIfNeeded. + // REVIEW: Audit if this is necessary or if it can be called after like __setNeedsLayout + // -> Likely possible because of the aggregation / trampoline to occur on a later runloop. + _messageToLayer(setNeedsDisplay); + [self __setNeedsDisplay]; } - (void)setNeedsLayout diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 0d3ab7efe3..2d3c389dcd 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -149,6 +149,8 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) { // Display the node's view/layer immediately on the current thread, bypassing the background thread rendering. Will be deprecated. - (void)displayImmediately; +- (void)__setNeedsDisplay; + // Returns the ancestor node that rasterizes descendants, or nil if none. - (ASDisplayNode *)__rasterizedContainerNode;