Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 125 additions & 63 deletions packages/react-native/ReactCommon/react/runtime/TimerManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,26 @@
#include "TimerManager.h"

#include <cxxreact/SystraceSection.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <utility>

namespace facebook::react {

namespace {
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";
}
}
} // namespace

TimerManager::TimerManager(
std::unique_ptr<PlatformTimerRegistry> platformTimerRegistry) noexcept
: platformTimerRegistry_(std::move(platformTimerRegistry)) {}
Expand Down Expand Up @@ -59,14 +75,28 @@ void TimerManager::callReactNativeMicrotasks(jsi::Runtime& runtime) {
TimerHandle TimerManager::createTimer(
jsi::Function&& callback,
std::vector<jsi::Value>&& args,
double delay) {
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));
std::move(callback),
std::move(args),
/* repeat */ false,
source));

platformTimerRegistry_->createTimer(timerID, delay);

Expand All @@ -76,14 +106,25 @@ TimerHandle TimerManager::createTimer(
TimerHandle TimerManager::createRecurringTimer(
jsi::Function&& callback,
std::vector<jsi::Value>&& args,
double delay) {
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));
std::move(callback), std::move(args), /* repeat */ true, source));

platformTimerRegistry_->createRecurringTimer(timerID, delay);

Expand Down Expand Up @@ -130,11 +171,20 @@ void TimerManager::deleteRecurringTimer(

void TimerManager::callTimer(TimerHandle timerHandle) {
runtimeExecutor_([this, timerHandle](jsi::Runtime& runtime) {
SystraceSection s("TimerManager::callTimer");
auto it = timers_.find(timerHandle);
if (it != timers_.end()) {
bool repeats = it->second.repeat;
it->second.invoke(runtime);
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
Expand All @@ -148,60 +198,64 @@ void TimerManager::callTimer(TimerHandle timerHandle) {
void TimerManager::attachGlobals(jsi::Runtime& runtime) {
// Install host functions for timers.
// TODO (T45786383): Add missing timer functions from JSTimers
// TODO (T96212789): Remove when JSVM microtask queue is used everywhere in
// bridgeless mode. This is being overwritten in JS in that case.
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)) {
throw jsi::JSError(
rt, "The first argument to setImmediate must be a function.");
}
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();
}));
// Ensure that we don't define `setImmediate` and `clearImmediate` if
// microtasks are enabled (as we polyfill them using `queueMicrotask` then).
if (!ReactNativeFeatureFlags::enableMicrotasks()) {
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)) {
throw jsi::JSError(
rt,
"The first argument to setImmediate must be a function.");
}
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,
Expand Down Expand Up @@ -241,7 +295,11 @@ void TimerManager::attachGlobals(jsi::Runtime& runtime) {
moreArgs.emplace_back(rt, args[extraArgNum]);
}

return createTimer(std::move(callback), std::move(moreArgs), delay);
return createTimer(
std::move(callback),
std::move(moreArgs),
delay,
TimerSource::SetTimeout);
}));

runtime.global().setProperty(
Expand Down Expand Up @@ -296,7 +354,10 @@ void TimerManager::attachGlobals(jsi::Runtime& runtime) {
}

return createRecurringTimer(
std::move(callback), std::move(moreArgs), delay);
std::move(callback),
std::move(moreArgs),
delay,
TimerSource::SetInterval);
}));

runtime.global().setProperty(
Expand Down Expand Up @@ -365,7 +426,8 @@ void TimerManager::attachGlobals(jsi::Runtime& runtime) {
return createTimer(
std::move(callback),
std::vector<jsi::Value>(),
/* delay */ 0);
/* delay */ 0,
TimerSource::RequestAnimationFrame);
}));

runtime.global().setProperty(
Expand Down
20 changes: 16 additions & 4 deletions packages/react-native/ReactCommon/react/runtime/TimerManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,26 @@ namespace facebook::react {

using TimerHandle = int;

enum class TimerSource {
Unknown,
SetTimeout,
SetInterval,
RequestAnimationFrame
};

/*
* Wraps a jsi::Function to make it copyable so we can pass it into a lambda.
*/
struct TimerCallback {
TimerCallback(
jsi::Function callback,
std::vector<jsi::Value> args,
bool repeat)
bool repeat,
TimerSource source = TimerSource::Unknown)
: callback_(std::move(callback)),
args_(std::move(args)),
repeat(repeat) {}
repeat(repeat),
source(source) {}

void invoke(jsi::Runtime& runtime) {
callback_.call(runtime, args_.data(), args_.size());
Expand All @@ -37,6 +46,7 @@ struct TimerCallback {
jsi::Function callback_;
const std::vector<jsi::Value> args_;
bool repeat;
TimerSource source;
};

class TimerManager {
Expand All @@ -62,14 +72,16 @@ class TimerManager {
TimerHandle createTimer(
jsi::Function&& callback,
std::vector<jsi::Value>&& args,
double delay);
double delay,
TimerSource source = TimerSource::Unknown);

void deleteTimer(jsi::Runtime& runtime, TimerHandle handle);

TimerHandle createRecurringTimer(
jsi::Function&& callback,
std::vector<jsi::Value>&& args,
double delay);
double delay,
TimerSource source = TimerSource::Unknown);

void deleteRecurringTimer(jsi::Runtime& runtime, TimerHandle handle);

Expand Down