2018-09-12 01:27:47 +03:00
|
|
|
// Copyright (c) Facebook, Inc. and its affiliates.
|
2018-06-01 01:31:03 +03:00
|
|
|
|
|
|
|
// This source code is licensed under the MIT license found in the
|
|
|
|
// LICENSE file in the root directory of this source tree.
|
2018-04-10 22:45:59 +03:00
|
|
|
|
|
|
|
#include "Differentiator.h"
|
|
|
|
|
2019-02-08 23:28:56 +03:00
|
|
|
#include <better/map.h>
|
2019-05-03 22:27:24 +03:00
|
|
|
#include <better/small_vector.h>
|
2018-11-11 01:19:08 +03:00
|
|
|
#include <react/core/LayoutableShadowNode.h>
|
2019-02-12 02:51:34 +03:00
|
|
|
#include <react/debug/SystraceSection.h>
|
2018-10-10 02:25:13 +03:00
|
|
|
#include "ShadowView.h"
|
2018-09-04 08:53:18 +03:00
|
|
|
|
2018-04-10 22:45:59 +03:00
|
|
|
namespace facebook {
|
|
|
|
namespace react {
|
|
|
|
|
2019-05-03 22:27:24 +03:00
|
|
|
/*
|
|
|
|
* 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 *;
|
|
|
|
|
|
|
|
inline Iterator begin() {
|
|
|
|
return (Pair *)vector_;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline Iterator end() {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline Iterator find(KeyT key) {
|
|
|
|
for (auto &item : vector_) {
|
|
|
|
if (item.first == key) {
|
|
|
|
return &item;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return end();
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void insert(Pair pair) {
|
|
|
|
assert(pair.first != 0);
|
|
|
|
vector_.push_back(pair);
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void erase(Iterator iterator) {
|
|
|
|
static_assert(
|
|
|
|
std::is_same<KeyT, Tag>::value,
|
|
|
|
"The collection is designed to store only `Tag`s as keys.");
|
|
|
|
// Zero is a invalid tag.
|
|
|
|
iterator->first = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
better::small_vector<Pair, DefaultSize> vector_;
|
|
|
|
};
|
|
|
|
|
2018-10-10 02:25:13 +03:00
|
|
|
static void sliceChildShadowNodeViewPairsRecursively(
|
2019-05-03 22:27:24 +03:00
|
|
|
ShadowViewNodePair::List &pairList,
|
2018-10-10 02:25:13 +03:00
|
|
|
Point layoutOffset,
|
2019-05-03 22:27:24 +03:00
|
|
|
ShadowNode const &shadowNode) {
|
|
|
|
for (auto const &childShadowNode : shadowNode.getChildren()) {
|
2018-09-04 08:53:26 +03:00
|
|
|
auto shadowView = ShadowView(*childShadowNode);
|
|
|
|
|
2019-05-03 22:27:24 +03:00
|
|
|
auto const layoutableShadowNode =
|
|
|
|
dynamic_cast<LayoutableShadowNode const *>(childShadowNode.get());
|
2018-11-26 09:15:00 +03:00
|
|
|
#ifndef ANDROID
|
|
|
|
// New approach (iOS):
|
|
|
|
// Non-view components are treated as layout-only views (they aren't
|
|
|
|
// represented as `ShadowView`s).
|
|
|
|
if (!layoutableShadowNode || layoutableShadowNode->isLayoutOnly()) {
|
|
|
|
#else
|
|
|
|
// Previous approach (Android):
|
|
|
|
// Non-view components are treated as normal views with an empty layout
|
|
|
|
// (they are represented as `ShadowView`s).
|
2018-09-04 08:53:26 +03:00
|
|
|
if (layoutableShadowNode && layoutableShadowNode->isLayoutOnly()) {
|
2018-11-26 09:15:00 +03:00
|
|
|
#endif
|
2018-09-04 08:53:26 +03:00
|
|
|
sliceChildShadowNodeViewPairsRecursively(
|
2018-10-10 02:25:13 +03:00
|
|
|
pairList,
|
|
|
|
layoutOffset + shadowView.layoutMetrics.frame.origin,
|
|
|
|
*childShadowNode);
|
2018-09-04 08:53:26 +03:00
|
|
|
} else {
|
|
|
|
shadowView.layoutMetrics.frame.origin += layoutOffset;
|
2019-03-04 20:57:01 +03:00
|
|
|
pairList.push_back({shadowView, childShadowNode.get()});
|
2018-09-04 08:53:26 +03:00
|
|
|
}
|
2018-09-04 08:53:18 +03:00
|
|
|
}
|
2018-09-04 08:53:26 +03:00
|
|
|
}
|
2018-09-04 08:53:18 +03:00
|
|
|
|
2019-05-03 22:27:24 +03:00
|
|
|
static ShadowViewNodePair::List sliceChildShadowNodeViewPairs(
|
|
|
|
ShadowNode const &shadowNode) {
|
|
|
|
auto pairList = ShadowViewNodePair::List{};
|
2018-09-04 08:53:26 +03:00
|
|
|
sliceChildShadowNodeViewPairsRecursively(pairList, {0, 0}, shadowNode);
|
2018-09-04 08:53:18 +03:00
|
|
|
return pairList;
|
|
|
|
}
|
|
|
|
|
2019-06-24 07:30:00 +03:00
|
|
|
/*
|
|
|
|
* 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_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`.");
|
|
|
|
|
2018-09-04 08:53:18 +03:00
|
|
|
static void calculateShadowViewMutations(
|
2019-05-03 22:27:24 +03:00
|
|
|
ShadowViewMutation::List &mutations,
|
|
|
|
ShadowView const &parentShadowView,
|
|
|
|
ShadowViewNodePair::List const &oldChildPairs,
|
|
|
|
ShadowViewNodePair::List const &newChildPairs) {
|
2018-04-10 22:45:59 +03:00
|
|
|
// The current version of the algorithm is otimized for simplicity,
|
2018-09-04 08:53:18 +03:00
|
|
|
// not for performance or optimal result.
|
2018-04-10 22:45:59 +03:00
|
|
|
|
2018-09-04 08:53:18 +03:00
|
|
|
if (oldChildPairs == newChildPairs) {
|
2018-04-10 22:45:59 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-09-04 08:53:18 +03:00
|
|
|
if (oldChildPairs.size() == 0 && newChildPairs.size() == 0) {
|
2018-04-10 22:45:59 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-05-03 22:27:24 +03:00
|
|
|
auto index = int{0};
|
|
|
|
|
|
|
|
// Maps inserted node tags to pointers to them in `newChildPairs`.
|
2019-05-03 22:27:24 +03:00
|
|
|
auto insertedPairs = TinyMap<Tag, ShadowViewNodePair const *>{};
|
2018-04-10 22:45:59 +03:00
|
|
|
|
2019-05-03 22:27:24 +03:00
|
|
|
// 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{};
|
2018-04-10 22:45:59 +03:00
|
|
|
|
2018-09-04 08:53:18 +03:00
|
|
|
// Stage 1: Collecting `Update` mutations
|
2018-10-10 02:25:13 +03:00
|
|
|
for (index = 0; index < oldChildPairs.size() && index < newChildPairs.size();
|
|
|
|
index++) {
|
2019-05-03 22:27:24 +03:00
|
|
|
auto const &oldChildPair = oldChildPairs[index];
|
|
|
|
auto const &newChildPair = newChildPairs[index];
|
2018-04-10 22:45:59 +03:00
|
|
|
|
2018-09-04 08:53:18 +03:00
|
|
|
if (oldChildPair.shadowView.tag != newChildPair.shadowView.tag) {
|
2018-04-10 22:45:59 +03:00
|
|
|
// Totally different nodes, updating is impossible.
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2018-09-04 08:53:18 +03:00
|
|
|
if (oldChildPair.shadowView != newChildPair.shadowView) {
|
2018-10-10 02:25:13 +03:00
|
|
|
updateMutations.push_back(ShadowViewMutation::UpdateMutation(
|
2018-09-04 08:53:18 +03:00
|
|
|
parentShadowView,
|
|
|
|
oldChildPair.shadowView,
|
|
|
|
newChildPair.shadowView,
|
2018-10-10 02:25:13 +03:00
|
|
|
index));
|
2018-04-10 22:45:59 +03:00
|
|
|
}
|
|
|
|
|
2019-05-03 22:27:24 +03:00
|
|
|
auto const oldGrandChildPairs =
|
2019-03-04 20:57:01 +03:00
|
|
|
sliceChildShadowNodeViewPairs(*oldChildPair.shadowNode);
|
2019-05-03 22:27:24 +03:00
|
|
|
auto const newGrandChildPairs =
|
2019-03-04 20:57:01 +03:00
|
|
|
sliceChildShadowNodeViewPairs(*newChildPair.shadowNode);
|
2018-09-04 08:53:18 +03:00
|
|
|
calculateShadowViewMutations(
|
2018-10-10 02:25:13 +03:00
|
|
|
*(newGrandChildPairs.size() ? &downwardMutations
|
|
|
|
: &destructiveDownwardMutations),
|
|
|
|
oldChildPair.shadowView,
|
|
|
|
oldGrandChildPairs,
|
|
|
|
newGrandChildPairs);
|
2018-04-10 22:45:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
int lastIndexAfterFirstStage = index;
|
|
|
|
|
2018-09-04 08:53:18 +03:00
|
|
|
// Stage 2: Collecting `Insert` mutations
|
|
|
|
for (; index < newChildPairs.size(); index++) {
|
2019-05-03 22:27:24 +03:00
|
|
|
auto const &newChildPair = newChildPairs[index];
|
2018-04-10 22:45:59 +03:00
|
|
|
|
2018-10-10 02:25:13 +03:00
|
|
|
insertMutations.push_back(ShadowViewMutation::InsertMutation(
|
2019-06-24 07:30:00 +03:00
|
|
|
parentShadowView, newChildPair.shadowView, index));
|
2018-04-10 22:45:59 +03:00
|
|
|
|
2019-05-03 22:27:24 +03:00
|
|
|
insertedPairs.insert({newChildPair.shadowView.tag, &newChildPair});
|
2018-04-10 22:45:59 +03:00
|
|
|
}
|
|
|
|
|
2018-09-04 08:53:18 +03:00
|
|
|
// Stage 3: Collecting `Delete` and `Remove` mutations
|
2018-10-10 02:25:13 +03:00
|
|
|
for (index = lastIndexAfterFirstStage; index < oldChildPairs.size();
|
|
|
|
index++) {
|
2019-05-03 22:27:24 +03:00
|
|
|
auto const &oldChildPair = oldChildPairs[index];
|
2018-04-10 22:45:59 +03:00
|
|
|
|
2018-09-04 08:53:18 +03:00
|
|
|
// Even if the old view was (re)inserted, we have to generate `remove`
|
|
|
|
// mutation.
|
2018-10-10 02:25:13 +03:00
|
|
|
removeMutations.push_back(ShadowViewMutation::RemoveMutation(
|
|
|
|
parentShadowView, oldChildPair.shadowView, index));
|
2018-04-10 22:46:07 +03:00
|
|
|
|
2019-05-03 22:27:24 +03:00
|
|
|
auto const it = insertedPairs.find(oldChildPair.shadowView.tag);
|
2018-04-10 22:45:59 +03:00
|
|
|
|
2019-02-08 23:28:56 +03:00
|
|
|
if (it == insertedPairs.end()) {
|
2018-09-04 08:53:18 +03:00
|
|
|
// The old view was *not* (re)inserted.
|
|
|
|
// We have to generate `delete` mutation and apply the algorithm
|
2018-04-10 22:45:59 +03:00
|
|
|
// recursively.
|
2018-09-04 08:53:18 +03:00
|
|
|
deleteMutations.push_back(
|
2018-10-10 02:25:13 +03:00
|
|
|
ShadowViewMutation::DeleteMutation(oldChildPair.shadowView));
|
2018-04-10 22:45:59 +03:00
|
|
|
|
2018-07-07 00:34:40 +03:00
|
|
|
// We also have to call the algorithm recursively to clean up the entire
|
2018-09-04 08:53:18 +03:00
|
|
|
// subtree starting from the removed view.
|
|
|
|
calculateShadowViewMutations(
|
2018-10-10 02:25:13 +03:00
|
|
|
destructiveDownwardMutations,
|
|
|
|
oldChildPair.shadowView,
|
2019-03-04 20:57:01 +03:00
|
|
|
sliceChildShadowNodeViewPairs(*oldChildPair.shadowNode),
|
2018-10-10 02:25:13 +03:00
|
|
|
{});
|
2018-07-07 00:34:40 +03:00
|
|
|
} else {
|
2018-09-04 08:53:18 +03:00
|
|
|
// The old view *was* (re)inserted.
|
|
|
|
// We have to call the algorithm recursively if the inserted view
|
2018-07-07 00:34:40 +03:00
|
|
|
// is *not* the same as removed one.
|
2019-05-03 22:27:24 +03:00
|
|
|
auto const &newChildPair = *it->second;
|
|
|
|
|
2019-03-30 22:18:47 +03:00
|
|
|
if (newChildPair != oldChildPair) {
|
2019-05-03 22:27:24 +03:00
|
|
|
auto const oldGrandChildPairs =
|
2019-03-04 20:57:01 +03:00
|
|
|
sliceChildShadowNodeViewPairs(*oldChildPair.shadowNode);
|
2019-05-03 22:27:24 +03:00
|
|
|
auto const newGrandChildPairs =
|
2019-03-04 20:57:01 +03:00
|
|
|
sliceChildShadowNodeViewPairs(*newChildPair.shadowNode);
|
2018-09-04 08:53:18 +03:00
|
|
|
calculateShadowViewMutations(
|
2018-10-10 02:25:13 +03:00
|
|
|
*(newGrandChildPairs.size() ? &downwardMutations
|
|
|
|
: &destructiveDownwardMutations),
|
|
|
|
newChildPair.shadowView,
|
|
|
|
oldGrandChildPairs,
|
|
|
|
newGrandChildPairs);
|
2018-07-07 00:34:40 +03:00
|
|
|
}
|
|
|
|
|
2019-02-08 23:28:56 +03:00
|
|
|
// In any case we have to remove the view from `insertedPairs` as
|
2018-09-04 08:53:18 +03:00
|
|
|
// indication that the view was actually removed (which means that
|
|
|
|
// the view existed before), hence we don't have to generate
|
|
|
|
// `create` mutation.
|
2019-02-08 23:28:56 +03:00
|
|
|
insertedPairs.erase(it);
|
2018-04-10 22:45:59 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-04 08:53:18 +03:00
|
|
|
// Stage 4: Collecting `Create` mutations
|
2018-10-10 02:25:13 +03:00
|
|
|
for (index = lastIndexAfterFirstStage; index < newChildPairs.size();
|
|
|
|
index++) {
|
2019-05-03 22:27:24 +03:00
|
|
|
auto const &newChildPair = newChildPairs[index];
|
2018-07-07 00:34:40 +03:00
|
|
|
|
2019-02-08 23:28:56 +03:00
|
|
|
if (insertedPairs.find(newChildPair.shadowView.tag) ==
|
|
|
|
insertedPairs.end()) {
|
2018-09-04 08:53:18 +03:00
|
|
|
// The new view was (re)inserted, so there is no need to create it.
|
2018-04-10 22:45:59 +03:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-09-04 08:53:18 +03:00
|
|
|
createMutations.push_back(
|
2018-10-10 02:25:13 +03:00
|
|
|
ShadowViewMutation::CreateMutation(newChildPair.shadowView));
|
2018-07-07 00:34:40 +03:00
|
|
|
|
2018-09-04 08:53:18 +03:00
|
|
|
calculateShadowViewMutations(
|
2018-10-10 02:25:13 +03:00
|
|
|
downwardMutations,
|
|
|
|
newChildPair.shadowView,
|
|
|
|
{},
|
2019-03-04 20:57:01 +03:00
|
|
|
sliceChildShadowNodeViewPairs(*newChildPair.shadowNode));
|
2018-04-10 22:45:59 +03:00
|
|
|
}
|
|
|
|
|
2018-09-04 08:53:18 +03:00
|
|
|
// All mutations in an optimal order:
|
2019-05-03 22:27:24 +03:00
|
|
|
std::move(
|
2018-10-10 02:25:13 +03:00
|
|
|
destructiveDownwardMutations.begin(),
|
2019-05-03 22:27:24 +03:00
|
|
|
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));
|
2019-06-12 17:30:13 +03:00
|
|
|
std::move(
|
|
|
|
downwardMutations.begin(),
|
|
|
|
downwardMutations.end(),
|
|
|
|
std::back_inserter(mutations));
|
2019-06-21 21:36:56 +03:00
|
|
|
std::move(
|
|
|
|
insertMutations.begin(),
|
|
|
|
insertMutations.end(),
|
|
|
|
std::back_inserter(mutations));
|
2018-04-10 22:45:59 +03:00
|
|
|
}
|
|
|
|
|
2019-05-03 22:27:24 +03:00
|
|
|
ShadowViewMutation::List calculateShadowViewMutations(
|
|
|
|
ShadowNode const &oldRootShadowNode,
|
|
|
|
ShadowNode const &newRootShadowNode) {
|
2019-02-12 02:51:34 +03:00
|
|
|
SystraceSection s("calculateShadowViewMutations");
|
|
|
|
|
2019-03-19 09:38:11 +03:00
|
|
|
// Root shadow nodes must be belong the same family.
|
|
|
|
assert(ShadowNode::sameFamily(oldRootShadowNode, newRootShadowNode));
|
2018-09-04 08:53:18 +03:00
|
|
|
|
2019-05-03 22:27:24 +03:00
|
|
|
auto mutations = ShadowViewMutation::List{};
|
2019-05-03 22:27:24 +03:00
|
|
|
mutations.reserve(256);
|
2018-09-04 08:53:18 +03:00
|
|
|
|
2018-09-08 07:44:18 +03:00
|
|
|
auto oldRootShadowView = ShadowView(oldRootShadowNode);
|
|
|
|
auto newRootShadowView = ShadowView(newRootShadowNode);
|
|
|
|
|
|
|
|
if (oldRootShadowView != newRootShadowView) {
|
2018-10-10 02:25:13 +03:00
|
|
|
mutations.push_back(ShadowViewMutation::UpdateMutation(
|
|
|
|
ShadowView(), oldRootShadowView, newRootShadowView, -1));
|
2018-05-16 09:32:34 +03:00
|
|
|
}
|
|
|
|
|
2018-09-04 08:53:18 +03:00
|
|
|
calculateShadowViewMutations(
|
2018-10-10 02:25:13 +03:00
|
|
|
mutations,
|
|
|
|
ShadowView(oldRootShadowNode),
|
|
|
|
sliceChildShadowNodeViewPairs(oldRootShadowNode),
|
|
|
|
sliceChildShadowNodeViewPairs(newRootShadowNode));
|
2018-09-04 08:53:18 +03:00
|
|
|
|
|
|
|
return mutations;
|
2018-05-16 09:32:34 +03:00
|
|
|
}
|
|
|
|
|
2018-04-10 22:45:59 +03:00
|
|
|
} // namespace react
|
|
|
|
} // namespace facebook
|