ComponentTextKit/Utility/CKAsyncLayer.mm (171 lines of code) (raw):
/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "CKAsyncLayer.h"
#import "CKAsyncLayerInternal.h"
#import "CKAsyncLayerSubclass.h"
#include <atomic>
#import <RenderCore/RCAssert.h>
#import "CKAsyncTransaction.h"
#import "CKAsyncTransactionContainer.h"
@implementation CKAsyncLayer
{
BOOL _needsAsyncDisplayOnly;
}
#pragma mark - Class Methods
+ (dispatch_queue_t)displayQueue
{
static dispatch_queue_t displayQueue = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
displayQueue = dispatch_queue_create("com.facebook.CKAsyncLayer.display", DISPATCH_QUEUE_CONCURRENT);
// we use the highpri queue to prioritize UI rendering over other async operations
dispatch_set_target_queue(displayQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
});
return displayQueue;
}
+ (id)defaultValueForKey:(NSString *)key
{
if ([key isEqualToString:@"displayMode"]) {
return @(CKAsyncLayerDisplayModeDefault);
} else {
return [super defaultValueForKey:key];
}
}
#pragma mark - Properties
- (NSString *)name
{
return [super name] ?: [NSString stringWithFormat:@"%@ (%p)", NSStringFromClass([self class]), self];
}
@dynamic displayMode;
- (void)setNeedsDisplay
{
RCAssertMainThread();
[self cancelAsyncDisplay];
[super setNeedsDisplay];
// Be sure to override any previous calls to -setNeedsAsyncDisplay:
_needsAsyncDisplayOnly = NO;
}
- (void)setNeedsAsyncDisplay
{
RCAssertMainThread();
if ([self needsDisplay]) {
// Either of these two situations:
// 1. -setNeedsDisplay has already been called; in that case it overrides the call to -setNeedsAsyncDisplay.
// 2. -setNeedsAsyncDisplay has already been called; in that case _needsAsyncDisplayOnly is already set.
return;
}
[self cancelAsyncDisplay];
[super setNeedsDisplay];
_needsAsyncDisplayOnly = YES;
}
#pragma mark - Display
- (void)cancelAsyncDisplay
{
RCAssertMainThread();
++_displaySentinel;
}
+ (ck_async_transaction_operation_block_t)asyncDisplayBlockWithBounds:(CGRect)bounds
contentsScale:(CGFloat)contentsScale
opaque:(BOOL)opaque
backgroundColor:(CGColorRef)backgroundColor
displaySentinel:(std::atomic_int32_t *)displaySentinel
expectedDisplaySentinelValue:(int32_t)expectedDisplaySentinelValue
drawingDelegate:(id<CKAsyncLayerDrawingDelegate>)drawingDelegate
drawParameters:(NSObject *)drawParameters
{
// make this an id so the block will capture it
id backgroundColorObject = (__bridge id)backgroundColor;
return [^id{
// Short-circuit to be efficient in the case where we've already started a different -display.
if ((displaySentinel != nil) && (*displaySentinel != expectedDisplaySentinelValue)) {
return nil;
}
if (CGRectIsEmpty(bounds)) {
return nil;
}
UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScale);
CGContextRef bitmapContext = UIGraphicsGetCurrentContext();
if (backgroundColorObject != NULL) {
CGContextSetFillColorWithColor(bitmapContext, (CGColorRef)backgroundColorObject);
CGContextFillRect(bitmapContext, bounds);
}
[drawingDelegate drawAsyncLayerInContext:bitmapContext parameters:drawParameters];
CGImageRef image = CGBitmapContextCreateImage(bitmapContext);
UIGraphicsEndImageContext();
return CFBridgingRelease(image);
} copy];
}
- (void)display
{
RCAssertMainThread();
BOOL renderSynchronously = NO;
CALayer *parentTransactionContainer;
if (!_needsAsyncDisplayOnly) {
switch (self.displayMode) {
case CKAsyncLayerDisplayModeDefault:
parentTransactionContainer = self.ck_parentTransactionContainer;
renderSynchronously = (parentTransactionContainer == nil);
break;
case CKAsyncLayerDisplayModeAlwaysAsync:
parentTransactionContainer = self.ck_parentTransactionContainer;
break;
case CKAsyncLayerDisplayModeAlwaysSync:
// Avoid cost of finding parentTransactionContainer, we're going to render synchronously regardless.
renderSynchronously = YES;
break;
}
}
if (renderSynchronously) {
[super display];
return;
}
if (!_needsAsyncDisplayOnly) {
// Reset needsDisplay to NO and remove any old content; otherwise it might appear stretched until rendering completes
self.contents = nil;
}
// Clear the _needsAsyncDisplayOnly flag for this display pass, since we've started async display.
_needsAsyncDisplayOnly = NO;
CGRect bounds = self.bounds;
if (CGRectIsEmpty(bounds)) {
return;
}
NSObject *drawParameters = [self drawParameters];
id shortCircuitContents = [self willDisplayAsynchronouslyWithDrawParameters:drawParameters];
if (shortCircuitContents) {
self.contents = shortCircuitContents;
return;
}
int32_t displaySentinelValue = ++_displaySentinel;
CALayer *containerLayer = parentTransactionContainer ?: self;
CKAsyncTransaction *transaction = containerLayer.ck_asyncTransaction;
RCAssertNotNil(transaction, @"Expected async layer transaction to be non-nil");
ck_async_transaction_operation_block_t transactionBlock = [[self class] asyncDisplayBlockWithBounds:bounds
contentsScale:self.contentsScale
opaque:self.opaque
backgroundColor:self.backgroundColor
displaySentinel:&_displaySentinel
expectedDisplaySentinelValue:displaySentinelValue
drawingDelegate:(id<CKAsyncLayerDrawingDelegate>)[self class]
drawParameters:drawParameters];
ck_async_transaction_operation_completion_block_t completionBlock = ^(id<NSObject> value, BOOL canceled) {
RCCAssertMainThread();
if (!canceled && (_displaySentinel == displaySentinelValue)) {
[self didDisplayAsynchronously:value withDrawParameters:drawParameters];
self.contents = value;
}
};
[transaction addOperationWithBlock:transactionBlock queue:[[self class] displayQueue] completion:completionBlock];
}
- (id)willDisplayAsynchronouslyWithDrawParameters:(id<NSObject>)drawParameters
{
return nil;
}
- (void)didDisplayAsynchronously:(id)newContents withDrawParameters:(id<NSObject>)drawParameters
{
}
#pragma mark - Drawing
/// this method exists to provide an override point for CKAsyncLayer where it can use its drawingDelegate in place
/// of self for this implementation
+ (void)drawAsyncLayerInContext:(CGContextRef)context parameters:(NSObject *)parameters
{
[self drawInContext:context parameters:parameters];
}
+ (void)drawInContext:(CGContextRef)context parameters:(NSObject *)parameters
{
// Empty in base class
}
- (void)drawInContext:(CGContextRef)context
{
RCAssertMainThread();
[[self class] drawInContext:context parameters:[self drawParameters]];
}
- (NSObject *)drawParameters
{
return nil;
}
@end