diff --git a/packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js b/packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js index 15df90071365..8c4a37c81100 100644 --- a/packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js +++ b/packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js @@ -77,6 +77,72 @@ test('animate marginLeft layout prop', () => { ); }); +// A layout animation on one view must not push the other views of the +// surface through a shadow tree commit: their non-layout props keep taking +// the direct path to the mounted views. +test('non-layout props stay on the direct path while another view animates layout', () => { + const movingRef = createRef(); + + let _translateX; + let _translateXAnimation; + let _siblingHeight; + let _siblingHeightAnimation; + + function MyApp() { + const translateX = useAnimatedValue(0); + const siblingHeight = useAnimatedValue(10); + _translateX = translateX; + _siblingHeight = siblingHeight; + return ( + + + + + ); + } + + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + Fantom.runTask(() => { + _translateXAnimation = Animated.timing(_translateX, { + toValue: 100, + duration: 200, + useNativeDriver: true, + }).start(); + _siblingHeightAnimation = Animated.timing(_siblingHeight, { + toValue: 110, + duration: 200, + useNativeDriver: true, + }).start(); + }); + + Fantom.unstable_produceFramesForDuration(100); + + // The sibling's height went through a commit; the transform did not. + expect(root.getRenderedOutput({props: ['height']}).toJSX()).toEqual( + + + + , + ); + expect( + Fantom.unstable_getDirectManipulationProps(nullthrows(movingRef.current)) + .transform, + ).toEqual([{translateX: 50}]); + + Fantom.runTask(() => { + _translateXAnimation?.stop(); + _siblingHeightAnimation?.stop(); + }); +}); + test('animated opacity', () => { let _opacity; let _opacityAnimation; diff --git a/packages/react-native/Package.swift b/packages/react-native/Package.swift index 0aeb2dff39c2..71564a4d0026 100644 --- a/packages/react-native/Package.swift +++ b/packages/react-native/Package.swift @@ -459,6 +459,7 @@ let reactFabric = RNTarget( path: "ReactCommon/react/renderer", excludedPaths: [ "animated/tests", + "animationbackend/tests", "animations/tests", "attributedstring/tests", "core/tests", diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java index 3fe95a4595db..04c8e7a1a09b 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java @@ -62,6 +62,7 @@ import com.facebook.react.fabric.mounting.MountingManager; import com.facebook.react.fabric.mounting.SurfaceMountingManager; import com.facebook.react.fabric.mounting.mountitems.BatchMountItem; +import com.facebook.react.fabric.mounting.mountitems.BatchedAnimatedPropsMountItem; import com.facebook.react.fabric.mounting.mountitems.DispatchCommandMountItem; import com.facebook.react.fabric.mounting.mountitems.MountItem; import com.facebook.react.fabric.mounting.mountitems.MountItemFactory; @@ -837,6 +838,27 @@ public void synchronouslyUpdateViewOnUIThread(final int reactTag, final Readable ReactMarkerConstants.FABRIC_UPDATE_UI_MAIN_THREAD_END, null, commitNumber); } + /** Called from C++ via JNI with a batch encoded by AnimatedPropBufferEncoder.cpp. */ + @SuppressLint("NotInvokedPrivateMethod") + @SuppressWarnings("unused") + @UiThread + @ThreadConfined(UI) + private void synchronouslyUpdateViewBatch( + int[] intBuffer, double[] doubleBuffer, Object[] rawPropsBuffer) { + UiThreadUtil.assertOnUiThread(); + + int commitNumber = mCurrentSynchronousCommitNumber++; + ReactMarker.logFabricMarker( + ReactMarkerConstants.FABRIC_UPDATE_UI_MAIN_THREAD_START, null, commitNumber); + + new BatchedAnimatedPropsMountItem( + intBuffer, doubleBuffer, rawPropsBuffer, mMountItemDispatcher) + .execute(mMountingManager); + + ReactMarker.logFabricMarker( + ReactMarkerConstants.FABRIC_UPDATE_UI_MAIN_THREAD_END, null, commitNumber); + } + /** Called from C++ via JNI. */ @SuppressLint("NotInvokedPrivateMethod") @SuppressWarnings("unused") diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.kt index fc1f576da33d..72b717d243da 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.kt @@ -268,6 +268,16 @@ internal class MountingManager( getSurfaceManagerForView(reactTag)?.storeSynchronousMountPropsOverride(reactTag, props) } + /** Returns false when no surface owns [reactTag] yet, so the caller can queue the update. */ + @UiThread + fun updateAnimatedPropsSynchronously(reactTag: Int, props: ReadableMap): Boolean { + assertOnUiThread() + val surfaceMountingManager = getSurfaceManagerForView(reactTag) ?: return false + surfaceMountingManager.storeSynchronousMountPropsOverride(reactTag, props) + surfaceMountingManager.updatePropsSynchronously(reactTag, props) + return true + } + @UiThread fun updatePropsSynchronously(reactTag: Int, props: ReadableMap?) { assertOnUiThread() diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/BatchedAnimatedPropsMountItem.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/BatchedAnimatedPropsMountItem.kt new file mode 100644 index 000000000000..21807a3ade44 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/BatchedAnimatedPropsMountItem.kt @@ -0,0 +1,274 @@ +/* + * Copyright (c) Meta Platforms, Inc. and 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 android.view.View +import com.facebook.react.bridge.JavaOnlyArray +import com.facebook.react.bridge.JavaOnlyMap +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.fabric.FabricUIManager.IS_DEVELOPMENT_ENVIRONMENT +import com.facebook.react.fabric.mounting.MountItemDispatcher +import com.facebook.react.fabric.mounting.MountingManager +import com.facebook.react.uimanager.ViewProps + +internal class BatchedAnimatedPropsMountItem( + private val intBuffer: IntArray, + private val doubleBuffer: DoubleArray, + private val rawPropsBuffer: Array, + private val mountItemDispatcher: MountItemDispatcher, +) : MountItem { + + override fun execute(mountingManager: MountingManager) { + decode { viewTag, props -> + try { + if (!mountingManager.updateAnimatedPropsSynchronously(viewTag, props)) { + mountItemDispatcher.addMountItem(SynchronousMountItem(viewTag, props)) + } + } catch (ex: Exception) { + // Match SynchronousMountItem's handling of view-manager failures. + } + } + } + + internal fun decode(apply: (Int, ReadableMap) -> Unit) { + var intIdx = 0 + var doubleIdx = 0 + var rawIdx = 0 + while (intIdx < intBuffer.size) { + val command = intBuffer[intIdx++] + require(command == CMD_START_OF_VIEW) { "Expected start of animated view, got $command" } + val viewTag = intBuffer[intIdx++] + + if (intBuffer[intIdx] == CMD_RAW_PROPS) { + require(intBuffer[intIdx + 1] == CMD_END_OF_VIEW) { "Raw props must be the whole view" } + intIdx += 2 + apply(viewTag, rawPropsBuffer[rawIdx++] as ReadableMap) + continue + } + + val props = JavaOnlyMap() + while (true) { + val cmd = intBuffer[intIdx++] + if (cmd == CMD_END_OF_VIEW) { + break + } + + when (cmd) { + CMD_OPACITY, + CMD_ELEVATION, + CMD_Z_INDEX, + CMD_SHADOW_OPACITY, + CMD_SHADOW_RADIUS, + CMD_OUTLINE_OFFSET, + CMD_OUTLINE_WIDTH -> props.putDouble(commandToString(cmd), doubleBuffer[doubleIdx++]) + CMD_BACKGROUND_COLOR, + CMD_COLOR, + CMD_TINT_COLOR, + CMD_PLACEHOLDER_TEXT_COLOR, + CMD_SHADOW_COLOR, + CMD_BORDER_COLOR, + CMD_BORDER_TOP_COLOR, + CMD_BORDER_BOTTOM_COLOR, + CMD_BORDER_LEFT_COLOR, + CMD_BORDER_RIGHT_COLOR, + CMD_BORDER_START_COLOR, + CMD_BORDER_END_COLOR, + CMD_BORDER_BLOCK_COLOR, + CMD_BORDER_BLOCK_START_COLOR, + CMD_BORDER_BLOCK_END_COLOR, + CMD_OUTLINE_COLOR -> props.putInt(commandToString(cmd), intBuffer[intIdx++]) + CMD_BORDER_RADIUS, + CMD_BORDER_TOP_LEFT_RADIUS, + CMD_BORDER_TOP_RIGHT_RADIUS, + CMD_BORDER_TOP_START_RADIUS, + CMD_BORDER_TOP_END_RADIUS, + CMD_BORDER_BOTTOM_LEFT_RADIUS, + CMD_BORDER_BOTTOM_RIGHT_RADIUS, + CMD_BORDER_BOTTOM_START_RADIUS, + CMD_BORDER_BOTTOM_END_RADIUS, + CMD_BORDER_START_START_RADIUS, + CMD_BORDER_START_END_RADIUS, + CMD_BORDER_END_START_RADIUS, + CMD_BORDER_END_END_RADIUS -> + putLength( + props, + commandToString(cmd), + intBuffer[intIdx++], + doubleBuffer[doubleIdx++], + ) + CMD_START_OF_TRANSFORM -> { + val transform = JavaOnlyArray() + while (true) { + val transformCmd = intBuffer[intIdx++] + if (transformCmd == CMD_END_OF_TRANSFORM) { + break + } + val name = transformCommandToString(transformCmd) + val entry = JavaOnlyMap() + when (transformCmd) { + CMD_TRANSLATE_X, + CMD_TRANSLATE_Y -> + putLength(entry, name, intBuffer[intIdx++], doubleBuffer[doubleIdx++]) + CMD_MATRIX -> { + val matrix = JavaOnlyArray() + repeat(16) { matrix.pushDouble(doubleBuffer[doubleIdx++]) } + entry.putArray(name, matrix) + } + // Angles are in radians. + else -> entry.putDouble(name, doubleBuffer[doubleIdx++]) + } + transform.pushMap(entry) + } + props.putArray(ViewProps.TRANSFORM, transform) + } + else -> error("Unknown animated prop command: $cmd") + } + } + + apply(viewTag, props) + } + require(doubleIdx == doubleBuffer.size && rawIdx == rawPropsBuffer.size) { + "Unused values in animated props buffer" + } + } + + override fun toString(): String { + if (!IS_DEVELOPMENT_ENVIRONMENT) return "BATCHED UPDATE PROPS " + val updates = mutableListOf() + decode { tag, props -> updates.add("[$tag]: ${props.toHashMap()}") } + return "BATCHED UPDATE PROPS ${updates.joinToString()}" + } + + override fun getSurfaceId(): Int = View.NO_ID + + companion object { + // Keep command values in sync with AnimatedPropBufferEncoder.cpp. + private const val CMD_START_OF_VIEW = 1 + private const val CMD_START_OF_TRANSFORM = 2 + private const val CMD_END_OF_TRANSFORM = 3 + private const val CMD_END_OF_VIEW = 4 + private const val CMD_RAW_PROPS = 5 + private const val CMD_OPACITY = 10 + private const val CMD_ELEVATION = 11 + private const val CMD_Z_INDEX = 12 + private const val CMD_SHADOW_OPACITY = 13 + private const val CMD_SHADOW_RADIUS = 14 + private const val CMD_BACKGROUND_COLOR = 15 + private const val CMD_COLOR = 16 + private const val CMD_TINT_COLOR = 17 + private const val CMD_PLACEHOLDER_TEXT_COLOR = 18 + private const val CMD_SHADOW_COLOR = 19 + private const val CMD_BORDER_RADIUS = 20 + private const val CMD_BORDER_TOP_LEFT_RADIUS = 21 + private const val CMD_BORDER_TOP_RIGHT_RADIUS = 22 + private const val CMD_BORDER_TOP_START_RADIUS = 23 + private const val CMD_BORDER_TOP_END_RADIUS = 24 + private const val CMD_BORDER_BOTTOM_LEFT_RADIUS = 25 + private const val CMD_BORDER_BOTTOM_RIGHT_RADIUS = 26 + private const val CMD_BORDER_BOTTOM_START_RADIUS = 27 + private const val CMD_BORDER_BOTTOM_END_RADIUS = 28 + private const val CMD_BORDER_START_START_RADIUS = 29 + private const val CMD_BORDER_START_END_RADIUS = 30 + private const val CMD_BORDER_END_START_RADIUS = 31 + private const val CMD_BORDER_END_END_RADIUS = 32 + private const val CMD_BORDER_COLOR = 40 + private const val CMD_BORDER_TOP_COLOR = 41 + private const val CMD_BORDER_BOTTOM_COLOR = 42 + private const val CMD_BORDER_LEFT_COLOR = 43 + private const val CMD_BORDER_RIGHT_COLOR = 44 + private const val CMD_BORDER_START_COLOR = 45 + private const val CMD_BORDER_END_COLOR = 46 + private const val CMD_BORDER_BLOCK_COLOR = 47 + private const val CMD_BORDER_BLOCK_START_COLOR = 48 + private const val CMD_BORDER_BLOCK_END_COLOR = 49 + private const val CMD_OUTLINE_COLOR = 50 + private const val CMD_OUTLINE_OFFSET = 51 + private const val CMD_OUTLINE_WIDTH = 52 + private const val CMD_TRANSLATE_X = 100 + private const val CMD_TRANSLATE_Y = 101 + private const val CMD_SCALE = 102 + private const val CMD_SCALE_X = 103 + private const val CMD_SCALE_Y = 104 + private const val CMD_ROTATE = 105 + private const val CMD_ROTATE_X = 106 + private const val CMD_ROTATE_Y = 107 + private const val CMD_ROTATE_Z = 108 + private const val CMD_SKEW_X = 109 + private const val CMD_SKEW_Y = 110 + private const val CMD_MATRIX = 111 + private const val CMD_PERSPECTIVE = 112 + private const val CMD_UNIT_PX = 202 + private const val CMD_UNIT_PERCENT = 203 + + private fun putLength(map: JavaOnlyMap, name: String, unit: Int, value: Double) = + when (unit) { + CMD_UNIT_PX -> map.putDouble(name, value) + CMD_UNIT_PERCENT -> map.putString(name, "$value%") + else -> error("Unknown length unit: $unit") + } + + private fun commandToString(command: Int): String = + when (command) { + CMD_OPACITY -> ViewProps.OPACITY + CMD_ELEVATION -> ViewProps.ELEVATION + CMD_Z_INDEX -> ViewProps.Z_INDEX + CMD_SHADOW_OPACITY -> "shadowOpacity" + CMD_SHADOW_RADIUS -> "shadowRadius" + CMD_BACKGROUND_COLOR -> ViewProps.BACKGROUND_COLOR + CMD_COLOR -> ViewProps.COLOR + CMD_TINT_COLOR -> "tintColor" + CMD_PLACEHOLDER_TEXT_COLOR -> "placeholderTextColor" + CMD_SHADOW_COLOR -> ViewProps.SHADOW_COLOR + CMD_BORDER_RADIUS -> ViewProps.BORDER_RADIUS + CMD_BORDER_TOP_LEFT_RADIUS -> ViewProps.BORDER_TOP_LEFT_RADIUS + CMD_BORDER_TOP_RIGHT_RADIUS -> ViewProps.BORDER_TOP_RIGHT_RADIUS + CMD_BORDER_TOP_START_RADIUS -> ViewProps.BORDER_TOP_START_RADIUS + CMD_BORDER_TOP_END_RADIUS -> ViewProps.BORDER_TOP_END_RADIUS + CMD_BORDER_BOTTOM_LEFT_RADIUS -> ViewProps.BORDER_BOTTOM_LEFT_RADIUS + CMD_BORDER_BOTTOM_RIGHT_RADIUS -> ViewProps.BORDER_BOTTOM_RIGHT_RADIUS + CMD_BORDER_BOTTOM_START_RADIUS -> ViewProps.BORDER_BOTTOM_START_RADIUS + CMD_BORDER_BOTTOM_END_RADIUS -> ViewProps.BORDER_BOTTOM_END_RADIUS + CMD_BORDER_START_START_RADIUS -> ViewProps.BORDER_START_START_RADIUS + CMD_BORDER_START_END_RADIUS -> ViewProps.BORDER_START_END_RADIUS + CMD_BORDER_END_START_RADIUS -> ViewProps.BORDER_END_START_RADIUS + CMD_BORDER_END_END_RADIUS -> ViewProps.BORDER_END_END_RADIUS + CMD_BORDER_COLOR -> ViewProps.BORDER_COLOR + CMD_BORDER_TOP_COLOR -> ViewProps.BORDER_TOP_COLOR + CMD_BORDER_BOTTOM_COLOR -> ViewProps.BORDER_BOTTOM_COLOR + CMD_BORDER_LEFT_COLOR -> ViewProps.BORDER_LEFT_COLOR + CMD_BORDER_RIGHT_COLOR -> ViewProps.BORDER_RIGHT_COLOR + CMD_BORDER_START_COLOR -> ViewProps.BORDER_START_COLOR + CMD_BORDER_END_COLOR -> ViewProps.BORDER_END_COLOR + CMD_BORDER_BLOCK_COLOR -> ViewProps.BORDER_BLOCK_COLOR + CMD_BORDER_BLOCK_START_COLOR -> ViewProps.BORDER_BLOCK_START_COLOR + CMD_BORDER_BLOCK_END_COLOR -> ViewProps.BORDER_BLOCK_END_COLOR + CMD_OUTLINE_COLOR -> ViewProps.OUTLINE_COLOR + CMD_OUTLINE_OFFSET -> ViewProps.OUTLINE_OFFSET + CMD_OUTLINE_WIDTH -> ViewProps.OUTLINE_WIDTH + else -> error("Unknown animated prop command: $command") + } + + private fun transformCommandToString(command: Int): String = + when (command) { + CMD_TRANSLATE_X -> ViewProps.TRANSLATE_X + CMD_TRANSLATE_Y -> ViewProps.TRANSLATE_Y + CMD_SCALE -> "scale" + CMD_SCALE_X -> ViewProps.SCALE_X + CMD_SCALE_Y -> ViewProps.SCALE_Y + CMD_ROTATE -> "rotate" + CMD_ROTATE_X -> "rotateX" + CMD_ROTATE_Y -> "rotateY" + CMD_ROTATE_Z -> "rotateZ" + CMD_SKEW_X -> "skewX" + CMD_SKEW_Y -> "skewY" + CMD_MATRIX -> "matrix" + CMD_PERSPECTIVE -> "perspective" + else -> error("Unknown animated prop command: $command") + } + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/TransformHelper.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/TransformHelper.kt index 8c713b98fbec..189c3ae53111 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/TransformHelper.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/TransformHelper.kt @@ -141,6 +141,7 @@ public object TransformHelper { val scale = transform.getDouble(transformType) MatrixMathHelper.applyScaleX(helperMatrix, scale) MatrixMathHelper.applyScaleY(helperMatrix, scale) + MatrixMathHelper.applyScaleZ(helperMatrix, scale) } "scaleX" -> MatrixMathHelper.applyScaleX(helperMatrix, transform.getDouble(transformType)) "scaleY" -> MatrixMathHelper.applyScaleY(helperMatrix, transform.getDouble(transformType)) diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/AnimatedPropBufferEncoder.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/AnimatedPropBufferEncoder.cpp new file mode 100644 index 000000000000..a619b78b31d7 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/AnimatedPropBufferEncoder.cpp @@ -0,0 +1,393 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "AnimatedPropBufferEncoder.h" + +#include +#include +#include +#include +#include +#include + +namespace facebook::react { + +namespace { + +// A view is START_OF_VIEW, tag, its props, END_OF_VIEW. Props take their values +// from the int and double buffers in order. A view with a prop that has no +// command is RAW_PROPS instead, taking the next map of the raw props buffer. +// Keep in sync with BatchedAnimatedPropsMountItem.kt on the Java side. + +constexpr int CMD_START_OF_VIEW = 1; +constexpr int CMD_START_OF_TRANSFORM = 2; +constexpr int CMD_END_OF_TRANSFORM = 3; +constexpr int CMD_END_OF_VIEW = 4; +constexpr int CMD_RAW_PROPS = 5; + +constexpr int CMD_OPACITY = 10; +constexpr int CMD_ELEVATION = 11; +constexpr int CMD_Z_INDEX = 12; +constexpr int CMD_SHADOW_OPACITY = 13; +constexpr int CMD_SHADOW_RADIUS = 14; + +constexpr int CMD_BACKGROUND_COLOR = 15; +constexpr int CMD_COLOR = 16; +constexpr int CMD_TINT_COLOR = 17; +constexpr int CMD_PLACEHOLDER_TEXT_COLOR = 18; +constexpr int CMD_SHADOW_COLOR = 19; + +constexpr int CMD_BORDER_RADIUS = 20; +constexpr int CMD_BORDER_TOP_LEFT_RADIUS = 21; +constexpr int CMD_BORDER_TOP_RIGHT_RADIUS = 22; +constexpr int CMD_BORDER_TOP_START_RADIUS = 23; +constexpr int CMD_BORDER_TOP_END_RADIUS = 24; +constexpr int CMD_BORDER_BOTTOM_LEFT_RADIUS = 25; +constexpr int CMD_BORDER_BOTTOM_RIGHT_RADIUS = 26; +constexpr int CMD_BORDER_BOTTOM_START_RADIUS = 27; +constexpr int CMD_BORDER_BOTTOM_END_RADIUS = 28; +constexpr int CMD_BORDER_START_START_RADIUS = 29; +constexpr int CMD_BORDER_START_END_RADIUS = 30; +constexpr int CMD_BORDER_END_START_RADIUS = 31; +constexpr int CMD_BORDER_END_END_RADIUS = 32; + +constexpr int CMD_BORDER_COLOR = 40; +constexpr int CMD_BORDER_TOP_COLOR = 41; +constexpr int CMD_BORDER_BOTTOM_COLOR = 42; +constexpr int CMD_BORDER_LEFT_COLOR = 43; +constexpr int CMD_BORDER_RIGHT_COLOR = 44; +constexpr int CMD_BORDER_START_COLOR = 45; +constexpr int CMD_BORDER_END_COLOR = 46; +constexpr int CMD_BORDER_BLOCK_COLOR = 47; +constexpr int CMD_BORDER_BLOCK_START_COLOR = 48; +constexpr int CMD_BORDER_BLOCK_END_COLOR = 49; + +constexpr int CMD_OUTLINE_COLOR = 50; +constexpr int CMD_OUTLINE_OFFSET = 51; +constexpr int CMD_OUTLINE_WIDTH = 52; + +constexpr int CMD_TRANSFORM_TRANSLATE_X = 100; +constexpr int CMD_TRANSFORM_TRANSLATE_Y = 101; +constexpr int CMD_TRANSFORM_SCALE = 102; +constexpr int CMD_TRANSFORM_SCALE_X = 103; +constexpr int CMD_TRANSFORM_SCALE_Y = 104; +constexpr int CMD_TRANSFORM_ROTATE = 105; +constexpr int CMD_TRANSFORM_ROTATE_X = 106; +constexpr int CMD_TRANSFORM_ROTATE_Y = 107; +constexpr int CMD_TRANSFORM_ROTATE_Z = 108; +constexpr int CMD_TRANSFORM_SKEW_X = 109; +constexpr int CMD_TRANSFORM_SKEW_Y = 110; +constexpr int CMD_TRANSFORM_MATRIX = 111; +constexpr int CMD_TRANSFORM_PERSPECTIVE = 112; + +constexpr int CMD_UNIT_PX = 202; +constexpr int CMD_UNIT_PERCENT = 203; + +std::optional propNameToCommand(const std::string& name) { + static const std::unordered_map kMap = { + {"opacity", CMD_OPACITY}, + {"elevation", CMD_ELEVATION}, + {"zIndex", CMD_Z_INDEX}, + {"shadowOpacity", CMD_SHADOW_OPACITY}, + {"shadowRadius", CMD_SHADOW_RADIUS}, + {"backgroundColor", CMD_BACKGROUND_COLOR}, + {"color", CMD_COLOR}, + {"tintColor", CMD_TINT_COLOR}, + {"placeholderTextColor", CMD_PLACEHOLDER_TEXT_COLOR}, + {"shadowColor", CMD_SHADOW_COLOR}, + {"borderRadius", CMD_BORDER_RADIUS}, + {"borderTopLeftRadius", CMD_BORDER_TOP_LEFT_RADIUS}, + {"borderTopRightRadius", CMD_BORDER_TOP_RIGHT_RADIUS}, + {"borderTopStartRadius", CMD_BORDER_TOP_START_RADIUS}, + {"borderTopEndRadius", CMD_BORDER_TOP_END_RADIUS}, + {"borderBottomLeftRadius", CMD_BORDER_BOTTOM_LEFT_RADIUS}, + {"borderBottomRightRadius", CMD_BORDER_BOTTOM_RIGHT_RADIUS}, + {"borderBottomStartRadius", CMD_BORDER_BOTTOM_START_RADIUS}, + {"borderBottomEndRadius", CMD_BORDER_BOTTOM_END_RADIUS}, + {"borderStartStartRadius", CMD_BORDER_START_START_RADIUS}, + {"borderStartEndRadius", CMD_BORDER_START_END_RADIUS}, + {"borderEndStartRadius", CMD_BORDER_END_START_RADIUS}, + {"borderEndEndRadius", CMD_BORDER_END_END_RADIUS}, + {"borderColor", CMD_BORDER_COLOR}, + {"borderTopColor", CMD_BORDER_TOP_COLOR}, + {"borderBottomColor", CMD_BORDER_BOTTOM_COLOR}, + {"borderLeftColor", CMD_BORDER_LEFT_COLOR}, + {"borderRightColor", CMD_BORDER_RIGHT_COLOR}, + {"borderStartColor", CMD_BORDER_START_COLOR}, + {"borderEndColor", CMD_BORDER_END_COLOR}, + {"borderBlockColor", CMD_BORDER_BLOCK_COLOR}, + {"borderBlockStartColor", CMD_BORDER_BLOCK_START_COLOR}, + {"borderBlockEndColor", CMD_BORDER_BLOCK_END_COLOR}, + {"outlineColor", CMD_OUTLINE_COLOR}, + {"outlineOffset", CMD_OUTLINE_OFFSET}, + {"outlineWidth", CMD_OUTLINE_WIDTH}, + {"transform", CMD_START_OF_TRANSFORM}, + }; + auto it = kMap.find(name); + if (it == kMap.end()) { + return std::nullopt; + } + return it->second; +} + +std::optional transformNameToCommand(const std::string& name) { + static const std::unordered_map kMap = { + {"translateX", CMD_TRANSFORM_TRANSLATE_X}, + {"translateY", CMD_TRANSFORM_TRANSLATE_Y}, + {"scale", CMD_TRANSFORM_SCALE}, + {"scaleX", CMD_TRANSFORM_SCALE_X}, + {"scaleY", CMD_TRANSFORM_SCALE_Y}, + {"rotate", CMD_TRANSFORM_ROTATE}, + {"rotateX", CMD_TRANSFORM_ROTATE_X}, + {"rotateY", CMD_TRANSFORM_ROTATE_Y}, + {"rotateZ", CMD_TRANSFORM_ROTATE_Z}, + {"skewX", CMD_TRANSFORM_SKEW_X}, + {"skewY", CMD_TRANSFORM_SKEW_Y}, + {"matrix", CMD_TRANSFORM_MATRIX}, + {"perspective", CMD_TRANSFORM_PERSPECTIVE}, + }; + auto it = kMap.find(name); + if (it == kMap.end()) { + return std::nullopt; + } + return it->second; +} + +bool packLength( + const folly::dynamic& value, + std::vector& intBuffer, + std::vector& doubleBuffer) { + if (value.isNumber()) { + intBuffer.push_back(CMD_UNIT_PX); + doubleBuffer.push_back(value.asDouble()); + return true; + } + if (!value.isString() || !value.getString().ends_with("%")) { + return false; + } + const auto& text = value.getString(); + auto percent = folly::tryTo(text.substr(0, text.size() - 1)); + if (!percent.hasValue()) { + return false; + } + intBuffer.push_back(CMD_UNIT_PERCENT); + doubleBuffer.push_back(percent.value()); + return true; +} + +std::optional toRadians(const folly::dynamic& value) { + if (value.isNumber()) { + return value.asDouble(); + } + if (!value.isString()) { + return std::nullopt; + } + const auto& text = value.getString(); + bool isDegrees = text.ends_with("deg"); + if (!isDegrees && !text.ends_with("rad")) { + return std::nullopt; + } + auto angle = folly::tryTo(text.substr(0, text.size() - 3)); + if (!angle.hasValue()) { + return std::nullopt; + } + return isDegrees ? angle.value() * std::numbers::pi / 180 : angle.value(); +} + +bool packTransformToBuffers( + const folly::dynamic& transform, + std::vector& intBuffer, + std::vector& doubleBuffer) { + if (!transform.isArray()) { + return false; + } + intBuffer.push_back(CMD_START_OF_TRANSFORM); + for (const auto& item : transform) { + if (!item.isObject() || item.size() != 1) { + return false; + } + const auto& [name, value] = *item.items().begin(); + auto cmd = transformNameToCommand(name.getString()); + if (!cmd.has_value()) { + return false; + } + intBuffer.push_back(cmd.value()); + switch (cmd.value()) { + case CMD_TRANSFORM_SCALE: + case CMD_TRANSFORM_SCALE_X: + case CMD_TRANSFORM_SCALE_Y: + case CMD_TRANSFORM_PERSPECTIVE: + if (!value.isNumber()) { + return false; + } + doubleBuffer.push_back(value.asDouble()); + break; + case CMD_TRANSFORM_TRANSLATE_X: + case CMD_TRANSFORM_TRANSLATE_Y: + if (!packLength(value, intBuffer, doubleBuffer)) { + return false; + } + break; + case CMD_TRANSFORM_ROTATE: + case CMD_TRANSFORM_ROTATE_X: + case CMD_TRANSFORM_ROTATE_Y: + case CMD_TRANSFORM_ROTATE_Z: + case CMD_TRANSFORM_SKEW_X: + case CMD_TRANSFORM_SKEW_Y: { + auto radians = toRadians(value); + if (!radians.has_value()) { + return false; + } + doubleBuffer.push_back(radians.value()); + break; + } + case CMD_TRANSFORM_MATRIX: + if (!value.isArray() || value.size() != 16) { + return false; + } + for (const auto& element : value) { + if (!element.isNumber()) { + return false; + } + doubleBuffer.push_back(element.asDouble()); + } + break; + default: + return false; + } + } + intBuffer.push_back(CMD_END_OF_TRANSFORM); + return true; +} + +bool packDynamicEntryToBuffers( + const std::string& key, + const folly::dynamic& value, + std::vector& intBuffer, + std::vector& doubleBuffer) { + auto cmd = propNameToCommand(key); + if (!cmd.has_value()) { + return false; + } + + switch (cmd.value()) { + case CMD_OPACITY: + case CMD_ELEVATION: + case CMD_Z_INDEX: + case CMD_SHADOW_OPACITY: + case CMD_SHADOW_RADIUS: + case CMD_OUTLINE_OFFSET: + case CMD_OUTLINE_WIDTH: + if (!value.isNumber()) { + return false; + } + intBuffer.push_back(cmd.value()); + doubleBuffer.push_back(value.asDouble()); + return true; + + case CMD_BACKGROUND_COLOR: + case CMD_COLOR: + case CMD_TINT_COLOR: + case CMD_PLACEHOLDER_TEXT_COLOR: + case CMD_SHADOW_COLOR: + case CMD_BORDER_COLOR: + case CMD_BORDER_TOP_COLOR: + case CMD_BORDER_BOTTOM_COLOR: + case CMD_BORDER_LEFT_COLOR: + case CMD_BORDER_RIGHT_COLOR: + case CMD_BORDER_START_COLOR: + case CMD_BORDER_END_COLOR: + case CMD_BORDER_BLOCK_COLOR: + case CMD_BORDER_BLOCK_START_COLOR: + case CMD_BORDER_BLOCK_END_COLOR: + case CMD_OUTLINE_COLOR: { + if (!value.isNumber()) { + return false; + } + auto color = value.isInt() ? folly::tryTo(value.getInt()) + : folly::tryTo(value.getDouble()); + if (!color.hasValue()) { + return false; + } + intBuffer.push_back(cmd.value()); + intBuffer.push_back(static_cast(color.value())); + return true; + } + + case CMD_BORDER_RADIUS: + case CMD_BORDER_TOP_LEFT_RADIUS: + case CMD_BORDER_TOP_RIGHT_RADIUS: + case CMD_BORDER_TOP_START_RADIUS: + case CMD_BORDER_TOP_END_RADIUS: + case CMD_BORDER_BOTTOM_LEFT_RADIUS: + case CMD_BORDER_BOTTOM_RIGHT_RADIUS: + case CMD_BORDER_BOTTOM_START_RADIUS: + case CMD_BORDER_BOTTOM_END_RADIUS: + case CMD_BORDER_START_START_RADIUS: + case CMD_BORDER_START_END_RADIUS: + case CMD_BORDER_END_START_RADIUS: + case CMD_BORDER_END_END_RADIUS: + intBuffer.push_back(cmd.value()); + return packLength(value, intBuffer, doubleBuffer); + + case CMD_START_OF_TRANSFORM: + return packTransformToBuffers(value, intBuffer, doubleBuffer); + + default: + return false; + } +} + +bool packAnimatedPropsToBuffers( + const AnimatedProps& animatedProps, + std::vector& intBuffer, + std::vector& doubleBuffer) { + if (!animatedProps.props.empty()) { + return false; + } + if (animatedProps.rawProps) { + std::optional converted; + const auto* rawProps = animatedProps.rawProps->getDynamic(); + if (rawProps == nullptr) { + rawProps = &converted.emplace(animatedProps.rawProps->toDynamic()); + } + for (const auto& [key, value] : rawProps->items()) { + if (!packDynamicEntryToBuffers( + key.getString(), value, intBuffer, doubleBuffer)) { + return false; + } + } + } + return true; +} + +} // namespace + +AnimatedPropsBuffer encodeAnimatedProps( + const std::unordered_map& updates) { + AnimatedPropsBuffer buffer; + buffer.ints.reserve(updates.size() * 4); + buffer.doubles.reserve(updates.size()); + for (const auto& [tag, animatedProps] : updates) { + buffer.ints.push_back(CMD_START_OF_VIEW); + buffer.ints.push_back(tag); + auto intSize = buffer.ints.size(); + auto doubleSize = buffer.doubles.size(); + if (!packAnimatedPropsToBuffers( + animatedProps, buffer.ints, buffer.doubles)) { + buffer.ints.resize(intSize); + buffer.doubles.resize(doubleSize); + buffer.ints.push_back(CMD_RAW_PROPS); + buffer.rawProps.push_back( + animationbackend::packAnimatedProps(animatedProps)); + } + buffer.ints.push_back(CMD_END_OF_VIEW); + } + return buffer; +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/AnimatedPropBufferEncoder.h b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/AnimatedPropBufferEncoder.h new file mode 100644 index 000000000000..316c910aa358 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/AnimatedPropBufferEncoder.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include + +namespace facebook::react { + +struct AnimatedPropsBuffer { + std::vector ints; + std::vector doubles; + std::vector rawProps; +}; + +AnimatedPropsBuffer encodeAnimatedProps(const std::unordered_map &updates); + +} // namespace facebook::react diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp index f1e15694da63..4be462f7c934 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp @@ -7,6 +7,7 @@ #include "FabricMountingManager.h" +#include "AnimatedPropBufferEncoder.h" #include "EventEmitterWrapper.h" #include "MountItem.h" #include "StateWrapperImpl.h" @@ -1246,6 +1247,34 @@ void FabricMountingManager::synchronouslyUpdateViewOnUIThread( synchronouslyUpdateViewOnUIThreadJNI(javaUIManager_, viewTag, propsMap); } +void FabricMountingManager::synchronouslyUpdateAnimatedProps( + const std::unordered_map& updates) { + TraceSection s( + "FabricMountingManager::synchronouslyUpdateAnimatedProps", + "viewCount", + updates.size()); + static auto synchronouslyUpdateAnimatedPropsJNI = + JFabricUIManager::javaClassStatic() + ->getMethod)>( + "synchronouslyUpdateViewBatch"); + auto buffer = encodeAnimatedProps(updates); + auto ints = jni::JArrayInt::newArray(buffer.ints.size()); + ints->setRegion( + 0, static_cast(buffer.ints.size()), buffer.ints.data()); + auto doubles = jni::JArrayDouble::newArray(buffer.doubles.size()); + doubles->setRegion( + 0, static_cast(buffer.doubles.size()), buffer.doubles.data()); + auto rawProps = jni::JArrayClass::newArray(buffer.rawProps.size()); + for (size_t i = 0; i < buffer.rawProps.size(); i++) { + rawProps->setElement( + i, + ReadableNativeMap::newObjectCxxArgs(std::move(buffer.rawProps[i])) + .get()); + } + synchronouslyUpdateAnimatedPropsJNI( + javaUIManager_, ints.get(), doubles.get(), rawProps.get()); +} + void FabricMountingManager::captureViewSnapshot(Tag tag, SurfaceId surfaceId) { static auto captureViewSnapshotJNI = JFabricUIManager::javaClassStatic()->getMethod( diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.h b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.h index 3d31c58db7eb..4067098e5c09 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.h @@ -17,6 +17,8 @@ namespace facebook::react { +struct AnimatedProps; + class MountingTransaction; struct ShadowView; @@ -84,6 +86,8 @@ class FabricMountingManager final { void synchronouslyUpdateViewOnUIThread(Tag viewTag, const folly::dynamic &props); + void synchronouslyUpdateAnimatedProps(const std::unordered_map &updates); + void captureViewSnapshot(Tag tag, SurfaceId surfaceId); void setViewSnapshot(Tag sourceTag, Tag targetTag, SurfaceId surfaceId); diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp index 2d2a2f3d37f4..d5c154f1e6f1 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp @@ -863,6 +863,13 @@ void FabricUIManagerBinding::schedulerShouldSynchronouslyUpdateViewOnUIThread( } } +void FabricUIManagerBinding::schedulerShouldSynchronouslyUpdateAnimatedProps( + const std::unordered_map& updates) { + if (ReactNativeFeatureFlags::cxxNativeAnimatedEnabled() && mountingManager_) { + mountingManager_->synchronouslyUpdateAnimatedProps(updates); + } +} + void FabricUIManagerBinding::schedulerDidUpdateShadowTree( const std::unordered_map& /*tagToProps*/) { // no-op diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.h b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.h index ea4b928357ec..97229f08fdcf 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.h @@ -115,6 +115,8 @@ class FabricUIManagerBinding : public jni::HybridClass, void schedulerShouldSynchronouslyUpdateViewOnUIThread(Tag tag, const folly::dynamic &props) override; + void schedulerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map &updates) override; + void schedulerDidUpdateShadowTree(const std::unordered_map &tagToProps) override; void schedulerDidCaptureViewSnapshot(Tag tag, SurfaceId surfaceId) override; diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/test/AnimatedPropBufferEncoderTest.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/test/AnimatedPropBufferEncoderTest.cpp new file mode 100644 index 000000000000..4e18b43b7c3d --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/test/AnimatedPropBufferEncoderTest.cpp @@ -0,0 +1,401 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include +#include +#include +#include +#include +#include + +namespace facebook::react { + +namespace { + +// Protocol codes read by BatchedAnimatedPropsMountItem. +enum Command : int { + CMD_START_OF_VIEW = 1, + CMD_START_OF_TRANSFORM = 2, + CMD_END_OF_TRANSFORM = 3, + CMD_END_OF_VIEW = 4, + CMD_RAW_PROPS = 5, + CMD_OPACITY = 10, + CMD_BACKGROUND_COLOR = 15, + CMD_PLACEHOLDER_TEXT_COLOR = 18, + CMD_SHADOW_COLOR = 19, + CMD_BORDER_RADIUS = 20, + CMD_BORDER_BLOCK_COLOR = 47, + CMD_BORDER_BLOCK_START_COLOR = 48, + CMD_BORDER_BLOCK_END_COLOR = 49, + CMD_OUTLINE_COLOR = 50, + CMD_OUTLINE_OFFSET = 51, + CMD_OUTLINE_WIDTH = 52, + CMD_TRANSFORM_TRANSLATE_X = 100, + CMD_TRANSFORM_ROTATE = 105, + CMD_TRANSFORM_SKEW_Y = 110, + CMD_UNIT_PX = 202, + CMD_UNIT_PERCENT = 203, +}; + +} // namespace + +TEST( + AnimatedPropBufferEncoderTest, + encodesDynamicOpacityAndTransformWithoutConsumingInput) { + folly::dynamic rawProps = folly::dynamic::object("opacity", 0.25)( + "transform", + folly::dynamic::array( + folly::dynamic::object("translateX", 10), + folly::dynamic::object("scale", 2))); + std::unordered_map updates; + updates.emplace(10, AnimatedProps{{}, std::make_unique(rawProps)}); + auto buffer = encodeAnimatedProps(updates); + EXPECT_EQ(updates.at(10).rawProps->toDynamic(), rawProps); + EXPECT_EQ(buffer.ints[0], CMD_START_OF_VIEW); + EXPECT_EQ(buffer.ints[1], 10); + EXPECT_EQ(buffer.ints.back(), CMD_END_OF_VIEW); + EXPECT_TRUE(buffer.rawProps.empty()); + auto again = encodeAnimatedProps(updates); + EXPECT_EQ(buffer.ints, again.ints); + EXPECT_EQ(buffer.doubles, again.doubles); +} + +TEST(AnimatedPropBufferEncoderTest, copiesFallbackPropsWithoutConsumingInput) { + folly::dynamic rawProps = folly::dynamic::object("opacity", nullptr)( + "transform", + folly::dynamic::array(folly::dynamic::object("futureTransform", 1)))( + "shadowOffset", folly::dynamic::object("width", 2)("height", 3)); + std::unordered_map updates; + updates.emplace(10, AnimatedProps{{}, std::make_unique(rawProps)}); + auto buffer = encodeAnimatedProps(updates); + EXPECT_EQ(updates.at(10).rawProps->toDynamic(), rawProps); + ASSERT_EQ(buffer.rawProps.size(), 1); + EXPECT_EQ(buffer.rawProps[0], rawProps); + EXPECT_EQ( + buffer.ints, + (std::vector{ + CMD_START_OF_VIEW, 10, CMD_RAW_PROPS, CMD_END_OF_VIEW})); + EXPECT_TRUE(buffer.doubles.empty()); +} + +TEST(AnimatedPropBufferEncoderTest, convertsJSIProps) { + auto runtime = hermes::makeHermesRuntime(); + auto object = jsi::Object(*runtime); + object.setProperty(*runtime, "opacity", 0.5); + std::unordered_map updates; + updates.emplace( + 10, + AnimatedProps{ + {}, + std::make_unique(*runtime, jsi::Value(*runtime, object))}); + auto buffer = encodeAnimatedProps(updates); + EXPECT_EQ( + buffer.ints, + (std::vector{CMD_START_OF_VIEW, 10, CMD_OPACITY, CMD_END_OF_VIEW})); + EXPECT_EQ(buffer.doubles, (std::vector{0.5})); + EXPECT_TRUE(buffer.rawProps.empty()); +} + +TEST(AnimatedPropBufferEncoderTest, convertsJSIFallbackWithoutConsumingInput) { + auto runtime = hermes::makeHermesRuntime(); + auto object = jsi::Object(*runtime); + object.setProperty(*runtime, "testID", "animated-view"); + std::unordered_map updates; + updates.emplace( + 10, + AnimatedProps{ + {}, + std::make_unique(*runtime, jsi::Value(*runtime, object))}); + auto buffer = encodeAnimatedProps(updates); + folly::dynamic expected = folly::dynamic::object("testID", "animated-view"); + ASSERT_EQ(buffer.rawProps.size(), 1); + EXPECT_EQ(buffer.rawProps[0], expected); + EXPECT_EQ(updates.at(10).rawProps->toDynamic(), expected); +} + +namespace { +AnimatedPropsBuffer encodeRawProps(const folly::dynamic& props) { + std::unordered_map updates; + updates.emplace(10, AnimatedProps{{}, std::make_unique(props)}); + return encodeAnimatedProps(updates); +} +} // namespace + +TEST(AnimatedPropBufferEncoderTest, encodesIntegerRadiusAndTranslation) { + auto radius = encodeRawProps(folly::dynamic::object("borderRadius", 4)); + EXPECT_EQ( + radius.ints, + (std::vector{ + CMD_START_OF_VIEW, + 10, + CMD_BORDER_RADIUS, + CMD_UNIT_PX, + CMD_END_OF_VIEW})); + EXPECT_EQ(radius.doubles, (std::vector{4})); + EXPECT_TRUE(radius.rawProps.empty()); + auto transform = encodeRawProps( + folly::dynamic::object( + "transform", + folly::dynamic::array(folly::dynamic::object("translateX", 10)))); + EXPECT_EQ( + transform.ints, + (std::vector{ + CMD_START_OF_VIEW, + 10, + CMD_START_OF_TRANSFORM, + CMD_TRANSFORM_TRANSLATE_X, + CMD_UNIT_PX, + CMD_END_OF_TRANSFORM, + CMD_END_OF_VIEW})); + EXPECT_EQ(transform.doubles, (std::vector{10})); + EXPECT_TRUE(transform.rawProps.empty()); +} + +TEST(AnimatedPropBufferEncoderTest, encodesPercentAndConvertsAnglesToRadians) { + folly::dynamic props = folly::dynamic::object( + "transform", + folly::dynamic::array( + folly::dynamic::object("translateX", "12.345678901%"), + folly::dynamic::object("rotate", "45.123456789deg"), + folly::dynamic::object("skewY", "0.123456789rad"))); + auto buffer = encodeRawProps(props); + EXPECT_EQ( + buffer.ints, + (std::vector{ + CMD_START_OF_VIEW, + 10, + CMD_START_OF_TRANSFORM, + CMD_TRANSFORM_TRANSLATE_X, + CMD_UNIT_PERCENT, + CMD_TRANSFORM_ROTATE, + CMD_TRANSFORM_SKEW_Y, + CMD_END_OF_TRANSFORM, + CMD_END_OF_VIEW})); + EXPECT_EQ( + buffer.doubles, + (std::vector{ + 12.345678901, 45.123456789 * std::numbers::pi / 180, 0.123456789})); + EXPECT_TRUE(buffer.rawProps.empty()); +} + +TEST(AnimatedPropBufferEncoderTest, sendsWholeViewAsRawPropsOnUnsupportedProp) { + folly::dynamic props = folly::dynamic::object("opacity", 0.5)( + "transform", + folly::dynamic::array( + folly::dynamic::object("scale", 2), + folly::dynamic::object("translateX", "auto"))); + auto buffer = encodeRawProps(props); + EXPECT_EQ( + buffer.ints, + (std::vector{ + CMD_START_OF_VIEW, 10, CMD_RAW_PROPS, CMD_END_OF_VIEW})); + EXPECT_TRUE(buffer.doubles.empty()); + ASSERT_EQ(buffer.rawProps.size(), 1); + EXPECT_EQ(buffer.rawProps[0], props); +} + +TEST(AnimatedPropBufferEncoderTest, preservesUnsupportedPropsAndResets) { + folly::dynamic props = + folly::dynamic::object("opacity", nullptr)("backgroundColor", nullptr)( + "transformOrigin", folly::dynamic::array("50%", 10, 0))( + "filter", + folly::dynamic::array(folly::dynamic::object("brightness", 0.5))); + auto buffer = encodeRawProps(props); + EXPECT_EQ( + buffer.ints, + (std::vector{ + CMD_START_OF_VIEW, 10, CMD_RAW_PROPS, CMD_END_OF_VIEW})); + ASSERT_EQ(buffer.rawProps.size(), 1); + EXPECT_EQ(buffer.rawProps[0], props); +} + +TEST(AnimatedPropBufferEncoderTest, rollsBackInvalidRadiusAndMatrix) { + for (const auto& props : std::vector{ + folly::dynamic::object("borderRadius", "wrong"), + folly::dynamic::object( + "transform", + folly::dynamic::array( + folly::dynamic::object( + "matrix", folly::dynamic::array(1, 2, 3))))}) { + auto buffer = encodeRawProps(props); + EXPECT_EQ( + buffer.ints, + (std::vector{ + CMD_START_OF_VIEW, 10, CMD_RAW_PROPS, CMD_END_OF_VIEW})); + EXPECT_TRUE(buffer.doubles.empty()); + ASSERT_EQ(buffer.rawProps.size(), 1); + EXPECT_EQ(buffer.rawProps[0], props); + } +} + +TEST(AnimatedPropBufferEncoderTest, encodesColorWithoutSignLoss) { + auto buffer = encodeRawProps( + folly::dynamic::object("backgroundColor", int64_t{0xff112233})); + EXPECT_EQ( + buffer.ints, + (std::vector{ + CMD_START_OF_VIEW, + 10, + CMD_BACKGROUND_COLOR, + static_cast(0xff112233), + CMD_END_OF_VIEW})); + EXPECT_TRUE(buffer.rawProps.empty()); +} + +TEST(AnimatedPropBufferEncoderTest, encodesAdditionalSynchronousColors) { + for (const auto& [name, command] : std::vector>{ + {"placeholderTextColor", CMD_PLACEHOLDER_TEXT_COLOR}, + {"shadowColor", CMD_SHADOW_COLOR}, + {"borderBlockColor", CMD_BORDER_BLOCK_COLOR}, + {"borderBlockStartColor", CMD_BORDER_BLOCK_START_COLOR}, + {"borderBlockEndColor", CMD_BORDER_BLOCK_END_COLOR}, + {"outlineColor", CMD_OUTLINE_COLOR}}) { + SCOPED_TRACE(name); + for (const auto& color : std::vector{ + int64_t{0xff112233}, double{0xff112233}, int64_t{-15654349}}) { + auto buffer = encodeRawProps(folly::dynamic::object(name, color)); + EXPECT_EQ( + buffer.ints, + (std::vector{ + CMD_START_OF_VIEW, + 10, + command, + static_cast(0xff112233), + CMD_END_OF_VIEW})); + EXPECT_TRUE(buffer.doubles.empty()); + EXPECT_TRUE(buffer.rawProps.empty()); + } + for (const auto& color : std::vector{ + nullptr, + folly::dynamic::object( + "resource_paths", + folly::dynamic::array("?attr/colorAccent"))}) { + folly::dynamic props = folly::dynamic::object(name, color); + auto buffer = encodeRawProps(props); + EXPECT_EQ( + buffer.ints, + (std::vector{ + CMD_START_OF_VIEW, 10, CMD_RAW_PROPS, CMD_END_OF_VIEW})); + EXPECT_TRUE(buffer.doubles.empty()); + ASSERT_EQ(buffer.rawProps.size(), 1); + EXPECT_EQ(buffer.rawProps[0], props); + } + } +} + +TEST(AnimatedPropBufferEncoderTest, encodesDoubleColor) { + auto buffer = encodeRawProps( + folly::dynamic::object("backgroundColor", double{0xff112233})); + EXPECT_EQ( + buffer.ints, + (std::vector{ + CMD_START_OF_VIEW, + 10, + CMD_BACKGROUND_COLOR, + static_cast(0xff112233), + CMD_END_OF_VIEW})); + EXPECT_TRUE(buffer.rawProps.empty()); +} + +TEST( + AnimatedPropBufferEncoderTest, + preservesUnsupportedNumericColorsInFallback) { + for (double color : + {-16777215.5, + 1.5, + std::numeric_limits::lowest(), + std::numeric_limits::max(), + -std::numeric_limits::infinity(), + std::numeric_limits::infinity(), + std::numeric_limits::quiet_NaN()}) { + SCOPED_TRACE(color); + auto buffer = encodeRawProps( + folly::dynamic::object("opacity", 0.5)("backgroundColor", color)); + EXPECT_EQ( + buffer.ints, + (std::vector{ + CMD_START_OF_VIEW, 10, CMD_RAW_PROPS, CMD_END_OF_VIEW})); + EXPECT_TRUE(buffer.doubles.empty()); + ASSERT_EQ(buffer.rawProps.size(), 1); + EXPECT_EQ(buffer.rawProps[0]["opacity"], 0.5); + auto decodedColor = buffer.rawProps[0]["backgroundColor"].asDouble(); + if (std::isnan(color)) { + EXPECT_TRUE(std::isnan(decodedColor)); + } else { + EXPECT_EQ(decodedColor, color); + } + } +} + +TEST(AnimatedPropBufferEncoderTest, encodesOutlineDimensions) { + for (const auto& [name, command] : std::vector>{ + {"outlineOffset", CMD_OUTLINE_OFFSET}, + {"outlineWidth", CMD_OUTLINE_WIDTH}}) { + SCOPED_TRACE(name); + for (const auto& value : std::vector{-2, 1.25}) { + auto buffer = encodeRawProps(folly::dynamic::object(name, value)); + EXPECT_EQ( + buffer.ints, + (std::vector{CMD_START_OF_VIEW, 10, command, CMD_END_OF_VIEW})); + EXPECT_EQ(buffer.doubles, (std::vector{value.asDouble()})); + EXPECT_TRUE(buffer.rawProps.empty()); + } + for (const auto& value : std::vector{nullptr, "10%"}) { + folly::dynamic props = folly::dynamic::object(name, value); + auto buffer = encodeRawProps(props); + EXPECT_EQ( + buffer.ints, + (std::vector{ + CMD_START_OF_VIEW, 10, CMD_RAW_PROPS, CMD_END_OF_VIEW})); + EXPECT_TRUE(buffer.doubles.empty()); + ASSERT_EQ(buffer.rawProps.size(), 1); + EXPECT_EQ(buffer.rawProps[0], props); + } + } +} + +TEST(AnimatedPropBufferEncoderTest, typedPropsOverrideRawPropsInFallback) { + AnimatedPropsBuilder builder; + builder.setShadowOffset(Size{2, 3}); + auto props = builder.get(); + props.rawProps = std::make_unique( + folly::dynamic::object("shadowOffset", nullptr)("opacity", 0.5)); + std::unordered_map updates; + updates.emplace(10, std::move(props)); + auto buffer = encodeAnimatedProps(updates); + EXPECT_EQ( + buffer.ints, + (std::vector{ + CMD_START_OF_VIEW, 10, CMD_RAW_PROPS, CMD_END_OF_VIEW})); + EXPECT_TRUE(buffer.doubles.empty()); + ASSERT_EQ(buffer.rawProps.size(), 1); + folly::dynamic expected = folly::dynamic::object("opacity", 0.5)( + "shadowOffset", folly::dynamic::object("width", 2)("height", 3)); + EXPECT_EQ(buffer.rawProps[0], expected); +} + +TEST(AnimatedPropBufferEncoderTest, sendsTypedPropsAsRawProps) { + AnimatedPropsBuilder builder; + builder.setOpacity(0.75); + auto props = builder.get(); + props.rawProps = + std::make_unique(folly::dynamic::object("opacity", 0.25)); + std::unordered_map updates; + updates.emplace(10, std::move(props)); + auto buffer = encodeAnimatedProps(updates); + EXPECT_EQ( + buffer.ints, + (std::vector{ + CMD_START_OF_VIEW, 10, CMD_RAW_PROPS, CMD_END_OF_VIEW})); + EXPECT_TRUE(buffer.doubles.empty()); + ASSERT_EQ(buffer.rawProps.size(), 1); + folly::dynamic expected = folly::dynamic::object("opacity", 0.75); + EXPECT_EQ(buffer.rawProps[0], expected); +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/BatchedAnimatedPropsMountItemTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/BatchedAnimatedPropsMountItemTest.kt new file mode 100644 index 000000000000..f6c1d767da62 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/BatchedAnimatedPropsMountItemTest.kt @@ -0,0 +1,358 @@ +/* + * Copyright (c) Meta Platforms, Inc. and 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 + +import com.facebook.react.bridge.JavaOnlyMap +import com.facebook.react.bridge.ReactNativeJNISoLoader +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.fabric.mounting.MountItemDispatcher +import com.facebook.react.fabric.mounting.MountingManager +import com.facebook.react.fabric.mounting.mountitems.BatchedAnimatedPropsMountItem +import com.facebook.react.uimanager.MatrixMathHelper +import com.facebook.react.uimanager.TransformHelper +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.within +import org.junit.Test +import org.mockito.Mockito.mockStatic +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +class BatchedAnimatedPropsMountItemTest { + private fun decode( + ints: IntArray, + doubles: DoubleArray, + rawProps: Array = emptyArray(), + ): List> { + val updates = mutableListOf>() + BatchedAnimatedPropsMountItem(ints, doubles, rawProps, mock()).decode { tag, props -> + updates.add(tag to props) + } + return updates + } + + @Test + fun decodesOpacityPerView() { + val updates = + decode( + intArrayOf( + CMD_START_OF_VIEW, + 11, + CMD_OPACITY, + CMD_END_OF_VIEW, + CMD_START_OF_VIEW, + 12, + CMD_OPACITY, + CMD_END_OF_VIEW), + doubleArrayOf(0.25, 0.75)) + + assertThat(updates.map { it.first }).containsExactly(11, 12) + assertThat(updates[0].second.getDouble("opacity")).isEqualTo(0.25) + assertThat(updates[1].second.getDouble("opacity")).isEqualTo(0.75) + } + + @Test + fun decodesAdditionalColorProps() { + val color = 0xff112233.toInt() + for ((command, name) in + listOf( + CMD_PLACEHOLDER_TEXT_COLOR to "placeholderTextColor", + CMD_SHADOW_COLOR to "shadowColor", + CMD_BORDER_BLOCK_COLOR to "borderBlockColor", + CMD_BORDER_BLOCK_START_COLOR to "borderBlockStartColor", + CMD_BORDER_BLOCK_END_COLOR to "borderBlockEndColor", + CMD_OUTLINE_COLOR to "outlineColor", + )) { + val props = + decode(intArrayOf(CMD_START_OF_VIEW, 7, command, color, CMD_END_OF_VIEW), doubleArrayOf()) + .single() + .second + assertThat(props.getInt(name)).describedAs(name).isEqualTo(color) + } + } + + @Test + fun decodesOutlinePropsAcrossViews() { + val updates = + decode( + intArrayOf( + CMD_START_OF_VIEW, + 7, + CMD_OUTLINE_OFFSET, + CMD_OUTLINE_WIDTH, + CMD_END_OF_VIEW, + CMD_START_OF_VIEW, + 8, + CMD_OPACITY, + CMD_END_OF_VIEW), + doubleArrayOf(-2.0, 1.25, 0.5)) + + assertThat(updates.map { it.first }).containsExactly(7, 8) + val props = updates[0].second + assertThat(props.getDouble("outlineOffset")).isEqualTo(-2.0) + assertThat(props.getDouble("outlineWidth")).isEqualTo(1.25) + assertThat(updates[1].second.getDouble("opacity")).isEqualTo(0.5) + } + + @Test + fun decodesTransformOpsInOrder() { + val updates = + decode( + intArrayOf( + CMD_START_OF_VIEW, + 7, + CMD_START_OF_TRANSFORM, + CMD_TRANSLATE_X, + CMD_UNIT_PX, + CMD_ROTATE, + CMD_SCALE, + CMD_END_OF_TRANSFORM, + CMD_END_OF_VIEW), + doubleArrayOf(10.0, 1.5, 2.0)) + + val transforms = checkNotNull(updates.single().second.getArray("transform")) + assertThat(transforms.size()).isEqualTo(3) + assertThat(transforms.getMap(0)?.getDouble("translateX")).isEqualTo(10.0) + assertThat(transforms.getMap(1)?.getDouble("rotate")).isEqualTo(1.5) + assertThat(transforms.getMap(2)?.getDouble("scale")).isEqualTo(2.0) + } + + @Test + fun decodesMatrixOpWithSixteenValues() { + val matrix = DoubleArray(16) { it.toDouble() } + val updates = + decode( + intArrayOf( + CMD_START_OF_VIEW, + 7, + CMD_START_OF_TRANSFORM, + CMD_MATRIX, + CMD_END_OF_TRANSFORM, + CMD_END_OF_VIEW), + matrix) + + val transforms = checkNotNull(updates.single().second.getArray("transform")) + val decoded = checkNotNull(transforms.getMap(0)?.getArray("matrix")) + assertThat(decoded.size()).isEqualTo(16) + assertThat(decoded.getDouble(15)).isEqualTo(15.0) + } + + @Test + fun preservesScaleWithThreeDimensionalRotations() { + mockStatic(ReactNativeJNISoLoader::class.java).use { + for ((rotation, axis) in listOf(CMD_ROTATE_X to 0, CMD_ROTATE_Y to 1)) { + val props = + decode( + intArrayOf( + CMD_START_OF_VIEW, + 7, + CMD_START_OF_TRANSFORM, + CMD_PERSPECTIVE, + CMD_SCALE, + rotation, + CMD_END_OF_TRANSFORM, + CMD_END_OF_VIEW, + ), + doubleArrayOf(200.0, 2.0, Math.PI / 4), + ) + .single() + .second + val matrix = DoubleArray(16) + TransformHelper.processTransform( + checkNotNull(props.getArray("transform")), + matrix, + 0f, + 0f, + null, + ) + val decomposition = MatrixMathHelper.MatrixDecompositionContext() + MatrixMathHelper.decomposeMatrix(matrix, decomposition) + + for (scale in decomposition.scale) { + assertThat(scale).isCloseTo(2.0, within(0.000001)) + } + assertThat(decomposition.rotationDegrees[axis]).isEqualTo(45.0) + } + } + } + + @Test + fun passesRawPropsThroughAsTheWholeView() { + val raw = object : ReadableMap by JavaOnlyMap.of("translateX", 4.0, "opacity", null) {} + val updates = + decode( + intArrayOf( + CMD_START_OF_VIEW, + 7, + CMD_RAW_PROPS, + CMD_END_OF_VIEW, + CMD_START_OF_VIEW, + 8, + CMD_OPACITY, + CMD_END_OF_VIEW), + doubleArrayOf(0.5), + arrayOf(raw)) + + assertThat(updates[0]).isEqualTo(7 to raw) + assertThat(updates[1].second.getDouble("opacity")).isEqualTo(0.5) + } + + @Test + fun rejectsUnknownEntries() { + org.junit.Assert.assertThrows(IllegalStateException::class.java) { + decode(intArrayOf(CMD_START_OF_VIEW, 7, UNKNOWN_COMMAND, CMD_END_OF_VIEW), doubleArrayOf()) + } + } + + @Test + fun decodesRadiusAndTranslationUnits() { + val props = + decode( + intArrayOf( + CMD_START_OF_VIEW, + 7, + CMD_BORDER_RADIUS, + CMD_UNIT_PX, + CMD_BORDER_TOP_LEFT_RADIUS, + CMD_UNIT_PERCENT, + CMD_START_OF_TRANSFORM, + CMD_TRANSLATE_X, + CMD_UNIT_PX, + CMD_TRANSLATE_Y, + CMD_UNIT_PERCENT, + CMD_END_OF_TRANSFORM, + CMD_END_OF_VIEW), + doubleArrayOf(4.0, 12.5, 10.0, 25.0), + ) + .single() + .second + assertThat(props.getDouble("borderRadius")).isEqualTo(4.0) + assertThat(props.getString("borderTopLeftRadius")).isEqualTo("12.5%") + val transform = checkNotNull(props.getArray("transform")) + assertThat(transform.getMap(0)?.getDouble("translateX")).isEqualTo(10.0) + assertThat(transform.getMap(1)?.getString("translateY")).isEqualTo("25.0%") + } + + @Test + fun rejectsMalformedCommandsAndUnits() { + for (ints in + listOf( + intArrayOf(UNKNOWN_COMMAND), + intArrayOf(CMD_START_OF_VIEW, 7, CMD_BORDER_RADIUS, UNKNOWN_UNIT, CMD_END_OF_VIEW), + intArrayOf( + CMD_START_OF_VIEW, + 7, + CMD_START_OF_TRANSFORM, + CMD_TRANSLATE_X, + UNKNOWN_UNIT, + CMD_END_OF_TRANSFORM, + CMD_END_OF_VIEW), + intArrayOf( + CMD_START_OF_VIEW, + 7, + CMD_START_OF_TRANSFORM, + UNKNOWN_TRANSFORM_COMMAND, + CMD_END_OF_TRANSFORM, + CMD_END_OF_VIEW), + intArrayOf( + CMD_START_OF_VIEW, + 7, + CMD_START_OF_TRANSFORM, + CMD_MATRIX, + CMD_END_OF_TRANSFORM, + CMD_END_OF_VIEW), + intArrayOf(CMD_START_OF_VIEW, 7, CMD_RAW_PROPS, CMD_OPACITY, CMD_END_OF_VIEW), + )) { + org.junit.Assert.assertThrows(RuntimeException::class.java) { + decode(ints, doubleArrayOf(1.0)) + } + } + } + + @Test + fun queuesMissingViewsAndContinuesTheBatch() { + val manager = mock() + val dispatcher = mock() + val second = JavaOnlyMap.of("opacity", 0.75) + whenever(manager.updateAnimatedPropsSynchronously(12, second)).thenReturn(true) + BatchedAnimatedPropsMountItem( + intArrayOf( + CMD_START_OF_VIEW, + 11, + CMD_OPACITY, + CMD_END_OF_VIEW, + CMD_START_OF_VIEW, + 12, + CMD_OPACITY, + CMD_END_OF_VIEW), + doubleArrayOf(0.25, 0.75), + emptyArray(), + dispatcher, + ) + .execute(manager) + verify(dispatcher).addMountItem(org.mockito.kotlin.any()) + verify(manager).updateAnimatedPropsSynchronously(12, second) + } + + @Test + fun continuesAfterOneViewManagerFails() { + val manager = mock() + val dispatcher = mock() + whenever(manager.updateAnimatedPropsSynchronously(11, JavaOnlyMap.of("opacity", 0.25))) + .thenThrow(IllegalStateException("view failure")) + BatchedAnimatedPropsMountItem( + intArrayOf( + CMD_START_OF_VIEW, + 11, + CMD_OPACITY, + CMD_END_OF_VIEW, + CMD_START_OF_VIEW, + 12, + CMD_OPACITY, + CMD_END_OF_VIEW), + doubleArrayOf(0.25, 0.75), + emptyArray(), + dispatcher, + ) + .execute(manager) + verify(manager).updateAnimatedPropsSynchronously(12, JavaOnlyMap.of("opacity", 0.75)) + } + + private companion object { + const val CMD_START_OF_VIEW = 1 + const val CMD_START_OF_TRANSFORM = 2 + const val CMD_END_OF_TRANSFORM = 3 + const val CMD_END_OF_VIEW = 4 + const val CMD_RAW_PROPS = 5 + const val CMD_OPACITY = 10 + const val CMD_PLACEHOLDER_TEXT_COLOR = 18 + const val CMD_SHADOW_COLOR = 19 + const val CMD_BORDER_RADIUS = 20 + const val CMD_BORDER_TOP_LEFT_RADIUS = 21 + const val CMD_BORDER_BLOCK_COLOR = 47 + const val CMD_BORDER_BLOCK_START_COLOR = 48 + const val CMD_BORDER_BLOCK_END_COLOR = 49 + const val CMD_OUTLINE_COLOR = 50 + const val CMD_OUTLINE_OFFSET = 51 + const val CMD_OUTLINE_WIDTH = 52 + const val CMD_TRANSLATE_X = 100 + const val CMD_TRANSLATE_Y = 101 + const val CMD_SCALE = 102 + const val CMD_ROTATE = 105 + const val CMD_ROTATE_X = 106 + const val CMD_ROTATE_Y = 107 + const val CMD_MATRIX = 111 + const val CMD_PERSPECTIVE = 112 + const val CMD_UNIT_PX = 202 + const val CMD_UNIT_PERCENT = 203 + + const val UNKNOWN_COMMAND = 9 + const val UNKNOWN_TRANSFORM_COMMAND = 999 + const val UNKNOWN_UNIT = 999 + } +} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/MountingManagerAnimatedPropsTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/MountingManagerAnimatedPropsTest.kt new file mode 100644 index 000000000000..f9f4f59e5795 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/MountingManagerAnimatedPropsTest.kt @@ -0,0 +1,99 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +@file:Suppress("DEPRECATION") + +package com.facebook.react.fabric + +import com.facebook.react.ReactRootView +import com.facebook.react.bridge.JavaOnlyMap +import com.facebook.react.bridge.ReactTestHelper +import com.facebook.react.fabric.mounting.MountingManager +import com.facebook.react.fabric.mounting.SurfaceMountingManager +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests +import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.uimanager.ViewManager +import com.facebook.react.uimanager.ViewManagerRegistry +import com.facebook.react.views.view.ReactViewManager +import com.facebook.testutils.shadows.ShadowNativeLoader +import com.facebook.testutils.shadows.ShadowNativeMap +import com.facebook.testutils.shadows.ShadowReadableNativeArray +import com.facebook.testutils.shadows.ShadowReadableNativeMap +import com.facebook.testutils.shadows.ShadowSoLoader +import com.facebook.testutils.shadows.ShadowWritableNativeArray +import com.facebook.testutils.shadows.ShadowWritableNativeMap +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config( + shadows = + [ + ShadowSoLoader::class, + ShadowNativeLoader::class, + ShadowNativeMap::class, + ShadowWritableNativeMap::class, + ShadowReadableNativeMap::class, + ShadowWritableNativeArray::class, + ShadowReadableNativeArray::class, + ], +) +class MountingManagerAnimatedPropsTest { + private lateinit var mountingManager: MountingManager + private lateinit var themedReactContext: ThemedReactContext + private val surfaceId = 1 + + @Before + fun setUp() { + ReactNativeFeatureFlagsForTests.setUp() + val reactContext = ReactTestHelper.createCatalystContextForTest() + themedReactContext = ThemedReactContext(reactContext, reactContext, null, -1) + mountingManager = + MountingManager(ViewManagerRegistry(listOf>(ReactViewManager())), {}) + } + + private fun startSurfaceWithView(tag: Int): SurfaceMountingManager { + mountingManager.startSurface(surfaceId, themedReactContext, ReactRootView(themedReactContext)) + val smm = mountingManager.getSurfaceManagerEnforced(surfaceId, "test") + smm.preallocateView("RCTView", tag, JavaOnlyMap.of(), null, true) + smm.addViewAt(surfaceId, tag, 0) + return smm + } + + @Test + fun appliesPropsToExistingView() { + val smm = startSurfaceWithView(42) + + val applied = + mountingManager.updateAnimatedPropsSynchronously(42, JavaOnlyMap.of("opacity", 0.3)) + + assertThat(applied).isTrue() + assertThat(smm.getView(42).alpha).isEqualTo(0.3f) + } + + @Test + fun reportsUnknownTag() { + startSurfaceWithView(42) + + assertThat(mountingManager.updateAnimatedPropsSynchronously(7, JavaOnlyMap.of("opacity", 0.3))) + .isFalse() + } + + @Test + fun storedValueOverridesStaleMountUpdate() { + val smm = startSurfaceWithView(42) + mountingManager.updateAnimatedPropsSynchronously(42, JavaOnlyMap.of("opacity", 0.3)) + + smm.updateProps(42, JavaOnlyMap.of("opacity", 1.0)) + + assertThat(smm.getView(42).alpha).isEqualTo(0.3f) + } +} diff --git a/packages/react-native/ReactCommon/React-Fabric.podspec b/packages/react-native/ReactCommon/React-Fabric.podspec index 8b84f704eef5..48dee62317d3 100644 --- a/packages/react-native/ReactCommon/React-Fabric.podspec +++ b/packages/react-native/ReactCommon/React-Fabric.podspec @@ -73,6 +73,7 @@ Pod::Spec.new do |s| s.subspec "animationbackend" do |ss| ss.source_files = podspec_sources("react/renderer/animationbackend/**/*.{m,mm,cpp,h}", "react/renderer/animationbackend/**/*.{h}") + ss.exclude_files = "react/renderer/animationbackend/tests" ss.header_dir = "react/renderer/animationbackend" end diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.cpp b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.cpp index 02fef88041cd..8d7120d835d2 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.cpp @@ -12,48 +12,47 @@ namespace facebook::react { void AnimatedPropsRegistry::update( - const std::unordered_map& surfaceUpdates) { + const std::vector& batches) { auto lock = std::lock_guard(mutex_); - for (const auto& [surfaceId, updates] : surfaceUpdates) { - auto contextIt = surfaceContexts_.find(surfaceId); - if (contextIt == surfaceContexts_.end()) { - continue; - } - auto& surfaceContext = contextIt->second; - auto& pendingMap = surfaceContext.pendingMap; - auto& pendingFamilies = surfaceContext.pendingFamilies; - - auto& updatesMap = updates.propsMap; - auto& updatesFamilies = updates.families; - - for (auto& family : updatesFamilies) { - pendingFamilies.insert(family); - } - - for (auto& [tag, animatedProps] : updatesMap) { + for (const auto& mutations : batches) { + for (const auto& mutation : mutations.batch) { + const auto& family = mutation.family; + auto contextIt = surfaceContexts_.find(family->getSurfaceId()); + if (contextIt == surfaceContexts_.end()) { + continue; + } + auto& surfaceContext = contextIt->second; + auto& pendingMap = surfaceContext.pendingMap; + surfaceContext.pendingFamilies.insert(family); + const auto tag = mutation.tag; + const auto& animatedProps = mutation.props; auto it = pendingMap.find(tag); if (it == pendingMap.end()) { it = pendingMap.insert_or_assign(tag, std::make_unique()) .first; } auto& snapshot = it->second; - auto& viewProps = snapshot->props; - if (animatedProps.rawProps) { const auto& newRawProps = *animatedProps.rawProps; auto& currentRawProps = snapshot->rawProps; if (currentRawProps) { - auto newRawPropsDynamic = newRawProps.toDynamic(); - currentRawProps->merge_patch(newRawPropsDynamic); + if (const auto* dynamic = newRawProps.getDynamic()) { + currentRawProps->merge_patch(*dynamic); + } else { + currentRawProps->merge_patch(newRawProps.toDynamic()); + } } else { currentRawProps = std::make_unique(newRawProps.toDynamic()); } } + if (!animatedProps.props.empty() && !snapshot->props) { + snapshot->props = std::make_unique(); + } for (const auto& animatedProp : animatedProps.props) { snapshot->propNames.insert(animatedProp->propName); - cloneProp(viewProps, *animatedProp); + cloneProp(*snapshot->props, *animatedProp); } } } @@ -88,9 +87,14 @@ AnimatedPropsRegistry::getMap(SurfaceId surfaceId) { currentSnapshot->rawProps = std::move(propsSnapshot->rawProps); } } - for (auto& propName : propsSnapshot->propNames) { - currentSnapshot->propNames.insert(propName); - updateProp(propName, currentSnapshot->props, *propsSnapshot); + if (!currentSnapshot->props) { + currentSnapshot->props = std::move(propsSnapshot->props); + currentSnapshot->propNames = std::move(propsSnapshot->propNames); + } else { + for (auto& propName : propsSnapshot->propNames) { + currentSnapshot->propNames.insert(propName); + updateProp(propName, *currentSnapshot->props, *propsSnapshot); + } } } } diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.h b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.h index 64eaa12b863a..00fd36cd0f27 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.h +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.h @@ -15,11 +15,12 @@ #include #include #include "AnimatedProps.h" +#include "AnimationMutation.h" namespace facebook::react { struct PropsSnapshot { - BaseViewProps props; + std::unique_ptr props; std::unordered_set propNames; std::unique_ptr rawProps; }; @@ -29,17 +30,11 @@ struct SurfaceContext { std::unordered_set> pendingFamilies, families; }; -struct SurfaceUpdates { - std::unordered_set> families; - std::unordered_map propsMap; - bool hasLayoutUpdates{false}; -}; - using SnapshotMap = std::unordered_map>; class AnimatedPropsRegistry { public: - void update(const std::unordered_map &surfaceUpdates); + void update(const std::vector &batches); void initializeSurface(SurfaceId surfaceId); void clear(SurfaceId surfaceId); void clearOnSurfaceStop(SurfaceId surfaceId); @@ -54,247 +49,247 @@ inline void updateProp(const PropName propName, BaseViewProps &viewProps, const { switch (propName) { case OPACITY: - viewProps.opacity = snapshot.props.opacity; + viewProps.opacity = snapshot.props->opacity; break; case WIDTH: viewProps.yogaStyle.setDimension( - yoga::Dimension::Width, snapshot.props.yogaStyle.dimension(yoga::Dimension::Width)); + yoga::Dimension::Width, snapshot.props->yogaStyle.dimension(yoga::Dimension::Width)); break; case HEIGHT: { - auto d = snapshot.props.yogaStyle.dimension(yoga::Dimension::Height); + auto d = snapshot.props->yogaStyle.dimension(yoga::Dimension::Height); viewProps.yogaStyle.setDimension(yoga::Dimension::Height, d); break; } case TRANSFORM: - viewProps.transform = snapshot.props.transform; + viewProps.transform = snapshot.props->transform; break; case TRANSFORM_ORIGIN: - viewProps.transformOrigin = snapshot.props.transformOrigin; + viewProps.transformOrigin = snapshot.props->transformOrigin; break; case BORDER_RADII: - viewProps.borderRadii = snapshot.props.borderRadii; + viewProps.borderRadii = snapshot.props->borderRadii; break; case FLEX: - viewProps.yogaStyle.setFlex(snapshot.props.yogaStyle.flex()); + viewProps.yogaStyle.setFlex(snapshot.props->yogaStyle.flex()); break; case BACKGROUND_COLOR: - viewProps.backgroundColor = snapshot.props.backgroundColor; + viewProps.backgroundColor = snapshot.props->backgroundColor; break; case SHADOW_COLOR: - viewProps.shadowColor = snapshot.props.shadowColor; + viewProps.shadowColor = snapshot.props->shadowColor; break; case SHADOW_OFFSET: - viewProps.shadowOffset = snapshot.props.shadowOffset; + viewProps.shadowOffset = snapshot.props->shadowOffset; break; case SHADOW_OPACITY: - viewProps.shadowOpacity = snapshot.props.shadowOpacity; + viewProps.shadowOpacity = snapshot.props->shadowOpacity; break; case SHADOW_RADIUS: - viewProps.shadowRadius = snapshot.props.shadowRadius; + viewProps.shadowRadius = snapshot.props->shadowRadius; break; case MARGIN: - viewProps.yogaStyle.setMargin(yoga::Edge::Left, snapshot.props.yogaStyle.margin(yoga::Edge::Left)); - viewProps.yogaStyle.setMargin(yoga::Edge::Right, snapshot.props.yogaStyle.margin(yoga::Edge::Right)); - viewProps.yogaStyle.setMargin(yoga::Edge::Top, snapshot.props.yogaStyle.margin(yoga::Edge::Top)); - viewProps.yogaStyle.setMargin(yoga::Edge::Bottom, snapshot.props.yogaStyle.margin(yoga::Edge::Bottom)); - viewProps.yogaStyle.setMargin(yoga::Edge::Start, snapshot.props.yogaStyle.margin(yoga::Edge::Start)); - viewProps.yogaStyle.setMargin(yoga::Edge::End, snapshot.props.yogaStyle.margin(yoga::Edge::End)); - viewProps.yogaStyle.setMargin(yoga::Edge::Horizontal, snapshot.props.yogaStyle.margin(yoga::Edge::Horizontal)); - viewProps.yogaStyle.setMargin(yoga::Edge::Vertical, snapshot.props.yogaStyle.margin(yoga::Edge::Vertical)); + viewProps.yogaStyle.setMargin(yoga::Edge::Left, snapshot.props->yogaStyle.margin(yoga::Edge::Left)); + viewProps.yogaStyle.setMargin(yoga::Edge::Right, snapshot.props->yogaStyle.margin(yoga::Edge::Right)); + viewProps.yogaStyle.setMargin(yoga::Edge::Top, snapshot.props->yogaStyle.margin(yoga::Edge::Top)); + viewProps.yogaStyle.setMargin(yoga::Edge::Bottom, snapshot.props->yogaStyle.margin(yoga::Edge::Bottom)); + viewProps.yogaStyle.setMargin(yoga::Edge::Start, snapshot.props->yogaStyle.margin(yoga::Edge::Start)); + viewProps.yogaStyle.setMargin(yoga::Edge::End, snapshot.props->yogaStyle.margin(yoga::Edge::End)); + viewProps.yogaStyle.setMargin(yoga::Edge::Horizontal, snapshot.props->yogaStyle.margin(yoga::Edge::Horizontal)); + viewProps.yogaStyle.setMargin(yoga::Edge::Vertical, snapshot.props->yogaStyle.margin(yoga::Edge::Vertical)); break; case PADDING: - viewProps.yogaStyle.setPadding(yoga::Edge::Left, snapshot.props.yogaStyle.padding(yoga::Edge::Left)); - viewProps.yogaStyle.setPadding(yoga::Edge::Right, snapshot.props.yogaStyle.padding(yoga::Edge::Right)); - viewProps.yogaStyle.setPadding(yoga::Edge::Top, snapshot.props.yogaStyle.padding(yoga::Edge::Top)); - viewProps.yogaStyle.setPadding(yoga::Edge::Bottom, snapshot.props.yogaStyle.padding(yoga::Edge::Bottom)); - viewProps.yogaStyle.setPadding(yoga::Edge::Start, snapshot.props.yogaStyle.padding(yoga::Edge::Start)); - viewProps.yogaStyle.setPadding(yoga::Edge::End, snapshot.props.yogaStyle.padding(yoga::Edge::End)); - viewProps.yogaStyle.setPadding(yoga::Edge::Horizontal, snapshot.props.yogaStyle.padding(yoga::Edge::Horizontal)); - viewProps.yogaStyle.setPadding(yoga::Edge::Vertical, snapshot.props.yogaStyle.padding(yoga::Edge::Vertical)); + viewProps.yogaStyle.setPadding(yoga::Edge::Left, snapshot.props->yogaStyle.padding(yoga::Edge::Left)); + viewProps.yogaStyle.setPadding(yoga::Edge::Right, snapshot.props->yogaStyle.padding(yoga::Edge::Right)); + viewProps.yogaStyle.setPadding(yoga::Edge::Top, snapshot.props->yogaStyle.padding(yoga::Edge::Top)); + viewProps.yogaStyle.setPadding(yoga::Edge::Bottom, snapshot.props->yogaStyle.padding(yoga::Edge::Bottom)); + viewProps.yogaStyle.setPadding(yoga::Edge::Start, snapshot.props->yogaStyle.padding(yoga::Edge::Start)); + viewProps.yogaStyle.setPadding(yoga::Edge::End, snapshot.props->yogaStyle.padding(yoga::Edge::End)); + viewProps.yogaStyle.setPadding(yoga::Edge::Horizontal, snapshot.props->yogaStyle.padding(yoga::Edge::Horizontal)); + viewProps.yogaStyle.setPadding(yoga::Edge::Vertical, snapshot.props->yogaStyle.padding(yoga::Edge::Vertical)); break; case POSITION: - viewProps.yogaStyle.setPosition(yoga::Edge::Left, snapshot.props.yogaStyle.position(yoga::Edge::Left)); - viewProps.yogaStyle.setPosition(yoga::Edge::Right, snapshot.props.yogaStyle.position(yoga::Edge::Right)); - viewProps.yogaStyle.setPosition(yoga::Edge::Top, snapshot.props.yogaStyle.position(yoga::Edge::Top)); - viewProps.yogaStyle.setPosition(yoga::Edge::Bottom, snapshot.props.yogaStyle.position(yoga::Edge::Bottom)); - viewProps.yogaStyle.setPosition(yoga::Edge::Start, snapshot.props.yogaStyle.position(yoga::Edge::Start)); - viewProps.yogaStyle.setPosition(yoga::Edge::End, snapshot.props.yogaStyle.position(yoga::Edge::End)); + viewProps.yogaStyle.setPosition(yoga::Edge::Left, snapshot.props->yogaStyle.position(yoga::Edge::Left)); + viewProps.yogaStyle.setPosition(yoga::Edge::Right, snapshot.props->yogaStyle.position(yoga::Edge::Right)); + viewProps.yogaStyle.setPosition(yoga::Edge::Top, snapshot.props->yogaStyle.position(yoga::Edge::Top)); + viewProps.yogaStyle.setPosition(yoga::Edge::Bottom, snapshot.props->yogaStyle.position(yoga::Edge::Bottom)); + viewProps.yogaStyle.setPosition(yoga::Edge::Start, snapshot.props->yogaStyle.position(yoga::Edge::Start)); + viewProps.yogaStyle.setPosition(yoga::Edge::End, snapshot.props->yogaStyle.position(yoga::Edge::End)); viewProps.yogaStyle.setPosition( - yoga::Edge::Horizontal, snapshot.props.yogaStyle.position(yoga::Edge::Horizontal)); - viewProps.yogaStyle.setPosition(yoga::Edge::Vertical, snapshot.props.yogaStyle.position(yoga::Edge::Vertical)); + yoga::Edge::Horizontal, snapshot.props->yogaStyle.position(yoga::Edge::Horizontal)); + viewProps.yogaStyle.setPosition(yoga::Edge::Vertical, snapshot.props->yogaStyle.position(yoga::Edge::Vertical)); break; case BORDER_WIDTH: - viewProps.yogaStyle.setBorder(yoga::Edge::Left, snapshot.props.yogaStyle.border(yoga::Edge::Left)); - viewProps.yogaStyle.setBorder(yoga::Edge::Right, snapshot.props.yogaStyle.border(yoga::Edge::Right)); - viewProps.yogaStyle.setBorder(yoga::Edge::Top, snapshot.props.yogaStyle.border(yoga::Edge::Top)); - viewProps.yogaStyle.setBorder(yoga::Edge::Bottom, snapshot.props.yogaStyle.border(yoga::Edge::Bottom)); - viewProps.yogaStyle.setBorder(yoga::Edge::Start, snapshot.props.yogaStyle.border(yoga::Edge::Start)); - viewProps.yogaStyle.setBorder(yoga::Edge::End, snapshot.props.yogaStyle.border(yoga::Edge::End)); - viewProps.yogaStyle.setBorder(yoga::Edge::Horizontal, snapshot.props.yogaStyle.border(yoga::Edge::Horizontal)); - viewProps.yogaStyle.setBorder(yoga::Edge::Vertical, snapshot.props.yogaStyle.border(yoga::Edge::Vertical)); + viewProps.yogaStyle.setBorder(yoga::Edge::Left, snapshot.props->yogaStyle.border(yoga::Edge::Left)); + viewProps.yogaStyle.setBorder(yoga::Edge::Right, snapshot.props->yogaStyle.border(yoga::Edge::Right)); + viewProps.yogaStyle.setBorder(yoga::Edge::Top, snapshot.props->yogaStyle.border(yoga::Edge::Top)); + viewProps.yogaStyle.setBorder(yoga::Edge::Bottom, snapshot.props->yogaStyle.border(yoga::Edge::Bottom)); + viewProps.yogaStyle.setBorder(yoga::Edge::Start, snapshot.props->yogaStyle.border(yoga::Edge::Start)); + viewProps.yogaStyle.setBorder(yoga::Edge::End, snapshot.props->yogaStyle.border(yoga::Edge::End)); + viewProps.yogaStyle.setBorder(yoga::Edge::Horizontal, snapshot.props->yogaStyle.border(yoga::Edge::Horizontal)); + viewProps.yogaStyle.setBorder(yoga::Edge::Vertical, snapshot.props->yogaStyle.border(yoga::Edge::Vertical)); break; case BORDER_COLOR: - viewProps.borderColors = snapshot.props.borderColors; + viewProps.borderColors = snapshot.props->borderColors; break; case FILTER: - viewProps.filter = snapshot.props.filter; + viewProps.filter = snapshot.props->filter; break; case OUTLINE_COLOR: - viewProps.outlineColor = snapshot.props.outlineColor; + viewProps.outlineColor = snapshot.props->outlineColor; break; case OUTLINE_OFFSET: - viewProps.outlineOffset = snapshot.props.outlineOffset; + viewProps.outlineOffset = snapshot.props->outlineOffset; break; case OUTLINE_STYLE: - viewProps.outlineStyle = snapshot.props.outlineStyle; + viewProps.outlineStyle = snapshot.props->outlineStyle; break; case OUTLINE_WIDTH: - viewProps.outlineWidth = snapshot.props.outlineWidth; + viewProps.outlineWidth = snapshot.props->outlineWidth; break; case ALIGN_CONTENT: - viewProps.yogaStyle.setAlignContent(snapshot.props.yogaStyle.alignContent()); + viewProps.yogaStyle.setAlignContent(snapshot.props->yogaStyle.alignContent()); break; case ALIGN_ITEMS: - viewProps.yogaStyle.setAlignItems(snapshot.props.yogaStyle.alignItems()); + viewProps.yogaStyle.setAlignItems(snapshot.props->yogaStyle.alignItems()); break; case ALIGN_SELF: - viewProps.yogaStyle.setAlignSelf(snapshot.props.yogaStyle.alignSelf()); + viewProps.yogaStyle.setAlignSelf(snapshot.props->yogaStyle.alignSelf()); break; case ASPECT_RATIO: - viewProps.yogaStyle.setAspectRatio(snapshot.props.yogaStyle.aspectRatio()); + viewProps.yogaStyle.setAspectRatio(snapshot.props->yogaStyle.aspectRatio()); break; case BOX_SIZING: - viewProps.yogaStyle.setBoxSizing(snapshot.props.yogaStyle.boxSizing()); + viewProps.yogaStyle.setBoxSizing(snapshot.props->yogaStyle.boxSizing()); break; case DISPLAY: - viewProps.yogaStyle.setDisplay(snapshot.props.yogaStyle.display()); + viewProps.yogaStyle.setDisplay(snapshot.props->yogaStyle.display()); break; case FLEX_BASIS: - viewProps.yogaStyle.setFlexBasis(snapshot.props.yogaStyle.flexBasis()); + viewProps.yogaStyle.setFlexBasis(snapshot.props->yogaStyle.flexBasis()); break; case FLEX_DIRECTION: - viewProps.yogaStyle.setFlexDirection(snapshot.props.yogaStyle.flexDirection()); + viewProps.yogaStyle.setFlexDirection(snapshot.props->yogaStyle.flexDirection()); break; case ROW_GAP: - viewProps.yogaStyle.setGap(yoga::Gutter::Row, snapshot.props.yogaStyle.gap(yoga::Gutter::Row)); + viewProps.yogaStyle.setGap(yoga::Gutter::Row, snapshot.props->yogaStyle.gap(yoga::Gutter::Row)); break; case COLUMN_GAP: - viewProps.yogaStyle.setGap(yoga::Gutter::Column, snapshot.props.yogaStyle.gap(yoga::Gutter::Column)); + viewProps.yogaStyle.setGap(yoga::Gutter::Column, snapshot.props->yogaStyle.gap(yoga::Gutter::Column)); break; case FLEX_GROW: - viewProps.yogaStyle.setFlexGrow(snapshot.props.yogaStyle.flexGrow()); + viewProps.yogaStyle.setFlexGrow(snapshot.props->yogaStyle.flexGrow()); break; case FLEX_SHRINK: - viewProps.yogaStyle.setFlexShrink(snapshot.props.yogaStyle.flexShrink()); + viewProps.yogaStyle.setFlexShrink(snapshot.props->yogaStyle.flexShrink()); break; case FLEX_WRAP: - viewProps.yogaStyle.setFlexWrap(snapshot.props.yogaStyle.flexWrap()); + viewProps.yogaStyle.setFlexWrap(snapshot.props->yogaStyle.flexWrap()); break; case JUSTIFY_CONTENT: - viewProps.yogaStyle.setJustifyContent(snapshot.props.yogaStyle.justifyContent()); + viewProps.yogaStyle.setJustifyContent(snapshot.props->yogaStyle.justifyContent()); break; case MAX_HEIGHT: viewProps.yogaStyle.setMaxDimension( - yoga::Dimension::Height, snapshot.props.yogaStyle.maxDimension(yoga::Dimension::Height)); + yoga::Dimension::Height, snapshot.props->yogaStyle.maxDimension(yoga::Dimension::Height)); break; case MAX_WIDTH: viewProps.yogaStyle.setMaxDimension( - yoga::Dimension::Width, snapshot.props.yogaStyle.maxDimension(yoga::Dimension::Width)); + yoga::Dimension::Width, snapshot.props->yogaStyle.maxDimension(yoga::Dimension::Width)); break; case MIN_HEIGHT: viewProps.yogaStyle.setMinDimension( - yoga::Dimension::Height, snapshot.props.yogaStyle.minDimension(yoga::Dimension::Height)); + yoga::Dimension::Height, snapshot.props->yogaStyle.minDimension(yoga::Dimension::Height)); break; case MIN_WIDTH: viewProps.yogaStyle.setMinDimension( - yoga::Dimension::Width, snapshot.props.yogaStyle.minDimension(yoga::Dimension::Width)); + yoga::Dimension::Width, snapshot.props->yogaStyle.minDimension(yoga::Dimension::Width)); break; case STYLE_OVERFLOW: - viewProps.yogaStyle.setOverflow(snapshot.props.yogaStyle.overflow()); + viewProps.yogaStyle.setOverflow(snapshot.props->yogaStyle.overflow()); break; case POSITION_TYPE: - viewProps.yogaStyle.setPositionType(snapshot.props.yogaStyle.positionType()); + viewProps.yogaStyle.setPositionType(snapshot.props->yogaStyle.positionType()); break; case Z_INDEX: - viewProps.zIndex = snapshot.props.zIndex; + viewProps.zIndex = snapshot.props->zIndex; break; case DIRECTION: - viewProps.yogaStyle.setDirection(snapshot.props.yogaStyle.direction()); + viewProps.yogaStyle.setDirection(snapshot.props->yogaStyle.direction()); break; case BORDER_CURVES: - viewProps.borderCurves = snapshot.props.borderCurves; + viewProps.borderCurves = snapshot.props->borderCurves; break; case BORDER_STYLES: - viewProps.borderStyles = snapshot.props.borderStyles; + viewProps.borderStyles = snapshot.props->borderStyles; break; case POINTER_EVENTS: - viewProps.pointerEvents = snapshot.props.pointerEvents; + viewProps.pointerEvents = snapshot.props->pointerEvents; break; case ISOLATION: - viewProps.isolation = snapshot.props.isolation; + viewProps.isolation = snapshot.props->isolation; break; case CURSOR: - viewProps.cursor = snapshot.props.cursor; + viewProps.cursor = snapshot.props->cursor; break; case BOX_SHADOW: - viewProps.boxShadow = snapshot.props.boxShadow; + viewProps.boxShadow = snapshot.props->boxShadow; break; case MIX_BLEND_MODE: - viewProps.mixBlendMode = snapshot.props.mixBlendMode; + viewProps.mixBlendMode = snapshot.props->mixBlendMode; break; case BACKFACE_VISIBILITY: - viewProps.backfaceVisibility = snapshot.props.backfaceVisibility; + viewProps.backfaceVisibility = snapshot.props->backfaceVisibility; break; default: diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp index e38386a9e44a..a16fc96b78d0 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp @@ -80,14 +80,10 @@ void AnimationBackend::unpackMutations( std::unordered_map& surfaceUpdates, std::set& asyncFlushSurfaces) { for (auto& mutation : mutations.batch) { - const auto family = mutation.family; - react_native_assert(family != nullptr); - - auto& [families, updates, hasLayoutUpdates] = - surfaceUpdates[family->getSurfaceId()]; - hasLayoutUpdates |= mutation.hasLayoutUpdates; - families.insert(family); - updates[mutation.tag] = std::move(mutation.props); + react_native_assert(mutation.family != nullptr); + auto& updates = surfaceUpdates[mutation.family->getSurfaceId()]; + const auto tag = mutation.tag; + updates.insert_or_assign(tag, std::move(mutation)); } asyncFlushSurfaces.merge(mutations.asyncFlushSurfaces); @@ -96,23 +92,34 @@ void AnimationBackend::unpackMutations( void AnimationBackend::applySurfaceUpdates( std::unordered_map& surfaceUpdates, const std::set& asyncFlushSurfaces) { - animatedPropsRegistry_->update(surfaceUpdates); - for (auto& [surfaceId, updates] : surfaceUpdates) { - if (updates.hasLayoutUpdates) { - commitUpdates(surfaceId, updates); - } else { - synchronouslyUpdateProps(updates.propsMap); + SurfaceUpdates layoutUpdates; + std::unordered_map directProps; + for (auto& [tag, mutation] : updates) { + if (mutation.hasLayoutUpdates) { + layoutUpdates.emplace(tag, std::move(mutation)); + } else { + directProps.emplace(tag, std::move(mutation.props)); + } + } + if (!layoutUpdates.empty()) { + commitUpdates(surfaceId, layoutUpdates); + } + if (!directProps.empty()) { + synchronouslyUpdateProps(directProps); } } requestAsyncFlushForSurfaces(asyncFlushSurfaces); } -void AnimationBackend::applyMutations(AnimationMutations mutations) { +void AnimationBackend::applyMutations(std::vector batches) { + animatedPropsRegistry_->update(batches); std::unordered_map surfaceUpdates; std::set asyncFlushSurfaces; - unpackMutations(mutations, surfaceUpdates, asyncFlushSurfaces); + for (auto& mutations : batches) { + unpackMutations(mutations, surfaceUpdates, asyncFlushSurfaces); + } applySurfaceUpdates(surfaceUpdates, asyncFlushSurfaces); } @@ -124,13 +131,11 @@ void AnimationBackend::onAnimationFrame(AnimationTimestamp timestamp) { callbacksCopy = callbacks; } - std::unordered_map surfaceUpdates; - std::set asyncFlushSurfaces; + std::vector batches; for (auto& callbackWithId : callbacksCopy) { - auto mutations = callbackWithId.callback(timestamp); - unpackMutations(mutations, surfaceUpdates, asyncFlushSurfaces); + batches.push_back(callbackWithId.callback(timestamp)); } - applySurfaceUpdates(surfaceUpdates, asyncFlushSurfaces); + applyMutations(std::move(batches)); } CallbackId AnimationBackend::start(const Callback& callback) { @@ -169,20 +174,23 @@ void AnimationBackend::trigger() { void AnimationBackend::pushAnimationMutations(const Callback& callback) { auto timestamp = animationChoreographer_->now(); - auto mutations = callback(timestamp); - applyMutations(std::move(mutations)); + std::vector batches; + batches.push_back(callback(timestamp)); + applyMutations(std::move(batches)); } void AnimationBackend::commitUpdates( SurfaceId surfaceId, - SurfaceUpdates& surfaceUpdates) { + SurfaceUpdates& updates) { auto uiManager = uiManager_.lock(); if (!uiManager) { return; } - auto& surfaceFamilies = surfaceUpdates.families; - auto& updates = surfaceUpdates.propsMap; + std::unordered_set> surfaceFamilies; + for (const auto& [tag, mutation] : updates) { + surfaceFamilies.insert(mutation.family); + } uiManager->getShadowTreeRegistry().visit( surfaceId, [&surfaceFamilies, &updates](const ShadowTree& shadowTree) { @@ -198,7 +206,8 @@ void AnimationBackend::commitUpdates( auto newProps = ShadowNodeFragment::propsPlaceholder(); if (surfaceFamilies.contains( shadowNode.getFamilyShared())) { - auto& animatedProps = updates.at(shadowNode.getTag()); + auto& animatedProps = + updates.at(shadowNode.getTag()).props; newProps = cloneProps(animatedProps, shadowNode); } return shadowNode.clone( @@ -213,6 +222,12 @@ void AnimationBackend::commitUpdates( void AnimationBackend::synchronouslyUpdateProps( const std::unordered_map& updates) { + if (ReactNativeFeatureFlags::optimizedAnimatedPropUpdates()) { + if (auto uiManager = uiManager_.lock()) { + uiManager->synchronouslyUpdateAnimatedProps(updates); + } + return; + } for (auto& [tag, animatedProps] : updates) { // TODO: We shouldn't repack it into dynamic, but for that a rewrite // of synchronouslyUpdateViewOnUIThread is needed diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h index d1a7d02df118..6abae3a73b27 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h @@ -21,22 +21,16 @@ #include "AnimatedPropsRegistry.h" #include "AnimationBackendCommitHook.h" #include "AnimationChoreographer.h" +#include "AnimationMutation.h" namespace facebook::react { class AnimationBackend; -struct AnimationMutation { - Tag tag; - std::shared_ptr family; - AnimatedProps props; - bool hasLayoutUpdates{false}; -}; - -struct AnimationMutations { - std::vector batch; - std::set asyncFlushSurfaces; -}; +// A frame's mutations on one surface, by view. Views with layout updates go +// through a shadow tree commit, the rest is applied directly to the mounted +// views. +using SurfaceUpdates = std::unordered_map; using Callback = std::function; @@ -74,7 +68,7 @@ class AnimationBackend : public UIManagerAnimationBackend { void applySurfaceUpdates( std::unordered_map &surfaceUpdates, const std::set &asyncFlushSurfaces); - void applyMutations(AnimationMutations mutations); + void applyMutations(std::vector batches); std::vector callbacks; std::shared_ptr animatedPropsRegistry_; std::shared_ptr animationChoreographer_; diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationMutation.h b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationMutation.h new file mode 100644 index 000000000000..a17749160aae --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationMutation.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include "AnimatedProps.h" + +namespace facebook::react { + +struct AnimationMutation { + Tag tag; + std::shared_ptr family; + AnimatedProps props; + bool hasLayoutUpdates{false}; +}; + +struct AnimationMutations { + std::vector batch; + std::set asyncFlushSurfaces; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/tests/AnimatedPropsRegistryTest.cpp b/packages/react-native/ReactCommon/react/renderer/animationbackend/tests/AnimatedPropsRegistryTest.cpp new file mode 100644 index 000000000000..c538626e5fd2 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/tests/AnimatedPropsRegistryTest.cpp @@ -0,0 +1,226 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace facebook::react { + +class AnimatedPropsRegistryTest : public ::testing::Test { + protected: + ComponentBuilder builder = + simpleComponentBuilder(std::make_shared()); + std::shared_ptr node = + builder.build(Element().surfaceId(1).tag(10)); + AnimatedPropsRegistry registry; + + void SetUp() override { + registry.initializeSurface(1); + } + + void update(AnimatedProps props) { + std::vector batches(1); + batches[0].batch.push_back( + AnimationMutation{ + .tag = 10, + .family = node->getFamilyShared(), + .props = std::move(props)}); + registry.update(batches); + } + + void updateRaw(folly::dynamic props) { + update({{}, std::make_unique(std::move(props))}); + } + + void expectRawProps(folly::dynamic expected) { + EXPECT_EQ(*snapshot().rawProps, expected); + } + + PropsSnapshot& snapshot() { + return *registry.getMap(1).second.at(10); + } +}; + +TEST_F(AnimatedPropsRegistryTest, rawUpdatesDoNotAllocateTypedProps) { + for (int frame = 0; frame < 10; frame++) { + updateRaw(folly::dynamic::object("opacity", frame / 10.0)); + auto& props = snapshot(); + EXPECT_EQ(props.props, nullptr); + EXPECT_TRUE(props.propNames.empty()); + EXPECT_EQ((*props.rawProps)["opacity"], frame / 10.0); + } +} + +TEST_F(AnimatedPropsRegistryTest, updatePreservesInputForSynchronousMount) { + folly::dynamic rawProps = folly::dynamic::object("opacity", 0.25)( + "transform", + folly::dynamic::array(folly::dynamic::object("translateX", 10))); + std::vector batches(1); + batches[0].batch.push_back( + AnimationMutation{ + .tag = 10, + .family = node->getFamilyShared(), + .props = AnimatedProps{{}, std::make_unique(rawProps)}}); + registry.update(batches); + registry.update(batches); + EXPECT_EQ(batches[0].batch[0].props.rawProps->toDynamic(), rawProps); + EXPECT_EQ(*snapshot().rawProps, rawProps); +} + +TEST_F(AnimatedPropsRegistryTest, mergesPendingUpdatesRecursively) { + updateRaw( + folly::dynamic::object("opacity", 0.25)( + "shadowOffset", folly::dynamic::object("width", 2)("height", 3))); + updateRaw( + folly::dynamic::object( + "shadowOffset", folly::dynamic::object("width", 7))); + expectRawProps( + folly::dynamic::object("opacity", 0.25)( + "shadowOffset", folly::dynamic::object("width", 7)("height", 3))); +} + +TEST_F(AnimatedPropsRegistryTest, mergesCommittedUpdatesRecursively) { + updateRaw( + folly::dynamic::object("opacity", 0.25)( + "shadowOffset", folly::dynamic::object("width", 2)("height", 3))); + snapshot(); + updateRaw( + folly::dynamic::object( + "shadowOffset", folly::dynamic::object("width", 7))); + expectRawProps( + folly::dynamic::object("opacity", 0.25)( + "shadowOffset", folly::dynamic::object("width", 7)("height", 3))); +} + +TEST_F(AnimatedPropsRegistryTest, nullDeletesPendingProps) { + updateRaw( + folly::dynamic::object("opacity", 0.25)( + "shadowOffset", folly::dynamic::object("width", 2)("height", 3))); + updateRaw( + folly::dynamic::object("opacity", nullptr)( + "shadowOffset", folly::dynamic::object("width", nullptr))); + expectRawProps( + folly::dynamic::object( + "shadowOffset", folly::dynamic::object("height", 3))); +} + +TEST_F(AnimatedPropsRegistryTest, nullDeletesCommittedProps) { + updateRaw( + folly::dynamic::object("opacity", 0.25)( + "shadowOffset", folly::dynamic::object("width", 2)("height", 3))); + snapshot(); + updateRaw( + folly::dynamic::object("opacity", nullptr)( + "shadowOffset", folly::dynamic::object("width", nullptr))); + expectRawProps( + folly::dynamic::object( + "shadowOffset", folly::dynamic::object("height", 3))); +} + +TEST_F(AnimatedPropsRegistryTest, arraysReplaceInsteadOfMerging) { + updateRaw( + folly::dynamic::object( + "transform", + folly::dynamic::array( + folly::dynamic::object("translateX", 2), + folly::dynamic::object("scale", 3)))); + snapshot(); + auto transform = + folly::dynamic::array(folly::dynamic::object("rotate", "1rad")); + updateRaw(folly::dynamic::object("transform", transform)); + EXPECT_EQ((*snapshot().rawProps)["transform"], transform); +} + +TEST_F(AnimatedPropsRegistryTest, objectPatchReplacesScalarAndRemovesNulls) { + updateRaw(folly::dynamic::object("value", 5)); + snapshot(); + updateRaw( + folly::dynamic::object( + "value", folly::dynamic::object("width", 2)("height", nullptr))); + expectRawProps( + folly::dynamic::object("value", folly::dynamic::object("width", 2))); + updateRaw(folly::dynamic::object("value", 7)); + expectRawProps(folly::dynamic::object("value", 7)); +} + +TEST_F(AnimatedPropsRegistryTest, typedPropsCanFollowRawProps) { + updateRaw(folly::dynamic::object("opacity", 0.25)); + snapshot(); + AnimatedPropsBuilder builder; + builder.setOpacity(0.75); + update(builder.get()); + auto& props = snapshot(); + ASSERT_NE(props.props, nullptr); + EXPECT_FLOAT_EQ(props.props->opacity, 0.75); + EXPECT_TRUE(props.propNames.contains(OPACITY)); + EXPECT_EQ((*props.rawProps)["opacity"], 0.25); + BaseViewProps viewProps; + updateProp(OPACITY, viewProps, props); + EXPECT_FLOAT_EQ(viewProps.opacity, 0.75); +} + +TEST_F(AnimatedPropsRegistryTest, rawPropsCanFollowTypedProps) { + AnimatedPropsBuilder builder; + builder.setOpacity(0.75); + update(builder.get()); + snapshot(); + updateRaw(folly::dynamic::object("opacity", 0.25)); + auto& props = snapshot(); + ASSERT_NE(props.props, nullptr); + EXPECT_FLOAT_EQ(props.props->opacity, 0.75); + EXPECT_TRUE(props.propNames.contains(OPACITY)); + EXPECT_EQ((*props.rawProps)["opacity"], 0.25); +} + +TEST_F(AnimatedPropsRegistryTest, mergesTypedUpdatesAcrossCommits) { + AnimatedPropsBuilder builder; + builder.setOpacity(0.75); + builder.setShadowRadius(2); + update(builder.get()); + snapshot(); + builder.setOpacity(0.5); + update(builder.get()); + auto& props = snapshot(); + ASSERT_NE(props.props, nullptr); + EXPECT_FLOAT_EQ(props.props->opacity, 0.5); + EXPECT_FLOAT_EQ(props.props->shadowRadius, 2); + EXPECT_EQ(props.propNames.size(), 2); +} + +TEST_F(AnimatedPropsRegistryTest, jsiRawPropsAreConverted) { + auto runtime = hermes::makeHermesRuntime(); + { + auto object = jsi::Object(*runtime); + object.setProperty(*runtime, "opacity", 0.25); + update( + {{}, + std::make_unique(*runtime, jsi::Value(*runtime, object))}); + object.setProperty(*runtime, "opacity", 0.5); + update( + {{}, + std::make_unique(*runtime, jsi::Value(*runtime, object))}); + } + runtime.reset(); + expectRawProps(folly::dynamic::object("opacity", 0.5)); + EXPECT_EQ(snapshot().props, nullptr); +} + +TEST_F(AnimatedPropsRegistryTest, stoppedSurfacesIgnoreUpdates) { + registry.clearOnSurfaceStop(1); + updateRaw(folly::dynamic::object("opacity", 0.5)); + registry.initializeSurface(1); + EXPECT_TRUE(registry.getMap(1).second.empty()); +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/core/RawProps.h b/packages/react-native/ReactCommon/react/renderer/core/RawProps.h index 8afc6f7ec890..8685ec6f575d 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/RawProps.h +++ b/packages/react-native/ReactCommon/react/renderer/core/RawProps.h @@ -83,6 +83,12 @@ class RawProps final { */ folly::dynamic toDynamic(const std::function &filterObjectKeys = nullptr) const; + // The pointer is valid while this RawProps owns its dynamic source. + const folly::dynamic *getDynamic() const noexcept + { + return mode_ == Mode::Dynamic ? &dynamic_ : nullptr; + } + /* * Returns `true` if the object is empty. * Empty `RawProps` does not have any stored data. diff --git a/packages/react-native/ReactCommon/react/renderer/core/tests/RawPropsTest.cpp b/packages/react-native/ReactCommon/react/renderer/core/tests/RawPropsTest.cpp index caff5c160c32..b23760236f21 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/tests/RawPropsTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/tests/RawPropsTest.cpp @@ -20,6 +20,29 @@ using namespace facebook; using namespace facebook::react; +TEST(RawPropsTest, dynamicSourceIsBorrowed) { + const auto rawProps = RawProps(folly::dynamic::object("opacity", 0.5)); + const auto* dynamic = rawProps.getDynamic(); + ASSERT_NE(dynamic, nullptr); + EXPECT_EQ(dynamic, rawProps.getDynamic()); + EXPECT_EQ(*dynamic, rawProps.toDynamic()); +} + +TEST(RawPropsTest, emptyPropsHaveNoDynamicSource) { + EXPECT_EQ(RawProps().getDynamic(), nullptr); + EXPECT_EQ(RawProps(nullptr).getDynamic(), nullptr); +} + +TEST(RawPropsTest, jsiPropsHaveNoDynamicSource) { + auto runtime = facebook::hermes::makeHermesRuntime(); + auto object = jsi::Object(*runtime); + object.setProperty(*runtime, "opacity", 0.5); + const auto rawProps = RawProps(*runtime, jsi::Value(*runtime, object)); + EXPECT_EQ(rawProps.getDynamic(), nullptr); + folly::dynamic expected = folly::dynamic::object("opacity", 0.5); + EXPECT_EQ(rawProps.toDynamic(), expected); +} + class PropsSingleFloat : public Props { public: PropsSingleFloat() = default; diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp index 81d9de17660c..a278fb2b3400 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp @@ -378,6 +378,13 @@ void Scheduler::uiManagerShouldSynchronouslyUpdateViewOnUIThread( } } +void Scheduler::uiManagerShouldSynchronouslyUpdateAnimatedProps( + const std::unordered_map& updates) { + if (delegate_ != nullptr) { + delegate_->schedulerShouldSynchronouslyUpdateAnimatedProps(updates); + } +} + void Scheduler::uiManagerDidUpdateShadowTree( const std::unordered_map& tagToProps) { if (delegate_ != nullptr) { diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h index 179d6701727e..b29e9591c1c1 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h @@ -100,6 +100,7 @@ class Scheduler final : public UIManagerDelegate { bool isJSResponder, bool blockNativeResponder) override; void uiManagerShouldSynchronouslyUpdateViewOnUIThread(Tag tag, const folly::dynamic &props) override; + void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map &updates) override; void uiManagerDidUpdateShadowTree(const std::unordered_map &tagToProps) override; void uiManagerDidCaptureViewSnapshot(Tag tag, SurfaceId surfaceId) override; void uiManagerDidSetViewSnapshot(Tag sourceTag, Tag targetTag, SurfaceId surfaceId) override; diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h b/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h index 10f45018d94a..f68b830dffee 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h @@ -11,6 +11,7 @@ #include +#include #include #include #include @@ -66,6 +67,13 @@ class SchedulerDelegate { virtual void schedulerShouldSynchronouslyUpdateViewOnUIThread(Tag tag, const folly::dynamic &props) = 0; + virtual void schedulerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map &updates) + { + for (const auto &[tag, props] : updates) { + schedulerShouldSynchronouslyUpdateViewOnUIThread(tag, animationbackend::packAnimatedProps(props)); + } + } + virtual void schedulerDidUpdateShadowTree(const std::unordered_map &tagToProps) = 0; // View transition bitmap snapshot capture and application. diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/tests/SchedulerDelegateInvalidationTest.cpp b/packages/react-native/ReactCommon/react/renderer/scheduler/tests/SchedulerDelegateInvalidationTest.cpp index 76c1646058f9..2f7957473128 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/tests/SchedulerDelegateInvalidationTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/tests/SchedulerDelegateInvalidationTest.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -588,4 +589,36 @@ TEST_F( EXPECT_EQ(delegate_->shouldRenderTransactionsCount(), 1); } +TEST(SchedulerDelegateTest, animatedPropsUseExistingViewUpdatesByDefault) { + class PropsDelegate : public RecordingDelegate { + public: + std::unordered_map received; + + void schedulerShouldSynchronouslyUpdateViewOnUIThread( + Tag tag, + const folly::dynamic& props) override { + received.emplace(tag, props); + } + } delegate; + + std::unordered_map updates; + updates.emplace( + 10, + AnimatedProps{ + {}, + std::make_unique(folly::dynamic::object("opacity", 0.25))}); + AnimatedPropsBuilder builder; + builder.setOpacity(0.75); + updates.emplace(20, builder.get()); + + delegate.schedulerShouldSynchronouslyUpdateAnimatedProps(updates); + + ASSERT_EQ(delegate.received.size(), 2); + folly::dynamic expectedRaw = folly::dynamic::object("opacity", 0.25); + folly::dynamic expectedTyped = folly::dynamic::object("opacity", 0.75); + EXPECT_EQ(delegate.received.at(10), expectedRaw); + EXPECT_EQ(delegate.received.at(20), expectedTyped); + EXPECT_EQ(updates.at(10).rawProps->toDynamic(), delegate.received.at(10)); +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp index 0cb62936c8b5..7eefe4606177 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp @@ -769,6 +769,13 @@ void UIManager::synchronouslyUpdateViewOnUIThread( } } +void UIManager::synchronouslyUpdateAnimatedProps( + const std::unordered_map& updates) { + if (delegate_ != nullptr) { + delegate_->uiManagerShouldSynchronouslyUpdateAnimatedProps(updates); + } +} + #pragma mark ContextContainer std::shared_ptr UIManager::getContextContainer() const { diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h index aaf758bcdf6b..541d57a0b258 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h @@ -89,6 +89,8 @@ class UIManager final : public ShadowTreeDelegate { void synchronouslyUpdateViewOnUIThread(Tag tag, const folly::dynamic &props); + void synchronouslyUpdateAnimatedProps(const std::unordered_map &updates); + /* * Provides access to a UIManagerBinding. * The `callback` methods will not be called if the internal pointer to diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h index 579989d8e01f..e431d6ee22b1 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h @@ -21,6 +21,8 @@ namespace facebook::react { +struct AnimatedProps; + /* * Abstract class for UIManager's delegate. */ @@ -71,6 +73,12 @@ class UIManagerDelegate { */ virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(Tag tag, const folly::dynamic &props) = 0; + /* + * Synchronous update of animated props for many views at once. + */ + virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps( + const std::unordered_map &updates) = 0; + /* * Called after updateShadowTree is invoked. */ diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index c61ae05c5935..c79ef62cc667 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -710,6 +710,7 @@ using facebook::react::SizeAsTuple = std::tuple>; using facebook::react::StatePipe = std::function; using facebook::react::SurfaceId = int32_t; +using facebook::react::SurfaceUpdates = std::unordered_map; using facebook::react::Tag = int32_t; using facebook::react::TelemetryClock = std::chrono::steady_clock; using facebook::react::TelemetryDuration = std::chrono::nanoseconds; @@ -864,6 +865,7 @@ double facebook::react::getCPUTimeNanos(); double facebook::react::interpolate(double inputValue, double inputMin, double inputMax, double outputMin, double outputMax, std::string_view extrapolateLeft, std::string_view extrapolateRight); double facebook::react::round(double value) noexcept; facebook::jsi::Value facebook::react::createPromiseAsJSIValue(facebook::jsi::Runtime& rt, facebook::react::PromiseSetupFunctionType&& func); +facebook::react::AnimatedPropsBuffer facebook::react::encodeAnimatedProps(const std::unordered_map& updates); facebook::react::Color facebook::react::hostPlatformColorFromComponents(facebook::react::ColorComponents components); facebook::react::Color facebook::react::hostPlatformColorFromRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a); facebook::react::ColorComponents facebook::react::colorComponentsFromColor(facebook::react::SharedColor color); @@ -1580,7 +1582,7 @@ class facebook::react::AnimatedPropsRegistry { public void clear(facebook::react::SurfaceId surfaceId); public void clearOnSurfaceStop(facebook::react::SurfaceId surfaceId); public void initializeSurface(facebook::react::SurfaceId surfaceId); - public void update(const std::unordered_map& surfaceUpdates); + public void update(const std::vector& batches); } class facebook::react::AnimationBackend : public facebook::react::UIManagerAnimationBackend { @@ -2400,6 +2402,7 @@ class facebook::react::FabricMountingManager { public void sendAccessibilityEvent(const facebook::react::ShadowView& shadowView, const std::string& eventType); public void setIsJSResponder(const facebook::react::ShadowView& shadowView, bool isJSResponder, bool blockNativeResponder); public void setViewSnapshot(facebook::react::Tag sourceTag, facebook::react::Tag targetTag, facebook::react::SurfaceId surfaceId); + public void synchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public void synchronouslyUpdateViewOnUIThread(facebook::react::Tag viewTag, const folly::dynamic& props); public ~FabricMountingManager(); } @@ -4259,6 +4262,7 @@ class facebook::react::RawProps { public bool isEmpty() const noexcept; public const facebook::react::RawValue* at(const char* name) const noexcept; public const facebook::react::RawValue* at(const char* name, const char* prefix, const char* suffix) const noexcept; + public const folly::dynamic* getDynamic() const noexcept; public facebook::react::RawProps& operator=(const facebook::react::RawProps& other) noexcept = delete; public facebook::react::RawProps& operator=(facebook::react::RawProps&& other) noexcept = delete; public folly::dynamic toDynamic(const std::function& filterObjectKeys = nullptr) const; @@ -4617,6 +4621,7 @@ class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) final; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) override; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) final; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) override; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) override; public void addEventListener(std::shared_ptr listener); public void animationTick() const; @@ -4640,6 +4645,7 @@ class facebook::react::SchedulerDelegate { public virtual void schedulerDidUpdateShadowTree(const std::unordered_map& tagToProps) = 0; public virtual void schedulerShouldMergeReactRevision(facebook::react::SurfaceId surfaceId) = 0; public virtual void schedulerShouldRenderTransactions(const std::shared_ptr& mountingCoordinator) = 0; + public virtual void schedulerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public virtual void schedulerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~SchedulerDelegate() noexcept = default; } @@ -5371,6 +5377,7 @@ class facebook::react::UIManager : public facebook::react::ShadowTreeDelegate { public void startEmptySurface(facebook::react::ShadowTree::Unique&& shadowTree) const noexcept; public void startSurface(facebook::react::ShadowTree::Unique&& shadowTree, std::string moduleName, folly::dynamic props, facebook::react::DisplayMode displayMode) const noexcept; public void stopSurfaceForAnimationDelegate(facebook::react::SurfaceId surfaceId) const; + public void synchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public void synchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props); public void unregisterCommitHook(facebook::react::UIManagerCommitHook& commitHook); public void unregisterMountHook(facebook::react::UIManagerMountHook& mountHook); @@ -5438,6 +5445,7 @@ class facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) = 0; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) = 0; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) = 0; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) = 0; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~UIManagerDelegate() noexcept = default; } @@ -6779,6 +6787,12 @@ struct facebook::react::AnimatedProps { public std::vector> props; } +struct facebook::react::AnimatedPropsBuffer { + public std::vector doubles; + public std::vector rawProps; + public std::vector ints; +} + struct facebook::react::AnimatedPropsBuilder { public facebook::react::AnimatedProps get(); public std::unique_ptr rawProps; @@ -7804,7 +7818,7 @@ struct facebook::react::PropsParserContext { } struct facebook::react::PropsSnapshot { - public facebook::react::BaseViewProps props; + public std::unique_ptr props; public std::unique_ptr rawProps; public std::unordered_set propNames; } @@ -8112,12 +8126,6 @@ struct facebook::react::SurfaceContext { public std::unordered_set> pendingFamilies; } -struct facebook::react::SurfaceUpdates { - public bool hasLayoutUpdates; - public std::unordered_map propsMap; - public std::unordered_set> families; -} - struct facebook::react::SystraceSection : public facebook::react::DummyTraceSection { template public SystraceSection(const char* name, ConvertsToStringPiece &&... args); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index 9d5008343459..fca91a4cc3ce 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -709,6 +709,7 @@ using facebook::react::SizeAsTuple = std::tuple>; using facebook::react::StatePipe = std::function; using facebook::react::SurfaceId = int32_t; +using facebook::react::SurfaceUpdates = std::unordered_map; using facebook::react::Tag = int32_t; using facebook::react::TelemetryClock = std::chrono::steady_clock; using facebook::react::TelemetryDuration = std::chrono::nanoseconds; @@ -863,6 +864,7 @@ double facebook::react::getCPUTimeNanos(); double facebook::react::interpolate(double inputValue, double inputMin, double inputMax, double outputMin, double outputMax, std::string_view extrapolateLeft, std::string_view extrapolateRight); double facebook::react::round(double value) noexcept; facebook::jsi::Value facebook::react::createPromiseAsJSIValue(facebook::jsi::Runtime& rt, facebook::react::PromiseSetupFunctionType&& func); +facebook::react::AnimatedPropsBuffer facebook::react::encodeAnimatedProps(const std::unordered_map& updates); facebook::react::Color facebook::react::hostPlatformColorFromComponents(facebook::react::ColorComponents components); facebook::react::Color facebook::react::hostPlatformColorFromRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a); facebook::react::ColorComponents facebook::react::colorComponentsFromColor(facebook::react::SharedColor color); @@ -1575,7 +1577,7 @@ class facebook::react::AnimatedPropsRegistry { public void clear(facebook::react::SurfaceId surfaceId); public void clearOnSurfaceStop(facebook::react::SurfaceId surfaceId); public void initializeSurface(facebook::react::SurfaceId surfaceId); - public void update(const std::unordered_map& surfaceUpdates); + public void update(const std::vector& batches); } class facebook::react::AnimationBackend : public facebook::react::UIManagerAnimationBackend { @@ -2383,6 +2385,7 @@ class facebook::react::FabricMountingManager { public void sendAccessibilityEvent(const facebook::react::ShadowView& shadowView, const std::string& eventType); public void setIsJSResponder(const facebook::react::ShadowView& shadowView, bool isJSResponder, bool blockNativeResponder); public void setViewSnapshot(facebook::react::Tag sourceTag, facebook::react::Tag targetTag, facebook::react::SurfaceId surfaceId); + public void synchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public void synchronouslyUpdateViewOnUIThread(facebook::react::Tag viewTag, const folly::dynamic& props); public ~FabricMountingManager(); } @@ -4103,6 +4106,7 @@ class facebook::react::RawProps { public bool isEmpty() const noexcept; public const facebook::react::RawValue* at(const char* name) const noexcept; public const facebook::react::RawValue* at(const char* name, const char* prefix, const char* suffix) const noexcept; + public const folly::dynamic* getDynamic() const noexcept; public facebook::react::RawProps& operator=(const facebook::react::RawProps& other) noexcept = delete; public facebook::react::RawProps& operator=(facebook::react::RawProps&& other) noexcept = delete; public folly::dynamic toDynamic(const std::function& filterObjectKeys = nullptr) const; @@ -4433,6 +4437,7 @@ class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) final; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) override; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) final; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) override; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) override; public void addEventListener(std::shared_ptr listener); public void animationTick() const; @@ -4456,6 +4461,7 @@ class facebook::react::SchedulerDelegate { public virtual void schedulerDidUpdateShadowTree(const std::unordered_map& tagToProps) = 0; public virtual void schedulerShouldMergeReactRevision(facebook::react::SurfaceId surfaceId) = 0; public virtual void schedulerShouldRenderTransactions(const std::shared_ptr& mountingCoordinator) = 0; + public virtual void schedulerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public virtual void schedulerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~SchedulerDelegate() noexcept = default; } @@ -5181,6 +5187,7 @@ class facebook::react::UIManager : public facebook::react::ShadowTreeDelegate { public void startEmptySurface(facebook::react::ShadowTree::Unique&& shadowTree) const noexcept; public void startSurface(facebook::react::ShadowTree::Unique&& shadowTree, std::string moduleName, folly::dynamic props, facebook::react::DisplayMode displayMode) const noexcept; public void stopSurfaceForAnimationDelegate(facebook::react::SurfaceId surfaceId) const; + public void synchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public void synchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props); public void unregisterCommitHook(facebook::react::UIManagerCommitHook& commitHook); public void unregisterMountHook(facebook::react::UIManagerMountHook& mountHook); @@ -5248,6 +5255,7 @@ class facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) = 0; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) = 0; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) = 0; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) = 0; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~UIManagerDelegate() noexcept = default; } @@ -6589,6 +6597,12 @@ struct facebook::react::AnimatedProps { public std::vector> props; } +struct facebook::react::AnimatedPropsBuffer { + public std::vector doubles; + public std::vector rawProps; + public std::vector ints; +} + struct facebook::react::AnimatedPropsBuilder { public facebook::react::AnimatedProps get(); public std::unique_ptr rawProps; @@ -7564,7 +7578,7 @@ struct facebook::react::PropsParserContext { } struct facebook::react::PropsSnapshot { - public facebook::react::BaseViewProps props; + public std::unique_ptr props; public std::unique_ptr rawProps; public std::unordered_set propNames; } @@ -7872,12 +7886,6 @@ struct facebook::react::SurfaceContext { public std::unordered_set> pendingFamilies; } -struct facebook::react::SurfaceUpdates { - public bool hasLayoutUpdates; - public std::unordered_map propsMap; - public std::unordered_set> families; -} - struct facebook::react::SystraceSection : public facebook::react::DummyTraceSection { template public SystraceSection(const char* name, ConvertsToStringPiece &&... args); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index 2470a7083fb9..e3a5749cc10a 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -710,6 +710,7 @@ using facebook::react::SizeAsTuple = std::tuple>; using facebook::react::StatePipe = std::function; using facebook::react::SurfaceId = int32_t; +using facebook::react::SurfaceUpdates = std::unordered_map; using facebook::react::Tag = int32_t; using facebook::react::TelemetryClock = std::chrono::steady_clock; using facebook::react::TelemetryDuration = std::chrono::nanoseconds; @@ -864,6 +865,7 @@ double facebook::react::getCPUTimeNanos(); double facebook::react::interpolate(double inputValue, double inputMin, double inputMax, double outputMin, double outputMax, std::string_view extrapolateLeft, std::string_view extrapolateRight); double facebook::react::round(double value) noexcept; facebook::jsi::Value facebook::react::createPromiseAsJSIValue(facebook::jsi::Runtime& rt, facebook::react::PromiseSetupFunctionType&& func); +facebook::react::AnimatedPropsBuffer facebook::react::encodeAnimatedProps(const std::unordered_map& updates); facebook::react::Color facebook::react::hostPlatformColorFromComponents(facebook::react::ColorComponents components); facebook::react::Color facebook::react::hostPlatformColorFromRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a); facebook::react::ColorComponents facebook::react::colorComponentsFromColor(facebook::react::SharedColor color); @@ -1579,7 +1581,7 @@ class facebook::react::AnimatedPropsRegistry { public void clear(facebook::react::SurfaceId surfaceId); public void clearOnSurfaceStop(facebook::react::SurfaceId surfaceId); public void initializeSurface(facebook::react::SurfaceId surfaceId); - public void update(const std::unordered_map& surfaceUpdates); + public void update(const std::vector& batches); } class facebook::react::AnimationBackend : public facebook::react::UIManagerAnimationBackend { @@ -2398,6 +2400,7 @@ class facebook::react::FabricMountingManager { public void sendAccessibilityEvent(const facebook::react::ShadowView& shadowView, const std::string& eventType); public void setIsJSResponder(const facebook::react::ShadowView& shadowView, bool isJSResponder, bool blockNativeResponder); public void setViewSnapshot(facebook::react::Tag sourceTag, facebook::react::Tag targetTag, facebook::react::SurfaceId surfaceId); + public void synchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public void synchronouslyUpdateViewOnUIThread(facebook::react::Tag viewTag, const folly::dynamic& props); public ~FabricMountingManager(); } @@ -4256,6 +4259,7 @@ class facebook::react::RawProps { public bool isEmpty() const noexcept; public const facebook::react::RawValue* at(const char* name) const noexcept; public const facebook::react::RawValue* at(const char* name, const char* prefix, const char* suffix) const noexcept; + public const folly::dynamic* getDynamic() const noexcept; public facebook::react::RawProps& operator=(const facebook::react::RawProps& other) noexcept = delete; public facebook::react::RawProps& operator=(facebook::react::RawProps&& other) noexcept = delete; public folly::dynamic toDynamic(const std::function& filterObjectKeys = nullptr) const; @@ -4614,6 +4618,7 @@ class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) final; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) override; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) final; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) override; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) override; public void addEventListener(std::shared_ptr listener); public void animationTick() const; @@ -4637,6 +4642,7 @@ class facebook::react::SchedulerDelegate { public virtual void schedulerDidUpdateShadowTree(const std::unordered_map& tagToProps) = 0; public virtual void schedulerShouldMergeReactRevision(facebook::react::SurfaceId surfaceId) = 0; public virtual void schedulerShouldRenderTransactions(const std::shared_ptr& mountingCoordinator) = 0; + public virtual void schedulerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public virtual void schedulerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~SchedulerDelegate() noexcept = default; } @@ -5362,6 +5368,7 @@ class facebook::react::UIManager : public facebook::react::ShadowTreeDelegate { public void startEmptySurface(facebook::react::ShadowTree::Unique&& shadowTree) const noexcept; public void startSurface(facebook::react::ShadowTree::Unique&& shadowTree, std::string moduleName, folly::dynamic props, facebook::react::DisplayMode displayMode) const noexcept; public void stopSurfaceForAnimationDelegate(facebook::react::SurfaceId surfaceId) const; + public void synchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public void synchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props); public void unregisterCommitHook(facebook::react::UIManagerCommitHook& commitHook); public void unregisterMountHook(facebook::react::UIManagerMountHook& mountHook); @@ -5429,6 +5436,7 @@ class facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) = 0; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) = 0; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) = 0; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) = 0; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~UIManagerDelegate() noexcept = default; } @@ -6770,6 +6778,12 @@ struct facebook::react::AnimatedProps { public std::vector> props; } +struct facebook::react::AnimatedPropsBuffer { + public std::vector doubles; + public std::vector rawProps; + public std::vector ints; +} + struct facebook::react::AnimatedPropsBuilder { public facebook::react::AnimatedProps get(); public std::unique_ptr rawProps; @@ -7795,7 +7809,7 @@ struct facebook::react::PropsParserContext { } struct facebook::react::PropsSnapshot { - public facebook::react::BaseViewProps props; + public std::unique_ptr props; public std::unique_ptr rawProps; public std::unordered_set propNames; } @@ -8103,12 +8117,6 @@ struct facebook::react::SurfaceContext { public std::unordered_set> pendingFamilies; } -struct facebook::react::SurfaceUpdates { - public bool hasLayoutUpdates; - public std::unordered_map propsMap; - public std::unordered_set> families; -} - struct facebook::react::SystraceSection : public facebook::react::DummyTraceSection { template public SystraceSection(const char* name, ConvertsToStringPiece &&... args); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index a8f040724fa6..87d736990291 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -3538,6 +3538,7 @@ using facebook::react::SizeAsTuple = std::tuple>; using facebook::react::StatePipe = std::function; using facebook::react::SurfaceId = int32_t; +using facebook::react::SurfaceUpdates = std::unordered_map; using facebook::react::Tag = int32_t; using facebook::react::TelemetryClock = std::chrono::steady_clock; using facebook::react::TelemetryDuration = std::chrono::nanoseconds; @@ -4126,7 +4127,7 @@ class facebook::react::AnimatedPropsRegistry { public void clear(facebook::react::SurfaceId surfaceId); public void clearOnSurfaceStop(facebook::react::SurfaceId surfaceId); public void initializeSurface(facebook::react::SurfaceId surfaceId); - public void update(const std::unordered_map& surfaceUpdates); + public void update(const std::vector& batches); } class facebook::react::AnimationBackend : public facebook::react::UIManagerAnimationBackend { @@ -6427,6 +6428,7 @@ class facebook::react::RawProps { public bool isEmpty() const noexcept; public const facebook::react::RawValue* at(const char* name) const noexcept; public const facebook::react::RawValue* at(const char* name, const char* prefix, const char* suffix) const noexcept; + public const folly::dynamic* getDynamic() const noexcept; public facebook::react::RawProps& operator=(const facebook::react::RawProps& other) noexcept = delete; public facebook::react::RawProps& operator=(facebook::react::RawProps&& other) noexcept = delete; public folly::dynamic toDynamic(const std::function& filterObjectKeys = nullptr) const; @@ -6766,6 +6768,7 @@ class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) final; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) override; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) final; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) override; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) override; public void addEventListener(std::shared_ptr listener); public void animationTick() const; @@ -6789,6 +6792,7 @@ class facebook::react::SchedulerDelegate { public virtual void schedulerDidUpdateShadowTree(const std::unordered_map& tagToProps) = 0; public virtual void schedulerShouldMergeReactRevision(facebook::react::SurfaceId surfaceId) = 0; public virtual void schedulerShouldRenderTransactions(const std::shared_ptr& mountingCoordinator) = 0; + public virtual void schedulerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public virtual void schedulerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~SchedulerDelegate() noexcept = default; } @@ -7501,6 +7505,7 @@ class facebook::react::UIManager : public facebook::react::ShadowTreeDelegate { public void startEmptySurface(facebook::react::ShadowTree::Unique&& shadowTree) const noexcept; public void startSurface(facebook::react::ShadowTree::Unique&& shadowTree, std::string moduleName, folly::dynamic props, facebook::react::DisplayMode displayMode) const noexcept; public void stopSurfaceForAnimationDelegate(facebook::react::SurfaceId surfaceId) const; + public void synchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public void synchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props); public void unregisterCommitHook(facebook::react::UIManagerCommitHook& commitHook); public void unregisterMountHook(facebook::react::UIManagerMountHook& mountHook); @@ -7568,6 +7573,7 @@ class facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) = 0; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) = 0; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) = 0; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) = 0; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~UIManagerDelegate() noexcept = default; } @@ -9799,7 +9805,7 @@ struct facebook::react::PropsParserContext { } struct facebook::react::PropsSnapshot { - public facebook::react::BaseViewProps props; + public std::unique_ptr props; public std::unique_ptr rawProps; public std::unordered_set propNames; } @@ -10076,12 +10082,6 @@ struct facebook::react::SurfaceContext { public std::unordered_set> pendingFamilies; } -struct facebook::react::SurfaceUpdates { - public bool hasLayoutUpdates; - public std::unordered_map propsMap; - public std::unordered_set> families; -} - struct facebook::react::SystraceSection : public facebook::react::DummyTraceSection { template public SystraceSection(const char* name, ConvertsToStringPiece &&... args); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api index 3c0a535fb10e..968828160b0b 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api @@ -3529,6 +3529,7 @@ using facebook::react::SizeAsTuple = std::tuple>; using facebook::react::StatePipe = std::function; using facebook::react::SurfaceId = int32_t; +using facebook::react::SurfaceUpdates = std::unordered_map; using facebook::react::Tag = int32_t; using facebook::react::TelemetryClock = std::chrono::steady_clock; using facebook::react::TelemetryDuration = std::chrono::nanoseconds; @@ -4114,7 +4115,7 @@ class facebook::react::AnimatedPropsRegistry { public void clear(facebook::react::SurfaceId surfaceId); public void clearOnSurfaceStop(facebook::react::SurfaceId surfaceId); public void initializeSurface(facebook::react::SurfaceId surfaceId); - public void update(const std::unordered_map& surfaceUpdates); + public void update(const std::vector& batches); } class facebook::react::AnimationBackend : public facebook::react::UIManagerAnimationBackend { @@ -6303,6 +6304,7 @@ class facebook::react::RawProps { public bool isEmpty() const noexcept; public const facebook::react::RawValue* at(const char* name) const noexcept; public const facebook::react::RawValue* at(const char* name, const char* prefix, const char* suffix) const noexcept; + public const folly::dynamic* getDynamic() const noexcept; public facebook::react::RawProps& operator=(const facebook::react::RawProps& other) noexcept = delete; public facebook::react::RawProps& operator=(facebook::react::RawProps&& other) noexcept = delete; public folly::dynamic toDynamic(const std::function& filterObjectKeys = nullptr) const; @@ -6614,6 +6616,7 @@ class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) final; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) override; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) final; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) override; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) override; public void addEventListener(std::shared_ptr listener); public void animationTick() const; @@ -6637,6 +6640,7 @@ class facebook::react::SchedulerDelegate { public virtual void schedulerDidUpdateShadowTree(const std::unordered_map& tagToProps) = 0; public virtual void schedulerShouldMergeReactRevision(facebook::react::SurfaceId surfaceId) = 0; public virtual void schedulerShouldRenderTransactions(const std::shared_ptr& mountingCoordinator) = 0; + public virtual void schedulerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public virtual void schedulerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~SchedulerDelegate() noexcept = default; } @@ -7343,6 +7347,7 @@ class facebook::react::UIManager : public facebook::react::ShadowTreeDelegate { public void startEmptySurface(facebook::react::ShadowTree::Unique&& shadowTree) const noexcept; public void startSurface(facebook::react::ShadowTree::Unique&& shadowTree, std::string moduleName, folly::dynamic props, facebook::react::DisplayMode displayMode) const noexcept; public void stopSurfaceForAnimationDelegate(facebook::react::SurfaceId surfaceId) const; + public void synchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public void synchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props); public void unregisterCommitHook(facebook::react::UIManagerCommitHook& commitHook); public void unregisterMountHook(facebook::react::UIManagerMountHook& mountHook); @@ -7410,6 +7415,7 @@ class facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) = 0; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) = 0; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) = 0; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) = 0; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~UIManagerDelegate() noexcept = default; } @@ -9615,7 +9621,7 @@ struct facebook::react::PropsParserContext { } struct facebook::react::PropsSnapshot { - public facebook::react::BaseViewProps props; + public std::unique_ptr props; public std::unique_ptr rawProps; public std::unordered_set propNames; } @@ -9892,12 +9898,6 @@ struct facebook::react::SurfaceContext { public std::unordered_set> pendingFamilies; } -struct facebook::react::SurfaceUpdates { - public bool hasLayoutUpdates; - public std::unordered_map propsMap; - public std::unordered_set> families; -} - struct facebook::react::SystraceSection : public facebook::react::DummyTraceSection { template public SystraceSection(const char* name, ConvertsToStringPiece &&... args); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index 958586fc805e..ea89b15e3778 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -3538,6 +3538,7 @@ using facebook::react::SizeAsTuple = std::tuple>; using facebook::react::StatePipe = std::function; using facebook::react::SurfaceId = int32_t; +using facebook::react::SurfaceUpdates = std::unordered_map; using facebook::react::Tag = int32_t; using facebook::react::TelemetryClock = std::chrono::steady_clock; using facebook::react::TelemetryDuration = std::chrono::nanoseconds; @@ -4125,7 +4126,7 @@ class facebook::react::AnimatedPropsRegistry { public void clear(facebook::react::SurfaceId surfaceId); public void clearOnSurfaceStop(facebook::react::SurfaceId surfaceId); public void initializeSurface(facebook::react::SurfaceId surfaceId); - public void update(const std::unordered_map& surfaceUpdates); + public void update(const std::vector& batches); } class facebook::react::AnimationBackend : public facebook::react::UIManagerAnimationBackend { @@ -6424,6 +6425,7 @@ class facebook::react::RawProps { public bool isEmpty() const noexcept; public const facebook::react::RawValue* at(const char* name) const noexcept; public const facebook::react::RawValue* at(const char* name, const char* prefix, const char* suffix) const noexcept; + public const folly::dynamic* getDynamic() const noexcept; public facebook::react::RawProps& operator=(const facebook::react::RawProps& other) noexcept = delete; public facebook::react::RawProps& operator=(facebook::react::RawProps&& other) noexcept = delete; public folly::dynamic toDynamic(const std::function& filterObjectKeys = nullptr) const; @@ -6763,6 +6765,7 @@ class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) final; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) override; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) final; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) override; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) override; public void addEventListener(std::shared_ptr listener); public void animationTick() const; @@ -6786,6 +6789,7 @@ class facebook::react::SchedulerDelegate { public virtual void schedulerDidUpdateShadowTree(const std::unordered_map& tagToProps) = 0; public virtual void schedulerShouldMergeReactRevision(facebook::react::SurfaceId surfaceId) = 0; public virtual void schedulerShouldRenderTransactions(const std::shared_ptr& mountingCoordinator) = 0; + public virtual void schedulerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public virtual void schedulerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~SchedulerDelegate() noexcept = default; } @@ -7492,6 +7496,7 @@ class facebook::react::UIManager : public facebook::react::ShadowTreeDelegate { public void startEmptySurface(facebook::react::ShadowTree::Unique&& shadowTree) const noexcept; public void startSurface(facebook::react::ShadowTree::Unique&& shadowTree, std::string moduleName, folly::dynamic props, facebook::react::DisplayMode displayMode) const noexcept; public void stopSurfaceForAnimationDelegate(facebook::react::SurfaceId surfaceId) const; + public void synchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public void synchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props); public void unregisterCommitHook(facebook::react::UIManagerCommitHook& commitHook); public void unregisterMountHook(facebook::react::UIManagerMountHook& mountHook); @@ -7559,6 +7564,7 @@ class facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) = 0; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) = 0; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) = 0; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) = 0; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~UIManagerDelegate() noexcept = default; } @@ -9790,7 +9796,7 @@ struct facebook::react::PropsParserContext { } struct facebook::react::PropsSnapshot { - public facebook::react::BaseViewProps props; + public std::unique_ptr props; public std::unique_ptr rawProps; public std::unordered_set propNames; } @@ -10067,12 +10073,6 @@ struct facebook::react::SurfaceContext { public std::unordered_set> pendingFamilies; } -struct facebook::react::SurfaceUpdates { - public bool hasLayoutUpdates; - public std::unordered_map propsMap; - public std::unordered_set> families; -} - struct facebook::react::SystraceSection : public facebook::react::DummyTraceSection { template public SystraceSection(const char* name, ConvertsToStringPiece &&... args); diff --git a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api index 8b46097a89f9..d1611c1c8a98 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api @@ -331,6 +331,7 @@ using facebook::react::SizeAsTuple = std::tuple; using facebook::react::SnapshotMap = std::unordered_map>; using facebook::react::StatePipe = std::function; using facebook::react::SurfaceId = int32_t; +using facebook::react::SurfaceUpdates = std::unordered_map; using facebook::react::Tag = int32_t; using facebook::react::TelemetryClock = std::chrono::steady_clock; using facebook::react::TelemetryDuration = std::chrono::nanoseconds; @@ -904,7 +905,7 @@ class facebook::react::AnimatedPropsRegistry { public void clear(facebook::react::SurfaceId surfaceId); public void clearOnSurfaceStop(facebook::react::SurfaceId surfaceId); public void initializeSurface(facebook::react::SurfaceId surfaceId); - public void update(const std::unordered_map& surfaceUpdates); + public void update(const std::vector& batches); } class facebook::react::AnimationBackend : public facebook::react::UIManagerAnimationBackend { @@ -2830,6 +2831,7 @@ class facebook::react::RawProps { public bool isEmpty() const noexcept; public const facebook::react::RawValue* at(const char* name) const noexcept; public const facebook::react::RawValue* at(const char* name, const char* prefix, const char* suffix) const noexcept; + public const folly::dynamic* getDynamic() const noexcept; public facebook::react::RawProps& operator=(const facebook::react::RawProps& other) noexcept = delete; public facebook::react::RawProps& operator=(facebook::react::RawProps&& other) noexcept = delete; public folly::dynamic toDynamic(const std::function& filterObjectKeys = nullptr) const; @@ -3161,6 +3163,7 @@ class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) final; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) override; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) final; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) override; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) override; public void addEventListener(std::shared_ptr listener); public void animationTick() const; @@ -3184,6 +3187,7 @@ class facebook::react::SchedulerDelegate { public virtual void schedulerDidUpdateShadowTree(const std::unordered_map& tagToProps) = 0; public virtual void schedulerShouldMergeReactRevision(facebook::react::SurfaceId surfaceId) = 0; public virtual void schedulerShouldRenderTransactions(const std::shared_ptr& mountingCoordinator) = 0; + public virtual void schedulerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public virtual void schedulerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~SchedulerDelegate() noexcept = default; } @@ -3812,6 +3816,7 @@ class facebook::react::UIManager : public facebook::react::ShadowTreeDelegate { public void startEmptySurface(facebook::react::ShadowTree::Unique&& shadowTree) const noexcept; public void startSurface(facebook::react::ShadowTree::Unique&& shadowTree, std::string moduleName, folly::dynamic props, facebook::react::DisplayMode displayMode) const noexcept; public void stopSurfaceForAnimationDelegate(facebook::react::SurfaceId surfaceId) const; + public void synchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public void synchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props); public void unregisterCommitHook(facebook::react::UIManagerCommitHook& commitHook); public void unregisterMountHook(facebook::react::UIManagerMountHook& mountHook); @@ -3879,6 +3884,7 @@ class facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) = 0; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) = 0; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) = 0; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) = 0; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~UIManagerDelegate() noexcept = default; } @@ -5951,7 +5957,7 @@ struct facebook::react::PropsParserContext { } struct facebook::react::PropsSnapshot { - public facebook::react::BaseViewProps props; + public std::unique_ptr props; public std::unique_ptr rawProps; public std::unordered_set propNames; } @@ -6228,12 +6234,6 @@ struct facebook::react::SurfaceContext { public std::unordered_set> pendingFamilies; } -struct facebook::react::SurfaceUpdates { - public bool hasLayoutUpdates; - public std::unordered_map propsMap; - public std::unordered_set> families; -} - struct facebook::react::SystraceSection : public facebook::react::DummyTraceSection { template public SystraceSection(const char* name, ConvertsToStringPiece &&... args); diff --git a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api index 692b5064e583..559ac5303a6b 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api @@ -330,6 +330,7 @@ using facebook::react::SizeAsTuple = std::tuple; using facebook::react::SnapshotMap = std::unordered_map>; using facebook::react::StatePipe = std::function; using facebook::react::SurfaceId = int32_t; +using facebook::react::SurfaceUpdates = std::unordered_map; using facebook::react::Tag = int32_t; using facebook::react::TelemetryClock = std::chrono::steady_clock; using facebook::react::TelemetryDuration = std::chrono::nanoseconds; @@ -900,7 +901,7 @@ class facebook::react::AnimatedPropsRegistry { public void clear(facebook::react::SurfaceId surfaceId); public void clearOnSurfaceStop(facebook::react::SurfaceId surfaceId); public void initializeSurface(facebook::react::SurfaceId surfaceId); - public void update(const std::unordered_map& surfaceUpdates); + public void update(const std::vector& batches); } class facebook::react::AnimationBackend : public facebook::react::UIManagerAnimationBackend { @@ -2714,6 +2715,7 @@ class facebook::react::RawProps { public bool isEmpty() const noexcept; public const facebook::react::RawValue* at(const char* name) const noexcept; public const facebook::react::RawValue* at(const char* name, const char* prefix, const char* suffix) const noexcept; + public const folly::dynamic* getDynamic() const noexcept; public facebook::react::RawProps& operator=(const facebook::react::RawProps& other) noexcept = delete; public facebook::react::RawProps& operator=(facebook::react::RawProps&& other) noexcept = delete; public folly::dynamic toDynamic(const std::function& filterObjectKeys = nullptr) const; @@ -3017,6 +3019,7 @@ class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) final; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) override; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) final; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) override; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) override; public void addEventListener(std::shared_ptr listener); public void animationTick() const; @@ -3040,6 +3043,7 @@ class facebook::react::SchedulerDelegate { public virtual void schedulerDidUpdateShadowTree(const std::unordered_map& tagToProps) = 0; public virtual void schedulerShouldMergeReactRevision(facebook::react::SurfaceId surfaceId) = 0; public virtual void schedulerShouldRenderTransactions(const std::shared_ptr& mountingCoordinator) = 0; + public virtual void schedulerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public virtual void schedulerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~SchedulerDelegate() noexcept = default; } @@ -3662,6 +3666,7 @@ class facebook::react::UIManager : public facebook::react::ShadowTreeDelegate { public void startEmptySurface(facebook::react::ShadowTree::Unique&& shadowTree) const noexcept; public void startSurface(facebook::react::ShadowTree::Unique&& shadowTree, std::string moduleName, folly::dynamic props, facebook::react::DisplayMode displayMode) const noexcept; public void stopSurfaceForAnimationDelegate(facebook::react::SurfaceId surfaceId) const; + public void synchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public void synchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props); public void unregisterCommitHook(facebook::react::UIManagerCommitHook& commitHook); public void unregisterMountHook(facebook::react::UIManagerMountHook& mountHook); @@ -3729,6 +3734,7 @@ class facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) = 0; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) = 0; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) = 0; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) = 0; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~UIManagerDelegate() noexcept = default; } @@ -5775,7 +5781,7 @@ struct facebook::react::PropsParserContext { } struct facebook::react::PropsSnapshot { - public facebook::react::BaseViewProps props; + public std::unique_ptr props; public std::unique_ptr rawProps; public std::unordered_set propNames; } @@ -6052,12 +6058,6 @@ struct facebook::react::SurfaceContext { public std::unordered_set> pendingFamilies; } -struct facebook::react::SurfaceUpdates { - public bool hasLayoutUpdates; - public std::unordered_map propsMap; - public std::unordered_set> families; -} - struct facebook::react::SystraceSection : public facebook::react::DummyTraceSection { template public SystraceSection(const char* name, ConvertsToStringPiece &&... args); diff --git a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api index ff0ef95bbfdb..3667b71dda10 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api @@ -331,6 +331,7 @@ using facebook::react::SizeAsTuple = std::tuple; using facebook::react::SnapshotMap = std::unordered_map>; using facebook::react::StatePipe = std::function; using facebook::react::SurfaceId = int32_t; +using facebook::react::SurfaceUpdates = std::unordered_map; using facebook::react::Tag = int32_t; using facebook::react::TelemetryClock = std::chrono::steady_clock; using facebook::react::TelemetryDuration = std::chrono::nanoseconds; @@ -903,7 +904,7 @@ class facebook::react::AnimatedPropsRegistry { public void clear(facebook::react::SurfaceId surfaceId); public void clearOnSurfaceStop(facebook::react::SurfaceId surfaceId); public void initializeSurface(facebook::react::SurfaceId surfaceId); - public void update(const std::unordered_map& surfaceUpdates); + public void update(const std::vector& batches); } class facebook::react::AnimationBackend : public facebook::react::UIManagerAnimationBackend { @@ -2827,6 +2828,7 @@ class facebook::react::RawProps { public bool isEmpty() const noexcept; public const facebook::react::RawValue* at(const char* name) const noexcept; public const facebook::react::RawValue* at(const char* name, const char* prefix, const char* suffix) const noexcept; + public const folly::dynamic* getDynamic() const noexcept; public facebook::react::RawProps& operator=(const facebook::react::RawProps& other) noexcept = delete; public facebook::react::RawProps& operator=(facebook::react::RawProps&& other) noexcept = delete; public folly::dynamic toDynamic(const std::function& filterObjectKeys = nullptr) const; @@ -3158,6 +3160,7 @@ class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) final; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) override; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) final; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) override; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) override; public void addEventListener(std::shared_ptr listener); public void animationTick() const; @@ -3181,6 +3184,7 @@ class facebook::react::SchedulerDelegate { public virtual void schedulerDidUpdateShadowTree(const std::unordered_map& tagToProps) = 0; public virtual void schedulerShouldMergeReactRevision(facebook::react::SurfaceId surfaceId) = 0; public virtual void schedulerShouldRenderTransactions(const std::shared_ptr& mountingCoordinator) = 0; + public virtual void schedulerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public virtual void schedulerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~SchedulerDelegate() noexcept = default; } @@ -3803,6 +3807,7 @@ class facebook::react::UIManager : public facebook::react::ShadowTreeDelegate { public void startEmptySurface(facebook::react::ShadowTree::Unique&& shadowTree) const noexcept; public void startSurface(facebook::react::ShadowTree::Unique&& shadowTree, std::string moduleName, folly::dynamic props, facebook::react::DisplayMode displayMode) const noexcept; public void stopSurfaceForAnimationDelegate(facebook::react::SurfaceId surfaceId) const; + public void synchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public void synchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props); public void unregisterCommitHook(facebook::react::UIManagerCommitHook& commitHook); public void unregisterMountHook(facebook::react::UIManagerMountHook& mountHook); @@ -3870,6 +3875,7 @@ class facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) = 0; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) = 0; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) = 0; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) = 0; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~UIManagerDelegate() noexcept = default; } @@ -5942,7 +5948,7 @@ struct facebook::react::PropsParserContext { } struct facebook::react::PropsSnapshot { - public facebook::react::BaseViewProps props; + public std::unique_ptr props; public std::unique_ptr rawProps; public std::unordered_set propNames; } @@ -6219,12 +6225,6 @@ struct facebook::react::SurfaceContext { public std::unordered_set> pendingFamilies; } -struct facebook::react::SurfaceUpdates { - public bool hasLayoutUpdates; - public std::unordered_map propsMap; - public std::unordered_set> families; -} - struct facebook::react::SystraceSection : public facebook::react::DummyTraceSection { template public SystraceSection(const char* name, ConvertsToStringPiece &&... args);