Skip to content

Commit

Permalink
Refactor EventEmitters to take optional surfaceId, migrate TouchEvent
Browse files Browse the repository at this point in the history
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
  • Loading branch information
JoshuaGross authored and facebook-github-bot committed Jan 23, 2021
1 parent 2fbbdbb commit 708038d
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

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;
Expand Down Expand Up @@ -76,14 +77,15 @@ public void receiveTouches(
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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,17 @@ public void onChildStartedNativeGesture(
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
Expand All @@ -70,9 +81,11 @@ public void handleTouchEvent(MotionEvent ev, EventDispatcher eventDispatcher) {
// this gesture
mChildIsHandlingNativeGesture = false;
mGestureStartTime = ev.getEventTime();

mTargetTag = findTargetTagAndSetCoordinates(ev);
eventDispatcher.dispatchEvent(
TouchEvent.obtain(
getSurfaceId(),
mTargetTag,
TouchEventType.START,
ev,
Expand All @@ -97,6 +110,7 @@ public void handleTouchEvent(MotionEvent ev, EventDispatcher eventDispatcher) {
findTargetTagAndSetCoordinates(ev);
eventDispatcher.dispatchEvent(
TouchEvent.obtain(
getSurfaceId(),
mTargetTag,
TouchEventType.END,
ev,
Expand All @@ -111,6 +125,7 @@ public void handleTouchEvent(MotionEvent ev, EventDispatcher eventDispatcher) {
findTargetTagAndSetCoordinates(ev);
eventDispatcher.dispatchEvent(
TouchEvent.obtain(
getSurfaceId(),
mTargetTag,
TouchEventType.MOVE,
ev,
Expand All @@ -122,6 +137,7 @@ public void handleTouchEvent(MotionEvent ev, EventDispatcher eventDispatcher) {
// 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,
Expand All @@ -133,6 +149,7 @@ public void handleTouchEvent(MotionEvent ev, EventDispatcher eventDispatcher) {
// Exactly onw of the pointers goes up
eventDispatcher.dispatchEvent(
TouchEvent.obtain(
getSurfaceId(),
mTargetTag,
TouchEventType.END,
ev,
Expand Down Expand Up @@ -181,6 +198,7 @@ private void dispatchCancelEvent(MotionEvent androidEvent, EventDispatcher event
Assertions.assertNotNull(eventDispatcher)
.dispatchEvent(
TouchEvent.obtain(
getSurfaceId(),
mTargetTag,
TouchEventType.CANCEL,
androidEvent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ public void removeBatchEventDispatchedListener(BatchEventDispatchedListener list
@Override
public void registerEventEmitter(int uiManagerType, RCTEventEmitter eventEmitter) {}

@Override
public void registerEventEmitter(int uiManagerType, RCTModernEventEmitter eventEmitter) {}

@Override
public void unregisterEventEmitter(int uiManagerType) {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,11 @@ public void registerEventEmitter(@UIManagerType int uiManagerType, RCTEventEmitt
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);
}
Expand Down Expand Up @@ -361,7 +366,7 @@ public void run() {
}
Systrace.endAsyncFlow(
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, event.getEventName(), event.getUniqueID());
event.dispatch(mReactEventEmitter);
event.dispatchV2(mReactEventEmitter);
event.dispose();
}
clearEventsToDispatch();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@

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;
Expand All @@ -21,58 +19,78 @@
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<RCTEventEmitter> 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;

public ReactEventEmitter(ReactApplicationContext reactContext) {
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,
Expand All @@ -84,6 +102,30 @@ private RCTEventEmitter getEventEmitter(int reactTag) {
+ " - 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
+ "]"));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public class TouchEvent extends Event<TouchEvent> {
public static final long UNSET = Long.MIN_VALUE;

public static TouchEvent obtain(
int surfaceId,
int viewTag,
TouchEventType touchEventType,
MotionEvent motionEventToCopy,
Expand All @@ -43,6 +44,7 @@ public static TouchEvent obtain(
event = new TouchEvent();
}
event.init(
surfaceId,
viewTag,
touchEventType,
motionEventToCopy,
Expand All @@ -64,14 +66,15 @@ public static TouchEvent obtain(
private TouchEvent() {}

private void init(
int surfaceId,
int viewTag,
TouchEventType touchEventType,
MotionEvent motionEventToCopy,
long gestureStartTime,
float viewX,
float viewY,
TouchEventCoalescingKeyHelper touchEventCoalescingKeyHelper) {
super.init(viewTag);
super.init(surfaceId, viewTag);

SoftAssertions.assertCondition(
gestureStartTime != UNSET, "Gesture start time must be initialized");
Expand Down Expand Up @@ -141,7 +144,11 @@ public short getCoalescingKey() {
@Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
TouchesHelper.sendTouchEvent(
rctEventEmitter, Assertions.assertNotNull(mTouchEventType), getViewTag(), this);
rctEventEmitter,
Assertions.assertNotNull(mTouchEventType),
getSurfaceId(),
getViewTag(),
this);
}

public MotionEvent getMotionEvent() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
/** 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";
Expand All @@ -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();

Expand All @@ -60,6 +62,7 @@ private static WritableArray createsPointersArray(int reactTarget, TouchEvent ev
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));
Expand All @@ -81,10 +84,10 @@ private static WritableArray createsPointersArray(int reactTarget, TouchEvent ev
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
Expand Down

0 comments on commit 708038d

Please sign in to comment.