// Copyright (c) Facebook, Inc. and its affiliates. // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. #include "ShadowTree.h" #include #include #include #include #include #include "ShadowTreeDelegate.h" namespace facebook { namespace react { static void updateMountedFlag( const SharedShadowNodeList &oldChildren, const SharedShadowNodeList &newChildren) { // This is a simplified version of Diffing algorithm that only updates // `mounted` flag on `ShadowNode`s. The algorithm sets "mounted" flag before // "unmounted" to allow `ShadowNode` detect a situation where the node was // remounted. if (&oldChildren == &newChildren) { // Lists are identical, nothing to do. return; } if (oldChildren.size() == 0 && newChildren.size() == 0) { // Both lists are empty, nothing to do. return; } int index; // Stage 1: Mount and unmount "updated" children. for (index = 0; index < oldChildren.size() && index < newChildren.size(); index++) { const auto &oldChild = oldChildren[index]; const auto &newChild = newChildren[index]; if (oldChild == newChild) { // Nodes are identical, skipping the subtree. continue; } if (oldChild->getTag() != newChild->getTag()) { // Totally different nodes, updating is impossible. break; } newChild->setMounted(true); oldChild->setMounted(false); updateMountedFlag(oldChild->getChildren(), newChild->getChildren()); } int lastIndexAfterFirstStage = index; // State 2: Mount new children. for (index = lastIndexAfterFirstStage; index < newChildren.size(); index++) { const auto &newChild = newChildren[index]; newChild->setMounted(true); updateMountedFlag({}, newChild->getChildren()); } // State 3: Unmount old children. for (index = lastIndexAfterFirstStage; index < oldChildren.size(); index++) { const auto &oldChild = oldChildren[index]; oldChild->setMounted(false); updateMountedFlag(oldChild->getChildren(), {}); } } ShadowTree::ShadowTree( SurfaceId surfaceId, const LayoutConstraints &layoutConstraints, const LayoutContext &layoutContext) : surfaceId_(surfaceId) { const auto noopEventEmitter = std::make_shared( nullptr, -1, std::shared_ptr()); const auto props = std::make_shared( *RootShadowNode::defaultSharedProps(), layoutConstraints, layoutContext); rootShadowNode_ = std::make_shared( ShadowNodeFragment{ .tag = surfaceId, .rootTag = surfaceId, .props = props, .eventEmitter = noopEventEmitter, }, nullptr); } ShadowTree::~ShadowTree() { commit([](const SharedRootShadowNode &oldRootShadowNode) { return std::make_shared( *oldRootShadowNode, ShadowNodeFragment{.children = ShadowNode::emptySharedShadowNodeSharedList()}); }); } Tag ShadowTree::getSurfaceId() const { return surfaceId_; } bool ShadowTree::commit( std::function transaction, int attempts, int *revision) const { SystraceSection s("ShadowTree::commit"); while (attempts) { attempts--; SharedRootShadowNode oldRootShadowNode; { // Reading `rootShadowNode_` in shared manner. std::shared_lock lock(commitMutex_); oldRootShadowNode = rootShadowNode_; } UnsharedRootShadowNode newRootShadowNode = transaction(oldRootShadowNode); if (!newRootShadowNode) { break; } newRootShadowNode->layout(); newRootShadowNode->sealRecursive(); auto mutations = calculateShadowViewMutations(*oldRootShadowNode, *newRootShadowNode); { // Updating `rootShadowNode_` in unique manner if it hasn't changed. std::unique_lock lock(commitMutex_); if (rootShadowNode_ != oldRootShadowNode) { continue; } rootShadowNode_ = newRootShadowNode; { std::lock_guard dispatchLock(EventEmitter::DispatchMutex()); updateMountedFlag( oldRootShadowNode->getChildren(), newRootShadowNode->getChildren()); } revision_++; // Returning last revision if requested. if (revision) { *revision = revision_; } } emitLayoutEvents(mutations); if (delegate_) { delegate_->shadowTreeDidCommit(*this, mutations); } return true; } return false; } void ShadowTree::emitLayoutEvents( const ShadowViewMutationList &mutations) const { SystraceSection s("ShadowTree::emitLayoutEvents"); for (const auto &mutation : mutations) { // Only `Insert` and `Update` mutations can affect layout metrics. if (mutation.type != ShadowViewMutation::Insert && mutation.type != ShadowViewMutation::Update) { continue; } const auto viewEventEmitter = std::dynamic_pointer_cast( mutation.newChildShadowView.eventEmitter); // Checking if particular shadow node supports `onLayout` event (part of // `ViewEventEmitter`). if (!viewEventEmitter) { continue; } // Checking if the `onLayout` event was requested for the particular Shadow // Node. const auto viewProps = std::dynamic_pointer_cast( mutation.newChildShadowView.props); if (viewProps && !viewProps->onLayout) { continue; } // In case if we have `oldChildShadowView`, checking that layout metrics // have changed. if (mutation.type != ShadowViewMutation::Update && mutation.oldChildShadowView.layoutMetrics == mutation.newChildShadowView.layoutMetrics) { continue; } viewEventEmitter->onLayout(mutation.newChildShadowView.layoutMetrics); } } #pragma mark - Delegate void ShadowTree::setDelegate(ShadowTreeDelegate const *delegate) { delegate_ = delegate; } ShadowTreeDelegate const *ShadowTree::getDelegate() const { return delegate_; } } // namespace react } // namespace facebook