diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricComponents.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricComponents.java index 9f0f32c169..ab16eac8e2 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricComponents.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricComponents.java @@ -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; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java index 3e5b6c37aa..406dd09a13 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java @@ -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(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp index 134c1c5de2..eeb9311630 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp @@ -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& cppCommonMountItems, + std::vector& cppDeleteMountItems, + std::vector& cppUpdatePropsMountItems, + std::vector& cppUpdateStateMountItems, + std::vector& cppUpdatePaddingMountItems, + std::vector& cppUpdateLayoutMountItems, + std::vector& 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::initHybrid( jni::alias_ref) { 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 createUpdatePropsMountItem( const jni::global_ref &javaUIManager, const ShadowViewMutation &mutation) { auto shadowView = mutation.newChildShadowView; - auto newViewProps = - *std::dynamic_pointer_cast(shadowView.props); // TODO: move props from map to a typed object. auto newProps = shadowView.props->rawProps; @@ -570,10 +738,469 @@ local_ref createCreateMountItem( isLayoutable); } +void Binding::schedulerDidFinishTransactionIntBuffer( + MountingCoordinator::Shared const &mountingCoordinator) { + std::lock_guard lock(commitMutex_); + + SystraceSection s( + "FabricUIManagerBinding::schedulerDidFinishTransactionIntBuffer"); + auto finishTransactionStartTime = telemetryTimePointNow(); + + jni::global_ref 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 cppCommonMountItems; + std::vector cppDeleteMountItems; + std::vector cppUpdatePropsMountItems; + std::vector cppUpdateStateMountItems; + std::vector cppUpdatePaddingMountItems; + std::vector cppUpdateLayoutMountItems; + std::vector 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( + jint, jintArray, jtypeArray, jint)>( + "createIntBufferBatchMountItem"); + + static auto scheduleMountItem = + jni::findClassStatic(Binding::UIManagerJavaDescriptor) + ->getMethod("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> objBufferArray = + JArrayClass::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 componentName = + getPlatformComponentName(mountItem.newChildShadowView); + + int isLayoutable = + mountItem.newChildShadowView.layoutMetrics != EmptyLayoutMetrics ? 1 + : 0; + + local_ref 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 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 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 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 lock(commitMutex_); + if (useIntBufferBatchMountItem_) { + return schedulerDidFinishTransactionIntBuffer(mountingCoordinator); + } + SystraceSection s("FabricUIManagerBinding::schedulerDidFinishTransaction"); auto finishTransactionStartTime = telemetryTimePointNow(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.h b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.h index 635b74185c..d0f13cc6e9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.h +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.h @@ -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, public SchedulerDelegate, public LayoutAnimationStatusDelegate { @@ -130,10 +169,15 @@ class Binding : public jni::HybridClass, float pointScaleFactor_ = 1; std::shared_ptr 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 diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/BatchMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/BatchMountItem.java index fc908e2dee..9a7e573edd 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/BatchMountItem.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/BatchMountItem.java @@ -94,8 +94,8 @@ public class BatchMountItem implements MountItem { return mRootTag; } - public int getSize() { - return mSize; + public boolean shouldSchedule() { + return mSize != 0; } @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/IntBufferBatchMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/IntBufferBatchMountItem.java new file mode 100644 index 0000000000..efaafa11ea --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/IntBufferBatchMountItem.java @@ -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. + * + *

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. + * + *

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() : "") + : ""; + 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 ""; + } + } +}