Attempt to collate Remove/Delete mounting items

Summary:
Collapse many Remove/Delete mount items into a single batched item.

Since a delete is always preceded by a remove mountitem, we can batch these into one instruction. Since deletes tend to come in large blocks, it might make sense to batch many into a single instruction.

Reviewed By: mdvacca

Differential Revision: D17254631

fbshipit-source-id: abfd54cdb0bbb9a4c0880ec8e8bbd681367aecd4
This commit is contained in:
Joshua Gross
2019-09-09 19:54:34 -07:00
committed by Facebook Github Bot
parent d7d848e824
commit 27fca36a9a
4 changed files with 170 additions and 11 deletions
@@ -50,6 +50,7 @@ import com.facebook.react.fabric.mounting.mountitems.DispatchStringCommandMountI
import com.facebook.react.fabric.mounting.mountitems.InsertMountItem;
import com.facebook.react.fabric.mounting.mountitems.MountItem;
import com.facebook.react.fabric.mounting.mountitems.PreAllocateViewMountItem;
import com.facebook.react.fabric.mounting.mountitems.RemoveDeleteMultiMountItem;
import com.facebook.react.fabric.mounting.mountitems.RemoveMountItem;
import com.facebook.react.fabric.mounting.mountitems.SendAccessibilityEvent;
import com.facebook.react.fabric.mounting.mountitems.UpdateEventEmitterMountItem;
@@ -280,6 +281,12 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
return new DeleteMountItem(reactTag);
}
@DoNotStrip
@SuppressWarnings("unused")
private MountItem removeDeleteMultiMountItem(int[] metadata) {
return new RemoveDeleteMultiMountItem(metadata);
}
@DoNotStrip
@SuppressWarnings("unused")
private MountItem updateLayoutMountItem(
@@ -42,6 +42,14 @@ struct JMountItem : public JavaClass<JMountItem> {
static constexpr auto UIManagerJavaDescriptor =
"com/facebook/react/fabric/FabricUIManager";
struct RemoveDeleteMetadata {
int tag;
int parentTag;
int index;
bool shouldRemove;
bool shouldDelete;
};
} // namespace
jni::local_ref<Binding::jhybriddata> Binding::initHybrid(
@@ -223,7 +231,9 @@ void Binding::installFabricUIManager(
contextContainer->insert("ReactNativeConfig", config);
contextContainer->insert("FabricUIManager", javaUIManager_);
// Keep reference to config object and cache some feature flags here
reactNativeConfig_ = config;
shouldCollateRemovesAndDeletes_ = reactNativeConfig_->getBool("react_fabric:enable_removedelete_collation_android");
auto toolbox = SchedulerToolbox{};
toolbox.contextContainer = contextContainer;
@@ -411,9 +421,9 @@ local_ref<JMountItem::javaobject> createUpdateStateMountItem(
const jni::global_ref<jobject> &javaUIManager,
const ShadowViewMutation &mutation) {
static auto updateStateInstruction =
jni::findClassStatic(UIManagerJavaDescriptor)
->getMethod<alias_ref<JMountItem>(jint, jobject)>(
"updateStateMountItem");
jni::findClassStatic(UIManagerJavaDescriptor)
->getMethod<alias_ref<JMountItem>(jint, jobject)>(
"updateStateMountItem");
auto state = mutation.newChildShadowView.state;
@@ -428,9 +438,9 @@ local_ref<JMountItem::javaobject> createUpdateStateMountItem(
}
return updateStateInstruction(
javaUIManager,
mutation.newChildShadowView.tag,
(javaStateWrapper != nullptr ? javaStateWrapper.get() : nullptr));
javaUIManager,
mutation.newChildShadowView.tag,
(javaStateWrapper != nullptr ? javaStateWrapper.get() : nullptr));
}
local_ref<JMountItem::javaobject> createRemoveMountItem(
@@ -458,6 +468,30 @@ local_ref<JMountItem::javaobject> createDeleteMountItem(
return deleteInstruction(javaUIManager, mutation.oldChildShadowView.tag);
}
local_ref<JMountItem::javaobject> createRemoveAndDeleteMultiMountItem(
const jni::global_ref<jobject> &javaUIManager,
std::vector<RemoveDeleteMetadata> metadata) {
auto env = Environment::current();
auto removeAndDeleteArray = env->NewIntArray(metadata.size()*4);
int position = 0;
jint temp[4];
for (const auto& x : metadata) {
temp[0] = x.tag;
temp[1] = x.parentTag;
temp[2] = x.index;
temp[3] = (x.shouldRemove ? 1 : 0) | (x.shouldDelete ? 2 : 0);
env->SetIntArrayRegion(removeAndDeleteArray, position, 4, temp);
position += 4;
}
static auto removeDeleteMultiInstruction =
jni::findClassStatic(UIManagerJavaDescriptor)
->getMethod<alias_ref<JMountItem>(jintArray)>("removeDeleteMultiMountItem");
return removeDeleteMultiInstruction(javaUIManager, removeAndDeleteArray);
}
// TODO T48019320: because we pass initial props and state to the Create (and preallocate) mount instruction,
// we technically don't need to pass the first Update to any components. Dedupe?
local_ref<JMountItem::javaobject> createCreateMountItem(
@@ -535,6 +569,9 @@ void Binding::schedulerDidFinishTransaction(
auto mountItems = *(mountItemsArray);
std::unordered_set<Tag> deletedViewTags;
// Find the set of tags that are removed and deleted in one block
std::vector<RemoveDeleteMetadata> toRemove;
int position = 0;
for (const auto &mutation : mutations) {
auto oldChildShadowView = mutation.oldChildShadowView;
@@ -543,6 +580,14 @@ void Binding::schedulerDidFinishTransaction(
bool isVirtual = newChildShadowView.layoutMetrics == EmptyLayoutMetrics &&
oldChildShadowView.layoutMetrics == EmptyLayoutMetrics;
// Handle accumulated removals/deletions
if (shouldCollateRemovesAndDeletes_ && mutation.type != ShadowViewMutation::Remove && mutation.type != ShadowViewMutation::Delete) {
if (toRemove.size() > 0) {
mountItems[position++] = createRemoveAndDeleteMultiMountItem(localJavaUIManager, toRemove);
toRemove.clear();
}
}
switch (mutation.type) {
case ShadowViewMutation::Create: {
if (mutation.newChildShadowView.props->revision > 1 ||
@@ -555,14 +600,27 @@ void Binding::schedulerDidFinishTransaction(
}
case ShadowViewMutation::Remove: {
if (!isVirtual) {
mountItems[position++] =
createRemoveMountItem(localJavaUIManager, mutation);
if (shouldCollateRemovesAndDeletes_) {
toRemove.push_back(RemoveDeleteMetadata{mutation.oldChildShadowView.tag, mutation.parentShadowView.tag, mutation.index, true, false});
} else {
mountItems[position++] = createRemoveMountItem(localJavaUIManager, mutation);
}
}
break;
}
case ShadowViewMutation::Delete: {
mountItems[position++] =
createDeleteMountItem(localJavaUIManager, mutation);
if (shouldCollateRemovesAndDeletes_) {
// It is impossible to delete without removing node first
const auto& it = std::find_if(std::begin(toRemove), std::end(toRemove), [&mutation](const auto& x) { return x.tag == mutation.oldChildShadowView.tag; });
if (it != std::end(toRemove)) {
it->shouldDelete = true;
} else {
toRemove.push_back(RemoveDeleteMetadata{mutation.oldChildShadowView.tag, -1, -1, false, true});
}
} else {
mountItems[position++] = createDeleteMountItem(localJavaUIManager, mutation);
}
deletedViewTags.insert(mutation.oldChildShadowView.tag);
break;
@@ -662,6 +720,12 @@ void Binding::schedulerDidFinishTransaction(
}
}
// Handle remaining removals and deletions
if (shouldCollateRemovesAndDeletes_ && toRemove.size() > 0) {
mountItems[position++] = createRemoveAndDeleteMultiMountItem(localJavaUIManager, toRemove);
toRemove.clear();
}
if (position <= 0) {
// If there are no mountItems to be sent to the platform, then it is not necessary to even call.
return;
@@ -100,7 +100,8 @@ class Binding : public jni::HybridClass<Binding>, public SchedulerDelegate {
float pointScaleFactor_ = 1;
std::shared_ptr<const ReactNativeConfig> reactNativeConfig_;
std::shared_ptr<const ReactNativeConfig> reactNativeConfig_{nullptr};
bool shouldCollateRemovesAndDeletes_{false};
};
} // namespace react
@@ -0,0 +1,87 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
*
* <p>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 com.facebook.react.fabric.mounting.MountingManager;
public class RemoveDeleteMultiMountItem implements MountItem {
// Metadata is an array of ints, grouped into 4 ints per instruction (so the length of metadata
// is always divisible by 4):
//
// `instruction*4 + 0`: react tag of view instruction
// `instruction*4 + 1`: react tag of view's parent
// `instruction*4 + 2`: index of view in parents' children instruction
// `instruction*4 + 3`: flags indicating if the view should be removed, and/or deleted
private int[] mMetadata;
// Bitfields of "flag", indicating if a view should be removed and/or deleted
private static final int REMOVE_FLAG = 1;
private static final int DELETE_FLAG = 2;
// Indices for each parameter within an "instruction"
private static final int INSTRUCTION_FIELDS_LEN = 4;
private static final int TAG_INDEX = 0;
private static final int PARENT_TAG_INDEX = 1;
private static final int VIEW_INDEX_INDEX = 2;
private static final int FLAGS_INDEX = 3;
public RemoveDeleteMultiMountItem(int[] metadata) {
mMetadata = metadata;
}
@Override
public void execute(MountingManager mountingManager) {
// First, go through instructions and remove all views that are marked
// for removal.
// Not all views that are removed are deleted, and not all deleted views
// are removed first.
// *All* views must be removed here before we can delete any views.
// Removal of a view from a parent is based on indices within the parents' children,
// and deletion causes reordering; so we must perform all removals first.
for (int i = 0; i < mMetadata.length; i += INSTRUCTION_FIELDS_LEN) {
int flags = mMetadata[i + FLAGS_INDEX];
if ((flags & REMOVE_FLAG) != 0) {
int parentTag = mMetadata[i + PARENT_TAG_INDEX];
int index = mMetadata[i + VIEW_INDEX_INDEX];
mountingManager.removeViewAt(parentTag, index);
}
}
// After removing all views, delete all views marked for deletion.
for (int i = 0; i < mMetadata.length; i += 4) {
int flags = mMetadata[i + FLAGS_INDEX];
if ((flags & DELETE_FLAG) != 0) {
int tag = mMetadata[i + TAG_INDEX];
mountingManager.deleteView(tag);
}
}
}
@Override
public String toString() {
StringBuilder s = new StringBuilder();
for (int i = 0; i < mMetadata.length; i += 4) {
if (s.length() > 0) {
s.append("\n");
}
s.append("RemoveDeleteMultiMountItem (")
.append(i / INSTRUCTION_FIELDS_LEN + 1)
.append("/")
.append(mMetadata.length / INSTRUCTION_FIELDS_LEN)
.append("): [")
.append(mMetadata[i + TAG_INDEX])
.append("] parent [")
.append(mMetadata[i + PARENT_TAG_INDEX])
.append("] idx ")
.append(mMetadata[i + VIEW_INDEX_INDEX])
.append(" ")
.append(mMetadata[i + FLAGS_INDEX]);
}
return s.toString();
}
}