react-native-macos/React/Fabric/RCTScheduler.mm

189 строки
5.8 KiB
Plaintext
Исходник Обычный вид История

/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTScheduler.h"
#import <react/renderer/animations/LayoutAnimationDriver.h>
#import <react/renderer/componentregistry/ComponentDescriptorFactory.h>
#import <react/renderer/debug/SystraceSection.h>
#import <react/renderer/scheduler/Scheduler.h>
#import <react/renderer/scheduler/SchedulerDelegate.h>
#include <react/utils/RunLoopObserver.h>
#import <React/RCTFollyConvert.h>
#import "RCTConversions.h"
using namespace facebook::react;
class SchedulerDelegateProxy : public SchedulerDelegate {
public:
SchedulerDelegateProxy(void *scheduler) : scheduler_(scheduler) {}
void schedulerDidFinishTransaction(MountingCoordinator::Shared const &mountingCoordinator) override
{
RCTScheduler *scheduler = (__bridge RCTScheduler *)scheduler_;
[scheduler.delegate schedulerDidFinishTransaction:mountingCoordinator];
}
void schedulerDidRequestPreliminaryViewAllocation(SurfaceId surfaceId, const ShadowNode &shadowNode) override
{
Fabric: Simpler view preallocation on iOS Summary: I measured the performance of UIMananger methods and found that `create` method spends a significant amount of time on preallocating views (around 10 ms on my device). Interestingly, this time was actually spent on dispatching operations on the main thread, not on performing those operations. That makes me think about think about the preallocation problem one more time. (Here and later I discuss preemptive optimistic preallocation views on iOS, but the issues are more-less applicable for all platforms.) BTW, what's view preallocation? – In React Native we believe that we can get significant TTI wins instantiating platform views ahead of time while JavaScript thread is busy doing reconciliation and the main one is idle. So, seems the current approach has those downsides: * We spend too much time on dispatching only (jumping threads, creation Objective-C blocks, managing them in GCD, incrementing atomic refcountes inside `ShadowView` and so on). That's wasteful. * We don't have much information during preallocations that can help prepare something to save time in the future. We don't know the future size of the component, so we cannot e.g. pre-draw a border or even allocate memory for a future drawing. * We pre-allocate to much. At the moment of component creation, we don't know will this component be filtered out by view-flattening infra or not, so we always allocate a view for it. That's ~30% more views that we actually need. * We don't stop allocating (or dispatching instructions for that) after the recycle pool is already filled in. That's also wasteful. Why is this so bad and how can we fix it properly?.. I thought about this problem for months. And I think the answer is trivial: We don't know the exact shape of the interface until we finish creating it, and there is no way to overcome this problem on the client side. In the ideal world, the server size (or hardcoded manifest somewhere) tells the renderer (at the moment where we started navigating to it) how many views of which type will some surface use. And until our world is not so perfect, I think we can get a significant performance improvement doing simple preallocation of 100 regular views, 30 text views, and 30 image views during rendering the first React Native surface. So I hardcoded that. Reviewed By: JoshuaGross Differential Revision: D14333606 fbshipit-source-id: 96beeb58b546258de1b8fd58550e0ae412b78aa9
2019-03-08 21:23:16 +03:00
// Does nothing.
// This delegate method is not currently used on iOS.
}
void schedulerDidCloneShadowNode(
SurfaceId surfaceId,
const ShadowNode &oldShadowNode,
const ShadowNode &newShadowNode) override
{
// Does nothing.
// This delegate method is not currently used on iOS.
}
void schedulerDidDispatchCommand(
const ShadowView &shadowView,
const std::string &commandName,
const folly::dynamic args) override
{
RCTScheduler *scheduler = (__bridge RCTScheduler *)scheduler_;
[scheduler.delegate schedulerDidDispatchCommand:shadowView commandName:commandName args:args];
}
void schedulerDidSetIsJSResponder(ShadowView const &shadowView, bool isJSResponder, bool blockNativeResponder)
override
{
RCTScheduler *scheduler = (__bridge RCTScheduler *)scheduler_;
[scheduler.delegate schedulerDidSetIsJSResponder:isJSResponder
blockNativeResponder:blockNativeResponder
forShadowView:shadowView];
}
void schedulerDidSendAccessibilityEvent(const ShadowView &shadowView, std::string const &eventType) override
{
RCTScheduler *scheduler = (__bridge RCTScheduler *)scheduler_;
[scheduler.delegate schedulerDidSendAccessibilityEvent:shadowView eventType:eventType];
}
private:
void *scheduler_;
};
class LayoutAnimationDelegateProxy : public LayoutAnimationStatusDelegate, public RunLoopObserver::Delegate {
public:
LayoutAnimationDelegateProxy(void *scheduler) : scheduler_(scheduler) {}
virtual ~LayoutAnimationDelegateProxy() {}
void onAnimationStarted() override
{
RCTScheduler *scheduler = (__bridge RCTScheduler *)scheduler_;
[scheduler onAnimationStarted];
}
/**
* Called when the LayoutAnimation engine completes all pending animations.
*/
void onAllAnimationsComplete() override
{
RCTScheduler *scheduler = (__bridge RCTScheduler *)scheduler_;
[scheduler onAllAnimationsComplete];
}
void activityDidChange(RunLoopObserver::Delegate const *delegate, RunLoopObserver::Activity activity)
const noexcept override
{
RCTScheduler *scheduler = (__bridge RCTScheduler *)scheduler_;
[scheduler animationTick];
}
private:
void *scheduler_;
};
@implementation RCTScheduler {
std::unique_ptr<Scheduler> _scheduler;
std::shared_ptr<LayoutAnimationDriver> _animationDriver;
std::shared_ptr<SchedulerDelegateProxy> _delegateProxy;
std::shared_ptr<LayoutAnimationDelegateProxy> _layoutAnimationDelegateProxy;
RunLoopObserver::Unique _uiRunLoopObserver;
BOOL _layoutAnimationsEnabled;
}
- (instancetype)initWithToolbox:(SchedulerToolbox)toolbox
{
if (self = [super init]) {
auto reactNativeConfig =
toolbox.contextContainer->at<std::shared_ptr<const ReactNativeConfig>>("ReactNativeConfig");
_layoutAnimationsEnabled = reactNativeConfig->getBool("react_fabric:enabled_layout_animations_ios");
_delegateProxy = std::make_shared<SchedulerDelegateProxy>((__bridge void *)self);
if (_layoutAnimationsEnabled) {
_layoutAnimationDelegateProxy = std::make_shared<LayoutAnimationDelegateProxy>((__bridge void *)self);
_animationDriver =
std::make_shared<LayoutAnimationDriver>(toolbox.runtimeExecutor, _layoutAnimationDelegateProxy.get());
_uiRunLoopObserver =
toolbox.mainRunLoopObserverFactory(RunLoopObserver::Activity::BeforeWaiting, _layoutAnimationDelegateProxy);
_uiRunLoopObserver->setDelegate(_layoutAnimationDelegateProxy.get());
}
_scheduler = std::make_unique<Scheduler>(
toolbox, (_animationDriver ? _animationDriver.get() : nullptr), _delegateProxy.get());
}
return self;
}
- (void)animationTick
{
_scheduler->animationTick();
}
- (void)dealloc
{
if (_animationDriver) {
_animationDriver->setLayoutAnimationStatusDelegate(nullptr);
}
_animationDriver = nullptr;
}
- (void)registerSurface:(facebook::react::SurfaceHandler const &)surfaceHandler
{
_scheduler->registerSurface(surfaceHandler);
}
- (void)unregisterSurface:(facebook::react::SurfaceHandler const &)surfaceHandler
{
_scheduler->unregisterSurface(surfaceHandler);
}
- (ComponentDescriptor const *)findComponentDescriptorByHandle_DO_NOT_USE_THIS_IS_BROKEN:(ComponentHandle)handle
{
return _scheduler->findComponentDescriptorByHandle_DO_NOT_USE_THIS_IS_BROKEN(handle);
}
- (void)setupAnimationDriver:(facebook::react::SurfaceHandler const &)surfaceHandler
{
surfaceHandler.getMountingCoordinator()->setMountingOverrideDelegate(_animationDriver);
}
- (void)onAnimationStarted
{
if (_uiRunLoopObserver) {
_uiRunLoopObserver->enable();
}
}
- (void)onAllAnimationsComplete
{
if (_uiRunLoopObserver) {
_uiRunLoopObserver->disable();
}
}
@end