Skip to content

Commit

Permalink
Calculate Android mounting instructions based on updates accumulated …
Browse files Browse the repository at this point in the history
…in rawProps (facebook#48303)

Summary:

## Context

If a component is re-rendered/committed multiple times before mount, Android mounting layer creates and executes mounting instructions for every commit. Component's native representation is updated multiple times, potentially triggering expensive computations (e.g. recreating boxShadows) for a single draw.

A more efficient way would be to create mounting instructions to update the component from the initial state (before the first render) to the final state (after the last render) in one go.

iOS does that.

This diff is an attempt to experiment on achieving such behaviour for Android.

## Implementation Details

1. When cloning a ShadowNode, accumulate all the updates in `Props.rawProps`.
2. For calculating prop update payloads to be sent to the Android mounting layer, diff old and new `rawProps` by calling `newProps->getDiffProps(oldProps)`.
3. Most importantly, move computing of the mounting instructions from `schedulerDidFinishTransaction`, which is called after every commit, to `schedulerShouldRenderTransactions` which happens only once, after the final commit.

Changelog: [Internal]

Reviewed By: javache

Differential Revision: D63457028
  • Loading branch information
dmytrorykun authored and facebook-github-bot committed Dec 17, 2024
1 parent 03b9f04 commit a444ece
Show file tree
Hide file tree
Showing 26 changed files with 397 additions and 119 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<609b55df15ac72da5a3c14dde18a4814>>
* @generated SignedSource<<90dcb658c018c8007953964b24b84278>>
*/

/**
Expand Down Expand Up @@ -52,6 +52,12 @@ public object ReactNativeFeatureFlags {
@JvmStatic
public fun disableMountItemReorderingAndroid(): Boolean = accessor.disableMountItemReorderingAndroid()

/**
* When enabled, Andoid will accumulate updates in rawProps to reduce the number of mounting instructions for cascading rerenders.
*/
@JvmStatic
public fun enableAccumulatedUpdatesInRawPropsAndroid(): Boolean = accessor.enableAccumulatedUpdatesInRawPropsAndroid()

/**
* Kill-switch to turn off support for aling-items:baseline on Fabric iOS.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<0be0090ba864cd4fe0bd2c22e419923c>>
* @generated SignedSource<<ad976a93c28bf6e46dfd7ed1ba0493ed>>
*/

/**
Expand All @@ -24,6 +24,7 @@ public class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAccesso
private var completeReactInstanceCreationOnBgThreadOnAndroidCache: Boolean? = null
private var disableEventLoopOnBridgelessCache: Boolean? = null
private var disableMountItemReorderingAndroidCache: Boolean? = null
private var enableAccumulatedUpdatesInRawPropsAndroidCache: Boolean? = null
private var enableAlignItemsBaselineOnFabricIOSCache: Boolean? = null
private var enableAndroidLineHeightCenteringCache: Boolean? = null
private var enableBridgelessArchitectureCache: Boolean? = null
Expand Down Expand Up @@ -104,6 +105,15 @@ public class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAccesso
return cached
}

override fun enableAccumulatedUpdatesInRawPropsAndroid(): Boolean {
var cached = enableAccumulatedUpdatesInRawPropsAndroidCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.enableAccumulatedUpdatesInRawPropsAndroid()
enableAccumulatedUpdatesInRawPropsAndroidCache = cached
}
return cached
}

override fun enableAlignItemsBaselineOnFabricIOS(): Boolean {
var cached = enableAlignItemsBaselineOnFabricIOSCache
if (cached == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<b92feb26341cd20ea526620ffef11352>>
* @generated SignedSource<<2e766bcc58b8d6b0fb605ee71520dd2e>>
*/

/**
Expand Down Expand Up @@ -36,6 +36,8 @@ public object ReactNativeFeatureFlagsCxxInterop {

@DoNotStrip @JvmStatic public external fun disableMountItemReorderingAndroid(): Boolean

@DoNotStrip @JvmStatic public external fun enableAccumulatedUpdatesInRawPropsAndroid(): Boolean

@DoNotStrip @JvmStatic public external fun enableAlignItemsBaselineOnFabricIOS(): Boolean

@DoNotStrip @JvmStatic public external fun enableAndroidLineHeightCentering(): Boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<04ac11c085b2880db40d44e431db42f2>>
* @generated SignedSource<<d21071fcbf501d1e7aba6b227ef74351>>
*/

/**
Expand All @@ -31,6 +31,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi

override fun disableMountItemReorderingAndroid(): Boolean = false

override fun enableAccumulatedUpdatesInRawPropsAndroid(): Boolean = false

override fun enableAlignItemsBaselineOnFabricIOS(): Boolean = true

override fun enableAndroidLineHeightCentering(): Boolean = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<0f7a852c7c44b4074bf618252fecb2f6>>
* @generated SignedSource<<f56022e0738217726e5e7b938666b56e>>
*/

/**
Expand All @@ -28,6 +28,7 @@ public class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcces
private var completeReactInstanceCreationOnBgThreadOnAndroidCache: Boolean? = null
private var disableEventLoopOnBridgelessCache: Boolean? = null
private var disableMountItemReorderingAndroidCache: Boolean? = null
private var enableAccumulatedUpdatesInRawPropsAndroidCache: Boolean? = null
private var enableAlignItemsBaselineOnFabricIOSCache: Boolean? = null
private var enableAndroidLineHeightCenteringCache: Boolean? = null
private var enableBridgelessArchitectureCache: Boolean? = null
Expand Down Expand Up @@ -112,6 +113,16 @@ public class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcces
return cached
}

override fun enableAccumulatedUpdatesInRawPropsAndroid(): Boolean {
var cached = enableAccumulatedUpdatesInRawPropsAndroidCache
if (cached == null) {
cached = currentProvider.enableAccumulatedUpdatesInRawPropsAndroid()
accessedFeatureFlags.add("enableAccumulatedUpdatesInRawPropsAndroid")
enableAccumulatedUpdatesInRawPropsAndroidCache = cached
}
return cached
}

override fun enableAlignItemsBaselineOnFabricIOS(): Boolean {
var cached = enableAlignItemsBaselineOnFabricIOSCache
if (cached == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<190f00e954f343b076cfe5ef75ee4539>>
* @generated SignedSource<<94d6ce778ccf52a7f7b2ab574b1c9547>>
*/

/**
Expand All @@ -31,6 +31,8 @@ public interface ReactNativeFeatureFlagsProvider {

@DoNotStrip public fun disableMountItemReorderingAndroid(): Boolean

@DoNotStrip public fun enableAccumulatedUpdatesInRawPropsAndroid(): Boolean

@DoNotStrip public fun enableAlignItemsBaselineOnFabricIOS(): Boolean

@DoNotStrip public fun enableAndroidLineHeightCentering(): Boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/jni/ReadableNativeMap.h>
#include <react/renderer/components/scrollview/ScrollViewProps.h>
#include <react/renderer/core/DynamicPropsUtilities.h>
#include <react/renderer/core/conversions.h>
#include <react/renderer/mounting/MountingTransaction.h>
#include <react/renderer/mounting/ShadowView.h>
Expand Down Expand Up @@ -215,18 +216,26 @@ inline float scale(Float value, Float pointScaleFactor) {
jni::local_ref<jobject> getProps(
const ShadowView& oldShadowView,
const ShadowView& newShadowView) {
auto componentName = newShadowView.componentName;
// We calculate the diffing between the props of the last mounted ShadowTree
// and the Props of the latest commited ShadowTree). ONLY for <View>
// components when the "enablePropsUpdateReconciliationAndroid" feature flag
// is enabled.
auto* oldProps = oldShadowView.props.get();
auto* newProps = newShadowView.props.get();
if (ReactNativeFeatureFlags::enablePropsUpdateReconciliationAndroid() &&
strcmp(componentName, "View") == 0) {
const Props* oldProps = oldShadowView.props.get();
auto diffProps = newShadowView.props->getDiffProps(oldProps);
return ReadableNativeMap::newObjectCxxArgs(diffProps);
strcmp(newShadowView.componentName, "View") == 0) {
return ReadableNativeMap::newObjectCxxArgs(
newProps->getDiffProps(oldProps));
}
return ReadableNativeMap::newObjectCxxArgs(newShadowView.props->rawProps);
if (ReactNativeFeatureFlags::enableAccumulatedUpdatesInRawPropsAndroid()) {
if (oldProps == nullptr) {
return ReadableNativeMap::newObjectCxxArgs(newProps->rawProps);
} else {
return ReadableNativeMap::newObjectCxxArgs(
diffDynamicProps(oldProps->rawProps, newProps->rawProps));
}
}
return ReadableNativeMap::newObjectCxxArgs(newProps->rawProps);
}

struct InstructionBuffer {
Expand Down Expand Up @@ -587,13 +596,25 @@ void FabricMountingManager::executeMount(

bool shouldCreateView =
!allocatedViewTags.contains(newChildShadowView.tag);
if (shouldCreateView) {
LOG(ERROR) << "Emitting insert for unallocated view "
<< newChildShadowView.tag;
if (ReactNativeFeatureFlags::
enableAccumulatedUpdatesInRawPropsAndroid()) {
if (shouldCreateView) {
LOG(ERROR) << "Emitting insert for unallocated view "
<< newChildShadowView.tag;
}
(maintainMutationOrder ? cppCommonMountItems
: cppUpdatePropsMountItems)
.push_back(CppMountItem::UpdatePropsMountItem(
{}, newChildShadowView));
} else {
if (shouldCreateView) {
LOG(ERROR) << "Emitting insert for unallocated view "
<< newChildShadowView.tag;
(maintainMutationOrder ? cppCommonMountItems
: cppUpdatePropsMountItems)
.push_back(CppMountItem::UpdatePropsMountItem(
{}, newChildShadowView));
}
}

// State
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -514,60 +514,71 @@ FabricUIManagerBinding::getMountingManager(const char* locationHint) {

void FabricUIManagerBinding::schedulerDidFinishTransaction(
const std::shared_ptr<const MountingCoordinator>& mountingCoordinator) {
// We shouldn't be pulling the transaction here (which triggers diffing of
// the trees to determine the mutations to run on the host platform),
// but we have to due to current limitations in the Android implementation.
auto mountingTransaction = mountingCoordinator->pullTransaction(
// Indicate that the transaction will be performed asynchronously
ReactNativeFeatureFlags::
fixMountingCoordinatorReportedPendingTransactionsOnAndroid());
if (!mountingTransaction.has_value()) {
return;
}
if (ReactNativeFeatureFlags::enableAccumulatedUpdatesInRawPropsAndroid()) {
// We don't do anything here. We will pull the transaction in
// `schedulerShouldRenderTransactions`.
} else {
// We shouldn't be pulling the transaction here (which triggers diffing of
// the trees to determine the mutations to run on the host platform),
// but we have to due to current limitations in the Android implementation.
auto mountingTransaction = mountingCoordinator->pullTransaction(
// Indicate that the transaction will be performed asynchronously
ReactNativeFeatureFlags::
fixMountingCoordinatorReportedPendingTransactionsOnAndroid());
if (!mountingTransaction.has_value()) {
return;
}

std::unique_lock<std::mutex> lock(pendingTransactionsMutex_);
auto pendingTransaction = std::find_if(
pendingTransactions_.begin(),
pendingTransactions_.end(),
[&](const auto& transaction) {
return transaction.getSurfaceId() ==
mountingTransaction->getSurfaceId();
});
std::unique_lock<std::mutex> lock(pendingTransactionsMutex_);
auto pendingTransaction = std::find_if(
pendingTransactions_.begin(),
pendingTransactions_.end(),
[&](const auto& transaction) {
return transaction.getSurfaceId() ==
mountingTransaction->getSurfaceId();
});

if (pendingTransaction != pendingTransactions_.end()) {
pendingTransaction->mergeWith(std::move(*mountingTransaction));
} else {
pendingTransactions_.push_back(std::move(*mountingTransaction));
if (pendingTransaction != pendingTransactions_.end()) {
pendingTransaction->mergeWith(std::move(*mountingTransaction));
} else {
pendingTransactions_.push_back(std::move(*mountingTransaction));
}
}
}

void FabricUIManagerBinding::schedulerShouldRenderTransactions(
const std::shared_ptr<
const MountingCoordinator>& /* mountingCoordinator */) {
const std::shared_ptr<const MountingCoordinator>& mountingCoordinator) {
auto mountingManager =
getMountingManager("schedulerShouldRenderTransactions");
if (!mountingManager) {
return;
}
if (ReactNativeFeatureFlags::enableAccumulatedUpdatesInRawPropsAndroid()) {
auto mountingTransaction = mountingCoordinator->pullTransaction();
if (mountingTransaction.has_value()) {
auto transaction = std::move(*mountingTransaction);
mountingManager->executeMount(transaction);
}
} else {
std::vector<MountingTransaction> pendingTransactions;

{
// Retain the lock to access the pending transactions but not to execute
// the mount operations because that method can call into this method
// again.
//
// This can be re-entrant when mounting manager triggers state updates
// synchronously (this can happen when committing from the UI thread).
// This is safe because we're already combining all the transactions for
// the same surface ID in a single transaction in the pending transactions
// list, so operations won't run out of order.
std::unique_lock<std::mutex> lock(pendingTransactionsMutex_);
pendingTransactions_.swap(pendingTransactions);
}

std::vector<MountingTransaction> pendingTransactions;

{
// Retain the lock to access the pending transactions but not to execute
// the mount operations because that method can call into this method
// again.
//
// This can be re-entrant when mounting manager triggers state updates
// synchronously (this can happen when committing from the UI thread).
// This is safe because we're already combining all the transactions for the
// same surface ID in a single transaction in the pending transactions list,
// so operations won't run out of order.
std::unique_lock<std::mutex> lock(pendingTransactionsMutex_);
pendingTransactions_.swap(pendingTransactions);
}

for (auto& transaction : pendingTransactions) {
mountingManager->executeMount(transaction);
for (auto& transaction : pendingTransactions) {
mountingManager->executeMount(transaction);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<8a9673f2f81917b3466fb955d5641720>>
* @generated SignedSource<<260f2446e2c2d5ad3e4089798bb86ae0>>
*/

/**
Expand Down Expand Up @@ -63,6 +63,12 @@ class ReactNativeFeatureFlagsProviderHolder
return method(javaProvider_);
}

bool enableAccumulatedUpdatesInRawPropsAndroid() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("enableAccumulatedUpdatesInRawPropsAndroid");
return method(javaProvider_);
}

bool enableAlignItemsBaselineOnFabricIOS() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("enableAlignItemsBaselineOnFabricIOS");
Expand Down Expand Up @@ -345,6 +351,11 @@ bool JReactNativeFeatureFlagsCxxInterop::disableMountItemReorderingAndroid(
return ReactNativeFeatureFlags::disableMountItemReorderingAndroid();
}

bool JReactNativeFeatureFlagsCxxInterop::enableAccumulatedUpdatesInRawPropsAndroid(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::enableAccumulatedUpdatesInRawPropsAndroid();
}

bool JReactNativeFeatureFlagsCxxInterop::enableAlignItemsBaselineOnFabricIOS(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::enableAlignItemsBaselineOnFabricIOS();
Expand Down Expand Up @@ -603,6 +614,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
makeNativeMethod(
"disableMountItemReorderingAndroid",
JReactNativeFeatureFlagsCxxInterop::disableMountItemReorderingAndroid),
makeNativeMethod(
"enableAccumulatedUpdatesInRawPropsAndroid",
JReactNativeFeatureFlagsCxxInterop::enableAccumulatedUpdatesInRawPropsAndroid),
makeNativeMethod(
"enableAlignItemsBaselineOnFabricIOS",
JReactNativeFeatureFlagsCxxInterop::enableAlignItemsBaselineOnFabricIOS),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<cf7e9685a1739386706934b8c249ed03>>
* @generated SignedSource<<bc53fc826ce10cd270fe71ebaefc9ad8>>
*/

/**
Expand Down Expand Up @@ -42,6 +42,9 @@ class JReactNativeFeatureFlagsCxxInterop
static bool disableMountItemReorderingAndroid(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);

static bool enableAccumulatedUpdatesInRawPropsAndroid(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);

static bool enableAlignItemsBaselineOnFabricIOS(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);

Expand Down
Loading

0 comments on commit a444ece

Please sign in to comment.