diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/mounting/CMakeLists.txt index cc493d878cf..778b5ed3d67 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/mounting/CMakeLists.txt @@ -16,6 +16,7 @@ add_compile_options( file(GLOB react_renderer_mounting_SRC CONFIGURE_DEPENDS *.cpp + internal/*.cpp stubs/*.cpp) add_library(react_renderer_mounting OBJECT ${react_renderer_mounting_SRC}) diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/Differentiator.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/Differentiator.cpp index 818bb8a0a03..e0e47713f1d 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/Differentiator.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mounting/Differentiator.cpp @@ -10,9 +10,10 @@ #include #include #include -#include -#include #include +#include "internal/CullingContext.h" +#include "internal/ShadowViewNodePair.h" +#include "internal/sliceChildShadowNodeViewPairs.h" #include "ShadowView.h" @@ -43,14 +44,6 @@ namespace facebook::react { enum class ReparentMode { Flatten, Unflatten }; -bool ShadowViewNodePair::operator==(const ShadowViewNodePair& rhs) const { - return this->shadowNode == rhs.shadowNode; -} - -bool ShadowViewNodePair::operator!=(const ShadowViewNodePair& rhs) const { - return !(*this == rhs); -} - #ifdef DEBUG_LOGS_DIFFER static std::ostream& operator<<( std::ostream& out, @@ -225,210 +218,6 @@ static std::ostream& operator<<(std::ostream& out, TinyMap& map) { } #endif -/* - * Sorting comparator for `reorderInPlaceIfNeeded`. - */ -static bool shouldFirstPairComesBeforeSecondOne( - const ShadowViewNodePair* lhs, - const ShadowViewNodePair* rhs) noexcept { - return lhs->shadowNode->getOrderIndex() < rhs->shadowNode->getOrderIndex(); -} - -static CullingContext adjustCullingContextIfNeeded( - CullingContext cullingContext, - const ShadowViewNodePair& pair) { - if (ReactNativeFeatureFlags::enableViewCulling()) { - if (auto scrollViewShadowNode = - dynamic_cast(pair.shadowNode)) { - cullingContext.frame.origin = - -scrollViewShadowNode->getContentOriginOffset( - /* includeTransform */ true); - cullingContext.frame.size = - scrollViewShadowNode->getLayoutMetrics().frame.size; - } else { - cullingContext.frame.origin -= pair.shadowView.layoutMetrics.frame.origin; - - if (auto layoutableShadowNode = - dynamic_cast(pair.shadowNode)) { - cullingContext.transform = - cullingContext.transform * layoutableShadowNode->getTransform(); - } - } - } - - return cullingContext; -} - -/* - * Reorders pairs in-place based on `orderIndex` using a stable sort algorithm. - */ -static void reorderInPlaceIfNeeded( - std::vector& pairs) noexcept { - if (pairs.size() < 2) { - return; - } - - auto isReorderNeeded = false; - for (const auto& pair : pairs) { - if (pair->shadowNode->getOrderIndex() != 0) { - isReorderNeeded = true; - break; - } - } - - if (!isReorderNeeded) { - return; - } - - std::stable_sort( - pairs.begin(), pairs.end(), &shouldFirstPairComesBeforeSecondOne); -} - -static void sliceChildShadowNodeViewPairsRecursively( - std::vector& pairList, - size_t& startOfStaticIndex, - ViewNodePairScope& scope, - Point layoutOffset, - const ShadowNode& shadowNode, - const CullingContext& cullingContext) { - for (const auto& sharedChildShadowNode : shadowNode.getChildren()) { - auto& childShadowNode = *sharedChildShadowNode; -#ifndef ANDROID - // T153547836: Disabled on Android because the mounting infrastructure - // is not fully ready yet. - if (childShadowNode.getTraits().check(ShadowNodeTraits::Trait::Hidden)) { - continue; - } -#endif - auto shadowView = ShadowView(childShadowNode); - - if (ReactNativeFeatureFlags::enableViewCulling()) { - if (cullingContext.shouldConsiderCulling() && - shadowView.layoutMetrics != EmptyLayoutMetrics) { - auto overflowInsetFrame = - shadowView.layoutMetrics.getOverflowInsetFrame() * - cullingContext.transform; - if (auto layoutableShadowNode = - dynamic_cast(&childShadowNode)) { - overflowInsetFrame = - overflowInsetFrame * layoutableShadowNode->getTransform(); - } - auto doesIntersect = - Rect::intersect(cullingContext.frame, overflowInsetFrame) != Rect{}; - if (!doesIntersect) { - continue; // Culling. - } - } - } - - auto origin = layoutOffset; - auto cullingContextCopy = adjustCullingContextIfNeeded( - cullingContext, - {.shadowView = shadowView, .shadowNode = &childShadowNode}); - - if (shadowView.layoutMetrics != EmptyLayoutMetrics) { - origin += shadowView.layoutMetrics.frame.origin; - shadowView.layoutMetrics.frame.origin += layoutOffset; - } - - // This might not be a FormsView, or a FormsStackingContext. We let the - // differ handle removal of flattened views from the Mounting layer and - // shuffling their children around. - bool childrenFormStackingContexts = shadowNode.getTraits().check( - ShadowNodeTraits::Trait::ChildrenFormStackingContext); - bool isConcreteView = (childShadowNode.getTraits().check( - ShadowNodeTraits::Trait::FormsView) || - childrenFormStackingContexts) && - !childShadowNode.getTraits().check( - ShadowNodeTraits::Trait::ForceFlattenView); - bool areChildrenFlattened = - (!childShadowNode.getTraits().check( - ShadowNodeTraits::Trait::FormsStackingContext) && - !childrenFormStackingContexts) || - childShadowNode.getTraits().check( - ShadowNodeTraits::Trait::ForceFlattenView); - - Point storedOrigin = {}; - if (areChildrenFlattened) { - storedOrigin = origin; - } - - auto isPositionStatic = - shadowView.layoutMetrics.positionType == PositionType::Static; - - scope.push_back( - {.shadowView = std::move(shadowView), - .shadowNode = &childShadowNode, - .flattened = areChildrenFlattened, - .isConcreteView = isConcreteView, - .contextOrigin = storedOrigin}); - - if (isPositionStatic) { - auto it = pairList.begin(); - std::advance(it, startOfStaticIndex); - pairList.insert(it, &scope.back()); - startOfStaticIndex++; - if (areChildrenFlattened) { - sliceChildShadowNodeViewPairsRecursively( - pairList, - startOfStaticIndex, - scope, - origin, - childShadowNode, - cullingContextCopy); - } - } else { - pairList.push_back(&scope.back()); - if (areChildrenFlattened) { - size_t pairListSize = pairList.size(); - sliceChildShadowNodeViewPairsRecursively( - pairList, - pairListSize, - scope, - origin, - childShadowNode, - cullingContextCopy); - } - } - } -} - -std::vector sliceChildShadowNodeViewPairs( - const ShadowViewNodePair& shadowNodePair, - ViewNodePairScope& scope, - bool allowFlattened, - Point layoutOffset, - const CullingContext& cullingContext) { - const auto& shadowNode = *shadowNodePair.shadowNode; - auto pairList = std::vector{}; - - if (shadowNodePair.flattened && shadowNodePair.isConcreteView && - !allowFlattened) { - return pairList; - } - - size_t startOfStaticIndex = 0; - - sliceChildShadowNodeViewPairsRecursively( - pairList, - startOfStaticIndex, - scope, - layoutOffset, - shadowNode, - cullingContext); - - // Sorting pairs based on `orderIndex` if needed. - reorderInPlaceIfNeeded(pairList); - - // Set list and mountIndex for each after reordering - size_t mountIndex = 0; - for (auto child : pairList) { - child->mountIndex = (child->isConcreteView ? mountIndex++ : -1); - } - - return pairList; -} - /** * Prefer calling this over `sliceChildShadowNodeViewPairs` directly, when * possible. This can account for adding parent LayoutMetrics that are @@ -641,9 +430,9 @@ static void updateMatchedPairSubtrees( if (oldPair.shadowNode != newPair.shadowNode || oldCullingContext != newCullingContext) { auto oldCullingContextCopy = - adjustCullingContextIfNeeded(oldCullingContext, oldPair); + oldCullingContext.adjustCullingContextIfNeeded(oldPair); auto newCullingContextCopy = - adjustCullingContextIfNeeded(newCullingContext, newPair); + newCullingContext.adjustCullingContextIfNeeded(newPair); ViewNodePairScope innerScope{}; auto oldGrandChildPairs = sliceChildShadowNodeViewPairsFromViewNodePair( @@ -1270,9 +1059,9 @@ static void calculateShadowViewMutations( (oldChildPair.shadowNode != newChildPair.shadowNode || oldCullingContext != newCullingContext)) { auto oldCullingContextCopy = - adjustCullingContextIfNeeded(oldCullingContext, oldChildPair); + oldCullingContext.adjustCullingContextIfNeeded(oldChildPair); auto newCullingContextCopy = - adjustCullingContextIfNeeded(newCullingContext, newChildPair); + newCullingContext.adjustCullingContextIfNeeded(newChildPair); ViewNodePairScope innerScope{}; auto oldGrandChildPairs = sliceChildShadowNodeViewPairsFromViewNodePair( @@ -1320,7 +1109,7 @@ static void calculateShadowViewMutations( oldChildPair.shadowView, static_cast(oldChildPair.mountIndex))); auto oldCullingContextCopy = - adjustCullingContextIfNeeded(oldCullingContext, oldChildPair); + oldCullingContext.adjustCullingContextIfNeeded(oldChildPair); // We also have to call the algorithm recursively to clean up the entire // subtree starting from the removed view. @@ -1358,7 +1147,7 @@ static void calculateShadowViewMutations( mutationContainer.createMutations.push_back( ShadowViewMutation::CreateMutation(newChildPair.shadowView)); auto newCullingContextCopy = - adjustCullingContextIfNeeded(newCullingContext, newChildPair); + newCullingContext.adjustCullingContextIfNeeded(newChildPair); ViewNodePairScope innerScope{}; calculateShadowViewMutations( @@ -1594,7 +1383,7 @@ static void calculateShadowViewMutations( mutationContainer.deleteMutations.push_back( ShadowViewMutation::DeleteMutation(oldChildPair.shadowView)); auto oldCullingContextCopy = - adjustCullingContextIfNeeded(oldCullingContext, oldChildPair); + oldCullingContext.adjustCullingContextIfNeeded(oldChildPair); // We also have to call the algorithm recursively to clean up the // entire subtree starting from the removed view. @@ -1645,7 +1434,7 @@ static void calculateShadowViewMutations( ShadowViewMutation::CreateMutation(newChildPair.shadowView)); auto newCullingContextCopy = - adjustCullingContextIfNeeded(newCullingContext, newChildPair); + newCullingContext.adjustCullingContextIfNeeded(newChildPair); ViewNodePairScope innerScope{}; @@ -1719,13 +1508,15 @@ ShadowViewMutation::List calculateShadowViewMutations( auto sliceOne = sliceChildShadowNodeViewPairs( ShadowViewNodePair{.shadowNode = &oldRootShadowNode}, viewNodePairScope, - false, - {}); + false /* allowFlattened */, + {} /* layoutOffset */, + {} /* cullingContext */); auto sliceTwo = sliceChildShadowNodeViewPairs( ShadowViewNodePair{.shadowNode = &newRootShadowNode}, viewNodePairScope, - false, - {}); + false /* allowFlattened */, + {} /* layoutOffset */, + {} /* cullingContext */); calculateShadowViewMutations( innerViewNodePairScope, mutations, diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/Differentiator.h b/packages/react-native/ReactCommon/react/renderer/mounting/Differentiator.h index 95277f41bc5..f0305dfcf5c 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/Differentiator.h +++ b/packages/react-native/ReactCommon/react/renderer/mounting/Differentiator.h @@ -8,88 +8,10 @@ #pragma once #include -#include -#include #include -#include namespace facebook::react { -/* - * Describes pair of a `ShadowView` and a `ShadowNode`. - * This is not exposed to the mounting layer. - */ -struct ShadowViewNodePair final { - ShadowView shadowView; - const ShadowNode* shadowNode; - - /** - * The ShadowNode does not form a stacking context, and the native views - * corresponding to its children may be parented to an ancestor. - */ - bool flattened{false}; - - /** - * Whether this ShadowNode should create a corresponding native view. - */ - bool isConcreteView{true}; - Point contextOrigin{0, 0}; - - size_t mountIndex{0}; - - /** - * This is nullptr unless `inOtherTree` is set to true. - * We rely on this only for marginal cases. TODO: could we - * rely on this more heavily to simplify the diffing algorithm - * overall? - */ - mutable const ShadowViewNodePair* otherTreePair{nullptr}; - - /* - * The stored pointer to `ShadowNode` represents an identity of the pair. - */ - bool operator==(const ShadowViewNodePair& rhs) const; - bool operator!=(const ShadowViewNodePair& rhs) const; - - bool inOtherTree() const { - return this->otherTreePair != nullptr; - } -}; - -struct CullingContext { - Rect frame; - Transform transform; - - bool shouldConsiderCulling() const { - return frame.size.width > 0 && frame.size.height > 0; - } - - bool operator==(const CullingContext& rhs) const = default; -}; - -/** - * During differ, we need to keep some `ShadowViewNodePair`s in memory. - * Some `ShadowViewNodePair`s are referenced from std::vectors returned - * by `sliceChildShadowNodeViewPairsV2`; some are referenced in TinyMaps - * for view (un)flattening especially; and it is not always clear which - * std::vectors will outlive which TinyMaps, and vice-versa, so it doesn't - * make sense for the std::vector or TinyMap to own any `ShadowViewNodePair`s. - * - * Thus, we introduce the concept of a scope. - * - * For the duration of some operation, we keep a ViewNodePairScope around, such - * that: (1) the ViewNodePairScope keeps each - * ShadowViewNodePair alive, (2) we have a stable pointer value that we can - * use to reference each ShadowViewNodePair (not guaranteed with std::vector, - * for example, which may have to resize and move values around). - * - * As long as we only manipulate the data-structure with push_back, std::deque - * both (1) ensures that pointers into the data-structure are never invalidated, - * and (2) tries to efficiently allocate storage such that as many objects as - * possible are close in memory, but does not guarantee adjacency. - */ -using ViewNodePairScope = std::deque; - /* * Calculates a list of view mutations which describes how the old * `ShadowTree` can be transformed to the new one. @@ -99,16 +21,4 @@ ShadowViewMutation::List calculateShadowViewMutations( const ShadowNode& oldRootShadowNode, const ShadowNode& newRootShadowNode); -/** - * Generates a list of `ShadowViewNodePair`s that represents a layer of a - * flattened view hierarchy. The V2 version preserves nodes even if they do - * not form views and their children are flattened. - */ -std::vector sliceChildShadowNodeViewPairs( - const ShadowViewNodePair& shadowNodePair, - ViewNodePairScope& viewNodePairScope, - bool allowFlattened, - Point layoutOffset, - const CullingContext& cullingContext = {}); - } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/internal/CullingContext.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/internal/CullingContext.cpp new file mode 100644 index 00000000000..7e8aa01dd47 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/mounting/internal/CullingContext.cpp @@ -0,0 +1,44 @@ +/* + * 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 "CullingContext.h" +#include +#include +#include +#include "ShadowViewNodePair.h" + +namespace facebook::react { + +bool CullingContext::shouldConsiderCulling() const { + return frame.size.width > 0 && frame.size.height > 0; +} + +CullingContext CullingContext::adjustCullingContextIfNeeded( + const ShadowViewNodePair& pair) const { + auto cullingContext = *this; + if (ReactNativeFeatureFlags::enableViewCulling()) { + if (auto scrollViewShadowNode = + dynamic_cast(pair.shadowNode)) { + cullingContext.frame.origin = + -scrollViewShadowNode->getContentOriginOffset( + /* includeTransform */ true); + cullingContext.frame.size = + scrollViewShadowNode->getLayoutMetrics().frame.size; + } else { + cullingContext.frame.origin -= pair.shadowView.layoutMetrics.frame.origin; + + if (auto layoutableShadowNode = + dynamic_cast(pair.shadowNode)) { + cullingContext.transform = + cullingContext.transform * layoutableShadowNode->getTransform(); + } + } + } + + return cullingContext; +} +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/internal/CullingContext.h b/packages/react-native/ReactCommon/react/renderer/mounting/internal/CullingContext.h new file mode 100644 index 00000000000..7b3ab79152f --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/mounting/internal/CullingContext.h @@ -0,0 +1,29 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +namespace facebook::react { + +struct ShadowViewNodePair; + +struct CullingContext { + Rect frame; + Transform transform; + + bool shouldConsiderCulling() const; + + CullingContext adjustCullingContextIfNeeded( + const ShadowViewNodePair& pair) const; + + bool operator==(const CullingContext& rhs) const = default; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/internal/ShadowViewNodePair.h b/packages/react-native/ReactCommon/react/renderer/mounting/internal/ShadowViewNodePair.h new file mode 100644 index 00000000000..7f64b8e9a8b --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/mounting/internal/ShadowViewNodePair.h @@ -0,0 +1,62 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include + +namespace facebook::react { + +/* + * Describes pair of a `ShadowView` and a `ShadowNode`. + * This is not exposed to the mounting layer. + */ +struct ShadowViewNodePair final { + ShadowView shadowView; + const ShadowNode* shadowNode; + + /** + * The ShadowNode does not form a stacking context, and the native views + * corresponding to its children may be parented to an ancestor. + */ + bool flattened{false}; + + /** + * Whether this ShadowNode should create a corresponding native view. + */ + bool isConcreteView{true}; + Point contextOrigin{0, 0}; + + size_t mountIndex{0}; + + /** + * This is nullptr unless `inOtherTree` is set to true. + * We rely on this only for marginal cases. TODO: could we + * rely on this more heavily to simplify the diffing algorithm + * overall? + */ + mutable const ShadowViewNodePair* otherTreePair{nullptr}; + + /* + * The stored pointer to `ShadowNode` represents an identity of the pair. + */ + bool operator==(const ShadowViewNodePair& rhs) const { + return this->shadowNode == rhs.shadowNode; + } + + bool operator!=(const ShadowViewNodePair& rhs) const { + return !(*this == rhs); + } + + bool inOtherTree() const { + return this->otherTreePair != nullptr; + } +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/internal/sliceChildShadowNodeViewPairs.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/internal/sliceChildShadowNodeViewPairs.cpp new file mode 100644 index 00000000000..4dd4fc834f4 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/mounting/internal/sliceChildShadowNodeViewPairs.cpp @@ -0,0 +1,191 @@ +/* + * 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 "sliceChildShadowNodeViewPairs.h" +#include +#include + +#include "ShadowViewNodePair.h" + +namespace facebook::react { + +/* + * Sorting comparator for `reorderInPlaceIfNeeded`. + */ +static bool shouldFirstPairComesBeforeSecondOne( + const ShadowViewNodePair* lhs, + const ShadowViewNodePair* rhs) noexcept { + return lhs->shadowNode->getOrderIndex() < rhs->shadowNode->getOrderIndex(); +} + +/* + * Reorders pairs in-place based on `orderIndex` using a stable sort algorithm. + */ +static void reorderInPlaceIfNeeded( + std::vector& pairs) noexcept { + if (pairs.size() < 2) { + return; + } + + auto isReorderNeeded = false; + for (const auto& pair : pairs) { + if (pair->shadowNode->getOrderIndex() != 0) { + isReorderNeeded = true; + break; + } + } + + if (!isReorderNeeded) { + return; + } + + std::stable_sort( + pairs.begin(), pairs.end(), &shouldFirstPairComesBeforeSecondOne); +} + +static void sliceChildShadowNodeViewPairsRecursively( + std::vector& pairList, + size_t& startOfStaticIndex, + ViewNodePairScope& scope, + Point layoutOffset, + const ShadowNode& shadowNode, + const CullingContext& cullingContext) { + for (const auto& sharedChildShadowNode : shadowNode.getChildren()) { + auto& childShadowNode = *sharedChildShadowNode; +#ifndef ANDROID + // T153547836: Disabled on Android because the mounting infrastructure + // is not fully ready yet. + if (childShadowNode.getTraits().check(ShadowNodeTraits::Trait::Hidden)) { + continue; + } +#endif + auto shadowView = ShadowView(childShadowNode); + + if (ReactNativeFeatureFlags::enableViewCulling()) { + if (cullingContext.shouldConsiderCulling() && + shadowView.layoutMetrics != EmptyLayoutMetrics) { + auto overflowInsetFrame = + shadowView.layoutMetrics.getOverflowInsetFrame() * + cullingContext.transform; + if (auto layoutableShadowNode = + dynamic_cast(&childShadowNode)) { + overflowInsetFrame = + overflowInsetFrame * layoutableShadowNode->getTransform(); + } + auto doesIntersect = + Rect::intersect(cullingContext.frame, overflowInsetFrame) != Rect{}; + if (!doesIntersect) { + continue; // Culling. + } + } + } + + auto origin = layoutOffset; + auto cullingContextCopy = cullingContext.adjustCullingContextIfNeeded( + {.shadowView = shadowView, .shadowNode = &childShadowNode}); + + if (shadowView.layoutMetrics != EmptyLayoutMetrics) { + origin += shadowView.layoutMetrics.frame.origin; + shadowView.layoutMetrics.frame.origin += layoutOffset; + } + + // This might not be a FormsView, or a FormsStackingContext. We let the + // differ handle removal of flattened views from the Mounting layer and + // shuffling their children around. + bool childrenFormStackingContexts = shadowNode.getTraits().check( + ShadowNodeTraits::Trait::ChildrenFormStackingContext); + bool isConcreteView = (childShadowNode.getTraits().check( + ShadowNodeTraits::Trait::FormsView) || + childrenFormStackingContexts) && + !childShadowNode.getTraits().check( + ShadowNodeTraits::Trait::ForceFlattenView); + bool areChildrenFlattened = + (!childShadowNode.getTraits().check( + ShadowNodeTraits::Trait::FormsStackingContext) && + !childrenFormStackingContexts) || + childShadowNode.getTraits().check( + ShadowNodeTraits::Trait::ForceFlattenView); + + Point storedOrigin = {}; + if (areChildrenFlattened) { + storedOrigin = origin; + } + scope.push_back( + {shadowView, + &childShadowNode, + areChildrenFlattened, + isConcreteView, + storedOrigin}); + + if (shadowView.layoutMetrics.positionType == PositionType::Static) { + auto it = pairList.begin(); + std::advance(it, startOfStaticIndex); + pairList.insert(it, &scope.back()); + startOfStaticIndex++; + if (areChildrenFlattened) { + sliceChildShadowNodeViewPairsRecursively( + pairList, + startOfStaticIndex, + scope, + origin, + childShadowNode, + cullingContextCopy); + } + } else { + pairList.push_back(&scope.back()); + if (areChildrenFlattened) { + size_t pairListSize = pairList.size(); + sliceChildShadowNodeViewPairsRecursively( + pairList, + pairListSize, + scope, + origin, + childShadowNode, + cullingContextCopy); + } + } + } +} + +std::vector sliceChildShadowNodeViewPairs( + const ShadowViewNodePair& shadowNodePair, + ViewNodePairScope& scope, + bool allowFlattened, + Point layoutOffset, + const CullingContext& cullingContext) { + const auto& shadowNode = *shadowNodePair.shadowNode; + auto pairList = std::vector{}; + + if (shadowNodePair.flattened && shadowNodePair.isConcreteView && + !allowFlattened) { + return pairList; + } + + size_t startOfStaticIndex = 0; + + sliceChildShadowNodeViewPairsRecursively( + pairList, + startOfStaticIndex, + scope, + layoutOffset, + shadowNode, + cullingContext); + + // Sorting pairs based on `orderIndex` if needed. + reorderInPlaceIfNeeded(pairList); + + // Set list and mountIndex for each after reordering + size_t mountIndex = 0; + for (auto child : pairList) { + child->mountIndex = + (child->isConcreteView ? mountIndex++ : static_cast(-1)); + } + + return pairList; +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/internal/sliceChildShadowNodeViewPairs.h b/packages/react-native/ReactCommon/react/renderer/mounting/internal/sliceChildShadowNodeViewPairs.h new file mode 100644 index 00000000000..278be011446 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/mounting/internal/sliceChildShadowNodeViewPairs.h @@ -0,0 +1,53 @@ +/* + * 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. + */ + +#pragma once + +#include + +#include "CullingContext.h" + +namespace facebook::react { + +struct ShadowViewNodePair; + +/** + * During differ, we need to keep some `ShadowViewNodePair`s in memory. + * Some `ShadowViewNodePair`s are referenced from std::vectors returned + * by `sliceChildShadowNodeViewPairs`; some are referenced in TinyMaps + * for view (un)flattening especially; and it is not always clear which + * std::vectors will outlive which TinyMaps, and vice-versa, so it doesn't + * make sense for the std::vector or TinyMap to own any `ShadowViewNodePair`s. + * + * Thus, we introduce the concept of a scope. + * + * For the duration of some operation, we keep a ViewNodePairScope around, such + * that: (1) the ViewNodePairScope keeps each + * ShadowViewNodePair alive, (2) we have a stable pointer value that we can + * use to reference each ShadowViewNodePair (not guaranteed with std::vector, + * for example, which may have to resize and move values around). + * + * As long as we only manipulate the data-structure with push_back, std::deque + * both (1) ensures that pointers into the data-structure are never invalidated, + * and (2) tries to efficiently allocate storage such that as many objects as + * possible are close in memory, but does not guarantee adjacency. + */ +using ViewNodePairScope = std::deque; + +/** + * Generates a list of `ShadowViewNodePair`s that represents a layer of a + * flattened view hierarchy. The V2 version preserves nodes even if they do + * not form views and their children are flattened. + */ +std::vector sliceChildShadowNodeViewPairs( + const ShadowViewNodePair& shadowNodePair, + ViewNodePairScope& viewNodePairScope, + bool allowFlattened, + Point layoutOffset, + const CullingContext& cullingContext); + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/stubs/stubs.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/stubs/stubs.cpp index 7a79245e2f8..22e7b6c07f6 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/stubs/stubs.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mounting/stubs/stubs.cpp @@ -10,6 +10,8 @@ #include #include #include +#include "../internal/ShadowViewNodePair.h" +#include "../internal/sliceChildShadowNodeViewPairs.h" namespace facebook::react {