mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
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:
committed by
Facebook Github Bot
parent
d7d848e824
commit
27fca36a9a
@@ -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
|
||||
|
||||
+87
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user