Fabric: Using ShadowView instead of ShadowNode in Mutations

Summary:
@public
This is quite a big diff but the actual meaningful change is simple: now we use ShadowView class instead of ShadowNode in mutation instructions.
Note:
 * In some places (especially during diffing) we have to operate with ShadowNodeViewPair objects (which represents a pair of ShadowNode and ShadowView). The reason for that is that we cannot construct child ShadowViews from parent ShadowViews because they don't have any information about children.
 * `ShadowTree::emitLayoutEvents` is now much simpler because ShadowView better represents the specifics of this kind of object.
 * The code in RCTMountingManager also became simpler.

This change will allow us to implement more cool tricks soon.

Reviewed By: mdvacca

Differential Revision: D9403564

fbshipit-source-id: dbc7c61af250144d6c7335a01dc30df0005559a2
This commit is contained in:
Valentin Shergin 2018-09-03 22:53:18 -07:00 committed by Facebook Github Bot
parent 5c83855c75
commit 0792fba63f
17 changed files with 266 additions and 598 deletions

View File

@ -7,7 +7,8 @@
#import <UIKit/UIKit.h>
#import <fabric/uimanager/TreeMutationInstruction.h>
#import <fabric/uimanager/ShadowView.h>
#import <fabric/uimanager/ShadowViewMutation.h>
#import <React/RCTPrimitives.h>
#import <React/RCTMountingManagerDelegate.h>
@ -28,8 +29,8 @@ NS_ASSUME_NONNULL_BEGIN
* The order of mutation tnstructions matters.
* Can be called from any thread.
*/
- (void)mutateComponentViewTreeWithMutationInstructions:(facebook::react::TreeMutationInstructionList)instructions
rootTag:(ReactTag)rootTag;
- (void)performTransactionWithMutations:(facebook::react::ShadowViewMutationList)mutations
rootTag:(ReactTag)rootTag;
/**
* Suggests preliminary creation of a component view of given type.

View File

@ -38,123 +38,112 @@ using namespace facebook::react;
return self;
}
- (void)mutateComponentViewTreeWithMutationInstructions:(facebook::react::TreeMutationInstructionList)instructions
rootTag:(ReactTag)rootTag
- (void)performTransactionWithMutations:(facebook::react::ShadowViewMutationList)mutations
rootTag:(ReactTag)rootTag
{
NSMutableArray<RCTMountItemProtocol> *mountItems =
[[NSMutableArray<RCTMountItemProtocol> alloc] initWithCapacity:instructions.size() * 2 /* ~ the worst case */];
[[NSMutableArray<RCTMountItemProtocol> alloc] initWithCapacity:mutations.size() * 2 /* ~ the worst case */];
for (auto instruction : instructions) {
switch (instruction.getType()) {
case TreeMutationInstruction::Creation: {
NSString *componentName = RCTNSStringFromString(instruction.getNewChildNode()->getComponentName(), NSASCIIStringEncoding);
for (const auto &mutation : mutations) {
switch (mutation.type) {
case ShadowViewMutation::Create: {
NSString *componentName = RCTNSStringFromString(mutation.newChildShadowView.componentName, NSASCIIStringEncoding);
RCTCreateMountItem *mountItem =
[[RCTCreateMountItem alloc] initWithComponentName:componentName
tag:instruction.getNewChildNode()->getTag()];
tag:mutation.newChildShadowView.tag];
[mountItems addObject:mountItem];
break;
}
case TreeMutationInstruction::Deletion: {
NSString *componentName = RCTNSStringFromString(instruction.getOldChildNode()->getComponentName(), NSASCIIStringEncoding);
case ShadowViewMutation::Delete: {
NSString *componentName = RCTNSStringFromString(mutation.oldChildShadowView.componentName, NSASCIIStringEncoding);
RCTDeleteMountItem *mountItem =
[[RCTDeleteMountItem alloc] initWithComponentName:componentName
tag:instruction.getOldChildNode()->getTag()];
tag:mutation.oldChildShadowView.tag];
[mountItems addObject:mountItem];
break;
}
case TreeMutationInstruction::Insertion: {
case ShadowViewMutation::Insert: {
// Props
[mountItems addObject:[[RCTUpdatePropsMountItem alloc] initWithTag:instruction.getNewChildNode()->getTag()
[mountItems addObject:[[RCTUpdatePropsMountItem alloc] initWithTag:mutation.newChildShadowView.tag
oldProps:nullptr
newProps:instruction.getNewChildNode()->getProps()]];
newProps:mutation.newChildShadowView.props]];
// EventEmitter
[mountItems addObject:[[RCTUpdateEventEmitterMountItem alloc] initWithTag:instruction.getNewChildNode()->getTag()
eventEmitter:instruction.getNewChildNode()->getEventEmitter()]];
[mountItems addObject:[[RCTUpdateEventEmitterMountItem alloc] initWithTag:mutation.newChildShadowView.tag
eventEmitter:mutation.newChildShadowView.eventEmitter]];
// LocalData
if (instruction.getNewChildNode()->getLocalData()) {
[mountItems addObject:[[RCTUpdateLocalDataMountItem alloc] initWithTag:instruction.getNewChildNode()->getTag()
if (mutation.newChildShadowView.localData) {
[mountItems addObject:[[RCTUpdateLocalDataMountItem alloc] initWithTag:mutation.newChildShadowView.tag
oldLocalData:nullptr
newLocalData:instruction.getNewChildNode()->getLocalData()]];
newLocalData:mutation.newChildShadowView.localData]];
}
// Layout
auto layoutableNewShadowNode =
std::dynamic_pointer_cast<const LayoutableShadowNode>(instruction.getNewChildNode());
if (layoutableNewShadowNode) {
[mountItems addObject:[[RCTUpdateLayoutMetricsMountItem alloc] initWithTag:instruction.getNewChildNode()->getTag()
if (mutation.newChildShadowView.layoutMetrics != EmptyLayoutMetrics) {
[mountItems addObject:[[RCTUpdateLayoutMetricsMountItem alloc] initWithTag:mutation.newChildShadowView.tag
oldLayoutMetrics:{}
newLayoutMetrics:layoutableNewShadowNode->getLayoutMetrics()]];
newLayoutMetrics:mutation.newChildShadowView.layoutMetrics]];
}
// Insertion
RCTInsertMountItem *mountItem =
[[RCTInsertMountItem alloc] initWithChildTag:instruction.getNewChildNode()->getTag()
parentTag:instruction.getParentNode()->getTag()
index:instruction.getIndex()];
[[RCTInsertMountItem alloc] initWithChildTag:mutation.newChildShadowView.tag
parentTag:mutation.parentShadowView.tag
index:mutation.index];
[mountItems addObject:mountItem];
break;
}
case TreeMutationInstruction::Removal: {
case ShadowViewMutation::Remove: {
RCTRemoveMountItem *mountItem =
[[RCTRemoveMountItem alloc] initWithChildTag:instruction.getOldChildNode()->getTag()
parentTag:instruction.getParentNode()->getTag()
index:instruction.getIndex()];
[[RCTRemoveMountItem alloc] initWithChildTag:mutation.oldChildShadowView.tag
parentTag:mutation.parentShadowView.tag
index:mutation.index];
[mountItems addObject:mountItem];
break;
}
case TreeMutationInstruction::Replacement: {
SharedShadowNode oldShadowNode = instruction.getOldChildNode();
SharedShadowNode newShadowNode = instruction.getNewChildNode();
case ShadowViewMutation::Update: {
auto oldChildShadowView = mutation.oldChildShadowView;
auto newChildShadowView = mutation.newChildShadowView;
// Props
if (oldShadowNode->getProps() != newShadowNode->getProps()) {
if (oldChildShadowView.props != newChildShadowView.props) {
RCTUpdatePropsMountItem *mountItem =
[[RCTUpdatePropsMountItem alloc] initWithTag:instruction.getOldChildNode()->getTag()
oldProps:instruction.getOldChildNode()->getProps()
newProps:instruction.getNewChildNode()->getProps()];
[[RCTUpdatePropsMountItem alloc] initWithTag:mutation.oldChildShadowView.tag
oldProps:mutation.oldChildShadowView.props
newProps:mutation.newChildShadowView.props];
[mountItems addObject:mountItem];
}
// EventEmitter
if (oldShadowNode->getEventEmitter() != newShadowNode->getEventEmitter()) {
if (oldChildShadowView.eventEmitter != newChildShadowView.eventEmitter) {
RCTUpdateEventEmitterMountItem *mountItem =
[[RCTUpdateEventEmitterMountItem alloc] initWithTag:instruction.getOldChildNode()->getTag()
eventEmitter:instruction.getOldChildNode()->getEventEmitter()];
[[RCTUpdateEventEmitterMountItem alloc] initWithTag:mutation.oldChildShadowView.tag
eventEmitter:mutation.oldChildShadowView.eventEmitter];
[mountItems addObject:mountItem];
}
// LocalData
if (oldShadowNode->getLocalData() != newShadowNode->getLocalData()) {
if (oldChildShadowView.localData != newChildShadowView.localData) {
RCTUpdateLocalDataMountItem *mountItem =
[[RCTUpdateLocalDataMountItem alloc] initWithTag:newShadowNode->getTag()
oldLocalData:oldShadowNode->getLocalData()
newLocalData:newShadowNode->getLocalData()];
[[RCTUpdateLocalDataMountItem alloc] initWithTag:newChildShadowView.tag
oldLocalData:oldChildShadowView.localData
newLocalData:newChildShadowView.localData];
[mountItems addObject:mountItem];
}
// Layout
auto layoutableOldShadowNode =
std::dynamic_pointer_cast<const LayoutableShadowNode>(oldShadowNode);
if (layoutableOldShadowNode) {
auto layoutableNewShadowNode =
std::dynamic_pointer_cast<const LayoutableShadowNode>(newShadowNode);
if (layoutableOldShadowNode->getLayoutMetrics() != layoutableNewShadowNode->getLayoutMetrics()) {
RCTUpdateLayoutMetricsMountItem *mountItem =
[[RCTUpdateLayoutMetricsMountItem alloc] initWithTag:instruction.getOldChildNode()->getTag()
oldLayoutMetrics:layoutableOldShadowNode->getLayoutMetrics()
newLayoutMetrics:layoutableNewShadowNode->getLayoutMetrics()];
[mountItems addObject:mountItem];
}
if (oldChildShadowView.layoutMetrics != newChildShadowView.layoutMetrics) {
RCTUpdateLayoutMetricsMountItem *mountItem =
[[RCTUpdateLayoutMetricsMountItem alloc] initWithTag:mutation.oldChildShadowView.tag
oldLayoutMetrics:oldChildShadowView.layoutMetrics
newLayoutMetrics:newChildShadowView.layoutMetrics];
[mountItems addObject:mountItem];
}
break;

View File

@ -12,7 +12,7 @@
#import <fabric/core/LayoutConstraints.h>
#import <fabric/core/LayoutContext.h>
#import <fabric/uimanager/FabricUIManager.h>
#import <fabric/uimanager/TreeMutationInstruction.h>
#import <fabric/uimanager/ShadowViewMutation.h>
NS_ASSUME_NONNULL_BEGIN
@ -23,7 +23,8 @@ NS_ASSUME_NONNULL_BEGIN
*/
@protocol RCTSchedulerDelegate
- (void)schedulerDidComputeMutationInstructions:(facebook::react::TreeMutationInstructionList)instructions rootTag:(ReactTag)rootTag;
- (void)schedulerDidFinishTransaction:(facebook::react::ShadowViewMutationList)mutations
rootTag:(ReactTag)rootTag;
- (void)schedulerDidRequestPreliminaryViewAllocationWithComponentName:(NSString *)componentName;

View File

@ -30,9 +30,9 @@ class SchedulerDelegateProxy: public SchedulerDelegate {
public:
SchedulerDelegateProxy(void *scheduler): scheduler_(scheduler) {}
void schedulerDidComputeMutationInstructions(Tag rootTag, const TreeMutationInstructionList &instructions) override {
void schedulerDidFinishTransaction(Tag rootTag, const ShadowViewMutationList &mutations) override {
RCTScheduler *scheduler = (__bridge RCTScheduler *)scheduler_;
[scheduler.delegate schedulerDidComputeMutationInstructions:instructions rootTag:rootTag];
[scheduler.delegate schedulerDidFinishTransaction:mutations rootTag:rootTag];
}
void schedulerDidRequestPreliminaryViewAllocation(ComponentName componentName) override {

View File

@ -69,11 +69,11 @@ using namespace facebook::react;
#pragma mark - RCTSchedulerDelegate
- (void)schedulerDidComputeMutationInstructions:(facebook::react::TreeMutationInstructionList)instructions
- (void)schedulerDidFinishTransaction:(facebook::react::ShadowViewMutationList)mutations
rootTag:(ReactTag)rootTag
{
[_mountingManager mutateComponentViewTreeWithMutationInstructions:instructions
rootTag:rootTag];
[_mountingManager performTransactionWithMutations:mutations
rootTag:rootTag];
}
- (void)schedulerDidRequestPreliminaryViewAllocationWithComponentName:(NSString *)componentName
@ -89,7 +89,7 @@ using namespace facebook::react;
[_scheduler registerRootTag:surface.rootTag];
[self runSurface:surface];
// FIXME: Mutation instruction MUST produce instruction for root node.
// FIXME: mutation MUST produce instruction for root node.
[_mountingManager.componentViewRegistry dequeueComponentViewWithName:@"Root" tag:surface.rootTag];
}

View File

@ -106,7 +106,7 @@ public:
/*
* Equality operators.
* Use this to compare `ShadowNode`s values for equality (and non-equality).
* Same values indicates that nodes must not produce mutation instructions
* Same values indicates that nodes must not produce mutations
* during tree diffing process.
* Child nodes are not considered as part of the value.
*/

View File

@ -5,202 +5,217 @@
#include "Differentiator.h"
#include "ShadowView.h"
#include <fabric/core/LayoutableShadowNode.h>
namespace facebook {
namespace react {
static void calculateMutationInstructions(
TreeMutationInstructionList &instructions,
SharedShadowNode parentNode,
const SharedShadowNodeList &oldChildNodes,
const SharedShadowNodeList &newChildNodes
static ShadowViewNodePairList sliceChildShadowNodeViewPairs(const ShadowNode &shadowNode) {
ShadowViewNodePairList pairList;
for (const auto &childShadowNode : shadowNode.getChildren()) {
pairList.push_back({ShadowView(*childShadowNode), *childShadowNode});
}
return pairList;
}
static void calculateShadowViewMutations(
ShadowViewMutationList &mutations,
const ShadowView &parentShadowView,
const ShadowViewNodePairList &oldChildPairs,
const ShadowViewNodePairList &newChildPairs
) {
// The current version of the algorithm is otimized for simplicity,
// not for performance of optimal result.
// not for performance or optimal result.
// TODO(shergin): Consider to use Minimal Edit Distance algorithm to produce
// optimal set of instructions and improve mounting performance.
// https://en.wikipedia.org/wiki/Edit_distance
// https://www.geeksforgeeks.org/dynamic-programming-set-5-edit-distance/
if (oldChildNodes == newChildNodes) {
if (oldChildPairs == newChildPairs) {
return;
}
if (oldChildNodes.size() == 0 && newChildNodes.size() == 0) {
if (oldChildPairs.size() == 0 && newChildPairs.size() == 0) {
return;
}
std::unordered_map<Tag, SharedShadowNode> insertedNodes;
std::unordered_map<Tag, ShadowViewNodePair> insertedPaires;
int index = 0;
TreeMutationInstructionList createInstructions = {};
TreeMutationInstructionList deleteInstructions = {};
TreeMutationInstructionList insertInstructions = {};
TreeMutationInstructionList removeInstructions = {};
TreeMutationInstructionList replaceInstructions = {};
TreeMutationInstructionList downwardInstructions = {};
TreeMutationInstructionList destructionDownwardInstructions = {};
ShadowViewMutationList createMutations = {};
ShadowViewMutationList deleteMutations = {};
ShadowViewMutationList insertMutations = {};
ShadowViewMutationList removeMutations = {};
ShadowViewMutationList updateMutations = {};
ShadowViewMutationList downwardMutations = {};
ShadowViewMutationList destructiveDownwardMutations = {};
// Stage 1: Collectings Updates
for (index = 0; index < oldChildNodes.size() && index < newChildNodes.size(); index++) {
const auto &oldChildNode = oldChildNodes.at(index);
const auto &newChildNode = newChildNodes.at(index);
// Stage 1: Collecting `Update` mutations
for (index = 0; index < oldChildPairs.size() && index < newChildPairs.size(); index++) {
const auto &oldChildPair = oldChildPairs[index];
const auto &newChildPair = newChildPairs[index];
if (oldChildNode->getTag() != newChildNode->getTag()) {
if (oldChildPair.shadowView.tag != newChildPair.shadowView.tag) {
// Totally different nodes, updating is impossible.
break;
}
if (*oldChildNode != *newChildNode) {
replaceInstructions.push_back(
TreeMutationInstruction::Replace(
parentNode,
oldChildNode,
newChildNode,
if (oldChildPair.shadowView != newChildPair.shadowView) {
updateMutations.push_back(
ShadowViewMutation::UpdateMutation(
parentShadowView,
oldChildPair.shadowView,
newChildPair.shadowView,
index
)
);
}
calculateMutationInstructions(
*(newChildNode->getChildren().size() ? &downwardInstructions : &destructionDownwardInstructions),
oldChildNode,
oldChildNode->getChildren(),
newChildNode->getChildren()
const auto oldGrandChildPairs = sliceChildShadowNodeViewPairs(oldChildPair.shadowNode);
const auto newGrandChildPairs = sliceChildShadowNodeViewPairs(newChildPair.shadowNode);
calculateShadowViewMutations(
*(newGrandChildPairs.size() ? &downwardMutations : &destructiveDownwardMutations),
oldChildPair.shadowView,
oldGrandChildPairs,
newGrandChildPairs
);
}
int lastIndexAfterFirstStage = index;
// Stage 2: Collectings Insertions
for (; index < newChildNodes.size(); index++) {
const auto &newChildNode = newChildNodes.at(index);
// Stage 2: Collecting `Insert` mutations
for (; index < newChildPairs.size(); index++) {
const auto &newChildPair = newChildPairs[index];
insertInstructions.push_back(
TreeMutationInstruction::Insert(
parentNode,
newChildNode,
insertMutations.push_back(
ShadowViewMutation::InsertMutation(
parentShadowView,
newChildPair.shadowView,
index
)
);
insertedNodes.insert({newChildNode->getTag(), newChildNode});
insertedPaires.insert({newChildPair.shadowView.tag, newChildPair});
}
// Stage 3: Collectings Deletions and Removals
for (index = lastIndexAfterFirstStage; index < oldChildNodes.size(); index++) {
const auto &oldChildNode = oldChildNodes.at(index);
// Stage 3: Collecting `Delete` and `Remove` mutations
for (index = lastIndexAfterFirstStage; index < oldChildPairs.size(); index++) {
const auto &oldChildPair = oldChildPairs[index];
// Even if the old node was (re)inserted, we have to generate `remove`
// instruction.
removeInstructions.push_back(
TreeMutationInstruction::Remove(
parentNode,
oldChildNode,
// Even if the old view was (re)inserted, we have to generate `remove`
// mutation.
removeMutations.push_back(
ShadowViewMutation::RemoveMutation(
parentShadowView,
oldChildPair.shadowView,
index
)
);
const auto &it = insertedNodes.find(oldChildNode->getTag());
const auto &it = insertedPaires.find(oldChildPair.shadowView.tag);
if (it == insertedNodes.end()) {
// The old node was *not* (re)inserted.
// We have to generate `delete` instruction and apply the algorithm
if (it == insertedPaires.end()) {
// The old view was *not* (re)inserted.
// We have to generate `delete` mutation and apply the algorithm
// recursively.
deleteInstructions.push_back(
TreeMutationInstruction::Delete(
oldChildNode
deleteMutations.push_back(
ShadowViewMutation::DeleteMutation(
oldChildPair.shadowView
)
);
// We also have to call the algorithm recursively to clean up the entire
// subtree starting from the removed node.
calculateMutationInstructions(
destructionDownwardInstructions,
oldChildNode,
oldChildNode->getChildren(),
// subtree starting from the removed view.
calculateShadowViewMutations(
destructiveDownwardMutations,
oldChildPair.shadowView,
sliceChildShadowNodeViewPairs(oldChildPair.shadowNode),
{}
);
} else {
// The old node *was* (re)inserted.
// We have to call the algorithm recursively if the inserted node
// The old view *was* (re)inserted.
// We have to call the algorithm recursively if the inserted view
// is *not* the same as removed one.
const auto &newChildNode = it->second;
if (newChildNode != oldChildNode) {
calculateMutationInstructions(
*(newChildNode->getChildren().size() ? &downwardInstructions : &destructionDownwardInstructions),
newChildNode,
oldChildNode->getChildren(),
newChildNode->getChildren()
const auto &newChildPair = it->second;
if (newChildPair.shadowView != oldChildPair.shadowView) {
const auto oldGrandChildPairs = sliceChildShadowNodeViewPairs(oldChildPair.shadowNode);
const auto newGrandChildPairs = sliceChildShadowNodeViewPairs(newChildPair.shadowNode);
calculateShadowViewMutations(
*(newGrandChildPairs.size() ? &downwardMutations : &destructiveDownwardMutations),
newChildPair.shadowView,
oldGrandChildPairs,
newGrandChildPairs
);
}
// In any case we have to remove the node from `insertedNodes` as
// indication that the node was actually removed (which means that
// the node existed before), hence we don't have to generate
// `create` instruction.
insertedNodes.erase(it);
// In any case we have to remove the view from `insertedPaires` as
// indication that the view was actually removed (which means that
// the view existed before), hence we don't have to generate
// `create` mutation.
insertedPaires.erase(it);
}
}
// Stage 4: Collectings Creations
for (index = lastIndexAfterFirstStage; index < newChildNodes.size(); index++) {
const auto &newChildNode = newChildNodes.at(index);
// Stage 4: Collecting `Create` mutations
for (index = lastIndexAfterFirstStage; index < newChildPairs.size(); index++) {
const auto &newChildPair = newChildPairs[index];
if (insertedNodes.find(newChildNode->getTag()) == insertedNodes.end()) {
// The new node was (re)inserted, so there is no need to create it.
if (insertedPaires.find(newChildPair.shadowView.tag) == insertedPaires.end()) {
// The new view was (re)inserted, so there is no need to create it.
continue;
}
createInstructions.push_back(
TreeMutationInstruction::Create(
newChildNode
createMutations.push_back(
ShadowViewMutation::CreateMutation(
newChildPair.shadowView
)
);
calculateMutationInstructions(
downwardInstructions,
newChildNode,
calculateShadowViewMutations(
downwardMutations,
newChildPair.shadowView,
{},
newChildNode->getChildren()
sliceChildShadowNodeViewPairs(newChildPair.shadowNode)
);
}
// All instructions in an optimal order:
instructions.insert(instructions.end(), destructionDownwardInstructions.begin(), destructionDownwardInstructions.end());
instructions.insert(instructions.end(), replaceInstructions.begin(), replaceInstructions.end());
instructions.insert(instructions.end(), removeInstructions.rbegin(), removeInstructions.rend());
instructions.insert(instructions.end(), createInstructions.begin(), createInstructions.end());
instructions.insert(instructions.end(), downwardInstructions.begin(), downwardInstructions.end());
instructions.insert(instructions.end(), insertInstructions.begin(), insertInstructions.end());
instructions.insert(instructions.end(), deleteInstructions.begin(), deleteInstructions.end());
// All mutations in an optimal order:
mutations.insert(mutations.end(), destructiveDownwardMutations.begin(), destructiveDownwardMutations.end());
mutations.insert(mutations.end(), updateMutations.begin(), updateMutations.end());
mutations.insert(mutations.end(), removeMutations.rbegin(), removeMutations.rend());
mutations.insert(mutations.end(), deleteMutations.begin(), deleteMutations.end());
mutations.insert(mutations.end(), createMutations.begin(), createMutations.end());
mutations.insert(mutations.end(), insertMutations.begin(), insertMutations.end());
mutations.insert(mutations.end(), downwardMutations.begin(), downwardMutations.end());
}
void calculateMutationInstructions(
TreeMutationInstructionList &instructions,
const SharedShadowNode &oldRootShadowNode,
const SharedShadowNode &newRootShadowNode
ShadowViewMutationList calculateShadowViewMutations(
const ShadowNode &oldRootShadowNode,
const ShadowNode &newRootShadowNode
) {
// Root shadow nodes must have same tag.
assert(oldRootShadowNode->getTag() == newRootShadowNode->getTag());
assert(oldRootShadowNode.getTag() == newRootShadowNode.getTag());
if (*oldRootShadowNode != *newRootShadowNode) {
instructions.push_back(
TreeMutationInstruction::Replace(
nullptr,
oldRootShadowNode,
newRootShadowNode,
ShadowViewMutationList mutations;
if (oldRootShadowNode != newRootShadowNode) {
mutations.push_back(
ShadowViewMutation::UpdateMutation(
ShadowView(),
ShadowView(oldRootShadowNode),
ShadowView(newRootShadowNode),
-1
)
);
}
calculateMutationInstructions(
instructions,
oldRootShadowNode,
oldRootShadowNode->getChildren(),
newRootShadowNode->getChildren()
calculateShadowViewMutations(
mutations,
ShadowView(oldRootShadowNode),
sliceChildShadowNodeViewPairs(oldRootShadowNode),
sliceChildShadowNodeViewPairs(newRootShadowNode)
);
return mutations;
}
} // namespace react

View File

@ -6,20 +6,19 @@
#pragma once
#include <fabric/core/ShadowNode.h>
#include <fabric/uimanager/TreeMutationInstruction.h>
#include <fabric/uimanager/ShadowViewMutation.h>
namespace facebook {
namespace react {
/*
* Calculates set of mutation instuctions which describe how the old
* ShadowNode tree can be transformed to the new ShadowNode tree.
* The set of instuctions might be and might not be optimal.
* Calculates a list of view mutations which describes how the old
* `ShadowTree` can be transformed to the new one.
* The list of mutations might be and might not be optimal.
*/
void calculateMutationInstructions(
TreeMutationInstructionList &instructions,
const SharedShadowNode &oldNode,
const SharedShadowNode &newNode
ShadowViewMutationList calculateShadowViewMutations(
const ShadowNode &oldRootShadowNode,
const ShadowNode &newRootShadowNode
);
} // namespace react

View File

@ -82,9 +82,9 @@ SchedulerDelegate *Scheduler::getDelegate() const {
#pragma mark - ShadowTreeDelegate
void Scheduler::shadowTreeDidCommit(const SharedShadowTree &shadowTree, const TreeMutationInstructionList &instructions) {
void Scheduler::shadowTreeDidCommit(const SharedShadowTree &shadowTree, const ShadowViewMutationList &mutations) {
if (delegate_) {
delegate_->schedulerDidComputeMutationInstructions(shadowTree->getRootTag(), instructions);
delegate_->schedulerDidFinishTransaction(shadowTree->getRootTag(), mutations);
}
}

View File

@ -57,7 +57,7 @@ public:
#pragma mark - ShadowTreeDelegate
void shadowTreeDidCommit(const SharedShadowTree &shadowTree, const TreeMutationInstructionList &instructions) override;
void shadowTreeDidCommit(const SharedShadowTree &shadowTree, const ShadowViewMutationList &mutations) override;
#pragma mark - Deprecated
@ -67,7 +67,6 @@ public:
std::shared_ptr<FabricUIManager> getUIManager_DO_NOT_USE();
private:
SchedulerDelegate *delegate_;
std::shared_ptr<FabricUIManager> uiManager_;
std::unordered_map<Tag, SharedShadowTree> shadowTreeRegistry_;

View File

@ -9,7 +9,7 @@
#include <fabric/core/ReactPrimitives.h>
#include <fabric/core/ShadowNode.h>
#include <fabric/uimanager/TreeMutationInstruction.h>
#include <fabric/uimanager/ShadowViewMutation.h>
namespace facebook {
namespace react {
@ -24,10 +24,10 @@ public:
/*
* Called right after Scheduler computed (and laid out) a new updated version
* of the tree and calculated a set of mutation instructions which are
* suffisient to construct a new one.
* of the tree and calculated a set of mutations which are suffisient
* to construct a new one.
*/
virtual void schedulerDidComputeMutationInstructions(Tag rootTag, const TreeMutationInstructionList &instructions) = 0;
virtual void schedulerDidFinishTransaction(Tag rootTag, const ShadowViewMutationList &mutations) = 0;
/*
* Called right after a new ShadowNode was created.

View File

@ -10,7 +10,7 @@
#include "ShadowTreeDelegate.h"
#include "Differentiator.h"
#include "TreeMutationInstruction.h"
#include "ShadowViewMutation.h"
namespace facebook {
namespace react {
@ -78,19 +78,16 @@ void ShadowTree::complete(UnsharedRootShadowNode newRootShadowNode) {
newRootShadowNode->sealRecursive();
TreeMutationInstructionList instructions = TreeMutationInstructionList();
calculateMutationInstructions(
instructions,
oldRootShadowNode,
newRootShadowNode
auto mutations = calculateShadowViewMutations(
*oldRootShadowNode,
*newRootShadowNode
);
if (commit(oldRootShadowNode, newRootShadowNode, instructions)) {
emitLayoutEvents(instructions);
if (commit(oldRootShadowNode, newRootShadowNode, mutations)) {
emitLayoutEvents(mutations);
if (delegate_) {
delegate_->shadowTreeDidCommit(shared_from_this(), instructions);
delegate_->shadowTreeDidCommit(shared_from_this(), mutations);
}
}
}
@ -98,7 +95,7 @@ void ShadowTree::complete(UnsharedRootShadowNode newRootShadowNode) {
bool ShadowTree::commit(
const SharedRootShadowNode &oldRootShadowNode,
const SharedRootShadowNode &newRootShadowNode,
const TreeMutationInstructionList &mutationInstructions
const ShadowViewMutationList &mutations
) {
std::lock_guard<std::mutex> lock(commitMutex_);
@ -108,69 +105,57 @@ bool ShadowTree::commit(
rootShadowNode_ = newRootShadowNode;
toggleEventEmitters(mutationInstructions);
toggleEventEmitters(mutations);
return true;
}
void ShadowTree::emitLayoutEvents(const TreeMutationInstructionList &instructions) {
for (const auto &instruction : instructions) {
const auto &type = instruction.getType();
// Only `Insertion` and `Replacement` instructions can affect layout metrics.
void ShadowTree::emitLayoutEvents(const ShadowViewMutationList &mutations) {
for (const auto &mutation : mutations) {
// Only `Insert` and `Update` mutations can affect layout metrics.
if (
type == TreeMutationInstruction::Insertion ||
type == TreeMutationInstruction::Replacement
mutation.type != ShadowViewMutation::Insert &&
mutation.type != ShadowViewMutation::Update
) {
const auto &newShadowNode = instruction.getNewChildNode();
const auto &eventEmitter = newShadowNode->getEventEmitter();
const auto &viewEventEmitter = std::dynamic_pointer_cast<const ViewEventEmitter>(eventEmitter);
// Checking if particular shadow node supports `onLayout` event (part of `ViewEventEmitter`).
if (viewEventEmitter) {
// Now we know that both (old and new) shadow nodes must be `LayoutableShadowNode` subclasses.
assert(std::dynamic_pointer_cast<const LayoutableShadowNode>(newShadowNode));
// Checking if the `onLayout` event was requested for the particular Shadow Node.
const auto &viewProps = std::dynamic_pointer_cast<const ViewProps>(newShadowNode->getProps());
if (viewProps && !viewProps->onLayout) {
continue;
}
// TODO(T29661055): Consider using `std::reinterpret_pointer_cast`.
const auto &newLayoutableShadowNode =
std::dynamic_pointer_cast<const LayoutableShadowNode>(newShadowNode);
// In case if we have `oldShadowNode`, we have to check that layout metrics have changed.
if (type == TreeMutationInstruction::Replacement) {
const auto &oldShadowNode = instruction.getOldChildNode();
assert(std::dynamic_pointer_cast<const LayoutableShadowNode>(oldShadowNode));
// TODO(T29661055): Consider using `std::reinterpret_pointer_cast`.
const auto &oldLayoutableShadowNode =
std::dynamic_pointer_cast<const LayoutableShadowNode>(oldShadowNode);
if (oldLayoutableShadowNode->getLayoutMetrics() == newLayoutableShadowNode->getLayoutMetrics()) {
continue;
}
}
viewEventEmitter->onLayout(newLayoutableShadowNode->getLayoutMetrics());
}
continue;
}
const auto viewEventEmitter = std::dynamic_pointer_cast<const ViewEventEmitter>(mutation.newChildShadowView.eventEmitter);
// Checking if particular shadow node supports `onLayout` event (part of `ViewEventEmitter`).
if (!viewEventEmitter) {
continue;
}
// Checking if the `onLayout` event was requested for the particular Shadow Node.
const auto viewProps = std::dynamic_pointer_cast<const ViewProps>(mutation.newChildShadowView.props);
if (viewProps && !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);
}
}
void ShadowTree::toggleEventEmitters(const TreeMutationInstructionList &instructions) {
void ShadowTree::toggleEventEmitters(const ShadowViewMutationList &mutations) {
std::lock_guard<std::recursive_mutex> lock(EventEmitter::DispatchMutex());
for (const auto &instruction : instructions) {
if (instruction.getType() == TreeMutationInstruction::Deletion) {
instruction.getOldChildNode()->getEventEmitter()->setEnabled(false);
for (const auto &mutation : mutations) {
if (mutation.type == ShadowViewMutation::Delete) {
mutation.oldChildShadowView.eventEmitter->setEnabled(false);
}
}
for (const auto &instruction : instructions) {
if (instruction.getType() == TreeMutationInstruction::Creation) {
instruction.getNewChildNode()->getEventEmitter()->setEnabled(true);
for (const auto &mutation : mutations) {
if (mutation.type == ShadowViewMutation::Create) {
mutation.newChildShadowView.eventEmitter->setEnabled(true);
}
}
}

View File

@ -13,6 +13,7 @@
#include <fabric/core/ReactPrimitives.h>
#include <fabric/core/ShadowNode.h>
#include <fabric/uimanager/ShadowTreeDelegate.h>
#include <fabric/uimanager/ShadowViewMutation.h>
namespace facebook {
namespace react {
@ -80,10 +81,10 @@ private:
bool commit(
const SharedRootShadowNode &oldRootShadowNode,
const SharedRootShadowNode &newRootShadowNode,
const TreeMutationInstructionList &mutationInstructions
const ShadowViewMutationList &mutations
);
void toggleEventEmitters(const TreeMutationInstructionList &instructions);
void emitLayoutEvents(const TreeMutationInstructionList &instructions);
void toggleEventEmitters(const ShadowViewMutationList &mutations);
void emitLayoutEvents(const ShadowViewMutationList &mutations);
const Tag rootTag_;
SharedRootShadowNode rootShadowNode_;

View File

@ -5,7 +5,7 @@
#pragma once
#include <fabric/uimanager/TreeMutationInstruction.h>
#include <fabric/uimanager/ShadowViewMutation.h>
namespace facebook {
namespace react {
@ -21,7 +21,7 @@ public:
/*
* Called right after Shadow Tree commit a new state of the the tree.
*/
virtual void shadowTreeDidCommit(const std::shared_ptr<ShadowTree> &shadowTree, const TreeMutationInstructionList &instructions) = 0;
virtual void shadowTreeDidCommit(const std::shared_ptr<ShadowTree> &shadowTree, const ShadowViewMutationList &mutations) = 0;
};
} // namespace react

View File

@ -15,7 +15,7 @@ namespace facebook {
namespace react {
/*
* Describes a single native view tree mutation instruction which may contain
* Describes a single native view tree mutation which may contain
* pointers to an old shadow view, a new shadow view, a parent shadow view and
* final index of inserted or updated view.
* Use static methods to instantiate mutations of different types.
@ -25,21 +25,21 @@ struct ShadowViewMutation final {
#pragma mark - Designated Initializers
/*
* Creates and returns an `Create` mutation instruction.
* Creates and returns an `Create` mutation.
*/
static ShadowViewMutation CreateMutation(
ShadowView shadowView
);
/*
* Creates and returns an `Delete` mutation instruction.
* Creates and returns an `Delete` mutation.
*/
static ShadowViewMutation DeleteMutation(
ShadowView shadowView
);
/*
* Creates and returns an `Insert` mutation instruction.
* Creates and returns an `Insert` mutation.
*/
static ShadowViewMutation InsertMutation(
ShadowView parentShadowView,
@ -48,16 +48,16 @@ struct ShadowViewMutation final {
);
/*
* Creates and returns a `Remove` mutation instruction.
* Creates and returns a `Remove` mutation.
*/
static ShadowViewMutation RemoveMutation(
ShadowView parentShadowView,
ShadowView childShadowView,
int index
);
/*
* Creates and returns an `Update` mutation instruction.
* Creates and returns an `Update` mutation.
*/
static ShadowViewMutation UpdateMutation(
ShadowView parentShadowView,

View File

@ -1,203 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "TreeMutationInstruction.h"
#include <fabric/debug/DebugStringConvertibleItem.h>
namespace facebook {
namespace react {
const TreeMutationInstruction TreeMutationInstruction::Create(
SharedShadowNode node
) {
assert(node);
return TreeMutationInstruction(
Creation,
nullptr,
nullptr,
node,
-1
);
}
const TreeMutationInstruction TreeMutationInstruction::Delete(
SharedShadowNode node
) {
assert(node);
return TreeMutationInstruction(
Deletion,
nullptr,
node,
nullptr,
-1
);
}
const TreeMutationInstruction TreeMutationInstruction::Insert(
SharedShadowNode parentNode,
SharedShadowNode childNode,
int index
) {
assert(parentNode);
assert(childNode);
assert(index != -1);
return TreeMutationInstruction(
Insertion,
parentNode,
nullptr,
childNode,
index
);
}
const TreeMutationInstruction TreeMutationInstruction::Remove(
SharedShadowNode parentNode,
SharedShadowNode childNode,
int index
) {
assert(parentNode);
assert(childNode);
assert(index != -1);
return TreeMutationInstruction(
Removal,
parentNode,
childNode,
nullptr,
index
);
}
const TreeMutationInstruction TreeMutationInstruction::Replace(
SharedShadowNode parentNode,
SharedShadowNode oldChildNode,
SharedShadowNode newChildNode,
int index
) {
assert(oldChildNode);
assert(newChildNode);
return TreeMutationInstruction(
Replacement,
parentNode,
oldChildNode,
newChildNode,
index
);
}
TreeMutationInstruction::TreeMutationInstruction(
Type type,
SharedShadowNode parentNode,
SharedShadowNode oldChildNode,
SharedShadowNode newChildNode,
int index
):
type_(type),
parentNode_(parentNode),
oldChildNode_(oldChildNode),
newChildNode_(newChildNode),
index_(index) {};
#pragma mark - Getters
TreeMutationInstruction::Type TreeMutationInstruction::getType() const {
return type_;
}
SharedShadowNode TreeMutationInstruction::getParentNode() const {
assert(parentNode_);
return parentNode_;
}
SharedShadowNode TreeMutationInstruction::getOldChildNode() const {
assert(oldChildNode_);
return oldChildNode_;
}
SharedShadowNode TreeMutationInstruction::getNewChildNode() const {
assert(newChildNode_);
return newChildNode_;
}
int TreeMutationInstruction::getIndex() const {
assert(index_ != -1);
return index_;
}
#pragma mark - DebugStringConvertible
std::string TreeMutationInstruction::getDebugName() const {
switch (type_) {
case Creation:
return "Create";
case Deletion:
return "Delete";
case Insertion:
return "Insert";
case Removal:
return "Remove";
case Replacement:
return "Replace";
}
};
std::string TreeMutationInstruction::getDebugValue() const {
switch (type_) {
case Creation:
return "[*" + folly::to<std::string>(newChildNode_->getTag()) + "]";
case Deletion:
return "[~" + folly::to<std::string>(oldChildNode_->getTag()) + "]";
case Insertion:
return "[" + folly::to<std::string>(newChildNode_->getTag()) + "->" + folly::to<std::string>(parentNode_->getTag()) + "]";
case Removal:
return "[" + folly::to<std::string>(oldChildNode_->getTag()) + "<~" + folly::to<std::string>(parentNode_->getTag()) + "]";
case Replacement:
return "[=" + folly::to<std::string>(oldChildNode_->getTag()) + "]";
}
};
SharedDebugStringConvertibleList TreeMutationInstruction::getDebugProps() const {
DebugStringConvertibleOptions options = {.maximumDepth = 1, .format = false};
switch (type_) {
case Creation:
return SharedDebugStringConvertibleList {
std::make_shared<DebugStringConvertibleItem>("node", newChildNode_->getDebugDescription(options)),
};
case Deletion:
return SharedDebugStringConvertibleList {
std::make_shared<DebugStringConvertibleItem>("node", oldChildNode_->getDebugDescription(options)),
};
case Insertion:
return SharedDebugStringConvertibleList {
std::make_shared<DebugStringConvertibleItem>("parentNode", parentNode_->getDebugDescription(options)),
std::make_shared<DebugStringConvertibleItem>("childNode", newChildNode_->getDebugDescription(options)),
std::make_shared<DebugStringConvertibleItem>("index", folly::to<std::string>(index_))
};
case Removal:
return SharedDebugStringConvertibleList {
std::make_shared<DebugStringConvertibleItem>("parentNode", parentNode_->getDebugDescription(options)),
std::make_shared<DebugStringConvertibleItem>("childNode", oldChildNode_->getDebugDescription(options)),
std::make_shared<DebugStringConvertibleItem>("index", folly::to<std::string>(index_))
};
case Replacement:
return SharedDebugStringConvertibleList {
std::make_shared<DebugStringConvertibleItem>("parentNode", parentNode_ ? parentNode_->getDebugDescription(options) : "nullptr"),
std::make_shared<DebugStringConvertibleItem>("oldChildNode", oldChildNode_->getDebugDescription(options)),
std::make_shared<DebugStringConvertibleItem>("newChildNode", newChildNode_->getDebugDescription(options)),
std::make_shared<DebugStringConvertibleItem>("index", folly::to<std::string>(index_))
};
}
}
} // namespace react
} // namespace facebook

View File

@ -1,119 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* 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 <vector>
#include <fabric/core/ShadowNode.h>
#include <fabric/debug/DebugStringConvertible.h>
namespace facebook {
namespace react {
class TreeMutationInstruction;
using TreeMutationInstructionList = std::vector<TreeMutationInstruction>;
/*
* Describes single native views tree mutation instruction which may contain
* pointers to an old shadow node, a new shadow node, a parent shadow node and
* final index of inserted or updated node.
* The relationship between native view instances and shadow node instances is
* defined by `tag` value.
* Use static methods to instantiate mutation instructions of different types.
*/
class TreeMutationInstruction:
public DebugStringConvertible {
public:
#pragma mark - Designated Initializers
/*
* Creates and returns an *Creation* instruction.
*/
static const TreeMutationInstruction Create(
SharedShadowNode node
);
/*
* Creates and returns an *Deletion* instruction.
*/
static const TreeMutationInstruction Delete(
SharedShadowNode node
);
/*
* Creates and returns an *Insertion* instruction.
*/
static const TreeMutationInstruction Insert(
SharedShadowNode parentNode,
SharedShadowNode childNode,
int index
);
/*
* Creates and returns a *Removal* instruction.
*/
static const TreeMutationInstruction Remove(
SharedShadowNode parentNode,
SharedShadowNode childNode,
int index
);
/*
* Creates and returns an *Replacement* instruction.
*/
static const TreeMutationInstruction Replace(
SharedShadowNode parentNode,
SharedShadowNode oldChildNode,
SharedShadowNode newChildNode,
int index
);
#pragma mark - Type
enum Type {
Creation,
Deletion,
Insertion,
Removal,
Replacement
};
#pragma mark - Getters
Type getType() const;
SharedShadowNode getParentNode() const;
SharedShadowNode getOldChildNode() const;
SharedShadowNode getNewChildNode() const;
int getIndex() const;
#pragma mark - DebugStringConvertible
std::string getDebugName() const override;
std::string getDebugValue() const override;
SharedDebugStringConvertibleList getDebugProps() const override;
private:
TreeMutationInstruction(
Type type,
SharedShadowNode parentNode,
SharedShadowNode oldChildNode,
SharedShadowNode newChildNode,
int index
);
Type type_ {Creation};
SharedShadowNode parentNode_ {nullptr};
SharedShadowNode oldChildNode_ {nullptr};
SharedShadowNode newChildNode_ {nullptr};
int index_ {-1};
};
} // namespace react
} // namespace facebook