Skip to content
This repository was archived by the owner on Feb 2, 2023. It is now read-only.

Commit 834cf79

Browse files
committed
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.
2 parents e15a7db + 578d0f4 commit 834cf79

8 files changed

Lines changed: 90 additions & 26 deletions

AsyncDisplayKit/ASDisplayNode.mm

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,29 @@ + (Class)layerClass
197197
return [_ASDisplayLayer class];
198198
}
199199

200+
+ (void)scheduleNodeForDisplay:(ASDisplayNode *)node
201+
{
202+
ASDisplayNodeAssertMainThread();
203+
static NSMutableSet *nodesToDisplay = nil;
204+
static BOOL displayScheduled = NO;
205+
if (!nodesToDisplay) {
206+
nodesToDisplay = [[NSMutableSet alloc] init];
207+
}
208+
[nodesToDisplay addObject:node];
209+
if (!displayScheduled) {
210+
displayScheduled = YES;
211+
// It's essenital that any layout pass that is scheduled during the current
212+
// runloop has a chance to be applied / scheduled, so always perform this after the current runloop.
213+
dispatch_async(dispatch_get_main_queue(), ^{
214+
displayScheduled = NO;
215+
for (ASDisplayNode *node in nodesToDisplay) {
216+
[node __recursivelyTriggerDisplayAndBlock:NO];
217+
}
218+
nodesToDisplay = nil;
219+
});
220+
}
221+
}
222+
200223
#pragma mark - Lifecycle
201224

202225
- (void)_staticInitialize
@@ -710,6 +733,7 @@ - (void)displayImmediately
710733
- (void)recursivelyDisplayImmediately
711734
{
712735
ASDN::MutexLocker l(_propertyLock);
736+
713737
for (ASDisplayNode *child in _subnodes) {
714738
[child recursivelyDisplayImmediately];
715739
}
@@ -1453,7 +1477,7 @@ - (void)_tearDownPlaceholderLayer
14531477
[_placeholderLayer removeFromSuperlayer];
14541478
}
14551479

1456-
void recursivelyEnsureDisplayForLayer(CALayer *layer)
1480+
void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
14571481
{
14581482
// This recursion must handle layers in various states:
14591483
// 1. Just added to hierarchy, CA hasn't yet called -display
@@ -1472,26 +1496,26 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer)
14721496

14731497
// Kick off the recursion first, so that all necessary display calls are sent and the displayQueue is full of parallelizable work.
14741498
for (CALayer *sublayer in layer.sublayers) {
1475-
recursivelyEnsureDisplayForLayer(sublayer);
1499+
recursivelyTriggerDisplayForLayer(sublayer, shouldBlock);
14761500
}
14771501

1478-
// As the recursion unwinds, verify each transaction is complete and block if it is not.
1479-
// While blocking on one transaction, others may be completing concurrently, so it doesn't matter which blocks first.
1480-
BOOL waitUntilComplete = (!node.shouldBypassEnsureDisplay);
1481-
if (waitUntilComplete) {
1482-
for (_ASAsyncTransaction *transaction in [layer.asyncdisplaykit_asyncLayerTransactions copy]) {
1483-
// 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.
1484-
// This significantly reduces time on the main thread relative to UIKit.
1485-
[transaction waitUntilComplete];
1502+
if (shouldBlock) {
1503+
// As the recursion unwinds, verify each transaction is complete and block if it is not.
1504+
// While blocking on one transaction, others may be completing concurrently, so it doesn't matter which blocks first.
1505+
BOOL waitUntilComplete = (!node.shouldBypassEnsureDisplay);
1506+
if (waitUntilComplete) {
1507+
for (_ASAsyncTransaction *transaction in [layer.asyncdisplaykit_asyncLayerTransactions copy]) {
1508+
// 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.
1509+
// This significantly reduces time on the main thread relative to UIKit.
1510+
[transaction waitUntilComplete];
1511+
}
14861512
}
14871513
}
14881514
}
14891515

1490-
- (void)recursivelyEnsureDisplay
1516+
- (void)__recursivelyTriggerDisplayAndBlock:(BOOL)shouldBlock
14911517
{
14921518
ASDisplayNodeAssertMainThread();
1493-
ASDisplayNodeAssert(self.isNodeLoaded, @"Node must have layer or view loaded to use -recursivelyEnsureDisplay");
1494-
ASDisplayNodeAssert(self.inHierarchy && (self.isLayerBacked || self.view.window != nil), @"Node must be in a hierarchy to use -recursivelyEnsureDisplay");
14951519

14961520
CALayer *layer = self.layer;
14971521
// -layoutIfNeeded is recursive, and even walks up to superlayers to check if they need layout,
@@ -1500,7 +1524,12 @@ - (void)recursivelyEnsureDisplay
15001524
if ([layer needsLayout]) {
15011525
[layer layoutIfNeeded];
15021526
}
1503-
recursivelyEnsureDisplayForLayer(layer);
1527+
recursivelyTriggerDisplayForLayer(layer, shouldBlock);
1528+
}
1529+
1530+
- (void)recursivelyEnsureDisplay
1531+
{
1532+
[self __recursivelyTriggerDisplayAndBlock:YES];
15041533
}
15051534

15061535
- (void)setShouldBypassEnsureDisplay:(BOOL)shouldBypassEnsureDisplay
@@ -2127,6 +2156,9 @@ - (BOOL)_isMarkedForReplacement
21272156
return _replaceAsyncSentinel != nil;
21282157
}
21292158

