mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Convert EventDispatcherImpl to Kotlin (#52150)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/52150 This is another class moving from Java to Kotlin. Changelog: [Internal] [Changed] - Reviewed By: javache Differential Revision: D77021952 fbshipit-source-id: 0b04a10bcbe65b7dc14ddd2821f80d90a43f8610
This commit is contained in:
committed by
Facebook GitHub Bot
parent
ff7e24f19a
commit
1ced97fbe6
@@ -4866,7 +4866,8 @@ public abstract interface class com/facebook/react/uimanager/events/EventDispatc
|
||||
public abstract fun removeListener (Lcom/facebook/react/uimanager/events/EventDispatcherListener;)V
|
||||
}
|
||||
|
||||
public class com/facebook/react/uimanager/events/EventDispatcherImpl : com/facebook/react/bridge/LifecycleEventListener, com/facebook/react/uimanager/events/EventDispatcher {
|
||||
public final class com/facebook/react/uimanager/events/EventDispatcherImpl : com/facebook/react/bridge/LifecycleEventListener, com/facebook/react/uimanager/events/EventDispatcher {
|
||||
public static final field Companion Lcom/facebook/react/uimanager/events/EventDispatcherImpl$Companion;
|
||||
public fun <init> (Lcom/facebook/react/bridge/ReactApplicationContext;)V
|
||||
public fun addBatchEventDispatchedListener (Lcom/facebook/react/uimanager/events/BatchEventDispatchedListener;)V
|
||||
public fun addListener (Lcom/facebook/react/uimanager/events/EventDispatcherListener;)V
|
||||
@@ -4880,6 +4881,9 @@ public class com/facebook/react/uimanager/events/EventDispatcherImpl : com/faceb
|
||||
public fun removeListener (Lcom/facebook/react/uimanager/events/EventDispatcherListener;)V
|
||||
}
|
||||
|
||||
public final class com/facebook/react/uimanager/events/EventDispatcherImpl$Companion {
|
||||
}
|
||||
|
||||
public abstract interface class com/facebook/react/uimanager/events/EventDispatcherListener {
|
||||
public abstract fun onEventDispatch (Lcom/facebook/react/uimanager/events/Event;)V
|
||||
}
|
||||
|
||||
+1
-1
@@ -95,7 +95,7 @@ public abstract class Event<T : Event<T>> {
|
||||
* Two events will only ever try to be coalesced if they have the same event name, view id, and
|
||||
* coalescing key.
|
||||
*/
|
||||
public open fun coalesce(otherEvent: Event<T>?): Event<T>? =
|
||||
public open fun coalesce(otherEvent: Event<*>?): Event<*>? =
|
||||
if (timestampMs >= otherEvent?.timestampMs ?: 0) this else otherEvent
|
||||
|
||||
/**
|
||||
|
||||
-372
@@ -1,372 +0,0 @@
|
||||
/*
|
||||
* 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 android.util.LongSparseArray;
|
||||
import android.view.Choreographer;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.infer.annotation.Nullsafe;
|
||||
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.common.annotations.internal.InteropLegacyArchitecture;
|
||||
import com.facebook.react.modules.core.ReactChoreographer;
|
||||
import com.facebook.systrace.Systrace;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Class responsible for dispatching UI events to JS. The main purpose of this class is to act as an
|
||||
* intermediary between UI code generating events and JS, making sure we don't send more events than
|
||||
* JS can process.
|
||||
*
|
||||
* <p>To use it, create a subclass of {@link Event} and call {@link #dispatchEvent(Event)} whenever
|
||||
* there's a UI event to dispatch.
|
||||
*
|
||||
* <p>This class works by installing a Choreographer frame callback on the main thread. This
|
||||
* callback then enqueues a runnable on the JS thread (if one is not already pending) that is
|
||||
* responsible for actually dispatch events to JS. This implementation depends on the properties
|
||||
* that 1) FrameCallbacks run after UI events have been processed in Choreographer.java 2) when we
|
||||
* enqueue a runnable on the JS queue thread, it won't be called until after any previously enqueued
|
||||
* JS jobs have finished processing
|
||||
*
|
||||
* <p>If JS is taking a long time processing events, then the UI events generated on the UI thread
|
||||
* can be coalesced into fewer events so that when the runnable runs, we don't overload JS with a
|
||||
* ton of events and make it get even farther behind.
|
||||
*
|
||||
* <p>Ideally, we don't need this and JS is fast enough to process all the events each frame, but
|
||||
* bad things happen, including load on CPUs from the system, and we should handle this case well.
|
||||
*
|
||||
* <p>== Event Cookies ==
|
||||
*
|
||||
* <p>An event cookie is made up of the event type id, view tag, and a custom coalescing key. Only
|
||||
* Events that have the same cookie can be coalesced.
|
||||
*
|
||||
* <p>Event Cookie Composition: VIEW_TAG_MASK = 0x00000000ffffffff EVENT_TYPE_ID_MASK =
|
||||
* 0x0000ffff00000000 COALESCING_KEY_MASK = 0xffff000000000000
|
||||
*/
|
||||
@Nullsafe(Nullsafe.Mode.LOCAL)
|
||||
@InteropLegacyArchitecture
|
||||
public class EventDispatcherImpl implements EventDispatcher, LifecycleEventListener {
|
||||
|
||||
private static final Comparator<Event> EVENT_COMPARATOR =
|
||||
new Comparator<Event>() {
|
||||
@Override
|
||||
public int compare(Event lhs, Event rhs) {
|
||||
if (lhs == null && rhs == null) {
|
||||
return 0;
|
||||
}
|
||||
if (lhs == null) {
|
||||
return -1;
|
||||
}
|
||||
if (rhs == null) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
long diff = lhs.getTimestampMs() - rhs.getTimestampMs();
|
||||
if (diff == 0) {
|
||||
return 0;
|
||||
} else if (diff < 0) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final Object mEventsStagingLock = new Object();
|
||||
private final Object mEventsToDispatchLock = new Object();
|
||||
private final ReactApplicationContext mReactContext;
|
||||
private final LongSparseArray<Integer> mEventCookieToLastEventIdx = new LongSparseArray<>();
|
||||
private final Map<String, Short> mEventNameToEventId = MapBuilder.newHashMap();
|
||||
private final DispatchEventsRunnable mDispatchEventsRunnable = new DispatchEventsRunnable();
|
||||
private final ArrayList<Event> mEventStaging = new ArrayList<>();
|
||||
private final CopyOnWriteArrayList<EventDispatcherListener> mListeners =
|
||||
new CopyOnWriteArrayList<>();
|
||||
private final CopyOnWriteArrayList<BatchEventDispatchedListener> mPostEventDispatchListeners =
|
||||
new CopyOnWriteArrayList<>();
|
||||
private final ScheduleDispatchFrameCallback mCurrentFrameCallback =
|
||||
new ScheduleDispatchFrameCallback();
|
||||
private final AtomicInteger mHasDispatchScheduledCount = new AtomicInteger();
|
||||
|
||||
private Event[] mEventsToDispatch = new Event[16];
|
||||
private int mEventsToDispatchSize = 0;
|
||||
private final EventEmitterImpl mReactEventEmitter;
|
||||
private short mNextEventTypeId = 0;
|
||||
private volatile boolean mHasDispatchScheduled = false;
|
||||
|
||||
public EventDispatcherImpl(ReactApplicationContext reactContext) {
|
||||
mReactContext = reactContext;
|
||||
mReactContext.addLifecycleEventListener(this);
|
||||
mReactEventEmitter = new EventEmitterImpl(mReactContext);
|
||||
}
|
||||
|
||||
/** Sends the given Event to JS, coalescing eligible events if JS is backed up. */
|
||||
public void dispatchEvent(Event event) {
|
||||
Assertions.assertCondition(event.isInitialized(), "Dispatched event hasn't been initialized");
|
||||
|
||||
for (EventDispatcherListener listener : mListeners) {
|
||||
listener.onEventDispatch(event);
|
||||
}
|
||||
|
||||
synchronized (mEventsStagingLock) {
|
||||
mEventStaging.add(event);
|
||||
Systrace.startAsyncFlow(Systrace.TRACE_TAG_REACT, event.getEventName(), event.getUniqueID());
|
||||
}
|
||||
maybePostFrameCallbackFromNonUI();
|
||||
}
|
||||
|
||||
public void dispatchAllEvents() {
|
||||
maybePostFrameCallbackFromNonUI();
|
||||
}
|
||||
|
||||
private void maybePostFrameCallbackFromNonUI() {
|
||||
mCurrentFrameCallback.maybePostFromNonUI();
|
||||
}
|
||||
|
||||
/** Add a listener to this EventDispatcher. */
|
||||
public void addListener(EventDispatcherListener listener) {
|
||||
mListeners.add(listener);
|
||||
}
|
||||
|
||||
/** Remove a listener from this EventDispatcher. */
|
||||
public void removeListener(EventDispatcherListener listener) {
|
||||
mListeners.remove(listener);
|
||||
}
|
||||
|
||||
public void addBatchEventDispatchedListener(BatchEventDispatchedListener listener) {
|
||||
mPostEventDispatchListeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeBatchEventDispatchedListener(BatchEventDispatchedListener listener) {
|
||||
mPostEventDispatchListeners.remove(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostResume() {
|
||||
maybePostFrameCallbackFromNonUI();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostPause() {
|
||||
stopFrameCallback();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostDestroy() {
|
||||
stopFrameCallback();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void onCatalystInstanceDestroyed() {
|
||||
UiThreadUtil.runOnUiThread(this::stopFrameCallback);
|
||||
}
|
||||
|
||||
private void stopFrameCallback() {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
mCurrentFrameCallback.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* We use a staging data structure so that all UI events generated in a single frame are
|
||||
* dispatched at once. Otherwise, a JS runnable enqueued in a previous frame could run while the
|
||||
* UI thread is in the process of adding UI events and we might incorrectly send one event this
|
||||
* frame and another from this frame during the next.
|
||||
*/
|
||||
private void moveStagedEventsToDispatchQueue() {
|
||||
synchronized (mEventsStagingLock) {
|
||||
synchronized (mEventsToDispatchLock) {
|
||||
for (int i = 0; i < mEventStaging.size(); i++) {
|
||||
Event event = mEventStaging.get(i);
|
||||
|
||||
if (!event.canCoalesce()) {
|
||||
addEventToEventsToDispatch(event);
|
||||
continue;
|
||||
}
|
||||
|
||||
long eventCookie =
|
||||
getEventCookie(event.getViewTag(), event.getEventName(), event.getCoalescingKey());
|
||||
|
||||
Event eventToAdd = null;
|
||||
Event eventToDispose = null;
|
||||
Integer lastEventIdx = mEventCookieToLastEventIdx.get(eventCookie);
|
||||
|
||||
if (lastEventIdx == null) {
|
||||
eventToAdd = event;
|
||||
mEventCookieToLastEventIdx.put(eventCookie, mEventsToDispatchSize);
|
||||
} else {
|
||||
Event lastEvent = mEventsToDispatch[lastEventIdx];
|
||||
Event coalescedEvent = event.coalesce(lastEvent);
|
||||
if (coalescedEvent != lastEvent) {
|
||||
eventToAdd = coalescedEvent;
|
||||
mEventCookieToLastEventIdx.put(eventCookie, mEventsToDispatchSize);
|
||||
eventToDispose = lastEvent;
|
||||
mEventsToDispatch[lastEventIdx] = null;
|
||||
} else {
|
||||
eventToDispose = event;
|
||||
}
|
||||
}
|
||||
|
||||
if (eventToAdd != null) {
|
||||
addEventToEventsToDispatch(eventToAdd);
|
||||
}
|
||||
if (eventToDispose != null) {
|
||||
eventToDispose.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
mEventStaging.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private long getEventCookie(int viewTag, String eventName, short coalescingKey) {
|
||||
short eventTypeId;
|
||||
Short eventIdObj = mEventNameToEventId.get(eventName);
|
||||
if (eventIdObj != null) {
|
||||
eventTypeId = eventIdObj;
|
||||
} else {
|
||||
eventTypeId = mNextEventTypeId++;
|
||||
mEventNameToEventId.put(eventName, eventTypeId);
|
||||
}
|
||||
return getEventCookie(viewTag, eventTypeId, coalescingKey);
|
||||
}
|
||||
|
||||
private static long getEventCookie(int viewTag, short eventTypeId, short coalescingKey) {
|
||||
return viewTag
|
||||
| (((long) eventTypeId) & 0xffff) << 32
|
||||
| (((long) coalescingKey) & 0xffff) << 48;
|
||||
}
|
||||
|
||||
private class ScheduleDispatchFrameCallback implements Choreographer.FrameCallback {
|
||||
private volatile boolean mIsPosted = false;
|
||||
private boolean mShouldStop = false;
|
||||
|
||||
@Override
|
||||
public void doFrame(long frameTimeNanos) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
if (mShouldStop) {
|
||||
mIsPosted = false;
|
||||
} else {
|
||||
post();
|
||||
}
|
||||
|
||||
Systrace.beginSection(Systrace.TRACE_TAG_REACT, "ScheduleDispatchFrameCallback");
|
||||
try {
|
||||
moveStagedEventsToDispatchQueue();
|
||||
|
||||
if (!mHasDispatchScheduled) {
|
||||
mHasDispatchScheduled = true;
|
||||
Systrace.startAsyncFlow(
|
||||
Systrace.TRACE_TAG_REACT,
|
||||
"ScheduleDispatchFrameCallback",
|
||||
mHasDispatchScheduledCount.get());
|
||||
mReactContext.runOnJSQueueThread(mDispatchEventsRunnable);
|
||||
}
|
||||
} finally {
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
mShouldStop = true;
|
||||
}
|
||||
|
||||
public void maybePost() {
|
||||
if (!mIsPosted) {
|
||||
mIsPosted = true;
|
||||
post();
|
||||
}
|
||||
}
|
||||
|
||||
private void post() {
|
||||
ReactChoreographer.getInstance()
|
||||
.postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, mCurrentFrameCallback);
|
||||
}
|
||||
|
||||
public void maybePostFromNonUI() {
|
||||
if (mIsPosted) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We should only hit this slow path when we receive events while the host activity is paused.
|
||||
if (mReactContext.isOnUiQueueThread()) {
|
||||
maybePost();
|
||||
} else {
|
||||
mReactContext.runOnUiQueueThread(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
maybePost();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class DispatchEventsRunnable implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Systrace.beginSection(Systrace.TRACE_TAG_REACT, "DispatchEventsRunnable");
|
||||
try {
|
||||
Systrace.endAsyncFlow(
|
||||
Systrace.TRACE_TAG_REACT,
|
||||
"ScheduleDispatchFrameCallback",
|
||||
mHasDispatchScheduledCount.getAndIncrement());
|
||||
mHasDispatchScheduled = false;
|
||||
synchronized (mEventsToDispatchLock) {
|
||||
if (mEventsToDispatchSize > 0) {
|
||||
// We avoid allocating an array and iterator, and "sorting" if we don't need to.
|
||||
// This occurs when the size of mEventsToDispatch is zero or one.
|
||||
if (mEventsToDispatchSize > 1) {
|
||||
Arrays.sort(mEventsToDispatch, 0, mEventsToDispatchSize, EVENT_COMPARATOR);
|
||||
}
|
||||
for (int eventIdx = 0; eventIdx < mEventsToDispatchSize; eventIdx++) {
|
||||
Event event = mEventsToDispatch[eventIdx];
|
||||
// Event can be null if it has been coalesced into another event.
|
||||
if (event == null) {
|
||||
continue;
|
||||
}
|
||||
Systrace.endAsyncFlow(
|
||||
Systrace.TRACE_TAG_REACT, event.getEventName(), event.getUniqueID());
|
||||
|
||||
event.dispatchModern(mReactEventEmitter);
|
||||
event.dispose();
|
||||
}
|
||||
clearEventsToDispatch();
|
||||
mEventCookieToLastEventIdx.clear();
|
||||
}
|
||||
}
|
||||
for (BatchEventDispatchedListener listener : mPostEventDispatchListeners) {
|
||||
listener.onBatchEventDispatched();
|
||||
}
|
||||
} finally {
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addEventToEventsToDispatch(Event event) {
|
||||
if (mEventsToDispatchSize == mEventsToDispatch.length) {
|
||||
mEventsToDispatch = Arrays.copyOf(mEventsToDispatch, 2 * mEventsToDispatch.length);
|
||||
}
|
||||
mEventsToDispatch[mEventsToDispatchSize++] = event;
|
||||
}
|
||||
|
||||
private void clearEventsToDispatch() {
|
||||
Arrays.fill(mEventsToDispatch, 0, mEventsToDispatchSize, null);
|
||||
mEventsToDispatchSize = 0;
|
||||
}
|
||||
}
|
||||
+334
@@ -0,0 +1,334 @@
|
||||
/*
|
||||
* 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 android.util.LongSparseArray
|
||||
import android.view.Choreographer
|
||||
import com.facebook.react.bridge.LifecycleEventListener
|
||||
import com.facebook.react.bridge.ReactApplicationContext
|
||||
import com.facebook.react.bridge.UiThreadUtil.assertOnUiThread
|
||||
import com.facebook.react.bridge.UiThreadUtil.runOnUiThread
|
||||
import com.facebook.react.common.annotations.internal.InteropLegacyArchitecture
|
||||
import com.facebook.react.modules.core.ReactChoreographer
|
||||
import com.facebook.systrace.Systrace
|
||||
import java.util.Arrays
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
/**
|
||||
* Class responsible for dispatching UI events to JS. The main purpose of this class is to act as an
|
||||
* intermediary between UI code generating events and JS, making sure we don't send more events than
|
||||
* JS can process.
|
||||
*
|
||||
* To use it, create a subclass of [Event] and call [dispatchEvent] whenever there's a UI event to
|
||||
* dispatch.
|
||||
*
|
||||
* This class works by installing a [Choreographer] frame callback on the main thread. This callback
|
||||
* then enqueues a runnable on the JS thread (if one is not already pending) that is responsible for
|
||||
* actually dispatch events to JS. This implementation depends on the properties that
|
||||
* 1) FrameCallbacks run after UI events have been processed in [Choreographer]
|
||||
* 2) when we enqueue a runnable on the JS queue thread, it won't be called until after any
|
||||
* previously enqueued JS jobs have finished processing
|
||||
*
|
||||
* If JS is taking a long time processing events, then the UI events generated on the UI thread can
|
||||
* be coalesced into fewer events so that when the runnable runs, we don't overload JS with a ton of
|
||||
* events and make it get even farther behind.
|
||||
*
|
||||
* Ideally, we don't need this and JS is fast enough to process all the events each frame, but bad
|
||||
* things happen, including load on CPUs from the system, and we should handle this case well.
|
||||
*
|
||||
* # Event Cookies
|
||||
*
|
||||
* An event cookie is made up of the event type id, view tag, and a custom coalescing key. Only
|
||||
* Events that have the same cookie can be coalesced.
|
||||
*
|
||||
* Event Cookie Composition: VIEW_TAG_MASK = 0x00000000ffffffff EVENT_TYPE_ID_MASK =
|
||||
* 0x0000ffff00000000 COALESCING_KEY_MASK = 0xffff000000000000
|
||||
*/
|
||||
@InteropLegacyArchitecture
|
||||
public class EventDispatcherImpl(private val reactContext: ReactApplicationContext) :
|
||||
EventDispatcher, LifecycleEventListener {
|
||||
private val eventsStagingLock = Any()
|
||||
private val eventsToDispatchLock = Any()
|
||||
private val eventCookieToLastEventIdx = LongSparseArray<Int>()
|
||||
private val eventNameToEventId: MutableMap<String, Short> = mutableMapOf()
|
||||
private val dispatchEventsRunnable: DispatchEventsRunnable = DispatchEventsRunnable()
|
||||
private val eventStaging = ArrayList<Event<*>>()
|
||||
private val listeners = CopyOnWriteArrayList<EventDispatcherListener>()
|
||||
private val postEventDispatchListeners = CopyOnWriteArrayList<BatchEventDispatchedListener>()
|
||||
private val currentFrameCallback: ScheduleDispatchFrameCallback = ScheduleDispatchFrameCallback()
|
||||
private val hasDispatchScheduledCount = AtomicInteger()
|
||||
private var eventsToDispatch: Array<Event<*>?> = arrayOfNulls(16)
|
||||
private var eventsToDispatchSize = 0
|
||||
private val reactEventEmitter: EventEmitterImpl
|
||||
private var nextEventTypeId: Short = 0
|
||||
@Volatile private var hasDispatchScheduled = false
|
||||
|
||||
init {
|
||||
reactContext.addLifecycleEventListener(this)
|
||||
reactEventEmitter = EventEmitterImpl(reactContext)
|
||||
}
|
||||
|
||||
/** Sends the given Event to JS, coalescing eligible events if JS is backed up. */
|
||||
override fun dispatchEvent(event: Event<*>) {
|
||||
require(event.isInitialized) { "Dispatched event hasn't been initialized" }
|
||||
|
||||
for (listener in listeners) {
|
||||
listener.onEventDispatch(event)
|
||||
}
|
||||
|
||||
synchronized(eventsStagingLock) {
|
||||
eventStaging.add(event)
|
||||
Systrace.startAsyncFlow(Systrace.TRACE_TAG_REACT, event.getEventName(), event.uniqueID)
|
||||
}
|
||||
maybePostFrameCallbackFromNonUI()
|
||||
}
|
||||
|
||||
override fun dispatchAllEvents() {
|
||||
maybePostFrameCallbackFromNonUI()
|
||||
}
|
||||
|
||||
private fun maybePostFrameCallbackFromNonUI() {
|
||||
currentFrameCallback.maybePostFromNonUI()
|
||||
}
|
||||
|
||||
/** Add a listener to this EventDispatcher. */
|
||||
override fun addListener(listener: EventDispatcherListener) {
|
||||
listeners.add(listener)
|
||||
}
|
||||
|
||||
/** Remove a listener from this EventDispatcher. */
|
||||
override fun removeListener(listener: EventDispatcherListener) {
|
||||
listeners.remove(listener)
|
||||
}
|
||||
|
||||
override fun addBatchEventDispatchedListener(listener: BatchEventDispatchedListener) {
|
||||
postEventDispatchListeners.add(listener)
|
||||
}
|
||||
|
||||
override fun removeBatchEventDispatchedListener(listener: BatchEventDispatchedListener) {
|
||||
postEventDispatchListeners.remove(listener)
|
||||
}
|
||||
|
||||
override fun onHostResume() {
|
||||
maybePostFrameCallbackFromNonUI()
|
||||
}
|
||||
|
||||
override fun onHostPause() {
|
||||
stopFrameCallback()
|
||||
}
|
||||
|
||||
override fun onHostDestroy() {
|
||||
stopFrameCallback()
|
||||
}
|
||||
|
||||
@Deprecated("Private API, should only be used when the concrete implementation is known.")
|
||||
override fun onCatalystInstanceDestroyed() {
|
||||
runOnUiThread { this.stopFrameCallback() }
|
||||
}
|
||||
|
||||
private fun stopFrameCallback() {
|
||||
assertOnUiThread()
|
||||
currentFrameCallback.stop()
|
||||
}
|
||||
|
||||
/**
|
||||
* We use a staging data structure so that all UI events generated in a single frame are
|
||||
* dispatched at once. Otherwise, a JS runnable enqueued in a previous frame could run while the
|
||||
* UI thread is in the process of adding UI events and we might incorrectly send one event this
|
||||
* frame and another from this frame during the next.
|
||||
*/
|
||||
private fun moveStagedEventsToDispatchQueue() {
|
||||
synchronized(eventsStagingLock) {
|
||||
synchronized(eventsToDispatchLock) {
|
||||
for (i in eventStaging.indices) {
|
||||
val event: Event<*> = eventStaging[i]
|
||||
|
||||
if (!event.canCoalesce()) {
|
||||
addEventToEventsToDispatch(event)
|
||||
continue
|
||||
}
|
||||
|
||||
val eventCookie =
|
||||
getEventCookie(event.viewTag, event.getEventName(), event.getCoalescingKey())
|
||||
|
||||
var eventToAdd: Event<*>? = null
|
||||
var eventToDispose: Event<*>? = null
|
||||
val lastEventIdx = eventCookieToLastEventIdx[eventCookie]
|
||||
|
||||
if (lastEventIdx == null) {
|
||||
eventToAdd = event
|
||||
eventCookieToLastEventIdx.put(eventCookie, eventsToDispatchSize)
|
||||
} else {
|
||||
val lastEvent: Event<*> = checkNotNull(eventsToDispatch[lastEventIdx])
|
||||
val coalescedEvent = event.coalesce(lastEvent)
|
||||
if (coalescedEvent !== lastEvent) {
|
||||
eventToAdd = coalescedEvent
|
||||
eventCookieToLastEventIdx.put(eventCookie, eventsToDispatchSize)
|
||||
eventToDispose = lastEvent
|
||||
eventsToDispatch[lastEventIdx] = null
|
||||
} else {
|
||||
eventToDispose = event
|
||||
}
|
||||
}
|
||||
|
||||
if (eventToAdd != null) {
|
||||
addEventToEventsToDispatch(eventToAdd)
|
||||
}
|
||||
eventToDispose?.dispose()
|
||||
}
|
||||
}
|
||||
eventStaging.clear()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getEventCookie(viewTag: Int, eventName: String, coalescingKey: Short): Long {
|
||||
val eventTypeId: Short
|
||||
val eventIdObj = eventNameToEventId[eventName]
|
||||
if (eventIdObj != null) {
|
||||
eventTypeId = eventIdObj
|
||||
} else {
|
||||
eventTypeId = nextEventTypeId++
|
||||
eventNameToEventId[eventName] = eventTypeId
|
||||
}
|
||||
return getEventCookie(viewTag, eventTypeId, coalescingKey)
|
||||
}
|
||||
|
||||
private inner class ScheduleDispatchFrameCallback : Choreographer.FrameCallback {
|
||||
@Volatile private var isPosted = false
|
||||
private var shouldStop = false
|
||||
|
||||
override fun doFrame(frameTimeNanos: Long) {
|
||||
assertOnUiThread()
|
||||
|
||||
if (shouldStop) {
|
||||
isPosted = false
|
||||
} else {
|
||||
post()
|
||||
}
|
||||
|
||||
Systrace.beginSection(Systrace.TRACE_TAG_REACT, "ScheduleDispatchFrameCallback")
|
||||
try {
|
||||
moveStagedEventsToDispatchQueue()
|
||||
|
||||
if (!hasDispatchScheduled) {
|
||||
hasDispatchScheduled = true
|
||||
Systrace.startAsyncFlow(
|
||||
Systrace.TRACE_TAG_REACT,
|
||||
"ScheduleDispatchFrameCallback",
|
||||
hasDispatchScheduledCount.get())
|
||||
reactContext.runOnJSQueueThread(dispatchEventsRunnable)
|
||||
}
|
||||
} finally {
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT)
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
shouldStop = true
|
||||
}
|
||||
|
||||
fun maybePost() {
|
||||
if (!isPosted) {
|
||||
isPosted = true
|
||||
post()
|
||||
}
|
||||
}
|
||||
|
||||
fun post() {
|
||||
ReactChoreographer.getInstance()
|
||||
.postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, currentFrameCallback)
|
||||
}
|
||||
|
||||
fun maybePostFromNonUI() {
|
||||
if (isPosted) {
|
||||
return
|
||||
}
|
||||
|
||||
// We should only hit this slow path when we receive events while the host activity is paused.
|
||||
if (reactContext.isOnUiQueueThread) {
|
||||
maybePost()
|
||||
} else {
|
||||
reactContext.runOnUiQueueThread { maybePost() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inner class DispatchEventsRunnable : Runnable {
|
||||
override fun run() {
|
||||
Systrace.beginSection(Systrace.TRACE_TAG_REACT, "DispatchEventsRunnable")
|
||||
try {
|
||||
Systrace.endAsyncFlow(
|
||||
Systrace.TRACE_TAG_REACT,
|
||||
"ScheduleDispatchFrameCallback",
|
||||
hasDispatchScheduledCount.getAndIncrement())
|
||||
hasDispatchScheduled = false
|
||||
synchronized(eventsToDispatchLock) {
|
||||
if (eventsToDispatchSize > 0) {
|
||||
// We avoid allocating an array and iterator, and "sorting" if we don't need to.
|
||||
// This occurs when the size of mEventsToDispatch is zero or one.
|
||||
if (eventsToDispatchSize > 1) {
|
||||
Arrays.sort(eventsToDispatch, 0, eventsToDispatchSize, EVENT_COMPARATOR)
|
||||
}
|
||||
for (eventIdx in 0..<eventsToDispatchSize) {
|
||||
val event = eventsToDispatch[eventIdx] ?: continue
|
||||
// Event can be null if it has been coalesced into another event.
|
||||
Systrace.endAsyncFlow(Systrace.TRACE_TAG_REACT, event.getEventName(), event.uniqueID)
|
||||
|
||||
event.dispatchModern(reactEventEmitter)
|
||||
event.dispose()
|
||||
}
|
||||
clearEventsToDispatch()
|
||||
eventCookieToLastEventIdx.clear()
|
||||
}
|
||||
}
|
||||
for (listener in postEventDispatchListeners) {
|
||||
listener.onBatchEventDispatched()
|
||||
}
|
||||
} finally {
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addEventToEventsToDispatch(event: Event<*>) {
|
||||
if (eventsToDispatchSize == eventsToDispatch.size) {
|
||||
eventsToDispatch = eventsToDispatch.copyOf(2 * eventsToDispatch.size)
|
||||
}
|
||||
eventsToDispatch[eventsToDispatchSize++] = event
|
||||
}
|
||||
|
||||
private fun clearEventsToDispatch() {
|
||||
Arrays.fill(eventsToDispatch, 0, eventsToDispatchSize, null)
|
||||
eventsToDispatchSize = 0
|
||||
}
|
||||
|
||||
public companion object {
|
||||
private val EVENT_COMPARATOR: java.util.Comparator<Event<*>?> =
|
||||
java.util.Comparator { lhs, rhs ->
|
||||
when {
|
||||
lhs == null && rhs == null -> 0
|
||||
lhs == null -> -1
|
||||
rhs == null -> 1
|
||||
else -> {
|
||||
val diff = lhs.timestampMs - rhs.timestampMs
|
||||
when {
|
||||
diff == 0L -> 0
|
||||
diff < 0 -> -1
|
||||
else -> 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getEventCookie(viewTag: Int, eventTypeId: Short, coalescingKey: Short): Long =
|
||||
(viewTag.toLong() or
|
||||
(((eventTypeId.toLong()) and 0xffffL) shl 32) or
|
||||
(((coalescingKey.toLong()) and 0xffffL) shl 48))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user