Skip to content

Commit fb8a6a5

Browse files
yungstersfacebook-github-bot
authored andcommitted
Animated: Avoid In-Band State Update (#49184)
Summary: Pull Request resolved: #49184 D65645985 shipped a refactor to `Animated`, so that it would use a custom `useAnimatedPropsMemo` instead of `useMemo`. This significantly improved update performance by no longer invalidating the `AnimatedProps` on effectively every update to `Animated` components. However, this was measured to increase memory usage. After a few experiments, we identified that use of the in-band state update was responsible for the memory regression. While this requires further root cause investigation, this diff attempts to mitigate the memory regression. This diff introduces a feature flag that enables an implementation that minimizes duplicated work, such as unnecessarily computing `compositeKey` or creating new instances of `AnimatedProps`. In addition, this implementation strives to do so without significantly degrading when an update is interrupted by a concurrent update. Changelog: [General][Changed] - Introduced a feature flag to test an optimization in `Animated` to reduce memory usage. Reviewed By: rickhanlonii Differential Revision: D69135223 fbshipit-source-id: a2699a314625e7570698bc41455b139711cfd7e3
1 parent bdb394f commit fb8a6a5

File tree

3 files changed

+65
-3
lines changed

3 files changed

+65
-3
lines changed

packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,17 @@ const definitions: FeatureFlagDefinitions = {
508508
},
509509
ossReleaseStage: 'none',
510510
},
511+
avoidStateUpdateInAnimatedPropsMemo: {
512+
defaultValue: false,
513+
metadata: {
514+
dateAdded: '2025-02-05',
515+
description:
516+
'Changes `useAnimatedPropsMemo` to avoid state updates to invalidate the cached `AnimatedProps`.',
517+
expectedReleaseValue: true,
518+
purpose: 'experimentation',
519+
},
520+
ossReleaseStage: 'none',
521+
},
511522
disableInteractionManager: {
512523
defaultValue: false,
513524
metadata: {

packages/react-native/src/private/animated/createAnimatedPropsMemoHook.js

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ import {AnimatedEvent} from '../../../Libraries/Animated/AnimatedEvent';
1717
import AnimatedNode from '../../../Libraries/Animated/nodes/AnimatedNode';
1818
import {isPlainObject} from '../../../Libraries/Animated/nodes/AnimatedObject';
1919
import flattenStyle from '../../../Libraries/StyleSheet/flattenStyle';
20+
import * as ReactNativeFeatureFlags from '../featureflags/ReactNativeFeatureFlags';
2021
import nullthrows from 'nullthrows';
21-
import {useMemo, useState} from 'react';
22+
import {useInsertionEffect, useMemo, useRef, useState} from 'react';
2223

2324
type CompositeKey = {
2425
style?: {[string]: CompositeKeyComponent},
@@ -64,6 +65,50 @@ export function createAnimatedPropsMemoHook(
6465
return function useAnimatedPropsMemo(
6566
create: () => AnimatedProps,
6667
props: $ReadOnly<{[string]: mixed}>,
68+
): AnimatedProps {
69+
// NOTE: This feature flag must be evaluated inside the hook because this
70+
// module factory can be evaluated much sooner, before overrides are set.
71+
const useAnimatedPropsImpl =
72+
ReactNativeFeatureFlags.avoidStateUpdateInAnimatedPropsMemo()
73+
? useAnimatedPropsMemo_ref
74+
: useAnimatedPropsMemo_state;
75+
return useAnimatedPropsImpl(create, props);
76+
};
77+
78+
function useAnimatedPropsMemo_ref(
79+
create: () => AnimatedProps,
80+
props: $ReadOnly<{[string]: mixed}>,
81+
): AnimatedProps {
82+
const compositeKey = useMemo(
83+
() => createCompositeKeyForProps(props, allowlist),
84+
[props],
85+
);
86+
87+
const prevRef = useRef<?$ReadOnly<{
88+
compositeKey: typeof compositeKey,
89+
node: AnimatedProps,
90+
}>>();
91+
const prev = prevRef.current;
92+
93+
const next =
94+
prev != null &&
95+
areCompositeKeysEqual(prev.compositeKey, compositeKey, allowlist)
96+
? prev
97+
: {
98+
compositeKey,
99+
node: create(),
100+
};
101+
102+
useInsertionEffect(() => {
103+
prevRef.current = next;
104+
}, [next]);
105+
106+
return next.node;
107+
}
108+
109+
function useAnimatedPropsMemo_state(
110+
create: () => AnimatedProps,
111+
props: $ReadOnly<{[string]: mixed}>,
67112
): AnimatedProps {
68113
const compositeKey = useMemo(
69114
() => createCompositeKeyForProps(props, allowlist),
@@ -91,7 +136,7 @@ export function createAnimatedPropsMemoHook(
91136
});
92137
}
93138
return state.value;
94-
};
139+
}
95140
}
96141

97142
/**

packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<d807507f5d26ccb2ba6849d40ed88e7a>>
7+
* @generated SignedSource<<189673854f1226119417140b4afd46e4>>
88
* @flow strict
99
*/
1010

@@ -30,6 +30,7 @@ export type ReactNativeFeatureFlagsJsOnly = $ReadOnly<{
3030
jsOnlyTestFlag: Getter<boolean>,
3131
animatedShouldDebounceQueueFlush: Getter<boolean>,
3232
animatedShouldUseSingleOp: Getter<boolean>,
33+
avoidStateUpdateInAnimatedPropsMemo: Getter<boolean>,
3334
disableInteractionManager: Getter<boolean>,
3435
enableAccessToHostTreeInFabric: Getter<boolean>,
3536
enableAnimatedClearImmediateFix: Getter<boolean>,
@@ -107,6 +108,11 @@ export const animatedShouldDebounceQueueFlush: Getter<boolean> = createJavaScrip
107108
*/
108109
export const animatedShouldUseSingleOp: Getter<boolean> = createJavaScriptFlagGetter('animatedShouldUseSingleOp', false);
109110

111+
/**
112+
* Changes `useAnimatedPropsMemo` to avoid state updates to invalidate the cached `AnimatedProps`.
113+
*/
114+
export const avoidStateUpdateInAnimatedPropsMemo: Getter<boolean> = createJavaScriptFlagGetter('avoidStateUpdateInAnimatedPropsMemo', false);
115+
110116
/**
111117
* Disables InteractionManager and replaces its scheduler with `setImmediate`.
112118
*/

0 commit comments

Comments
 (0)