345 lines
11 KiB
C++
345 lines
11 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/core/LayoutContext.h>
|
|
#include <react/debug/SystraceSection.h>
|
|
#include <react/uimanager/ComponentDescriptorRegistry.h>
|
|
#include <react/uimanager/UIManager.h>
|
|
#include <react/uimanager/UIManagerBinding.h>
|
|
#include <react/uimanager/UITemplateProcessor.h>
|
|
|
|
namespace facebook {
|
|
namespace react {
|
|
|
|
Scheduler::Scheduler(
|
|
SchedulerToolbox schedulerToolbox,
|
|
SchedulerDelegate *delegate) {
|
|
runtimeExecutor_ = schedulerToolbox.runtimeExecutor;
|
|
|
|
reactNativeConfig_ =
|
|
schedulerToolbox.contextContainer
|
|
->at<std::shared_ptr<const ReactNativeConfig>>("ReactNativeConfig");
|
|
|
|
auto uiManager = std::make_shared<UIManager>();
|
|
auto eventOwnerBox = std::make_shared<EventBeat::OwnerBox>();
|
|
|
|
auto eventPipe = [uiManager](
|
|
jsi::Runtime &runtime,
|
|
const EventTarget *eventTarget,
|
|
const std::string &type,
|
|
const ValueFactory &payloadFactory) {
|
|
uiManager->visitBinding([&](UIManagerBinding const &uiManagerBinding) {
|
|
uiManagerBinding.dispatchEvent(
|
|
runtime, eventTarget, type, payloadFactory);
|
|
});
|
|
};
|
|
|
|
auto statePipe = [uiManager](StateUpdate const &stateUpdate) {
|
|
uiManager->updateState(stateUpdate);
|
|
};
|
|
|
|
eventDispatcher_ = std::make_shared<EventDispatcher>(
|
|
eventPipe,
|
|
statePipe,
|
|
schedulerToolbox.synchronousEventBeatFactory,
|
|
schedulerToolbox.asynchronousEventBeatFactory,
|
|
eventOwnerBox);
|
|
|
|
eventOwnerBox->owner = eventDispatcher_;
|
|
|
|
componentDescriptorRegistry_ = schedulerToolbox.componentRegistryFactory(
|
|
eventDispatcher_, schedulerToolbox.contextContainer);
|
|
|
|
rootComponentDescriptor_ = std::make_unique<const RootComponentDescriptor>(
|
|
ComponentDescriptorParameters{eventDispatcher_, nullptr, nullptr});
|
|
|
|
uiManager->setDelegate(this);
|
|
uiManager->setComponentDescriptorRegistry(componentDescriptorRegistry_);
|
|
|
|
runtimeExecutor_([=](jsi::Runtime &runtime) {
|
|
auto uiManagerBinding = UIManagerBinding::createAndInstallIfNeeded(runtime);
|
|
uiManagerBinding->attach(uiManager);
|
|
});
|
|
|
|
auto componentDescriptorRegistryKey =
|
|
"ComponentDescriptorRegistry_DO_NOT_USE_PRETTY_PLEASE";
|
|
schedulerToolbox.contextContainer->erase(componentDescriptorRegistryKey);
|
|
schedulerToolbox.contextContainer->insert(
|
|
componentDescriptorRegistryKey,
|
|
std::weak_ptr<ComponentDescriptorRegistry const>(
|
|
componentDescriptorRegistry_));
|
|
|
|
delegate_ = delegate;
|
|
uiManager_ = uiManager;
|
|
}
|
|
|
|
Scheduler::~Scheduler() {
|
|
LOG(WARNING) << "Scheduler::~Scheduler() was called (address: " << this
|
|
<< ").";
|
|
|
|
// 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);
|
|
|
|
// 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());
|
|
});
|
|
|
|
assert(
|
|
surfaceIds.size() == 0 &&
|
|
"Scheduler was destroyed with outstanding Surfaces.");
|
|
|
|
if (surfaceIds.size() == 0) {
|
|
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(); });
|
|
}
|
|
}
|
|
|
|
void Scheduler::startSurface(
|
|
SurfaceId surfaceId,
|
|
const std::string &moduleName,
|
|
const folly::dynamic &initialProps,
|
|
const LayoutConstraints &layoutConstraints,
|
|
const LayoutContext &layoutContext) const {
|
|
SystraceSection s("Scheduler::startSurface");
|
|
|
|
auto shadowTree = std::make_unique<ShadowTree>(
|
|
surfaceId,
|
|
layoutConstraints,
|
|
layoutContext,
|
|
*rootComponentDescriptor_,
|
|
*uiManager_);
|
|
|
|
auto uiManager = uiManager_;
|
|
|
|
uiManager->getShadowTreeRegistry().add(std::move(shadowTree));
|
|
|
|
runtimeExecutor_([=](jsi::Runtime &runtime) {
|
|
uiManager->visitBinding([&](UIManagerBinding const &uiManagerBinding) {
|
|
uiManagerBinding.startSurface(
|
|
runtime, surfaceId, moduleName, initialProps);
|
|
});
|
|
});
|
|
}
|
|
|
|
void Scheduler::renderTemplateToSurface(
|
|
SurfaceId surfaceId,
|
|
const std::string &uiTemplate) {
|
|
SystraceSection s("Scheduler::renderTemplateToSurface");
|
|
try {
|
|
if (uiTemplate.size() == 0) {
|
|
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::Shared 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();
|
|
}
|
|
}
|
|
|
|
void Scheduler::stopSurface(SurfaceId surfaceId) const {
|
|
SystraceSection s("Scheduler::stopSurface");
|
|
|
|
// Note, we have to do in inside `visit` function while the Shadow Tree
|
|
// is still being registered.
|
|
uiManager_->getShadowTreeRegistry().visit(
|
|
surfaceId, [](ShadowTree const &shadowTree) {
|
|
// As part of stopping a Surface, we need to properly destroy all
|
|
// mounted views, so we need to commit an empty tree to trigger all
|
|
// side-effects that will perform that.
|
|
shadowTree.commitEmptyTree();
|
|
});
|
|
|
|
// Waiting for all concurrent commits to be finished and unregistering the
|
|
// `ShadowTree`.
|
|
uiManager_->getShadowTreeRegistry().remove(surfaceId);
|
|
|
|
// We execute JavaScript/React part of the process at the very end to minimize
|
|
// any visible side-effects of stopping the Surface. Any possible commits from
|
|
// the JavaScript side will not be able to reference a `ShadowTree` and will
|
|
// fail silently.
|
|
auto uiManager = uiManager_;
|
|
runtimeExecutor_([=](jsi::Runtime &runtime) {
|
|
uiManager->visitBinding([&](UIManagerBinding const &uiManagerBinding) {
|
|
uiManagerBinding.stopSurface(runtime, surfaceId);
|
|
});
|
|
});
|
|
}
|
|
|
|
Size Scheduler::measureSurface(
|
|
SurfaceId surfaceId,
|
|
const LayoutConstraints &layoutConstraints,
|
|
const LayoutContext &layoutContext) const {
|
|
SystraceSection s("Scheduler::measureSurface");
|
|
|
|
Size size;
|
|
uiManager_->getShadowTreeRegistry().visit(
|
|
surfaceId, [&](const ShadowTree &shadowTree) {
|
|
shadowTree.tryCommit(
|
|
[&](RootShadowNode::Shared const &oldRootShadowNode) {
|
|
auto rootShadowNode =
|
|
oldRootShadowNode->clone(layoutConstraints, layoutContext);
|
|
rootShadowNode->layoutIfNeeded();
|
|
size = rootShadowNode->getLayoutMetrics().frame.size;
|
|
return nullptr;
|
|
});
|
|
});
|
|
return size;
|
|
}
|
|
|
|
MountingCoordinator::Shared Scheduler::findMountingCoordinator(
|
|
SurfaceId surfaceId) const {
|
|
MountingCoordinator::Shared mountingCoordinator = nullptr;
|
|
uiManager_->getShadowTreeRegistry().visit(
|
|
surfaceId, [&](const ShadowTree &shadowTree) {
|
|
mountingCoordinator = shadowTree.getMountingCoordinator();
|
|
});
|
|
return mountingCoordinator;
|
|
}
|
|
|
|
void Scheduler::constraintSurfaceLayout(
|
|
SurfaceId surfaceId,
|
|
const LayoutConstraints &layoutConstraints,
|
|
const LayoutContext &layoutContext) const {
|
|
SystraceSection s("Scheduler::constraintSurfaceLayout");
|
|
|
|
uiManager_->getShadowTreeRegistry().visit(
|
|
surfaceId, [&](ShadowTree const &shadowTree) {
|
|
shadowTree.commit([&](RootShadowNode::Shared const &oldRootShadowNode) {
|
|
return oldRootShadowNode->clone(layoutConstraints, layoutContext);
|
|
});
|
|
});
|
|
}
|
|
|
|
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 - UIManagerDelegate
|
|
|
|
void Scheduler::uiManagerDidFinishTransaction(
|
|
MountingCoordinator::Shared const &mountingCoordinator) {
|
|
SystraceSection s("Scheduler::uiManagerDidFinishTransaction");
|
|
|
|
if (delegate_) {
|
|
delegate_->schedulerDidFinishTransaction(mountingCoordinator);
|
|
}
|
|
}
|
|
|
|
void Scheduler::uiManagerDidCreateShadowNode(
|
|
const ShadowNode::Shared &shadowNode) {
|
|
SystraceSection s("Scheduler::uiManagerDidCreateShadowNode");
|
|
|
|
if (delegate_) {
|
|
auto shadowView = ShadowView(*shadowNode);
|
|
delegate_->schedulerDidRequestPreliminaryViewAllocation(
|
|
shadowNode->getSurfaceId(), shadowView);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Set JS responder for a view
|
|
*/
|
|
void Scheduler::uiManagerDidSetJSResponder(
|
|
SurfaceId surfaceId,
|
|
const ShadowNode::Shared &shadowNode,
|
|
bool blockNativeResponder) {
|
|
if (delegate_) {
|
|
// TODO: the first shadowView paramenter, should be the first parent that
|
|
// is non virtual.
|
|
auto shadowView = ShadowView(*shadowNode);
|
|
delegate_->schedulerDidSetJSResponder(
|
|
surfaceId, shadowView, shadowView, blockNativeResponder);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Clear the JSResponder for a view
|
|
*/
|
|
void Scheduler::uiManagerDidClearJSResponder() {
|
|
if (delegate_) {
|
|
delegate_->schedulerDidClearJSResponder();
|
|
}
|
|
}
|
|
|
|
} // namespace react
|
|
} // namespace facebook
|