Dirty nodes when dynamically setting config (#37207)
Summary: X-link: https://github.com/facebook/react-native/pull/37207 Pull Request resolved: https://github.com/facebook/yoga/pull/1274 Yoga exposes public APIs for dirtying Nodes, but will itself perform dirty marking when changing bits which invalidate layout. E.g. changing the style of a Node will invalidate it along with every parent Node. Because config setting is newly public to the C ABI, this makes a similar change so that replacing a Node's config will dirty the tree above the node if there is a layout impacting config change (I don't think children need to be invalidated since child output shouldn't change given the same owner dimensions). One quirk of this is that configs may be changed independently of the node. So someone could attach a config to a Node, then change the live config after the fact. The config does not currently have a back pointer to the Node, so we do not invalidate in that case of live config edits. The future work to rectify this would be to make configs immutable once created. There are also currently some experimental features here which should maybe be compared, but these should be moved to YGErrata anyway. Reviewed By: javache Differential Revision: D45505089 fbshipit-source-id: 72b2b84ba758679af081d92e7403750c9cc53cb5
This commit is contained in:
committed by
Facebook GitHub Bot
parent
54d78926ce
commit
b8126cdc6c
@@ -68,6 +68,99 @@ TEST(YogaTest, dirty_propagation_only_if_prop_changed) {
|
|||||||
YGNodeFreeRecursive(root);
|
YGNodeFreeRecursive(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(YogaTest, dirty_propagation_changing_layout_config) {
|
||||||
|
const YGNodeRef root = YGNodeNew();
|
||||||
|
YGNodeStyleSetAlignItems(root, YGAlignFlexStart);
|
||||||
|
YGNodeStyleSetWidth(root, 100);
|
||||||
|
YGNodeStyleSetHeight(root, 100);
|
||||||
|
|
||||||
|
const YGNodeRef root_child0 = YGNodeNew();
|
||||||
|
YGNodeStyleSetWidth(root_child0, 50);
|
||||||
|
YGNodeStyleSetHeight(root_child0, 20);
|
||||||
|
YGNodeInsertChild(root, root_child0, 0);
|
||||||
|
|
||||||
|
const YGNodeRef root_child1 = YGNodeNew();
|
||||||
|
YGNodeStyleSetWidth(root_child1, 50);
|
||||||
|
YGNodeStyleSetHeight(root_child1, 20);
|
||||||
|
YGNodeInsertChild(root, root_child1, 1);
|
||||||
|
|
||||||
|
const YGNodeRef root_child0_child0 = YGNodeNew();
|
||||||
|
YGNodeStyleSetWidth(root_child0_child0, 25);
|
||||||
|
YGNodeStyleSetHeight(root_child0_child0, 20);
|
||||||
|
YGNodeInsertChild(root, root_child0_child0, 0);
|
||||||
|
|
||||||
|
YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR);
|
||||||
|
|
||||||
|
EXPECT_FALSE(root->isDirty());
|
||||||
|
EXPECT_FALSE(root_child0->isDirty());
|
||||||
|
EXPECT_FALSE(root_child1->isDirty());
|
||||||
|
EXPECT_FALSE(root_child0_child0->isDirty());
|
||||||
|
|
||||||
|
YGConfigRef newConfig = YGConfigNew();
|
||||||
|
YGConfigSetErrata(newConfig, YGErrataStretchFlexBasis);
|
||||||
|
YGNodeSetConfig(root_child0, newConfig);
|
||||||
|
|
||||||
|
EXPECT_TRUE(root->isDirty());
|
||||||
|
EXPECT_TRUE(root_child0->isDirty());
|
||||||
|
EXPECT_FALSE(root_child1->isDirty());
|
||||||
|
EXPECT_FALSE(root_child0_child0->isDirty());
|
||||||
|
|
||||||
|
YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR);
|
||||||
|
|
||||||
|
EXPECT_FALSE(root->isDirty());
|
||||||
|
EXPECT_FALSE(root_child0->isDirty());
|
||||||
|
EXPECT_FALSE(root_child1->isDirty());
|
||||||
|
EXPECT_FALSE(root_child0_child0->isDirty());
|
||||||
|
|
||||||
|
YGConfigFree(newConfig);
|
||||||
|
YGNodeFreeRecursive(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(YogaTest, dirty_propagation_changing_benign_config) {
|
||||||
|
const YGNodeRef root = YGNodeNew();
|
||||||
|
YGNodeStyleSetAlignItems(root, YGAlignFlexStart);
|
||||||
|
YGNodeStyleSetWidth(root, 100);
|
||||||
|
YGNodeStyleSetHeight(root, 100);
|
||||||
|
|
||||||
|
const YGNodeRef root_child0 = YGNodeNew();
|
||||||
|
YGNodeStyleSetWidth(root_child0, 50);
|
||||||
|
YGNodeStyleSetHeight(root_child0, 20);
|
||||||
|
YGNodeInsertChild(root, root_child0, 0);
|
||||||
|
|
||||||
|
const YGNodeRef root_child1 = YGNodeNew();
|
||||||
|
YGNodeStyleSetWidth(root_child1, 50);
|
||||||
|
YGNodeStyleSetHeight(root_child1, 20);
|
||||||
|
YGNodeInsertChild(root, root_child1, 1);
|
||||||
|
|
||||||
|
const YGNodeRef root_child0_child0 = YGNodeNew();
|
||||||
|
YGNodeStyleSetWidth(root_child0_child0, 25);
|
||||||
|
YGNodeStyleSetHeight(root_child0_child0, 20);
|
||||||
|
YGNodeInsertChild(root, root_child0_child0, 0);
|
||||||
|
|
||||||
|
YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR);
|
||||||
|
|
||||||
|
EXPECT_FALSE(root->isDirty());
|
||||||
|
EXPECT_FALSE(root_child0->isDirty());
|
||||||
|
EXPECT_FALSE(root_child1->isDirty());
|
||||||
|
EXPECT_FALSE(root_child0_child0->isDirty());
|
||||||
|
|
||||||
|
YGConfigRef newConfig = YGConfigNew();
|
||||||
|
YGConfigSetLogger(
|
||||||
|
newConfig,
|
||||||
|
[](const YGConfigRef, const YGNodeRef, YGLogLevel, const char*, va_list) {
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
YGNodeSetConfig(root_child0, newConfig);
|
||||||
|
|
||||||
|
EXPECT_FALSE(root->isDirty());
|
||||||
|
EXPECT_FALSE(root_child0->isDirty());
|
||||||
|
EXPECT_FALSE(root_child1->isDirty());
|
||||||
|
EXPECT_FALSE(root_child0_child0->isDirty());
|
||||||
|
|
||||||
|
YGConfigFree(newConfig);
|
||||||
|
YGNodeFreeRecursive(root);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(YogaTest, dirty_mark_all_children_as_dirty_when_display_changes) {
|
TEST(YogaTest, dirty_mark_all_children_as_dirty_when_display_changes) {
|
||||||
const YGNodeRef root = YGNodeNew();
|
const YGNodeRef root = YGNodeNew();
|
||||||
YGNodeStyleSetFlexDirection(root, YGFlexDirectionRow);
|
YGNodeStyleSetFlexDirection(root, YGFlexDirectionRow);
|
||||||
|
@@ -7,7 +7,18 @@
|
|||||||
|
|
||||||
#include "YGConfig.h"
|
#include "YGConfig.h"
|
||||||
|
|
||||||
using namespace facebook::yoga::detail;
|
using namespace facebook::yoga;
|
||||||
|
|
||||||
|
namespace facebook {
|
||||||
|
namespace yoga {
|
||||||
|
bool configUpdateInvalidatesLayout(YGConfigRef a, YGConfigRef b) {
|
||||||
|
return a->getErrata() != b->getErrata() ||
|
||||||
|
a->getEnabledExperiments() != b->getEnabledExperiments() ||
|
||||||
|
a->getPointScaleFactor() != b->getPointScaleFactor() ||
|
||||||
|
a->useWebDefaults() != b->useWebDefaults();
|
||||||
|
}
|
||||||
|
} // namespace yoga
|
||||||
|
} // namespace facebook
|
||||||
|
|
||||||
YGConfig::YGConfig(YGLogger logger) : cloneNodeCallback_{nullptr} {
|
YGConfig::YGConfig(YGLogger logger) : cloneNodeCallback_{nullptr} {
|
||||||
setLogger(logger);
|
setLogger(logger);
|
||||||
@@ -40,6 +51,10 @@ bool YGConfig::isExperimentalFeatureEnabled(
|
|||||||
return experimentalFeatures_.test(feature);
|
return experimentalFeatures_.test(feature);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ExperimentalFeatureSet YGConfig::getEnabledExperiments() const {
|
||||||
|
return experimentalFeatures_;
|
||||||
|
}
|
||||||
|
|
||||||
void YGConfig::setErrata(YGErrata errata) {
|
void YGConfig::setErrata(YGErrata errata) {
|
||||||
errata_ = errata;
|
errata_ = errata;
|
||||||
}
|
}
|
||||||
|
@@ -14,8 +14,13 @@
|
|||||||
|
|
||||||
namespace facebook {
|
namespace facebook {
|
||||||
namespace yoga {
|
namespace yoga {
|
||||||
namespace detail {
|
|
||||||
|
|
||||||
|
// Whether moving a node from config "a" to config "b" should dirty previously
|
||||||
|
// calculated layout results.
|
||||||
|
bool configUpdateInvalidatesLayout(YGConfigRef a, YGConfigRef b);
|
||||||
|
|
||||||
|
// Internal variants of log functions, currently used only by JNI bindings.
|
||||||
|
// TODO: Reconcile this with the public API
|
||||||
using LogWithContextFn = int (*)(
|
using LogWithContextFn = int (*)(
|
||||||
YGConfigRef config,
|
YGConfigRef config,
|
||||||
YGNodeRef node,
|
YGNodeRef node,
|
||||||
@@ -29,8 +34,12 @@ using CloneWithContextFn = YGNodeRef (*)(
|
|||||||
int childIndex,
|
int childIndex,
|
||||||
void* cloneContext);
|
void* cloneContext);
|
||||||
|
|
||||||
|
using ExperimentalFeatureSet =
|
||||||
|
facebook::yoga::detail::EnumBitset<YGExperimentalFeature>;
|
||||||
|
|
||||||
#pragma pack(push)
|
#pragma pack(push)
|
||||||
#pragma pack(1)
|
#pragma pack(1)
|
||||||
|
// Packed structure of <32-bit options to miminize size per node.
|
||||||
struct YGConfigFlags {
|
struct YGConfigFlags {
|
||||||
bool useWebDefaults : 1;
|
bool useWebDefaults : 1;
|
||||||
bool printTree : 1;
|
bool printTree : 1;
|
||||||
@@ -39,7 +48,6 @@ struct YGConfigFlags {
|
|||||||
};
|
};
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
} // namespace detail
|
|
||||||
} // namespace yoga
|
} // namespace yoga
|
||||||
} // namespace facebook
|
} // namespace facebook
|
||||||
|
|
||||||
@@ -56,6 +64,7 @@ struct YOGA_EXPORT YGConfig {
|
|||||||
YGExperimentalFeature feature,
|
YGExperimentalFeature feature,
|
||||||
bool enabled);
|
bool enabled);
|
||||||
bool isExperimentalFeatureEnabled(YGExperimentalFeature feature) const;
|
bool isExperimentalFeatureEnabled(YGExperimentalFeature feature) const;
|
||||||
|
facebook::yoga::ExperimentalFeatureSet getEnabledExperiments() const;
|
||||||
|
|
||||||
void setErrata(YGErrata errata);
|
void setErrata(YGErrata errata);
|
||||||
YGErrata getErrata() const;
|
YGErrata getErrata() const;
|
||||||
@@ -67,13 +76,12 @@ struct YOGA_EXPORT YGConfig {
|
|||||||
void* getContext() const;
|
void* getContext() const;
|
||||||
|
|
||||||
void setLogger(YGLogger logger);
|
void setLogger(YGLogger logger);
|
||||||
void setLogger(facebook::yoga::detail::LogWithContextFn logger);
|
void setLogger(facebook::yoga::LogWithContextFn logger);
|
||||||
void setLogger(std::nullptr_t);
|
void setLogger(std::nullptr_t);
|
||||||
void log(YGConfig*, YGNode*, YGLogLevel, void*, const char*, va_list) const;
|
void log(YGConfig*, YGNode*, YGLogLevel, void*, const char*, va_list) const;
|
||||||
|
|
||||||
void setCloneNodeCallback(YGCloneNodeFunc cloneNode);
|
void setCloneNodeCallback(YGCloneNodeFunc cloneNode);
|
||||||
void setCloneNodeCallback(
|
void setCloneNodeCallback(facebook::yoga::CloneWithContextFn cloneNode);
|
||||||
facebook::yoga::detail::CloneWithContextFn cloneNode);
|
|
||||||
void setCloneNodeCallback(std::nullptr_t);
|
void setCloneNodeCallback(std::nullptr_t);
|
||||||
YGNodeRef cloneNode(
|
YGNodeRef cloneNode(
|
||||||
YGNodeRef node,
|
YGNodeRef node,
|
||||||
@@ -83,17 +91,16 @@ struct YOGA_EXPORT YGConfig {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
union {
|
union {
|
||||||
facebook::yoga::detail::CloneWithContextFn withContext;
|
facebook::yoga::CloneWithContextFn withContext;
|
||||||
YGCloneNodeFunc noContext;
|
YGCloneNodeFunc noContext;
|
||||||
} cloneNodeCallback_;
|
} cloneNodeCallback_;
|
||||||
union {
|
union {
|
||||||
facebook::yoga::detail::LogWithContextFn withContext;
|
facebook::yoga::LogWithContextFn withContext;
|
||||||
YGLogger noContext;
|
YGLogger noContext;
|
||||||
} logger_;
|
} logger_;
|
||||||
|
|
||||||
facebook::yoga::detail::YGConfigFlags flags_{};
|
facebook::yoga::YGConfigFlags flags_{};
|
||||||
facebook::yoga::detail::EnumBitset<YGExperimentalFeature>
|
facebook::yoga::ExperimentalFeatureSet experimentalFeatures_{};
|
||||||
experimentalFeatures_{};
|
|
||||||
YGErrata errata_ = YGErrataNone;
|
YGErrata errata_ = YGErrataNone;
|
||||||
float pointScaleFactor_ = 1.0f;
|
float pointScaleFactor_ = 1.0f;
|
||||||
void* context_ = nullptr;
|
void* context_ = nullptr;
|
||||||
|
@@ -269,6 +269,11 @@ void YGNode::setConfig(YGConfigRef config) {
|
|||||||
config,
|
config,
|
||||||
config->useWebDefaults() == config_->useWebDefaults(),
|
config->useWebDefaults() == config_->useWebDefaults(),
|
||||||
"UseWebDefaults may not be changed after constructing a YGNode");
|
"UseWebDefaults may not be changed after constructing a YGNode");
|
||||||
|
|
||||||
|
if (yoga::configUpdateInvalidatesLayout(config_, config)) {
|
||||||
|
markDirtyAndPropagate();
|
||||||
|
}
|
||||||
|
|
||||||
config_ = config;
|
config_ = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user