Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,22 @@ import type AnimatedValue from '../nodes/AnimatedValue';
import type AnimatedValueXY from '../nodes/AnimatedValueXY';
import type {AnimationConfig, EndCallback} from './Animation';

import NativeAnimatedModule from '../NativeAnimatedModule';
import NativeAnimatedTurboModule from '../NativeAnimatedTurboModule';
import AnimatedColor from '../nodes/AnimatedColor';
import Animation from './Animation';

const Platform = require('../Utilities/Platform').default;

const singleFrameInterval =
Platform.OS === 'ios'
? NativeAnimatedTurboModule?.getConstants().singleFrameInterval
: NativeAnimatedModule?.getConstants().singleFrameInterval;
Comment on lines 25 to 28

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cortinico @cipolleschi Can you help me understand why are there two turbo modules for animated? I found one is working only for iOS and one only for Android.. Not sure which one I should use

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use NativeAnimatedHelper for this.


const frameDuration = singleFrameInterval
? singleFrameInterval * 1000
: 1000.0 / 60.0;

export type TimingAnimationConfig = $ReadOnly<{
...AnimationConfig,
toValue:
Expand Down Expand Up @@ -88,7 +101,6 @@ export default class TimingAnimation extends Animation {
platformConfig: ?PlatformConfig,
...
}> {
const frameDuration = 1000.0 / 60.0;
const frames = [];
const numFrames = Math.round(this._duration / frameDuration);
for (let frame = 0; frame < numFrames; frame++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,30 @@
#import <Foundation/Foundation.h>

#import <React/RCTBridgeModule.h>

static CGFloat RCTSingleFrameInterval = (CGFloat)(1.0 / 60.0);
#import <React/RCTUtils.h>

#ifdef __cplusplus
#include <react/featureflags/ReactNativeFeatureFlags.h>

static CGFloat RCTSingleFrameInterval(void)
{
if (facebook::react::ReactNativeFeatureFlags::disableHighRefreshRateAnimations()) {
// Fallback to 60 fps if disabled.
return 1.0 / 60;
}

static CGFloat maximumFramesPerSecond;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
RCTUnsafeExecuteOnMainQueueSync(^{
maximumFramesPerSecond = [UIScreen mainScreen].maximumFramesPerSecond;
Comment thread
okwasniewski marked this conversation as resolved.
Outdated
});
Comment thread
okwasniewski marked this conversation as resolved.
Outdated
});

return 1.0 / maximumFramesPerSecond;
};

#endif

@class RCTValueAnimatedNode;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ - (void)stepAnimationWithTime:(NSTimeInterval)currentTime

if (_frameStartTime == -1) {
// Since this is the first animation step, consider the start to be on the previous frame.
_frameStartTime = currentTime - RCTSingleFrameInterval;
_frameStartTime = currentTime - RCTSingleFrameInterval();
if (_fromValue == _lastValue) {
// First iteration, assign _fromValue based on _valueNode.
_fromValue = _valueNode.value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,12 @@ - (void)stepAnimationWithTime:(NSTimeInterval)currentTime

_animationCurrentTime = currentTime;
NSTimeInterval currentDuration = (_animationCurrentTime - _animationStartTime) / RCTAnimationDragCoefficient();

CGFloat singleFrameInterval = RCTSingleFrameInterval();

// Determine how many frames have passed since last update.
// Get index of frames that surround the current interval
NSUInteger startIndex = floor(currentDuration / RCTSingleFrameInterval);
NSUInteger startIndex = floor(currentDuration / singleFrameInterval);
NSUInteger nextIndex = startIndex + 1;

if (nextIndex >= _frames.count) {
Expand All @@ -121,8 +123,8 @@ - (void)stepAnimationWithTime:(NSTimeInterval)currentTime
// Do a linear remap of the two frames to safeguard against variable framerates
NSNumber *fromFrameValue = _frames[startIndex];
NSNumber *toFrameValue = _frames[nextIndex];
NSTimeInterval fromInterval = (double)startIndex * RCTSingleFrameInterval;
NSTimeInterval toInterval = (double)nextIndex * RCTSingleFrameInterval;
NSTimeInterval fromInterval = (double)startIndex * singleFrameInterval;
NSTimeInterval toInterval = (double)nextIndex * singleFrameInterval;
Comment thread
okwasniewski marked this conversation as resolved.
Outdated

// Interpolate between the individual frames to ensure the animations are
// smooth and of the proper duration regardless of the framerate.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,7 @@ - (void)startAnimationLoopIfNeeded
{
if (!_displayLink && _activeAnimations.count > 0) {
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(stepAnimations:)];
_displayLink.preferredFramesPerSecond = [UIScreen mainScreen].maximumFramesPerSecond;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs the same feature flag gating

[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@
#import <React/RCTNativeAnimatedNodesManager.h>
#import <React/RCTNativeAnimatedTurboModule.h>
#import <react/featureflags/ReactNativeFeatureFlags.h>
#import <React/RCTAnimationDriver.h>

#import "RCTAnimationPlugins.h"

using namespace facebook::react;

typedef void (^AnimatedOperation)(RCTNativeAnimatedNodesManager *nodesManager);

@interface RCTNativeAnimatedTurboModule () <NativeAnimatedModuleSpec, RCTInitializing>
Expand All @@ -28,13 +31,14 @@ @implementation RCTNativeAnimatedTurboModule {
NSMutableArray<AnimatedOperation> *_preOperations;

NSSet<NSString *> *_userDrivenAnimationEndedEvents;
ModuleConstants<JS::NativeAnimatedModule::Constants> _constants;
}

RCT_EXPORT_MODULE();

+ (BOOL)requiresMainQueueSetup
{
return NO;
return YES;
}

- (instancetype)init
Expand All @@ -43,6 +47,10 @@ - (instancetype)init
_operations = [NSMutableArray new];
_preOperations = [NSMutableArray new];
_userDrivenAnimationEndedEvents = [NSSet setWithArray:@[ @"onScrollEnded" ]];

_constants = typedConstants<JS::NativeAnimatedModule::Constants>({
.singleFrameInterval = RCTSingleFrameInterval()
});
}
return self;
}
Expand Down Expand Up @@ -371,6 +379,18 @@ - (void)eventDispatcherWillDispatchEvent:(id<RCTEvent>)event
});
}

#pragma mark -- Constants

- (ModuleConstants<JS::NativeAnimatedModule::Constants>)constantsToExport
{
return _constants;
}

- (ModuleConstants<JS::NativeAnimatedModule::Constants>)getConstants
{
return _constants;
}

- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
Expand Down
3 changes: 2 additions & 1 deletion packages/react-native/React/CoreModules/RCTFPSGraph.mm
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ - (void)onTick:(NSTimeInterval)timestamp
});

