Skip to content

Commit a2feaeb

Browse files
Andrei Shikovfacebook-github-bot
Andrei Shikov
authored andcommitted
Refactor touch event dispatch
Summary: Updates touch events in Fabric to be dispatched through the same pipeline as the rest of events, instead of relying on custom dispatch behavior. Previous method of handling touches was reusing Paper behavior which required: 1. Transform event into a Paper-compatible form of WritableArray and dispatch it to `RCTEventEmitter.receiveTouches`. 2. Intercept `receiveTouches` for Fabric and redirect it to `FabricEventEmitter` 3. Perform transformations copied from Paper JS renderer in Java, transform it to the final form and dispatch this event as usual after. The new behavior uses emitter's `receiveEvent` method directly to dispatch events. Additionally, it should decrease allocations done when transforming events during step 3 above, as `WritableNativeMap`-based operations performed many re-allocations when reading/re-creating arrays. Changelog: [Android][Changed] - Added an experimental touch dispatch path Reviewed By: JoshuaGross Differential Revision: D31280052 fbshipit-source-id: 829c2646ac6b0ebff0f0106159e76d84324ac732
1 parent 53fd0f4 commit a2feaeb

File tree

3 files changed

+143
-6
lines changed

3 files changed

+143
-6
lines changed

ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java

+2
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,6 @@ public static boolean isMapBufferSerializationEnabled() {
103103
public static boolean enableScrollViewSnapToAlignmentProp = true;
104104

105105
public static boolean useDispatchUniqueForCoalescableEvents = false;
106+
107+
public static boolean useUpdatedTouchPreprocessing = false;
106108
}

ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchEvent.java

+37-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import com.facebook.infer.annotation.Assertions;
1414
import com.facebook.react.bridge.ReactSoftExceptionLogger;
1515
import com.facebook.react.bridge.SoftAssertions;
16+
import com.facebook.react.bridge.WritableMap;
17+
import com.facebook.react.config.ReactFeatureFlags;
1618

1719
/**
1820
* An event representing the start, end or movement of a touch. Corresponds to a single {@link
@@ -194,7 +196,41 @@ public void dispatch(RCTEventEmitter rctEventEmitter) {
194196

195197
@Override
196198
public void dispatchModern(RCTModernEventEmitter rctEventEmitter) {
197-
dispatch(rctEventEmitter);
199+
if (ReactFeatureFlags.useUpdatedTouchPreprocessing) {
200+
TouchesHelper.sendTouchEventModern(rctEventEmitter, this, /* useDispatchV2 */ false);
201+
} else {
202+
dispatch(rctEventEmitter);
203+
}
204+
}
205+
206+
@Override
207+
public void dispatchModernV2(RCTModernEventEmitter rctEventEmitter) {
208+
if (ReactFeatureFlags.useUpdatedTouchPreprocessing) {
209+
TouchesHelper.sendTouchEventModern(rctEventEmitter, this, /* useDispatchV2 */ true);
210+
} else {
211+
dispatch(rctEventEmitter);
212+
}
213+
}
214+
215+
@Override
216+
protected int getEventCategory() {
217+
TouchEventType type = mTouchEventType;
218+
if (type == null) {
219+
return EventCategoryDef.UNSPECIFIED;
220+
}
221+
222+
switch (type) {
223+
case START:
224+
return EventCategoryDef.CONTINUOUS_START;
225+
case END:
226+
case CANCEL:
227+
return EventCategoryDef.CONTINUOUS_END;
228+
case MOVE:
229+
return EventCategoryDef.CONTINUOUS;
230+
}
231+
232+
// Something something smart compiler...
233+
return super.getEventCategory();
198234
}
199235