2159+
// FIXME: This method doesn't appear to be called, and could be removed.
2160+
// However, it may be useful for an API similar to what Paper used to create a new node hierarchy,
2161+
// trigger asynchronous measurement and display on it, and have it swap out and replace an old hierarchy.
21302162
- (ASSentinel *)_asyncReplaceSentinel
21312163
{
21322164
ASDN::MutexLocker l(_propertyLock);

AsyncDisplayKit/Details/ASRangeHandlerRender.mm

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ @interface ASRangeHandlerRender ()
1717
@end
1818

1919
@implementation ASRangeHandlerRender
20-
@synthesize workingWindow = _workingWindow;
2120

21+
#if USE_WORKING_WINDOW
22+
@synthesize workingWindow = _workingWindow;
2223
- (UIWindow *)workingWindow
2324
{
2425
ASDisplayNodeAssertMainThread();
@@ -45,6 +46,7 @@ - (void)dealloc
4546
[self node:node exitedRangeOfType:ASLayoutRangeTypeRender];
4647
}
4748
}
49+
#endif
4850

4951
- (void)node:(ASDisplayNode *)node enteredRangeOfType:(ASLayoutRangeType)rangeType
5052
{
@@ -60,12 +62,17 @@ - (void)node:(ASDisplayNode *)node enteredRangeOfType:(ASLayoutRangeType)rangeTy
6062
// The node un-suspends display.
6163
[node enterInterfaceState:ASInterfaceStateDisplay];
6264

65+
66+
#if USE_WORKING_WINDOW
6367
// Add the node's layer to an off-screen window to trigger display and mark its contents as non-volatile.
6468
// Use the layer directly to avoid the substantial overhead of UIView heirarchy manipulations.
6569
// Any view-backed nodes will still create their views in order to assemble the layer heirarchy, and they will
6670
// also assemble a view subtree for the node, but we avoid the much more significant expense triggered by a view
6771
// being added or removed from an onscreen window (responder chain setup, will/DidMoveToWindow: recursive calls, etc)
6872
[[[self workingWindow] layer] addSublayer:node.layer];
73+
#else
74+
[node recursivelyEnsureDisplay]; // Need to do this without waiting
75+
#endif
6976
}
7077

7178
- (void)node:(ASDisplayNode *)node exitedRangeOfType:(ASLayoutRangeType)rangeType
@@ -93,6 +100,7 @@ - (void)node:(ASDisplayNode *)node exitedRangeOfType:(ASLayoutRangeType)rangeTyp
93100
// The node calls clearCurrentContents and suspends display
94101
[node exitInterfaceState:ASInterfaceStateDisplay];
95102

