From fcda1ac51477ab276cfcb57dec8f63b196c08c81 Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Tue, 7 Dec 2021 12:09:26 -0800 Subject: [PATCH] Add support for synchronous completeRoot Summary: changelog: [internal] Exposes a new flag on RuntimeScheduler: `unstable_getIsSynchronous`. Flag indicates if the current code is run synchronously and therefore commit phase should be synchronous. Unit tests will be added later, to keep this diff short. This code path is not executed yet. Reviewed By: mdvacca, ShikaSD Differential Revision: D32677814 fbshipit-source-id: e01d4fff7e716d627ff99fe104965851138c3aef --- .../implementations/ReactFabric-dev.fb.js | 2 +- .../runtimescheduler/RuntimeScheduler.cpp | 29 ++++++-- .../runtimescheduler/RuntimeScheduler.h | 13 +++- .../RuntimeSchedulerBinding.cpp | 18 +++++ .../RuntimeSchedulerBinding.h | 9 +++ .../runtimescheduler/SchedulerPriority.h | 16 +++++ .../react/renderer/uimanager/Android.mk | 2 + ReactCommon/react/renderer/uimanager/BUCK | 1 + .../renderer/uimanager/UIManagerBinding.cpp | 67 ++++++++++++------- 9 files changed, 124 insertions(+), 33 deletions(-) diff --git a/Libraries/Renderer/implementations/ReactFabric-dev.fb.js b/Libraries/Renderer/implementations/ReactFabric-dev.fb.js index e3e5f40045e..3c37320dbd2 100644 --- a/Libraries/Renderer/implementations/ReactFabric-dev.fb.js +++ b/Libraries/Renderer/implementations/ReactFabric-dev.fb.js @@ -24603,6 +24603,6 @@ if ( ) { __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(new Error()); } - + })(); } diff --git a/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.cpp b/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.cpp index ecf2589dddc..b25efc0a5ac 100644 --- a/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.cpp +++ b/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.cpp @@ -58,6 +58,10 @@ bool RuntimeScheduler::getShouldYield() const noexcept { return shouldYield_; } +bool RuntimeScheduler::getIsSynchronous() const noexcept { + return isSynchronous_; +} + void RuntimeScheduler::cancelTask(const std::shared_ptr &task) noexcept { task->callback.reset(); } @@ -75,14 +79,31 @@ void RuntimeScheduler::setEnableYielding(bool enableYielding) { } void RuntimeScheduler::executeNowOnTheSameThread( - std::function callback) const { + std::function callback) { shouldYield_ = true; executeSynchronouslyOnSameThread_CAN_DEADLOCK( runtimeExecutor_, - [callback = std::move(callback)](jsi::Runtime &runtime) { - callback(runtime); + [this, callback = std::move(callback)](jsi::Runtime &runtime) { + shouldYield_ = false; + auto task = jsi::Function::createFromHostFunction( + runtime, + jsi::PropNameID::forUtf8(runtime, ""), + 3, + [callback = std::move(callback)]( + jsi::Runtime &runtime, + jsi::Value const &, + jsi::Value const *arguments, + size_t) -> jsi::Value { + callback(runtime); + return jsi::Value::undefined(); + }); + assert(!isPerformingWork_); + this->scheduleTask( + SchedulerPriority::ImmediatePriority, std::move(task)); + isSynchronous_ = true; + startWorkLoop(runtime); + isSynchronous_ = false; }); - shouldYield_ = false; } #pragma mark - Private diff --git a/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.h b/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.h index 5d18a7d9ea8..1cd6a4c3f3f 100644 --- a/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.h +++ b/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.h @@ -45,8 +45,14 @@ class RuntimeScheduler final { * component. */ void executeNowOnTheSameThread( - std::function callback) const; + std::function callback); + /* + * 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); @@ -55,6 +61,8 @@ class RuntimeScheduler final { bool getShouldYield() const noexcept; + bool getIsSynchronous() const noexcept; + SchedulerPriority getCurrentPriorityLevel() const noexcept; RuntimeSchedulerTimePoint now() const noexcept; @@ -71,6 +79,7 @@ class RuntimeScheduler final { RuntimeExecutor const runtimeExecutor_; mutable SchedulerPriority currentPriority_{SchedulerPriority::NormalPriority}; mutable std::atomic_bool shouldYield_{false}; + mutable std::atomic_bool isSynchronous_{false}; void startWorkLoop(jsi::Runtime &runtime) const; @@ -84,7 +93,7 @@ class RuntimeScheduler final { * Flag indicating if callback on JavaScript queue has been * scheduled. */ - std::atomic_bool isCallbackScheduled_{false}; + mutable std::atomic_bool isCallbackScheduled_{false}; /* * Flag indicating if yielding is enabled. diff --git a/ReactCommon/react/renderer/runtimescheduler/RuntimeSchedulerBinding.cpp b/ReactCommon/react/renderer/runtimescheduler/RuntimeSchedulerBinding.cpp index 92525118e28..e68b479f5e1 100644 --- a/ReactCommon/react/renderer/runtimescheduler/RuntimeSchedulerBinding.cpp +++ b/ReactCommon/react/renderer/runtimescheduler/RuntimeSchedulerBinding.cpp @@ -42,10 +42,28 @@ RuntimeSchedulerBinding::createAndInstallIfNeeded( return runtimeSchedulerObject.getHostObject(runtime); } +std::shared_ptr RuntimeSchedulerBinding::getBinding( + jsi::Runtime &runtime) { + auto runtimeSchedulerModuleName = "nativeRuntimeScheduler"; + + auto runtimeSchedulerValue = + runtime.global().getProperty(runtime, runtimeSchedulerModuleName); + if (runtimeSchedulerValue.isUndefined()) { + return nullptr; + } + + auto runtimeSchedulerObject = runtimeSchedulerValue.asObject(runtime); + return runtimeSchedulerObject.getHostObject(runtime); +} + RuntimeSchedulerBinding::RuntimeSchedulerBinding( std::shared_ptr const &runtimeScheduler) : runtimeScheduler_(runtimeScheduler) {} +bool RuntimeSchedulerBinding::getIsSynchronous() const { + return runtimeScheduler_->getIsSynchronous(); +} + jsi::Value RuntimeSchedulerBinding::get( jsi::Runtime &runtime, jsi::PropNameID const &name) { diff --git a/ReactCommon/react/renderer/runtimescheduler/RuntimeSchedulerBinding.h b/ReactCommon/react/renderer/runtimescheduler/RuntimeSchedulerBinding.h index 27c1ad9f31d..ccb3fb7f261 100644 --- a/ReactCommon/react/renderer/runtimescheduler/RuntimeSchedulerBinding.h +++ b/ReactCommon/react/renderer/runtimescheduler/RuntimeSchedulerBinding.h @@ -31,11 +31,20 @@ class RuntimeSchedulerBinding : public jsi::HostObject { jsi::Runtime &runtime, std::shared_ptr const &runtimeScheduler); + /* + * Returns a shared pointer to RuntimeSchedulerBinding previously installed + * into a runtime. Thread synchronization must be enforced externally. + */ + static std::shared_ptr getBinding( + jsi::Runtime &runtime); + /* * `jsi::HostObject` specific overloads. */ jsi::Value get(jsi::Runtime &runtime, jsi::PropNameID const &name) override; + bool getIsSynchronous() const; + private: std::shared_ptr runtimeScheduler_; }; diff --git a/ReactCommon/react/renderer/runtimescheduler/SchedulerPriority.h b/ReactCommon/react/renderer/runtimescheduler/SchedulerPriority.h index 329fe252aa3..2639705f7f4 100644 --- a/ReactCommon/react/renderer/runtimescheduler/SchedulerPriority.h +++ b/ReactCommon/react/renderer/runtimescheduler/SchedulerPriority.h @@ -61,5 +61,21 @@ static inline std::chrono::milliseconds timeoutForSchedulerPriority( } } +static inline std::string debugValueForSchedulerPriority( + SchedulerPriority schedulerPriority) { + switch (schedulerPriority) { + case SchedulerPriority::ImmediatePriority: + return "SchedulerPriority::ImmediatePriority"; + case SchedulerPriority::UserBlockingPriority: + return "SchedulerPriority::UserBlockingPriority"; + case SchedulerPriority::NormalPriority: + return "SchedulerPriority::NormalPriority"; + case SchedulerPriority::LowPriority: + return "SchedulerPriority::LowPriority"; + case SchedulerPriority::IdlePriority: + return "SchedulerPriority::IdlePriority"; + } +} + } // namespace react } // namespace facebook diff --git a/ReactCommon/react/renderer/uimanager/Android.mk b/ReactCommon/react/renderer/uimanager/Android.mk index fb1d9c2e9b8..fe59da25ea9 100644 --- a/ReactCommon/react/renderer/uimanager/Android.mk +++ b/ReactCommon/react/renderer/uimanager/Android.mk @@ -32,6 +32,7 @@ LOCAL_SHARED_LIBRARIES := \ libreact_render_debug \ libreact_render_graphics \ libreact_render_leakchecker \ + libreact_render_runtimescheduler \ libreact_render_mounting \ libreact_config \ librrc_root \ @@ -49,6 +50,7 @@ $(call import-module,react/renderer/components/view) $(call import-module,react/renderer/componentregistry) $(call import-module,react/renderer/core) $(call import-module,react/renderer/leakchecker) +$(call import-module,react/renderer/runtimescheduler) $(call import-module,react/renderer/debug) $(call import-module,react/renderer/graphics) $(call import-module,react/renderer/mounting) diff --git a/ReactCommon/react/renderer/uimanager/BUCK b/ReactCommon/react/renderer/uimanager/BUCK index d518a76c385..9633c00bdec 100644 --- a/ReactCommon/react/renderer/uimanager/BUCK +++ b/ReactCommon/react/renderer/uimanager/BUCK @@ -60,6 +60,7 @@ rn_xplat_cxx_library( react_native_xplat_target("react/renderer/componentregistry:componentregistry"), react_native_xplat_target("react/renderer/debug:debug"), react_native_xplat_target("runtimeexecutor:runtimeexecutor"), + react_native_xplat_target("react/renderer/runtimescheduler:runtimescheduler"), ], ) diff --git a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp index e76348b74f6..b604a3fe3ab 100644 --- a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp +++ b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include namespace facebook::react { @@ -543,33 +544,47 @@ jsi::Value UIManagerBinding::get( jsi::Value const &thisValue, jsi::Value const *arguments, size_t count) noexcept -> jsi::Value { + auto runtimeSchedulerBinding = + RuntimeSchedulerBinding::getBinding(runtime); auto surfaceId = surfaceIdFromValue(runtime, arguments[0]); - auto weakShadowNodeList = - weakShadowNodeListFromValue(runtime, arguments[1]); - static std::atomic_uint_fast8_t completeRootEventCounter{0}; - static std::atomic_uint_fast32_t mostRecentSurfaceId{0}; - completeRootEventCounter += 1; - mostRecentSurfaceId = surfaceId; - uiManager->backgroundExecutor_( - [weakUIManager, - weakShadowNodeList, - surfaceId, - eventCount = completeRootEventCounter.load()] { - auto shouldYield = [=]() -> bool { - // If `completeRootEventCounter` was incremented, another - // `completeSurface` call has been scheduled and current - // `completeSurface` should yield to it. - return completeRootEventCounter > eventCount && - mostRecentSurfaceId == surfaceId; - }; - auto shadowNodeList = - shadowNodeListFromWeakList(weakShadowNodeList); - auto strongUIManager = weakUIManager.lock(); - if (shadowNodeList && strongUIManager) { - strongUIManager->completeSurface( - surfaceId, shadowNodeList, {true, shouldYield}); - } - }); + + if (runtimeSchedulerBinding && + runtimeSchedulerBinding->getIsSynchronous()) { + auto weakShadowNodeList = + weakShadowNodeListFromValue(runtime, arguments[1]); + auto shadowNodeList = + shadowNodeListFromWeakList(weakShadowNodeList); + if (shadowNodeList) { + uiManager->completeSurface(surfaceId, shadowNodeList, {true}); + } + } else { + auto weakShadowNodeList = + weakShadowNodeListFromValue(runtime, arguments[1]); + static std::atomic_uint_fast8_t completeRootEventCounter{0}; + static std::atomic_uint_fast32_t mostRecentSurfaceId{0}; + completeRootEventCounter += 1; + mostRecentSurfaceId = surfaceId; + uiManager->backgroundExecutor_( + [weakUIManager, + weakShadowNodeList, + surfaceId, + eventCount = completeRootEventCounter.load()] { + auto shouldYield = [=]() -> bool { + // If `completeRootEventCounter` was incremented, another + // `completeSurface` call has been scheduled and current + // `completeSurface` should yield to it. + return completeRootEventCounter > eventCount && + mostRecentSurfaceId == surfaceId; + }; + auto shadowNodeList = + shadowNodeListFromWeakList(weakShadowNodeList); + auto strongUIManager = weakUIManager.lock(); + if (shadowNodeList && strongUIManager) { + strongUIManager->completeSurface( + surfaceId, shadowNodeList, {true, shouldYield}); + } + }); + } return jsi::Value::undefined(); });