Only collect state from nodes that have been updated

Summary: We are currently traversing entire tree every time there is any update to any of the nodes, which is hugely inefficient. Instead, we can early out for nodes that are known to contain no updates. This saves a lof of CPU. This optimization can be turned off, and an Exception will be thrown if there was an unexpected update.

Reviewed By: ahmedre

Differential Revision: D2975706
This commit is contained in:
Denis Koroskin 2016-02-25 20:38:01 -08:00 коммит произвёл Ahmed El-Helw
Родитель 7055c52288
Коммит 42fb9a3d91
3 изменённых файлов: 64 добавлений и 19 удалений

Просмотреть файл

@ -52,6 +52,10 @@ import com.facebook.react.uimanager.annotations.ReactProp;
private int mMoveToIndexInParent; private int mMoveToIndexInParent;
private boolean mClipToBounds = false; private boolean mClipToBounds = false;
private boolean mIsUpdated = true; private boolean mIsUpdated = true;
private float mClipLeft;
private float mClipTop;
private float mClipRight;
private float mClipBottom;
// last OnLayoutEvent info, only used when shouldNotifyOnLayout() is true. // last OnLayoutEvent info, only used when shouldNotifyOnLayout() is true.
private int mLayoutX; private int mLayoutX;
@ -174,6 +178,26 @@ import com.facebook.react.uimanager.annotations.ReactProp;
mIsUpdated = false; mIsUpdated = false;
} }
/* package */ final boolean clipBoundsChanged(
float clipLeft,
float clipTop,
float clipRight,
float clipBottom) {
return mClipLeft != clipLeft || mClipTop != clipTop ||
mClipRight != clipRight || mClipBottom != clipBottom;
}
/* package */ final void setClipBounds(
float clipLeft,
float clipTop,
float clipRight,
float clipBottom) {
mClipLeft = clipLeft;
mClipTop = clipTop;
mClipRight = clipRight;
mClipBottom = clipBottom;
}
/** /**
* Returns an array of DrawCommands to perform during the View's draw pass. * Returns an array of DrawCommands to perform during the View's draw pass.
*/ */

Просмотреть файл

@ -402,23 +402,13 @@ public class FlatUIImplementation extends UIImplementation {
parentNode.addChildAt(childNode, index); parentNode.addChildAt(childNode, index);
} }
@Override
protected void calculateRootLayout(ReactShadowNode cssRoot) {
}
@Override @Override
protected void applyUpdatesRecursive( protected void applyUpdatesRecursive(
ReactShadowNode cssNode, ReactShadowNode cssNode,
float absoluteX, float absoluteX,
float absoluteY, float absoluteY,
EventDispatcher eventDispatcher) { EventDispatcher eventDispatcher) {
FlatRootShadowNode rootNode = (FlatRootShadowNode) cssNode; mStateBuilder.applyUpdates(eventDispatcher, (FlatRootShadowNode) cssNode);
if (!rootNode.needsLayout() && !rootNode.isUpdated()) {
return;
}
super.calculateRootLayout(rootNode);
mStateBuilder.applyUpdates(eventDispatcher, rootNode);
} }
@Override @Override

Просмотреть файл

