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 "CellRenderMask" Region Tracking Structure #31420

Closed
wants to merge 1 commit into from

Conversation

NickGerleman
Copy link
Contributor

Summary

A CellRenderMask helps track regions of cells/spacers to render. It's API allows adding ranges of cells, where its otput be an ordered list of contiguous spacer/non-spacer ranges.

The implementation keeps this region list internally, splitting or merging regions when cells are added. This output will be used by the render function of a refactored VirtualizedList.

Changelog

[Internal] [Added] - Add "CellRenderMask" Region Tracking Structure

Test Plan

Validated via UTs.

A CellRenderMask helps track regions of cells/spacers to render. It's API allows adding ranges of cells, where its otput be an ordered list of contiguous spacer/non-spacer ranges.

The implementation keeps this region list internally, splitting or merging regions when cells are added. This output will be used by the render function of a refactored VirtualizedList.

Validated via UTs.
@facebook-github-bot facebook-github-bot added p: Microsoft Partner: Microsoft Partner CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. labels Apr 23, 2021
@analysis-bot
Copy link

analysis-bot commented Apr 23, 2021

Platform Engine Arch Size (bytes) Diff
ios - universal n/a --

Base commit: de477a0

NickGerleman added a commit to NickGerleman/react-native that referenced this pull request Apr 24, 2021
Depends on:
facebook#31401
facebook#31420

VirtualizedList currently keeps a [first, last] range as state, tracking the region of cells to render. The render functions uses this as an input, along with a few special cases to render more (sticky headers, initial render region.)

This change moves to instead keep state which describes discontiguous render regions. This mask is continually updated as the viewport changes, batch renders expand the region, etc. Special cases are baked into the render mask, with a relatively simple tranformation from the mask to render function.

This representation makes it much easier to support keyboarding scenarios, which require keeping distinct regions (e.g. for last focused) realized while out of viewport.

MSFT employees can see more here: https://msit.microsoftstream.com/video/fe01a1ff-0400-94b1-d4f1-f1eb924b1809
@facebook-github-bot
Copy link
Contributor

@rozele has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

return this._regions;
}

addCells(cells: {first: number, last: number}) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm curious why we don't also need removeCells. I presume you may want to remove entire ranges as well as things virtualize away.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The current VirtualizedList implementation I have in #31422 will compute a whole new mask if inputs change, instead of trying to reason about the existing one.

A cell can have multiple conditions that would cause it to be rendered, so we do not know at the absense of just one condition that the cell should not be rendered. So it becomes tricky to mutate the existing mask.

Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a use case of adding cells that are spacers?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The render function in the linked PR above gives a good example of usage. When we hit spacers we still need to add views to the scroll view. So this structure allows us to iterate through regions and either add cells, or if a spacer, add the spacer view. If we omitted spacers we would need to derive where they were in VirtualizedList.


// We need to replace the existing covered regions with 1-3 new regions
// depending whether we need to split spacers out of overlapping regions.
const newLeadRegion: Array<CellRegion> = [];
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are these arrays? Is it just for ease of spreading? I guess in that case could we create the replacementRegions array at this point and push the new regions into it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, that is why they are arrays. Meant to represent that there may be zero or one region.

We could create replacementRegions a bit earlier and push. Might be marginally more efficient, but also maybe less clear to reason about.

...newTailRegion,
];
const numRegionsToDelete = lastIntersectIdx - firstIntersectIdx + 1;
this._regions.splice(
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be simpler to create a new regions array?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't necessarily think so. We would still need to figure out how to split existing intersecting cells when iterating through them. It would also be less efficient if we ever had large masks since we would need to rebuild the whole buffer.

);
}

equals(other: CellRenderMask): boolean {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we add test cases for this? What's the use case for checking equality for render masks?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can add tests for this. The use-case is comparing newly derived state to the existing, for whether to return null in setState to avoid extra renders. Right now this comparison is based on the first/last indices.

return this._regions;
}

addCells(cells: {first: number, last: number}) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a use case of adding cells that are spacers?

Copy link
Contributor

@lunaleaps lunaleaps left a comment

Choose a reason for hiding this comment

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

lgtm, would be nice to get tests (re: equality bits) but can do that in a follow-up. Will accept on imported PR as well

@facebook-github-bot
Copy link
Contributor

@rozele merged this pull request in 5cb2deb.

@facebook-github-bot facebook-github-bot added the Merged This PR has been merged. label Jun 24, 2021
NickGerleman added a commit to NickGerleman/react-native that referenced this pull request Nov 21, 2021
Builds upon the `CellRenderMask` data structure added with facebook#31420, and VirtualizedList coverage added with facebook#31401.

VirtualizedList currently keeps a [first, last] range as state, tracking the region of cells to render. The render functions uses this as an input, along with a few special cases to render more (sticky headers, initial render region.)

This change moves to instead keep state which describes discontiguous render regions. This mask is continually updated as the viewport changes, batch renders expand the region, etc. Special cases are baked into the render mask, with a relatively simple tranformation from the mask to render function.

This representation makes it much easier to support keyboarding scenarios, which require keeping distinct regions (e.g. for last focused) realized while out of viewport.

MS/FB folks have a video discussion about VirtualizedList here: https://msit.microsoftstream.com/video/fe01a1ff-0400-94b1-d4f1-f1eb924b1809

facebook#31401 added quite a few snapshot tests, centering around the logic this change is touching. I manually validated RNTester FlatList examples (and their should be some upstream UI testing for them).
NickGerleman added a commit to NickGerleman/react-native that referenced this pull request Jul 20, 2022
Builds upon the `CellRenderMask` data structure added with facebook#31420, and VirtualizedList coverage added with facebook#31401.

VirtualizedList currently keeps a [first, last] range as state, tracking the region of cells to render. The render functions uses this as an input, along with a few special cases to render more (sticky headers, initial render region.)

This change moves to instead keep state which describes discontiguous render regions. This mask is continually updated as the viewport changes, batch renders expand the region, etc. Special cases are baked into the render mask, with a relatively simple tranformation from the mask to render function.

This representation makes it much easier to support keyboarding scenarios, which require keeping distinct regions (e.g. for last focused) realized while out of viewport.

MS/FB folks have a video discussion about VirtualizedList here: https://msit.microsoftstream.com/video/fe01a1ff-0400-94b1-d4f1-f1eb924b1809

facebook#31401 added quite a few snapshot tests, centering around the logic this change is touching. I manually validated RNTester FlatList examples (and their should be some upstream UI testing for them).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Merged This PR has been merged. p: Microsoft Partner: Microsoft Partner
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants