diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h index c7ce8a0f0bb..674b812510c 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h @@ -201,6 +201,9 @@ class UIManager final : public ShadowTreeDelegate { void reportMount(SurfaceId surfaceId) const; + void updateShadowTree( + const std::unordered_map& tagToProps); + private: friend class UIManagerBinding; friend class Scheduler; diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerUpdateShadowTree.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerUpdateShadowTree.cpp new file mode 100644 index 00000000000..c031c2acc68 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerUpdateShadowTree.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +namespace facebook::react { + +namespace { + +struct ShadowNodeUpdateInfo { + Tag tag; + int depth; + std::weak_ptr node; + // populate for node that needs props update + std::optional changedProps; + // populate for node that's also a ancestor node for updated nodes + std::vector updatedChildrenIndices{}; +}; + +void addAncestorsToUpdateList( + std::shared_ptr& shadowNode, + std::shared_ptr& rootShadowNode, + std::vector& shadowNodesToUpdate) { + // list of ancestors from root ShadowNode to current ShadowNode's parent + auto ancestors = shadowNode->getFamily().getAncestors(*rootShadowNode); + + std::vector> ancestorShadowNodesShared{}; + ancestorShadowNodesShared.resize(ancestors.size()); + std::shared_ptr 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(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& tagToProps) { + const auto& contextContainer = *contextContainer_; + + std::unordered_map 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 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> 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> 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(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(rootNode); + }, + {}); + } else { + LOG(ERROR) << "Root ShadowNode has not been cloned"; + } + }); +} + +} // namespace facebook::react