Skip to content
Closed
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
51 changes: 51 additions & 0 deletions packages/react-native/React/Fabric/RCTScheduler.mm
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#import "RCTScheduler.h"

#import <QuartzCore/CADisplayLink.h>
#import <cxxreact/TraceSection.h>
#import <react/featureflags/ReactNativeFeatureFlags.h>
#import <react/renderer/animations/LayoutAnimationDriver.h>
Expand Down Expand Up @@ -113,6 +114,52 @@ void activityDidChange(const RunLoopObserver::Delegate *delegate, RunLoopObserve
void *scheduler_;
};

@interface RCTAnimationChoreographerDisplayLinkTarget : NSObject
@property (nonatomic, assign) AnimationChoreographer *choreographer;
- (void)displayLinkTick:(CADisplayLink *)sender;
@end

@implementation RCTAnimationChoreographerDisplayLinkTarget
- (void)displayLinkTick:(CADisplayLink *)sender
{
if (_choreographer != nullptr) {
_choreographer->onAnimationFrame(static_cast<float>(sender.targetTimestamp * 1000));
}
}
@end

class RCTAnimationChoreographer : public AnimationChoreographer {
CADisplayLink *_animationDisplayLink;
RCTAnimationChoreographerDisplayLinkTarget *_displayLinkTarget;

public:
RCTAnimationChoreographer() : _displayLinkTarget([[RCTAnimationChoreographerDisplayLinkTarget alloc] init])
{
_displayLinkTarget.choreographer = this;
}
~RCTAnimationChoreographer() override
{
if (_animationDisplayLink != nil) {
[_animationDisplayLink invalidate];
_animationDisplayLink = nil;
}
_displayLinkTarget.choreographer = nullptr;
}
void resume() override
{
if (_animationDisplayLink == nil) {
_animationDisplayLink = [CADisplayLink displayLinkWithTarget:_displayLinkTarget
selector:@selector(displayLinkTick:)];
[_animationDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
[_animationDisplayLink setPaused:NO];
}
void pause() override
{
[_animationDisplayLink setPaused:YES];
}
};

@implementation RCTScheduler {
std::unique_ptr<Scheduler> _scheduler;
std::shared_ptr<LayoutAnimationDriver> _animationDriver;
Expand All @@ -137,6 +184,10 @@ - (instancetype)initWithToolbox:(SchedulerToolbox)toolbox
_uiRunLoopObserver->setDelegate(_layoutAnimationDelegateProxy.get());
}

if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) {
toolbox.animationChoreographer = std::make_shared<RCTAnimationChoreographer>();
}

_scheduler = std::make_unique<Scheduler>(
toolbox, (_animationDriver ? _animationDriver.get() : nullptr), _delegateProxy.get());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* 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.fabric

import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.modules.core.ReactChoreographer
import com.facebook.react.uimanager.GuardedFrameCallback
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.synchronized

internal fun interface AnimationFrameCallback {
fun onAnimationFrame(frameTimeMs: Double)
}

internal class AnimationBackendChoreographer(
reactApplicationContext: ReactApplicationContext,
) {

var frameCallback: AnimationFrameCallback? = null
private var lastFrameTimeMs: Double = 0.0
private val reactChoreographer: ReactChoreographer = ReactChoreographer.getInstance()
private val choreographerCallback: GuardedFrameCallback =
object : GuardedFrameCallback(reactApplicationContext) {
override fun doFrameGuarded(frameTimeNanos: Long) {
executeFrameCallback(frameTimeNanos)
}
}
private val callbackPosted: AtomicBoolean = AtomicBoolean()
private val paused: AtomicBoolean = AtomicBoolean(true)

/*
* resume() and pause() should be called with the same lock to avoid race conditions.
*/

fun resume() {
if (paused.getAndSet(false)) {
scheduleCallback()
}
}

fun pause() {
synchronized(paused) {
if (!paused.getAndSet(true) && callbackPosted.getAndSet(false)) {
reactChoreographer.removeFrameCallback(
ReactChoreographer.CallbackType.NATIVE_ANIMATED_MODULE,
choreographerCallback,
)
}
}
}

private fun scheduleCallback() {
synchronized(paused) {
if (!paused.get() && !callbackPosted.getAndSet(true)) {
reactChoreographer.postFrameCallback(
ReactChoreographer.CallbackType.NATIVE_ANIMATED_MODULE,
choreographerCallback,
)
}
}
}

private fun executeFrameCallback(frameTimeNanos: Long) {
callbackPosted.set(false)
val currentFrameTimeMs = calculateTimestamp(frameTimeNanos)
// It is possible for ChoreographerCallback to be executed twice within the same frame
// due to frame drops. If this occurs, the additional callback execution should be ignored.
if (currentFrameTimeMs > lastFrameTimeMs) {
frameCallback?.onAnimationFrame(currentFrameTimeMs)
}

lastFrameTimeMs = currentFrameTimeMs
scheduleCallback()
}

private fun calculateTimestamp(frameTimeNanos: Long): Double {
val nanosecondsInMilliseconds = 1000000.0
return frameTimeNanos / nanosecondsInMilliseconds
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,18 +79,30 @@ internal class FabricUIManagerBinding : HybridClassBase() {

external fun driveCxxAnimations()

external fun driveAnimationBackend(frameTimeMs: Double)

external fun drainPreallocateViewsQueue()

external fun reportMount(surfaceId: Int)

external fun setAnimationBackendChoreographer(
animationBackendChoreographer: AnimationBackendChoreographer
)

fun register(
runtimeExecutor: RuntimeExecutor,
runtimeScheduler: RuntimeScheduler,
fabricUIManager: FabricUIManager,
eventBeatManager: EventBeatManager,
componentFactory: ComponentFactory,
animationBackendChoreographer: AnimationBackendChoreographer,
) {
fabricUIManager.setBinding(this)
animationBackendChoreographer.frameCallback = AnimationFrameCallback { frameTimeMs: Double ->
driveAnimationBackend(frameTimeMs)
}
setAnimationBackendChoreographer(animationBackendChoreographer)

installFabricUIManager(
runtimeExecutor,
runtimeScheduler,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,16 @@ public class FabricUIManagerProviderImpl(
val runtimeExecutor = catalystInstance?.runtimeExecutor
val runtimeScheduler = catalystInstance?.runtimeScheduler

val animationBackendChoreographer = AnimationBackendChoreographer(context)

if (runtimeExecutor != null && runtimeScheduler != null) {
binding.register(
runtimeExecutor,
runtimeScheduler,
fabricUIManager,
eventBeatManager,
componentFactory,
animationBackendChoreographer,
)
} else {
throw IllegalStateException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import com.facebook.react.common.annotations.UnstableReactNativeAPI
import com.facebook.react.devsupport.InspectorFlags.getIsProfilingBuild
import com.facebook.react.devsupport.StackTraceHelper
import com.facebook.react.devsupport.interfaces.DevSupportManager
import com.facebook.react.fabric.AnimationBackendChoreographer
import com.facebook.react.fabric.ComponentFactory
import com.facebook.react.fabric.FabricUIManager
import com.facebook.react.fabric.FabricUIManagerBinding
Expand Down Expand Up @@ -250,13 +251,16 @@ internal class ReactInstance(
// Misc initialization that needs to be done before Fabric init
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(context)

val animationBackendChoreographer = AnimationBackendChoreographer(context)

val binding = FabricUIManagerBinding()
binding.register(
getBufferedRuntimeExecutor(),
getRuntimeScheduler(),
fabricUIManager,
eventBeatManager,
componentFactory,
animationBackendChoreographer,
)

// Initialize the FabricUIManager
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ add_react_common_subdir(react/debug)
add_react_common_subdir(react/featureflags)
add_react_common_subdir(react/performance/cdpmetrics)
add_react_common_subdir(react/performance/timeline)
add_react_common_subdir(react/renderer/animationbackend)
add_react_common_subdir(react/renderer/animations)
add_react_common_subdir(react/renderer/attributedstring)
add_react_common_subdir(react/renderer/componentregistry)
Expand Down Expand Up @@ -200,6 +201,7 @@ add_library(reactnative
$<TARGET_OBJECTS:react_newarchdefaults>
$<TARGET_OBJECTS:react_performance_cdpmetrics>
$<TARGET_OBJECTS:react_performance_timeline>
$<TARGET_OBJECTS:react_renderer_animationbackend>
$<TARGET_OBJECTS:react_renderer_animations>
$<TARGET_OBJECTS:react_renderer_attributedstring>
$<TARGET_OBJECTS:react_renderer_componentregistry>
Expand Down Expand Up @@ -293,6 +295,7 @@ target_include_directories(reactnative
$<TARGET_PROPERTY:react_newarchdefaults,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:react_performance_cdpmetrics,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:react_performance_timeline,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:react_renderer_animationbackend,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:react_renderer_animations,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:react_renderer_attributedstring,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:react_renderer_componentregistry,INTERFACE_INCLUDE_DIRECTORIES>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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.
*/

#pragma once

#include <fbjni/fbjni.h>
#include <react/renderer/animationbackend/AnimationChoreographer.h>

#include "JAnimationBackendChoreographer.h"

namespace facebook::react {

class AndroidAnimationChoreographer : public AnimationChoreographer {
public:
explicit AndroidAnimationChoreographer(jni::alias_ref<JAnimationBackendChoreographer> jChoreographer)
: jChoreographer_(jni::make_global(jChoreographer))
{
}

void resume() override
{
jChoreographer_->resume();
}

void pause() override
{
jChoreographer_->pause();
}

private:
jni::global_ref<JAnimationBackendChoreographer> jChoreographer_;
};

} // namespace facebook::react
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include "FabricUIManagerBinding.h"

#include "AndroidAnimationChoreographer.h"
#include "AndroidEventBeat.h"
#include "ComponentFactory.h"
#include "EventBeatManager.h"
Expand Down Expand Up @@ -54,6 +55,10 @@ void FabricUIManagerBinding::driveCxxAnimations() {
getScheduler()->animationTick();
}

void FabricUIManagerBinding::driveAnimationBackend(jdouble frameTimeMs) {
animationChoreographer_->onAnimationFrame(static_cast<float>(frameTimeMs));
}

void FabricUIManagerBinding::drainPreallocateViewsQueue() {
auto mountingManager = getMountingManager("drainPreallocateViewsQueue");
if (!mountingManager) {
Expand Down Expand Up @@ -572,6 +577,11 @@ void FabricUIManagerBinding::installFabricUIManager(

toolbox.eventBeatFactory = eventBeatFactory;

react_native_assert(
animationChoreographer_ != nullptr &&
"AnimationChoreographer is nullptr");
toolbox.animationChoreographer = animationChoreographer_;

animationDriver_ = std::make_shared<LayoutAnimationDriver>(
runtimeExecutor, contextContainer, this);
scheduler_ =
Expand Down Expand Up @@ -797,6 +807,9 @@ void FabricUIManagerBinding::registerNatives() {
"setPixelDensity", FabricUIManagerBinding::setPixelDensity),
makeNativeMethod(
"driveCxxAnimations", FabricUIManagerBinding::driveCxxAnimations),
makeNativeMethod(
"driveAnimationBackend",
FabricUIManagerBinding::driveAnimationBackend),
makeNativeMethod(
"drainPreallocateViewsQueue",
FabricUIManagerBinding::drainPreallocateViewsQueue),
Expand All @@ -816,7 +829,17 @@ void FabricUIManagerBinding::registerNatives() {
makeNativeMethod(
"getRelativeAncestorList",
FabricUIManagerBinding::getRelativeAncestorList),
makeNativeMethod(
"setAnimationBackendChoreographer",
FabricUIManagerBinding::setAnimationBackendChoreographer),
});
}

void FabricUIManagerBinding::setAnimationBackendChoreographer(
jni::alias_ref<JAnimationBackendChoreographer::javaobject>
animationBackendChoreographer) {
animationChoreographer_ = std::make_shared<AndroidAnimationChoreographer>(
animationBackendChoreographer);
}

} // namespace facebook::react
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <react/renderer/uimanager/LayoutAnimationStatusDelegate.h>
#include <react/renderer/uimanager/primitives.h>

#include "AndroidAnimationChoreographer.h"
#include "JFabricUIManager.h"
#include "SurfaceHandlerBinding.h"

Expand Down Expand Up @@ -116,6 +117,8 @@ class FabricUIManagerBinding : public jni::HybridClass<FabricUIManagerBinding>,

void driveCxxAnimations();

void driveAnimationBackend(jdouble frameTimeMs);

void drainPreallocateViewsQueue();

void reportMount(SurfaceId surfaceId);
Expand Down Expand Up @@ -153,6 +156,10 @@ class FabricUIManagerBinding : public jni::HybridClass<FabricUIManagerBinding>,
float pointScaleFactor_ = 1;

bool enableFabricLogs_{false};

std::shared_ptr<AndroidAnimationChoreographer> animationChoreographer_;

void setAnimationBackendChoreographer(jni::alias_ref<JAnimationBackendChoreographer::javaobject> animationBackend);
};

} // namespace facebook::react
Loading