From 708038d80ed5793ae97bc49802bffef23df72bc3 Mon Sep 17 00:00:00 2001 From: Joshua Gross Date: Fri, 22 Jan 2021 19:29:00 -0800 Subject: [PATCH] Refactor EventEmitters to take optional surfaceId, migrate TouchEvent Summary: Refactor EventEmitters to take an optional SurfaceId that Fabric will use, and non-Fabric will not. Migrating touches is a good proof-of-concept for how this could be used generally, and as it turns out, TouchEvent's API is more flexible than most other event APIs (because it uses a dictionary to pass data around, so we can just stuff SurfaceId into it - not efficient, but flexible). All new APIs are backwards-compatible and designed to work with old-style events, with Fabric and non-Fabric. Native Views that migrate to the new API will be backwards-compatible and get an efficiency boost in Fabric. Changelog: [Internal] Reviewed By: mdvacca Differential Revision: D26025135 fbshipit-source-id: 5b418951e9d0a3882f2d67398f2aaadac8a3a556 --- .../fabric/events/FabricEventEmitter.java | 10 ++- .../react/uimanager/JSTouchDispatcher.java | 18 ++++ .../events/BlackHoleEventDispatcher.java | 3 + .../uimanager/events/EventDispatcher.java | 3 + .../uimanager/events/EventDispatcherImpl.java | 7 +- .../uimanager/events/RCTEventEmitter.java | 1 + .../uimanager/events/ReactEventEmitter.java | 86 ++++++++++++++----- .../react/uimanager/events/TouchEvent.java | 11 ++- .../react/uimanager/events/TouchesHelper.java | 9 +- 9 files changed, 116 insertions(+), 32 deletions(-) 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 6d0654db38a..f0856af612e 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 @@ -9,6 +9,7 @@ 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.TOP_TOUCH_CANCEL_KEY; import static com.facebook.react.uimanager.events.TouchesHelper.TOP_TOUCH_END_KEY; import static com.facebook.react.uimanager.events.TouchesHelper.TOUCHES_KEY; @@ -76,14 +77,15 @@ public class FabricEventEmitter implements RCTModernEventEmitter { touch.putArray(TOUCHES_KEY, copyWritableArray(touches)); WritableMap nativeEvent = touch; int rootNodeID = 0; - int target = nativeEvent.getInt(TARGET_KEY); - if (target < 1) { + 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 = target; + rootNodeID = targetReactTag; } - receiveEvent(rootNodeID, eventTopLevelType, touch); + receiveEvent(targetSurfaceId, rootNodeID, eventTopLevelType, touch); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/JSTouchDispatcher.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/JSTouchDispatcher.java index e103cd3923c..db0224d0871 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/JSTouchDispatcher.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/JSTouchDispatcher.java @@ -50,6 +50,17 @@ public class JSTouchDispatcher { mTargetTag = -1; } + private int getSurfaceId() { + if (mRootViewGroup instanceof ReactRoot) { + return ((ReactRoot) mRootViewGroup).getRootViewTag(); + } + if (mRootViewGroup != null && mRootViewGroup.getContext() instanceof ThemedReactContext) { + ThemedReactContext context = (ThemedReactContext) mRootViewGroup.getContext(); + return context.getSurfaceId(); + } + return -1; + } + /** * Main catalyst view is responsible for collecting and sending touch events to JS. This method * reacts for an incoming android native touch events ({@link MotionEvent}) and calls into {@link @@ -70,9 +81,11 @@ public class JSTouchDispatcher { // this gesture mChildIsHandlingNativeGesture = false; mGestureStartTime = ev.getEventTime(); + mTargetTag = findTargetTagAndSetCoordinates(ev); eventDispatcher.dispatchEvent( TouchEvent.obtain( + getSurfaceId(), mTargetTag, TouchEventType.START, ev, @@ -97,6 +110,7 @@ public class JSTouchDispatcher { findTargetTagAndSetCoordinates(ev); eventDispatcher.dispatchEvent( TouchEvent.obtain( + getSurfaceId(), mTargetTag, TouchEventType.END, ev, @@ -111,6 +125,7 @@ public class JSTouchDispatcher { findTargetTagAndSetCoordinates(ev); eventDispatcher.dispatchEvent( TouchEvent.obtain( + getSurfaceId(), mTargetTag, TouchEventType.MOVE, ev, @@ -122,6 +137,7 @@ public class JSTouchDispatcher { // New pointer goes down, this can only happen after ACTION_DOWN is sent for the first pointer eventDispatcher.dispatchEvent( TouchEvent.obtain( + getSurfaceId(), mTargetTag, TouchEventType.START, ev, @@ -133,6 +149,7 @@ public class JSTouchDispatcher { // Exactly onw of the pointers goes up eventDispatcher.dispatchEvent( TouchEvent.obtain( + getSurfaceId(), mTargetTag, TouchEventType.END, ev, @@ -181,6 +198,7 @@ public class JSTouchDispatcher { Assertions.assertNotNull(eventDispatcher) .dispatchEvent( TouchEvent.obtain( + getSurfaceId(), mTargetTag, TouchEventType.CANCEL, androidEvent, diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/BlackHoleEventDispatcher.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/BlackHoleEventDispatcher.java index 1fce7974087..15abfde7a04 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/BlackHoleEventDispatcher.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/BlackHoleEventDispatcher.java @@ -49,6 +49,9 @@ public class BlackHoleEventDispatcher implements EventDispatcher { @Override public void registerEventEmitter(int uiManagerType, RCTEventEmitter eventEmitter) {} + @Override + public void registerEventEmitter(int uiManagerType, RCTModernEventEmitter eventEmitter) {} + @Override public void unregisterEventEmitter(int uiManagerType) {} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.java index 2ba18340977..2e9ac90cc2c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.java @@ -26,8 +26,11 @@ public interface EventDispatcher { void removeBatchEventDispatchedListener(BatchEventDispatchedListener listener); + @Deprecated void registerEventEmitter(@UIManagerType int uiManagerType, RCTEventEmitter eventEmitter); + void registerEventEmitter(@UIManagerType int uiManagerType, RCTModernEventEmitter eventEmitter); + void unregisterEventEmitter(@UIManagerType int uiManagerType); void onCatalystInstanceDestroyed(); 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 6f1fd9730b2..29816986a7f 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 @@ -263,6 +263,11 @@ public class EventDispatcherImpl implements EventDispatcher, LifecycleEventListe mReactEventEmitter.register(uiManagerType, eventEmitter); } + public void registerEventEmitter( + @UIManagerType int uiManagerType, RCTModernEventEmitter eventEmitter) { + mReactEventEmitter.register(uiManagerType, eventEmitter); + } + public void unregisterEventEmitter(@UIManagerType int uiManagerType) { mReactEventEmitter.unregister(uiManagerType); } @@ -361,7 +366,7 @@ public class EventDispatcherImpl implements EventDispatcher, LifecycleEventListe } Systrace.endAsyncFlow( Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, event.getEventName(), event.getUniqueID()); - event.dispatch(mReactEventEmitter); + event.dispatchV2(mReactEventEmitter); event.dispose(); } clearEventsToDispatch(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTEventEmitter.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTEventEmitter.java index fc2ba315806..7661d5c57fe 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTEventEmitter.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTEventEmitter.java @@ -13,6 +13,7 @@ import com.facebook.react.bridge.JavaScriptModule; import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; +/** Deprecated in favor of RCTModernEventEmitter, which extends this interface. */ @DoNotStrip @Deprecated public interface RCTEventEmitter extends JavaScriptModule { 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 28931ad83cc..49b05347a20 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 @@ -9,9 +9,7 @@ package com.facebook.react.uimanager.events; import static com.facebook.react.uimanager.events.TouchesHelper.TARGET_KEY; -import android.util.SparseArray; import androidx.annotation.Nullable; -import com.facebook.common.logging.FLog; import com.facebook.infer.annotation.Assertions; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactNoCrashSoftException; @@ -21,11 +19,16 @@ import com.facebook.react.bridge.WritableMap; import com.facebook.react.uimanager.common.UIManagerType; import com.facebook.react.uimanager.common.ViewUtil; -public class ReactEventEmitter implements RCTEventEmitter { +public class ReactEventEmitter implements RCTModernEventEmitter { private static final String TAG = "ReactEventEmitter"; - private final SparseArray mEventEmitters = new SparseArray<>(); + @Nullable + private RCTModernEventEmitter mFabricEventEmitter = + null; /* Corresponds to a Fabric EventEmitter */ + + @Nullable + private RCTEventEmitter mRCTEventEmitter = null; /* Corresponds to a Non-Fabric EventEmitter */ private final ReactApplicationContext mReactContext; @@ -33,46 +36,61 @@ public class ReactEventEmitter implements RCTEventEmitter { mReactContext = reactContext; } + public void register(@UIManagerType int uiManagerType, RCTModernEventEmitter eventEmitter) { + assert uiManagerType == UIManagerType.FABRIC; + mFabricEventEmitter = eventEmitter; + } + public void register(@UIManagerType int uiManagerType, RCTEventEmitter eventEmitter) { - mEventEmitters.put(uiManagerType, eventEmitter); + assert uiManagerType == UIManagerType.DEFAULT; + mRCTEventEmitter = eventEmitter; } public void unregister(@UIManagerType int uiManagerType) { - mEventEmitters.remove(uiManagerType); + if (uiManagerType == UIManagerType.DEFAULT) { + mRCTEventEmitter = null; + } else { + mFabricEventEmitter = null; + } } @Override public void receiveEvent(int targetReactTag, String eventName, @Nullable WritableMap event) { - RCTEventEmitter eventEmitter = getEventEmitter(targetReactTag); - if (eventEmitter != null) { - eventEmitter.receiveEvent(targetReactTag, eventName, event); - } + receiveEvent(-1, targetReactTag, eventName, event); } @Override public void receiveTouches( String eventName, WritableArray touches, WritableArray changedIndices) { - Assertions.assertCondition(touches.size() > 0); int reactTag = touches.getMap(0).getInt(TARGET_KEY); - RCTEventEmitter eventEmitter = getEventEmitter(reactTag); - if (eventEmitter != null) { - eventEmitter.receiveTouches(eventName, touches, changedIndices); + @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) { + mRCTEventEmitter.receiveTouches(eventName, touches, changedIndices); + } else { + ReactSoftException.logSoftException( + TAG, + new ReactNoCrashSoftException( + "Cannot find EventEmitter for receivedTouches: ReactTag[" + + reactTag + + "] UIManagerType[" + + uiManagerType + + "] EventName[" + + eventName + + "]")); } } @Nullable private RCTEventEmitter getEventEmitter(int reactTag) { int type = ViewUtil.getUIManagerType(reactTag); - RCTEventEmitter eventEmitter = mEventEmitters.get(type); - if (eventEmitter == null) { - // TODO T54145494: Refactor RN Event Emitter system to make sure reactTags are always managed - // by RN - FLog.e( - TAG, "Unable to find event emitter for reactTag: %d - uiManagerType: %d", reactTag, type); + assert type == UIManagerType.DEFAULT; + if (mRCTEventEmitter == null) { if (mReactContext.hasActiveCatalystInstance()) { - eventEmitter = mReactContext.getJSModule(RCTEventEmitter.class); + mRCTEventEmitter = mReactContext.getJSModule(RCTEventEmitter.class); } else { ReactSoftException.logSoftException( TAG, @@ -84,6 +102,30 @@ public class ReactEventEmitter implements RCTEventEmitter { + " - No active Catalyst instance!")); } } - return eventEmitter; + return mRCTEventEmitter; + } + + @Override + public void receiveEvent( + int surfaceId, int targetReactTag, String eventName, @Nullable WritableMap event) { + @UIManagerType int uiManagerType = ViewUtil.getUIManagerType(targetReactTag); + if (uiManagerType == UIManagerType.FABRIC && mFabricEventEmitter != null) { + mFabricEventEmitter.receiveEvent(surfaceId, targetReactTag, eventName, event); + } else if (uiManagerType == UIManagerType.DEFAULT && getEventEmitter(targetReactTag) != null) { + mRCTEventEmitter.receiveEvent(targetReactTag, eventName, event); + } else { + ReactSoftException.logSoftException( + TAG, + new ReactNoCrashSoftException( + "Cannot find EventEmitter for receiveEvent: SurfaceId[" + + surfaceId + + "] ReactTag[" + + targetReactTag + + "] UIManagerType[" + + uiManagerType + + "] EventName[" + + eventName + + "]")); + } } } 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 1445735f6f5..e6a58104638 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 @@ -31,6 +31,7 @@ public class TouchEvent extends Event { public static final long UNSET = Long.MIN_VALUE; public static TouchEvent obtain( + int surfaceId, int viewTag, TouchEventType touchEventType, MotionEvent motionEventToCopy, @@ -43,6 +44,7 @@ public class TouchEvent extends Event { event = new TouchEvent(); } event.init( + surfaceId, viewTag, touchEventType, motionEventToCopy, @@ -64,6 +66,7 @@ public class TouchEvent extends Event { private TouchEvent() {} private void init( + int surfaceId, int viewTag, TouchEventType touchEventType, MotionEvent motionEventToCopy, @@ -71,7 +74,7 @@ public class TouchEvent extends Event { float viewX, float viewY, TouchEventCoalescingKeyHelper touchEventCoalescingKeyHelper) { - super.init(viewTag); + super.init(surfaceId, viewTag); SoftAssertions.assertCondition( gestureStartTime != UNSET, "Gesture start time must be initialized"); @@ -141,7 +144,11 @@ public class TouchEvent extends Event { @Override public void dispatch(RCTEventEmitter rctEventEmitter) { TouchesHelper.sendTouchEvent( - rctEventEmitter, Assertions.assertNotNull(mTouchEventType), getViewTag(), this); + rctEventEmitter, + Assertions.assertNotNull(mTouchEventType), + getSurfaceId(), + getViewTag(), + this); } public MotionEvent getMotionEvent() { 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 0c222ad9502..187e6121160 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 @@ -16,6 +16,7 @@ import com.facebook.react.uimanager.PixelUtil; /** Class responsible for generating catalyst touch events based on android {@link MotionEvent}. */ public class TouchesHelper { + public static final String TARGET_SURFACE_KEY = "targetSurface"; public static final String TARGET_KEY = "target"; public static final String CHANGED_TOUCHES_KEY = "changedTouches"; public static final String TOUCHES_KEY = "touches"; @@ -34,7 +35,8 @@ public class TouchesHelper { * given {@param event} instance. This method use {@param reactTarget} parameter to set as a * target view id associated with current gesture. */ - private static WritableArray createsPointersArray(int reactTarget, TouchEvent event) { + private static WritableArray createsPointersArray( + int surfaceId, int reactTarget, TouchEvent event) { WritableArray touches = Arguments.createArray(); MotionEvent motionEvent = event.getMotionEvent(); @@ -60,6 +62,7 @@ public class TouchesHelper { float locationY = motionEvent.getY(index) - targetViewCoordinateY; touch.putDouble(LOCATION_X_KEY, PixelUtil.toDIPFromPixel(locationX)); touch.putDouble(LOCATION_Y_KEY, PixelUtil.toDIPFromPixel(locationY)); + touch.putInt(TARGET_SURFACE_KEY, surfaceId); touch.putInt(TARGET_KEY, reactTarget); touch.putDouble(TIMESTAMP_KEY, event.getTimestampMs()); touch.putDouble(POINTER_IDENTIFIER_KEY, motionEvent.getPointerId(index)); @@ -81,10 +84,10 @@ public class TouchesHelper { public static void sendTouchEvent( RCTEventEmitter rctEventEmitter, TouchEventType type, + int surfaceId, int reactTarget, TouchEvent touchEvent) { - - WritableArray pointers = createsPointersArray(reactTarget, touchEvent); + WritableArray pointers = createsPointersArray(surfaceId, reactTarget, touchEvent); MotionEvent motionEvent = touchEvent.getMotionEvent(); // For START and END events send only index of the pointer that is associated with that event