-
Notifications
You must be signed in to change notification settings - Fork 24.6k
Add cloneMultiple
method to the ShadowNode
class
#50624
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Add cloneMultiple
method to the ShadowNode
class
#50624
Conversation
cloneMultiple
method to the ShadowNode
class
const ShadowNode& oldShadowNode, | ||
const std::optional<ShadowNode::ListOfShared>& newChildren)>& callback) | ||
const { | ||
std::unordered_map<const ShadowNodeFamily*, int> childrenCount; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
std::unordered_map<const ShadowNodeFamily*, int> childrenCount; | |
std::unordered_map<const std::ref<const ShadowNodeFamily>, unsigned int> childrenCounts; |
@@ -252,6 +266,15 @@ class ShadowNode : public Sealable, | |||
const ShadowNode& sourceShadowNode, | |||
const Props::Shared& props); | |||
|
|||
Unshared cloneMultipleRecursive( | |||
const ShadowNode& shadowNode, | |||
const std::unordered_set<const ShadowNodeFamily*>& families, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's pass ShadowNodeFamily
as a std::ref
instead of a pointer maybe?
const std::unordered_set<const ShadowNodeFamily*>& families, | |
const std::unordered_set<const ShadowNodeFamily &>& families, |
const std::unordered_set<const ShadowNodeFamily*>& families, | ||
const std::function<Unshared( | ||
const ShadowNode& oldShadowNode, | ||
const std::optional<ShadowNode::ListOfShared>& newChildren)>& |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's also explain why newChildren
is optional.
*/ | ||
Unshared cloneMultiple( | ||
const std::unordered_set<const ShadowNodeFamily*>& families, | ||
const std::function<Unshared( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's also mention what happens if callback
returns nullptr
?
} | ||
|
||
if (childrenCount.empty()) { | ||
return ShadowNode::Unshared{nullptr}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to be that explicit about the return type?
return ShadowNode::Unshared{nullptr}; | |
return nullptr; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since these methods don't actually need to live on ShadowNode, what about creating a ShadowNodeMutations helper class (and defining it as a friend class if necessary)
const std::unordered_set<const ShadowNodeFamily*>& families, | ||
const std::unordered_map<const ShadowNodeFamily*, int>& childrenCount, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of using pointers to ShadowNodeFamily, let's try to use refs, eg using std::reference_wrapper
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried that. The problem with it is that I use unordered containers, so that would require implementing hash
methods for the family.
For now I changed the loop over ancestors, to lock
the weak pointer, so that when we access any fields of a family, we are sure it's still around. After that we only compare these pointers as numbers - we never dereference them so it will be safe. This would only fail if someone passed familiesToUpdate
to the function, without owning them. In reanimated we keep shared_ptrs
of ShadowNodes related to these families to ensure that they are still available when we perform the algorithm.
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could change the api to instead work with shared_ptr
s. The problem is that users of this api (so for example reanimated), don't have access to shared_ptr
s of the ShadowNodeFamily
class, as you only can get a reference from the ShadowNode.getFamily()
method.
const std::optional<ShadowNode::ListOfShared>& newChildren)>& callback) | ||
const { | ||
const auto family = &shadowNode.getFamily(); | ||
auto children = shadowNode.getChildren(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid copying the list
auto children = shadowNode.getChildren(); | |
const auto& children = shadowNode.getChildren(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am copying that on purpose, as I want to modify it later on in the loop.
@@ -368,6 +368,80 @@ ShadowNode::Unshared ShadowNode::cloneTree( | |||
return std::const_pointer_cast<ShadowNode>(childNode); | |||
} | |||
|
|||
ShadowNode::Unshared ShadowNode::cloneMultipleRecursive( | |||
const ShadowNode& shadowNode, | |||
const std::unordered_set<const ShadowNodeFamily*>& families, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this familiesToUpdate
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will rename
const std::unordered_set<const ShadowNodeFamily*>& families, | ||
const std::function<Unshared( | ||
const ShadowNode& oldShadowNode, | ||
const std::optional<ShadowNode::ListOfShared>& newChildren)>& callback) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pass in a ShadowNodeFragment
instead of children explicitly
@javache Sure I can extract it to a separate file. Should I also move the |
Summary:
This PR adds a new cloning method, allowing for updating multiple nodes in a single transaction. It works in two phases:
So the idea is that when we want to update all the red nodes in this picture, we first find the nodes in the green area and the clone only them in the correct order (children are cloned before parents):

Adapting this method brought a huge performance gain to reanimated. I want to upstream it, so that:
ShadowNode
class gives us access to the parent field inShadowNodeFamily
so we can traverse the tree upwards, allowing for a optimal implementation of the first phase (in reanimated we repeatedly callgetAncestors
, which revisits some nodes multiple times)A naive approach that calls
cloneTree
for every node is much slower, as it has to repeat many operations.Changelog:
[GENERAL] [ADDED] - Added
cloneMultiple
toShadowNode
class.Test Plan:
I tested it with the following reanimated implementation and everything works fine:
I would like to add tests for it, but I'm not sure what's the best approach for that in the repo.