CGFloat previousScale = _scale;
CGFloat targetFps = MAX(_maxFPS, 60.0);
CGFloat screenRefreshRate = [UIScreen mainScreen].maximumFramesPerSecond;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be the current refresh rate, otherwise we. may be giving a wrong signal

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a separate PR

CGFloat targetFps = MAX(_maxFPS, screenRefreshRate);
_scale = targetFps / (CGFloat)_height;
for (NSUInteger i = 0; i < _length - 1; i++) {
// Move each Frame back one position and adjust to new scale (if there is a new scale)
Expand Down
1 change: 1 addition & 0 deletions packages/react-native/React/CoreModules/RCTPerfMonitor.mm
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ - (void)show
[RCTKeyWindow() addSubview:self.container];

_uiDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(threadUpdate:)];
_uiDisplayLink.preferredFramesPerSecond = [UIScreen mainScreen].maximumFramesPerSecond;
Comment thread
okwasniewski marked this conversation as resolved.
Outdated
[_uiDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];

self.container.frame =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ package com.facebook.react.animated
import com.facebook.react.bridge.Callback
import com.facebook.react.bridge.JSApplicationCausedNativeException
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
import android.content.Context
import android.view.WindowManager

public fun getSingleFrameInterval(context: Context): Double {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's do Android in a separate PR

if (ReactNativeFeatureFlags.disableHighRefreshRateAnimations()) {
return 60.0
}

val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager

val refreshRate = windowManager.defaultDisplay.supportedRefreshRates.maxOrNull()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return refreshRate?.toDouble() ?: 60.0
}

