mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
NativeAnimatedDriver: synchronize animation lifecycle closer to Fabric or Paper lifecycle
Summary: Switch between "Fabric" and "Non-Fabric" modes based on which types of native Views are being attached to animations. Don't allow non-Fabric to drive Fabric animations and vice-versa. Changelog: [Internal] Reviewed By: mdvacca Differential Revision: D21985411 fbshipit-source-id: fb9bef1e38375b384430b4e0275e7b6d62eda7a4
This commit is contained in:
committed by
Facebook GitHub Bot
parent
3346ac7f96
commit
472cf3f4ad
@@ -42,12 +42,12 @@ import com.facebook.react.bridge.ReadableMap;
|
||||
}
|
||||
if (value == 0) {
|
||||
throw new JSApplicationCausedNativeException(
|
||||
"Detected a division by zero in " + "Animated.divide node");
|
||||
"Detected a division by zero in Animated.divide node with Animated ID " + mTag);
|
||||
}
|
||||
mValue /= value;
|
||||
} else {
|
||||
throw new JSApplicationCausedNativeException(
|
||||
"Illegal node ID set as an input for " + "Animated.divide node");
|
||||
"Illegal node ID set as an input for Animated.divide node with Animated ID " + mTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ package com.facebook.react.animated;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.fbreact.specs.NativeAnimatedModuleSpec;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
@@ -27,10 +28,13 @@ import com.facebook.react.modules.core.ReactChoreographer;
|
||||
import com.facebook.react.uimanager.GuardedFrameCallback;
|
||||
import com.facebook.react.uimanager.NativeViewHierarchyManager;
|
||||
import com.facebook.react.uimanager.UIBlock;
|
||||
import com.facebook.react.uimanager.UIManagerHelper;
|
||||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import com.facebook.react.uimanager.common.UIManagerType;
|
||||
import com.facebook.react.uimanager.common.ViewUtil;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
/**
|
||||
* Module that exposes interface for creating and managing animated nodes on the "native" side.
|
||||
@@ -81,6 +85,7 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
|
||||
implements LifecycleEventListener, UIManagerListener {
|
||||
|
||||
public static final String NAME = "NativeAnimatedModule";
|
||||
public static final boolean ANIMATED_MODULE_DEBUG = false;
|
||||
|
||||
private interface UIThreadOperation {
|
||||
void execute(NativeAnimatedNodesManager animatedNodesManager);
|
||||
@@ -88,14 +93,21 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
|
||||
|
||||
@NonNull private final GuardedFrameCallback mAnimatedFrameCallback;
|
||||
private final ReactChoreographer mReactChoreographer;
|
||||
@NonNull private List<UIThreadOperation> mOperations = new ArrayList<>();
|
||||
@NonNull private List<UIThreadOperation> mPreOperations = new ArrayList<>();
|
||||
|
||||
@NonNull private List<UIBlock> mPreOperationsUIBlock = new ArrayList<>();
|
||||
@NonNull private List<UIBlock> mOperationsUIBlock = new ArrayList<>();
|
||||
@NonNull
|
||||
private ConcurrentLinkedQueue<UIThreadOperation> mOperations = new ConcurrentLinkedQueue<>();
|
||||
|
||||
@NonNull
|
||||
private ConcurrentLinkedQueue<UIThreadOperation> mPreOperations = new ConcurrentLinkedQueue<>();
|
||||
|
||||
private @Nullable NativeAnimatedNodesManager mNodesManager;
|
||||
|
||||
private volatile boolean mFabricBatchCompleted = false;
|
||||
private boolean mInitializedForFabric = false;
|
||||
private @UIManagerType int mUIManagerType = UIManagerType.DEFAULT;
|
||||
private int mNumFabricAnimations = 0;
|
||||
private int mNumNonFabricAnimations = 0;
|
||||
|
||||
public NativeAnimatedModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
|
||||
@@ -147,68 +159,69 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
|
||||
enqueueFrameCallback();
|
||||
}
|
||||
|
||||
// For FabricUIManager
|
||||
// For FabricUIManager only
|
||||
@Override
|
||||
public void didScheduleMountItems(UIManager uiManager) {
|
||||
if (mUIManagerType != UIManagerType.FABRIC) {
|
||||
return;
|
||||
}
|
||||
|
||||
mFabricBatchCompleted = true;
|
||||
}
|
||||
|
||||
// For FabricUIManager only
|
||||
@Override
|
||||
@UiThread
|
||||
public void willDispatchPreMountItems() {
|
||||
if (mPreOperationsUIBlock.size() != 0) {
|
||||
List<UIBlock> preOperations = mPreOperationsUIBlock;
|
||||
mPreOperationsUIBlock = new ArrayList<>();
|
||||
public void didDispatchMountItems(UIManager uiManager) {
|
||||
if (mUIManagerType != UIManagerType.FABRIC) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (UIBlock op : preOperations) {
|
||||
op.execute(null);
|
||||
}
|
||||
if (mFabricBatchCompleted) {
|
||||
// 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.
|
||||
executeAllOperations(mPreOperations);
|
||||
executeAllOperations(mOperations);
|
||||
mFabricBatchCompleted = false;
|
||||
}
|
||||
}
|
||||
|
||||
// For FabricUIManager
|
||||
@Override
|
||||
@UiThread
|
||||
public void willDispatchMountItems() {
|
||||
if (mOperationsUIBlock.size() != 0) {
|
||||
List<UIBlock> operations = mOperationsUIBlock;
|
||||
mOperationsUIBlock = new ArrayList<>();
|
||||
|
||||
for (UIBlock op : operations) {
|
||||
op.execute(null);
|
||||
}
|
||||
private void executeAllOperations(Queue<UIThreadOperation> operationQueue) {
|
||||
NativeAnimatedNodesManager nodesManager = getNodesManager();
|
||||
while (!operationQueue.isEmpty()) {
|
||||
operationQueue.poll().execute(nodesManager);
|
||||
}
|
||||
}
|
||||
|
||||
// For non-FabricUIManager
|
||||
// For non-FabricUIManager only
|
||||
@Override
|
||||
@UiThread
|
||||
public void willDispatchViewUpdates(final UIManager uiManager) {
|
||||
if (mOperations.isEmpty() && mPreOperations.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (mUIManagerType == UIManagerType.FABRIC) {
|
||||
return;
|
||||
}
|
||||
|
||||
final AtomicBoolean hasRunPreOperations = new AtomicBoolean(false);
|
||||
final AtomicBoolean hasRunOperations = new AtomicBoolean(false);
|
||||
final List<UIThreadOperation> preOperations = mPreOperations;
|
||||
final List<UIThreadOperation> operations = mOperations;
|
||||
mPreOperations = new ArrayList<>();
|
||||
mOperations = new ArrayList<>();
|
||||
|
||||
// This is kind of a hack. Basically UIManagerListener cannot import UIManagerModule
|
||||
// (that would cause an import cycle) and they're not in the same package. But,
|
||||
// UIManagerModule is the only thing that calls `willDispatchViewUpdates` so we
|
||||
// know this is safe.
|
||||
// This goes away entirely in Fabric/Venice.
|
||||
UIManagerModule uiManagerModule = (UIManagerModule) uiManager;
|
||||
final Queue<UIThreadOperation> preOperations = new LinkedList<>();
|
||||
final Queue<UIThreadOperation> operations = new LinkedList<>();
|
||||
while (!mPreOperations.isEmpty()) {
|
||||
preOperations.add(mPreOperations.poll());
|
||||
}
|
||||
while (!mOperations.isEmpty()) {
|
||||
operations.add(mOperations.poll());
|
||||
}
|
||||
|
||||
UIBlock preOperationsUIBlock =
|
||||
new UIBlock() {
|
||||
@Override
|
||||
public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
|
||||
if (!hasRunPreOperations.compareAndSet(false, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
NativeAnimatedNodesManager nodesManager = getNodesManager();
|
||||
for (UIThreadOperation operation : preOperations) {
|
||||
operation.execute(nodesManager);
|
||||
}
|
||||
executeAllOperations(preOperations);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -216,23 +229,13 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
|
||||
new UIBlock() {
|
||||
@Override
|
||||
public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
|
||||
if (!hasRunOperations.compareAndSet(false, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
NativeAnimatedNodesManager nodesManager = getNodesManager();
|
||||
for (UIThreadOperation operation : operations) {
|
||||
operation.execute(nodesManager);
|
||||
}
|
||||
executeAllOperations(operations);
|
||||
}
|
||||
};
|
||||
|
||||
// Queue up operations for Fabric
|
||||
mPreOperationsUIBlock.add(preOperationsUIBlock);
|
||||
mOperationsUIBlock.add(operationsUIBlock);
|
||||
|
||||
// Here we queue up the UI Blocks for the old, non-Fabric UIManager.
|
||||
// We queue them in both systems, let them race, and see which wins.
|
||||
assert (uiManager instanceof UIManagerModule);
|
||||
UIManagerModule uiManagerModule = (UIManagerModule) uiManager;
|
||||
uiManagerModule.prependUIBlock(preOperationsUIBlock);
|
||||
uiManagerModule.addUIBlock(operationsUIBlock);
|
||||
}
|
||||
@@ -265,10 +268,7 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
|
||||
ReactApplicationContext reactApplicationContext = getReactApplicationContextIfActiveOrWarn();
|
||||
|
||||
if (reactApplicationContext != null) {
|
||||
UIManagerModule uiManager =
|
||||
Assertions.assertNotNull(
|
||||
reactApplicationContext.getNativeModule(UIManagerModule.class));
|
||||
mNodesManager = new NativeAnimatedNodesManager(uiManager);
|
||||
mNodesManager = new NativeAnimatedNodesManager(reactApplicationContext);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,11 +295,23 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
|
||||
@Override
|
||||
public void createAnimatedNode(final double tagDouble, final ReadableMap config) {
|
||||
final int tag = (int) tagDouble;
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(
|
||||
NAME, "queue createAnimatedNode: " + tag + " config: " + config.toHashMap().toString());
|
||||
}
|
||||
|
||||
mOperations.add(
|
||||
new UIThreadOperation() {
|
||||
@Override
|
||||
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(
|
||||
NAME,
|
||||
"execute createAnimatedNode: "
|
||||
+ tag
|
||||
+ " config: "
|
||||
+ config.toHashMap().toString());
|
||||
}
|
||||
animatedNodesManager.createAnimatedNode(tag, config);
|
||||
}
|
||||
});
|
||||
@@ -308,6 +320,9 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
|
||||
@Override
|
||||
public void startListeningToAnimatedNodeValue(final double tagDouble) {
|
||||
final int tag = (int) tagDouble;
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(NAME, "queue startListeningToAnimatedNodeValue: " + tag);
|
||||
}
|
||||
|
||||
final AnimatedNodeValueListener listener =
|
||||
new AnimatedNodeValueListener() {
|
||||
@@ -330,6 +345,9 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
|
||||
new UIThreadOperation() {
|
||||
@Override
|
||||
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(NAME, "execute startListeningToAnimatedNodeValue: " + tag);
|
||||
}
|
||||
animatedNodesManager.startListeningToAnimatedNodeValue(tag, listener);
|
||||
}
|
||||
});
|
||||
@@ -338,11 +356,17 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
|
||||
@Override
|
||||
public void stopListeningToAnimatedNodeValue(final double tagDouble) {
|
||||
final int tag = (int) tagDouble;
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(NAME, "queue stopListeningToAnimatedNodeValue: " + tag);
|
||||
}
|
||||
|
||||
mOperations.add(
|
||||
new UIThreadOperation() {
|
||||
@Override
|
||||
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(NAME, "execute stopListeningToAnimatedNodeValue: " + tag);
|
||||
}
|
||||
animatedNodesManager.stopListeningToAnimatedNodeValue(tag);
|
||||
}
|
||||
});
|
||||
@@ -351,11 +375,17 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
|
||||
@Override
|
||||
public void dropAnimatedNode(final double tagDouble) {
|
||||
final int tag = (int) tagDouble;
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(NAME, "queue dropAnimatedNode: " + tag);
|
||||
}
|
||||
|
||||
mOperations.add(
|
||||
new UIThreadOperation() {
|
||||
@Override
|
||||
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(NAME, "execute dropAnimatedNode: " + tag);
|
||||
}
|
||||
animatedNodesManager.dropAnimatedNode(tag);
|
||||
}
|
||||
});
|
||||
@@ -364,11 +394,17 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
|
||||
@Override
|
||||
public void setAnimatedNodeValue(final double tagDouble, final double value) {
|
||||
final int tag = (int) tagDouble;
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(NAME, "queue setAnimatedNodeValue: " + tag + " value: " + value);
|
||||
}
|
||||
|
||||
mOperations.add(
|
||||
new UIThreadOperation() {
|
||||
@Override
|
||||
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(NAME, "execute setAnimatedNodeValue: " + tag + " value: " + value);
|
||||
}
|
||||
animatedNodesManager.setAnimatedNodeValue(tag, value);
|
||||
}
|
||||
});
|
||||
@@ -377,11 +413,17 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
|
||||
@Override
|
||||
public void setAnimatedNodeOffset(final double tagDouble, final double value) {
|
||||
final int tag = (int) tagDouble;
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(NAME, "queue setAnimatedNodeOffset: " + tag + " offset: " + value);
|
||||
}
|
||||
|
||||
mOperations.add(
|
||||
new UIThreadOperation() {
|
||||
@Override
|
||||
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(NAME, "execute setAnimatedNodeOffset: " + tag + " offset: " + value);
|
||||
}
|
||||
animatedNodesManager.setAnimatedNodeOffset(tag, value);
|
||||
}
|
||||
});
|
||||
@@ -390,11 +432,17 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
|
||||
@Override
|
||||
public void flattenAnimatedNodeOffset(final double tagDouble) {
|
||||
final int tag = (int) tagDouble;
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(NAME, "queue flattenAnimatedNodeOffset: " + tag);
|
||||
}
|
||||
|
||||
mOperations.add(
|
||||
new UIThreadOperation() {
|
||||
@Override
|
||||
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(NAME, "execute flattenAnimatedNodeOffset: " + tag);
|
||||
}
|
||||
animatedNodesManager.flattenAnimatedNodeOffset(tag);
|
||||
}
|
||||
});
|
||||
@@ -403,11 +451,17 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
|
||||
@Override
|
||||
public void extractAnimatedNodeOffset(final double tagDouble) {
|
||||
final int tag = (int) tagDouble;
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(NAME, "queue extractAnimatedNodeOffset: " + tag);
|
||||
}
|
||||
|
||||
mOperations.add(
|
||||
new UIThreadOperation() {
|
||||
@Override
|
||||
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(NAME, "execute extractAnimatedNodeOffset: " + tag);
|
||||
}
|
||||
animatedNodesManager.extractAnimatedNodeOffset(tag);
|
||||
}
|
||||
});
|
||||
@@ -421,11 +475,19 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
|
||||
final Callback endCallback) {
|
||||
final int animationId = (int) animationIdDouble;
|
||||
final int animatedNodeTag = (int) animatedNodeTagDouble;
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(NAME, "queue startAnimatingNode: ID: " + animationId + " tag: " + animatedNodeTag);
|
||||
}
|
||||
|
||||
mOperations.add(
|
||||
new UIThreadOperation() {
|
||||
@Override
|
||||
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(
|
||||
NAME,
|
||||
"execute startAnimatingNode: ID: " + animationId + " tag: " + animatedNodeTag);
|
||||
}
|
||||
animatedNodesManager.startAnimatingNode(
|
||||
animationId, animatedNodeTag, animationConfig, endCallback);
|
||||
}
|
||||
@@ -435,11 +497,17 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
|
||||
@Override
|
||||
public void stopAnimation(final double animationIdDouble) {
|
||||
final int animationId = (int) animationIdDouble;
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(NAME, "queue stopAnimation: ID: " + animationId);
|
||||
}
|
||||
|
||||
mOperations.add(
|
||||
new UIThreadOperation() {
|
||||
@Override
|
||||
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(NAME, "execute stopAnimation: ID: " + animationId);
|
||||
}
|
||||
animatedNodesManager.stopAnimation(animationId);
|
||||
}
|
||||
});
|
||||
@@ -450,11 +518,23 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
|
||||
final double parentNodeTagDouble, final double childNodeTagDouble) {
|
||||
final int parentNodeTag = (int) parentNodeTagDouble;
|
||||
final int childNodeTag = (int) childNodeTagDouble;
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(
|
||||
NAME, "queue connectAnimatedNodes: parent: " + parentNodeTag + " child: " + childNodeTag);
|
||||
}
|
||||
|
||||
mOperations.add(
|
||||
new UIThreadOperation() {
|
||||
@Override
|
||||
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(
|
||||
NAME,
|
||||
"execute connectAnimatedNodes: parent: "
|
||||
+ parentNodeTag
|
||||
+ " child: "
|
||||
+ childNodeTag);
|
||||
}
|
||||
animatedNodesManager.connectAnimatedNodes(parentNodeTag, childNodeTag);
|
||||
}
|
||||
});
|
||||
@@ -465,11 +545,24 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
|
||||
final double parentNodeTagDouble, final double childNodeTagDouble) {
|
||||
final int parentNodeTag = (int) parentNodeTagDouble;
|
||||
final int childNodeTag = (int) childNodeTagDouble;
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(
|
||||
NAME,
|
||||
"queue disconnectAnimatedNodes: parent: " + parentNodeTag + " child: " + childNodeTag);
|
||||
}
|
||||
|
||||
mOperations.add(
|
||||
new UIThreadOperation() {
|
||||
@Override
|
||||
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(
|
||||
NAME,
|
||||
"execute disconnectAnimatedNodes: parent: "
|
||||
+ parentNodeTag
|
||||
+ " child: "
|
||||
+ childNodeTag);
|
||||
}
|
||||
animatedNodesManager.disconnectAnimatedNodes(parentNodeTag, childNodeTag);
|
||||
}
|
||||
});
|
||||
@@ -480,11 +573,27 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
|
||||
final double animatedNodeTagDouble, final double viewTagDouble) {
|
||||
final int animatedNodeTag = (int) animatedNodeTagDouble;
|
||||
final int viewTag = (int) viewTagDouble;
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(
|
||||
NAME,
|
||||
"queue connectAnimatedNodeToView: animatedNodeTag: "
|
||||
+ animatedNodeTag
|
||||
+ " viewTag: "
|
||||
+ viewTag);
|
||||
}
|
||||
|
||||
mOperations.add(
|
||||
new UIThreadOperation() {
|
||||
@Override
|
||||
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(
|
||||
NAME,
|
||||
"execute connectAnimatedNodeToView: animatedNodeTag: "
|
||||
+ animatedNodeTag
|
||||
+ " viewTag: "
|
||||
+ viewTag);
|
||||
}
|
||||
animatedNodesManager.connectAnimatedNodeToView(animatedNodeTag, viewTag);
|
||||
}
|
||||
});
|
||||
@@ -495,11 +604,27 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
|
||||
final double animatedNodeTagDouble, final double viewTagDouble) {
|
||||
final int animatedNodeTag = (int) animatedNodeTagDouble;
|
||||
final int viewTag = (int) viewTagDouble;
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(
|
||||
NAME,
|
||||
"queue connectAnimatedNodeToView: disconnectAnimatedNodeFromView: "
|
||||
+ animatedNodeTag
|
||||
+ " viewTag: "
|
||||
+ viewTag);
|
||||
}
|
||||
|
||||
mOperations.add(
|
||||
new UIThreadOperation() {
|
||||
@Override
|
||||
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(
|
||||
NAME,
|
||||
"execute connectAnimatedNodeToView: disconnectAnimatedNodeFromView: "
|
||||
+ animatedNodeTag
|
||||
+ " viewTag: "
|
||||
+ viewTag);
|
||||
}
|
||||
animatedNodesManager.disconnectAnimatedNodeFromView(animatedNodeTag, viewTag);
|
||||
}
|
||||
});
|
||||
@@ -508,11 +633,21 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
|
||||
@Override
|
||||
public void restoreDefaultValues(final double animatedNodeTagDouble) {
|
||||
final int animatedNodeTag = (int) animatedNodeTagDouble;
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(
|
||||
NAME, "queue restoreDefaultValues: disconnectAnimatedNodeFromView: " + animatedNodeTag);
|
||||
}
|
||||
|
||||
mPreOperations.add(
|
||||
new UIThreadOperation() {
|
||||
@Override
|
||||
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(
|
||||
NAME,
|
||||
"execute restoreDefaultValues: disconnectAnimatedNodeFromView: "
|
||||
+ animatedNodeTag);
|
||||
}
|
||||
animatedNodesManager.restoreDefaultValues(animatedNodeTag);
|
||||
}
|
||||
});
|
||||
@@ -522,11 +657,52 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
|
||||
public void addAnimatedEventToView(
|
||||
final double viewTagDouble, final String eventName, final ReadableMap eventMapping) {
|
||||
final int viewTag = (int) viewTagDouble;
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(
|
||||
NAME,
|
||||
"queue addAnimatedEventToView: "
|
||||
+ viewTag
|
||||
+ " eventName: "
|
||||
+ eventName
|
||||
+ " eventMapping: "
|
||||
+ eventMapping.toHashMap().toString());
|
||||
}
|
||||
|
||||
mUIManagerType = ViewUtil.getUIManagerType(viewTag);
|
||||
if (mUIManagerType == UIManagerType.FABRIC) {
|
||||
mNumFabricAnimations++;
|
||||
} else {
|
||||
mNumNonFabricAnimations++;
|
||||
}
|
||||
|
||||
// Subscribe to FabricUIManager lifecycle events if we haven't yet
|
||||
if (!mInitializedForFabric && mUIManagerType == UIManagerType.FABRIC) {
|
||||
ReactApplicationContext reactApplicationContext = getReactApplicationContext();
|
||||
if (reactApplicationContext != null) {
|
||||
@Nullable
|
||||
UIManager uiManager =
|
||||
UIManagerHelper.getUIManager(reactApplicationContext, UIManagerType.FABRIC);
|
||||
if (uiManager != null) {
|
||||
uiManager.addUIManagerEventListener(this);
|
||||
mInitializedForFabric = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mOperations.add(
|
||||
new UIThreadOperation() {
|
||||
@Override
|
||||
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(
|
||||
NAME,
|
||||
"execute addAnimatedEventToView: "
|
||||
+ viewTag
|
||||
+ " eventName: "
|
||||
+ eventName
|
||||
+ " eventMapping: "
|
||||
+ eventMapping.toHashMap().toString());
|
||||
}
|
||||
animatedNodesManager.addAnimatedEventToView(viewTag, eventName, eventMapping);
|
||||
}
|
||||
});
|
||||
@@ -537,11 +713,53 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
|
||||
final double viewTagDouble, final String eventName, final double animatedValueTagDouble) {
|
||||
final int viewTag = (int) viewTagDouble;
|
||||
final int animatedValueTag = (int) animatedValueTagDouble;
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(
|
||||
NAME,
|
||||
"queue addAnimatedEventToView: removeAnimatedEventFromView: "
|
||||
+ viewTag
|
||||
+ " eventName: "
|
||||
+ eventName
|
||||
+ " animatedValueTag: "
|
||||
+ animatedValueTag);
|
||||
}
|
||||
|
||||
@UIManagerType int animationManagerType = ViewUtil.getUIManagerType(viewTag);
|
||||
if (animationManagerType == UIManagerType.FABRIC) {
|
||||
mNumFabricAnimations--;
|
||||
} else {
|
||||
mNumNonFabricAnimations--;
|
||||
}
|
||||
|
||||
// Should we switch to a different animation mode?
|
||||
// This can be useful when navigating between Fabric and non-Fabric screens:
|
||||
// If there are ongoing Fabric animations from a previous screen,
|
||||
// and we tear down the current non-Fabric screen, we should expect
|
||||
// the animation mode to switch back - and vice-versa.
|
||||
if (mNumNonFabricAnimations == 0
|
||||
&& mNumFabricAnimations > 0
|
||||
&& mUIManagerType != UIManagerType.FABRIC) {
|
||||
mUIManagerType = UIManagerType.FABRIC;
|
||||
} else if (mNumFabricAnimations == 0
|
||||
&& mNumNonFabricAnimations > 0
|
||||
&& mUIManagerType != UIManagerType.DEFAULT) {
|
||||
mUIManagerType = UIManagerType.DEFAULT;
|
||||
}
|
||||
|
||||
mOperations.add(
|
||||
new UIThreadOperation() {
|
||||
@Override
|
||||
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
||||
if (ANIMATED_MODULE_DEBUG) {
|
||||
FLog.d(
|
||||
NAME,
|
||||
"execute addAnimatedEventToView: removeAnimatedEventFromView: "
|
||||
+ viewTag
|
||||
+ " eventName: "
|
||||
+ eventName
|
||||
+ " animatedValueTag: "
|
||||
+ animatedValueTag);
|
||||
}
|
||||
animatedNodesManager.removeAnimatedEventFromView(viewTag, eventName, animatedValueTag);
|
||||
}
|
||||
});
|
||||
|
||||
+80
-29
@@ -10,16 +10,19 @@ package com.facebook.react.animated;
|
||||
import android.util.SparseArray;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.bridge.JSApplicationCausedNativeException;
|
||||
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.UIManager;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.uimanager.IllegalViewOperationException;
|
||||
import com.facebook.react.uimanager.UIManagerHelper;
|
||||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
import com.facebook.react.uimanager.events.Event;
|
||||
import com.facebook.react.uimanager.events.EventDispatcher;
|
||||
@@ -48,6 +51,9 @@ import java.util.Queue;
|
||||
*/
|
||||
/*package*/ class NativeAnimatedNodesManager implements EventDispatcherListener {
|
||||
|
||||
private static final String TAG = "NativeAnimatedNodesManager";
|
||||
private static final int MAX_INCONSISTENT_FRAMES = 64;
|
||||
|
||||
private final SparseArray<AnimatedNode> mAnimatedNodes = new SparseArray<>();
|
||||
private final SparseArray<AnimationDriver> mActiveAnimations = new SparseArray<>();
|
||||
private final SparseArray<AnimatedNode> mUpdatedNodes = new SparseArray<>();
|
||||
@@ -55,14 +61,19 @@ import java.util.Queue;
|
||||
// there will be only one driver per mapping so all code code should be optimized around that.
|
||||
private final Map<String, List<EventAnimationDriver>> mEventDrivers = new HashMap<>();
|
||||
private final UIManagerModule.CustomEventNamesResolver mCustomEventNamesResolver;
|
||||
private final UIManager mUIManager;
|
||||
private final ReactApplicationContext mReactApplicationContext;
|
||||
private int mAnimatedGraphBFSColor = 0;
|
||||
private int mNumInconsistentFrames = 0;
|
||||
// Used to avoid allocating a new array on every frame in `runUpdates` and `onEventDispatch`.
|
||||
private final List<AnimatedNode> mRunUpdateNodeList = new LinkedList<>();
|
||||
|
||||
public NativeAnimatedNodesManager(UIManagerModule uiManager) {
|
||||
mUIManager = uiManager;
|
||||
mUIManager.<EventDispatcher>getEventDispatcher().addListener(this);
|
||||
public NativeAnimatedNodesManager(ReactApplicationContext reactApplicationContext) {
|
||||
mReactApplicationContext = reactApplicationContext;
|
||||
|
||||
UIManagerModule uiManager =
|
||||
Assertions.assertNotNull(reactApplicationContext.getNativeModule(UIManagerModule.class));
|
||||
|
||||
uiManager.<EventDispatcher>getEventDispatcher().addListener(this);
|
||||
// TODO T64216139 Remove dependency of UIManagerModule when the Constants are not in Native
|
||||
// anymore
|
||||
mCustomEventNamesResolver = uiManager.getDirectEventNamesResolver();
|
||||
@@ -89,7 +100,7 @@ import java.util.Queue;
|
||||
} else if ("value".equals(type)) {
|
||||
node = new ValueAnimatedNode(config);
|
||||
} else if ("props".equals(type)) {
|
||||
node = new PropsAnimatedNode(config, this, mUIManager);
|
||||
node = new PropsAnimatedNode(config, this);
|
||||
} else if ("interpolation".equals(type)) {
|
||||
node = new InterpolationAnimatedNode(config);
|
||||
} else if ("addition".equals(type)) {
|
||||
@@ -301,8 +312,21 @@ import java.util.Queue;
|
||||
+ "of type "
|
||||
+ PropsAnimatedNode.class.getName());
|
||||
}
|
||||
if (mReactApplicationContext == null) {
|
||||
throw new IllegalStateException(
|
||||
"Animated node could not be connected, no ReactApplicationContext: " + viewTag);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
UIManager uiManager =
|
||||
UIManagerHelper.getUIManagerForReactTag(mReactApplicationContext, viewTag);
|
||||
if (uiManager == null) {
|
||||
throw new IllegalStateException(
|
||||
"Animated node could not be connected to UIManager: " + viewTag);
|
||||
}
|
||||
|
||||
PropsAnimatedNode propsAnimatedNode = (PropsAnimatedNode) node;
|
||||
propsAnimatedNode.connectToView(viewTag);
|
||||
propsAnimatedNode.connectToView(viewTag, uiManager);
|
||||
mUpdatedNodes.put(animatedNodeTag, node);
|
||||
}
|
||||
|
||||
@@ -543,26 +567,43 @@ import java.util.Queue;
|
||||
}
|
||||
|
||||
// Run main "update" loop
|
||||
boolean errorsCaught = false;
|
||||
while (!nodesQueue.isEmpty()) {
|
||||
AnimatedNode nextNode = nodesQueue.poll();
|
||||
nextNode.update();
|
||||
if (nextNode instanceof PropsAnimatedNode) {
|
||||
// Send property updates to native view manager
|
||||
try {
|
||||
try {
|
||||
nextNode.update();
|
||||
if (nextNode instanceof PropsAnimatedNode) {
|
||||
// Send property updates to native view manager
|
||||
((PropsAnimatedNode) nextNode).updateView();
|
||||
} catch (IllegalViewOperationException e) {
|
||||
// An exception is thrown if the view hasn't been created yet. This can happen because
|
||||
// views are
|
||||
// created in batches. If this particular view didn't make it into a batch yet, the view
|
||||
// won't
|
||||
// exist and an exception will be thrown when attempting to start an animation on it.
|
||||
//
|
||||
// Eat the exception rather than crashing. The impact is that we may drop one or more
|
||||
// frames of the
|
||||
// animation.
|
||||
}
|
||||
} catch (IllegalViewOperationException e) {
|
||||
// An exception is thrown if the view hasn't been created yet. This can happen because
|
||||
// views are
|
||||
// created in batches. If this particular view didn't make it into a batch yet, the view
|
||||
// won't
|
||||
// exist and an exception will be thrown when attempting to start an animation on it.
|
||||
//
|
||||
// Eat the exception rather than crashing. The impact is that we may drop one or more
|
||||
// frames of the
|
||||
// animation.
|
||||
FLog.e(TAG, "Native animation workaround, frame lost as result of race condition", e);
|
||||
} catch (JSApplicationCausedNativeException e) {
|
||||
// In Fabric there can be race conditions between the JS thread setting up or tearing down
|
||||
// animated nodes, and Fabric executing them on the UI thread, leading to temporary
|
||||
// inconsistent
|
||||
// states. We require that the inconsistency last for N frames before throwing these
|
||||
// exceptions.
|
||||
if (!errorsCaught) {
|
||||
errorsCaught = true;
|
||||
mNumInconsistentFrames++;
|
||||
}
|
||||
if (mNumInconsistentFrames > MAX_INCONSISTENT_FRAMES) {
|
||||
throw new IllegalStateException(e);
|
||||
} else {
|
||||
FLog.e(
|
||||
ReactConstants.TAG,
|
||||
"Native animation workaround, frame lost as result of race condition",
|
||||
TAG,
|
||||
"Swallowing exception due to potential race between JS and UI threads: inconsistent frame counter: "
|
||||
+ mNumInconsistentFrames,
|
||||
e);
|
||||
}
|
||||
}
|
||||
@@ -586,13 +627,23 @@ import java.util.Queue;
|
||||
// Verify that we've visited *all* active nodes. Throw otherwise as this would mean there is a
|
||||
// cycle in animated node graph. We also take advantage of the fact that all active nodes are
|
||||
// visited in the step above so that all the nodes properties `mActiveIncomingNodes` are set to
|
||||
// zero
|
||||
// zero.
|
||||
// In Fabric there can be race conditions between the JS thread setting up or tearing down
|
||||
// animated nodes, and Fabric executing them on the UI thread, leading to temporary inconsistent
|
||||
// states. We require that the inconsistency last for 64 frames before throwing this exception.
|
||||
if (activeNodesCount != updatedNodesCount) {
|
||||
throw new IllegalStateException(
|
||||
"Looks like animated nodes graph has cycles, there are "
|
||||
+ activeNodesCount
|
||||
+ " but toposort visited only "
|
||||
+ updatedNodesCount);
|
||||
if (!errorsCaught) {
|
||||
mNumInconsistentFrames++;
|
||||
}
|
||||
if (mNumInconsistentFrames > MAX_INCONSISTENT_FRAMES) {
|
||||
throw new IllegalStateException(
|
||||
"Looks like animated nodes graph has cycles, there are "
|
||||
+ activeNodesCount
|
||||
+ " but toposort visited only "
|
||||
+ updatedNodesCount);
|
||||
}
|
||||
} else if (!errorsCaught) {
|
||||
mNumInconsistentFrames = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,14 +25,11 @@ import java.util.Map;
|
||||
|
||||
private int mConnectedViewTag = -1;
|
||||
private final NativeAnimatedNodesManager mNativeAnimatedNodesManager;
|
||||
private final UIManager mUIManager;
|
||||
private final Map<String, Integer> mPropNodeMapping;
|
||||
private final JavaOnlyMap mPropMap;
|
||||
@Nullable private UIManager mUIManager;
|
||||
|
||||
PropsAnimatedNode(
|
||||
ReadableMap config,
|
||||
NativeAnimatedNodesManager nativeAnimatedNodesManager,
|
||||
UIManager uiManager) {
|
||||
PropsAnimatedNode(ReadableMap config, NativeAnimatedNodesManager nativeAnimatedNodesManager) {
|
||||
ReadableMap props = config.getMap("props");
|
||||
ReadableMapKeySetIterator iter = props.keySetIterator();
|
||||
mPropNodeMapping = new HashMap<>();
|
||||
@@ -43,15 +40,15 @@ import java.util.Map;
|
||||
}
|
||||
mPropMap = new JavaOnlyMap();
|
||||
mNativeAnimatedNodesManager = nativeAnimatedNodesManager;
|
||||
mUIManager = uiManager;
|
||||
}
|
||||
|
||||
public void connectToView(int viewTag) {
|
||||
public void connectToView(int viewTag, UIManager uiManager) {
|
||||
if (mConnectedViewTag != -1) {
|
||||
throw new JSApplicationIllegalArgumentException(
|
||||
"Animated node " + mTag + " is " + "already attached to a view");
|
||||
}
|
||||
mConnectedViewTag = viewTag;
|
||||
mUIManager = uiManager;
|
||||
}
|
||||
|
||||
public void disconnectFromView(int viewTag) {
|
||||
@@ -65,6 +62,11 @@ import java.util.Map;
|
||||
}
|
||||
|
||||
public void restoreDefaultValues() {
|
||||
// Cannot restore default values if this view has already been disconnected.
|
||||
if (mConnectedViewTag == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
ReadableMapKeySetIterator it = mPropMap.keySetIterator();
|
||||
while (it.hasNextKey()) {
|
||||
mPropMap.putNull(it.nextKey());
|
||||
|
||||
@@ -14,8 +14,8 @@ public interface UIManagerListener {
|
||||
* module needs to add UIBlocks to the queue before it is flushed.
|
||||
*/
|
||||
void willDispatchViewUpdates(UIManager uiManager);
|
||||
/** Called on the UI thread right before normal mount items are executed. */
|
||||
void willDispatchMountItems();
|
||||
/** Called on the UI thread right before premount items are executed. */
|
||||
void willDispatchPreMountItems();
|
||||
/* Called right after view updates are dispatched for a frame. */
|
||||
void didDispatchMountItems(UIManager uiManager);
|
||||
/* Called right after scheduleMountItems is called in Fabric, after a new tree is committed. */
|
||||
void didScheduleMountItems(UIManager uiManager);
|
||||
}
|
||||
|
||||
@@ -512,7 +512,11 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
|
||||
ReactMarker.logFabricMarker(
|
||||
ReactMarkerConstants.FABRIC_UPDATE_UI_MAIN_THREAD_START, null, commitNumber);
|
||||
if (ENABLE_FABRIC_LOGS) {
|
||||
FLog.d(TAG, "SynchronouslyUpdateViewOnUIThread for tag %d", reactTag);
|
||||
FLog.d(
|
||||
TAG,
|
||||
"SynchronouslyUpdateViewOnUIThread for tag %d: %s",
|
||||
reactTag,
|
||||
(IS_DEVELOPMENT_ENVIRONMENT ? props.toHashMap().toString() : "<hidden>"));
|
||||
}
|
||||
|
||||
updatePropsMountItem(reactTag, props).execute(mMountingManager);
|
||||
@@ -617,10 +621,6 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
|
||||
return;
|
||||
}
|
||||
|
||||
for (UIManagerListener listener : mListeners) {
|
||||
listener.willDispatchMountItems();
|
||||
}
|
||||
|
||||
final boolean didDispatchItems;
|
||||
try {
|
||||
didDispatchItems = dispatchMountItems();
|
||||
@@ -632,6 +632,10 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
|
||||
mInDispatch = false;
|
||||
}
|
||||
|
||||
for (UIManagerListener listener : mListeners) {
|
||||
listener.didDispatchMountItems(this);
|
||||
}
|
||||
|
||||
// Decide if we want to try reentering
|
||||
if (mReDispatchCounter < 10 && didDispatchItems) {
|
||||
// Executing twice in a row is normal. Only log after that point.
|
||||
@@ -880,10 +884,6 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
|
||||
// reentering during dispatchPreMountItems
|
||||
mInDispatch = true;
|
||||
|
||||
for (UIManagerListener listener : mListeners) {
|
||||
listener.willDispatchPreMountItems();
|
||||
}
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
long timeLeftInFrame = FRAME_TIME_MS - ((System.nanoTime() - frameTimeNanos) / 1000000);
|
||||
|
||||
Reference in New Issue
Block a user