Improvements to the efficiency of recursivelySetInterfaceState: and the beta range controller.

This commit is contained in:
Scott Goodson 2016-01-10 02:33:34 -08:00
parent 03d13b19b0
commit 0feaa2a368
9 changed files with 103 additions and 47 deletions

View File

@ -495,6 +495,13 @@
remoteGlobalIDString = 058D09AB195D04C000B7D73C; remoteGlobalIDString = 058D09AB195D04C000B7D73C;
remoteInfo = AsyncDisplayKit; remoteInfo = AsyncDisplayKit;
}; };
DEACA2B11C425DC400FA9DDF /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 058D09A4195D04C000B7D73C /* Project object */;
proxyType = 1;
remoteGlobalIDString = 058D09AB195D04C000B7D73C;
remoteInfo = AsyncDisplayKit;
};
/* End PBXContainerItemProxy section */ /* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */ /* Begin PBXCopyFilesBuildPhase section */
@ -1524,6 +1531,7 @@
buildRules = ( buildRules = (
); );
dependencies = ( dependencies = (
DEACA2B21C425DC400FA9DDF /* PBXTargetDependency */,
); );
name = AsyncDisplayKitTestHost; name = AsyncDisplayKitTestHost;
productName = AsyncDisplayKitTestHost; productName = AsyncDisplayKitTestHost;
@ -1939,6 +1947,11 @@
target = 058D09AB195D04C000B7D73C /* AsyncDisplayKit */; target = 058D09AB195D04C000B7D73C /* AsyncDisplayKit */;
targetProxy = 058D09C2195D04C000B7D73C /* PBXContainerItemProxy */; targetProxy = 058D09C2195D04C000B7D73C /* PBXContainerItemProxy */;
}; };
DEACA2B21C425DC400FA9DDF /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 058D09AB195D04C000B7D73C /* AsyncDisplayKit */;
targetProxy = DEACA2B11C425DC400FA9DDF /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */ /* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */ /* Begin PBXVariantGroup section */

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded</key>
<false/>
</dict>
</plist>

View File

