From 3b6d8af2908985c5be6200319d82c5594c22d889 Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Tue, 14 Dec 2021 10:43:14 -0800 Subject: [PATCH] Remove old touch processing for Fabric Summary: With the updates to touch processing rolled out, we can remove the feature flag and clean up the old code. The old path is now used exclusively by legacy renderer and Fabric uses new `EventEmitter#receiveTouches` method to process touches. Changelog: [Changed][Android] - Update touch processing internals Reviewed By: mdvacca Differential Revision: D32953664 fbshipit-source-id: 517a4ce6ce9bc15528c2db94d7d11bdff8b78743 --- .../react/config/ReactFeatureFlags.java | 4 - .../fabric/events/FabricEventEmitter.java | 153 +----------------- .../react/uimanager/events/Event.java | 25 +-- .../uimanager/events/EventDispatcherImpl.java | 7 +- .../events/LockFreeEventDispatcherImpl.java | 2 +- .../events/RCTModernEventEmitter.java | 2 + .../uimanager/events/ReactEventEmitter.java | 21 ++- .../react/uimanager/events/TouchEvent.java | 22 +-- .../react/uimanager/events/TouchesHelper.java | 40 +++-- 9 files changed, 56 insertions(+), 220 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java b/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java index c7410ce8199..2136c6cce57 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java +++ b/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java @@ -105,10 +105,6 @@ public class ReactFeatureFlags { public static boolean enableScrollViewSnapToAlignmentProp = true; - public static boolean useDispatchUniqueForCoalescableEvents = false; - - public static boolean useUpdatedTouchPreprocessing = false; - /** TODO: T103427072 Delete ReactFeatureFlags.enableNestedTextOnPressEventFix */ public static boolean enableNestedTextOnPressEventFix = true; diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/events/FabricEventEmitter.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/events/FabricEventEmitter.java index 983466ab382..425e7d8f2bc 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/events/FabricEventEmitter.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/events/FabricEventEmitter.java @@ -7,27 +7,16 @@ package com.facebook.react.fabric.events; -import static com.facebook.react.uimanager.events.TouchesHelper.CHANGED_TOUCHES_KEY; -import static com.facebook.react.uimanager.events.TouchesHelper.TARGET_KEY; -import static com.facebook.react.uimanager.events.TouchesHelper.TARGET_SURFACE_KEY; -import static com.facebook.react.uimanager.events.TouchesHelper.TOUCHES_KEY; - -import android.util.Pair; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.facebook.common.logging.FLog; -import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; -import com.facebook.react.bridge.WritableNativeArray; -import com.facebook.react.bridge.WritableNativeMap; import com.facebook.react.fabric.FabricUIManager; import com.facebook.react.uimanager.events.EventCategoryDef; import com.facebook.react.uimanager.events.RCTModernEventEmitter; -import com.facebook.react.uimanager.events.TouchEventType; +import com.facebook.react.uimanager.events.TouchEvent; +import com.facebook.react.uimanager.events.TouchesHelper; import com.facebook.systrace.Systrace; -import java.util.HashSet; -import java.util.Set; public class FabricEventEmitter implements RCTModernEventEmitter { @@ -67,145 +56,17 @@ public class FabricEventEmitter implements RCTModernEventEmitter { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } - /** - * Processes touches in a JS compatible way and send it to Fabric core - * - * @param eventName the event name (see {@link TouchEventType}) - * @param touches all the touch data extracted from MotionEvent - * @param changedIndices the indices of the pointers that changed (MOVE/CANCEL includes all - * touches, START/END only the one that was added/removed) - */ + /** Touches are dispatched by {@link #receiveTouches(TouchEvent)} */ @Override public void receiveTouches( @NonNull String eventName, @NonNull WritableArray touches, @NonNull WritableArray changedIndices) { - Systrace.beginSection( - Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, - "FabricEventEmitter.receiveTouches('" + eventName + "')"); - - boolean isFinalEvent = - TouchEventType.END.getJsName().equalsIgnoreCase(eventName) - || TouchEventType.CANCEL.getJsName().equalsIgnoreCase(eventName); - - Pair result = - isFinalEvent - ? removeTouchesAtIndices(touches, changedIndices) - : touchSubsequence(touches, changedIndices); - - WritableArray changedTouches = result.first; - touches = result.second; - - int eventCategory = getTouchCategory(eventName); - for (int jj = 0; jj < changedTouches.size(); jj++) { - WritableMap touch = getWritableMap(changedTouches.getMap(jj)); - // Touch objects can fulfill the role of `DOM` `Event` objects if we set - // the `changedTouches`/`touches`. This saves allocations. - - touch.putArray(CHANGED_TOUCHES_KEY, copyWritableArray(changedTouches)); - touch.putArray(TOUCHES_KEY, copyWritableArray(touches)); - WritableMap nativeEvent = touch; - int rootNodeID = 0; - int targetSurfaceId = nativeEvent.getInt(TARGET_SURFACE_KEY); - int targetReactTag = nativeEvent.getInt(TARGET_KEY); - if (targetReactTag < 1) { - FLog.e(TAG, "A view is reporting that a touch occurred on tag zero."); - } else { - rootNodeID = targetReactTag; - } - - receiveEvent(targetSurfaceId, rootNodeID, eventName, false, 0, touch, eventCategory); - } - - Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + throw new IllegalStateException("EventEmitter#receiveTouches is not supported by Fabric"); } - /** TODO T31905686 optimize this to avoid copying arrays */ - private WritableArray copyWritableArray(@NonNull WritableArray array) { - WritableNativeArray ret = new WritableNativeArray(); - for (int i = 0; i < array.size(); i++) { - ret.pushMap(getWritableMap(array.getMap(i))); - } - return ret; - } - - /** - * Destroys `touches` by removing touch objects at indices `indices`. This is to maintain - * compatibility with W3C touch "end" events, where the active touches don't include the set that - * has just been "ended". - * - *

This method was originally in ReactNativeRenderer.js - * - *

TODO: this method is a copy from ReactNativeRenderer.removeTouchesAtIndices and it needs to - * be rewritten in a more efficient way, - * - * @param touches {@link WritableArray} Deserialized touch objects. - * @param indices {WritableArray} Indices to remove from `touches`. - * @return {Array} Subsequence of removed touch objects. - */ - private @NonNull Pair removeTouchesAtIndices( - @NonNull WritableArray touches, @NonNull WritableArray indices) { - WritableArray rippedOut = new WritableNativeArray(); - // use an unsafe downcast to alias to nullable elements, - // so we can delete and then compact. - WritableArray tempTouches = new WritableNativeArray(); - Set rippedOutIndices = new HashSet<>(); - for (int i = 0; i < indices.size(); i++) { - int index = indices.getInt(i); - rippedOut.pushMap(getWritableMap(touches.getMap(index))); - rippedOutIndices.add(index); - } - for (int j = 0; j < touches.size(); j++) { - if (!rippedOutIndices.contains(j)) { - tempTouches.pushMap(getWritableMap(touches.getMap(j))); - } - } - - return new Pair<>(rippedOut, tempTouches); - } - - /** - * Selects a subsequence of `Touch`es, without destroying `touches`. - * - *

This method was originally in ReactNativeRenderer.js - * - * @param touches {@link WritableArray} Deserialized touch objects. - * @param changedIndices {@link WritableArray} Indices by which to pull subsequence. - * @return {Array} Subsequence of touch objects. - */ - private @NonNull Pair touchSubsequence( - @NonNull WritableArray touches, @NonNull WritableArray changedIndices) { - WritableArray result = new WritableNativeArray(); - for (int i = 0; i < changedIndices.size(); i++) { - result.pushMap(getWritableMap(touches.getMap(changedIndices.getInt(i)))); - } - return new Pair<>(result, touches); - } - - /** - * TODO: this is required because the WritableNativeArray.getMap() returns a ReadableMap instead - * of the original writableMap. this will change in the near future. - * - * @param readableMap {@link ReadableMap} source map - */ - private @NonNull WritableMap getWritableMap(@NonNull ReadableMap readableMap) { - WritableNativeMap map = new WritableNativeMap(); - map.merge(readableMap); - return map; - } - - @EventCategoryDef - private static int getTouchCategory(String touchEventType) { - int category = EventCategoryDef.UNSPECIFIED; - if (TouchEventType.MOVE.getJsName().equals(touchEventType)) { - category = EventCategoryDef.CONTINUOUS; - } else if (TouchEventType.START.getJsName().equals(touchEventType)) { - category = EventCategoryDef.CONTINUOUS_START; - } else if (TouchEventType.END.getJsName().equals(touchEventType) - || TouchEventType.CANCEL.getJsName().equals(touchEventType)) { - category = EventCategoryDef.CONTINUOUS_END; - } - - return category; + @Override + public void receiveTouches(TouchEvent event) { + TouchesHelper.sendTouchEvent(this, event); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.java index b205bdeb3c7..7a67e7a2a82 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.java @@ -159,7 +159,7 @@ public abstract class Event { /** * Dispatch this event to JS using the given event emitter. Compatible with old and new renderer. * Instead of using this or dispatchModern, it is recommended that you simply override - * `getEventData`. In the future + * `getEventData`. */ @Deprecated public void dispatch(RCTEventEmitter rctEventEmitter) { @@ -192,25 +192,14 @@ public abstract class Event { * non-null, this will use the RCTModernEventEmitter API. Otherwise, it falls back to the * old-style dispatch function. For Event classes that need to do something different, this method * can always be overridden entirely, but it is not recommended. - */ - public void dispatchModern(RCTModernEventEmitter rctEventEmitter) { - if (getSurfaceId() != -1) { - WritableMap eventData = getEventData(); - if (eventData != null) { - rctEventEmitter.receiveEvent(getSurfaceId(), getViewTag(), getEventName(), eventData); - return; - } - } - dispatch(rctEventEmitter); - } - - /** - * Dispatch this event to JS using a V2 version of dispatchModern. See all comments from - * `dispatchModern` - all still apply. This method additionally allows C++ to coalesce events - * (Fabric only). This will ONLY be called in an experimental path, and in Fabric only. + * + *

This method additionally allows C++ to coalesce events and detect continuous ones for + * concurrent mode (Fabric only). + * + * @see #dispatch */ @Deprecated - public void dispatchModernV2(RCTModernEventEmitter rctEventEmitter) { + public void dispatchModern(RCTModernEventEmitter rctEventEmitter) { if (getSurfaceId() != -1) { WritableMap eventData = getEventData(); if (eventData != null) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcherImpl.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcherImpl.java index c972fe03ad1..29039f8790d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcherImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcherImpl.java @@ -13,7 +13,6 @@ import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.common.MapBuilder; -import com.facebook.react.config.ReactFeatureFlags; import com.facebook.react.modules.core.ChoreographerCompat; import com.facebook.react.modules.core.ReactChoreographer; import com.facebook.react.uimanager.common.UIManagerType; @@ -368,11 +367,7 @@ public class EventDispatcherImpl implements EventDispatcher, LifecycleEventListe Systrace.endAsyncFlow( Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, event.getEventName(), event.getUniqueID()); - if (ReactFeatureFlags.useDispatchUniqueForCoalescableEvents) { - event.dispatchModernV2(mReactEventEmitter); - } else { - event.dispatchModern(mReactEventEmitter); - } + event.dispatchModern(mReactEventEmitter); event.dispose(); } clearEventsToDispatch(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/LockFreeEventDispatcherImpl.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/LockFreeEventDispatcherImpl.java index a16f43494a2..fff8349b3e7 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/LockFreeEventDispatcherImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/LockFreeEventDispatcherImpl.java @@ -84,7 +84,7 @@ public class LockFreeEventDispatcherImpl implements EventDispatcher, LifecycleEv listener.onEventDispatch(event); } - event.dispatchModernV2(mReactEventEmitter); + event.dispatchModern(mReactEventEmitter); event.dispose(); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTModernEventEmitter.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTModernEventEmitter.java index 4466e07b11c..66cccdcaaad 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTModernEventEmitter.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTModernEventEmitter.java @@ -30,4 +30,6 @@ public interface RCTModernEventEmitter extends RCTEventEmitter { int customCoalesceKey, @Nullable WritableMap event, @EventCategoryDef int category); + + void receiveTouches(TouchEvent event); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/ReactEventEmitter.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/ReactEventEmitter.java index 1ab5400106e..200f98e2b82 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/ReactEventEmitter.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/ReactEventEmitter.java @@ -70,14 +70,27 @@ public class ReactEventEmitter implements RCTModernEventEmitter { @Override public void receiveTouches( String eventName, WritableArray touches, WritableArray changedIndices) { + /* + * This method should be unused by default processing pipeline, but leaving it here to make sure + * that any custom code using it in legacy renderer is compatible + */ Assertions.assertCondition(touches.size() > 0); int reactTag = touches.getMap(0).getInt(TARGET_KEY); @UIManagerType int uiManagerType = ViewUtil.getUIManagerType(reactTag); - if (uiManagerType == UIManagerType.FABRIC && mFabricEventEmitter != null) { - mFabricEventEmitter.receiveTouches(eventName, touches, changedIndices); - } else if (uiManagerType == UIManagerType.DEFAULT && getEventEmitter(reactTag) != null) { + if (uiManagerType == UIManagerType.DEFAULT && getEventEmitter(reactTag) != null) { mRCTEventEmitter.receiveTouches(eventName, touches, changedIndices); + } + } + + @Override + public void receiveTouches(TouchEvent event) { + int reactTag = event.getViewTag(); + @UIManagerType int uiManagerType = ViewUtil.getUIManagerType(reactTag); + if (uiManagerType == UIManagerType.FABRIC && mFabricEventEmitter != null) { + mFabricEventEmitter.receiveTouches(event); + } else if (uiManagerType == UIManagerType.DEFAULT && getEventEmitter(reactTag) != null) { + TouchesHelper.sendTouchesLegacy(mRCTEventEmitter, event); } else { ReactSoftExceptionLogger.logSoftException( TAG, @@ -87,7 +100,7 @@ public class ReactEventEmitter implements RCTModernEventEmitter { + "] UIManagerType[" + uiManagerType + "] EventName[" - + eventName + + event.getEventName() + "]")); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchEvent.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchEvent.java index f9d6661684d..b00ac95dac1 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchEvent.java @@ -13,7 +13,6 @@ import androidx.core.util.Pools; import com.facebook.infer.annotation.Assertions; import com.facebook.react.bridge.ReactSoftExceptionLogger; import com.facebook.react.bridge.SoftAssertions; -import com.facebook.react.config.ReactFeatureFlags; /** * An event representing the start, end or movement of a touch. Corresponds to a single {@link @@ -183,29 +182,14 @@ public class TouchEvent extends Event { @Override public void dispatch(RCTEventEmitter rctEventEmitter) { if (verifyMotionEvent()) { - TouchesHelper.sendTouchEvent(rctEventEmitter, this); + TouchesHelper.sendTouchesLegacy(rctEventEmitter, this); } } @Override public void dispatchModern(RCTModernEventEmitter rctEventEmitter) { - if (ReactFeatureFlags.useUpdatedTouchPreprocessing) { - if (verifyMotionEvent()) { - TouchesHelper.sendTouchEventModern(rctEventEmitter, this, /* useDispatchV2 */ false); - } - } else { - dispatch(rctEventEmitter); - } - } - - @Override - public void dispatchModernV2(RCTModernEventEmitter rctEventEmitter) { - if (ReactFeatureFlags.useUpdatedTouchPreprocessing) { - if (verifyMotionEvent()) { - TouchesHelper.sendTouchEventModern(rctEventEmitter, this, /* useDispatchV2 */ true); - } - } else { - dispatch(rctEventEmitter); + if (verifyMotionEvent()) { + rctEventEmitter.receiveTouches(this); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.java index 42610200d5a..c7761261737 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.java @@ -13,6 +13,7 @@ import com.facebook.react.bridge.ReactSoftExceptionLogger; import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; import com.facebook.react.uimanager.PixelUtil; +import com.facebook.systrace.Systrace; /** Class responsible for generating catalyst touch events based on android {@link MotionEvent}. */ public class TouchesHelper { @@ -74,15 +75,16 @@ public class TouchesHelper { /** * Generate and send touch event to RCTEventEmitter JS module associated with the given {@param - * context}. Touch event can encode multiple concurrent touches (pointers). + * context} for legacy renderer. Touch event can encode multiple concurrent touches (pointers). * * @param rctEventEmitter Event emitter used to execute JS module call * @param touchEvent native touch event to read pointers count and coordinates from */ - public static void sendTouchEvent(RCTEventEmitter rctEventEmitter, TouchEvent touchEvent) { + public static void sendTouchesLegacy(RCTEventEmitter rctEventEmitter, TouchEvent touchEvent) { TouchEventType type = touchEvent.getTouchEventType(); - WritableArray pointers = getWritableArray(createPointersArray(touchEvent)); + WritableArray pointers = + getWritableArray(/* copyObjects */ false, createPointersArray(touchEvent)); MotionEvent motionEvent = touchEvent.getMotionEvent(); // For START and END events send only index of the pointer that is associated with that event @@ -111,10 +113,11 @@ public class TouchesHelper { * * @param eventEmitter emitter to dispatch event to * @param event the touch event to extract data from - * @param useDispatchV2 whether to dispatch additional data used by {@link Event#dispatchModernV2} */ - public static void sendTouchEventModern( - RCTModernEventEmitter eventEmitter, TouchEvent event, boolean useDispatchV2) { + public static void sendTouchEvent(RCTModernEventEmitter eventEmitter, TouchEvent event) { + Systrace.beginSection( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, + "TouchesHelper.sentTouchEventModern(" + event.getEventName() + ")"); TouchEventType type = event.getTouchEventType(); MotionEvent motionEvent = event.getMotionEvent(); @@ -166,24 +169,17 @@ public class TouchesHelper { eventData.putArray(CHANGED_TOUCHES_KEY, changedTouchesArray); eventData.putArray(TOUCHES_KEY, touchesArray); - if (useDispatchV2) { - eventEmitter.receiveEvent( - event.getSurfaceId(), - event.getViewTag(), - event.getEventName(), - event.canCoalesce(), - 0, - eventData, - event.getEventCategory()); - } else { - eventEmitter.receiveEvent( - event.getSurfaceId(), event.getViewTag(), event.getEventName(), eventData); - } + eventEmitter.receiveEvent( + event.getSurfaceId(), + event.getViewTag(), + event.getEventName(), + event.canCoalesce(), + 0, + eventData, + event.getEventCategory()); } - } - private static WritableArray getWritableArray(WritableMap... objects) { - return getWritableArray(false, objects); + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } private static WritableArray getWritableArray(boolean copyObjects, WritableMap... objects) {