diff --git a/tests/YGScaleChangeTest.cpp b/tests/YGScaleChangeTest.cpp new file mode 100644 index 00000000..11de9c07 --- /dev/null +++ b/tests/YGScaleChangeTest.cpp @@ -0,0 +1,176 @@ +/* + * 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 +#include + +TEST(YogaTest, scale_change_invalidates_layout) { + YGConfigRef config = YGConfigNew(); + + YGNodeRef root = YGNodeNewWithConfig(config); + YGConfigSetPointScaleFactor(config, 1.0f); + + YGNodeStyleSetFlexDirection(root, YGFlexDirectionRow); + YGNodeStyleSetWidth(root, 50); + YGNodeStyleSetHeight(root, 50); + + YGNodeRef root_child0 = YGNodeNewWithConfig(config); + YGNodeStyleSetFlexGrow(root_child0, 1); + YGNodeInsertChild(root, root_child0, 0); + + YGNodeRef root_child1 = YGNodeNewWithConfig(config); + YGNodeStyleSetFlexGrow(root_child1, 1); + YGNodeInsertChild(root, root_child1, 1); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_FLOAT_EQ(25, YGNodeLayoutGetLeft(root_child1)); + + YGConfigSetPointScaleFactor(config, 1.5f); + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + // Left should change due to pixel alignment of new scale factor + ASSERT_FLOAT_EQ(25.333334f, YGNodeLayoutGetLeft(root_child1)); + + YGNodeFreeRecursive(root); + YGConfigFree(config); +} + +TEST(YogaTest, errata_config_change_relayout) { + YGConfig* config = YGConfigNew(); + YGConfigSetErrata(config, YGErrataStretchFlexBasis); + YGNodeRef root = YGNodeNewWithConfig(config); + YGNodeStyleSetWidth(root, 500); + YGNodeStyleSetHeight(root, 500); + + YGNodeRef root_child0 = YGNodeNewWithConfig(config); + YGNodeStyleSetAlignItems(root_child0, YGAlignFlexStart); + YGNodeInsertChild(root, root_child0, 0); + + YGNodeRef root_child0_child0 = YGNodeNewWithConfig(config); + YGNodeStyleSetFlexGrow(root_child0_child0, 1); + YGNodeStyleSetFlexShrink(root_child0_child0, 1); + YGNodeInsertChild(root_child0, root_child0_child0, 0); + + YGNodeRef root_child0_child0_child0 = YGNodeNewWithConfig(config); + YGNodeStyleSetFlexGrow(root_child0_child0_child0, 1); + YGNodeStyleSetFlexShrink(root_child0_child0_child0, 1); + YGNodeInsertChild(root_child0_child0, root_child0_child0_child0, 0); + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetWidth(root_child0_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child0_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetWidth(root_child0_child0_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child0_child0_child0)); + + YGConfigSetErrata(config, YGErrataNone); + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetWidth(root_child0)); + // This should be modified by the lack of the errata + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetWidth(root_child0_child0)); + // This should be modified by the lack of the errata + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetHeight(root_child0_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetWidth(root_child0_child0_child0)); + // This should be modified by the lack of the errata + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetHeight(root_child0_child0_child0)); + + YGNodeFreeRecursive(root); + + YGConfigFree(config); +} + +TEST(YogaTest, setting_compatible_config_maintains_layout_cache) { + static uint32_t measureCallCount = 0; + auto measureCustom = [](YGNodeConstRef /*node*/, + float /*width*/, + YGMeasureMode /*widthMode*/, + float /*height*/, + YGMeasureMode /*heightMode*/) { + measureCallCount++; + return YGSize{ + .width = 25.0f, + .height = 25.0f, + }; + }; + + YGConfigRef config = YGConfigNew(); + + YGNodeRef root = YGNodeNewWithConfig(config); + YGConfigSetPointScaleFactor(config, 1.0f); + + YGNodeStyleSetFlexDirection(root, YGFlexDirectionRow); + YGNodeStyleSetWidth(root, 50); + YGNodeStyleSetHeight(root, 50); + + YGNodeRef root_child0 = YGNodeNewWithConfig(config); + EXPECT_EQ(0, measureCallCount); + + YGNodeSetMeasureFunc(root_child0, measureCustom); + YGNodeInsertChild(root, root_child0, 0); + + YGNodeRef root_child1 = YGNodeNewWithConfig(config); + YGNodeStyleSetFlexGrow(root_child1, 1); + YGNodeInsertChild(root, root_child1, 1); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + EXPECT_EQ(1, measureCallCount); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_FLOAT_EQ(25, YGNodeLayoutGetLeft(root_child1)); + + YGConfigRef config2 = YGConfigNew(); + // Calling YGConfigSetPointScaleFactor multiple times, ensures that config2 + // gets a different config version that config1 + YGConfigSetPointScaleFactor(config2, 1.0f); + YGConfigSetPointScaleFactor(config2, 1.5f); + YGConfigSetPointScaleFactor(config2, 1.0f); + + YGNodeSetConfig(root, config2); + YGNodeSetConfig(root_child0, config2); + YGNodeSetConfig(root_child1, config2); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + // Measure should not be called again, as layout should have been cached since + // config is functionally the same as before + EXPECT_EQ(1, measureCallCount); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_FLOAT_EQ(25, YGNodeLayoutGetLeft(root_child1)); + + YGNodeFreeRecursive(root); + YGConfigFree(config); + YGConfigFree(config2); +} diff --git a/yoga/algorithm/CalculateLayout.cpp b/yoga/algorithm/CalculateLayout.cpp index 58394a45..8db5fa41 100644 --- a/yoga/algorithm/CalculateLayout.cpp +++ b/yoga/algorithm/CalculateLayout.cpp @@ -2140,6 +2140,7 @@ bool calculateLayoutInternal( const bool needToVisitNode = (node->isDirty() && layout->generationCount != generationCount) || + layout->configVersion != node->getConfig()->getVersion() || layout->lastOwnerDirection != ownerDirection; if (needToVisitNode) { @@ -2255,6 +2256,7 @@ bool calculateLayoutInternal( reason); layout->lastOwnerDirection = ownerDirection; + layout->configVersion = node->getConfig()->getVersion(); if (cachedResults == nullptr) { layoutMarkerData.maxMeasureCache = std::max( diff --git a/yoga/config/Config.cpp b/yoga/config/Config.cpp index 881a3773..0f5fb019 100644 --- a/yoga/config/Config.cpp +++ b/yoga/config/Config.cpp @@ -31,7 +31,10 @@ bool Config::useWebDefaults() const { void Config::setExperimentalFeatureEnabled( ExperimentalFeature feature, bool enabled) { - experimentalFeatures_.set(static_cast(feature), enabled); + if (isExperimentalFeatureEnabled(feature) != enabled) { + experimentalFeatures_.set(static_cast(feature), enabled); + version_++; + } } bool Config::isExperimentalFeatureEnabled(ExperimentalFeature feature) const { @@ -43,15 +46,24 @@ ExperimentalFeatureSet Config::getEnabledExperiments() const { } void Config::setErrata(Errata errata) { - errata_ = errata; + if (errata_ != errata) { + errata_ = errata; + version_++; + } } void Config::addErrata(Errata errata) { - errata_ |= errata; + if (!hasErrata(errata)) { + errata_ |= errata; + version_++; + } } void Config::removeErrata(Errata errata) { - errata_ &= (~errata); + if (hasErrata(errata)) { + errata_ &= (~errata); + version_++; + } } Errata Config::getErrata() const { @@ -63,7 +75,10 @@ bool Config::hasErrata(Errata errata) const { } void Config::setPointScaleFactor(float pointScaleFactor) { - pointScaleFactor_ = pointScaleFactor; + if (pointScaleFactor_ != pointScaleFactor) { + pointScaleFactor_ = pointScaleFactor; + version_++; + } } float Config::getPointScaleFactor() const { @@ -78,6 +93,10 @@ void* Config::getContext() const { return context_; } +uint32_t Config::getVersion() const noexcept { + return version_; +} + void Config::setLogger(YGLogger logger) { logger_ = logger; } diff --git a/yoga/config/Config.h b/yoga/config/Config.h index 4db25bd0..7bcffd14 100644 --- a/yoga/config/Config.h +++ b/yoga/config/Config.h @@ -53,6 +53,8 @@ class YG_EXPORT Config : public ::YGConfig { void setContext(void* context); void* getContext() const; + uint32_t getVersion() const noexcept; + void setLogger(YGLogger logger); void log( const yoga::Node* node, @@ -72,6 +74,7 @@ class YG_EXPORT Config : public ::YGConfig { bool useWebDefaults_ : 1 = false; + uint32_t version_ = 0; ExperimentalFeatureSet experimentalFeatures_{}; Errata errata_ = Errata::None; float pointScaleFactor_ = 1.0f; diff --git a/yoga/node/LayoutResults.cpp b/yoga/node/LayoutResults.cpp index fd70870e..acc37a1b 100644 --- a/yoga/node/LayoutResults.cpp +++ b/yoga/node/LayoutResults.cpp @@ -21,6 +21,7 @@ bool LayoutResults::operator==(LayoutResults layout) const { direction() == layout.direction() && hadOverflow() == layout.hadOverflow() && lastOwnerDirection == layout.lastOwnerDirection && + configVersion == layout.configVersion && nextCachedMeasurementsIndex == layout.nextCachedMeasurementsIndex && cachedLayout == layout.cachedLayout && computedFlexBasis == layout.computedFlexBasis; diff --git a/yoga/node/LayoutResults.h b/yoga/node/LayoutResults.h index f42c2779..9f0aeaf7 100644 --- a/yoga/node/LayoutResults.h +++ b/yoga/node/LayoutResults.h @@ -30,6 +30,7 @@ struct LayoutResults { // Instead of recomputing the entire layout every single time, we cache some // information to break early when nothing changed uint32_t generationCount = 0; + uint32_t configVersion = 0; Direction lastOwnerDirection = Direction::Inherit; uint32_t nextCachedMeasurementsIndex = 0; diff --git a/yoga/node/Node.cpp b/yoga/node/Node.cpp index abda52f5..f1f96339 100644 --- a/yoga/node/Node.cpp +++ b/yoga/node/Node.cpp @@ -136,6 +136,11 @@ void Node::setConfig(yoga::Config* config) { if (yoga::configUpdateInvalidatesLayout(*config_, *config)) { markDirtyAndPropagate(); + layout_.configVersion = 0; + } else { + // If the config is functionally the same, then align the configVersion so + // that we can reuse the layout cache + layout_.configVersion = config->getVersion(); } config_ = config;