feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
add_compile_options(
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic
-DLOG_TAG=\"Fabric\")
file(GLOB react_render_runtimescheduler_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_render_runtimescheduler STATIC ${react_render_runtimescheduler_SRC})
target_include_directories(react_render_runtimescheduler PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_render_runtimescheduler
callinvoker
jsi
react_debug
react_render_core
react_render_debug
react_utils
react_featureflags
runtimeexecutor)

View File

@@ -0,0 +1,34 @@
/*
* 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 <jsi/jsi.h>
namespace facebook::react {
inline static void handleFatalError(
jsi::Runtime& runtime,
const jsi::JSError& error) {
auto reportFatalError = "reportFatalError";
auto errorUtils = runtime.global().getProperty(runtime, "ErrorUtils");
if (errorUtils.isUndefined() || !errorUtils.isObject() ||
!errorUtils.getObject(runtime).hasProperty(runtime, reportFatalError)) {
// ErrorUtils was not set up. This probably means the bundle didn't
// load properly.
throw jsi::JSError(
runtime,
"ErrorUtils is not set up properly. Something probably went wrong trying to load the JS bundle. Trying to report error " +
error.getMessage(),
error.getStack());
}
auto func = errorUtils.asObject(runtime).getPropertyAsFunction(
runtime, reportFatalError);
func.call(runtime, error.value());
}
} // namespace facebook::react

View File

@@ -0,0 +1,71 @@
# 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
folly_config = get_folly_config()
folly_compiler_flags = folly_config[:compiler_flags]
folly_version = folly_config[:version]
header_search_paths = [
"\"$(PODS_ROOT)/RCT-Folly\"",
"\"$(PODS_ROOT)/boost\"",
]
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 = "**/*.{cpp,h}"
s.compiler_flags = folly_compiler_flags
s.header_dir = "react/renderer/runtimescheduler"
s.exclude_files = "tests"
s.pod_target_xcconfig = {
"CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
"HEADER_SEARCH_PATHS" => header_search_paths.join(' ')}
if ENV['USE_FRAMEWORKS']
s.module_name = "React_runtimescheduler"
s.header_mappings_dir = "../../.."
end
s.dependency "React-runtimeexecutor"
s.dependency "React-callinvoker"
s.dependency "React-cxxreact"
s.dependency "React-rendererdebug"
s.dependency "React-utils"
s.dependency "React-featureflags"
s.dependency "glog"
s.dependency "RCT-Folly", folly_version
s.dependency "React-jsi"
add_dependency(s, "React-debug")
if ENV["USE_HERMES"] == nil || ENV["USE_HERMES"] == "1"
s.dependency "hermes-engine"
else
s.dependency "React-jsc"
end
end

View File

@@ -0,0 +1,91 @@
/*
* 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 "SchedulerPriorityUtils.h"
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/debug/SystraceSection.h>
#include <utility>
#include "ErrorUtils.h"
namespace facebook::react {
namespace {
std::unique_ptr<RuntimeSchedulerBase> getRuntimeSchedulerImplementation(
RuntimeExecutor runtimeExecutor,
std::function<RuntimeSchedulerTimePoint()> now) {
if (ReactNativeFeatureFlags::useModernRuntimeScheduler()) {
return std::make_unique<RuntimeScheduler_Modern>(
std::move(runtimeExecutor), std::move(now));
} else {
return std::make_unique<RuntimeScheduler_Legacy>(
std::move(runtimeExecutor), std::move(now));
}
}
} // namespace
RuntimeScheduler::RuntimeScheduler(
RuntimeExecutor runtimeExecutor,
std::function<RuntimeSchedulerTimePoint()> now)
: runtimeSchedulerImpl_(getRuntimeSchedulerImplementation(
std::move(runtimeExecutor),
std::move(now))) {}
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));
}
bool RuntimeScheduler::getShouldYield() const noexcept {
return runtimeSchedulerImpl_->getShouldYield();
}
bool RuntimeScheduler::getIsSynchronous() const noexcept {
return runtimeSchedulerImpl_->getIsSynchronous();
}
void RuntimeScheduler::cancelTask(Task& task) noexcept {
return runtimeSchedulerImpl_->cancelTask(task);
}
SchedulerPriority RuntimeScheduler::getCurrentPriorityLevel() const noexcept {
return runtimeSchedulerImpl_->getCurrentPriorityLevel();
}
RuntimeSchedulerTimePoint 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(
RuntimeSchedulerRenderingUpdate&& renderingUpdate) {
return runtimeSchedulerImpl_->scheduleRenderingUpdate(
std::move(renderingUpdate));
}
} // namespace facebook::react

View File

@@ -0,0 +1,145 @@
/*
* 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/runtimescheduler/RuntimeSchedulerClock.h>
#include <react/renderer/runtimescheduler/Task.h>
namespace facebook::react {
using RuntimeSchedulerRenderingUpdate = std::function<void()>;
// 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 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;
virtual void scheduleRenderingUpdate(
RuntimeSchedulerRenderingUpdate&& renderingUpdate) = 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<RuntimeSchedulerTimePoint()> now =
RuntimeSchedulerClock::now);
/*
* 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;
/*
* 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;
void scheduleRenderingUpdate(
RuntimeSchedulerRenderingUpdate&& renderingUpdate) override;
private:
// Actual implementation, stored as a unique pointer to simplify memory
// management.
std::unique_ptr<RuntimeSchedulerBase> runtimeSchedulerImpl_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,202 @@
/*
* 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 "RuntimeScheduler.h"
#include "SchedulerPriorityUtils.h"
#include "primitives.h"
#include <chrono>
#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)) {}
bool RuntimeSchedulerBinding::getIsSynchronous() const {
return runtimeScheduler_->getIsSynchronous();
}
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 asDouble =
std::chrono::duration<double, std::milli>(now.time_since_epoch())
.count();
return {asDouble};
});
}
// 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,51 @@
/*
* 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);
/*
* `jsi::HostObject` specific overloads.
*/
jsi::Value get(jsi::Runtime& runtime, const jsi::PropNameID& name) override;
bool getIsSynchronous() const;
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&) { func(); });
}
}
void RuntimeSchedulerCallInvoker::invokeSync(CallFunc&& func) {
if (auto runtimeScheduler = runtimeScheduler_.lock()) {
runtimeScheduler->executeNowOnTheSameThread(
[func = std::move(func)](jsi::Runtime&) { func(); });
}
}
void RuntimeSchedulerCallInvoker::invokeAsync(
SchedulerPriority priority,
CallFunc&& func) noexcept {
if (auto runtimeScheduler = runtimeScheduler_.lock()) {
runtimeScheduler->scheduleTask(
priority, [func = std::move(func)](jsi::Runtime&) { func(); });
}
}
} // 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>
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,22 @@
/*
* 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 <chrono>
namespace facebook::react {
/*
* Represents a monotonic clock suitable for measuring intervals.
*/
using RuntimeSchedulerClock = std::chrono::steady_clock;
using RuntimeSchedulerTimePoint = RuntimeSchedulerClock::time_point;
using RuntimeSchedulerDuration = RuntimeSchedulerClock::duration;
} // namespace facebook::react

