This commit is contained in:
Yamozha
2021-04-02 02:24:13 +03:00
parent c23950b545
commit 7256d79e2c
31493 changed files with 3036630 additions and 0 deletions

View File

@ -0,0 +1,85 @@
load("@fbsource//tools/build_defs/apple:flag_defs.bzl", "get_preprocessor_flags_for_build_mode")
load(
"//tools/build_defs/oss:rn_defs.bzl",
"ANDROID",
"APPLE",
"CXX",
"fb_xplat_cxx_test",
"get_apple_compiler_flags",
"get_apple_inspector_flags",
"react_native_xplat_target",
"rn_xplat_cxx_library",
"subdir_glob",
)
APPLE_COMPILER_FLAGS = get_apple_compiler_flags()
rn_xplat_cxx_library(
name = "mounting",
srcs = glob(
["**/*.cpp"],
exclude = glob(["tests/**/*.cpp"]),
),
headers = glob(
["**/*.h"],
exclude = glob(["tests/**/*.h"]),
),
header_namespace = "",
exported_headers = subdir_glob(
[
("", "*.h"),
("stubs", "*.h"),
],
prefix = "react/mounting",
),
compiler_flags = [
"-fexceptions",
"-frtti",
"-std=c++14",
"-Wall",
],
fbobjc_compiler_flags = APPLE_COMPILER_FLAGS,
fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"],
fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(),
force_static = True,
macosx_tests_override = [],
platforms = (ANDROID, APPLE, CXX),
preprocessor_flags = [
"-DLOG_TAG=\"ReactNative\"",
"-DWITH_FBSYSTRACE=1",
],
tests = [":tests"],
visibility = ["PUBLIC"],
deps = [
"//xplat/fbsystrace:fbsystrace",
"//xplat/folly:headers_only",
"//xplat/folly:memory",
"//xplat/folly:molly",
"//xplat/third-party/glog:glog",
react_native_xplat_target("better:better"),
react_native_xplat_target("fabric/components/root:root"),
react_native_xplat_target("fabric/components/view:view"),
react_native_xplat_target("fabric/core:core"),
react_native_xplat_target("fabric/debug:debug"),
react_native_xplat_target("utils:utils"),
],
)
fb_xplat_cxx_test(
name = "tests",
srcs = glob(["tests/**/*.cpp"]),
headers = glob(["tests/**/*.h"]),
compiler_flags = [
"-fexceptions",
"-frtti",
"-std=c++14",
"-Wall",
],
contacts = ["oncall+react_native@xmail.facebook.com"],
platforms = (ANDROID, APPLE, CXX),
deps = [
":mounting",
"//xplat/folly:molly",
"//xplat/third-party/gmock:gtest",
],
)

View File

