mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Summary: Unfortunately, parsing some props requires stateful context - namely, PlatformColor on Android. We explored several different options but they all seemed inferior to the approach of using ContextContainer, and most would require using global state. By introducing this change everywhere as early as possible, we can avoid later pain. It is likely that some prop, on some platform, will require this mechanism. We'll be ready for it! Because we can pass a constref of the ContextContainer through to all props and because the context and context data is never retained by prop parsers, perf and memory hit should be ~0. This diff contains core changes only. Leaf changes to all props structs and conversions files will be in next diff(s). Changelog: [Internal] Reviewed By: mdvacca Differential Revision: D29838789 fbshipit-source-id: f5090e7f02eb6e8fbe0ef4dd201e7d12104a3e3c
363 lines
12 KiB
C++
363 lines
12 KiB
C++
/*
|
|
* 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.
|
|
*/
|
|
|
|
#include "Scheduler.h"
|
|
|
|
#include <glog/logging.h>
|
|
#include <jsi/jsi.h>
|
|
|
|
#include <react/debug/react_native_assert.h>
|
|
#include <react/renderer/componentregistry/ComponentDescriptorRegistry.h>
|
|
#include <react/renderer/core/Constants.h>
|
|
#include <react/renderer/core/EventQueueProcessor.h>
|
|
#include <react/renderer/core/LayoutContext.h>
|
|
#include <react/renderer/debug/SystraceSection.h>
|
|
#include <react/renderer/mounting/MountingOverrideDelegate.h>
|
|
#include <react/renderer/mounting/ShadowViewMutation.h>
|
|
#include <react/renderer/runtimescheduler/RuntimeSchedulerBinding.h>
|
|
#include <react/renderer/templateprocessor/UITemplateProcessor.h>
|
|
#include <react/renderer/uimanager/UIManager.h>
|
|
#include <react/renderer/uimanager/UIManagerBinding.h>
|
|
|
|
#ifdef RN_SHADOW_TREE_INTROSPECTION
|
|
#include <react/renderer/mounting/stubs.h>
|
|
#include <iostream>
|
|
#endif
|
|
|
|
namespace facebook {
|
|
namespace react {
|
|
|
|
Scheduler::Scheduler(
|
|
SchedulerToolbox schedulerToolbox,
|
|
UIManagerAnimationDelegate *animationDelegate,
|
|
SchedulerDelegate *delegate) {
|
|
runtimeExecutor_ = schedulerToolbox.runtimeExecutor;
|
|
contextContainer_ = schedulerToolbox.contextContainer;
|
|
|
|
reactNativeConfig_ =
|
|
contextContainer_->at<std::shared_ptr<const ReactNativeConfig>>(
|
|
"ReactNativeConfig");
|
|
|
|
// Creating a container for future `EventDispatcher` instance.
|
|
eventDispatcher_ =
|
|
std::make_shared<better::optional<EventDispatcher const>>();
|
|
|
|
auto uiManager = std::make_shared<UIManager>(
|
|
runtimeExecutor_, schedulerToolbox.backgroundExecutor, contextContainer_);
|
|
auto eventOwnerBox = std::make_shared<EventBeat::OwnerBox>();
|
|
eventOwnerBox->owner = eventDispatcher_;
|
|
|
|
auto eventPipe = [uiManager](
|
|
jsi::Runtime &runtime,
|
|
const EventTarget *eventTarget,
|
|
const std::string &type,
|
|
ReactEventPriority priority,
|
|
const ValueFactory &payloadFactory) {
|
|
uiManager->visitBinding(
|
|
[&](UIManagerBinding const &uiManagerBinding) {
|
|
uiManagerBinding.dispatchEvent(
|
|
runtime, eventTarget, type, priority, payloadFactory);
|
|
},
|
|
runtime);
|
|
};
|
|
|
|
auto statePipe = [uiManager](StateUpdate const &stateUpdate) {
|
|
uiManager->updateState(stateUpdate);
|
|
};
|
|
|
|
#ifdef ANDROID
|
|
auto unbatchedQueuesOnly =
|
|
reactNativeConfig_->getBool("react_fabric:unbatched_queues_only_android");
|
|
#else
|
|
auto unbatchedQueuesOnly =
|
|
reactNativeConfig_->getBool("react_fabric:unbatched_queues_only_ios");
|
|
#endif
|
|
|
|
// Creating an `EventDispatcher` instance inside the already allocated
|
|
// container (inside the optional).
|
|
eventDispatcher_->emplace(
|
|
EventQueueProcessor(eventPipe, statePipe),
|
|
schedulerToolbox.synchronousEventBeatFactory,
|
|
schedulerToolbox.asynchronousEventBeatFactory,
|
|
eventOwnerBox,
|
|
unbatchedQueuesOnly);
|
|
|
|
// Casting to `std::shared_ptr<EventDispatcher const>`.
|
|
auto eventDispatcher =
|
|
EventDispatcher::Shared{eventDispatcher_, &eventDispatcher_->value()};
|
|
|
|
componentDescriptorRegistry_ = schedulerToolbox.componentRegistryFactory(
|
|
eventDispatcher, contextContainer_);
|
|
|
|
uiManager->setDelegate(this);
|
|
uiManager->setComponentDescriptorRegistry(componentDescriptorRegistry_);
|
|
|
|
runtimeExecutor_([uiManager,
|
|
runtimeExecutor = runtimeExecutor_](jsi::Runtime &runtime) {
|
|
auto uiManagerBinding =
|
|
UIManagerBinding::createAndInstallIfNeeded(runtime, runtimeExecutor);
|
|
uiManagerBinding->attach(uiManager);
|
|
});
|
|
|
|
auto componentDescriptorRegistryKey =
|
|
"ComponentDescriptorRegistry_DO_NOT_USE_PRETTY_PLEASE";
|
|
contextContainer_->erase(componentDescriptorRegistryKey);
|
|
contextContainer_->insert(
|
|
componentDescriptorRegistryKey,
|
|
std::weak_ptr<ComponentDescriptorRegistry const>(
|
|
componentDescriptorRegistry_));
|
|
|
|
delegate_ = delegate;
|
|
commitHooks_ = schedulerToolbox.commitHooks;
|
|
uiManager_ = uiManager;
|
|
|
|
for (auto commitHook : commitHooks_) {
|
|
uiManager->registerCommitHook(*commitHook);
|
|
}
|
|
|
|
if (animationDelegate != nullptr) {
|
|
animationDelegate->setComponentDescriptorRegistry(
|
|
componentDescriptorRegistry_);
|
|
}
|
|
uiManager_->setAnimationDelegate(animationDelegate);
|
|
|
|
#ifdef ANDROID
|
|
removeOutstandingSurfacesOnDestruction_ = reactNativeConfig_->getBool(
|
|
"react_fabric:remove_outstanding_surfaces_on_destruction_android");
|
|
Constants::setPropsForwardingEnabled(reactNativeConfig_->getBool(
|
|
"react_fabric:enable_props_forwarding_android"));
|
|
#else
|
|
removeOutstandingSurfacesOnDestruction_ = reactNativeConfig_->getBool(
|
|
"react_fabric:remove_outstanding_surfaces_on_destruction_ios");
|
|
#endif
|
|
}
|
|
|
|
Scheduler::~Scheduler() {
|
|
LOG(WARNING) << "Scheduler::~Scheduler() was called (address: " << this
|
|
<< ").";
|
|
|
|
for (auto commitHook : commitHooks_) {
|
|
uiManager_->unregisterCommitHook(*commitHook);
|
|
}
|
|
|
|
// All Surfaces must be explicitly stopped before destroying `Scheduler`.
|
|
// The idea is that `UIManager` is allowed to call `Scheduler` only if the
|
|
// corresponding `ShadowTree` instance exists.
|
|
|
|
// The thread-safety of this operation is guaranteed by this requirement.
|
|
uiManager_->setDelegate(nullptr);
|
|
uiManager_->setAnimationDelegate(nullptr);
|
|
|
|
// Then, let's verify that the requirement was satisfied.
|
|
auto surfaceIds = std::vector<SurfaceId>{};
|
|
uiManager_->getShadowTreeRegistry().enumerate(
|
|
[&](ShadowTree const &shadowTree, bool &stop) {
|
|
surfaceIds.push_back(shadowTree.getSurfaceId());
|
|
});
|
|
|
|
// TODO(T88046056): Fix Android memory leak before uncommenting changes
|
|
// react_native_assert(
|
|
// surfaceIds.empty() &&
|
|
// "Scheduler was destroyed with outstanding Surfaces.");
|
|
|
|
if (surfaceIds.empty()) {
|
|
return;
|
|
}
|
|
|
|
LOG(ERROR) << "Scheduler was destroyed with outstanding Surfaces.";
|
|
|
|
// If we are here, that means assert didn't fire which indicates that we in
|
|
// production.
|
|
|
|
// Now we have still-running surfaces, which is no good, no good.
|
|
// That's indeed a sign of a severe issue on the application layer.
|
|
// At this point, we don't have much to lose, so we are trying to unmount all
|
|
// outstanding `ShadowTree`s to prevent all stored JSI entities from
|
|
// overliving the `Scheduler`. (Unmounting `ShadowNode`s disables
|
|
// `EventEmitter`s which destroys JSI objects.)
|
|
for (auto surfaceId : surfaceIds) {
|
|
uiManager_->getShadowTreeRegistry().visit(
|
|
surfaceId,
|
|
[](ShadowTree const &shadowTree) { shadowTree.commitEmptyTree(); });
|
|
|
|
// Removing surfaces is gated because it acquires mutex waiting for commits
|
|
// in flight; in theory, it can deadlock.
|
|
if (removeOutstandingSurfacesOnDestruction_) {
|
|
uiManager_->getShadowTreeRegistry().remove(surfaceId);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Scheduler::registerSurface(
|
|
SurfaceHandler const &surfaceHandler) const noexcept {
|
|
surfaceHandler.setContextContainer(getContextContainer());
|
|
surfaceHandler.setUIManager(uiManager_.get());
|
|
}
|
|
|
|
InspectorData Scheduler::getInspectorDataForInstance(
|
|
SharedEventEmitter eventEmitter) const noexcept {
|
|
return executeSynchronouslyOnSameThread_CAN_DEADLOCK<InspectorData>(
|
|
runtimeExecutor_, [=](jsi::Runtime &runtime) -> InspectorData {
|
|
auto uiManagerBinding = UIManagerBinding::getBinding(runtime);
|
|
auto value = uiManagerBinding->getInspectorDataForInstance(
|
|
runtime, eventEmitter);
|
|
|
|
// TODO: avoid transforming jsi
|
|
auto dynamic = jsi::dynamicFromValue(runtime, value);
|
|
auto source = dynamic["source"];
|
|
|
|
InspectorData result = {};
|
|
result.fileName = source["fileName"].c_str();
|
|
result.lineNumber = (int)source["lineNumber"].getDouble();
|
|
result.columnNumber = (int)source["columnNumber"].getDouble();
|
|
result.selectedIndex = (int)dynamic["selectedIndex"].getDouble();
|
|
auto hierarchy = dynamic["hierarchy"];
|
|
for (size_t i = 0; i < hierarchy.size(); i++) {
|
|
result.hierarchy.push_back(hierarchy[i]["name"].c_str());
|
|
}
|
|
return result;
|
|
});
|
|
}
|
|
|
|
void Scheduler::unregisterSurface(
|
|
SurfaceHandler const &surfaceHandler) const noexcept {
|
|
surfaceHandler.setUIManager(nullptr);
|
|
}
|
|
|
|
void Scheduler::renderTemplateToSurface(
|
|
SurfaceId surfaceId,
|
|
const std::string &uiTemplate) {
|
|
SystraceSection s("Scheduler::renderTemplateToSurface");
|
|
try {
|
|
if (uiTemplate.empty()) {
|
|
return;
|
|
}
|
|
NativeModuleRegistry nMR;
|
|
auto tree = UITemplateProcessor::buildShadowTree(
|
|
uiTemplate,
|
|
surfaceId,
|
|
folly::dynamic::object(),
|
|
*componentDescriptorRegistry_,
|
|
nMR,
|
|
reactNativeConfig_);
|
|
|
|
uiManager_->getShadowTreeRegistry().visit(
|
|
surfaceId, [=](const ShadowTree &shadowTree) {
|
|
return shadowTree.tryCommit(
|
|
[&](RootShadowNode const &oldRootShadowNode) {
|
|
return std::make_shared<RootShadowNode>(
|
|
oldRootShadowNode,
|
|
ShadowNodeFragment{
|
|
/* .props = */ ShadowNodeFragment::propsPlaceholder(),
|
|
/* .children = */
|
|
std::make_shared<SharedShadowNodeList>(
|
|
SharedShadowNodeList{tree}),
|
|
});
|
|
});
|
|
});
|
|
} catch (const std::exception &e) {
|
|
LOG(ERROR) << " >>>> EXCEPTION <<< rendering uiTemplate in "
|
|
<< "Scheduler::renderTemplateToSurface: " << e.what();
|
|
}
|
|
}
|
|
|
|
ComponentDescriptor const *
|
|
Scheduler::findComponentDescriptorByHandle_DO_NOT_USE_THIS_IS_BROKEN(
|
|
ComponentHandle handle) const {
|
|
return componentDescriptorRegistry_
|
|
->findComponentDescriptorByHandle_DO_NOT_USE_THIS_IS_BROKEN(handle);
|
|
}
|
|
|
|
#pragma mark - Delegate
|
|
|
|
void Scheduler::setDelegate(SchedulerDelegate *delegate) {
|
|
delegate_ = delegate;
|
|
}
|
|
|
|
SchedulerDelegate *Scheduler::getDelegate() const {
|
|
return delegate_;
|
|
}
|
|
|
|
#pragma mark - UIManagerAnimationDelegate
|
|
|
|
void Scheduler::animationTick() const {
|
|
uiManager_->animationTick();
|
|
}
|
|
|
|
#pragma mark - UIManagerDelegate
|
|
|
|
void Scheduler::uiManagerDidFinishTransaction(
|
|
MountingCoordinator::Shared const &mountingCoordinator) {
|
|
SystraceSection s("Scheduler::uiManagerDidFinishTransaction");
|
|
|
|
if (delegate_) {
|
|
delegate_->schedulerDidFinishTransaction(mountingCoordinator);
|
|
}
|
|
}
|
|
void Scheduler::uiManagerDidCreateShadowNode(const ShadowNode &shadowNode) {
|
|
SystraceSection s("Scheduler::uiManagerDidCreateShadowNode");
|
|
|
|
if (delegate_) {
|
|
delegate_->schedulerDidRequestPreliminaryViewAllocation(
|
|
shadowNode.getSurfaceId(), shadowNode);
|
|
}
|
|
}
|
|
|
|
void Scheduler::uiManagerDidCloneShadowNode(
|
|
const ShadowNode &oldShadowNode,
|
|
const ShadowNode &newShadowNode) {
|
|
SystraceSection s("Scheduler::uiManagerDidCloneShadowNode");
|
|
|
|
if (delegate_) {
|
|
delegate_->schedulerDidCloneShadowNode(
|
|
newShadowNode.getSurfaceId(), oldShadowNode, newShadowNode);
|
|
}
|
|
}
|
|
|
|
void Scheduler::uiManagerDidDispatchCommand(
|
|
const ShadowNode::Shared &shadowNode,
|
|
std::string const &commandName,
|
|
folly::dynamic const args) {
|
|
SystraceSection s("Scheduler::uiManagerDispatchCommand");
|
|
|
|
if (delegate_) {
|
|
auto shadowView = ShadowView(*shadowNode);
|
|
delegate_->schedulerDidDispatchCommand(shadowView, commandName, args);
|
|
}
|
|
}
|
|
|
|
void Scheduler::uiManagerDidSendAccessibilityEvent(
|
|
const ShadowNode::Shared &shadowNode,
|
|
std::string const &eventType) {
|
|
SystraceSection s("Scheduler::uiManagerDidSendAccessibilityEvent");
|
|
|
|
if (delegate_) {
|
|
auto shadowView = ShadowView(*shadowNode);
|
|
delegate_->schedulerDidSendAccessibilityEvent(shadowView, eventType);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Set JS responder for a view.
|
|
*/
|
|
void Scheduler::uiManagerDidSetIsJSResponder(
|
|
ShadowNode::Shared const &shadowNode,
|
|
bool isJSResponder,
|
|
bool blockNativeResponder) {
|
|
if (delegate_) {
|
|
delegate_->schedulerDidSetIsJSResponder(
|
|
ShadowView(*shadowNode), isJSResponder, blockNativeResponder);
|
|
}
|
|
}
|
|
|
|
ContextContainer::Shared Scheduler::getContextContainer() const {
|
|
return contextContainer_;
|
|
}
|
|
|
|
} // namespace react
|
|
} // namespace facebook
|