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
+46 -14
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);
@@ -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
+3 -5
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];
@@ -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"];
@@ -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];
@@ -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.
@@ -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
}
}
@@ -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;