From 8b61578076dc298a86fe87031053c1ffcb6d0d12 Mon Sep 17 00:00:00 2001 From: Joshua Gross Date: Tue, 10 Mar 2020 20:23:21 -0700 Subject: [PATCH] Maintain a separate queue for ViewCommands and execute before everything else Summary: ViewCommands are sort of like setNativeProps updates, in that they're direct manipulations of Views on-screen that don't go through the normal commit/diff process that other UI updates do. This is an MVP that shows how we can do this in non-Fabric RN. Changelog: [Internal] experiment, allow ViewCommands to be executed before other types of UIOperations Reviewed By: axe-fb, mdvacca Differential Revision: D20378633 fbshipit-source-id: 5f3c54d3c84b4e4f7cb060a9505b20b0e5b7afed --- .../react/config/ReactFeatureFlags.java | 6 +++ .../react/uimanager/UIViewOperationQueue.java | 38 +++++++++++++++++-- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java b/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java index b746fcdc1cc..dfb268bdc68 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java +++ b/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java @@ -79,6 +79,12 @@ public class ReactFeatureFlags { */ public static boolean allowDisablingImmediateExecutionOfScheduleMountItems = false; + /** + * Temporary flag. See UIImplementation: if this flag is enabled, ViewCommands will be queued and + * executed before any other types of UI operations. + */ + public static boolean allowEarlyViewCommandExecution = false; + /** * This react flag enables a custom algorithm for the getChildVisibleRect() method in the classes * ReactViewGroup, ReactHorizontalScrollView and ReactScrollView. diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java index da2576c048d..a01cdb032a1 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java @@ -21,6 +21,7 @@ import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.SoftAssertions; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.common.ReactConstants; +import com.facebook.react.config.ReactFeatureFlags; import com.facebook.react.modules.core.ReactChoreographer; import com.facebook.react.uimanager.debug.NotThreadSafeViewHierarchyUpdateDebugListener; import com.facebook.systrace.Systrace; @@ -518,6 +519,9 @@ public class UIViewOperationQueue { private final DispatchUIFrameCallback mDispatchUIFrameCallback; private final ReactApplicationContext mReactApplicationContext; + private final boolean mAllowViewCommandsQueue; + private ArrayList mViewCommandOperations = new ArrayList<>(); + // Only called from the UIManager queue? private ArrayList mOperations = new ArrayList<>(); @@ -556,6 +560,7 @@ public class UIViewOperationQueue { ? DEFAULT_MIN_TIME_LEFT_IN_FRAME_FOR_NONBATCHED_OPERATION_MS : minTimeLeftInFrameForNonBatchedOperationMs); mReactApplicationContext = reactContext; + mAllowViewCommandsQueue = ReactFeatureFlags.allowEarlyViewCommandExecution; } /*package*/ NativeViewHierarchyManager getNativeViewHierarchyManager() { @@ -591,7 +596,7 @@ public class UIViewOperationQueue { } public boolean isEmpty() { - return mOperations.isEmpty(); + return mOperations.isEmpty() && mViewCommandOperations.isEmpty(); } public void addRootView(final int tag, final View rootView) { @@ -625,12 +630,24 @@ public class UIViewOperationQueue { @Deprecated public void enqueueDispatchCommand( int reactTag, int commandId, @Nullable ReadableArray commandArgs) { - mOperations.add(new DispatchCommandOperation(reactTag, commandId, commandArgs)); + final DispatchCommandOperation command = + new DispatchCommandOperation(reactTag, commandId, commandArgs); + if (mAllowViewCommandsQueue) { + mViewCommandOperations.add(command); + } else { + mOperations.add(command); + } } public void enqueueDispatchCommand( int reactTag, String commandId, @Nullable ReadableArray commandArgs) { - mOperations.add(new DispatchStringCommandOperation(reactTag, commandId, commandArgs)); + final DispatchStringCommandOperation command = + new DispatchStringCommandOperation(reactTag, commandId, commandArgs); + if (mAllowViewCommandsQueue) { + mViewCommandOperations.add(command); + } else { + mOperations.add(command); + } } public void enqueueUpdateExtraData(int reactTag, Object extraData) { @@ -742,6 +759,14 @@ public class UIViewOperationQueue { // Store the current operation queues to dispatch and create new empty ones to continue // receiving new operations + final ArrayList viewCommandOperations; + if (!mViewCommandOperations.isEmpty()) { + viewCommandOperations = mViewCommandOperations; + mViewCommandOperations = new ArrayList<>(); + } else { + viewCommandOperations = null; + } + final ArrayList batchedOperations; if (!mOperations.isEmpty()) { batchedOperations = mOperations; @@ -774,6 +799,13 @@ public class UIViewOperationQueue { try { long runStartTime = SystemClock.uptimeMillis(); + // All ViewCommands should be executed first as a perf optimization + if (mViewCommandOperations != null) { + for (UIOperation viewCommandOp : mViewCommandOperations) { + viewCommandOp.execute(); + } + } + // All nonBatchedOperations should be executed before regular operations as // regular operations may depend on them if (nonBatchedOperations != null) {