mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
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:
committed by
Facebook GitHub Bot
parent
54b22211dc
commit
01eafef8b4
@@ -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;
|
||||
|
||||
+223
@@ -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
|
||||
Reference in New Issue
Block a user