Files
bsky-social-app/patches/react-native-reanimated@4.3.2.patch
T
2026-07-23 18:25:40 +03:00

501 lines
21 KiB
Diff

diff --git a/Common/cpp/reanimated/Fabric/updates/AnimatedPropsRegistry.cpp b/Common/cpp/reanimated/Fabric/updates/AnimatedPropsRegistry.cpp
index 531f0dc7b4eeb9b29cb2255d8444da02a74c35b7..534f419fce55c39a09a7eebfb7ab3c53f8a16637 100644
--- a/Common/cpp/reanimated/Fabric/updates/AnimatedPropsRegistry.cpp
+++ b/Common/cpp/reanimated/Fabric/updates/AnimatedPropsRegistry.cpp
@@ -1,8 +1,10 @@
#include <reanimated/Fabric/updates/AnimatedPropsRegistry.h>
#include <reanimated/Tools/FeatureFlags.h>
+#include <functional>
#include <memory>
#include <utility>
+#include <vector>
namespace reanimated {
@@ -25,25 +27,59 @@ void AnimatedPropsRegistry::update(jsi::Runtime &rt, const jsi::Value &operation
addUpdatesToBatch(shadowNode, jsi::dynamicFromValue(rt, updates));
if constexpr (StaticFeatureFlags::getFlag("FORCE_REACT_RENDER_FOR_SETTLED_ANIMATIONS")) {
- timestampMap_[shadowNode->getTag()] = timestamp;
+ const auto tag = shadowNode->getTag();
+ timestampMap_[tag] = timestamp;
+ // If JS already has a `settledProps` snapshot for this tag, it is now
+ // stale — schedule a refresh on the next `collectSettledUpdates`.
+ if (syncedTags_.erase(tag) > 0) {
+ invalidatedTags_.insert(tag);
+ }
}
}
}
-jsi::Value AnimatedPropsRegistry::getUpdatesOlderThanTimestamp(
- jsi::Runtime &rt,
- const double timestamp,
- const double cleanupTimestamp) {
+jsi::Value AnimatedPropsRegistry::collectSettledUpdates(jsi::Runtime &rt, const double settledTimestamp) {
std::lock_guard<std::mutex> lock{mutex_};
- removeUpdatesOlderThanTimestamp(cleanupTimestamp);
std::vector<std::pair<Tag, std::reference_wrapper<const folly::dynamic>>> updates;
- for (const auto &[viewTag, pair] : updatesRegistry_) {
- auto it = timestampMap_.find(viewTag);
- if (it != timestampMap_.end() && it->second < timestamp) {
- updates.emplace_back(viewTag, std::cref(pair.second));
+ for (auto it = updatesRegistry_.begin(); it != updatesRegistry_.end();) {
+ const auto viewTag = it->first;
+
+ if (syncedTags_.contains(viewTag)) {
+ // React already has the latest value for this tag (synced on a previous
+ // call, so the `settledProps` state is committed by now) — the registry
+ // entry is redundant. `syncedTags_` is intentionally retained to detect
+ // re-animation staleness. Note that `syncedTags_` and `invalidatedTags_`
+ // are disjoint — `update()` moves tags from the former to the latter.
+ timestampMap_.erase(viewTag);
+ it = updatesRegistry_.erase(it);
+ continue;
+ }
+
+ const auto timestampIt = timestampMap_.find(viewTag);
+ if (timestampIt == timestampMap_.end()) {
+ ++it;
+ continue;
+ }
+ const bool isSettled = timestampIt->second < settledTimestamp;
+ const auto invalidatedIt = invalidatedTags_.find(viewTag);
+ const bool isInvalidated = invalidatedIt != invalidatedTags_.end();
+ if (isSettled || isInvalidated) {
+ updates.emplace_back(viewTag, std::cref(it->second.second));
+ if (isSettled) {
+ // Only settled-path tags are tracked as "synced" so that an ongoing
+ // animation doesn't re-trigger an invalidation/sync on every GC tick.
+ syncedTags_.insert(viewTag);
+ }
+ if (isInvalidated) {
+ // Only erase serviced invalidations; if a tag was invalidated but the
+ // matching update batch hasn't been flushed into updatesRegistry_ yet,
+ // we leave the entry so the next sync picks it up.
+ invalidatedTags_.erase(invalidatedIt);
+ }
}
+ ++it;
}
const jsi::Array array(rt, updates.size());
@@ -58,22 +94,11 @@ jsi::Value AnimatedPropsRegistry::getUpdatesOlderThanTimestamp(
return jsi::Value(rt, array);
}
-void AnimatedPropsRegistry::removeUpdatesOlderThanTimestamp(const double timestamp) {
- for (auto it = timestampMap_.begin(); it != timestampMap_.end();) {
- const auto viewTag = it->first;
- const auto viewTimestamp = it->second;
- if (viewTimestamp < timestamp) {
- it = timestampMap_.erase(it);
- updatesRegistry_.erase(viewTag);
- } else {
- it++;
- }
- }
-}
-
void AnimatedPropsRegistry::removeTag(const Tag tag) {
updatesRegistry_.erase(tag);
timestampMap_.erase(tag);
+ syncedTags_.erase(tag);
+ invalidatedTags_.erase(tag);
}
} // namespace reanimated
diff --git a/Common/cpp/reanimated/Fabric/updates/AnimatedPropsRegistry.h b/Common/cpp/reanimated/Fabric/updates/AnimatedPropsRegistry.h
index 2c6c0e13604c9421e147d7eea7f4a4752288011c..8cd67f118501c2786b94d76541aea29a14ba8c16 100644
--- a/Common/cpp/reanimated/Fabric/updates/AnimatedPropsRegistry.h
+++ b/Common/cpp/reanimated/Fabric/updates/AnimatedPropsRegistry.h
@@ -4,10 +4,8 @@
#include <react/renderer/uimanager/UIManager.h>
-#include <memory>
-#include <string>
#include <unordered_map>
-#include <vector>
+#include <unordered_set>
namespace reanimated {
@@ -15,13 +13,22 @@ class AnimatedPropsRegistry : public UpdatesRegistry {
public:
void update(jsi::Runtime &rt, const jsi::Value &operations, double timestamp);
- /// Also removes updates older than `cleanupTimestamp` from the registry.
- jsi::Value getUpdatesOlderThanTimestamp(jsi::Runtime &rt, double timestamp, double cleanupTimestamp);
+ /// Returns updates that settled (received no update since `settledTimestamp`)
+ /// or whose synced `settledProps` snapshot was invalidated by a fresh update.
+ /// Also evicts entries that have already been synced to React — by the time
+ /// of the next call, the corresponding `settledProps` state is guaranteed to
+ /// be committed, so the registry entries are redundant.
+ jsi::Value collectSettledUpdates(jsi::Runtime &rt, double settledTimestamp);
private:
std::unordered_map<Tag, double> timestampMap_; // viewTag -> timestamp, protected by `mutex_`
+ // Tags whose latest values have already been pushed to React `settledProps`.
+ // Intentionally retained after eviction to detect re-animation staleness.
+ std::unordered_set<Tag> syncedTags_;
+ // Tags that were synced to React but received a fresh worklet update since;
+ // their `settledProps` are stale and need to be refreshed on the next sync.
+ std::unordered_set<Tag> invalidatedTags_;
- void removeUpdatesOlderThanTimestamp(double timestamp);
void removeTag(Tag tag) override;
};
diff --git a/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxyCommon.h b/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxyCommon.h
index 096218ab9659955cd6272c97181bce3c893ed591..1a8e25fc8295b3ac943130709bf063ea41a50585 100644
--- a/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxyCommon.h
+++ b/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxyCommon.h
@@ -57,11 +57,11 @@ class LayoutAnimationsProxyCommon : public facebook::react::MountingOverrideDele
const SharedComponentDescriptorRegistry &componentDescriptorRegistry,
const std::shared_ptr<const ContextContainer> &contextContainer,
jsi::Runtime &uiRuntime,
- const std::shared_ptr<UIScheduler> &uiScheduler
+ const std::shared_ptr<UIScheduler> &uiScheduler,
+ const std::shared_ptr<facebook::react::UIManager> &uiManager
#ifdef ANDROID
,
const PreserveMountedTagsFunction &filterUnmountedTagsFunction,
- const std::shared_ptr<facebook::react::UIManager> &uiManager,
const std::shared_ptr<facebook::react::CallInvoker> &jsInvoker
#endif
)
@@ -69,11 +69,11 @@ class LayoutAnimationsProxyCommon : public facebook::react::MountingOverrideDele
contextContainer_(contextContainer),
componentDescriptorRegistry_(componentDescriptorRegistry),
uiRuntime_(uiRuntime),
- uiScheduler_(uiScheduler)
+ uiScheduler_(uiScheduler),
+ uiManager_(uiManager)
#ifdef ANDROID
,
preserveMountedTags_(filterUnmountedTagsFunction),
- uiManager_(uiManager),
jsInvoker_(jsInvoker)
#endif
{
@@ -93,10 +93,10 @@ class LayoutAnimationsProxyCommon : public facebook::react::MountingOverrideDele
SharedComponentDescriptorRegistry componentDescriptorRegistry_;
jsi::Runtime &uiRuntime_;
const std::shared_ptr<UIScheduler> uiScheduler_;
+ std::shared_ptr<facebook::react::UIManager> uiManager_;
PreserveMountedTagsFunction preserveMountedTags_;
#ifdef ANDROID
- std::shared_ptr<facebook::react::UIManager> uiManager_;
std::shared_ptr<facebook::react::CallInvoker> jsInvoker_;
void restoreOpacityInCaseOfFlakyEnteringAnimation(SurfaceId surfaceId) const;
diff --git a/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h b/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h
index eca44e4cf651d16e9741806004ce9119c85d07d6..e39c79a5d7b52659106ed6fe6fbcbbc048bf4787 100644
--- a/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h
+++ b/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h
@@ -66,11 +66,11 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon,
const SharedComponentDescriptorRegistry &componentDescriptorRegistry,
const std::shared_ptr<const ContextContainer> &contextContainer,
jsi::Runtime &uiRuntime,
- const std::shared_ptr<UIScheduler> &uiScheduler
+ const std::shared_ptr<UIScheduler> &uiScheduler,
+ const std::shared_ptr<UIManager> &uiManager
#ifdef ANDROID
,
const PreserveMountedTagsFunction &filterUnmountedTagsFunction,
- const std::shared_ptr<UIManager> &uiManager,
const std::shared_ptr<CallInvoker> &jsInvoker
#endif
)
@@ -79,11 +79,11 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon,
componentDescriptorRegistry,
contextContainer,
uiRuntime,
- uiScheduler
+ uiScheduler,
+ uiManager
#ifdef ANDROID
,
filterUnmountedTagsFunction,
- uiManager,
jsInvoker
#endif
),
diff --git a/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.cpp b/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.cpp
index 83ef7430b923b6b3b99368ee8072168769110cd0..2affd12822ab19bdc90963d3ce8ca1e6bb0d43b7 100644
--- a/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.cpp
+++ b/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.cpp
@@ -2,6 +2,7 @@
#include <reanimated/NativeModules/ReanimatedModuleProxy.h>
#include <react/renderer/animations/utils.h>
+#include <react/renderer/mounting/ShadowTree.h>
#include <react/renderer/mounting/ShadowViewMutation.h>
#include <memory>
@@ -53,14 +54,37 @@ std::optional<MountingTransaction> LayoutAnimationsProxy_Legacy::pullTransaction
parseRemoveMutations(movedViews, mutations, roots);
- auto shouldAnimate = !surfacesToRemove_.contains(surfaceId);
- surfacesToRemove_.erase(surfaceId);
+ // Consume the teardown mark only on the transaction that actually clears
+ // the root — pulls emitted for animation frames must not eat it early.
+ auto shouldAnimate = true;
+ const auto removesRootChildren = std::ranges::any_of(mutations, [surfaceId](const auto &mutation) {
+ return mutation.type == ShadowViewMutation::Remove && mutation.parentTag == surfaceId;
+ });
+ if (removesRootChildren) {
+ shouldAnimate = surfacesToRemove_.erase(surfaceId) == 0;
+ }
handleRemovals(filteredMutations, roots, deadNodes, shouldAnimate);
handleUpdatesAndEnterings(filteredMutations, movedViews, mutations, propsParserContext, surfaceId);
addOngoingAnimations(surfaceId, filteredMutations);
+ // The LayoutAnimationDriver can emit a final keyframe update in the same
+ // transaction as the deferred Remove/Delete it withheld for a delete
+ // animation. We emit removals before updates, so such an update would
+ // otherwise reach the mounting layer after its view was deleted.
+ std::unordered_set<Tag> deletedTags;
+ for (const auto &mutation : filteredMutations) {
+ if (mutation.type == ShadowViewMutation::Delete) {
+ deletedTags.insert(mutation.oldChildShadowView.tag);
+ }
+ }
+ if (!deletedTags.empty()) {
+ std::erase_if(filteredMutations, [&deletedTags](const auto &mutation) {
+ return mutation.type == ShadowViewMutation::Update && deletedTags.contains(mutation.newChildShadowView.tag);
+ });
+ }
+
return MountingTransaction{surfaceId, transactionNumber, std::move(filteredMutations), telemetry};
}
@@ -947,23 +971,22 @@ inline bool MutationNode::isMutationNode() {
return true;
}
-// UIManagerAnimationDelegate
-
-void LayoutAnimationsProxy_Legacy::uiManagerDidConfigureNextLayoutAnimation(
- jsi::Runtime &runtime,
- const RawValue &config,
- const jsi::Value &successCallbackValue,
- const jsi::Value &failureCallbackValue) const {}
+// UIManagerCommitHook
-void LayoutAnimationsProxy_Legacy::setComponentDescriptorRegistry(
- const SharedComponentDescriptorRegistry &componentDescriptorRegistry) {}
-
-bool LayoutAnimationsProxy_Legacy::shouldAnimateFrame() const {
- return false;
-}
-
-void LayoutAnimationsProxy_Legacy::stopSurface(SurfaceId surfaceId) {
- surfacesToRemove_.insert(surfaceId);
+// Surface teardown commits an empty root (SurfaceHandler::stop) before the
+// teardown transaction is pulled — mark it so pullTransaction skips exit
+// animations. Reading the ShadowTreeRegistry here instead would deadlock (#8579).
+RootShadowNode::Unshared LayoutAnimationsProxy_Legacy::shadowTreeWillCommit(
+ const ShadowTree &shadowTree,
+ const RootShadowNode::Shared & /*oldRootShadowNode*/,
+ const RootShadowNode::Unshared &newRootShadowNode) noexcept {
+ auto lock = std::unique_lock<std::recursive_mutex>(mutex);
+ if (newRootShadowNode->getChildren().empty()) {
+ surfacesToRemove_.insert(shadowTree.getSurfaceId());
+ } else {
+ surfacesToRemove_.erase(shadowTree.getSurfaceId());
+ }
+ return newRootShadowNode;
}
} // namespace reanimated
diff --git a/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.h b/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.h
index e9a5e9959e89ec33cee179ddb907c17f6dfbd3de..a2c89041518cd71e8ba5ac62ef89c0022d197c9b 100644
--- a/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.h
+++ b/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.h
@@ -3,8 +3,8 @@
#include <react/renderer/componentregistry/ComponentDescriptorFactory.h>
#include <react/renderer/mounting/MountingOverrideDelegate.h>
#include <react/renderer/scheduler/Scheduler.h>
-#include <react/renderer/uimanager/UIManagerAnimationDelegate.h>
#include <react/renderer/uimanager/UIManagerBinding.h>
+#include <react/renderer/uimanager/UIManagerCommitHook.h>
#include <reanimated/Compat/WorkletsApi.h>
#include <reanimated/LayoutAnimations/LayoutAnimationsManager.h>
#include <reanimated/LayoutAnimations/LayoutAnimationsProxyCommon.h>
@@ -102,7 +102,7 @@ struct SurfaceContext {
};
struct LayoutAnimationsProxy_Legacy : public LayoutAnimationsProxyCommon,
- public UIManagerAnimationDelegate,
+ public UIManagerCommitHook,
public std::enable_shared_from_this<LayoutAnimationsProxy_Legacy> {
mutable std::unordered_map<Tag, std::shared_ptr<Node>> nodeForTag_;
mutable std::recursive_mutex mutex;
@@ -116,11 +116,11 @@ struct LayoutAnimationsProxy_Legacy : public LayoutAnimationsProxyCommon,
const SharedComponentDescriptorRegistry &componentDescriptorRegistry,
const std::shared_ptr<const ContextContainer> &contextContainer,
jsi::Runtime &uiRuntime,
- const std::shared_ptr<UIScheduler> &uiScheduler
+ const std::shared_ptr<UIScheduler> &uiScheduler,
+ const std::shared_ptr<UIManager> &uiManager
#ifdef ANDROID
,
const PreserveMountedTagsFunction &filterUnmountedTagsFunction,
- const std::shared_ptr<UIManager> &uiManager,
const std::shared_ptr<CallInvoker> &jsInvoker
#endif
)
@@ -129,14 +129,19 @@ struct LayoutAnimationsProxy_Legacy : public LayoutAnimationsProxyCommon,
componentDescriptorRegistry,
contextContainer,
uiRuntime,
- uiScheduler
+ uiScheduler,
+ uiManager
#ifdef ANDROID
,
filterUnmountedTagsFunction,
- uiManager,
jsInvoker
#endif
) {
+ uiManager->registerCommitHook(*this);
+ }
+
+ ~LayoutAnimationsProxy_Legacy() override {
+ uiManager_->unregisterCommitHook(*this);
}
void startEnteringAnimation(const int tag, ShadowViewMutation &mutation) const;
@@ -202,19 +207,15 @@ struct LayoutAnimationsProxy_Legacy : public LayoutAnimationsProxyCommon,
const TransactionTelemetry &telemetry,
ShadowViewMutationList mutations) const override;
- // UIManagerAnimationDelegate
-
- void uiManagerDidConfigureNextLayoutAnimation(
- jsi::Runtime &runtime,
- const RawValue &config,
- const jsi::Value &successCallbackValue,
- const jsi::Value &failureCallbackValue) const override;
-
- void setComponentDescriptorRegistry(const SharedComponentDescriptorRegistry &componentDescriptorRegistry) override;
+ // UIManagerCommitHook
- bool shouldAnimateFrame() const override;
+ void commitHookWasRegistered(const UIManager &uiManager) noexcept override {}
+ void commitHookWasUnregistered(const UIManager &uiManager) noexcept override {}
- void stopSurface(SurfaceId surfaceId) override;
+ RootShadowNode::Unshared shadowTreeWillCommit(
+ const ShadowTree &shadowTree,
+ const RootShadowNode::Shared &oldRootShadowNode,
+ const RootShadowNode::Unshared &newRootShadowNode) noexcept override;
};
} // namespace reanimated
diff --git a/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp b/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp
index 9ade22bf773005613048a00c47b35767628e86c6..f3415e824da1a8da5c83762415ca54646bd6429f 100644
--- a/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp
+++ b/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp
@@ -524,15 +524,13 @@ jsi::Value ReanimatedModuleProxy::getSettledUpdates(jsi::Runtime &rt) {
StaticFeatureFlags::getFlag("FORCE_REACT_RENDER_FOR_SETTLED_ANIMATIONS") &&
"getSettledUpdates requires FORCE_REACT_RENDER_FOR_SETTLED_ANIMATIONS static feature flag to be enabled");
+ constexpr double SETTLED_ANIMATION_THRESHOLD_MS = 1000;
+
// TODO(future): use unified timestamp
const auto currentTimestamp = getAnimationTimestamp_();
- // TODO: fix bug when threshold difference is smaller than 1 second
// TODO(future): flush updates from CSS animations and CSS transitions registries
- // TODO(future): find a better way to obtain timestamp for removing updates
- // TODO(future): move removing old updates to separate method
- return animatedPropsRegistry_->getUpdatesOlderThanTimestamp(
- rt, currentTimestamp - 1000 /* 1 second */, currentTimestamp - 2000 /* 2 seconds */);
+ return animatedPropsRegistry_->collectSettledUpdates(rt, currentTimestamp - SETTLED_ANIMATION_THRESHOLD_MS);
}
bool ReanimatedModuleProxy::handleEvent(
@@ -1306,11 +1304,11 @@ void ReanimatedModuleProxy::initializeLayoutAnimationsProxy() {
componentDescriptorRegistry,
scheduler->getContextContainer(),
getJSIRuntimeFromWorkletRuntime(uiRuntime_),
- uiScheduler_
+ uiScheduler_,
+ uiManager_
#ifdef ANDROID
,
filterUnmountedTagsFunction_,
- uiManager_,
jsInvoker_
#endif
);
@@ -1319,22 +1317,19 @@ void ReanimatedModuleProxy::initializeLayoutAnimationsProxy() {
#endif
layoutAnimationsProxy_ = std::move(layoutAnimationsProxyExperimental);
} else {
- auto layoutAnimationsProxyLegacy = std::make_shared<LayoutAnimationsProxy_Legacy>(
+ layoutAnimationsProxy_ = std::make_shared<LayoutAnimationsProxy_Legacy>(
layoutAnimationsManager_,
componentDescriptorRegistry,
scheduler->getContextContainer(),
getJSIRuntimeFromWorkletRuntime(uiRuntime_),
- uiScheduler_
+ uiScheduler_,
+ uiManager_
#ifdef ANDROID
,
filterUnmountedTagsFunction_,
- uiManager_,
jsInvoker_
#endif
);
- // TODO (future): support in experimental
- uiManager_->setAnimationDelegate(layoutAnimationsProxyLegacy.get());
- layoutAnimationsProxy_ = std::move(layoutAnimationsProxyLegacy);
}
}
}
diff --git a/src/PropsRegistryGarbageCollector.ts b/src/PropsRegistryGarbageCollector.ts
index f917ce5a8586c02855f1d8d9ae73154592d22510..32148fbac8a9224ffec6edc784b48938da9585fb 100644
--- a/src/PropsRegistryGarbageCollector.ts
+++ b/src/PropsRegistryGarbageCollector.ts
@@ -11,7 +11,6 @@ import { ReanimatedModule } from './ReanimatedModule';
const FLUSH_INTERVAL_MS = 500;
export const PropsRegistryGarbageCollector = {
- viewsCount: 0,
viewsMap: new Map<number, IAnimatedComponentInternal>(),
intervalId: null as NodeJS.Timeout | null,
@@ -25,16 +24,14 @@ export const PropsRegistryGarbageCollector = {
return;
}
this.viewsMap.set(viewTag, component);
- this.viewsCount++;
- if (this.viewsCount === 1) {
+ if (this.viewsMap.size === 1) {
this.registerInterval();
}
},
unregisterView(viewTag: number) {
- this.viewsMap.delete(viewTag);
- this.viewsCount--;
- if (this.viewsCount === 0) {
+ const deleted = this.viewsMap.delete(viewTag);
+ if (deleted && this.viewsMap.size === 0) {
this.unregisterInterval();
}
},