void Binding::schedulerDidFinishTransaction()

in ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp [626:1089]


void Binding::schedulerDidFinishTransaction(
    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 = mutation.mutatedViewIsVirtual();

    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[7];
  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;
      }

      // Do not hold a reference to javaEventEmitter from the C++ side.
      SharedEventEmitter eventEmitter =
          mountItem.newChildShadowView.eventEmitter;
      auto javaEventEmitter = EventEmitterWrapper::newObjectJavaArgs();
      EventEmitterWrapper *cEventEmitter = cthis(javaEventEmitter);
      cEventEmitter->eventEmitter = eventEmitter;

      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;
      (*objBufferArray)[objBufferPosition++] = javaEventEmitter.get();
    } 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(scale(contentInsets.left, pointScaleFactor));
      int top = floor(scale(contentInsets.top, pointScaleFactor));
      int right = floor(scale(contentInsets.right, pointScaleFactor));
      int bottom = floor(scale(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(scale(frame.origin.x, pointScaleFactor));
      int y = round(scale(frame.origin.y, pointScaleFactor));
      int w = round(scale(frame.size.width, pointScaleFactor));
      int h = round(scale(frame.size.height, pointScaleFactor));
      int displayType =
          toInt(mountItem.newChildShadowView.layoutMetrics.displayType);

      temp[0] = mountItem.newChildShadowView.tag;
      temp[1] = x;
      temp[2] = y;
      temp[3] = w;
      temp[4] = h;
      temp[5] = displayType;
      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);
}