-
Notifications
You must be signed in to change notification settings - Fork 246
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
Calculate average item size #296
Conversation
I'll walk you folks through the changes in our next sync up. |
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.
Thanks for making all those tests 👏 I have a couple of smaller things but mostly looks good 💯
src/utils/AverageWindow.ts
Outdated
this.currentCount = newCount; | ||
} | ||
|
||
protected getStoredValues() { |
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.
could be a getter method if we rename the underlying variable to have underscore.
src/__tests__/AverageWindow.test.ts
Outdated
import { AverageWindow } from "../utils/AverageWindow"; | ||
|
||
class AverageWindowTest extends AverageWindow { | ||
public get storedValues(): (number | undefined)[] { |
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 don't really see why this is necessary? As commented in AverageWindow
, let's make this method part of AverageWindow
itself and remove getStoredValues
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.
Point was to make sure storedValues isn't a public API. I don't want people to ever have access unless it's for tests. I just saw this issue in TS: microsoft/TypeScript#19335
It seems like string accessor is an escape hatch for testing :) I'll try and use that
const reduceObj = inputValues.reduce( | ||
(obj, val) => { | ||
if (val !== undefined) { | ||
obj.sum += val; | ||
obj.count++; | ||
} | ||
return obj; | ||
}, | ||
{ sum: 0, count: 0 } | ||
); |
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.
can't we compute the value outside the test and then use absolute value? I don't really like adding non-trivial logic to a test just for the sake of using it as an assert.
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 logic only belongs to this test and totally irrelevant outside. We wouldn't want this code to be in average window. This is verifying if running average is same as what we'd get from explicit calculation.
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 understand but I still think it would be better for the assert to rather work with hard values instead of computing them on the go. But it's not a big deal, so we can keep it.
Co-authored-by: Marek Fořt <marek.fort@shopify.com>
Co-authored-by: Marek Fořt <marek.fort@shopify.com>
Co-authored-by: Marek Fořt <marek.fort@shopify.com>
@fortmarek I've addressed the comments. Please check again when you have time. |
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 are some discussions but most of them low prio, so nothing holding back to merge this from my side.
/** | ||
* Can be used to get the current average value | ||
*/ | ||
public get currentValue(): number { |
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.
+1 to Marek, I'd rather have a longer, but more obvious meaning.
averageWindow.current
doesn't mean currentAverage
imho, it could be an instance of averageWindow
for example
const target = this.getNextIndex(); | ||
const oldValue = this.inputValues[target]; | ||
const newCount = | ||
oldValue === undefined ? this.currentCount + 1 : this.currentCount; | ||
|
||
this.inputValues[target] = value; | ||
|
||
this.currentAverage = | ||
this.currentAverage * (this.currentCount / newCount) + | ||
(value - (oldValue ?? 0)) / newCount; | ||
|
||
this.currentCount = newCount; |
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.
const target = this.getNextIndex(); | |
const oldValue = this.inputValues[target]; | |
const newCount = | |
oldValue === undefined ? this.currentCount + 1 : this.currentCount; | |
this.inputValues[target] = value; | |
this.currentAverage = | |
this.currentAverage * (this.currentCount / newCount) + | |
(value - (oldValue ?? 0)) / newCount; | |
this.currentCount = newCount; | |
// The index of the next value to be added | |
const targetIndex = this.getNextIndex(); | |
// Current value at the target index. can be undefined if we are adding the first value | |
const oldValue = this.inputValues[targetIndex]; | |
// If no value was previously at the target index, we need to increase the number of items | |
const newCount = | |
oldValue === undefined ? this.numberOfItems + 1 : this.numberOfItems; | |
// Set the new value at the target index | |
this.inputValues[targetIndex] = value; | |
const targetValueDiff = value - (oldValue ?? 0); | |
const newAverage = | |
(this.currentAverage * this.numberOfItems + targetValueDiff) / newCount; | |
this.currentAverage = newAverage; | |
this.numberOfItems = newCount; |
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.
In running average it's better to divide number of items with new count before multiplying. This way the output of the multiplication can never overshoot number's max size.
e.g, currentAverage * numberOfItems
can be very big if numbers you're averaging are large and can go over what the variable can handle.
public newLayoutManager( | ||
renderWindowSize: Dimension, | ||
isHorizontal?: boolean, | ||
cachedLayouts?: Layout[] | ||
): LayoutManager { | ||
// Average window is updated whenever a new layout manager is created. This is because old values are not relevant anymore. | ||
const estimatedItemCount = Math.max( |
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.
What does 3
mean here?
@@ -698,6 +705,12 @@ class FlashList<T> extends React.PureComponent< | |||
return currentOffset >= this.distanceFromWindow; | |||
} | |||
|
|||
private onItemLayout = (index: number) => { | |||
// Informing the layout provider about change to an item's layout. It already knows the dimensions so there's not need to pass them. |
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.
// Informing the layout provider about change to an item's layout. It already knows the dimensions so there's not need to pass them. | |
// Informing the layout provider about change to an item's layout. It already knows the dimensions so there's no need to pass them. |
* | ||
* @param value Add new value to the average window and updated current average | ||
*/ | ||
public addValue(value: number): void { |
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.
+1, I first thought it's related to UI window. However, reference to Sliding Window is helpful to understand the meaning of what we do here. AverageSlidingWindow
? AverageValueTracker
is good too.
Description
resolves #297
FlashList will compute average sizes from onLayout callbacks. I've introduced an average window so that the list reacts quickly to size changes and we can avoid worrying about cleaning up average based on prop changes. This is needed because average for a large number of items can become very rigid. Swapping out total count for a smaller number was another way to achieve the same thing but deciding where to do it was a problem.
I'm not changing estimatedItemSize requirement as part of this. This is a behind the scene optimization for v0.5 milestone. I'd want to discuss and make the prop optional as part of OSS milestone.
Reviewers’ hat-rack 🎩
GridLayoutProviderWithProps.ts
fileaverageItemSize
at the end ofreportItemLayout
method and see it updating in realtimeCheck if the values look correct and constantly update with changes.
Checklist