Skip to content
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

fix: flicker when starting pan: panOffset value race condition #576

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

nmassey
Copy link
Contributor

@nmassey nmassey commented Mar 25, 2024

What: the bug

When onGestureUpdate is called, it may be using the previous value for panOffset (rather than the value just set in onGestureStart). This seems to cause a flicker on slow devices since a translation is shown that is from a different screen.

I see this flicker most obviously on my Android simulator (please see video demonstration below), but the problem exists on other devices as well.

Why

This is due to updates to shared values created via useSharedValue() updating asynchronously (see https://docs.swmansion.com/react-native-reanimated/docs/core/useSharedValue/#remarks ).

This flicker seemed to get worse after this commit b654f43 - "feat: replaced onScrollBegin with onScrollStart" (I'm not sure if there's a corresponding PR?).
This is due to the order of the handlers being triggered: onBegin then onStart then onUpdate ...

  • Before this commit, the panOffset was saved during onBegin -- which would often happen much longer before onUpdate.
  • After this commit, the panOffset is saved during onStart -- which might happen immediately before onUpdate and thus may not allow enough time for the asynchronous update to occur.

What: proposed fix

Let's add a check to make sure that panOffset has a valid value before we try to use it.

  1. When a pan is not active, panOffset will have a value of undefined.
  2. (Upon pan start, in onGestureStart, set panOffset to its correct value. This is not a change from existing code.)
  3. In onGestureUpdate and onGestureEnd, check to ensure that panOffset is not undefined before attempting to use it.
  4. In onGestureEnd, reset panOffset to undefined (since pan is no longer active).

Screengrab video recordings

These recordings were taken on an Android simulator.

buggy behavior on 4.0.0-alpha.10 (unpatched)

Notice the flicker to a different "page" when starting pan (i.e. starting touch).

Screen.Recording.2024-03-25.at.09.35.52.mov

improved behavior with patch

Screen.Recording.2024-03-25.at.09.44.35.mov

Copy link

changeset-bot bot commented Mar 25, 2024

🦋 Changeset detected

Latest commit: a99f069

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
react-native-reanimated-carousel Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link

vercel bot commented Mar 25, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
react-native-reanimated-carousel ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 6, 2024 4:31pm

@dosubot dosubot bot added the size:S This PR changes 10-29 lines, ignoring generated files. label Mar 25, 2024
@nmassey nmassey changed the title fix: flicker when starting pan; panOffset value race condition fix: flicker when starting pan: panOffset value race condition Mar 25, 2024
@BrodaNoel
Copy link
Contributor

@nmassey have you found a workaround while you wait this to be merged? I have a release blocked because of this (I guess)

@nmassey
Copy link
Contributor Author

nmassey commented Apr 22, 2024

@BrodaNoel wrote:

@nmassey have you found a workaround while you wait this to be merged? I have a release blocked because of this (I guess)

I use yarn patch to get this in (along with a couple of other tweaks I have).

Are you on the Discord?

@BrodaNoel
Copy link
Contributor

@nmassey I just joined that Discord, but if you can share the patch here, will be awesome!

@BrodaNoel
Copy link
Contributor

I had created and applied this patch, but it doesn't fix the issue I mention in #590 (which I thiiiink it's the same as you are trying to fix here)

diff --git a/node_modules/react-native-reanimated-carousel/src/components/ScrollViewGesture.tsx b/node_modules/react-native-reanimated-carousel/src/components/ScrollViewGesture.tsx
index 41e39b4..f71128f 100644
--- a/node_modules/react-native-reanimated-carousel/src/components/ScrollViewGesture.tsx
+++ b/node_modules/react-native-reanimated-carousel/src/components/ScrollViewGesture.tsx
@@ -65,7 +65,7 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
   const maxPage = dataLength;
   const isHorizontal = useDerivedValue(() => !vertical, [vertical]);
   const max = useSharedValue(0);
-  const panOffset = useSharedValue(0);
+  const panOffset = useSharedValue<number | undefined>(undefined); // set to undefined when not actively in a pan gesture
   const touching = useSharedValue(false);
   const validStart = useSharedValue(false);
   const scrollEndTranslation = useSharedValue(0);
@@ -290,6 +290,20 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
   const onGestureUpdate = useCallback((e: PanGestureHandlerEventPayload) => {
     "worklet";
 
+    if (panOffset.value === undefined) {
+      // This may happen if `onGestureStart` is called as a part of the
+      // JS thread (instead of the UI thread / worklet). If so, when
+      // `onGestureStart` sets panOffset.value, the set will be asynchronous,
+      // and so it may not actually occur before `onGestureUpdate` is called.
+      //
+      // Keeping this value as `undefined` when it is not active protects us
+      // from the situation where we may use the previous value for panOffset
+      // instead; this would cause a visual flicker in the carousel.
+
+      // console.warn("onGestureUpdate: panOffset is undefined");
+      return;
+    }
+
     if (validStart.value) {
       validStart.value = false;
       cancelAnimation(translation);
@@ -334,6 +348,10 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
   const onGestureEnd = useCallback((e: GestureStateChangeEvent<PanGestureHandlerEventPayload>, _success: boolean) => {
     "worklet";
 
+    if (panOffset.value === undefined) {
+      return;
+    }
+
     const { velocityX, velocityY, translationX, translationY } = e;
     scrollEndVelocity.value = isHorizontal.value
       ? velocityX
@@ -379,6 +397,8 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
 
     if (!loop)
       touching.value = false;
+
+    panOffset.value = undefined;
   }, [
     size,
     loop,

@nmassey
Copy link
Contributor Author

nmassey commented Apr 22, 2024

I had created and applied this patch, but it doesn't fix the issue I mention in #590 (which I thiiiink it's the same as you are trying to fix here)
...

In addition to patching the source code, you'll also need to patch the relevant built lib/ files in the plugin. (When you use the plugin from your code, those are the actual JS files that get used.)

You can either patch the source code by hand (possible, but annoying), or patch the target plugin in its own repo and rebuild it to generate the relevant lib/ code (and then copy those files over). I sent some instructions for how I do this on Discord.

Note that you can confirm that your patch is running by adding some easily-recognizable code to the relevant lib/ file. e.g. you can add console.log('hello 123') to a specific lib/ file to make sure that it is getting executed in your local development build.

I hope this helps! 🤓🙏 And, catch you on Discord!

@BrodaNoel
Copy link
Contributor

@nmassey can you please share the versions of all the packages you are using?
I applied your PRs but still some small issues (which we talked in Discord) and I'm thinking it may be related to other libraries' versions

@sieeniu
Copy link

sieeniu commented May 6, 2024

when it would be released? 😄

@dohooo
Copy link
Owner

dohooo commented May 6, 2024

Incredible work, thank you! But before merging, could you gen a changeset for this PR?

npx changeset

@sieeniu
Copy link

sieeniu commented May 8, 2024

so it is now ready to merge imho

@sieeniu
Copy link

sieeniu commented May 10, 2024

^up

@alessandro-bottamedi
Copy link

alessandro-bottamedi commented Jun 4, 2024

I also see this problem on Alpha 12, on both emulator and real devices, but only android.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
size:S This PR changes 10-29 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants