diff --git a/tests/YGPersistenceTest.cpp b/tests/YGPersistenceTest.cpp new file mode 100644 index 00000000..17334e89 --- /dev/null +++ b/tests/YGPersistenceTest.cpp @@ -0,0 +1,251 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +// @Generated by gentest/gentest.rb from gentest/fixtures/YGPercentageTest.html + +#include +#include + +TEST(YogaTest, cloning_shared_root) { + const YGConfigRef config = YGConfigNew(); + + const YGNodeRef root = YGNodeNewWithConfig(config); + YGNodeStyleSetWidth(root, 100); + YGNodeStyleSetHeight(root, 100); + + const YGNodeRef root_child0 = YGNodeNewWithConfig(config); + YGNodeStyleSetFlexGrow(root_child0, 1); + YGNodeStyleSetFlexBasis(root_child0, 50); + YGNodeInsertChild(root, root_child0, 0); + + const 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)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(75, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child1)); + ASSERT_FLOAT_EQ(75, YGNodeLayoutGetTop(root_child1)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetWidth(root_child1)); + ASSERT_FLOAT_EQ(25, YGNodeLayoutGetHeight(root_child1)); + + const YGNodeRef root2 = YGNodeClone(root); + YGNodeStyleSetWidth(root2, 100); + + ASSERT_EQ(2, YGNodeGetChildCount(root2)); + // The children should have referential equality at this point. + ASSERT_EQ(root_child0, YGNodeGetChild(root2, 0)); + ASSERT_EQ(root_child1, YGNodeGetChild(root2, 1)); + + YGNodeCalculateLayout(root2, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_EQ(2, YGNodeGetChildCount(root2)); + // Relayout with no changed input should result in referential equality. + ASSERT_EQ(root_child0, YGNodeGetChild(root2, 0)); + ASSERT_EQ(root_child1, YGNodeGetChild(root2, 1)); + + YGNodeStyleSetWidth(root2, 150); + YGNodeStyleSetHeight(root2, 200); + YGNodeCalculateLayout(root2, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_EQ(2, YGNodeGetChildCount(root2)); + // Relayout with changed input should result in cloned children. + const YGNodeRef root2_child0 = YGNodeGetChild(root2, 0); + const YGNodeRef root2_child1 = YGNodeGetChild(root2, 1); + ASSERT_NE(root_child0, root2_child0); + ASSERT_NE(root_child1, root2_child1); + + // Everything in the root should remain unchanged. + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(75, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child1)); + ASSERT_FLOAT_EQ(75, YGNodeLayoutGetTop(root_child1)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetWidth(root_child1)); + ASSERT_FLOAT_EQ(25, YGNodeLayoutGetHeight(root_child1)); + + // The new root now has new layout. + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root2)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root2)); + ASSERT_FLOAT_EQ(150, YGNodeLayoutGetWidth(root2)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetHeight(root2)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root2_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root2_child0)); + ASSERT_FLOAT_EQ(150, YGNodeLayoutGetWidth(root2_child0)); + ASSERT_FLOAT_EQ(125, YGNodeLayoutGetHeight(root2_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root2_child1)); + ASSERT_FLOAT_EQ(125, YGNodeLayoutGetTop(root2_child1)); + ASSERT_FLOAT_EQ(150, YGNodeLayoutGetWidth(root2_child1)); + ASSERT_FLOAT_EQ(75, YGNodeLayoutGetHeight(root2_child1)); + + YGNodeFreeRecursive(root2); + + YGNodeFreeRecursive(root); + + YGConfigFree(config); +} + +TEST(YogaTest, mutating_children_of_a_clone_clones) { + const YGConfigRef config = YGConfigNew(); + + const YGNodeRef root = YGNodeNewWithConfig(config); + ASSERT_EQ(0, YGNodeGetChildCount(root)); + + const YGNodeRef root2 = YGNodeClone(root); + ASSERT_EQ(0, YGNodeGetChildCount(root2)); + + const YGNodeRef root2_child0 = YGNodeNewWithConfig(config); + YGNodeInsertChild(root2, root2_child0, 0); + + ASSERT_EQ(0, YGNodeGetChildCount(root)); + ASSERT_EQ(1, YGNodeGetChildCount(root2)); + + const YGNodeRef root3 = YGNodeClone(root2); + ASSERT_EQ(1, YGNodeGetChildCount(root2)); + ASSERT_EQ(1, YGNodeGetChildCount(root3)); + ASSERT_EQ(YGNodeGetChild(root2, 0), YGNodeGetChild(root3, 0)); + + const YGNodeRef root3_child1 = YGNodeNewWithConfig(config); + YGNodeInsertChild(root3, root3_child1, 1); + ASSERT_EQ(1, YGNodeGetChildCount(root2)); + ASSERT_EQ(2, YGNodeGetChildCount(root3)); + ASSERT_EQ(root3_child1, YGNodeGetChild(root3, 1)); + ASSERT_NE(YGNodeGetChild(root2, 0), YGNodeGetChild(root3, 0)); + + const YGNodeRef root4 = YGNodeClone(root3); + ASSERT_EQ(root3_child1, YGNodeGetChild(root4, 1)); + + YGNodeRemoveChild(root4, root3_child1); + ASSERT_EQ(2, YGNodeGetChildCount(root3)); + ASSERT_EQ(1, YGNodeGetChildCount(root4)); + ASSERT_NE(YGNodeGetChild(root3, 0), YGNodeGetChild(root4, 0)); + + YGNodeFreeRecursive(root4); + YGNodeFreeRecursive(root3); + YGNodeFreeRecursive(root2); + YGNodeFreeRecursive(root); + + YGConfigFree(config); +} + +TEST(YogaTest, cloning_two_levels) { + const YGConfigRef config = YGConfigNew(); + + const YGNodeRef root = YGNodeNewWithConfig(config); + YGNodeStyleSetWidth(root, 100); + YGNodeStyleSetHeight(root, 100); + + const YGNodeRef root_child0 = YGNodeNewWithConfig(config); + YGNodeStyleSetFlexGrow(root_child0, 1); + YGNodeStyleSetFlexBasis(root_child0, 15); + YGNodeInsertChild(root, root_child0, 0); + + const YGNodeRef root_child1 = YGNodeNewWithConfig(config); + YGNodeStyleSetFlexGrow(root_child1, 1); + YGNodeInsertChild(root, root_child1, 1); + + const YGNodeRef root_child1_0 = YGNodeNewWithConfig(config); + YGNodeStyleSetFlexBasis(root_child1_0, 10); + YGNodeStyleSetFlexGrow(root_child1_0, 1); + YGNodeInsertChild(root_child1, root_child1_0, 0); + + const YGNodeRef root_child1_1 = YGNodeNewWithConfig(config); + YGNodeStyleSetFlexBasis(root_child1_1, 25); + YGNodeInsertChild(root_child1, root_child1_1, 1); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_FLOAT_EQ(40, YGNodeLayoutGetHeight(root_child0)); + ASSERT_FLOAT_EQ(60, YGNodeLayoutGetHeight(root_child1)); + ASSERT_FLOAT_EQ(35, YGNodeLayoutGetHeight(root_child1_0)); + ASSERT_FLOAT_EQ(25, YGNodeLayoutGetHeight(root_child1_1)); + + const YGNodeRef root2_child0 = YGNodeClone(root_child0); + const YGNodeRef root2_child1 = YGNodeClone(root_child1); + const YGNodeRef root2 = YGNodeClone(root); + + YGNodeStyleSetFlexGrow(root2_child0, 0); + YGNodeStyleSetFlexBasis(root2_child0, 40); + + YGNodeRemoveAllChildren(root2); + YGNodeInsertChild(root2, root2_child0, 0); + YGNodeInsertChild(root2, root2_child1, 1); + ASSERT_EQ(2, YGNodeGetChildCount(root2)); + + YGNodeCalculateLayout(root2, YGUndefined, YGUndefined, YGDirectionLTR); + + // Original root is unchanged + ASSERT_FLOAT_EQ(40, YGNodeLayoutGetHeight(root_child0)); + ASSERT_FLOAT_EQ(60, YGNodeLayoutGetHeight(root_child1)); + ASSERT_FLOAT_EQ(35, YGNodeLayoutGetHeight(root_child1_0)); + ASSERT_FLOAT_EQ(25, YGNodeLayoutGetHeight(root_child1_1)); + + // New root has new layout at the top + ASSERT_FLOAT_EQ(40, YGNodeLayoutGetHeight(root2_child0)); + ASSERT_FLOAT_EQ(60, YGNodeLayoutGetHeight(root2_child1)); + + // The deeper children are untouched. + ASSERT_EQ(YGNodeGetChild(root2_child1, 0), root_child1_0); + ASSERT_EQ(YGNodeGetChild(root2_child1, 1), root_child1_1); + + YGNodeFreeRecursive(root2); + YGNodeFreeRecursive(root); + + YGConfigFree(config); +} + +TEST(YogaTest, cloning_and_freeing) { + const int32_t initialInstanceCount = YGNodeGetInstanceCount(); + + const YGConfigRef config = YGConfigNew(); + + const YGNodeRef root = YGNodeNewWithConfig(config); + YGNodeStyleSetWidth(root, 100); + YGNodeStyleSetHeight(root, 100); + const YGNodeRef root_child0 = YGNodeNewWithConfig(config); + YGNodeInsertChild(root, root_child0, 0); + const YGNodeRef root_child1 = YGNodeNewWithConfig(config); + YGNodeInsertChild(root, root_child1, 1); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + const YGNodeRef root2 = YGNodeClone(root); + + // Freeing the original root should be safe as long as we don't free its children. + YGNodeFree(root); + + YGNodeCalculateLayout(root2, YGUndefined, YGUndefined, YGDirectionLTR); + + YGNodeFreeRecursive(root2); + + YGNodeFree(root_child0); + YGNodeFree(root_child1); + + YGConfigFree(config); + + ASSERT_EQ(initialInstanceCount, YGNodeGetInstanceCount()); +} diff --git a/yoga/YGNodeList.c b/yoga/YGNodeList.c index 216085cc..5bac4d85 100644 --- a/yoga/YGNodeList.c +++ b/yoga/YGNodeList.c @@ -7,6 +7,8 @@ * of patent rights can be found in the PATENTS file in the same directory. */ +#include + #include "YGNodeList.h" extern YGMalloc gYGMalloc; @@ -72,6 +74,17 @@ void YGNodeListInsert(YGNodeListRef *listp, const YGNodeRef node, const uint32_t list->items[index] = node; } +void YGNodeListReplace(YGNodeListRef list, const uint32_t index, const YGNodeRef newNode) { + list->items[index] = newNode; +} + +void YGNodeListRemoveAll(const YGNodeListRef list) { + for (uint32_t i = 0; i < list->count; i++) { + list->items[i] = NULL; + } + list->count = 0; +} + YGNodeRef YGNodeListRemove(const YGNodeListRef list, const uint32_t index) { const YGNodeRef removed = list->items[index]; list->items[index] = NULL; @@ -102,3 +115,17 @@ YGNodeRef YGNodeListGet(const YGNodeListRef list, const uint32_t index) { return NULL; } + +YGNodeListRef YGNodeListClone(const YGNodeListRef oldList) { + if (!oldList) { + return NULL; + } + const uint32_t count = oldList->count; + if (count == 0) { + return NULL; + } + const YGNodeListRef newList = YGNodeListNew(count); + memcpy(newList->items, oldList->items, sizeof(YGNodeRef) * count); + newList->count = count; + return newList; +} diff --git a/yoga/YGNodeList.h b/yoga/YGNodeList.h index 41e272ab..9c05eac0 100644 --- a/yoga/YGNodeList.h +++ b/yoga/YGNodeList.h @@ -26,8 +26,11 @@ void YGNodeListFree(const YGNodeListRef list); uint32_t YGNodeListCount(const YGNodeListRef list); void YGNodeListAdd(YGNodeListRef *listp, const YGNodeRef node); void YGNodeListInsert(YGNodeListRef *listp, const YGNodeRef node, const uint32_t index); +void YGNodeListReplace(const YGNodeListRef list, const uint32_t index, const YGNodeRef newNode); +void YGNodeListRemoveAll(const YGNodeListRef list); YGNodeRef YGNodeListRemove(const YGNodeListRef list, const uint32_t index); YGNodeRef YGNodeListDelete(const YGNodeListRef list, const YGNodeRef node); YGNodeRef YGNodeListGet(const YGNodeListRef list, const uint32_t index); +YGNodeListRef YGNodeListClone(YGNodeListRef list); YG_EXTERN_C_END diff --git a/yoga/Yoga.c b/yoga/Yoga.c index f8e20e65..d1ce158e 100644 --- a/yoga/Yoga.c +++ b/yoga/Yoga.c @@ -102,6 +102,7 @@ typedef struct YGConfig { bool useLegacyStretchBehaviour; float pointScaleFactor; YGLogger logger; + YGNodeClonedFunc cloneNodeCallback; void *context; } YGConfig; @@ -362,6 +363,17 @@ YGNodeRef YGNodeNew(void) { return YGNodeNewWithConfig(&gYGConfigDefaults); } +YGNodeRef YGNodeClone(const YGNodeRef oldNode) { + const YGNodeRef node = gYGMalloc(sizeof(YGNode)); + YGAssertWithConfig(oldNode->config, node != NULL, "Could not allocate memory for node"); + gNodeInstanceCount++; + + memcpy(node, oldNode, sizeof(YGNode)); + node->children = YGNodeListClone(oldNode->children); + node->parent = NULL; + return node; +} + void YGNodeFree(const YGNodeRef node) { if (node->parent) { YGNodeListDelete(node->parent->children, node); @@ -382,6 +394,10 @@ void YGNodeFree(const YGNodeRef node) { void YGNodeFreeRecursive(const YGNodeRef root) { while (YGNodeGetChildCount(root) > 0) { const YGNodeRef child = YGNodeGetChild(root, 0); + if (child->parent != root) { + // Don't free shared nodes that we don't own. + break; + } YGNodeRemoveChild(root, child); YGNodeFreeRecursive(child); } @@ -474,6 +490,34 @@ YGBaselineFunc YGNodeGetBaselineFunc(const YGNodeRef node) { return node->baseline; } +static void YGCloneChildrenIfNeeded(const YGNodeRef parent) { + // YGNodeRemoveChild has a forked variant of this algorithm optimized for deletions. + const uint32_t childCount = YGNodeGetChildCount(parent); + if (childCount == 0) { + // This is an empty set. Nothing to clone. + return; + } + const YGNodeRef firstChild = YGNodeGetChild(parent, 0); + if (firstChild->parent == parent) { + // If the first child has this node as its parent, we assume that it is already unique. + // We can do this because if we have it has a child, that means that its parent was at some + // point cloned which made that subtree immutable. + // We also assume that all its sibling are cloned as well. + return; + } + const YGNodeClonedFunc cloneNodeCallback = parent->config->cloneNodeCallback; + const YGNodeListRef children = parent->children; + for (uint32_t i = 0; i < childCount; i++) { + const YGNodeRef oldChild = YGNodeListGet(children, i); + const YGNodeRef newChild = YGNodeClone(oldChild); + YGNodeListReplace(children, i, newChild); + newChild->parent = parent; + if (cloneNodeCallback) { + cloneNodeCallback(oldChild, newChild, parent, i); + } + } +} + void YGNodeInsertChild(const YGNodeRef node, const YGNodeRef child, const uint32_t index) { YGAssertWithNode(node, child->parent == NULL, @@ -482,17 +526,81 @@ void YGNodeInsertChild(const YGNodeRef node, const YGNodeRef child, const uint32 node->measure == NULL, "Cannot add child: Nodes with measure functions cannot have children."); + YGCloneChildrenIfNeeded(node); + YGNodeListInsert(&node->children, child, index); child->parent = node; YGNodeMarkDirtyInternal(node); } -void YGNodeRemoveChild(const YGNodeRef node, const YGNodeRef child) { - if (YGNodeListDelete(node->children, child) != NULL) { - child->layout = gYGNodeDefaults.layout; // layout is no longer valid - child->parent = NULL; - YGNodeMarkDirtyInternal(node); +void YGNodeRemoveChild(const YGNodeRef parent, const YGNodeRef excludedChild) { + // This algorithm is a forked variant from YGCloneChildrenIfNeeded that excludes a child. + const uint32_t childCount = YGNodeGetChildCount(parent); + if (childCount == 0) { + // This is an empty set. Nothing to remove. + return; } + const YGNodeRef firstChild = YGNodeGetChild(parent, 0); + if (firstChild->parent == parent) { + // If the first child has this node as its parent, we assume that it is already unique. + // We can now try to delete a child in this list. + if (YGNodeListDelete(parent->children, excludedChild) != NULL) { + excludedChild->layout = gYGNodeDefaults.layout; // layout is no longer valid + excludedChild->parent = NULL; + YGNodeMarkDirtyInternal(parent); + } + return; + } + // Otherwise we have to clone the node list except for the child we're trying to delete. + // We don't want to simply clone all children, because then the host will need to free + // the clone of the child that was just deleted. + const YGNodeClonedFunc cloneNodeCallback = parent->config->cloneNodeCallback; + const YGNodeListRef children = parent->children; + uint32_t nextInsertIndex = 0; + for (uint32_t i = 0; i < childCount; i++) { + const YGNodeRef oldChild = YGNodeListGet(children, i); + if (excludedChild == oldChild) { + // Ignore the deleted child. Don't reset its layout or parent since it is still valid + // in the other parent. However, since this parent has now changed, we need to mark it + // as dirty. + YGNodeMarkDirtyInternal(parent); + continue; + } + const YGNodeRef newChild = YGNodeClone(oldChild); + YGNodeListReplace(children, nextInsertIndex, newChild); + newChild->parent = parent; + if (cloneNodeCallback) { + cloneNodeCallback(oldChild, newChild, parent, nextInsertIndex); + } + nextInsertIndex++; + } + while (nextInsertIndex < childCount) { + YGNodeListRemove(children, nextInsertIndex); + nextInsertIndex++; + } +} + +void YGNodeRemoveAllChildren(const YGNodeRef parent) { + const uint32_t childCount = YGNodeGetChildCount(parent); + if (childCount == 0) { + // This is an empty set already. Nothing to do. + return; + } + const YGNodeRef firstChild = YGNodeGetChild(parent, 0); + if (firstChild->parent == parent) { + // If the first child has this node as its parent, we assume that this child set is unique. + for (uint32_t i = 0; i < childCount; i++) { + const YGNodeRef oldChild = YGNodeGetChild(parent, i); + oldChild->layout = gYGNodeDefaults.layout; // layout is no longer valid + oldChild->parent = NULL; + } + YGNodeListRemoveAll(parent->children); + YGNodeMarkDirtyInternal(parent); + return; + } + // Otherwise, we are not the owner of the child set. We don't have to do anything to clear it. + parent->children = NULL; + YGNodeMarkDirtyInternal(parent); } YGNodeRef YGNodeGetChild(const YGNodeRef node, const uint32_t index) { @@ -1909,6 +2017,7 @@ static bool YGNodeFixedSizeSetMeasuredDimensions(const YGNodeRef node, static void YGZeroOutLayoutRecursivly(const YGNodeRef node) { memset(&(node->layout), 0, sizeof(YGLayout)); node->hasNewLayout = true; + YGCloneChildrenIfNeeded(node); const uint32_t childCount = YGNodeGetChildCount(node); for (uint32_t i = 0; i < childCount; i++) { const YGNodeRef child = YGNodeListGet(node->children, i); @@ -2082,6 +2191,9 @@ static void YGNodelayoutImpl(const YGNodeRef node, return; } + // At this point we know we're going to perform work. Ensure that each child has a mutable copy. + YGCloneChildrenIfNeeded(node); + // Reset layout flags, as they could have changed. node->layout.hadOverflow = false; @@ -3698,6 +3810,10 @@ void *YGConfigGetContext(const YGConfigRef config) { return config->context; } +void YGConfigSetNodeClonedFunc(const YGConfigRef config, const YGNodeClonedFunc callback) { + config->cloneNodeCallback = callback; +} + void YGSetMemoryFuncs(YGMalloc ygmalloc, YGCalloc yccalloc, YGRealloc ygrealloc, YGFree ygfree) { YGAssert(gNodeInstanceCount == 0 && gConfigInstanceCount == 0, "Cannot set memory functions: all node must be freed first"); diff --git a/yoga/Yoga.h b/yoga/Yoga.h index 81bbe7b3..ae954c51 100644 --- a/yoga/Yoga.h +++ b/yoga/Yoga.h @@ -60,6 +60,10 @@ typedef int (*YGLogger)(const YGConfigRef config, YGLogLevel level, const char *format, va_list args); +typedef void (*YGNodeClonedFunc)(YGNodeRef oldNode, + YGNodeRef newNode, + YGNodeRef parent, + int childIndex); typedef void *(*YGMalloc)(size_t size); typedef void *(*YGCalloc)(size_t count, size_t size); @@ -69,6 +73,7 @@ typedef void (*YGFree)(void *ptr); // YGNode WIN_EXPORT YGNodeRef YGNodeNew(void); WIN_EXPORT YGNodeRef YGNodeNewWithConfig(const YGConfigRef config); +WIN_EXPORT YGNodeRef YGNodeClone(const YGNodeRef node); WIN_EXPORT void YGNodeFree(const YGNodeRef node); WIN_EXPORT void YGNodeFreeRecursive(const YGNodeRef node); WIN_EXPORT void YGNodeReset(const YGNodeRef node); @@ -78,6 +83,7 @@ WIN_EXPORT void YGNodeInsertChild(const YGNodeRef node, const YGNodeRef child, const uint32_t index); WIN_EXPORT void YGNodeRemoveChild(const YGNodeRef node, const YGNodeRef child); +WIN_EXPORT void YGNodeRemoveAllChildren(const YGNodeRef node); WIN_EXPORT YGNodeRef YGNodeGetChild(const YGNodeRef node, const uint32_t index); WIN_EXPORT YGNodeRef YGNodeGetParent(const YGNodeRef node); WIN_EXPORT uint32_t YGNodeGetChildCount(const YGNodeRef node); @@ -264,6 +270,9 @@ WIN_EXPORT bool YGConfigIsExperimentalFeatureEnabled(const YGConfigRef config, WIN_EXPORT void YGConfigSetUseWebDefaults(const YGConfigRef config, const bool enabled); WIN_EXPORT bool YGConfigGetUseWebDefaults(const YGConfigRef config); +WIN_EXPORT void YGConfigSetNodeClonedFunc(const YGConfigRef config, + const YGNodeClonedFunc callback); + // Export only for C# WIN_EXPORT YGConfigRef YGConfigGetDefault(void);