-
-
Notifications
You must be signed in to change notification settings - Fork 980
Commit
This PR slightly modifies how gesture callbacks are updated when using Reanimated. Instead of updating them every time, it will now be updated only when the gesture config has changed. In combination with `useMemo`, this will make optimizing performance-critical use cases easier. It accomplishes this by adding `gestureId` property to every gesture instance, which then is checked before updating the shared value. The update happens only when the id is different (which will happen only when the gesture inside `useMemo` is recalculated, i.e. only when the dependencies change).
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -268,11 +268,34 @@ function updateHandlers( | |
} | ||
|
||
if (preparedGesture.animatedHandlers) { | ||
preparedGesture.animatedHandlers.value = (preparedGesture.config | ||
const previousHandlersValue = | ||
preparedGesture.animatedHandlers.value ?? []; | ||
const newHandlersValue = (preparedGesture.config | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
j-piasecki
Author
Member
|
||
.filter((g) => g.shouldUseReanimated) // ignore gestures that shouldn't run on UI | ||
.map((g) => g.handlers) as unknown) as HandlerCallbacks< | ||
Record<string, unknown> | ||
>[]; | ||
|
||
// if amount of gesture configs changes, we need to update the callbacks in shared value | ||
let shouldUpdateSharedValue = | ||
previousHandlersValue.length !== newHandlersValue.length; | ||
|
||
if (!shouldUpdateSharedValue) { | ||
// if the amount is the same, we need to check if any of the configs inside has changed | ||
for (let i = 0; i < newHandlersValue.length; i++) { | ||
if ( | ||
// we can use the `gestureId` prop as it's unique for every config instance | ||
newHandlersValue[i].gestureId !== previousHandlersValue[i].gestureId | ||
) { | ||
shouldUpdateSharedValue = true; | ||
break; | ||
} | ||
} | ||
} | ||
|
||
if (shouldUpdateSharedValue) { | ||
preparedGesture.animatedHandlers.value = newHandlersValue; | ||
} | ||
} | ||
|
||
scheduleFlushOperations(); | ||
|
@j-piasecki @kmagiera we stumbled onto while investigating performance issues on Android. We recently introduced GestureDetector around individual items in a list and we are seeing many performance regressions to our prod metrics.
We recently also learned that reading SharedValues.value on the main JS thread blocks the thread (I wrote an article about this finding).
When running the profiler in Release on a low end Android device, we saw this:
By profling our app in production for a total amount of 12 seconds for us to open the app and navigate to our search screen. The profilings indicate that the JS thread is blocked by this
preparedGesture.animatedHandlers.value
for an average time of 900ms. This is particularly exacerbated because we render a list of items each wrapped with a GestureDetector.I reverted this change you made with a patch and performance got better. I'm wondering now if this logic that checks previous handlers value is even necessary given that updateHandlers call is wrapped within a useEffect that should only run if children or the gesture prop changes. Perhaps that useEffect could be further narrowed down to just checking if props.gesture changed allowing the consumer to control the updateHandlers with a useMemo at the consumer site.
We are on version 2.14 however the logic above looks the same on latest despite the recent house clean up which split this logic in multiple files.