Files
react-native/ReactAndroid/src/main/java/com/facebook/react/flat/MoveProxy.java
Denis Koroskin 848bae2e95 Fix FlatUIImplementation.manageChildren() failing when moveFrom is not sorted
Summary:
Code in FlatUIImplementation.manageChildren() incorrectly assumed that moveFrom is sorted and moveTo is not, which is actually reverse: moveFrom is not sorted, and moveTo is. This means that we need to sort moveFrom before we can traverse it, and that we no longer need to sort moveTo (we did it by moving nodes to mNodesToMove first and then sorting it).

The sorting algorithm used is borrowed from Android implementation of insertion sort used in DualPivotQuicksort.doSort() when number of elements < INSERTION_SORT_THRESHOLD(32) which is 99.999% the case in UIImplementation.manageChildren() (most of the time this array is either empty or only contains 1 element).

Another (very rare) bug this is fixing is that the code only worked for FlatShadowNodes, but not all shadow nodes are FlatShadowNodes (there are rare exceptions, such as ARTShape, ARTGroup etc). New code works with all types of shadow nodes.

Reviewed By: ahmedre

Differential Revision: D2975787
2016-12-19 13:40:23 -08:00

171 lines
4.5 KiB
Java

/**
* 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 javax.annotation.Nullable;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.uimanager.ReactShadowNode;
/**
* Helper class that sorts moveFrom/moveTo arrays in lockstep.
*/
/* package */ final class MoveProxy {
private @Nullable ReadableArray mMoveTo;
private int mSize;
private int[] mMapping = new int[8];
private ReactShadowNode[] mChildren = new ReactShadowNode[4];
/**
* Retuns size of underlying moveTo/moveFrom arrays
*/
public int size() {
return mSize;
}
/**
* Assigns ith child that we want to move if moveFrom was sorted.
*/
public void setChildMoveFrom(int moveFromIndex, ReactShadowNode node) {
mChildren[moveFromToIndex(moveFromIndex)] = node;
}
/**
* Returns ith child that we want to move if moveTo was sorted.
*/
public ReactShadowNode getChildMoveTo(int moveToIndex) {
return mChildren[moveToToIndex(moveToIndex)];
}
/**
* Returns index of the ith child that we want to move if moveFrom was sorted
*/
public int getMoveFrom(int moveFromIndex) {
return moveFromToValue(moveFromIndex);
}
/**
* Returns index of the ith child that we want to move to if moveTo was sorted
*/
public int getMoveTo(int moveToIndex) {
return moveToToValue(moveToIndex);
}
/**
* Initialize MoveProxy with given moveFrom and moveTo arrays.
*/
public void setup(ReadableArray moveFrom, ReadableArray moveTo) {
mMoveTo = moveTo;
if (moveFrom == null) {
setSize(0);
return;
}
int size = moveFrom.size();
int requiredSpace = size + size;
if (mMapping.length < requiredSpace) {
mMapping = new int[requiredSpace];
mChildren = new FlatShadowNode[size];
}
setSize(size);
// Array contains data in the following way:
// [ k0, v0, k1, v1, k2, v2, ... ]
//
// where vi = moveFrom.getInt(ki)
// We don't technically *need* to store vi, but they are accessed so often that it makes sense
// to cache it instead of calling ReadableArray.getInt() all the time.
// Sorting algorithm will reorder ki/vi pairs in such a way that vi < v(i+1)
// Code below is an insertion sort, adapted from DualPivotQuicksort.doSort()
// At each step i, we got the following data:
// [k0, v0, k1, v2, .. k(i-1), v(i-1), unused...]
// where v0 < v1 < v2 ... < v(i-1)
//
// This holds true for step i = 0 (array of size one is sorted)
// Again, k0 = 0, v0 = moveFrom.getInt(k0)
setKeyValue(0, 0, moveFrom.getInt(0));
// At each of the next steps, we grab a new key and walk back until we find first key that is
// less than current, shifting key/value pairs if they are larger than current key.
for (int i = 1; i < size; i++) {
// this is our next key
int current = moveFrom.getInt(i);
// this loop will find correct position for it
int j;
// At this point, array is like this: [ k0, v0, k1, v1, k2, v2, ..., k(i-1), v(i-1), ... ]
for (j = i - 1; j >= 0; j--) {
if (moveFromToValue(j) < current) {
break;
}
// value at index j is < current value, shift that value and its key
setKeyValue(j + 1, moveFromToIndex(j), moveFromToValue(j));
}
setKeyValue(j + 1, i, current);
}
}
/**
* Returns index of ith key in array.
*/
private static int k(int i) {
return i * 2;
}
/**
* Returns index of ith value in array.
*/
private static int v(int i) {
return i * 2 + 1;
}
private void setKeyValue(int index, int key, int value) {
mMapping[k(index)] = key;
mMapping[v(index)] = value;
}
private int moveFromToIndex(int index) {
return mMapping[k(index)];
}
private int moveFromToValue(int index) {
return mMapping[v(index)];
}
private static int moveToToIndex(int index) {
return index;
}
private int moveToToValue(int index) {
return Assertions.assumeNotNull(mMoveTo).getInt(index);
}
private void setSize(int newSize) {
// reset references to null when shrinking to avoid memory leaks
for (int i = newSize; i < mSize; ++i) {
mChildren[i] = null;
}
mSize = newSize;
}
}