View File

@@ -0,0 +1,215 @@
/*
* 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 <react/renderer/debug/SystraceSection.h>
#include <utility>
#include "ErrorUtils.h"
namespace facebook::react {
#pragma mark - Public
RuntimeScheduler_Legacy::RuntimeScheduler_Legacy(
RuntimeExecutor runtimeExecutor,
std::function<RuntimeSchedulerTimePoint()> now)
: runtimeExecutor_(std::move(runtimeExecutor)), now_(std::move(now)) {}
void RuntimeScheduler_Legacy::scheduleWork(RawCallback&& callback) 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<Task> RuntimeScheduler_Legacy::scheduleTask(
SchedulerPriority priority,
jsi::Function&& callback) noexcept {
SystraceSection 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 {
SystraceSection 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;
}
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;
}
void RuntimeScheduler_Legacy::scheduleRenderingUpdate(
RuntimeSchedulerRenderingUpdate&& renderingUpdate) {
SystraceSection s("RuntimeScheduler::scheduleRenderingUpdate");
if (renderingUpdate != nullptr) {
renderingUpdate();
}
}
#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) {
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>& task,
bool didUserCallbackTimeout) {
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

View File

@@ -0,0 +1,164 @@
/*
* 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/runtimescheduler/RuntimeScheduler.h>
#include <react/renderer/runtimescheduler/RuntimeSchedulerClock.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<RuntimeSchedulerTimePoint()> 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) 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;
/*
* 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;
void scheduleRenderingUpdate(
RuntimeSchedulerRenderingUpdate&& renderingUpdate) 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<RuntimeSchedulerTimePoint()> 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};
};
} // namespace facebook::react

View File

@@ -0,0 +1,342 @@
/*
* 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 "SchedulerPriorityUtils.h"
#include <cxxreact/ErrorUtils.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/debug/SystraceSection.h>
#include <utility>
#include "ErrorUtils.h"
namespace facebook::react {
namespace {
/**
* 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 executeMicrotasks(jsi::Runtime& runtime) {
SystraceSection s("RuntimeScheduler::executeMicrotasks");
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) {
handleJSError(runtime, error, true);
}
retries++;
}
if (retries == kRetriesBound) {
throw std::runtime_error("Hits microtasks retries bound.");
}
}
} // namespace
#pragma mark - Public
RuntimeScheduler_Modern::RuntimeScheduler_Modern(
RuntimeExecutor runtimeExecutor,
std::function<RuntimeSchedulerTimePoint()> now)
: runtimeExecutor_(std::move(runtimeExecutor)), now_(std::move(now)) {}
void RuntimeScheduler_Modern::scheduleWork(RawCallback&& callback) noexcept {
SystraceSection s("RuntimeScheduler::scheduleWork");
scheduleTask(SchedulerPriority::ImmediatePriority, std::move(callback));
}
std::shared_ptr<Task> RuntimeScheduler_Modern::scheduleTask(
SchedulerPriority priority,
jsi::Function&& callback) noexcept {
SystraceSection 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);
scheduleTask(task);
return task;
}
std::shared_ptr<Task> RuntimeScheduler_Modern::scheduleTask(
SchedulerPriority priority,
RawCallback&& callback) noexcept {
SystraceSection s(
"RuntimeScheduler::scheduleTask",
"priority",
serialize(priority),
"callbackType",
"RawCallback");
auto expirationTime = now_() + timeoutForSchedulerPriority(priority);
auto task =
std::make_shared<Task>(priority, std::move(callback), expirationTime);
scheduleTask(task);
return task;
}
bool RuntimeScheduler_Modern::getShouldYield() const noexcept {
std::shared_lock lock(schedulingMutex_);
return syncTaskRequests_ > 0 ||
(!taskQueue_.empty() && taskQueue_.top() != currentTask_);
}
bool RuntimeScheduler_Modern::getIsSynchronous() const noexcept {
return isSynchronous_;
}
void RuntimeScheduler_Modern::cancelTask(Task& task) noexcept {
task.callback.reset();
}
SchedulerPriority RuntimeScheduler_Modern::getCurrentPriorityLevel()
const noexcept {
return currentPriority_;
}
RuntimeSchedulerTimePoint RuntimeScheduler_Modern::now() const noexcept {
return now_();
}
void RuntimeScheduler_Modern::executeNowOnTheSameThread(
RawCallback&& callback) {
SystraceSection s("RuntimeScheduler::executeNowOnTheSameThread");
syncTaskRequests_++;
executeSynchronouslyOnSameThread_CAN_DEADLOCK(
runtimeExecutor_,
[this, callback = std::move(callback)](jsi::Runtime& runtime) mutable {
SystraceSection s2(
"RuntimeScheduler::executeNowOnTheSameThread callback");
syncTaskRequests_--;
isSynchronous_ = true;
auto currentTime = now_();
auto priority = SchedulerPriority::ImmediatePriority;
auto expirationTime =
currentTime + timeoutForSchedulerPriority(priority);
auto task = std::make_shared<Task>(
priority, std::move(callback), expirationTime);
executeTask(runtime, task, currentTime);
isSynchronous_ = false;
});
bool shouldScheduleWorkLoop = false;
{
// Unique access because we might write to `isWorkLoopScheduled_`.
std::unique_lock lock(schedulingMutex_);
// We only need to schedule the work loop if there any remaining tasks
// in the queue.
if (!taskQueue_.empty() && !isWorkLoopScheduled_) {
isWorkLoopScheduled_ = true;
shouldScheduleWorkLoop = true;
}
}
if (shouldScheduleWorkLoop) {
scheduleWorkLoop();
}
}
void RuntimeScheduler_Modern::callExpiredTasks(jsi::Runtime& runtime) {
// If we have first-class support for microtasks, this a no-op.
if (ReactNativeFeatureFlags::enableMicrotasks()) {
return;
}
SystraceSection s("RuntimeScheduler::callExpiredTasks");
startWorkLoop(runtime, true);
}
void RuntimeScheduler_Modern::scheduleRenderingUpdate(
RuntimeSchedulerRenderingUpdate&& renderingUpdate) {
SystraceSection s("RuntimeScheduler::scheduleRenderingUpdate");
if (ReactNativeFeatureFlags::batchRenderingUpdatesInEventLoop()) {
pendingRenderingUpdates_.push(renderingUpdate);
} else {
if (renderingUpdate != nullptr) {
renderingUpdate();
}
}
}
#pragma mark - Private
void RuntimeScheduler_Modern::scheduleTask(std::shared_ptr<Task> task) {
bool shouldScheduleWorkLoop = false;
{
std::unique_lock lock(schedulingMutex_);
// We only need to schedule the work 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() && !isWorkLoopScheduled_) {
isWorkLoopScheduled_ = true;
shouldScheduleWorkLoop = true;
}
taskQueue_.push(task);
}
if (shouldScheduleWorkLoop) {
scheduleWorkLoop();
}
}
void RuntimeScheduler_Modern::scheduleWorkLoop() {
runtimeExecutor_(
[this](jsi::Runtime& runtime) { startWorkLoop(runtime, false); });
}
void RuntimeScheduler_Modern::startWorkLoop(
jsi::Runtime& runtime,
bool onlyExpired) {
SystraceSection s("RuntimeScheduler::startWorkLoop");
auto previousPriority = currentPriority_;
try {
while (syncTaskRequests_ == 0) {
auto currentTime = now_();
auto topPriorityTask = selectTask(currentTime, onlyExpired);
if (!topPriorityTask) {
// No pending work to do.
// Events will restart the loop when necessary.
break;
}
executeTask(runtime, topPriorityTask, currentTime);
}
} catch (jsi::JSError& error) {
handleFatalError(runtime, error);
}
currentPriority_ = previousPriority;
}
std::shared_ptr<Task> RuntimeScheduler_Modern::selectTask(
RuntimeSchedulerTimePoint currentTime,
bool onlyExpired) {
// 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.
isWorkLoopScheduled_ = false;
// Skip executed tasks
while (!taskQueue_.empty() && !taskQueue_.top()->callback) {
taskQueue_.pop();
}
if (!taskQueue_.empty()) {
auto task = taskQueue_.top();
auto didUserCallbackTimeout = task->expirationTime <= currentTime;
if (!onlyExpired || didUserCallbackTimeout) {
return task;
}
}
return nullptr;
}
void RuntimeScheduler_Modern::executeTask(
jsi::Runtime& runtime,
const std::shared_ptr<Task>& task,
RuntimeSchedulerTimePoint currentTime) {
auto didUserCallbackTimeout = task->expirationTime <= currentTime;
SystraceSection s(
"RuntimeScheduler::executeTask",
"priority",
serialize(task->priority),
"didUserCallbackTimeout",
didUserCallbackTimeout);
currentTask_ = task;
currentPriority_ = task->priority;
executeMacrotask(runtime, task, didUserCallbackTimeout);
if (ReactNativeFeatureFlags::enableMicrotasks()) {
// "Perform a microtask checkpoint" step.
executeMicrotasks(runtime);
}
if (ReactNativeFeatureFlags::batchRenderingUpdatesInEventLoop()) {
// "Update the rendering" step.
updateRendering();
}
}
/**
* 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() {
SystraceSection s("RuntimeScheduler::updateRendering");
while (!pendingRenderingUpdates_.empty()) {
auto& pendingRenderingUpdate = pendingRenderingUpdates_.front();
if (pendingRenderingUpdate != nullptr) {
pendingRenderingUpdate();
}
pendingRenderingUpdates_.pop();
}
}
void RuntimeScheduler_Modern::executeMacrotask(
jsi::Runtime& runtime,
std::shared_ptr<Task> task,
bool didUserCallbackTimeout) const {
SystraceSection s("RuntimeScheduler::executeMacrotask");
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);
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,196 @@
/*
* 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/runtimescheduler/RuntimeScheduler.h>
#include <react/renderer/runtimescheduler/RuntimeSchedulerClock.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<RuntimeSchedulerTimePoint()> now =
RuntimeSchedulerClock::now);
/*
* 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 afte 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 workloop 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 workloop if needed.
*/
std::shared_ptr<Task> 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.
*
* Can be called from any thread.
*/
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.
*
* 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(
RuntimeSchedulerRenderingUpdate&& renderingUpdate) override;
private:
std::atomic<uint_fast8_t> syncTaskRequests_{0};
std::priority_queue<
std::shared_ptr<Task>,
std::vector<std::shared_ptr<Task>>,
TaskPriorityComparer>
taskQueue_;
std::shared_ptr<Task> currentTask_;
/**
* This protects the access to `taskQueue_` and `isWorkLoopScheduled_`.
*/
mutable std::shared_mutex schedulingMutex_;
const RuntimeExecutor runtimeExecutor_;
SchedulerPriority currentPriority_{SchedulerPriority::NormalPriority};
std::atomic_bool isSynchronous_{false};
void scheduleWorkLoop();
void startWorkLoop(jsi::Runtime& runtime, bool onlyExpired);
std::shared_ptr<Task> selectTask(
RuntimeSchedulerTimePoint currentTime,
bool onlyExpired);
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 executeTask(
jsi::Runtime& runtime,
const std::shared_ptr<Task>& task,
RuntimeSchedulerTimePoint currentTime);
void executeMacrotask(
jsi::Runtime& runtime,
std::shared_ptr<Task> task,
bool didUserCallbackTimeout) const;
void updateRendering();
/*
* Returns a time point representing the current point in time. May be called
* from multiple threads.
*/
std::function<RuntimeSchedulerTimePoint()> now_;
/*
* Flag indicating if callback on JavaScript queue has been
* scheduled.
*/
bool isWorkLoopScheduled_{false};
std::queue<RuntimeSchedulerRenderingUpdate> pendingRenderingUpdates_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,56 @@
/*
* 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 <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 std::chrono::milliseconds timeoutForSchedulerPriority(
SchedulerPriority schedulerPriority) noexcept {
switch (schedulerPriority) {
case SchedulerPriority::ImmediatePriority:
return std::chrono::milliseconds(-1);
case SchedulerPriority::UserBlockingPriority:
return std::chrono::milliseconds(250);
case SchedulerPriority::NormalPriority:
return std::chrono::milliseconds(5000);
case SchedulerPriority::LowPriority:
return std::chrono::milliseconds(10'000);
case SchedulerPriority::IdlePriority:
return std::chrono::milliseconds::max();
}
}
} // 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.
*/
#include "RuntimeScheduler.h"
namespace facebook::react {
Task::Task(
SchedulerPriority priority,
jsi::Function&& callback,
std::chrono::steady_clock::time_point expirationTime)
: priority(priority),
callback(std::move(callback)),
expirationTime(expirationTime) {}
Task::Task(
SchedulerPriority priority,
RawCallback&& callback,
std::chrono::steady_clock::time_point expirationTime)
: priority(priority),
callback(std::move(callback)),
expirationTime(expirationTime) {}
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;
}
auto& cbVal = callback.value();
if (cbVal.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>(cbVal).call(runtime, {didUserCallbackTimeout});
} else {
// Calling a raw callback
std::get<RawCallback>(cbVal)(runtime);
}
// Destroying callback to prevent calling it twice.
callback.reset();
return result;
}
} // namespace facebook::react

