This repository has been archived by the owner on Feb 2, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
[ASDisplayNode] Always layout nodes on a background thread #1907
Merged
appleguy
merged 5 commits into
facebookarchive:master
from
maicki:MSLayoutNodesAsynchronously
Jul 15, 2016
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
e1557df
Always layout nodes on a background thread
maicki 3f44959
Remove semaphore in ASDataController for allocating nodes and layout
maicki e52f020
Fix variable not used error
maicki 10ccfc1
Remove overhead to create subarray of contexts of nodes while layout …
maicki b2182f1
Remove extra allocation of allocatedNodes and indexPaths array
maicki File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -165,16 +165,12 @@ - (void)_layoutNodes:(NSArray<ASCellNode *> *)nodes fromContexts:(NSArray<ASInde | |
return; | ||
} | ||
|
||
// For any given layout pass that occurs, this method will be called at least twice, once on the main thread and | ||
// the background, to result in complete coverage of both loaded and unloaded nodes | ||
BOOL isMainThread = ASDisplayNodeThreadIsMain(); | ||
// Layout node on whatever thread we are on. We handle the trampoline to the main thread in case the node is | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't you mean ASDisplayNode (rather than We = ASDataController) handles the trampoline? |
||
// already loaded | ||
for (NSUInteger k = range.location; k < NSMaxRange(range); k++) { | ||
ASCellNode *node = nodes[k]; | ||
// Only nodes that are loaded should be layout on the main thread | ||
if (node.isNodeLoaded == isMainThread) { | ||
ASIndexedNodeContext *context = contexts[k]; | ||
[self _layoutNode:node withConstrainedSize:context.constrainedSize]; | ||
} | ||
ASIndexedNodeContext *context = contexts[k]; | ||
[self _layoutNode:node withConstrainedSize:context.constrainedSize]; | ||
} | ||
} | ||
|
||
|
@@ -187,78 +183,42 @@ - (void)_layoutNodesFromContexts:(NSArray<ASIndexedNodeContext *> *)contexts ofK | |
} | ||
|
||
NSUInteger nodeCount = contexts.count; | ||
NSMutableArray<ASCellNode *> *allocatedNodes = [NSMutableArray<ASCellNode *> arrayWithCapacity:nodeCount]; | ||
dispatch_group_t layoutGroup = dispatch_group_create(); | ||
__strong NSIndexPath **allocatedContextIndexPaths = (__strong NSIndexPath **)calloc(nodeCount, sizeof(NSIndexPath *)); | ||
__strong ASCellNode **allocatedNodeBuffer = (__strong ASCellNode **)calloc(nodeCount, sizeof(ASCellNode *)); | ||
|
||
for (NSUInteger j = 0; j < nodeCount; j += kASDataControllerSizingCountPerProcessor) { | ||
NSInteger batchCount = MIN(kASDataControllerSizingCountPerProcessor, nodeCount - j); | ||
|
||
// Allocate nodes concurrently. | ||
__block NSArray *subarrayOfContexts; | ||
__block NSArray *subarrayOfNodes; | ||
dispatch_block_t allocationBlock = ^{ | ||
__strong ASIndexedNodeContext **allocatedContextBuffer = (__strong ASIndexedNodeContext **)calloc(batchCount, sizeof(ASIndexedNodeContext *)); | ||
__strong ASCellNode **allocatedNodeBuffer = (__strong ASCellNode **)calloc(batchCount, sizeof(ASCellNode *)); | ||
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); | ||
dispatch_apply(batchCount, queue, ^(size_t i) { | ||
unsigned long k = j + i; | ||
ASIndexedNodeContext *context = contexts[k]; | ||
ASCellNode *node = [context allocateNode]; | ||
if (node == nil) { | ||
ASDisplayNodeAssertNotNil(node, @"Node block created nil node; %@, %@", self, self.dataSource); | ||
node = [[ASCellNode alloc] init]; // Fallback to avoid crash for production apps. | ||
} | ||
allocatedNodeBuffer[i] = node; | ||
allocatedContextBuffer[i] = context; | ||
}); | ||
subarrayOfNodes = [NSArray arrayWithObjects:allocatedNodeBuffer count:batchCount]; | ||
subarrayOfContexts = [NSArray arrayWithObjects:allocatedContextBuffer count:batchCount]; | ||
// Nil out buffer indexes to allow arc to free the stored cells. | ||
for (int i = 0; i < batchCount; i++) { | ||
allocatedContextBuffer[i] = nil; | ||
allocatedNodeBuffer[i] = nil; | ||
|
||
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); | ||
dispatch_apply(batchCount, queue, ^(size_t i) { | ||
unsigned long k = j + i; | ||
ASIndexedNodeContext *context = contexts[k]; | ||
ASCellNode *node = [context allocateNode]; | ||
if (node == nil) { | ||
ASDisplayNodeAssertNotNil(node, @"Node block created nil node; %@, %@", self, self.dataSource); | ||
node = [[ASCellNode alloc] init]; // Fallback to avoid crash for production apps. | ||
} | ||
free(allocatedContextBuffer); | ||
free(allocatedNodeBuffer); | ||
}; | ||
|
||
// Run the allocation block to concurrently create the cell nodes. Then, handle layout for nodes that are already loaded | ||
// (e.g. the dataSource may have provided cells that have been used before), which must do layout on the main thread. | ||
NSRange batchRange = NSMakeRange(0, batchCount); | ||
if (ASDisplayNodeThreadIsMain()) { | ||
dispatch_semaphore_t sema = dispatch_semaphore_create(0); | ||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ | ||
allocationBlock(); | ||
dispatch_semaphore_signal(sema); | ||
}); | ||
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); | ||
|
||
allocatedContextIndexPaths[k] = context.indexPath; | ||
allocatedNodeBuffer[k] = node; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. later, seems like we don't need a separate node buffer if we have the contexts, if the context contains the allocated node (maybe there is a retain issue with this, not sure) |
||
|
||
[self _layoutNodes:subarrayOfNodes fromContexts:subarrayOfContexts atIndexesOfRange:batchRange ofKind:kind]; | ||
} else { | ||
allocationBlock(); | ||
[_mainSerialQueue performBlockOnMainThread:^{ | ||
[self _layoutNodes:subarrayOfNodes fromContexts:subarrayOfContexts atIndexesOfRange:batchRange ofKind:kind]; | ||
}]; | ||
} | ||
|
||
[allocatedNodes addObjectsFromArray:subarrayOfNodes]; | ||
|
||
dispatch_group_async(layoutGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ | ||
// We should already have measured loaded nodes before we left the main thread. Layout the remaining ones on a background thread. | ||
NSRange asyncBatchRange = NSMakeRange(j, batchCount); | ||
[self _layoutNodes:allocatedNodes fromContexts:contexts atIndexesOfRange:asyncBatchRange ofKind:kind]; | ||
[self _layoutNode:node withConstrainedSize:context.constrainedSize]; | ||
}); | ||
} | ||
|
||
// Block the _editingTransactionQueue from executing a new edit transaction until layout is done & _editingNodes array is updated. | ||
dispatch_group_wait(layoutGroup, DISPATCH_TIME_FOREVER); | ||
|
||
if (completionBlock) { | ||
NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:nodeCount]; | ||
for (ASIndexedNodeContext *context in contexts) { | ||
[indexPaths addObject:context.indexPath]; | ||
} | ||
// Create nodes and indexPaths array's | ||
NSArray *allocatedNodes = [NSArray arrayWithObjects:allocatedNodeBuffer count:nodeCount]; | ||
NSArray *indexPaths = [NSArray arrayWithObjects:allocatedContextIndexPaths count:nodeCount]; | ||
|
||
// Nil out buffer indexes to allow arc to free the stored cells. | ||
for (int i = 0; i < nodeCount; i++) { | ||
allocatedContextIndexPaths[i] = nil; | ||
allocatedNodeBuffer[i] = nil; | ||
} | ||
free(allocatedContextIndexPaths); | ||
free(allocatedNodeBuffer); | ||
|
||
if (completionBlock) { | ||
completionBlock(allocatedNodes, indexPaths); | ||
} | ||
} | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this code already checks if it is on a background thread and performs it inline if so - maybe not?