Fabric/Text: ShadowNode::clone() - dynamic self-cloning mechanism

Summary:
The previous approach simply didn't work. :(
As you might notice, in previous implementation of ViewShadowNode::cloneAndReplaceChild we used `ViewShadowNode` class for cloning `childNode`, and this is incorrect becasue `childNode` might be instance of any class.

Reviewed By: fkgozali

Differential Revision: D7595016

fbshipit-source-id: 2215414926f2f7a2e2fd05ca2d065f10d6d32b74
This commit is contained in:
Valentin Shergin 2018-04-26 17:51:27 -07:00 committed by Facebook Github Bot
parent 2e7dbc82b3
commit bd91eaf664
7 changed files with 106 additions and 15 deletions

View File

@ -8,6 +8,7 @@
#pragma once
#include <memory>
#include <functional>
#include <fabric/core/ComponentDescriptor.h>
#include <fabric/core/Props.h>
@ -40,7 +41,14 @@ public:
const InstanceHandle &instanceHandle,
const SharedProps &props
) const override {
return std::make_shared<ShadowNodeT>(tag, rootTag, instanceHandle, std::static_pointer_cast<const ConcreteProps>(props));
return std::make_shared<ShadowNodeT>(
tag,
rootTag,
instanceHandle,
std::static_pointer_cast<const ConcreteProps>(props),
ShadowNode::emptySharedShadowNodeSharedList(),
getCloneFunction()
);
}
SharedShadowNode cloneShadowNode(
@ -67,6 +75,18 @@ public:
return ShadowNodeT::Props(rawProps, props);
};
private:
mutable ShadowNodeCloneFunction cloneFunction_;
ShadowNodeCloneFunction getCloneFunction() const {
if (!cloneFunction_) {
cloneFunction_ = [this](const SharedShadowNode &shadowNode, const SharedProps &props, const SharedShadowNodeSharedList &children) {
return this->cloneShadowNode(shadowNode, props, children);
};
}
return cloneFunction_;
}
};
} // namespace react

View File

