mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
a8b090b128
Summary: This is *another* attempt to solve a failed state update problem. Unfortunately, some applications are inherently not compatible with the "let's recommit the update on the application side in case it failed" approach. The problem is that if we call `updateState` on the application side, we miss the original event window. E.g. if we need to deliver some state update with AsyncBatched priority and if the update fails, we lose the opportunity to commit it on time. These issues can be critical for some complex use-cases as ComponentKit interop. This diff adds implementation for `updateState` that does the work a bit differently. For all failed state updates it tres to recommit them asap using `ShadowTree::commit` and calling lambda on every attempt. With this approach the update might fail in two cases: The node disappeared from the tree, so there is no way to update it. The lambda returned `nullptr` indicating that the update is no longer needed. We need this for the ComponentKit interoperability layer that is very sensitive for missing state updates. Changelog: [Internal] Fabric-specific internal change. Reviewed By: sammy-SC Differential Revision: D23603958 fbshipit-source-id: a3b8c09fb2f1c8302583aa5880b48fc0840224e3
402 lines
13 KiB
C++
402 lines
13 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 "UIManager.h"
|
|
|
|
#include <react/renderer/core/ShadowNodeFragment.h>
|
|
#include <react/renderer/debug/SystraceSection.h>
|
|
#include <react/renderer/graphics/Geometry.h>
|
|
|
|
#include <glog/logging.h>
|
|
|
|
namespace facebook {
|
|
namespace react {
|
|
|
|
UIManager::~UIManager() {
|
|
LOG(WARNING) << "UIManager::~UIManager() was called (address: " << this
|
|
<< ").";
|
|
}
|
|
|
|
SharedShadowNode UIManager::createNode(
|
|
Tag tag,
|
|
std::string const &name,
|
|
SurfaceId surfaceId,
|
|
const RawProps &rawProps,
|
|
SharedEventTarget eventTarget) const {
|
|
SystraceSection s("UIManager::createNode");
|
|
|
|
auto &componentDescriptor = componentDescriptorRegistry_->at(name);
|
|
auto fallbackDescriptor =
|
|
componentDescriptorRegistry_->getFallbackComponentDescriptor();
|
|
|
|
auto family = componentDescriptor.createFamily(
|
|
ShadowNodeFamilyFragment{tag, surfaceId, nullptr},
|
|
std::move(eventTarget));
|
|
auto const props = componentDescriptor.cloneProps(nullptr, rawProps);
|
|
auto const state =
|
|
componentDescriptor.createInitialState(ShadowNodeFragment{props}, family);
|
|
|
|
auto shadowNode = componentDescriptor.createShadowNode(
|
|
ShadowNodeFragment{
|
|
/* .props = */
|
|
fallbackDescriptor != nullptr &&
|
|
fallbackDescriptor->getComponentHandle() ==
|
|
componentDescriptor.getComponentHandle()
|
|
? componentDescriptor.cloneProps(
|
|
props, RawProps(folly::dynamic::object("name", name)))
|
|
: props,
|
|
/* .children = */ ShadowNodeFragment::childrenPlaceholder(),
|
|
/* .state = */ state,
|
|
},
|
|
family);
|
|
|
|
if (delegate_) {
|
|
delegate_->uiManagerDidCreateShadowNode(shadowNode);
|
|
}
|
|
|
|
return shadowNode;
|
|
}
|
|
|
|
SharedShadowNode UIManager::cloneNode(
|
|
const ShadowNode::Shared &shadowNode,
|
|
const SharedShadowNodeSharedList &children,
|
|
const RawProps *rawProps) const {
|
|
SystraceSection s("UIManager::cloneNode");
|
|
|
|
auto &componentDescriptor = shadowNode->getComponentDescriptor();
|
|
auto clonedShadowNode = componentDescriptor.cloneShadowNode(
|
|
*shadowNode,
|
|
{
|
|
/* .props = */
|
|
rawProps ? componentDescriptor.cloneProps(
|
|
shadowNode->getProps(), *rawProps)
|
|
: ShadowNodeFragment::propsPlaceholder(),
|
|
/* .children = */ children,
|
|
});
|
|
|
|
return clonedShadowNode;
|
|
}
|
|
|
|
void UIManager::appendChild(
|
|
const ShadowNode::Shared &parentShadowNode,
|
|
const ShadowNode::Shared &childShadowNode) const {
|
|
SystraceSection s("UIManager::appendChild");
|
|
|
|
auto &componentDescriptor = parentShadowNode->getComponentDescriptor();
|
|
componentDescriptor.appendChild(parentShadowNode, childShadowNode);
|
|
}
|
|
|
|
void UIManager::completeSurface(
|
|
SurfaceId surfaceId,
|
|
const SharedShadowNodeUnsharedList &rootChildren) const {
|
|
SystraceSection s("UIManager::completeSurface");
|
|
|
|
shadowTreeRegistry_.visit(surfaceId, [&](ShadowTree const &shadowTree) {
|
|
shadowTree.commit(
|
|
[&](RootShadowNode::Shared const &oldRootShadowNode) {
|
|
return std::make_shared<RootShadowNode>(
|
|
*oldRootShadowNode,
|
|
ShadowNodeFragment{
|
|
/* .props = */ ShadowNodeFragment::propsPlaceholder(),
|
|
/* .children = */ rootChildren,
|
|
});
|
|
},
|
|
true);
|
|
});
|
|
}
|
|
|
|
void UIManager::setJSResponder(
|
|
const ShadowNode::Shared &shadowNode,
|
|
const bool blockNativeResponder) const {
|
|
if (delegate_) {
|
|
delegate_->uiManagerDidSetJSResponder(
|
|
shadowNode->getSurfaceId(), shadowNode, blockNativeResponder);
|
|
}
|
|
}
|
|
|
|
void UIManager::clearJSResponder() const {
|
|
if (delegate_) {
|
|
delegate_->uiManagerDidClearJSResponder();
|
|
}
|
|
}
|
|
|
|
ShadowNode::Shared UIManager::getNewestCloneOfShadowNode(
|
|
ShadowNode const &shadowNode) const {
|
|
auto findNewestChildInParent =
|
|
[&](auto const &parentNode) -> ShadowNode::Shared {
|
|
for (auto const &child : parentNode.getChildren()) {
|
|
if (ShadowNode::sameFamily(*child, shadowNode)) {
|
|
return child;
|
|
}
|
|
}
|
|
return nullptr;
|
|
};
|
|
|
|
auto ancestorShadowNode = ShadowNode::Shared{};
|
|
shadowTreeRegistry_.visit(
|
|
shadowNode.getSurfaceId(), [&](ShadowTree const &shadowTree) {
|
|
shadowTree.tryCommit(
|
|
[&](RootShadowNode::Shared const &oldRootShadowNode) {
|
|
ancestorShadowNode = oldRootShadowNode;
|
|
return nullptr;
|
|
},
|
|
true);
|
|
});
|
|
|
|
if (!ancestorShadowNode) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto ancestors = shadowNode.getFamily().getAncestors(*ancestorShadowNode);
|
|
|
|
if (ancestors.empty()) {
|
|
return nullptr;
|
|
}
|
|
|
|
return findNewestChildInParent(ancestors.rbegin()->first.get());
|
|
}
|
|
|
|
ShadowNode::Shared UIManager::findNodeAtPoint(
|
|
ShadowNode::Shared const &node,
|
|
Point point) const {
|
|
return LayoutableShadowNode::findNodeAtPoint(
|
|
getNewestCloneOfShadowNode(*node), point);
|
|
}
|
|
|
|
void UIManager::setNativeProps(
|
|
ShadowNode const &shadowNode,
|
|
RawProps const &rawProps) const {
|
|
SystraceSection s("UIManager::setNativeProps");
|
|
|
|
auto &componentDescriptor = shadowNode.getComponentDescriptor();
|
|
auto props = componentDescriptor.cloneProps(shadowNode.getProps(), rawProps);
|
|
|
|
shadowTreeRegistry_.visit(
|
|
shadowNode.getSurfaceId(), [&](ShadowTree const &shadowTree) {
|
|
shadowTree.tryCommit(
|
|
[&](RootShadowNode::Shared const &oldRootShadowNode) {
|
|
return std::static_pointer_cast<RootShadowNode>(
|
|
oldRootShadowNode->cloneTree(
|
|
shadowNode.getFamily(),
|
|
[&](ShadowNode const &oldShadowNode) {
|
|
return oldShadowNode.clone({
|
|
/* .props = */ props,
|
|
});
|
|
}));
|
|
},
|
|
true);
|
|
});
|
|
}
|
|
|
|
LayoutMetrics UIManager::getRelativeLayoutMetrics(
|
|
ShadowNode const &shadowNode,
|
|
ShadowNode const *ancestorShadowNode,
|
|
LayoutableShadowNode::LayoutInspectingPolicy policy) const {
|
|
SystraceSection s("UIManager::getRelativeLayoutMetrics");
|
|
|
|
// We might store here an owning pointer to `ancestorShadowNode` to ensure
|
|
// that the node is not deallocated during method execution lifetime.
|
|
auto owningAncestorShadowNode = ShadowNode::Shared{};
|
|
|
|
if (!ancestorShadowNode) {
|
|
shadowTreeRegistry_.visit(
|
|
shadowNode.getSurfaceId(), [&](ShadowTree const &shadowTree) {
|
|
shadowTree.tryCommit(
|
|
[&](RootShadowNode::Shared const &oldRootShadowNode) {
|
|
owningAncestorShadowNode = oldRootShadowNode;
|
|
ancestorShadowNode = oldRootShadowNode.get();
|
|
return nullptr;
|
|
},
|
|
true);
|
|
});
|
|
} else {
|
|
// It is possible for JavaScript (or other callers) to have a reference
|
|
// to a previous version of ShadowNodes, but we enforce that
|
|
// metrics are only calculated on most recently committed versions.
|
|
owningAncestorShadowNode = getNewestCloneOfShadowNode(*ancestorShadowNode);
|
|
ancestorShadowNode = owningAncestorShadowNode.get();
|
|
}
|
|
|
|
auto layoutableAncestorShadowNode =
|
|
traitCast<LayoutableShadowNode const *>(ancestorShadowNode);
|
|
|
|
if (!layoutableAncestorShadowNode) {
|
|
return EmptyLayoutMetrics;
|
|
}
|
|
|
|
return LayoutableShadowNode::computeRelativeLayoutMetrics(
|
|
shadowNode.getFamily(), *layoutableAncestorShadowNode, policy);
|
|
}
|
|
|
|
void UIManager::updateStateWithAutorepeat(
|
|
StateUpdate const &stateUpdate) const {
|
|
auto &callback = stateUpdate.callback;
|
|
auto &family = stateUpdate.family;
|
|
auto &componentDescriptor = family->getComponentDescriptor();
|
|
|
|
shadowTreeRegistry_.visit(
|
|
family->getSurfaceId(), [&](ShadowTree const &shadowTree) {
|
|
shadowTree.commit([&](RootShadowNode::Shared const &oldRootShadowNode) {
|
|
auto isValid = true;
|
|
|
|
auto rootNode = oldRootShadowNode->cloneTree(
|
|
*family, [&](ShadowNode const &oldShadowNode) {
|
|
auto newData =
|
|
callback(oldShadowNode.getState()->getDataPointer());
|
|
|
|
if (!newData) {
|
|
isValid = false;
|
|
// Just return something, we will discard it anyway.
|
|
return oldShadowNode.clone({});
|
|
}
|
|
|
|
auto newState =
|
|
componentDescriptor.createState(*family, newData);
|
|
|
|
return oldShadowNode.clone({
|
|
/* .props = */ ShadowNodeFragment::propsPlaceholder(),
|
|
/* .children = */
|
|
ShadowNodeFragment::childrenPlaceholder(),
|
|
/* .state = */ newState,
|
|
});
|
|
});
|
|
|
|
return isValid ? std::static_pointer_cast<RootShadowNode>(rootNode)
|
|
: nullptr;
|
|
});
|
|
});
|
|
}
|
|
|
|
void UIManager::updateState(StateUpdate const &stateUpdate) const {
|
|
if (stateUpdate.autorepeat) {
|
|
updateStateWithAutorepeat(stateUpdate);
|
|
return;
|
|
}
|
|
|
|
auto &callback = stateUpdate.callback;
|
|
auto &family = stateUpdate.family;
|
|
auto &componentDescriptor = family->getComponentDescriptor();
|
|
|
|
shadowTreeRegistry_.visit(
|
|
family->getSurfaceId(), [&](ShadowTree const &shadowTree) {
|
|
auto status = shadowTree.tryCommit([&](RootShadowNode::Shared const
|
|
&oldRootShadowNode) {
|
|
return std::static_pointer_cast<RootShadowNode>(
|
|
oldRootShadowNode->cloneTree(
|
|
*family, [&](ShadowNode const &oldShadowNode) {
|
|
auto newData =
|
|
callback(oldShadowNode.getState()->getDataPointer());
|
|
auto newState =
|
|
componentDescriptor.createState(*family, newData);
|
|
|
|
return oldShadowNode.clone({
|
|
/* .props = */ ShadowNodeFragment::propsPlaceholder(),
|
|
/* .children = */
|
|
ShadowNodeFragment::childrenPlaceholder(),
|
|
/* .state = */ newState,
|
|
});
|
|
}));
|
|
});
|
|
if (status != ShadowTree::CommitStatus::Succeeded &&
|
|
stateUpdate.failureCallback) {
|
|
stateUpdate.failureCallback();
|
|
}
|
|
});
|
|
}
|
|
|
|
void UIManager::dispatchCommand(
|
|
const ShadowNode::Shared &shadowNode,
|
|
std::string const &commandName,
|
|
folly::dynamic const args) const {
|
|
if (delegate_) {
|
|
delegate_->uiManagerDidDispatchCommand(shadowNode, commandName, args);
|
|
}
|
|
}
|
|
|
|
void UIManager::configureNextLayoutAnimation(
|
|
jsi::Runtime &runtime,
|
|
RawValue const &config,
|
|
const jsi::Value &successCallback,
|
|
const jsi::Value &failureCallback) const {
|
|
if (animationDelegate_) {
|
|
animationDelegate_->uiManagerDidConfigureNextLayoutAnimation(
|
|
runtime,
|
|
config,
|
|
std::move(successCallback),
|
|
std::move(failureCallback));
|
|
}
|
|
}
|
|
|
|
void UIManager::setComponentDescriptorRegistry(
|
|
const SharedComponentDescriptorRegistry &componentDescriptorRegistry) {
|
|
componentDescriptorRegistry_ = componentDescriptorRegistry;
|
|
}
|
|
|
|
void UIManager::setDelegate(UIManagerDelegate *delegate) {
|
|
delegate_ = delegate;
|
|
}
|
|
|
|
UIManagerDelegate *UIManager::getDelegate() {
|
|
return delegate_;
|
|
}
|
|
|
|
void UIManager::setBackgroundExecutor(
|
|
BackgroundExecutor const &backgroundExecutor) {
|
|
backgroundExecutor_ = backgroundExecutor;
|
|
}
|
|
|
|
void UIManager::visitBinding(
|
|
std::function<void(UIManagerBinding const &uiManagerBinding)> callback)
|
|
const {
|
|
if (!uiManagerBinding_) {
|
|
return;
|
|
}
|
|
|
|
callback(*uiManagerBinding_);
|
|
}
|
|
|
|
ShadowTreeRegistry const &UIManager::getShadowTreeRegistry() const {
|
|
return shadowTreeRegistry_;
|
|
}
|
|
|
|
#pragma mark - ShadowTreeDelegate
|
|
|
|
void UIManager::shadowTreeDidFinishTransaction(
|
|
ShadowTree const &shadowTree,
|
|
MountingCoordinator::Shared const &mountingCoordinator) const {
|
|
SystraceSection s("UIManager::shadowTreeDidFinishTransaction");
|
|
|
|
if (delegate_) {
|
|
delegate_->uiManagerDidFinishTransaction(mountingCoordinator);
|
|
}
|
|
}
|
|
|
|
#pragma mark - UIManagerAnimationDelegate
|
|
|
|
void UIManager::setAnimationDelegate(UIManagerAnimationDelegate *delegate) {
|
|
animationDelegate_ = delegate;
|
|
}
|
|
|
|
void UIManager::stopSurfaceForAnimationDelegate(SurfaceId surfaceId) {
|
|
if (animationDelegate_ != nullptr) {
|
|
animationDelegate_->stopSurface(surfaceId);
|
|
}
|
|
}
|
|
|
|
void UIManager::animationTick() {
|
|
if (animationDelegate_ != nullptr &&
|
|
animationDelegate_->shouldAnimateFrame()) {
|
|
shadowTreeRegistry_.enumerate(
|
|
[&](ShadowTree const &shadowTree, bool &stop) {
|
|
shadowTree.notifyDelegatesOfUpdates();
|
|
});
|
|
}
|
|
}
|
|
|
|
} // namespace react
|
|
} // namespace facebook
|