diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index bf8f9e77f8..1f51e272ac 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -43,6 +43,7 @@ import com.facebook.react.module.annotations.ReactModule; import com.facebook.react.uimanager.common.ViewUtil; import com.facebook.react.uimanager.debug.NotThreadSafeViewHierarchyUpdateDebugListener; import com.facebook.react.uimanager.events.EventDispatcher; +import com.facebook.react.uimanager.events.EventDispatcherImpl; import com.facebook.react.uimanager.events.RCTEventEmitter; import com.facebook.systrace.Systrace; import com.facebook.systrace.SystraceMessage; @@ -156,7 +157,7 @@ public class UIManagerModule extends ReactContextBaseJavaModule int minTimeLeftInFrameForNonBatchedOperationMs) { super(reactContext); DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(reactContext); - mEventDispatcher = new EventDispatcher(reactContext); + mEventDispatcher = new EventDispatcherImpl(reactContext); mModuleConstants = createConstants(viewManagerResolver); mCustomDirectEvents = UIManagerModuleConstants.getDirectEventTypeConstants(); mViewManagerRegistry = new ViewManagerRegistry(viewManagerResolver); @@ -178,7 +179,7 @@ public class UIManagerModule extends ReactContextBaseJavaModule int minTimeLeftInFrameForNonBatchedOperationMs) { super(reactContext); DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(reactContext); - mEventDispatcher = new EventDispatcher(reactContext); + mEventDispatcher = new EventDispatcherImpl(reactContext); mCustomDirectEvents = MapBuilder.newHashMap(); mModuleConstants = createConstants(viewManagersList, null, mCustomDirectEvents); mViewManagerRegistry = new ViewManagerRegistry(viewManagersList); 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 68ed01c985..2ba1834097 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 @@ -7,383 +7,28 @@ package com.facebook.react.uimanager.events; -import android.util.LongSparseArray; -import com.facebook.infer.annotation.Assertions; -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.modules.core.ChoreographerCompat; -import com.facebook.react.modules.core.ReactChoreographer; import com.facebook.react.uimanager.common.UIManagerType; -import com.facebook.systrace.Systrace; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -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 {@link Event} and call {@link #dispatchEvent(Event)} 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.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 - * - *

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 - */ -public class EventDispatcher implements LifecycleEventListener { - - private static final Comparator EVENT_COMPARATOR = - new Comparator() { - @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 mEventCookieToLastEventIdx = new LongSparseArray<>(); - private final Map mEventNameToEventId = MapBuilder.newHashMap(); - private final DispatchEventsRunnable mDispatchEventsRunnable = new DispatchEventsRunnable(); - private final ArrayList mEventStaging = new ArrayList<>(); - private final ArrayList mListeners = new ArrayList<>(); - private final List mPostEventDispatchListeners = new ArrayList<>(); - private final ScheduleDispatchFrameCallback mCurrentFrameCallback = - new ScheduleDispatchFrameCallback(); - private final AtomicInteger mHasDispatchScheduledCount = new AtomicInteger(); - - private Event[] mEventsToDispatch = new Event[16]; - private int mEventsToDispatchSize = 0; - private volatile ReactEventEmitter mReactEventEmitter; - private short mNextEventTypeId = 0; - private volatile boolean mHasDispatchScheduled = false; - - public EventDispatcher(ReactApplicationContext reactContext) { - mReactContext = reactContext; - mReactContext.addLifecycleEventListener(this); - mReactEventEmitter = new ReactEventEmitter(mReactContext); - } +public interface EventDispatcher { /** 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"); + void dispatchEvent(Event event); - for (EventDispatcherListener listener : mListeners) { - listener.onEventDispatch(event); - } - - synchronized (mEventsStagingLock) { - mEventStaging.add(event); - Systrace.startAsyncFlow( - Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, event.getEventName(), event.getUniqueID()); - } - maybePostFrameCallbackFromNonUI(); - } - - public void dispatchAllEvents() { - maybePostFrameCallbackFromNonUI(); - } - - private void maybePostFrameCallbackFromNonUI() { - if (mReactEventEmitter != null) { - // If the host activity is paused, the frame callback may not be currently - // posted. Ensure that it is so that this event gets delivered promptly. - mCurrentFrameCallback.maybePostFromNonUI(); - } else { - // No JS application has started yet, or resumed. This can happen when a ReactRootView is - // added to view hierarchy, but ReactContext creation has not completed yet. In this case, any - // touch event dispatch will hit this codepath, and we simply queue them so that they - // are dispatched once ReactContext creation completes and JS app is running. - } - } + void dispatchAllEvents(); /** Add a listener to this EventDispatcher. */ - public void addListener(EventDispatcherListener listener) { - mListeners.add(listener); - } + void addListener(EventDispatcherListener listener); /** Remove a listener from this EventDispatcher. */ - public void removeListener(EventDispatcherListener listener) { - mListeners.remove(listener); - } + void removeListener(EventDispatcherListener listener); - public void addBatchEventDispatchedListener(BatchEventDispatchedListener listener) { - mPostEventDispatchListeners.add(listener); - } + void addBatchEventDispatchedListener(BatchEventDispatchedListener listener); - public void removeBatchEventDispatchedListener(BatchEventDispatchedListener listener) { - mPostEventDispatchListeners.remove(listener); - } + void removeBatchEventDispatchedListener(BatchEventDispatchedListener listener); - @Override - public void onHostResume() { - maybePostFrameCallbackFromNonUI(); - } + void registerEventEmitter(@UIManagerType int uiManagerType, RCTEventEmitter eventEmitter); - @Override - public void onHostPause() { - stopFrameCallback(); - } + void unregisterEventEmitter(@UIManagerType int uiManagerType); - @Override - public void onHostDestroy() { - stopFrameCallback(); - } - - public void onCatalystInstanceDestroyed() { - UiThreadUtil.runOnUiThread( - new Runnable() { - @Override - public void run() { - 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; - } - - public void registerEventEmitter(@UIManagerType int uiManagerType, RCTEventEmitter eventEmitter) { - mReactEventEmitter.register(uiManagerType, eventEmitter); - } - - public void unregisterEventEmitter(@UIManagerType int uiManagerType) { - mReactEventEmitter.unregister(uiManagerType); - } - - private class ScheduleDispatchFrameCallback extends ChoreographerCompat.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_JAVA_BRIDGE, "ScheduleDispatchFrameCallback"); - try { - moveStagedEventsToDispatchQueue(); - - if (!mHasDispatchScheduled) { - mHasDispatchScheduled = true; - Systrace.startAsyncFlow( - Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, - "ScheduleDispatchFrameCallback", - mHasDispatchScheduledCount.get()); - mReactContext.runOnJSQueueThread(mDispatchEventsRunnable); - } - } finally { - Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); - } - } - - 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_JAVA_BRIDGE, "DispatchEventsRunnable"); - try { - Systrace.endAsyncFlow( - Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, - "ScheduleDispatchFrameCallback", - mHasDispatchScheduledCount.getAndIncrement()); - mHasDispatchScheduled = false; - Assertions.assertNotNull(mReactEventEmitter); - 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_JAVA_BRIDGE, event.getEventName(), event.getUniqueID()); - event.dispatch(mReactEventEmitter); - event.dispose(); - } - clearEventsToDispatch(); - mEventCookieToLastEventIdx.clear(); - } - } - for (BatchEventDispatchedListener listener : mPostEventDispatchListeners) { - listener.onBatchEventDispatched(); - } - } finally { - Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); - } - } - } - - 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; - } + 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 new file mode 100644 index 0000000000..542f03616f --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcherImpl.java @@ -0,0 +1,389 @@ +/* + * Copyright (c) Facebook, Inc. and its 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 com.facebook.infer.annotation.Assertions; +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.modules.core.ChoreographerCompat; +import com.facebook.react.modules.core.ReactChoreographer; +import com.facebook.react.uimanager.common.UIManagerType; +import com.facebook.systrace.Systrace; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +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 {@link Event} and call {@link #dispatchEvent(Event)} 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.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 + * + *

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 + */ +public class EventDispatcherImpl implements EventDispatcher, LifecycleEventListener { + + private static final Comparator EVENT_COMPARATOR = + new Comparator() { + @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 mEventCookieToLastEventIdx = new LongSparseArray<>(); + private final Map mEventNameToEventId = MapBuilder.newHashMap(); + private final DispatchEventsRunnable mDispatchEventsRunnable = new DispatchEventsRunnable(); + private final ArrayList mEventStaging = new ArrayList<>(); + private final ArrayList mListeners = new ArrayList<>(); + private final List mPostEventDispatchListeners = new ArrayList<>(); + private final ScheduleDispatchFrameCallback mCurrentFrameCallback = + new ScheduleDispatchFrameCallback(); + private final AtomicInteger mHasDispatchScheduledCount = new AtomicInteger(); + + private Event[] mEventsToDispatch = new Event[16]; + private int mEventsToDispatchSize = 0; + private volatile ReactEventEmitter mReactEventEmitter; + private short mNextEventTypeId = 0; + private volatile boolean mHasDispatchScheduled = false; + + public EventDispatcherImpl(ReactApplicationContext reactContext) { + mReactContext = reactContext; + mReactContext.addLifecycleEventListener(this); + mReactEventEmitter = new ReactEventEmitter(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_JAVA_BRIDGE, event.getEventName(), event.getUniqueID()); + } + maybePostFrameCallbackFromNonUI(); + } + + public void dispatchAllEvents() { + maybePostFrameCallbackFromNonUI(); + } + + private void maybePostFrameCallbackFromNonUI() { + if (mReactEventEmitter != null) { + // If the host activity is paused, the frame callback may not be currently + // posted. Ensure that it is so that this event gets delivered promptly. + mCurrentFrameCallback.maybePostFromNonUI(); + } else { + // No JS application has started yet, or resumed. This can happen when a ReactRootView is + // added to view hierarchy, but ReactContext creation has not completed yet. In this case, any + // touch event dispatch will hit this codepath, and we simply queue them so that they + // are dispatched once ReactContext creation completes and JS app is running. + } + } + + /** 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(); + } + + public void onCatalystInstanceDestroyed() { + UiThreadUtil.runOnUiThread( + new Runnable() { + @Override + public void run() { + 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; + } + + public void registerEventEmitter(@UIManagerType int uiManagerType, RCTEventEmitter eventEmitter) { + mReactEventEmitter.register(uiManagerType, eventEmitter); + } + + public void unregisterEventEmitter(@UIManagerType int uiManagerType) { + mReactEventEmitter.unregister(uiManagerType); + } + + private class ScheduleDispatchFrameCallback extends ChoreographerCompat.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_JAVA_BRIDGE, "ScheduleDispatchFrameCallback"); + try { + moveStagedEventsToDispatchQueue(); + + if (!mHasDispatchScheduled) { + mHasDispatchScheduled = true; + Systrace.startAsyncFlow( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, + "ScheduleDispatchFrameCallback", + mHasDispatchScheduledCount.get()); + mReactContext.runOnJSQueueThread(mDispatchEventsRunnable); + } + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } + } + + 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_JAVA_BRIDGE, "DispatchEventsRunnable"); + try { + Systrace.endAsyncFlow( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, + "ScheduleDispatchFrameCallback", + mHasDispatchScheduledCount.getAndIncrement()); + mHasDispatchScheduled = false; + Assertions.assertNotNull(mReactEventEmitter); + 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_JAVA_BRIDGE, event.getEventName(), event.getUniqueID()); + event.dispatch(mReactEventEmitter); + event.dispose(); + } + clearEventsToDispatch(); + mEventCookieToLastEventIdx.clear(); + } + } + for (BatchEventDispatchedListener listener : mPostEventDispatchListeners) { + listener.onBatchEventDispatched(); + } + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } + } + } + + 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; + } +}