169 lines
5.3 KiB
C++
169 lines
5.3 KiB
C++
|
/*
|
||
|
* 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 "NativeIdleCallbacks.h"
|
||
|
#include <react/renderer/runtimescheduler/RuntimeScheduler.h>
|
||
|
#include <react/renderer/runtimescheduler/RuntimeSchedulerBinding.h>
|
||
|
#include <react/renderer/runtimescheduler/Task.h>
|
||
|
#include <chrono>
|
||
|
#include <utility>
|
||
|
|
||
|
#ifdef RN_DISABLE_OSS_PLUGIN_HEADER
|
||
|
#include "Plugins.h"
|
||
|
#endif
|
||
|
|
||
|
std::shared_ptr<facebook::react::TurboModule> NativeIdleCallbacksModuleProvider(
|
||
|
std::shared_ptr<facebook::react::CallInvoker> jsInvoker) {
|
||
|
return std::make_shared<facebook::react::NativeIdleCallbacks>(
|
||
|
std::move(jsInvoker));
|
||
|
}
|
||
|
|
||
|
namespace facebook::react {
|
||
|
|
||
|
namespace {
|
||
|
|
||
|
class IdleTaskRef : public jsi::NativeState {
|
||
|
public:
|
||
|
IdleTaskRef(std::shared_ptr<Task> task) : task(std::move(task)) {}
|
||
|
|
||
|
std::shared_ptr<Task> task;
|
||
|
};
|
||
|
|
||
|
jsi::Function makeTimeRemainingFunction(
|
||
|
jsi::Runtime& runtime,
|
||
|
std::shared_ptr<RuntimeScheduler> runtimeScheduler,
|
||
|
RuntimeSchedulerTimePoint deadline) {
|
||
|
return jsi::Function::createFromHostFunction(
|
||
|
runtime,
|
||
|
jsi::PropNameID::forAscii(runtime, "timeRemaining"),
|
||
|
0,
|
||
|
[runtimeScheduler, deadline, expired = false](
|
||
|
jsi::Runtime& runtime,
|
||
|
const jsi::Value& /* unused */,
|
||
|
const jsi::Value* /* unused */,
|
||
|
size_t /* unused */) mutable {
|
||
|
double remainingTime = 0;
|
||
|
|
||
|
// No need to access the runtime scheduler if this idle callback expired
|
||
|
// already.
|
||
|
if (!expired) {
|
||
|
if (runtimeScheduler->getShouldYield()) {
|
||
|
expired = true;
|
||
|
} else {
|
||
|
auto now = runtimeScheduler->now();
|
||
|
|
||
|
remainingTime = std::max(
|
||
|
static_cast<double>(
|
||
|
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||
|
deadline - now)
|
||
|
.count()),
|
||
|
0.0);
|
||
|
|
||
|
if (remainingTime == 0) {
|
||
|
expired = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return jsi::Value(runtime, remainingTime);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
} // namespace
|
||
|
|
||
|
NativeIdleCallbacks::NativeIdleCallbacks(std::shared_ptr<CallInvoker> jsInvoker)
|
||
|
: NativeIdleCallbacksCxxSpec(std::move(jsInvoker)) {}
|
||
|
|
||
|
CallbackHandle NativeIdleCallbacks::requestIdleCallback(
|
||
|
jsi::Runtime& runtime,
|
||
|
SyncCallback<void(jsi::Object)>&& userCallback,
|
||
|
std::optional<NativeRequestIdleCallbackOptions> options) {
|
||
|
auto binding = RuntimeSchedulerBinding::getBinding(runtime);
|
||
|
auto runtimeScheduler = binding->getRuntimeScheduler();
|
||
|
|
||
|
// handle timeout parameter
|
||
|
std::optional<RuntimeSchedulerTimeout> timeout;
|
||
|
std::optional<RuntimeSchedulerTimePoint> expirationTime;
|
||
|
|
||
|
if (options.has_value() && options.value().timeout.has_value()) {
|
||
|
auto userTimeout = (options.value().timeout.value());
|
||
|
if (userTimeout > 0) {
|
||
|
timeout = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||
|
std::chrono::duration<double, std::milli>(userTimeout));
|
||
|
expirationTime = runtimeScheduler->now() + timeout.value();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
auto userCallbackShared = std::make_shared<SyncCallback<void(jsi::Object)>>(
|
||
|
std::move(userCallback));
|
||
|
|
||
|
auto wrappedCallback = [runtimeScheduler, expirationTime, userCallbackShared](
|
||
|
jsi::Runtime& runtime) -> void {
|
||
|
// This implementation gives each idle callback a 50ms deadline, instead of
|
||
|
// being shared by all idle callbacks. This is ok because we don't really
|
||
|
// have idle periods, and if a higher priority task comes in while we're
|
||
|
// executing an idle callback, we don't execute any more idle callbacks and
|
||
|
// we interrupt the current one. The general outcome should be the same.
|
||
|
|
||
|
auto executionStartTime = runtimeScheduler->now();
|
||
|
auto deadline = executionStartTime + std::chrono::milliseconds(50);
|
||
|
auto didTimeout = expirationTime.has_value()
|
||
|
? executionStartTime > expirationTime
|
||
|
: false;
|
||
|
|
||
|
jsi::Object idleDeadline{runtime};
|
||
|
idleDeadline.setProperty(runtime, "didTimeout", didTimeout);
|
||
|
idleDeadline.setProperty(
|
||
|
runtime,
|
||
|
"timeRemaining",
|
||
|
makeTimeRemainingFunction(runtime, runtimeScheduler, deadline));
|
||
|
|
||
|
userCallbackShared->call(std::move(idleDeadline));
|
||
|
};
|
||
|
|
||
|
std::shared_ptr<Task> task;
|
||
|
if (timeout.has_value()) {
|
||
|
task = runtimeScheduler->scheduleIdleTask(
|
||
|
std::move(wrappedCallback), timeout.value());
|
||
|
} else {
|
||
|
task = runtimeScheduler->scheduleIdleTask(std::move(wrappedCallback));
|
||
|
}
|
||
|
|
||
|
if (task == nullptr) {
|
||
|
throw jsi::JSError(
|
||
|
runtime,
|
||
|
"requestIdleCallback is not supported in legacy runtime scheduler");
|
||
|
}
|
||
|
|
||
|
jsi::Object taskHandle{runtime};
|
||
|
auto taskNativeState = std::make_shared<IdleTaskRef>(task);
|
||
|
taskHandle.setNativeState(runtime, std::move(taskNativeState));
|
||
|
|
||
|
return taskHandle;
|
||
|
}
|
||
|
|
||
|
void NativeIdleCallbacks::cancelIdleCallback(
|
||
|
jsi::Runtime& runtime,
|
||
|
jsi::Object handle) {
|
||
|
auto binding = RuntimeSchedulerBinding::getBinding(runtime);
|
||
|
auto runtimeScheduler = binding->getRuntimeScheduler();
|
||
|
|
||
|
if (!handle.hasNativeState(runtime)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
auto taskHandle =
|
||
|
std::dynamic_pointer_cast<IdleTaskRef>(handle.getNativeState(runtime));
|
||
|
if (!taskHandle) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
runtimeScheduler->cancelTask(*taskHandle->task);
|
||
|
}
|
||
|
|
||
|
} // namespace facebook::react
|