@ -0,0 +1,739 @@
/*
* 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 "Differentiator.h"
#include <better/map.h>
#include <better/small_vector.h>
#include <react/core/LayoutableShadowNode.h>
#include <react/debug/SystraceSection.h>
#include <algorithm>
#include "ShadowView.h"
namespace facebook {
namespace react {
/*
* Extremely simple and naive implementation of a map.
* The map is simple but it's optimized for particular constraints that we have
* here.
*
* A regular map implementation (e.g. `std::unordered_map`) has some basic
* performance guarantees like constant average insertion and lookup complexity.
* This is nice, but it's *average* complexity measured on a non-trivial amount
* of data. The regular map is a very complex data structure that using hashing,
* buckets, multiple comprising operations, multiple allocations and so on.
*
* In our particular case, we need a map for `int` to `void *` with a dozen
* values. In these conditions, nothing can beat a naive implementation using a
* stack-allocated vector. And this implementation is exactly this: no
* allocation, no hashing, no complex branching, no buckets, no iterators, no
* rehashing, no other guarantees. It's crazy limited, unsafe, and performant on
* a trivial amount of data.
*
* Besides that, we also need to optimize for insertion performance (the case
* where a bunch of views appears on the screen first time); in this
* implementation, this is as performant as vector `push_back`.
*/
template <typename KeyT, typename ValueT, int DefaultSize = 16>
class TinyMap final {
public:
using Pair = std::pair<KeyT, ValueT>;
using Iterator = Pair *;
/**
* This must strictly only be called from outside of this class.
*/
inline Iterator begin() {
// Force a clean so that iterating over this TinyMap doesn't iterate over
// erased elements. If all elements erased are at the front of the vector,
// then we don't need to clean.
cleanVector(erasedAtFront_ != numErased_);
return begin_();
}
inline Iterator end() {
// `back()` asserts on the vector being non-empty
if (vector_.size() == 0 || numErased_ == vector_.size()) {
return nullptr;
}
return &vector_.back() + 1;
}
inline Iterator find(KeyT key) {
cleanVector();
assert(key != 0);
for (auto it = begin_() + erasedAtFront_; it != end(); it++) {
if (it->first == key) {
return it;
}
}
return end();
}
inline void insert(Pair pair) {
assert(pair.first != 0);
vector_.push_back(pair);
}
inline void erase(Iterator iterator) {
numErased_++;
// Invalidate tag.
iterator->first = 0;
if (iterator == begin_() + erasedAtFront_) {
erasedAtFront_++;
}
}
private:
/**
* Same as begin() but doesn't call cleanVector at the beginning.
*/
inline Iterator begin_() {
// `front()` asserts on the vector being non-empty
if (vector_.size() == 0 || vector_.size() == numErased_) {
return nullptr;
}
return &vector_.front();
}
/**
* Remove erased elements from internal vector.
* We only modify the vector if erased elements are at least half of the
* vector.
*/
inline void cleanVector(bool forceClean = false) {
if ((numErased_ < (vector_.size() / 2) && !forceClean) ||
vector_.size() == 0 || numErased_ == 0 ||
numErased_ == erasedAtFront_) {
return;
}
if (numErased_ == vector_.size()) {
vector_.clear();
} else {
vector_.erase(
std::remove_if(
vector_.begin(),
vector_.end(),
[](auto const &item) { return item.first == 0; }),
vector_.end());
}
numErased_ = 0;
erasedAtFront_ = 0;
}
better::small_vector<Pair, DefaultSize> vector_;
int numErased_{0};
int erasedAtFront_{0};
};
/*
* Sorting comparator for `reorderInPlaceIfNeeded`.
*/
static bool shouldFirstPairComesBeforeSecondOne(
ShadowViewNodePair const &lhs,
ShadowViewNodePair const &rhs) noexcept {
return lhs.shadowNode->getOrderIndex() < rhs.shadowNode->getOrderIndex();
}
/*
* Reorders pairs in-place based on `orderIndex` using a stable sort algorithm.
*/
static void reorderInPlaceIfNeeded(ShadowViewNodePair::List &pairs) noexcept {
if (pairs.size() < 2) {
return;
}
auto isReorderNeeded = false;
for (auto const &pair : pairs) {
if (pair.shadowNode->getOrderIndex() != 0) {
isReorderNeeded = true;
break;
}
}
if (!isReorderNeeded) {
return;
}
std::stable_sort(
pairs.begin(), pairs.end(), &shouldFirstPairComesBeforeSecondOne);
}
static void sliceChildShadowNodeViewPairsRecursively(
ShadowViewNodePair::List &pairList,
Point layoutOffset,
ShadowNode const &shadowNode) {
for (auto const &sharedChildShadowNode : shadowNode.getChildren()) {
auto &childShadowNode = *sharedChildShadowNode;
auto shadowView = ShadowView(childShadowNode);
if (shadowView.layoutMetrics != EmptyLayoutMetrics) {
shadowView.layoutMetrics.frame.origin += layoutOffset;
}
if (childShadowNode.getTraits().check(
ShadowNodeTraits::Trait::FormsStackingContext)) {
pairList.push_back({shadowView, &childShadowNode});
} else {
if (childShadowNode.getTraits().check(
ShadowNodeTraits::Trait::FormsView)) {
pairList.push_back({shadowView, &childShadowNode});
}
sliceChildShadowNodeViewPairsRecursively(
pairList, shadowView.layoutMetrics.frame.origin, childShadowNode);
}
}
}
ShadowViewNodePair::List sliceChildShadowNodeViewPairs(
ShadowNode const &shadowNode) {
auto pairList = ShadowViewNodePair::List{};
if (!shadowNode.getTraits().check(
ShadowNodeTraits::Trait::FormsStackingContext) &&
shadowNode.getTraits().check(ShadowNodeTraits::Trait::FormsView)) {
return pairList;
}
sliceChildShadowNodeViewPairsRecursively(pairList, {0, 0}, shadowNode);
return pairList;
}
/*
* Before we start to diff, let's make sure all our core data structures are in
* good shape to deliver the best performance.
*/
static_assert(
std::is_move_constructible<ShadowViewMutation>::value,
"`ShadowViewMutation` must be `move constructible`.");
static_assert(
std::is_move_constructible<ShadowView>::value,
"`ShadowView` must be `move constructible`.");
static_assert(
std::is_move_constructible<ShadowViewNodePair>::value,
"`ShadowViewNodePair` must be `move constructible`.");
static_assert(
std::is_move_constructible<ShadowViewNodePair::List>::value,
"`ShadowViewNodePair::List` must be `move constructible`.");
static_assert(
std::is_move_assignable<ShadowViewMutation>::value,
"`ShadowViewMutation` must be `move assignable`.");
static_assert(
std::is_move_assignable<ShadowView>::value,
"`ShadowView` must be `move assignable`.");
static_assert(
std::is_move_assignable<ShadowViewNodePair>::value,
"`ShadowViewNodePair` must be `move assignable`.");
static_assert(
std::is_move_assignable<ShadowViewNodePair::List>::value,
"`ShadowViewNodePair::List` must be `move assignable`.");
static void calculateShadowViewMutationsClassic(
ShadowViewMutation::List &mutations,
ShadowView const &parentShadowView,
ShadowViewNodePair::List &&oldChildPairs,
ShadowViewNodePair::List &&newChildPairs) {
// This version of the algorithm is optimized for simplicity,
// not for performance or optimal result.
if (oldChildPairs.size() == 0 && newChildPairs.size() == 0) {
return;
}
// Sorting pairs based on `orderIndex` if needed.
reorderInPlaceIfNeeded(oldChildPairs);
reorderInPlaceIfNeeded(newChildPairs);
auto index = int{0};
// Maps inserted node tags to pointers to them in `newChildPairs`.
auto insertedPairs = TinyMap<Tag, ShadowViewNodePair const *>{};
// Lists of mutations
auto createMutations = ShadowViewMutation::List{};
auto deleteMutations = ShadowViewMutation::List{};
auto insertMutations = ShadowViewMutation::List{};
auto removeMutations = ShadowViewMutation::List{};
auto updateMutations = ShadowViewMutation::List{};
auto downwardMutations = ShadowViewMutation::List{};
auto destructiveDownwardMutations = ShadowViewMutation::List{};
// Stage 1: Collecting `Update` mutations
for (index = 0; index < oldChildPairs.size() && index < newChildPairs.size();
index++) {
auto const &oldChildPair = oldChildPairs[index];
auto const &newChildPair = newChildPairs[index];
if (oldChildPair.shadowView.tag != newChildPair.shadowView.tag) {
// Totally different nodes, updating is impossible.
break;
}
if (oldChildPair.shadowView != newChildPair.shadowView) {
updateMutations.push_back(ShadowViewMutation::UpdateMutation(
parentShadowView,
oldChildPair.shadowView,
newChildPair.shadowView,
index));
}
auto oldGrandChildPairs =
sliceChildShadowNodeViewPairs(*oldChildPair.shadowNode);
auto newGrandChildPairs =
sliceChildShadowNodeViewPairs(*newChildPair.shadowNode);
calculateShadowViewMutationsClassic(
*(newGrandChildPairs.size() ? &downwardMutations
: &destructiveDownwardMutations),
oldChildPair.shadowView,
std::move(oldGrandChildPairs),
std::move(newGrandChildPairs));
}
int lastIndexAfterFirstStage = index;
// Stage 2: Collecting `Insert` mutations
for (; index < newChildPairs.size(); index++) {
auto const &newChildPair = newChildPairs[index];
insertMutations.push_back(ShadowViewMutation::InsertMutation(
parentShadowView, newChildPair.shadowView, index));
insertedPairs.insert({newChildPair.shadowView.tag, &newChildPair});
}
// Stage 3: Collecting `Delete` and `Remove` mutations
for (index = lastIndexAfterFirstStage; index < oldChildPairs.size();
index++) {
auto const &oldChildPair = oldChildPairs[index];
// Even if the old view was (re)inserted, we have to generate `remove`
// mutation.
removeMutations.push_back(ShadowViewMutation::RemoveMutation(
parentShadowView, oldChildPair.shadowView, index));
auto const it = insertedPairs.find(oldChildPair.shadowView.tag);
if (it == insertedPairs.end()) {
// The old view was *not* (re)inserted.
// We have to generate `delete` mutation and apply the algorithm
// recursively.
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 view.
calculateShadowViewMutationsClassic(
destructiveDownwardMutations,
oldChildPair.shadowView,
sliceChildShadowNodeViewPairs(*oldChildPair.shadowNode),
{});
} else {
// The old view *was* (re)inserted.
// We have to call the algorithm recursively if the inserted view
// is *not* the same as removed one.
auto const &newChildPair = *it->second;
if (newChildPair != oldChildPair) {
auto oldGrandChildPairs =
sliceChildShadowNodeViewPairs(*oldChildPair.shadowNode);
auto newGrandChildPairs =
sliceChildShadowNodeViewPairs(*newChildPair.shadowNode);
calculateShadowViewMutationsClassic(
*(newGrandChildPairs.size() ? &downwardMutations
: &destructiveDownwardMutations),
newChildPair.shadowView,
std::move(oldGrandChildPairs),
std::move(newGrandChildPairs));
}
// In any case we have to remove the view from `insertedPairs` as
// indication that the view was actually removed (which means that
// the view existed before), hence we don't have to generate
// `create` mutation.
insertedPairs.erase(it);
}
}
// Stage 4: Collecting `Create` mutations
for (index = lastIndexAfterFirstStage; index < newChildPairs.size();
index++) {
auto const &newChildPair = newChildPairs[index];
if (insertedPairs.find(newChildPair.shadowView.tag) ==
insertedPairs.end()) {
// The new view was (re)inserted, so there is no need to create it.
continue;
}
createMutations.push_back(
ShadowViewMutation::CreateMutation(newChildPair.shadowView));
calculateShadowViewMutationsClassic(
downwardMutations,
newChildPair.shadowView,
{},
sliceChildShadowNodeViewPairs(*newChildPair.shadowNode));
}
// All mutations in an optimal order:
std::move(
destructiveDownwardMutations.begin(),
destructiveDownwardMutations.end(),
std::back_inserter(mutations));
std::move(
updateMutations.begin(),
updateMutations.end(),
std::back_inserter(mutations));
std::move(
removeMutations.rbegin(),
removeMutations.rend(),
std::back_inserter(mutations));
std::move(
deleteMutations.begin(),
deleteMutations.end(),
std::back_inserter(mutations));
std::move(
createMutations.begin(),
createMutations.end(),
std::back_inserter(mutations));
std::move(
downwardMutations.begin(),
downwardMutations.end(),
std::back_inserter(mutations));
std::move(
insertMutations.begin(),
insertMutations.end(),
std::back_inserter(mutations));
}
static void calculateShadowViewMutationsOptimizedMoves(
ShadowViewMutation::List &mutations,
ShadowView const &parentShadowView,
ShadowViewNodePair::List &&oldChildPairs,
ShadowViewNodePair::List &&newChildPairs) {
if (oldChildPairs.size() == 0 && newChildPairs.size() == 0) {
return;
}
// Sorting pairs based on `orderIndex` if needed.
reorderInPlaceIfNeeded(oldChildPairs);
reorderInPlaceIfNeeded(newChildPairs);
auto index = int{0};
// Lists of mutations
auto createMutations = ShadowViewMutation::List{};
auto deleteMutations = ShadowViewMutation::List{};
auto insertMutations = ShadowViewMutation::List{};
auto removeMutations = ShadowViewMutation::List{};
auto updateMutations = ShadowViewMutation::List{};
auto downwardMutations = ShadowViewMutation::List{};
auto destructiveDownwardMutations = ShadowViewMutation::List{};
// Stage 1: Collecting `Update` mutations
for (index = 0; index < oldChildPairs.size() && index < newChildPairs.size();
index++) {
auto const &oldChildPair = oldChildPairs[index];
auto const &newChildPair = newChildPairs[index];
if (oldChildPair.shadowView.tag != newChildPair.shadowView.tag) {
// Totally different nodes, updating is impossible.
break;
}
if (oldChildPair.shadowView != newChildPair.shadowView) {
updateMutations.push_back(ShadowViewMutation::UpdateMutation(
parentShadowView,
oldChildPair.shadowView,
newChildPair.shadowView,
index));
}
auto oldGrandChildPairs =
sliceChildShadowNodeViewPairs(*oldChildPair.shadowNode);
auto newGrandChildPairs =
sliceChildShadowNodeViewPairs(*newChildPair.shadowNode);
calculateShadowViewMutationsOptimizedMoves(
*(newGrandChildPairs.size() ? &downwardMutations
: &destructiveDownwardMutations),
oldChildPair.shadowView,
std::move(oldGrandChildPairs),
std::move(newGrandChildPairs));
}
int lastIndexAfterFirstStage = index;
if (index == newChildPairs.size()) {
// We've reached the end of the new children. We can delete+remove the
// rest.
for (; index < oldChildPairs.size(); index++) {
auto const &oldChildPair = oldChildPairs[index];
deleteMutations.push_back(
ShadowViewMutation::DeleteMutation(oldChildPair.shadowView));
removeMutations.push_back(ShadowViewMutation::RemoveMutation(
parentShadowView, oldChildPair.shadowView, index));
// We also have to call the algorithm recursively to clean up the entire
// subtree starting from the removed view.
calculateShadowViewMutationsOptimizedMoves(
destructiveDownwardMutations,
oldChildPair.shadowView,
sliceChildShadowNodeViewPairs(*oldChildPair.shadowNode),
{});
}
} else if (index == oldChildPairs.size()) {
// If we don't have any more existing children we can choose a fast path
// since the rest will all be create+insert.
for (; index < newChildPairs.size(); index++) {
auto const &newChildPair = newChildPairs[index];
insertMutations.push_back(ShadowViewMutation::InsertMutation(
parentShadowView, newChildPair.shadowView, index));
createMutations.push_back(
ShadowViewMutation::CreateMutation(newChildPair.shadowView));
calculateShadowViewMutationsOptimizedMoves(
downwardMutations,
newChildPair.shadowView,
{},
sliceChildShadowNodeViewPairs(*newChildPair.shadowNode));
}
} else {
// Collect map of tags in the new list
// In the future it would be nice to use TinyMap for newInsertedPairs, but
// it's challenging to build an iterator that will work for our use-case
// here.
auto newRemainingPairs = TinyMap<Tag, ShadowViewNodePair const *>{};
auto newInsertedPairs = TinyMap<Tag, ShadowViewNodePair const *>{};
for (; index < newChildPairs.size(); index++) {
auto const &newChildPair = newChildPairs[index];
newRemainingPairs.insert({newChildPair.shadowView.tag, &newChildPair});
}
// Walk through both lists at the same time
// We will perform updates, create+insert, remove+delete, remove+insert
// (move) here.
int oldIndex = lastIndexAfterFirstStage,
newIndex = lastIndexAfterFirstStage, newSize = newChildPairs.size(),
oldSize = oldChildPairs.size();
while (newIndex < newSize || oldIndex < oldSize) {
bool haveNewPair = newIndex < newSize;
bool haveOldPair = oldIndex < oldSize;
// Advance both pointers if pointing to the same element
if (haveNewPair && haveOldPair) {
auto const &newChildPair = newChildPairs[newIndex];
auto const &oldChildPair = oldChildPairs[oldIndex];
int newTag = newChildPair.shadowView.tag;
int oldTag = oldChildPair.shadowView.tag;
if (newTag == oldTag) {
// Generate Update instructions
if (oldChildPair.shadowView != newChildPair.shadowView) {
updateMutations.push_back(ShadowViewMutation::UpdateMutation(
parentShadowView,
oldChildPair.shadowView,
newChildPair.shadowView,
index));
}
// Remove from newRemainingPairs
auto newRemainingPairIt = newRemainingPairs.find(oldTag);
if (newRemainingPairIt != newRemainingPairs.end()) {
newRemainingPairs.erase(newRemainingPairIt);
}
// Update subtrees
auto oldGrandChildPairs =
sliceChildShadowNodeViewPairs(*oldChildPair.shadowNode);
auto newGrandChildPairs =
sliceChildShadowNodeViewPairs(*newChildPair.shadowNode);
calculateShadowViewMutationsOptimizedMoves(
*(newGrandChildPairs.size() ? &downwardMutations
: &destructiveDownwardMutations),
oldChildPair.shadowView,
std::move(oldGrandChildPairs),
std::move(newGrandChildPairs));
newIndex++;
oldIndex++;
continue;
}
}
if (haveOldPair) {
auto const &oldChildPair = oldChildPairs[oldIndex];
int oldTag = oldChildPair.shadowView.tag;
// Was oldTag already inserted? This indicates a reordering, not just
// a move. The new node has already been inserted, we just need to
// remove the node from its old position now.
auto const insertedIt = newInsertedPairs.find(oldTag);
if (insertedIt != newInsertedPairs.end()) {
removeMutations.push_back(ShadowViewMutation::RemoveMutation(
parentShadowView, oldChildPair.shadowView, oldIndex));
// Generate update instruction since we have an iterator ref to the
// new node
auto const &newChildPair = *insertedIt->second;
if (oldChildPair.shadowView != newChildPair.shadowView) {
updateMutations.push_back(ShadowViewMutation::UpdateMutation(
parentShadowView,
oldChildPair.shadowView,
newChildPair.shadowView,
index));
}
// Update subtrees
auto oldGrandChildPairs =
sliceChildShadowNodeViewPairs(*oldChildPair.shadowNode);
auto newGrandChildPairs =
sliceChildShadowNodeViewPairs(*newChildPair.shadowNode);
calculateShadowViewMutationsOptimizedMoves(
*(newGrandChildPairs.size() ? &downwardMutations
: &destructiveDownwardMutations),
oldChildPair.shadowView,
std::move(oldGrandChildPairs),
std::move(newGrandChildPairs));
newInsertedPairs.erase(insertedIt);
oldIndex++;
continue;
}
// Should we generate a delete+remove instruction for the old node?
// If there's an old node and it's not found in the "new" list, we
// generate remove+delete for this node and its subtree.
auto const newIt = newRemainingPairs.find(oldTag);
if (newIt == newRemainingPairs.end()) {
removeMutations.push_back(ShadowViewMutation::RemoveMutation(
parentShadowView, oldChildPair.shadowView, oldIndex));
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 view.
calculateShadowViewMutationsOptimizedMoves(
destructiveDownwardMutations,
oldChildPair.shadowView,
sliceChildShadowNodeViewPairs(*oldChildPair.shadowNode),
{});
oldIndex++;
continue;
}
}
// At this point, oldTag is -1 or is in the new list, and hasn't been
// inserted or matched yet We're not sure yet if the new node is in the
// old list - generate an insert instruction for the new node.
auto const &newChildPair = newChildPairs[newIndex];
insertMutations.push_back(ShadowViewMutation::InsertMutation(
parentShadowView, newChildPair.shadowView, newIndex));
newInsertedPairs.insert({newChildPair.shadowView.tag, &newChildPair});
newIndex++;
}
// Final step: generate Create instructions for new nodes
for (auto it = newInsertedPairs.begin(); it != newInsertedPairs.end();
it++) {
auto const &newChildPair = *it->second;
createMutations.push_back(
ShadowViewMutation::CreateMutation(newChildPair.shadowView));
calculateShadowViewMutationsOptimizedMoves(
downwardMutations,
newChildPair.shadowView,
{},
sliceChildShadowNodeViewPairs(*newChildPair.shadowNode));
}
}
// All mutations in an optimal order:
std::move(
destructiveDownwardMutations.begin(),
destructiveDownwardMutations.end(),
std::back_inserter(mutations));
std::move(
updateMutations.begin(),
updateMutations.end(),
std::back_inserter(mutations));
std::move(
removeMutations.rbegin(),
removeMutations.rend(),
std::back_inserter(mutations));
std::move(
deleteMutations.begin(),
deleteMutations.end(),
std::back_inserter(mutations));
std::move(
createMutations.begin(),
createMutations.end(),
std::back_inserter(mutations));
std::move(
downwardMutations.begin(),
downwardMutations.end(),
std::back_inserter(mutations));
std::move(
insertMutations.begin(),
insertMutations.end(),
std::back_inserter(mutations));
}
ShadowViewMutation::List calculateShadowViewMutations(
DifferentiatorMode differentiatorMode,
ShadowNode const &oldRootShadowNode,
ShadowNode const &newRootShadowNode) {
SystraceSection s("calculateShadowViewMutations");
// Root shadow nodes must be belong the same family.
assert(ShadowNode::sameFamily(oldRootShadowNode, newRootShadowNode));
auto mutations = ShadowViewMutation::List{};
mutations.reserve(256);
auto oldRootShadowView = ShadowView(oldRootShadowNode);
auto newRootShadowView = ShadowView(newRootShadowNode);
if (oldRootShadowView != newRootShadowView) {
mutations.push_back(ShadowViewMutation::UpdateMutation(
ShadowView(), oldRootShadowView, newRootShadowView, -1));
}
if (differentiatorMode == DifferentiatorMode::Classic) {
calculateShadowViewMutationsClassic(
mutations,
ShadowView(oldRootShadowNode),
sliceChildShadowNodeViewPairs(oldRootShadowNode),
sliceChildShadowNodeViewPairs(newRootShadowNode));
} else {
calculateShadowViewMutationsOptimizedMoves(
mutations,
ShadowView(oldRootShadowNode),
sliceChildShadowNodeViewPairs(oldRootShadowNode),
sliceChildShadowNodeViewPairs(newRootShadowNode));
}
return mutations;
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,36 @@
/*
* 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 <react/core/ShadowNode.h>
#include <react/mounting/ShadowViewMutation.h>
namespace facebook {
namespace react {
enum class DifferentiatorMode { Classic, OptimizedMoves };
/*
* 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.
*/
ShadowViewMutationList calculateShadowViewMutations(
DifferentiatorMode differentiatorMode,
ShadowNode const &oldRootShadowNode,
ShadowNode const &newRootShadowNode);
/*
* Generates a list of `ShadowViewNodePair`s that represents a layer of a
* flattened view hierarchy.
*/
ShadowViewNodePair::List sliceChildShadowNodeViewPairs(
ShadowNode const &shadowNode);
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,124 @@
/*
* 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 "MountingCoordinator.h"
#ifdef RN_SHADOW_TREE_INTROSPECTION
#include <glog/logging.h>
#include <sstream>
#endif
#include <condition_variable>
#include <react/mounting/ShadowViewMutation.h>
namespace facebook {
namespace react {
MountingCoordinator::MountingCoordinator(ShadowTreeRevision baseRevision)
: surfaceId_(baseRevision.getRootShadowNode().getSurfaceId()),
baseRevision_(baseRevision) {
#ifdef RN_SHADOW_TREE_INTROSPECTION
stubViewTree_ = stubViewTreeFromShadowNode(baseRevision_.getRootShadowNode());
#endif
}
SurfaceId MountingCoordinator::getSurfaceId() const {
return surfaceId_;
}
void MountingCoordinator::push(ShadowTreeRevision &&revision) const {
{
std::lock_guard<std::mutex> lock(mutex_);
assert(revision.getNumber() > baseRevision_.getNumber());
assert(
!lastRevision_.has_value() ||
revision.getNumber() != lastRevision_->getNumber());
if (!lastRevision_.has_value() ||
lastRevision_->getNumber() < revision.getNumber()) {
lastRevision_ = std::move(revision);
}
}
signal_.notify_all();
}
void MountingCoordinator::revoke() const {
std::lock_guard<std::mutex> lock(mutex_);
// We have two goals here.
// 1. We need to stop retaining `ShadowNode`s to not prolong their lifetime
// to prevent them from overliving `ComponentDescriptor`s.
// 2. A possible call to `pullTransaction()` should return empty optional.
baseRevision_.rootShadowNode_.reset();
lastRevision_.reset();
}
bool MountingCoordinator::waitForTransaction(
std::chrono::duration<double> timeout) const {
std::unique_lock<std::mutex> lock(mutex_);
return signal_.wait_for(
lock, timeout, [this]() { return lastRevision_.has_value(); });
}
better::optional<MountingTransaction> MountingCoordinator::pullTransaction(
DifferentiatorMode differentiatorMode) const {
std::lock_guard<std::mutex> lock(mutex_);
if (!lastRevision_.has_value()) {
return {};
}
number_++;
auto telemetry = lastRevision_->getTelemetry();
telemetry.willDiff();
auto mutations = calculateShadowViewMutations(
differentiatorMode,
baseRevision_.getRootShadowNode(),
lastRevision_->getRootShadowNode());
telemetry.didDiff();
#ifdef RN_SHADOW_TREE_INTROSPECTION
stubViewTree_.mutate(mutations);
auto stubViewTree =
stubViewTreeFromShadowNode(lastRevision_->getRootShadowNode());
std::string line;
std::stringstream ssOldTree(
baseRevision_.getRootShadowNode().getDebugDescription());
while (std::getline(ssOldTree, line, '\n')) {
LOG(ERROR) << "Old tree:" << line;
}
std::stringstream ssNewTree(
lastRevision_->getRootShadowNode().getDebugDescription());
while (std::getline(ssNewTree, line, '\n')) {
LOG(ERROR) << "New tree:" << line;
}
std::stringstream ssMutations(getDebugDescription(mutations, {}));
while (std::getline(ssMutations, line, '\n')) {
LOG(ERROR) << "Mutations:" << line;
}
assert(stubViewTree_ == stubViewTree);
#endif
baseRevision_ = std::move(*lastRevision_);
lastRevision_.reset();
return MountingTransaction{
surfaceId_, number_, std::move(mutations), telemetry};
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,101 @@
/*
* 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 <better/optional.h>
#include <chrono>
#include <react/mounting/Differentiator.h>
#include <react/mounting/MountingTransaction.h>
#include <react/mounting/ShadowTreeRevision.h>
#ifdef RN_SHADOW_TREE_INTROSPECTION
#include <react/mounting/stubs.h>
#endif
namespace facebook {
namespace react {
/*
* Stores inside all non-mounted yet revisions of a shadow tree and coordinates
* mounting. The object stores the most recent mounted revision and the most
* recent committed one. Then when a new mounting transaction is requested the
* object generates mutation instructions and returns it as a
* `MountingTransaction`.
*/
class MountingCoordinator final {
public:
using Shared = std::shared_ptr<MountingCoordinator const>;
/*
* The constructor is ment to be used only inside `ShadowTree`, and it's
* `public` only to enable using with `std::make_shared<>`.
*/
MountingCoordinator(ShadowTreeRevision baseRevision);
/*
* Returns the id of the surface that the coordinator belongs to.
*/
SurfaceId getSurfaceId() const;
/*
* Computes a consequent mounting transaction and returns it.
* The returning transaction can accumulate multiple recent revisions of a
* shadow tree. Returns empty optional if there no new shadow tree revision to
* mount.
* The method is thread-safe and can be called from any thread.
* However, a consumer should always call it on the same thread (e.g. on the
* main thread) or ensure sequentiality of mount transactions separately.
*/
better::optional<MountingTransaction> pullTransaction(
DifferentiatorMode differentiatorMode) const;
/*
* Blocks the current thread until a new mounting transaction is available or
* after the specified `timeout` duration.
* Returns `false` if a timeout occurred before a new transaction available.
* Call `pullTransaction` right after the method to retrieve the transaction.
* Similarly to `pullTransaction` this method is thread-safe but the consumer
* should call it on the same thread (e.g. on the main thread) or ensure
* sequentiality of mount transactions separately.
*/
bool waitForTransaction(std::chrono::duration<double> timeout) const;
private:
friend class ShadowTree;
/*
* Methods from this section are meant to be used by `ShadowTree` only.
*/
void push(ShadowTreeRevision &&revision) const;
/*
* Revokes the last pushed `ShadowTreeRevision`.
* Generating a `MountingTransaction` requires some resources which the
* `MountingCoordinator` does not own (e.g. `ComponentDescriptor`s). Revoking
* committed revisions allows the owner (a Shadow Tree) to make sure that
* those resources will not be accessed (e.g. by the Mouting Layer).
*/
void revoke() const;
private:
SurfaceId const surfaceId_;
mutable std::mutex mutex_;
mutable ShadowTreeRevision baseRevision_;
mutable better::optional<ShadowTreeRevision> lastRevision_{};
mutable MountingTransaction::Number number_{0};
mutable std::condition_variable signal_;
#ifdef RN_SHADOW_TREE_INTROSPECTION
mutable StubViewTree stubViewTree_; // Protected by `mutex_`.
#endif
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,117 @@
/*
* 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 "MountingTelemetry.h"
#include <cassert>
namespace facebook {
namespace react {
void MountingTelemetry::willCommit() {
assert(commitStartTime_ == kTelemetryUndefinedTimePoint);
assert(commitEndTime_ == kTelemetryUndefinedTimePoint);
commitStartTime_ = telemetryTimePointNow();
commitNumber_++;
}
void MountingTelemetry::didCommit() {
assert(commitStartTime_ != kTelemetryUndefinedTimePoint);
assert(commitEndTime_ == kTelemetryUndefinedTimePoint);
commitEndTime_ = telemetryTimePointNow();
}
void MountingTelemetry::willDiff() {
assert(diffStartTime_ == kTelemetryUndefinedTimePoint);
assert(diffEndTime_ == kTelemetryUndefinedTimePoint);
diffStartTime_ = telemetryTimePointNow();
}
void MountingTelemetry::didDiff() {
assert(diffStartTime_ != kTelemetryUndefinedTimePoint);
assert(diffEndTime_ == kTelemetryUndefinedTimePoint);
diffEndTime_ = telemetryTimePointNow();
}
void MountingTelemetry::willLayout() {
assert(layoutStartTime_ == kTelemetryUndefinedTimePoint);
assert(layoutEndTime_ == kTelemetryUndefinedTimePoint);
layoutStartTime_ = telemetryTimePointNow();
}
void MountingTelemetry::didLayout() {
assert(layoutStartTime_ != kTelemetryUndefinedTimePoint);
assert(layoutEndTime_ == kTelemetryUndefinedTimePoint);
layoutEndTime_ = telemetryTimePointNow();
}
void MountingTelemetry::willMount() {
assert(mountStartTime_ == kTelemetryUndefinedTimePoint);
assert(mountEndTime_ == kTelemetryUndefinedTimePoint);
mountStartTime_ = telemetryTimePointNow();
}
void MountingTelemetry::didMount() {
assert(mountStartTime_ != kTelemetryUndefinedTimePoint);
assert(mountEndTime_ == kTelemetryUndefinedTimePoint);
mountEndTime_ = telemetryTimePointNow();
}
TelemetryTimePoint MountingTelemetry::getDiffStartTime() const {
assert(diffStartTime_ != kTelemetryUndefinedTimePoint);
assert(diffEndTime_ != kTelemetryUndefinedTimePoint);
return diffStartTime_;
}
TelemetryTimePoint MountingTelemetry::getDiffEndTime() const {
assert(diffStartTime_ != kTelemetryUndefinedTimePoint);
assert(diffEndTime_ != kTelemetryUndefinedTimePoint);
return diffEndTime_;
}
TelemetryTimePoint MountingTelemetry::getCommitStartTime() const {
assert(commitStartTime_ != kTelemetryUndefinedTimePoint);
assert(commitEndTime_ != kTelemetryUndefinedTimePoint);
return commitStartTime_;
}
TelemetryTimePoint MountingTelemetry::getCommitEndTime() const {
assert(commitStartTime_ != kTelemetryUndefinedTimePoint);
assert(commitEndTime_ != kTelemetryUndefinedTimePoint);
return commitEndTime_;
}
TelemetryTimePoint MountingTelemetry::getLayoutStartTime() const {
assert(layoutStartTime_ != kTelemetryUndefinedTimePoint);
assert(layoutEndTime_ != kTelemetryUndefinedTimePoint);
return layoutStartTime_;
}
TelemetryTimePoint MountingTelemetry::getLayoutEndTime() const {
assert(layoutStartTime_ != kTelemetryUndefinedTimePoint);
assert(layoutEndTime_ != kTelemetryUndefinedTimePoint);
return layoutEndTime_;
}
TelemetryTimePoint MountingTelemetry::getMountStartTime() const {
assert(mountStartTime_ != kTelemetryUndefinedTimePoint);
assert(mountEndTime_ != kTelemetryUndefinedTimePoint);
return mountStartTime_;
}
TelemetryTimePoint MountingTelemetry::getMountEndTime() const {
assert(mountStartTime_ != kTelemetryUndefinedTimePoint);
assert(mountEndTime_ != kTelemetryUndefinedTimePoint);
return mountEndTime_;
}
int MountingTelemetry::getCommitNumber() const {
return commitNumber_;
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,64 @@
/*
* 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 <chrono>
#include <cstdint>
#include <react/utils/Telemetry.h>
namespace facebook {
namespace react {
/*
* Represent arbitrary telemetry data that can be associated with the
* particular revision of `ShadowTree`.
*/
class MountingTelemetry final {
public:
/*
* Signaling
*/
void willDiff();
void didDiff();
void willCommit();
void didCommit();
void willLayout();
void didLayout();
void willMount();
void didMount();
/*
* Reading
*/
TelemetryTimePoint getDiffStartTime() const;
TelemetryTimePoint getDiffEndTime() const;
TelemetryTimePoint getLayoutStartTime() const;
TelemetryTimePoint getLayoutEndTime() const;
TelemetryTimePoint getCommitStartTime() const;
TelemetryTimePoint getCommitEndTime() const;
TelemetryTimePoint getMountStartTime() const;
TelemetryTimePoint getMountEndTime() const;
int getCommitNumber() const;
private:
TelemetryTimePoint diffStartTime_{kTelemetryUndefinedTimePoint};
TelemetryTimePoint diffEndTime_{kTelemetryUndefinedTimePoint};
TelemetryTimePoint commitStartTime_{kTelemetryUndefinedTimePoint};
TelemetryTimePoint commitEndTime_{kTelemetryUndefinedTimePoint};
TelemetryTimePoint layoutStartTime_{kTelemetryUndefinedTimePoint};
TelemetryTimePoint layoutEndTime_{kTelemetryUndefinedTimePoint};
TelemetryTimePoint mountStartTime_{kTelemetryUndefinedTimePoint};
TelemetryTimePoint mountEndTime_{kTelemetryUndefinedTimePoint};
int commitNumber_{0};
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,46 @@
/*
* 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 "MountingTransaction.h"
namespace facebook {
namespace react {
using Number = MountingTransaction::Number;
MountingTransaction::MountingTransaction(
SurfaceId surfaceId,
Number number,
ShadowViewMutationList &&mutations,
MountingTelemetry telemetry)
: surfaceId_(surfaceId),
number_(number),
mutations_(std::move(mutations)),
telemetry_(std::move(telemetry)) {}
ShadowViewMutationList const &MountingTransaction::getMutations() const & {
return mutations_;
}
ShadowViewMutationList MountingTransaction::getMutations() && {
return std::move(mutations_);
}
MountingTelemetry const &MountingTransaction::getTelemetry() const {
return telemetry_;
}
SurfaceId MountingTransaction::getSurfaceId() const {
return surfaceId_;
}
Number MountingTransaction::getNumber() const {
return number_;
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,87 @@
/*
* 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 <react/mounting/MountingTelemetry.h>
#include <react/mounting/ShadowViewMutation.h>
namespace facebook {
namespace react {
/*
* Encapsulates all artifacts of `ShadowTree` commit (or a series of them),
* particularly list of mutations and meta-data associated with the commit.
* Movable and copyable, but moving is strongly encouraged.
* Beware: A moved-from object of this type has unspecified value and accessing
* that is UB.
*/
class MountingTransaction final {
public:
/*
* A Number (or revision) grows continuously starting from `1`. Value `0`
* represents the state before the very first transaction happens.
*/
using Number = int64_t;
/*
* Copying a list of `ShadowViewMutation` is expensive, so the constructor
* accepts it as rvalue reference to discourage copying.
*/
MountingTransaction(
SurfaceId surfaceId,
Number number,
ShadowViewMutationList &&mutations,
MountingTelemetry telemetry);
/*
* Copy semantic.
* Copying of MountingTransaction is expensive, so copy-constructor is
* explicit and copy-assignment is deleted to prevent accidental copying.
*/
explicit MountingTransaction(const MountingTransaction &mountingTransaction) =
default;
MountingTransaction &operator=(const MountingTransaction &other) = delete;
/*
* Move semantic.
*/
MountingTransaction(MountingTransaction &&mountingTransaction) noexcept =
default;
MountingTransaction &operator=(MountingTransaction &&other) = default;
/*
* Returns a list of mutations that represent the transaction. The list can be
* empty (theoretically).
*/
ShadowViewMutationList const &getMutations() const &;
ShadowViewMutationList getMutations() &&;
/*
* Returns telemetry associated with this transaction.
*/
MountingTelemetry const &getTelemetry() const;
/*
* Returns the id of the surface that the transaction belongs to.
*/
SurfaceId getSurfaceId() const;
/*
* Returns a sequential number of the particular transaction.
*/
Number getNumber() const;
private:
SurfaceId surfaceId_;
Number number_;
ShadowViewMutationList mutations_;
MountingTelemetry telemetry_;
};
} // namespace react
} // namespace facebook

View File

@ -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 "MountingTransactionMetadata.h"
namespace facebook {
namespace react {} // namespace react
} // namespace facebook

View File

@ -0,0 +1,31 @@
/*
* 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 <react/mounting/MountingTelemetry.h>
#include <react/mounting/MountingTransaction.h>
namespace facebook {
namespace react {
/*
* Contains all (meta)information related to a MountingTransaction except a list
* of mutation instructions.
* The class is meant to be used when a consumer should not have access to all
* information about the transaction (incapsulation) but still needs to observe
* it to produce some side-effects.
*/
class MountingTransactionMetadata final {
public:
SurfaceId surfaceId;
MountingTransaction::Number number;
MountingTelemetry telemetry;
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,258 @@
/*
* 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 "ShadowTree.h"
#include <react/components/root/RootComponentDescriptor.h>
#include <react/components/view/ViewShadowNode.h>
#include <react/core/LayoutContext.h>
#include <react/core/LayoutPrimitives.h>
#include <react/debug/SystraceSection.h>
#include <react/mounting/MountingTelemetry.h>
#include <react/mounting/ShadowTreeRevision.h>
#include <react/mounting/ShadowViewMutation.h>
#include "ShadowTreeDelegate.h"
#include "TreeStateReconciliation.h"
namespace facebook {
namespace react {
static void updateMountedFlag(
const SharedShadowNodeList &oldChildren,
const SharedShadowNodeList &newChildren) {
// This is a simplified version of Diffing algorithm that only updates
// `mounted` flag on `ShadowNode`s. The algorithm sets "mounted" flag before
// "unmounted" to allow `ShadowNode` detect a situation where the node was
// remounted.
if (&oldChildren == &newChildren) {
// Lists are identical, nothing to do.
return;
}
if (oldChildren.size() == 0 && newChildren.size() == 0) {
// Both lists are empty, nothing to do.
return;
}
int index;
// Stage 1: Mount and unmount "updated" children.
for (index = 0; index < oldChildren.size() && index < newChildren.size();
index++) {
const auto &oldChild = oldChildren[index];
const auto &newChild = newChildren[index];
if (oldChild == newChild) {
// Nodes are identical, skipping the subtree.
continue;
}
if (!ShadowNode::sameFamily(*oldChild, *newChild)) {
// Totally different nodes, updating is impossible.
break;
}
newChild->setMounted(true);
oldChild->setMounted(false);
updateMountedFlag(oldChild->getChildren(), newChild->getChildren());
}
int lastIndexAfterFirstStage = index;
// State 2: Mount new children.
for (index = lastIndexAfterFirstStage; index < newChildren.size(); index++) {
const auto &newChild = newChildren[index];
newChild->setMounted(true);
updateMountedFlag({}, newChild->getChildren());
}
// State 3: Unmount old children.
for (index = lastIndexAfterFirstStage; index < oldChildren.size(); index++) {
const auto &oldChild = oldChildren[index];
oldChild->setMounted(false);
updateMountedFlag(oldChild->getChildren(), {});
}
}
ShadowTree::ShadowTree(
SurfaceId surfaceId,
LayoutConstraints const &layoutConstraints,
LayoutContext const &layoutContext,
RootComponentDescriptor const &rootComponentDescriptor,
ShadowTreeDelegate const &delegate)
: surfaceId_(surfaceId), delegate_(delegate) {
const auto noopEventEmitter = std::make_shared<const ViewEventEmitter>(
nullptr, -1, std::shared_ptr<const EventDispatcher>());
const auto props = std::make_shared<const RootProps>(
*RootShadowNode::defaultSharedProps(), layoutConstraints, layoutContext);
auto family = rootComponentDescriptor.createFamily(
ShadowNodeFamilyFragment{surfaceId, surfaceId, noopEventEmitter},
nullptr);
rootShadowNode_ = std::static_pointer_cast<const RootShadowNode>(
rootComponentDescriptor.createShadowNode(
ShadowNodeFragment{
/* .props = */ props,
},
family));
mountingCoordinator_ = std::make_shared<MountingCoordinator const>(
ShadowTreeRevision{rootShadowNode_, 0, {}});
}
ShadowTree::~ShadowTree() {
mountingCoordinator_->revoke();
}
Tag ShadowTree::getSurfaceId() const {
return surfaceId_;
}
MountingCoordinator::Shared ShadowTree::getMountingCoordinator() const {
return mountingCoordinator_;
}
void ShadowTree::commit(
ShadowTreeCommitTransaction transaction,
bool enableStateReconciliation) const {
SystraceSection s("ShadowTree::commit");
int attempts = 0;
while (true) {
attempts++;
if (tryCommit(transaction, enableStateReconciliation)) {
return;
}
// After multiple attempts, we failed to commit the transaction.
// Something internally went terribly wrong.
assert(attempts < 1024);
}
}
bool ShadowTree::tryCommit(
ShadowTreeCommitTransaction transaction,
bool enableStateReconciliation) const {
SystraceSection s("ShadowTree::tryCommit");
auto telemetry = MountingTelemetry{};
telemetry.willCommit();
RootShadowNode::Shared oldRootShadowNode;
{
// Reading `rootShadowNode_` in shared manner.
std::shared_lock<better::shared_mutex> lock(commitMutex_);
oldRootShadowNode = rootShadowNode_;
}
RootShadowNode::Unshared newRootShadowNode = transaction(oldRootShadowNode);
if (!newRootShadowNode) {
return false;
}
// Compare state revisions of old and new root
// Children of the root node may be mutated in-place
if (enableStateReconciliation) {
UnsharedShadowNode reconciledNode =
reconcileStateWithTree(newRootShadowNode.get(), oldRootShadowNode);
if (reconciledNode != nullptr) {
newRootShadowNode = std::make_shared<RootShadowNode>(
*reconciledNode, ShadowNodeFragment{});
}
}
// Layout nodes
std::vector<LayoutableShadowNode const *> affectedLayoutableNodes{};
affectedLayoutableNodes.reserve(1024);
telemetry.willLayout();
newRootShadowNode->layoutIfNeeded(&affectedLayoutableNodes);
telemetry.didLayout();
// Seal the shadow node so it can no longer be mutated
newRootShadowNode->sealRecursive();
auto revisionNumber = ShadowTreeRevision::Number{};
{
// Updating `rootShadowNode_` in unique manner if it hasn't changed.
std::unique_lock<better::shared_mutex> lock(commitMutex_);
if (rootShadowNode_ != oldRootShadowNode) {
return false;
}
rootShadowNode_ = newRootShadowNode;
{
std::lock_guard<std::mutex> dispatchLock(EventEmitter::DispatchMutex());
updateMountedFlag(
oldRootShadowNode->getChildren(), newRootShadowNode->getChildren());
}
revisionNumber_++;
revisionNumber = revisionNumber_;
}
emitLayoutEvents(affectedLayoutableNodes);
telemetry.didCommit();
mountingCoordinator_->push(
ShadowTreeRevision{newRootShadowNode, revisionNumber, telemetry});
delegate_.shadowTreeDidFinishTransaction(*this, mountingCoordinator_);
return true;
}
void ShadowTree::commitEmptyTree() const {
commit(
[](RootShadowNode::Shared const &oldRootShadowNode)
-> RootShadowNode::Unshared {
return std::make_shared<RootShadowNode>(
*oldRootShadowNode,
ShadowNodeFragment{
/* .props = */ ShadowNodeFragment::propsPlaceholder(),
/* .children = */ ShadowNode::emptySharedShadowNodeSharedList(),
});
});
}
void ShadowTree::emitLayoutEvents(
std::vector<LayoutableShadowNode const *> &affectedLayoutableNodes) const {
SystraceSection s("ShadowTree::emitLayoutEvents");
for (auto const *layoutableNode : affectedLayoutableNodes) {
// Only instances of `ViewShadowNode` (and subclasses) are supported.
auto const &viewShadowNode =
static_cast<ViewShadowNode const &>(*layoutableNode);
auto const &viewEventEmitter = static_cast<ViewEventEmitter const &>(
*viewShadowNode.getEventEmitter());
// Checking if the `onLayout` event was requested for the particular Shadow
// Node.
auto const &viewProps =
static_cast<ViewProps const &>(*viewShadowNode.getProps());
if (!viewProps.onLayout) {
continue;
}
viewEventEmitter.onLayout(layoutableNode->getLayoutMetrics());
}
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,94 @@
/*
* 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 <better/mutex.h>
#include <memory>
#include <react/components/root/RootComponentDescriptor.h>
#include <react/components/root/RootShadowNode.h>
#include <react/core/LayoutConstraints.h>
#include <react/core/ReactPrimitives.h>
#include <react/core/ShadowNode.h>
#include <react/mounting/MountingCoordinator.h>
#include <react/mounting/ShadowTreeDelegate.h>
#include <react/mounting/ShadowTreeRevision.h>
namespace facebook {
namespace react {
using ShadowTreeCommitTransaction = std::function<RootShadowNode::Unshared(
RootShadowNode::Shared const &oldRootShadowNode)>;
/*
* Represents the shadow tree and its lifecycle.
*/
class ShadowTree final {
public:
/*
* Creates a new shadow tree instance.
*/
ShadowTree(
SurfaceId surfaceId,
LayoutConstraints const &layoutConstraints,
LayoutContext const &layoutContext,
RootComponentDescriptor const &rootComponentDescriptor,
ShadowTreeDelegate const &delegate);
~ShadowTree();
/*
* Returns the `SurfaceId` associated with the shadow tree.
*/
SurfaceId getSurfaceId() const;
/*
* Performs commit calling `transaction` function with a `oldRootShadowNode`
* and expecting a `newRootShadowNode` as a return value.
* The `transaction` function can abort commit returning `nullptr`.
* Returns `true` if the operation finished successfully.
*/
bool tryCommit(
ShadowTreeCommitTransaction transaction,
bool enableStateReconciliation = false) const;
/*
* Calls `tryCommit` in a loop until it finishes successfully.
*/
void commit(
ShadowTreeCommitTransaction transaction,
bool enableStateReconciliation = false) const;
/*
* Commit an empty tree (a new `RootShadowNode` with no children).
*/
void commitEmptyTree() const;
MountingCoordinator::Shared getMountingCoordinator() const;
private:
RootShadowNode::Unshared cloneRootShadowNode(
RootShadowNode::Shared const &oldRootShadowNode,
LayoutConstraints const &layoutConstraints,
LayoutContext const &layoutContext) const;
void emitLayoutEvents(
std::vector<LayoutableShadowNode const *> &affectedLayoutableNodes) const;
SurfaceId const surfaceId_;
ShadowTreeDelegate const &delegate_;
mutable better::shared_mutex commitMutex_;
mutable RootShadowNode::Shared
rootShadowNode_; // Protected by `commitMutex_`.
mutable ShadowTreeRevision::Number revisionNumber_{
0}; // Protected by `commitMutex_`.
MountingCoordinator::Shared mountingCoordinator_;
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,33 @@
/*
* 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 <react/mounting/MountingCoordinator.h>
namespace facebook {
namespace react {
class ShadowTree;
/*
* Abstract class for ShadowTree's delegate.
*/
class ShadowTreeDelegate {
public:
/*
* Called right after Shadow Tree commit a new state of the tree.
*/
virtual void shadowTreeDidFinishTransaction(
ShadowTree const &shadowTree,
MountingCoordinator::Shared const &mountingCoordinator) const = 0;
virtual ~ShadowTreeDelegate() noexcept = default;
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,64 @@
/*
* 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 "ShadowTreeRegistry.h"
namespace facebook {
namespace react {
ShadowTreeRegistry::~ShadowTreeRegistry() {
assert(
registry_.size() == 0 &&
"Deallocation of non-empty `ShadowTreeRegistry`.");
}
void ShadowTreeRegistry::add(std::unique_ptr<ShadowTree> &&shadowTree) const {
std::unique_lock<better::shared_mutex> lock(mutex_);
registry_.emplace(shadowTree->getSurfaceId(), std::move(shadowTree));
}
std::unique_ptr<ShadowTree> ShadowTreeRegistry::remove(
SurfaceId surfaceId) const {
std::unique_lock<better::shared_mutex> lock(mutex_);
auto iterator = registry_.find(surfaceId);
auto shadowTree = std::unique_ptr<ShadowTree>(iterator->second.release());
registry_.erase(iterator);
return shadowTree;
}
bool ShadowTreeRegistry::visit(
SurfaceId surfaceId,
std::function<void(const ShadowTree &shadowTree)> callback) const {
std::shared_lock<better::shared_mutex> lock(mutex_);
auto iterator = registry_.find(surfaceId);
if (iterator == registry_.end()) {
return false;
}
callback(*iterator->second);
return true;
}
void ShadowTreeRegistry::enumerate(
std::function<void(const ShadowTree &shadowTree, bool &stop)> callback)
const {
std::shared_lock<better::shared_mutex> lock(mutex_);
bool stop = false;
for (auto const &pair : registry_) {
callback(*pair.second, stop);
if (stop) {
break;
}
}
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,69 @@
/*
* 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 <better/map.h>
#include <better/mutex.h>
#include <react/core/ReactPrimitives.h>
#include <react/mounting/ShadowTree.h>
namespace facebook {
namespace react {
/*
* Owning registry of `ShadowTree`s.
*/
class ShadowTreeRegistry final {
public:
ShadowTreeRegistry() = default;
~ShadowTreeRegistry();
/*
* Adds a `ShadowTree` instance to the registry.
* The ownership of the instance is also transferred to the registry.
* Can be called from any thread.
*/
void add(std::unique_ptr<ShadowTree> &&shadowTree) const;
/*
* Removes a `ShadowTree` instance with given `surfaceId` from the registry
* and returns it as a result.
* The ownership of the instance is also transferred to the caller.
* Can be called from any thread.
*/
std::unique_ptr<ShadowTree> remove(SurfaceId surfaceId) const;
/*
* Finds a `ShadowTree` instance with a given `surfaceId` in the registry and
* synchronously calls the `callback` with a reference to the instance while
* the mutex is being acquired.
* Returns `true` if the registry has `ShadowTree` instance with corresponding
* `surfaceId`, otherwise returns `false` without calling the `callback`.
* Can be called from any thread.
*/
bool visit(
SurfaceId surfaceId,
std::function<void(const ShadowTree &shadowTree)> callback) const;
/*
* Enumerates all stored shadow trees.
* Set `stop` to `true` to interrupt the enumeration.
* Can be called from any thread.
*/
void enumerate(std::function<void(const ShadowTree &shadowTree, bool &stop)>
callback) const;
private:
mutable better::shared_mutex mutex_;
mutable better::map<SurfaceId, std::unique_ptr<ShadowTree>>
registry_; // Protected by `mutex_`.
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,34 @@
/*
* 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 "ShadowTreeRevision.h"
namespace facebook {
namespace react {
using Number = ShadowTreeRevision::Number;
ShadowTreeRevision::ShadowTreeRevision(
ShadowNode::Shared const &rootShadowNode,
Number number,
MountingTelemetry telemetry)
: rootShadowNode_(rootShadowNode), number_(number), telemetry_(telemetry) {}
MountingTelemetry const &ShadowTreeRevision::getTelemetry() const {
return telemetry_;
}
ShadowNode const &ShadowTreeRevision::getRootShadowNode() {
return *rootShadowNode_;
}
Number ShadowTreeRevision::getNumber() const {
return number_;
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,62 @@
/*
* 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 <better/optional.h>
#include <react/mounting/MountingTelemetry.h>
#include <react/mounting/MountingTransaction.h>
#include <react/mounting/ShadowViewMutation.h>
namespace facebook {
namespace react {
/*
* Represent a particular committed state of a shadow tree. The object contains
* a pointer to a root shadow node, a sequential number of commit and telemetry.
*/
class ShadowTreeRevision final {
public:
/*
* Sequential number of the commit that created this revision of a shadow
* tree.
*/
using Number = int64_t;
/*
* Creates the object with given root shadow node, revision number and
* telemetry.
*/
ShadowTreeRevision(
ShadowNode::Shared const &rootShadowNode,
Number number,
MountingTelemetry telemetry);
/*
* Returns telemetry associated with this revision.
*/
MountingTelemetry const &getTelemetry() const;
private:
friend class MountingCoordinator;
/*
* Methods from this section are meant to be used by `MountingCoordinator`
* only.
*/
ShadowNode const &getRootShadowNode();
Number getNumber() const;
private:
ShadowNode::Shared rootShadowNode_;
Number number_;
MountingTelemetry telemetry_;
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,81 @@
/*
* 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 "ShadowView.h"
#include <react/core/LayoutableShadowNode.h>
namespace facebook {
namespace react {
static LayoutMetrics layoutMetricsFromShadowNode(ShadowNode const &shadowNode) {
auto layotableShadowNode =
traitCast<LayoutableShadowNode const *>(&shadowNode);
return layotableShadowNode ? layotableShadowNode->getLayoutMetrics()
: EmptyLayoutMetrics;
}
ShadowView::ShadowView(const ShadowNode &shadowNode)
: componentName(shadowNode.getComponentName()),
componentHandle(shadowNode.getComponentHandle()),
tag(shadowNode.getTag()),
props(shadowNode.getProps()),
eventEmitter(shadowNode.getEventEmitter()),
layoutMetrics(layoutMetricsFromShadowNode(shadowNode)),
state(shadowNode.getState()) {}
bool ShadowView::operator==(const ShadowView &rhs) const {
return std::tie(
this->tag,
this->componentName,
this->props,
this->eventEmitter,
this->layoutMetrics,
this->state) ==
std::tie(
rhs.tag,
rhs.componentName,
rhs.props,
rhs.eventEmitter,
rhs.layoutMetrics,
rhs.state);
}
bool ShadowView::operator!=(const ShadowView &rhs) const {
return !(*this == rhs);
}
#if RN_DEBUG_STRING_CONVERTIBLE
std::string getDebugName(ShadowView const &object) {
return object.componentHandle == 0 ? "Invalid" : object.componentName;
}
std::vector<DebugStringConvertibleObject> getDebugProps(
ShadowView const &object,
DebugStringConvertibleOptions options) {
return {
{"tag", getDebugDescription(object.tag, options)},
{"props", getDebugDescription(object.props, options)},
{"eventEmitter", getDebugDescription(object.eventEmitter, options)},
{"layoutMetrics", getDebugDescription(object.layoutMetrics, options)},
{"state", getDebugDescription(object.state, options)},
};
}
#endif
bool ShadowViewNodePair::operator==(const ShadowViewNodePair &rhs) const {
return this->shadowNode == rhs.shadowNode;
}
bool ShadowViewNodePair::operator!=(const ShadowViewNodePair &rhs) const {
return !(*this == rhs);
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,93 @@
/*
* 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 <better/small_vector.h>
#include <folly/Hash.h>
#include <react/core/EventEmitter.h>
#include <react/core/LayoutMetrics.h>
#include <react/core/Props.h>
#include <react/core/ReactPrimitives.h>
#include <react/core/ShadowNode.h>
namespace facebook {
namespace react {
/*
* Describes a view that can be mounted.
*/
struct ShadowView final {
ShadowView() = default;
ShadowView(ShadowView const &shadowView) = default;
ShadowView(ShadowView &&shadowView) noexcept = default;
/*
* Constructs a `ShadowView` from given `ShadowNode`.
*/
explicit ShadowView(ShadowNode const &shadowNode);
ShadowView &operator=(ShadowView const &other) = default;
ShadowView &operator=(ShadowView &&other) = default;
bool operator==(ShadowView const &rhs) const;
bool operator!=(ShadowView const &rhs) const;
ComponentName componentName{};
ComponentHandle componentHandle{};
Tag tag{};
Props::Shared props{};
EventEmitter::Shared eventEmitter{};
LayoutMetrics layoutMetrics{EmptyLayoutMetrics};
State::Shared state{};
};
#if RN_DEBUG_STRING_CONVERTIBLE
std::string getDebugName(ShadowView const &object);
std::vector<DebugStringConvertibleObject> getDebugProps(
ShadowView const &object,
DebugStringConvertibleOptions options);
#endif
/*
* Describes pair of a `ShadowView` and a `ShadowNode`.
*/
struct ShadowViewNodePair final {
using List = better::
small_vector<ShadowViewNodePair, kShadowNodeChildrenSmallVectorSize>;
ShadowView shadowView;
ShadowNode const *shadowNode;
/*
* The stored pointer to `ShadowNode` represents an indentity of the pair.
*/
bool operator==(const ShadowViewNodePair &rhs) const;
bool operator!=(const ShadowViewNodePair &rhs) const;
};
} // namespace react
} // namespace facebook
namespace std {
template <>
struct hash<facebook::react::ShadowView> {
size_t operator()(const facebook::react::ShadowView &shadowView) const {
return folly::hash::hash_combine(
0,
shadowView.componentHandle,
shadowView.tag,
shadowView.props,
shadowView.eventEmitter,
shadowView.state);
}
};
} // namespace std

View File

@ -0,0 +1,123 @@
/*
* 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 "ShadowViewMutation.h"
namespace facebook {
namespace react {
ShadowViewMutation ShadowViewMutation::CreateMutation(ShadowView shadowView) {
return {
/* .type = */ Create,
/* .parentShadowView = */ {},
/* .oldChildShadowView = */ {},
/* .newChildShadowView = */ shadowView,
/* .index = */ -1,
};
}
ShadowViewMutation ShadowViewMutation::DeleteMutation(ShadowView shadowView) {
return {
/* .type = */ Delete,
/* .parentShadowView = */ {},
/* .oldChildShadowView = */ shadowView,
/* .newChildShadowView = */ {},
/* .index = */ -1,
};
}
ShadowViewMutation ShadowViewMutation::InsertMutation(
ShadowView parentShadowView,
ShadowView childShadowView,
int index) {
return {
/* .type = */ Insert,
/* .parentShadowView = */ parentShadowView,
/* .oldChildShadowView = */ {},
/* .newChildShadowView = */ childShadowView,
/* .index = */ index,
};
}
ShadowViewMutation ShadowViewMutation::RemoveMutation(
ShadowView parentShadowView,
ShadowView childShadowView,
int index) {
return {
/* .type = */ Remove,
/* .parentShadowView = */ parentShadowView,
/* .oldChildShadowView = */ childShadowView,
/* .newChildShadowView = */ {},
/* .index = */ index,
};
}
ShadowViewMutation ShadowViewMutation::UpdateMutation(
ShadowView parentShadowView,
ShadowView oldChildShadowView,
ShadowView newChildShadowView,
int index) {
return {
/* .type = */ Update,
/* .parentShadowView = */ parentShadowView,
/* .oldChildShadowView = */ oldChildShadowView,
/* .newChildShadowView = */ newChildShadowView,
/* .index = */ index,
};
}
#if RN_DEBUG_STRING_CONVERTIBLE
std::string getDebugName(ShadowViewMutation const &mutation) {
switch (mutation.type) {
case ShadowViewMutation::Create:
return "Create";
case ShadowViewMutation::Delete:
return "Delete";
case ShadowViewMutation::Insert:
return "Insert";
case ShadowViewMutation::Remove:
return "Remove";
case ShadowViewMutation::Update:
return "Update";
}
}
std::vector<DebugStringConvertibleObject> getDebugProps(
ShadowViewMutation const &mutation,
DebugStringConvertibleOptions options) {
return {
mutation.oldChildShadowView.componentHandle
? DebugStringConvertibleObject{"oldChild",
getDebugDescription(
mutation.oldChildShadowView,
options)}
: DebugStringConvertibleObject{},
mutation.newChildShadowView.componentHandle
? DebugStringConvertibleObject{"newChild",
getDebugDescription(
mutation.newChildShadowView,
options)}
: DebugStringConvertibleObject{},
mutation.parentShadowView.componentHandle
? DebugStringConvertibleObject{"parent",
getDebugDescription(
mutation.parentShadowView,
options)}
: DebugStringConvertibleObject{},
mutation.index != -1
? DebugStringConvertibleObject{"index",
getDebugDescription(
mutation.index, options)}
: DebugStringConvertibleObject{},
};
}
#endif
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,88 @@
/*
* 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 <vector>
#include <react/mounting/ShadowView.h>
namespace facebook {
namespace react {
/*
* 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.
*/
struct ShadowViewMutation final {
using List = std::vector<ShadowViewMutation>;
#pragma mark - Designated Initializers
/*
* Creates and returns an `Create` mutation.
*/
static ShadowViewMutation CreateMutation(ShadowView shadowView);
/*
* Creates and returns an `Delete` mutation.
*/
static ShadowViewMutation DeleteMutation(ShadowView shadowView);
/*
* Creates and returns an `Insert` mutation.
*/
static ShadowViewMutation InsertMutation(
ShadowView parentShadowView,
ShadowView childShadowView,
int index);
/*
* Creates and returns a `Remove` mutation.
*/
static ShadowViewMutation RemoveMutation(
ShadowView parentShadowView,
ShadowView childShadowView,
int index);
/*
* Creates and returns an `Update` mutation.
*/
static ShadowViewMutation UpdateMutation(
ShadowView parentShadowView,
ShadowView oldChildShadowView,
ShadowView newChildShadowView,
int index);
#pragma mark - Type
enum Type { Create, Delete, Insert, Remove, Update };
#pragma mark - Fields
Type type = {Create};
ShadowView parentShadowView = {};
ShadowView oldChildShadowView = {};
ShadowView newChildShadowView = {};
int index = {};
};
using ShadowViewMutationList = std::vector<ShadowViewMutation>;
#if RN_DEBUG_STRING_CONVERTIBLE
std::string getDebugName(ShadowViewMutation const &object);
std::vector<DebugStringConvertibleObject> getDebugProps(
ShadowViewMutation const &object,
DebugStringConvertibleOptions options);
#endif
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,94 @@
/*
* 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 "TreeStateReconciliation.h"
namespace facebook {
namespace react {
using ChangedShadowNodePairs =
std::vector<std::pair<ShadowNode::Shared, ShadowNode::Unshared>>;
/**
* Clones any children in the subtree that need to be cloned, and adds those to
* the `changedPairs` vector argument.
*/
static ChangedShadowNodePairs reconcileStateWithChildren(
SharedShadowNodeList const &newChildren,
SharedShadowNodeList const &oldChildren) {
ChangedShadowNodePairs changedPairs;
// Find children that are the same family in both trees.
// We only want to find nodes that existing in the new tree - if they
// don't exist in the new tree, they're being deleted; if they don't exist
// in the old tree, they're new. We don't need to deal with either of those
// cases here.
// Currently we use a naive double loop - this could be improved, but we need
// to be able to handle cases where nodes are entirely reordered, for
// instance.
for (auto const &child : newChildren) {
auto const oldChild = std::find_if(
oldChildren.begin(), oldChildren.end(), [&](const auto &el) {
return ShadowNode::sameFamily(*child, *el);
});
if (oldChild != oldChildren.end()) {
UnsharedShadowNode newChild =
reconcileStateWithTree(child.get(), *oldChild);
if (newChild != nullptr) {
changedPairs.push_back(std::make_pair(child, newChild));
}
}
};
return changedPairs;
}
UnsharedShadowNode reconcileStateWithTree(
ShadowNode const *newNode,
SharedShadowNode committedNode) {
// If the revisions on the node are the same, we can finish here.
// Subtrees are guaranteed to be identical at this point, too.
if (committedNode->getStateRevision() <= newNode->getStateRevision()) {
return nullptr;
}
// If we got this fair, we're guaranteed that the state of 1) this node,
// and/or 2) some descendant node is out-of-date and must be reconciled.
// This requires traversing all children, and we must at *least* clone
// this node, whether or not we clone and update any children.
auto const &newChildren = newNode->getChildren();
auto const &oldChildren = committedNode->getChildren();
auto const changedPairs =
reconcileStateWithChildren(newChildren, oldChildren);
ShadowNode::SharedListOfShared clonedChildren =
ShadowNodeFragment::childrenPlaceholder();
// If any children were cloned, we need to recreate the child list.
// This won't cause any children to be cloned that weren't already cloned -
// it just collects all children, cloned or uncloned, into a new list.
if (!changedPairs.empty()) {
ShadowNode::UnsharedListOfShared newList =
std::make_shared<ShadowNode::ListOfShared>();
for (std::size_t i = 0, j = 0; i < newChildren.size(); ++i) {
if (j < changedPairs.size() && changedPairs[j].first == newChildren[i]) {
newList->push_back(changedPairs[j].second);
++j;
} else {
newList->push_back(newChildren[i]);
}
}
clonedChildren = newList;
}
return newNode->clone({/* .props = */ ShadowNodeFragment::propsPlaceholder(),
/* .children = */ clonedChildren,
/* .state = */ newNode->getMostRecentState()});
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,33 @@
/*
* 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 <react/core/ShadowNode.h>
#include <react/core/ShadowNodeFragment.h>
namespace facebook {
namespace react {
/**
* Problem Description: because of C++ State, the React Native C++ ShadowTree
* can diverge from the ReactJS ShadowTree; ReactJS communicates all tree
* changes to C++, but C++ state commits are not propagated to ReactJS (ReactJS
* may or may not clone nodes with state changes, but it has no way of knowing
* if it /should/ clone those nodes; so those clones may never happen). This
* causes a number of problems. This function resolves the problem by taking a
* candidate tree being committed, and sees if any State changes need to be
* applied to it. If any changes need to be made, a new ShadowNode is returned;
* otherwise, nullptr is returned if the node is already consistent with the
* latest tree, including all state changes.
*
* This should be called during the commit phase, pre-layout and pre-diff.
*/
UnsharedShadowNode reconcileStateWithTree(
ShadowNode const *newNode,
SharedShadowNode committedNode);
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,65 @@
/*
* 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 "StubView.h"
namespace facebook {
namespace react {
void StubView::update(ShadowView const &shadowView) {
componentName = shadowView.componentName;
componentHandle = shadowView.componentHandle;
tag = shadowView.tag;
props = shadowView.props;
eventEmitter = shadowView.eventEmitter;
layoutMetrics = shadowView.layoutMetrics;
state = shadowView.state;
}
bool operator==(StubView const &lhs, StubView const &rhs) {
return std::tie(lhs.props, lhs.layoutMetrics) ==
std::tie(rhs.props, rhs.layoutMetrics);
}
bool operator!=(StubView const &lhs, StubView const &rhs) {
return !(lhs == rhs);
}
#if RN_DEBUG_STRING_CONVERTIBLE
std::string getDebugName(StubView const &stubView) {
return std::string{"Stub"} +
std::string{stubView.componentHandle ? stubView.componentName
: "[invalid]"};
}
std::vector<DebugStringConvertibleObject> getDebugProps(
StubView const &stubView,
DebugStringConvertibleOptions options) {
return {
{"tag", getDebugDescription(stubView.tag, options)},
{"props", getDebugDescription(stubView.props, options)},
{"eventEmitter", getDebugDescription(stubView.eventEmitter, options)},
{"layoutMetrics", getDebugDescription(stubView.layoutMetrics, options)},
{"state", getDebugDescription(stubView.state, options)},
};
}
std::vector<StubView> getDebugChildren(
StubView const &stubView,
DebugStringConvertibleOptions options) {
std::vector<StubView> result;
for (auto const &child : stubView.children) {
result.push_back(*child);
}
return result;
}
#endif
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,57 @@
/*
* 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 <memory>
#include <vector>
#include <react/core/LayoutMetrics.h>
#include <react/core/State.h>
#include <react/debug/debugStringConvertibleUtils.h>
#include <react/mounting/ShadowView.h>
namespace facebook {
namespace react {
class StubView final {
public:
using Shared = std::shared_ptr<StubView>;
StubView() = default;
StubView(StubView const &stubView) = default;
void update(ShadowView const &shadowView);
ComponentName componentName;
ComponentHandle componentHandle;
Tag tag;
SharedProps props;
SharedEventEmitter eventEmitter;
LayoutMetrics layoutMetrics;
State::Shared state;
std::vector<StubView::Shared> children;
};
bool operator==(StubView const &lhs, StubView const &rhs);
bool operator!=(StubView const &lhs, StubView const &rhs);
#if RN_DEBUG_STRING_CONVERTIBLE
std::string getDebugName(StubView const &stubView);
std::vector<DebugStringConvertibleObject> getDebugProps(
StubView const &stubView,
DebugStringConvertibleOptions options);
std::vector<StubView> getDebugChildren(
StubView const &stubView,
DebugStringConvertibleOptions options);
#endif
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,111 @@
/*
* 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 "StubViewTree.h"
namespace facebook {
namespace react {
StubViewTree::StubViewTree(ShadowView const &shadowView) {
auto view = std::make_shared<StubView>();
view->update(shadowView);
rootTag = shadowView.tag;
registry[shadowView.tag] = view;
}
StubView const &StubViewTree::getRootStubView() const {
return *registry.at(rootTag);
}
void StubViewTree::mutate(ShadowViewMutationList const &mutations) {
for (auto const &mutation : mutations) {
switch (mutation.type) {
case ShadowViewMutation::Create: {
assert(mutation.parentShadowView == ShadowView{});
assert(mutation.oldChildShadowView == ShadowView{});
auto stubView = std::make_shared<StubView>();
auto tag = mutation.newChildShadowView.tag;
assert(registry.find(tag) == registry.end());
registry[tag] = stubView;
break;
}
case ShadowViewMutation::Delete: {
assert(mutation.parentShadowView == ShadowView{});
assert(mutation.newChildShadowView == ShadowView{});
auto tag = mutation.oldChildShadowView.tag;
assert(registry.find(tag) != registry.end());
registry.erase(tag);
break;
}
case ShadowViewMutation::Insert: {
assert(mutation.oldChildShadowView == ShadowView{});
auto parentTag = mutation.parentShadowView.tag;
assert(registry.find(parentTag) != registry.end());
auto parentStubView = registry[parentTag];
auto childTag = mutation.newChildShadowView.tag;
assert(registry.find(childTag) != registry.end());
auto childStubView = registry[childTag];
childStubView->update(mutation.newChildShadowView);
parentStubView->children.insert(
parentStubView->children.begin() + mutation.index, childStubView);
break;
}
case ShadowViewMutation::Remove: {
assert(mutation.newChildShadowView == ShadowView{});
auto parentTag = mutation.parentShadowView.tag;
assert(registry.find(parentTag) != registry.end());
auto parentStubView = registry[parentTag];
auto childTag = mutation.oldChildShadowView.tag;
assert(registry.find(childTag) != registry.end());
auto childStubView = registry[childTag];
assert(
parentStubView->children[mutation.index]->tag ==
childStubView->tag);
parentStubView->children.erase(
parentStubView->children.begin() + mutation.index);
break;
}
case ShadowViewMutation::Update: {
assert(
mutation.newChildShadowView.tag == mutation.oldChildShadowView.tag);
assert(
registry.find(mutation.newChildShadowView.tag) != registry.end());
auto stubView = registry[mutation.newChildShadowView.tag];
stubView->update(mutation.newChildShadowView);
break;
}
}
}
}
bool operator==(StubViewTree const &lhs, StubViewTree const &rhs) {
if (lhs.registry.size() != rhs.registry.size()) {
return false;
}
for (auto const &pair : lhs.registry) {
auto &lhsStubView = *lhs.registry.at(pair.first);
auto &rhsStubView = *rhs.registry.at(pair.first);
if (lhsStubView != rhsStubView) {
return false;
}
}
return true;
}
bool operator!=(StubViewTree const &lhs, StubViewTree const &rhs) {
return !(lhs == rhs);
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,36 @@
/*
* 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 <memory>
#include <unordered_map>
#include <react/mounting/ShadowViewMutation.h>
#include <react/mounting/StubView.h>
namespace facebook {
namespace react {
class StubViewTree {
public:
StubViewTree() = default;
StubViewTree(ShadowView const &shadowView);
void mutate(ShadowViewMutationList const &mutations);
StubView const &getRootStubView() const;
Tag rootTag;
std::unordered_map<Tag, StubView::Shared> registry{};
};
bool operator==(StubViewTree const &lhs, StubViewTree const &rhs);
bool operator!=(StubViewTree const &lhs, StubViewTree const &rhs);
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,62 @@
/*
* 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 "stubs.h"
#include <react/core/LayoutableShadowNode.h>
#include <react/core/ShadowNodeFragment.h>
#include <react/mounting/Differentiator.h>
namespace facebook {
namespace react {
/*
* Generates `create` and `insert` instructions recursively traversing a shadow
* tree.
* This is a trivial implementation of diffing algorithm that can only "diff"
* an empty tree with some other one.
*/
static void calculateShadowViewMutationsForNewTree(
ShadowViewMutation::List &mutations,
ShadowView const &parentShadowView,
ShadowViewNodePair::List const &newChildPairs) {
for (auto index = 0; index < newChildPairs.size(); index++) {
auto const &newChildPair = newChildPairs[index];
mutations.push_back(
ShadowViewMutation::CreateMutation(newChildPair.shadowView));
mutations.push_back(ShadowViewMutation::InsertMutation(
parentShadowView, newChildPair.shadowView, index));
auto const newGrandChildPairs =
sliceChildShadowNodeViewPairs(*newChildPair.shadowNode);
calculateShadowViewMutationsForNewTree(
mutations, newChildPair.shadowView, newGrandChildPairs);
}
}
StubViewTree stubViewTreeFromShadowNode(ShadowNode const &rootShadowNode) {
auto mutations = ShadowViewMutation::List{};
mutations.reserve(256);
calculateShadowViewMutationsForNewTree(
mutations,
ShadowView(rootShadowNode),
sliceChildShadowNodeViewPairs(rootShadowNode));
auto emptyRootShadowNode = rootShadowNode.clone(
ShadowNodeFragment{ShadowNodeFragment::propsPlaceholder(),
ShadowNode::emptySharedShadowNodeSharedList()});
auto stubViewTree = StubViewTree(ShadowView(*emptyRootShadowNode));
stubViewTree.mutate(mutations);
return stubViewTree;
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,20 @@
/*
* 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 <react/core/ShadowNode.h>
#include "StubView.h"
#include "StubViewTree.h"
namespace facebook {
namespace react {
StubViewTree stubViewTreeFromShadowNode(ShadowNode const &rootShadowNode);
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,131 @@
/*
* 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 <algorithm>
#include <random>
namespace facebook {
namespace react {
/*
* The source of pseudo-random numbers and some problem-oriented tools built on
* top of that. We need this class to maintain a reproducible stream of random
* numbers and abstract away complex math of and C++ STL API behind that.
*/
class Entropy final {
public:
using Generator = std::mt19937;
/*
* Creates an instance seeded with a real, not pseudo-random, number.
*/
Entropy() {
std::random_device device;
seed_ = device();
generator_ = std::mt19937(seed_);
}
/*
* Creates an instance seeded with a given number.
*/
Entropy(uint_fast32_t seed) {
seed_ = seed;
generator_ = std::mt19937(seed_);
}
uint_fast32_t getSeed() const {
return seed_;
}
/*
* Family of methods that return uniformly distributed instances of a type
* within a specified range.
*/
template <typename T>
bool random() const {
T result;
generateRandomValue(generator_, result);
return result;
}
template <typename T, typename Arg1>
T random(Arg1 arg1) const {
T result;
generateRandomValue(generator_, result, arg1);
return result;
}
template <typename T, typename Arg1, typename Arg2>
T random(Arg1 arg1, Arg2 arg2) const {
T result;
generateRandomValue(generator_, result, arg1, arg2);
return result;
}
void generateRandomValue(
Generator &generator,
bool &result,
double ratio = 0.5) const {
result = generator() % 10000 < 10000 * ratio;
}
void generateRandomValue(Generator &generator, int &result, int min, int max)
const {
std::uniform_int_distribution<int> distribution(min, max);
result = distribution(generator);
}
/*
* Shuffles `std::vector` in place.
*/
template <typename T>
void shuffle(T array) const {
std::shuffle(array.begin(), array.end(), generator_);
}
/*
* Distribute items from a given array into buckets using a normal
* distribution and given `deviation`.
*/
template <typename T>
std::vector<std::vector<T>> distribute(std::vector<T> items, double deviation)
const {
std::normal_distribution<> distribution{0, deviation};
auto deviationLimit = int(deviation * 10);
auto spreadResult = std::vector<std::vector<T>>(deviationLimit * 2);
std::fill(spreadResult.begin(), spreadResult.end(), std::vector<T>{});
for (auto const &item : items) {
auto position = int(distribution(generator_) + deviationLimit);
position = std::max(0, std::min(position, deviationLimit * 2));
if (position < spreadResult.size()) {
spreadResult[position].push_back(item);
}
}
auto result = std::vector<std::vector<T>>{};
for (auto const &chunk : spreadResult) {
if (chunk.size() == 0) {
continue;
}
result.push_back(chunk);
}
return result;
}
private:
mutable std::mt19937 generator_;
uint_fast32_t seed_;
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,103 @@
/*
* 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 <chrono>
#include <thread>
#include <gtest/gtest.h>
#include <react/mounting/MountingTelemetry.h>
#include <react/utils/Telemetry.h>
using namespace facebook::react;
#define EXPECT_EQ_WITH_THRESHOLD(a, b, threshold) \
EXPECT_TRUE((a >= b - threshold) && (a <= b + threshold))
TEST(MountingTelemetryTest, timepoints) {
auto threshold = int64_t{100};
auto timepointA = telemetryTimePointNow();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
auto timepointB = telemetryTimePointNow();
auto duration = telemetryDurationToMilliseconds(timepointB - timepointA);
EXPECT_EQ_WITH_THRESHOLD(duration, 100, threshold);
}
TEST(MountingTelemetryTest, normalUseCase) {
auto threshold = int64_t{100};
auto telemetry = MountingTelemetry{};
telemetry.willCommit();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
telemetry.willLayout();
std::this_thread::sleep_for(std::chrono::milliseconds(200));
telemetry.didLayout();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
telemetry.didCommit();
std::this_thread::sleep_for(std::chrono::milliseconds(300));
telemetry.willMount();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
telemetry.didMount();
auto commitDuration = telemetryDurationToMilliseconds(
telemetry.getCommitEndTime() - telemetry.getCommitStartTime());
auto layoutDuration = telemetryDurationToMilliseconds(
telemetry.getLayoutEndTime() - telemetry.getLayoutStartTime());
auto mountDuration = telemetryDurationToMilliseconds(
telemetry.getMountEndTime() - telemetry.getMountStartTime());
EXPECT_EQ_WITH_THRESHOLD(commitDuration, 400, threshold * 2);
EXPECT_EQ_WITH_THRESHOLD(layoutDuration, 200, threshold);
EXPECT_EQ_WITH_THRESHOLD(mountDuration, 100, threshold);
}
TEST(MountingTelemetryTest, abnormalUseCases) {
// Calling `did` before `will` should crash.
EXPECT_DEATH_IF_SUPPORTED(
{
auto telemetry = MountingTelemetry{};
telemetry.didDiff();
},
"diffStartTime_");
EXPECT_DEATH_IF_SUPPORTED(
{
auto telemetry = MountingTelemetry{};
telemetry.didCommit();
},
"commitStartTime_");
EXPECT_DEATH_IF_SUPPORTED(
{
auto telemetry = MountingTelemetry{};
telemetry.didMount();
},
"mountStartTime_");
// Getting `start` *or* `end` timepoints before a pair of `will` and `did`
// should crash.
EXPECT_DEATH_IF_SUPPORTED(
{
auto telemetry = MountingTelemetry{};
telemetry.willCommit();
telemetry.getCommitStartTime();
},
"commitEndTime_");
EXPECT_DEATH_IF_SUPPORTED(
{
auto telemetry = MountingTelemetry{};
telemetry.willCommit();
telemetry.getCommitEndTime();
},
"commitEndTime_");
}

View File

@ -0,0 +1,325 @@
/*
* 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 <memory>
#include <react/components/root/RootComponentDescriptor.h>
#include <react/components/view/ViewComponentDescriptor.h>
#include <react/mounting/Differentiator.h>
#include <react/mounting/stubs.h>
#include "shadowTreeGeneration.h"
#include <Glog/logging.h>
#include <gtest/gtest.h>
namespace facebook {
namespace react {
static ShadowNode::Shared makeNode(
ComponentDescriptor const &componentDescriptor,
int tag,
ShadowNode::ListOfShared children) {
auto props = generateDefaultProps(componentDescriptor);
// Make sure node is layoutable by giving it dimensions and making it
// accessible This is an implementation detail and subject to change.
folly::dynamic dynamic = folly::dynamic::object();
dynamic["position"] = "absolute";
dynamic["top"] = 0;
dynamic["left"] = 0;
dynamic["width"] = 100;
dynamic["height"] = 100;
dynamic["nativeId"] = tag;
dynamic["accessible"] = true;
auto newProps = componentDescriptor.cloneProps(props, RawProps(dynamic));
return componentDescriptor.createShadowNode(
ShadowNodeFragment{newProps,
std::make_shared<SharedShadowNodeList>(children)},
componentDescriptor.createFamily({tag, SurfaceId(1), nullptr}, nullptr));
}
TEST(MountingTest, testMinimalInstructionGeneration) {
auto eventDispatcher = EventDispatcher::Shared{};
auto contextContainer = std::make_shared<ContextContainer>();
auto componentDescriptorParameters =
ComponentDescriptorParameters{eventDispatcher, contextContainer, nullptr};
auto viewComponentDescriptor =
ViewComponentDescriptor(componentDescriptorParameters);
auto rootComponentDescriptor =
RootComponentDescriptor(componentDescriptorParameters);
auto rootFamily = rootComponentDescriptor.createFamily(
{Tag(1), SurfaceId(1), nullptr}, nullptr);
// Creating an initial root shadow node.
auto emptyRootNode = std::const_pointer_cast<RootShadowNode>(
std::static_pointer_cast<RootShadowNode const>(
rootComponentDescriptor.createShadowNode(
ShadowNodeFragment{RootShadowNode::defaultSharedProps()},
rootFamily)));
// Applying size constraints.
emptyRootNode = emptyRootNode->clone(
LayoutConstraints{Size{512, 0},
Size{512, std::numeric_limits<Float>::infinity()}},
LayoutContext{});
auto childA = makeNode(viewComponentDescriptor, 100, {});
auto childB = makeNode(viewComponentDescriptor, 101, {});
auto childC = makeNode(viewComponentDescriptor, 102, {});
auto childD = makeNode(viewComponentDescriptor, 103, {});
auto childE = makeNode(viewComponentDescriptor, 104, {});
auto childF = makeNode(viewComponentDescriptor, 105, {});
auto family = viewComponentDescriptor.createFamily(
{10, SurfaceId(1), nullptr}, nullptr);
// Construct "identical" shadow nodes: they differ only in children.
auto shadowNodeV1 = viewComponentDescriptor.createShadowNode(
ShadowNodeFragment{generateDefaultProps(viewComponentDescriptor),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{childB, childC, childD})},
family);
auto shadowNodeV2 = shadowNodeV1->clone(ShadowNodeFragment{
generateDefaultProps(viewComponentDescriptor),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{childA, childB, childC, childD})});
auto shadowNodeV3 = shadowNodeV2->clone(
ShadowNodeFragment{generateDefaultProps(viewComponentDescriptor),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{childB, childC, childD})});
auto shadowNodeV4 = shadowNodeV3->clone(
ShadowNodeFragment{generateDefaultProps(viewComponentDescriptor),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{childB, childD, childE})});
auto shadowNodeV5 = shadowNodeV4->clone(ShadowNodeFragment{
generateDefaultProps(viewComponentDescriptor),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{childB, childA, childE, childC})});
auto shadowNodeV6 = shadowNodeV5->clone(ShadowNodeFragment{
generateDefaultProps(viewComponentDescriptor),
std::make_shared<SharedShadowNodeList>(SharedShadowNodeList{
childB, childA, childD, childF, childE, childC})});
// Injecting a tree into the root node.
auto rootNodeV1 = std::static_pointer_cast<RootShadowNode const>(
emptyRootNode->ShadowNode::clone(
ShadowNodeFragment{ShadowNodeFragment::propsPlaceholder(),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{shadowNodeV1})}));
auto rootNodeV2 = std::static_pointer_cast<RootShadowNode const>(
rootNodeV1->ShadowNode::clone(
ShadowNodeFragment{ShadowNodeFragment::propsPlaceholder(),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{shadowNodeV2})}));
auto rootNodeV3 = std::static_pointer_cast<RootShadowNode const>(
rootNodeV2->ShadowNode::clone(
ShadowNodeFragment{ShadowNodeFragment::propsPlaceholder(),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{shadowNodeV3})}));
auto rootNodeV4 = std::static_pointer_cast<RootShadowNode const>(
rootNodeV3->ShadowNode::clone(
ShadowNodeFragment{ShadowNodeFragment::propsPlaceholder(),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{shadowNodeV4})}));
auto rootNodeV5 = std::static_pointer_cast<RootShadowNode const>(
rootNodeV4->ShadowNode::clone(
ShadowNodeFragment{ShadowNodeFragment::propsPlaceholder(),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{shadowNodeV5})}));
auto rootNodeV6 = std::static_pointer_cast<RootShadowNode const>(
rootNodeV5->ShadowNode::clone(
ShadowNodeFragment{ShadowNodeFragment::propsPlaceholder(),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{shadowNodeV6})}));
// Layout and diff
std::vector<LayoutableShadowNode const *> affectedLayoutableNodesV1{};
affectedLayoutableNodesV1.reserve(1024);
std::const_pointer_cast<RootShadowNode>(rootNodeV1)
->layoutIfNeeded(&affectedLayoutableNodesV1);
rootNodeV1->sealRecursive();
std::vector<LayoutableShadowNode const *> affectedLayoutableNodesV2{};
affectedLayoutableNodesV2.reserve(1024);
std::const_pointer_cast<RootShadowNode>(rootNodeV2)
->layoutIfNeeded(&affectedLayoutableNodesV2);
rootNodeV2->sealRecursive();
std::vector<LayoutableShadowNode const *> affectedLayoutableNodesV3{};
affectedLayoutableNodesV3.reserve(1024);
std::const_pointer_cast<RootShadowNode>(rootNodeV3)
->layoutIfNeeded(&affectedLayoutableNodesV3);
rootNodeV3->sealRecursive();
std::vector<LayoutableShadowNode const *> affectedLayoutableNodesV4{};
affectedLayoutableNodesV4.reserve(1024);
std::const_pointer_cast<RootShadowNode>(rootNodeV4)
->layoutIfNeeded(&affectedLayoutableNodesV4);
rootNodeV4->sealRecursive();
std::vector<LayoutableShadowNode const *> affectedLayoutableNodesV5{};
affectedLayoutableNodesV5.reserve(1024);
std::const_pointer_cast<RootShadowNode>(rootNodeV5)
->layoutIfNeeded(&affectedLayoutableNodesV5);
rootNodeV5->sealRecursive();
std::vector<LayoutableShadowNode const *> affectedLayoutableNodesV6{};
affectedLayoutableNodesV6.reserve(1024);
std::const_pointer_cast<RootShadowNode>(rootNodeV6)
->layoutIfNeeded(&affectedLayoutableNodesV6);
rootNodeV6->sealRecursive();
// This block displays all the mutations for debugging purposes.
/*
LOG(ERROR) << "Num mutations: " << mutations.size();
for (auto const &mutation : mutations) {
switch (mutation.type) {
case ShadowViewMutation::Create: {
LOG(ERROR) << "CREATE " << mutation.newChildShadowView.tag;
break;
}
case ShadowViewMutation::Delete: {
LOG(ERROR) << "DELETE " << mutation.oldChildShadowView.tag;
break;
}
case ShadowViewMutation::Remove: {
LOG(ERROR) << "REMOVE " << mutation.oldChildShadowView.tag << " " <<
mutation.index; break;
}
case ShadowViewMutation::Insert: {
LOG(ERROR) << "INSERT " << mutation.newChildShadowView.tag << " " <<
mutation.index; break;
}
case ShadowViewMutation::Update: {
LOG(ERROR) << "UPDATE " << mutation.newChildShadowView.tag;
break;
}
}
}*/
// Calculating mutations.
auto mutations1 = calculateShadowViewMutations(
DifferentiatorMode::OptimizedMoves, *rootNodeV1, *rootNodeV2);
// The order and exact mutation instructions here may change at any time.
// This test just ensures that any changes are intentional.
// This test, in particular, ensures that inserting a node at the beginning
// produces a single "Insert" instruction, and no remove/insert (move)
// operations. All these nodes are laid out with absolute positioning, so
// moving them around does not change layout.
assert(mutations1.size() == 2);
assert(mutations1[0].type == ShadowViewMutation::Create);
assert(mutations1[0].newChildShadowView.tag == 100);
assert(mutations1[1].type == ShadowViewMutation::Insert);
assert(mutations1[1].newChildShadowView.tag == 100);
assert(mutations1[1].index == 0);
// Calculating mutations.
auto mutations2 = calculateShadowViewMutations(
DifferentiatorMode::OptimizedMoves, *rootNodeV2, *rootNodeV3);
// The order and exact mutation instructions here may change at any time.
// This test just ensures that any changes are intentional.
// This test, in particular, ensures that removing a node at the beginning
// produces a single remove (and delete) instruction, and no remove/insert
// (move) operations. All these nodes are laid out with absolute positioning,
// so moving them around does not change layout.
assert(mutations2.size() == 2);
assert(mutations2[0].type == ShadowViewMutation::Remove);
assert(mutations2[0].oldChildShadowView.tag == 100);
assert(mutations2[0].index == 0);
assert(mutations2[1].type == ShadowViewMutation::Delete);
assert(mutations2[1].oldChildShadowView.tag == 100);
// Calculating mutations.
auto mutations3 = calculateShadowViewMutations(
DifferentiatorMode::OptimizedMoves, *rootNodeV3, *rootNodeV4);
// The order and exact mutation instructions here may change at any time.
// This test just ensures that any changes are intentional.
// This test, in particular, ensures that removing a node in the middle
// produces a single remove (and delete) instruction, and no remove/insert
// (move) operations; and that simultaneously, we can insert a node at the
// end. NOTE: This list of mutations has some unexpected "Update"
// instructions, due to layout issues (some LayoutMetrics are 0). Not sure
// why, but the point of this test is to make sure there aren't unnecessary
// insert/deletes, so we can ignore for now.
assert(mutations3.size() == 7);
assert(mutations3[0].type == ShadowViewMutation::Update);
assert(mutations3[1].type == ShadowViewMutation::Update);
assert(mutations3[2].type == ShadowViewMutation::Update);
assert(mutations3[3].type == ShadowViewMutation::Remove);
assert(mutations3[3].oldChildShadowView.tag == 102);
assert(mutations3[3].index == 1);
assert(mutations3[4].type == ShadowViewMutation::Delete);
assert(mutations3[4].oldChildShadowView.tag == 102);
assert(mutations3[5].type == ShadowViewMutation::Create);
assert(mutations3[5].newChildShadowView.tag == 104);
assert(mutations3[6].type == ShadowViewMutation::Insert);
assert(mutations3[6].newChildShadowView.tag == 104);
assert(mutations3[6].index == 2);
// Calculating mutations.
auto mutations4 = calculateShadowViewMutations(
DifferentiatorMode::OptimizedMoves, *rootNodeV4, *rootNodeV5);
// The order and exact mutation instructions here may change at any time.
// This test just ensures that any changes are intentional.
// This test, in particular, ensures that inserting a child at the middle, and
// at the end, and removing a node in the middle, produces the minimal set of
// instructions. All these nodes are laid out with absolute positioning, so
// moving them around does not change layout. NOTE: This list of mutations has
// some unexpected "Update" instructions, due to layout issues (some
// LayoutMetrics are 0). Not sure why, but the point of this test is to make
// sure there aren't unnecessary insert/deletes, so we can ignore for now.
assert(mutations4.size() == 9);
assert(mutations4[0].type == ShadowViewMutation::Update);
assert(mutations4[1].type == ShadowViewMutation::Update);
assert(mutations4[2].type == ShadowViewMutation::Update);
assert(mutations4[3].type == ShadowViewMutation::Remove);
assert(mutations4[3].oldChildShadowView.tag == 103);
assert(mutations4[3].index == 1);
assert(mutations4[4].type == ShadowViewMutation::Delete);
assert(mutations4[4].oldChildShadowView.tag == 103);
assert(mutations4[5].type == ShadowViewMutation::Create);
assert(mutations4[5].newChildShadowView.tag == 100);
assert(mutations4[6].type == ShadowViewMutation::Create);
assert(mutations4[6].newChildShadowView.tag == 102);
assert(mutations4[7].type == ShadowViewMutation::Insert);
assert(mutations4[7].newChildShadowView.tag == 100);
assert(mutations4[7].index == 1);
assert(mutations4[8].type == ShadowViewMutation::Insert);
assert(mutations4[8].newChildShadowView.tag == 102);
assert(mutations4[8].index == 3);
auto mutations5 = calculateShadowViewMutations(
DifferentiatorMode::OptimizedMoves, *rootNodeV5, *rootNodeV6);
// The order and exact mutation instructions here may change at any time.
// This test just ensures that any changes are intentional.
// This test, in particular, ensures that inserting TWO children in the middle
// produces the minimal set of instructions. All these nodes are laid out with
// absolute positioning, so moving them around does not change layout.
assert(mutations5.size() == 4);
assert(mutations5[0].type == ShadowViewMutation::Create);
assert(mutations5[0].newChildShadowView.tag == 103);
assert(mutations5[1].type == ShadowViewMutation::Create);
assert(mutations5[1].newChildShadowView.tag == 105);
assert(mutations5[2].type == ShadowViewMutation::Insert);
assert(mutations5[2].newChildShadowView.tag == 103);
assert(mutations5[2].index == 2);
assert(mutations5[3].type == ShadowViewMutation::Insert);
assert(mutations5[3].newChildShadowView.tag == 105);
assert(mutations5[3].index == 3);
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,181 @@
/*
* 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 <glog/logging.h>
#include <gtest/gtest.h>
#include <react/components/root/RootComponentDescriptor.h>
#include <react/components/view/ViewComponentDescriptor.h>
#include <react/mounting/Differentiator.h>
#include <react/mounting/stubs.h>
#include "Entropy.h"
#include "shadowTreeGeneration.h"
namespace facebook {
namespace react {
static void testShadowNodeTreeLifeCycle(
DifferentiatorMode differentiatorMode,
uint_fast32_t seed,
int treeSize,
int repeats,
int stages) {
auto entropy = seed == 0 ? Entropy() : Entropy(seed);
auto eventDispatcher = EventDispatcher::Shared{};
auto contextContainer = std::make_shared<ContextContainer>();
auto componentDescriptorParameters =
ComponentDescriptorParameters{eventDispatcher, contextContainer, nullptr};
auto viewComponentDescriptor =
ViewComponentDescriptor(componentDescriptorParameters);
auto rootComponentDescriptor =
RootComponentDescriptor(componentDescriptorParameters);
auto noopEventEmitter =
std::make_shared<ViewEventEmitter const>(nullptr, -1, eventDispatcher);
auto allNodes = std::vector<ShadowNode::Shared>{};
for (int i = 0; i < repeats; i++) {
allNodes.clear();
auto family = rootComponentDescriptor.createFamily(
{Tag(1), SurfaceId(1), nullptr}, nullptr);
// Creating an initial root shadow node.
auto emptyRootNode = std::const_pointer_cast<RootShadowNode>(
std::static_pointer_cast<RootShadowNode const>(
rootComponentDescriptor.createShadowNode(
ShadowNodeFragment{RootShadowNode::defaultSharedProps()},
family)));
// Applying size constraints.
emptyRootNode = emptyRootNode->clone(
LayoutConstraints{Size{512, 0},
Size{512, std::numeric_limits<Float>::infinity()}},
LayoutContext{});
// Generation of a random tree.
auto singleRootChildNode =
generateShadowNodeTree(entropy, viewComponentDescriptor, treeSize);
// Injecting a tree into the root node.
auto currentRootNode = std::static_pointer_cast<RootShadowNode const>(
emptyRootNode->ShadowNode::clone(ShadowNodeFragment{
ShadowNodeFragment::propsPlaceholder(),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{singleRootChildNode})}));
// Building an initial view hierarchy.
auto viewTree = stubViewTreeFromShadowNode(*emptyRootNode);
viewTree.mutate(calculateShadowViewMutations(
differentiatorMode, *emptyRootNode, *currentRootNode));
for (int j = 0; j < stages; j++) {
auto nextRootNode = currentRootNode;
// Mutating the tree.
alterShadowTree(
entropy,
nextRootNode,
{
&messWithChildren,
&messWithYogaStyles,
&messWithLayotableOnlyFlag,
});
std::vector<LayoutableShadowNode const *> affectedLayoutableNodes{};
affectedLayoutableNodes.reserve(1024);
// Laying out the tree.
std::const_pointer_cast<RootShadowNode>(nextRootNode)
->layoutIfNeeded(&affectedLayoutableNodes);
nextRootNode->sealRecursive();
allNodes.push_back(nextRootNode);
// Calculating mutations.
auto mutations = calculateShadowViewMutations(
differentiatorMode, *currentRootNode, *nextRootNode);
// Mutating the view tree.
viewTree.mutate(mutations);
// Building a view tree to compare with.
auto rebuiltViewTree = stubViewTreeFromShadowNode(*nextRootNode);
// Comparing the newly built tree with the updated one.
if (rebuiltViewTree != viewTree) {
// Something went wrong.
LOG(ERROR) << "Entropy seed: " << entropy.getSeed() << "\n";
LOG(ERROR) << "Shadow Tree before: \n"
<< currentRootNode->getDebugDescription();
LOG(ERROR) << "Shadow Tree after: \n"
<< nextRootNode->getDebugDescription();
LOG(ERROR) << "View Tree before: \n"
<< getDebugDescription(viewTree.getRootStubView(), {});
LOG(ERROR) << "View Tree after: \n"
<< getDebugDescription(
rebuiltViewTree.getRootStubView(), {});
LOG(ERROR) << "Mutations:"
<< "\n"
<< getDebugDescription(mutations, {});
FAIL();
}
currentRootNode = nextRootNode;
}
}
SUCCEED();
}
} // namespace react
} // namespace facebook
using namespace facebook::react;
TEST(MountingTest, stableBiggerTreeFewerIterationsClassic) {
testShadowNodeTreeLifeCycle(
DifferentiatorMode::Classic,
/* seed */ 1,
/* size */ 512,
/* repeats */ 32,
/* stages */ 32);
}
TEST(MountingTest, stableSmallerTreeMoreIterationsClassic) {
testShadowNodeTreeLifeCycle(
DifferentiatorMode::Classic,
/* seed */ 1,
/* size */ 16,
/* repeats */ 512,
/* stages */ 32);
}
TEST(MountingTest, stableBiggerTreeFewerIterationsOptimizedMoves) {
testShadowNodeTreeLifeCycle(
DifferentiatorMode::OptimizedMoves,
/* seed */ 1,
/* size */ 512,
/* repeats */ 32,
/* stages */ 32);
}
TEST(MountingTest, stableSmallerTreeMoreIterationsOptimizedMoves) {
testShadowNodeTreeLifeCycle(
DifferentiatorMode::OptimizedMoves,
/* seed */ 1,
/* size */ 16,
/* repeats */ 512,
/* stages */ 32);
}

View File

@ -0,0 +1,259 @@
/*
* 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 <glog/logging.h>
#include <algorithm>
#include <iostream>
#include <memory>
#include <random>
#include <react/mounting/Differentiator.h>
#include <react/mounting/stubs.h>
#include <react/components/root/RootComponentDescriptor.h>
#include <react/components/view/ViewComponentDescriptor.h>
#include "Entropy.h"
namespace facebook {
namespace react {
static Tag generateReactTag() {
static Tag tag = 1000;
return tag++;
}
class ShadowTreeEdge final {
public:
ShadowNode::Shared shadowNode{nullptr};
ShadowNode::Shared parentShadowNode{nullptr};
int index{0};
};
static bool traverseShadowTree(
ShadowNode::Shared const &parentShadowNode,
std::function<void(ShadowTreeEdge const &edge, bool &stop)> const
&callback) {
auto index = int{0};
for (auto const &childNode : parentShadowNode->getChildren()) {
auto stop = bool{false};
callback(ShadowTreeEdge{childNode, parentShadowNode, index}, stop);
if (stop) {
return true;
}
if (traverseShadowTree(childNode, callback)) {
return true;
}
index++;
}
return false;
}
static int countShadowNodes(ShadowNode::Shared const &rootShadowNode) {
auto counter = int{0};
traverseShadowTree(
rootShadowNode,
[&](ShadowTreeEdge const &edge, bool &stop) { counter++; });
return counter;
}
static ShadowTreeEdge findShadowNodeWithIndex(
ShadowNode::Shared const &rootNode,
int index) {
auto counter = int{0};
auto result = ShadowTreeEdge{};
traverseShadowTree(rootNode, [&](ShadowTreeEdge const &edge, bool &stop) {
if (index == counter) {
result = edge;
}
counter++;
});
return result;
}
static ShadowTreeEdge findRandomShadowNode(
Entropy const &entropy,
ShadowNode::Shared const &rootShadowNode) {
auto count = countShadowNodes(rootShadowNode);
return findShadowNodeWithIndex(
rootShadowNode,
entropy.random<int>(1 /* Excluding a root node */, count - 1));
}
static ShadowNode::ListOfShared cloneSharedShadowNodeList(
ShadowNode::ListOfShared const &list) {
auto result = ShadowNode::ListOfShared{};
result.reserve(list.size());
for (auto const &shadowNode : list) {
result.push_back(shadowNode->clone({}));
}
return result;
}
static inline ShadowNode::Unshared messWithChildren(
Entropy const &entropy,
ShadowNode const &shadowNode) {
auto children = shadowNode.getChildren();
children = cloneSharedShadowNodeList(children);
entropy.shuffle(children);
return shadowNode.clone(
{ShadowNodeFragment::propsPlaceholder(),
std::make_shared<ShadowNode::ListOfShared const>(children)});
}
static inline ShadowNode::Unshared messWithLayotableOnlyFlag(
Entropy const &entropy,
ShadowNode const &shadowNode) {
auto oldProps = shadowNode.getProps();
auto newProps = shadowNode.getComponentDescriptor().cloneProps(
oldProps, RawProps(folly::dynamic::object()));
auto &viewProps =
const_cast<ViewProps &>(static_cast<ViewProps const &>(*newProps));
if (entropy.random<bool>(0.1)) {
viewProps.nativeId = entropy.random<bool>() ? "42" : "";
}
if (entropy.random<bool>(0.1)) {
viewProps.backgroundColor =
entropy.random<bool>() ? SharedColor() : whiteColor();
}
if (entropy.random<bool>(0.1)) {
viewProps.foregroundColor =
entropy.random<bool>() ? SharedColor() : blackColor();
}
if (entropy.random<bool>(0.1)) {
viewProps.shadowColor =
entropy.random<bool>() ? SharedColor() : blackColor();
}
if (entropy.random<bool>(0.1)) {
viewProps.accessible = entropy.random<bool>();
}
if (entropy.random<bool>(0.1)) {
viewProps.zIndex = entropy.random<bool>() ? 1 : 0;
}
if (entropy.random<bool>(0.1)) {
viewProps.pointerEvents = entropy.random<bool>() ? PointerEventsMode::Auto
: PointerEventsMode::None;
}
if (entropy.random<bool>(0.1)) {
viewProps.transform = entropy.random<bool>() ? Transform::Identity()
: Transform::Perspective(42);
}
return shadowNode.clone({newProps});
}
static inline ShadowNode::Unshared messWithYogaStyles(
Entropy const &entropy,
ShadowNode const &shadowNode) {
folly::dynamic dynamic = folly::dynamic::object();
if (entropy.random<bool>()) {
dynamic["flexDirection"] = entropy.random<bool>() ? "row" : "column";
}
std::vector<std::string> properties = {
"flex", "flexGrow", "flexShrink", "flexBasis",
"left", "top", "marginLeft", "marginTop",
"marginRight", "marginBottom", "paddingLeft", "paddingTop",
"paddingRight", "paddingBottom", "width", "height",
"maxWidth", "maxHeight", "minWidth", "minHeight",
};
for (auto const &property : properties) {
if (entropy.random<bool>(0.1)) {
dynamic[property] = entropy.random<int>(0, 1024);
}
}
auto oldProps = shadowNode.getProps();
auto newProps = shadowNode.getComponentDescriptor().cloneProps(
oldProps, RawProps(dynamic));
return shadowNode.clone({newProps});
}
using ShadowNodeAlteration = std::function<
ShadowNode::Unshared(Entropy const &entropy, ShadowNode const &shadowNode)>;
static inline void alterShadowTree(
Entropy const &entropy,
RootShadowNode::Shared &rootShadowNode,
ShadowNodeAlteration alteration) {
auto edge = findRandomShadowNode(entropy, rootShadowNode);
rootShadowNode =
std::static_pointer_cast<RootShadowNode>(rootShadowNode->cloneTree(
edge.shadowNode->getFamily(), [&](ShadowNode const &oldShadowNode) {
return alteration(entropy, oldShadowNode);
}));
}
static inline void alterShadowTree(
Entropy const &entropy,
RootShadowNode::Shared &rootShadowNode,
std::vector<ShadowNodeAlteration> alterations) {
auto i = entropy.random<int>(0, alterations.size() - 1);
alterShadowTree(entropy, rootShadowNode, alterations[i]);
}
static SharedViewProps generateDefaultProps(
ComponentDescriptor const &componentDescriptor) {
return std::static_pointer_cast<ViewProps const>(
componentDescriptor.cloneProps(nullptr, RawProps{}));
}
static inline ShadowNode::Shared generateShadowNodeTree(
Entropy const &entropy,
ComponentDescriptor const &componentDescriptor,
int size,
int deviation = 3) {
if (size <= 1) {
auto family = componentDescriptor.createFamily(
{generateReactTag(), SurfaceId(1), nullptr}, nullptr);
return componentDescriptor.createShadowNode(
ShadowNodeFragment{generateDefaultProps(componentDescriptor)}, family);
}
auto items = std::vector<int>(size);
std::fill(items.begin(), items.end(), 1);
auto chunks = entropy.distribute(items, deviation);
auto children = ShadowNode::ListOfShared{};
for (auto const &chunk : chunks) {
children.push_back(
generateShadowNodeTree(entropy, componentDescriptor, chunk.size()));
}
auto family = componentDescriptor.createFamily(
{generateReactTag(), SurfaceId(1), nullptr}, nullptr);
return componentDescriptor.createShadowNode(
ShadowNodeFragment{generateDefaultProps(componentDescriptor),
std::make_shared<SharedShadowNodeList>(children)},
family);
}
} // namespace react
} // namespace facebook