Skip to content

Commit

Permalink
Tighten Rasterization API, Undeprecate It (#82)
Browse files Browse the repository at this point in the history
* Tamp down the rasterization API, and undeprecate it

* Update license header

* Update chornglorg

* Address comments
  • Loading branch information
Adlai-Holler authored May 1, 2017
1 parent 03a1aa2 commit 4d5e3ce
Show file tree
Hide file tree
Showing 11 changed files with 81 additions and 159 deletions.
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
## master

* Add your own contributions to the next release on the line below this with your name.
- Add support for IGListKit post-removal-of-IGListSectionType, in preparation for IGListKit 3.0.0 release. (Adlai-Holler)[https://github.com/Adlai-Holler] (#49)[https://github.com/TextureGroup/Texture/pull/49]
- Add support for IGListKit post-removal-of-IGListSectionType, in preparation for IGListKit 3.0.0 release. [Adlai Holler](https://github.com/Adlai-Holler) [#49](https://github.com/TextureGroup/Texture/pull/49)
- Fix `__has_include` check in ASLog.h [Philipp Smorygo](Philipp.Smorygo@jetbrains.com)
- Fix potential deadlock in ASControlNode [Garrett Moon](https://github.com/garrettmoon)
- [Yoga Beta] Improvements to the experimental support for Yoga layout [Scott Goodson](appleguy)
- Simplified & optimized hashing code. [Adlai Holler](https://github.com/Adlai-Holler) [#86](https://github.com/TextureGroup/Texture/pull/86)
- Update the rasterization API and un-deprecate it. [Adlai Holler](https://github.com/Adlai-Holler)[#82](https://github.com/TextureGroup/Texture/pull/49)
- Simplified & optimized hashing code. [Adlai Holler](https://github.com/Adlai-Holler) [#86](https://github.com/TextureGroup/Texture/pull/86)
11 changes: 7 additions & 4 deletions Source/ASDisplayNode+Beta.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,11 @@ typedef struct {
+ (void)setRangeModeForMemoryWarnings:(ASLayoutRangeMode)rangeMode;

/**
* @abstract Whether to draw all descendant nodes' layers/views into this node's layer/view's backing store.
* @abstract Whether to draw all descendent nodes' contents into this node's layer's backing store.
*
* @discussion
* When set to YES, causes all descendant nodes' layers/views to be drawn directly into this node's layer/view's backing
* store. Defaults to NO.
* When called, causes all descendent nodes' contents to be drawn directly into this node's layer's backing
* store.
*
* If a node's descendants are static (never animated or never change attributes after creation) then that node is a
* good candidate for rasterization. Rasterizing descendants has two main benefits:
Expand All @@ -154,8 +154,11 @@ typedef struct {
*
* Note: this has nothing to do with -[CALayer shouldRasterize], which doesn't work with ASDisplayNode's asynchronous
* rendering model.
*
* Note: You cannot add subnodes whose layers/views are already loaded to a rasterized node.
* Note: You cannot call this method after the receiver's layer/view is loaded.
*/
@property (nonatomic, assign) BOOL shouldRasterizeDescendants ASDISPLAYNODE_DEPRECATED_MSG("Deprecated in version 2.2");
- (void)enableSubtreeRasterization;

@end

Expand Down
2 changes: 0 additions & 2 deletions Source/ASDisplayNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,6 @@ extern NSInteger const ASDefaultDrawingPriority;
* @param body The work to be performed when the node is loaded.
*
* @precondition The node is not already loaded.
* @note This will only be called the next time the node is loaded. If the node is later added to a subtree of a node
* that has `shouldRasterizeDescendants=YES`, and is unloaded, this block will not be called if it is loaded again.
*/
- (void)onDidLoad:(ASDisplayNodeDidLoadBlock)body;

Expand Down
147 changes: 55 additions & 92 deletions Source/ASDisplayNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -541,28 +541,7 @@ + (NSValue * _Nonnull)_ivarsThatMayNeedMainDeallocation
return result;
}

#pragma mark - Loading / Unloading

- (void)__unloadNode
{
ASDisplayNodeAssertMainThread();
ASDisplayNodeAssert([self isNodeLoaded], @"Implementation shouldn't call __unloadNode if not loaded: %@", self);
ASDisplayNodeAssert(checkFlag(Synchronous) == NO, @"Node created using -initWithViewBlock:/-initWithLayerBlock: cannot be unloaded. Node: %@", self);
ASDN::MutexLocker l(__instanceLock__);

if (_flags.layerBacked) {
_pendingViewState = [_ASPendingState pendingViewStateFromLayer:_layer];
} else {
_pendingViewState = [_ASPendingState pendingViewStateFromView:_view];
}

[_view removeFromSuperview];
_view = nil;
if (_flags.layerBacked)
_layer.delegate = nil;
[_layer removeFromSuperlayer];
_layer = nil;
}
#pragma mark - Loading

- (BOOL)_locked_shouldLoadViewOrLayer
{
Expand Down Expand Up @@ -1970,63 +1949,46 @@ - (void)setDisplaysAsynchronously:(BOOL)displaysAsynchronously
self._locked_asyncLayer.displaysAsynchronously = displaysAsynchronously;
}

- (BOOL)shouldRasterizeDescendants
- (BOOL)rasterizesSubtree
{
ASDN::MutexLocker l(__instanceLock__);
ASDisplayNodeAssert(!((_hierarchyState & ASHierarchyStateRasterized) && _flags.shouldRasterizeDescendants),
@"Subnode of a rasterized node should not have redundant shouldRasterizeDescendants enabled");
return _flags.shouldRasterizeDescendants;
return _flags.rasterizesSubtree;
}

- (void)setShouldRasterizeDescendants:(BOOL)shouldRasterize
- (void)enableSubtreeRasterization
{
ASDisplayNodeAssertThreadAffinity(self);
BOOL rasterizedFromSelfOrAncestor = NO;
{
ASDN::MutexLocker l(__instanceLock__);

if (_flags.shouldRasterizeDescendants == shouldRasterize)
ASDN::MutexLocker l(__instanceLock__);
// Already rasterized from self.
if (_flags.rasterizesSubtree) {
return;
}

// If rasterized from above, bail.
if (ASHierarchyStateIncludesRasterized(_hierarchyState)) {
ASDisplayNodeFailAssert(@"Subnode of a rasterized node should not have redundant -enableSubtreeRasterization.");
return;
}

// Ensure not loaded.
if ([self _locked_isNodeLoaded]) {
ASDisplayNodeFailAssert(@"Cannot call %@ on loaded node: %@", NSStringFromSelector(_cmd), self);
return;
}

// Ensure no loaded subnodes
ASDisplayNode *loadedSubnode = ASDisplayNodeFindFirstSubnode(self, ^BOOL(ASDisplayNode * _Nonnull node) {
return node.nodeLoaded;
});
if (loadedSubnode != nil) {
ASDisplayNodeFailAssert(@"Cannot call %@ on node %@ with loaded subnode %@", NSStringFromSelector(_cmd), self, loadedSubnode);
return;

_flags.shouldRasterizeDescendants = shouldRasterize;
rasterizedFromSelfOrAncestor = shouldRasterize || ASHierarchyStateIncludesRasterized(_hierarchyState);
}

if (self.isNodeLoaded) {
// Recursively tear down or build up subnodes.
// TODO: When disabling rasterization, preserve rasterized backing store as placeholderImage
// while the newly materialized subtree finishes rendering. Then destroy placeholderImage to save memory.
[self recursivelyClearContents];

ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode *node) {
if (rasterizedFromSelfOrAncestor) {
[node enterHierarchyState:ASHierarchyStateRasterized];
if (node.isNodeLoaded) {
[node __unloadNode];
}
} else {
[node exitHierarchyState:ASHierarchyStateRasterized];
// We can avoid eagerly loading this node. We will load it on-demand as usual.
}
});
if (!rasterizedFromSelfOrAncestor) {
// If we are not going to rasterize at all, go ahead and set up our view hierarchy.
[self _addSubnodeViewsAndLayers];
}

if (ASInterfaceStateIncludesVisible(self.interfaceState)) {
// TODO: Change this to recursivelyEnsureDisplay - but need a variant that does not skip
// nodes that have shouldBypassEnsureDisplay set (such as image nodes) so they are rasterized.
[self recursivelyDisplayImmediately];
}
} else {
ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode *node) {
if (rasterizedFromSelfOrAncestor) {
[node enterHierarchyState:ASHierarchyStateRasterized];
} else {
[node exitHierarchyState:ASHierarchyStateRasterized];
}
});

_flags.rasterizesSubtree = YES;

// Tell subnodes that now they're in a rasterized hierarchy (while holding lock!)
for (ASDisplayNode *subnode in _subnodes) {
[subnode enterHierarchyState:ASHierarchyStateRasterized];
}
}

Expand Down Expand Up @@ -2108,7 +2070,7 @@ - (BOOL)_implementsDisplay
{
ASDN::MutexLocker l(__instanceLock__);

return _flags.implementsDrawRect || _flags.implementsImageDisplay || _flags.shouldRasterizeDescendants ||
return _flags.implementsDrawRect || _flags.implementsImageDisplay || _flags.rasterizesSubtree ||
_flags.implementsInstanceDrawRect || _flags.implementsInstanceImageDisplay;
}

Expand Down Expand Up @@ -2291,7 +2253,7 @@ static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer,
// Set the flag on the node. If this is a pure layer (no node) then this has no effect (plain layers don't support preventing/cancelling display).
node.displaySuspended = flag;

if (layer && !node.shouldRasterizeDescendants) {
if (layer && !node.rasterizesSubtree) {
// If there is a layer, recurse down the layer hierarchy to set the flag on descendants. This will cover both layer-based and node-based children.
for (CALayer *sublayer in layer.sublayers) {
_recursivelySetDisplaySuspended(nil, sublayer, flag);
Expand Down Expand Up @@ -2620,8 +2582,8 @@ ASDISPLAYNODE_INLINE BOOL canUseViewAPI(ASDisplayNode *node, ASDisplayNode *subn
}

/// Returns if node is a member of a rasterized tree
ASDISPLAYNODE_INLINE BOOL nodeIsInRasterizedTree(ASDisplayNode *node) {
return (node.shouldRasterizeDescendants || (node.hierarchyState & ASHierarchyStateRasterized));
ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) {
return (node.rasterizesSubtree || (node.hierarchyState & ASHierarchyStateRasterized));
}

// NOTE: This method must be dealloc-safe (should not retain self).
Expand Down Expand Up @@ -2658,8 +2620,8 @@ - (void)_setSupernode:(ASDisplayNode *)newSupernode
: oldSupernode.hierarchyState);

// Rasterized state
BOOL parentWasOrIsRasterized = (newSupernode ? newSupernode.shouldRasterizeDescendants
: oldSupernode.shouldRasterizeDescendants);
BOOL parentWasOrIsRasterized = (newSupernode ? newSupernode.rasterizesSubtree
: oldSupernode.rasterizesSubtree);
if (parentWasOrIsRasterized) {
stateToEnterOrExit |= ASHierarchyStateRasterized;
}
Expand Down Expand Up @@ -2745,6 +2707,12 @@ - (void)_insertSubnode:(ASDisplayNode *)subnode atSubnodeIndex:(NSInteger)subnod
return;
}

BOOL isRasterized = subtreeIsRasterized(self);
if (isRasterized && subnode.nodeLoaded) {
ASDisplayNodeFailAssert(@"Cannot add loaded node %@ to rasterized subtree of node %@", ASObjectDescriptionMakeTiny(subnode), ASObjectDescriptionMakeTiny(self));
return;
}

__instanceLock__.lock();
NSUInteger subnodesCount = _subnodes.count;
__instanceLock__.unlock();
Expand Down Expand Up @@ -2774,15 +2742,10 @@ - (void)_insertSubnode:(ASDisplayNode *)subnode atSubnodeIndex:(NSInteger)subnod
// If we are a managed hierarchy, as in ASCellNode trees, it will also apply our .interfaceState.
[subnode _setSupernode:self];

// If this subnode will be rasterized, update its hierarchy state & enter hierarchy if needed
if (nodeIsInRasterizedTree(self)) {
ASDisplayNodePerformBlockOnEveryNodeBFS(subnode, ^(ASDisplayNode * _Nonnull node) {
[node enterHierarchyState:ASHierarchyStateRasterized];
if (node.isNodeLoaded) {
[node __unloadNode];
}
});
if (self.isInHierarchy) {
// If this subnode will be rasterized, enter hierarchy if needed
// TODO: Move this into _setSupernode: ?
if (isRasterized) {
if (self.inHierarchy) {
[subnode __enterHierarchy];
}
} else if (self.nodeLoaded) {
Expand Down Expand Up @@ -2911,7 +2874,7 @@ - (void)_replaceSubnode:(ASDisplayNode *)oldSubnode withSubnode:(ASDisplayNode *

// Don't bother figuring out the sublayerIndex if in a rasterized subtree, because there are no layers in the
// hierarchy and none of this could possibly work.
if (nodeIsInRasterizedTree(self) == NO) {
if (subtreeIsRasterized(self) == NO) {
if (_layer) {
sublayerIndex = [_layer.sublayers indexOfObjectIdenticalTo:oldSubnode.layer];
ASDisplayNodeAssert(sublayerIndex != NSNotFound, @"Somehow oldSubnode's supernode is self, yet we could not find it in our layers to replace");
Expand Down Expand Up @@ -2957,7 +2920,7 @@ - (void)_insertSubnode:(ASDisplayNode *)subnode belowSubnode:(ASDisplayNode *)be

// Don't bother figuring out the sublayerIndex if in a rasterized subtree, because there are no layers in the
// hierarchy and none of this could possibly work.
if (nodeIsInRasterizedTree(self) == NO) {
if (subtreeIsRasterized(self) == NO) {
if (_layer) {
belowSublayerIndex = [_layer.sublayers indexOfObjectIdenticalTo:below.layer];
ASDisplayNodeAssert(belowSublayerIndex != NSNotFound, @"Somehow below's supernode is self, yet we could not find it in our layers to reference");
Expand Down Expand Up @@ -3021,7 +2984,7 @@ - (void)_insertSubnode:(ASDisplayNode *)subnode aboveSubnode:(ASDisplayNode *)ab

// Don't bother figuring out the sublayerIndex if in a rasterized subtree, because there are no layers in the
// hierarchy and none of this could possibly work.
if (nodeIsInRasterizedTree(self) == NO) {
if (subtreeIsRasterized(self) == NO) {
if (_layer) {
aboveSublayerIndex = [_layer.sublayers indexOfObjectIdenticalTo:above.layer];
ASDisplayNodeAssert(aboveSublayerIndex != NSNotFound, @"Somehow above's supernode is self, yet we could not find it in our layers to replace");
Expand Down Expand Up @@ -3079,7 +3042,7 @@ - (void)_insertSubnode:(ASDisplayNode *)subnode atIndex:(NSInteger)idx

// Don't bother figuring out the sublayerIndex if in a rasterized subtree, because there are no layers in the
// hierarchy and none of this could possibly work.
if (nodeIsInRasterizedTree(self) == NO) {
if (subtreeIsRasterized(self) == NO) {
// Account for potentially having other subviews
if (_layer && idx == 0) {
sublayerIndex = 0;
Expand Down Expand Up @@ -3405,7 +3368,7 @@ - (void)setHierarchyState:(ASHierarchyState)newState

// Entered rasterization state.
if (newState & ASHierarchyStateRasterized) {
ASDisplayNodeAssert(checkFlag(Synchronous) == NO, @"Node created using -initWithViewBlock:/-initWithLayerBlock: cannot be added to subtree of node with shouldRasterizeDescendants=YES. Node: %@", self);
ASDisplayNodeAssert(checkFlag(Synchronous) == NO, @"Node created using -initWithViewBlock:/-initWithLayerBlock: cannot be added to subtree of node with subtree rasterization enabled. Node: %@", self);
}

// Entered or exited range managed state.
Expand Down
2 changes: 1 addition & 1 deletion Source/ASDisplayNodeExtras.mm
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ extern void ASDisplayNodePerformBlockOnEveryNode(CALayer * _Nullable layer, ASDi
layer = node.layer;
}

if (traverseSublayers && layer && node.shouldRasterizeDescendants == NO) {
if (traverseSublayers && layer && node.rasterizesSubtree == NO) {
/// NOTE: The docs say `sublayers` returns a copy, but it does not.
/// See: http://stackoverflow.com/questions/14854480/collection-calayerarray-0x1ed8faa0-was-mutated-while-being-enumerated
for (CALayer *sublayer in [[layer sublayers] copy]) {
Expand Down
4 changes: 2 additions & 2 deletions Source/Details/_ASDisplayViewAccessiblity.mm
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ static void CollectUIAccessibilityElementsForNode(ASDisplayNode *node, ASDisplay
ASDisplayNodeCAssertNotNil(elements, @"Should pass in a NSMutableArray");

ASDisplayNodePerformBlockOnEveryNodeBFS(node, ^(ASDisplayNode * _Nonnull currentNode) {
// For every subnode that is layer backed or it's supernode has shouldRasterizeDescendants enabled
// For every subnode that is layer backed or it's supernode has subtree rasterization enabled
// we have to create a UIAccessibilityElement as no view for this node exists
if (currentNode != containerNode && currentNode.isAccessibilityElement) {
UIAccessibilityElement *accessibilityElement = [ASAccessibilityElement accessibilityElementWithContainer:container node:currentNode containerNode:containerNode];
Expand All @@ -108,7 +108,7 @@ static void CollectAccessibilityElementsForView(_ASDisplayView *view, NSMutableA
ASDisplayNode *node = view.asyncdisplaykit_node;

// Handle rasterize case
if (node.shouldRasterizeDescendants) {
if (node.rasterizesSubtree) {
CollectUIAccessibilityElementsForNode(node, node, view, elements);
return;
}
Expand Down
4 changes: 2 additions & 2 deletions Source/Private/ASDisplayNode+AsyncDisplay.mm
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ - (void)_recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:(asdisplaynode
CGRect frame;

// If this is the root container node, use a frame with a zero origin to draw into. If not, calculate the correct frame using the node's position, transform and anchorPoint.
if (self.shouldRasterizeDescendants) {
if (self.rasterizesSubtree) {
frame = CGRectMake(0.0f, 0.0f, bounds.size.width, bounds.size.height);
} else {
CGPoint position = self.position;
Expand Down Expand Up @@ -169,7 +169,7 @@ - (asyncdisplaykit_async_transaction_operation_block_t)_displayBlockWithAsynchro

// We always create a graphics context, unless a -display method is used, OR if we are a subnode drawing into a rasterized parent.
BOOL shouldCreateGraphicsContext = (flags.implementsInstanceImageDisplay == NO && flags.implementsImageDisplay == NO && rasterizing == NO);
BOOL shouldBeginRasterizing = (rasterizing == NO && flags.shouldRasterizeDescendants);
BOOL shouldBeginRasterizing = (rasterizing == NO && flags.rasterizesSubtree);
BOOL usesInstanceMethodDisplay = (flags.implementsInstanceDrawRect || flags.implementsInstanceImageDisplay);
BOOL usesImageDisplay = (flags.implementsImageDisplay || flags.implementsInstanceImageDisplay);
BOOL usesDrawRect = (flags.implementsDrawRect || flags.implementsInstanceDrawRect);
Expand Down
2 changes: 1 addition & 1 deletion Source/Private/ASDisplayNode+FrameworkPrivate.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ typedef NS_OPTIONS(NSUInteger, ASHierarchyState)
{
/** The node may or may not have a supernode, but no supernode has a special hierarchy-influencing option enabled. */
ASHierarchyStateNormal = 0,
/** The node has a supernode with .shouldRasterizeDescendants = YES.
/** The node has a supernode with .rasterizesSubtree = YES.
Note: the root node of the rasterized subtree (the one with the property set on it) will NOT have this state set. */
ASHierarchyStateRasterized = 1 << 0,
/** The node or one of its supernodes is managed by a class like ASRangeController. Most commonly, these nodes are
Expand Down
4 changes: 2 additions & 2 deletions Source/Private/ASDisplayNode+UIViewBridge.mm
Original file line number Diff line number Diff line change
Expand Up @@ -319,12 +319,12 @@ - (void)setNeedsDisplay
ASPerformBlockOnMainThread(^{
// The below operation must be performed on the main thread to ensure against an extremely rare deadlock, where a parent node
// begins materializing the view / layer hierarchy (locking itself or a descendant) while this node walks up
// the tree and requires locking that node to access .shouldRasterizeDescendants.
// the tree and requires locking that node to access .rasterizesSubtree.
// For this reason, this method should be avoided when possible. Use _hierarchyState & ASHierarchyStateRasterized.
ASDisplayNodeAssertMainThread();
ASDisplayNode *rasterizedContainerNode = self.supernode;
while (rasterizedContainerNode) {
if (rasterizedContainerNode.shouldRasterizeDescendants) {
if (rasterizedContainerNode.rasterizesSubtree) {
break;
}
rasterizedContainerNode = rasterizedContainerNode.supernode;
Expand Down
7 changes: 3 additions & 4 deletions Source/Private/ASDisplayNodeInternal.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
unsigned viewEverHadAGestureRecognizerAttached:1;
unsigned layerBacked:1;
unsigned displaysAsynchronously:1;
unsigned shouldRasterizeDescendants:1;
unsigned rasterizesSubtree:1;
unsigned shouldBypassEnsureDisplay:1;
unsigned displaySuspended:1;
unsigned shouldAnimateSizeChanges:1;
Expand Down Expand Up @@ -297,10 +297,9 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
- (ASPrimitiveTraitCollection)primitiveTraitCollection;

/**
* This is a non-deprecated internal declaration of the property. Public declaration
* is in ASDisplayNode+Beta.h
* Whether this node rasterizes its descendants. See -enableSubtreeRasterization.
*/
@property (nonatomic, assign) BOOL shouldRasterizeDescendants;
@property (atomic, readonly) BOOL rasterizesSubtree;

- (void)nodeViewDidAddGestureRecognizer;

Expand Down
Loading

0 comments on commit 4d5e3ce

Please sign in to comment.