first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,29 @@
# 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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB react_renderer_runtimescheduler_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_renderer_runtimescheduler STATIC ${react_renderer_runtimescheduler_SRC})
target_include_directories(react_renderer_runtimescheduler PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_renderer_runtimescheduler
callinvoker
jsi
react_debug
react_performance_timeline
react_renderer_consistency
react_renderer_debug
react_timing
react_utils
react_featureflags
runtimeexecutor
jsinspector_tracing)
target_compile_reactnative_options(react_renderer_runtimescheduler PRIVATE)
target_compile_options(react_renderer_runtimescheduler PRIVATE -Wpedantic)

View File

@@ -0,0 +1,59 @@
# 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.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "..", "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
header_search_paths = []
if ENV['USE_FRAMEWORKS']
header_search_paths << "\"$(PODS_TARGET_SRCROOT)/../../..\"" # this is needed to allow the RuntimeScheduler access its own files
end
Pod::Spec.new do |s|
s.name = "React-runtimescheduler"
s.version = version
s.summary = "-" # TODO
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.source_files = podspec_sources("**/*.{cpp,h}", "**/*.h")
s.header_dir = "react/renderer/runtimescheduler"
s.exclude_files = "tests"
s.pod_target_xcconfig = {
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"HEADER_SEARCH_PATHS" => header_search_paths.join(' ')}
resolve_use_frameworks(s, header_mappings_dir: "../../..", module_name: "React_runtimescheduler")
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
s.dependency "React-callinvoker"
s.dependency "React-cxxreact"
s.dependency "React-rendererdebug"
s.dependency "React-utils"
s.dependency "React-featureflags"
s.dependency "React-timing"
s.dependency "React-jsi"
s.dependency "React-performancetimeline"
s.dependency "React-rendererconsistency"
add_dependency(s, "React-debug")
add_dependency(s, "React-jsinspectortracing", :framework_name => 'jsinspector_moderntracing')
depend_on_js_engine(s)
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
end

View File

@@ -0,0 +1,136 @@
/*
* 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.h"
#include "RuntimeScheduler_Legacy.h"
#include "RuntimeScheduler_Modern.h"
#include <cxxreact/ErrorUtils.h>
#include <cxxreact/TraceSection.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <utility>
namespace facebook::react {
extern const char RuntimeSchedulerKey[] = "RuntimeScheduler";
namespace {
std::unique_ptr<RuntimeSchedulerBase> getRuntimeSchedulerImplementation(
RuntimeExecutor runtimeExecutor,
std::function<HighResTimeStamp()> now,
RuntimeSchedulerTaskErrorHandler onTaskError) {
if (ReactNativeFeatureFlags::enableBridgelessArchitecture()) {
return std::make_unique<RuntimeScheduler_Modern>(
std::move(runtimeExecutor), std::move(now), std::move(onTaskError));
} else {
return std::make_unique<RuntimeScheduler_Legacy>(
std::move(runtimeExecutor), std::move(now), std::move(onTaskError));
}
}
} // namespace
RuntimeScheduler::RuntimeScheduler(
RuntimeExecutor runtimeExecutor,
std::function<HighResTimeStamp()> now,
RuntimeSchedulerTaskErrorHandler onTaskError)
: runtimeSchedulerImpl_(getRuntimeSchedulerImplementation(
std::move(runtimeExecutor),
std::move(now),
std::move(onTaskError))) {}
/* static */ void RuntimeScheduler::handleTaskErrorDefault(
jsi::Runtime& runtime,
jsi::JSError& error) {
handleJSError(runtime, error, true);
}
void RuntimeScheduler::scheduleWork(RawCallback&& callback) noexcept {
return runtimeSchedulerImpl_->scheduleWork(std::move(callback));
}
std::shared_ptr<Task> RuntimeScheduler::scheduleTask(
SchedulerPriority priority,
jsi::Function&& callback) noexcept {
return runtimeSchedulerImpl_->scheduleTask(priority, std::move(callback));
}
std::shared_ptr<Task> RuntimeScheduler::scheduleTask(
SchedulerPriority priority,
RawCallback&& callback) noexcept {
return runtimeSchedulerImpl_->scheduleTask(priority, std::move(callback));
}
std::shared_ptr<Task> RuntimeScheduler::scheduleIdleTask(
jsi::Function&& callback,
HighResDuration timeout) noexcept {
return runtimeSchedulerImpl_->scheduleIdleTask(std::move(callback), timeout);
}
std::shared_ptr<Task> RuntimeScheduler::scheduleIdleTask(
RawCallback&& callback,
HighResDuration timeout) noexcept {
return runtimeSchedulerImpl_->scheduleIdleTask(std::move(callback), timeout);
}
bool RuntimeScheduler::getShouldYield() noexcept {
return runtimeSchedulerImpl_->getShouldYield();
}
void RuntimeScheduler::cancelTask(Task& task) noexcept {
return runtimeSchedulerImpl_->cancelTask(task);
}
SchedulerPriority RuntimeScheduler::getCurrentPriorityLevel() const noexcept {
return runtimeSchedulerImpl_->getCurrentPriorityLevel();
}
HighResTimeStamp RuntimeScheduler::now() const noexcept {
return runtimeSchedulerImpl_->now();
}
void RuntimeScheduler::executeNowOnTheSameThread(RawCallback&& callback) {
return runtimeSchedulerImpl_->executeNowOnTheSameThread(std::move(callback));
}
void RuntimeScheduler::callExpiredTasks(jsi::Runtime& runtime) {
return runtimeSchedulerImpl_->callExpiredTasks(runtime);
}
void RuntimeScheduler::scheduleRenderingUpdate(
SurfaceId surfaceId,
RuntimeSchedulerRenderingUpdate&& renderingUpdate) {
return runtimeSchedulerImpl_->scheduleRenderingUpdate(
surfaceId, std::move(renderingUpdate));
}
void RuntimeScheduler::setShadowTreeRevisionConsistencyManager(
ShadowTreeRevisionConsistencyManager*
shadowTreeRevisionConsistencyManager) {
return runtimeSchedulerImpl_->setShadowTreeRevisionConsistencyManager(
shadowTreeRevisionConsistencyManager);
}
void RuntimeScheduler::setPerformanceEntryReporter(
PerformanceEntryReporter* performanceEntryReporter) {
return runtimeSchedulerImpl_->setPerformanceEntryReporter(
performanceEntryReporter);
}
void RuntimeScheduler::setEventTimingDelegate(
RuntimeSchedulerEventTimingDelegate* eventTimingDelegate) {
return runtimeSchedulerImpl_->setEventTimingDelegate(eventTimingDelegate);
}
void RuntimeScheduler::setIntersectionObserverDelegate(
RuntimeSchedulerIntersectionObserverDelegate*
intersectionObserverDelegate) {
return runtimeSchedulerImpl_->setIntersectionObserverDelegate(
intersectionObserverDelegate);
}
} // namespace facebook::react

View File

