Skip to content

Commit 9822880

Browse files
Bartlomiej Bloniarzmeta-codesync[bot]
authored andcommitted
Add batched animated prop update delegate chain
Summary: Wire the delegate chain for batched animated property updates through UIManager, Scheduler, and platform-specific SurfaceManagers. iOS and CxxPlatform get no-op stubs. Gated behind the optimizedAnimatedPropUpdates feature flag. Differential Revision: D101157453
1 parent e5a2ae0 commit 9822880

15 files changed

Lines changed: 563 additions & 0 deletions

File tree

packages/react-native/React/Fabric/RCTScheduler.mm

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,13 @@ void schedulerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag,
7979
[scheduler.delegate schedulerDidSynchronouslyUpdateViewOnUIThread:tag props:props];
8080
}
8181

82+
void schedulerShouldSynchronouslyUpdateAnimatedViewsOnUIThread(
83+
const std::vector<int> &intBuffer,
84+
const std::vector<double> &doubleBuffer) override
85+
{
86+
// No-op on iOS. Batched animated prop updates are Android-only.
87+
}
88+
8289
void schedulerDidUpdateShadowTree(const std::unordered_map<Tag, folly::dynamic> &tagToProps) override
8390
{
8491
// Does nothing.

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
import com.facebook.proguard.annotations.DoNotStripAny;
3636
import com.facebook.react.bridge.ColorPropConverter;
3737
import com.facebook.react.bridge.GuardedRunnable;
38+
import com.facebook.react.bridge.JavaOnlyArray;
39+
import com.facebook.react.bridge.JavaOnlyMap;
3840
import com.facebook.react.bridge.LifecycleEventListener;
3941
import com.facebook.react.bridge.NativeArray;
4042
import com.facebook.react.bridge.NativeMap;
@@ -811,6 +813,251 @@ public void synchronouslyUpdateViewOnUIThread(final int reactTag, final Readable
811813
ReactMarkerConstants.FABRIC_UPDATE_UI_MAIN_THREAD_END, null, commitNumber);
812814
}
813815

