From 059142935a397345276fb97901ba323683571ca7 Mon Sep 17 00:00:00 2001 From: Valentin Shergin Date: Fri, 15 Nov 2019 19:47:51 -0800 Subject: [PATCH] Fabric: Introducing ShadowNodeTraits Summary: Quite often in the Fabric codebase, we need to iterate on a list of shadow nodes and perform some work depending on the exact type (or kind) of shadow node we have. Today, we exclusively use RTTI to test for exact type. The problem here is that RTTI is not only expensive in terms of code size (we are moving towards removing RTTI), it's expensive in terms of CPU cycles. (Don't mistake RTTI/dynamic_cast overhead with dynamic dispatch overhead!) `dynamic_cast` has it's own advantages, of cource: it's simple and straight-forward and does not require additional configuration. On the other side, it requires knowledge about exact class relationship configuration and knowing the exact name of the class. ShadowNodeTraits is the generalized solution for storing predefined traits right inside ShadowNode object and check/set/unset them efficiently and in a particular-class-independent manner. This diff only implements it in general and switching storing an internal flag of ShadowNode inside the trait collection to save some space (and for illustration purposes as well). In the coming diff, we will see how this approach allows tackling the real, non-trivial problem. In addition to the concept, this diff implements some convenience infra, such as `ConcreteShadowNode::BaseTraits()` static method that allows designating intrinsic-to-a-class traits declaratively. Changelog: [Internal] Fabric-specific internal change. Reviewed By: sammy-SC Differential Revision: D18525348 fbshipit-source-id: 083bb85e0a9dc9d6c69bf395bd338d3c18def688 --- .../components/view/ConcreteViewShadowNode.h | 11 +-- .../componentdescriptor/ComponentDescriptor.h | 7 +- .../ConcreteComponentDescriptor.h | 7 +- .../core/shadownode/ConcreteShadowNode.h | 8 ++ .../fabric/core/shadownode/ShadowNode.cpp | 22 ++++-- .../fabric/core/shadownode/ShadowNode.h | 17 ++-- .../core/shadownode/ShadowNodeTraits.cpp | 12 +++ .../fabric/core/shadownode/ShadowNodeTraits.h | 77 +++++++++++++++++++ .../fabric/core/tests/ShadowNodeTest.cpp | 51 ++++++++---- 9 files changed, 177 insertions(+), 35 deletions(-) create mode 100644 ReactCommon/fabric/core/shadownode/ShadowNodeTraits.cpp create mode 100644 ReactCommon/fabric/core/shadownode/ShadowNodeTraits.h diff --git a/ReactCommon/fabric/components/view/ConcreteViewShadowNode.h b/ReactCommon/fabric/components/view/ConcreteViewShadowNode.h index e6eccb28fb1..de1a995b332 100644 --- a/ReactCommon/fabric/components/view/ConcreteViewShadowNode.h +++ b/ReactCommon/fabric/components/view/ConcreteViewShadowNode.h @@ -54,9 +54,10 @@ class ConcreteViewShadowNode : public ConcreteShadowNode< using ConcreteViewProps = ViewPropsT; ConcreteViewShadowNode( - const ShadowNodeFragment &fragment, - const ComponentDescriptor &componentDescriptor) - : BaseShadowNode(fragment, componentDescriptor), + ShadowNodeFragment const &fragment, + ComponentDescriptor const &componentDescriptor, + ShadowNodeTraits traits) + : BaseShadowNode(fragment, componentDescriptor, traits), YogaLayoutableShadowNode() { YogaLayoutableShadowNode::setProps( *std::static_pointer_cast(fragment.props)); @@ -65,8 +66,8 @@ class ConcreteViewShadowNode : public ConcreteShadowNode< }; ConcreteViewShadowNode( - const ShadowNode &sourceShadowNode, - const ShadowNodeFragment &fragment) + ShadowNode const &sourceShadowNode, + ShadowNodeFragment const &fragment) : BaseShadowNode(sourceShadowNode, fragment), YogaLayoutableShadowNode( static_cast(sourceShadowNode)) { diff --git a/ReactCommon/fabric/core/componentdescriptor/ComponentDescriptor.h b/ReactCommon/fabric/core/componentdescriptor/ComponentDescriptor.h index a0c226c0f52..e6271d689c6 100644 --- a/ReactCommon/fabric/core/componentdescriptor/ComponentDescriptor.h +++ b/ReactCommon/fabric/core/componentdescriptor/ComponentDescriptor.h @@ -70,7 +70,12 @@ class ComponentDescriptor { virtual ComponentName getComponentName() const = 0; /* - * Creates a new `ShadowNode` of a particular type. + * Returns traits associated with a particular component type. + */ + virtual ShadowNodeTraits getTraits() const = 0; + + /* + * Creates a new `ShadowNode` of a particular component type. */ virtual SharedShadowNode createShadowNode( const ShadowNodeFragment &fragment) const = 0; diff --git a/ReactCommon/fabric/core/componentdescriptor/ConcreteComponentDescriptor.h b/ReactCommon/fabric/core/componentdescriptor/ConcreteComponentDescriptor.h index f87bc5051a5..2e9e7cbb989 100644 --- a/ReactCommon/fabric/core/componentdescriptor/ConcreteComponentDescriptor.h +++ b/ReactCommon/fabric/core/componentdescriptor/ConcreteComponentDescriptor.h @@ -61,13 +61,18 @@ class ConcreteComponentDescriptor : public ComponentDescriptor { return ShadowNodeT::Name(); } + ShadowNodeTraits getTraits() const override { + return ShadowNodeT::BaseTraits(); + } + SharedShadowNode createShadowNode( const ShadowNodeFragment &fragment) const override { assert(std::dynamic_pointer_cast(fragment.props)); assert(std::dynamic_pointer_cast( fragment.eventEmitter)); - auto shadowNode = std::make_shared(fragment, *this); + auto shadowNode = + std::make_shared(fragment, *this, getTraits()); adopt(shadowNode); diff --git a/ReactCommon/fabric/core/shadownode/ConcreteShadowNode.h b/ReactCommon/fabric/core/shadownode/ConcreteShadowNode.h index cebd47dca4b..60fad4e5e10 100644 --- a/ReactCommon/fabric/core/shadownode/ConcreteShadowNode.h +++ b/ReactCommon/fabric/core/shadownode/ConcreteShadowNode.h @@ -50,6 +50,14 @@ class ConcreteShadowNode : public ShadowNode { return ComponentHandle(concreteComponentName); } + /* + * A set of traits associated with a particular class. + * Reimplement in subclasses to declare class-specific traits. + */ + static ShadowNodeTraits BaseTraits() { + return ShadowNodeTraits{}; + } + static SharedConcreteProps Props( const RawProps &rawProps, const SharedProps &baseProps = nullptr) { diff --git a/ReactCommon/fabric/core/shadownode/ShadowNode.cpp b/ReactCommon/fabric/core/shadownode/ShadowNode.cpp index ab7ef1c64bf..453c2227797 100644 --- a/ReactCommon/fabric/core/shadownode/ShadowNode.cpp +++ b/ReactCommon/fabric/core/shadownode/ShadowNode.cpp @@ -32,8 +32,9 @@ bool ShadowNode::sameFamily(const ShadowNode &first, const ShadowNode &second) { #pragma mark - Constructors ShadowNode::ShadowNode( - const ShadowNodeFragment &fragment, - const ComponentDescriptor &componentDescriptor) + ShadowNodeFragment const &fragment, + ComponentDescriptor const &componentDescriptor, + ShadowNodeTraits traits) : #if RN_DEBUG_STRING_CONVERTIBLE revision_(1), @@ -48,10 +49,12 @@ ShadowNode::ShadowNode( fragment.surfaceId, fragment.eventEmitter, componentDescriptor)), - childrenAreShared_(true) { + traits_(traits) { assert(props_); assert(children_); + traits_.set(ShadowNodeTraits::Trait::ChildrenAreShared); + for (const auto &child : *children_) { child->family_->setParent(family_); } @@ -74,7 +77,7 @@ ShadowNode::ShadowNode( fragment.state ? fragment.state : sourceShadowNode.getMostRecentState()), family_(sourceShadowNode.family_), - childrenAreShared_(true) { + traits_(sourceShadowNode.traits_) { // `tag`, `surfaceId`, and `eventEmitter` cannot be changed with cloning. assert(fragment.tag == ShadowNodeFragment::tagPlaceholder()); assert(fragment.surfaceId == ShadowNodeFragment::surfaceIdPlaceholder()); @@ -84,6 +87,8 @@ ShadowNode::ShadowNode( assert(props_); assert(children_); + traits_.set(ShadowNodeTraits::Trait::ChildrenAreShared); + if (fragment.children) { for (const auto &child : *children_) { child->family_->setParent(family_); @@ -109,6 +114,10 @@ const SharedShadowNodeList &ShadowNode::getChildren() const { return *children_; } +ShadowNodeTraits ShadowNode::getTraits() const { + return traits_; +} + const SharedProps &ShadowNode::getProps() const { return props_; } @@ -215,10 +224,11 @@ void ShadowNode::setLocalData(const SharedLocalData &localData) { } void ShadowNode::cloneChildrenIfShared() { - if (!childrenAreShared_) { + if (!traits_.check(ShadowNodeTraits::Trait::ChildrenAreShared)) { return; } - childrenAreShared_ = false; + + traits_.unset(ShadowNodeTraits::Trait::ChildrenAreShared); children_ = std::make_shared(*children_); } diff --git a/ReactCommon/fabric/core/shadownode/ShadowNode.h b/ReactCommon/fabric/core/shadownode/ShadowNode.h index de525f07324..9b6440e0f2b 100644 --- a/ReactCommon/fabric/core/shadownode/ShadowNode.h +++ b/ReactCommon/fabric/core/shadownode/ShadowNode.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -70,8 +71,9 @@ class ShadowNode : public virtual Sealable, * Creates a Shadow Node based on fields specified in a `fragment`. */ ShadowNode( - const ShadowNodeFragment &fragment, - const ComponentDescriptor &componentDescriptor); + ShadowNodeFragment const &fragment, + ComponentDescriptor const &componentDescriptor, + ShadowNodeTraits traits); /* * Creates a Shadow Node via cloning given `sourceShadowNode` and @@ -94,6 +96,11 @@ class ShadowNode : public virtual Sealable, ComponentName getComponentName() const; ComponentHandle getComponentHandle() const; + /* + * Returns a stored traits. + */ + ShadowNodeTraits getTraits() const; + SharedProps const &getProps() const; SharedShadowNodeList const &getChildren() const; SharedEventEmitter const &getEventEmitter() const; @@ -196,10 +203,10 @@ class ShadowNode : public virtual Sealable, ShadowNodeFamily::Shared family_; /* - * Indicates that `children` list is shared between nodes and need - * to be cloned before the first mutation. + * Traits associated with the particular `ShadowNode` class and an instance of + * that class. */ - bool childrenAreShared_; + ShadowNodeTraits traits_; }; } // namespace react diff --git a/ReactCommon/fabric/core/shadownode/ShadowNodeTraits.cpp b/ReactCommon/fabric/core/shadownode/ShadowNodeTraits.cpp new file mode 100644 index 00000000000..6e200c32be3 --- /dev/null +++ b/ReactCommon/fabric/core/shadownode/ShadowNodeTraits.cpp @@ -0,0 +1,12 @@ +/* + * 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 "ShadowNodeTraits.h" + +namespace facebook { +namespace react {} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/core/shadownode/ShadowNodeTraits.h b/ReactCommon/fabric/core/shadownode/ShadowNodeTraits.h new file mode 100644 index 00000000000..5fe7c69c6f2 --- /dev/null +++ b/ReactCommon/fabric/core/shadownode/ShadowNodeTraits.h @@ -0,0 +1,77 @@ +/* + * 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. + */ + +#pragma once + +#include + +namespace facebook { +namespace react { + +/* + * A set of predefined traits associated with a particular `ShadowNode` class + * and an instance of that class. Used for efficient checking for interface + * conformance for and storing important flags. + */ +class ShadowNodeTraits { + public: + /* + * Underlying type for the traits. + * The first 16 bits are reserved for Core. + */ + enum Trait : int32_t { + None = 0, + + // Note: + // Not all traits are used yet (but all will be used in the near future). + + // Inherits `LayoutableShadowNode`. + LayoutableKind = 1 << 0, + + // Inherits `YogaLayoutableShadowNode`. + YogaLayoutableKind = 1 << 1, + + // Inherits `ConcreteViewShadowNode<>` template. + ViewKind = 1 << 2, + + // Inherits `BaseTextShadowNode`. + TextKind = 1 << 3, + + // Inherits `YogaLayoutableShadowNode` and enforces that the `YGNode` is a + // leaf. + LeafYogaNode = 1 << 10, + + // Inherits `LayoutableShadowNode` and calls `measure()`. + HasMeasure = 1 << 11, + + // Internal to `ShadowNode`; do not use it outside. + // Indicates that `children` list is shared between nodes and need + // to be cloned before the first mutation. + ChildrenAreShared = 1 << 15, + }; + + /* + * Sets, unsets, and checks individual traits. + */ + inline void set(Trait trait) { + traits_ = ShadowNodeTraits::Trait(traits_ | trait); + } + + inline void unset(Trait trait) { + traits_ = ShadowNodeTraits::Trait(traits_ & ~trait); + } + + inline bool check(Trait traits) const { + return ShadowNodeTraits::Trait(traits_ & traits) == traits; + } + + private: + Trait traits_{Trait::None}; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/core/tests/ShadowNodeTest.cpp b/ReactCommon/fabric/core/tests/ShadowNodeTest.cpp index 901bca38cc0..071ea5bba1c 100644 --- a/ReactCommon/fabric/core/tests/ShadowNodeTest.cpp +++ b/ReactCommon/fabric/core/tests/ShadowNodeTest.cpp @@ -40,7 +40,8 @@ TEST(ShadowNodeTest, handleShadowNodeCreation) { /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), /* .children = */ ShadowNode::emptySharedShadowNodeSharedList(), }, - componentDescriptor); + componentDescriptor, + ShadowNodeTraits{}); ASSERT_FALSE(node->getSealed()); ASSERT_STREQ(node->getComponentName(), "Test"); @@ -65,7 +66,8 @@ TEST(ShadowNodeTest, handleShadowNodeSimpleCloning) { /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), /* .children = */ ShadowNode::emptySharedShadowNodeSharedList(), }, - componentDescriptor); + componentDescriptor, + ShadowNodeTraits{}); auto node2 = std::make_shared(*node, ShadowNodeFragment{}); ASSERT_STREQ(node->getComponentName(), "Test"); @@ -86,7 +88,8 @@ TEST(ShadowNodeTest, handleShadowNodeMutation) { /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), /* .children = */ ShadowNode::emptySharedShadowNodeSharedList(), }, - componentDescriptor); + componentDescriptor, + ShadowNodeTraits{}); auto node2 = std::make_shared( ShadowNodeFragment{ /* .tag = */ 2, @@ -95,7 +98,8 @@ TEST(ShadowNodeTest, handleShadowNodeMutation) { /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), /* .children = */ ShadowNode::emptySharedShadowNodeSharedList(), }, - componentDescriptor); + componentDescriptor, + ShadowNodeTraits{}); auto node3 = std::make_shared( ShadowNodeFragment{ /* .tag = */ 3, @@ -104,7 +108,8 @@ TEST(ShadowNodeTest, handleShadowNodeMutation) { /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), /* .children = */ ShadowNode::emptySharedShadowNodeSharedList(), }, - componentDescriptor); + componentDescriptor, + ShadowNodeTraits{}); node1->appendChild(node2); node1->appendChild(node3); @@ -146,7 +151,8 @@ TEST(ShadowNodeTest, handleCloneFunction) { /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), /* .children = */ ShadowNode::emptySharedShadowNodeSharedList(), }, - componentDescriptor); + componentDescriptor, + ShadowNodeTraits{}); auto firstNodeClone = firstNode->clone({}); @@ -183,7 +189,8 @@ TEST(ShadowNodeTest, handleLocalData) { /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), /* .children = */ ShadowNode::emptySharedShadowNodeSharedList(), }, - componentDescriptor); + componentDescriptor, + ShadowNodeTraits{}); auto secondNode = std::make_shared( ShadowNodeFragment{ /* .tag = */ 9, @@ -192,7 +199,8 @@ TEST(ShadowNodeTest, handleLocalData) { /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), /* .children = */ ShadowNode::emptySharedShadowNodeSharedList(), }, - componentDescriptor); + componentDescriptor, + ShadowNodeTraits{}); auto thirdNode = std::make_shared( ShadowNodeFragment{ /* .tag = */ 9, @@ -201,7 +209,8 @@ TEST(ShadowNodeTest, handleLocalData) { /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), /* .children = */ ShadowNode::emptySharedShadowNodeSharedList(), }, - componentDescriptor); + componentDescriptor, + ShadowNodeTraits{}); firstNode->setLocalData(localData42); secondNode->setLocalData(localData42); @@ -244,7 +253,8 @@ TEST(ShadowNodeTest, handleBacktracking) { /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), /* .children = */ ShadowNode::emptySharedShadowNodeSharedList(), }, - componentDescriptor); + componentDescriptor, + ShadowNodeTraits{}); auto nodeABA = std::make_shared( ShadowNodeFragment{ @@ -254,7 +264,8 @@ TEST(ShadowNodeTest, handleBacktracking) { /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), /* .children = */ ShadowNode::emptySharedShadowNodeSharedList(), }, - componentDescriptor); + componentDescriptor, + ShadowNodeTraits{}); auto nodeABB = std::make_shared( ShadowNodeFragment{ /* .tag = */ ShadowNodeFragment::tagPlaceholder(), @@ -263,7 +274,8 @@ TEST(ShadowNodeTest, handleBacktracking) { /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), /* .children = */ ShadowNode::emptySharedShadowNodeSharedList(), }, - componentDescriptor); + componentDescriptor, + ShadowNodeTraits{}); auto nodeABC = std::make_shared( ShadowNodeFragment{ /* .tag = */ ShadowNodeFragment::tagPlaceholder(), @@ -272,7 +284,8 @@ TEST(ShadowNodeTest, handleBacktracking) { /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), /* .children = */ ShadowNode::emptySharedShadowNodeSharedList(), }, - componentDescriptor); + componentDescriptor, + ShadowNodeTraits{}); auto nodeABChildren = std::make_shared( SharedShadowNodeList{nodeABA, nodeABB, nodeABC}); @@ -284,7 +297,8 @@ TEST(ShadowNodeTest, handleBacktracking) { /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), /* .children = */ nodeABChildren, }, - componentDescriptor); + componentDescriptor, + ShadowNodeTraits{}); auto nodeAC = std::make_shared( ShadowNodeFragment{ @@ -294,7 +308,8 @@ TEST(ShadowNodeTest, handleBacktracking) { /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), /* .children = */ ShadowNode::emptySharedShadowNodeSharedList(), }, - componentDescriptor); + componentDescriptor, + ShadowNodeTraits{}); auto nodeAChildren = std::make_shared( SharedShadowNodeList{nodeAA, nodeAB, nodeAC}); @@ -306,7 +321,8 @@ TEST(ShadowNodeTest, handleBacktracking) { /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), /* .children = */ nodeAChildren, }, - componentDescriptor); + componentDescriptor, + ShadowNodeTraits{}); auto nodeZ = std::make_shared( ShadowNodeFragment{ @@ -316,7 +332,8 @@ TEST(ShadowNodeTest, handleBacktracking) { /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), /* .children = */ ShadowNode::emptySharedShadowNodeSharedList(), }, - componentDescriptor); + componentDescriptor, + ShadowNodeTraits{}); std::vector> ancestors = {};