Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it may have been important for the original code to enter the Rasterized hierarchy state before __enterHierarchy is called, because it may have invoked delegate methods that check this. I haven't looked closely though, this might not be true - just something to take a close look at.

[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