@@ -0,0 +1,167 @@
/*
* 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 <ReactCommon/RuntimeExecutor.h>
#include <react/performance/timeline/PerformanceEntryReporter.h>
#include <react/renderer/consistency/ShadowTreeRevisionConsistencyManager.h>
#include <react/renderer/runtimescheduler/SchedulerPriorityUtils.h>
#include <react/renderer/runtimescheduler/Task.h>
#include <react/timing/primitives.h>
#include "RuntimeSchedulerEventTimingDelegate.h"
#include "RuntimeSchedulerIntersectionObserverDelegate.h"
namespace facebook::react {
using RuntimeSchedulerRenderingUpdate = std::function<void()>;
using SurfaceId = int32_t;
using RuntimeSchedulerTaskErrorHandler = std::function<void(jsi::Runtime &runtime, jsi::JSError &error)>;
extern const char RuntimeSchedulerKey[];
// 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) noexcept = 0;
virtual void executeNowOnTheSameThread(RawCallback &&callback) = 0;
virtual std::shared_ptr<Task> scheduleTask(SchedulerPriority priority, jsi::Function &&callback) noexcept = 0;
virtual std::shared_ptr<Task> scheduleTask(SchedulerPriority priority, RawCallback &&callback) noexcept = 0;
virtual std::shared_ptr<Task> scheduleIdleTask(
jsi::Function &&callback,
HighResDuration timeout = timeoutForSchedulerPriority(SchedulerPriority::IdlePriority)) noexcept = 0;
virtual std::shared_ptr<Task> scheduleIdleTask(
RawCallback &&callback,
HighResDuration timeout = timeoutForSchedulerPriority(SchedulerPriority::IdlePriority)) noexcept = 0;
virtual void cancelTask(Task &task) noexcept = 0;
virtual bool getShouldYield() noexcept = 0;
virtual SchedulerPriority getCurrentPriorityLevel() const noexcept = 0;
virtual HighResTimeStamp now() const noexcept = 0;
virtual void callExpiredTasks(jsi::Runtime &runtime) = 0;
virtual void scheduleRenderingUpdate(SurfaceId surfaceId, RuntimeSchedulerRenderingUpdate &&renderingUpdate) = 0;
virtual void setShadowTreeRevisionConsistencyManager(ShadowTreeRevisionConsistencyManager *provider) = 0;
virtual void setPerformanceEntryReporter(PerformanceEntryReporter *reporter) = 0;
virtual void setEventTimingDelegate(RuntimeSchedulerEventTimingDelegate *eventTimingDelegate) = 0;
virtual void setIntersectionObserverDelegate(
RuntimeSchedulerIntersectionObserverDelegate *intersectionObserverDelegate) = 0;
};
// This is a proxy for RuntimeScheduler implementation, which will be selected
// at runtime based on a feature flag.
class RuntimeScheduler final : RuntimeSchedulerBase {
public:
explicit RuntimeScheduler(
RuntimeExecutor runtimeExecutor,
std::function<HighResTimeStamp()> now = HighResTimeStamp::now,
RuntimeSchedulerTaskErrorHandler onTaskError = handleTaskErrorDefault);
/*
* Not copyable.
*/
RuntimeScheduler(const RuntimeScheduler &) = delete;
RuntimeScheduler &operator=(const RuntimeScheduler &) = delete;
/*
* Not movable.
*/
RuntimeScheduler(RuntimeScheduler &&) = delete;
RuntimeScheduler &operator=(RuntimeScheduler &&) = delete;
void scheduleWork(RawCallback &&callback) 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<Task> scheduleTask(SchedulerPriority priority, jsi::Function &&callback) noexcept override;
std::shared_ptr<Task> scheduleTask(SchedulerPriority priority, RawCallback &&callback) noexcept override;
std::shared_ptr<Task> scheduleIdleTask(
jsi::Function &&callback,
HighResDuration timeout = timeoutForSchedulerPriority(SchedulerPriority::IdlePriority)) noexcept override;
std::shared_ptr<Task> scheduleIdleTask(
RawCallback &&callback,
HighResDuration timeout = timeoutForSchedulerPriority(SchedulerPriority::IdlePriority)) 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() 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.
*/
HighResTimeStamp 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;
void scheduleRenderingUpdate(SurfaceId surfaceId, RuntimeSchedulerRenderingUpdate &&renderingUpdate) override;
void setShadowTreeRevisionConsistencyManager(
ShadowTreeRevisionConsistencyManager *shadowTreeRevisionConsistencyManager) override;
void setPerformanceEntryReporter(PerformanceEntryReporter *reporter) override;
void setEventTimingDelegate(RuntimeSchedulerEventTimingDelegate *eventTimingDelegate) override;
void setIntersectionObserverDelegate(
RuntimeSchedulerIntersectionObserverDelegate *intersectionObserverDelegate) override;
private:
// Actual implementation, stored as a unique pointer to simplify memory
// management.
std::unique_ptr<RuntimeSchedulerBase> runtimeSchedulerImpl_;
static void handleTaskErrorDefault(jsi::Runtime &runtime, jsi::JSError &error);
};
} // namespace facebook::react

View File

