diff --git a/Libraries/Animated/src/NativeAnimatedModule.js b/Libraries/Animated/src/NativeAnimatedModule.js index 51bb3e50d0c..35367d8a072 100644 --- a/Libraries/Animated/src/NativeAnimatedModule.js +++ b/Libraries/Animated/src/NativeAnimatedModule.js @@ -28,6 +28,9 @@ export type AnimatedNodeConfig = Object; export type AnimatingNodeConfig = Object; export interface Spec extends TurboModule { + +startOperationBatch: () => void; + +finishOperationBatch: () => void; + +createAnimatedNode: (tag: number, config: AnimatedNodeConfig) => void; +getValue: (tag: number, saveValueCallback: SaveValueCallback) => void; +startListeningToAnimatedNodeValue: (tag: number) => void; diff --git a/Libraries/Animated/src/NativeAnimatedTurboModule.js b/Libraries/Animated/src/NativeAnimatedTurboModule.js index d31a36fdebc..7224b209783 100644 --- a/Libraries/Animated/src/NativeAnimatedTurboModule.js +++ b/Libraries/Animated/src/NativeAnimatedTurboModule.js @@ -28,6 +28,9 @@ export type AnimatedNodeConfig = Object; export type AnimatingNodeConfig = Object; export interface Spec extends TurboModule { + +startOperationBatch: () => void; + +finishOperationBatch: () => void; + +createAnimatedNode: (tag: number, config: AnimatedNodeConfig) => void; +getValue: (tag: number, saveValueCallback: SaveValueCallback) => void; +startListeningToAnimatedNodeValue: (tag: number) => void; diff --git a/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec-generated.mm b/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec-generated.mm index a3a1068464e..1f7227f478f 100644 --- a/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec-generated.mm +++ b/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec-generated.mm @@ -229,6 +229,14 @@ namespace facebook { namespace react { + static facebook::jsi::Value __hostFunction_NativeAnimatedModuleSpecJSI_startOperationBatch(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule).invokeObjCMethod(rt, VoidKind, "startOperationBatch", @selector(startOperationBatch), args, count); + } + + static facebook::jsi::Value __hostFunction_NativeAnimatedModuleSpecJSI_finishOperationBatch(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule).invokeObjCMethod(rt, VoidKind, "finishOperationBatch", @selector(finishOperationBatch), args, count); + } + static facebook::jsi::Value __hostFunction_NativeAnimatedModuleSpecJSI_createAnimatedNode(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { return static_cast(turboModule).invokeObjCMethod(rt, VoidKind, "createAnimatedNode", @selector(createAnimatedNode:config:), args, count); } @@ -313,6 +321,12 @@ namespace facebook { NativeAnimatedModuleSpecJSI::NativeAnimatedModuleSpecJSI(const ObjCTurboModule::InitParams ¶ms) : ObjCTurboModule(params) { + methodMap_["startOperationBatch"] = MethodMetadata {0, __hostFunction_NativeAnimatedModuleSpecJSI_startOperationBatch}; + + + methodMap_["finishOperationBatch"] = MethodMetadata {0, __hostFunction_NativeAnimatedModuleSpecJSI_finishOperationBatch}; + + methodMap_["createAnimatedNode"] = MethodMetadata {2, __hostFunction_NativeAnimatedModuleSpecJSI_createAnimatedNode}; @@ -395,6 +409,14 @@ namespace facebook { namespace react { + static facebook::jsi::Value __hostFunction_NativeAnimatedTurboModuleSpecJSI_startOperationBatch(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule).invokeObjCMethod(rt, VoidKind, "startOperationBatch", @selector(startOperationBatch), args, count); + } + + static facebook::jsi::Value __hostFunction_NativeAnimatedTurboModuleSpecJSI_finishOperationBatch(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule).invokeObjCMethod(rt, VoidKind, "finishOperationBatch", @selector(finishOperationBatch), args, count); + } + static facebook::jsi::Value __hostFunction_NativeAnimatedTurboModuleSpecJSI_createAnimatedNode(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { return static_cast(turboModule).invokeObjCMethod(rt, VoidKind, "createAnimatedNode", @selector(createAnimatedNode:config:), args, count); } @@ -479,6 +501,12 @@ namespace facebook { NativeAnimatedTurboModuleSpecJSI::NativeAnimatedTurboModuleSpecJSI(const ObjCTurboModule::InitParams ¶ms) : ObjCTurboModule(params) { + methodMap_["startOperationBatch"] = MethodMetadata {0, __hostFunction_NativeAnimatedTurboModuleSpecJSI_startOperationBatch}; + + + methodMap_["finishOperationBatch"] = MethodMetadata {0, __hostFunction_NativeAnimatedTurboModuleSpecJSI_finishOperationBatch}; + + methodMap_["createAnimatedNode"] = MethodMetadata {2, __hostFunction_NativeAnimatedTurboModuleSpecJSI_createAnimatedNode}; diff --git a/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec.h b/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec.h index 6cb6d035cfd..ea6aaa0bcb7 100644 --- a/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec.h +++ b/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec.h @@ -269,6 +269,8 @@ namespace JS { @end @protocol NativeAnimatedModuleSpec +- (void)startOperationBatch; +- (void)finishOperationBatch; - (void)createAnimatedNode:(double)tag config:(NSDictionary *)config; - (void)getValue:(double)tag @@ -354,6 +356,8 @@ namespace JS { @end @protocol NativeAnimatedTurboModuleSpec +- (void)startOperationBatch; +- (void)finishOperationBatch; - (void)createAnimatedNode:(double)tag config:(NSDictionary *)config; - (void)getValue:(double)tag diff --git a/Libraries/NativeAnimation/RCTNativeAnimatedTurboModule.mm b/Libraries/NativeAnimation/RCTNativeAnimatedTurboModule.mm index 4d41beaeb1a..81adca98f30 100644 --- a/Libraries/NativeAnimation/RCTNativeAnimatedTurboModule.mm +++ b/Libraries/NativeAnimation/RCTNativeAnimatedTurboModule.mm @@ -63,6 +63,16 @@ RCT_EXPORT_MODULE(); #pragma mark -- API +RCT_EXPORT_METHOD(startOperationBatch) +{ + // TODO T71377585 +} + +RCT_EXPORT_METHOD(finishOperationBatch) +{ + // TODO T71377585 +} + RCT_EXPORT_METHOD(createAnimatedNode:(double)tag config:(NSDictionary *)config) { diff --git a/ReactAndroid/src/main/java/com/facebook/fbreact/specs/NativeAnimatedModuleSpec.java b/ReactAndroid/src/main/java/com/facebook/fbreact/specs/NativeAnimatedModuleSpec.java index 0bfc02aaaf2..109fe5e29cd 100644 --- a/ReactAndroid/src/main/java/com/facebook/fbreact/specs/NativeAnimatedModuleSpec.java +++ b/ReactAndroid/src/main/java/com/facebook/fbreact/specs/NativeAnimatedModuleSpec.java @@ -56,6 +56,9 @@ public abstract class NativeAnimatedModuleSpec extends ReactContextBaseJavaModul @ReactMethod public abstract void setAnimatedNodeValue(double nodeTag, double value); + @ReactMethod + public abstract void startOperationBatch(); + @ReactMethod public abstract void setAnimatedNodeOffset(double nodeTag, double offset); @@ -79,6 +82,9 @@ public abstract class NativeAnimatedModuleSpec extends ReactContextBaseJavaModul @ReactMethod public abstract void createAnimatedNode(double tag, ReadableMap config); + @ReactMethod + public abstract void finishOperationBatch(); + @ReactMethod public abstract void startListeningToAnimatedNodeValue(double tag); diff --git a/ReactAndroid/src/main/java/com/facebook/fbreact/specs/NativeAnimatedTurboModuleSpec.java b/ReactAndroid/src/main/java/com/facebook/fbreact/specs/NativeAnimatedTurboModuleSpec.java index a875589d80c..27c614e9a3d 100644 --- a/ReactAndroid/src/main/java/com/facebook/fbreact/specs/NativeAnimatedTurboModuleSpec.java +++ b/ReactAndroid/src/main/java/com/facebook/fbreact/specs/NativeAnimatedTurboModuleSpec.java @@ -56,6 +56,9 @@ public abstract class NativeAnimatedTurboModuleSpec extends ReactContextBaseJava @ReactMethod public abstract void setAnimatedNodeValue(double nodeTag, double value); + @ReactMethod + public abstract void startOperationBatch(); + @ReactMethod public abstract void setAnimatedNodeOffset(double nodeTag, double offset); @@ -79,6 +82,9 @@ public abstract class NativeAnimatedTurboModuleSpec extends ReactContextBaseJava @ReactMethod public abstract void createAnimatedNode(double tag, ReadableMap config); + @ReactMethod + public abstract void finishOperationBatch(); + @ReactMethod public abstract void startListeningToAnimatedNodeValue(double tag); diff --git a/ReactAndroid/src/main/java/com/facebook/fbreact/specs/jni/FBReactNativeSpec-generated.cpp b/ReactAndroid/src/main/java/com/facebook/fbreact/specs/jni/FBReactNativeSpec-generated.cpp index e61f1dee002..85c52d7e795 100644 --- a/ReactAndroid/src/main/java/com/facebook/fbreact/specs/jni/FBReactNativeSpec-generated.cpp +++ b/ReactAndroid/src/main/java/com/facebook/fbreact/specs/jni/FBReactNativeSpec-generated.cpp @@ -182,6 +182,14 @@ namespace facebook { namespace react { + static facebook::jsi::Value __hostFunction_NativeAnimatedModuleSpecJSI_startOperationBatch(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "startOperationBatch", "()V", args, count); + } + + static facebook::jsi::Value __hostFunction_NativeAnimatedModuleSpecJSI_finishOperationBatch(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "finishOperationBatch", "()V", args, count); + } + static facebook::jsi::Value __hostFunction_NativeAnimatedModuleSpecJSI_createAnimatedNode(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "createAnimatedNode", "(DLcom/facebook/react/bridge/ReadableMap;)V", args, count); } @@ -266,6 +274,12 @@ namespace facebook { NativeAnimatedModuleSpecJSI::NativeAnimatedModuleSpecJSI(const JavaTurboModule::InitParams ¶ms) : JavaTurboModule(params) { + methodMap_["startOperationBatch"] = MethodMetadata {0, __hostFunction_NativeAnimatedModuleSpecJSI_startOperationBatch}; + + + methodMap_["finishOperationBatch"] = MethodMetadata {0, __hostFunction_NativeAnimatedModuleSpecJSI_finishOperationBatch}; + + methodMap_["createAnimatedNode"] = MethodMetadata {2, __hostFunction_NativeAnimatedModuleSpecJSI_createAnimatedNode}; @@ -335,6 +349,14 @@ namespace facebook { namespace react { + static facebook::jsi::Value __hostFunction_NativeAnimatedTurboModuleSpecJSI_startOperationBatch(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "startOperationBatch", "()V", args, count); + } + + static facebook::jsi::Value __hostFunction_NativeAnimatedTurboModuleSpecJSI_finishOperationBatch(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "finishOperationBatch", "()V", args, count); + } + static facebook::jsi::Value __hostFunction_NativeAnimatedTurboModuleSpecJSI_createAnimatedNode(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "createAnimatedNode", "(DLcom/facebook/react/bridge/ReadableMap;)V", args, count); } @@ -419,6 +441,12 @@ namespace facebook { NativeAnimatedTurboModuleSpecJSI::NativeAnimatedTurboModuleSpecJSI(const JavaTurboModule::InitParams ¶ms) : JavaTurboModule(params) { + methodMap_["startOperationBatch"] = MethodMetadata {0, __hostFunction_NativeAnimatedTurboModuleSpecJSI_startOperationBatch}; + + + methodMap_["finishOperationBatch"] = MethodMetadata {0, __hostFunction_NativeAnimatedTurboModuleSpecJSI_finishOperationBatch}; + + methodMap_["createAnimatedNode"] = MethodMetadata {2, __hostFunction_NativeAnimatedTurboModuleSpecJSI_createAnimatedNode}; diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java index b30edeeab20..8a05e0627ed 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java @@ -89,14 +89,14 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec private abstract class UIThreadOperation { abstract void execute(NativeAnimatedNodesManager animatedNodesManager); - long mFrameNumber = -1; + long mBatchNumber = -1; - public void setFrameNumber(long frameNumber) { - mFrameNumber = frameNumber; + public void setBatchNumber(long batchNumber) { + mBatchNumber = batchNumber; } - public long getFrameNumber() { - return mFrameNumber; + public long getBatchNumber() { + return mBatchNumber; } } @@ -113,8 +113,10 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec private @Nullable NativeAnimatedNodesManager mNodesManager; - private volatile long mFrameNumber = 0; - private long mDispatchedFrameNumber = 0; + private boolean mBatchingControlledByJS = false; // TODO T71377544: delete + private volatile long mCurrentFrameNumber; // TODO T71377544: delete + private volatile long mCurrentBatchNumber; + private boolean mInitializedForFabric = false; private boolean mInitializedForNonFabric = false; private @UIManagerType int mUIManagerType = UIManagerType.DEFAULT; @@ -169,23 +171,24 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec } private void addOperation(UIThreadOperation operation) { - operation.setFrameNumber(mFrameNumber); + operation.setBatchNumber(mCurrentBatchNumber); + mOperations.add(operation); + } + + private void addUnbatchedOperation(UIThreadOperation operation) { + operation.setBatchNumber(-1); mOperations.add(operation); } private void addPreOperation(UIThreadOperation operation) { - operation.setFrameNumber(mFrameNumber); + operation.setBatchNumber(mCurrentBatchNumber); mPreOperations.add(operation); } // For FabricUIManager only @Override public void didScheduleMountItems(UIManager uiManager) { - if (mUIManagerType != UIManagerType.FABRIC) { - return; - } - - mFrameNumber++; + mCurrentFrameNumber++; } // For FabricUIManager only @@ -196,37 +199,31 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec return; } - // The problem we're trying to solve here: we could be in the middle of queueing - // a batch of related animation operations when Fabric flushes a batch of MountItems. - // It's visually bad if we execute half of the animation ops and then wait another frame - // (or more) to execute the rest. - // See mFrameNumber. If the dispatchedFrameNumber drifts too far - that - // is, if no MountItems are scheduled for a while, which can happen if a tree - // is committed but there are no changes - bring these counts back in sync and - // execute any queued operations. This number is arbitrary, but we want it low - // enough that the user shouldn't be able to see this delay in most cases. - mDispatchedFrameNumber++; - long currentFrameNo = mFrameNumber - 1; - if ((mDispatchedFrameNumber - mFrameNumber) > 2) { - mFrameNumber = mDispatchedFrameNumber; - currentFrameNo = mFrameNumber; + long batchNumber = mCurrentBatchNumber - 1; + + // TODO T71377544: delete this when the JS method is confirmed safe + if (!mBatchingControlledByJS) { + // The problem we're trying to solve here: we could be in the middle of queueing + // a batch of related animation operations when Fabric flushes a batch of MountItems. + // It's visually bad if we execute half of the animation ops and then wait another frame + // (or more) to execute the rest. + // See mFrameNumber. If the dispatchedFrameNumber drifts too far - that + // is, if no MountItems are scheduled for a while, which can happen if a tree + // is committed but there are no changes - bring these counts back in sync and + // execute any queued operations. This number is arbitrary, but we want it low + // enough that the user shouldn't be able to see this delay in most cases. + mCurrentFrameNumber++; + if ((mCurrentFrameNumber - mCurrentBatchNumber) > 2) { + mCurrentBatchNumber = mCurrentFrameNumber; + batchNumber = mCurrentBatchNumber; + } } - // This will execute all operations and preOperations queued - // since the last time this was run, and will race with anything - // being queued from the JS thread. That is, if the JS thread - // is still queuing operations, we might execute some of them - // at the very end until we exhaust the queue faster than the - // JS thread can queue up new items. - // The reason we increment in scheduleMountItems and subtract 1 here - // is that `scheduleMountItems` happens as close to the JS commit as - // possible, whereas execution of those same items might happen sometime - // later on the UI thread while the JS thread keeps plugging along. - executeAllOperations(mPreOperations, currentFrameNo); - executeAllOperations(mOperations, currentFrameNo); + executeAllOperations(mPreOperations, batchNumber); + executeAllOperations(mOperations, batchNumber); } - private void executeAllOperations(Queue operationQueue, long maxFrameNumber) { + private void executeAllOperations(Queue operationQueue, long maxBatchNumber) { NativeAnimatedNodesManager nodesManager = getNodesManager(); while (true) { // There is a race condition where `peek` may return a non-null value and isEmpty() is false, @@ -241,7 +238,7 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec return; } // The rest of the operations are for the next frame. - if (peekedOperation.getFrameNumber() > maxFrameNumber) { + if (peekedOperation.getBatchNumber() > maxBatchNumber) { return; } @@ -270,7 +267,7 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec return; } - final long frameNo = mFrameNumber++; + final long frameNo = mCurrentBatchNumber++; UIBlock preOperationsUIBlock = new UIBlock() { @@ -417,6 +414,18 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec } } + @Override + public void startOperationBatch() { + mBatchingControlledByJS = true; + mCurrentBatchNumber++; + } + + @Override + public void finishOperationBatch() { + mBatchingControlledByJS = true; + mCurrentBatchNumber++; + } + @Override public void createAnimatedNode(final double tagDouble, final ReadableMap config) { final int tag = (int) tagDouble; @@ -604,7 +613,7 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec FLog.d(NAME, "queue startAnimatingNode: ID: " + animationId + " tag: " + animatedNodeTag); } - addOperation( + addUnbatchedOperation( new UIThreadOperation() { @Override public void execute(NativeAnimatedNodesManager animatedNodesManager) {