229 lines
8.1 KiB
C++
229 lines
8.1 KiB
C++
/*
|
|
* 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<
|
|
std::vector<std::shared_ptr<const ShadowNode>>>(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";
|
|
}
|
|
});
|
|
|
|
if (delegate_ != nullptr) {
|
|
delegate_->uiManagerDidUpdateShadowTree(tagToProps);
|
|
}
|
|
}
|
|
|
|
} // namespace facebook::react
|