diff --git a/ReactCommon/react/renderer/scheduler/Scheduler.cpp b/ReactCommon/react/renderer/scheduler/Scheduler.cpp index 25a73923827..6fae548a012 100644 --- a/ReactCommon/react/renderer/scheduler/Scheduler.cpp +++ b/ReactCommon/react/renderer/scheduler/Scheduler.cpp @@ -81,6 +81,7 @@ Scheduler::Scheduler( uiManager->setBackgroundExecutor(schedulerToolbox.backgroundExecutor); uiManager->setDelegate(this); + uiManager->setRuntimeExecutor(runtimeExecutor_); uiManager->setComponentDescriptorRegistry(componentDescriptorRegistry_); runtimeExecutor_([=](jsi::Runtime &runtime) { @@ -179,6 +180,16 @@ Scheduler::~Scheduler() { } } +void Scheduler::registerSurface( + SurfaceHandler const &surfaceHandler) const noexcept { + surfaceHandler.setUIManager(uiManager_.get()); +} + +void Scheduler::unregisterSurface( + SurfaceHandler const &surfaceHandler) const noexcept { + surfaceHandler.setUIManager(nullptr); +} + void Scheduler::startSurface( SurfaceId surfaceId, const std::string &moduleName, diff --git a/ReactCommon/react/renderer/scheduler/Scheduler.h b/ReactCommon/react/renderer/scheduler/Scheduler.h index 2d31b39ef19..5d0da01eee8 100644 --- a/ReactCommon/react/renderer/scheduler/Scheduler.h +++ b/ReactCommon/react/renderer/scheduler/Scheduler.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -40,6 +41,14 @@ class Scheduler final : public UIManagerDelegate { #pragma mark - Surface Management + /* + * Registers and unregisters a `SurfaceHandler` object in the `Scheduler`. + * All registered `SurfaceHandler` objects must be unregistered + * (with the same `Scheduler`) before their deallocation. + */ + void registerSurface(SurfaceHandler const &surfaceHandler) const noexcept; + void unregisterSurface(SurfaceHandler const &surfaceHandler) const noexcept; + void startSurface( SurfaceId surfaceId, const std::string &moduleName, @@ -73,7 +82,7 @@ class Scheduler final : public UIManagerDelegate { /* * This is broken. Please do not use. * `ComponentDescriptor`s are not designed to be used outside of `UIManager`, - * there is no any garantees about their lifetime. + * there is no any guarantees about their lifetime. */ ComponentDescriptor const * findComponentDescriptorByHandle_DO_NOT_USE_THIS_IS_BROKEN( @@ -119,6 +128,8 @@ class Scheduler final : public UIManagerDelegate { void uiManagerDidClearJSResponder() override; private: + friend class SurfaceHandler; + SchedulerDelegate *delegate_; SharedComponentDescriptorRegistry componentDescriptorRegistry_; RuntimeExecutor runtimeExecutor_; diff --git a/ReactCommon/react/renderer/scheduler/SurfaceHandler.cpp b/ReactCommon/react/renderer/scheduler/SurfaceHandler.cpp new file mode 100644 index 00000000000..b805dc54f02 --- /dev/null +++ b/ReactCommon/react/renderer/scheduler/SurfaceHandler.cpp @@ -0,0 +1,256 @@ +/* + * 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 "SurfaceHandler.h" + +#include +#include + +namespace facebook { +namespace react { + +using Status = SurfaceHandler::Status; +using DisplayMode = SurfaceHandler::DisplayMode; + +SurfaceHandler::SurfaceHandler( + std::string const &moduleName, + SurfaceId surfaceId) noexcept { + parameters_.moduleName = moduleName; + parameters_.surfaceId = surfaceId; +} + +SurfaceHandler::SurfaceHandler(SurfaceHandler &&other) noexcept { + operator=(std::move(other)); +} + +SurfaceHandler &SurfaceHandler::operator=(SurfaceHandler &&other) noexcept { + std::unique_lock lock1(linkMutex_, std::defer_lock); + std::unique_lock lock2( + parametersMutex_, std::defer_lock); + std::unique_lock lock3( + other.linkMutex_, std::defer_lock); + std::unique_lock lock4( + other.parametersMutex_, std::defer_lock); + std::lock(lock1, lock2, lock3, lock4); + + link_ = other.link_; + parameters_ = other.parameters_; + + other.link_ = Link{}; + other.parameters_ = Parameters{}; + return *this; +} + +#pragma mark - Surface Life-Cycle Management + +Status SurfaceHandler::getStatus() const noexcept { + std::shared_lock lock(linkMutex_); + return link_.status; +} + +void SurfaceHandler::start() const noexcept { + { + std::unique_lock lock(linkMutex_); + assert(link_.status == Status::Registered && "Surface must be registered."); + + auto parameters = Parameters{}; + { + std::shared_lock parametersLock(parametersMutex_); + parameters = parameters_; + } + + link_.shadowTree = &link_.uiManager->startSurface( + parameters.surfaceId, + parameters.moduleName, + parameters.props, + parameters.layoutConstraints, + parameters.layoutContext); + + link_.status = Status::Running; + + applyDisplayMode(parameters.displayMode); + } +} + +void SurfaceHandler::stop() const noexcept { + std::unique_lock lock(linkMutex_); + assert(link_.status == Status::Running && "Surface must be running."); + + link_.status = Status::Registered; + link_.shadowTree = nullptr; + link_.uiManager->stopSurface(parameters_.surfaceId); +} + +void SurfaceHandler::setDisplayMode(DisplayMode displayMode) const noexcept { + { + std::unique_lock lock(parametersMutex_); + if (parameters_.displayMode == displayMode) { + return; + } + + parameters_.displayMode = displayMode; + } + + { + std::shared_lock lock(linkMutex_); + + if (link_.status != Status::Running) { + return; + } + + applyDisplayMode(displayMode); + } +} + +DisplayMode SurfaceHandler::getDisplayMode() const noexcept { + std::shared_lock lock(parametersMutex_); + return parameters_.displayMode; +} + +#pragma mark - Accessors + +SurfaceId SurfaceHandler::getSurfaceId() const noexcept { + std::shared_lock lock(parametersMutex_); + return parameters_.surfaceId; +} + +std::string SurfaceHandler::getModuleName() const noexcept { + std::shared_lock lock(parametersMutex_); + return parameters_.moduleName; +} + +void SurfaceHandler::setProps(folly::dynamic const &props) const noexcept { + std::unique_lock lock(parametersMutex_); + parameters_.props = props; +} + +folly::dynamic SurfaceHandler::getProps() const noexcept { + std::shared_lock lock(parametersMutex_); + return parameters_.props; +} + +std::shared_ptr +SurfaceHandler::getMountingCoordinator() const noexcept { + std::shared_lock lock(linkMutex_); + assert(link_.status != Status::Unregistered && "Surface must be registered."); + assert(link_.shadowTree && "`link_.shadowTree` must not be null."); + return link_.shadowTree->getMountingCoordinator(); +} + +#pragma mark - Layout + +Size SurfaceHandler::measure( + LayoutConstraints const &layoutConstraints, + LayoutContext const &layoutContext) const noexcept { + std::shared_lock lock(linkMutex_); + + if (link_.status != Status::Running) { + return layoutConstraints.clamp({0, 0}); + } + + assert(link_.shadowTree && "`link_.shadowTree` must not be null."); + + auto currentRootShadowNode = + link_.shadowTree->getCurrentRevision().rootShadowNode; + + auto rootShadowNode = + currentRootShadowNode->clone(layoutConstraints, layoutContext); + rootShadowNode->layoutIfNeeded(); + return rootShadowNode->getLayoutMetrics().frame.size; +} + +void SurfaceHandler::constraintLayout( + LayoutConstraints const &layoutConstraints, + LayoutContext const &layoutContext) const noexcept { + { + std::unique_lock lock(parametersMutex_); + + if (parameters_.layoutConstraints == layoutConstraints && + parameters_.layoutContext == layoutContext) { + return; + } + + parameters_.layoutConstraints = layoutConstraints; + parameters_.layoutContext = layoutContext; + } + + { + std::shared_lock lock(linkMutex_); + + if (link_.status != Status::Running) { + return; + } + + assert(link_.shadowTree && "`link_.shadowTree` must not be null."); + link_.shadowTree->commit([&](RootShadowNode const &oldRootShadowNode) { + return oldRootShadowNode.clone(layoutConstraints, layoutContext); + }); + } +} + +LayoutConstraints SurfaceHandler::getLayoutConstraints() const noexcept { + std::shared_lock lock(parametersMutex_); + return parameters_.layoutConstraints; +} + +LayoutContext SurfaceHandler::getLayoutContext() const noexcept { + std::shared_lock lock(parametersMutex_); + return parameters_.layoutContext; +} + +#pragma mark - Private + +void SurfaceHandler::applyDisplayMode(DisplayMode displayMode) const noexcept { + assert(link_.status == Status::Running && "Surface must be running."); + assert(link_.shadowTree && "`link_.shadowTree` must not be null."); + + switch (displayMode) { + case DisplayMode::Visible: + link_.shadowTree->setCommitMode(ShadowTree::CommitMode::Normal); + break; + case DisplayMode::Suspended: + link_.shadowTree->setCommitMode(ShadowTree::CommitMode::Suspended); + break; + case DisplayMode::Hidden: + link_.shadowTree->setCommitMode(ShadowTree::CommitMode::Normal); + // Getting a current revision. + auto revision = link_.shadowTree->getCurrentRevision(); + // Committing an empty tree to force mounting to disassemble view + // hierarchy. + link_.shadowTree->commitEmptyTree(); + link_.shadowTree->setCommitMode(ShadowTree::CommitMode::Suspended); + // Committing the current revision back. It will be mounted only when + // `DisplayMode` is changed back to `Normal`. + link_.shadowTree->commit([&](RootShadowNode const &oldRootShadowNode) { + return std::static_pointer_cast( + revision.rootShadowNode->ShadowNode::clone(ShadowNodeFragment{})); + }); + break; + } +} + +void SurfaceHandler::setUIManager(UIManager const *uiManager) const noexcept { + std::unique_lock lock(linkMutex_); + + assert(link_.status != Status::Running && "Surface must not be running."); + + if (link_.uiManager == uiManager) { + return; + } + + link_.uiManager = uiManager; + link_.status = uiManager ? Status::Registered : Status::Unregistered; +} + +SurfaceHandler::~SurfaceHandler() noexcept { + assert( + link_.status == Status::Unregistered && + "`SurfaceHandler` must be unregistered (or moved-from) before deallocation."); +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/react/renderer/scheduler/SurfaceHandler.h b/ReactCommon/react/renderer/scheduler/SurfaceHandler.h new file mode 100644 index 00000000000..4867608ddfc --- /dev/null +++ b/ReactCommon/react/renderer/scheduler/SurfaceHandler.h @@ -0,0 +1,234 @@ +/* + * 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. + */ + +#pragma once + +#include + +#include +#include +#include + +namespace facebook { +namespace react { + +class Scheduler; +class ShadowTree; +class MountingCoordinator; +class UIManager; + +/* + * Represents a running React Native surface and provides control over it. + * The instances of this class are movable only. + * The instances of this class can be safely deallocated only if `status` is + * `Unregistered`; this is a way to enforce internal consistency and + * deallocation ordering constraints the core relies on. + * + * + * Even though all methods of the class are thread-safe, the consumer side must + * ensure the logical consistency of some methods (e.g. calling `stop` for + * non-running surface will crash). + */ +class SurfaceHandler final { + public: + /* + * Represents a status of the `SurfaceHandler` instance. + */ + enum class Status { + /* + * Newly created, moved-from, or already-unregistered instances. The only + * state in which the object can be safely deallocated. + */ + Unregistered, + + /* + * Registered instances that have an internal reference to a `UIManager` + * instance and ready to start a surface. + */ + Registered, + + /* + * Registered and running instances. + */ + Running, + }; + + /* + * Defines how visual side effects (views, images, text, and so on) are + * mounted (on not) on the screen. + */ + enum class DisplayMode { + /* + * The surface is running normally. All visual side-effects will be rendered + * on the screen. + */ + Visible, + + /* + * The surface is `Suspended`. All new (committed after switching to the + * mode) visual side-effects will *not* be mounted on the screen (the screen + * will stop updating). + * + * The mode can be used for preparing a surface for possible future use. + * The surface will be prepared without spending computing resources + * on mounting, and then can be instantly mounted if needed. + */ + Suspended, + + /* + * The surface is `Hidden`. All previously mounted visual side-effects + * will be unmounted, and all new (committed after switching to the mode) + * visual side-effects will *not* be mounted on the screen until the mode is + * switched back to `normal`. + * + * The mode can be used for temporarily freeing computing resources of + * off-the-screen surfaces. + */ + Hidden, + }; + + /* + * Can be constructed anytime with a `moduleName` and a `surfaceId`. + */ + SurfaceHandler(std::string const &moduleName, SurfaceId surfaceId) noexcept; + ~SurfaceHandler() noexcept; + + /* + * Movable-only. + */ + SurfaceHandler(SurfaceHandler &&SurfaceHandler) noexcept; + SurfaceHandler(SurfaceHandler const &SurfaceHandler) noexcept = delete; + SurfaceHandler &operator=(SurfaceHandler &&other) noexcept; + SurfaceHandler &operator=(SurfaceHandler const &other) noexcept = delete; + +#pragma mark - Surface Life-Cycle Management + + /* + * Returns a momentum value of the status. + */ + Status getStatus() const noexcept; + + /* + * Starts or stops the surface. + * Can not be called when the status is `Unregistered`. + * `start()` must not be called for a running surface, and `stop()` must not + * be called for a not running surface. + */ + void start() const noexcept; + void stop() const noexcept; + + /* + * Sets (and gets) the runnnig mode. + * The running mode can be changed anytime (even for `Unregistered` surface). + */ + void setDisplayMode(DisplayMode displayMode) const noexcept; + DisplayMode getDisplayMode() const noexcept; + +#pragma mark - Accessors + + SurfaceId getSurfaceId() const noexcept; + std::string getModuleName() const noexcept; + + /* + * Provides access for surface props. + * Props can be changed anytime (even for `Unregistered` surface). + */ + void setProps(folly::dynamic const &props) const noexcept; + folly::dynamic getProps() const noexcept; + + /* + * Returns a `MountingCoordinator` instance associated with a running surface. + * Can be not be called when the status is `Unregistered`. + * The returning value cannot be `nullptr`. + */ + std::shared_ptr getMountingCoordinator() + const noexcept; + +#pragma mark - Layout + + /* + * Measures the surface with given layout constraints and layout context. + * Returns zero size if called on the stopped or unregistered surface. + */ + Size measure( + LayoutConstraints const &layoutConstraints, + LayoutContext const &layoutContext) const noexcept; + + /* + * Sets layout constraints and layout context for the surface. + */ + void constraintLayout( + LayoutConstraints const &layoutConstraints, + LayoutContext const &layoutContext) const noexcept; + + /* + * Returns layout constraints and layout context associated with the surface. + */ + LayoutConstraints getLayoutConstraints() const noexcept; + LayoutContext getLayoutContext() const noexcept; + + private: + friend class Scheduler; + + /* + * Must be called by `Scheduler` during registration process. + */ + void setUIManager(UIManager const *uiManager) const noexcept; + + void applyDisplayMode(DisplayMode displayMode) const noexcept; + +#pragma mark - Link & Parameters + + /* + * All data members of the class are split into two groups (`Link` and + * `Parameters`) that require separate synchronization. This way it's easier + * to see that proper lock is acquired. Separate synchronization is needed to + * prevent deadlocks. + */ + + /* + * Represents parameters of the surface. Parameters can be changed + * independently from controlling the running state + * (registering/unregistering, starting/stopping) of the surface. + * Changing parameters requires acquiring a unique lock; reading needs only + * a shared lock. + */ + struct Parameters { + std::string moduleName{}; + SurfaceId surfaceId{}; + DisplayMode displayMode{DisplayMode::Visible}; + folly::dynamic props{}; + LayoutConstraints layoutConstraints{}; + LayoutContext layoutContext{}; + }; + + /* + * Represents an underlying link to a `ShadowTree` and an `UIMananger`. + * Registering, unregistering, starting, and stopping the surface requires + * acquiring a unique lock; other access needs only a shared lock. + */ + struct Link { + Status status{Status::Unregistered}; + UIManager const *uiManager{}; + ShadowTree const *shadowTree{}; + }; + + /* + * `link_` and `linkMutex_` pair. + */ + mutable better::shared_mutex linkMutex_; + mutable Link link_; + + /* + * `parameters_` and `parametersMutex_` pair. + */ + mutable better::shared_mutex parametersMutex_; + mutable Parameters parameters_; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/react/renderer/uimanager/UIManager.cpp b/ReactCommon/react/renderer/uimanager/UIManager.cpp index 6b9924dfc5b..6b31bf8e990 100644 --- a/ReactCommon/react/renderer/uimanager/UIManager.cpp +++ b/ReactCommon/react/renderer/uimanager/UIManager.cpp @@ -127,6 +127,62 @@ void UIManager::clearJSResponder() const { } } +ShadowTree const &UIManager::startSurface( + SurfaceId surfaceId, + std::string const &moduleName, + folly::dynamic const &props, + LayoutConstraints const &layoutConstraints, + LayoutContext const &layoutContext) const { + SystraceSection s("UIManager::startSurface"); + + auto shadowTree = std::make_unique( + surfaceId, layoutConstraints, layoutContext, *this); + auto shadowTreePointer = shadowTree.get(); + shadowTreeRegistry_.add(std::move(shadowTree)); + + runtimeExecutor_([=](jsi::Runtime &runtime) { + auto uiManagerBinding = UIManagerBinding::getBinding(runtime); + if (!uiManagerBinding) { + return; + } + + uiManagerBinding->startSurface(runtime, surfaceId, moduleName, props); + }); + + return *shadowTreePointer; +} + +void UIManager::stopSurface(SurfaceId surfaceId) const { + SystraceSection s("UIManager::stopSurface"); + + // Stop any ongoing animations. + stopSurfaceForAnimationDelegate(surfaceId); + + // Waiting for all concurrent commits to be finished and unregistering the + // `ShadowTree`. + auto shadowTree = getShadowTreeRegistry().remove(surfaceId); + + // 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 (including destroying and removing mounted views). + if (shadowTree) { + shadowTree->commitEmptyTree(); + } + + // 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. + runtimeExecutor_([=](jsi::Runtime &runtime) { + auto uiManagerBinding = UIManagerBinding::getBinding(runtime); + if (!uiManagerBinding) { + return; + } + + uiManagerBinding->stopSurface(runtime, surfaceId); + }); +} + ShadowNode::Shared UIManager::getNewestCloneOfShadowNode( ShadowNode const &shadowNode) const { auto ancestorShadowNode = ShadowNode::Shared{}; @@ -279,6 +335,10 @@ void UIManager::setBackgroundExecutor( backgroundExecutor_ = backgroundExecutor; } +void UIManager::setRuntimeExecutor(RuntimeExecutor const &runtimeExecutor) { + runtimeExecutor_ = runtimeExecutor; +} + void UIManager::visitBinding( std::function callback, jsi::Runtime &runtime) const { @@ -354,7 +414,7 @@ void UIManager::setAnimationDelegate(UIManagerAnimationDelegate *delegate) { animationDelegate_ = delegate; } -void UIManager::stopSurfaceForAnimationDelegate(SurfaceId surfaceId) { +void UIManager::stopSurfaceForAnimationDelegate(SurfaceId surfaceId) const { if (animationDelegate_ != nullptr) { animationDelegate_->stopSurface(surfaceId); } diff --git a/ReactCommon/react/renderer/uimanager/UIManager.h b/ReactCommon/react/renderer/uimanager/UIManager.h index b4b5e908fdb..6dc8a44f968 100644 --- a/ReactCommon/react/renderer/uimanager/UIManager.h +++ b/ReactCommon/react/renderer/uimanager/UIManager.h @@ -10,6 +10,8 @@ #include #include +#include + #include #include #include @@ -42,6 +44,8 @@ class UIManager final : public ShadowTreeDelegate { void setDelegate(UIManagerDelegate *delegate); UIManagerDelegate *getDelegate(); + void setRuntimeExecutor(RuntimeExecutor const &runtimeExecutor); + void setBackgroundExecutor(BackgroundExecutor const &backgroundExecutor); /** @@ -54,7 +58,7 @@ class UIManager final : public ShadowTreeDelegate { /** * Execute stopSurface on any UIMAnagerAnimationDelegate. */ - void stopSurfaceForAnimationDelegate(SurfaceId surfaceId); + void stopSurfaceForAnimationDelegate(SurfaceId surfaceId) const; void animationTick(); @@ -77,6 +81,17 @@ class UIManager final : public ShadowTreeDelegate { ShadowNode::Shared getNewestCloneOfShadowNode( ShadowNode const &shadowNode) const; +#pragma mark - Surface Start & Stop + + ShadowTree const &startSurface( + SurfaceId surfaceId, + std::string const &moduleName, + folly::dynamic const &props, + LayoutConstraints const &layoutConstraints, + LayoutContext const &layoutContext) const; + + void stopSurface(SurfaceId surfaceId) const; + #pragma mark - ShadowTreeDelegate void shadowTreeDidFinishTransaction( @@ -91,6 +106,7 @@ class UIManager final : public ShadowTreeDelegate { private: friend class UIManagerBinding; friend class Scheduler; + friend class SurfaceHandler; ShadowNode::Shared createNode( Tag tag, @@ -164,6 +180,7 @@ class UIManager final : public ShadowTreeDelegate { UIManagerDelegate *delegate_; UIManagerAnimationDelegate *animationDelegate_{nullptr}; UIManagerBinding *uiManagerBinding_; + RuntimeExecutor runtimeExecutor_{}; ShadowTreeRegistry shadowTreeRegistry_{}; BackgroundExecutor backgroundExecutor_{}; diff --git a/packages/rn-tester/Podfile.lock b/packages/rn-tester/Podfile.lock index 6b5c99abc7d..3181e51504f 100644 --- a/packages/rn-tester/Podfile.lock +++ b/packages/rn-tester/Podfile.lock @@ -798,7 +798,7 @@ SPEC CHECKSUMS: CocoaLibEvent: 2fab71b8bd46dd33ddb959f7928ec5909f838e3f DoubleConversion: cde416483dac037923206447da6e1454df403714 FBLazyVector: 91e874a8823933a268c38765a88cbd5dba1fa024 - FBReactNativeSpec: f413828a0c0ca7fb738e67dd90281e454d353a46 + FBReactNativeSpec: 17a863c5e24969051850a3acab3a06069bb06e7f Flipper: be611d4b742d8c87fbae2ca5f44603a02539e365 Flipper-DoubleConversion: 38631e41ef4f9b12861c67d17cb5518d06badc41 Flipper-Folly: e4493b013c02d9347d5e0cb4d128680239f6c78a