200236
public MotionEvent getMotionEvent() {

ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.java

+104-5
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@
99

1010
import android.view.MotionEvent;
1111
import com.facebook.react.bridge.Arguments;
12+
import com.facebook.react.bridge.ReactSoftExceptionLogger;
1213
import com.facebook.react.bridge.WritableArray;
1314
import com.facebook.react.bridge.WritableMap;
15+
import com.facebook.react.bridge.WritableNativeArray;
1416
import com.facebook.react.uimanager.PixelUtil;
1517

1618
/** Class responsible for generating catalyst touch events based on android {@link MotionEvent}. */
1719
public class TouchesHelper {
18-
1920
public static final String TARGET_SURFACE_KEY = "targetSurface";
2021
public static final String TARGET_KEY = "target";
2122
public static final String CHANGED_TOUCHES_KEY = "changedTouches";
@@ -28,14 +29,16 @@ public class TouchesHelper {
2829
private static final String LOCATION_X_KEY = "locationX";
2930
private static final String LOCATION_Y_KEY = "locationY";
3031

32+
private static final String TAG = "TouchesHelper";
33+
3134
/**
3235
* Creates catalyst pointers array in format that is expected by RCTEventEmitter JS module from
3336
* given {@param event} instance. This method use {@param reactTarget} parameter to set as a
3437
* target view id associated with current gesture.
3538
*/
36-
private static WritableArray createsPointersArray(TouchEvent event) {
37-
WritableArray touches = Arguments.createArray();
39+
private static WritableMap[] createPointersArray(TouchEvent event) {
3840
MotionEvent motionEvent = event.getMotionEvent();
41+
WritableMap[] touches = new WritableMap[motionEvent.getPointerCount()];
3942

4043
// Calculate the coordinates for the target view.
4144
// The MotionEvent contains the X,Y of the touch in the coordinate space of the root view
@@ -63,7 +66,8 @@ private static WritableArray createsPointersArray(TouchEvent event) {
6366
touch.putInt(TARGET_KEY, event.getViewTag());
6467
touch.putDouble(TIMESTAMP_KEY, event.getTimestampMs());
6568
touch.putDouble(POINTER_IDENTIFIER_KEY, motionEvent.getPointerId(index));
66-
touches.pushMap(touch);
69+
70+
touches[index] = touch;
6771
}
6872

6973
return touches;
@@ -78,7 +82,8 @@ private static WritableArray createsPointersArray(TouchEvent event) {
7882
*/
7983
public static void sendTouchEvent(RCTEventEmitter rctEventEmitter, TouchEvent touchEvent) {
8084
TouchEventType type = touchEvent.getTouchEventType();
81-
WritableArray pointers = createsPointersArray(touchEvent);
85+
86+
WritableArray pointers = getWritableArray(createPointersArray(touchEvent));
8287
MotionEvent motionEvent = touchEvent.getMotionEvent();
8388

8489
// For START and END events send only index of the pointer that is associated with that event
@@ -96,4 +101,98 @@ public static void sendTouchEvent(RCTEventEmitter rctEventEmitter, TouchEvent to
96101

97102
rctEventEmitter.receiveTouches(TouchEventType.getJSEventName(type), pointers, changedIndices);
98103
}
104+
105+
/**
106+
* Generate touch event data to match JS expectations. Combines logic in {@link #sendTouchEvent}
107+
* and FabricEventEmitter to create the same data structure in a more efficient manner.
108+
*
109+
* <p>Touches have to be dispatched as separate events for each changed pointer to make JS process
110+
* them correctly. To avoid allocations, we preprocess touch events in Java world and then convert
111+
* them to native before dispatch.
112+
*
113+
* @param eventEmitter emitter to dispatch event to
114+
* @param event the touch event to extract data from
115+
* @param useDispatchV2 whether to dispatch additional data used by {@link Event#dispatchModernV2}
116+
*/
117+
public static void sendTouchEventModern(
118+
RCTModernEventEmitter eventEmitter, TouchEvent event, boolean useDispatchV2) {
119+
TouchEventType type = event.getTouchEventType();
120+
MotionEvent motionEvent = event.getMotionEvent();
121+
122+
if (motionEvent == null) {
123+
ReactSoftExceptionLogger.logSoftException(
124+
TAG,
125+
new IllegalStateException(
126+
"Cannot dispatch a TouchEvent that has no MotionEvent; the TouchEvent has been recycled"));
127+
return;
128+
}
129+
130+
WritableMap[] touches = createPointersArray(event);
131+
WritableMap[] changedTouches = null;
132+
133+
switch (type) {
134+
case START:
135+
int newPointerIndex = motionEvent.getActionIndex();
136+
137+
changedTouches = new WritableMap[] {touches[newPointerIndex].copy()};
138+
break;
139+
case END:
140+
int finishedPointerIndex = motionEvent.getActionIndex();
141+
/*
142+
* Clear finished pointer index for compatibility with W3C touch "end" events, where the
143+
* active touches don't include the set that has just been "ended".
144+
*/
145+
WritableMap finishedPointer = touches[finishedPointerIndex];
146+
touches[finishedPointerIndex] = null;
147+
148+
changedTouches = new WritableMap[] {finishedPointer};
149+
break;
150+
case MOVE:
151+
changedTouches = new WritableMap[touches.length];
152+
for (int i = 0; i < touches.length; i++) {
153+
changedTouches[i] = touches[i].copy();
154+
}
155+
break;
156+
case CANCEL:
157+
changedTouches = touches;
158+
touches = new WritableMap[0];
159+
break;
160+
}
161+
162+
WritableArray touchesArray = getWritableArray(touches);
163+
WritableArray changedTouchesArray = getWritableArray(/* copyObjects */ true, changedTouches);
164+
165+
for (WritableMap eventData : changedTouches) {
166+
eventData.putArray(CHANGED_TOUCHES_KEY, changedTouchesArray);
167+
eventData.putArray(TOUCHES_KEY, touchesArray);
168+
169+
if (useDispatchV2) {
170+
eventEmitter.receiveEvent(
171+
event.getSurfaceId(),
172+
event.getViewTag(),
173+
event.getEventName(),
174+
event.canCoalesce(),
175+
0,
176+
eventData,
177+
event.getEventCategory());
178+
} else {
179+
eventEmitter.receiveEvent(
180+
event.getSurfaceId(), event.getViewTag(), event.getEventName(), eventData);
181+
}
182+
}
183+
}
184+
185+
private static WritableArray getWritableArray(WritableMap... objects) {
186+
return getWritableArray(false, objects);
187+
}
188+
189+
private static WritableArray getWritableArray(boolean copyObjects, WritableMap... objects) {
190+
WritableArray result = new WritableNativeArray();
191+
for (WritableMap object : objects) {
192+
if (object != null) {
193+
result.pushMap(copyObjects ? object.copy() : object);
194+
}
195+
}
196+
return result;
197+
}
99198
}

0 commit comments

Comments
 (0)