Skip to content

Commit

Permalink
Fling][Android] Add synchronous event support (facebook#44489)
Browse files Browse the repository at this point in the history
Summary:

Changelog: [internal]
Integrate a synchronous event API to trigger synchronous scroll events in Android. The API will be changed in the future, this is exposed only for experimentation.

Reviewed By: sammy-SC

Differential Revision: D56886403
  • Loading branch information
Thomas Nardone authored and facebook-github-bot committed May 8, 2024
1 parent 1b5411e commit aee4bf3
Show file tree
Hide file tree
Showing 13 changed files with 183 additions and 20 deletions.
18 changes: 15 additions & 3 deletions packages/react-native/ReactAndroid/api/ReactAndroid.api
Original file line number Diff line number Diff line change
Expand Up @@ -2569,7 +2569,7 @@ public final class com/facebook/react/fabric/FabricSoLoader {
public static final fun staticInit ()V
}

public class com/facebook/react/fabric/FabricUIManager : com/facebook/react/bridge/LifecycleEventListener, com/facebook/react/bridge/UIManager, com/facebook/react/fabric/interop/UIBlockViewResolver {
public class com/facebook/react/fabric/FabricUIManager : com/facebook/react/bridge/LifecycleEventListener, com/facebook/react/bridge/UIManager, com/facebook/react/fabric/interop/UIBlockViewResolver, com/facebook/react/uimanager/events/SynchronousEventReceiver {
public static final field ENABLE_FABRIC_LOGS Z
public static final field ENABLE_FABRIC_PERF_LOGS Z
public static final field IS_DEVELOPMENT_ENVIRONMENT Z
Expand Down Expand Up @@ -2603,6 +2603,7 @@ public class com/facebook/react/fabric/FabricUIManager : com/facebook/react/brid
public fun profileNextBatch ()V
public fun receiveEvent (IILjava/lang/String;Lcom/facebook/react/bridge/WritableMap;)V
public fun receiveEvent (IILjava/lang/String;ZLcom/facebook/react/bridge/WritableMap;I)V
public fun receiveEvent (IILjava/lang/String;ZLcom/facebook/react/bridge/WritableMap;IZ)V
public fun receiveEvent (ILjava/lang/String;Lcom/facebook/react/bridge/WritableMap;)V
public fun removeUIManagerEventListener (Lcom/facebook/react/bridge/UIManagerListener;)V
public fun resolveCustomDirectEventName (Ljava/lang/String;)Ljava/lang/String;
Expand Down Expand Up @@ -2680,6 +2681,7 @@ public final class com/facebook/react/fabric/events/EventBeatManager : com/faceb
public class com/facebook/react/fabric/events/EventEmitterWrapper {
public fun destroy ()V
public fun dispatch (Ljava/lang/String;Lcom/facebook/react/bridge/WritableMap;I)V
public fun dispatchEventSynchronously (Ljava/lang/String;Lcom/facebook/react/bridge/WritableMap;)V
public fun dispatchUnique (Ljava/lang/String;Lcom/facebook/react/bridge/WritableMap;)V
}

Expand Down Expand Up @@ -5535,6 +5537,7 @@ public abstract class com/facebook/react/uimanager/events/Event {
public fun coalesce (Lcom/facebook/react/uimanager/events/Event;)Lcom/facebook/react/uimanager/events/Event;
public fun dispatch (Lcom/facebook/react/uimanager/events/RCTEventEmitter;)V
public fun dispatchModern (Lcom/facebook/react/uimanager/events/RCTModernEventEmitter;)V
protected fun experimental_isSynchronous ()Z
public fun getCoalescingKey ()S
public fun getEventAnimationDriverMatchSpec ()Lcom/facebook/react/uimanager/events/Event$EventAnimationDriverMatchSpec;
protected fun getEventCategory ()I
Expand Down Expand Up @@ -5709,6 +5712,10 @@ public abstract interface class com/facebook/react/uimanager/events/RCTModernEve
public abstract fun receiveTouches (Lcom/facebook/react/uimanager/events/TouchEvent;)V
}

public abstract interface class com/facebook/react/uimanager/events/SynchronousEventReceiver {
public abstract fun receiveEvent (IILjava/lang/String;ZLcom/facebook/react/bridge/WritableMap;IZ)V
}

public final class com/facebook/react/uimanager/events/TouchEvent : com/facebook/react/uimanager/events/Event {
public static final field Companion Lcom/facebook/react/uimanager/events/TouchEvent$Companion;
public static final field UNSET J
Expand Down Expand Up @@ -6653,6 +6660,7 @@ public class com/facebook/react/views/scroll/ReactHorizontalScrollView : android
public fun setBorderWidth (IF)V
public fun setDecelerationRate (F)V
public fun setDisableIntervalMomentum (Z)V
public fun setEnableSyncOnScroll (Z)V
public fun setEndFillColor (I)V
public fun setLastScrollDispatchTime (J)V
public fun setMaintainVisibleContentPosition (Lcom/facebook/react/views/scroll/MaintainVisibleScrollPositionHelper$Config;)V
Expand Down Expand Up @@ -6700,6 +6708,7 @@ public class com/facebook/react/views/scroll/ReactHorizontalScrollViewManager :
public fun setContentOffset (Lcom/facebook/react/views/scroll/ReactHorizontalScrollView;Lcom/facebook/react/bridge/ReadableMap;)V
public fun setDecelerationRate (Lcom/facebook/react/views/scroll/ReactHorizontalScrollView;F)V
public fun setDisableIntervalMomentum (Lcom/facebook/react/views/scroll/ReactHorizontalScrollView;Z)V
public fun setEnableSyncOnScroll (Lcom/facebook/react/views/scroll/ReactHorizontalScrollView;Z)V
public fun setFadingEdgeLength (Lcom/facebook/react/views/scroll/ReactHorizontalScrollView;I)V
public fun setHorizontal (Lcom/facebook/react/views/scroll/ReactHorizontalScrollView;Z)V
public fun setMaintainVisibleContentPosition (Lcom/facebook/react/views/scroll/ReactHorizontalScrollView;Lcom/facebook/react/bridge/ReadableMap;)V
Expand Down Expand Up @@ -6780,6 +6789,7 @@ public class com/facebook/react/views/scroll/ReactScrollView : android/widget/Sc
public fun setContentOffset (Lcom/facebook/react/bridge/ReadableMap;)V
public fun setDecelerationRate (F)V
public fun setDisableIntervalMomentum (Z)V
public fun setEnableSyncOnScroll (Z)V
public fun setEndFillColor (I)V
public fun setLastScrollDispatchTime (J)V
public fun setMaintainVisibleContentPosition (Lcom/facebook/react/views/scroll/MaintainVisibleScrollPositionHelper$Config;)V
Expand Down Expand Up @@ -6858,6 +6868,7 @@ public final class com/facebook/react/views/scroll/ReactScrollViewHelper {
public static final fun updateFabricScrollState (Landroid/view/ViewGroup;)V
public final fun updateFabricScrollState (Landroid/view/ViewGroup;II)V
public static final fun updateStateOnScrollChanged (Landroid/view/ViewGroup;FF)V
public static final fun updateStateOnScrollChanged (Landroid/view/ViewGroup;FFZ)V
}

public abstract interface class com/facebook/react/views/scroll/ReactScrollViewHelper$HasFlingAnimator {
Expand Down Expand Up @@ -6936,6 +6947,7 @@ public class com/facebook/react/views/scroll/ReactScrollViewManager : com/facebo
public fun setContentOffset (Lcom/facebook/react/views/scroll/ReactScrollView;Lcom/facebook/react/bridge/ReadableMap;)V
public fun setDecelerationRate (Lcom/facebook/react/views/scroll/ReactScrollView;F)V
public fun setDisableIntervalMomentum (Lcom/facebook/react/views/scroll/ReactScrollView;Z)V
public fun setEnableSyncOnScroll (Lcom/facebook/react/views/scroll/ReactScrollView;Z)V
public fun setFadingEdgeLength (Lcom/facebook/react/views/scroll/ReactScrollView;I)V
public fun setHorizontal (Lcom/facebook/react/views/scroll/ReactScrollView;Z)V
public fun setIsInvertedVirtualizedList (Lcom/facebook/react/views/scroll/ReactScrollView;Z)V
Expand Down Expand Up @@ -6972,13 +6984,13 @@ public final class com/facebook/react/views/scroll/ScrollEvent : com/facebook/re
public static final field Companion Lcom/facebook/react/views/scroll/ScrollEvent$Companion;
public fun canCoalesce ()Z
public fun getEventName ()Ljava/lang/String;
public static final fun obtain (IILcom/facebook/react/views/scroll/ScrollEventType;FFFFIIII)Lcom/facebook/react/views/scroll/ScrollEvent;
public static final fun obtain (IILcom/facebook/react/views/scroll/ScrollEventType;FFFFIIIIZ)Lcom/facebook/react/views/scroll/ScrollEvent;
public static final fun obtain (ILcom/facebook/react/views/scroll/ScrollEventType;FFFFIIII)Lcom/facebook/react/views/scroll/ScrollEvent;
public fun onDispose ()V
}

public final class com/facebook/react/views/scroll/ScrollEvent$Companion {
public final fun obtain (IILcom/facebook/react/views/scroll/ScrollEventType;FFFFIIII)Lcom/facebook/react/views/scroll/ScrollEvent;
public final fun obtain (IILcom/facebook/react/views/scroll/ScrollEventType;FFFFIIIIZ)Lcom/facebook/react/views/scroll/ScrollEvent;
public final fun obtain (ILcom/facebook/react/views/scroll/ScrollEventType;FFFFIIII)Lcom/facebook/react/views/scroll/ScrollEvent;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.react.uimanager.events.FabricEventDispatcher;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.facebook.react.uimanager.events.SynchronousEventReceiver;
import com.facebook.react.views.text.TextLayoutManager;
import java.util.ArrayList;
import java.util.HashMap;
Expand All @@ -99,7 +100,8 @@
*/
@SuppressLint("MissingNativeLoadLibrary")
@DoNotStripAny
public class FabricUIManager implements UIManager, LifecycleEventListener, UIBlockViewResolver {
public class FabricUIManager
implements UIManager, LifecycleEventListener, UIBlockViewResolver, SynchronousEventReceiver {
public static final String TAG = FabricUIManager.class.getSimpleName();

// The IS_DEVELOPMENT_ENVIRONMENT variable is used to log extra data when running fabric in a
Expand Down Expand Up @@ -955,6 +957,19 @@ public void receiveEvent(
boolean canCoalesceEvent,
@Nullable WritableMap params,
@EventCategoryDef int eventCategory) {
receiveEvent(surfaceId, reactTag, eventName, canCoalesceEvent, params, eventCategory, false);
}

@Override
public void receiveEvent(
int surfaceId,
int reactTag,
String eventName,
boolean canCoalesceEvent,
@Nullable WritableMap params,
@EventCategoryDef int eventCategory,
boolean experimental_isSynchronous) {

if (ReactBuildConfig.DEBUG && surfaceId == View.NO_ID) {
FLog.d(TAG, "Emitted event without surfaceId: [%d] %s", reactTag, eventName);
}
Expand All @@ -979,10 +994,14 @@ public void receiveEvent(
return;
}

if (canCoalesceEvent) {
eventEmitter.dispatchUnique(eventName, params);
if (experimental_isSynchronous) {
eventEmitter.dispatchEventSynchronously(eventName, params);
} else {
eventEmitter.dispatch(eventName, params, eventCategory);
if (canCoalesceEvent) {
eventEmitter.dispatchUnique(eventName, params);
} else {
eventEmitter.dispatch(eventName, params, eventCategory);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ public synchronized void dispatch(
dispatchEvent(eventName, (NativeMap) params, eventCategory);
}

public synchronized void dispatchEventSynchronously(
String eventName, @Nullable WritableMap params) {
if (!isValid()) {
return;
}
dispatchEventSynchronously(eventName, (NativeMap) params);
}

/**
* Invokes the execution of the C++ EventEmitter. C++ will coalesce events sent to the same
* target.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ protected int getEventCategory() {
return EventCategoryDef.UNSPECIFIED;
}

protected boolean experimental_isSynchronous() {
return false;
}

/**
* Dispatch this event to JS using a V2 EventEmitter. If surfaceId is not -1 and `getEventData` is
* non-null, this will use the RCTModernEventEmitter API. Otherwise, it falls back to the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactNoCrashSoftException;
import com.facebook.react.bridge.ReactSoftExceptionLogger;
import com.facebook.react.bridge.UIManager;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.modules.core.ReactChoreographer;
import com.facebook.react.uimanager.UIManagerHelper;
import com.facebook.react.uimanager.common.UIManagerType;
import com.facebook.systrace.Systrace;
import java.util.concurrent.CopyOnWriteArrayList;
Expand Down Expand Up @@ -43,12 +47,43 @@ public void dispatchEvent(Event event) {
for (EventDispatcherListener listener : mListeners) {
listener.onEventDispatch(event);
}
event.dispatchModern(mReactEventEmitter);
if (event.experimental_isSynchronous()) {
dispatchSynchronous(event);
} else {
event.dispatchModern(mReactEventEmitter);
}

event.dispose();
maybePostFrameCallbackFromNonUI();
}

private void dispatchSynchronous(Event event) {
Systrace.beginSection(
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
"FabricEventDispatcher.dispatchSynchronous('" + event.getEventName() + "')");
try {
UIManager fabricUIManager = UIManagerHelper.getUIManager(mReactContext, UIManagerType.FABRIC);
if (fabricUIManager instanceof SynchronousEventReceiver) {
((SynchronousEventReceiver) fabricUIManager)
.receiveEvent(
event.getSurfaceId(),
event.getViewTag(),
event.getEventName(),
event.canCoalesce(),
event.getEventData(),
event.getEventCategory(),
true);
} else {
ReactSoftExceptionLogger.logSoftException(
"FabricEventDispatcher",
new ReactNoCrashSoftException(
"Fabric UIManager expected to implement SynchronousEventReceiver."));
}
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}

public void dispatchAllEvents() {
maybePostFrameCallbackFromNonUI();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.react.uimanager.events

import com.facebook.react.bridge.WritableMap

@Deprecated("Experimental")
public interface SynchronousEventReceiver {
public fun receiveEvent(
surfaceId: Int,
reactTag: Int,
eventName: String,
canCoalesceEvent: Boolean,
params: WritableMap?,
@EventCategoryDef eventCategory: Int,
experimental_isSynchronous: Boolean
): Unit
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
private @Nullable Runnable mPostTouchRunnable;
private boolean mRemoveClippedSubviews;
private boolean mScrollEnabled = true;
private boolean mPreventReentry = false;
private boolean mEnableSyncOnScroll = false;
private boolean mSendMomentumEvents;
private @Nullable FpsListener mFpsListener = null;
private @Nullable String mScrollPerfTag;
Expand Down Expand Up @@ -220,6 +222,10 @@ public void setScrollEnabled(boolean scrollEnabled) {
mScrollEnabled = scrollEnabled;
}

public void setEnableSyncOnScroll(boolean enableSyncOnScroll) {
mEnableSyncOnScroll = enableSyncOnScroll;
}

public void setPagingEnabled(boolean pagingEnabled) {
mPagingEnabled = pagingEnabled;
}
Expand Down Expand Up @@ -470,11 +476,16 @@ protected void onScrollChanged(int x, int y, int oldX, int oldY) {
if (mRemoveClippedSubviews) {
updateClippingRect();
}

if (mPreventReentry) {
return;
}
mPreventReentry = true;
ReactScrollViewHelper.updateStateOnScrollChanged(
this,
mOnScrollDispatchHelper.getXFlingVelocity(),
mOnScrollDispatchHelper.getYFlingVelocity());
mOnScrollDispatchHelper.getYFlingVelocity(),
mEnableSyncOnScroll);
mPreventReentry = false;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ public void setDecelerationRate(ReactHorizontalScrollView view, float decelerati
view.setDecelerationRate(decelerationRate);
}

@ReactProp(name = "enableSyncOnScroll")
public void setEnableSyncOnScroll(ReactHorizontalScrollView view, boolean enableSyncOnScroll) {
view.setEnableSyncOnScroll(enableSyncOnScroll);
}

@ReactProp(name = "disableIntervalMomentum")
public void setDisableIntervalMomentum(
ReactHorizontalScrollView view, boolean disableIntervalMomentum) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,11 @@ public class ReactScrollView extends ScrollView
private @Nullable Runnable mPostTouchRunnable;
private boolean mRemoveClippedSubviews;
private boolean mScrollEnabled = true;
private boolean mPreventReentry = false;
private boolean mSendMomentumEvents;
private @Nullable FpsListener mFpsListener = null;
private @Nullable String mScrollPerfTag;
private boolean mEnableSyncOnScroll = false;
private @Nullable Drawable mEndBackground;
private int mEndFillColor = Color.TRANSPARENT;
private boolean mDisableIntervalMomentum = false;
Expand Down Expand Up @@ -201,6 +203,10 @@ public void setScrollPerfTag(@Nullable String scrollPerfTag) {
mScrollPerfTag = scrollPerfTag;
}

public void setEnableSyncOnScroll(boolean enableSyncOnScroll) {
mEnableSyncOnScroll = enableSyncOnScroll;
}

public void setScrollEnabled(boolean scrollEnabled) {
mScrollEnabled = scrollEnabled;
}
Expand Down Expand Up @@ -388,11 +394,16 @@ protected void onScrollChanged(int x, int y, int oldX, int oldY) {
if (mRemoveClippedSubviews) {
updateClippingRect();
}

if (mPreventReentry) {
return;
}
mPreventReentry = true;
ReactScrollViewHelper.updateStateOnScrollChanged(
this,
mOnScrollDispatchHelper.getXFlingVelocity(),
mOnScrollDispatchHelper.getYFlingVelocity());
mOnScrollDispatchHelper.getYFlingVelocity(),
mEnableSyncOnScroll);
mPreventReentry = false;
}
}

Expand Down
Loading

0 comments on commit aee4bf3

Please sign in to comment.