C++ Fabric Core LayoutAnimations
Summary: This is the V1 implementation of Fabric Core LayoutAnimations. The intention is to structure this in such a way that it's easy for each platform to customize the "AnimationDriver" class (to do platform-specific optimizations) without changing the KeyFrameManager at all. In the future, this structure and architecture should allow us to iterate faster on new animation APIs. Changelog: [Internal] Support for LayoutAnimations in Fabric Reviewed By: mdvacca Differential Revision: D21675808 fbshipit-source-id: b3ef44729bb8b6217f90760aec9737276c9601d1
This commit is contained in:
Родитель
2e756e024f
Коммит
3331962279
|
@ -70,7 +70,7 @@ class SchedulerDelegateProxy : public SchedulerDelegate {
|
||||||
{
|
{
|
||||||
if (self = [super init]) {
|
if (self = [super init]) {
|
||||||
_delegateProxy = std::make_shared<SchedulerDelegateProxy>((__bridge void *)self);
|
_delegateProxy = std::make_shared<SchedulerDelegateProxy>((__bridge void *)self);
|
||||||
_scheduler = std::make_shared<Scheduler>(toolbox, _delegateProxy.get());
|
_scheduler = std::make_shared<Scheduler>(toolbox, nullptr, _delegateProxy.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
|
|
|
@ -288,7 +288,7 @@ void Binding::installFabricUIManager(
|
||||||
toolbox.runtimeExecutor = runtimeExecutor;
|
toolbox.runtimeExecutor = runtimeExecutor;
|
||||||
toolbox.synchronousEventBeatFactory = synchronousBeatFactory;
|
toolbox.synchronousEventBeatFactory = synchronousBeatFactory;
|
||||||
toolbox.asynchronousEventBeatFactory = asynchronousBeatFactory;
|
toolbox.asynchronousEventBeatFactory = asynchronousBeatFactory;
|
||||||
scheduler_ = std::make_shared<Scheduler>(toolbox, this);
|
scheduler_ = std::make_shared<Scheduler>(toolbox, nullptr, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Binding::uninstallFabricUIManager() {
|
void Binding::uninstallFabricUIManager() {
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
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 = "animations",
|
||||||
|
srcs = glob(
|
||||||
|
["**/*.cpp"],
|
||||||
|
exclude = glob(["tests/**/*.cpp"]),
|
||||||
|
),
|
||||||
|
headers = glob(
|
||||||
|
["**/*.h"],
|
||||||
|
exclude = glob(["tests/**/*.h"]),
|
||||||
|
),
|
||||||
|
header_namespace = "",
|
||||||
|
exported_headers = subdir_glob(
|
||||||
|
[
|
||||||
|
("", "*.h"),
|
||||||
|
],
|
||||||
|
prefix = "react/animations",
|
||||||
|
),
|
||||||
|
compiler_flags = [
|
||||||
|
"-fexceptions",
|
||||||
|
"-frtti",
|
||||||
|
"-std=c++14",
|
||||||
|
"-Wall",
|
||||||
|
],
|
||||||
|
fbobjc_compiler_flags = APPLE_COMPILER_FLAGS,
|
||||||
|
fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(),
|
||||||
|
force_static = True,
|
||||||
|
labels = ["supermodule:xplat/default/public.react_native.infra"],
|
||||||
|
macosx_tests_override = [],
|
||||||
|
platforms = (ANDROID, APPLE, CXX),
|
||||||
|
preprocessor_flags = [
|
||||||
|
"-DLOG_TAG=\"ReactNative\"",
|
||||||
|
"-DWITH_FBSYSTRACE=1",
|
||||||
|
],
|
||||||
|
tests = [":tests"],
|
||||||
|
visibility = ["PUBLIC"],
|
||||||
|
deps = [
|
||||||
|
"//third-party/glog:glog",
|
||||||
|
"//xplat/fbsystrace:fbsystrace",
|
||||||
|
"//xplat/folly:headers_only",
|
||||||
|
"//xplat/folly:memory",
|
||||||
|
"//xplat/folly:molly",
|
||||||
|
"//xplat/jsi:JSIDynamic",
|
||||||
|
"//xplat/jsi:jsi",
|
||||||
|
react_native_xplat_target("config:config"),
|
||||||
|
react_native_xplat_target("fabric/componentregistry:componentregistry"),
|
||||||
|
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("fabric/mounting:mounting"),
|
||||||
|
react_native_xplat_target("fabric/uimanager:uimanager"),
|
||||||
|
react_native_xplat_target("runtimeexecutor:runtimeexecutor"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
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 = [
|
||||||
|
":animations",
|
||||||
|
"//xplat/folly:molly",
|
||||||
|
"//xplat/third-party/gmock:gtest",
|
||||||
|
react_native_xplat_target("config:config"),
|
||||||
|
react_native_xplat_target("fabric/components/activityindicator:activityindicator"),
|
||||||
|
react_native_xplat_target("fabric/components/image:image"),
|
||||||
|
react_native_xplat_target("fabric/components/root:root"),
|
||||||
|
react_native_xplat_target("fabric/components/scrollview:scrollview"),
|
||||||
|
react_native_xplat_target("fabric/components/view:view"),
|
||||||
|
"//xplat/js/react-native-github:generated_components-rncore",
|
||||||
|
],
|
||||||
|
)
|
|
@ -0,0 +1,197 @@
|
||||||
|
/*
|
||||||
|
* 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 "LayoutAnimationDriver.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
#include <react/componentregistry/ComponentDescriptorFactory.h>
|
||||||
|
#include <react/components/root/RootShadowNode.h>
|
||||||
|
#include <react/components/view/ViewProps.h>
|
||||||
|
#include <react/core/ComponentDescriptor.h>
|
||||||
|
#include <react/core/LayoutMetrics.h>
|
||||||
|
#include <react/core/LayoutableShadowNode.h>
|
||||||
|
#include <react/core/Props.h>
|
||||||
|
#include <react/mounting/MountingCoordinator.h>
|
||||||
|
|
||||||
|
#include <react/mounting/Differentiator.h>
|
||||||
|
#include <react/mounting/ShadowTreeRevision.h>
|
||||||
|
#include <react/mounting/ShadowView.h>
|
||||||
|
#include <react/mounting/ShadowViewMutation.h>
|
||||||
|
|
||||||
|
#include <glog/logging.h>
|
||||||
|
|
||||||
|
namespace facebook {
|
||||||
|
namespace react {
|
||||||
|
|
||||||
|
static double
|
||||||
|
getProgressFromValues(double start, double end, double currentValue) {
|
||||||
|
auto opacityMinmax = std::minmax({start, end});
|
||||||
|
auto min = opacityMinmax.first;
|
||||||
|
auto max = opacityMinmax.second;
|
||||||
|
return (
|
||||||
|
currentValue < min
|
||||||
|
? 0
|
||||||
|
: (currentValue > max ? 0 : ((max - currentValue) / (max - min))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an animation and a ShadowView with properties set on it, detect how
|
||||||
|
* far through the animation the ShadowView has progressed.
|
||||||
|
*
|
||||||
|
* @param mutationsList
|
||||||
|
* @param now
|
||||||
|
*/
|
||||||
|
double LayoutAnimationDriver::getProgressThroughAnimation(
|
||||||
|
AnimationKeyFrame const &keyFrame,
|
||||||
|
LayoutAnimation const *layoutAnimation,
|
||||||
|
ShadowView const &animationStateView) const {
|
||||||
|
auto layoutAnimationConfig = layoutAnimation->layoutAnimationConfig;
|
||||||
|
auto const mutationConfig =
|
||||||
|
*(keyFrame.type == AnimationConfigurationType::Delete
|
||||||
|
? layoutAnimationConfig.deleteConfig
|
||||||
|
: (keyFrame.type == AnimationConfigurationType::Create
|
||||||
|
? layoutAnimationConfig.createConfig
|
||||||
|
: layoutAnimationConfig.updateConfig));
|
||||||
|
|
||||||
|
auto initialProps = keyFrame.viewStart.props;
|
||||||
|
auto finalProps = keyFrame.viewEnd.props;
|
||||||
|
|
||||||
|
if (mutationConfig.animationProperty == AnimationProperty::Opacity) {
|
||||||
|
// Detect progress through opacity animation.
|
||||||
|
const auto &oldViewProps =
|
||||||
|
dynamic_cast<const ViewProps *>(initialProps.get());
|
||||||
|
const auto &newViewProps =
|
||||||
|
dynamic_cast<const ViewProps *>(finalProps.get());
|
||||||
|
const auto &animationStateViewProps =
|
||||||
|
dynamic_cast<const ViewProps *>(animationStateView.props.get());
|
||||||
|
if (oldViewProps != nullptr && newViewProps != nullptr &&
|
||||||
|
animationStateViewProps != nullptr) {
|
||||||
|
return getProgressFromValues(
|
||||||
|
oldViewProps->opacity,
|
||||||
|
newViewProps->opacity,
|
||||||
|
animationStateViewProps->opacity);
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
mutationConfig.animationProperty != AnimationProperty::NotApplicable) {
|
||||||
|
// Detect progress through layout animation.
|
||||||
|
LayoutMetrics const &finalLayoutMetrics = keyFrame.viewEnd.layoutMetrics;
|
||||||
|
LayoutMetrics const &baselineLayoutMetrics =
|
||||||
|
keyFrame.viewStart.layoutMetrics;
|
||||||
|
LayoutMetrics const &animationStateLayoutMetrics =
|
||||||
|
animationStateView.layoutMetrics;
|
||||||
|
|
||||||
|
if (baselineLayoutMetrics.frame.size.height !=
|
||||||
|
finalLayoutMetrics.frame.size.height) {
|
||||||
|
return getProgressFromValues(
|
||||||
|
baselineLayoutMetrics.frame.size.height,
|
||||||
|
finalLayoutMetrics.frame.size.height,
|
||||||
|
animationStateLayoutMetrics.frame.size.height);
|
||||||
|
}
|
||||||
|
if (baselineLayoutMetrics.frame.size.width !=
|
||||||
|
finalLayoutMetrics.frame.size.width) {
|
||||||
|
return getProgressFromValues(
|
||||||
|
baselineLayoutMetrics.frame.size.width,
|
||||||
|
finalLayoutMetrics.frame.size.width,
|
||||||
|
animationStateLayoutMetrics.frame.size.width);
|
||||||
|
}
|
||||||
|
if (baselineLayoutMetrics.frame.origin.x !=
|
||||||
|
finalLayoutMetrics.frame.origin.x) {
|
||||||
|
return getProgressFromValues(
|
||||||
|
baselineLayoutMetrics.frame.origin.x,
|
||||||
|
finalLayoutMetrics.frame.origin.x,
|
||||||
|
animationStateLayoutMetrics.frame.origin.x);
|
||||||
|
}
|
||||||
|
if (baselineLayoutMetrics.frame.origin.y !=
|
||||||
|
finalLayoutMetrics.frame.origin.y) {
|
||||||
|
return getProgressFromValues(
|
||||||
|
baselineLayoutMetrics.frame.origin.y,
|
||||||
|
finalLayoutMetrics.frame.origin.y,
|
||||||
|
animationStateLayoutMetrics.frame.origin.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LayoutAnimationDriver::animationMutationsForFrame(
|
||||||
|
SurfaceId surfaceId,
|
||||||
|
ShadowViewMutation::List &mutationsList,
|
||||||
|
uint64_t now) const {
|
||||||
|
for (auto &animation : inflightAnimations_) {
|
||||||
|
if (animation.surfaceId != surfaceId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int incompleteAnimations = 0;
|
||||||
|
for (const auto &keyframe : animation.keyFrames) {
|
||||||
|
if (keyframe.type == AnimationConfigurationType::Noop) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const &baselineShadowView = keyframe.viewStart;
|
||||||
|
auto const &finalShadowView = keyframe.viewEnd;
|
||||||
|
|
||||||
|
// The contract with the "keyframes generation" phase is that any animated
|
||||||
|
// node will have a valid configuration.
|
||||||
|
auto const layoutAnimationConfig = animation.layoutAnimationConfig;
|
||||||
|
auto const mutationConfig =
|
||||||
|
(keyframe.type == AnimationConfigurationType::Delete
|
||||||
|
? layoutAnimationConfig.deleteConfig
|
||||||
|
: (keyframe.type == AnimationConfigurationType::Create
|
||||||
|
? layoutAnimationConfig.createConfig
|
||||||
|
: layoutAnimationConfig.updateConfig));
|
||||||
|
|
||||||
|
// Interpolate
|
||||||
|
std::pair<double, double> progress =
|
||||||
|
calculateAnimationProgress(now, animation, *mutationConfig);
|
||||||
|
double animationTimeProgressLinear = progress.first;
|
||||||
|
double animationInterpolationFactor = progress.second;
|
||||||
|
|
||||||
|
auto mutatedShadowView = createInterpolatedShadowView(
|
||||||
|
animationInterpolationFactor,
|
||||||
|
*mutationConfig,
|
||||||
|
baselineShadowView,
|
||||||
|
finalShadowView);
|
||||||
|
|
||||||
|
// Create the mutation instruction
|
||||||
|
mutationsList.push_back(ShadowViewMutation::UpdateMutation(
|
||||||
|
keyframe.parentView, baselineShadowView, mutatedShadowView, -1));
|
||||||
|
|
||||||
|
if (animationTimeProgressLinear < 1) {
|
||||||
|
incompleteAnimations++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Are there no ongoing mutations left in this animation?
|
||||||
|
if (incompleteAnimations == 0) {
|
||||||
|
animation.completed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear out finished animations
|
||||||
|
for (auto it = inflightAnimations_.begin();
|
||||||
|
it != inflightAnimations_.end();) {
|
||||||
|
const auto &animation = *it;
|
||||||
|
if (animation.completed) {
|
||||||
|
// Queue up "final" mutations for all keyframes in the completed animation
|
||||||
|
for (auto const &keyframe : animation.keyFrames) {
|
||||||
|
if (keyframe.finalMutationForKeyFrame.hasValue()) {
|
||||||
|
mutationsList.push_back(*keyframe.finalMutationForKeyFrame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it = inflightAnimations_.erase(it);
|
||||||
|
} else {
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace react
|
||||||
|
} // namespace facebook
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* 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/EventTarget.h>
|
||||||
|
#include <react/mounting/Differentiator.h>
|
||||||
|
#include <react/mounting/MountingCoordinator.h>
|
||||||
|
#include <react/mounting/MountingOverrideDelegate.h>
|
||||||
|
#include <react/mounting/MountingTransaction.h>
|
||||||
|
#include <react/uimanager/UIManagerAnimationDelegate.h>
|
||||||
|
|
||||||
|
#include <folly/dynamic.h>
|
||||||
|
|
||||||
|
#include "LayoutAnimationKeyFrameManager.h"
|
||||||
|
|
||||||
|
namespace facebook {
|
||||||
|
namespace react {
|
||||||
|
|
||||||
|
class LayoutAnimationDriver : public LayoutAnimationKeyFrameManager {
|
||||||
|
public:
|
||||||
|
virtual ~LayoutAnimationDriver() {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void animationMutationsForFrame(
|
||||||
|
SurfaceId surfaceId,
|
||||||
|
ShadowViewMutation::List &mutationsList,
|
||||||
|
uint64_t now) const override;
|
||||||
|
virtual double getProgressThroughAnimation(
|
||||||
|
AnimationKeyFrame const &keyFrame,
|
||||||
|
LayoutAnimation const *layoutAnimation,
|
||||||
|
ShadowView const &animationStateView) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace react
|
||||||
|
} // namespace facebook
|
|
@ -0,0 +1,847 @@
|
||||||
|
/*
|
||||||
|
* 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 "LayoutAnimationKeyFrameManager.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
#include <react/componentregistry/ComponentDescriptorFactory.h>
|
||||||
|
#include <react/components/root/RootShadowNode.h>
|
||||||
|
#include <react/components/view/ViewProps.h>
|
||||||
|
#include <react/core/ComponentDescriptor.h>
|
||||||
|
#include <react/core/LayoutMetrics.h>
|
||||||
|
#include <react/core/LayoutableShadowNode.h>
|
||||||
|
#include <react/core/Props.h>
|
||||||
|
#include <react/core/RawValue.h>
|
||||||
|
#include <react/mounting/MountingCoordinator.h>
|
||||||
|
|
||||||
|
#include <react/mounting/Differentiator.h>
|
||||||
|
#include <react/mounting/ShadowTreeRevision.h>
|
||||||
|
#include <react/mounting/ShadowView.h>
|
||||||
|
#include <react/mounting/ShadowViewMutation.h>
|
||||||
|
|
||||||
|
#include <Glog/logging.h>
|
||||||
|
|
||||||
|
namespace facebook {
|
||||||
|
namespace react {
|
||||||
|
|
||||||
|
static better::optional<AnimationType> parseAnimationType(std::string param) {
|
||||||
|
if (param == "spring") {
|
||||||
|
return better::optional<AnimationType>(AnimationType::Spring);
|
||||||
|
}
|
||||||
|
if (param == "linear") {
|
||||||
|
return better::optional<AnimationType>(AnimationType::Linear);
|
||||||
|
}
|
||||||
|
if (param == "easeInEaseOut") {
|
||||||
|
return better::optional<AnimationType>(AnimationType::EaseInEaseOut);
|
||||||
|
}
|
||||||
|
if (param == "easeIn") {
|
||||||
|
return better::optional<AnimationType>(AnimationType::EaseIn);
|
||||||
|
}
|
||||||
|
if (param == "easeOut") {
|
||||||
|
return better::optional<AnimationType>(AnimationType::EaseOut);
|
||||||
|
}
|
||||||
|
if (param == "keyboard") {
|
||||||
|
return better::optional<AnimationType>(AnimationType::Keyboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
static better::optional<AnimationProperty> parseAnimationProperty(
|
||||||
|
std::string param) {
|
||||||
|
if (param == "opacity") {
|
||||||
|
return better::optional<AnimationProperty>(AnimationProperty::Opacity);
|
||||||
|
}
|
||||||
|
if (param == "scaleX") {
|
||||||
|
return better::optional<AnimationProperty>(AnimationProperty::ScaleX);
|
||||||
|
}
|
||||||
|
if (param == "scaleY") {
|
||||||
|
return better::optional<AnimationProperty>(AnimationProperty::ScaleY);
|
||||||
|
}
|
||||||
|
if (param == "scaleXY") {
|
||||||
|
return better::optional<AnimationProperty>(AnimationProperty::ScaleXY);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
static better::optional<AnimationConfig> parseAnimationConfig(
|
||||||
|
folly::dynamic const &config,
|
||||||
|
double defaultDuration) {
|
||||||
|
if (config.empty() || !config.isObject()) {
|
||||||
|
return better::optional<AnimationConfig>(
|
||||||
|
AnimationConfig{AnimationType::Linear,
|
||||||
|
AnimationProperty::NotApplicable,
|
||||||
|
defaultDuration,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0});
|
||||||
|
}
|
||||||
|
|
||||||
|
folly::dynamic const &animationTypeParam = config["type"];
|
||||||
|
if (animationTypeParam.empty() || !animationTypeParam.isString()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto animationType = parseAnimationType(animationTypeParam.asString());
|
||||||
|
if (!animationType) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
folly::dynamic const &animationPropertyParam = config["property"];
|
||||||
|
if (animationPropertyParam.empty() || !animationPropertyParam.isString()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto animationProperty =
|
||||||
|
parseAnimationProperty(animationPropertyParam.asString());
|
||||||
|
if (!animationProperty) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
double duration = defaultDuration;
|
||||||
|
double delay = 0;
|
||||||
|
double springDamping = 0;
|
||||||
|
double initialVelocity = 0;
|
||||||
|
|
||||||
|
auto const durationIt = config.find("duration");
|
||||||
|
if (durationIt != config.items().end()) {
|
||||||
|
if (durationIt->second.isDouble()) {
|
||||||
|
duration = durationIt->second.asDouble();
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const delayIt = config.find("delay");
|
||||||
|
if (delayIt != config.items().end()) {
|
||||||
|
if (delayIt->second.isDouble()) {
|
||||||
|
delay = delayIt->second.asDouble();
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const springDampingIt = config.find("springDamping");
|
||||||
|
if (springDampingIt != config.items().end() &&
|
||||||
|
springDampingIt->second.isDouble()) {
|
||||||
|
if (springDampingIt->second.isDouble()) {
|
||||||
|
springDamping = springDampingIt->second.asDouble();
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const initialVelocityIt = config.find("initialVelocity");
|
||||||
|
if (initialVelocityIt != config.items().end()) {
|
||||||
|
if (initialVelocityIt->second.isDouble()) {
|
||||||
|
initialVelocity = initialVelocityIt->second.asDouble();
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return better::optional<AnimationConfig>(AnimationConfig{*animationType,
|
||||||
|
*animationProperty,
|
||||||
|
duration,
|
||||||
|
delay,
|
||||||
|
springDamping,
|
||||||
|
initialVelocity});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse animation config from JS
|
||||||
|
static better::optional<LayoutAnimationConfig> parseLayoutAnimationConfig(
|
||||||
|
folly::dynamic const &config) {
|
||||||
|
if (config.empty() || !config.isObject()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const durationIt = config.find("duration");
|
||||||
|
if (durationIt == config.items().end() || !durationIt->second.isDouble()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const double duration = durationIt->second.asDouble();
|
||||||
|
|
||||||
|
const auto createConfig = parseAnimationConfig(config["create"], duration);
|
||||||
|
if (!createConfig) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto updateConfig = parseAnimationConfig(config["update"], duration);
|
||||||
|
if (!updateConfig) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto deleteConfig = parseAnimationConfig(config["delete"], duration);
|
||||||
|
if (!deleteConfig) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return better::optional<LayoutAnimationConfig>(LayoutAnimationConfig{
|
||||||
|
duration, *createConfig, *updateConfig, *deleteConfig});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Globally configure next LayoutAnimation.
|
||||||
|
*/
|
||||||
|
void LayoutAnimationKeyFrameManager::uiManagerDidConfigureNextLayoutAnimation(
|
||||||
|
RawValue const &config,
|
||||||
|
std::shared_ptr<const EventTarget> successCallback,
|
||||||
|
std::shared_ptr<const EventTarget> errorCallback) const {
|
||||||
|
auto layoutAnimationConfig =
|
||||||
|
parseLayoutAnimationConfig((folly::dynamic)config);
|
||||||
|
|
||||||
|
if (layoutAnimationConfig) {
|
||||||
|
std::lock_guard<std::mutex> lock(currentAnimationMutex_);
|
||||||
|
currentAnimation_ = better::optional<LayoutAnimation>{
|
||||||
|
LayoutAnimation{-1,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
*layoutAnimationConfig,
|
||||||
|
successCallback,
|
||||||
|
errorCallback,
|
||||||
|
{}}};
|
||||||
|
} else {
|
||||||
|
// TODO: call errorCallback
|
||||||
|
LOG(ERROR) << "Parsing LayoutAnimationConfig failed: "
|
||||||
|
<< (folly::dynamic)config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LayoutAnimationKeyFrameManager::shouldOverridePullTransaction() const {
|
||||||
|
return shouldAnimateFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LayoutAnimationKeyFrameManager::shouldAnimateFrame() const {
|
||||||
|
// There is potentially a race here between getting and setting
|
||||||
|
// `currentMutation_`. We don't want to lock around this because then we're
|
||||||
|
// creating contention between pullTransaction and the JS thread.
|
||||||
|
return currentAnimation_ || !inflightAnimations_.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline const float
|
||||||
|
interpolateFloats(float coefficient, float oldValue, float newValue) {
|
||||||
|
return oldValue + (newValue - oldValue) * coefficient;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<double, double>
|
||||||
|
LayoutAnimationKeyFrameManager::calculateAnimationProgress(
|
||||||
|
uint64_t now,
|
||||||
|
const LayoutAnimation &animation,
|
||||||
|
const AnimationConfig &mutationConfig) const {
|
||||||
|
uint64_t startTime = animation.startTime;
|
||||||
|
uint64_t delay = mutationConfig.delay;
|
||||||
|
uint64_t endTime = startTime + delay + mutationConfig.duration;
|
||||||
|
double progress = (now >= endTime)
|
||||||
|
? 1
|
||||||
|
: ((now < startTime + delay) ? 0
|
||||||
|
: 1 -
|
||||||
|
(double)(endTime - delay - now) /
|
||||||
|
(double)(endTime - animation.startTime));
|
||||||
|
return {progress, progress};
|
||||||
|
}
|
||||||
|
|
||||||
|
void LayoutAnimationKeyFrameManager::adjustDelayedMutationIndicesForMutation(
|
||||||
|
SurfaceId surfaceId,
|
||||||
|
ShadowViewMutation const &mutation) const {
|
||||||
|
bool isRemoveMutation = mutation.type == ShadowViewMutation::Type::Remove;
|
||||||
|
bool isInsertMutation = mutation.type == ShadowViewMutation::Type::Insert;
|
||||||
|
assert(isRemoveMutation || isInsertMutation);
|
||||||
|
|
||||||
|
for (auto &inflightAnimation : inflightAnimations_) {
|
||||||
|
if (inflightAnimation.surfaceId != surfaceId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto it = inflightAnimation.keyFrames.begin();
|
||||||
|
it != inflightAnimation.keyFrames.end();
|
||||||
|
it++) {
|
||||||
|
auto &animatedKeyFrame = *it;
|
||||||
|
|
||||||
|
// Detect if they're in the same view hierarchy, but not equivalent
|
||||||
|
// (We've already detected direct conflicts and handled them above)
|
||||||
|
if (animatedKeyFrame.parentView.tag != mutation.parentShadowView.tag) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (animatedKeyFrame.type != AnimationConfigurationType::Noop) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!animatedKeyFrame.finalMutationForKeyFrame.has_value()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ShadowViewMutation &finalAnimationMutation =
|
||||||
|
*animatedKeyFrame.finalMutationForKeyFrame;
|
||||||
|
|
||||||
|
if (finalAnimationMutation.type != ShadowViewMutation::Type::Remove) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do we need to adjust the index of this operation?
|
||||||
|
if (isRemoveMutation && mutation.index <= finalAnimationMutation.index) {
|
||||||
|
finalAnimationMutation.index--;
|
||||||
|
} else if (
|
||||||
|
isInsertMutation && mutation.index <= finalAnimationMutation.index) {
|
||||||
|
finalAnimationMutation.index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
better::optional<MountingTransaction>
|
||||||
|
LayoutAnimationKeyFrameManager::pullTransaction(
|
||||||
|
SurfaceId surfaceId,
|
||||||
|
MountingTransaction::Number transactionNumber,
|
||||||
|
MountingTelemetry const &telemetry,
|
||||||
|
ShadowViewMutationList mutations) const {
|
||||||
|
// Current time in milliseconds
|
||||||
|
uint64_t now =
|
||||||
|
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
std::chrono::high_resolution_clock::now().time_since_epoch())
|
||||||
|
.count();
|
||||||
|
|
||||||
|
if (!mutations.empty()) {
|
||||||
|
#ifdef RN_SHADOW_TREE_INTROSPECTION
|
||||||
|
{
|
||||||
|
std::stringstream ss(getDebugDescription(mutations, {}));
|
||||||
|
std::string to;
|
||||||
|
while (std::getline(ss, to, '\n')) {
|
||||||
|
LOG(ERROR)
|
||||||
|
<< "LayoutAnimationKeyFrameManager.cpp: got mutation list: Line: "
|
||||||
|
<< to;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// What to do if we detect a conflict? Get current value and make
|
||||||
|
// that the baseline of the next animation. Scale the remaining time
|
||||||
|
// in the animation
|
||||||
|
// Types of conflicts and how we handle them:
|
||||||
|
// Update -> update: remove the previous update, make it the baseline of the
|
||||||
|
// next update (with current progress) Update -> remove: same, with final
|
||||||
|
// mutation being a remove Insert -> update: treat as update->update Insert
|
||||||
|
// -> remove: same, as update->remove Remove -> update/insert: not possible
|
||||||
|
// We just collect pairs here of <Mutation, AnimationConfig> and delete them
|
||||||
|
// from active animations. If another animation is queued up from the
|
||||||
|
// current mutations then these deleted mutations will serve as the baseline
|
||||||
|
// for the next animation. If not, the current mutations are executed
|
||||||
|
// immediately without issues.
|
||||||
|
std::vector<
|
||||||
|
std::tuple<AnimationKeyFrame, AnimationConfig, LayoutAnimation *>>
|
||||||
|
conflictingAnimations{};
|
||||||
|
for (auto &mutation : mutations) {
|
||||||
|
auto const &baselineShadowView =
|
||||||
|
(mutation.type == ShadowViewMutation::Type::Insert)
|
||||||
|
? mutation.newChildShadowView
|
||||||
|
: mutation.oldChildShadowView;
|
||||||
|
|
||||||
|
for (auto &inflightAnimation : inflightAnimations_) {
|
||||||
|
if (inflightAnimation.surfaceId != surfaceId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto it = inflightAnimation.keyFrames.begin();
|
||||||
|
it != inflightAnimation.keyFrames.end();) {
|
||||||
|
auto &animatedKeyFrame = *it;
|
||||||
|
|
||||||
|
// Conflicting animation detected
|
||||||
|
if (animatedKeyFrame.tag == baselineShadowView.tag) {
|
||||||
|
auto const layoutAnimationConfig =
|
||||||
|
inflightAnimation.layoutAnimationConfig;
|
||||||
|
|
||||||
|
auto const mutationConfig =
|
||||||
|
(animatedKeyFrame.type == AnimationConfigurationType::Delete
|
||||||
|
? layoutAnimationConfig.deleteConfig
|
||||||
|
: (animatedKeyFrame.type ==
|
||||||
|
AnimationConfigurationType::Create
|
||||||
|
? layoutAnimationConfig.createConfig
|
||||||
|
: layoutAnimationConfig.updateConfig));
|
||||||
|
|
||||||
|
conflictingAnimations.push_back(std::make_tuple(
|
||||||
|
animatedKeyFrame, *mutationConfig, &inflightAnimation));
|
||||||
|
|
||||||
|
// Delete from existing animation
|
||||||
|
it = inflightAnimation.keyFrames.erase(it);
|
||||||
|
} else {
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Are we animating this list of mutations?
|
||||||
|
better::optional<LayoutAnimation> currentAnimation{};
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(currentAnimationMutex_);
|
||||||
|
if (currentAnimation_) {
|
||||||
|
currentAnimation = currentAnimation_;
|
||||||
|
currentAnimation_ = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentAnimation) {
|
||||||
|
LayoutAnimation animation = currentAnimation.value();
|
||||||
|
animation.surfaceId = surfaceId;
|
||||||
|
animation.startTime = now;
|
||||||
|
|
||||||
|
// Pre-process list to:
|
||||||
|
// Catch remove+reinsert (reorders)
|
||||||
|
// Catch delete+create (reparenting) (this should be optimized away at
|
||||||
|
// the diffing level eventually?)
|
||||||
|
// TODO: to prevent this step we could tag Remove/Insert mutations as
|
||||||
|
// being moves on the Differ level, since we know that there? We could use
|
||||||
|
// TinyMap here, but it's not exposed by Differentiator (yet).
|
||||||
|
std::vector<Tag> insertedTags;
|
||||||
|
std::vector<Tag> createdTags;
|
||||||
|
std::unordered_map<Tag, ShadowViewMutation> movedTags;
|
||||||
|
std::vector<Tag> reparentedTags;
|
||||||
|
for (const auto &mutation : mutations) {
|
||||||
|
if (mutation.type == ShadowViewMutation::Type::Insert) {
|
||||||
|
insertedTags.push_back(mutation.newChildShadowView.tag);
|
||||||
|
}
|
||||||
|
if (mutation.type == ShadowViewMutation::Type::Create) {
|
||||||
|
createdTags.push_back(mutation.newChildShadowView.tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process mutations list into operations that can be sent to platform
|
||||||
|
// immediately, and those that need to be animated Deletions, removals,
|
||||||
|
// updates are delayed and animated. Creations and insertions are sent to
|
||||||
|
// platform and then "animated in" with opacity updates. Upon completion,
|
||||||
|
// removals and deletions are sent to platform
|
||||||
|
ShadowViewMutation::List immediateMutations;
|
||||||
|
|
||||||
|
// Remove operations that are actually moves should be copied to
|
||||||
|
// "immediate mutations". The corresponding "insert" will also be executed
|
||||||
|
// immediately and animated as an update.
|
||||||
|
std::vector<AnimationKeyFrame> keyFramesToAnimate;
|
||||||
|
std::vector<AnimationKeyFrame> movesToAnimate;
|
||||||
|
auto const layoutAnimationConfig = animation.layoutAnimationConfig;
|
||||||
|
for (auto &mutation : mutations) {
|
||||||
|
ShadowView baselineShadowView =
|
||||||
|
(mutation.type == ShadowViewMutation::Type::Delete ||
|
||||||
|
mutation.type == ShadowViewMutation::Type::Remove
|
||||||
|
? mutation.oldChildShadowView
|
||||||
|
: mutation.newChildShadowView);
|
||||||
|
auto const &componentDescriptor =
|
||||||
|
getComponentDescriptorForShadowView(baselineShadowView);
|
||||||
|
|
||||||
|
auto mutationConfig =
|
||||||
|
(mutation.type == ShadowViewMutation::Type::Delete
|
||||||
|
? layoutAnimationConfig.deleteConfig
|
||||||
|
: (mutation.type == ShadowViewMutation::Type::Insert
|
||||||
|
? layoutAnimationConfig.createConfig
|
||||||
|
: layoutAnimationConfig.updateConfig));
|
||||||
|
|
||||||
|
bool isRemoveReinserted =
|
||||||
|
mutation.type == ShadowViewMutation::Type::Remove &&
|
||||||
|
std::find(
|
||||||
|
insertedTags.begin(),
|
||||||
|
insertedTags.end(),
|
||||||
|
mutation.oldChildShadowView.tag) != insertedTags.end();
|
||||||
|
|
||||||
|
// Reparenting can result in a node being removed, inserted (moved) and
|
||||||
|
// also deleted and created in the same frame, with the same props etc.
|
||||||
|
// This should eventually be optimized out of the diffing algorithm, but
|
||||||
|
// for now we detect reparenting and prevent the corresponding
|
||||||
|
// Delete/Create instructions from being animated.
|
||||||
|
bool isReparented =
|
||||||
|
(mutation.type == ShadowViewMutation::Delete &&
|
||||||
|
std::find(
|
||||||
|
createdTags.begin(),
|
||||||
|
createdTags.end(),
|
||||||
|
mutation.oldChildShadowView.tag) != createdTags.end()) ||
|
||||||
|
(mutation.type == ShadowViewMutation::Create &&
|
||||||
|
std::find(
|
||||||
|
reparentedTags.begin(),
|
||||||
|
reparentedTags.end(),
|
||||||
|
mutation.newChildShadowView.tag) != reparentedTags.end());
|
||||||
|
|
||||||
|
if (isRemoveReinserted) {
|
||||||
|
movedTags.insert({mutation.oldChildShadowView.tag, mutation});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isReparented && mutation.type == ShadowViewMutation::Delete) {
|
||||||
|
reparentedTags.push_back(mutation.oldChildShadowView.tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inserts that follow a "remove" of the same tag should be treated as
|
||||||
|
// an update (move) animation.
|
||||||
|
bool wasInsertedTagRemoved = false;
|
||||||
|
bool haveConfiguration = mutationConfig.has_value();
|
||||||
|
if (mutation.type == ShadowViewMutation::Type::Insert) {
|
||||||
|
// If this is a move, we actually don't want to copy this insert
|
||||||
|
// instruction to animated instructions - we want to
|
||||||
|
// generate an Update mutation for Remove+Insert pairs to animate
|
||||||
|
// the layout.
|
||||||
|
// The corresponding Remove and Insert instructions will instead
|
||||||
|
// be treated as "immediate" instructions.
|
||||||
|
auto movedIt = movedTags.find(mutation.newChildShadowView.tag);
|
||||||
|
wasInsertedTagRemoved = movedIt != movedTags.end();
|
||||||
|
if (wasInsertedTagRemoved) {
|
||||||
|
mutationConfig = layoutAnimationConfig.updateConfig;
|
||||||
|
}
|
||||||
|
haveConfiguration = mutationConfig.has_value();
|
||||||
|
|
||||||
|
if (wasInsertedTagRemoved && haveConfiguration) {
|
||||||
|
movesToAnimate.push_back(
|
||||||
|
AnimationKeyFrame{{},
|
||||||
|
AnimationConfigurationType::Update,
|
||||||
|
mutation.newChildShadowView.tag,
|
||||||
|
mutation.parentShadowView,
|
||||||
|
movedIt->second.oldChildShadowView,
|
||||||
|
mutation.newChildShadowView});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates and inserts should also be executed immediately.
|
||||||
|
// Mutations that would otherwise be animated, but have no
|
||||||
|
// configuration, are also executed immediately.
|
||||||
|
if (isRemoveReinserted || !haveConfiguration || isReparented ||
|
||||||
|
mutation.type == ShadowViewMutation::Type::Create ||
|
||||||
|
mutation.type == ShadowViewMutation::Type::Insert) {
|
||||||
|
immediateMutations.push_back(mutation);
|
||||||
|
|
||||||
|
// Adjust indices for any non-directly-conflicting animations that
|
||||||
|
// affect the same parent view by inserting or removing anything
|
||||||
|
// from the hierarchy.
|
||||||
|
if (mutation.type == ShadowViewMutation::Type::Insert ||
|
||||||
|
mutation.type == ShadowViewMutation::Type::Remove) {
|
||||||
|
adjustDelayedMutationIndicesForMutation(surfaceId, mutation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deletes, non-move inserts, updates get animated
|
||||||
|
if (!wasInsertedTagRemoved && !isRemoveReinserted && !isReparented &&
|
||||||
|
haveConfiguration &&
|
||||||
|
mutation.type != ShadowViewMutation::Type::Create) {
|
||||||
|
ShadowView viewStart = ShadowView(
|
||||||
|
mutation.type == ShadowViewMutation::Type::Insert
|
||||||
|
? mutation.newChildShadowView
|
||||||
|
: mutation.oldChildShadowView);
|
||||||
|
ShadowView viewFinal = ShadowView(
|
||||||
|
mutation.type == ShadowViewMutation::Type::Update
|
||||||
|
? mutation.newChildShadowView
|
||||||
|
: viewStart);
|
||||||
|
ShadowView parent = mutation.parentShadowView;
|
||||||
|
Tag tag = viewStart.tag;
|
||||||
|
Tag parentTag = mutation.parentShadowView.tag;
|
||||||
|
|
||||||
|
AnimationKeyFrame keyFrame{};
|
||||||
|
if (mutation.type == ShadowViewMutation::Type::Insert) {
|
||||||
|
if (mutationConfig->animationProperty ==
|
||||||
|
AnimationProperty::Opacity) {
|
||||||
|
auto props = componentDescriptor.cloneProps(viewStart.props, {});
|
||||||
|
const auto viewProps =
|
||||||
|
dynamic_cast<const ViewProps *>(props.get());
|
||||||
|
if (viewProps != nullptr) {
|
||||||
|
const_cast<ViewProps *>(viewProps)->opacity = 0;
|
||||||
|
}
|
||||||
|
viewStart.props = props;
|
||||||
|
}
|
||||||
|
bool isScaleX = mutationConfig->animationProperty ==
|
||||||
|
AnimationProperty::ScaleX ||
|
||||||
|
mutationConfig->animationProperty == AnimationProperty::ScaleXY;
|
||||||
|
bool isScaleY = mutationConfig->animationProperty ==
|
||||||
|
AnimationProperty::ScaleY ||
|
||||||
|
mutationConfig->animationProperty == AnimationProperty::ScaleXY;
|
||||||
|
if (isScaleX || isScaleY) {
|
||||||
|
auto props = componentDescriptor.cloneProps(viewStart.props, {});
|
||||||
|
const auto viewProps =
|
||||||
|
dynamic_cast<const ViewProps *>(props.get());
|
||||||
|
if (viewProps != nullptr) {
|
||||||
|
const_cast<ViewProps *>(viewProps)->transform =
|
||||||
|
Transform::Scale(isScaleX ? 0 : 1, isScaleY ? 0 : 1, 1);
|
||||||
|
}
|
||||||
|
viewStart.props = props;
|
||||||
|
}
|
||||||
|
|
||||||
|
keyFrame = AnimationKeyFrame{{},
|
||||||
|
AnimationConfigurationType::Create,
|
||||||
|
tag,
|
||||||
|
parent,
|
||||||
|
viewStart,
|
||||||
|
viewFinal,
|
||||||
|
0};
|
||||||
|
} else if (mutation.type == ShadowViewMutation::Type::Delete) {
|
||||||
|
if (mutationConfig->animationProperty ==
|
||||||
|
AnimationProperty::Opacity) {
|
||||||
|
auto props = componentDescriptor.cloneProps(viewFinal.props, {});
|
||||||
|
const auto viewProps =
|
||||||
|
dynamic_cast<const ViewProps *>(props.get());
|
||||||
|
if (viewProps != nullptr) {
|
||||||
|
const_cast<ViewProps *>(viewProps)->opacity = 0;
|
||||||
|
}
|
||||||
|
viewFinal.props = props;
|
||||||
|
}
|
||||||
|
bool isScaleX = mutationConfig->animationProperty ==
|
||||||
|
AnimationProperty::ScaleX ||
|
||||||
|
mutationConfig->animationProperty == AnimationProperty::ScaleXY;
|
||||||
|
bool isScaleY = mutationConfig->animationProperty ==
|
||||||
|
AnimationProperty::ScaleY ||
|
||||||
|
mutationConfig->animationProperty == AnimationProperty::ScaleXY;
|
||||||
|
if (isScaleX || isScaleY) {
|
||||||
|
auto props = componentDescriptor.cloneProps(viewFinal.props, {});
|
||||||
|
const auto viewProps =
|
||||||
|
dynamic_cast<const ViewProps *>(props.get());
|
||||||
|
if (viewProps != nullptr) {
|
||||||
|
const_cast<ViewProps *>(viewProps)->transform =
|
||||||
|
Transform::Scale(isScaleX ? 0 : 1, isScaleY ? 0 : 1, 1);
|
||||||
|
}
|
||||||
|
viewFinal.props = props;
|
||||||
|
}
|
||||||
|
|
||||||
|
keyFrame = AnimationKeyFrame{
|
||||||
|
better::optional<ShadowViewMutation>(mutation),
|
||||||
|
AnimationConfigurationType::Delete,
|
||||||
|
tag,
|
||||||
|
parent,
|
||||||
|
viewStart,
|
||||||
|
viewFinal,
|
||||||
|
0};
|
||||||
|
} else if (mutation.type == ShadowViewMutation::Type::Update) {
|
||||||
|
viewFinal = ShadowView(mutation.newChildShadowView);
|
||||||
|
|
||||||
|
keyFrame = AnimationKeyFrame{
|
||||||
|
better::optional<ShadowViewMutation>(mutation),
|
||||||
|
AnimationConfigurationType::Update,
|
||||||
|
tag,
|
||||||
|
parent,
|
||||||
|
viewStart,
|
||||||
|
viewFinal,
|
||||||
|
0};
|
||||||
|
} else {
|
||||||
|
// This should just be "Remove" instructions that are not animated
|
||||||
|
// (either this is a "move", or there's a corresponding "Delete"
|
||||||
|
// that is animated). We configure it as a Noop animation so it is
|
||||||
|
// executed when all the other animations are completed.
|
||||||
|
assert(mutation.type == ShadowViewMutation::Type::Remove);
|
||||||
|
|
||||||
|
// For remove instructions: since the execution of the Remove
|
||||||
|
// instruction will be delayed and therefore may execute outside of
|
||||||
|
// otherwise-expected order, other views may be inserted before the
|
||||||
|
// Remove is executed, requiring index adjustment.
|
||||||
|
{
|
||||||
|
int adjustedIndex = mutation.index;
|
||||||
|
for (const auto &otherMutation : mutations) {
|
||||||
|
if (otherMutation.type == ShadowViewMutation::Type::Insert &&
|
||||||
|
otherMutation.parentShadowView.tag == parentTag) {
|
||||||
|
if (otherMutation.index <= adjustedIndex) {
|
||||||
|
adjustedIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation = ShadowViewMutation::RemoveMutation(
|
||||||
|
mutation.parentShadowView,
|
||||||
|
mutation.oldChildShadowView,
|
||||||
|
adjustedIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
keyFrame = AnimationKeyFrame{
|
||||||
|
better::optional<ShadowViewMutation>(mutation),
|
||||||
|
AnimationConfigurationType::Noop,
|
||||||
|
tag,
|
||||||
|
parent,
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
0};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle conflicting animations
|
||||||
|
for (auto &conflictingKeyframeTuple : conflictingAnimations) {
|
||||||
|
auto &conflictingKeyFrame = std::get<0>(conflictingKeyframeTuple);
|
||||||
|
auto const &conflictingMutationBaselineShadowView =
|
||||||
|
conflictingKeyFrame.viewStart;
|
||||||
|
|
||||||
|
// We've found a conflict.
|
||||||
|
if (conflictingMutationBaselineShadowView.tag == tag) {
|
||||||
|
// What's the progress of this ongoing animation?
|
||||||
|
double conflictingAnimationProgress =
|
||||||
|
calculateAnimationProgress(
|
||||||
|
now,
|
||||||
|
*std::get<2>(conflictingKeyframeTuple),
|
||||||
|
std::get<1>(conflictingKeyframeTuple))
|
||||||
|
.first;
|
||||||
|
|
||||||
|
// Get a baseline ShadowView at the current progress of the
|
||||||
|
// inflight animation. TODO: handle multiple properties being
|
||||||
|
// animated separately?
|
||||||
|
auto interpolatedInflightShadowView =
|
||||||
|
createInterpolatedShadowView(
|
||||||
|
conflictingAnimationProgress,
|
||||||
|
std::get<1>(conflictingKeyframeTuple),
|
||||||
|
conflictingKeyFrame.viewStart,
|
||||||
|
conflictingKeyFrame.viewEnd);
|
||||||
|
|
||||||
|
// Pick a Prop or layout property, depending on the current
|
||||||
|
// animation configuration. Figure out how much progress we've
|
||||||
|
// already made in the current animation, and start the animation
|
||||||
|
// from this point.
|
||||||
|
keyFrame.viewStart = interpolatedInflightShadowView;
|
||||||
|
keyFrame.initialProgress = getProgressThroughAnimation(
|
||||||
|
keyFrame, &animation, interpolatedInflightShadowView);
|
||||||
|
|
||||||
|
// We're guaranteed that a tag only has one animation associated
|
||||||
|
// with it, so we can break here. If we support multiple
|
||||||
|
// animations and animation curves over the same tag in the
|
||||||
|
// future, this will need to be modified to support that.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keyFramesToAnimate.push_back(keyFrame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef RN_SHADOW_TREE_INTROSPECTION
|
||||||
|
{
|
||||||
|
std::stringstream ss(getDebugDescription(immediateMutations, {}));
|
||||||
|
std::string to;
|
||||||
|
while (std::getline(ss, to, '\n')) {
|
||||||
|
LOG(ERROR)
|
||||||
|
<< "LayoutAnimationKeyFrameManager.cpp: got IMMEDIATE list: Line: "
|
||||||
|
<< to;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::stringstream ss(getDebugDescription(mutationsToAnimate, {}));
|
||||||
|
std::string to;
|
||||||
|
while (std::getline(ss, to, '\n')) {
|
||||||
|
LOG(ERROR)
|
||||||
|
<< "LayoutAnimationKeyFrameManager.cpp: got FINAL list: Line: "
|
||||||
|
<< to;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
animation.keyFrames = keyFramesToAnimate;
|
||||||
|
inflightAnimations_.push_back(animation);
|
||||||
|
|
||||||
|
// These will be executed immediately.
|
||||||
|
mutations = immediateMutations;
|
||||||
|
} /* if (currentAnimation) */ else {
|
||||||
|
// If there's no "next" animation, make sure we queue up "final"
|
||||||
|
// operations from all ongoing animations.
|
||||||
|
ShadowViewMutationList finalMutationsForConflictingAnimations{};
|
||||||
|
for (auto &conflictingKeyframeTuple : conflictingAnimations) {
|
||||||
|
auto &keyFrame = std::get<0>(conflictingKeyframeTuple);
|
||||||
|
if (keyFrame.finalMutationForKeyFrame.hasValue()) {
|
||||||
|
finalMutationsForConflictingAnimations.push_back(
|
||||||
|
*keyFrame.finalMutationForKeyFrame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append mutations to this list and swap - so that the final
|
||||||
|
// conflicting mutations happen before any other mutations
|
||||||
|
finalMutationsForConflictingAnimations.insert(
|
||||||
|
finalMutationsForConflictingAnimations.end(),
|
||||||
|
mutations.begin(),
|
||||||
|
mutations.end());
|
||||||
|
mutations = finalMutationsForConflictingAnimations;
|
||||||
|
|
||||||
|
// Adjust pending mutation indices base on these operations
|
||||||
|
for (auto &mutation : mutations) {
|
||||||
|
if (mutation.type == ShadowViewMutation::Type::Insert ||
|
||||||
|
mutation.type == ShadowViewMutation::Type::Remove) {
|
||||||
|
adjustDelayedMutationIndicesForMutation(surfaceId, mutation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // if (mutations)
|
||||||
|
|
||||||
|
// We never commit a different root or modify anything -
|
||||||
|
// we just send additional mutations to the mounting layer until the
|
||||||
|
// animations are finished and the mounting layer (view) represents exactly
|
||||||
|
// what is in the most recent shadow tree
|
||||||
|
// Add animation mutations to the end of our existing mutations list in this
|
||||||
|
// function.
|
||||||
|
ShadowViewMutationList mutationsForAnimation{};
|
||||||
|
animationMutationsForFrame(surfaceId, mutationsForAnimation, now);
|
||||||
|
|
||||||
|
// Adjust pending mutation indices base on these operations
|
||||||
|
// For example: if a final "remove" mutation has been performed, and there is
|
||||||
|
// another that has not yet been executed because it is a part of an ongoing
|
||||||
|
// animation, its index may need to be adjusted.
|
||||||
|
for (auto const &animatedMutation : mutationsForAnimation) {
|
||||||
|
if (animatedMutation.type == ShadowViewMutation::Type::Insert ||
|
||||||
|
animatedMutation.type == ShadowViewMutation::Type::Remove) {
|
||||||
|
adjustDelayedMutationIndicesForMutation(surfaceId, animatedMutation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutations.insert(
|
||||||
|
mutations.end(),
|
||||||
|
mutationsForAnimation.begin(),
|
||||||
|
mutationsForAnimation.end());
|
||||||
|
|
||||||
|
// TODO: fill in telemetry
|
||||||
|
return MountingTransaction{
|
||||||
|
surfaceId, transactionNumber, std::move(mutations), {}};
|
||||||
|
}
|
||||||
|
|
||||||
|
ComponentDescriptor const &
|
||||||
|
LayoutAnimationKeyFrameManager::getComponentDescriptorForShadowView(
|
||||||
|
ShadowView const &shadowView) const {
|
||||||
|
return componentDescriptorRegistry_->at(shadowView.componentHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LayoutAnimationKeyFrameManager::setComponentDescriptorRegistry(
|
||||||
|
const SharedComponentDescriptorRegistry &componentDescriptorRegistry) {
|
||||||
|
componentDescriptorRegistry_ = componentDescriptorRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a `progress` between 0 and 1, a mutation and LayoutAnimation config,
|
||||||
|
* return a ShadowView with mutated props and/or LayoutMetrics.
|
||||||
|
*
|
||||||
|
* @param progress
|
||||||
|
* @param layoutAnimation
|
||||||
|
* @param animatedMutation
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
ShadowView LayoutAnimationKeyFrameManager::createInterpolatedShadowView(
|
||||||
|
double progress,
|
||||||
|
AnimationConfig const &animationConfig,
|
||||||
|
ShadowView startingView,
|
||||||
|
ShadowView finalView) const {
|
||||||
|
ComponentDescriptor const &componentDescriptor =
|
||||||
|
getComponentDescriptorForShadowView(startingView);
|
||||||
|
auto mutatedShadowView = ShadowView(startingView);
|
||||||
|
|
||||||
|
// Animate opacity or scale/transform
|
||||||
|
mutatedShadowView.props = componentDescriptor.interpolateProps(
|
||||||
|
progress, startingView.props, finalView.props);
|
||||||
|
|
||||||
|
// Interpolate LayoutMetrics
|
||||||
|
LayoutMetrics const &finalLayoutMetrics = finalView.layoutMetrics;
|
||||||
|
LayoutMetrics const &baselineLayoutMetrics = startingView.layoutMetrics;
|
||||||
|
LayoutMetrics interpolatedLayoutMetrics = finalLayoutMetrics;
|
||||||
|
interpolatedLayoutMetrics.frame.origin.x = interpolateFloats(
|
||||||
|
progress,
|
||||||
|
baselineLayoutMetrics.frame.origin.x,
|
||||||
|
finalLayoutMetrics.frame.origin.x);
|
||||||
|
interpolatedLayoutMetrics.frame.origin.y = interpolateFloats(
|
||||||
|
progress,
|
||||||
|
baselineLayoutMetrics.frame.origin.y,
|
||||||
|
finalLayoutMetrics.frame.origin.y);
|
||||||
|
interpolatedLayoutMetrics.frame.size.width = interpolateFloats(
|
||||||
|
progress,
|
||||||
|
baselineLayoutMetrics.frame.size.width,
|
||||||
|
finalLayoutMetrics.frame.size.width);
|
||||||
|
interpolatedLayoutMetrics.frame.size.height = interpolateFloats(
|
||||||
|
progress,
|
||||||
|
baselineLayoutMetrics.frame.size.height,
|
||||||
|
finalLayoutMetrics.frame.size.height);
|
||||||
|
mutatedShadowView.layoutMetrics = interpolatedLayoutMetrics;
|
||||||
|
|
||||||
|
return mutatedShadowView;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace react
|
||||||
|
} // namespace facebook
|
|
@ -0,0 +1,164 @@
|
||||||
|
/*
|
||||||
|
* 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/EventTarget.h>
|
||||||
|
#include <react/core/RawValue.h>
|
||||||
|
#include <react/mounting/Differentiator.h>
|
||||||
|
#include <react/mounting/MountingCoordinator.h>
|
||||||
|
#include <react/mounting/MountingOverrideDelegate.h>
|
||||||
|
#include <react/mounting/MountingTransaction.h>
|
||||||
|
#include <react/mounting/ShadowViewMutation.h>
|
||||||
|
#include <react/uimanager/UIManagerAnimationDelegate.h>
|
||||||
|
|
||||||
|
namespace facebook {
|
||||||
|
namespace react {
|
||||||
|
|
||||||
|
// This corresponds exactly with JS.
|
||||||
|
enum class AnimationType {
|
||||||
|
Spring,
|
||||||
|
Linear,
|
||||||
|
EaseInEaseOut,
|
||||||
|
EaseIn,
|
||||||
|
EaseOut,
|
||||||
|
Keyboard
|
||||||
|
};
|
||||||
|
enum class AnimationProperty {
|
||||||
|
NotApplicable,
|
||||||
|
Opacity,
|
||||||
|
ScaleX,
|
||||||
|
ScaleY,
|
||||||
|
ScaleXY
|
||||||
|
};
|
||||||
|
enum class AnimationConfigurationType {
|
||||||
|
Noop, // for animation placeholders that are not animated, and should be
|
||||||
|
// executed once other animations have completed
|
||||||
|
Create,
|
||||||
|
Update,
|
||||||
|
Delete
|
||||||
|
};
|
||||||
|
|
||||||
|
// This corresponds exactly with JS.
|
||||||
|
struct AnimationConfig {
|
||||||
|
AnimationType animationType;
|
||||||
|
AnimationProperty animationProperty;
|
||||||
|
double duration; // these are perhaps better represented as uint64_t, but they
|
||||||
|
// come from JS as doubles
|
||||||
|
double delay;
|
||||||
|
double springDamping;
|
||||||
|
double initialVelocity;
|
||||||
|
};
|
||||||
|
|
||||||
|
// This corresponds exactly with JS.
|
||||||
|
struct LayoutAnimationConfig {
|
||||||
|
double duration; // ms
|
||||||
|
better::optional<AnimationConfig> createConfig;
|
||||||
|
better::optional<AnimationConfig> updateConfig;
|
||||||
|
better::optional<AnimationConfig> deleteConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AnimationKeyFrame {
|
||||||
|
// The mutation that should be executed once the animation completes
|
||||||
|
// (optional).
|
||||||
|
better::optional<ShadowViewMutation> finalMutationForKeyFrame;
|
||||||
|
|
||||||
|
// The type of animation this is (for configuration purposes)
|
||||||
|
AnimationConfigurationType type;
|
||||||
|
|
||||||
|
// Tag representing the node being animated.
|
||||||
|
Tag tag;
|
||||||
|
|
||||||
|
ShadowView parentView;
|
||||||
|
|
||||||
|
// ShadowView representing the start and end points of this animation.
|
||||||
|
ShadowView viewStart;
|
||||||
|
ShadowView viewEnd;
|
||||||
|
|
||||||
|
// If an animation interrupts an existing one, the starting state may actually
|
||||||
|
// be halfway through the intended transition.
|
||||||
|
double initialProgress;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LayoutAnimation {
|
||||||
|
SurfaceId surfaceId;
|
||||||
|
uint64_t startTime;
|
||||||
|
bool completed = false;
|
||||||
|
LayoutAnimationConfig layoutAnimationConfig;
|
||||||
|
std::shared_ptr<const EventTarget> successCallback;
|
||||||
|
std::shared_ptr<const EventTarget> errorCallback;
|
||||||
|
std::vector<AnimationKeyFrame> keyFrames;
|
||||||
|
};
|
||||||
|
|
||||||
|
class LayoutAnimationKeyFrameManager : public UIManagerAnimationDelegate,
|
||||||
|
public MountingOverrideDelegate {
|
||||||
|
public:
|
||||||
|
void uiManagerDidConfigureNextLayoutAnimation(
|
||||||
|
RawValue const &config,
|
||||||
|
std::shared_ptr<EventTarget const> successCallback,
|
||||||
|
std::shared_ptr<EventTarget const> errorCallback) const override;
|
||||||
|
void setComponentDescriptorRegistry(SharedComponentDescriptorRegistry const &
|
||||||
|
componentDescriptorRegistry) override;
|
||||||
|
|
||||||
|
// TODO: add SurfaceId to this API as well
|
||||||
|
bool shouldAnimateFrame() const override;
|
||||||
|
|
||||||
|
bool shouldOverridePullTransaction() const override;
|
||||||
|
|
||||||
|
// This is used to "hijack" the diffing process to figure out which mutations
|
||||||
|
// should be animated. The mutations returned by this function will be
|
||||||
|
// executed immediately.
|
||||||
|
better::optional<MountingTransaction> pullTransaction(
|
||||||
|
SurfaceId surfaceId,
|
||||||
|
MountingTransaction::Number number,
|
||||||
|
MountingTelemetry const &telemetry,
|
||||||
|
ShadowViewMutationList mutations) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void adjustDelayedMutationIndicesForMutation(
|
||||||
|
SurfaceId surfaceId,
|
||||||
|
ShadowViewMutation const &mutation) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
ComponentDescriptor const &getComponentDescriptorForShadowView(
|
||||||
|
ShadowView const &shadowView) const;
|
||||||
|
std::pair<double, double> calculateAnimationProgress(
|
||||||
|
uint64_t now,
|
||||||
|
LayoutAnimation const &animation,
|
||||||
|
AnimationConfig const &mutationConfig) const;
|
||||||
|
|
||||||
|
ShadowView createInterpolatedShadowView(
|
||||||
|
double progress,
|
||||||
|
AnimationConfig const &animationConfig,
|
||||||
|
ShadowView startingView,
|
||||||
|
ShadowView finalView) const;
|
||||||
|
|
||||||
|
virtual void animationMutationsForFrame(
|
||||||
|
SurfaceId surfaceId,
|
||||||
|
ShadowViewMutation::List &mutationsList,
|
||||||
|
uint64_t now) const = 0;
|
||||||
|
|
||||||
|
virtual double getProgressThroughAnimation(
|
||||||
|
AnimationKeyFrame const &keyFrame,
|
||||||
|
LayoutAnimation const *layoutAnimation,
|
||||||
|
ShadowView const &animationStateView) const = 0;
|
||||||
|
|
||||||
|
SharedComponentDescriptorRegistry componentDescriptorRegistry_;
|
||||||
|
mutable better::optional<LayoutAnimation> currentAnimation_{};
|
||||||
|
mutable std::mutex currentAnimationMutex_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All mutations of inflightAnimations_ are thread-safe as long as
|
||||||
|
* we keep the contract of: only mutate it within the context of
|
||||||
|
* `pullTransaction`. If that contract is held, this is implicitly protected
|
||||||
|
* by the MountingCoordinator's mutex.
|
||||||
|
*/
|
||||||
|
mutable std::vector<LayoutAnimation> inflightAnimations_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace react
|
||||||
|
} // namespace facebook
|
|
@ -64,6 +64,7 @@ class RawValue {
|
||||||
private:
|
private:
|
||||||
friend class RawProps;
|
friend class RawProps;
|
||||||
friend class RawPropsParser;
|
friend class RawPropsParser;
|
||||||
|
friend class UIManagerBinding;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Arbitrary constructors are private only for RawProps and internal usage.
|
* Arbitrary constructors are private only for RawProps and internal usage.
|
||||||
|
@ -73,9 +74,9 @@ class RawValue {
|
||||||
RawValue(folly::dynamic &&dynamic) noexcept : dynamic_(std::move(dynamic)){};
|
RawValue(folly::dynamic &&dynamic) noexcept : dynamic_(std::move(dynamic)){};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copy constructor and copy assignment operator are private and only for
|
* Copy constructor and copy assignment operator would be private and only for
|
||||||
* internal use. Basically, it's implementation details. Other particular
|
* internal use, but it's needed for user-code that does `auto val =
|
||||||
* implementations of the `RawValue` interface may not have them.
|
* (better::map<std::string, RawValue>)rawVal;`
|
||||||
*/
|
*/
|
||||||
RawValue(RawValue const &other) noexcept : dynamic_(other.dynamic_) {}
|
RawValue(RawValue const &other) noexcept : dynamic_(other.dynamic_) {}
|
||||||
|
|
||||||
|
|
|
@ -19,9 +19,12 @@
|
||||||
namespace facebook {
|
namespace facebook {
|
||||||
namespace react {
|
namespace react {
|
||||||
|
|
||||||
MountingCoordinator::MountingCoordinator(ShadowTreeRevision baseRevision)
|
MountingCoordinator::MountingCoordinator(
|
||||||
|
ShadowTreeRevision baseRevision,
|
||||||
|
MountingOverrideDelegate *delegate)
|
||||||
: surfaceId_(baseRevision.getRootShadowNode().getSurfaceId()),
|
: surfaceId_(baseRevision.getRootShadowNode().getSurfaceId()),
|
||||||
baseRevision_(baseRevision) {
|
baseRevision_(baseRevision),
|
||||||
|
mountingOverrideDelegate_(delegate) {
|
||||||
#ifdef RN_SHADOW_TREE_INTROSPECTION
|
#ifdef RN_SHADOW_TREE_INTROSPECTION
|
||||||
stubViewTree_ = stubViewTreeFromShadowNode(baseRevision_.getRootShadowNode());
|
stubViewTree_ = stubViewTreeFromShadowNode(baseRevision_.getRootShadowNode());
|
||||||
#endif
|
#endif
|
||||||
|
@ -66,31 +69,30 @@ bool MountingCoordinator::waitForTransaction(
|
||||||
lock, timeout, [this]() { return lastRevision_.has_value(); });
|
lock, timeout, [this]() { return lastRevision_.has_value(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
better::optional<MountingTransaction> MountingCoordinator::pullTransaction()
|
void MountingCoordinator::updateBaseRevision(
|
||||||
const {
|
ShadowTreeRevision const &baseRevision) const {
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
baseRevision_ = std::move(baseRevision);
|
||||||
|
}
|
||||||
|
|
||||||
if (!lastRevision_.has_value()) {
|
void MountingCoordinator::resetLatestRevision() const {
|
||||||
return {};
|
lastRevision_.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
number_++;
|
|
||||||
|
|
||||||
auto telemetry = lastRevision_->getTelemetry();
|
|
||||||
telemetry.willDiff();
|
|
||||||
|
|
||||||
auto mutations = calculateShadowViewMutations(
|
|
||||||
baseRevision_.getRootShadowNode(), lastRevision_->getRootShadowNode());
|
|
||||||
|
|
||||||
telemetry.didDiff();
|
|
||||||
|
|
||||||
#ifdef RN_SHADOW_TREE_INTROSPECTION
|
#ifdef RN_SHADOW_TREE_INTROSPECTION
|
||||||
|
void MountingCoordinator::validateTransactionAgainstStubViewTree(
|
||||||
|
ShadowViewMutationList const &mutations,
|
||||||
|
bool assertEquality) const {
|
||||||
|
std::string line;
|
||||||
|
|
||||||
|
std::stringstream ssMutations(getDebugDescription(mutations, {}));
|
||||||
|
while (std::getline(ssMutations, line, '\n')) {
|
||||||
|
LOG(ERROR) << "Mutations:" << line;
|
||||||
|
}
|
||||||
|
|
||||||
stubViewTree_.mutate(mutations);
|
stubViewTree_.mutate(mutations);
|
||||||
auto stubViewTree =
|
auto stubViewTree =
|
||||||
stubViewTreeFromShadowNode(lastRevision_->getRootShadowNode());
|
stubViewTreeFromShadowNode(lastRevision_->getRootShadowNode());
|
||||||
|
|
||||||
std::string line;
|
|
||||||
|
|
||||||
std::stringstream ssOldTree(
|
std::stringstream ssOldTree(
|
||||||
baseRevision_.getRootShadowNode().getDebugDescription());
|
baseRevision_.getRootShadowNode().getDebugDescription());
|
||||||
while (std::getline(ssOldTree, line, '\n')) {
|
while (std::getline(ssOldTree, line, '\n')) {
|
||||||
|
@ -103,19 +105,65 @@ better::optional<MountingTransaction> MountingCoordinator::pullTransaction()
|
||||||
LOG(ERROR) << "New tree:" << line;
|
LOG(ERROR) << "New tree:" << line;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::stringstream ssMutations(getDebugDescription(mutations, {}));
|
if (assertEquality) {
|
||||||
while (std::getline(ssMutations, line, '\n')) {
|
assert(stubViewTree_ == stubViewTree);
|
||||||
LOG(ERROR) << "Mutations:" << line;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
assert(stubViewTree_ == stubViewTree);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
baseRevision_ = std::move(*lastRevision_);
|
better::optional<MountingTransaction> MountingCoordinator::pullTransaction()
|
||||||
lastRevision_.reset();
|
const {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
|
||||||
return MountingTransaction{
|
bool shouldOverridePullTransaction = mountingOverrideDelegate_ != nullptr &&
|
||||||
surfaceId_, number_, std::move(mutations), telemetry};
|
mountingOverrideDelegate_->shouldOverridePullTransaction();
|
||||||
|
|
||||||
|
if (!shouldOverridePullTransaction && !lastRevision_.has_value()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
number_++;
|
||||||
|
|
||||||
|
ShadowViewMutation::List diffMutations{};
|
||||||
|
auto telemetry =
|
||||||
|
(lastRevision_.hasValue() ? lastRevision_->getTelemetry()
|
||||||
|
: MountingTelemetry{});
|
||||||
|
if (lastRevision_.hasValue()) {
|
||||||
|
telemetry.willDiff();
|
||||||
|
|
||||||
|
diffMutations = calculateShadowViewMutations(
|
||||||
|
baseRevision_.getRootShadowNode(), lastRevision_->getRootShadowNode());
|
||||||
|
|
||||||
|
telemetry.didDiff();
|
||||||
|
}
|
||||||
|
|
||||||
|
better::optional<MountingTransaction> transaction{};
|
||||||
|
|
||||||
|
// The override delegate can provide custom mounting instructions,
|
||||||
|
// even if there's no `lastRevision_`. Consider cases of animation frames
|
||||||
|
// in between React tree updates.
|
||||||
|
if (shouldOverridePullTransaction) {
|
||||||
|
transaction = mountingOverrideDelegate_->pullTransaction(
|
||||||
|
surfaceId_, number_, telemetry, std::move(diffMutations));
|
||||||
|
} else if (lastRevision_.hasValue()) {
|
||||||
|
transaction = MountingTransaction{
|
||||||
|
surfaceId_, number_, std::move(diffMutations), telemetry};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastRevision_.hasValue()) {
|
||||||
|
#ifdef RN_SHADOW_TREE_INTROSPECTION
|
||||||
|
// Only validate non-animated transactions - it's garbage to validate
|
||||||
|
// animated transactions, since the stub view tree likely won't match
|
||||||
|
// the committed tree during an animation.
|
||||||
|
this->validateTransactionAgainstStubViewTree(
|
||||||
|
transaction->getMutations(), !shouldOverridePullTransaction);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
baseRevision_ = std::move(*lastRevision_);
|
||||||
|
lastRevision_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
return transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace react
|
} // namespace react
|
||||||
|
|
|
@ -11,8 +11,10 @@
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
#include <react/mounting/Differentiator.h>
|
#include <react/mounting/Differentiator.h>
|
||||||
|
#include <react/mounting/MountingOverrideDelegate.h>
|
||||||
#include <react/mounting/MountingTransaction.h>
|
#include <react/mounting/MountingTransaction.h>
|
||||||
#include <react/mounting/ShadowTreeRevision.h>
|
#include <react/mounting/ShadowTreeRevision.h>
|
||||||
|
#include "ShadowTreeRevision.h"
|
||||||
|
|
||||||
#ifdef RN_SHADOW_TREE_INTROSPECTION
|
#ifdef RN_SHADOW_TREE_INTROSPECTION
|
||||||
#include <react/mounting/stubs.h>
|
#include <react/mounting/stubs.h>
|
||||||
|
@ -33,10 +35,12 @@ class MountingCoordinator final {
|
||||||
using Shared = std::shared_ptr<MountingCoordinator const>;
|
using Shared = std::shared_ptr<MountingCoordinator const>;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The constructor is ment to be used only inside `ShadowTree`, and it's
|
* The constructor is meant to be used only inside `ShadowTree`, and it's
|
||||||
* `public` only to enable using with `std::make_shared<>`.
|
* `public` only to enable using with `std::make_shared<>`.
|
||||||
*/
|
*/
|
||||||
MountingCoordinator(ShadowTreeRevision baseRevision);
|
MountingCoordinator(
|
||||||
|
ShadowTreeRevision baseRevision,
|
||||||
|
MountingOverrideDelegate *delegate);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Returns the id of the surface that the coordinator belongs to.
|
* Returns the id of the surface that the coordinator belongs to.
|
||||||
|
@ -65,12 +69,20 @@ class MountingCoordinator final {
|
||||||
*/
|
*/
|
||||||
bool waitForTransaction(std::chrono::duration<double> timeout) const;
|
bool waitForTransaction(std::chrono::duration<double> timeout) const;
|
||||||
|
|
||||||
private:
|
/*
|
||||||
friend class ShadowTree;
|
* Methods from this section are meant to be used by
|
||||||
|
* `MountingOverrideDelegate` only.
|
||||||
|
*/
|
||||||
|
public:
|
||||||
|
void updateBaseRevision(ShadowTreeRevision const &baseRevision) const;
|
||||||
|
void resetLatestRevision() const;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Methods from this section are meant to be used by `ShadowTree` only.
|
* Methods from this section are meant to be used by `ShadowTree` only.
|
||||||
*/
|
*/
|
||||||
|
private:
|
||||||
|
friend class ShadowTree;
|
||||||
|
|
||||||
void push(ShadowTreeRevision &&revision) const;
|
void push(ShadowTreeRevision &&revision) const;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -78,7 +90,7 @@ class MountingCoordinator final {
|
||||||
* Generating a `MountingTransaction` requires some resources which the
|
* Generating a `MountingTransaction` requires some resources which the
|
||||||
* `MountingCoordinator` does not own (e.g. `ComponentDescriptor`s). Revoking
|
* `MountingCoordinator` does not own (e.g. `ComponentDescriptor`s). Revoking
|
||||||
* committed revisions allows the owner (a Shadow Tree) to make sure that
|
* committed revisions allows the owner (a Shadow Tree) to make sure that
|
||||||
* those resources will not be accessed (e.g. by the Mouting Layer).
|
* those resources will not be accessed (e.g. by the Mounting Layer).
|
||||||
*/
|
*/
|
||||||
void revoke() const;
|
void revoke() const;
|
||||||
|
|
||||||
|
@ -90,8 +102,12 @@ class MountingCoordinator final {
|
||||||
mutable better::optional<ShadowTreeRevision> lastRevision_{};
|
mutable better::optional<ShadowTreeRevision> lastRevision_{};
|
||||||
mutable MountingTransaction::Number number_{0};
|
mutable MountingTransaction::Number number_{0};
|
||||||
mutable std::condition_variable signal_;
|
mutable std::condition_variable signal_;
|
||||||
|
mutable MountingOverrideDelegate *mountingOverrideDelegate_{nullptr};
|
||||||
|
|
||||||
#ifdef RN_SHADOW_TREE_INTROSPECTION
|
#ifdef RN_SHADOW_TREE_INTROSPECTION
|
||||||
|
void validateTransactionAgainstStubViewTree(
|
||||||
|
ShadowViewMutationList const &mutations,
|
||||||
|
bool assertEquality) const;
|
||||||
mutable StubViewTree stubViewTree_; // Protected by `mutex_`.
|
mutable StubViewTree stubViewTree_; // Protected by `mutex_`.
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* 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/mounting/MountingTransaction.h>
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace facebook {
|
||||||
|
namespace react {
|
||||||
|
|
||||||
|
class MountingCoordinator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic interface for anything that needs to override specific
|
||||||
|
* MountingCoordinator methods. This is for platform-specific escape hatches
|
||||||
|
* like animations.
|
||||||
|
*/
|
||||||
|
class MountingOverrideDelegate {
|
||||||
|
public:
|
||||||
|
virtual bool shouldOverridePullTransaction() const = 0;
|
||||||
|
virtual ~MountingOverrideDelegate() {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delegates that override this method are responsible for:
|
||||||
|
*
|
||||||
|
* - Returning a MountingTransaction with mutations
|
||||||
|
* - Calling
|
||||||
|
* - Telemetry, if appropriate
|
||||||
|
*
|
||||||
|
* @param surfaceId
|
||||||
|
* @param number
|
||||||
|
* @param mountingCoordinator
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
virtual better::optional<MountingTransaction> pullTransaction(
|
||||||
|
SurfaceId surfaceId,
|
||||||
|
MountingTransaction::Number number,
|
||||||
|
MountingTelemetry const &telemetry,
|
||||||
|
ShadowViewMutationList mutations) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace react
|
||||||
|
} // namespace facebook
|
|
@ -18,7 +18,7 @@ namespace react {
|
||||||
* particularly list of mutations and meta-data associated with the commit.
|
* particularly list of mutations and meta-data associated with the commit.
|
||||||
* Movable and copyable, but moving is strongly encouraged.
|
* Movable and copyable, but moving is strongly encouraged.
|
||||||
* Beware: A moved-from object of this type has unspecified value and accessing
|
* Beware: A moved-from object of this type has unspecified value and accessing
|
||||||
* that is UB.
|
* that is UB (Undefined Behaviour).
|
||||||
*/
|
*/
|
||||||
class MountingTransaction final {
|
class MountingTransaction final {
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -221,7 +221,8 @@ ShadowTree::ShadowTree(
|
||||||
LayoutConstraints const &layoutConstraints,
|
LayoutConstraints const &layoutConstraints,
|
||||||
LayoutContext const &layoutContext,
|
LayoutContext const &layoutContext,
|
||||||
RootComponentDescriptor const &rootComponentDescriptor,
|
RootComponentDescriptor const &rootComponentDescriptor,
|
||||||
ShadowTreeDelegate const &delegate)
|
ShadowTreeDelegate const &delegate,
|
||||||
|
MountingOverrideDelegate *mountingOverrideDelegate)
|
||||||
: surfaceId_(surfaceId), delegate_(delegate) {
|
: surfaceId_(surfaceId), delegate_(delegate) {
|
||||||
const auto noopEventEmitter = std::make_shared<const ViewEventEmitter>(
|
const auto noopEventEmitter = std::make_shared<const ViewEventEmitter>(
|
||||||
nullptr, -1, std::shared_ptr<const EventDispatcher>());
|
nullptr, -1, std::shared_ptr<const EventDispatcher>());
|
||||||
|
@ -240,7 +241,7 @@ ShadowTree::ShadowTree(
|
||||||
family));
|
family));
|
||||||
|
|
||||||
mountingCoordinator_ = std::make_shared<MountingCoordinator const>(
|
mountingCoordinator_ = std::make_shared<MountingCoordinator const>(
|
||||||
ShadowTreeRevision{rootShadowNode_, 0, {}});
|
ShadowTreeRevision{rootShadowNode_, 0, {}}, mountingOverrideDelegate);
|
||||||
}
|
}
|
||||||
|
|
||||||
ShadowTree::~ShadowTree() {
|
ShadowTree::~ShadowTree() {
|
||||||
|
@ -357,7 +358,7 @@ bool ShadowTree::tryCommit(
|
||||||
mountingCoordinator_->push(
|
mountingCoordinator_->push(
|
||||||
ShadowTreeRevision{newRootShadowNode, revisionNumber, telemetry});
|
ShadowTreeRevision{newRootShadowNode, revisionNumber, telemetry});
|
||||||
|
|
||||||
delegate_.shadowTreeDidFinishTransaction(*this, mountingCoordinator_);
|
notifyDelegatesOfUpdates();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -398,5 +399,9 @@ void ShadowTree::emitLayoutEvents(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ShadowTree::notifyDelegatesOfUpdates() const {
|
||||||
|
delegate_.shadowTreeDidFinishTransaction(*this, mountingCoordinator_);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace react
|
} // namespace react
|
||||||
} // namespace facebook
|
} // namespace facebook
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#include <react/mounting/MountingCoordinator.h>
|
#include <react/mounting/MountingCoordinator.h>
|
||||||
#include <react/mounting/ShadowTreeDelegate.h>
|
#include <react/mounting/ShadowTreeDelegate.h>
|
||||||
#include <react/mounting/ShadowTreeRevision.h>
|
#include <react/mounting/ShadowTreeRevision.h>
|
||||||
|
#include "MountingOverrideDelegate.h"
|
||||||
|
|
||||||
namespace facebook {
|
namespace facebook {
|
||||||
namespace react {
|
namespace react {
|
||||||
|
@ -38,7 +39,8 @@ class ShadowTree final {
|
||||||
LayoutConstraints const &layoutConstraints,
|
LayoutConstraints const &layoutConstraints,
|
||||||
LayoutContext const &layoutContext,
|
LayoutContext const &layoutContext,
|
||||||
RootComponentDescriptor const &rootComponentDescriptor,
|
RootComponentDescriptor const &rootComponentDescriptor,
|
||||||
ShadowTreeDelegate const &delegate);
|
ShadowTreeDelegate const &delegate,
|
||||||
|
MountingOverrideDelegate *mountingOverrideDelegate);
|
||||||
|
|
||||||
~ShadowTree();
|
~ShadowTree();
|
||||||
|
|
||||||
|
@ -69,6 +71,13 @@ class ShadowTree final {
|
||||||
*/
|
*/
|
||||||
void commitEmptyTree() const;
|
void commitEmptyTree() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forces the ShadowTree to ping its delegate that an update is available.
|
||||||
|
* Useful for animations on Android.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
void notifyDelegatesOfUpdates() const;
|
||||||
|
|
||||||
MountingCoordinator::Shared getMountingCoordinator() const;
|
MountingCoordinator::Shared getMountingCoordinator() const;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -22,6 +22,10 @@ MountingTelemetry const &ShadowTreeRevision::getTelemetry() const {
|
||||||
return telemetry_;
|
return telemetry_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ShadowNode::Shared ShadowTreeRevision::getSharedRootShadowNode() {
|
||||||
|
return rootShadowNode_;
|
||||||
|
}
|
||||||
|
|
||||||
ShadowNode const &ShadowTreeRevision::getRootShadowNode() {
|
ShadowNode const &ShadowTreeRevision::getRootShadowNode() {
|
||||||
return *rootShadowNode_;
|
return *rootShadowNode_;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
#include <better/optional.h>
|
#include <better/optional.h>
|
||||||
|
|
||||||
|
#include <react/mounting/MountingOverrideDelegate.h>
|
||||||
#include <react/mounting/MountingTelemetry.h>
|
#include <react/mounting/MountingTelemetry.h>
|
||||||
#include <react/mounting/MountingTransaction.h>
|
#include <react/mounting/MountingTransaction.h>
|
||||||
#include <react/mounting/ShadowViewMutation.h>
|
#include <react/mounting/ShadowViewMutation.h>
|
||||||
|
@ -42,14 +43,21 @@ class ShadowTreeRevision final {
|
||||||
*/
|
*/
|
||||||
MountingTelemetry const &getTelemetry() const;
|
MountingTelemetry const &getTelemetry() const;
|
||||||
|
|
||||||
private:
|
/*
|
||||||
friend class MountingCoordinator;
|
* Methods from this section are meant to be used by
|
||||||
|
* `MountingOverrideDelegate` only.
|
||||||
|
*/
|
||||||
|
public:
|
||||||
|
ShadowNode const &getRootShadowNode();
|
||||||
|
ShadowNode::Shared getSharedRootShadowNode();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Methods from this section are meant to be used by `MountingCoordinator`
|
* Methods from this section are meant to be used by `MountingCoordinator`
|
||||||
* only.
|
* only.
|
||||||
*/
|
*/
|
||||||
ShadowNode const &getRootShadowNode();
|
private:
|
||||||
|
friend class MountingCoordinator;
|
||||||
|
|
||||||
Number getNumber() const;
|
Number getNumber() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -13,10 +13,10 @@ namespace facebook {
|
||||||
namespace react {
|
namespace react {
|
||||||
|
|
||||||
static LayoutMetrics layoutMetricsFromShadowNode(ShadowNode const &shadowNode) {
|
static LayoutMetrics layoutMetricsFromShadowNode(ShadowNode const &shadowNode) {
|
||||||
auto layotableShadowNode =
|
auto layoutableShadowNode =
|
||||||
traitCast<LayoutableShadowNode const *>(&shadowNode);
|
traitCast<LayoutableShadowNode const *>(&shadowNode);
|
||||||
return layotableShadowNode ? layotableShadowNode->getLayoutMetrics()
|
return layoutableShadowNode ? layoutableShadowNode->getLayoutMetrics()
|
||||||
: EmptyLayoutMetrics;
|
: EmptyLayoutMetrics;
|
||||||
}
|
}
|
||||||
|
|
||||||
ShadowView::ShadowView(const ShadowNode &shadowNode)
|
ShadowView::ShadowView(const ShadowNode &shadowNode)
|
||||||
|
|
|
@ -66,7 +66,7 @@ struct ShadowViewNodePair final {
|
||||||
ShadowNode const *shadowNode;
|
ShadowNode const *shadowNode;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The stored pointer to `ShadowNode` represents an indentity of the pair.
|
* The stored pointer to `ShadowNode` represents an identity of the pair.
|
||||||
*/
|
*/
|
||||||
bool operator==(const ShadowViewNodePair &rhs) const;
|
bool operator==(const ShadowViewNodePair &rhs) const;
|
||||||
bool operator!=(const ShadowViewNodePair &rhs) const;
|
bool operator!=(const ShadowViewNodePair &rhs) const;
|
||||||
|
|
|
@ -103,7 +103,8 @@ TEST(StateReconciliationTest, testStateReconciliation) {
|
||||||
LayoutConstraints{},
|
LayoutConstraints{},
|
||||||
LayoutContext{},
|
LayoutContext{},
|
||||||
rootComponentDescriptor,
|
rootComponentDescriptor,
|
||||||
shadowTreeDelegate};
|
shadowTreeDelegate,
|
||||||
|
nullptr};
|
||||||
|
|
||||||
shadowTree.commit(
|
shadowTree.commit(
|
||||||
[&](RootShadowNode::Shared const &oldRootShadowNode) {
|
[&](RootShadowNode::Shared const &oldRootShadowNode) {
|
||||||
|
|
|
@ -13,15 +13,23 @@
|
||||||
#include <react/componentregistry/ComponentDescriptorRegistry.h>
|
#include <react/componentregistry/ComponentDescriptorRegistry.h>
|
||||||
#include <react/core/LayoutContext.h>
|
#include <react/core/LayoutContext.h>
|
||||||
#include <react/debug/SystraceSection.h>
|
#include <react/debug/SystraceSection.h>
|
||||||
|
#include <react/mounting/MountingOverrideDelegate.h>
|
||||||
|
#include <react/mounting/ShadowViewMutation.h>
|
||||||
#include <react/templateprocessor/UITemplateProcessor.h>
|
#include <react/templateprocessor/UITemplateProcessor.h>
|
||||||
#include <react/uimanager/UIManager.h>
|
#include <react/uimanager/UIManager.h>
|
||||||
#include <react/uimanager/UIManagerBinding.h>
|
#include <react/uimanager/UIManagerBinding.h>
|
||||||
|
|
||||||
|
#ifdef RN_SHADOW_TREE_INTROSPECTION
|
||||||
|
#include <react/mounting/stubs.h>
|
||||||
|
#include <iostream>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace facebook {
|
namespace facebook {
|
||||||
namespace react {
|
namespace react {
|
||||||
|
|
||||||
Scheduler::Scheduler(
|
Scheduler::Scheduler(
|
||||||
SchedulerToolbox schedulerToolbox,
|
SchedulerToolbox schedulerToolbox,
|
||||||
|
UIManagerAnimationDelegate *animationDelegate,
|
||||||
SchedulerDelegate *delegate) {
|
SchedulerDelegate *delegate) {
|
||||||
runtimeExecutor_ = schedulerToolbox.runtimeExecutor;
|
runtimeExecutor_ = schedulerToolbox.runtimeExecutor;
|
||||||
|
|
||||||
|
@ -90,6 +98,12 @@ Scheduler::Scheduler(
|
||||||
delegate_ = delegate;
|
delegate_ = delegate;
|
||||||
uiManager_ = uiManager;
|
uiManager_ = uiManager;
|
||||||
|
|
||||||
|
if (animationDelegate != nullptr) {
|
||||||
|
animationDelegate->setComponentDescriptorRegistry(
|
||||||
|
componentDescriptorRegistry_);
|
||||||
|
}
|
||||||
|
uiManager_->setAnimationDelegate(animationDelegate);
|
||||||
|
|
||||||
#ifdef ANDROID
|
#ifdef ANDROID
|
||||||
enableNewStateReconciliation_ = reactNativeConfig_->getBool(
|
enableNewStateReconciliation_ = reactNativeConfig_->getBool(
|
||||||
"react_fabric:enable_new_state_reconciliation_android");
|
"react_fabric:enable_new_state_reconciliation_android");
|
||||||
|
@ -158,7 +172,8 @@ void Scheduler::startSurface(
|
||||||
const std::string &moduleName,
|
const std::string &moduleName,
|
||||||
const folly::dynamic &initialProps,
|
const folly::dynamic &initialProps,
|
||||||
const LayoutConstraints &layoutConstraints,
|
const LayoutConstraints &layoutConstraints,
|
||||||
const LayoutContext &layoutContext) const {
|
const LayoutContext &layoutContext,
|
||||||
|
MountingOverrideDelegate *mountingOverrideDelegate) const {
|
||||||
SystraceSection s("Scheduler::startSurface");
|
SystraceSection s("Scheduler::startSurface");
|
||||||
|
|
||||||
auto shadowTree = std::make_unique<ShadowTree>(
|
auto shadowTree = std::make_unique<ShadowTree>(
|
||||||
|
@ -166,7 +181,8 @@ void Scheduler::startSurface(
|
||||||
layoutConstraints,
|
layoutConstraints,
|
||||||
layoutContext,
|
layoutContext,
|
||||||
*rootComponentDescriptor_,
|
*rootComponentDescriptor_,
|
||||||
*uiManager_);
|
*uiManager_,
|
||||||
|
mountingOverrideDelegate);
|
||||||
|
|
||||||
shadowTree->setEnableNewStateReconciliation(enableNewStateReconciliation_);
|
shadowTree->setEnableNewStateReconciliation(enableNewStateReconciliation_);
|
||||||
|
|
||||||
|
@ -310,6 +326,12 @@ SchedulerDelegate *Scheduler::getDelegate() const {
|
||||||
return delegate_;
|
return delegate_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma mark - UIManagerAnimationDelegate
|
||||||
|
|
||||||
|
void Scheduler::animationTick() const {
|
||||||
|
uiManager_->animationTick();
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - UIManagerDelegate
|
#pragma mark - UIManagerDelegate
|
||||||
|
|
||||||
void Scheduler::uiManagerDidFinishTransaction(
|
void Scheduler::uiManagerDidFinishTransaction(
|
||||||
|
@ -320,7 +342,6 @@ void Scheduler::uiManagerDidFinishTransaction(
|
||||||
delegate_->schedulerDidFinishTransaction(mountingCoordinator);
|
delegate_->schedulerDidFinishTransaction(mountingCoordinator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scheduler::uiManagerDidCreateShadowNode(
|
void Scheduler::uiManagerDidCreateShadowNode(
|
||||||
const ShadowNode::Shared &shadowNode) {
|
const ShadowNode::Shared &shadowNode) {
|
||||||
SystraceSection s("Scheduler::uiManagerDidCreateShadowNode");
|
SystraceSection s("Scheduler::uiManagerDidCreateShadowNode");
|
||||||
|
|
|
@ -12,13 +12,14 @@
|
||||||
|
|
||||||
#include <ReactCommon/RuntimeExecutor.h>
|
#include <ReactCommon/RuntimeExecutor.h>
|
||||||
#include <react/componentregistry/ComponentDescriptorFactory.h>
|
#include <react/componentregistry/ComponentDescriptorFactory.h>
|
||||||
#include <react/componentregistry/ComponentDescriptorRegistry.h>
|
|
||||||
#include <react/components/root/RootComponentDescriptor.h>
|
#include <react/components/root/RootComponentDescriptor.h>
|
||||||
#include <react/config/ReactNativeConfig.h>
|
#include <react/config/ReactNativeConfig.h>
|
||||||
#include <react/core/ComponentDescriptor.h>
|
#include <react/core/ComponentDescriptor.h>
|
||||||
#include <react/core/LayoutConstraints.h>
|
#include <react/core/LayoutConstraints.h>
|
||||||
|
#include <react/mounting/MountingOverrideDelegate.h>
|
||||||
#include <react/scheduler/SchedulerDelegate.h>
|
#include <react/scheduler/SchedulerDelegate.h>
|
||||||
#include <react/scheduler/SchedulerToolbox.h>
|
#include <react/scheduler/SchedulerToolbox.h>
|
||||||
|
#include <react/uimanager/UIManagerAnimationDelegate.h>
|
||||||
#include <react/uimanager/UIManagerBinding.h>
|
#include <react/uimanager/UIManagerBinding.h>
|
||||||
#include <react/uimanager/UIManagerDelegate.h>
|
#include <react/uimanager/UIManagerDelegate.h>
|
||||||
#include <react/utils/ContextContainer.h>
|
#include <react/utils/ContextContainer.h>
|
||||||
|
@ -31,7 +32,10 @@ namespace react {
|
||||||
*/
|
*/
|
||||||
class Scheduler final : public UIManagerDelegate {
|
class Scheduler final : public UIManagerDelegate {
|
||||||
public:
|
public:
|
||||||
Scheduler(SchedulerToolbox schedulerToolbox, SchedulerDelegate *delegate);
|
Scheduler(
|
||||||
|
SchedulerToolbox schedulerToolbox,
|
||||||
|
UIManagerAnimationDelegate *animationDelegate,
|
||||||
|
SchedulerDelegate *delegate);
|
||||||
~Scheduler();
|
~Scheduler();
|
||||||
|
|
||||||
#pragma mark - Surface Management
|
#pragma mark - Surface Management
|
||||||
|
@ -41,7 +45,8 @@ class Scheduler final : public UIManagerDelegate {
|
||||||
const std::string &moduleName,
|
const std::string &moduleName,
|
||||||
const folly::dynamic &initialProps,
|
const folly::dynamic &initialProps,
|
||||||
const LayoutConstraints &layoutConstraints = {},
|
const LayoutConstraints &layoutConstraints = {},
|
||||||
const LayoutContext &layoutContext = {}) const;
|
const LayoutContext &layoutContext = {},
|
||||||
|
MountingOverrideDelegate *mountingOverrideDelegate = nullptr) const;
|
||||||
|
|
||||||
void renderTemplateToSurface(
|
void renderTemplateToSurface(
|
||||||
SurfaceId surfaceId,
|
SurfaceId surfaceId,
|
||||||
|
@ -88,6 +93,13 @@ class Scheduler final : public UIManagerDelegate {
|
||||||
void setDelegate(SchedulerDelegate *delegate);
|
void setDelegate(SchedulerDelegate *delegate);
|
||||||
SchedulerDelegate *getDelegate() const;
|
SchedulerDelegate *getDelegate() const;
|
||||||
|
|
||||||
|
#pragma mark - UIManagerAnimationDelegate
|
||||||
|
// This is not needed on iOS or any platform that has a "pull" instead of
|
||||||
|
// "push" MountingCoordinator model. This just tells the delegate an update
|
||||||
|
// is available and that it should `pullTransaction`; we may want to rename
|
||||||
|
// this to be more generic and not animation-specific.
|
||||||
|
void animationTick() const;
|
||||||
|
|
||||||
#pragma mark - UIManagerDelegate
|
#pragma mark - UIManagerDelegate
|
||||||
|
|
||||||
void uiManagerDidFinishTransaction(
|
void uiManagerDidFinishTransaction(
|
||||||
|
|
|
@ -268,9 +268,15 @@ void UIManager::dispatchCommand(
|
||||||
}
|
}
|
||||||
|
|
||||||
void UIManager::configureNextLayoutAnimation(
|
void UIManager::configureNextLayoutAnimation(
|
||||||
const folly::dynamic config,
|
RawValue const &config,
|
||||||
SharedEventTarget successCallback,
|
SharedEventTarget successCallback,
|
||||||
SharedEventTarget errorCallback) const {}
|
SharedEventTarget errorCallback) const {
|
||||||
|
if (animationDelegate_) {
|
||||||
|
animationDelegate_->uiManagerDidConfigureNextLayoutAnimation(
|
||||||
|
config, successCallback, errorCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void UIManager::setComponentDescriptorRegistry(
|
void UIManager::setComponentDescriptorRegistry(
|
||||||
const SharedComponentDescriptorRegistry &componentDescriptorRegistry) {
|
const SharedComponentDescriptorRegistry &componentDescriptorRegistry) {
|
||||||
componentDescriptorRegistry_ = componentDescriptorRegistry;
|
componentDescriptorRegistry_ = componentDescriptorRegistry;
|
||||||
|
@ -310,5 +316,22 @@ void UIManager::shadowTreeDidFinishTransaction(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma mark - UIManagerAnimationDelegate
|
||||||
|
|
||||||
|
void UIManager::setAnimationDelegate(
|
||||||
|
UIManagerAnimationDelegate *delegate) const {
|
||||||
|
animationDelegate_ = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UIManager::animationTick() {
|
||||||
|
if (animationDelegate_ != nullptr &&
|
||||||
|
animationDelegate_->shouldAnimateFrame()) {
|
||||||
|
shadowTreeRegistry_.enumerate(
|
||||||
|
[&](ShadowTree const &shadowTree, bool &stop) {
|
||||||
|
shadowTree.notifyDelegatesOfUpdates();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace react
|
} // namespace react
|
||||||
} // namespace facebook
|
} // namespace facebook
|
||||||
|
|
|
@ -12,11 +12,13 @@
|
||||||
#include <jsi/jsi.h>
|
#include <jsi/jsi.h>
|
||||||
|
|
||||||
#include <react/componentregistry/ComponentDescriptorRegistry.h>
|
#include <react/componentregistry/ComponentDescriptorRegistry.h>
|
||||||
|
#include <react/core/RawValue.h>
|
||||||
#include <react/core/ShadowNode.h>
|
#include <react/core/ShadowNode.h>
|
||||||
#include <react/core/StateData.h>
|
#include <react/core/StateData.h>
|
||||||
#include <react/mounting/ShadowTree.h>
|
#include <react/mounting/ShadowTree.h>
|
||||||
#include <react/mounting/ShadowTreeDelegate.h>
|
#include <react/mounting/ShadowTreeDelegate.h>
|
||||||
#include <react/mounting/ShadowTreeRegistry.h>
|
#include <react/mounting/ShadowTreeRegistry.h>
|
||||||
|
#include <react/uimanager/UIManagerAnimationDelegate.h>
|
||||||
#include <react/uimanager/UIManagerDelegate.h>
|
#include <react/uimanager/UIManagerDelegate.h>
|
||||||
|
|
||||||
namespace facebook {
|
namespace facebook {
|
||||||
|
@ -39,6 +41,15 @@ class UIManager final : public ShadowTreeDelegate {
|
||||||
void setDelegate(UIManagerDelegate *delegate);
|
void setDelegate(UIManagerDelegate *delegate);
|
||||||
UIManagerDelegate *getDelegate();
|
UIManagerDelegate *getDelegate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets and gets the UIManager's Animation APIs delegate.
|
||||||
|
* The delegate is stored as a raw pointer, so the owner must null
|
||||||
|
* the pointer before being destroyed.
|
||||||
|
*/
|
||||||
|
void setAnimationDelegate(UIManagerAnimationDelegate *delegate) const;
|
||||||
|
|
||||||
|
void animationTick();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Provides access to a UIManagerBindging.
|
* Provides access to a UIManagerBindging.
|
||||||
* The `callback` methods will not be called if the internal pointer to
|
* The `callback` methods will not be called if the internal pointer to
|
||||||
|
@ -121,13 +132,15 @@ class UIManager final : public ShadowTreeDelegate {
|
||||||
* This API configures a global LayoutAnimation starting from the root node.
|
* This API configures a global LayoutAnimation starting from the root node.
|
||||||
*/
|
*/
|
||||||
void configureNextLayoutAnimation(
|
void configureNextLayoutAnimation(
|
||||||
const folly::dynamic config,
|
RawValue const &config,
|
||||||
SharedEventTarget successCallback,
|
SharedEventTarget successCallback,
|
||||||
SharedEventTarget errorCallback) const;
|
SharedEventTarget errorCallback) const;
|
||||||
|
|
||||||
ShadowTreeRegistry const &getShadowTreeRegistry() const;
|
ShadowTreeRegistry const &getShadowTreeRegistry() const;
|
||||||
|
|
||||||
SharedComponentDescriptorRegistry componentDescriptorRegistry_;
|
SharedComponentDescriptorRegistry componentDescriptorRegistry_;
|
||||||
UIManagerDelegate *delegate_;
|
UIManagerDelegate *delegate_;
|
||||||
|
mutable UIManagerAnimationDelegate *animationDelegate_{nullptr};
|
||||||
UIManagerBinding *uiManagerBinding_;
|
UIManagerBinding *uiManagerBinding_;
|
||||||
ShadowTreeRegistry shadowTreeRegistry_{};
|
ShadowTreeRegistry shadowTreeRegistry_{};
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* 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/componentregistry/ComponentDescriptorFactory.h>
|
||||||
|
#include <react/core/EventTarget.h>
|
||||||
|
#include <react/core/RawValue.h>
|
||||||
|
|
||||||
|
namespace facebook {
|
||||||
|
namespace react {
|
||||||
|
|
||||||
|
class UIManagerAnimationDelegate {
|
||||||
|
public:
|
||||||
|
virtual ~UIManagerAnimationDelegate() {};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Configure a LayoutAnimation.
|
||||||
|
* TODO: need SurfaceId here
|
||||||
|
*/
|
||||||
|
virtual void uiManagerDidConfigureNextLayoutAnimation(
|
||||||
|
RawValue const &config,
|
||||||
|
SharedEventTarget successCallback,
|
||||||
|
SharedEventTarget errorCallback) const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set ComponentDescriptor registry.
|
||||||
|
*
|
||||||
|
* @param componentDescriptorRegistry
|
||||||
|
*/
|
||||||
|
virtual void setComponentDescriptorRegistry(
|
||||||
|
const SharedComponentDescriptorRegistry &componentDescriptorRegistry) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only needed on Android to drive animations.
|
||||||
|
*/
|
||||||
|
virtual bool shouldAnimateFrame() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace react
|
||||||
|
} // namespace facebook
|
|
@ -619,16 +619,14 @@ jsi::Value UIManagerBinding::get(
|
||||||
const jsi::Value *arguments,
|
const jsi::Value *arguments,
|
||||||
size_t count) -> jsi::Value {
|
size_t count) -> jsi::Value {
|
||||||
uiManager->configureNextLayoutAnimation(
|
uiManager->configureNextLayoutAnimation(
|
||||||
commandArgsFromValue(
|
// TODO: pass in JSI value instead of folly::dynamic to RawValue
|
||||||
runtime,
|
RawValue(commandArgsFromValue(runtime, arguments[0])),
|
||||||
arguments[0]), // TODO T66507273: do a better job of parsing
|
|
||||||
// these arguments into a real struct / use a
|
|
||||||
// C++ typed object instead of folly::dynamic
|
|
||||||
eventTargetFromValue(runtime, arguments[1], -1),
|
eventTargetFromValue(runtime, arguments[1], -1),
|
||||||
eventTargetFromValue(runtime, arguments[2], -1));
|
eventTargetFromValue(runtime, arguments[2], -1));
|
||||||
return jsi::Value::undefined();
|
return jsi::Value::undefined();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return jsi::Value::undefined();
|
return jsi::Value::undefined();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
#include <folly/dynamic.h>
|
#include <folly/dynamic.h>
|
||||||
#include <jsi/jsi.h>
|
#include <jsi/jsi.h>
|
||||||
|
#include <react/core/RawValue.h>
|
||||||
#include <react/uimanager/UIManager.h>
|
#include <react/uimanager/UIManager.h>
|
||||||
#include <react/uimanager/primitives.h>
|
#include <react/uimanager/primitives.h>
|
||||||
|
|
||||||
|
|
Загрузка…
Ссылка в новой задаче