816+
@SuppressWarnings("unused")
817+
@UiThread
818+
@ThreadConfined(UI)
819+
public void synchronouslyUpdateViewBatch(final int[] intBuffer, final double[] doubleBuffer) {
820+
UiThreadUtil.assertOnUiThread();
821+
822+
// Decode buffer protocol and apply props per-view.
823+
// For the skeleton implementation, we decode into ReadableMap per-view
824+
// and delegate to the existing synchronouslyUpdateViewOnUIThread path.
825+
int intIdx = 0;
826+
int doubleIdx = 0;
827+
while (intIdx < intBuffer.length) {
828+
int command = intBuffer[intIdx++];
829+
if (command != 1) { // CMD_START_OF_VIEW
830+
break;
831+
}
832+
int viewTag = intBuffer[intIdx++];
833+
JavaOnlyMap props = new JavaOnlyMap();
834+
835+
while (intIdx < intBuffer.length) {
836+
command = intBuffer[intIdx++];
837+
if (command == 4) { // CMD_END_OF_VIEW
838+
break;
839+
}
840+
841+
switch (command) {
842+
case 10: // CMD_OPACITY
843+
case 11: // CMD_ELEVATION
844+
case 12: // CMD_Z_INDEX
845+
case 13: // CMD_SHADOW_OPACITY
846+
case 14: // CMD_SHADOW_RADIUS
847+
props.putDouble(commandToString(command), doubleBuffer[doubleIdx++]);
848+
break;
849+
850+
case 15: // CMD_BACKGROUND_COLOR
851+
case 16: // CMD_COLOR
852+
case 17: // CMD_TINT_COLOR
853+
case 40: // CMD_BORDER_COLOR
854+
case 41: // CMD_BORDER_TOP_COLOR
855+
case 42: // CMD_BORDER_BOTTOM_COLOR
856+
case 43: // CMD_BORDER_LEFT_COLOR
857+
case 44: // CMD_BORDER_RIGHT_COLOR
858+
case 45: // CMD_BORDER_START_COLOR
859+
case 46: // CMD_BORDER_END_COLOR
860+
props.putInt(commandToString(command), intBuffer[intIdx++]);
861+
break;
862+
863+
case 20:
864+
case 21:
865+
case 22:
866+
case 23:
867+
case 24:
868+
case 25:
869+
case 26:
870+
case 27:
871+
case 28:
872+
case 29:
873+
case 30:
874+
case 31:
875+
case 32:
876+
{
877+
// Border radius: value in doubleBuffer, unit in intBuffer
878+
double value = doubleBuffer[doubleIdx++];
879+
int unit = intBuffer[intIdx++];
880+
if (unit == 202) { // CMD_UNIT_PX
881+
props.putDouble(commandToString(command), value);
882+
} else if (unit == 203) { // CMD_UNIT_PERCENT
883+
props.putString(commandToString(command), value + "%");
884+
}
885+
break;
886+
}
887+
888+
case 2:
889+
{ // CMD_START_OF_TRANSFORM
890+
JavaOnlyArray transform = new JavaOnlyArray();
891+
while (intIdx < intBuffer.length) {
892+
int transformCmd = intBuffer[intIdx++];
893+
if (transformCmd == 3) { // CMD_END_OF_TRANSFORM
894+
props.putArray("transform", transform);
895+
break;
896+
}
897+
String name = transformCommandToString(transformCmd);
898+
switch (transformCmd) {
899+
case 102:
900+
case 103:
901+
case 104:
902+
case 112:
903+
{
904+
// scale, scaleX, scaleY, perspective
905+
double val = doubleBuffer[doubleIdx++];
906+
JavaOnlyMap entry = new JavaOnlyMap();
907+
entry.putDouble(name, val);
908+
transform.pushMap(entry);
909+
break;
910+
}
911+
case 100:
912+
case 101:
913+
{
914+
// translateX, translateY
915+
double val = doubleBuffer[doubleIdx++];
916+
int unitCmd = intBuffer[intIdx++];
917+
JavaOnlyMap entry = new JavaOnlyMap();
918+
if (unitCmd == 202) { // PX
919+
entry.putDouble(name, val);
920+
} else { // PERCENT
921+
entry.putString(name, val + "%");
922+
}
923+
transform.pushMap(entry);
924+
break;
925+
}
926+
case 105:
927+
case 106:
928+
case 107:
929+
case 108:
930+
case 109:
931+
case 110:
932+
{
933+
// rotate, rotateX/Y/Z, skewX/Y
934+
double angle = doubleBuffer[doubleIdx++];
935+
int unitCmd = intBuffer[intIdx++];
936+
String unitStr = unitCmd == 200 ? "deg" : "rad";
937+
JavaOnlyMap entry = new JavaOnlyMap();
938+
entry.putString(name, angle + unitStr);
939+
transform.pushMap(entry);
940+
break;
941+
}
942+
case 111:
943+
{
944+
// matrix
945+
int size = intBuffer[intIdx++];
946+
JavaOnlyArray matrix = new JavaOnlyArray();
947+
for (int m = 0; m < size; m++) {
948+
matrix.pushDouble(doubleBuffer[doubleIdx++]);
949+
}
950+
JavaOnlyMap entry = new JavaOnlyMap();
951+
entry.putArray(name, matrix);
952+
transform.pushMap(entry);
953+
break;
954+
}
955+
}
956+
}
957+
break;
958+
}
959+
}
960+
}
961+
synchronouslyUpdateViewOnUIThread(viewTag, props);
962+
}
963+
}
964+
965+
private static String commandToString(int command) {
966+
switch (command) {
967+
case 10:
968+
return "opacity";
969+
case 11:
970+
return "elevation";
971+
case 12:
972+
return "zIndex";
973+
case 13:
974+
return "shadowOpacity";
975+
case 14:
976+
return "shadowRadius";
977+
case 15:
978+
return "backgroundColor";
979+
case 16:
980+
return "color";
981+
case 17:
982+
return "tintColor";
983+
case 20:
984+
return "borderRadius";
985+
case 21:
986+
return "borderTopLeftRadius";
987+
case 22:
988+
return "borderTopRightRadius";
989+
case 23:
990+
return "borderTopStartRadius";
991+
case 24:
992+
return "borderTopEndRadius";
993+
case 25:
994+
return "borderBottomLeftRadius";
995+
case 26:
996+
return "borderBottomRightRadius";
997+
case 27:
998+
return "borderBottomStartRadius";
999+
case 28:
1000+
return "borderBottomEndRadius";
1001+
case 29:
1002+
return "borderStartStartRadius";
1003+
case 30:
1004+
return "borderStartEndRadius";
1005+
case 31:
1006+
return "borderEndStartRadius";
1007+
case 32:
1008+
return "borderEndEndRadius";
1009+
case 40:
1010+
return "borderColor";
1011+
case 41:
1012+
return "borderTopColor";
1013+
case 42:
1014+
return "borderBottomColor";
1015+
case 43:
1016+
return "borderLeftColor";
1017+
case 44:
1018+
return "borderRightColor";
1019+
case 45:
1020+
return "borderStartColor";
1021+
case 46:
1022+
return "borderEndColor";
1023+
default:
1024+
return "unknown";
1025+
}
1026+
}
1027+
1028+
private static String transformCommandToString(int command) {
1029+
switch (command) {
1030+
case 100:
1031+
return "translateX";
1032+
case 101:
1033+
return "translateY";
1034+
case 102:
1035+
return "scale";
1036+
case 103:
1037+
return "scaleX";
1038+
case 104:
1039+
return "scaleY";
1040+
case 105:
1041+
return "rotate";
1042+
case 106:
1043+
return "rotateX";
1044+
case 107:
1045+
return "rotateY";
1046+
case 108:
1047+
return "rotateZ";
1048+
case 109:
1049+
return "skewX";
1050+
case 110:
1051+
return "skewY";
1052+
case 111:
1053+
return "matrix";
1054+
case 112:
1055+
return "perspective";
1056+
default:
1057+
return "unknown";
1058+
}
1059+
}
1060+
8141061
@SuppressLint("NotInvokedPrivateMethod")
8151062
@SuppressWarnings("unused")
8161063
@AnyThread

packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1229,6 +1229,33 @@ void FabricMountingManager::synchronouslyUpdateViewOnUIThread(
12291229
synchronouslyUpdateViewOnUIThreadJNI(javaUIManager_, viewTag, propsMap);
12301230
}
12311231

1232+
void FabricMountingManager::synchronouslyUpdateViewBatchOnUIThread(
1233+
const std::vector<int>& intBuffer,
1234+
const std::vector<double>& doubleBuffer) {
1235+
auto env = jni::Environment::current();
1236+
1237+
auto jIntArray = env->NewIntArray(static_cast<jsize>(intBuffer.size()));
1238+
env->SetIntArrayRegion(
1239+
jIntArray, 0, static_cast<jsize>(intBuffer.size()), intBuffer.data());
1240+
1241+
auto jDoubleArray =
1242+
env->NewDoubleArray(static_cast<jsize>(doubleBuffer.size()));
1243+
env->SetDoubleArrayRegion(
1244+
jDoubleArray,
1245+
0,
1246+
static_cast<jsize>(doubleBuffer.size()),
1247+
doubleBuffer.data());
1248+
1249+
static auto synchronouslyUpdateViewBatchJNI =
1250+
JFabricUIManager::javaClassStatic()
1251+
->getMethod<void(jintArray, jdoubleArray)>(
1252+
"synchronouslyUpdateViewBatch");
1253+
synchronouslyUpdateViewBatchJNI(javaUIManager_, jIntArray, jDoubleArray);
1254+
1255+
env->DeleteLocalRef(jIntArray);
1256+
env->DeleteLocalRef(jDoubleArray);
1257+
}
1258+
12321259
void FabricMountingManager::scheduleReactRevisionMerge(SurfaceId surfaceId) {
12331260
static const auto scheduleReactRevisionMerge =
12341261
JFabricUIManager::javaClassStatic()->getMethod<void(int32_t)>(

packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ class FabricMountingManager final {
6868

6969
void synchronouslyUpdateViewOnUIThread(Tag viewTag, const folly::dynamic &props);
7070

71+
void synchronouslyUpdateViewBatchOnUIThread(
72+
const std::vector<int> &intBuffer,
73+
const std::vector<double> &doubleBuffer);
74+
7175
void scheduleReactRevisionMerge(SurfaceId surfaceId);
7276

7377
private:

packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,16 @@ void FabricUIManagerBinding::schedulerShouldSynchronouslyUpdateViewOnUIThread(
792792
}
793793
}
794794

795+
void FabricUIManagerBinding::
796+
schedulerShouldSynchronouslyUpdateAnimatedViewsOnUIThread(
797+
const std::vector<int>& intBuffer,
798+
const std::vector<double>& doubleBuffer) {
799+
if (ReactNativeFeatureFlags::cxxNativeAnimatedEnabled() && mountingManager_) {
800+
mountingManager_->synchronouslyUpdateViewBatchOnUIThread(
801+
intBuffer, doubleBuffer);
802+
}
803+
}
804+
795805
void FabricUIManagerBinding::schedulerDidUpdateShadowTree(
796806
const std::unordered_map<Tag, folly::dynamic>& /*tagToProps*/) {
797807
// no-op

packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,10 @@ class FabricUIManagerBinding : public jni::HybridClass<FabricUIManagerBinding>,
115115

116116
void schedulerShouldSynchronouslyUpdateViewOnUIThread(Tag tag, const folly::dynamic &props) override;
117117

118+
void schedulerShouldSynchronouslyUpdateAnimatedViewsOnUIThread(
119+
const std::vector<int> &intBuffer,
120+
const std::vector<double> &doubleBuffer) override;
121+
118122
void schedulerDidUpdateShadowTree(const std::unordered_map<Tag, folly::dynamic> &tagToProps) override;
119123

120124
void setPixelDensity(float pointScaleFactor);

0 commit comments

Comments
 (0)