Skip to content

Commit 12ffcc4

Browse files
j-piaseckim-bert
andauthored
[General] Schedule flushes instead of flushing immediately (#3830)
## Description Currently, when creating a gesture or configuring relations, `flushOperations` is called immediately. When rendering multiple detectors in the same batch, this adds unnecessary overhead as it can be done a single time at the end of the batch. This PR changes that. I also noticed that `createGestureHandler` is scheduled on the UI anyway, so I figured there may be no point in keeping it synchronous. ## Test plan <details> <summary>Used this stress test to measure impact:</summary> ```js import React, { Profiler, useEffect, useRef } from 'react'; import { ScrollView, StyleSheet, View } from 'react-native'; import { GestureDetector, usePan } from 'react-native-gesture-handler'; import Animated, { useAnimatedStyle, useSharedValue, } from 'react-native-reanimated'; const DATA = new Array(500).fill(null).map((_, i) => `Item ${i + 1}`); function Item() { const translateX = useSharedValue(0); const style = useAnimatedStyle(() => { return { transform: [{ translateX: translateX.value }], }; }); const pan = usePan({ onUpdate: (event) => { 'worklet'; translateX.value += event.handlerData.changeX; }, }); return ( <View style={{ height: 80, padding: 16, backgroundColor: 'gray' }}> <GestureDetector gesture={pan}> {/* <View collapsable={false} style={{opacity: 0.5}}> */} <Animated.View style={[ { backgroundColor: 'red', height: '100%', aspectRatio: 1 }, style, ]} /> {/* </View> */} </GestureDetector> </View> ); } function Benchmark() { return ( <ScrollView style={{ flex: 1 }} contentContainerStyle={{ flexGrow: 1, gap: 8 }}> {DATA.map((_, index) => ( <Item key={index} /> ))} </ScrollView> ); } const TIMES = 30; export default function EmptyExample() { const times = useRef<number[]>([]).current; const [visible, setVisible] = React.useState(false); useEffect(() => { if (!visible && times.length < TIMES) { setTimeout(() => { setVisible(true); }, 24); } if (times.length === TIMES) { // calculate average, but remove highest and lowest const sortedTimes = [...times].sort((a, b) => a - b); sortedTimes.shift(); sortedTimes.pop(); const avgTime = sortedTimes.reduce((sum, time) => sum + time, 0) / sortedTimes.length; console.log(`Average render time: ${avgTime} ms`); } }, [visible]); return ( <View style={styles.container}> {visible && ( <Profiler id="v3" onRender={(_id, _phase, actualDuration) => { times.push(actualDuration); setTimeout(() => { setVisible(false); }, 24); }}> <Benchmark /> </Profiler> )} </View> ); } const styles = StyleSheet.create({ container: { flex: 1, }, }); ``` </details> I saw the average time fall from ~610ms to ~595ms. --------- Co-authored-by: Michał Bert <[email protected]>
1 parent a5fc2c6 commit 12ffcc4

File tree

5 files changed

+10
-11
lines changed

5 files changed

+10
-11
lines changed

packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,16 +61,14 @@ class RNGestureHandlerModule(reactContext: ReactApplicationContext?) :
6161
}
6262

6363
@ReactMethod
64-
override fun createGestureHandler(handlerName: String, handlerTagDouble: Double, config: ReadableMap): Boolean {
64+
override fun createGestureHandler(handlerName: String, handlerTagDouble: Double, config: ReadableMap) {
6565
if (ReanimatedProxy.REANIMATED_INSTALLED && !uiRuntimeDecorated) {
6666
uiRuntimeDecorated = decorateUIRuntime()
6767
}
6868

6969
val handlerTag = handlerTagDouble.toInt()
7070

7171
createGestureHandlerHelper<GestureHandler>(handlerName, handlerTag, config)
72-
73-
return true
7472
}
7573

7674
@ReactMethod

packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ - (bool)installUIRuntimeBindings
119119
});
120120
}
121121

122-
- (NSNumber *)createGestureHandler:(NSString *)handlerName handlerTag:(double)handlerTag config:(NSDictionary *)config
122+
- (void)createGestureHandler:(NSString *)handlerName handlerTag:(double)handlerTag config:(NSDictionary *)config
123123
{
124124
if (!_checkedIfReanimatedIsAvailable) {
125125
_isReanimatedAvailable = [self.moduleRegistry moduleForName:"ReanimatedModule"] != nil;
@@ -132,8 +132,6 @@ - (NSNumber *)createGestureHandler:(NSString *)handlerName handlerTag:(double)ha
132132
[self addOperationBlock:^(RNGestureHandlerManager *manager) {
133133
[manager createGestureHandler:handlerName tag:[NSNumber numberWithDouble:handlerTag] config:config];
134134
}];
135-
136-
return @1;
137135
}
138136

139137
- (void)attachGestureHandler:(double)handlerTag newView:(double)viewTag actionType:(double)actionType

packages/react-native-gesture-handler/src/specs/NativeRNGestureHandlerModule.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export interface Spec extends TurboModule {
1010
// Record<> is not supported by codegen
1111
// eslint-disable-next-line @typescript-eslint/ban-types
1212
config: Object
13-
) => boolean;
13+
) => void;
1414
attachGestureHandler: (
1515
handlerTag: Double,
1616
newView: Double,

packages/react-native-gesture-handler/src/v3/detectors/utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// For `waitFor` we need array as order of the gestures matters.
55
// For `simultaneousHandlers` we use Set as the order doesn't matter.
66

7+
import { scheduleFlushOperations } from '../../handlers/utils';
78
import RNGestureHandlerModule from '../../RNGestureHandlerModule';
89
import { tagMessage } from '../../utils';
910
import {
@@ -148,7 +149,7 @@ export function configureRelations<THandlerData, TConfig>(
148149
);
149150
}
150151

151-
RNGestureHandlerModule.flushOperations();
152+
scheduleFlushOperations();
152153
}
153154

154155
export function ensureNativeDetectorComponent(

packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
} from './utils';
1212
import { tagMessage } from '../../utils';
1313
import { BaseGestureConfig, SingleGesture, SingleGestureName } from '../types';
14+
import { scheduleFlushOperations } from '../../handlers/utils';
1415

1516
export function useGesture<THandlerData, TConfig>(
1617
type: SingleGestureName,
@@ -81,20 +82,21 @@ export function useGesture<THandlerData, TConfig>(
8182
) {
8283
currentGestureRef.current = { type, tag };
8384
RNGestureHandlerModule.createGestureHandler(type, tag, {});
84-
RNGestureHandlerModule.flushOperations();
85+
// It's possible that this can cause errors about handler not being created when attempting to mount NativeDetector
86+
scheduleFlushOperations();
8587
}
8688

8789
useEffect(() => {
8890
return () => {
8991
RNGestureHandlerModule.dropGestureHandler(tag);
90-
RNGestureHandlerModule.flushOperations();
92+
scheduleFlushOperations();
9193
};
9294
}, [type, tag]);
9395

9496
useEffect(() => {
9597
const preparedConfig = prepareConfigForNativeSide(type, config);
9698
RNGestureHandlerModule.setGestureHandlerConfig(tag, preparedConfig);
97-
RNGestureHandlerModule.flushOperations();
99+
scheduleFlushOperations();
98100

99101
bindSharedValues(config, tag);
100102

0 commit comments

Comments
 (0)