103+
#if USE_WORKING_WINDOW
96104
if (node.layer.superlayer != [[self workingWindow] layer]) {
97105
// 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.
98106
if (![node isLayerBacked]) {
@@ -104,6 +112,13 @@ - (void)node:(ASDisplayNode *)node exitedRangeOfType:(ASLayoutRangeType)rangeTyp
104112

105113
// At this point, the node's layer may validly be present either in the workingWindow, or in the contentsView of a cell.
106114
[node.layer removeFromSuperlayer];
115+
#else
116+
if (![node isLayerBacked]) {
117+
[node.view removeFromSuperview];
118+
} else {
119+
[node.layer removeFromSuperlayer];
120+
}
121+
#endif
107122
}
108123

109124
@end

AsyncDisplayKit/Details/_ASDisplayLayer.mm

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,11 +147,9 @@ + (id)defaultValueForKey:(NSString *)key
147147

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

156154
ASDisplayNodeAssertMainThread();
157155
[self display:NO];

AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ + (NSString *)horizontalBoxStringForChildren:(NSArray *)children parent:(NSStrin
111111
[paddedLines addObject:paddedLine];
112112
}
113113
concatenatedLines = paddedLines;
114-
totalLineLength += difference;
114+
// totalLineLength += difference;
115115
}
116116
concatenatedLines = [self appendTopAndBottomToBoxString:concatenatedLines parent:parent];
117117
return [concatenatedLines componentsJoinedByString:@"\n"];

AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -304,10 +304,9 @@ - (void)displayAsyncLayer:(_ASDisplayLayer *)asyncLayer asynchronously:(BOOL)asy
304304
// for async display, capture the current displaySentinel value to bail early when the job is executed if another is
305305
// enqueued
306306
// for sync display, just use nil for the displaySentinel and go
307-
//
308-
// REVIEW: what about the degenerate case where we are calling setNeedsDisplay faster than the jobs are dequeuing
309-
// from the displayQueue? do we want to put in some kind of timer to not cancel early fails from displaySentinel
310-
// changes?
307+
308+
// FIXME: what about the degenerate case where we are calling setNeedsDisplay faster than the jobs are dequeuing
309+
// from the displayQueue? Need to not cancel early fails from displaySentinel changes.
311310
ASSentinel *displaySentinel = (asynchronously ? _displaySentinel : nil);
312311
int64_t displaySentinelValue = [displaySentinel increment];
313312

AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
#import "ASThread.h"
2020
#import "ASLayoutOptions.h"
2121

22+
// Project-wide control for whether the offscreen UIWindow is used for display, or if
23+
// ASDK's internal system for coalescing and triggering display events is used.
24+
#define USE_WORKING_WINDOW 1
25+
2226
/**
2327
Hierarchy state is propogated from nodes to all of their children when certain behaviors are required from the subtree.
2428
Examples include rasterization and external driving of the .interfaceState property.

AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,8 @@ - (void)__setSafeFrame:(CGRect)rect
221221

222222
- (void)setNeedsDisplay
223223
{
224+
_bridge_prologue;
225+
224226
if (_hierarchyState & ASHierarchyStateRasterized) {
225227
ASPerformBlockOnMainThread(^{
226228
// 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 @@ - (void)setNeedsDisplay
238240
[rasterizedContainerNode setNeedsDisplay];
239241
});
240242
} else {
241-
[_layer setNeedsDisplay];
243+
// If not rasterized (and therefore we certainly have a view or layer),
244+
// Send the message to the view/layer first, as scheduleNodeForDisplay may call -displayIfNeeded.
245+
// Wrapped / synchronous nodes created with initWithView/LayerBlock: do not need scheduleNodeForDisplay,
246+
// as they don't need to display in the working range at all - since at all times onscreen, one
247+
// -setNeedsDisplay to the CALayer will result in a synchronous display in the next frame.
248+
249+
_messageToViewOrLayer(setNeedsDisplay);
250+
251+
#if !USE_WORKING_WINDOW
252+
if (_layer && !self.isSynchronous) {
253+
[ASDisplayNode scheduleNodeForDisplay:self];
254+
}
255+
#endif
242256
}
243257
}
244258

AsyncDisplayKit/Private/ASDisplayNodeInternal.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides)
108108

109109
}
110110

111+
+ (void)scheduleNodeForDisplay:(ASDisplayNode *)node;
112+
111113
// The _ASDisplayLayer backing the node, if any.
112114
@property (nonatomic, readonly, retain) _ASDisplayLayer *asyncLayer;
113115

0 commit comments

Comments
 (0)