@ -51,14 +51,16 @@ public:
const Tag &rootTag,
const InstanceHandle &instanceHandle,
const SharedConcreteProps &props = ConcreteShadowNode::defaultSharedProps(),
const SharedShadowNodeSharedList &children = ShadowNode::emptySharedShadowNodeSharedList()
const SharedShadowNodeSharedList &children = ShadowNode::emptySharedShadowNodeSharedList(),
const ShadowNodeCloneFunction &cloneFunction = nullptr
):
ShadowNode(
tag,
rootTag,
instanceHandle,
(SharedProps)props,
children
children,
cloneFunction
) {};
ConcreteShadowNode(

View File

@ -25,13 +25,15 @@ ShadowNode::ShadowNode(
const Tag &rootTag,
const InstanceHandle &instanceHandle,
const SharedProps &props,
const SharedShadowNodeSharedList &children
const SharedShadowNodeSharedList &children,
const ShadowNodeCloneFunction &cloneFunction
):
tag_(tag),
rootTag_(rootTag),
instanceHandle_(instanceHandle),
props_(props),
children_(std::make_shared<SharedShadowNodeList>(*children)),
cloneFunction_(cloneFunction),
revision_(1) {}
ShadowNode::ShadowNode(
@ -45,8 +47,17 @@ ShadowNode::ShadowNode(
props_(props ? props : shadowNode->props_),
children_(std::make_shared<SharedShadowNodeList>(*(children ? children : shadowNode->children_))),
sourceNode_(shadowNode),
cloneFunction_(shadowNode->cloneFunction_),
revision_(shadowNode->revision_ + 1) {}
SharedShadowNode ShadowNode::clone(
const SharedProps &props,
const SharedShadowNodeSharedList &children
) const {
assert(cloneFunction_);
return cloneFunction_(shared_from_this(), props_, children_);
}
#pragma mark - Getters
SharedShadowNodeSharedList ShadowNode::getChildren() const {

View File

@ -8,6 +8,7 @@
#pragma once
#include <string>
#include <memory>
#include <vector>
#include <fabric/core/Props.h>
@ -26,9 +27,12 @@ using SharedShadowNodeSharedList = std::shared_ptr<const SharedShadowNodeList>;
using SharedShadowNodeUnsharedList = std::shared_ptr<SharedShadowNodeList>;
using WeakShadowNode = std::weak_ptr<const ShadowNode>;
using ShadowNodeCloneFunction = std::function<SharedShadowNode(SharedShadowNode shadowNode, SharedProps props, SharedShadowNodeSharedList children)>;
class ShadowNode:
public virtual Sealable,
public virtual DebugStringConvertible {
public virtual DebugStringConvertible,
public std::enable_shared_from_this<ShadowNode> {
public:
static SharedShadowNodeSharedList emptySharedShadowNodeSharedList();
@ -39,7 +43,8 @@ public:
const Tag &rootTag,
const InstanceHandle &instanceHandle,
const SharedProps &props = SharedProps(),
const SharedShadowNodeSharedList &children = SharedShadowNodeSharedList()
const SharedShadowNodeSharedList &children = SharedShadowNodeSharedList(),
const ShadowNodeCloneFunction &cloneFunction = nullptr
);
ShadowNode(
@ -48,6 +53,14 @@ public:
const SharedShadowNodeSharedList &children = nullptr
);
/*
* Clones the shadow node using stored `cloneFunction`.
*/
SharedShadowNode clone(
const SharedProps &props = nullptr,
const SharedShadowNodeSharedList &children = nullptr
) const;
#pragma mark - Getters
virtual ComponentHandle getComponentHandle() const = 0;
@ -112,6 +125,12 @@ protected:
private:
/*
* A reference to a cloning function that understands how to clone
* the specific type of ShadowNode.
*/
ShadowNodeCloneFunction cloneFunction_;
/*
* A number of the generation of the ShadowNode instance;
* is used and useful for debug-printing purposes *only*.

View File

@ -115,3 +115,38 @@ TEST(ShadowNodeTest, handleSourceNode) {
ASSERT_EQ(nodeThirdGeneration->getSourceNode(), nodeSecondGeneration);
ASSERT_EQ(nodeSecondGeneration->getSourceNode(), nodeFirstGeneration);
}
TEST(ShadowNodeTest, handleCloneFunction) {
auto firstNode = std::make_shared<TestShadowNode>(9, 1, (void *)NULL);
// The shadow node is not clonable if `cloneFunction` is not provided,
ASSERT_DEATH_IF_SUPPORTED(firstNode->clone(), "cloneFunction_");
auto secondNode = std::make_shared<TestShadowNode>(
9,
1,
(void *)NULL,
std::make_shared<const TestProps>(),
ShadowNode::emptySharedShadowNodeSharedList(),
[](const SharedShadowNode &shadowNode, const SharedProps &props, const SharedShadowNodeSharedList &children) {
return std::make_shared<const TestShadowNode>(
std::static_pointer_cast<const TestShadowNode>(shadowNode),
props,
children
);
}
);
auto secondNodeClone = secondNode->clone();
// Those two nodes are *not* same.
ASSERT_NE(secondNode, secondNodeClone);
// `secondNodeClone` is an instance of `TestShadowNode`.
ASSERT_NE(std::dynamic_pointer_cast<const TestShadowNode>(secondNodeClone), nullptr);
// Both nodes have same content.
ASSERT_EQ(secondNode->getTag(), secondNodeClone->getTag());
ASSERT_EQ(secondNode->getRootTag(), secondNodeClone->getRootTag());
ASSERT_EQ(secondNode->getProps(), secondNodeClone->getProps());
}

View File

@ -21,14 +21,16 @@ ViewShadowNode::ViewShadowNode(
const Tag &rootTag,
const InstanceHandle &instanceHandle,
const SharedViewProps &props,
const SharedShadowNodeSharedList &children
const SharedShadowNodeSharedList &children,
const ShadowNodeCloneFunction &cloneFunction
):
ConcreteShadowNode(
tag,
rootTag,
instanceHandle,
props,
children
children,
cloneFunction
),
AccessibleShadowNode(
props
@ -92,9 +94,9 @@ SharedLayoutableShadowNodeList ViewShadowNode::getLayoutableChildNodes() const {
SharedLayoutableShadowNode ViewShadowNode::cloneAndReplaceChild(const SharedLayoutableShadowNode &child) {
ensureUnsealed();
auto viewShadowNodeChild = std::dynamic_pointer_cast<const ViewShadowNode>(child);
assert(viewShadowNodeChild);
auto viewShadowNodeChildClone = std::make_shared<ViewShadowNode>(viewShadowNodeChild);
auto childShadowNode = std::dynamic_pointer_cast<const ShadowNode>(child);
assert(childShadowNode);
auto childShadowNodeClone = childShadowNode->clone();
// This is overloading of `SharedLayoutableShadowNode::cloneAndReplaceChild`,
// the method is used to clone some node as a preparation for future mutation
@ -107,10 +109,11 @@ SharedLayoutableShadowNode ViewShadowNode::cloneAndReplaceChild(const SharedLayo
// In other words, if we don't compensate this change here,
// the Diffing algorithm will compare wrong trees
// ("new-but-not-laid-out-yet vs. new" instead of "committed vs. new").
viewShadowNodeChildClone->shallowSourceNode();
auto nonConstChildShadowNodeClone = std::const_pointer_cast<ShadowNode>(childShadowNodeClone);
nonConstChildShadowNodeClone->shallowSourceNode();
ShadowNode::replaceChild(viewShadowNodeChild, viewShadowNodeChildClone);
return std::static_pointer_cast<const LayoutableShadowNode>(viewShadowNodeChildClone);
ShadowNode::replaceChild(childShadowNode, childShadowNodeClone);
return std::dynamic_pointer_cast<const LayoutableShadowNode>(childShadowNodeClone);
}
#pragma mark - Equality

View File

@ -35,7 +35,8 @@ public:
const Tag &rootTag,
const InstanceHandle &instanceHandle,
const SharedViewProps &props = ViewShadowNode::defaultSharedProps(),
const SharedShadowNodeSharedList &children = ShadowNode::emptySharedShadowNodeSharedList()
const SharedShadowNodeSharedList &children = ShadowNode::emptySharedShadowNodeSharedList(),
const ShadowNodeCloneFunction &cloneFunction = nullptr
);
ViewShadowNode(