Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an experimental framesetter cache in ASTextNode2 #1063

Merged
merged 5 commits into from
Aug 4, 2018
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
- Optimize ASDisplayNode -> ASNodeController reference by removing weak proxy and objc associated objects. [Adlai Holler](https://github.com/Adlai-Holler)
- Remove CA transaction signpost injection because it causes more transactions and is too chatty. [Adlai Holler](https://github.com/Adlai-Holler)
- Optimize display node accessibility by not creating attributed & non-attributed copies of hint, label, and value. [Adlai Holler](https://github.com/Adlai-Holler)
- Add an experimental feature that reuses CTFramesetter objects in ASTextNode2 to improve performance. [Adlai Holler](https://github.com/Adlai-Holler)


## 2.7
Expand Down
1 change: 1 addition & 0 deletions Schemas/configuration.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"exp_network_image_queue",
"exp_dealloc_queue_v2",
"exp_collection_teardown",
"exp_framesetter_cache"
]
}
}
Expand Down
1 change: 1 addition & 0 deletions Source/ASExperimentalFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ typedef NS_OPTIONS(NSUInteger, ASExperimentalFeatures) {
ASExperimentalNetworkImageQueue = 1 << 5, // exp_network_image_queue
ASExperimentalDeallocQueue = 1 << 6, // exp_dealloc_queue_v2
ASExperimentalCollectionTeardown = 1 << 7, // exp_collection_teardown
ASExperimentalFramesetterCache = 1 << 8, // exp_framesetter_cache
ASExperimentalFeatureAll = 0xFFFFFFFF
};

Expand Down
3 changes: 2 additions & 1 deletion Source/ASExperimentalFeatures.m
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
@"exp_infer_layer_defaults",
@"exp_network_image_queue",
@"exp_dealloc_queue_v2",
@"exp_collection_teardown"]));
@"exp_collection_teardown",
@"exp_framesetter_cache"]));

if (flags == ASExperimentalFeatureAll) {
return allNames;
Expand Down
2 changes: 0 additions & 2 deletions Source/Private/TextExperiment/Component/ASTextLayout.h
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,6 @@ AS_EXTERN const CGSize ASTextContainerMaxSize;
@property (nonatomic, readonly) NSAttributedString *text;
///< The text range in full text
@property (nonatomic, readonly) NSRange range;
///< CTFrameSetter
@property (nonatomic, readonly) CTFramesetterRef frameSetter;
///< CTFrame
@property (nonatomic, readonly) CTFrameRef frame;
///< Array of `ASTextLine`, no truncated
Expand Down
82 changes: 68 additions & 14 deletions Source/Private/TextExperiment/Component/ASTextLayout.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@
//

#import <AsyncDisplayKit/ASTextLayout.h>

#import <AsyncDisplayKit/ASConfigurationInternal.h>
#import <AsyncDisplayKit/ASTextUtilities.h>
#import <AsyncDisplayKit/ASTextAttribute.h>
#import <AsyncDisplayKit/NSAttributedString+ASText.h>
#import <AsyncDisplayKit/ASInternalHelpers.h>

#import <pthread.h>

const CGSize ASTextContainerMaxSize = (CGSize){0x100000, 0x100000};

typedef struct {
Expand Down Expand Up @@ -320,7 +324,6 @@ @interface ASTextLayout ()
@property (nonatomic) NSAttributedString *text;
@property (nonatomic) NSRange range;

@property (nonatomic) CTFramesetterRef frameSetter;
@property (nonatomic) CTFrameRef frame;
@property (nonatomic) NSArray *lines;
@property (nonatomic) ASTextLine *truncatedLine;
Expand Down Expand Up @@ -484,10 +487,71 @@ + (ASTextLayout *)layoutWithContainer:(ASTextContainer *)container text:(NSAttri
frameAttrs[(id)kCTFrameProgressionAttributeName] = @(kCTFrameProgressionRightToLeft);
}

// create CoreText objects
ctSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)text);
/*
* Framesetter cache.
* Framesetters can only be used by one thread at a time.
* Create a CFSet with no callbacks (raw pointers) to keep track of which
* framesetters are in use on other threads. If the one for our string is already in use,
* just create a new one. This should be pretty rare.
*/
static pthread_mutex_t busyFramesettersLock = PTHREAD_MUTEX_INITIALIZER;
static NSCache<NSAttributedString *, id> *framesetterCache;
static CFMutableSetRef busyFramesetters;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (ASActivateExperimentalFeature(ASExperimentalFramesetterCache)) {
framesetterCache = [[NSCache alloc] init];
framesetterCache.name = @"org.TextureGroup.Texture.framesetterCache";
busyFramesetters = CFSetCreateMutable(NULL, 0, NULL);
}
});

BOOL haveCached = NO, useCached = NO;
if (framesetterCache) {
// Check if there's one in the cache.
ctSetter = (__bridge_retained CTFramesetterRef)[framesetterCache objectForKey:text];

if (ctSetter) {
haveCached = YES;

// Check-and-set busy on the cached one.
pthread_mutex_lock(&busyFramesettersLock);
BOOL busy = CFSetContainsValue(busyFramesetters, ctSetter);
if (!busy) {
CFSetAddValue(busyFramesetters, ctSetter);
useCached = YES;
}
pthread_mutex_unlock(&busyFramesettersLock);

// Release if it was busy.
if (busy) {
CFRelease(ctSetter);
ctSetter = NULL;
}
}
}

// Create a framesetter if needed.
if (!ctSetter) {
ctSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)text);
}

if (!ctSetter) FAIL_AND_RETURN
ctFrame = CTFramesetterCreateFrame(ctSetter, ASTextCFRangeFromNSRange(range), cgPath, (CFDictionaryRef)frameAttrs);

// Return to cache.
if (framesetterCache) {
if (useCached) {
// If reused: mark available.
pthread_mutex_lock(&busyFramesettersLock);
CFSetRemoveValue(busyFramesetters, ctSetter);
pthread_mutex_unlock(&busyFramesettersLock);
} else if (!haveCached) {
// If first framesetter, add to cache.
[framesetterCache setObject:(__bridge id)ctSetter forKey:text];
}
}

if (!ctFrame) FAIL_AND_RETURN
lines = [NSMutableArray new];
ctLines = CTFrameGetLines(ctFrame);
Expand Down Expand Up @@ -857,8 +921,7 @@ + (ASTextLayout *)layoutWithContainer:(ASTextContainer *)container text:(NSAttri
if (attachments.count == 0) {
attachments = attachmentRanges = attachmentRects = nil;
}

layout.frameSetter = ctSetter;

layout.frame = ctFrame;
layout.lines = lines;
layout.truncatedLine = truncatedLine;
Expand Down Expand Up @@ -903,14 +966,6 @@ + (NSArray *)layoutWithContainers:(NSArray *)containers text:(NSAttributedString
return layouts;
}

- (void)setFrameSetter:(CTFramesetterRef)frameSetter {
if (_frameSetter != frameSetter) {
if (frameSetter) CFRetain(frameSetter);
if (_frameSetter) CFRelease(_frameSetter);
_frameSetter = frameSetter;
}
}

- (void)setFrame:(CTFrameRef)frame {
if (_frame != frame) {
if (frame) CFRetain(frame);
Expand All @@ -920,7 +975,6 @@ - (void)setFrame:(CTFrameRef)frame {
}

- (void)dealloc {
if (_frameSetter) CFRelease(_frameSetter);
if (_frame) CFRelease(_frame);
if (_lineRowsIndex) free(_lineRowsIndex);
if (_lineRowsEdge) free(_lineRowsEdge);
Expand Down