jiuyiUniapp/service/node_modules/react-native/ReactCommon/react/runtime/TimerManager.cpp

473 lines
15 KiB
C++
Raw Normal View History

2025-02-13 09:59:20 +08:00
/*
* 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 "TimerManager.h"
#include <cxxreact/SystraceSection.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <cmath>
#include <utility>
namespace facebook::react {
namespace {
double coerceNumberTimeout(jsi::Runtime& rt, const jsi::Value& timeout) {
double delay = 0.0;
// fast-path
if (timeout.isNumber()) {
delay = timeout.getNumber();
} else {
// perform number coercion for timeout to be web spec compliant
auto numberCtor = rt.global().getPropertyAsObject(rt, "Number");
auto delayNumberObject =
numberCtor.getFunction(rt).callAsConstructor(rt, timeout).getObject(rt);
auto delayNumericValue =
delayNumberObject.getPropertyAsFunction(rt, "valueOf")
.callWithThis(rt, delayNumberObject);
delay = delayNumericValue.isNumber() ? delayNumericValue.getNumber() : 0;
}
return std::isnan(delay) ? 0.0 : std::max(0.0, delay);
}
inline const char* getTimerSourceName(TimerSource source) {
switch (source) {
case TimerSource::Unknown:
return "unknown";
case TimerSource::SetTimeout:
return "setTimeout";
case TimerSource::SetInterval:
return "setInterval";
case TimerSource::RequestAnimationFrame:
return "requestAnimationFrame";
}
return "unknown";
}
} // namespace
TimerManager::TimerManager(
std::unique_ptr<PlatformTimerRegistry> platformTimerRegistry) noexcept
: platformTimerRegistry_(std::move(platformTimerRegistry)) {}
void TimerManager::setRuntimeExecutor(
RuntimeExecutor runtimeExecutor) noexcept {
runtimeExecutor_ = runtimeExecutor;
}
TimerHandle TimerManager::createReactNativeMicrotask(
jsi::Function&& callback,
std::vector<jsi::Value>&& args) {
// Get the id for the callback.
TimerHandle timerID = timerIndex_++;
timers_.emplace(
std::piecewise_construct,
std::forward_as_tuple(timerID),
std::forward_as_tuple(
std::move(callback), std::move(args), /* repeat */ false));
reactNativeMicrotasksQueue_.push_back(timerID);
return timerID;
}
void TimerManager::callReactNativeMicrotasks(jsi::Runtime& runtime) {
std::vector<TimerHandle> reactNativeMicrotasksQueue;
while (!reactNativeMicrotasksQueue_.empty()) {
reactNativeMicrotasksQueue.clear();
reactNativeMicrotasksQueue.swap(reactNativeMicrotasksQueue_);
for (auto reactNativeMicrotaskID : reactNativeMicrotasksQueue) {
// ReactNativeMicrotasks can clear other scheduled reactNativeMicrotasks.
auto it = timers_.find(reactNativeMicrotaskID);
if (it != timers_.end()) {
it->second.invoke(runtime);
// Invoking a timer has the potential to delete it. Do not re-use the
// existing iterator to erase it from the map.
timers_.erase(reactNativeMicrotaskID);
}
}
}
}
TimerHandle TimerManager::createTimer(
jsi::Function&& callback,
std::vector<jsi::Value>&& args,
double delay,
TimerSource source) {
// Get the id for the callback.
TimerHandle timerID = timerIndex_++;
SystraceSection s(
"TimerManager::createTimer",
"id",
timerID,
"type",
getTimerSourceName(source),
"delay",
delay);
timers_.emplace(
std::piecewise_construct,
std::forward_as_tuple(timerID),
std::forward_as_tuple(
std::move(callback),
std::move(args),
/* repeat */ false,
source));
platformTimerRegistry_->createTimer(timerID, delay);
return timerID;
}
TimerHandle TimerManager::createRecurringTimer(
jsi::Function&& callback,
std::vector<jsi::Value>&& args,
double delay,
TimerSource source) {
// Get the id for the callback.
TimerHandle timerID = timerIndex_++;
SystraceSection s(
"TimerManager::createRecurringTimer",
"id",
timerID,
"type",
getTimerSourceName(source),
"delay",
delay);
timers_.emplace(
std::piecewise_construct,
std::forward_as_tuple(timerID),
std::forward_as_tuple(
std::move(callback), std::move(args), /* repeat */ true, source));
platformTimerRegistry_->createRecurringTimer(timerID, delay);
return timerID;
}
void TimerManager::deleteReactNativeMicrotask(
jsi::Runtime& runtime,
TimerHandle timerHandle) {
if (timerHandle < 0) {
throw jsi::JSError(
runtime, "clearReactNativeMicrotask was called with an invalid handle");
}
auto it = std::find(
reactNativeMicrotasksQueue_.begin(),
reactNativeMicrotasksQueue_.end(),
timerHandle);
if (it != reactNativeMicrotasksQueue_.end()) {
reactNativeMicrotasksQueue_.erase(it);
timers_.erase(timerHandle);
}
}
void TimerManager::deleteTimer(jsi::Runtime& runtime, TimerHandle timerHandle) {
if (timerHandle < 0) {
throw jsi::JSError(runtime, "clearTimeout called with an invalid handle");
}
platformTimerRegistry_->deleteTimer(timerHandle);
timers_.erase(timerHandle);
}
void TimerManager::deleteRecurringTimer(
jsi::Runtime& runtime,
TimerHandle timerHandle) {
if (timerHandle < 0) {
throw jsi::JSError(runtime, "clearInterval called with an invalid handle");
}
platformTimerRegistry_->deleteTimer(timerHandle);
timers_.erase(timerHandle);
}
void TimerManager::callTimer(TimerHandle timerHandle) {
runtimeExecutor_([this, timerHandle](jsi::Runtime& runtime) {
auto it = timers_.find(timerHandle);
if (it != timers_.end()) {
auto& timerCallback = it->second;
bool repeats = timerCallback.repeat;
{
SystraceSection s(
"TimerManager::callTimer",
"id",
timerHandle,
"type",
getTimerSourceName(timerCallback.source));
timerCallback.invoke(runtime);
}
if (!repeats) {
// Invoking a timer has the potential to delete it. Do not re-use the
// existing iterator to erase it from the map.
timers_.erase(timerHandle);
}
}
});
}
void TimerManager::attachGlobals(jsi::Runtime& runtime) {
// Install host functions for timers.
// TODO (T45786383): Add missing timer functions from JSTimers
// Ensure that we don't define `setImmediate` and `clearImmediate` if
// microtasks are enabled (as we polyfill them using `queueMicrotask` then).
if (ReactNativeFeatureFlags::disableEventLoopOnBridgeless()) {
runtime.global().setProperty(
runtime,
"setImmediate",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "setImmediate"),
2, // Function, ...args
[this](
jsi::Runtime& rt,
const jsi::Value& thisVal,
const jsi::Value* args,
size_t count) {
if (count == 0) {
throw jsi::JSError(
rt,
"setImmediate must be called with at least one argument (a function to call)");
}
if (!args[0].isObject() || !args[0].asObject(rt).isFunction(rt)) {
// Do not throw any error to match web spec
return timerIndex_++;
}
auto callback = args[0].getObject(rt).getFunction(rt);
// Package up the remaining argument values into one place.
std::vector<jsi::Value> moreArgs;
for (size_t extraArgNum = 1; extraArgNum < count; extraArgNum++) {
moreArgs.emplace_back(rt, args[extraArgNum]);
}
return createReactNativeMicrotask(
std::move(callback), std::move(moreArgs));
}));
runtime.global().setProperty(
runtime,
"clearImmediate",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "clearImmediate"),
1, // handle
[this](
jsi::Runtime& rt,
const jsi::Value& thisVal,
const jsi::Value* args,
size_t count) {
if (count > 0 && args[0].isNumber()) {
auto handle = (TimerHandle)args[0].asNumber();
deleteReactNativeMicrotask(rt, handle);
}
return jsi::Value::undefined();
}));
}
runtime.global().setProperty(
runtime,
"setTimeout",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "setTimeout"),
3, // Function, delay, ...args
[this](
jsi::Runtime& rt,
const jsi::Value& thisVal,
const jsi::Value* args,
size_t count) {
if (count == 0) {
throw jsi::JSError(
rt,
"setTimeout must be called with at least one argument (the function to call).");
}
if (!args[0].isObject() || !args[0].asObject(rt).isFunction(rt)) {
// Do not throw any error to match web spec
return timerIndex_++;
}
auto callback = args[0].getObject(rt).getFunction(rt);
auto delay = count > 1 ? coerceNumberTimeout(rt, args[1]) : 0.0;
// Package up the remaining argument values into one place.
std::vector<jsi::Value> moreArgs;
for (size_t extraArgNum = 2; extraArgNum < count; extraArgNum++) {
moreArgs.emplace_back(rt, args[extraArgNum]);
}
return createTimer(
std::move(callback),
std::move(moreArgs),
delay,
TimerSource::SetTimeout);
}));
runtime.global().setProperty(
runtime,
"clearTimeout",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "clearTimeout"),
1, // timerID
[this](
jsi::Runtime& rt,
const jsi::Value& thisVal,
const jsi::Value* args,
size_t count) {
if (count > 0 && args[0].isNumber()) {
auto handle = (TimerHandle)args[0].asNumber();
deleteTimer(rt, handle);
}
return jsi::Value::undefined();
}));
runtime.global().setProperty(
runtime,
"setInterval",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "setInterval"),
3, // Function, delay, ...args
[this](
jsi::Runtime& rt,
const jsi::Value& thisVal,
const jsi::Value* args,
size_t count) {
if (count == 0) {
throw jsi::JSError(
rt,
"setInterval must be called with at least one argument (the function to call).");
}
if (!args[0].isObject() || !args[0].asObject(rt).isFunction(rt)) {
throw jsi::JSError(
rt, "The first argument to setInterval must be a function.");
}
auto callback = args[0].getObject(rt).getFunction(rt);
auto delay = count > 1
? coerceNumberTimeout(rt, jsi::Value{rt, args[1]})
: 0.0;
// Package up the remaining argument values into one place.
std::vector<jsi::Value> moreArgs;
for (size_t extraArgNum = 2; extraArgNum < count; extraArgNum++) {
moreArgs.emplace_back(rt, args[extraArgNum]);
}
return createRecurringTimer(
std::move(callback),
std::move(moreArgs),
delay,
TimerSource::SetInterval);
}));
runtime.global().setProperty(
runtime,
"clearInterval",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "clearInterval"),
1, // timerID
[this](
jsi::Runtime& rt,
const jsi::Value& thisVal,
const jsi::Value* args,
size_t count) {
if (count > 0 && args[0].isNumber()) {
auto handle = (TimerHandle)args[0].asNumber();
deleteRecurringTimer(rt, handle);
}
return jsi::Value::undefined();
}));
runtime.global().setProperty(
runtime,
"requestAnimationFrame",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "requestAnimationFrame"),
1, // callback
[this](
jsi::Runtime& rt,
const jsi::Value& thisVal,
const jsi::Value* args,
size_t count) {
if (count == 0) {
throw jsi::JSError(
rt,
"requestAnimationFrame must be called with at least one argument (i.e: a callback)");
}
if (!args[0].isObject() || !args[0].asObject(rt).isFunction(rt)) {
throw jsi::JSError(
rt,
"The first argument to requestAnimationFrame must be a function.");
}
auto callback = jsi::Function::createFromHostFunction(
rt,
jsi::PropNameID::forAscii(rt, "RN$rafFn"),
0,
[callbackContainer = std::make_shared<jsi::Function>(
args[0].getObject(rt).getFunction(rt))](
jsi::Runtime& rt,
const jsi::Value& thisVal,
const jsi::Value* args,
size_t count) {
auto performance =
rt.global().getPropertyAsObject(rt, "performance");
auto nowFn = performance.getPropertyAsFunction(rt, "now");
auto now = nowFn.callWithThis(rt, performance, {});
return callbackContainer->call(rt, {std::move(now)});
});
// The current implementation of requestAnimationFrame is the same
// as setTimeout(0). This isn't exactly how requestAnimationFrame
// is supposed to work on web, and may change in the future.
return createTimer(
std::move(callback),
std::vector<jsi::Value>(),
/* delay */ 0,
TimerSource::RequestAnimationFrame);
}));
runtime.global().setProperty(
runtime,
"cancelAnimationFrame",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "cancelAnimationFrame"),
1, // timerID
[this](
jsi::Runtime& rt,
const jsi::Value& thisVal,
const jsi::Value* args,
size_t count) {
if (count > 0 && args[0].isNumber()) {
auto handle = (TimerHandle)args[0].asNumber();
deleteTimer(rt, handle);
}
return jsi::Value::undefined();
}));
}
} // namespace facebook::react