Skip to content

Commit f4c2cbd

Browse files
jwatzmanfacebook-github-bot
authored andcommitted
Don't break a flex line on floating-point rounding noise
Summary: A content-sized wrapping row sizes itself to the max-content sum of its items, then recovers the space available to those items by subtracting its padding and border back off (calculateAvailableInnerDimension). In float32 that add-then-subtract round-trip can land a fraction of an ulp below the sum it came from. For an item with a measure function the measurement cache still returns the max-content measurement in that situation: oldSizeIsMaxContentAndStillFits accepts a cached width up to the inexactEquals epsilon (1e-4) wider than the space now available. calculateFlexLine then compared that basis against availableInnerMainDim with a bare `>`, so the basis the cache had just accepted as fitting read as overflow and the line broke. Because the container had already been sized for a single line, the result was a box one line tall with its items laid out on two, overflowing its own padding and border. In React Native this reproduced on both iOS and Android and was extremely sensitive to the measured text width, since only some values leave a residue inside the window. Make the overflow test tolerant of the same epsilon the cache uses, so the two agree. Items without a measure function are unaffected: they take their exact style width in both passes and never enter that cache path. The regression test sweeps fractional measured widths whose content sum sits just under 128 while the outer width sits just over it, so adding padding and border crosses a float32 binade and the subtraction cannot always recover the original value. Changelog: [Internal] X-link: react/yoga#2022 Differential Revision: D126040642 Pulled By: javache
1 parent 51d3e67 commit f4c2cbd

1 file changed

Lines changed: 18 additions & 3 deletions

File tree

  • packages/react-native/ReactCommon/yoga/yoga/algorithm

‎packages/react-native/ReactCommon/yoga/yoga/algorithm/FlexLine.cpp‎

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <yoga/algorithm/BoundAxis.h>
1111
#include <yoga/algorithm/FlexDirection.h>
1212
#include <yoga/algorithm/FlexLine.h>
13+
#include <yoga/numeric/Comparison.h>
1314

1415
namespace facebook::yoga {
1516

@@ -74,12 +75,26 @@ FlexLine calculateFlexLine(
7475
ownerWidth)
7576
.unwrap();
7677

78+
const float requiredMainDim = sizeConsumedIncludingMinConstraint +
79+
flexBasisWithMinAndMaxConstraints + childMarginMainAxis +
80+
childLeadingGapMainAxis;
81+
7782
// If this is a multi-line flow and this item pushes us over the available
7883
// size, we've hit the end of the current line. Break out of the loop and
7984
// lay out the current line.
80-
if (sizeConsumedIncludingMinConstraint + flexBasisWithMinAndMaxConstraints +
81-
childMarginMainAxis + childLeadingGapMainAxis >
82-
availableInnerMainDim &&
85+
//
86+
// The overflow test is tolerant of the same epsilon the measurement cache
87+
// uses when it decides a cached measurement "still fits" (see
88+
// oldSizeIsMaxContentAndStillFits in Cache.cpp). A content-sized wrap
89+
// container adds padding and border to the max-content sum to size itself
90+
// and then subtracts them again to recover availableInnerMainDim, and in
91+
// float32 that round-trip can land a few ulps low. The cache accepts a
92+
// basis up to the epsilon wider than the space now available and hands
93+
// back the max-content measurement unchanged; without the same tolerance
94+
// here, that basis reads as overflow and the line breaks, leaving the
95+
// container sized for one line with its items laid out on two.
96+
if (requiredMainDim > availableInnerMainDim &&
97+
!yoga::inexactEquals(requiredMainDim, availableInnerMainDim) &&
8398
isNodeFlexWrap && !itemsInFlow.empty()) {
8499
break;
85100
}

0 commit comments

Comments
 (0)