Merge pull request #905 from facebook/RemoveWorkingWindow

[ASRangeController] Implement #if that controls removal of the offscreen UIWindow used for render-ahead.  Leave enabled for now.
This commit is contained in:
appleguy
2015-12-18 19:43:02 -08:00
8 changed files with 90 additions and 26 deletions

View File

@@ -197,6 +197,29 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
return [_ASDisplayLayer class];
}
+ (void)scheduleNodeForDisplay:(ASDisplayNode *)node
{
ASDisplayNodeAssertMainThread();
static NSMutableSet *nodesToDisplay = nil;
static BOOL displayScheduled = NO;
if (!nodesToDisplay) {
nodesToDisplay = [[NSMutableSet alloc] init];
}
[nodesToDisplay addObject:node];
if (!displayScheduled) {
displayScheduled = YES;
// It's essenital that any layout pass that is scheduled during the current
// runloop has a chance to be applied / scheduled, so always perform this after the current runloop.
dispatch_async(dispatch_get_main_queue(), ^{
displayScheduled = NO;
for (ASDisplayNode *node in nodesToDisplay) {
[node __recursivelyTriggerDisplayAndBlock:NO];
}
nodesToDisplay = nil;
});
}
}
#pragma mark - Lifecycle
- (void)_staticInitialize
@@ -710,6 +733,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
- (void)recursivelyDisplayImmediately
{
ASDN::MutexLocker l(_propertyLock);
for (ASDisplayNode *child in _subnodes) {
[child recursivelyDisplayImmediately];
}
@@ -1453,7 +1477,7 @@ static NSInteger incrementIfFound(NSInteger i) {
[_placeholderLayer removeFromSuperlayer];
}
void recursivelyEnsureDisplayForLayer(CALayer *layer)
void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
{
// This recursion must handle layers in various states:
// 1. Just added to hierarchy, CA hasn't yet called -display
@@ -1472,26 +1496,26 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer)
// Kick off the recursion first, so that all necessary display calls are sent and the displayQueue is full of parallelizable work.
for (CALayer *sublayer in layer.sublayers) {
recursivelyEnsureDisplayForLayer(sublayer);
recursivelyTriggerDisplayForLayer(sublayer, shouldBlock);
}
// As the recursion unwinds, verify each transaction is complete and block if it is not.
// While blocking on one transaction, others may be completing concurrently, so it doesn't matter which blocks first.
BOOL waitUntilComplete = (!node.shouldBypassEnsureDisplay);
if (waitUntilComplete) {
for (_ASAsyncTransaction *transaction in [layer.asyncdisplaykit_asyncLayerTransactions copy]) {
// Even if none of the layers have had a chance to start display earlier, they will still be allowed to saturate a multicore CPU while blocking main.
// This significantly reduces time on the main thread relative to UIKit.
[transaction waitUntilComplete];
if (shouldBlock) {
// As the recursion unwinds, verify each transaction is complete and block if it is not.
// While blocking on one transaction, others may be completing concurrently, so it doesn't matter which blocks first.
BOOL waitUntilComplete = (!node.shouldBypassEnsureDisplay);
if (waitUntilComplete) {
for (_ASAsyncTransaction *transaction in [layer.asyncdisplaykit_asyncLayerTransactions copy]) {
// Even if none of the layers have had a chance to start display earlier, they will still be allowed to saturate a multicore CPU while blocking main.
// This significantly reduces time on the main thread relative to UIKit.
[transaction waitUntilComplete];
}
}
}
}
- (void)recursivelyEnsureDisplay
- (void)__recursivelyTriggerDisplayAndBlock:(BOOL)shouldBlock
{
ASDisplayNodeAssertMainThread();
ASDisplayNodeAssert(self.isNodeLoaded, @"Node must have layer or view loaded to use -recursivelyEnsureDisplay");
ASDisplayNodeAssert(self.inHierarchy && (self.isLayerBacked || self.view.window != nil), @"Node must be in a hierarchy to use -recursivelyEnsureDisplay");
CALayer *layer = self.layer;
// -layoutIfNeeded is recursive, and even walks up to superlayers to check if they need layout,
@@ -1500,7 +1524,12 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer)
if ([layer needsLayout]) {
[layer layoutIfNeeded];
}
recursivelyEnsureDisplayForLayer(layer);
recursivelyTriggerDisplayForLayer(layer, shouldBlock);
}
- (void)recursivelyEnsureDisplay
{
[self __recursivelyTriggerDisplayAndBlock:YES];
}
- (void)setShouldBypassEnsureDisplay:(BOOL)shouldBypassEnsureDisplay
@@ -2127,6 +2156,9 @@ static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer,
return _replaceAsyncSentinel != nil;
}
// FIXME: This method doesn't appear to be called, and could be removed.
// However, it may be useful for an API similar to what Paper used to create a new node hierarchy,
// trigger asynchronous measurement and display on it, and have it swap out and replace an old hierarchy.
- (ASSentinel *)_asyncReplaceSentinel
{
ASDN::MutexLocker l(_propertyLock);

View File

@@ -17,8 +17,9 @@
@end
@implementation ASRangeHandlerRender
@synthesize workingWindow = _workingWindow;
#if USE_WORKING_WINDOW
@synthesize workingWindow = _workingWindow;
- (UIWindow *)workingWindow
{
ASDisplayNodeAssertMainThread();
@@ -45,6 +46,7 @@
[self node:node exitedRangeOfType:ASLayoutRangeTypeRender];
}
}
#endif
- (void)node:(ASDisplayNode *)node enteredRangeOfType:(ASLayoutRangeType)rangeType
{
@@ -60,12 +62,17 @@
// The node un-suspends display.
[node enterInterfaceState:ASInterfaceStateDisplay];
#if USE_WORKING_WINDOW
// Add the node's layer to an off-screen window to trigger display and mark its contents as non-volatile.
// Use the layer directly to avoid the substantial overhead of UIView heirarchy manipulations.
// Any view-backed nodes will still create their views in order to assemble the layer heirarchy, and they will
// also assemble a view subtree for the node, but we avoid the much more significant expense triggered by a view
// being added or removed from an onscreen window (responder chain setup, will/DidMoveToWindow: recursive calls, etc)
[[[self workingWindow] layer] addSublayer:node.layer];
#else
[node recursivelyEnsureDisplay]; // Need to do this without waiting
#endif
}
- (void)node:(ASDisplayNode *)node exitedRangeOfType:(ASLayoutRangeType)rangeType
@@ -93,6 +100,7 @@
// The node calls clearCurrentContents and suspends display
[node exitInterfaceState:ASInterfaceStateDisplay];
#if USE_WORKING_WINDOW
if (node.layer.superlayer != [[self workingWindow] layer]) {
// In this case, the node has previously passed through the working range (or it is zero), and it has now fallen outside the working range.
if (![node isLayerBacked]) {
@@ -104,6 +112,13 @@
// At this point, the node's layer may validly be present either in the workingWindow, or in the contentsView of a cell.
[node.layer removeFromSuperlayer];
#else
if (![node isLayerBacked]) {
[node.view removeFromSuperview];
} else {
[node.layer removeFromSuperlayer];
}
#endif
}
@end

View File

@@ -147,11 +147,9 @@
- (void)displayImmediately
{
// REVIEW: Should this respect isDisplaySuspended? If so, we'd probably want to synchronously display when
// setDisplaySuspended:No is called, rather than just scheduling. The thread affinity for the displayImmediately
// call will be tricky if we need to support this, though. It probably should just execute if displayImmediately is
// called directly. The caller should be responsible for not calling displayImmediately if it wants to obey the
// suspended state.
// This method is a low-level bypass that avoids touching CA, including any reset of the
// needsDisplay flag, until the .contents property is set with the result.
// It is designed to be able to block the thread of any caller and fully execute the display.
ASDisplayNodeAssertMainThread();
[self display:NO];

View File

@@ -111,7 +111,7 @@ typedef NS_ENUM(NSUInteger, PIDebugBoxPaddingLocation)
[paddedLines addObject:paddedLine];
}
concatenatedLines = paddedLines;
totalLineLength += difference;
// totalLineLength += difference;
}
concatenatedLines = [self appendTopAndBottomToBoxString:concatenatedLines parent:parent];
return [concatenatedLines componentsJoinedByString:@"\n"];