/**
* Base class for different types of animation drivers. Can be used to implement simple time-based
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,23 @@

package com.facebook.react.animated

import android.content.Context
import com.facebook.common.logging.FLog
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.ReadableType
import com.facebook.react.common.ReactConstants
import com.facebook.react.common.build.ReactBuildConfig
import kotlin.math.roundToInt

/**
* Implementation of [AnimationDriver] which provides a support for simple time-based animations
* that are pre-calculate on the JS side. For each animation frame JS provides a value from 0 to 1
* that indicates a progress of the animation at that frame.
*/
internal class FrameBasedAnimationDriver(config: ReadableMap) : AnimationDriver() {
internal class FrameBasedAnimationDriver(config: ReadableMap,
private var context: ReactApplicationContext?
) : AnimationDriver() {
private var startFrameTimeNanos: Long = -1
private var frames: DoubleArray = DoubleArray(0)
private var toValue = 0.0
Expand Down Expand Up @@ -62,7 +67,8 @@ internal class FrameBasedAnimationDriver(config: ReadableMap) : AnimationDriver(
}
}
val timeFromStartMillis = (frameTimeNanos - startFrameTimeNanos) / 1000000
val frameIndex = Math.round(timeFromStartMillis / FRAME_TIME_MILLIS).toInt()
val frameTime = getFrameTimeMillis(context)
val frameIndex = (timeFromStartMillis / frameTime).roundToInt()
if (frameIndex < 0) {
val message =
("Calculated frame index should never be lower than 0. Called with frameTimeNanos " +
Expand Down Expand Up @@ -98,7 +104,11 @@ internal class FrameBasedAnimationDriver(config: ReadableMap) : AnimationDriver(
}

companion object {
// 60FPS
private const val FRAME_TIME_MILLIS = 1000.0 / 60.0
fun getFrameTimeMillis(context: Context?): Double {
if (context == null) {
return 1000.0 / 60.0
}
return 1000.0 / getSingleFrameInterval(context)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

package com.facebook.react.animated

import android.content.Context
import android.view.WindowManager
import androidx.annotation.AnyThread
import androidx.annotation.UiThread
import com.facebook.common.logging.FLog
Expand All @@ -23,6 +25,7 @@ import com.facebook.react.bridge.buildReadableMap
import com.facebook.react.common.annotations.UnstableReactNativeAPI
import com.facebook.react.common.annotations.VisibleForTesting
import com.facebook.react.common.build.ReactBuildConfig
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.modules.core.ReactChoreographer
import com.facebook.react.uimanager.GuardedFrameCallback
Expand Down Expand Up @@ -1112,6 +1115,12 @@ public class NativeAnimatedModule(reactContext: ReactApplicationContext) :
finishOperationBatch()
}

override fun getTypedExportedConstants(): Map<String?, Any?>? {
return mapOf(
"singleFrameInterval" to getSingleFrameInterval(context = reactApplicationContext),
)
}

public companion object {
public const val NAME: String = NativeAnimatedModuleSpec.NAME

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ public class NativeAnimatedNodesManager(

val animation =
when (val type = animationConfig.getString("type")) {
"frames" -> FrameBasedAnimationDriver(animationConfig)
"frames" -> FrameBasedAnimationDriver(animationConfig, reactApplicationContext)
"spring" -> SpringAnimation(animationConfig)
"decay" -> DecayAnimation(animationConfig)
else -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<1e8a84a53072fa0c8665aead80d0199f>>
* @generated SignedSource<<1049251770171308f0dd79974aa9627c>>
*/

/**
Expand Down Expand Up @@ -60,6 +60,12 @@ public object ReactNativeFeatureFlags {
@JvmStatic
public fun disableFabricCommitInCXXAnimated(): Boolean = accessor.disableFabricCommitInCXXAnimated()

/**
* Disables high refresh rate animations on devices that support it (e.g. 120Hz).
*/
@JvmStatic
public fun disableHighRefreshRateAnimations(): Boolean = accessor.disableHighRefreshRateAnimations()

/**
* Prevent FabricMountingManager from reordering mountItems, which may lead to invalid state on the UI thread
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<d07316109dce5cc1ce7b0dc010962c3c>>
* @generated SignedSource<<7347872aafe465d387af7706930203fb>>
*/

/**
Expand All @@ -25,6 +25,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
private var cxxNativeAnimatedEnabledCache: Boolean? = null
private var cxxNativeAnimatedRemoveJsSyncCache: Boolean? = null
private var disableFabricCommitInCXXAnimatedCache: Boolean? = null
private var disableHighRefreshRateAnimationsCache: Boolean? = null
private var disableMountItemReorderingAndroidCache: Boolean? = null
private var disableOldAndroidAttachmentMetricsWorkaroundsCache: Boolean? = null
private var disableTextLayoutManagerCacheAndroidCache: Boolean? = null
Expand Down Expand Up @@ -134,6 +135,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
return cached
}

override fun disableHighRefreshRateAnimations(): Boolean {
var cached = disableHighRefreshRateAnimationsCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.disableHighRefreshRateAnimations()
disableHighRefreshRateAnimationsCache = cached
}
return cached
}

override fun disableMountItemReorderingAndroid(): Boolean {
var cached = disableMountItemReorderingAndroidCache
if (cached == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<6e384a07f0e7bc237f72b49a1268e16b>>
* @generated SignedSource<<c54ef984ff9f2bcd280133efa560fe69>>
*/

/**
Expand Down Expand Up @@ -38,6 +38,8 @@ public object ReactNativeFeatureFlagsCxxInterop {

@DoNotStrip @JvmStatic public external fun disableFabricCommitInCXXAnimated(): Boolean

@DoNotStrip @JvmStatic public external fun disableHighRefreshRateAnimations(): Boolean

@DoNotStrip @JvmStatic public external fun disableMountItemReorderingAndroid(): Boolean

@DoNotStrip @JvmStatic public external fun disableOldAndroidAttachmentMetricsWorkarounds(): Boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<3c47ba9c7fdbb37f0e06fdca56de3223>>
* @generated SignedSource<<7a6d60bf914bbeee4a50389ead452ec2>>
*/

/**
Expand Down Expand Up @@ -33,6 +33,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi

override fun disableFabricCommitInCXXAnimated(): Boolean = false

override fun disableHighRefreshRateAnimations(): Boolean = false

override fun disableMountItemReorderingAndroid(): Boolean = false

override fun disableOldAndroidAttachmentMetricsWorkarounds(): Boolean = true
Expand Down
Loading
Loading