Valentin Shergin b8947c459f Fabric: ShadowTree::synchronize and reliable constraintLayout
Summary:
New `ShadowTree::synchronize` method allows to perform operations on ShadowTree without a risk of an unsuccessful commit. To make it happen, the `commitMutex_` is now recursive and `synchronize` acquires it before calling the callback.
Using that we finally can implement reliable `constraintLayout`.

Reviewed By: mdvacca

Differential Revision: D10174281

fbshipit-source-id: 9864ebb5343d40e2da205272a834710f0ab730db
2018-10-09 16:31:48 -07:00

193 lines
5.5 KiB
C++

// 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 <fabric/core/LayoutContext.h>
#include <fabric/core/LayoutPrimitives.h>
#include "ShadowTreeDelegate.h"
#include "Differentiator.h"
#include "ShadowViewMutation.h"
namespace facebook {
namespace react {
ShadowTree::ShadowTree(Tag rootTag):
rootTag_(rootTag) {
const auto noopEventEmitter = std::make_shared<const ViewEventEmitter>(nullptr, rootTag, std::shared_ptr<const EventDispatcher>());
rootShadowNode_ = std::make_shared<RootShadowNode>(
ShadowNodeFragment {
.tag = rootTag,
.rootTag = rootTag,
.props = RootShadowNode::defaultSharedProps(),
.eventEmitter = noopEventEmitter,
},
nullptr
);
}
ShadowTree::~ShadowTree() {
complete(std::make_shared<SharedShadowNodeList>(SharedShadowNodeList {}));
}
Tag ShadowTree::getRootTag() const {
return rootTag_;
}
SharedRootShadowNode ShadowTree::getRootShadowNode() const {
std::lock_guard<std::recursive_mutex> lock(commitMutex_);
return rootShadowNode_;
}
void ShadowTree::synchronize(std::function<void(void)> function) const {
std::lock_guard<std::recursive_mutex> lock(commitMutex_);
function();
}
#pragma mark - Layout
Size ShadowTree::measure(const LayoutConstraints &layoutConstraints, const LayoutContext &layoutContext) const {
auto newRootShadowNode = cloneRootShadowNode(layoutConstraints, layoutContext);
newRootShadowNode->layout();
return newRootShadowNode->getLayoutMetrics().frame.size;
}
bool ShadowTree::constraintLayout(const LayoutConstraints &layoutConstraints, const LayoutContext &layoutContext) const {
auto newRootShadowNode = cloneRootShadowNode(layoutConstraints, layoutContext);
return complete(newRootShadowNode);
}
#pragma mark - Commiting
UnsharedRootShadowNode ShadowTree::cloneRootShadowNode(const LayoutConstraints &layoutConstraints, const LayoutContext &layoutContext) const {
auto oldRootShadowNode = getRootShadowNode();
const auto &props = std::make_shared<const RootProps>(*oldRootShadowNode->getProps(), layoutConstraints, layoutContext);
auto newRootShadowNode =
std::make_shared<RootShadowNode>(*oldRootShadowNode, ShadowNodeFragment {.props = props});
return newRootShadowNode;
}
bool ShadowTree::complete(const SharedShadowNodeUnsharedList &rootChildNodes) const {
auto oldRootShadowNode = getRootShadowNode();
auto newRootShadowNode =
std::make_shared<RootShadowNode>(
*oldRootShadowNode,
ShadowNodeFragment {
.children = SharedShadowNodeSharedList(rootChildNodes)
}
);
return complete(newRootShadowNode);
}
bool ShadowTree::complete(UnsharedRootShadowNode newRootShadowNode) const {
SharedRootShadowNode oldRootShadowNode = getRootShadowNode();
newRootShadowNode->layout();
newRootShadowNode->sealRecursive();
auto mutations = calculateShadowViewMutations(
*oldRootShadowNode,
*newRootShadowNode
);
if (!commit(oldRootShadowNode, newRootShadowNode, mutations)) {
return false;
}
emitLayoutEvents(mutations);
if (delegate_) {
delegate_->shadowTreeDidCommit(*this, mutations);
}
return true;
}
bool ShadowTree::commit(
const SharedRootShadowNode &oldRootShadowNode,
const SharedRootShadowNode &newRootShadowNode,
const ShadowViewMutationList &mutations
) const {
std::lock_guard<std::recursive_mutex> lock(commitMutex_);
if (oldRootShadowNode != rootShadowNode_) {
return false;
}
rootShadowNode_ = newRootShadowNode;
toggleEventEmitters(mutations);
return true;
}
void ShadowTree::emitLayoutEvents(const ShadowViewMutationList &mutations) const {
for (const auto &mutation : mutations) {
// Only `Insert` and `Update` mutations can affect layout metrics.
if (
mutation.type != ShadowViewMutation::Insert &&
mutation.type != ShadowViewMutation::Update
) {
continue;
}
const auto viewEventEmitter = std::dynamic_pointer_cast<const ViewEventEmitter>(mutation.newChildShadowView.eventEmitter);
// Checking if particular shadow node supports `onLayout` event (part of `ViewEventEmitter`).
if (!viewEventEmitter) {
continue;
}
// Checking if the `onLayout` event was requested for the particular Shadow Node.
const auto viewProps = std::dynamic_pointer_cast<const ViewProps>(mutation.newChildShadowView.props);
if (viewProps && !viewProps->onLayout) {
continue;
}
// In case if we have `oldChildShadowView`, checking that layout metrics have changed.
if (
mutation.type != ShadowViewMutation::Update &&
mutation.oldChildShadowView.layoutMetrics == mutation.newChildShadowView.layoutMetrics
) {
continue;
}
viewEventEmitter->onLayout(mutation.newChildShadowView.layoutMetrics);
}
}
void ShadowTree::toggleEventEmitters(const ShadowViewMutationList &mutations) const {
std::lock_guard<std::recursive_mutex> lock(EventEmitter::DispatchMutex());
for (const auto &mutation : mutations) {
if (mutation.type == ShadowViewMutation::Delete) {
mutation.oldChildShadowView.eventEmitter->setEnabled(false);
}
}
for (const auto &mutation : mutations) {
if (mutation.type == ShadowViewMutation::Create) {
mutation.newChildShadowView.eventEmitter->setEnabled(true);
}
}
}
#pragma mark - Delegate
void ShadowTree::setDelegate(ShadowTreeDelegate const *delegate) {
delegate_ = delegate;
}
ShadowTreeDelegate const *ShadowTree::getDelegate() const {
return delegate_;
}
} // namespace react
} // namespace facebook