Kotlinify FrameBasedAnimationDriver (#45764)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/45764

# Changelog:
[Internal] -

As in the title.

Reviewed By: cortinico

Differential Revision: D60348765

fbshipit-source-id: 1507d802044eb59c7d224ee313ae98c96468f0cd
This commit is contained in:
Ruslan Shestopalyuk
2024-07-30 06:17:30 -07:00
committed by Facebook GitHub Bot
parent df7e0d0fa5
commit 7903dba7ac
2 changed files with 103 additions and 114 deletions
@@ -1,114 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.animated;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.ReadableArray;
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;
/**
* Implementation of {@link 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.
*/
class FrameBasedAnimationDriver extends AnimationDriver {
// 60FPS
private static final double FRAME_TIME_MILLIS = 1000d / 60d;
private long mStartFrameTimeNanos;
private double[] mFrames;
private double mToValue;
private double mFromValue;
private int mIterations;
private int mCurrentLoop;
private int logCount = 0;
FrameBasedAnimationDriver(ReadableMap config) {
resetConfig(config);
}
@Override
public void resetConfig(ReadableMap config) {
ReadableArray frames = config.getArray("frames");
int numberOfFrames = frames.size();
if (mFrames == null || mFrames.length != numberOfFrames) {
mFrames = new double[numberOfFrames];
}
for (int i = 0; i < numberOfFrames; i++) {
mFrames[i] = frames.getDouble(i);
}
if (config.hasKey("toValue")) {
mToValue = config.getType("toValue") == ReadableType.Number ? config.getDouble("toValue") : 0;
} else {
mToValue = 0;
}
if (config.hasKey("iterations")) {
mIterations =
config.getType("iterations") == ReadableType.Number ? config.getInt("iterations") : 1;
} else {
mIterations = 1;
}
mCurrentLoop = 1;
mHasFinished = mIterations == 0;
mStartFrameTimeNanos = -1;
}
@Override
public void runAnimationStep(long frameTimeNanos) {
if (mStartFrameTimeNanos < 0) {
mStartFrameTimeNanos = frameTimeNanos;
if (mCurrentLoop == 1) {
// initiate start value when animation runs for the first time
mFromValue = mAnimatedValue.nodeValue;
}
}
long timeFromStartMillis = (frameTimeNanos - mStartFrameTimeNanos) / 1000000;
int frameIndex = (int) Math.round(timeFromStartMillis / FRAME_TIME_MILLIS);
if (frameIndex < 0) {
String message =
"Calculated frame index should never be lower than 0. Called with frameTimeNanos "
+ frameTimeNanos
+ " and mStartFrameTimeNanos "
+ mStartFrameTimeNanos;
if (ReactBuildConfig.DEBUG) {
throw new IllegalStateException(message);
} else {
if (logCount < 100) {
FLog.w(ReactConstants.TAG, message);
logCount++;
}
return;
}
} else if (mHasFinished) {
// nothing to do here
return;
}
double nextValue;
if (frameIndex >= mFrames.length - 1) {
if (mIterations == -1 || mCurrentLoop < mIterations) {
// Use last frame value, just in case it's different from mToValue
nextValue = mFromValue + mFrames[mFrames.length - 1] * (mToValue - mFromValue);
mStartFrameTimeNanos = -1;
mCurrentLoop++;
} else {
// animation has completed, no more frames left
nextValue = mToValue;
mHasFinished = true;
}
} else {
nextValue = mFromValue + mFrames[frameIndex] * (mToValue - mFromValue);
}
mAnimatedValue.nodeValue = nextValue;
}
}
@@ -0,0 +1,103 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.animated
import com.facebook.common.logging.FLog
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
/**
* 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() {
private var startFrameTimeNanos: Long = -1
private var frames: DoubleArray = DoubleArray(0)
private var toValue = 0.0
private var fromValue = 0.0
private var iterations = 1
private var currentLoop = 1
private var logCount = 0
init {
resetConfig(config)
}
override fun resetConfig(config: ReadableMap) {
val framesConfig = config.getArray("frames")
if (framesConfig != null) {
val numberOfFrames = framesConfig.size()
if (frames.size != numberOfFrames) {
frames = DoubleArray(numberOfFrames) { i -> framesConfig.getDouble(i) }
}
}
toValue =
if (config.hasKey("toValue") && config.getType("toValue") == ReadableType.Number)
config.getDouble("toValue")
else 0.0
iterations =
if (config.hasKey("iterations") && config.getType("iterations") == ReadableType.Number)
config.getInt("iterations")
else 1
currentLoop = 1
mHasFinished = iterations == 0
startFrameTimeNanos = -1
}
override fun runAnimationStep(frameTimeNanos: Long) {
if (startFrameTimeNanos < 0) {
startFrameTimeNanos = frameTimeNanos
if (currentLoop == 1) {
// initiate start value when animation runs for the first time
fromValue = mAnimatedValue.nodeValue
}
}
val timeFromStartMillis = (frameTimeNanos - startFrameTimeNanos) / 1000000
val frameIndex = Math.round(timeFromStartMillis / FRAME_TIME_MILLIS).toInt()
if (frameIndex < 0) {
val message =
("Calculated frame index should never be lower than 0. Called with frameTimeNanos " +
frameTimeNanos +
" and mStartFrameTimeNanos " +
startFrameTimeNanos)
check(!ReactBuildConfig.DEBUG) { message }
if (logCount < 100) {
FLog.w(ReactConstants.TAG, message)
logCount++
}
return
} else if (mHasFinished) {
// nothing to do here
return
}
val nextValue: Double
if (frameIndex >= frames.size - 1) {
if (iterations == -1 || currentLoop < iterations) {
// Use last frame value, just in case it's different from mToValue
nextValue = fromValue + frames[frames.size - 1] * (toValue - fromValue)
startFrameTimeNanos = -1
currentLoop++
} else {
// animation has completed, no more frames left
nextValue = toValue
mHasFinished = true
}
} else {
nextValue = fromValue + frames[frameIndex] * (toValue - fromValue)
}
mAnimatedValue.nodeValue = nextValue
}
companion object {
// 60FPS
private const val FRAME_TIME_MILLIS = 1000.0 / 60.0
}
}