Skip to content

Commit

Permalink
Immediately dispatch events to the shared C++ infrastructure to suppo…
Browse files Browse the repository at this point in the history
…rt interruptability (facebook#39380)

Summary:
Pull Request resolved: facebook#39380

Changelog:
[Internal][Added] - Created a new event dispatching pipeline that immediately moves events over to the C++ queue, along with the onFrame that triggers the event beat ticking mechanism.

Reviewed By: sammy-SC

Differential Revision: D49012996

fbshipit-source-id: 0bc9067e5b019f308ec1f45ca8bd83fd195b37ce
  • Loading branch information
Jesse Watts-Russell authored and facebook-github-bot committed Sep 13, 2023
1 parent eddefec commit daa2bc7
Show file tree
Hide file tree
Showing 3 changed files with 196 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,7 @@ public class ReactFeatureFlags {

/** Default state updates and events to async batched priority. */
public static boolean enableDefaultAsyncBatchedPriority = false;

/** Utilize shared Event C++ pipeline with fabric's renderer */
public static boolean enableFabricSharedEventPipeline = false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
import com.facebook.react.uimanager.events.EventCategoryDef;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.react.uimanager.events.EventDispatcherImpl;
import com.facebook.react.uimanager.events.FabricEventDispatcher;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.facebook.react.views.text.TextLayoutManager;
import com.facebook.react.views.text.TextLayoutManagerMapBuffer;
Expand Down Expand Up @@ -220,7 +221,11 @@ public FabricUIManager(
mMountingManager = new MountingManager(viewManagerRegistry, mMountItemExecutor);
mMountItemDispatcher =
new MountItemDispatcher(mMountingManager, new MountItemDispatchListener());
mEventDispatcher = new EventDispatcherImpl(reactContext);
if (ReactFeatureFlags.enableFabricSharedEventPipeline) {
mEventDispatcher = new FabricEventDispatcher(reactContext);
} else {
mEventDispatcher = new EventDispatcherImpl(reactContext);
}
mBatchEventDispatchedListener = batchEventDispatchedListener;
mReactApplicationContext.addLifecycleEventListener(this);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/*
* 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.LifecycleEventListener;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.UiThreadUtil;
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.concurrent.CopyOnWriteArrayList;

/**
* A singleton class that overrides {@link EventDispatcher} with no-op methods, to be used by
* callers that expect an EventDispatcher when the instance doesn't exist.
*/
public class FabricEventDispatcher implements EventDispatcher, LifecycleEventListener {
private final ReactEventEmitter mReactEventEmitter;
private final ReactApplicationContext mReactContext;
private final CopyOnWriteArrayList<EventDispatcherListener> mListeners =
new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<BatchEventDispatchedListener> mPostEventDispatchListeners =
new CopyOnWriteArrayList<>();
private final FabricEventDispatcher.ScheduleDispatchFrameCallback mCurrentFrameCallback =
new FabricEventDispatcher.ScheduleDispatchFrameCallback();

public FabricEventDispatcher(ReactApplicationContext reactContext) {
mReactContext = reactContext;
mReactContext.addLifecycleEventListener(this);
mReactEventEmitter = new ReactEventEmitter(mReactContext);
}

@Override
public void dispatchEvent(Event event) {
event.dispatchModern(mReactEventEmitter);
for (EventDispatcherListener listener : mListeners) {
listener.onEventDispatch(event);
}

event.dispose();
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();
}

public void registerEventEmitter(@UIManagerType int uiManagerType, RCTEventEmitter eventEmitter) {
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);
}

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, "BatchEventDispatchedListeners");
try {
for (BatchEventDispatchedListener listener : mPostEventDispatchListeners) {
listener.onBatchEventDispatched();
}
} 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();
}
});
}
}
}
}

0 comments on commit daa2bc7

Please sign in to comment.