Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -609,23 +609,9 @@ void NativeAnimatedNodesManager::startRenderCallbackIfNeeded(bool isAsync) {
if (!animationBackend) {
return;
}
// AnimationBackend::start registers the callback before returning the id.
// Keep registration and id publication in one critical section; otherwise a
// concurrent stop can observe no id and leave the registered callback
// orphaned in the backend.
std::lock_guard<std::mutex> lock(animationBackendCallbackMutex_);
if (animationBackendCallbackId_.has_value()) {
return;
}
auto weak = weak_from_this();
auto callbackId = animationBackend->start(
[weak](AnimationTimestamp timestamp) -> AnimationMutations {
if (auto self = weak.lock()) {
return self->pullAnimationMutations(timestamp);
}
return {};
});
animationBackendCallbackId_ = callbackId;
animationBackend->startFrontend(
animationFrontendIdLocked(*animationBackend));
}
}

Expand All @@ -651,24 +637,29 @@ void NativeAnimatedNodesManager::stopRenderCallbackIfNeeded(

{
auto animationBackend = animationBackend_.lock();
CallbackId callbackId = 0;
{
std::lock_guard<std::mutex> lock(animationBackendCallbackMutex_);
if (!animationBackendCallbackId_.has_value()) {
return;
}
// Clear the published id while holding the same mutex that protects
// start registration. This makes a concurrent start either see an active
// callback or wait until this stop owns the callback id.
callbackId = *animationBackendCallbackId_;
animationBackendCallbackId_.reset();
}
if (animationBackend) {
animationBackend->stop(callbackId);
std::lock_guard<std::mutex> lock(animationBackendCallbackMutex_);
if (animationBackend && animationFrontendId_.has_value()) {
animationBackend->stopFrontend(*animationFrontendId_);
}
}
}

AnimationFrontendId NativeAnimatedNodesManager::animationFrontendIdLocked(
UIManagerAnimationBackend& animationBackend) {
if (!animationFrontendId_.has_value()) {
animationFrontendId_ = animationBackend.registerFrontend(
{.onAnimationFrame =
[weak = weak_from_this()](
AnimationTimestamp timestamp) -> AnimationMutations {
if (auto self = weak.lock()) {
return self->pullAnimationMutations(timestamp);
}
return {};
}});
}
return *animationFrontendId_;
}

bool NativeAnimatedNodesManager::isAnimationUpdateNeeded() const noexcept {
return !activeAnimations_.empty() || !updatedNodeTags_.empty() ||
isEventAnimationInProgress_;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ class NativeAnimatedNodesManager : public std::enable_shared_from_this<NativeAni
private:
void stopRenderCallbackIfNeeded(bool isAsync) noexcept;

AnimationFrontendId animationFrontendIdLocked(UIManagerAnimationBackend &animationBackend);

bool onAnimationFrame(double timestamp);

bool isAnimationUpdateNeeded() const noexcept;
Expand Down Expand Up @@ -302,10 +304,10 @@ class NativeAnimatedNodesManager : public std::enable_shared_from_this<NativeAni
bool warnedAboutGraphTraversal_ = false;
#endif

// Protects the register/publish and exchange/stop lifecycle for the shared
// AnimationBackend callback.
// Protects the registration of the frontend and its start and stop.
std::mutex animationBackendCallbackMutex_;
std::optional<CallbackId> animationBackendCallbackId_;
// Registered on the first start, and kept until the backend is destroyed.
std::optional<AnimationFrontendId> animationFrontendId_;

friend class ColorAnimatedNode;
friend class AnimationDriver;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ class AnimationTestsBase : public testing::Test {
NativeAnimatedNodesManager::isOnRenderThread_ = true;
}

static void runOnRenderThread() noexcept
{
NativeAnimatedNodesManager::isOnRenderThread_ = true;
}

bool nodeNeedsUpdate(Tag nodeTag) const
{
return nodesManager_->updatedNodeTags_.contains(nodeTag);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ class FakeAnimationBackend : public UIManagerAnimationBackend {
AnimationTimestamp pushTimestamp{0};

void onAnimationFrame(AnimationTimestamp /*timestamp*/) override {}
AnimationFrontendId registerFrontend(
AnimationFrontend /*frontend*/) override {
return 1;
}
void startFrontend(AnimationFrontendId /*frontendId*/) override {}
void stopFrontend(AnimationFrontendId /*frontendId*/) override {}
CallbackId start(const Callback& /*callback*/) override {
return 0;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#include "AnimationTestsBase.h"

#include <react/renderer/animated/NativeAnimatedNodesManager.h>
#include <react/renderer/animationbackend/AnimationMutation.h>
#include <react/renderer/components/view/ViewComponentDescriptor.h>
#include <react/renderer/element/ComponentBuilder.h>
#include <react/renderer/element/Element.h>
#include <react/renderer/element/testUtils.h>
#include <react/renderer/uimanager/UIManagerAnimationBackend.h>
#include <react/utils/ContextContainer.h>

namespace facebook::react {

namespace {

class RecordingAnimationBackend : public UIManagerAnimationBackend {
public:
std::vector<AnimationFrontend> frontends;
std::vector<AnimationFrontendId> startedFrontends;
int startedCallbacks{0};

void onAnimationFrame(AnimationTimestamp /*timestamp*/) override {}
AnimationFrontendId registerFrontend(AnimationFrontend frontend) override {
frontends.push_back(std::move(frontend));
return static_cast<AnimationFrontendId>(frontends.size());
}
void startFrontend(AnimationFrontendId frontendId) override {
startedFrontends.push_back(frontendId);
}
void stopFrontend(AnimationFrontendId /*frontendId*/) override {}
CallbackId start(const Callback& /*callback*/) override {
startedCallbacks++;
return 0;
}
void stop(CallbackId /*callbackId*/) override {}
void clearRegistry(SurfaceId /*surfaceId*/) override {}
void clearRegistryOnSurfaceStop(SurfaceId /*surfaceId*/) override {}
void trigger() override {}
void pushAnimationMutations(const Callback& /*callback*/) override {}
void registerJSInvoker(std::shared_ptr<CallInvoker> /*jsInvoker*/) override {}
};

constexpr Tag kValueNode = 1;
constexpr Tag kStyleNode = 2;
constexpr Tag kPropsNode = 3;
constexpr Tag kView = 10;

} // namespace

class NativeAnimatedFrontendTests : public AnimationTestsBase {
protected:
void SetUp() override {
runOnRenderThread();
}

// Starts animating the view's opacity in four frames.
void startAnimation() {
manager_ = std::make_shared<NativeAnimatedNodesManager>(backend_);
manager_->createAnimatedNode(
kValueNode,
folly::dynamic::object("type", "value")("value", 0)("offset", 0));
manager_->createAnimatedNode(
kStyleNode,
folly::dynamic::object("type", "style")(
"style", folly::dynamic::object("opacity", kValueNode)));
manager_->createAnimatedNode(
kPropsNode,
folly::dynamic::object("type", "props")(
"props", folly::dynamic::object("style", kStyleNode)));
manager_->connectAnimatedNodes(kValueNode, kStyleNode);
manager_->connectAnimatedNodes(kStyleNode, kPropsNode);
manager_->connectAnimatedNodeToView(kPropsNode, kView);
manager_->connectAnimatedNodeToShadowNodeFamily(
kPropsNode,
std::const_pointer_cast<ShadowNodeFamily>(view_->getFamilyShared()));
manager_->startAnimatingNode(
1,
kValueNode,
folly::dynamic::object("type", "frames")(
"frames", folly::dynamic::array(0.0, 0.5, 1.0))("toValue", 1),
std::nullopt);
manager_->startRenderCallbackIfNeeded(false);
}

std::shared_ptr<RecordingAnimationBackend> backend_ =
std::make_shared<RecordingAnimationBackend>();
std::shared_ptr<NativeAnimatedNodesManager> manager_;
ComponentBuilder builder_ =
simpleComponentBuilder(std::make_shared<ContextContainer>());
std::shared_ptr<ViewShadowNode> view_ =
builder_.build(Element<ViewShadowNode>().surfaceId(1).tag(kView));
};

TEST_F(NativeAnimatedFrontendTests, registersAndStartsAFrontend) {
startAnimation();

ASSERT_EQ(backend_->frontends.size(), 1u);
EXPECT_EQ(backend_->startedFrontends, std::vector<AnimationFrontendId>{1});
EXPECT_EQ(backend_->startedCallbacks, 0);
}

} // namespace facebook::react
Original file line number Diff line number Diff line change
Expand Up @@ -163,34 +163,59 @@ void AnimationBackend::applyMutations(std::vector<AnimationMutations> batches) {

void AnimationBackend::onAnimationFrame(AnimationTimestamp timestamp) {
std::vector<CallbackWithId> callbacksCopy;
std::vector<AnimationFrontend> startedFrontends;

{
std::lock_guard lock(mutex_);
callbacksCopy = callbacks;
for (const auto& registered : frontends_) {
if (registered.started) {
startedFrontends.push_back(registered.frontend);
}
}
}

// Sized up front rather than grown: MSVC's std::set move isn't noexcept, so
// growing a vector of AnimationMutations would try to copy move-only props.
std::vector<AnimationMutations> batches(callbacksCopy.size());
std::vector<AnimationMutations> batches(
callbacksCopy.size() + startedFrontends.size());
std::transform(
callbacksCopy.begin(),
callbacksCopy.end(),
batches.begin(),
[timestamp](const CallbackWithId& callbackWithId) {
return callbackWithId.callback(timestamp);
});
for (size_t i = 0; i < startedFrontends.size(); i++) {
batches[callbacksCopy.size() + i] =
startedFrontends[i].onAnimationFrame(timestamp);
}
applyMutations(std::move(batches));
}

void AnimationBackend::resumeFramesLocked() {
if (!isRenderCallbackStarted_) {
animationChoreographer_->resume();
isRenderCallbackStarted_ = true;
}
}

void AnimationBackend::pauseFramesIfIdleLocked() {
if (isRenderCallbackStarted_ && callbacks.empty() &&
std::none_of(frontends_.begin(), frontends_.end(), [](auto& frontend) {
return frontend.started;
})) {
animationChoreographer_->pause();
isRenderCallbackStarted_ = false;
}
}

CallbackId AnimationBackend::start(const Callback& callback) {
std::lock_guard lock(mutex_);

auto callbackId = nextCallbackId_++;
callbacks.push_back({.callbackId = callbackId, .callback = callback});
if (!isRenderCallbackStarted_) {
animationChoreographer_->resume();
isRenderCallbackStarted_ = true;
}
resumeFramesLocked();

return callbackId;
}
Expand All @@ -206,10 +231,34 @@ void AnimationBackend::stop(CallbackId callbackId) {
}

callbacks.erase(it);
if (isRenderCallbackStarted_ && callbacks.empty()) {
animationChoreographer_->pause();
isRenderCallbackStarted_ = false;
pauseFramesIfIdleLocked();
}

AnimationFrontendId AnimationBackend::registerFrontend(
AnimationFrontend frontend) {
std::lock_guard lock(mutex_);
frontends_.push_back({.frontend = std::move(frontend)});
return static_cast<AnimationFrontendId>(frontends_.size());
}

void AnimationBackend::startFrontend(AnimationFrontendId frontendId) {
std::lock_guard lock(mutex_);
auto& registered = frontends_.at(frontendId - 1);
if (registered.started || !registered.frontend.onAnimationFrame) {
return;
}
registered.started = true;
resumeFramesLocked();
}

void AnimationBackend::stopFrontend(AnimationFrontendId frontendId) {
std::lock_guard lock(mutex_);
auto& registered = frontends_.at(frontendId - 1);
if (!registered.started) {
return;
}
registered.started = false;
pauseFramesIfIdleLocked();
}

void AnimationBackend::trigger() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ struct CallbackWithId {
Callback callback;
};

struct RegisteredFrontend {
AnimationFrontend frontend;
bool started{false};
};

class AnimationBackend : public UIManagerAnimationBackend {
public:
using ResumeCallback = std::function<void()>;
Expand All @@ -56,6 +61,9 @@ class AnimationBackend : public UIManagerAnimationBackend {
void pushAnimationMutations(const Callback &callback) override;
CallbackId start(const Callback &callback) override;
void stop(CallbackId callbackId) override;
AnimationFrontendId registerFrontend(AnimationFrontend frontend) override;
void startFrontend(AnimationFrontendId frontendId) override;
void stopFrontend(AnimationFrontendId frontendId) override;

private:
using SurfaceUpdates = std::unordered_map<Tag, AnimationMutation>;
Expand All @@ -69,7 +77,11 @@ class AnimationBackend : public UIManagerAnimationBackend {
std::unordered_map<SurfaceId, SurfaceUpdates> &surfaceUpdates,
const std::set<SurfaceId> &asyncFlushSurfaces);
void applyMutations(std::vector<AnimationMutations> batches);
void resumeFramesLocked();
void pauseFramesIfIdleLocked();
std::vector<CallbackWithId> callbacks;
// Indexed by frontend id - 1.
std::vector<RegisteredFrontend> frontends_;
std::shared_ptr<AnimatedPropsRegistry> animatedPropsRegistry_;
std::shared_ptr<AnimationChoreographer> animationChoreographer_;
std::unique_ptr<AnimationBackendCommitHook> commitHook_;
Expand Down
Loading
Loading