From 2ac8dc1e78ddf72fe196aa6217a93d414ea36cad Mon Sep 17 00:00:00 2001 From: Thomas Nardone Date: Wed, 8 May 2024 14:32:24 -0700 Subject: [PATCH] Coordinate Synchronous Events with Choreographer (#44491) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/44491 Changelog: [internal] Ensure only one scroll event is fired per frame by tracking the events in FabricUIManager Reviewed By: sammy-SC Differential Revision: D57018741 fbshipit-source-id: c1ad59f934e359edfeb8f3e084106eebd467a0b1 --- .../facebook/react/fabric/FabricUIManager.java | 14 +++++++++++++- .../facebook/react/fabric/SynchronousEvent.kt | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/SynchronousEvent.kt diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java index d86ea31b67f..a6fd6c05875 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java @@ -89,9 +89,11 @@ import com.facebook.react.uimanager.events.SynchronousEventReceiver; import com.facebook.react.views.text.TextLayoutManager; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Queue; +import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; /** @@ -183,6 +185,9 @@ public class FabricUIManager @NonNull private final DispatchUIFrameCallback mDispatchUIFrameCallback; + /** Set of events sent synchronously during the current frame render. Cleared after each frame. */ + private final Set mSynchronousEvents = new HashSet<>(); + /** * This is used to keep track of whether or not the FabricUIManager has been destroyed. Once the * Catalyst instance is being destroyed, we should cease all operation here. @@ -995,7 +1000,12 @@ public class FabricUIManager } if (experimental_isSynchronous) { - eventEmitter.dispatchEventSynchronously(eventName, params); + // add() returns true only if there are no equivalent events already in the set + boolean firstEventForFrame = + mSynchronousEvents.add(new SynchronousEvent(surfaceId, reactTag, eventName)); + if (firstEventForFrame) { + eventEmitter.dispatchEventSynchronously(eventName, params); + } } else { if (canCoalesceEvent) { eventEmitter.dispatchUnique(eventName, params); @@ -1351,6 +1361,8 @@ public class FabricUIManager .postFrameCallback( ReactChoreographer.CallbackType.DISPATCH_UI, mDispatchUIFrameCallback); } + + mSynchronousEvents.clear(); } } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/SynchronousEvent.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/SynchronousEvent.kt new file mode 100644 index 00000000000..5d9cffcc332 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/SynchronousEvent.kt @@ -0,0 +1,18 @@ +/* + * 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.fabric + +/** + * Represents the identifying criteria of a synchronous event that was sent directly on the main + * thread. Used to determine if subsequent events are duplicates and should not be emitted. + */ +internal data class SynchronousEvent( + val surfaceId: Int, + val viewTag: Int, + val eventName: String, +)