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
This commit is contained in:
Valentin Shergin 2019-11-15 19:47:51 -08:00 коммит произвёл Facebook Github Bot
Родитель 29b99720b2
Коммит 059142935a
9 изменённых файлов: 177 добавлений и 35 удалений

Просмотреть файл

@ -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<const ConcreteViewProps>(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<const ConcreteViewShadowNode &>(sourceShadowNode)) {

Просмотреть файл

@ -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;

Просмотреть файл

@ -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<const ConcreteProps>(fragment.props));
assert(std::dynamic_pointer_cast<const ConcreteEventEmitter>(
fragment.eventEmitter));
auto shadowNode = std::make_shared<ShadowNodeT>(fragment, *this);
auto shadowNode =
std::make_shared<ShadowNodeT>(fragment, *this, getTraits());
adopt(shadowNode);

Просмотреть файл

@ -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) {

Просмотреть файл

@ -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<SharedShadowNodeList>(*children_);
}

Просмотреть файл

@ -18,6 +18,7 @@
#include <react/core/ReactPrimitives.h>
#include <react/core/Sealable.h>
#include <react/core/ShadowNodeFamily.h>
#include <react/core/ShadowNodeTraits.h>
#include <react/core/State.h>
#include <react/debug/DebugStringConvertible.h>
@ -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

Просмотреть файл

@ -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

Просмотреть файл

@ -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 <cstdint>
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

Просмотреть файл

@ -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<TestShadowNode>(*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<TestShadowNode>(
ShadowNodeFragment{
/* .tag = */ 2,
@ -95,7 +98,8 @@ TEST(ShadowNodeTest, handleShadowNodeMutation) {
/* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(),
/* .children = */ ShadowNode::emptySharedShadowNodeSharedList(),
},
componentDescriptor);
componentDescriptor,
ShadowNodeTraits{});
auto node3 = std::make_shared<TestShadowNode>(
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<TestShadowNode>(
ShadowNodeFragment{
/* .tag = */ 9,
@ -192,7 +199,8 @@ TEST(ShadowNodeTest, handleLocalData) {
/* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(),
/* .children = */ ShadowNode::emptySharedShadowNodeSharedList(),
},
componentDescriptor);
componentDescriptor,
ShadowNodeTraits{});
auto thirdNode = std::make_shared<TestShadowNode>(
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<TestShadowNode>(
ShadowNodeFragment{
@ -254,7 +264,8 @@ TEST(ShadowNodeTest, handleBacktracking) {
/* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(),
/* .children = */ ShadowNode::emptySharedShadowNodeSharedList(),
},
componentDescriptor);
componentDescriptor,
ShadowNodeTraits{});
auto nodeABB = std::make_shared<TestShadowNode>(
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<TestShadowNode>(
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>(
SharedShadowNodeList{nodeABA, nodeABB, nodeABC});
@ -284,7 +297,8 @@ TEST(ShadowNodeTest, handleBacktracking) {
/* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(),
/* .children = */ nodeABChildren,
},
componentDescriptor);
componentDescriptor,
ShadowNodeTraits{});
auto nodeAC = std::make_shared<TestShadowNode>(
ShadowNodeFragment{
@ -294,7 +308,8 @@ TEST(ShadowNodeTest, handleBacktracking) {
/* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(),
/* .children = */ ShadowNode::emptySharedShadowNodeSharedList(),
},
componentDescriptor);
componentDescriptor,
ShadowNodeTraits{});
auto nodeAChildren = std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{nodeAA, nodeAB, nodeAC});
@ -306,7 +321,8 @@ TEST(ShadowNodeTest, handleBacktracking) {
/* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(),
/* .children = */ nodeAChildren,
},
componentDescriptor);
componentDescriptor,
ShadowNodeTraits{});
auto nodeZ = std::make_shared<TestShadowNode>(
ShadowNodeFragment{
@ -316,7 +332,8 @@ TEST(ShadowNodeTest, handleBacktracking) {
/* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(),
/* .children = */ ShadowNode::emptySharedShadowNodeSharedList(),
},
componentDescriptor);
componentDescriptor,
ShadowNodeTraits{});
std::vector<std::reference_wrapper<const ShadowNode>> ancestors = {};