From e3302eeeab3c285b4927442cd17527d6aa60fa0b Mon Sep 17 00:00:00 2001 From: Joshua Gross Date: Sun, 2 Aug 2020 16:34:31 -0700 Subject: [PATCH] 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 --- React/Fabric/RCTScheduler.mm | 3 +- .../com/facebook/react/fabric/jni/Binding.cpp | 3 +- .../animations/LayoutAnimationDriver.cpp | 2 + .../animations/LayoutAnimationDriver.h | 6 +- .../LayoutAnimationKeyFrameManager.cpp | 68 ++++++++++++++++-- .../LayoutAnimationKeyFrameManager.h | 72 ++++++++++++++++--- .../react/renderer/uimanager/UIManager.cpp | 10 ++- .../react/renderer/uimanager/UIManager.h | 6 +- .../uimanager/UIManagerAnimationDelegate.h | 8 ++- .../renderer/uimanager/UIManagerBinding.cpp | 5 +- 10 files changed, 152 insertions(+), 31 deletions(-) diff --git a/React/Fabric/RCTScheduler.mm b/React/Fabric/RCTScheduler.mm index 27043b3da5..3119000246 100644 --- a/React/Fabric/RCTScheduler.mm +++ b/React/Fabric/RCTScheduler.mm @@ -114,7 +114,8 @@ class LayoutAnimationDelegateProxy : public LayoutAnimationStatusDelegate, publi if (_layoutAnimationsEnabled) { _layoutAnimationDelegateProxy = std::make_shared((__bridge void *)self); - _animationDriver = std::make_shared(_layoutAnimationDelegateProxy.get()); + _animationDriver = + std::make_shared(toolbox.runtimeExecutor, _layoutAnimationDelegateProxy.get()); _uiRunLoopObserver = toolbox.mainRunLoopObserverFactory(RunLoopObserver::Activity::BeforeWaiting, _layoutAnimationDelegateProxy); _uiRunLoopObserver->setDelegate(_layoutAnimationDelegateProxy.get()); diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp index eae4491ce9..d691b6f64f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp @@ -285,7 +285,8 @@ void Binding::installFabricUIManager( } if (enableLayoutAnimations) { - animationDriver_ = std::make_shared(this); + animationDriver_ = + std::make_shared(runtimeExecutor, this); } scheduler_ = std::make_shared( toolbox, (animationDriver_ ? animationDriver_.get() : nullptr), this); diff --git a/ReactCommon/react/renderer/animations/LayoutAnimationDriver.cpp b/ReactCommon/react/renderer/animations/LayoutAnimationDriver.cpp index 727ffc49a9..13e624416d 100644 --- a/ReactCommon/react/renderer/animations/LayoutAnimationDriver.cpp +++ b/ReactCommon/react/renderer/animations/LayoutAnimationDriver.cpp @@ -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()) { diff --git a/ReactCommon/react/renderer/animations/LayoutAnimationDriver.h b/ReactCommon/react/renderer/animations/LayoutAnimationDriver.h index 525d4a7f80..59033afca1 100644 --- a/ReactCommon/react/renderer/animations/LayoutAnimationDriver.h +++ b/ReactCommon/react/renderer/animations/LayoutAnimationDriver.h @@ -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() {} diff --git a/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.cpp b/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.cpp index 42c5ff05a9..038dc2cbd5 100644 --- a/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.cpp +++ b/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.cpp @@ -225,28 +225,45 @@ static better::optional 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 successCallback, - std::shared_ptr 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 lock(currentAnimationMutex_); + currentAnimation_ = better::optional{ 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 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 copiedCallback( + std::make_unique(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 lock(callbackWrappersPendingMutex_); + + // Clean any stale data in the retention vector + callbackWrappersPending_.erase( + std::remove_if( + callbackWrappersPending_.begin(), + callbackWrappersPending_.end(), + [](const std::unique_ptr &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 diff --git a/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.h b/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.h index 8de5eef1fd..4672840efe 100644 --- a/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.h +++ b/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.h @@ -7,6 +7,8 @@ #pragma once +#include +#include #include #include #include @@ -87,30 +89,72 @@ struct AnimationKeyFrame { double initialProgress; }; +class LayoutAnimationCallbackWrapper { + public: + LayoutAnimationCallbackWrapper(jsi::Function &&callback) + : callback_(std::make_shared(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 callable = callback_; + std::shared_ptr 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 callComplete_ = std::make_shared(false); + std::shared_ptr callback_; +}; + struct LayoutAnimation { SurfaceId surfaceId; uint64_t startTime; bool completed = false; LayoutAnimationConfig layoutAnimationConfig; - std::shared_ptr successCallback; - std::shared_ptr errorCallback; + LayoutAnimationCallbackWrapper successCallback; + LayoutAnimationCallbackWrapper failureCallback; std::vector 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 successCallback, - std::shared_ptr 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 inflightAnimations_{}; + + private: + // A vector of callable function wrappers that are in the process of being + // called + mutable std::mutex callbackWrappersPendingMutex_; + mutable std::vector> + callbackWrappersPending_{}; }; } // namespace react diff --git a/ReactCommon/react/renderer/uimanager/UIManager.cpp b/ReactCommon/react/renderer/uimanager/UIManager.cpp index 3444273d6c..980de3b541 100644 --- a/ReactCommon/react/renderer/uimanager/UIManager.cpp +++ b/ReactCommon/react/renderer/uimanager/UIManager.cpp @@ -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)); } } diff --git a/ReactCommon/react/renderer/uimanager/UIManager.h b/ReactCommon/react/renderer/uimanager/UIManager.h index 26081a1236..4c9688537b 100644 --- a/ReactCommon/react/renderer/uimanager/UIManager.h +++ b/ReactCommon/react/renderer/uimanager/UIManager.h @@ -7,7 +7,6 @@ #pragma once -#include #include #include @@ -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; diff --git a/ReactCommon/react/renderer/uimanager/UIManagerAnimationDelegate.h b/ReactCommon/react/renderer/uimanager/UIManagerAnimationDelegate.h index 735a73fe51..3dcb549f14 100644 --- a/ReactCommon/react/renderer/uimanager/UIManagerAnimationDelegate.h +++ b/ReactCommon/react/renderer/uimanager/UIManagerAnimationDelegate.h @@ -7,8 +7,9 @@ #pragma once +#include + #include -#include #include 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. diff --git a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp index 0cf68d55f9..d6abbfca88 100644 --- a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp +++ b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp @@ -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(); }); }