diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 2d9f279763..2efe52160e 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -261,6 +261,8 @@ 69F10C861C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = 69F10C851C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; 69F10C871C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = 69F10C851C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6BDC61F61979037800E50D21 /* AsyncDisplayKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 81EE384F1C8E94F000456208 /* ASRunLoopQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */; }; + 81EE38501C8E94F000456208 /* ASRunLoopQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */; }; 92DD2FE31BF4B97E0074C9DD /* ASMapNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 92DD2FE41BF4B97E0074C9DD /* ASMapNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */; }; 92DD2FE61BF4D05E0074C9DD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */; }; @@ -722,6 +724,8 @@ 68EE0DBC1C1B4ED300BA1B99 /* ASMainSerialQueue.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMainSerialQueue.mm; sourceTree = ""; }; 69F10C851C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASRangeControllerUpdateRangeProtocol+Beta.h"; sourceTree = ""; }; 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.c.h; path = AsyncDisplayKit.h; sourceTree = ""; }; + 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRunLoopQueue.h; sourceTree = ""; }; + 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRunLoopQueue.mm; sourceTree = ""; }; 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMapNode.h; sourceTree = ""; }; 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMapNode.mm; sourceTree = ""; }; 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; }; @@ -1003,6 +1007,8 @@ 058D09E0195D050800B7D73C /* ASTextNode.mm */, ACC945A81BA9E7A0005E1FB8 /* ASViewController.h */, ACC945AA1BA9E7C1005E1FB8 /* ASViewController.m */, + 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */, + 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */, 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */, DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */, 058D09E1195D050800B7D73C /* Details */, @@ -1460,6 +1466,7 @@ 257754C11BEE458E00737CA5 /* ASTextKitHelpers.h in Headers */, B30BF6521C5964B0004FCD53 /* ASLayoutManager.h in Headers */, 0574D5E219C110940097DC25 /* ASTableViewProtocols.h in Headers */, + 81EE384F1C8E94F000456208 /* ASRunLoopQueue.h in Headers */, CC3B20831C3F76D600798563 /* ASPendingStateController.h in Headers */, 058D0A51195D05CB00B7D73C /* ASTextNode.h in Headers */, 058D0A81195D05F900B7D73C /* ASThread.h in Headers */, @@ -1883,6 +1890,7 @@ ACF6ED501B17847A00DA7C62 /* ASStackPositionedLayout.mm in Sources */, ACF6ED521B17847A00DA7C62 /* ASStackUnpositionedLayout.mm in Sources */, 257754A61BEE44CD00737CA5 /* ASTextKitAttributes.mm in Sources */, + 81EE38501C8E94F000456208 /* ASRunLoopQueue.mm in Sources */, ACF6ED321B17843500DA7C62 /* ASStaticLayoutSpec.mm in Sources */, AC026B6B1BD57D6F00BBC17E /* ASChangeSetDataController.m in Sources */, 055F1A3519ABD3E3004DAFF1 /* ASTableView.mm in Sources */, diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 6db7219703..ebedf8aef1 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -22,6 +22,7 @@ #import "_ASCoreAnimationExtras.h" #import "ASDisplayNodeExtras.h" #import "ASEqualityHelpers.h" +#import "ASRunLoopQueue.h" #import "NSArray+Diffing.h" #import "ASInternalHelpers.h" @@ -212,75 +213,22 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) + (void)scheduleNodeForRecursiveDisplay:(ASDisplayNode *)node { - - static ASDN::RecursiveMutex __displaySchedulerLock; - static std::deque __renderQueue = std::deque(); - static CFRunLoopObserverRef __mainRunLoopObserver = NULL; - static NSUInteger __renderBatchSize = 1; - { - ASDN::MutexLocker l(__displaySchedulerLock); - - // Check if the node exists. - BOOL foundNode = NO; - for (ASDisplayNode *currentNode : __renderQueue) { - if (currentNode == node) { - foundNode = YES; - break; + static dispatch_once_t onceToken; + static ASRunLoopQueue *renderQueue; + dispatch_once(&onceToken, ^{ + renderQueue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() + andHandler:^(ASDisplayNode * _Nonnull dequeuedItem, BOOL isQueueDrained) { + CFAbsoluteTime timestamp = isQueueDrained ? CFAbsoluteTimeGetCurrent() : 0; + [dequeuedItem __recursivelyTriggerDisplayAndBlock:NO]; + if (isQueueDrained) { + [[NSNotificationCenter defaultCenter] postNotificationName:ASRenderingEngineDidDisplayScheduledNodesNotification + object:nil + userInfo:@{ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp: [NSNumber numberWithDouble:timestamp]}]; } - } + }]; + }); - if (!foundNode) { - __renderQueue.push_back(node); - } - - if (!__mainRunLoopObserver) { - - void (^handlerBlock) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) = ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { - std::deque displayingNodes = std::deque(); - BOOL isQueueDrained = NO; - CFAbsoluteTime timestamp = 0; - - // Create a lock scope. - { - ASDN::MutexLocker l(__displaySchedulerLock); - - // Early-exit if we don't have any nodes to render. - if (__renderQueue.empty()) { - return; - } - - // Snatch the next batch of nodes. - NSUInteger totalNodeCount = __renderQueue.size(); - for (int i = 0; i < MIN(__renderBatchSize, totalNodeCount); i++) { - ASDisplayNode *node = __renderQueue[0]; - displayingNodes.push_back(node); - __renderQueue.pop_front(); - } - - if (__renderQueue.empty()) { - isQueueDrained = YES; - timestamp = CFAbsoluteTimeGetCurrent(); - } - } - - for (ASDisplayNode *node : displayingNodes) { - [node __recursivelyTriggerDisplayAndBlock:NO]; - } - - if (isQueueDrained) { - [[NSNotificationCenter defaultCenter] postNotificationName:ASRenderingEngineDidDisplayScheduledNodesNotification - object:nil - userInfo:@{ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp: [NSNumber numberWithDouble:timestamp]}]; - } - }; - - // Scheduling in kCFRunLoopBeforeWaiting to allow timers and other sources to process first. - __mainRunLoopObserver = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, 0, handlerBlock); - CFRunLoopAddObserver(CFRunLoopGetMain(), - __mainRunLoopObserver, - kCFRunLoopCommonModes); - } - } + [renderQueue enqueue:node]; } #pragma mark - Lifecycle diff --git a/AsyncDisplayKit/ASRunLoopQueue.h b/AsyncDisplayKit/ASRunLoopQueue.h new file mode 100644 index 0000000000..7335528a2c --- /dev/null +++ b/AsyncDisplayKit/ASRunLoopQueue.h @@ -0,0 +1,23 @@ +// +// ASRunLoopQueue.h +// AsyncDisplayKit +// +// Created by Rahul Malik on 3/7/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface ASRunLoopQueue : NSObject + +- (instancetype)initWithRunLoop:(CFRunLoopRef)runloop andHandler:(void(^)(ObjectType dequeuedItem, BOOL isQueueDrained))handlerBlock; + +- (void)enqueue:(ObjectType)object; + +@property (nonatomic, assign) NSUInteger batchSize; + +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/AsyncDisplayKit/ASRunLoopQueue.mm b/AsyncDisplayKit/ASRunLoopQueue.mm new file mode 100644 index 0000000000..9b8bee4e24 --- /dev/null +++ b/AsyncDisplayKit/ASRunLoopQueue.mm @@ -0,0 +1,107 @@ +// +// ASRunLoopQueue.m +// AsyncDisplayKit +// +// Created by Rahul Malik on 3/7/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASRunLoopQueue.h" +#import "ASThread.h" + +#import + +@interface ASRunLoopQueue () { + CFRunLoopRef _runLoop; + CFRunLoopObserverRef _runLoopObserver; + std::deque _internalQueue; + ASDN::RecursiveMutex _internalQueueLock; +} + +@property (nonatomic, copy) void (^queueConsumer)(id dequeuedItem, BOOL isQueueDrained); + +@end + +@implementation ASRunLoopQueue + +- (instancetype)initWithRunLoop:(CFRunLoopRef)runloop andHandler:(void(^)(id dequeuedItem, BOOL isQueueDrained))handlerBlock +{ + if (self = [super init]) { + _runLoop = runloop; + _internalQueue = std::deque(); + _queueConsumer = [handlerBlock copy]; + _batchSize = 1; + void (^handlerBlock) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) = ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { + [self processQueue]; + }; + _runLoopObserver = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, 0, handlerBlock); + CFRunLoopAddObserver(_runLoop, _runLoopObserver, kCFRunLoopCommonModes); + } + return self; +} + +- (void)dealloc +{ + if (CFRunLoopObserverIsValid(_runLoopObserver)) { + CFRunLoopObserverInvalidate(_runLoopObserver); + } + CFRelease(_runLoopObserver); + _runLoopObserver = nil; +} + +- (void)processQueue +{ + std::deque itemsToProcess = std::deque(); + BOOL isQueueDrained = NO; + CFAbsoluteTime timestamp = 0; + { + ASDN::MutexLocker l(_internalQueueLock); + + // Early-exit if the queue is empty. + if (_internalQueue.empty()) { + return; + } + + // Snatch the next batch of items. + NSUInteger totalNodeCount = _internalQueue.size(); + for (int i = 0; i < MIN(self.batchSize, totalNodeCount); i++) { + id node = _internalQueue[0]; + itemsToProcess.push_back(node); + _internalQueue.pop_front(); + } + + if (_internalQueue.empty()) { + isQueueDrained = YES; + timestamp = CFAbsoluteTimeGetCurrent(); + } + } + + unsigned long numberOfItems = itemsToProcess.size(); + for (int i = 0; i < numberOfItems; i++) { + if (isQueueDrained && i == numberOfItems - 1) { + self.queueConsumer(itemsToProcess[i], YES); + } else { + self.queueConsumer(itemsToProcess[i], isQueueDrained); + } + } +} + +- (void)enqueue:(id)object +{ + ASDN::MutexLocker l(_internalQueueLock); + + // Check if the object exists. + BOOL foundObject = NO; + for (id currentObject : _internalQueue) { + if (currentObject == object) { + foundObject = YES; + break; + } + } + + if (!foundObject) { + _internalQueue.push_back(object); + } +} + +@end