Skip to content
This repository has been archived by the owner on Feb 2, 2023. It is now read-only.

[ASRangeController] Minimize number of registrations to node display notifications #1864

Merged
merged 1 commit into from
Jul 8, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 16 additions & 16 deletions AsyncDisplayKit/ASDisplayNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -245,9 +245,9 @@ + (void)scheduleNodeForRecursiveDisplay:(ASDisplayNode *)node
dispatch_once(&onceToken, ^{
renderQueue = [[ASRunLoopQueue<ASDisplayNode *> alloc] initWithRunLoop:CFRunLoopGetMain()
andHandler:^(ASDisplayNode * _Nonnull dequeuedItem, BOOL isQueueDrained) {
CFAbsoluteTime timestamp = isQueueDrained ? CFAbsoluteTimeGetCurrent() : 0;
[dequeuedItem _recursivelyTriggerDisplayAndBlock:NO];
if (isQueueDrained) {
CFAbsoluteTime timestamp = CFAbsoluteTimeGetCurrent();
[[NSNotificationCenter defaultCenter] postNotificationName:ASRenderingEngineDidDisplayScheduledNodesNotification
object:nil
userInfo:@{ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp: @(timestamp)}];
Expand Down Expand Up @@ -2317,27 +2317,27 @@ - (void)exitInterfaceState:(ASInterfaceState)interfaceState
});
}

- (void)recursivelySetInterfaceState:(ASInterfaceState)interfaceState
- (void)recursivelySetInterfaceState:(ASInterfaceState)newInterfaceState
{
ASInterfaceState oldState = self.interfaceState;
ASInterfaceState newState = interfaceState;
// Instead of each node in the recursion assuming it needs to schedule itself for display,
// setInterfaceState: skips this when handling range-managed nodes (our whole subtree has this set).
// If our range manager intends for us to be displayed right now, and didn't before, get started!
BOOL shouldScheduleDisplay = [self supportsRangeManagedInterfaceState] && [self shouldScheduleDisplayWithNewInterfaceState:newInterfaceState];
ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) {
node.interfaceState = interfaceState;
node.interfaceState = newInterfaceState;
});

if ([self supportsRangeManagedInterfaceState]) {
// Instead of each node in the recursion assuming it needs to schedule itself for display,
// setInterfaceState: skips this when handling range-managed nodes (our whole subtree has this set).
// If our range manager intends for us to be displayed right now, and didn't before, get started!

BOOL nowDisplay = ASInterfaceStateIncludesDisplay(newState);
BOOL wasDisplay = ASInterfaceStateIncludesDisplay(oldState);
if (nowDisplay && (nowDisplay != wasDisplay)) {
[ASDisplayNode scheduleNodeForRecursiveDisplay:self];
}
if (shouldScheduleDisplay) {
[ASDisplayNode scheduleNodeForRecursiveDisplay:self];
}
}

- (BOOL)shouldScheduleDisplayWithNewInterfaceState:(ASInterfaceState)newInterfaceState
{
BOOL willDisplay = ASInterfaceStateIncludesDisplay(newInterfaceState);
BOOL nowDisplay = ASInterfaceStateIncludesDisplay(self.interfaceState);
return willDisplay && (willDisplay != nowDisplay);
}

- (ASHierarchyState)hierarchyState
{
ASDN::MutexLocker l(_propertyLock);
Expand Down
29 changes: 15 additions & 14 deletions AsyncDisplayKit/Details/ASRangeController.mm
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ @interface ASRangeController ()
NSSet<NSIndexPath *> *_allPreviousIndexPaths;
ASLayoutRangeMode _currentRangeMode;
BOOL _didUpdateCurrentRange;
BOOL _didRegisterForNotifications;
BOOL _didRegisterForNodeDisplayNotifications;
CFAbsoluteTime _pendingDisplayNodesTimestamp;
}

Expand Down Expand Up @@ -56,7 +56,7 @@ - (instancetype)init

- (void)dealloc
{
if (_didRegisterForNotifications) {
if (_didRegisterForNodeDisplayNotifications) {
[[NSNotificationCenter defaultCenter] removeObserver:self name:ASRenderingEngineDidDisplayScheduledNodesNotification object:nil];
}
}
Expand Down Expand Up @@ -242,10 +242,6 @@ - (void)_updateVisibleNodeIndexPaths
[allIndexPaths addObjectsFromArray:ASIndexPathsForTwoDimensionalArray(allNodes)];
}

// TODO Don't register for notifications if this range update doesn't cause any node to enter rendering pipeline.
// This can be done once there is an API to observe to (or be notified upon) interface state changes or pipeline enterings
[self registerForNotificationsForInterfaceStateIfNeeded:selfInterfaceState];

#if ASRangeControllerLoggingEnabled
ASDisplayNodeAssertTrue([visibleIndexPaths isSubsetOfSet:displayIndexPaths]);
NSMutableArray<NSIndexPath *> *modifiedIndexPaths = (ASRangeControllerLoggingEnabled ? [NSMutableArray array] : nil);
Expand Down Expand Up @@ -309,16 +305,21 @@ - (void)_updateVisibleNodeIndexPaths
#if ASRangeControllerLoggingEnabled
[modifiedIndexPaths addObject:indexPath];
#endif

BOOL nodeShouldScheduleDisplay = [node shouldScheduleDisplayWithNewInterfaceState:interfaceState];
[node recursivelySetInterfaceState:interfaceState];

if (nodeShouldScheduleDisplay) {
[self registerForNodeDisplayNotificationsForInterfaceStateIfNeeded:selfInterfaceState];
if (_didRegisterForNodeDisplayNotifications) {
_pendingDisplayNodesTimestamp = CFAbsoluteTimeGetCurrent();
}
}
}
}
}
}

