Add updateShadowTree to UIManager (#50020)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/50020

Changelog: [Internal]
[General][Added] - Add `updateShadowTree` to UIManager

We have an internal method `updateShadowTree` that does similar thing as `ReanimatedModuleProxy::cloneShadowTreeWithNewProps` in reanimated (https://github.com/software-mansion/react-native-reanimated/blob/6516b2e4e12c0186cdf00e7f85b8fef820b97088/packages/react-native-reanimated/Common/cpp/reanimated/Fabric/ShadowTreeCloner.cpp#L67 it's logically similar but not copied over), with which we can efficiently commit props for multiple shadowNodes onto ShadowTree. it guarantees every ShadowNode that needs to be updated in this commit is only cloned once.

Reviewed By: javache

Differential Revision: D71129150

fbshipit-source-id: 92156114ec6c2694e1639cf2ecf153d243ade769
This commit is contained in:
Zeya Peng
2025-03-14 08:42:23 -07:00
committed by Facebook GitHub Bot
parent 54b22211dc
commit 01eafef8b4
2 changed files with 226 additions and 0 deletions
@@ -201,6 +201,9 @@ class UIManager final : public ShadowTreeDelegate {
void reportMount(SurfaceId surfaceId) const;
void updateShadowTree(
const std::unordered_map<Tag, folly::dynamic>& tagToProps);
private:
friend class UIManagerBinding;
friend class Scheduler;
@@ -0,0 +1,223 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "UIManager.h"
#include <glog/logging.h>
#include <react/debug/react_native_assert.h>
#include <react/renderer/components/root/RootShadowNode.h>
#include <react/renderer/core/ShadowNode.h>
#include <deque>
#include <memory>
#include <optional>
#include <ranges>
#include <vector>
namespace facebook::react {
namespace {
struct ShadowNodeUpdateInfo {
Tag tag;
int depth;
std::weak_ptr<const ShadowNode> node;
// populate for node that needs props update
std::optional<folly::dynamic> changedProps;
// populate for node that's also a ancestor node for updated nodes
std::vector<int> updatedChildrenIndices{};
};
void addAncestorsToUpdateList(
std::shared_ptr<const ShadowNode>& shadowNode,
std::shared_ptr<const RootShadowNode>& rootShadowNode,
std::vector<ShadowNodeUpdateInfo>& shadowNodesToUpdate) {
// list of ancestors from root ShadowNode to current ShadowNode's parent
auto ancestors = shadowNode->getFamily().getAncestors(*rootShadowNode);
std::vector<std::shared_ptr<const ShadowNode>> ancestorShadowNodesShared{};
ancestorShadowNodesShared.resize(ancestors.size());
std::shared_ptr<const ShadowNode> currentShadowNode = rootShadowNode;
auto ancestorIndex = 0;
while (!currentShadowNode->getChildren().empty() &&
currentShadowNode->getTag() != shadowNode->getTag()) {
ancestorShadowNodesShared[ancestorIndex] = currentShadowNode;
auto children = currentShadowNode->getChildren();
auto childIndex = ancestors[ancestorIndex].second;
currentShadowNode = children[childIndex];
ancestorIndex++;
}
int ancestorDepth = static_cast<int>(ancestors.size() - 1);
// iterate from current ShadowNode's parent to root ShadowNode
for (auto iter = ancestors.rbegin(); iter != ancestors.rend(); ++iter) {
auto& ancestorShadowNode = iter->first.get();
auto ancestorTag = ancestorShadowNode.getTag();
auto ancestorAddedToUpdateList = std::find_if(
shadowNodesToUpdate.begin(),
shadowNodesToUpdate.end(),
[ancestorTag](const auto& elem) { return elem.tag == ancestorTag; });
if (ancestorAddedToUpdateList == shadowNodesToUpdate.end()) {
react_native_assert(
ancestorShadowNodesShared[ancestorDepth]->getTag() ==
ancestorShadowNode.getTag());
shadowNodesToUpdate.push_back({
.tag = ancestorShadowNode.getTag(),
.depth = ancestorDepth,
.node = ancestorShadowNodesShared[ancestorDepth],
.updatedChildrenIndices = {iter->second},
});
} else {
ancestorAddedToUpdateList->updatedChildrenIndices.push_back(iter->second);
}
ancestorDepth--;
}
}
} // namespace
/* Commit a map of ShadowNode props to ShadowTree, with guarantee that each
* ShadowNode is cloned only once in the commit.
* The tree cloning algorithm is inspired by `cloneShadowTreeWithNewProps` in
* https://github.com/software-mansion/react-native-reanimated/ (under MIT
* license).
*/
void UIManager::updateShadowTree(
const std::unordered_map<Tag, folly::dynamic>& tagToProps) {
const auto& contextContainer = *contextContainer_;
std::unordered_map<Tag, folly::dynamic> remainingTagToProps = tagToProps;
getShadowTreeRegistry().enumerate([&](const ShadowTree& shadowTree,
bool& stop) {
if (remainingTagToProps.empty()) {
stop = true;
return;
}
auto rootShadowNode = shadowTree.getCurrentRevision().rootShadowNode;
auto surfaceId = rootShadowNode->getSurfaceId();
std::vector<ShadowNodeUpdateInfo> shadowNodesToUpdate;
// Step 1: Create a list of shadow nodes to update
// which includes all the ShadowNodes of tags in input map, plus all
// their ancestors
std::deque<std::shared_ptr<const ShadowNode>> deque{rootShadowNode};
int depth = 0;
while (!deque.empty()) {
auto size = deque.size();
for (auto i = 0; i < size; i++) {
auto shadowNode = std::move(deque.front());
deque.pop_front();
if (auto nodesPropsIt = remainingTagToProps.find(shadowNode->getTag());
nodesPropsIt != remainingTagToProps.end()) {
auto shadowNodeTag = shadowNode->getTag();
auto shadowNodeAddedToUpdateList = std::find_if(
shadowNodesToUpdate.begin(),
shadowNodesToUpdate.end(),
[shadowNodeTag](const auto& elem) {
return elem.tag == shadowNodeTag;
});
if (shadowNodeAddedToUpdateList == shadowNodesToUpdate.end()) {
shadowNodesToUpdate.push_back(
{.tag = shadowNodeTag,
.depth = depth,
.node = shadowNode,
.changedProps = nodesPropsIt->second});
} else {
shadowNodeAddedToUpdateList->changedProps = nodesPropsIt->second;
}
remainingTagToProps.erase(nodesPropsIt->first);
// Add all its ancestors to shadowNodesToUpdate list
addAncestorsToUpdateList(
shadowNode, rootShadowNode, shadowNodesToUpdate);
}
for (const auto& child : shadowNode->getChildren()) {
deque.push_back(child);
}
}
depth++;
}
if (shadowNodesToUpdate.empty()) {
return;
}
// Step 2: Clone nodes from children to ancestors
std::sort(
shadowNodesToUpdate.begin(),
shadowNodesToUpdate.end(),
[](const auto& a, const auto& b) { return a.depth > b.depth; });
std::unordered_map<Tag, std::shared_ptr<ShadowNode>> clonedShadowNodes;
for (auto& nodeUpdateInfo : shadowNodesToUpdate) {
if (auto oldShadowNode = nodeUpdateInfo.node.lock()) {
Props::Shared newProps;
if (nodeUpdateInfo.changedProps) {
PropsParserContext propsParserContext{surfaceId, contextContainer};
auto componentDescriptor =
componentDescriptorRegistry_
->findComponentDescriptorByHandle_DO_NOT_USE_THIS_IS_BROKEN(
oldShadowNode->getComponentHandle());
newProps = componentDescriptor->cloneProps(
propsParserContext,
oldShadowNode->getProps(),
RawProps(nodeUpdateInfo.changedProps.value()));
} else {
newProps = oldShadowNode->getProps();
}
ShadowNodeFragment fragment;
auto children = oldShadowNode->getChildren();
// If children are previously updated (children should be cloned and
// updated before parents), add it to the children list of ShadowNode
for (auto& updatedChildIndex : nodeUpdateInfo.updatedChildrenIndices) {
if (updatedChildIndex < children.size()) {
auto childTag = children[updatedChildIndex]->getTag();
auto clonedShadowNodesIt = clonedShadowNodes.find(childTag);
if (clonedShadowNodesIt != clonedShadowNodes.end()) {
children[updatedChildIndex] = clonedShadowNodesIt->second;
} else {
LOG(ERROR) << "Child ShadowNode has not been cloned";
}
} else {
LOG(WARNING) << "Child no longer exits";
}
}
auto cloned = oldShadowNode->clone(
{.props = newProps,
.children = std::make_shared<ShadowNode::ListOfShared>(children)});
clonedShadowNodes.insert({oldShadowNode->getTag(), std::move(cloned)});
} else {
LOG(ERROR) << "oldShadowNode is null";
continue;
}
}
// Step 3: Commit ShadowTree
if (auto it = clonedShadowNodes.find(rootShadowNode->getTag());
it != clonedShadowNodes.end()) {
shadowTree.commit(
[rootNode =
it->second](const RootShadowNode&) -> RootShadowNode::Unshared {
return std::static_pointer_cast<RootShadowNode>(rootNode);
},
{});
} else {
LOG(ERROR) << "Root ShadowNode has not been cloned";
}
});
}
} // namespace facebook::react