diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.cpp b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.cpp index 8aeb9f9826721b..34aaeebc4b09eb 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.cpp +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.cpp @@ -6,6 +6,7 @@ */ #include "RuntimeScheduler.h" +#include "RuntimeScheduler_Legacy.h" #include "SchedulerPriorityUtils.h" #include @@ -14,177 +15,55 @@ namespace facebook::react { -#pragma mark - Public - RuntimeScheduler::RuntimeScheduler( RuntimeExecutor runtimeExecutor, std::function now) - : runtimeExecutor_(std::move(runtimeExecutor)), now_(std::move(now)) {} + : runtimeSchedulerImpl_(std::make_unique( + std::move(runtimeExecutor), + std::move(now))) {} void RuntimeScheduler::scheduleWork(RawCallback&& callback) const noexcept { - SystraceSection s("RuntimeScheduler::scheduleWork"); - - runtimeAccessRequests_ += 1; - - runtimeExecutor_( - [this, callback = std::move(callback)](jsi::Runtime& runtime) { - SystraceSection s2("RuntimeScheduler::scheduleWork callback"); - runtimeAccessRequests_ -= 1; - callback(runtime); - startWorkLoop(runtime); - }); + return runtimeSchedulerImpl_->scheduleWork(std::move(callback)); } std::shared_ptr RuntimeScheduler::scheduleTask( SchedulerPriority priority, jsi::Function&& callback) noexcept { - auto expirationTime = now_() + timeoutForSchedulerPriority(priority); - auto task = - std::make_shared(priority, std::move(callback), expirationTime); - taskQueue_.push(task); - - scheduleWorkLoopIfNecessary(); - - return task; + return runtimeSchedulerImpl_->scheduleTask(priority, std::move(callback)); } std::shared_ptr RuntimeScheduler::scheduleTask( SchedulerPriority priority, RawCallback&& callback) noexcept { - auto expirationTime = now_() + timeoutForSchedulerPriority(priority); - auto task = - std::make_shared(priority, std::move(callback), expirationTime); - taskQueue_.push(task); - - scheduleWorkLoopIfNecessary(); - - return task; + return runtimeSchedulerImpl_->scheduleTask(priority, std::move(callback)); } bool RuntimeScheduler::getShouldYield() const noexcept { - return runtimeAccessRequests_ > 0; + return runtimeSchedulerImpl_->getShouldYield(); } bool RuntimeScheduler::getIsSynchronous() const noexcept { - return isSynchronous_; + return runtimeSchedulerImpl_->getIsSynchronous(); } void RuntimeScheduler::cancelTask(Task& task) noexcept { - task.callback.reset(); + return runtimeSchedulerImpl_->cancelTask(task); } SchedulerPriority RuntimeScheduler::getCurrentPriorityLevel() const noexcept { - return currentPriority_; + return runtimeSchedulerImpl_->getCurrentPriorityLevel(); } RuntimeSchedulerTimePoint RuntimeScheduler::now() const noexcept { - return now_(); + return runtimeSchedulerImpl_->now(); } void RuntimeScheduler::executeNowOnTheSameThread(RawCallback&& callback) { - SystraceSection s("RuntimeScheduler::executeNowOnTheSameThread"); - - runtimeAccessRequests_ += 1; - executeSynchronouslyOnSameThread_CAN_DEADLOCK( - runtimeExecutor_, - [this, callback = std::move(callback)](jsi::Runtime& runtime) { - SystraceSection s2( - "RuntimeScheduler::executeNowOnTheSameThread callback"); - - runtimeAccessRequests_ -= 1; - isSynchronous_ = true; - callback(runtime); - isSynchronous_ = false; - }); - - // Resume work loop if needed. In synchronous mode - // only expired tasks are executed. Tasks with lower priority - // might be still in the queue. - scheduleWorkLoopIfNecessary(); + return runtimeSchedulerImpl_->executeNowOnTheSameThread(std::move(callback)); } void RuntimeScheduler::callExpiredTasks(jsi::Runtime& runtime) { - SystraceSection s("RuntimeScheduler::callExpiredTasks"); - - auto previousPriority = currentPriority_; - try { - while (!taskQueue_.empty()) { - auto topPriorityTask = taskQueue_.top(); - auto now = now_(); - auto didUserCallbackTimeout = topPriorityTask->expirationTime <= now; - - if (!didUserCallbackTimeout) { - break; - } - - executeTask(runtime, topPriorityTask, didUserCallbackTimeout); - } - } catch (jsi::JSError& error) { - handleFatalError(runtime, error); - } - - currentPriority_ = previousPriority; -} - -#pragma mark - Private - -void RuntimeScheduler::scheduleWorkLoopIfNecessary() const { - if (!isWorkLoopScheduled_ && !isPerformingWork_) { - isWorkLoopScheduled_ = true; - runtimeExecutor_([this](jsi::Runtime& runtime) { - isWorkLoopScheduled_ = false; - startWorkLoop(runtime); - }); - } -} - -void RuntimeScheduler::startWorkLoop(jsi::Runtime& runtime) const { - SystraceSection s("RuntimeScheduler::startWorkLoop"); - - auto previousPriority = currentPriority_; - isPerformingWork_ = true; - try { - while (!taskQueue_.empty()) { - auto topPriorityTask = taskQueue_.top(); - auto now = now_(); - auto didUserCallbackTimeout = topPriorityTask->expirationTime <= now; - - if (!didUserCallbackTimeout && getShouldYield()) { - // This currentTask hasn't expired, and we need to yield. - break; - } - - executeTask(runtime, topPriorityTask, didUserCallbackTimeout); - } - } catch (jsi::JSError& error) { - handleFatalError(runtime, error); - } - - currentPriority_ = previousPriority; - isPerformingWork_ = false; -} - -void RuntimeScheduler::executeTask( - jsi::Runtime& runtime, - const std::shared_ptr& task, - bool didUserCallbackTimeout) const { - SystraceSection s( - "RuntimeScheduler::executeTask", - "priority", - serialize(task->priority), - "didUserCallbackTimeout", - didUserCallbackTimeout); - - currentPriority_ = task->priority; - auto result = task->execute(runtime, didUserCallbackTimeout); - - if (result.isObject() && result.getObject(runtime).isFunction(runtime)) { - task->callback = result.getObject(runtime).getFunction(runtime); - } else { - if (taskQueue_.top() == task) { - taskQueue_.pop(); - } - } + return runtimeSchedulerImpl_->callExpiredTasks(runtime); } } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.h b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.h index fa760cbcad1b9d..d07d8f46889c1a 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.h +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.h @@ -10,18 +10,39 @@ #include #include #include -#include -#include -#include namespace facebook::react { -class RuntimeScheduler final { +// This is a temporary abstract class for RuntimeScheduler forks to implement +// (and use them interchangeably). +class RuntimeSchedulerBase { + public: + virtual ~RuntimeSchedulerBase() = default; + virtual void scheduleWork(RawCallback&& callback) const noexcept = 0; + virtual void executeNowOnTheSameThread(RawCallback&& callback) = 0; + virtual std::shared_ptr scheduleTask( + SchedulerPriority priority, + jsi::Function&& callback) noexcept = 0; + virtual std::shared_ptr scheduleTask( + SchedulerPriority priority, + RawCallback&& callback) noexcept = 0; + virtual void cancelTask(Task& task) noexcept = 0; + virtual bool getShouldYield() const noexcept = 0; + virtual bool getIsSynchronous() const noexcept = 0; + virtual SchedulerPriority getCurrentPriorityLevel() const noexcept = 0; + virtual RuntimeSchedulerTimePoint now() const noexcept = 0; + virtual void callExpiredTasks(jsi::Runtime& runtime) = 0; +}; + +// This is a proxy for RuntimeScheduler implementation, which will be selected +// at runtime based on a feature flag. +class RuntimeScheduler final : RuntimeSchedulerBase { public: RuntimeScheduler( RuntimeExecutor runtimeExecutor, std::function now = RuntimeSchedulerClock::now); + /* * Not copyable. */ @@ -34,7 +55,7 @@ class RuntimeScheduler final { RuntimeScheduler(RuntimeScheduler&&) = delete; RuntimeScheduler& operator=(RuntimeScheduler&&) = delete; - void scheduleWork(RawCallback&& callback) const noexcept; + void scheduleWork(RawCallback&& callback) const noexcept override; /* * Grants access to the runtime synchronously on the caller's thread. @@ -43,7 +64,7 @@ class RuntimeScheduler final { * by dispatching a synchronous event via event emitter in your native * component. */ - void executeNowOnTheSameThread(RawCallback&& callback); + void executeNowOnTheSameThread(RawCallback&& callback) override; /* * Adds a JavaScript callback to priority queue with given priority. @@ -53,11 +74,11 @@ class RuntimeScheduler final { */ std::shared_ptr scheduleTask( SchedulerPriority priority, - jsi::Function&& callback) noexcept; + jsi::Function&& callback) noexcept override; std::shared_ptr scheduleTask( SchedulerPriority priority, - RawCallback&& callback) noexcept; + RawCallback&& callback) noexcept override; /* * Cancelled task will never be executed. @@ -65,7 +86,7 @@ class RuntimeScheduler final { * Operates on JSI object. * Thread synchronization must be enforced externally. */ - void cancelTask(Task& task) noexcept; + void cancelTask(Task& task) noexcept override; /* * Return value indicates if host platform has a pending access to the @@ -73,7 +94,7 @@ class RuntimeScheduler final { * * Can be called from any thread. */ - bool getShouldYield() const noexcept; + bool getShouldYield() const noexcept override; /* * Return value informs if the current task is executed inside synchronous @@ -81,14 +102,14 @@ class RuntimeScheduler final { * * Can be called from any thread. */ - bool getIsSynchronous() const noexcept; + bool getIsSynchronous() const noexcept override; /* * Returns value of currently executed task. Designed to be called from React. * * Thread synchronization must be enforced externally. */ - SchedulerPriority getCurrentPriorityLevel() const noexcept; + SchedulerPriority getCurrentPriorityLevel() const noexcept override; /* * Returns current monotonic time. This time is not related to wall clock @@ -96,7 +117,7 @@ class RuntimeScheduler final { * * Thread synchronization must be enforced externally. */ - RuntimeSchedulerTimePoint now() const noexcept; + RuntimeSchedulerTimePoint now() const noexcept override; /* * Expired task is a task that should have been already executed. Designed to @@ -106,54 +127,12 @@ class RuntimeScheduler final { * * Thread synchronization must be enforced externally. */ - void callExpiredTasks(jsi::Runtime& runtime); + void callExpiredTasks(jsi::Runtime& runtime) override; private: - mutable std::priority_queue< - std::shared_ptr, - std::vector>, - TaskPriorityComparer> - taskQueue_; - - const RuntimeExecutor runtimeExecutor_; - mutable SchedulerPriority currentPriority_{SchedulerPriority::NormalPriority}; - - /* - * Counter indicating how many access to the runtime have been requested. - */ - mutable std::atomic runtimeAccessRequests_{0}; - - mutable std::atomic_bool isSynchronous_{false}; - - void startWorkLoop(jsi::Runtime& runtime) const; - - /* - * Schedules a work loop unless it has been already scheduled - * This is to avoid unnecessary calls to `runtimeExecutor`. - */ - void scheduleWorkLoopIfNecessary() const; - - void executeTask( - jsi::Runtime& runtime, - const std::shared_ptr& task, - bool didUserCallbackTimeout) const; - - /* - * Returns a time point representing the current point in time. May be called - * from multiple threads. - */ - std::function now_; - - /* - * Flag indicating if callback on JavaScript queue has been - * scheduled. - */ - mutable std::atomic_bool isWorkLoopScheduled_{false}; - - /* - * This flag is set while performing work, to prevent re-entrancy. - */ - mutable std::atomic_bool isPerformingWork_{false}; + // Actual implementation, stored as a unique pointer to simplify memory + // management. + std::unique_ptr runtimeSchedulerImpl_; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.cpp b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.cpp new file mode 100644 index 00000000000000..238084ea85f102 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.cpp @@ -0,0 +1,193 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "RuntimeScheduler_Legacy.h" +#include "SchedulerPriorityUtils.h" + +#include +#include +#include "ErrorUtils.h" + +namespace facebook::react { + +#pragma mark - Public + +RuntimeScheduler_Legacy::RuntimeScheduler_Legacy( + RuntimeExecutor runtimeExecutor, + std::function now) + : runtimeExecutor_(std::move(runtimeExecutor)), now_(std::move(now)) {} + +void RuntimeScheduler_Legacy::scheduleWork( + RawCallback&& callback) const noexcept { + SystraceSection s("RuntimeScheduler::scheduleWork"); + + runtimeAccessRequests_ += 1; + + runtimeExecutor_( + [this, callback = std::move(callback)](jsi::Runtime& runtime) { + SystraceSection s2("RuntimeScheduler::scheduleWork callback"); + runtimeAccessRequests_ -= 1; + callback(runtime); + startWorkLoop(runtime); + }); +} + +std::shared_ptr RuntimeScheduler_Legacy::scheduleTask( + SchedulerPriority priority, + jsi::Function&& callback) noexcept { + auto expirationTime = now_() + timeoutForSchedulerPriority(priority); + auto task = + std::make_shared(priority, std::move(callback), expirationTime); + taskQueue_.push(task); + + scheduleWorkLoopIfNecessary(); + + return task; +} + +std::shared_ptr RuntimeScheduler_Legacy::scheduleTask( + SchedulerPriority priority, + RawCallback&& callback) noexcept { + auto expirationTime = now_() + timeoutForSchedulerPriority(priority); + auto task = + std::make_shared(priority, std::move(callback), expirationTime); + taskQueue_.push(task); + + scheduleWorkLoopIfNecessary(); + + return task; +} + +bool RuntimeScheduler_Legacy::getShouldYield() const noexcept { + return runtimeAccessRequests_ > 0; +} + +bool RuntimeScheduler_Legacy::getIsSynchronous() const noexcept { + return isSynchronous_; +} + +void RuntimeScheduler_Legacy::cancelTask(Task& task) noexcept { + task.callback.reset(); +} + +SchedulerPriority RuntimeScheduler_Legacy::getCurrentPriorityLevel() + const noexcept { + return currentPriority_; +} + +RuntimeSchedulerTimePoint RuntimeScheduler_Legacy::now() const noexcept { + return now_(); +} + +void RuntimeScheduler_Legacy::executeNowOnTheSameThread( + RawCallback&& callback) { + SystraceSection s("RuntimeScheduler::executeNowOnTheSameThread"); + + runtimeAccessRequests_ += 1; + executeSynchronouslyOnSameThread_CAN_DEADLOCK( + runtimeExecutor_, + [this, callback = std::move(callback)](jsi::Runtime& runtime) { + SystraceSection s2( + "RuntimeScheduler::executeNowOnTheSameThread callback"); + + runtimeAccessRequests_ -= 1; + isSynchronous_ = true; + callback(runtime); + isSynchronous_ = false; + }); + + // Resume work loop if needed. In synchronous mode + // only expired tasks are executed. Tasks with lower priority + // might be still in the queue. + scheduleWorkLoopIfNecessary(); +} + +void RuntimeScheduler_Legacy::callExpiredTasks(jsi::Runtime& runtime) { + SystraceSection s("RuntimeScheduler::callExpiredTasks"); + + auto previousPriority = currentPriority_; + try { + while (!taskQueue_.empty()) { + auto topPriorityTask = taskQueue_.top(); + auto now = now_(); + auto didUserCallbackTimeout = topPriorityTask->expirationTime <= now; + + if (!didUserCallbackTimeout) { + break; + } + + executeTask(runtime, topPriorityTask, didUserCallbackTimeout); + } + } catch (jsi::JSError& error) { + handleFatalError(runtime, error); + } + + currentPriority_ = previousPriority; +} + +#pragma mark - Private + +void RuntimeScheduler_Legacy::scheduleWorkLoopIfNecessary() const { + if (!isWorkLoopScheduled_ && !isPerformingWork_) { + isWorkLoopScheduled_ = true; + runtimeExecutor_([this](jsi::Runtime& runtime) { + isWorkLoopScheduled_ = false; + startWorkLoop(runtime); + }); + } +} + +void RuntimeScheduler_Legacy::startWorkLoop(jsi::Runtime& runtime) const { + SystraceSection s("RuntimeScheduler::startWorkLoop"); + + auto previousPriority = currentPriority_; + isPerformingWork_ = true; + try { + while (!taskQueue_.empty()) { + auto topPriorityTask = taskQueue_.top(); + auto now = now_(); + auto didUserCallbackTimeout = topPriorityTask->expirationTime <= now; + + if (!didUserCallbackTimeout && getShouldYield()) { + // This currentTask hasn't expired, and we need to yield. + break; + } + + executeTask(runtime, topPriorityTask, didUserCallbackTimeout); + } + } catch (jsi::JSError& error) { + handleFatalError(runtime, error); + } + + currentPriority_ = previousPriority; + isPerformingWork_ = false; +} + +void RuntimeScheduler_Legacy::executeTask( + jsi::Runtime& runtime, + const std::shared_ptr& task, + bool didUserCallbackTimeout) const { + SystraceSection s( + "RuntimeScheduler::executeTask", + "priority", + serialize(task->priority), + "didUserCallbackTimeout", + didUserCallbackTimeout); + + currentPriority_ = task->priority; + auto result = task->execute(runtime, didUserCallbackTimeout); + + if (result.isObject() && result.getObject(runtime).isFunction(runtime)) { + task->callback = result.getObject(runtime).getFunction(runtime); + } else { + if (taskQueue_.top() == task) { + taskQueue_.pop(); + } + } +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.h b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.h new file mode 100644 index 00000000000000..6645a20d96834b --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.h @@ -0,0 +1,161 @@ +/* + * Copyright (c) Meta Platforms, Inc. and 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 +#include +#include +#include + +namespace facebook::react { + +class RuntimeScheduler_Legacy final : public RuntimeSchedulerBase { + public: + RuntimeScheduler_Legacy( + RuntimeExecutor runtimeExecutor, + std::function now = + RuntimeSchedulerClock::now); + + /* + * Not copyable. + */ + RuntimeScheduler_Legacy(const RuntimeScheduler_Legacy&) = delete; + RuntimeScheduler_Legacy& operator=(const RuntimeScheduler_Legacy&) = delete; + + /* + * Not movable. + */ + RuntimeScheduler_Legacy(RuntimeScheduler_Legacy&&) = delete; + RuntimeScheduler_Legacy& operator=(RuntimeScheduler_Legacy&&) = delete; + + void scheduleWork(RawCallback&& callback) const noexcept override; + + /* + * Grants access to the runtime synchronously on the caller's thread. + * + * Shouldn't be called directly. it is expected to be used + * by dispatching a synchronous event via event emitter in your native + * component. + */ + void executeNowOnTheSameThread(RawCallback&& callback) override; + + /* + * Adds a JavaScript callback to priority queue with given priority. + * Triggers workloop if needed. + * + * Thread synchronization must be enforced externally. + */ + std::shared_ptr scheduleTask( + SchedulerPriority priority, + jsi::Function&& callback) noexcept override; + + std::shared_ptr scheduleTask( + SchedulerPriority priority, + RawCallback&& callback) noexcept override; + + /* + * Cancelled task will never be executed. + * + * Operates on JSI object. + * Thread synchronization must be enforced externally. + */ + void cancelTask(Task& task) noexcept override; + + /* + * Return value indicates if host platform has a pending access to the + * runtime. + * + * Can be called from any thread. + */ + bool getShouldYield() const noexcept override; + + /* + * Return value informs if the current task is executed inside synchronous + * block. + * + * Can be called from any thread. + */ + bool getIsSynchronous() const noexcept override; + + /* + * Returns value of currently executed task. Designed to be called from React. + * + * Thread synchronization must be enforced externally. + */ + SchedulerPriority getCurrentPriorityLevel() const noexcept override; + + /* + * Returns current monotonic time. This time is not related to wall clock + * time. + * + * Thread synchronization must be enforced externally. + */ + RuntimeSchedulerTimePoint now() const noexcept override; + + /* + * Expired task is a task that should have been already executed. Designed to + * be called in the event pipeline after an event is dispatched to React. + * React may schedule events with immediate priority which need to be handled + * before the next event is sent to React. + * + * Thread synchronization must be enforced externally. + */ + void callExpiredTasks(jsi::Runtime& runtime) override; + + private: + mutable std::priority_queue< + std::shared_ptr, + std::vector>, + TaskPriorityComparer> + taskQueue_; + + const RuntimeExecutor runtimeExecutor_; + mutable SchedulerPriority currentPriority_{SchedulerPriority::NormalPriority}; + + /* + * Counter indicating how many access to the runtime have been requested. + */ + mutable std::atomic runtimeAccessRequests_{0}; + + mutable std::atomic_bool isSynchronous_{false}; + + void startWorkLoop(jsi::Runtime& runtime) const; + + /* + * Schedules a work loop unless it has been already scheduled + * This is to avoid unnecessary calls to `runtimeExecutor`. + */ + void scheduleWorkLoopIfNecessary() const; + + void executeTask( + jsi::Runtime& runtime, + const std::shared_ptr& task, + bool didUserCallbackTimeout) const; + + /* + * Returns a time point representing the current point in time. May be called + * from multiple threads. + */ + std::function now_; + + /* + * Flag indicating if callback on JavaScript queue has been + * scheduled. + */ + mutable std::atomic_bool isWorkLoopScheduled_{false}; + + /* + * This flag is set while performing work, to prevent re-entrancy. + */ + mutable std::atomic_bool isPerformingWork_{false}; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/Task.h b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/Task.h index 3447c662dfda6b..0d292e2ad1d8f0 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/Task.h +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/Task.h @@ -16,7 +16,7 @@ namespace facebook::react { -class RuntimeScheduler; +class RuntimeScheduler_Legacy; class TaskPriorityComparer; using RawCallback = std::function; @@ -33,7 +33,7 @@ struct Task final : public jsi::NativeState { std::chrono::steady_clock::time_point expirationTime); private: - friend RuntimeScheduler; + friend RuntimeScheduler_Legacy; friend TaskPriorityComparer; SchedulerPriority priority;