View File

@@ -304,10 +304,9 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync,
// for async display, capture the current displaySentinel value to bail early when the job is executed if another is
// enqueued
// for sync display, just use nil for the displaySentinel and go
//
// REVIEW: what about the degenerate case where we are calling setNeedsDisplay faster than the jobs are dequeuing
// from the displayQueue? do we want to put in some kind of timer to not cancel early fails from displaySentinel
// changes?
// FIXME: what about the degenerate case where we are calling setNeedsDisplay faster than the jobs are dequeuing
// from the displayQueue? Need to not cancel early fails from displaySentinel changes.
ASSentinel *displaySentinel = (asynchronously ? _displaySentinel : nil);
int64_t displaySentinelValue = [displaySentinel increment];

View File

@@ -19,6 +19,10 @@
#import "ASThread.h"
#import "ASLayoutOptions.h"
// Project-wide control for whether the offscreen UIWindow is used for display, or if
// ASDK's internal system for coalescing and triggering display events is used.
#define USE_WORKING_WINDOW 1
/**
Hierarchy state is propogated from nodes to all of their children when certain behaviors are required from the subtree.
Examples include rasterization and external driving of the .interfaceState property.

View File

@@ -221,6 +221,8 @@
- (void)setNeedsDisplay
{
_bridge_prologue;
if (_hierarchyState & ASHierarchyStateRasterized) {
ASPerformBlockOnMainThread(^{
// The below operation must be performed on the main thread to ensure against an extremely rare deadlock, where a parent node
@@ -238,7 +240,19 @@
[rasterizedContainerNode setNeedsDisplay];
});
} else {
[_layer setNeedsDisplay];
// If not rasterized (and therefore we certainly have a view or layer),
// Send the message to the view/layer first, as scheduleNodeForDisplay may call -displayIfNeeded.
// Wrapped / synchronous nodes created with initWithView/LayerBlock: do not need scheduleNodeForDisplay,
// as they don't need to display in the working range at all - since at all times onscreen, one
// -setNeedsDisplay to the CALayer will result in a synchronous display in the next frame.
_messageToViewOrLayer(setNeedsDisplay);
#if !USE_WORKING_WINDOW
if (_layer && !self.isSynchronous) {
[ASDisplayNode scheduleNodeForDisplay:self];
}
#endif
}
}

View File

@@ -108,6 +108,8 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides)
}
+ (void)scheduleNodeForDisplay:(ASDisplayNode *)node;
// The _ASDisplayLayer backing the node, if any.
@property (nonatomic, readonly, retain) _ASDisplayLayer *asyncLayer;