diff --git a/Common/cpp/Fabric/ShadowTreeCloner.cpp b/Common/cpp/Fabric/ShadowTreeCloner.cpp new file mode 100644 index 00000000000..c6fd600d26d --- /dev/null +++ b/Common/cpp/Fabric/ShadowTreeCloner.cpp @@ -0,0 +1,92 @@ +#ifdef RCT_NEW_ARCH_ENABLED + +#include "ShadowTreeCloner.h" +#include "FabricUtils.h" + +namespace reanimated { + +ShadowTreeCloner::ShadowTreeCloner( + std::shared_ptr newestShadowNodesRegistry, + std::shared_ptr uiManager, + SurfaceId surfaceId) + : newestShadowNodesRegistry_{newestShadowNodesRegistry}, + propsParserContext_{ + surfaceId, + *getContextContainerFromUIManager(&*uiManager)} {} + +ShadowTreeCloner::~ShadowTreeCloner() { +#ifdef DEBUG + react_native_assert( + yogaChildrenUpdates_.empty() && + "Deallocating `ShadowTreeCloner` without calling `updateYogaChildren`."); +#endif +} + +ShadowNode::Unshared ShadowTreeCloner::cloneWithNewProps( + const ShadowNode::Shared &oldRootNode, + const ShadowNodeFamily &family, + RawProps &&rawProps) { + // adapted from ShadowNode::cloneTree + + auto ancestors = family.getAncestors(*oldRootNode); + + if (ancestors.empty()) { + return ShadowNode::Unshared{nullptr}; + } + + auto &parent = ancestors.back(); + auto &oldShadowNode = parent.first.get().getChildren().at(parent.second); + + const auto newest = newestShadowNodesRegistry_->get(oldShadowNode->getTag()); + + const auto &source = newest == nullptr ? oldShadowNode : newest; + + const auto props = source->getComponentDescriptor().cloneProps( + propsParserContext_, source->getProps(), rawProps); + + auto newChildNode = source->clone({/* .props = */ props}); + + for (auto it = ancestors.rbegin(); it != ancestors.rend(); ++it) { + auto &parentNode = it->first.get(); + auto childIndex = it->second; + + auto children = parentNode.getChildren(); + const auto &oldChildNode = *children.at(childIndex); + react_native_assert(ShadowNode::sameFamily(oldChildNode, *newChildNode)); + + newestShadowNodesRegistry_->set(newChildNode, parentNode.getTag()); + + if (!parentNode.getSealed()) { + // Optimization: if a ShadowNode is unsealed, we can directly update its + // children instead of cloning the whole path to the root node. + auto &parentNodeNonConst = const_cast(parentNode); + parentNodeNonConst.replaceChild(oldChildNode, newChildNode, childIndex); + yogaChildrenUpdates_.insert(&parentNodeNonConst); + return std::const_pointer_cast(oldRootNode); + } + + children[childIndex] = newChildNode; + + newChildNode = parentNode.clone({ + ShadowNodeFragment::propsPlaceholder(), + std::make_shared(children), + }); + } + + return std::const_pointer_cast(newChildNode); +} + +void ShadowTreeCloner::updateYogaChildren() { + // Unfortunately, `replaceChild` does not update Yoga nodes, so we need to + // update them manually here. + for (ShadowNode *shadowNode : yogaChildrenUpdates_) { + static_cast(shadowNode)->updateYogaChildren(); + } +#ifdef DEBUG + yogaChildrenUpdates_.clear(); +#endif +} + +} // namespace reanimated + +#endif // RCT_NEW_ARCH_ENABLED diff --git a/Common/cpp/NativeModules/NativeReanimatedModule.cpp b/Common/cpp/NativeModules/NativeReanimatedModule.cpp index 2d8a41d959e..3e1d8a1c397 100644 --- a/Common/cpp/NativeModules/NativeReanimatedModule.cpp +++ b/Common/cpp/NativeModules/NativeReanimatedModule.cpp @@ -13,6 +13,7 @@ #include "FabricUtils.h" #include "NewestShadowNodesRegistry.h" #include "ReanimatedUIManagerBinding.h" +#include "ShadowTreeCloner.h" #endif #include "EventHandlerRegistry.h" @@ -507,56 +508,42 @@ void NativeReanimatedModule::performOperations() { react_native_assert(uiManager_ != nullptr); const auto &shadowTreeRegistry = uiManager_->getShadowTreeRegistry(); - auto contextContainer = getContextContainerFromUIManager( - &*uiManager_); // TODO: use Scheduler::getContextContainer - PropsParserContext propsParserContext{surfaceId_, *contextContainer}; jsi::Runtime &rt = *runtime.get(); shadowTreeRegistry.visit(surfaceId_, [&](ShadowTree const &shadowTree) { shadowTree.commit([&](RootShadowNode const &oldRootShadowNode) { - // lock once due to performance reasons - auto lock = newestShadowNodesRegistry_->createLock(); - auto rootNode = oldRootShadowNode.ShadowNode::clone(ShadowNodeFragment{}); - for (const auto &pair : copiedOperationsQueue) { - const ShadowNodeFamily &family = pair.first->getFamily(); - react_native_assert(family.getSurfaceId() == surfaceId_); - - auto newRootNode = - rootNode->cloneTree(family, [&](ShadowNode const &oldShadowNode) { - const auto newest = - newestShadowNodesRegistry_->get(oldShadowNode.getTag()); + ShadowTreeCloner shadowTreeCloner{ + newestShadowNodesRegistry_, uiManager_, surfaceId_}; - const auto &source = newest == nullptr ? oldShadowNode : *newest; + { + // lock once due to performance reasons + auto lock = newestShadowNodesRegistry_->createLock(); - const auto newProps = source.getComponentDescriptor().cloneProps( - propsParserContext, - source.getProps(), - RawProps(rt, *pair.second)); + for (const auto &pair : copiedOperationsQueue) { + const ShadowNodeFamily &family = pair.first->getFamily(); + react_native_assert(family.getSurfaceId() == surfaceId_); - return source.clone({/* .props = */ newProps}); - }); + auto newRootNode = shadowTreeCloner.cloneWithNewProps( + rootNode, family, RawProps(rt, *pair.second)); - if (newRootNode == nullptr) { - // this happens when React removed the component but Reanimated still - // tries to animate it, let's skip update for this specific component - continue; + if (newRootNode == nullptr) { + // this happens when React removed the component but Reanimated + // still tries to animate it, let's skip update for this specific + // component + continue; + } + rootNode = newRootNode; } - rootNode = newRootNode; - auto ancestors = family.getAncestors(*rootNode); - for (const auto &pair : ancestors) { - const auto &parent = pair.first.get(); - const auto &child = parent.getChildren().at(pair.second); - newestShadowNodesRegistry_->set(child, parent.getTag()); + // remove ShadowNodes and its ancestors from NewestShadowNodesRegistry + for (auto tag : copiedTagsToRemove) { + newestShadowNodesRegistry_->remove(tag); } } - // remove ShadowNodes and its ancestors from NewestShadowNodesRegistry - for (auto tag : copiedTagsToRemove) { - newestShadowNodesRegistry_->remove(tag); - } + shadowTreeCloner.updateYogaChildren(); return std::static_pointer_cast(rootNode); }); diff --git a/Common/cpp/headers/Fabric/ShadowTreeCloner.h b/Common/cpp/headers/Fabric/ShadowTreeCloner.h new file mode 100644 index 00000000000..ae7a9871d05 --- /dev/null +++ b/Common/cpp/headers/Fabric/ShadowTreeCloner.h @@ -0,0 +1,41 @@ +#pragma once +#ifdef RCT_NEW_ARCH_ENABLED + +#include +#include + +#include +#include + +#include "NewestShadowNodesRegistry.h" + +using namespace facebook; +using namespace react; + +namespace reanimated { + +class ShadowTreeCloner { + public: + ShadowTreeCloner( + std::shared_ptr newestShadowNodesRegistry, + std::shared_ptr uiManager, + SurfaceId surfaceId); + + ~ShadowTreeCloner(); + + ShadowNode::Unshared cloneWithNewProps( + const ShadowNode::Shared &oldRootNode, + const ShadowNodeFamily &family, + RawProps &&rawProps); + + void updateYogaChildren(); + + private: + PropsParserContext propsParserContext_; + std::shared_ptr newestShadowNodesRegistry_; + std::set yogaChildrenUpdates_; +}; + +} // namespace reanimated + +#endif // RCT_NEW_ARCH_ENABLED diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt index 948b8a79dcd..835ed09db19 100644 --- a/android/CMakeLists.txt +++ b/android/CMakeLists.txt @@ -212,6 +212,12 @@ if(${IS_NEW_ARCHITECTURE_ENABLED}) PATHS ${LIBRN_DIR} NO_CMAKE_FIND_ROOT_PATH ) + find_library( + RRC_VIEW + rrc_view + PATHS ${LIBRN_DIR} + NO_CMAKE_FIND_ROOT_PATH + ) find_library( REACT_RENDER_SCHEDULER react_render_scheduler @@ -307,6 +313,7 @@ if(${IS_NEW_ARCHITECTURE_ENABLED}) ${REACT_DEBUG} ${REACT_RENDER_DEBUG} ${RRC_ROOT} + ${RRC_VIEW} ${FABRICJNI} ${REACT_RENDER_SCHEDULER} )