Skip to content

Commit

Permalink
C++ Cleanup 10/N: YGNodeCalculateLayout (facebook#39195)
Browse files Browse the repository at this point in the history
Summary:

X-link: facebook/yoga#1352

## This diff

This splits out all of the logic under `YGNodeCalculateLayout` to a couple of different files, does some mechanical renaming, and starts to split up the implementation a tiny bit. After this, core layout functions are all C++ convention and namespaced.

Each new file is marked as a move for the sake of blame history. It means Phabricator has a very inaccurate count of lines removed though.

## This stack

The organization of the C++ internals of Yoga are in need of attention.
1. Some of the C++ internals are namespaced, but others not.
2. Some of the namespaces include `detail`, but are meant to be used outside of the translation unit (FB Clang Tidy rules warn on any usage of these)
2. Most of the files are in a flat hierarchy, except for event tracing in its own folder
3. Some files and functions begin with YG, others don’t
4. Some functions are uppercase, others are not
5. Almost all of the interesting logic is in Yoga.cpp, and the file is too large to reason about
6. There are multiple grab bag files where folks put random functions they need in (Utils, BitUtils, Yoga-Internal.h)
7. There is no clear indication from file structure or type naming what is private vs not
8. Handles like `YGNodeRef` and `YGConfigRef` can be used to access internals just by importing headers

This stack does some much needed spring cleaning:
1. All non-public headers and C++ implementation details are in separate folders from the root level `yoga`. This will give us room to split up logic and add more files without too large a flat hierarchy
3. All private C++ internals are under the `facebook::yoga` namespace. Details namespaces are only ever used within the same header, as they are intended
4. Utils files are split
5. Most C++ internals drop the YG prefix
6. Most C++ internal function names are all lower camel case
7. We start to split up Yoga.cpp
8. Every header beginning with YG or at the top-level directory is public and C only, with the exception of Yoga-Internal.h which has non-public functions for bindings
9. It is not possible to use private APIs without static casting handles to internal classes

This will give us more leeway to continue splitting monolithic files, and consistent guidelines for style in new files as well.

These changes should not be breaking to any project using only public Yoga headers. This includes every usage of Yoga in fbsource except for RN Fabric which is currently tied to internals. This refactor should make that boundary clearer.

Reviewed By: rshest

Differential Revision: D48770478
  • Loading branch information
NickGerleman authored and facebook-github-bot committed Sep 5, 2023
1 parent 4bc089a commit 1309b90
Show file tree
Hide file tree
Showing 15 changed files with 3,543 additions and 3,292 deletions.
3,351 changes: 72 additions & 3,279 deletions packages/react-native/ReactCommon/yoga/yoga/Yoga.cpp

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions packages/react-native/ReactCommon/yoga/yoga/Yoga.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,17 @@ WIN_EXPORT void YGNodePrint(YGNodeRef node, YGPrintOptions options);

WIN_EXPORT bool YGFloatIsUndefined(float value);

// TODO: This should not be part of the public API. Remove after removing
// ComponentKit usage of it.
WIN_EXPORT bool YGNodeCanUseCachedMeasurement(
YGMeasureMode widthMode,
float width,
float availableWidth,
YGMeasureMode heightMode,
float height,
float availableHeight,
YGMeasureMode lastWidthMode,
float lastWidth,
float lastAvailableWidth,
YGMeasureMode lastHeightMode,
float lastHeight,
float lastAvailableHeight,
float lastComputedWidth,
float lastComputedHeight,
float marginRow,
Expand Down
29 changes: 29 additions & 0 deletions packages/react-native/ReactCommon/yoga/yoga/algorithm/Align.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#pragma once

#include <yoga/Yoga.h>

#include <yoga/algorithm/FlexDirection.h>
#include <yoga/node/Node.h>

namespace facebook::yoga {

inline YGAlign resolveChildAlignment(
const yoga::Node* node,
const yoga::Node* child) {
const YGAlign align = child->getStyle().alignSelf() == YGAlignAuto
? node->getStyle().alignItems()
: child->getStyle().alignSelf();
if (align == YGAlignBaseline && isColumn(node->getStyle().flexDirection())) {
return YGAlignFlexStart;
}
return align;
}

} // namespace facebook::yoga
65 changes: 65 additions & 0 deletions packages/react-native/ReactCommon/yoga/yoga/algorithm/Baseline.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#include <yoga/Yoga.h>

#include <yoga/algorithm/Align.h>
#include <yoga/algorithm/Baseline.h>
#include <yoga/debug/AssertFatal.h>
#include <yoga/event/event.h>

namespace facebook::yoga {

float calculateBaseline(const yoga::Node* node, void* layoutContext) {
if (node->hasBaselineFunc()) {

Event::publish<Event::NodeBaselineStart>(node);

const float baseline = node->baseline(
node->getLayout().measuredDimensions[YGDimensionWidth],
node->getLayout().measuredDimensions[YGDimensionHeight],
layoutContext);

Event::publish<Event::NodeBaselineEnd>(node);

yoga::assertFatalWithNode(
node,
!std::isnan(baseline),
"Expect custom baseline function to not return NaN");
return baseline;
}

yoga::Node* baselineChild = nullptr;
const uint32_t childCount = YGNodeGetChildCount(node);
for (uint32_t i = 0; i < childCount; i++) {
auto child = node->getChild(i);
if (child->getLineIndex() > 0) {
break;
}
if (child->getStyle().positionType() == YGPositionTypeAbsolute) {
continue;
}
if (resolveChildAlignment(node, child) == YGAlignBaseline ||
child->isReferenceBaseline()) {
baselineChild = child;
break;
}

if (baselineChild == nullptr) {
baselineChild = child;
}
}

if (baselineChild == nullptr) {
return node->getLayout().measuredDimensions[YGDimensionHeight];
}

const float baseline = calculateBaseline(baselineChild, layoutContext);
return baseline + baselineChild->getLayout().position[YGEdgeTop];
}

} // namespace facebook::yoga
18 changes: 18 additions & 0 deletions packages/react-native/ReactCommon/yoga/yoga/algorithm/Baseline.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#pragma once

#include <yoga/Yoga.h>
#include <yoga/node/Node.h>

namespace facebook::yoga {

// Calculate baseline represented as an offset from the top edge of the node.
float calculateBaseline(const yoga::Node* node, void* layoutContext);

} // namespace facebook::yoga
123 changes: 123 additions & 0 deletions packages/react-native/ReactCommon/yoga/yoga/algorithm/Cache.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#include <yoga/Yoga.h>

#include <yoga/algorithm/Cache.h>
#include <yoga/algorithm/PixelGrid.h>
#include <yoga/numeric/Comparison.h>

namespace facebook::yoga {

static inline bool sizeIsExactAndMatchesOldMeasuredSize(
YGMeasureMode sizeMode,
float size,
float lastComputedSize) {
return sizeMode == YGMeasureModeExactly &&
yoga::inexactEquals(size, lastComputedSize);
}

static inline bool oldSizeIsUnspecifiedAndStillFits(
YGMeasureMode sizeMode,
float size,
YGMeasureMode lastSizeMode,
float lastComputedSize) {
return sizeMode == YGMeasureModeAtMost &&
lastSizeMode == YGMeasureModeUndefined &&
(size >= lastComputedSize || yoga::inexactEquals(size, lastComputedSize));
}

static inline bool newMeasureSizeIsStricterAndStillValid(
YGMeasureMode sizeMode,
float size,
YGMeasureMode lastSizeMode,
float lastSize,
float lastComputedSize) {
return lastSizeMode == YGMeasureModeAtMost &&
sizeMode == YGMeasureModeAtMost && !std::isnan(lastSize) &&
!std::isnan(size) && !std::isnan(lastComputedSize) && lastSize > size &&
(lastComputedSize <= size || yoga::inexactEquals(size, lastComputedSize));
}

bool canUseCachedMeasurement(
const YGMeasureMode widthMode,
const float availableWidth,
const YGMeasureMode heightMode,
const float availableHeight,
const YGMeasureMode lastWidthMode,
const float lastAvailableWidth,
const YGMeasureMode lastHeightMode,
const float lastAvailableHeight,
const float lastComputedWidth,
const float lastComputedHeight,
const float marginRow,
const float marginColumn,
const yoga::Config* const config) {
if ((!std::isnan(lastComputedHeight) && lastComputedHeight < 0) ||
(!std::isnan(lastComputedWidth) && lastComputedWidth < 0)) {
return false;
}

const float pointScaleFactor = config->getPointScaleFactor();

bool useRoundedComparison = config != nullptr && pointScaleFactor != 0;
const float effectiveWidth = useRoundedComparison
? roundValueToPixelGrid(availableWidth, pointScaleFactor, false, false)
: availableWidth;
const float effectiveHeight = useRoundedComparison
? roundValueToPixelGrid(availableHeight, pointScaleFactor, false, false)
: availableHeight;
const float effectiveLastWidth = useRoundedComparison
? roundValueToPixelGrid(
lastAvailableWidth, pointScaleFactor, false, false)
: lastAvailableWidth;
const float effectiveLastHeight = useRoundedComparison
? roundValueToPixelGrid(
lastAvailableHeight, pointScaleFactor, false, false)
: lastAvailableHeight;

const bool hasSameWidthSpec = lastWidthMode == widthMode &&
yoga::inexactEquals(effectiveLastWidth, effectiveWidth);
const bool hasSameHeightSpec = lastHeightMode == heightMode &&
yoga::inexactEquals(effectiveLastHeight, effectiveHeight);

const bool widthIsCompatible =
hasSameWidthSpec ||
sizeIsExactAndMatchesOldMeasuredSize(
widthMode, availableWidth - marginRow, lastComputedWidth) ||
oldSizeIsUnspecifiedAndStillFits(
widthMode,
availableWidth - marginRow,
lastWidthMode,
lastComputedWidth) ||
newMeasureSizeIsStricterAndStillValid(
widthMode,
availableWidth - marginRow,
lastWidthMode,
lastAvailableWidth,
lastComputedWidth);

const bool heightIsCompatible =
hasSameHeightSpec ||
sizeIsExactAndMatchesOldMeasuredSize(
heightMode, availableHeight - marginColumn, lastComputedHeight) ||
oldSizeIsUnspecifiedAndStillFits(
heightMode,
availableHeight - marginColumn,
lastHeightMode,
lastComputedHeight) ||
newMeasureSizeIsStricterAndStillValid(
heightMode,
availableHeight - marginColumn,
lastHeightMode,
lastAvailableHeight,
lastComputedHeight);

return widthIsCompatible && heightIsCompatible;
}

} // namespace facebook::yoga
30 changes: 30 additions & 0 deletions packages/react-native/ReactCommon/yoga/yoga/algorithm/Cache.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#pragma once

#include <yoga/Yoga.h>
#include <yoga/config/Config.h>

namespace facebook::yoga {

bool canUseCachedMeasurement(
YGMeasureMode widthMode,
float availableWidth,
YGMeasureMode heightMode,
float availableHeight,
YGMeasureMode lastWidthMode,
float lastAvailableWidth,
YGMeasureMode lastHeightMode,
float lastAvailableHeight,
float lastComputedWidth,
float lastComputedHeight,
float marginRow,
float marginColumn,
const yoga::Config* config);

} // namespace facebook::yoga
Loading

0 comments on commit 1309b90

Please sign in to comment.