@@ -0,0 +1,201 @@
/*
* 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 "RuntimeSchedulerBinding.h"
#include <ReactCommon/SchedulerPriority.h>
#include <react/timing/primitives.h>
#include "RuntimeScheduler.h"
#include "SchedulerPriorityUtils.h"
#include "primitives.h"
#include <memory>
#include <utility>
namespace facebook::react {
std::shared_ptr<RuntimeSchedulerBinding>
RuntimeSchedulerBinding::createAndInstallIfNeeded(
jsi::Runtime& runtime,
const std::shared_ptr<RuntimeScheduler>& runtimeScheduler) {
auto runtimeSchedulerModuleName = "nativeRuntimeScheduler";
auto runtimeSchedulerValue =
runtime.global().getProperty(runtime, runtimeSchedulerModuleName);
if (runtimeSchedulerValue.isUndefined()) {
// The global namespace does not have an instance of the binding;
// we need to create, install and return it.
auto runtimeSchedulerBinding =
std::make_shared<RuntimeSchedulerBinding>(runtimeScheduler);
auto object =
jsi::Object::createFromHostObject(runtime, runtimeSchedulerBinding);
runtime.global().setProperty(
runtime, runtimeSchedulerModuleName, std::move(object));
return runtimeSchedulerBinding;
}
// The global namespace already has an instance of the binding;
// we need to return that.
auto runtimeSchedulerObject = runtimeSchedulerValue.asObject(runtime);
return runtimeSchedulerObject.getHostObject<RuntimeSchedulerBinding>(runtime);
}
std::shared_ptr<RuntimeSchedulerBinding> 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<RuntimeSchedulerBinding>(runtime);
}
RuntimeSchedulerBinding::RuntimeSchedulerBinding(
std::shared_ptr<RuntimeScheduler> runtimeScheduler)
: runtimeScheduler_(std::move(runtimeScheduler)) {}
std::shared_ptr<RuntimeScheduler>
RuntimeSchedulerBinding::getRuntimeScheduler() noexcept {
return runtimeScheduler_;
}
jsi::Value RuntimeSchedulerBinding::get(
jsi::Runtime& runtime,
const jsi::PropNameID& name) {
auto propertyName = name.utf8(runtime);
if (propertyName == "unstable_scheduleCallback") {
return jsi::Function::createFromHostFunction(
runtime,
name,
3,
[this](
jsi::Runtime& runtime,
const jsi::Value&,
const jsi::Value* arguments,
size_t) noexcept -> jsi::Value {
SchedulerPriority priority = fromRawValue(arguments[0].getNumber());
auto callback = arguments[1].getObject(runtime).getFunction(runtime);
auto task =
runtimeScheduler_->scheduleTask(priority, std::move(callback));
return valueFromTask(runtime, task);
});
}
if (propertyName == "unstable_cancelCallback") {
return jsi::Function::createFromHostFunction(
runtime,
name,
1,
[this](
jsi::Runtime& runtime,
const jsi::Value&,
const jsi::Value* arguments,
size_t) noexcept -> jsi::Value {
runtimeScheduler_->cancelTask(*taskFromValue(runtime, arguments[0]));
return jsi::Value::undefined();
});
}
if (propertyName == "unstable_shouldYield") {
return jsi::Function::createFromHostFunction(
runtime,
name,
0,
[this](
jsi::Runtime&,
const jsi::Value&,
const jsi::Value*,
size_t) noexcept -> jsi::Value {
auto shouldYield = runtimeScheduler_->getShouldYield();
return {shouldYield};
});
}
if (propertyName == "unstable_requestPaint") {
return jsi::Function::createFromHostFunction(
runtime,
name,
0,
[](jsi::Runtime&, const jsi::Value&, const jsi::Value*, size_t) noexcept
-> jsi::Value {
// RequestPaint is left empty by design.
return jsi::Value::undefined();
});
}
if (propertyName == "unstable_now") {
return jsi::Function::createFromHostFunction(
runtime,
name,
0,
[this](
jsi::Runtime&,
const jsi::Value&,
const jsi::Value*,
size_t) noexcept -> jsi::Value {
auto now = runtimeScheduler_->now();
auto domHighResTimeStamp = now.toDOMHighResTimeStamp();
return {domHighResTimeStamp};
});
}
// TODO: remove this, as it's deprecated in the JS scheduler
if (propertyName == "unstable_getCurrentPriorityLevel") {
return jsi::Function::createFromHostFunction(
runtime,
name,
0,
[this](
jsi::Runtime& runtime,
const jsi::Value&,
const jsi::Value*,
size_t) noexcept -> jsi::Value {
auto currentPriorityLevel =
runtimeScheduler_->getCurrentPriorityLevel();
return jsi::Value(runtime, serialize(currentPriorityLevel));
});
}
if (propertyName == "unstable_ImmediatePriority") {
return jsi::Value(runtime, serialize(SchedulerPriority::ImmediatePriority));
}
if (propertyName == "unstable_UserBlockingPriority") {
return jsi::Value(
runtime, serialize(SchedulerPriority::UserBlockingPriority));
}
if (propertyName == "unstable_NormalPriority") {
return jsi::Value(runtime, serialize(SchedulerPriority::NormalPriority));
}
if (propertyName == "unstable_LowPriority") {
return jsi::Value(runtime, serialize(SchedulerPriority::LowPriority));
}
if (propertyName == "unstable_IdlePriority") {
return jsi::Value(runtime, serialize(SchedulerPriority::IdlePriority));
}
if (propertyName == "$$typeof") {
return jsi::Value::undefined();
}
#ifdef REACT_NATIVE_DEBUG
throw std::runtime_error("undefined property");
#else
return jsi::Value::undefined();
#endif
}
} // namespace facebook::react

View File

@@ -0,0 +1,53 @@
/*
* 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 <jsi/jsi.h>
namespace facebook::react {
class RuntimeScheduler;
/*
* Exposes RuntimeScheduler to JavaScript realm.
*/
class RuntimeSchedulerBinding : public jsi::HostObject {
public:
RuntimeSchedulerBinding(std::shared_ptr<RuntimeScheduler> runtimeScheduler);
/*
* Installs RuntimeSchedulerBinding into JavaScript runtime if needed.
* Creates and sets `RuntimeSchedulerBinding` into the global namespace.
* In case if the global namespace already has a `RuntimeSchedulerBinding`
* installed, returns that.
*/
static std::shared_ptr<RuntimeSchedulerBinding> createAndInstallIfNeeded(
jsi::Runtime &runtime,
const std::shared_ptr<RuntimeScheduler> &runtimeScheduler);
/*
* Returns a shared pointer to RuntimeSchedulerBinding previously installed
* into a runtime. Thread synchronization must be enforced externally.
*/
static std::shared_ptr<RuntimeSchedulerBinding> getBinding(jsi::Runtime &runtime);
/*
* Returns shared pointer to RuntimeScheduler for use in native modules
*/
std::shared_ptr<RuntimeScheduler> getRuntimeScheduler() noexcept;
/*
* `jsi::HostObject` specific overloads.
*/
jsi::Value get(jsi::Runtime &runtime, const jsi::PropNameID &name) override;
private:
std::shared_ptr<RuntimeScheduler> runtimeScheduler_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,42 @@
/*
* 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 "RuntimeSchedulerCallInvoker.h"
#include "RuntimeScheduler.h"
#include <utility>
namespace facebook::react {
RuntimeSchedulerCallInvoker::RuntimeSchedulerCallInvoker(
std::weak_ptr<RuntimeScheduler> runtimeScheduler)
: runtimeScheduler_(std::move(runtimeScheduler)) {}
void RuntimeSchedulerCallInvoker::invokeAsync(CallFunc&& func) noexcept {
if (auto runtimeScheduler = runtimeScheduler_.lock()) {
runtimeScheduler->scheduleWork(
[func = std::move(func)](jsi::Runtime& rt) { func(rt); });
}
}
void RuntimeSchedulerCallInvoker::invokeSync(CallFunc&& func) {
if (auto runtimeScheduler = runtimeScheduler_.lock()) {
runtimeScheduler->executeNowOnTheSameThread(
[func = std::move(func)](jsi::Runtime& rt) { func(rt); });
}
}
void RuntimeSchedulerCallInvoker::invokeAsync(
SchedulerPriority priority,
CallFunc&& func) noexcept {
if (auto runtimeScheduler = runtimeScheduler_.lock()) {
runtimeScheduler->scheduleTask(
priority, [func = std::move(func)](jsi::Runtime& rt) { func(rt); });
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,36 @@
/*
* 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 <ReactCommon/CallInvoker.h>
#include <memory>
namespace facebook::react {
class RuntimeScheduler;
/*
* Exposes RuntimeScheduler to native modules. All calls invoked on JavaScript
* queue from native modules will be funneled through RuntimeScheduler.
*/
class RuntimeSchedulerCallInvoker : public CallInvoker {
public:
RuntimeSchedulerCallInvoker(std::weak_ptr<RuntimeScheduler> runtimeScheduler);
void invokeAsync(CallFunc &&func) noexcept override;
void invokeSync(CallFunc &&func) override;
void invokeAsync(SchedulerPriority priority, CallFunc &&func) noexcept override;
private:
/*
* RuntimeScheduler is retained by the runtime. It must not be
* retained by anything beyond the runtime.
*/
std::weak_ptr<RuntimeScheduler> runtimeScheduler_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,26 @@
/*
* 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 <react/timing/primitives.h>
#include <unordered_set>
namespace facebook::react {
using SurfaceId = int32_t;
class RuntimeSchedulerEventTimingDelegate {
public:
virtual ~RuntimeSchedulerEventTimingDelegate() = default;
virtual void dispatchPendingEventTimingEntries(
HighResTimeStamp taskEndTime,
const std::unordered_set<SurfaceId> &surfaceIdsWithPendingRenderingUpdates) = 0;
};
} // namespace facebook::react

View File

@@ -0,0 +1,24 @@
/*
* 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 <unordered_set>
namespace facebook::react {
using SurfaceId = int32_t;
class RuntimeSchedulerIntersectionObserverDelegate {
public:
virtual ~RuntimeSchedulerIntersectionObserverDelegate() = default;
virtual void updateIntersectionObservations(
const std::unordered_set<SurfaceId> &surfaceIdsWithPendingRenderingUpdates) = 0;
};
} // namespace facebook::react

View File

@@ -0,0 +1,284 @@
/*
* 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 <ReactCommon/RuntimeExecutorSyncUIThreadUtils.h>
#include <cxxreact/TraceSection.h>
#include <react/renderer/consistency/ScopedShadowTreeRevisionLock.h>
#include <utility>
namespace facebook::react {
#pragma mark - Public
RuntimeScheduler_Legacy::RuntimeScheduler_Legacy(
RuntimeExecutor runtimeExecutor,
std::function<HighResTimeStamp()> now,
RuntimeSchedulerTaskErrorHandler onTaskError)
: runtimeExecutor_(std::move(runtimeExecutor)),
now_(std::move(now)),
onTaskError_(std::move(onTaskError)) {}
void RuntimeScheduler_Legacy::scheduleWork(RawCallback&& callback) noexcept {
TraceSection s("RuntimeScheduler::scheduleWork");
runtimeAccessRequests_ += 1;
runtimeExecutor_(
[this, callback = std::move(callback)](jsi::Runtime& runtime) {
TraceSection s2("RuntimeScheduler::scheduleWork callback");
runtimeAccessRequests_ -= 1;
{
ScopedShadowTreeRevisionLock revisionLock(
shadowTreeRevisionConsistencyManager_);
callback(runtime);
}
startWorkLoop(runtime);
});
}
std::shared_ptr<Task> RuntimeScheduler_Legacy::scheduleTask(
SchedulerPriority priority,
jsi::Function&& callback) noexcept {
TraceSection s(
"RuntimeScheduler::scheduleTask",
"priority",
serialize(priority),
"callbackType",
"jsi::Function");
auto expirationTime = now_() + timeoutForSchedulerPriority(priority);
auto task =
std::make_shared<Task>(priority, std::move(callback), expirationTime);
taskQueue_.push(task);
scheduleWorkLoopIfNecessary();
return task;
}
std::shared_ptr<Task> RuntimeScheduler_Legacy::scheduleTask(
SchedulerPriority priority,
RawCallback&& callback) noexcept {
TraceSection s(
"RuntimeScheduler::scheduleTask",
"priority",
serialize(priority),
"callbackType",
"RawCallback");
auto expirationTime = now_() + timeoutForSchedulerPriority(priority);
auto task =
std::make_shared<Task>(priority, std::move(callback), expirationTime);
taskQueue_.push(task);
scheduleWorkLoopIfNecessary();
return task;
}
std::shared_ptr<Task> RuntimeScheduler_Legacy::scheduleIdleTask(
jsi::Function&& /*callback*/,
HighResDuration /*timeout*/) noexcept {
// Idle tasks are not supported on Legacy RuntimeScheduler.
// Because the method is `noexcept`, we return `nullptr` here and handle it
// on the caller side.
return nullptr;
}
std::shared_ptr<Task> RuntimeScheduler_Legacy::scheduleIdleTask(
RawCallback&& /*callback*/,
HighResDuration /*timeout*/) noexcept {
// Idle tasks are not supported on Legacy RuntimeScheduler.
// Because the method is `noexcept`, we return `nullptr` here and handle it
// on the caller side.
return nullptr;
}
bool RuntimeScheduler_Legacy::getShouldYield() noexcept {
return runtimeAccessRequests_ > 0;
}
void RuntimeScheduler_Legacy::cancelTask(Task& task) noexcept {
task.callback.reset();
}
SchedulerPriority RuntimeScheduler_Legacy::getCurrentPriorityLevel()
const noexcept {
return currentPriority_;
}
HighResTimeStamp RuntimeScheduler_Legacy::now() const noexcept {
return now_();
}
void RuntimeScheduler_Legacy::executeNowOnTheSameThread(
RawCallback&& callback) {
TraceSection s("RuntimeScheduler::executeNowOnTheSameThread");
static thread_local jsi::Runtime* runtimePtr = nullptr;
if (runtimePtr == nullptr) {
runtimeAccessRequests_ += 1;
executeSynchronouslyOnSameThread_CAN_DEADLOCK(
runtimeExecutor_, [this, &callback](jsi::Runtime& runtime) {
TraceSection s2(
"RuntimeScheduler::executeNowOnTheSameThread callback");
runtimeAccessRequests_ -= 1;
{
ScopedShadowTreeRevisionLock revisionLock(
shadowTreeRevisionConsistencyManager_);
runtimePtr = &runtime;
callback(runtime);
runtimePtr = nullptr;
}
});
} else {
// Protecting against re-entry into `executeNowOnTheSameThread` from within
// `executeNowOnTheSameThread`. Without accounting for re-rentry, a deadlock
// will occur when trying to gain access to the runtime.
return callback(*runtimePtr);
}
// 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) {
TraceSection 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) {
onTaskError_(runtime, error);
} catch (std::exception& ex) {
jsi::JSError error(runtime, std::string("Non-js exception: ") + ex.what());
onTaskError_(runtime, error);
}
currentPriority_ = previousPriority;
}
void RuntimeScheduler_Legacy::scheduleRenderingUpdate(
SurfaceId /*surfaceId*/,
RuntimeSchedulerRenderingUpdate&& renderingUpdate) {
TraceSection s("RuntimeScheduler::scheduleRenderingUpdate");
if (renderingUpdate != nullptr) {
renderingUpdate();
}
}
void RuntimeScheduler_Legacy::setShadowTreeRevisionConsistencyManager(
ShadowTreeRevisionConsistencyManager*
shadowTreeRevisionConsistencyManager) {
shadowTreeRevisionConsistencyManager_ = shadowTreeRevisionConsistencyManager;
}
void RuntimeScheduler_Legacy::setPerformanceEntryReporter(
PerformanceEntryReporter* /*performanceEntryReporter*/) {
// No-op in the legacy scheduler
}
void RuntimeScheduler_Legacy::setEventTimingDelegate(
RuntimeSchedulerEventTimingDelegate* /*eventTimingDelegate*/) {
// No-op in the legacy scheduler
}
void RuntimeScheduler_Legacy::setIntersectionObserverDelegate(
RuntimeSchedulerIntersectionObserverDelegate*
/*intersectionObserverDelegate*/) {
// No-op in the legacy scheduler
}
#pragma mark - Private
void RuntimeScheduler_Legacy::scheduleWorkLoopIfNecessary() {
if (!isWorkLoopScheduled_ && !isPerformingWork_) {
isWorkLoopScheduled_ = true;
runtimeExecutor_([this](jsi::Runtime& runtime) {
isWorkLoopScheduled_ = false;
startWorkLoop(runtime);
});
}
}
void RuntimeScheduler_Legacy::startWorkLoop(jsi::Runtime& runtime) {
TraceSection 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) {
onTaskError_(runtime, error);
} catch (std::exception& ex) {
jsi::JSError error(runtime, std::string("Non-js exception: ") + ex.what());
onTaskError_(runtime, error);
}
currentPriority_ = previousPriority;
isPerformingWork_ = false;
}
void RuntimeScheduler_Legacy::executeTask(
jsi::Runtime& runtime,
const std::shared_ptr<Task>& task,
bool didUserCallbackTimeout) {
TraceSection s(
"RuntimeScheduler::executeTask",
"priority",
serialize(task->priority),
"didUserCallbackTimeout",
didUserCallbackTimeout);
currentPriority_ = task->priority;
{
ScopedShadowTreeRevisionLock revisionLock(
shadowTreeRevisionConsistencyManager_);
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

View File

@@ -0,0 +1,174 @@
/*
* 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 <ReactCommon/RuntimeExecutor.h>
#include <react/renderer/consistency/ShadowTreeRevisionConsistencyManager.h>
#include <react/renderer/runtimescheduler/RuntimeScheduler.h>
#include <react/renderer/runtimescheduler/Task.h>
#include <atomic>
#include <memory>
#include <queue>
namespace facebook::react {
class RuntimeScheduler_Legacy final : public RuntimeSchedulerBase {
public:
explicit RuntimeScheduler_Legacy(
RuntimeExecutor runtimeExecutor,
std::function<HighResTimeStamp()> now,
RuntimeSchedulerTaskErrorHandler onTaskError);
/*
* 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) 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<Task> scheduleTask(SchedulerPriority priority, jsi::Function &&callback) noexcept override;
std::shared_ptr<Task> scheduleTask(SchedulerPriority priority, RawCallback &&callback) noexcept override;
/*
* Adds a JavaScript callback to the idle queue with the given timeout.
* Triggers workloop if needed.
*/
std::shared_ptr<Task> scheduleIdleTask(
jsi::Function &&callback,
HighResDuration timeout = timeoutForSchedulerPriority(SchedulerPriority::IdlePriority)) noexcept override;
/*
* Adds a custom callback to the idle queue with the given timeout.
* Triggers workloop if needed.
*/
std::shared_ptr<Task> scheduleIdleTask(
RawCallback &&callback,
HighResDuration timeout = timeoutForSchedulerPriority(SchedulerPriority::IdlePriority)) 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() 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.
*/
HighResTimeStamp 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;
void scheduleRenderingUpdate(SurfaceId surfaceId, RuntimeSchedulerRenderingUpdate &&renderingUpdate) override;
void setShadowTreeRevisionConsistencyManager(
ShadowTreeRevisionConsistencyManager *shadowTreeRevisionConsistencyManager) override;
void setPerformanceEntryReporter(PerformanceEntryReporter *performanceEntryReporter) override;
void setEventTimingDelegate(RuntimeSchedulerEventTimingDelegate *eventTimingDelegate) override;
void setIntersectionObserverDelegate(
RuntimeSchedulerIntersectionObserverDelegate *intersectionObserverDelegate) override;
private:
std::priority_queue<std::shared_ptr<Task>, std::vector<std::shared_ptr<Task>>, TaskPriorityComparer> taskQueue_;
const RuntimeExecutor runtimeExecutor_;
SchedulerPriority currentPriority_{SchedulerPriority::NormalPriority};
/*
* Counter indicating how many access to the runtime have been requested.
*/
std::atomic<uint_fast8_t> runtimeAccessRequests_{0};
std::atomic_bool isSynchronous_{false};
void startWorkLoop(jsi::Runtime &runtime);
/*
* Schedules a work loop unless it has been already scheduled
* This is to avoid unnecessary calls to `runtimeExecutor`.
*/
void scheduleWorkLoopIfNecessary();
void executeTask(jsi::Runtime &runtime, const std::shared_ptr<Task> &task, bool didUserCallbackTimeout);
/*
* Returns a time point representing the current point in time. May be called
* from multiple threads.
*/
std::function<HighResTimeStamp()> now_;
/*
* Flag indicating if callback on JavaScript queue has been
* scheduled.
*/
std::atomic_bool isWorkLoopScheduled_{false};
/*
* This flag is set while performing work, to prevent re-entrancy.
*/
std::atomic_bool isPerformingWork_{false};
std::atomic<ShadowTreeRevisionConsistencyManager *> shadowTreeRevisionConsistencyManager_{nullptr};
RuntimeSchedulerTaskErrorHandler onTaskError_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,461 @@
/*
* 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_Modern.h"
#include <ReactCommon/RuntimeExecutorSyncUIThreadUtils.h>
#include <cxxreact/TraceSection.h>
#include <jsinspector-modern/tracing/EventLoopReporter.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/consistency/ScopedShadowTreeRevisionLock.h>
#include <react/timing/primitives.h>
#include <react/utils/OnScopeExit.h>
namespace facebook::react {
namespace {
HighResDuration getResolvedTimeoutForIdleTask(HighResDuration customTimeout) {
return customTimeout <
timeoutForSchedulerPriority(SchedulerPriority::IdlePriority)
? timeoutForSchedulerPriority(SchedulerPriority::LowPriority) +
customTimeout
: customTimeout;
}
int64_t durationToMilliseconds(HighResDuration duration) {
return duration.toNanoseconds() / static_cast<int64_t>(1e6);
}
} // namespace
#pragma mark - Public
RuntimeScheduler_Modern::RuntimeScheduler_Modern(
RuntimeExecutor runtimeExecutor,
std::function<HighResTimeStamp()> now,
RuntimeSchedulerTaskErrorHandler onTaskError)
: runtimeExecutor_(std::move(runtimeExecutor)),
now_(std::move(now)),
onTaskError_(std::move(onTaskError)) {}
void RuntimeScheduler_Modern::scheduleWork(RawCallback&& callback) noexcept {
TraceSection s("RuntimeScheduler::scheduleWork");
scheduleTask(SchedulerPriority::ImmediatePriority, std::move(callback));
}
std::shared_ptr<Task> RuntimeScheduler_Modern::scheduleTask(
SchedulerPriority priority,
jsi::Function&& callback) noexcept {
auto expirationTime = now_() + timeoutForSchedulerPriority(priority);
auto task =
std::make_shared<Task>(priority, std::move(callback), expirationTime);
scheduleTask(task);
return task;
}
std::shared_ptr<Task> RuntimeScheduler_Modern::scheduleTask(
SchedulerPriority priority,
RawCallback&& callback) noexcept {
auto expirationTime = now_() + timeoutForSchedulerPriority(priority);
auto task =
std::make_shared<Task>(priority, std::move(callback), expirationTime);
scheduleTask(task);
return task;
}
std::shared_ptr<Task> RuntimeScheduler_Modern::scheduleIdleTask(
jsi::Function&& callback,
HighResDuration customTimeout) noexcept {
TraceSection s(
"RuntimeScheduler::scheduleIdleTask",
"customTimeout",
durationToMilliseconds(customTimeout),
"callbackType",
"jsi::Function");
auto timeout = getResolvedTimeoutForIdleTask(customTimeout);
auto expirationTime = now_() + timeout;
auto task = std::make_shared<Task>(
SchedulerPriority::IdlePriority, std::move(callback), expirationTime);
scheduleTask(task);
return task;
}
std::shared_ptr<Task> RuntimeScheduler_Modern::scheduleIdleTask(
RawCallback&& callback,
HighResDuration customTimeout) noexcept {
TraceSection s(
"RuntimeScheduler::scheduleIdleTask",
"customTimeout",
durationToMilliseconds(customTimeout),
"callbackType",
"RawCallback");
auto expirationTime = now_() + getResolvedTimeoutForIdleTask(customTimeout);
auto task = std::make_shared<Task>(
SchedulerPriority::IdlePriority, std::move(callback), expirationTime);
scheduleTask(task);
return task;
}
bool RuntimeScheduler_Modern::getShouldYield() noexcept {
markYieldingOpportunity(now_());
std::shared_lock lock(schedulingMutex_);
return syncTaskRequests_ > 0 ||
(!taskQueue_.empty() && taskQueue_.top().get() != currentTask_);
}
void RuntimeScheduler_Modern::cancelTask(Task& task) noexcept {
task.callback.reset();
}
SchedulerPriority RuntimeScheduler_Modern::getCurrentPriorityLevel()
const noexcept {
return currentPriority_;
}
HighResTimeStamp RuntimeScheduler_Modern::now() const noexcept {
return now_();
}
void RuntimeScheduler_Modern::executeNowOnTheSameThread(
RawCallback&& callback) {
TraceSection s("RuntimeScheduler::executeNowOnTheSameThread");
static thread_local jsi::Runtime* runtimePtr = nullptr;
auto currentTime = now_();
auto priority = SchedulerPriority::ImmediatePriority;
auto expirationTime = currentTime + timeoutForSchedulerPriority(priority);
Task task{priority, std::move(callback), expirationTime};
if (runtimePtr != nullptr) {
// Protecting against re-entry into `executeNowOnTheSameThread` from within
// `executeNowOnTheSameThread`. Without accounting for re-rentry, a deadlock
// will occur when trying to gain access to the runtime.
return executeTask(*runtimePtr, task, true);
}
syncTaskRequests_++;
executeSynchronouslyOnSameThread_CAN_DEADLOCK(
runtimeExecutor_, [&](jsi::Runtime& runtime) mutable {
TraceSection s2("RuntimeScheduler::executeNowOnTheSameThread callback");
syncTaskRequests_--;
runtimePtr = &runtime;
runEventLoopTick(runtime, task);
runtimePtr = nullptr;
});
bool shouldScheduleEventLoop = false;
{
// Unique access because we might write to `isEventLoopScheduled_`.
std::unique_lock lock(schedulingMutex_);
// We only need to schedule the event loop if there any remaining tasks
// in the queue.
if (!taskQueue_.empty() && !isEventLoopScheduled_) {
isEventLoopScheduled_ = true;
shouldScheduleEventLoop = true;
}
}
if (shouldScheduleEventLoop) {
scheduleEventLoop();
}
}
void RuntimeScheduler_Modern::callExpiredTasks(jsi::Runtime& runtime) {
// No-op in the event loop implementation.
}
void RuntimeScheduler_Modern::scheduleRenderingUpdate(
SurfaceId surfaceId,
RuntimeSchedulerRenderingUpdate&& renderingUpdate) {
TraceSection s("RuntimeScheduler::scheduleRenderingUpdate");
surfaceIdsWithPendingRenderingUpdates_.insert(surfaceId);
pendingRenderingUpdates_.push(renderingUpdate);
}
void RuntimeScheduler_Modern::setShadowTreeRevisionConsistencyManager(
ShadowTreeRevisionConsistencyManager*
shadowTreeRevisionConsistencyManager) {
shadowTreeRevisionConsistencyManager_ = shadowTreeRevisionConsistencyManager;
}
void RuntimeScheduler_Modern::setPerformanceEntryReporter(
PerformanceEntryReporter* performanceEntryReporter) {
performanceEntryReporter_ = performanceEntryReporter;
}
void RuntimeScheduler_Modern::setEventTimingDelegate(
RuntimeSchedulerEventTimingDelegate* eventTimingDelegate) {
eventTimingDelegate_ = eventTimingDelegate;
}
void RuntimeScheduler_Modern::setIntersectionObserverDelegate(
RuntimeSchedulerIntersectionObserverDelegate*
intersectionObserverDelegate) {
intersectionObserverDelegate_ = intersectionObserverDelegate;
}
#pragma mark - Private
void RuntimeScheduler_Modern::scheduleTask(std::shared_ptr<Task> task) {
TraceSection s(
"RuntimeScheduler::scheduleTask",
"priority",
serialize(task->priority),
"id",
task->id);
bool shouldScheduleEventLoop = false;
{
std::unique_lock lock(schedulingMutex_);
// We only need to schedule the event loop if the task we're about to
// schedule is the only one in the queue.
// Otherwise, we don't need to schedule it because there's another one
// running already that will pick up the new task.
if (taskQueue_.empty() && !isEventLoopScheduled_) {
isEventLoopScheduled_ = true;
shouldScheduleEventLoop = true;
}
taskQueue_.push(std::move(task));
}
if (shouldScheduleEventLoop) {
scheduleEventLoop();
}
}
void RuntimeScheduler_Modern::scheduleEventLoop() {
runtimeExecutor_([this](jsi::Runtime& runtime) { runEventLoop(runtime); });
}
void RuntimeScheduler_Modern::runEventLoop(jsi::Runtime& runtime) {
TraceSection s("RuntimeScheduler::runEventLoop");
auto previousPriority = currentPriority_;
// `selectTask` must be called unconditionaly to ensure that
// `isEventLoopScheduled_` is set to false and the event loop resume
// correctly if a synchronous task is scheduled.
// Unit test normalTaskYieldsToSynchronousAccessAndResumes covers this
// scenario.
auto topPriorityTask = selectTask();
while (topPriorityTask && syncTaskRequests_ == 0) {
runEventLoopTick(runtime, *topPriorityTask);
topPriorityTask = selectTask();
}
currentPriority_ = previousPriority;
}
std::shared_ptr<Task> RuntimeScheduler_Modern::selectTask() {
// We need a unique lock here because we'll also remove executed tasks from
// the top of the queue.
std::unique_lock lock(schedulingMutex_);
// It's safe to reset the flag here, as its access is also synchronized with
// the access to the task queue.
isEventLoopScheduled_ = false;
// Skip executed tasks
while (!taskQueue_.empty() && !taskQueue_.top()->callback) {
taskQueue_.pop();
}
if (!taskQueue_.empty()) {
return taskQueue_.top();
}
return nullptr;
}
void RuntimeScheduler_Modern::runEventLoopTick(
jsi::Runtime& runtime,
Task& task) {
TraceSection s("RuntimeScheduler::runEventLoopTick");
jsinspector_modern::tracing::EventLoopReporter performanceReporter(
jsinspector_modern::tracing::EventLoopPhase::Task);
ScopedShadowTreeRevisionLock revisionLock(
shadowTreeRevisionConsistencyManager_);
currentTask_ = &task;
currentPriority_ = task.priority;
auto taskStartTime = now_();
lastYieldingOpportunity_ = taskStartTime;
longestPeriodWithoutYieldingOpportunity_ = HighResDuration::zero();
auto didUserCallbackTimeout = task.expirationTime <= taskStartTime;
executeTask(runtime, task, didUserCallbackTimeout);
// "Perform a microtask checkpoint" step.
performMicrotaskCheckpoint(runtime);
auto taskEndTime = now_();
markYieldingOpportunity(taskEndTime);
reportLongTasks(task, taskStartTime, taskEndTime);
// "Update the rendering" step.
updateRendering(taskEndTime);
currentTask_ = nullptr;
}
/**
* This is partially equivalent to the "Update the rendering" step in the Web
* event loop. See
* https://html.spec.whatwg.org/multipage/webappapis.html#update-the-rendering.
*/
void RuntimeScheduler_Modern::updateRendering(HighResTimeStamp taskEndTime) {
TraceSection s("RuntimeScheduler::updateRendering");
// This is the integration of the Event Timing API in the Event Loop.
// See https://w3c.github.io/event-timing/#sec-modifications-HTML
const auto eventTimingDelegate = eventTimingDelegate_.load();
if (eventTimingDelegate != nullptr) {
eventTimingDelegate->dispatchPendingEventTimingEntries(
taskEndTime, surfaceIdsWithPendingRenderingUpdates_);
}
// This is the integration of the Intersection Observer API in the Event Loop.
// See
if (intersectionObserverDelegate_ != nullptr) {
intersectionObserverDelegate_->updateIntersectionObservations(
surfaceIdsWithPendingRenderingUpdates_);
}
surfaceIdsWithPendingRenderingUpdates_.clear();
while (!pendingRenderingUpdates_.empty()) {
auto& pendingRenderingUpdate = pendingRenderingUpdates_.front();
if (pendingRenderingUpdate != nullptr) {
pendingRenderingUpdate();
}
pendingRenderingUpdates_.pop();
}
}
void RuntimeScheduler_Modern::executeTask(
jsi::Runtime& runtime,
Task& task,
bool didUserCallbackTimeout) const {
TraceSection s(
"RuntimeScheduler::executeTask",
"id",
task.id,
"priority",
serialize(task.priority),
"didUserCallbackTimeout",
didUserCallbackTimeout);
try {
auto result = task.execute(runtime, didUserCallbackTimeout);
if (result.isObject() && result.getObject(runtime).isFunction(runtime)) {
// If the task returned a continuation callback, we re-assign it to the
// task and keep the task in the queue.
task.callback = result.getObject(runtime).getFunction(runtime);
}
} catch (jsi::JSError& error) {
onTaskError_(runtime, error);
} catch (std::exception& ex) {
jsi::JSError error(runtime, std::string("Non-js exception: ") + ex.what());
onTaskError_(runtime, error);
}
}
/**
* This is partially equivalent to the "Perform a microtask checkpoint" step in
* the Web event loop. See
* https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint.
*
* Iterates on \c drainMicrotasks until it completes or hits the retries bound.
*/
void RuntimeScheduler_Modern::performMicrotaskCheckpoint(
jsi::Runtime& runtime) {
if (performingMicrotaskCheckpoint_) {
return;
}
TraceSection s("RuntimeScheduler::performMicrotaskCheckpoint");
jsinspector_modern::tracing::EventLoopReporter performanceReporter(
jsinspector_modern::tracing::EventLoopPhase::Microtasks);
performingMicrotaskCheckpoint_ = true;
OnScopeExit restoreFlag([&]() { performingMicrotaskCheckpoint_ = false; });
uint8_t retries = 0;
// A heuristic number to guard infinite or absurd numbers of retries.
const static unsigned int kRetriesBound = 255;
while (retries < kRetriesBound) {
try {
// The default behavior of \c drainMicrotasks is unbounded execution.
// We may want to make it bounded in the future.
if (runtime.drainMicrotasks()) {
break;
}
} catch (jsi::JSError& error) {
onTaskError_(runtime, error);
} catch (std::exception& ex) {
jsi::JSError error(
runtime, std::string("Non-js exception: ") + ex.what());
onTaskError_(runtime, error);
}
retries++;
}
if (retries == kRetriesBound) {
throw std::runtime_error("Hits microtasks retries bound.");
}
}
void RuntimeScheduler_Modern::reportLongTasks(
const Task& /*task*/,
HighResTimeStamp startTime,
HighResTimeStamp endTime) {
auto reporter = performanceEntryReporter_;
if (reporter == nullptr) {
return;
}
if (longestPeriodWithoutYieldingOpportunity_ >=
LONG_TASK_DURATION_THRESHOLD) {
auto duration = endTime - startTime;
reporter->reportLongTask(startTime, duration);
}
}
void RuntimeScheduler_Modern::markYieldingOpportunity(
HighResTimeStamp currentTime) {
auto currentPeriod = currentTime - lastYieldingOpportunity_;
if (currentPeriod > longestPeriodWithoutYieldingOpportunity_) {
longestPeriodWithoutYieldingOpportunity_ = currentPeriod;
}
lastYieldingOpportunity_ = currentTime;
}
} // namespace facebook::react

View File

@@ -0,0 +1,216 @@
/*
* 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 <ReactCommon/RuntimeExecutor.h>
#include <react/renderer/consistency/ShadowTreeRevisionConsistencyManager.h>
#include <react/renderer/runtimescheduler/RuntimeScheduler.h>
#include <react/renderer/runtimescheduler/Task.h>
#include <atomic>
#include <memory>
#include <queue>
#include <shared_mutex>
namespace facebook::react {
class RuntimeScheduler_Modern final : public RuntimeSchedulerBase {
public:
explicit RuntimeScheduler_Modern(
RuntimeExecutor runtimeExecutor,
std::function<HighResTimeStamp()> now,
RuntimeSchedulerTaskErrorHandler onTaskError);
/*
* Not copyable.
*/
RuntimeScheduler_Modern(const RuntimeScheduler_Modern &) = delete;
RuntimeScheduler_Modern &operator=(const RuntimeScheduler_Modern &) = delete;
/*
* Not movable.
*/
RuntimeScheduler_Modern(RuntimeScheduler_Modern &&) = delete;
RuntimeScheduler_Modern &operator=(RuntimeScheduler_Modern &&) = delete;
/*
* Alias for scheduleTask with immediate priority.
*
* To be removed when we finish testing this implementation.
* All callers should use scheduleTask with the right priority after that.
*/
void scheduleWork(RawCallback &&callback) 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 the priority queue with the given priority.
* Triggers event loop if needed.
*/
std::shared_ptr<Task> scheduleTask(SchedulerPriority priority, jsi::Function &&callback) noexcept override;
/*
* Adds a custom callback to the priority queue with the given priority.
* Triggers event loop if needed.
*/
std::shared_ptr<Task> scheduleTask(SchedulerPriority priority, RawCallback &&callback) noexcept override;
/*
* Adds a JavaScript callback to the idle queue with the given timeout.
* Triggers event loop if needed.
*/
std::shared_ptr<Task> scheduleIdleTask(
jsi::Function &&callback,
HighResDuration customTimeout = timeoutForSchedulerPriority(SchedulerPriority::IdlePriority)) noexcept override;
/*
* Adds a custom callback to the idle queue with the given timeout.
* Triggers event loop if needed.
*/
std::shared_ptr<Task> scheduleIdleTask(
RawCallback &&callback,
HighResDuration customTimeout = timeoutForSchedulerPriority(SchedulerPriority::IdlePriority)) 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() 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.
*
* Can be called from any thread.
*/
HighResTimeStamp 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.
*
* TODO remove when we add support for microtasks
*/
void callExpiredTasks(jsi::Runtime &runtime) override;
/**
* Schedules a function that notifies or applies UI changes in the host
* platform, to be executed during the "Update the rendering" step of the
* event loop. If the step is not enabled, the function is executed
* immediately.
*/
void scheduleRenderingUpdate(SurfaceId surfaceId, RuntimeSchedulerRenderingUpdate &&renderingUpdate) override;
void setShadowTreeRevisionConsistencyManager(
ShadowTreeRevisionConsistencyManager *shadowTreeRevisionConsistencyManager) override;
void setPerformanceEntryReporter(PerformanceEntryReporter *performanceEntryReporter) override;
void setEventTimingDelegate(RuntimeSchedulerEventTimingDelegate *eventTimingDelegate) override;
void setIntersectionObserverDelegate(
RuntimeSchedulerIntersectionObserverDelegate *intersectionObserverDelegate) override;
private:
std::atomic<uint_fast8_t> syncTaskRequests_{0};
std::priority_queue<std::shared_ptr<Task>, std::vector<std::shared_ptr<Task>>, TaskPriorityComparer> taskQueue_;
Task *currentTask_{};
HighResTimeStamp lastYieldingOpportunity_;
HighResDuration longestPeriodWithoutYieldingOpportunity_;
void markYieldingOpportunity(HighResTimeStamp currentTime);
/**
* This protects the access to `taskQueue_` and `isevent loopScheduled_`.
*/
mutable std::shared_mutex schedulingMutex_;
const RuntimeExecutor runtimeExecutor_;
SchedulerPriority currentPriority_{SchedulerPriority::NormalPriority};
void scheduleEventLoop();
void runEventLoop(jsi::Runtime &runtime);
std::shared_ptr<Task> selectTask();
void scheduleTask(std::shared_ptr<Task> task);
/**
* Follows all the steps necessary to execute the given task.
* Depending on feature flags, this could also execute its microtasks.
* In the future, this will include other steps in the Web event loop, like
* updating the UI in native, executing resize observer callbacks, etc.
*/
void runEventLoopTick(jsi::Runtime &runtime, Task &task);
void executeTask(jsi::Runtime &runtime, Task &task, bool didUserCallbackTimeout) const;
void updateRendering(HighResTimeStamp taskEndTime);
bool performingMicrotaskCheckpoint_{false};
void performMicrotaskCheckpoint(jsi::Runtime &runtime);
void reportLongTasks(const Task &task, HighResTimeStamp startTime, HighResTimeStamp endTime);
/*
* Returns a time point representing the current point in time. May be called
* from multiple threads.
*/
std::function<HighResTimeStamp()> now_;
/*
* Flag indicating if callback on JavaScript queue has been
* scheduled.
*/
bool isEventLoopScheduled_{false};
std::queue<RuntimeSchedulerRenderingUpdate> pendingRenderingUpdates_;
std::unordered_set<SurfaceId> surfaceIdsWithPendingRenderingUpdates_;
// TODO(T227212654) eventTimingDelegate_ is only set once during startup, so
// the real fix here would be to delay runEventLoop until
// setEventTimingDelegate.
std::atomic<ShadowTreeRevisionConsistencyManager *> shadowTreeRevisionConsistencyManager_{nullptr};
std::atomic<RuntimeSchedulerEventTimingDelegate *> eventTimingDelegate_{nullptr};
PerformanceEntryReporter *performanceEntryReporter_{nullptr};
RuntimeSchedulerIntersectionObserverDelegate *intersectionObserverDelegate_{nullptr};
RuntimeSchedulerTaskErrorHandler onTaskError_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,60 @@
/*
* 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 <ReactCommon/SchedulerPriority.h>
#include <react/debug/react_native_assert.h>
#include <react/timing/primitives.h>
#include <chrono>
namespace facebook::react {
static constexpr std::underlying_type<SchedulerPriority>::type serialize(SchedulerPriority schedulerPriority)
{
return static_cast<std::underlying_type<SchedulerPriority>::type>(schedulerPriority);
}
static inline SchedulerPriority fromRawValue(double value)
{
switch ((int)value) {
case 1:
return SchedulerPriority::ImmediatePriority;
case 2:
return SchedulerPriority::UserBlockingPriority;
case 3:
return SchedulerPriority::NormalPriority;
case 4:
return SchedulerPriority::LowPriority;
case 5:
return SchedulerPriority::IdlePriority;
default:
react_native_assert(false && "Unsupported SchedulerPriority value");
return SchedulerPriority::NormalPriority;
}
}
static inline HighResDuration timeoutForSchedulerPriority(SchedulerPriority schedulerPriority) noexcept
{
switch (schedulerPriority) {
case SchedulerPriority::ImmediatePriority:
return HighResDuration::fromChrono(std::chrono::milliseconds(0));
case SchedulerPriority::UserBlockingPriority:
return HighResDuration::fromChrono(std::chrono::milliseconds(250));
case SchedulerPriority::NormalPriority:
return HighResDuration::fromChrono(std::chrono::seconds(5));
case SchedulerPriority::LowPriority:
return HighResDuration::fromChrono(std::chrono::seconds(10));
case SchedulerPriority::IdlePriority:
return HighResDuration::fromChrono(std::chrono::minutes(5));
default:
react_native_assert(false && "Unsupported SchedulerPriority value");
return HighResDuration::fromChrono(std::chrono::seconds(5));
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,66 @@
/*
* 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 "Task.h"
#include <atomic>
namespace facebook::react {
namespace {
inline uint64_t getNextId() noexcept {
static std::atomic<uint64_t> nextId = 1;
return nextId++;
}
} // namespace
Task::Task(
SchedulerPriority priority,
jsi::Function&& callback,
HighResTimeStamp expirationTime)
: priority(priority),
callback(std::move(callback)),
expirationTime(expirationTime),
id(getNextId()) {}
Task::Task(
SchedulerPriority priority,
RawCallback&& callback,
HighResTimeStamp expirationTime)
: priority(priority),
callback(std::move(callback)),
expirationTime(expirationTime),
id(getNextId()) {}
jsi::Value Task::execute(jsi::Runtime& runtime, bool didUserCallbackTimeout) {
auto result = jsi::Value::undefined();
// Canceled task doesn't have a callback.
if (!callback) {
return result;
}
// We get the value of the callback and reset it immediately to avoid it being
// called more than once (including when the callback throws).
auto originalCallback = std::move(*callback);
callback.reset();
if (originalCallback.index() == 0) {
// Callback in JavaScript is expecting a single bool parameter.
// React team plans to remove it in the future when a scheduler bug on web
// is resolved.
result = std::get<jsi::Function>(originalCallback)
.call(runtime, {didUserCallbackTimeout});
} else {
// Calling a raw callback
std::get<RawCallback>(originalCallback)(runtime);
}
return result;
}
} // namespace facebook::react

View File

@@ -0,0 +1,52 @@
/*
* 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 <ReactCommon/SchedulerPriority.h>
#include <jsi/jsi.h>
#include <react/timing/primitives.h>
#include <cstdint>
#include <optional>
#include <variant>
namespace facebook::react {
class RuntimeScheduler_Legacy;
class RuntimeScheduler_Modern;
class TaskPriorityComparer;
using RawCallback = std::function<void(jsi::Runtime &)>;
struct Task final : public jsi::NativeState {
Task(SchedulerPriority priority, jsi::Function &&callback, HighResTimeStamp expirationTime);
Task(SchedulerPriority priority, RawCallback &&callback, HighResTimeStamp expirationTime);
private:
friend RuntimeScheduler_Legacy;
friend RuntimeScheduler_Modern;
friend TaskPriorityComparer;
SchedulerPriority priority;
std::optional<std::variant<jsi::Function, RawCallback>> callback;
HighResTimeStamp expirationTime;
uint64_t id;
jsi::Value execute(jsi::Runtime &runtime, bool didUserCallbackTimeout);
};
class TaskPriorityComparer {
public:
inline bool operator()(const std::shared_ptr<Task> &lhs, const std::shared_ptr<Task> &rhs)
{
return lhs->expirationTime > rhs->expirationTime;
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,30 @@
/*
* 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 <jsi/jsi.h>
#include <react/renderer/runtimescheduler/Task.h>
namespace facebook::react {
inline static jsi::Value valueFromTask(jsi::Runtime &runtime, std::shared_ptr<Task> task)
{
jsi::Object obj(runtime);
obj.setNativeState(runtime, std::move(task));
return obj;
}
inline static std::shared_ptr<Task> taskFromValue(jsi::Runtime &runtime, const jsi::Value &value)
{
if (value.isNull()) {
return nullptr;
}
return value.getObject(runtime).getNativeState<Task>(runtime);
}
} // namespace facebook::react

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,47 @@
/*
* 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 <gtest/gtest.h>
#include <react/renderer/runtimescheduler/SchedulerPriorityUtils.h>
#include <react/renderer/runtimescheduler/Task.h>
#include <chrono>
using namespace facebook::react;
TEST(SchedulerPriorityTest, fromRawValue) {
EXPECT_EQ(SchedulerPriority::ImmediatePriority, fromRawValue(1.0));
EXPECT_EQ(SchedulerPriority::UserBlockingPriority, fromRawValue(2.0));
EXPECT_EQ(SchedulerPriority::NormalPriority, fromRawValue(3.0));
EXPECT_EQ(SchedulerPriority::LowPriority, fromRawValue(4.0));
EXPECT_EQ(SchedulerPriority::IdlePriority, fromRawValue(5.0));
}
TEST(SchedulerPriorityTest, serialize) {
EXPECT_EQ(serialize(SchedulerPriority::ImmediatePriority), 1);
EXPECT_EQ(serialize(SchedulerPriority::UserBlockingPriority), 2);
EXPECT_EQ(serialize(SchedulerPriority::NormalPriority), 3);
EXPECT_EQ(serialize(SchedulerPriority::LowPriority), 4);
EXPECT_EQ(serialize(SchedulerPriority::IdlePriority), 5);
}
TEST(SchedulerPriorityTest, timeoutForSchedulerPriority) {
EXPECT_EQ(
timeoutForSchedulerPriority(SchedulerPriority::ImmediatePriority),
std::chrono::milliseconds(0));
EXPECT_EQ(
timeoutForSchedulerPriority(SchedulerPriority::UserBlockingPriority),
std::chrono::milliseconds(250));
EXPECT_EQ(
timeoutForSchedulerPriority(SchedulerPriority::NormalPriority),
std::chrono::seconds(5));
EXPECT_EQ(
timeoutForSchedulerPriority(SchedulerPriority::LowPriority),
std::chrono::seconds(10));
EXPECT_EQ(
timeoutForSchedulerPriority(SchedulerPriority::IdlePriority),
std::chrono::minutes(5));
}

View File

@@ -0,0 +1,40 @@
/*
* 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 <react/timing/primitives.h>
namespace facebook::react {
class StubClock {
public:
HighResTimeStamp getNow() const
{
return timePoint_;
}
void setTimePoint(HighResTimeStamp timePoint)
{
timePoint_ = timePoint;
}
HighResTimeStamp getTimePoint()
{
return timePoint_;
}
void advanceTimeBy(HighResDuration duration)
{
timePoint_ += duration;
}
private:
HighResTimeStamp timePoint_ = HighResTimeStamp::now();
};
} // namespace facebook::react

View File

@@ -0,0 +1,67 @@
/*
* 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 <glog/logging.h>
#include <jsi/jsi.h>
namespace facebook::react {
/*
* Exposes StubErrorUtils to JavaScript realm.
*/
class StubErrorUtils : public jsi::HostObject {
public:
static std::shared_ptr<StubErrorUtils> createAndInstallIfNeeded(jsi::Runtime &runtime)
{
auto errorUtilsModuleName = "ErrorUtils";
auto errorUtilsValue = runtime.global().getProperty(runtime, errorUtilsModuleName);
if (errorUtilsValue.isUndefined()) {
auto stubErrorUtils = std::make_shared<StubErrorUtils>();
auto object = jsi::Object::createFromHostObject(runtime, stubErrorUtils);
runtime.global().setProperty(runtime, errorUtilsModuleName, std::move(object));
return stubErrorUtils;
}
auto stubErrorUtilsObject = errorUtilsValue.asObject(runtime);
return stubErrorUtilsObject.getHostObject<StubErrorUtils>(runtime);
}
/*
* `jsi::HostObject` specific overloads.
*/
jsi::Value get(jsi::Runtime &runtime, const jsi::PropNameID &name) override
{
auto propertyName = name.utf8(runtime);
if (propertyName == "reportFatalError") {
return jsi::Function::createFromHostFunction(
runtime,
name,
1,
[this](
jsi::Runtime &runtime, const jsi::Value &, const jsi::Value *arguments, size_t) noexcept -> jsi::Value {
reportFatalCallCount_++;
return jsi::Value::undefined();
});
}
return jsi::Value::undefined();
}
int getReportFatalCallCount() const
{
return reportFatalCallCount_;
}
private:
int reportFatalCallCount_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,73 @@
/*
* 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 <condition_variable>
#include <queue>
class StubQueue {
public:
void runOnQueue(std::function<void()> &&func)
{
{
std::lock_guard<std::mutex> lock(mutex_);
callbackQueue_.push(func);
}
signal_.notify_one();
}
void flush()
{
while (size() > 0) {
tick();
}
}
void tick()
{
std::function<void()> callback;
{
std::lock_guard<std::mutex> lock(mutex_);
if (!callbackQueue_.empty()) {
callback = callbackQueue_.front();
callbackQueue_.pop();
}
}
if (callback) {
callback();
}
}
size_t size() const
{
std::lock_guard<std::mutex> lock(mutex_);
return callbackQueue_.size();
}
bool waitForTask() const
{
std::unique_lock<std::mutex> lock(mutex_);
return signal_.wait_for(lock, StubQueue::timeout, [this]() { return !callbackQueue_.empty(); });
}
bool waitForTasks(std::size_t numberOfTasks) const
{
std::unique_lock<std::mutex> lock(mutex_);
return signal_.wait_for(
lock, StubQueue::timeout, [this, numberOfTasks]() { return numberOfTasks == callbackQueue_.size(); });
}
private:
mutable std::condition_variable signal_;
mutable std::mutex mutex_;
std::queue<std::function<void()>> callbackQueue_;
static constexpr std::chrono::duration<double> timeout = std::chrono::milliseconds(100);
};