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

Commit d708874

Browse files
committed
Merge pull request #1101 from facebook/ASRangeControllerPrioritization
[ASRangeControllerBeta] Utilize NSMutableOrderedSet to ensure visible items are prioritized.
2 parents 344b3a4 + 92ce6ce commit d708874

1 file changed

Lines changed: 37 additions & 24 deletions

File tree

AsyncDisplayKit/Details/ASRangeControllerBeta.mm

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ @interface ASRangeControllerBeta ()
2121
{
2222
BOOL _rangeIsValid;
2323
BOOL _queuedRangeUpdate;
24+
BOOL _layoutControllerImplementsSetVisibleIndexPaths;
2425
ASScrollDirection _scrollDirection;
25-
NSSet *_allPreviousIndexPaths;
26+
NSSet<NSIndexPath *> *_allPreviousIndexPaths;
2627
}
2728

2829
@end
@@ -58,41 +59,50 @@ - (void)visibleNodeIndexPathsDidChangeWithScrollDirection:(ASScrollDirection)scr
5859
});
5960
}
6061

62+
- (void)setLayoutController:(id<ASLayoutController>)layoutController
63+
{
64+
_layoutController = layoutController;
65+
_layoutControllerImplementsSetVisibleIndexPaths = [_layoutController respondsToSelector:@selector(setVisibleNodeIndexPaths:)];
66+
}
67+
6168
- (void)_updateVisibleNodeIndexPaths
6269
{
63-
if (!_queuedRangeUpdate) {
70+
ASDisplayNodeAssert(_layoutController, @"An ASLayoutController is required by ASRangeController");
71+
if (!_queuedRangeUpdate || !_layoutController) {
6472
return;
6573
}
6674

67-
// FIXME: Consider if we need to check this separately from the range calculation below.
68-
NSArray *visibleNodePaths = [_dataSource visibleNodeIndexPathsForRangeController:self];
75+
// TODO: Consider if we need to use this codepath, or can rely on something more similar to the data & display ranges
76+
// Example: ... = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeVisible];
77+
NSArray<NSIndexPath *> *visibleNodePaths = [_dataSource visibleNodeIndexPathsForRangeController:self];
6978

7079
if (visibleNodePaths.count == 0) { // if we don't have any visibleNodes currently (scrolled before or after content)...
7180
_queuedRangeUpdate = NO;
7281
return; // don't do anything for this update, but leave _rangeIsValid == NO to make sure we update it later
7382
}
7483

75-
CGSize viewportSize = [_dataSource viewportSizeForRangeController:self];
76-
[_layoutController setViewportSize:viewportSize];
84+
[_layoutController setViewportSize:[_dataSource viewportSizeForRangeController:self]];
7785

7886
// the layout controller needs to know what the current visible indices are to calculate range offsets
79-
if ([_layoutController respondsToSelector:@selector(setVisibleNodeIndexPaths:)]) {
87+
if (_layoutControllerImplementsSetVisibleIndexPaths) {
8088
[_layoutController setVisibleNodeIndexPaths:visibleNodePaths];
8189
}
8290

83-
NSArray *allNodes = [_dataSource completedNodes];
84-
NSArray *currentSectionNodes = nil;
85-
NSInteger currentSectionIndex = -1; // Will be unequal to any indexPath.section, so we set currentSectionNodes.
86-
91+
// allNodes is a 2D array: it contains arrays for each section, each containing nodes.
92+
NSArray<NSArray *> *allNodes = [_dataSource completedNodes];
8793
NSUInteger numberOfSections = [allNodes count];
94+
95+
NSArray<ASDisplayNode *> *currentSectionNodes = nil;
96+
NSInteger currentSectionIndex = -1; // Set to -1 so we don't match any indexPath.section on the first iteration.
8897
NSUInteger numberOfNodesInSection = 0;
8998

90-
NSSet *visibleIndexPaths = [NSSet setWithArray:visibleNodePaths];
91-
// = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeVisible];
92-
NSSet *displayIndexPaths = nil;
93-
NSSet *fetchDataIndexPaths = nil;
94-
NSMutableSet *allIndexPaths = nil;
95-
NSMutableArray *modifiedIndexPaths = (RangeControllerLoggingEnabled ? [NSMutableArray array] : nil);
99+
NSSet<NSIndexPath *> *visibleIndexPaths = [NSSet setWithArray:visibleNodePaths];
100+
NSSet<NSIndexPath *> *displayIndexPaths = nil;
101+
NSSet<NSIndexPath *> *fetchDataIndexPaths = nil;
102+
103+
// Prioritize the order in which we visit each. Visible nodes should be updated first so they are enqueued on
104+
// the network or display queues before preloading (offscreen) nodes are enqueued.
105+
NSMutableOrderedSet<NSIndexPath *> *allIndexPaths = [[NSMutableOrderedSet alloc] initWithSet:visibleIndexPaths];
96106

97107
ASInterfaceState selfInterfaceState = [_dataSource interfaceStateForRangeController:self];
98108

@@ -102,20 +112,23 @@ - (void)_updateVisibleNodeIndexPaths
102112
displayIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeDisplay];
103113

104114
// Typically the fetchDataIndexPaths will be the largest, and be a superset of the others, though it may be disjoint.
105-
allIndexPaths = [fetchDataIndexPaths mutableCopy];
115+
// Because allIndexPaths is an NSMutableOrderedSet, this adds the non-duplicate items /after/ the existing items.
116+
// This means that during iteration, we will first visit visible, then display, then fetch data nodes.
106117
[allIndexPaths unionSet:displayIndexPaths];
107-
[allIndexPaths unionSet:visibleIndexPaths];
108-
} else {
109-
allIndexPaths = [visibleIndexPaths mutableCopy];
118+
[allIndexPaths unionSet:fetchDataIndexPaths];
110119
}
111120

112-
// Sets are magical. Add anything we had applied interfaceState to in the last update, so we can clear any
121+
// Add anything we had applied interfaceState to in the last update, but is no longer in range, so we can clear any
113122
// range flags it still has enabled. Most of the time, all but a few elements are equal; a large programmatic
114-
// scroll or major main thread stall could cause entirely disjoint sets, but we must visit all.
115-
NSSet *allCurrentIndexPaths = [allIndexPaths copy];
123+
// scroll or major main thread stall could cause entirely disjoint sets. In either case we must visit all.
124+
// Calling "-set" on NSMutableOrderedSet just references the underlying mutable data store, so we must copy it.
125+
NSSet<NSIndexPath *> *allCurrentIndexPaths = [[allIndexPaths set] copy];
116126
[allIndexPaths unionSet:_allPreviousIndexPaths];
117127
_allPreviousIndexPaths = allCurrentIndexPaths;
118128

129+
// This array is only used if logging is enabled.
130+
NSMutableArray<NSIndexPath *> *modifiedIndexPaths = (RangeControllerLoggingEnabled ? [NSMutableArray array] : nil);
131+
119132
for (NSIndexPath *indexPath in allIndexPaths) {
120133
// Before a node / indexPath is exposed to ASRangeController, ASDataController should have already measured it.
121134
// For consistency, make sure each node knows that it should measure itself if something changes.

0 commit comments

Comments
 (0)