Skip to content

Commit

Permalink
Refactor the run loop implementation code into a proper queue abstrac…
Browse files Browse the repository at this point in the history
…tion.

ASRunLoopQueue can process a specified number of items per run loop iteration
via a block based api.
  • Loading branch information
rahul-malik committed Mar 8, 2016
1 parent 9e72679 commit f388180
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 67 deletions.
8 changes: 8 additions & 0 deletions AsyncDisplayKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -722,6 +724,8 @@
68EE0DBC1C1B4ED300BA1B99 /* ASMainSerialQueue.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMainSerialQueue.mm; sourceTree = "<group>"; };
69F10C851C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASRangeControllerUpdateRangeProtocol+Beta.h"; sourceTree = "<group>"; };
6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.c.h; path = AsyncDisplayKit.h; sourceTree = "<group>"; };
81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRunLoopQueue.h; sourceTree = "<group>"; };
81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRunLoopQueue.mm; sourceTree = "<group>"; };
92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMapNode.h; sourceTree = "<group>"; };
92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMapNode.mm; sourceTree = "<group>"; };
92DD2FE51BF4D05E0074C9DD /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; };
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down
82 changes: 15 additions & 67 deletions AsyncDisplayKit/ASDisplayNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#import "_ASCoreAnimationExtras.h"
#import "ASDisplayNodeExtras.h"
#import "ASEqualityHelpers.h"
#import "ASRunLoopQueue.h"
#import "NSArray+Diffing.h"

#import "ASInternalHelpers.h"
Expand Down Expand Up @@ -212,75 +213,22 @@ + (Class)layerClass

+ (void)scheduleNodeForRecursiveDisplay:(ASDisplayNode *)node
{

static ASDN::RecursiveMutex __displaySchedulerLock;
static std::deque<ASDisplayNode *> __renderQueue = std::deque<ASDisplayNode *>();
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<ASDisplayNode *> *renderQueue;
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) {
[[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<ASDisplayNode *> displayingNodes = std::deque<ASDisplayNode *>();
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
Expand Down
23 changes: 23 additions & 0 deletions AsyncDisplayKit/ASRunLoopQueue.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// ASRunLoopQueue.h
// AsyncDisplayKit
//
// Created by Rahul Malik on 3/7/16.
// Copyright © 2016 Facebook. All rights reserved.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface ASRunLoopQueue<ObjectType> : 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
107 changes: 107 additions & 0 deletions AsyncDisplayKit/ASRunLoopQueue.mm
Original file line number Diff line number Diff line change
@@ -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 <deque>

@interface ASRunLoopQueue () {
CFRunLoopRef _runLoop;
CFRunLoopObserverRef _runLoopObserver;
std::deque<id> _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<id>();
_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<id> itemsToProcess = std::deque<id>();
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

0 comments on commit f388180

Please sign in to comment.