@ -198,12 +198,14 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
return [_ASDisplayLayer class]; return [_ASDisplayLayer class];
} }
+ (void)scheduleNodeForDisplay:(ASDisplayNode *)node + (void)scheduleNodeForRecursiveDisplay:(ASDisplayNode *)node
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
ASDisplayNodeAssert([ASDisplayNode shouldUseNewRenderingRange], @"+scheduleNodeForRecursiveDisplay: should never be called without the new rendering range enabled");
static NSMutableSet *nodesToDisplay = nil; static NSMutableSet *nodesToDisplay = nil;
static BOOL displayScheduled = NO; static BOOL displayScheduled = NO;
static ASDN::RecursiveMutex displaySchedulerLock; static ASDN::RecursiveMutex displaySchedulerLock;
{ {
ASDN::MutexLocker l(displaySchedulerLock); ASDN::MutexLocker l(displaySchedulerLock);
if (!nodesToDisplay) { if (!nodesToDisplay) {
@ -211,6 +213,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
} }
[nodesToDisplay addObject:node]; [nodesToDisplay addObject:node];
} }
if (!displayScheduled) { if (!displayScheduled) {
displayScheduled = YES; displayScheduled = YES;
// It's essenital that any layout pass that is scheduled during the current // It's essenital that any layout pass that is scheduled during the current
@ -1557,13 +1560,11 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
- (void)setShouldBypassEnsureDisplay:(BOOL)shouldBypassEnsureDisplay - (void)setShouldBypassEnsureDisplay:(BOOL)shouldBypassEnsureDisplay
{ {
ASDN::MutexLocker l(_propertyLock);
_flags.shouldBypassEnsureDisplay = shouldBypassEnsureDisplay; _flags.shouldBypassEnsureDisplay = shouldBypassEnsureDisplay;
} }
- (BOOL)shouldBypassEnsureDisplay - (BOOL)shouldBypassEnsureDisplay
{ {
ASDN::MutexLocker l(_propertyLock);
return _flags.shouldBypassEnsureDisplay; return _flags.shouldBypassEnsureDisplay;
} }
@ -1612,7 +1613,7 @@ static BOOL ShouldUseNewRenderingRange = NO;
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{ {
ASDisplayNodeAssertThreadAffinity(self); ASDisplayNodeAssertThreadAffinity(self);
return [ASLayoutSpec new]; return nil;
} }
- (ASLayout *)calculatedLayout - (ASLayout *)calculatedLayout
@ -1772,7 +1773,7 @@ static BOOL ShouldUseNewRenderingRange = NO;
oldState = _interfaceState; oldState = _interfaceState;
_interfaceState = newState; _interfaceState = newState;
} }
if ((newState & ASInterfaceStateMeasureLayout) != (oldState & ASInterfaceStateMeasureLayout)) { if ((newState & ASInterfaceStateMeasureLayout) != (oldState & ASInterfaceStateMeasureLayout)) {
// Trigger asynchronous measurement if it is not already cached or being calculated. // Trigger asynchronous measurement if it is not already cached or being calculated.
} }
@ -1782,8 +1783,11 @@ static BOOL ShouldUseNewRenderingRange = NO;
// Still, the interfaceState should be updated to the current state of the node; just don't act on the transition. // Still, the interfaceState should be updated to the current state of the node; just don't act on the transition.
// Entered or exited data loading state. // Entered or exited data loading state.
if ((newState & ASInterfaceStateFetchData) != (oldState & ASInterfaceStateFetchData)) { BOOL nowFetchData = ASInterfaceStateIncludesFetchData(newState);
if (newState & ASInterfaceStateFetchData) { BOOL wasFetchData = ASInterfaceStateIncludesFetchData(oldState);
if (nowFetchData != wasFetchData) {
if (nowFetchData) {
[self fetchData]; [self fetchData];
} else { } else {
if ([self supportsRangeManagedInterfaceState]) { if ([self supportsRangeManagedInterfaceState]) {
@ -1793,21 +1797,42 @@ static BOOL ShouldUseNewRenderingRange = NO;
} }
// Entered or exited contents rendering state. // Entered or exited contents rendering state.
if ((newState & ASInterfaceStateDisplay) != (oldState & ASInterfaceStateDisplay)) { BOOL nowDisplay = ASInterfaceStateIncludesDisplay(newState);
BOOL wasDisplay = ASInterfaceStateIncludesDisplay(oldState);
if (nowDisplay != wasDisplay) {
if ([self supportsRangeManagedInterfaceState]) { if ([self supportsRangeManagedInterfaceState]) {
if (newState & ASInterfaceStateDisplay) { if (nowDisplay) {
// Once the working window is eliminated (ASRangeHandlerRender), trigger display directly here. // Once the working window is eliminated (ASRangeHandlerRender), trigger display directly here.
[self setDisplaySuspended:NO]; [self setDisplaySuspended:NO];
} else { } else {
[self setDisplaySuspended:YES]; [self setDisplaySuspended:YES];
[self clearContents]; [self clearContents];
} }
} else {
// NOTE: This case isn't currently supported as setInterfaceState: isn't exposed externally, and all
// internal use cases are range-managed. When a node is visible, don't mess with display - CA will start it.
if ([ASDisplayNode shouldUseNewRenderingRange] && !ASInterfaceStateIncludesVisible(newState)) {
// Check __implementsDisplay purely for efficiency - it's faster even than calling -asyncLayer.
if ([self __implementsDisplay]) {
if (nowDisplay) {
[ASDisplayNode scheduleNodeForRecursiveDisplay:self];
} else {
[[self asyncLayer] cancelAsyncDisplay];
[self clearContents];
}
}
}
} }
} }
// Entered or exited data loading state. // Became visible or invisible. When range-managed, this represents literal visibility - at least one pixel
if ((newState & ASInterfaceStateVisible) != (oldState & ASInterfaceStateVisible)) { // is onscreen. If not range-managed, we can't guarantee more than the node being present in an onscreen window.
if (newState & ASInterfaceStateVisible) { BOOL nowVisible = ASInterfaceStateIncludesVisible(newState);
BOOL wasVisible = ASInterfaceStateIncludesVisible(oldState);
if (nowVisible != wasVisible) {
if (nowVisible) {
[self visibilityDidChange:YES]; [self visibilityDidChange:YES];
} else { } else {
[self visibilityDidChange:NO]; [self visibilityDidChange:NO];
@ -1843,11 +1868,23 @@ static BOOL ShouldUseNewRenderingRange = NO;
- (void)recursivelySetInterfaceState:(ASInterfaceState)interfaceState - (void)recursivelySetInterfaceState:(ASInterfaceState)interfaceState
{ {
ASInterfaceState oldState = self.interfaceState;
ASInterfaceState newState = interfaceState;
ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) { ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) {
node.interfaceState = interfaceState; node.interfaceState = interfaceState;
}); });
// FIXME: This should also be called in setInterfaceState: if it isn't being applied recursively.
[ASDisplayNode scheduleNodeForDisplay:self]; if ([self supportsRangeManagedInterfaceState]) {
// Instead of each node in the recursion assuming it needs to schedule itself for display,
// setInterfaceState: skips this when handling range-managed nodes (our whole subtree has this set).
// If our range manager intends for us to be displayed right now, and didn't before, get started!
BOOL nowDisplay = ASInterfaceStateIncludesDisplay(newState);
BOOL wasDisplay = ASInterfaceStateIncludesDisplay(oldState);
if (nowDisplay && (nowDisplay != wasDisplay)) {
[ASDisplayNode scheduleNodeForRecursiveDisplay:self];
}
}
} }
- (ASHierarchyState)hierarchyState - (ASHierarchyState)hierarchyState

View File

@ -12,6 +12,23 @@
#import <AsyncDisplayKit/ASBaseDefines.h> #import <AsyncDisplayKit/ASBaseDefines.h>
#import <AsyncDisplayKit/ASDisplayNode.h> #import <AsyncDisplayKit/ASDisplayNode.h>
// Because inline methods can't be extern'd and need to be part of the translation unit of code
// that compiles with them to actually inline, we both declare and define these in the header.
inline BOOL ASInterfaceStateIncludesVisible(ASInterfaceState interfaceState)
{
return ((interfaceState & ASInterfaceStateVisible) == ASInterfaceStateVisible);
}
inline BOOL ASInterfaceStateIncludesDisplay(ASInterfaceState interfaceState)
{
return ((interfaceState & ASInterfaceStateDisplay) == ASInterfaceStateDisplay);
}
inline BOOL ASInterfaceStateIncludesFetchData(ASInterfaceState interfaceState)
{
return ((interfaceState & ASInterfaceStateFetchData) == ASInterfaceStateFetchData);
}
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
ASDISPLAYNODE_EXTERN_C_BEGIN ASDISPLAYNODE_EXTERN_C_BEGIN

View File

@ -17,21 +17,6 @@
#import "ASInternalHelpers.h" #import "ASInternalHelpers.h"
#import "ASDisplayNode+FrameworkPrivate.h" #import "ASDisplayNode+FrameworkPrivate.h"
extern BOOL ASInterfaceStateIncludesVisible(ASInterfaceState interfaceState)
{
return ((interfaceState & ASInterfaceStateVisible) == ASInterfaceStateVisible);
}
extern BOOL ASInterfaceStateIncludesDisplay(ASInterfaceState interfaceState)
{
return ((interfaceState & ASInterfaceStateDisplay) == ASInterfaceStateDisplay);
}
extern BOOL ASInterfaceStateIncludesFetchData(ASInterfaceState interfaceState)
{
return ((interfaceState & ASInterfaceStateFetchData) == ASInterfaceStateFetchData);
}
@interface ASRangeControllerBeta () @interface ASRangeControllerBeta ()
{ {
BOOL _rangeIsValid; BOOL _rangeIsValid;

View File

@ -110,6 +110,8 @@
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
ASDN::MutexLocker l(_displaySuspendedLock); ASDN::MutexLocker l(_displaySuspendedLock);
// FIXME: Reconsider whether we should cancel a display in progress.
// We should definitely cancel a display that is scheduled, but unstarted display.
[self cancelAsyncDisplay]; [self cancelAsyncDisplay];
// Short circuit if display is suspended. When resumed, we will setNeedsDisplay at that time. // Short circuit if display is suspended. When resumed, we will setNeedsDisplay at that time.

View File

@ -39,7 +39,6 @@ static NSString * const kDefaultChildrenKey = @"kDefaultChildrenKey";
if (!(self = [super init])) { if (!(self = [super init])) {
return nil; return nil;
} }
_layoutChildren = [NSMutableDictionary dictionary];
_isMutable = YES; _isMutable = YES;
return self; return self;
} }
@ -56,11 +55,6 @@ static NSString * const kDefaultChildrenKey = @"kDefaultChildrenKey";
return self; return self;
} }
- (void)setChild:(id<ASLayoutable>)child;
{
[self setChild:child forIdentifier:kDefaultChildKey];
}
- (id<ASLayoutable>)layoutableToAddFromLayoutable:(id<ASLayoutable>)child - (id<ASLayoutable>)layoutableToAddFromLayoutable:(id<ASLayoutable>)child
{ {
if (self.isFinalLayoutable == NO) { if (self.isFinalLayoutable == NO) {
@ -88,6 +82,19 @@ static NSString * const kDefaultChildrenKey = @"kDefaultChildrenKey";
return child; return child;
} }
- (NSMutableDictionary *)layoutChildren
{
if (!_layoutChildren) {
_layoutChildren = [NSMutableDictionary dictionary];
}
return _layoutChildren;
}
- (void)setChild:(id<ASLayoutable>)child;
{
[self setChild:child forIdentifier:kDefaultChildKey];
}
- (void)setChild:(id<ASLayoutable>)child forIdentifier:(NSString *)identifier - (void)setChild:(id<ASLayoutable>)child forIdentifier:(NSString *)identifier
{ {
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");

View File

@ -11,6 +11,7 @@
#import "ASInternalHelpers.h" #import "ASInternalHelpers.h"
#import "ASAssert.h" #import "ASAssert.h"
#import "ASDisplayNodeInternal.h" #import "ASDisplayNodeInternal.h"
#import "ASDisplayNodeExtras.h"
#import "ASDisplayNode+Subclasses.h" #import "ASDisplayNode+Subclasses.h"
#import "ASDisplayNode+FrameworkPrivate.h" #import "ASDisplayNode+FrameworkPrivate.h"
#import "ASDisplayNode+Beta.h" #import "ASDisplayNode+Beta.h"
@ -250,9 +251,11 @@
_messageToViewOrLayer(setNeedsDisplay); _messageToViewOrLayer(setNeedsDisplay);
if ([ASDisplayNode shouldUseNewRenderingRange]) { if ([ASDisplayNode shouldUseNewRenderingRange]) {
BOOL shouldDisplay = ((_interfaceState & ASInterfaceStateDisplay) == ASInterfaceStateDisplay); BOOL nowDisplay = ASInterfaceStateIncludesDisplay(_interfaceState);
if (_layer && !_flags.synchronous && shouldDisplay) { // FIXME: This should not need to recursively display, so create a non-recursive variant.
[ASDisplayNode scheduleNodeForDisplay:self]; // The semantics of setNeedsDisplay (as defined by CALayer behavior) are not recursive.
if (_layer && !_flags.synchronous && nowDisplay && [self __implementsDisplay]) {
[ASDisplayNode scheduleNodeForRecursiveDisplay:self];
} }
} }
} }

View File

@ -109,7 +109,7 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides)
#endif #endif
} }
+ (void)scheduleNodeForDisplay:(ASDisplayNode *)node; + (void)scheduleNodeForRecursiveDisplay:(ASDisplayNode *)node;
// The _ASDisplayLayer backing the node, if any. // The _ASDisplayLayer backing the node, if any.
@property (nonatomic, readonly, retain) _ASDisplayLayer *asyncLayer; @property (nonatomic, readonly, retain) _ASDisplayLayer *asyncLayer;