Skip to content

cherry-pick: Remove non-layout style and prop updates path via synchronouslyUpdatePropsOnUIThread #7313

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

tomekzaw
Copy link
Member

@tomekzaw tomekzaw commented Mar 27, 2025

Summary

This PR cherry-picks #7014 to Reanimated 3.17-stable.

Test plan

…ePropsOnUIThread` (#7014)

Currently, there are two ways to update native view props and styles in
Reanimated. The default path (so-called slow path) is to apply all props
changes to the ShadowTree via C++ API and let React Native mount the
changes. However, if all props updated in given batch are non-layout
props (i.e. those that don't require layout recalculation, like
background color or opacity) we use a fast path that calls
`synchronouslyUpdatePropsOnUIThread` from React Native and applies the
changes directly to platform views, without making changes to ShadowTree
in C++. Turns out, some features like view measurement or touch
detection system use C++ ShadowTree which is not consistent with what's
currently on the screen. Because of that, we're removing the fast path
(turns out it's not that fast, especially on iOS) to restore the
correctness of view measurement and touch detection for animated
components.

* Performance monitor example → Bokeh Example
* Android emulator / iPhone 14 Pro real device
* Debug mode
* Animating `transform` prop using `useAnimatedStyle`

| Platform | Before (main) | After (this PR) |
|:-:|:-:|:-:|
| Android (count=200) | 20 fps | 15 fps |
| iOS (count=500) | 22 fps | 22 fps |

<details>
<summary>App.tsx</summary>

```tsx
import React, { useState } from 'react';
import { Dimensions, StyleSheet, View } from 'react-native';
import Animated, {
  Easing,
  useAnimatedStyle,
  useReducedMotion,
  useSharedValue,
  withTiming,
} from 'react-native-reanimated';

const dimensions = Dimensions.get('window');

function randBetween(min: number, max: number) {
  return min + Math.random() * (max - min);
}

function Circle() {
  const shouldReduceMotion = useReducedMotion();

  const [power] = useState(randBetween(0, 1));
  const [duration] = useState(randBetween(2000, 3000));

  const size = 100 + power * 250;
  const width = size;
  const height = size;
  const hue = randBetween(100, 200);
  const backgroundColor = `hsl(${hue},100%,50%)`;

  const opacity = 0.1 + (1 - power) * 0.1;
  const config = { duration, easing: Easing.linear };

  const left = useSharedValue(randBetween(0, dimensions.width) - size / 2);
  const top = useSharedValue(randBetween(0, dimensions.height) - size / 2);

  const update = () => {
    left.value = withTiming(left.value + randBetween(-100, 100), config);
    top.value = withTiming(top.value + randBetween(-100, 100), config);
  };

  React.useEffect(() => {
    update();
    if (shouldReduceMotion) {
      return;
    }
    const id = setInterval(update, duration);
    return () => clearInterval(id);
  });

  const animatedStyle = useAnimatedStyle(
    () => ({
      transform: [{ translateX: left.value }, { translateY: top.value }],
    }),
    []
  );

  return (
    <Animated.View
      style={[
        styles.circle,
        { width, height, backgroundColor, opacity },
        animatedStyle,
      ]}
    />
  );
}

interface BokehProps {
  count: number;
}

function Bokeh({ count }: BokehProps) {
  return (
    <>
      {[...Array(count)].map((_, i) => (
        <Circle key={i} />
      ))}
    </>
  );
}

export default function App() {
  return (
    <View style={styles.container}>
      <Bokeh count={200} />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: 'black',
    overflow: 'hidden',
  },
  circle: {
    position: 'absolute',
    borderRadius: 999,
  },
});
```

</details>

* Fixes
#6832.
* Fixes
#6676.
Copy link
Contributor

@patrycjakalinska patrycjakalinska left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🪨

@tomekzaw tomekzaw merged commit 73b9ff2 into 3.17-stable Mar 28, 2025
33 of 47 checks passed
@tomekzaw tomekzaw deleted the @tomekzaw/cherry-pick/remove-synchronously-update-props-on-ui-thread branch March 28, 2025 17:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants