Experiment: use int buffer to represent MountItems instead of concrete classes

Summary:
I've had my eyes on this optimization for a while: during TTRC especially, but really during any heavy render path, Fabric will create hundreds to thousands of MountItem class instances in order to construct a BatchMountItem.

This results in: hundreds/thousands of round-trip JNI calls, hundreds/thousands of Object allocations, etc. This will also result in increased memory and GC pressure.

Theoretically, by reducing the number of JNI calls and reducing allocations, we may be able to get some small wins in memory and CPU usage during very hot paths.

I am motivated to do this, in part, to indirectly measure the cost of JNI calls as well as allocating many objects vs an int buffer. I am unaware of such a measurement that we can use to make architectural decisions for React Native Android overall.

The other reason this could be a positive change: if it's successful and we can delete the old path, we'll be able to delete a bunch of Java classes entirely which is great for APK size wins.

Changelog: [Internal]

Reviewed By: shergin

Differential Revision: D24631230

fbshipit-source-id: 86a46ffcaef4ecbec2e608ed226aed0b5c4b832e
This commit is contained in:
Joshua Gross 2020-10-30 20:38:07 -07:00 коммит произвёл Facebook GitHub Bot
Родитель d7b3daa643
Коммит 2cd89d040b
6 изменённых файлов: 976 добавлений и 10 удалений

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

@ -43,7 +43,7 @@ public class FabricComponents {
}
/** @return the name of component in the Fabric environment */
static String getFabricComponentName(String componentName) {
public static String getFabricComponentName(String componentName) {
String component = sComponentNames.get(componentName);
return component != null ? component : componentName;
}

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

@ -64,6 +64,7 @@ import com.facebook.react.fabric.mounting.mountitems.DispatchCommandMountItem;
import com.facebook.react.fabric.mounting.mountitems.DispatchIntCommandMountItem;
import com.facebook.react.fabric.mounting.mountitems.DispatchStringCommandMountItem;
import com.facebook.react.fabric.mounting.mountitems.InsertMountItem;
import com.facebook.react.fabric.mounting.mountitems.IntBufferBatchMountItem;
import com.facebook.react.fabric.mounting.mountitems.MountItem;
import com.facebook.react.fabric.mounting.mountitems.PreAllocateViewMountItem;
import com.facebook.react.fabric.mounting.mountitems.RemoveDeleteMultiMountItem;
@ -446,6 +447,20 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
return new BatchMountItem(rootTag, items, size, commitNumber);
}
@DoNotStrip
@SuppressWarnings("unused")
@AnyThread
@ThreadConfined(ANY)
private MountItem createIntBufferBatchMountItem(
int rootTag, int[] intBuffer, Object[] objBuffer, int commitNumber) {
// This could be null if teardown/navigation away from a surface on the main thread happens
// while a commit is being processed in a different thread. By contract we expect this to be
// possible at teardown, but this race should *never* happen at startup.
@Nullable ThemedReactContext reactContext = mReactContextForRootTag.get(rootTag);
return new IntBufferBatchMountItem(rootTag, reactContext, intBuffer, objBuffer, commitNumber);
}
@DoNotStrip
@SuppressWarnings("unused")
private NativeArray measureLines(
@ -602,7 +617,7 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
@AnyThread
@ThreadConfined(ANY)
private void scheduleMountItem(
@NonNull final MountItem mountItem,
@Nullable final MountItem mountItem,
int commitNumber,
long commitStartTime,
long diffStartTime,
@ -614,8 +629,13 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
// When Binding.cpp calls scheduleMountItems during a commit phase, it always calls with
// a BatchMountItem. No other sites call into this with a BatchMountItem, and Binding.cpp only
// calls scheduleMountItems with a BatchMountItem.
boolean isBatchMountItem = mountItem instanceof BatchMountItem;
boolean shouldSchedule = !(isBatchMountItem && ((BatchMountItem) mountItem).getSize() == 0);
boolean isClassicBatchMountItem = mountItem instanceof BatchMountItem;
boolean isIntBufferMountItem = mountItem instanceof IntBufferBatchMountItem;
boolean isBatchMountItem = isClassicBatchMountItem || isIntBufferMountItem;
boolean shouldSchedule =
(isClassicBatchMountItem && ((BatchMountItem) mountItem).shouldSchedule())
|| (isIntBufferMountItem && ((IntBufferBatchMountItem) mountItem).shouldSchedule())
|| (!isBatchMountItem && mountItem != null);
// In case of sync rendering, this could be called on the UI thread. Otherwise,
// it should ~always be called on the JS thread.
@ -631,11 +651,10 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
mDispatchViewUpdatesTime = SystemClock.uptimeMillis();
}
if (shouldSchedule) {
if (shouldSchedule && mountItem != null) {
synchronized (mMountItemsLock) {
mMountItems.add(mountItem);
}
if (UiThreadUtil.isOnUiThread()) {
// We only read these flags on the UI thread.
tryDispatchMountItems();

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

@ -53,6 +53,173 @@ struct RemoveDeleteMetadata {
} // namespace
CppMountItem CppMountItem::CreateMountItem(ShadowView shadowView) {
return {CppMountItem::Type::Create, {}, {}, shadowView, -1};
}
CppMountItem CppMountItem::DeleteMountItem(ShadowView shadowView) {
return {CppMountItem::Type::Delete, {}, shadowView, {}, -1};
}
CppMountItem CppMountItem::InsertMountItem(
ShadowView parentView,
ShadowView shadowView,
int index) {
return {CppMountItem::Type::Insert, parentView, {}, shadowView, index};
}
CppMountItem CppMountItem::RemoveMountItem(
ShadowView parentView,
ShadowView shadowView,
int index) {
return {CppMountItem::Type::Remove, parentView, shadowView, {}, index};
}
CppMountItem CppMountItem::UpdatePropsMountItem(ShadowView shadowView) {
return {CppMountItem::Type::UpdateProps, {}, {}, shadowView, -1};
}
CppMountItem CppMountItem::UpdateStateMountItem(ShadowView shadowView) {
return {CppMountItem::Type::UpdateState, {}, {}, shadowView, -1};
}
CppMountItem CppMountItem::UpdateLayoutMountItem(ShadowView shadowView) {
return {CppMountItem::Type::UpdateLayout, {}, {}, shadowView, -1};
}
CppMountItem CppMountItem::UpdateEventEmitterMountItem(ShadowView shadowView) {
return {CppMountItem::Type::UpdateEventEmitter, {}, {}, shadowView, -1};
}
CppMountItem CppMountItem::UpdatePaddingMountItem(ShadowView shadowView) {
return {CppMountItem::Type::UpdatePadding, {}, {}, shadowView, -1};
}
static inline int getIntBufferSizeForType(CppMountItem::Type mountItemType) {
if (mountItemType == CppMountItem::Type::Create) {
return 2; // tag, isLayoutable
} else if (mountItemType == CppMountItem::Type::Insert) {
return 3; // tag, parentTag, index
} else if (mountItemType == CppMountItem::Type::Remove) {
return 3; // tag, parentTag, index
} else if (mountItemType == CppMountItem::Type::Delete) {
return 1; // tag
} else if (mountItemType == CppMountItem::Type::UpdateProps) {
return 1; // tag
} else if (mountItemType == CppMountItem::Type::UpdateState) {
return 1; // tag
} else if (mountItemType == CppMountItem::Type::UpdatePadding) {
return 5; // tag, top, left, bottom, right
} else if (mountItemType == CppMountItem::Type::UpdateLayout) {
return 6; // tag, x, y, w, h, layoutDirection
} else if (mountItemType == CppMountItem::Type::UpdateEventEmitter) {
return 1; // tag
} else {
return -1;
}
}
static inline void updateBufferSizes(
CppMountItem::Type mountItemType,
int numInstructions,
int &batchMountItemIntsSize,
int &batchMountItemObjectsSize) {
if (numInstructions == 0) {
return;
}
batchMountItemIntsSize +=
numInstructions == 1 ? 1 : 2; // instructionType[, numInstructions]
batchMountItemIntsSize +=
numInstructions * getIntBufferSizeForType(mountItemType);
if (mountItemType == CppMountItem::Type::UpdateProps) {
batchMountItemObjectsSize +=
numInstructions; // props object * numInstructions
} else if (mountItemType == CppMountItem::Type::UpdateState) {
batchMountItemObjectsSize +=
numInstructions; // state object * numInstructions
} else if (mountItemType == CppMountItem::Type::UpdateEventEmitter) {
batchMountItemObjectsSize +=
numInstructions; // EventEmitter object * numInstructions
}
}
static inline void computeBufferSizes(int& batchMountItemIntsSize, int& batchMountItemObjectsSize,
std::vector<CppMountItem>& cppCommonMountItems,
std::vector<CppMountItem>& cppDeleteMountItems,
std::vector<CppMountItem>& cppUpdatePropsMountItems,
std::vector<CppMountItem>& cppUpdateStateMountItems,
std::vector<CppMountItem>& cppUpdatePaddingMountItems,
std::vector<CppMountItem>& cppUpdateLayoutMountItems,
std::vector<CppMountItem>& cppUpdateEventEmitterMountItems) {
CppMountItem::Type lastType = CppMountItem::Type::Undefined;
int numSameType = 0;
for (const auto &mountItem : cppCommonMountItems) {
const auto &mountItemType = mountItem.type;
if (lastType == mountItemType) {
numSameType++;
if (numSameType == 2) {
batchMountItemIntsSize += 1; // numInstructions
}
} else {
numSameType = 1;
lastType = mountItemType;
batchMountItemIntsSize += 1; // instructionType
}
batchMountItemIntsSize += getIntBufferSizeForType(mountItemType);
if (mountItemType == CppMountItem::Type::Create) {
batchMountItemObjectsSize += 3; // component name, props, state
}
}
updateBufferSizes(
CppMountItem::Type::UpdateProps,
cppUpdatePropsMountItems.size(),
batchMountItemIntsSize,
batchMountItemObjectsSize);
updateBufferSizes(
CppMountItem::Type::UpdateState,
cppUpdateStateMountItems.size(),
batchMountItemIntsSize,
batchMountItemObjectsSize);
updateBufferSizes(
CppMountItem::Type::UpdatePadding,
cppUpdatePaddingMountItems.size(),
batchMountItemIntsSize,
batchMountItemObjectsSize);
updateBufferSizes(
CppMountItem::Type::UpdateLayout,
cppUpdateLayoutMountItems.size(),
batchMountItemIntsSize,
batchMountItemObjectsSize);
updateBufferSizes(
CppMountItem::Type::UpdateEventEmitter,
cppUpdateEventEmitterMountItems.size(),
batchMountItemIntsSize,
batchMountItemObjectsSize);
updateBufferSizes(
CppMountItem::Type::Delete,
cppDeleteMountItems.size(),
batchMountItemIntsSize,
batchMountItemObjectsSize);
}
static inline void writeIntBufferTypePreamble(
int mountItemType,
int numItems,
_JNIEnv *env,
jintArray &intBufferArray,
int &intBufferPosition) {
jint temp[2];
if (numItems == 1) {
temp[0] = mountItemType;
env->SetIntArrayRegion(intBufferArray, intBufferPosition, 1, temp);
intBufferPosition += 1;
} else {
temp[0] = mountItemType | CppMountItem::Type::Multiple;
temp[1] = numItems;
env->SetIntArrayRegion(intBufferArray, intBufferPosition, 2, temp);
intBufferPosition += 2;
}
}
jni::local_ref<Binding::jhybriddata> Binding::initHybrid(
jni::alias_ref<jclass>) {
return makeCxxInstance();
@ -278,6 +445,9 @@ void Binding::installFabricUIManager(
!reactNativeConfig_->getBool(
"react_fabric:enabled_layout_animations_android");
useIntBufferBatchMountItem_ = reactNativeConfig_->getBool(
"react_fabric:use_int_buffer_batch_mountitem_android");
disablePreallocateViews_ = reactNativeConfig_->getBool(
"react_fabric:disabled_view_preallocation_android");
@ -375,8 +545,6 @@ local_ref<JMountItem::javaobject> createUpdatePropsMountItem(
const jni::global_ref<jobject> &javaUIManager,
const ShadowViewMutation &mutation) {
auto shadowView = mutation.newChildShadowView;
auto newViewProps =
*std::dynamic_pointer_cast<const ViewProps>(shadowView.props);
// TODO: move props from map to a typed object.
auto newProps = shadowView.props->rawProps;
@ -570,10 +738,469 @@ local_ref<JMountItem::javaobject> createCreateMountItem(
isLayoutable);
}
void Binding::schedulerDidFinishTransactionIntBuffer(
MountingCoordinator::Shared const &mountingCoordinator) {
std::lock_guard<std::recursive_mutex> lock(commitMutex_);
SystraceSection s(
"FabricUIManagerBinding::schedulerDidFinishTransactionIntBuffer");
auto finishTransactionStartTime = telemetryTimePointNow();
jni::global_ref<jobject> localJavaUIManager = getJavaUIManager();
if (!localJavaUIManager) {
LOG(ERROR)
<< "Binding::schedulerDidFinishTransaction: JavaUIManager disappeared";
return;
}
auto mountingTransaction = mountingCoordinator->pullTransaction();
if (!mountingTransaction.has_value()) {
return;
}
auto env = Environment::current();
auto telemetry = mountingTransaction->getTelemetry();
auto surfaceId = mountingTransaction->getSurfaceId();
auto &mutations = mountingTransaction->getMutations();
auto revisionNumber = telemetry.getRevisionNumber();
std::vector<CppMountItem> cppCommonMountItems;
std::vector<CppMountItem> cppDeleteMountItems;
std::vector<CppMountItem> cppUpdatePropsMountItems;
std::vector<CppMountItem> cppUpdateStateMountItems;
std::vector<CppMountItem> cppUpdatePaddingMountItems;
std::vector<CppMountItem> cppUpdateLayoutMountItems;
std::vector<CppMountItem> cppUpdateEventEmitterMountItems;
for (const auto &mutation : mutations) {
const auto &parentShadowView = mutation.parentShadowView;
const auto &oldChildShadowView = mutation.oldChildShadowView;
const auto &newChildShadowView = mutation.newChildShadowView;
auto &mutationType = mutation.type;
auto &index = mutation.index;
bool isVirtual = newChildShadowView.layoutMetrics == EmptyLayoutMetrics &&
oldChildShadowView.layoutMetrics == EmptyLayoutMetrics;
switch (mutationType) {
case ShadowViewMutation::Create: {
if (disablePreallocateViews_ ||
newChildShadowView.props->revision > 1) {
cppCommonMountItems.push_back(
CppMountItem::CreateMountItem(newChildShadowView));
}
break;
}
case ShadowViewMutation::Remove: {
if (!isVirtual) {
cppCommonMountItems.push_back(CppMountItem::RemoveMountItem(
parentShadowView, oldChildShadowView, index));
}
break;
}
case ShadowViewMutation::Delete: {
cppDeleteMountItems.push_back(
CppMountItem::DeleteMountItem(oldChildShadowView));
break;
}
case ShadowViewMutation::Update: {
if (!isVirtual) {
if (oldChildShadowView.props != newChildShadowView.props) {
cppUpdatePropsMountItems.push_back(
CppMountItem::UpdatePropsMountItem(newChildShadowView));
}
if (oldChildShadowView.state != newChildShadowView.state) {
cppUpdateStateMountItems.push_back(
CppMountItem::UpdateStateMountItem(newChildShadowView));
}
// Padding: padding mountItems must be executed before layout props
// are updated in the view. This is necessary to ensure that events
// (resulting from layout changes) are dispatched with the correct
// padding information.
if (oldChildShadowView.layoutMetrics.contentInsets !=
newChildShadowView.layoutMetrics.contentInsets) {
cppUpdatePaddingMountItems.push_back(
CppMountItem::UpdatePaddingMountItem(newChildShadowView));
}
if (oldChildShadowView.layoutMetrics !=
newChildShadowView.layoutMetrics) {
cppUpdateLayoutMountItems.push_back(
CppMountItem::UpdateLayoutMountItem(
mutation.newChildShadowView));
}
}
if (oldChildShadowView.eventEmitter !=
newChildShadowView.eventEmitter) {
cppUpdateEventEmitterMountItems.push_back(
CppMountItem::UpdateEventEmitterMountItem(
mutation.newChildShadowView));
}
break;
}
case ShadowViewMutation::Insert: {
if (!isVirtual) {
// Insert item
cppCommonMountItems.push_back(CppMountItem::InsertMountItem(
parentShadowView, newChildShadowView, index));
if (disablePreallocateViews_ ||
newChildShadowView.props->revision > 1) {
cppUpdatePropsMountItems.push_back(
CppMountItem::UpdatePropsMountItem(newChildShadowView));
}
// State
if (newChildShadowView.state) {
cppUpdateStateMountItems.push_back(
CppMountItem::UpdateStateMountItem(newChildShadowView));
}
// Padding: padding mountItems must be executed before layout props
// are updated in the view. This is necessary to ensure that events
// (resulting from layout changes) are dispatched with the correct
// padding information.
cppUpdatePaddingMountItems.push_back(
CppMountItem::UpdatePaddingMountItem(
mutation.newChildShadowView));
// Layout
cppUpdateLayoutMountItems.push_back(
CppMountItem::UpdateLayoutMountItem(mutation.newChildShadowView));
}
// EventEmitter
cppUpdateEventEmitterMountItems.push_back(
CppMountItem::UpdateEventEmitterMountItem(
mutation.newChildShadowView));
break;
}
default: {
break;
}
}
}
// We now have all the information we need, including ordering of mount items,
// to know exactly how much space must be allocated
int batchMountItemIntsSize = 0;
int batchMountItemObjectsSize = 0;
computeBufferSizes(batchMountItemIntsSize, batchMountItemObjectsSize, cppCommonMountItems,
cppDeleteMountItems,
cppUpdatePropsMountItems,
cppUpdateStateMountItems,
cppUpdatePaddingMountItems,
cppUpdateLayoutMountItems,
cppUpdateEventEmitterMountItems);
static auto createMountItemsIntBufferBatchContainer =
jni::findClassStatic(Binding::UIManagerJavaDescriptor)
->getMethod<alias_ref<JMountItem>(
jint, jintArray, jtypeArray<jobject>, jint)>(
"createIntBufferBatchMountItem");
static auto scheduleMountItem =
jni::findClassStatic(Binding::UIManagerJavaDescriptor)
->getMethod<void(
JMountItem::javaobject,
jint,
jlong,
jlong,
jlong,
jlong,
jlong,
jlong,
jlong)>("scheduleMountItem");
if (batchMountItemIntsSize == 0) {
auto finishTransactionEndTime = telemetryTimePointNow();
scheduleMountItem(
localJavaUIManager,
nullptr,
telemetry.getRevisionNumber(),
telemetryTimePointToMilliseconds(telemetry.getCommitStartTime()),
telemetryTimePointToMilliseconds(telemetry.getDiffStartTime()),
telemetryTimePointToMilliseconds(telemetry.getDiffEndTime()),
telemetryTimePointToMilliseconds(telemetry.getLayoutStartTime()),
telemetryTimePointToMilliseconds(telemetry.getLayoutEndTime()),
telemetryTimePointToMilliseconds(finishTransactionStartTime),
telemetryTimePointToMilliseconds(finishTransactionEndTime));
return;
}
// Allocate the intBuffer and object array, now that we know exact sizes
// necessary
// TODO: don't allocate at all if size is zero
jintArray intBufferArray = env->NewIntArray(batchMountItemIntsSize);
local_ref<JArrayClass<jobject>> objBufferArray =
JArrayClass<jobject>::newArray(batchMountItemObjectsSize);
// Fill in arrays
int intBufferPosition = 0;
int objBufferPosition = 0;
int prevMountItemType = -1;
jint temp[6];
for (int i = 0; i < cppCommonMountItems.size(); i++) {
const auto &mountItem = cppCommonMountItems[i];
const auto &mountItemType = mountItem.type;
// Get type here, and count forward how many items of this type are in a
// row. Write preamble to any common type here.
if (prevMountItemType != mountItemType) {
int numSameItemTypes = 1;
for (int j = i + 1; j < cppCommonMountItems.size() &&
cppCommonMountItems[j].type == mountItemType;
j++) {
numSameItemTypes++;
}
writeIntBufferTypePreamble(
mountItemType,
numSameItemTypes,
env,
intBufferArray,
intBufferPosition);
}
prevMountItemType = mountItemType;
// TODO: multi-create, multi-insert, etc
if (mountItemType == CppMountItem::Type::Create) {
local_ref<JString> componentName =
getPlatformComponentName(mountItem.newChildShadowView);
int isLayoutable =
mountItem.newChildShadowView.layoutMetrics != EmptyLayoutMetrics ? 1
: 0;
local_ref<ReadableMap::javaobject> props =
castReadableMap(ReadableNativeMap::newObjectCxxArgs(
mountItem.newChildShadowView.props->rawProps));
// Do not hold onto Java object from C
// We DO want to hold onto C object from Java, since we don't know the
// lifetime of the Java object
local_ref<StateWrapperImpl::JavaPart> javaStateWrapper = nullptr;
if (mountItem.newChildShadowView.state != nullptr) {
javaStateWrapper = StateWrapperImpl::newObjectJavaArgs();
StateWrapperImpl *cStateWrapper = cthis(javaStateWrapper);
cStateWrapper->state_ = mountItem.newChildShadowView.state;
}
temp[0] = mountItem.newChildShadowView.tag;
temp[1] = isLayoutable;
env->SetIntArrayRegion(intBufferArray, intBufferPosition, 2, temp);
intBufferPosition += 2;
(*objBufferArray)[objBufferPosition++] = componentName.get();
(*objBufferArray)[objBufferPosition++] = props.get();
(*objBufferArray)[objBufferPosition++] =
javaStateWrapper != nullptr ? javaStateWrapper.get() : nullptr;
} else if (mountItemType == CppMountItem::Type::Insert) {
temp[0] = mountItem.newChildShadowView.tag;
temp[1] = mountItem.parentShadowView.tag;
temp[2] = mountItem.index;
env->SetIntArrayRegion(intBufferArray, intBufferPosition, 3, temp);
intBufferPosition += 3;
} else if (mountItemType == CppMountItem::Remove) {
temp[0] = mountItem.oldChildShadowView.tag;
temp[1] = mountItem.parentShadowView.tag;
temp[2] = mountItem.index;
env->SetIntArrayRegion(intBufferArray, intBufferPosition, 3, temp);
intBufferPosition += 3;
} else {
LOG(ERROR) << "Unexpected CppMountItem type";
}
}
if (cppUpdatePropsMountItems.size() > 0) {
writeIntBufferTypePreamble(
CppMountItem::Type::UpdateProps,
cppUpdatePropsMountItems.size(),
env,
intBufferArray,
intBufferPosition);
for (const auto &mountItem : cppUpdatePropsMountItems) {
temp[0] = mountItem.newChildShadowView.tag;
env->SetIntArrayRegion(intBufferArray, intBufferPosition, 1, temp);
intBufferPosition += 1;
auto newProps = mountItem.newChildShadowView.props->rawProps;
local_ref<ReadableMap::javaobject> newPropsReadableMap =
castReadableMap(ReadableNativeMap::newObjectCxxArgs(newProps));
(*objBufferArray)[objBufferPosition++] = newPropsReadableMap.get();
}
}
if (cppUpdateStateMountItems.size() > 0) {
writeIntBufferTypePreamble(
CppMountItem::Type::UpdateState,
cppUpdateStateMountItems.size(),
env,
intBufferArray,
intBufferPosition);
for (const auto &mountItem : cppUpdateStateMountItems) {
temp[0] = mountItem.newChildShadowView.tag;
env->SetIntArrayRegion(intBufferArray, intBufferPosition, 1, temp);
intBufferPosition += 1;
auto state = mountItem.newChildShadowView.state;
// Do not hold onto Java object from C
// We DO want to hold onto C object from Java, since we don't know the
// lifetime of the Java object
local_ref<StateWrapperImpl::JavaPart> javaStateWrapper = nullptr;
if (state != nullptr) {
javaStateWrapper = StateWrapperImpl::newObjectJavaArgs();
StateWrapperImpl *cStateWrapper = cthis(javaStateWrapper);
cStateWrapper->state_ = state;
}
(*objBufferArray)[objBufferPosition++] =
(javaStateWrapper != nullptr ? javaStateWrapper.get() : nullptr);
}
}
if (cppUpdatePaddingMountItems.size() > 0) {
writeIntBufferTypePreamble(
CppMountItem::Type::UpdatePadding,
cppUpdatePaddingMountItems.size(),
env,
intBufferArray,
intBufferPosition);
for (const auto &mountItem : cppUpdatePaddingMountItems) {
auto layoutMetrics = mountItem.newChildShadowView.layoutMetrics;
auto pointScaleFactor = layoutMetrics.pointScaleFactor;
auto contentInsets = layoutMetrics.contentInsets;
int left = floor(contentInsets.left * pointScaleFactor);
int top = floor(contentInsets.top * pointScaleFactor);
int right = floor(contentInsets.right * pointScaleFactor);
int bottom = floor(contentInsets.bottom * pointScaleFactor);
temp[0] = mountItem.newChildShadowView.tag;
temp[1] = left;
temp[2] = top;
temp[3] = right;
temp[4] = bottom;
env->SetIntArrayRegion(intBufferArray, intBufferPosition, 5, temp);
intBufferPosition += 5;
}
}
if (cppUpdateLayoutMountItems.size() > 0) {
writeIntBufferTypePreamble(
CppMountItem::Type::UpdateLayout,
cppUpdateLayoutMountItems.size(),
env,
intBufferArray,
intBufferPosition);
for (const auto &mountItem : cppUpdateLayoutMountItems) {
auto layoutMetrics = mountItem.newChildShadowView.layoutMetrics;
auto pointScaleFactor = layoutMetrics.pointScaleFactor;
auto frame = layoutMetrics.frame;
int x = round(frame.origin.x * pointScaleFactor);
int y = round(frame.origin.y * pointScaleFactor);
int w = round(frame.size.width * pointScaleFactor);
int h = round(frame.size.height * pointScaleFactor);
int layoutDirection =
toInt(mountItem.newChildShadowView.layoutMetrics.layoutDirection);
temp[0] = mountItem.newChildShadowView.tag;
temp[1] = x;
temp[2] = y;
temp[3] = w;
temp[4] = h;
temp[5] = layoutDirection;
env->SetIntArrayRegion(intBufferArray, intBufferPosition, 6, temp);
intBufferPosition += 6;
}
}
if (cppUpdateEventEmitterMountItems.size() > 0) {
writeIntBufferTypePreamble(
CppMountItem::Type::UpdateEventEmitter,
cppUpdateEventEmitterMountItems.size(),
env,
intBufferArray,
intBufferPosition);
for (const auto &mountItem : cppUpdateEventEmitterMountItems) {
temp[0] = mountItem.newChildShadowView.tag;
env->SetIntArrayRegion(intBufferArray, intBufferPosition, 1, temp);
intBufferPosition += 1;
SharedEventEmitter eventEmitter =
mountItem.newChildShadowView.eventEmitter;
// Do not hold a reference to javaEventEmitter from the C++ side.
auto javaEventEmitter = EventEmitterWrapper::newObjectJavaArgs();
EventEmitterWrapper *cEventEmitter = cthis(javaEventEmitter);
cEventEmitter->eventEmitter = eventEmitter;
(*objBufferArray)[objBufferPosition++] = javaEventEmitter.get();
}
}
// Write deletes last - so that all prop updates, etc, for the tag in the same
// batch don't fail. Without additional machinery, moving deletes here
// requires that the differ never produces "DELETE...CREATE" in that order for
// the same tag. It's nice to be able to batch all similar operations together
// for space efficiency.
if (cppDeleteMountItems.size() > 0) {
writeIntBufferTypePreamble(
CppMountItem::Type::Delete,
cppDeleteMountItems.size(),
env,
intBufferArray,
intBufferPosition);
for (const auto &mountItem : cppDeleteMountItems) {
temp[0] = mountItem.oldChildShadowView.tag;
env->SetIntArrayRegion(intBufferArray, intBufferPosition, 1, temp);
intBufferPosition += 1;
}
}
// If there are no items, we pass a nullptr instead of passing the object
// through the JNI
auto batch = createMountItemsIntBufferBatchContainer(
localJavaUIManager,
surfaceId,
batchMountItemIntsSize == 0 ? nullptr : intBufferArray,
batchMountItemObjectsSize == 0 ? nullptr : objBufferArray.get(),
revisionNumber);
auto finishTransactionEndTime = telemetryTimePointNow();
scheduleMountItem(
localJavaUIManager,
batch.get(),
telemetry.getRevisionNumber(),
telemetryTimePointToMilliseconds(telemetry.getCommitStartTime()),
telemetryTimePointToMilliseconds(telemetry.getDiffStartTime()),
telemetryTimePointToMilliseconds(telemetry.getDiffEndTime()),
telemetryTimePointToMilliseconds(telemetry.getLayoutStartTime()),
telemetryTimePointToMilliseconds(telemetry.getLayoutEndTime()),
telemetryTimePointToMilliseconds(finishTransactionStartTime),
telemetryTimePointToMilliseconds(finishTransactionEndTime));
env->DeleteLocalRef(intBufferArray);
}
void Binding::schedulerDidFinishTransaction(
MountingCoordinator::Shared const &mountingCoordinator) {
std::lock_guard<std::recursive_mutex> lock(commitMutex_);
if (useIntBufferBatchMountItem_) {
return schedulerDidFinishTransactionIntBuffer(mountingCoordinator);
}
SystraceSection s("FabricUIManagerBinding::schedulerDidFinishTransaction");
auto finishTransactionStartTime = telemetryTimePointNow();

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

@ -26,6 +26,45 @@ namespace react {
class Instance;
struct CppMountItem final {
#pragma mark - Designated Initializers
static CppMountItem CreateMountItem(ShadowView shadowView);
static CppMountItem DeleteMountItem(ShadowView shadowView);
static CppMountItem
InsertMountItem(ShadowView parentView, ShadowView shadowView, int index);
static CppMountItem
RemoveMountItem(ShadowView parentView, ShadowView shadowView, int index);
static CppMountItem UpdatePropsMountItem(ShadowView shadowView);
static CppMountItem UpdateStateMountItem(ShadowView shadowView);
static CppMountItem UpdateLayoutMountItem(ShadowView shadowView);
static CppMountItem UpdateEventEmitterMountItem(ShadowView shadowView);
static CppMountItem UpdatePaddingMountItem(ShadowView shadowView);
#pragma mark - Type
enum Type {
Undefined = -1,
Multiple = 1,
Create = 2,
Delete = 4,
Insert = 8,
Remove = 16,
UpdateProps = 32,
UpdateState = 64,
UpdateLayout = 128,
UpdateEventEmitter = 256,
UpdatePadding = 512
};
#pragma mark - Fields
Type type = {Create};
ShadowView parentShadowView = {};
ShadowView oldChildShadowView = {};
ShadowView newChildShadowView = {};
int index = {};
};
class Binding : public jni::HybridClass<Binding>,
public SchedulerDelegate,
public LayoutAnimationStatusDelegate {
@ -130,10 +169,15 @@ class Binding : public jni::HybridClass<Binding>,
float pointScaleFactor_ = 1;
std::shared_ptr<const ReactNativeConfig> reactNativeConfig_{nullptr};
bool useIntBufferBatchMountItem_{false};
bool collapseDeleteCreateMountingInstructions_{false};
bool disablePreallocateViews_{false};
bool disableVirtualNodePreallocation_{false};
bool enableFabricLogs_{false};
private:
void schedulerDidFinishTransactionIntBuffer(
MountingCoordinator::Shared const &mountingCoordinator);
};
} // namespace react

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

@ -94,8 +94,8 @@ public class BatchMountItem implements MountItem {
return mRootTag;
}
public int getSize() {
return mSize;
public boolean shouldSchedule() {
return mSize != 0;
}
@Override

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

@ -0,0 +1,276 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.fabric.mounting.mountitems;
import static com.facebook.react.fabric.FabricComponents.getFabricComponentName;
import static com.facebook.react.fabric.FabricUIManager.IS_DEVELOPMENT_ENVIRONMENT;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.common.logging.FLog;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.bridge.ReactMarker;
import com.facebook.react.bridge.ReactMarkerConstants;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.fabric.events.EventEmitterWrapper;
import com.facebook.react.fabric.mounting.MountingManager;
import com.facebook.react.uimanager.StateWrapper;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.systrace.Systrace;
/**
* This class represents a batch of {@link MountItem}s, represented directly as int buffers to
* remove the need for actual MountItem instances.
*
* <p>An IntBufferBatchMountItem batch contains an array of ints, indicating the mount actions that
* should be taken, and a size; as well as an array of Objects, and a corresponding array size, for
* any data that cannot be passed as a raw int.
*
* <p>The purpose of encapsulating the array of MountItems this way, is to reduce the amount of
* allocations in C++ and JNI round-trips.
*/
@DoNotStrip
public class IntBufferBatchMountItem implements MountItem {
static final String TAG = IntBufferBatchMountItem.class.getSimpleName();
static final int INSTRUCTION_FLAG_MULTIPLE = 1;
static final int INSTRUCTION_CREATE = 2;
static final int INSTRUCTION_DELETE = 4;
static final int INSTRUCTION_INSERT = 8;
static final int INSTRUCTION_REMOVE = 16;
static final int INSTRUCTION_UPDATE_PROPS = 32;
static final int INSTRUCTION_UPDATE_STATE = 64;
static final int INSTRUCTION_UPDATE_LAYOUT = 128;
static final int INSTRUCTION_UPDATE_EVENT_EMITTER = 256;
static final int INSTRUCTION_UPDATE_PADDING = 512;
private final int mRootTag;
private final int mCommitNumber;
@NonNull private final ThemedReactContext mContext;
@NonNull private final int[] mIntBuffer;
@NonNull private final Object[] mObjBuffer;
private final int mIntBufferLen;
private final int mObjBufferLen;
public IntBufferBatchMountItem(
int rootTag,
@Nullable ThemedReactContext context,
int[] intBuf,
Object[] objBuf,
int commitNumber) {
mRootTag = rootTag;
mCommitNumber = commitNumber;
mContext = context;
mIntBuffer = intBuf;
mObjBuffer = objBuf;
mIntBufferLen = mIntBuffer != null ? mIntBuffer.length : 0;
mObjBufferLen = mObjBuffer != null ? mObjBuffer.length : 0;
}
private void beginMarkers(String reason) {
Systrace.beginSection(
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
"FabricUIManager::"
+ reason
+ " - "
+ mIntBufferLen
+ " intBufSize "
+ " - "
+ mObjBufferLen
+ " objBufSize");
if (mCommitNumber > 0) {
ReactMarker.logFabricMarker(
ReactMarkerConstants.FABRIC_BATCH_EXECUTION_START, null, mCommitNumber);
}
}
private void endMarkers() {
if (mCommitNumber > 0) {
ReactMarker.logFabricMarker(
ReactMarkerConstants.FABRIC_BATCH_EXECUTION_END, null, mCommitNumber);
}
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
private static StateWrapper castToState(Object obj) {
return obj != null ? (StateWrapper) obj : null;
}
private static ReadableMap castToProps(Object obj) {
return obj != null ? (ReadableMap) obj : null;
}
private static EventEmitterWrapper castToEventEmitter(Object obj) {
return obj != null ? (EventEmitterWrapper) obj : null;
}
@Override
public void execute(@NonNull MountingManager mountingManager) {
if (mContext == null) {
FLog.e(
TAG,
"Cannot execute batch of %s MountItems; no context. Hopefully this is because StopSurface was called.",
TAG);
return;
}
beginMarkers("mountViews");
int i = 0, j = 0;
while (i < mIntBufferLen) {
int rawType = mIntBuffer[i++];
int type = rawType & ~INSTRUCTION_FLAG_MULTIPLE;
int numInstructions = ((rawType & INSTRUCTION_FLAG_MULTIPLE) != 0 ? mIntBuffer[i++] : 1);
for (int k = 0; k < numInstructions; k++) {
if (type == INSTRUCTION_CREATE) {
String componentName = getFabricComponentName((String) mObjBuffer[j++]);
mountingManager.createView(
mContext,
componentName,
mIntBuffer[i++],
castToProps(mObjBuffer[j++]),
castToState(mObjBuffer[j++]),
mIntBuffer[i++] == 1);
} else if (type == INSTRUCTION_DELETE) {
mountingManager.deleteView(mIntBuffer[i++]);
} else if (type == INSTRUCTION_INSERT) {
int tag = mIntBuffer[i++];
int parentTag = mIntBuffer[i++];
mountingManager.addViewAt(parentTag, tag, mIntBuffer[i++]);
} else if (type == INSTRUCTION_REMOVE) {
mountingManager.removeViewAt(mIntBuffer[i++], mIntBuffer[i++], mIntBuffer[i++]);
} else if (type == INSTRUCTION_UPDATE_PROPS) {
mountingManager.updateProps(mIntBuffer[i++], castToProps(mObjBuffer[j++]));
} else if (type == INSTRUCTION_UPDATE_STATE) {
mountingManager.updateState(mIntBuffer[i++], castToState(mObjBuffer[j++]));
} else if (type == INSTRUCTION_UPDATE_LAYOUT) {
mountingManager.updateLayout(
mIntBuffer[i++], mIntBuffer[i++], mIntBuffer[i++], mIntBuffer[i++], mIntBuffer[i++]);
// The final buffer, layoutDirection, seems unused?
i++;
} else if (type == INSTRUCTION_UPDATE_PADDING) {
mountingManager.updatePadding(
mIntBuffer[i++], mIntBuffer[i++], mIntBuffer[i++], mIntBuffer[i++], mIntBuffer[i++]);
} else if (type == INSTRUCTION_UPDATE_EVENT_EMITTER) {
mountingManager.updateEventEmitter(mIntBuffer[i++], castToEventEmitter(mObjBuffer[j++]));
} else {
throw new IllegalArgumentException(
"Invalid type argument to IntBufferBatchMountItem: " + type + " at index: " + i);
}
}
}
endMarkers();
}
public int getRootTag() {
return mRootTag;
}
public boolean shouldSchedule() {
return mIntBufferLen != 0;
}
@Override
public String toString() {
try {
StringBuilder s = new StringBuilder();
s.append("IntBufferBatchMountItem:");
int i = 0, j = 0;
while (i < mIntBufferLen) {
int rawType = mIntBuffer[i++];
int type = rawType & ~INSTRUCTION_FLAG_MULTIPLE;
int numInstructions = ((rawType & INSTRUCTION_FLAG_MULTIPLE) != 0 ? mIntBuffer[i++] : 1);
for (int k = 0; k < numInstructions; k++) {
if (type == INSTRUCTION_CREATE) {
String componentName = getFabricComponentName((String) mObjBuffer[j++]);
j += 2;
s.append(
String.format(
"CREATE [%d] - layoutable:%d - %s\n",
mIntBuffer[i++], mIntBuffer[i++], componentName));
} else if (type == INSTRUCTION_DELETE) {
s.append(String.format("DELETE [%d]\n", mIntBuffer[i++]));
} else if (type == INSTRUCTION_INSERT) {
s.append(
String.format(
"INSERT [%d]->[%d] @%d\n", mIntBuffer[i++], mIntBuffer[i++], mIntBuffer[i++]));
} else if (type == INSTRUCTION_REMOVE) {
s.append(
String.format(
"REMOVE [%d]->[%d] @%d\n", mIntBuffer[i++], mIntBuffer[i++], mIntBuffer[i++]));
} else if (type == INSTRUCTION_UPDATE_PROPS) {
ReadableMap props = castToProps(mObjBuffer[j++]);
String propsString =
IS_DEVELOPMENT_ENVIRONMENT
? (props != null ? props.toHashMap().toString() : "<null>")
: "<hidden>";
s.append(String.format("UPDATE PROPS [%d]: %s\n", mIntBuffer[i++], propsString));
} else if (type == INSTRUCTION_UPDATE_STATE) {
j += 1;
s.append(String.format("UPDATE STATE [%d]\n", mIntBuffer[i++]));
} else if (type == INSTRUCTION_UPDATE_LAYOUT) {
s.append(
String.format(
"UPDATE LAYOUT [%d]: x:%d y:%d w:%d h:%d layoutDirection:%d\n",
mIntBuffer[i++],
mIntBuffer[i++],
mIntBuffer[i++],
mIntBuffer[i++],
mIntBuffer[i++],
mIntBuffer[i++]));
} else if (type == INSTRUCTION_UPDATE_PADDING) {
s.append(
String.format(
"UPDATE PADDING [%d]: top:%d right:%d bottom:%d left:%d\n",
mIntBuffer[i++],
mIntBuffer[i++],
mIntBuffer[i++],
mIntBuffer[i++],
mIntBuffer[i++]));
} else if (type == INSTRUCTION_UPDATE_EVENT_EMITTER) {
j += 1;
s.append(String.format("UPDATE EVENTEMITTER [%d]\n", mIntBuffer[i++]));
} else {
FLog.e(TAG, "String so far: " + s.toString());
throw new IllegalArgumentException(
"Invalid type argument to IntBufferBatchMountItem: " + type + " at index: " + i);
}
}
}
return s.toString();
} catch (Exception e) {
// Generally, this only happens during development when a malformed buffer is sent through.
// In these cases, we print the buffers to assist in debugging.
// This should never happen in production, but if it does... it'd still be helpful to know.
FLog.e(TAG, "Caught exception trying to print", e);
StringBuilder ss = new StringBuilder();
for (int ii = 0; ii < mIntBufferLen; ii++) {
ss.append(mIntBuffer[ii]);
ss.append(", ");
}
FLog.e(TAG, ss.toString());
for (int jj = 0; jj < mObjBufferLen; jj++) {
FLog.e(TAG, mObjBuffer[jj] != null ? mObjBuffer[jj].toString() : "null");
}
return "";
}
}
}