if (_didRegisterForNotifications) {
_pendingDisplayNodesTimestamp = CFAbsoluteTimeGetCurrent();
}

_rangeIsValid = YES;
_queuedRangeUpdate = NO;

Expand All @@ -338,17 +339,17 @@ - (void)_updateVisibleNodeIndexPaths

#pragma mark - Notification observers

- (void)registerForNotificationsForInterfaceStateIfNeeded:(ASInterfaceState)interfaceState
- (void)registerForNodeDisplayNotificationsForInterfaceStateIfNeeded:(ASInterfaceState)interfaceState
{
if (!_didRegisterForNotifications) {
if (!_didRegisterForNodeDisplayNotifications) {
ASLayoutRangeMode nextRangeMode = [ASRangeController rangeModeForInterfaceState:interfaceState
currentRangeMode:_currentRangeMode];
if (_currentRangeMode != nextRangeMode) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(scheduledNodesDidDisplay:)
name:ASRenderingEngineDidDisplayScheduledNodesNotification
object:nil];
_didRegisterForNotifications = YES;
_didRegisterForNodeDisplayNotifications = YES;
}
}
}
Expand All @@ -359,7 +360,7 @@ - (void)scheduledNodesDidDisplay:(NSNotification *)notification
if (_pendingDisplayNodesTimestamp < notificationTimestamp) {
// The rendering engine has processed all the nodes this range controller scheduled. Let's schedule a range update
[[NSNotificationCenter defaultCenter] removeObserver:self name:ASRenderingEngineDidDisplayScheduledNodesNotification object:nil];
_didRegisterForNotifications = NO;
_didRegisterForNodeDisplayNotifications = NO;

[self scheduleRangeUpdate];
}
Expand Down
5 changes: 5 additions & 0 deletions AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,11 @@ inline BOOL ASHierarchyStateIncludesRangeManaged(ASHierarchyState hierarchyState
*/
@property (nonatomic, assign) BOOL shouldBypassEnsureDisplay;

/**
* @abstract Checks whether a node should be scheduled for display, considering its current and new interface states.
*/
- (BOOL)shouldScheduleDisplayWithNewInterfaceState:(ASInterfaceState)newInterfaceState;

@end

@interface UIView (ASDisplayNodeInternal)
Expand Down