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

Commit

Permalink
[ASDisplayNode] Always layout nodes on a background thread (#1907)
Browse files Browse the repository at this point in the history
* Always layout nodes on a background thread

* Remove semaphore in ASDataController for allocating nodes and layout

* Fix variable not used error

* Remove overhead to create subarray of contexts of nodes while layout nodes

* Remove extra allocation of allocatedNodes and indexPaths array
  • Loading branch information
maicki authored and appleguy committed Jul 15, 2016
1 parent f95790f commit a8c5ac1
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 72 deletions.
2 changes: 1 addition & 1 deletion AsyncDisplayKit/ASDisplayNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -791,7 +791,7 @@ - (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize
};

// TODO ihm: Can we always push the measure to the background thread and remove the parameter from the API?
if (shouldMeasureAsync) {
if (ASDisplayNodeThreadIsMain()) {
ASPerformBlockOnBackgroundThread(transitionBlock);
} else {
transitionBlock();
Expand Down
102 changes: 31 additions & 71 deletions AsyncDisplayKit/Details/ASDataController.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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
// 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];
}
}

Expand All @@ -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;

[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);
}
}
Expand Down

0 comments on commit a8c5ac1

Please sign in to comment.