View File

@@ -0,0 +1,57 @@
/*
* 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/renderer/runtimescheduler/RuntimeSchedulerClock.h>
#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,
std::chrono::steady_clock::time_point expirationTime);
Task(
SchedulerPriority priority,
RawCallback&& callback,
std::chrono::steady_clock::time_point expirationTime);
private:
friend RuntimeScheduler_Legacy;
friend RuntimeScheduler_Modern;
friend TaskPriorityComparer;
SchedulerPriority priority;
std::optional<std::variant<jsi::Function, RawCallback>> callback;
RuntimeSchedulerClock::time_point expirationTime;
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,33 @@
/*
* 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>
#include <react/utils/CoreFeatures.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

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(-1));
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::milliseconds::max());
}

View File

@@ -0,0 +1,41 @@
/*
* 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/renderer/runtimescheduler/RuntimeSchedulerClock.h>
#include <chrono>
namespace facebook::react {
class StubClock {
public:
RuntimeSchedulerTimePoint getNow() const {
return timePoint_;
}
void setTimePoint(RuntimeSchedulerTimePoint timePoint) {
timePoint_ = timePoint;
}
void setTimePoint(RuntimeSchedulerDuration duration) {
timePoint_ = RuntimeSchedulerTimePoint(duration);
}
RuntimeSchedulerTimePoint getTimePoint() {
return timePoint_;
}
void advanceTimeBy(RuntimeSchedulerDuration duration) {
timePoint_ += duration;
}
private:
RuntimeSchedulerTimePoint timePoint_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,70 @@
/*
* 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,70 @@
/*
* 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);
};