Persistent Yoga
Summary:
This is meant to show a possible route format for a persistent form of Yoga. Where previous layouts can remain intact while still taking advantage of incremental layout by reusing previous subtrees.
```c
YGNodeRef YGNodeClone(const YGNodeRef node);
```
The core of this functionality is a new API to clone an existing node. This makes a new detached node with all the same values as the previous one. Conceptually this makes the original node "frozen" from that point on. It's now immutable. (This is not yet enforced at runtime in this PR but something we should add.)
Since the original is frozen, we reuse the children set from the original node. Their parent pointers still point back to the original tree though.
The cloned node is still mutable. It can have its styles updated, and nodes can be inserted or deleted. If an insertion/deletion happens on a cloned node whose children were reused, it'll first shallow clone its children automatically.
As a convenience I also added an API to clear all children:
```c
void YGNodeRemoveAllChildren(const YGNodeRef node);
```
During insert/delete, or as a result of layout a set of reused children may need to be first cloned. A kind of copy-on-write. When that happens, the host may want to respond. E.g. by updating the `context` such as by cloning any wrapper objects and attaching them to the new node.
```c
typedef void (*YGNodeClonedFunc)(YGNodeRef oldNode,
YGNodeRef newNode,
YGNodeRef parent,
int childIndex);
void YGConfigSetNodeClonedFunc(YGConfigRef config,
YGNodeClonedFunc callback);
```
This PR doesn't change any existing semantics for trees that are not first cloned.
It's possible for a single node to exist in two trees at once and be used by multiple threads. Therefore it's not safe to recursively free a whole tree when you use persistence. To solve this, any user of the library has to manually manage ref counting or tracing GC. E.g. by replicating the tree structure in a wrapper.
In a follow up we could consider moving ref counting into Yoga.
Closes https://github.com/facebook/yoga/pull/636
Reviewed By: emilsjolander
Differential Revision: D5941921
Pulled By: sebmarkbage
fbshipit-source-id: c8e93421824c112d09c4773bed4e3141b6491ccf
2017-10-17 01:02:04 -07:00
|
|
|
/**
|
2018-09-11 15:27:47 -07:00
|
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
Persistent Yoga
Summary:
This is meant to show a possible route format for a persistent form of Yoga. Where previous layouts can remain intact while still taking advantage of incremental layout by reusing previous subtrees.
```c
YGNodeRef YGNodeClone(const YGNodeRef node);
```
The core of this functionality is a new API to clone an existing node. This makes a new detached node with all the same values as the previous one. Conceptually this makes the original node "frozen" from that point on. It's now immutable. (This is not yet enforced at runtime in this PR but something we should add.)
Since the original is frozen, we reuse the children set from the original node. Their parent pointers still point back to the original tree though.
The cloned node is still mutable. It can have its styles updated, and nodes can be inserted or deleted. If an insertion/deletion happens on a cloned node whose children were reused, it'll first shallow clone its children automatically.
As a convenience I also added an API to clear all children:
```c
void YGNodeRemoveAllChildren(const YGNodeRef node);
```
During insert/delete, or as a result of layout a set of reused children may need to be first cloned. A kind of copy-on-write. When that happens, the host may want to respond. E.g. by updating the `context` such as by cloning any wrapper objects and attaching them to the new node.
```c
typedef void (*YGNodeClonedFunc)(YGNodeRef oldNode,
YGNodeRef newNode,
YGNodeRef parent,
int childIndex);
void YGConfigSetNodeClonedFunc(YGConfigRef config,
YGNodeClonedFunc callback);
```
This PR doesn't change any existing semantics for trees that are not first cloned.
It's possible for a single node to exist in two trees at once and be used by multiple threads. Therefore it's not safe to recursively free a whole tree when you use persistence. To solve this, any user of the library has to manually manage ref counting or tracing GC. E.g. by replicating the tree structure in a wrapper.
In a follow up we could consider moving ref counting into Yoga.
Closes https://github.com/facebook/yoga/pull/636
Reviewed By: emilsjolander
Differential Revision: D5941921
Pulled By: sebmarkbage
fbshipit-source-id: c8e93421824c112d09c4773bed4e3141b6491ccf
2017-10-17 01:02:04 -07:00
|
|
|
*
|
2018-11-29 11:35:34 -08:00
|
|
|
* This source code is licensed under the MIT license found in the LICENSE
|
|
|
|
* file in the root directory of this source tree.
|
Persistent Yoga
Summary:
This is meant to show a possible route format for a persistent form of Yoga. Where previous layouts can remain intact while still taking advantage of incremental layout by reusing previous subtrees.
```c
YGNodeRef YGNodeClone(const YGNodeRef node);
```
The core of this functionality is a new API to clone an existing node. This makes a new detached node with all the same values as the previous one. Conceptually this makes the original node "frozen" from that point on. It's now immutable. (This is not yet enforced at runtime in this PR but something we should add.)
Since the original is frozen, we reuse the children set from the original node. Their parent pointers still point back to the original tree though.
The cloned node is still mutable. It can have its styles updated, and nodes can be inserted or deleted. If an insertion/deletion happens on a cloned node whose children were reused, it'll first shallow clone its children automatically.
As a convenience I also added an API to clear all children:
```c
void YGNodeRemoveAllChildren(const YGNodeRef node);
```
During insert/delete, or as a result of layout a set of reused children may need to be first cloned. A kind of copy-on-write. When that happens, the host may want to respond. E.g. by updating the `context` such as by cloning any wrapper objects and attaching them to the new node.
```c
typedef void (*YGNodeClonedFunc)(YGNodeRef oldNode,
YGNodeRef newNode,
YGNodeRef parent,
int childIndex);
void YGConfigSetNodeClonedFunc(YGConfigRef config,
YGNodeClonedFunc callback);
```
This PR doesn't change any existing semantics for trees that are not first cloned.
It's possible for a single node to exist in two trees at once and be used by multiple threads. Therefore it's not safe to recursively free a whole tree when you use persistence. To solve this, any user of the library has to manually manage ref counting or tracing GC. E.g. by replicating the tree structure in a wrapper.
In a follow up we could consider moving ref counting into Yoga.
Closes https://github.com/facebook/yoga/pull/636
Reviewed By: emilsjolander
Differential Revision: D5941921
Pulled By: sebmarkbage
fbshipit-source-id: c8e93421824c112d09c4773bed4e3141b6491ccf
2017-10-17 01:02:04 -07:00
|
|
|
*/
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <yoga/Yoga.h>
|
2019-02-20 11:52:38 -08:00
|
|
|
#include <yoga/YGNode.h>
|
Persistent Yoga
Summary:
This is meant to show a possible route format for a persistent form of Yoga. Where previous layouts can remain intact while still taking advantage of incremental layout by reusing previous subtrees.
```c
YGNodeRef YGNodeClone(const YGNodeRef node);
```
The core of this functionality is a new API to clone an existing node. This makes a new detached node with all the same values as the previous one. Conceptually this makes the original node "frozen" from that point on. It's now immutable. (This is not yet enforced at runtime in this PR but something we should add.)
Since the original is frozen, we reuse the children set from the original node. Their parent pointers still point back to the original tree though.
The cloned node is still mutable. It can have its styles updated, and nodes can be inserted or deleted. If an insertion/deletion happens on a cloned node whose children were reused, it'll first shallow clone its children automatically.
As a convenience I also added an API to clear all children:
```c
void YGNodeRemoveAllChildren(const YGNodeRef node);
```
During insert/delete, or as a result of layout a set of reused children may need to be first cloned. A kind of copy-on-write. When that happens, the host may want to respond. E.g. by updating the `context` such as by cloning any wrapper objects and attaching them to the new node.
```c
typedef void (*YGNodeClonedFunc)(YGNodeRef oldNode,
YGNodeRef newNode,
YGNodeRef parent,
int childIndex);
void YGConfigSetNodeClonedFunc(YGConfigRef config,
YGNodeClonedFunc callback);
```
This PR doesn't change any existing semantics for trees that are not first cloned.
It's possible for a single node to exist in two trees at once and be used by multiple threads. Therefore it's not safe to recursively free a whole tree when you use persistence. To solve this, any user of the library has to manually manage ref counting or tracing GC. E.g. by replicating the tree structure in a wrapper.
In a follow up we could consider moving ref counting into Yoga.
Closes https://github.com/facebook/yoga/pull/636
Reviewed By: emilsjolander
Differential Revision: D5941921
Pulled By: sebmarkbage
fbshipit-source-id: c8e93421824c112d09c4773bed4e3141b6491ccf
2017-10-17 01:02:04 -07:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2019-02-20 11:52:38 -08:00
|
|
|
TEST(YogaTest, mutating_children_of_a_clone_clones_only_after_layout) {
|
Persistent Yoga
Summary:
This is meant to show a possible route format for a persistent form of Yoga. Where previous layouts can remain intact while still taking advantage of incremental layout by reusing previous subtrees.
```c
YGNodeRef YGNodeClone(const YGNodeRef node);
```
The core of this functionality is a new API to clone an existing node. This makes a new detached node with all the same values as the previous one. Conceptually this makes the original node "frozen" from that point on. It's now immutable. (This is not yet enforced at runtime in this PR but something we should add.)
Since the original is frozen, we reuse the children set from the original node. Their parent pointers still point back to the original tree though.
The cloned node is still mutable. It can have its styles updated, and nodes can be inserted or deleted. If an insertion/deletion happens on a cloned node whose children were reused, it'll first shallow clone its children automatically.
As a convenience I also added an API to clear all children:
```c
void YGNodeRemoveAllChildren(const YGNodeRef node);
```
During insert/delete, or as a result of layout a set of reused children may need to be first cloned. A kind of copy-on-write. When that happens, the host may want to respond. E.g. by updating the `context` such as by cloning any wrapper objects and attaching them to the new node.
```c
typedef void (*YGNodeClonedFunc)(YGNodeRef oldNode,
YGNodeRef newNode,
YGNodeRef parent,
int childIndex);
void YGConfigSetNodeClonedFunc(YGConfigRef config,
YGNodeClonedFunc callback);
```
This PR doesn't change any existing semantics for trees that are not first cloned.
It's possible for a single node to exist in two trees at once and be used by multiple threads. Therefore it's not safe to recursively free a whole tree when you use persistence. To solve this, any user of the library has to manually manage ref counting or tracing GC. E.g. by replicating the tree structure in a wrapper.
In a follow up we could consider moving ref counting into Yoga.
Closes https://github.com/facebook/yoga/pull/636
Reviewed By: emilsjolander
Differential Revision: D5941921
Pulled By: sebmarkbage
fbshipit-source-id: c8e93421824c112d09c4773bed4e3141b6491ccf
2017-10-17 01:02:04 -07:00
|
|
|
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));
|
2019-02-20 11:52:38 -08:00
|
|
|
ASSERT_EQ(YGNodeGetChild(root2, 0), YGNodeGetChild(root3, 0));
|
Persistent Yoga
Summary:
This is meant to show a possible route format for a persistent form of Yoga. Where previous layouts can remain intact while still taking advantage of incremental layout by reusing previous subtrees.
```c
YGNodeRef YGNodeClone(const YGNodeRef node);
```
The core of this functionality is a new API to clone an existing node. This makes a new detached node with all the same values as the previous one. Conceptually this makes the original node "frozen" from that point on. It's now immutable. (This is not yet enforced at runtime in this PR but something we should add.)
Since the original is frozen, we reuse the children set from the original node. Their parent pointers still point back to the original tree though.
The cloned node is still mutable. It can have its styles updated, and nodes can be inserted or deleted. If an insertion/deletion happens on a cloned node whose children were reused, it'll first shallow clone its children automatically.
As a convenience I also added an API to clear all children:
```c
void YGNodeRemoveAllChildren(const YGNodeRef node);
```
During insert/delete, or as a result of layout a set of reused children may need to be first cloned. A kind of copy-on-write. When that happens, the host may want to respond. E.g. by updating the `context` such as by cloning any wrapper objects and attaching them to the new node.
```c
typedef void (*YGNodeClonedFunc)(YGNodeRef oldNode,
YGNodeRef newNode,
YGNodeRef parent,
int childIndex);
void YGConfigSetNodeClonedFunc(YGConfigRef config,
YGNodeClonedFunc callback);
```
This PR doesn't change any existing semantics for trees that are not first cloned.
It's possible for a single node to exist in two trees at once and be used by multiple threads. Therefore it's not safe to recursively free a whole tree when you use persistence. To solve this, any user of the library has to manually manage ref counting or tracing GC. E.g. by replicating the tree structure in a wrapper.
In a follow up we could consider moving ref counting into Yoga.
Closes https://github.com/facebook/yoga/pull/636
Reviewed By: emilsjolander
Differential Revision: D5941921
Pulled By: sebmarkbage
fbshipit-source-id: c8e93421824c112d09c4773bed4e3141b6491ccf
2017-10-17 01:02:04 -07:00
|
|
|
|
|
|
|
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));
|
2019-02-20 11:52:38 -08:00
|
|
|
ASSERT_EQ(YGNodeGetChild(root3, 0), YGNodeGetChild(root4, 0));
|
|
|
|
|
|
|
|
YGNodeCalculateLayout(root4, YGUndefined, YGUndefined, YGDirectionLTR);
|
Persistent Yoga
Summary:
This is meant to show a possible route format for a persistent form of Yoga. Where previous layouts can remain intact while still taking advantage of incremental layout by reusing previous subtrees.
```c
YGNodeRef YGNodeClone(const YGNodeRef node);
```
The core of this functionality is a new API to clone an existing node. This makes a new detached node with all the same values as the previous one. Conceptually this makes the original node "frozen" from that point on. It's now immutable. (This is not yet enforced at runtime in this PR but something we should add.)
Since the original is frozen, we reuse the children set from the original node. Their parent pointers still point back to the original tree though.
The cloned node is still mutable. It can have its styles updated, and nodes can be inserted or deleted. If an insertion/deletion happens on a cloned node whose children were reused, it'll first shallow clone its children automatically.
As a convenience I also added an API to clear all children:
```c
void YGNodeRemoveAllChildren(const YGNodeRef node);
```
During insert/delete, or as a result of layout a set of reused children may need to be first cloned. A kind of copy-on-write. When that happens, the host may want to respond. E.g. by updating the `context` such as by cloning any wrapper objects and attaching them to the new node.
```c
typedef void (*YGNodeClonedFunc)(YGNodeRef oldNode,
YGNodeRef newNode,
YGNodeRef parent,
int childIndex);
void YGConfigSetNodeClonedFunc(YGConfigRef config,
YGNodeClonedFunc callback);
```
This PR doesn't change any existing semantics for trees that are not first cloned.
It's possible for a single node to exist in two trees at once and be used by multiple threads. Therefore it's not safe to recursively free a whole tree when you use persistence. To solve this, any user of the library has to manually manage ref counting or tracing GC. E.g. by replicating the tree structure in a wrapper.
In a follow up we could consider moving ref counting into Yoga.
Closes https://github.com/facebook/yoga/pull/636
Reviewed By: emilsjolander
Differential Revision: D5941921
Pulled By: sebmarkbage
fbshipit-source-id: c8e93421824c112d09c4773bed4e3141b6491ccf
2017-10-17 01:02:04 -07:00
|
|
|
ASSERT_NE(YGNodeGetChild(root3, 0), YGNodeGetChild(root4, 0));
|
2019-02-20 11:52:38 -08:00
|
|
|
YGNodeCalculateLayout(root3, YGUndefined, YGUndefined, YGDirectionLTR);
|
|
|
|
ASSERT_NE(YGNodeGetChild(root2, 0), YGNodeGetChild(root3, 0));
|
Persistent Yoga
Summary:
This is meant to show a possible route format for a persistent form of Yoga. Where previous layouts can remain intact while still taking advantage of incremental layout by reusing previous subtrees.
```c
YGNodeRef YGNodeClone(const YGNodeRef node);
```
The core of this functionality is a new API to clone an existing node. This makes a new detached node with all the same values as the previous one. Conceptually this makes the original node "frozen" from that point on. It's now immutable. (This is not yet enforced at runtime in this PR but something we should add.)
Since the original is frozen, we reuse the children set from the original node. Their parent pointers still point back to the original tree though.
The cloned node is still mutable. It can have its styles updated, and nodes can be inserted or deleted. If an insertion/deletion happens on a cloned node whose children were reused, it'll first shallow clone its children automatically.
As a convenience I also added an API to clear all children:
```c
void YGNodeRemoveAllChildren(const YGNodeRef node);
```
During insert/delete, or as a result of layout a set of reused children may need to be first cloned. A kind of copy-on-write. When that happens, the host may want to respond. E.g. by updating the `context` such as by cloning any wrapper objects and attaching them to the new node.
```c
typedef void (*YGNodeClonedFunc)(YGNodeRef oldNode,
YGNodeRef newNode,
YGNodeRef parent,
int childIndex);
void YGConfigSetNodeClonedFunc(YGConfigRef config,
YGNodeClonedFunc callback);
```
This PR doesn't change any existing semantics for trees that are not first cloned.
It's possible for a single node to exist in two trees at once and be used by multiple threads. Therefore it's not safe to recursively free a whole tree when you use persistence. To solve this, any user of the library has to manually manage ref counting or tracing GC. E.g. by replicating the tree structure in a wrapper.
In a follow up we could consider moving ref counting into Yoga.
Closes https://github.com/facebook/yoga/pull/636
Reviewed By: emilsjolander
Differential Revision: D5941921
Pulled By: sebmarkbage
fbshipit-source-id: c8e93421824c112d09c4773bed4e3141b6491ccf
2017-10-17 01:02:04 -07:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
2017-12-05 08:16:49 -08:00
|
|
|
// Freeing the original root should be safe as long as we don't free its
|
|
|
|
// children.
|
Persistent Yoga
Summary:
This is meant to show a possible route format for a persistent form of Yoga. Where previous layouts can remain intact while still taking advantage of incremental layout by reusing previous subtrees.
```c
YGNodeRef YGNodeClone(const YGNodeRef node);
```
The core of this functionality is a new API to clone an existing node. This makes a new detached node with all the same values as the previous one. Conceptually this makes the original node "frozen" from that point on. It's now immutable. (This is not yet enforced at runtime in this PR but something we should add.)
Since the original is frozen, we reuse the children set from the original node. Their parent pointers still point back to the original tree though.
The cloned node is still mutable. It can have its styles updated, and nodes can be inserted or deleted. If an insertion/deletion happens on a cloned node whose children were reused, it'll first shallow clone its children automatically.
As a convenience I also added an API to clear all children:
```c
void YGNodeRemoveAllChildren(const YGNodeRef node);
```
During insert/delete, or as a result of layout a set of reused children may need to be first cloned. A kind of copy-on-write. When that happens, the host may want to respond. E.g. by updating the `context` such as by cloning any wrapper objects and attaching them to the new node.
```c
typedef void (*YGNodeClonedFunc)(YGNodeRef oldNode,
YGNodeRef newNode,
YGNodeRef parent,
int childIndex);
void YGConfigSetNodeClonedFunc(YGConfigRef config,
YGNodeClonedFunc callback);
```
This PR doesn't change any existing semantics for trees that are not first cloned.
It's possible for a single node to exist in two trees at once and be used by multiple threads. Therefore it's not safe to recursively free a whole tree when you use persistence. To solve this, any user of the library has to manually manage ref counting or tracing GC. E.g. by replicating the tree structure in a wrapper.
In a follow up we could consider moving ref counting into Yoga.
Closes https://github.com/facebook/yoga/pull/636
Reviewed By: emilsjolander
Differential Revision: D5941921
Pulled By: sebmarkbage
fbshipit-source-id: c8e93421824c112d09c4773bed4e3141b6491ccf
2017-10-17 01:02:04 -07:00
|
|
|
YGNodeFree(root);
|
|
|
|
|
|
|
|
YGNodeCalculateLayout(root2, YGUndefined, YGUndefined, YGDirectionLTR);
|
|
|
|
|
|
|
|
YGNodeFreeRecursive(root2);
|
|
|
|
|
|
|
|
YGNodeFree(root_child0);
|
|
|
|
YGNodeFree(root_child1);
|
|
|
|
|
|
|
|
YGConfigFree(config);
|
|
|
|
|
|
|
|
ASSERT_EQ(initialInstanceCount, YGNodeGetInstanceCount());
|
|
|
|
}
|
2019-02-20 11:52:38 -08:00
|
|
|
|
|
|
|
TEST(YogaTest, mixed_shared_and_owned_children) {
|
|
|
|
// Don't try this at home!
|
|
|
|
|
|
|
|
YGNodeRef root0 = YGNodeNew();
|
|
|
|
YGNodeRef root1 = YGNodeNew();
|
|
|
|
|
|
|
|
YGNodeRef root0_child0 = YGNodeNew();
|
|
|
|
YGNodeRef root0_child0_0 = YGNodeNew();
|
|
|
|
YGNodeInsertChild(root0, root0_child0, 0);
|
|
|
|
YGNodeInsertChild(root0_child0, root0_child0_0, 0);
|
|
|
|
|
|
|
|
YGNodeRef root1_child0 = YGNodeNew();
|
|
|
|
YGNodeRef root1_child2 = YGNodeNew();
|
|
|
|
YGNodeInsertChild(root1, root1_child0, 0);
|
|
|
|
YGNodeInsertChild(root1, root1_child2, 1);
|
|
|
|
|
|
|
|
auto children = root1->getChildren();
|
|
|
|
children.insert(children.begin() + 1, root0_child0);
|
|
|
|
root1->setChildren(children);
|
|
|
|
auto secondChild = YGNodeGetChild(root1, 1);
|
|
|
|
ASSERT_EQ(secondChild, YGNodeGetChild(root0, 0));
|
|
|
|
ASSERT_EQ(YGNodeGetChild(secondChild, 0), YGNodeGetChild(root0_child0, 0));
|
|
|
|
|
|
|
|
YGNodeCalculateLayout(root1, YGUndefined, YGUndefined, YGDirectionLTR);
|
|
|
|
secondChild = YGNodeGetChild(root1, 1);
|
|
|
|
ASSERT_NE(secondChild, YGNodeGetChild(root0, 0));
|
|
|
|
ASSERT_EQ(YGNodeGetOwner(secondChild), root1);
|
|
|
|
ASSERT_NE(YGNodeGetChild(secondChild, 0), YGNodeGetChild(root0_child0, 0));
|
|
|
|
ASSERT_EQ(YGNodeGetOwner(YGNodeGetChild(secondChild, 0)), secondChild);
|
|
|
|
}
|