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

Merged
merged 3 commits into from
Sep 11, 2024

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.

@paulokaome
Copy link

paulokaome commented Jun 18, 2024

When will this patch be released?

@alfilimonov
Copy link

Waiting for this patch sooo much too!

@keenan35i
Copy link

Any update for this issue? Would be nice to start using 4.0 but Alpha 12 still has flickering issues.

@RikSchefferAmsterdam
Copy link

@dohooo any chance this can be merged soon?

@Crizzooo
Copy link

Subscribing to know when this is merged.

Going back to old RNCarousel temporarily 🥺

@nmassey
Copy link
Contributor Author

nmassey commented Aug 22, 2024

hi @Crizzooo - I've been using yarn patch to unblock me while waiting for these PRs to be merged 😕
Are you on the Discord?

@Crizzooo
Copy link

hi @Crizzooo - I've been using yarn patch to unblock me while waiting for these PRs to be merged 😕
Are you on the Discord?

One of our engineers just joined and is applying the patch for us :)

mmeissonnier-pass pushed a commit to pass-culture/pass-culture-app-native that referenced this pull request Sep 10, 2024
mmeissonnier-pass pushed a commit to pass-culture/pass-culture-app-native that referenced this pull request Sep 10, 2024
Copy link
Owner

@dohooo dohooo left a comment

Choose a reason for hiding this comment

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

Thank you.

@dosubot dosubot bot added the lgtm This PR has been approved by a maintainer label Sep 11, 2024
@dohooo dohooo changed the base branch from main to dev September 11, 2024 07:26
@dohooo dohooo merged commit 6794ac6 into dohooo:dev Sep 11, 2024
2 checks passed
mmeissonnier-pass pushed a commit to pass-culture/pass-culture-app-native that referenced this pull request Sep 11, 2024
mmeissonnier-pass added a commit to pass-culture/pass-culture-app-native that referenced this pull request Sep 11, 2024
* (PC-31740) chore: upgrade reanimated to 3.15

* fix: issues with carousel

* fix: patch react-native-reanimated-carousel (dohooo/react-native-reanimated-carousel#576)

* test: fix tests

* fix: OfferPreviewModal .web file

---------

Co-authored-by: Mathieu Meissonnier <mathieu.meissonnier@passculture.app>
bebstein-pass pushed a commit to pass-culture/pass-culture-app-native that referenced this pull request Sep 12, 2024
* (PC-31740) chore: upgrade reanimated to 3.15

* fix: issues with carousel

* fix: patch react-native-reanimated-carousel (dohooo/react-native-reanimated-carousel#576)

* test: fix tests

* fix: OfferPreviewModal .web file

---------

Co-authored-by: Mathieu Meissonnier <mathieu.meissonnier@passculture.app>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
lgtm This PR has been approved by a maintainer size:S This PR changes 10-29 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants