jiuyiUniapp/service/node_modules/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp

421 lines
13 KiB
C++
Raw Normal View History

2025-02-13 09:59:20 +08:00
/*
* Copyright (c) Meta Platforms, Inc. and 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 <cxxreact/SystraceSection.h>
#include <react/debug/react_native_assert.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/components/root/RootComponentDescriptor.h>
#include <react/renderer/components/view/ViewShadowNode.h>
#include <react/renderer/core/LayoutContext.h>
#include <react/renderer/core/LayoutPrimitives.h>
#include <react/renderer/mounting/ShadowTreeRevision.h>
#include <react/renderer/mounting/ShadowViewMutation.h>
#include <react/renderer/telemetry/TransactionTelemetry.h>
#include "updateMountedFlag.h"
#include "ShadowTreeDelegate.h"
namespace facebook::react {
using CommitStatus = ShadowTree::CommitStatus;
using CommitMode = ShadowTree::CommitMode;
/*
* Generates (possibly) a new tree where all nodes with non-obsolete `State`
* objects. If all `State` objects in the tree are not obsolete for the moment
* of calling, the function returns `nullptr` (as an indication that no
* additional work is required).
*/
static ShadowNode::Unshared progressState(const ShadowNode& shadowNode) {
auto isStateChanged = false;
auto areChildrenChanged = false;
auto newState = shadowNode.getState();
if (newState) {
newState = newState->getMostRecentStateIfObsolete();
if (newState) {
isStateChanged = true;
}
}
auto newChildren = ShadowNode::ListOfShared{};
if (!shadowNode.getChildren().empty()) {
auto index = size_t{0};
for (const auto& childNode : shadowNode.getChildren()) {
auto newChildNode = progressState(*childNode);
if (newChildNode) {
if (!areChildrenChanged) {
// Making a copy before the first mutation.
newChildren = shadowNode.getChildren();
}
newChildren[index] = newChildNode;
areChildrenChanged = true;
}
index++;
}
}
if (!areChildrenChanged && !isStateChanged) {
return nullptr;
}
return shadowNode.clone({
ShadowNodeFragment::propsPlaceholder(),
areChildrenChanged ? std::make_shared<const ShadowNode::ListOfShared>(
std::move(newChildren))
: ShadowNodeFragment::childrenPlaceholder(),
isStateChanged ? newState : ShadowNodeFragment::statePlaceholder(),
});
}
/*
* An optimized version of the previous function (and relies on it).
* The function uses a given base tree to exclude unchanged (equal) parts
* of the three from the traversing.
*/
static ShadowNode::Unshared progressState(
const ShadowNode& shadowNode,
const ShadowNode& baseShadowNode) {
// The intuition behind the complexity:
// - A very few nodes have associated state, therefore it's mostly reading and
// it only writes when state objects were found obsolete;
// - Most before-after trees are aligned, therefore most tree branches will be
// skipped;
// - If trees are significantly different, any other algorithm will have
// close to linear complexity.
auto isStateChanged = false;
auto areChildrenChanged = false;
auto newState = shadowNode.getState();
if (newState) {
newState = newState->getMostRecentStateIfObsolete();
if (newState) {
isStateChanged = true;
}
}
auto& children = shadowNode.getChildren();
auto& baseChildren = baseShadowNode.getChildren();
auto newChildren = ShadowNode::ListOfShared{};
auto childrenSize = children.size();
auto baseChildrenSize = baseChildren.size();
auto index = size_t{0};
// Stage 1: Aligned part.
for (index = 0; index < childrenSize && index < baseChildrenSize; index++) {
const auto& childNode = *children[index];
const auto& baseChildNode = *baseChildren[index];
if (&childNode == &baseChildNode) {
// Nodes are identical, skipping.
continue;
}
if (!ShadowNode::sameFamily(childNode, baseChildNode)) {
// Totally different nodes, updating is impossible.
break;
}
auto newChildNode = progressState(childNode, baseChildNode);
if (newChildNode) {
if (!areChildrenChanged) {
// Making a copy before the first mutation.
newChildren = children;
}
newChildren[index] = newChildNode;
areChildrenChanged = true;
}
}
// Stage 2: Misaligned part.
for (; index < childrenSize; index++) {
auto newChildNode = progressState(*children[index]);
if (newChildNode) {
if (!areChildrenChanged) {
// Making a copy before the first mutation.
newChildren = children;
}
newChildren[index] = newChildNode;
areChildrenChanged = true;
}
}
if (!areChildrenChanged && !isStateChanged) {
return nullptr;
}
return shadowNode.clone({
ShadowNodeFragment::propsPlaceholder(),
areChildrenChanged ? std::make_shared<const ShadowNode::ListOfShared>(
std::move(newChildren))
: ShadowNodeFragment::childrenPlaceholder(),
isStateChanged ? newState : ShadowNodeFragment::statePlaceholder(),
});
}
ShadowTree::ShadowTree(
SurfaceId surfaceId,
const LayoutConstraints& layoutConstraints,
const LayoutContext& layoutContext,
const ShadowTreeDelegate& delegate,
const ContextContainer& contextContainer)
: surfaceId_(surfaceId), delegate_(delegate) {
static auto globalRootComponentDescriptor =
std::make_unique<const RootComponentDescriptor>(
ComponentDescriptorParameters{
EventDispatcher::Shared{}, nullptr, nullptr});
const auto props = std::make_shared<const RootProps>(
PropsParserContext{surfaceId, contextContainer},
*RootShadowNode::defaultSharedProps(),
layoutConstraints,
layoutContext);
auto family = globalRootComponentDescriptor->createFamily(
{surfaceId, surfaceId, nullptr});
auto rootShadowNode = std::static_pointer_cast<const RootShadowNode>(
globalRootComponentDescriptor->createShadowNode(
ShadowNodeFragment{
/* .props = */ props,
},
family));
currentRevision_ = ShadowTreeRevision{
rootShadowNode, INITIAL_REVISION, TransactionTelemetry{}};
lastRevisionNumberWithNewState_ = currentRevision_.number;
mountingCoordinator_ =
std::make_shared<const MountingCoordinator>(currentRevision_);
}
ShadowTree::~ShadowTree() {
mountingCoordinator_->revoke();
}
Tag ShadowTree::getSurfaceId() const {
return surfaceId_;
}
void ShadowTree::setCommitMode(CommitMode commitMode) const {
auto revision = ShadowTreeRevision{};
{
std::unique_lock lock(commitMutex_);
if (commitMode_ == commitMode) {
return;
}
commitMode_ = commitMode;
revision = currentRevision_;
}
// initial revision never contains any commits so mounting it here is
// incorrect
if (commitMode == CommitMode::Normal && revision.number != INITIAL_REVISION) {
mount(revision, true);
}
}
CommitMode ShadowTree::getCommitMode() const {
std::shared_lock lock(commitMutex_);
return commitMode_;
}
std::shared_ptr<const MountingCoordinator> ShadowTree::getMountingCoordinator()
const {
return mountingCoordinator_;
}
CommitStatus ShadowTree::commit(
const ShadowTreeCommitTransaction& transaction,
const CommitOptions& commitOptions) const {
[[maybe_unused]] int attempts = 0;
while (true) {
attempts++;
auto status = tryCommit(transaction, commitOptions);
if (status != CommitStatus::Failed) {
return status;
}
// After multiple attempts, we failed to commit the transaction.
// Something internally went terribly wrong.
react_native_assert(attempts < 1024);
}
}
CommitStatus ShadowTree::tryCommit(
const ShadowTreeCommitTransaction& transaction,
const CommitOptions& commitOptions) const {
SystraceSection s("ShadowTree::commit");
auto telemetry = TransactionTelemetry{};
telemetry.willCommit();
CommitMode commitMode;
auto oldRevision = ShadowTreeRevision{};
auto newRevision = ShadowTreeRevision{};
ShadowTreeRevision::Number lastRevisionNumberWithNewState;
{
// Reading `currentRevision_` in shared manner.
std::shared_lock lock(commitMutex_);
commitMode = commitMode_;
oldRevision = currentRevision_;
lastRevisionNumberWithNewState = lastRevisionNumberWithNewState_;
}
const auto& oldRootShadowNode = oldRevision.rootShadowNode;
auto newRootShadowNode = transaction(*oldRevision.rootShadowNode);
if (!newRootShadowNode) {
return CommitStatus::Cancelled;
}
if (commitOptions.enableStateReconciliation) {
auto updatedNewRootShadowNode =
progressState(*newRootShadowNode, *oldRootShadowNode);
if (updatedNewRootShadowNode) {
newRootShadowNode =
std::static_pointer_cast<RootShadowNode>(updatedNewRootShadowNode);
}
}
// Run commit hooks.
newRootShadowNode = delegate_.shadowTreeWillCommit(
*this, oldRootShadowNode, newRootShadowNode);
if (!newRootShadowNode) {
return CommitStatus::Cancelled;
}
// Layout nodes.
std::vector<const LayoutableShadowNode*> affectedLayoutableNodes{};
affectedLayoutableNodes.reserve(1024);
telemetry.willLayout();
telemetry.setAsThreadLocal();
newRootShadowNode->layoutIfNeeded(&affectedLayoutableNodes);
telemetry.unsetAsThreadLocal();
telemetry.didLayout(static_cast<int>(affectedLayoutableNodes.size()));
{
// Updating `currentRevision_` in unique manner if it hasn't changed.
std::unique_lock lock(commitMutex_);
if (ReactNativeFeatureFlags::
enableGranularShadowTreeStateReconciliation()) {
auto lastRevisionNumberWithNewStateChanged =
lastRevisionNumberWithNewState != lastRevisionNumberWithNewState_;
// Commit should only fail if we propagated the wrong state.
if (commitOptions.enableStateReconciliation &&
lastRevisionNumberWithNewStateChanged) {
return CommitStatus::Failed;
}
} else {
if (currentRevision_.number != oldRevision.number) {
return CommitStatus::Failed;
}
}
auto newRevisionNumber = currentRevision_.number + 1;
{
std::scoped_lock dispatchLock(EventEmitter::DispatchMutex());
updateMountedFlag(
currentRevision_.rootShadowNode->getChildren(),
newRootShadowNode->getChildren());
}
telemetry.didCommit();
telemetry.setRevisionNumber(static_cast<int>(newRevisionNumber));
// Seal the shadow node so it can no longer be mutated
// Does nothing in release.
newRootShadowNode->sealRecursive();
newRevision = ShadowTreeRevision{
std::move(newRootShadowNode), newRevisionNumber, telemetry};
currentRevision_ = newRevision;
if (!commitOptions.enableStateReconciliation) {
lastRevisionNumberWithNewState_ = newRevisionNumber;
}
}
emitLayoutEvents(affectedLayoutableNodes);
if (commitMode == CommitMode::Normal) {
mount(std::move(newRevision), commitOptions.mountSynchronously);
}
return CommitStatus::Succeeded;
}
ShadowTreeRevision ShadowTree::getCurrentRevision() const {
std::shared_lock lock(commitMutex_);
return currentRevision_;
}
void ShadowTree::mount(ShadowTreeRevision revision, bool mountSynchronously)
const {
mountingCoordinator_->push(std::move(revision));
delegate_.shadowTreeDidFinishTransaction(
mountingCoordinator_, mountSynchronously);
}
void ShadowTree::commitEmptyTree() const {
commit(
[](const RootShadowNode& oldRootShadowNode) -> RootShadowNode::Unshared {
return std::make_shared<RootShadowNode>(
oldRootShadowNode,
ShadowNodeFragment{
/* .props = */ ShadowNodeFragment::propsPlaceholder(),
/* .children = */ ShadowNode::emptySharedShadowNodeSharedList(),
});
},
{/* default commit options */});
}
void ShadowTree::emitLayoutEvents(
std::vector<const LayoutableShadowNode*>& affectedLayoutableNodes) const {
SystraceSection s(
"ShadowTree::emitLayoutEvents",
"affectedLayoutableNodes",
affectedLayoutableNodes.size());
for (const auto* layoutableNode : affectedLayoutableNodes) {
// Only instances of `ViewShadowNode` (and subclasses) are supported.
const auto& viewEventEmitter = static_cast<const BaseViewEventEmitter&>(
*layoutableNode->getEventEmitter());
// Checking if the `onLayout` event was requested for the particular Shadow
// Node.
const auto& viewProps =
static_cast<const BaseViewProps&>(*layoutableNode->getProps());
if (!viewProps.onLayout) {
continue;
}
viewEventEmitter.onLayout(layoutableNode->getLayoutMetrics());
}
}
void ShadowTree::notifyDelegatesOfUpdates() const {
delegate_.shadowTreeDidFinishTransaction(mountingCoordinator_, true);
}
} // namespace facebook::react