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

Shift VirtualizedList State Representation to CellRenderMask #31422

Closed
wants to merge 3 commits into from

Conversation

NickGerleman
Copy link
Contributor

@NickGerleman NickGerleman commented Apr 24, 2021

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

Summary

Changelog

[Internal] [Changed] - Shift VirtualizedList State Representation to CellRenderMask

Test Plan

This change is a bit hairy, given the number of special cases and wide usage. #31401 added quite a few snapshot tests, centering around the logic this change is touching. Some quick manual sanity testing was done apart from this.

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 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 24, 2021
@NickGerleman NickGerleman marked this pull request as draft April 24, 2021 00:45
@NickGerleman
Copy link
Contributor Author

Keeping this as a draft, since I still have a couple of failing tests to look at, and need to do some final look through/polish. This should be the rough change though.

Comment on lines +1161 to +1163
// Without getItemLayout, we limit our tail spacer to the _highestMeasuredFrameIndex to
// prevent the user for hyperscrolling into un-measured area because otherwise content will
// likely jump around as it renders in above the viewport.
Copy link
Contributor Author

@NickGerleman NickGerleman Apr 24, 2021

Choose a reason for hiding this comment

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

Need to think about how to fold this into masking, since the same issue would happen if we were trying to render the end region without having rendered it before (with bonus wrong offset from incorrect spacer).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe we should just avoid all spacers in not yet measured areas.

Copy link
Contributor Author

@NickGerleman NickGerleman Apr 24, 2021

Choose a reason for hiding this comment

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

Everything at or after the first spacer in unmeasured area seems right. Should avoid shifting at the cost of not being able to pre-realize.

Comment on lines 355 to 367
function findLastWhere<T>(
arr: Array<T>,
pred: (element: T) => boolean,
): T | null {
for (let i = arr.length - 1; i >= 0; i--) {
if (pred(arr[i])) {
return arr[i];
}
}

return null;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will move this somewhere more shared if it stays after fixes to "no getLayoutView clamping"

? clamp(
section.first - 1,
section.last,
this._highestMeasuredFrameIndex,
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 existing behavior here I think assumes that there will not be many unmeasured frames before this. This logic would effectively be broken by large gaps if we did something like end region realization.

This is I think effectively solved if we constrain realized items in the render mask as well, since we will never render and lay them out. Otherwise we might need to only set _highestMeasuredFrameIndex when viewport window intersecting frames are measured.

@@ -1094,8 +1254,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
: this.props.style,
};

this._hasMore =
this.state.last < this.props.getItemCount(this.props.data) - 1;
this._hasMore = this.state.viewportWindow.last < itemCount - 1;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This seems a bit wonky, since it is possible we will never realize up to the end. This ends up blocking parent rendering though. Seems like an existing bug here?

Math.min(
itemCount,
scrollIndex + initialNumToRenderOrDefault(props.initialNumToRender),
) - 1,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This allows something odd like [0, -1], but that is previous behavior for how we represent a zero-item range.

@analysis-bot
Copy link

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

Base commit: 26e30a5
Branch: main

@analysis-bot
Copy link

Platform Engine Arch Size (bytes) Diff
android hermes arm64-v8a 8,479,772 +1,950
android hermes armeabi-v7a 7,778,976 +1,942
android hermes x86 8,948,541 +1,945
android hermes x86_64 8,892,606 +1,938
android jsc arm64-v8a 9,793,584 +963
android jsc armeabi-v7a 8,754,008 +959
android jsc x86 9,742,310 +975
android jsc x86_64 10,343,123 +962

Base commit: 26e30a5
Branch: main

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. p: Microsoft Partner: Microsoft Partner
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants