Skip to content

Commit a967514

Browse files
committed
Remove non-layout style and prop updates path via synchronouslyUpdatePropsOnUIThread (#7014)
Currently, there are two ways to update native view props and styles in Reanimated. The default path (so-called slow path) is to apply all props changes to the ShadowTree via C++ API and let React Native mount the changes. However, if all props updated in given batch are non-layout props (i.e. those that don't require layout recalculation, like background color or opacity) we use a fast path that calls `synchronouslyUpdatePropsOnUIThread` from React Native and applies the changes directly to platform views, without making changes to ShadowTree in C++. Turns out, some features like view measurement or touch detection system use C++ ShadowTree which is not consistent with what's currently on the screen. Because of that, we're removing the fast path (turns out it's not that fast, especially on iOS) to restore the correctness of view measurement and touch detection for animated components. * Performance monitor example &rarr; Bokeh Example * Android emulator / iPhone 14 Pro real device * Debug mode * Animating `transform` prop using `useAnimatedStyle` | Platform | Before (main) | After (this PR) | |:-:|:-:|:-:| | Android (count=200) | 20 fps | 15 fps | | iOS (count=500) | 22 fps | 22 fps | <details> <summary>App.tsx</summary> ```tsx import React, { useState } from 'react'; import { Dimensions, StyleSheet, View } from 'react-native'; import Animated, { Easing, useAnimatedStyle, useReducedMotion, useSharedValue, withTiming, } from 'react-native-reanimated'; const dimensions = Dimensions.get('window'); function randBetween(min: number, max: number) { return min + Math.random() * (max - min); } function Circle() { const shouldReduceMotion = useReducedMotion(); const [power] = useState(randBetween(0, 1)); const [duration] = useState(randBetween(2000, 3000)); const size = 100 + power * 250; const width = size; const height = size; const hue = randBetween(100, 200); const backgroundColor = `hsl(${hue},100%,50%)`; const opacity = 0.1 + (1 - power) * 0.1; const config = { duration, easing: Easing.linear }; const left = useSharedValue(randBetween(0, dimensions.width) - size / 2); const top = useSharedValue(randBetween(0, dimensions.height) - size / 2); const update = () => { left.value = withTiming(left.value + randBetween(-100, 100), config); top.value = withTiming(top.value + randBetween(-100, 100), config); }; React.useEffect(() => { update(); if (shouldReduceMotion) { return; } const id = setInterval(update, duration); return () => clearInterval(id); }); const animatedStyle = useAnimatedStyle( () => ({ transform: [{ translateX: left.value }, { translateY: top.value }], }), [] ); return ( <Animated.View style={[ styles.circle, { width, height, backgroundColor, opacity }, animatedStyle, ]} /> ); } interface BokehProps { count: number; } function Bokeh({ count }: BokehProps) { return ( <> {[...Array(count)].map((_, i) => ( <Circle key={i} /> ))} </> ); } export default function App() { return ( <View style={styles.container}> <Bokeh count={200} /> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor: 'black', overflow: 'hidden', }, circle: { position: 'absolute', borderRadius: 999, }, }); ``` </details> * Fixes #6832. * Fixes #6676.
1 parent c583efd commit a967514

File tree

13 files changed

+5
-133
lines changed

13 files changed

+5
-133
lines changed

Diff for: packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp

-38
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,6 @@ ReanimatedModuleProxy::ReanimatedModuleProxy(
6464
layoutAnimationsManager_(
6565
std::make_shared<LayoutAnimationsManager>(jsLogger_)),
6666
#ifdef RCT_NEW_ARCH_ENABLED
67-
synchronouslyUpdateUIPropsFunction_(
68-
platformDepMethodsHolder.synchronouslyUpdateUIPropsFunction),
6967
propsRegistry_(std::make_shared<PropsRegistry>()),
7068
#else
7169
obtainPropFunction_(platformDepMethodsHolder.obtainPropFunction),
@@ -489,7 +487,6 @@ jsi::Value ReanimatedModuleProxy::configureProps(
489487
auto nativePropsArray = nativeProps.asObject(rt).asArray(rt);
490488
for (size_t i = 0; i < nativePropsArray.size(rt); ++i) {
491489
auto name = nativePropsArray.getValueAtIndex(rt, i).asString(rt).utf8(rt);
492-
nativePropNames_.insert(name);
493490
animatablePropNames_.insert(name);
494491
}
495492
#else
@@ -600,22 +597,6 @@ void ReanimatedModuleProxy::cleanupSensors() {
600597
}
601598

602599
#ifdef RCT_NEW_ARCH_ENABLED
603-
bool ReanimatedModuleProxy::isThereAnyLayoutProp(
604-
jsi::Runtime &rt,
605-
const jsi::Object &props) {
606-
const jsi::Array propNames = props.getPropertyNames(rt);
607-
for (size_t i = 0; i < propNames.size(rt); ++i) {
608-
const std::string propName =
609-
propNames.getValueAtIndex(rt, i).asString(rt).utf8(rt);
610-
bool isLayoutProp =
611-
nativePropNames_.find(propName) != nativePropNames_.end();
612-
if (isLayoutProp) {
613-
return true;
614-
}
615-
}
616-
return false;
617-
}
618-
619600
jsi::Value ReanimatedModuleProxy::filterNonAnimatableProps(
620601
jsi::Runtime &rt,
621602
const jsi::Value &props) {
@@ -760,25 +741,6 @@ void ReanimatedModuleProxy::performOperations() {
760741
jsPropsUpdater.call(rt, viewTag, nonAnimatableProps);
761742
}
762743

763-
bool hasLayoutUpdates = false;
764-
765-
for (const auto &[shadowNode, props] : copiedOperationsQueue) {
766-
if (isThereAnyLayoutProp(rt, props->asObject(rt))) {
767-
hasLayoutUpdates = true;
768-
break;
769-
}
770-
}
771-
772-
if (!hasLayoutUpdates) {
773-
// If there's no layout props to be updated, we can apply the updates
774-
// directly onto the components and skip the commit.
775-
for (const auto &[shadowNode, props] : copiedOperationsQueue) {
776-
Tag tag = shadowNode->getTag();
777-
synchronouslyUpdateUIPropsFunction_(rt, tag, props->asObject(rt));
778-
}
779-
return;
780-
}
781-
782744
if (propsRegistry_->shouldReanimatedSkipCommit()) {
783745
// It may happen that `performOperations` is called on the UI thread
784746
// while React Native tries to commit a new tree on the JS thread.

Diff for: packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.h

-4
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,6 @@ class ReanimatedModuleProxy
191191
void requestAnimationFrame(jsi::Runtime &rt, const jsi::Value &callback);
192192

193193
#ifdef RCT_NEW_ARCH_ENABLED
194-
bool isThereAnyLayoutProp(jsi::Runtime &rt, const jsi::Object &props);
195194
jsi::Value filterNonAnimatableProps(
196195
jsi::Runtime &rt,
197196
const jsi::Value &props);
@@ -213,9 +212,6 @@ class ReanimatedModuleProxy
213212
std::shared_ptr<LayoutAnimationsManager> layoutAnimationsManager_;
214213

215214
#ifdef RCT_NEW_ARCH_ENABLED
216-
const SynchronouslyUpdateUIPropsFunction synchronouslyUpdateUIPropsFunction_;
217-
218-
std::unordered_set<std::string> nativePropNames_; // filled by configureProps
219215
std::unordered_set<std::string>
220216
animatablePropNames_; // filled by configureProps
221217
std::shared_ptr<UIManager> uiManager_;

Diff for: packages/react-native-reanimated/Common/cpp/reanimated/Tools/PlatformDepMethodsHolder.h

+1-3
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ namespace reanimated {
2020

2121
#ifdef RCT_NEW_ARCH_ENABLED
2222

23-
using SynchronouslyUpdateUIPropsFunction =
24-
std::function<void(jsi::Runtime &rt, Tag tag, const jsi::Object &props)>;
2523
using UpdatePropsFunction =
2624
std::function<void(jsi::Runtime &rt, const jsi::Value &operations)>;
2725
using RemoveFromPropsRegistryFunction =
@@ -79,7 +77,7 @@ using MaybeFlushUIUpdatesQueueFunction = std::function<void()>;
7977
struct PlatformDepMethodsHolder {
8078
RequestRenderFunction requestRender;
8179
#ifdef RCT_NEW_ARCH_ENABLED
82-
SynchronouslyUpdateUIPropsFunction synchronouslyUpdateUIPropsFunction;
80+
// nothing
8381
#else
8482
UpdatePropsFunction updatePropsFunction;
8583
ScrollToFunction scrollToFunction;

Diff for: packages/react-native-reanimated/android/src/fabric/java/com/swmansion/reanimated/ReaCompatibility.java

-4
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,4 @@ public void unregisterFabricEventListener(NodesManager nodesManager) {
2626
fabricUIManager.getEventDispatcher().removeListener(nodesManager);
2727
}
2828
}
29-
30-
public void synchronouslyUpdateUIProps(int viewTag, ReadableMap uiProps) {
31-
fabricUIManager.synchronouslyUpdateViewOnUIThread(viewTag, uiProps);
32-
}
3329
}

Diff for: packages/react-native-reanimated/android/src/main/cpp/reanimated/android/NativeProxy.cpp

+2-23
Original file line numberDiff line numberDiff line change
@@ -318,26 +318,6 @@ std::vector<std::pair<std::string, double>> NativeProxy::measure(int viewTag) {
318318
}
319319
#endif // RCT_NEW_ARCH_ENABLED
320320

321-
#ifdef RCT_NEW_ARCH_ENABLED
322-
inline jni::local_ref<ReadableMap::javaobject> castReadableMap(
323-
jni::local_ref<ReadableNativeMap::javaobject> const &nativeMap) {
324-
return make_local(reinterpret_cast<ReadableMap::javaobject>(nativeMap.get()));
325-
}
326-
327-
void NativeProxy::synchronouslyUpdateUIProps(
328-
jsi::Runtime &rt,
329-
Tag tag,
330-
const jsi::Object &props) {
331-
static const auto method =
332-
getJniMethod<void(int, jni::local_ref<ReadableMap::javaobject>)>(
333-
"synchronouslyUpdateUIProps");
334-
jni::local_ref<ReadableMap::javaobject> uiProps =
335-
castReadableMap(ReadableNativeMap::newObjectCxxArgs(
336-
jsi::dynamicFromValue(rt, jsi::Value(rt, props))));
337-
method(javaPart_.get(), tag, uiProps);
338-
}
339-
#endif
340-
341321
int NativeProxy::registerSensor(
342322
int sensorType,
343323
int interval,
@@ -456,8 +436,7 @@ PlatformDepMethodsHolder NativeProxy::getPlatformDependentMethods() {
456436
auto requestRender = bindThis(&NativeProxy::requestRender);
457437

458438
#ifdef RCT_NEW_ARCH_ENABLED
459-
auto synchronouslyUpdateUIPropsFunction =
460-
bindThis(&NativeProxy::synchronouslyUpdateUIProps);
439+
// nothing
461440
#else
462441
auto configurePropsFunction = bindThis(&NativeProxy::configureProps);
463442
#endif
@@ -491,7 +470,7 @@ PlatformDepMethodsHolder NativeProxy::getPlatformDependentMethods() {
491470
return {
492471
requestRender,
493472
#ifdef RCT_NEW_ARCH_ENABLED
494-
synchronouslyUpdateUIPropsFunction,
473+
// nothing
495474
#else
496475
updatePropsFunction,
497476
scrollToFunction,

Diff for: packages/react-native-reanimated/android/src/main/cpp/reanimated/android/NativeProxy.h

-6
Original file line numberDiff line numberDiff line change
@@ -181,12 +181,6 @@ class NativeProxy : public jni::HybridClass<NativeProxy>,
181181
// std::shared_ptr<EventListener> eventListener_;
182182
#endif // RCT_NEW_ARCH_ENABLED
183183
void installJSIBindings();
184-
#ifdef RCT_NEW_ARCH_ENABLED
185-
void synchronouslyUpdateUIProps(
186-
jsi::Runtime &rt,
187-
Tag viewTag,
188-
const jsi::Object &props);
189-
#endif
190184
PlatformDepMethodsHolder getPlatformDependentMethods();
191185
void setupLayoutAnimations();
192186

Diff for: packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NodesManager.java

-4
Original file line numberDiff line numberDiff line change
@@ -440,10 +440,6 @@ public void updateProps(int viewTag, Map<String, Object> props) {
440440
}
441441
}
442442

443-
public void synchronouslyUpdateUIProps(int viewTag, ReadableMap uiProps) {
444-
compatibility.synchronouslyUpdateUIProps(viewTag, uiProps);
445-
}
446-
447443
public String obtainProp(int viewTag, String propName) {
448444
View view;
449445
try {

Diff for: packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/nativeProxy/NativeProxyCommon.java

-5
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,6 @@ public void updateProps(int viewTag, Map<String, Object> props) {
129129
mNodesManager.updateProps(viewTag, props);
130130
}
131131

132-
@DoNotStrip
133-
public void synchronouslyUpdateUIProps(int viewTag, ReadableMap uiProps) {
134-
mNodesManager.synchronouslyUpdateUIProps(viewTag, uiProps);
135-
}
136-
137132
@DoNotStrip
138133
public String obtainProp(int viewTag, String propName) {
139134
return mNodesManager.obtainProp(viewTag, propName);

Diff for: packages/react-native-reanimated/android/src/paper/java/com/swmansion/reanimated/ReaCompatibility.java

-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,4 @@ public ReaCompatibility(ReactApplicationContext reactApplicationContext) {}
99
public void registerFabricEventListener(NodesManager nodesManager) {}
1010

1111
public void unregisterFabricEventListener(NodesManager nodesManager) {}
12-
13-
public void synchronouslyUpdateUIProps(int viewTag, ReadableMap uiProps) {}
1412
}

Diff for: packages/react-native-reanimated/apple/reanimated/apple/REANodesManager.h

-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ typedef void (^REAPerformOperations)();
4343

4444
#ifdef RCT_NEW_ARCH_ENABLED
4545
- (void)registerPerformOperations:(REAPerformOperations)performOperations;
46-
- (void)synchronouslyUpdateViewOnUIThread:(nonnull NSNumber *)viewTag props:(nonnull NSDictionary *)uiProps;
4746
#else
4847
- (void)configureUiProps:(nonnull NSSet<NSString *> *)uiPropsSet
4948
andNativeProps:(nonnull NSSet<NSString *> *)nativePropsSet;

Diff for: packages/react-native-reanimated/apple/reanimated/apple/REANodesManager.mm

+1-20
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,6 @@ @implementation REANodesManager {
161161
#ifdef RCT_NEW_ARCH_ENABLED
162162
__weak RCTBridge *_bridge;
163163
REAPerformOperations _performOperations;
164-
__weak id<RCTSurfacePresenterStub> _surfacePresenter;
165164
NSMutableDictionary<NSNumber *, NSMutableDictionary *> *_operationsInBatch;
166165
#else
167166
NSMutableArray<REANativeAnimationOp> *_operationsInBatch;
@@ -202,7 +201,6 @@ - (nonnull instancetype)initWithModule:(REAModule *)reanimatedModule
202201
{
203202
if ((self = [super init])) {
204203
_bridge = bridge;
205-
_surfacePresenter = surfacePresenter;
206204
_reanimatedModule = reanimatedModule;
207205
_wantRunUpdates = NO;
208206
_onAnimationCallbacks = [NSMutableArray new];
@@ -416,24 +414,7 @@ - (BOOL)isNativeViewMounted:(NSNumber *)viewTag
416414
}
417415

418416
#ifdef RCT_NEW_ARCH_ENABLED
419-
420-
- (void)synchronouslyUpdateViewOnUIThread:(nonnull NSNumber *)viewTag props:(nonnull NSDictionary *)uiProps
421-
{
422-
// adapted from RCTPropsAnimatedNode.m
423-
RCTSurfacePresenter *surfacePresenter = _bridge.surfacePresenter ?: _surfacePresenter;
424-
RCTComponentViewRegistry *componentViewRegistry = surfacePresenter.mountingManager.componentViewRegistry;
425-
REAUIView<RCTComponentViewProtocol> *componentView =
426-
[componentViewRegistry findComponentViewWithTag:[viewTag integerValue]];
427-
428-
NSSet<NSString *> *propKeysManagedByAnimated = [componentView propKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN];
429-
[surfacePresenter synchronouslyUpdateViewOnUIThread:viewTag props:uiProps];
430-
[componentView setPropKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN:propKeysManagedByAnimated];
431-
432-
// `synchronouslyUpdateViewOnUIThread` does not flush props like `backgroundColor` etc.
433-
// so that's why we need to call `finalizeUpdates` here.
434-
[componentView finalizeUpdates:RNComponentViewUpdateMask{}];
435-
}
436-
417+
// nothing
437418
#else
438419

439420
- (void)updateProps:(nonnull NSDictionary *)props

Diff for: packages/react-native-reanimated/apple/reanimated/apple/native/PlatformDepMethodsHolderImpl.h

-3
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@ PlatformDepMethodsHolder makePlatformDepMethodsHolderBridgeless(
2323
REAModule *reaModule);
2424
SetGestureStateFunction makeSetGestureStateFunctionBridgeless(
2525
RCTModuleRegistry *moduleRegistry);
26-
27-
SynchronouslyUpdateUIPropsFunction makeSynchronouslyUpdateUIPropsFunction(
28-
REANodesManager *nodesManager);
2926
#else // RCT_NEW_ARCH_ENABLED
3027
UpdatePropsFunction makeUpdatePropsFunction(REAModule *reaModule);
3128
MeasureFunction makeMeasureFunction(RCTUIManager *uiManager);

Diff for: packages/react-native-reanimated/apple/reanimated/apple/native/PlatformDepMethodsHolderImpl.mm

+1-20
Original file line numberDiff line numberDiff line change
@@ -101,18 +101,6 @@ RequestRenderFunction makeRequestRender(REANodesManager *nodesManager)
101101
return requestRender;
102102
}
103103

104-
#ifdef RCT_NEW_ARCH_ENABLED
105-
SynchronouslyUpdateUIPropsFunction makeSynchronouslyUpdateUIPropsFunction(REANodesManager *nodesManager)
106-
{
107-
auto synchronouslyUpdateUIPropsFunction = [nodesManager](jsi::Runtime &rt, Tag tag, const jsi::Object &props) {
108-
NSNumber *viewTag = @(tag);
109-
NSDictionary *uiProps = convertJSIObjectToNSDictionary(rt, props);
110-
[nodesManager synchronouslyUpdateViewOnUIThread:viewTag props:uiProps];
111-
};
112-
return synchronouslyUpdateUIPropsFunction;
113-
}
114-
#endif // RCT_NEW_ARCH_ENABLED
115-
116104
#ifdef RCT_NEW_ARCH_ENABLED
117105
// nothing
118106
#else // RCT_NEW_ARCH_ENABLED
@@ -290,10 +278,6 @@ KeyboardEventUnsubscribeFunction makeUnsubscribeFromKeyboardEventsFunction(REAKe
290278
{
291279
auto requestRender = makeRequestRender(nodesManager);
292280

293-
#ifdef RCT_NEW_ARCH_ENABLED
294-
auto synchronouslyUpdateUIPropsFunction = makeSynchronouslyUpdateUIPropsFunction(nodesManager);
295-
#endif // RCT_NEW_ARCH_ENABLED
296-
297281
#ifdef RCT_NEW_ARCH_ENABLED
298282
// nothing
299283
#else
@@ -354,7 +338,7 @@ KeyboardEventUnsubscribeFunction makeUnsubscribeFromKeyboardEventsFunction(REAKe
354338
PlatformDepMethodsHolder platformDepMethodsHolder = {
355339
requestRender,
356340
#ifdef RCT_NEW_ARCH_ENABLED
357-
synchronouslyUpdateUIPropsFunction,
341+
// nothing
358342
#else
359343
updatePropsFunction,
360344
scrollToFunction,
@@ -384,8 +368,6 @@ PlatformDepMethodsHolder makePlatformDepMethodsHolderBridgeless(
384368
{
385369
auto requestRender = makeRequestRender(nodesManager);
386370

387-
auto synchronouslyUpdateUIPropsFunction = makeSynchronouslyUpdateUIPropsFunction(nodesManager);
388-
389371
auto getAnimationTimestamp = makeGetAnimationTimestamp();
390372

391373
auto progressLayoutAnimation = makeProgressLayoutAnimation(reaModule);
@@ -410,7 +392,6 @@ PlatformDepMethodsHolder makePlatformDepMethodsHolderBridgeless(
410392

411393
PlatformDepMethodsHolder platformDepMethodsHolder = {
412394
requestRender,
413-
synchronouslyUpdateUIPropsFunction,
414395
getAnimationTimestamp,
415396
progressLayoutAnimation,
416397
endLayoutAnimation,

0 commit comments

Comments
 (0)