From 025b6a74c8bcdb2c71222a370aa6281e342b0862 Mon Sep 17 00:00:00 2001 From: Valentin Shergin Date: Wed, 6 May 2020 18:25:58 -0700 Subject: [PATCH] Fabric: Cross-platform implementation of `SynchronousEventBeat` and `AsynchronousEventBeat` Summary: `SynchronousEventBeat` and `AsynchronousEventBeat` are a cross-platform re-implementation of run loop related parts of `MainRunLoopEventBeat` and `RuntimeEventBeat` (iOS specific classes for now). In the future, they will replace iOS- and Android-specifc event beat classes. Changelog: [Internal] Fabric-specific internal change. Reviewed By: sammy-SC Differential Revision: D21341996 fbshipit-source-id: 8eda9a5df537cd666b7728e32212a8bb5ddb3ab7 --- .../scheduler/AsynchronousEventBeat.cpp | 55 +++++++++++++++++++ .../fabric/scheduler/AsynchronousEventBeat.h | 41 ++++++++++++++ .../fabric/scheduler/SynchronousEventBeat.cpp | 50 +++++++++++++++++ .../fabric/scheduler/SynchronousEventBeat.h | 44 +++++++++++++++ 4 files changed, 190 insertions(+) create mode 100644 ReactCommon/fabric/scheduler/AsynchronousEventBeat.cpp create mode 100644 ReactCommon/fabric/scheduler/AsynchronousEventBeat.h create mode 100644 ReactCommon/fabric/scheduler/SynchronousEventBeat.cpp create mode 100644 ReactCommon/fabric/scheduler/SynchronousEventBeat.h diff --git a/ReactCommon/fabric/scheduler/AsynchronousEventBeat.cpp b/ReactCommon/fabric/scheduler/AsynchronousEventBeat.cpp new file mode 100644 index 00000000000..2649227d327 --- /dev/null +++ b/ReactCommon/fabric/scheduler/AsynchronousEventBeat.cpp @@ -0,0 +1,55 @@ +/* + * 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 "AsynchronousEventBeat.h" + +namespace facebook { +namespace react { + +AsynchronousEventBeat::AsynchronousEventBeat( + RunLoopObserver::Unique uiRunLoopObserver, + RuntimeExecutor runtimeExecutor) + : EventBeat({}), + uiRunLoopObserver_(std::move(uiRunLoopObserver)), + runtimeExecutor_(std::move(runtimeExecutor)) { + uiRunLoopObserver_->setDelegate(this); + uiRunLoopObserver_->enable(); +} + +void AsynchronousEventBeat::activityDidChange( + RunLoopObserver::Delegate const *delegate, + RunLoopObserver::Activity activity) const noexcept { + assert(delegate == this); + induce(); +} + +void AsynchronousEventBeat::induce() const { + if (!isRequested_) { + return; + } + + // Here we know that `this` object exists because the caller has a strong + // pointer to `owner`. To ensure the object will exist inside + // `runtimeExecutor_` callback, we need to copy the pointer there. + auto weakOwner = uiRunLoopObserver_->getOwner(); + + runtimeExecutor_([this, weakOwner](jsi::Runtime &runtime) mutable { + auto owner = weakOwner.lock(); + if (!owner) { + return; + } + + if (!isRequested_) { + return; + } + + this->beat(runtime); + }); +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/scheduler/AsynchronousEventBeat.h b/ReactCommon/fabric/scheduler/AsynchronousEventBeat.h new file mode 100644 index 00000000000..6e95c9b8f79 --- /dev/null +++ b/ReactCommon/fabric/scheduler/AsynchronousEventBeat.h @@ -0,0 +1,41 @@ +/* + * 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 +#include +#include + +namespace facebook { +namespace react { + +/* + * Event beat associated with JavaScript runtime. + * The beat is called on `RuntimeExecutor`'s thread induced by the UI thread + * event loop. + */ +class AsynchronousEventBeat : public EventBeat, + public RunLoopObserver::Delegate { + public: + AsynchronousEventBeat( + RunLoopObserver::Unique uiRunLoopObserver, + RuntimeExecutor runtimeExecutor); + + void induce() const override; + +#pragma mark - RunLoopObserver::Delegate + + void activityDidChange( + RunLoopObserver::Delegate const *delegate, + RunLoopObserver::Activity activity) const noexcept override; + + private: + RunLoopObserver::Unique uiRunLoopObserver_; + RuntimeExecutor runtimeExecutor_; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/scheduler/SynchronousEventBeat.cpp b/ReactCommon/fabric/scheduler/SynchronousEventBeat.cpp new file mode 100644 index 00000000000..a30552eb51c --- /dev/null +++ b/ReactCommon/fabric/scheduler/SynchronousEventBeat.cpp @@ -0,0 +1,50 @@ +/* + * 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 "SynchronousEventBeat.h" + +namespace facebook { +namespace react { + +SynchronousEventBeat::SynchronousEventBeat( + RunLoopObserver::Unique uiRunLoopObserver, + RuntimeExecutor runtimeExecutor) + : EventBeat({}), + uiRunLoopObserver_(std::move(uiRunLoopObserver)), + runtimeExecutor_(std::move(runtimeExecutor)) { + uiRunLoopObserver_->setDelegate(this); + uiRunLoopObserver_->enable(); +} + +void SynchronousEventBeat::activityDidChange( + RunLoopObserver::Delegate const *delegate, + RunLoopObserver::Activity activity) const noexcept { + assert(delegate == this); + lockExecutorAndBeat(); +} + +void SynchronousEventBeat::induce() const { + if (!this->isRequested_) { + return; + } + + if (uiRunLoopObserver_->isOnRunLoopThread()) { + this->lockExecutorAndBeat(); + } +} + +void SynchronousEventBeat::lockExecutorAndBeat() const { + if (!this->isRequested_) { + return; + } + + executeSynchronouslyOnSameThread_CAN_DEADLOCK( + runtimeExecutor_, [this](jsi::Runtime &runtime) { beat(runtime); }); +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/scheduler/SynchronousEventBeat.h b/ReactCommon/fabric/scheduler/SynchronousEventBeat.h new file mode 100644 index 00000000000..bf192e9078a --- /dev/null +++ b/ReactCommon/fabric/scheduler/SynchronousEventBeat.h @@ -0,0 +1,44 @@ +/* + * 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 + +namespace facebook { +namespace react { + +/* + * Event beat associated with main run loop. + * The callback is always called on the main thread. + */ +class SynchronousEventBeat final : public EventBeat, + public RunLoopObserver::Delegate { + public: + SynchronousEventBeat( + RunLoopObserver::Unique uiRunLoopObserver, + RuntimeExecutor runtimeExecutor); + + void induce() const override; + +#pragma mark - RunLoopObserver::Delegate + + void activityDidChange( + RunLoopObserver::Delegate const *delegate, + RunLoopObserver::Activity activity) const noexcept override; + + private: + void lockExecutorAndBeat() const; + + RunLoopObserver::Unique uiRunLoopObserver_; + RuntimeExecutor runtimeExecutor_; +}; + +} // namespace react +} // namespace facebook