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/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/renderer/animationbackend/AnimatedPropsRegistry.cpp b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.cpp index 02fef88041cd..8ffa63811469 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.cpp @@ -12,25 +12,20 @@ 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()) diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.h b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.h index 64eaa12b863a..394e8c446a29 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.h +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.h @@ -15,6 +15,7 @@ #include #include #include "AnimatedProps.h" +#include "AnimationMutation.h" namespace facebook::react { @@ -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); 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/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..a233a78d244f 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; @@ -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..1138238b6f7b 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; @@ -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..d633789807bb 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; @@ -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..8dd5edcda290 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; } @@ -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..1819dfe3b52c 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; } @@ -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..ab171200ceb8 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; } @@ -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..c878ab945208 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; } @@ -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..75c1654006e3 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; } @@ -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..6c3f59647833 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; } @@ -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);