LayoutAnimations: call onSuccess, onFailure callbacks

Summary:
Hook up onSuccess and onFailure callbacks to LayoutAnimations.

Note that in non-Fabric RN, onSuccess is not known to work in Android. I could not find any uses of onFailure and it's not documented, so for now, it is only called if the setup of the animation fails.

Changelog: [Internal]

Reviewed By: shergin

Differential Revision: D22889352

fbshipit-source-id: 4306debb350388dd2b7a2cbfe295eb99723988e2
This commit is contained in:
Joshua Gross 2020-08-02 16:34:31 -07:00 коммит произвёл Facebook GitHub Bot
Родитель 777957c6fb
Коммит e3302eeeab
10 изменённых файлов: 152 добавлений и 31 удалений

Просмотреть файл

@ -114,7 +114,8 @@ class LayoutAnimationDelegateProxy : public LayoutAnimationStatusDelegate, publi
if (_layoutAnimationsEnabled) {
_layoutAnimationDelegateProxy = std::make_shared<LayoutAnimationDelegateProxy>((__bridge void *)self);
_animationDriver = std::make_shared<LayoutAnimationDriver>(_layoutAnimationDelegateProxy.get());
_animationDriver =
std::make_shared<LayoutAnimationDriver>(toolbox.runtimeExecutor, _layoutAnimationDelegateProxy.get());
_uiRunLoopObserver =
toolbox.mainRunLoopObserverFactory(RunLoopObserver::Activity::BeforeWaiting, _layoutAnimationDelegateProxy);
_uiRunLoopObserver->setDelegate(_layoutAnimationDelegateProxy.get());

Просмотреть файл

@ -285,7 +285,8 @@ void Binding::installFabricUIManager(
}
if (enableLayoutAnimations) {
animationDriver_ = std::make_shared<LayoutAnimationDriver>(this);
animationDriver_ =
std::make_shared<LayoutAnimationDriver>(runtimeExecutor, this);
}
scheduler_ = std::make_shared<Scheduler>(
toolbox, (animationDriver_ ? animationDriver_.get() : nullptr), this);

Просмотреть файл

@ -179,6 +179,8 @@ void LayoutAnimationDriver::animationMutationsForFrame(
it != inflightAnimations_.end();) {
const auto &animation = *it;
if (animation.completed) {
callCallback(animation.successCallback);
// Queue up "final" mutations for all keyframes in the completed animation
for (auto const &keyframe : animation.keyFrames) {
if (keyframe.finalMutationForKeyFrame.hasValue()) {

Просмотреть файл

@ -23,8 +23,10 @@ namespace react {
class LayoutAnimationDriver : public LayoutAnimationKeyFrameManager {
public:
LayoutAnimationDriver(LayoutAnimationStatusDelegate *delegate)
: LayoutAnimationKeyFrameManager(delegate) {}
LayoutAnimationDriver(
RuntimeExecutor runtimeExecutor,
LayoutAnimationStatusDelegate *delegate)
: LayoutAnimationKeyFrameManager(runtimeExecutor, delegate) {}
virtual ~LayoutAnimationDriver() {}

Просмотреть файл

@ -225,28 +225,45 @@ static better::optional<LayoutAnimationConfig> parseLayoutAnimationConfig(
/**
* Globally configure next LayoutAnimation.
* This is guaranteed to be called only on the JS thread.
*/
void LayoutAnimationKeyFrameManager::uiManagerDidConfigureNextLayoutAnimation(
jsi::Runtime &runtime,
RawValue const &config,
std::shared_ptr<const EventTarget> successCallback,
std::shared_ptr<const EventTarget> errorCallback) const {
const jsi::Value &successCallbackValue,
const jsi::Value &failureCallbackValue) const {
bool hasSuccessCallback = successCallbackValue.isObject() &&
successCallbackValue.getObject(runtime).isFunction(runtime);
bool hasFailureCallback = failureCallbackValue.isObject() &&
failureCallbackValue.getObject(runtime).isFunction(runtime);
LayoutAnimationCallbackWrapper successCallback = hasSuccessCallback
? LayoutAnimationCallbackWrapper(
successCallbackValue.getObject(runtime).getFunction(runtime))
: LayoutAnimationCallbackWrapper();
LayoutAnimationCallbackWrapper failureCallback = hasFailureCallback
? LayoutAnimationCallbackWrapper(
failureCallbackValue.getObject(runtime).getFunction(runtime))
: LayoutAnimationCallbackWrapper();
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,
failureCallback,
{}}};
} else {
// TODO: call errorCallback
LOG(ERROR) << "Parsing LayoutAnimationConfig failed: "
<< (folly::dynamic)config;
callCallback(failureCallback);
}
}
@ -472,13 +489,14 @@ LayoutAnimationKeyFrameManager::pullTransaction(
{
std::lock_guard<std::mutex> lock(currentAnimationMutex_);
if (currentAnimation_) {
currentAnimation = currentAnimation_;
currentAnimation = std::move(currentAnimation_);
currentAnimation_ = {};
}
}
if (currentAnimation) {
LayoutAnimation animation = currentAnimation.value();
LayoutAnimation animation = std::move(currentAnimation.value());
currentAnimation = {};
animation.surfaceId = surfaceId;
animation.startTime = now;
@ -909,7 +927,7 @@ LayoutAnimationKeyFrameManager::pullTransaction(
#endif
animation.keyFrames = keyFramesToAnimate;
inflightAnimations_.push_back(animation);
inflightAnimations_.push_back(std::move(animation));
// These will be executed immediately.
mutations = immediateMutations;
@ -1069,5 +1087,41 @@ ShadowView LayoutAnimationKeyFrameManager::createInterpolatedShadowView(
return mutatedShadowView;
}
void LayoutAnimationKeyFrameManager::callCallback(
const LayoutAnimationCallbackWrapper &callback) const {
if (callback.readyForCleanup()) {
return;
}
// Callbacks can only be called once. Replace the callsite with an empty
// CallbackWrapper. We use a unique_ptr to avoid copying into the vector.
std::unique_ptr<LayoutAnimationCallbackWrapper> copiedCallback(
std::make_unique<LayoutAnimationCallbackWrapper>(callback));
// Call the callback that is being retained in the vector
copiedCallback->call(runtimeExecutor_);
// Protect with a mutex: this can be called on failure callbacks in the JS
// thread and success callbacks on the UI thread
{
std::lock_guard<std::mutex> lock(callbackWrappersPendingMutex_);
// Clean any stale data in the retention vector
callbackWrappersPending_.erase(
std::remove_if(
callbackWrappersPending_.begin(),
callbackWrappersPending_.end(),
[](const std::unique_ptr<LayoutAnimationCallbackWrapper> &wrapper) {
return wrapper->readyForCleanup();
}),
callbackWrappersPending_.end());
// Hold onto a reference to the callback, only while
// LayoutAnimationKeyFrameManager is alive and the callback hasn't completed
// yet.
callbackWrappersPending_.push_back(std::move(copiedCallback));
}
}
} // namespace react
} // namespace facebook

Просмотреть файл

@ -7,6 +7,8 @@
#pragma once
#include <ReactCommon/RuntimeExecutor.h>
#include <better/optional.h>
#include <react/renderer/core/EventTarget.h>
#include <react/renderer/core/RawValue.h>
#include <react/renderer/mounting/Differentiator.h>
@ -87,30 +89,72 @@ struct AnimationKeyFrame {
double initialProgress;
};
class LayoutAnimationCallbackWrapper {
public:
LayoutAnimationCallbackWrapper(jsi::Function &&callback)
: callback_(std::make_shared<jsi::Function>(std::move(callback))) {}
LayoutAnimationCallbackWrapper() : callback_(nullptr) {}
~LayoutAnimationCallbackWrapper() {}
// Copy and assignment-copy constructors should copy callback_, and not
// std::move it. Copying is desirable, otherwise the shared_ptr and
// jsi::Function will be deallocated too early.
bool readyForCleanup() const {
return callback_ == nullptr || *callComplete_;
}
void call(const RuntimeExecutor &runtimeExecutor) const {
if (readyForCleanup()) {
return;
}
std::weak_ptr<jsi::Function> callable = callback_;
std::shared_ptr<bool> callComplete = callComplete_;
runtimeExecutor(
[=, callComplete = std::move(callComplete)](jsi::Runtime &runtime) {
auto fn = callable.lock();
if (!fn || *callComplete) {
return;
}
fn->call(runtime);
*callComplete = true;
});
}
private:
std::shared_ptr<bool> callComplete_ = std::make_shared<bool>(false);
std::shared_ptr<jsi::Function> callback_;
};
struct LayoutAnimation {
SurfaceId surfaceId;
uint64_t startTime;
bool completed = false;
LayoutAnimationConfig layoutAnimationConfig;
std::shared_ptr<const EventTarget> successCallback;
std::shared_ptr<const EventTarget> errorCallback;
LayoutAnimationCallbackWrapper successCallback;
LayoutAnimationCallbackWrapper failureCallback;
std::vector<AnimationKeyFrame> keyFrames;
};
class LayoutAnimationKeyFrameManager : public UIManagerAnimationDelegate,
public MountingOverrideDelegate {
public:
LayoutAnimationKeyFrameManager(LayoutAnimationStatusDelegate *delegate)
: layoutAnimationStatusDelegate_(delegate) {
// This is the ONLY place where we set or access
// layoutAnimationStatusDelegate_ without a mutex.
}
LayoutAnimationKeyFrameManager(
RuntimeExecutor runtimeExecutor,
LayoutAnimationStatusDelegate *delegate)
: runtimeExecutor_(runtimeExecutor),
layoutAnimationStatusDelegate_(delegate) {}
~LayoutAnimationKeyFrameManager() {}
void uiManagerDidConfigureNextLayoutAnimation(
jsi::Runtime &runtime,
RawValue const &config,
std::shared_ptr<EventTarget const> successCallback,
std::shared_ptr<EventTarget const> errorCallback) const override;
const jsi::Value &successCallbackValue,
const jsi::Value &failureCallbackValue) const override;
void setComponentDescriptorRegistry(SharedComponentDescriptorRegistry const &
componentDescriptorRegistry) override;
@ -138,6 +182,7 @@ class LayoutAnimationKeyFrameManager : public UIManagerAnimationDelegate,
LayoutAnimationStatusDelegate *delegate) const;
private:
RuntimeExecutor runtimeExecutor_;
mutable std::mutex layoutAnimationStatusDelegateMutex_;
mutable LayoutAnimationStatusDelegate *layoutAnimationStatusDelegate_{};
@ -162,6 +207,8 @@ class LayoutAnimationKeyFrameManager : public UIManagerAnimationDelegate,
ShadowView startingView,
ShadowView finalView) const;
void callCallback(const LayoutAnimationCallbackWrapper &callback) const;
virtual void animationMutationsForFrame(
SurfaceId surfaceId,
ShadowViewMutation::List &mutationsList,
@ -183,6 +230,13 @@ class LayoutAnimationKeyFrameManager : public UIManagerAnimationDelegate,
* by the MountingCoordinator's mutex.
*/
mutable std::vector<LayoutAnimation> inflightAnimations_{};
private:
// A vector of callable function wrappers that are in the process of being
// called
mutable std::mutex callbackWrappersPendingMutex_;
mutable std::vector<std::unique_ptr<LayoutAnimationCallbackWrapper>>
callbackWrappersPending_{};
};
} // namespace react

Просмотреть файл

@ -268,12 +268,16 @@ void UIManager::dispatchCommand(
}
void UIManager::configureNextLayoutAnimation(
jsi::Runtime &runtime,
RawValue const &config,
SharedEventTarget successCallback,
SharedEventTarget errorCallback) const {
const jsi::Value &successCallback,
const jsi::Value &failureCallback) const {
if (animationDelegate_) {
animationDelegate_->uiManagerDidConfigureNextLayoutAnimation(
config, successCallback, errorCallback);
runtime,
config,
std::move(successCallback),
std::move(failureCallback));
}
}

Просмотреть файл

@ -7,7 +7,6 @@
#pragma once
#include <folly/Optional.h>
#include <folly/dynamic.h>
#include <jsi/jsi.h>
@ -135,9 +134,10 @@ class UIManager final : public ShadowTreeDelegate {
* This API configures a global LayoutAnimation starting from the root node.
*/
void configureNextLayoutAnimation(
jsi::Runtime &runtime,
RawValue const &config,
SharedEventTarget successCallback,
SharedEventTarget errorCallback) const;
const jsi::Value &successCallback,
const jsi::Value &failureCallback) const;
ShadowTreeRegistry const &getShadowTreeRegistry() const;

Просмотреть файл

@ -7,8 +7,9 @@
#pragma once
#include <jsi/jsi.h>
#include <react/renderer/componentregistry/ComponentDescriptorFactory.h>
#include <react/renderer/core/EventTarget.h>
#include <react/renderer/core/RawValue.h>
namespace facebook {
@ -23,9 +24,10 @@ class UIManagerAnimationDelegate {
* TODO: need SurfaceId here
*/
virtual void uiManagerDidConfigureNextLayoutAnimation(
jsi::Runtime &runtime,
RawValue const &config,
SharedEventTarget successCallback,
SharedEventTarget errorCallback) const = 0;
const jsi::Value &successCallback,
const jsi::Value &failureCallback) const = 0;
/**
* Set ComponentDescriptor registry.

Просмотреть файл

@ -651,10 +651,11 @@ jsi::Value UIManagerBinding::get(
const jsi::Value *arguments,
size_t count) -> jsi::Value {
uiManager->configureNextLayoutAnimation(
runtime,
// TODO: pass in JSI value instead of folly::dynamic to RawValue
RawValue(commandArgsFromValue(runtime, arguments[0])),
eventTargetFromValue(runtime, arguments[1], -1),
eventTargetFromValue(runtime, arguments[2], -1));
arguments[1],
arguments[2]);
return jsi::Value::undefined();
});
}