Skip to content

Commit

Permalink
Add an experimental framesetter cache in ASTextNode2 (TextureGroup#1063)
Browse files Browse the repository at this point in the history
* Add an experimental framesetter cache in ASTextNode2, and stop keeping framesetters around

* Update configuration schema

* Fix imports

* Fix import again and remove set statement
  • Loading branch information
Adlai-Holler authored and mikezucc committed Oct 2, 2018
1 parent a0b7c00 commit e638909
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 17 deletions.
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

0 comments on commit e638909

Please sign in to comment.