Extract DrawCommands tracking in StateBuilder into a helper class.

Summary: @public This is a pure refactoring diff makes `StateBuilder` code a little bit easier to read. This gets increasingly important as new features with similar logic are added to `StateBuilder`.

Reviewed By: sriramramani

Differential Revision: D2564342
This commit is contained in:
Denis Koroskin 2015-12-07 17:34:54 -08:00 committed by Ahmed El-Helw
parent fbe1b61fe1
commit 5b182fa0ca
2 changed files with 182 additions and 48 deletions

View File

@ -0,0 +1,175 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.flat;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.lang.reflect.Array;
/**
* Helper class that supports 3 main operations: start(), add() an element and finish().
*
* When started, it takes a baseline array to compare to. When adding a new element, it checks
* whether a corresponding element in baseline array is the same. On finish(), it will return null
* if baseline array contains exactly the same elements that were added with a sequence of add()
* calls, or a new array the recorded elements:
*
* Example 1:
* -----
* start([A])
* add(A)
* finish() -> null (because [A] == [A])
*
* Example 2:
* ----
* start([A])
* add(B)
* finish() -> [B] (because [A] != [B])
*
* It is important that start/finish can be nested:
* ----
* start([A])
* add(A)
* start([B])
* add(B)
* finish() -> null
* add(C)
* finish() -> [A, C]
*
* StateBuilder is using this class to check if e.g. a DrawCommand list for a given View needs to be
* updated.
*/
/* package */ final class ElementsList<E> {
private static final class Scope {
Object[] elements;
int index;
int size;
}
private final ArrayList<Scope> mScopesStack = new ArrayList<>();
private final ArrayDeque<E> mElements = new ArrayDeque<>();
private final E[] mEmptyArray;
private Scope mCurrentScope = null;
private int mScopeIndex = 0;
public ElementsList(E[] emptyArray) {
mEmptyArray = emptyArray;
mScopesStack.add(mCurrentScope);
}
/**
* Starts a new scope.
*/
public void start(Object[] elements) {
pushScope();
Scope scope = getCurrentScope();
scope.elements = elements;
scope.index = 0;
scope.size = mElements.size();
}
/**
* Finished current scope, and returns null if there were no changes recorded, or a new array
* containing all the recorded elements otherwise.
*/
public E[] finish() {
Scope scope = getCurrentScope();
popScope();
E[] result = null;
int size = mElements.size() - scope.size;
if (scope.index != scope.elements.length) {
result = extractElements(size);
} else {
// downsize
for (int i = 0; i < size; ++i) {
mElements.pollLast();
}
}
// to prevent leaks
scope.elements = null;
return result;
}
/**
* Adds a new element to the list. This method can be optimized to avoid inserts on same elements.
*/
public void add(E element) {
Scope scope = getCurrentScope();
if (scope.index < scope.elements.length &&
scope.elements[scope.index] == element) {
++scope.index;
} else {
scope.index = Integer.MAX_VALUE;
}
mElements.add(element);
}
/**
* Resets all references to the elements to null to avoid memory leaks.
*/
public void clear() {
if (getCurrentScope() != null) {
throw new RuntimeException("Must call finish() for every start() call being made.");
}
mElements.clear();
}
/**
* Extracts last size elements into an array.
*/
private E[] extractElements(int size) {
if (size == 0) {
// avoid allocating empty array
return mEmptyArray;
}
E[] elements = (E[]) Array.newInstance(mEmptyArray.getClass().getComponentType(), size);
for (int i = size - 1; i >= 0; --i) {
elements[i] = mElements.pollLast();
}
return elements;
}
/**
* Saves current scope in a stack.
*/
private void pushScope() {
++mScopeIndex;
if (mScopeIndex == mScopesStack.size()) {
mCurrentScope = new Scope();
mScopesStack.add(mCurrentScope);
} else {
mCurrentScope = mScopesStack.get(mScopeIndex);
}
}
/**
* Restores last save current scope.
*/
private void popScope() {
--mScopeIndex;
mCurrentScope = mScopesStack.get(mScopeIndex);
}
/**
* Returns current scope.
*/
private Scope getCurrentScope() {
return mCurrentScope;
}
}

View File

@ -9,8 +9,6 @@
package com.facebook.react.flat;
import java.util.ArrayDeque;
/**
* Shadow node hierarchy by itself cannot display UI, it is only a representation of what UI should
* be from JavaScript perspective. StateBuilder is a helper class that can walk the shadow node tree
@ -21,10 +19,8 @@ import java.util.ArrayDeque;
private final FlatUIViewOperationQueue mOperationsQueue;
// DrawCommands
private final ArrayDeque<DrawCommand> mDrawCommands = new ArrayDeque<>();
private DrawCommand[] mPreviousDrawCommands = DrawCommand.EMPTY_ARRAY;
private int mPreviousDrawCommandsIndex;
private final ElementsList<DrawCommand> mDrawCommands =
new ElementsList(DrawCommand.EMPTY_ARRAY);
/* package */ StateBuilder(FlatUIViewOperationQueue operationsQueue) {
mOperationsQueue = operationsQueue;
@ -42,14 +38,7 @@ import java.util.ArrayDeque;
* Adds a DrawCommand for current mountable node.
*/
/* package */ void addDrawCommand(AbstractDrawCommand drawCommand) {
if (mPreviousDrawCommandsIndex < mPreviousDrawCommands.length &&
mPreviousDrawCommands[mPreviousDrawCommandsIndex] == drawCommand) {
++mPreviousDrawCommandsIndex;
} else {
mPreviousDrawCommandsIndex = mPreviousDrawCommands.length + 1;
}
mDrawCommands.addLast(drawCommand);
mDrawCommands.add(drawCommand);
}
/**
@ -86,47 +75,17 @@ import java.util.ArrayDeque;
int tag,
float width,
float height) {
// save
int d = mDrawCommands.size();
DrawCommand[] previousDrawCommands = mPreviousDrawCommands;
int previousDrawCommandsIndex = mPreviousDrawCommandsIndex;
// reset
mPreviousDrawCommands = node.getDrawCommands();
mPreviousDrawCommandsIndex = 0;
mDrawCommands.start(node.getDrawCommands());
collectStateRecursively(node, 0, 0, width, height);
if (mPreviousDrawCommandsIndex != mPreviousDrawCommands.length) {
// DrawCommands changes, need to re-mount them and re-draw the View.
DrawCommand[] drawCommands = extractDrawCommands(d);
final DrawCommand[] drawCommands = mDrawCommands.finish();
if (drawCommands != null) {
// DrawCommands changed, need to re-mount them and re-draw the View.
node.setDrawCommands(drawCommands);
mOperationsQueue.enqueueUpdateMountState(tag, drawCommands);
}
// restore
mPreviousDrawCommandsIndex = previousDrawCommandsIndex;
mPreviousDrawCommands = previousDrawCommands;
}
/**
* Returns all DrawCommands collectes so far starting from a given index.
*/
private DrawCommand[] extractDrawCommands(int lowerBound) {
int upperBound = mDrawCommands.size();
int size = upperBound - lowerBound;
if (size == 0) {
// avoid allocating empty array
return DrawCommand.EMPTY_ARRAY;
}
DrawCommand[] drawCommands = new DrawCommand[size];
for (int i = 0; i < size; ++i) {
drawCommands[i] = mDrawCommands.pollFirst();
}
return drawCommands;
}
/**