-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Implement ASPageTable #81
Conversation
🚫 CI failed with log |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nguyenhuy Awesome! We both know how much friggin time the range controller spends querying layoutAttributesForElementsInRect:, and stupid UIKit won't expose their cached version to us, so this is a major step forward.
elementToLayoutArrtibutesMap:[NSMapTable weakToStrongObjectsMapTable]]; | ||
return [[ASCollectionLayoutState alloc] initWithContext:context | ||
contentSize:CGSizeZero | ||
elementToLayoutArrtibutesTable:[NSMapTable weakToStrongObjectsMapTable]]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a typo here we should fix – Attributes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While we're at it, we should initialize this map table with NSObjectPointerPersonality | NSMapTableWeakMemory
so that we get pointer equality/hashing. Maybe it's worth making a convenience initializer that specifies these options.
NSMutableArray<UICollectionViewLayoutAttributes *> *results = [NSMutableArray array]; | ||
for (ASPageCoordinates *page in _pageToLayoutAttributesTable) { | ||
NSArray<UICollectionViewLayoutAttributes *> *allAttrs = [[_pageToLayoutAttributesTable objectForKey:page] allObjects]; | ||
CGRect pageRect = [page pageRectForPageSize:pageSize]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is such a hot path, it's probably worth only calling -allObjects
inside the CGRectContainsRect
branch, since it will allocate memory.
Source/Details/ASPageTable.h
Outdated
@property (nonatomic, assign, readonly) NSUInteger x; | ||
@property (nonatomic, assign, readonly) NSUInteger y; | ||
|
||
+ (instancetype)pageCoordinatesWithX:(NSUInteger)x Y:(NSUInteger)y; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since these are going to be created a lot I think it's worth optimizing upfront. Let's pack the coordinates into an integer and trick NSMapTable into using it.
typedef struct {
uint16_t x,
uint16_t y
} ASPageCoordinate;
__unsafe_unretained id ASPageCoordinateMakeKey(ASPageCoordinate coordinate) {
return (__bridge id)(void *)((coordinate.x << 16) + coordinate.y + 1);
}
- We add 1 because 0 is not a valid key for NSMapTable.
- It might be worth asserting that
x, y < (1 << 16)
and maskingx,y & 0xFFFF
.
Then create the map table with pointerFunctionsWithOptions:NSPointerFunctionsIntegerPersonality | NSPointerFunctionsOpaqueMemory
and use [mapTable objectForKey:ASPageCoordinateMakeKey(coord)]
to get your value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. At first, ASPageCoordinate
was actually implemented as a struct. But back then I was not sure if I'd use it frequently enough to justify the NSMapTable and NSArray tricks, so I made it an object. Now that we have a better idea of its usage, let me convert it back to a struct =))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, an integer is preferable even to a struct, since it'll avoid malloc/free. That said, if the code is cleaner the code is cleaner and that's mostly what matters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be reasonable to use an inlined or #define accessor to unpack the x / y. CGRectGetMinX() type function, could be ASCoordinateX(coordinate). Might not be worth it though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Adlai-Holler @appleguy I've pushed a new change that turns ASPageCoordinate
into an uint32_t
. And yes, we need ASPageCoordinateGetX()
and ASPageCoordinateGetY()
. Please have another look when you have time!
Source/Private/ASCollectionLayout.mm
Outdated
return @[]; | ||
} | ||
|
||
ASCollectionLayoutState *layout = _layout; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to create these local copies of strong ivars – since the ivar isn't marked volatile
the compiler is free to cache the value in a register. If there's gonna be a lot of underscores, this can make things prettier, and if the ivar is weak, then we definitely ought to get a strong local copy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Source/Details/ASPageTable.mm
Outdated
dispatch_once(&onceToken, ^{ | ||
weakObjectPointerFuncs = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPointerPersonality]; | ||
}); | ||
return [self pageTableWithValuePointerFunctions:weakObjectPointerFuncs]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: Personality doesn't matter for values, since they're never hashed/equaled.
Source/Details/ASPageTable.mm
Outdated
|
||
+ (NSArray<ASPageCoordinates *> *)pageCoordinatesForPagesInRect:(CGRect)rect withContentSize:(CGSize)contentSize pageSize:(CGSize)pageSize | ||
{ | ||
CGRect contentRect = CGRectMake(0.0f, 0.0f, contentSize.width, contentSize.height); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's never use 3.0f
0.0f
etc – the compiler will choose the appropriate type. CGFloat is double on 64-bit, after all.
Source/Details/ASPageTable.h
Outdated
* | ||
* @param pageSize The size of each page. | ||
*/ | ||
+ (ASPageTable<ASPageCoordinates *, NSMutableSet<UICollectionViewLayoutAttributes *> *> *)pageTableWithLayoutAttributes:(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributes pageSize:(CGSize)pageSize; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may not be worth it, but we could make the layoutAttributes argument id<NSFastEnumeration>
and then pass the map table's object enumerator directly, rather than forming an array.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually keyEnumerator
and objectEnumerator
of NSMapTable
are of type NSEnumerator
. But yes, it's better to just accept an enumerator.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NSEnumerator conforms to NSFastEnumeration
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yeah, that's true! Thanks for clarifying
* | ||
* @discussion This method will be called on main thread. | ||
*/ | ||
- (nullable id)additionalInfoForLayoutWithElements:(ASElementMap *)elements; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's make this required – they can always return nil
if they want, and that way they are sure to know that this method exists (and we don't have to ask the runtime whether they implement.)
de85f8d
to
2faf387
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sweeeeet! One more round of refinements and then let's move forward. Feel free to merge this if you address these and want to do more work when it's the middle-of-the-night in Cali.
/// Element to layout attributes map. Should use weak pointers for elements. | ||
@property (nonatomic, strong, readonly) NSMapTable<ASCollectionElement *, UICollectionViewLayoutAttributes *> *elementToLayoutArrtibutesMap; | ||
/// The final content rect calculated from content size | ||
@property (nonatomic, assign, readonly) CGRect contentRect; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's make this contentSize
since the origin will always be 0
* | ||
* @param rect The rect containing the target elements. | ||
*/ | ||
- (nullable NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's make this non-null, since @[]
is free.
Source/Details/ASPageTable.mm
Outdated
{ | ||
ASPageTable *result = [ASPageTable pageTableForStrongObjectPointers]; | ||
for (UICollectionViewLayoutAttributes *attrs in layoutAttributesEnumerator) { | ||
ASPageCoordinate page = ASPageCoordinateForPageThatContainsPoint(attrs.frame.origin, pageSize); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need to key this on page-that-intersects-rect. You can imagine an item that spans 10 pages – we need to make sure we include that one whenever we search any of those 10 pages.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great catch! Fixed.
return nil; | ||
} | ||
|
||
NSMutableArray<UICollectionViewLayoutAttributes *> *results = [NSMutableArray array]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's make this a mutable set, since layout attributes could be span multiple pages and we want to dedupe (see previous comment).
|
||
@implementation ASCollectionLayoutState { | ||
NSMapTable<ASCollectionElement *,UICollectionViewLayoutAttributes *> *_elementToLayoutAttributesTable; | ||
ASPageTable<id, NSMutableSet<UICollectionViewLayoutAttributes *> *> *_pageToLayoutAttributesTable; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's have the value-type here be array rather than set. That way we avoid hashing, equality, and converting-to-array.
Source/Details/ASPageTable.mm
Outdated
return nil; | ||
} | ||
|
||
NSPointerArray *results = [NSPointerArray pointerArrayWithOptions:(NSPointerFunctionsIntegerPersonality | NSPointerFunctionsOpaqueMemory)]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is clever! Nice!
Source/Details/ASPageTable.mm
Outdated
@@ -0,0 +1,146 @@ | |||
// | |||
// ASPageTable.mm |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like there's no C++ in this file, let's drop it down to .m
.
Source/Details/ASPageTable.h
Outdated
* | ||
* @param page A page to lookup the object for. | ||
*/ | ||
- (ObjectType)objectForPage:(ASPageCoordinate)page; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mark this nullable
/** | ||
* Returns all layout attributes present in this object. | ||
*/ | ||
- (NSArray<UICollectionViewLayoutAttributes *> *)allLayoutAttributes; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this isn't currently used. Unless you have a specific use in mind, let's remove for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's used by the coming gallery layout delegate.
- It is a screen page table that can be used to quickly filter out objects in a certain rect without checking each and every one of them. - ASCollectionLayoutState generates and keeps a table that maps page to layout attributes within that page. - ASCollectionLayout (and later, ASCollectionGalleryLayoutDelegate) consults its layout state for `layoutAttributesForElementsInRect:`. This ensures the method can return as quickly as possible, especially on a large data set (I heard some people have galleries with thousands of photos!).
Generated by 🚫 Danger |
layoutAttributesForElementsInRect:
. This ensures the method can return as quickly as possible, especially on a large data set (I heard some people have galleries with thousands of photos!).This PR was extracted from #76. Ticket: #186