From ea8a57116f2b615b4826884f61b127af2f724e3c Mon Sep 17 00:00:00 2001 From: Valentin Shergin Date: Wed, 1 May 2019 16:24:23 -0700 Subject: [PATCH] Fabric: A new way to compute nodes for `onLayout` event Summary: Previously we computed the list of nodes that need to be notified about layout changes using a list of mutation instructions. That was fine, but that's not really compatible with some other changes that I plan to make, so I decided to change it (make it better). Besides the better design (debatable; fewer dependencies to unrelated moving pieces), here is why I believe the new way is more performant: * The new approach has no `dynamic_casts`, whereas the previous has tons of them (two per a mutation). If a `dynamic_cast` takes 10 ns, for 500 nodes it can take up to 5ms only for casts. (Non-scientific assumption.) * After removing dependency to mutation instruction, we can enable flattening for views which have `onLayout` event. Reviewed By: mdvacca Differential Revision: D15110725 fbshipit-source-id: 31a657ccfd02441734ad1d71a833653223163289 --- .../fabric/components/root/RootShadowNode.cpp | 9 +++- .../fabric/components/root/RootShadowNode.h | 2 +- .../view/yoga/YogaLayoutableShadowNode.cpp | 6 ++- .../fabric/core/layout/LayoutContext.h | 14 +++++- ReactCommon/fabric/mounting/ShadowTree.cpp | 48 +++++++------------ ReactCommon/fabric/mounting/ShadowTree.h | 3 +- 6 files changed, 45 insertions(+), 37 deletions(-) diff --git a/ReactCommon/fabric/components/root/RootShadowNode.cpp b/ReactCommon/fabric/components/root/RootShadowNode.cpp index 0c2340ada..9a71a8f0f 100644 --- a/ReactCommon/fabric/components/root/RootShadowNode.cpp +++ b/ReactCommon/fabric/components/root/RootShadowNode.cpp @@ -15,10 +15,15 @@ namespace react { const char RootComponentName[] = "RootView"; -void RootShadowNode::layout() { +void RootShadowNode::layout( + std::vector *affectedNodes) { SystraceSection s("RootShadowNode::layout"); ensureUnsealed(); - layout(getProps()->layoutContext); + + auto layoutContext = getProps()->layoutContext; + layoutContext.affectedNodes = affectedNodes; + + layout(layoutContext); // This is the rare place where shadow node must layout (set `layoutMetrics`) // itself because there is no a parent node which usually should do it. diff --git a/ReactCommon/fabric/components/root/RootShadowNode.h b/ReactCommon/fabric/components/root/RootShadowNode.h index cc0596d04..3537b5fef 100644 --- a/ReactCommon/fabric/components/root/RootShadowNode.h +++ b/ReactCommon/fabric/components/root/RootShadowNode.h @@ -37,7 +37,7 @@ class RootShadowNode final /* * Layouts the shadow tree. */ - void layout(); + void layout(std::vector *affectedNodes = {}); /* * Clones the node with given `layoutConstraints` and `layoutContext`. diff --git a/ReactCommon/fabric/components/view/yoga/YogaLayoutableShadowNode.cpp b/ReactCommon/fabric/components/view/yoga/YogaLayoutableShadowNode.cpp index 450c92e99..28f90139f 100644 --- a/ReactCommon/fabric/components/view/yoga/YogaLayoutableShadowNode.cpp +++ b/ReactCommon/fabric/components/view/yoga/YogaLayoutableShadowNode.cpp @@ -199,7 +199,11 @@ void YogaLayoutableShadowNode::layoutChildren(LayoutContext layoutContext) { assert(childYogaNode->getOwner() == &yogaNode_); childNode->ensureUnsealed(); - childNode->setLayoutMetrics(childLayoutMetrics); + auto affected = childNode->setLayoutMetrics(childLayoutMetrics); + + if (affected && layoutContext.affectedNodes) { + layoutContext.affectedNodes->push_back(childNode); + } } } diff --git a/ReactCommon/fabric/core/layout/LayoutContext.h b/ReactCommon/fabric/core/layout/LayoutContext.h index 184d44f1c..e59f1508e 100644 --- a/ReactCommon/fabric/core/layout/LayoutContext.h +++ b/ReactCommon/fabric/core/layout/LayoutContext.h @@ -7,6 +7,9 @@ #pragma once +#include + +#include #include namespace facebook { @@ -28,7 +31,16 @@ struct LayoutContext { * Some layout systems *might* use this to round layout metric values * to `pixel value`. */ - Float pointScaleFactor = {1.0}; + Float pointScaleFactor{1.0}; + + /* + * A raw pointer to list of raw pointers to `LayoutableShadowNode`s that were + * affected by the re-layout pass. If the field is not `nullptr`, a particular + * `LayoutableShadowNode` implementation should add mutated nodes to this + * list. The order is not specified. Nothing in this collection is owing (on + * purpose), make sure the memory is managed responsibly. + */ + std::vector *affectedNodes{}; }; } // namespace react diff --git a/ReactCommon/fabric/mounting/ShadowTree.cpp b/ReactCommon/fabric/mounting/ShadowTree.cpp index 4cf7f5b60..53c1bb140 100644 --- a/ReactCommon/fabric/mounting/ShadowTree.cpp +++ b/ReactCommon/fabric/mounting/ShadowTree.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -163,8 +164,11 @@ bool ShadowTree::tryCommit( return false; } + std::vector affectedLayoutableNodes{}; + affectedLayoutableNodes.reserve(1024); + long layoutTime = getTime(); - newRootShadowNode->layout(); + newRootShadowNode->layout(&affectedLayoutableNodes); layoutTime = getTime() - layoutTime; newRootShadowNode->sealRecursive(); @@ -210,7 +214,7 @@ bool ShadowTree::tryCommit( #endif } - emitLayoutEvents(mutations); + emitLayoutEvents(affectedLayoutableNodes); if (delegate_) { delegate_->shadowTreeDidCommit( @@ -225,43 +229,25 @@ bool ShadowTree::tryCommit( } void ShadowTree::emitLayoutEvents( - const ShadowViewMutationList &mutations) const { + std::vector &affectedLayoutableNodes) 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; - } + for (auto const *layoutableNode : affectedLayoutableNodes) { + // Only instances of `ViewShadowNode` (and subclasses) are supported. + auto const &viewShadowNode = + static_cast(*layoutableNode); + auto const &viewEventEmitter = static_cast( + *viewShadowNode.getEventEmitter()); // 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) { + auto const &viewProps = + static_cast(*viewShadowNode.getProps()); + if (!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); + viewEventEmitter.onLayout(layoutableNode->getLayoutMetrics()); } } diff --git a/ReactCommon/fabric/mounting/ShadowTree.h b/ReactCommon/fabric/mounting/ShadowTree.h index 9a87e55d8..d49305d76 100644 --- a/ReactCommon/fabric/mounting/ShadowTree.h +++ b/ReactCommon/fabric/mounting/ShadowTree.h @@ -82,7 +82,8 @@ class ShadowTree final { const LayoutConstraints &layoutConstraints, const LayoutContext &layoutContext) const; - void emitLayoutEvents(const ShadowViewMutationList &mutations) const; + void emitLayoutEvents( + std::vector &affectedLayoutableNodes) const; const SurfaceId surfaceId_; mutable better::shared_mutex commitMutex_;