@ -27,6 +27,8 @@ import com.facebook.react.uimanager.events.EventDispatcher;
*/ */
/* package */ final class StateBuilder { /* package */ final class StateBuilder {
private static final boolean SKIP_UP_TO_DATE_NODES = true;
private static final int[] EMPTY_INT_ARRAY = new int[0]; private static final int[] EMPTY_INT_ARRAY = new int[0];
private final FlatUIViewOperationQueue mOperationsQueue; private final FlatUIViewOperationQueue mOperationsQueue;
@ -225,8 +227,9 @@ import com.facebook.react.uimanager.events.EventDispatcher;
/** /**
* Collects state (DrawCommands) for a given node that will mount to a View. * Collects state (DrawCommands) for a given node that will mount to a View.
* Returns true if this node or any of its descendants that mount to View generated any updates.
*/ */
private void collectStateForMountableNode( private boolean collectStateForMountableNode(
FlatShadowNode node, FlatShadowNode node,
float left, float left,
float top, float top,
@ -236,6 +239,18 @@ import com.facebook.react.uimanager.events.EventDispatcher;
float clipTop, float clipTop,
float clipRight, float clipRight,
float clipBottom) { float clipBottom) {
// Normally, this would be a node.hasNewLayout() check, but we also need to check if a node
// needs onCollectExtraUpdates() call.
boolean hasUpdates = node.hasUpdates();
boolean expectingUpdate = hasUpdates || node.isUpdated() ||
node.clipBoundsChanged(clipLeft, clipTop, clipRight, clipBottom);
if (SKIP_UP_TO_DATE_NODES && !expectingUpdate) {
return false;
}
node.setClipBounds(clipLeft, clipTop, clipRight, clipBottom);
mDrawCommands.start(node.getDrawCommands()); mDrawCommands.start(node.getDrawCommands());
mAttachDetachListeners.start(node.getAttachDetachListeners()); mAttachDetachListeners.start(node.getAttachDetachListeners());
mNodeRegions.start(node.getNodeRegions()); mNodeRegions.start(node.getNodeRegions());
@ -259,7 +274,7 @@ import com.facebook.react.uimanager.events.EventDispatcher;
clipBottom = Float.POSITIVE_INFINITY; clipBottom = Float.POSITIVE_INFINITY;
} }
collectStateRecursively( boolean descendantUpdated = collectStateRecursively(
node, node,
left, left,
top, top,
@ -308,7 +323,7 @@ import com.facebook.react.uimanager.events.EventDispatcher;
nodeRegions); nodeRegions);
} }
if (node.hasUpdates()) { if (hasUpdates) {
node.onCollectExtraUpdates(mOperationsQueue); node.onCollectExtraUpdates(mOperationsQueue);
node.markUpdateSeen(); node.markUpdateSeen();
} }
@ -317,6 +332,14 @@ import com.facebook.react.uimanager.events.EventDispatcher;
if (nativeChildren != null) { if (nativeChildren != null) {
updateNativeChildren(node, node.getNativeChildren(), nativeChildren); updateNativeChildren(node, node.getNativeChildren(), nativeChildren);
} }
boolean updated = shouldUpdateMountState || nativeChildren != null || descendantUpdated;
if (!expectingUpdate && updated) {
throw new RuntimeException("Node " + node.getReactTag() + " updated unexpectedly.");
}
return updated;
} }
private void updateNativeChildren( private void updateNativeChildren(
@ -378,7 +401,7 @@ import com.facebook.react.uimanager.events.EventDispatcher;
/** /**
* Recursively walks node tree from a given node and collects DrawCommands. * Recursively walks node tree from a given node and collects DrawCommands.
*/ */
private void collectStateRecursively( private boolean collectStateRecursively(
FlatShadowNode node, FlatShadowNode node,
float left, float left,
float top, float top,
@ -429,6 +452,7 @@ import com.facebook.react.uimanager.events.EventDispatcher;
roundToPixel(clipRight), roundToPixel(clipRight),
clipBottom); clipBottom);
boolean updated = false;
for (int i = 0, childCount = node.getChildCount(); i != childCount; ++i) { for (int i = 0, childCount = node.getChildCount(); i != childCount; ++i) {
ReactShadowNode child = node.getChildAt(i); ReactShadowNode child = node.getChildAt(i);
if (child.isVirtual()) { if (child.isVirtual()) {
@ -436,7 +460,7 @@ import com.facebook.react.uimanager.events.EventDispatcher;
continue; continue;
} }
processNodeAndCollectState( updated |= processNodeAndCollectState(
(FlatShadowNode) child, (FlatShadowNode) child,
left, left,
top, top,
@ -449,6 +473,8 @@ import com.facebook.react.uimanager.events.EventDispatcher;
} }
node.resetUpdated(); node.resetUpdated();
return updated;
} }
private void markLayoutSeenRecursively(ReactShadowNode node) { private void markLayoutSeenRecursively(ReactShadowNode node) {
@ -463,8 +489,9 @@ import com.facebook.react.uimanager.events.EventDispatcher;
/** /**
* Collects state and updates View boundaries for a given node tree. * Collects state and updates View boundaries for a given node tree.
* Returns true if this node or any of its descendants that mount to View generated any updates.
*/ */
private void processNodeAndCollectState( private boolean processNodeAndCollectState(
FlatShadowNode node, FlatShadowNode node,
float parentLeft, float parentLeft,
float parentTop, float parentTop,
@ -482,6 +509,8 @@ import com.facebook.react.uimanager.events.EventDispatcher;
float right = left + width; float right = left + width;
float bottom = top + height; float bottom = top + height;
final boolean updated;
if (node.mountsToView()) { if (node.mountsToView()) {
ensureBackingViewIsCreated(node); ensureBackingViewIsCreated(node);
@ -494,7 +523,7 @@ import com.facebook.react.uimanager.events.EventDispatcher;
parentClipBottom)); parentClipBottom));
} }
collectStateForMountableNode( updated = collectStateForMountableNode(
node, node,
left - left, left - left,
top - top, top - top,
@ -509,7 +538,7 @@ import com.facebook.react.uimanager.events.EventDispatcher;
updateViewBounds(node, left, top, right, bottom); updateViewBounds(node, left, top, right, bottom);
} }
} else { } else {
collectStateRecursively( updated = collectStateRecursively(
node, node,
left, left,
top, top,
@ -523,6 +552,8 @@ import com.facebook.react.uimanager.events.EventDispatcher;
false); false);
addNodeRegion(node, left, top, right, bottom); addNodeRegion(node, left, top, right, bottom);
} }
return updated;
} }
private void updateViewPadding(AndroidView androidView, int reactTag) { private void updateViewPadding(AndroidView androidView, int reactTag) {