-
Notifications
You must be signed in to change notification settings - Fork 24.7k
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
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
?
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.
The implementation is not really well defined if you call this method on anything that's not the RootShadowNode. Should this live on the root instead?
} | ||
|
||
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.
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.
You could also model this as a std::optional and only do the copy when you first need to. That also avoids the need for shouldUpdateChildren
.
@@ -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 |
@javache What is the state of this? Do you think this approach needs reworking? |
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.
@@ -368,6 +368,80 @@ ShadowNode::Unshared ShadowNode::cloneTree( | |||
return std::const_pointer_cast<ShadowNode>(childNode); | |||
} | |||
|
|||
ShadowNode::Unshared ShadowNode::cloneMultipleRecursive( | |||
const ShadowNode& shadowNode, |
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.
Could this just be a instance method? Alternatively make it static if it doesn't depend on instance state.
const std::function<Unshared( | ||
const ShadowNode& oldShadowNode, | ||
const ShadowNodeFragment& fragment)>& callback) const { | ||
const auto family = &shadowNode.getFamily(); |
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.
Use const auto *
to make it explicit this isn't a copy. Even better would be to just use the ref here, and only deref it when looking it up.
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.
You could also model this as a std::optional and only do the copy when you first need to. That also avoids the need for shouldUpdateChildren
.
*/ | ||
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.
The implementation is not really well defined if you call this method on anything that's not the RootShadowNode. Should this live on the root instead?
Yeah, that seems reasonable. Let's keep it backwards compatible though and forward the API. |
@javache has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator. |
This pull request was successfully merged by @bartlomiejbloniarz in 1161fb4 When will my fix make it into a release? | How to file a pick request? |
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.