From 6a3b334139d528cef075555d3ebbe8a1a5ec92c5 Mon Sep 17 00:00:00 2001 From: Andy Street Date: Thu, 31 Mar 2016 09:20:19 -0700 Subject: [PATCH] Start creating views before end of batch Summary:Creating views shouldn't have side effects so we can start doing them before the end of a batch. In order to limit the effect on framerate, we stop executing these create view operations after we've passed halfway through the frame. Note, this doesn't seem to work yet for nodes: I'll address that in a followup diff. Reviewed By: lexs Differential Revision: D3120631 fb-gh-sync-id: 981540fac5a7499158146adb72c1be21fd0b5702 fbshipit-source-id: 981540fac5a7499158146adb72c1be21fd0b5702 --- .../react/uimanager/UIViewOperationQueue.java | 73 ++++++++++++++++--- 1 file changed, 63 insertions(+), 10 deletions(-) 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 114417c95..1bfbbadf0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java @@ -12,6 +12,7 @@ package com.facebook.react.uimanager; import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; @@ -417,8 +418,6 @@ public class UIViewOperationQueue { } } - private ArrayList mOperations = new ArrayList<>(); - private final class FindTargetForTouchOperation implements UIOperation { private final int mReactTag; @@ -493,14 +492,17 @@ public class UIViewOperationQueue { private final NativeViewHierarchyManager mNativeViewHierarchyManager; private final AnimationRegistry mAnimationRegistry; - private final Object mDispatchRunnablesLock = new Object(); + private final Object mNonBatchedOperationsLock = new Object(); private final DispatchUIFrameCallback mDispatchUIFrameCallback; private final ReactApplicationContext mReactApplicationContext; - @GuardedBy("mDispatchRunnablesLock") private final ArrayList mDispatchUIRunnables = new ArrayList<>(); + private ArrayList mOperations = new ArrayList<>(); + @GuardedBy("mNonBatchedOperationsLock") + private ArrayDeque mNonBatchedOperations = new ArrayDeque<>(); + private @Nullable NotThreadSafeViewHierarchyUpdateDebugListener mViewHierarchyUpdateDebugListener; public UIViewOperationQueue( @@ -604,12 +606,14 @@ public class UIViewOperationQueue { int viewReactTag, String viewClassName, @Nullable ReactStylesDiffMap initialProps) { - mOperations.add( + synchronized (mNonBatchedOperationsLock) { + mNonBatchedOperations.addLast( new CreateViewOperation( - themedContext, - viewReactTag, - viewClassName, - initialProps)); + themedContext, + viewReactTag, + viewClassName, + initialProps)); + } } public void enqueueUpdateProperties(int reactTag, String className, ReactStylesDiffMap props) { @@ -699,6 +703,17 @@ public class UIViewOperationQueue { mOperations = new ArrayList<>(); } + final UIOperation[] nonBatchedOperations; + synchronized (mNonBatchedOperationsLock) { + if (!mNonBatchedOperations.isEmpty()) { + nonBatchedOperations = + mNonBatchedOperations.toArray(new UIOperation[mNonBatchedOperations.size()]); + mNonBatchedOperations.clear(); + } else { + nonBatchedOperations = null; + } + } + if (mViewHierarchyUpdateDebugListener != null) { mViewHierarchyUpdateDebugListener.onViewHierarchyUpdateEnqueued(); } @@ -712,6 +727,14 @@ public class UIViewOperationQueue { .arg("BatchId", batchId) .flush(); try { + // All nonBatchedOperations should be executed before regular operations as + // regular operations may depend on them + if (nonBatchedOperations != null) { + for (UIOperation op : nonBatchedOperations) { + op.execute(); + } + } + if (operations != null) { for (int i = 0; i < operations.size(); i++) { operations.get(i).execute(); @@ -760,12 +783,22 @@ public class UIViewOperationQueue { */ private class DispatchUIFrameCallback extends GuardedChoreographerFrameCallback { + private static final int MIN_TIME_LEFT_IN_FRAME_TO_SCHEDULE_MORE_WORK_MS = 8; + private static final int FRAME_TIME_MS = 16; + private DispatchUIFrameCallback(ReactContext reactContext) { super(reactContext); } @Override public void doFrameGuarded(long frameTimeNanos) { + Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "dispatchNonBatchedUIOperations"); + try { + dispatchPendingNonBatchedOperations(frameTimeNanos); + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } + synchronized (mDispatchRunnablesLock) { for (int i = 0; i < mDispatchUIRunnables.size(); i++) { mDispatchUIRunnables.get(i).run(); @@ -774,7 +807,27 @@ public class UIViewOperationQueue { } ReactChoreographer.getInstance().postFrameCallback( - ReactChoreographer.CallbackType.DISPATCH_UI, this); + ReactChoreographer.CallbackType.DISPATCH_UI, this); + } + + private void dispatchPendingNonBatchedOperations(long frameTimeNanos) { + while (true) { + long timeLeftInFrame = FRAME_TIME_MS - ((System.nanoTime() - frameTimeNanos) / 1000000); + if (timeLeftInFrame < MIN_TIME_LEFT_IN_FRAME_TO_SCHEDULE_MORE_WORK_MS) { + break; + } + + UIOperation nextOperation; + synchronized (mNonBatchedOperationsLock) { + if (mNonBatchedOperations.isEmpty()) { + break; + } + + nextOperation = mNonBatchedOperations.pollFirst(); + } + + nextOperation.execute(); + } } } }