Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[0.74] Use Turbo Modules for Desktop instead of CxxModules #13602

Merged
merged 4 commits into from
Aug 26, 2024
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Enable Timer TurboModule for Desktop",
"packageName": "react-native-windows",
"email": "vmorozov@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,19 @@ using namespace std;

#include <folly/dynamic.h>
#include <cassert>
#include "TimingModule.h"
#include "DesktopTimingModule.h"

#include <cxxreact/Instance.h>
#include <cxxreact/JsArgumentHelpers.h>

using namespace facebook::xplat;
using namespace folly;

namespace facebook {
namespace react {
namespace facebook::react {

//==============================================================================
// TimerQueue
//==============================================================================

bool operator<(const Timer &leftTimer, const Timer &rightTimer) {
return rightTimer.DueTime < leftTimer.DueTime;
Expand Down Expand Up @@ -65,17 +68,21 @@ bool TimerQueue::IsEmpty() const {
return m_timerVector.empty();
}

/*static*/ void Timing::ThreadpoolTimerCallback(PTP_CALLBACK_INSTANCE, PVOID Parameter, PTP_TIMER) noexcept {
static_cast<Timing *>(Parameter)->OnTimerRaised();
//==============================================================================
// TimingHelper
//==============================================================================

/*static*/ void TimingHelper::ThreadpoolTimerCallback(PTP_CALLBACK_INSTANCE, PVOID Parameter, PTP_TIMER) noexcept {
static_cast<TimingHelper *>(Parameter)->OnTimerRaised();
}

void Timing::OnTimerRaised() noexcept {
void TimingHelper::OnTimerRaised() noexcept {
if (auto inst = m_wkInstance.lock()) {
if (auto nativeThread = m_nativeThread.lock()) {
// Make sure we execute it on native thread for native modules
// Capture weak_ptr "this" because callback will be executed on native
// thread even if "this" is destroyed.
nativeThread->runOnQueue([weakThis = std::weak_ptr<Timing>(shared_from_this())]() {
nativeThread->runOnQueue([weakThis = std::weak_ptr<TimingHelper>(shared_from_this())]() {
auto strongThis = weakThis.lock();
if (!strongThis) {
return;
Expand Down Expand Up @@ -126,7 +133,7 @@ void Timing::OnTimerRaised() noexcept {
}
}

void Timing::createTimer(
void TimingHelper::createTimer(
std::weak_ptr<facebook::react::Instance> instance,
uint64_t id,
double duration,
Expand Down Expand Up @@ -159,7 +166,7 @@ void Timing::createTimer(
TimersChanged();
}

void Timing::TimersChanged() noexcept {
void TimingHelper::TimersChanged() noexcept {
if (m_timerQueue.IsEmpty()) {
// TimerQueue is empty.
// Stop the kernel timer only when it is about to fire
Expand All @@ -186,7 +193,7 @@ void Timing::TimersChanged() noexcept {
}
}

bool Timing::KernelTimerIsAboutToFire() noexcept {
bool TimingHelper::KernelTimerIsAboutToFire() noexcept {
// Here we assume if kernel timer is going to fire within 2 frames (about
// 33ms), we return true I am not sure the 2 frames assumption is good enough.
// We may need adjustment after performance analysis.
Expand All @@ -197,12 +204,12 @@ bool Timing::KernelTimerIsAboutToFire() noexcept {
return false;
}

void Timing::SetInstance(std::weak_ptr<facebook::react::Instance> instance) noexcept {
void TimingHelper::SetInstance(std::weak_ptr<facebook::react::Instance> instance) noexcept {
if (m_wkInstance.expired())
m_wkInstance = instance;
}

void Timing::SetKernelTimer(DateTime dueTime) noexcept {
void TimingHelper::SetKernelTimer(DateTime dueTime) noexcept {
m_dueTime = dueTime;
FILETIME FileDueTime;
ULARGE_INTEGER ulDueTime;
Expand All @@ -220,40 +227,44 @@ void Timing::SetKernelTimer(DateTime dueTime) noexcept {
SetThreadpoolTimer(m_threadpoolTimer, &FileDueTime, 0, 0);
}

void Timing::InitializeKernelTimer() noexcept {
void TimingHelper::InitializeKernelTimer() noexcept {
// Create ThreadPoolTimer
m_threadpoolTimer = CreateThreadpoolTimer(&Timing::ThreadpoolTimerCallback, static_cast<PVOID>(this), NULL);
m_threadpoolTimer = CreateThreadpoolTimer(&TimingHelper::ThreadpoolTimerCallback, static_cast<PVOID>(this), NULL);
assert(m_threadpoolTimer && "CreateThreadpoolTimer failed.");
}

void Timing::deleteTimer(uint64_t id) noexcept {
void TimingHelper::deleteTimer(uint64_t id) noexcept {
if (m_timerQueue.IsEmpty())
return;
if (m_timerQueue.Remove(id)) {
TimersChanged();
}
}

void Timing::StopKernelTimer() noexcept {
void TimingHelper::StopKernelTimer() noexcept {
// Cancel pending callbacks
SetThreadpoolTimer(m_threadpoolTimer, NULL, 0, 0);
m_dueTime = DateTime::max();
}

void Timing::setSendIdleEvents(bool /*sendIdleEvents*/) noexcept {
void TimingHelper::setSendIdleEvents(bool /*sendIdleEvents*/) noexcept {
// It seems we don't need this API. Leave it empty for now.
assert(false && "not implemented");
}

Timing::~Timing() {
TimingHelper::~TimingHelper() {
if (m_threadpoolTimer) {
StopKernelTimer();
WaitForThreadpoolTimerCallbacks(m_threadpoolTimer, true);
CloseThreadpoolTimer(m_threadpoolTimer);
}
}

TimingModule::TimingModule(std::shared_ptr<Timing> &&timing) : m_timing(std::move(timing)) {}
//==============================================================================
// TimingModule
//==============================================================================

TimingModule::TimingModule(std::shared_ptr<TimingHelper> &&timing) : m_timing(std::move(timing)) {}

std::string TimingModule::getName() {
return "Timing";
Expand Down Expand Up @@ -291,9 +302,183 @@ std::vector<module::CxxModule::Method> TimingModule::getMethods() noexcept {

std::unique_ptr<facebook::xplat::module::CxxModule> CreateTimingModule(
const std::shared_ptr<facebook::react::MessageQueueThread> &nativeThread) noexcept {
auto module = std::make_unique<TimingModule>(std::make_shared<Timing>(nativeThread));
auto module = std::make_unique<TimingModule>(std::make_shared<TimingHelper>(nativeThread));
return std::move(module);
}

} // namespace react
} // namespace facebook
//==============================================================================
// Timing
//==============================================================================

void Timing::Initialize(winrt::Microsoft::ReactNative::ReactContext const &reactContext) noexcept {
m_reactContext = reactContext;
}

/*static*/ void Timing::ThreadpoolTimerCallback(PTP_CALLBACK_INSTANCE, PVOID Parameter, PTP_TIMER) noexcept {
static_cast<Timing *>(Parameter)->OnTimerRaised();
}

void Timing::OnTimerRaised() noexcept {
// Make sure we execute it on native thread for native modules
// Capture weak_ptr "this" because callback will be executed on native
// thread even if "this" is destroyed.
m_reactContext.JSDispatcher().Post([weakThis = std::weak_ptr<Timing>(shared_from_this())]() {
auto strongThis = weakThis.lock();
if (!strongThis) {
return;
}

if ((!strongThis->m_threadpoolTimer) || strongThis->m_timerQueue.IsEmpty()) {
return;
}

winrt::Microsoft::ReactNative::JSValueArray readyTimers;
auto now = std::chrono::system_clock::now();
auto now_ms = std::chrono::time_point_cast<std::chrono::milliseconds>(now);

// Fire timers which will be expired in 10ms
while (!strongThis->m_timerQueue.IsEmpty() && now_ms > strongThis->m_timerQueue.Front().DueTime - 10ms) {
// Pop first timer from the queue and add it to list of timers ready
// to fire
auto next = strongThis->m_timerQueue.Front();
strongThis->m_timerQueue.Pop();

// VSO:1916882 potential overflow
readyTimers.push_back(next.Id);

// If timer is repeating push it back onto the queue for the next
// repetition 'next.Period' being greater than 10ms is intended to
// prevent infinite loops
if (next.Repeat)
strongThis->m_timerQueue.Push(Timer{next.Id, now_ms + next.Period, next.Period, true});
}

if (!readyTimers.empty()) {
strongThis->m_reactContext.CallJSFunction(
L"JSTimers", L"callTimers", winrt::Microsoft::ReactNative::JSValueArray{std::move(readyTimers)});
}

if (!strongThis->m_timerQueue.IsEmpty()) {
strongThis->SetKernelTimer(strongThis->m_timerQueue.Front().DueTime);
} else {
strongThis->m_dueTime = DateTime::max();
}
});
}

void Timing::createTimer(uint64_t id, double duration, double jsSchedulingTime, bool repeat) noexcept {
auto now = std::chrono::system_clock::now();
auto now_ms = std::chrono::time_point_cast<std::chrono::milliseconds>(now);

// Convert double duration to std::chrono::duration
auto period = TimeSpan{(int64_t)duration};
// Convert int64_t scheduletime to std::chrono::time_point
DateTime scheduledTime = DateTime(TimeSpan((int64_t)jsSchedulingTime));
// Calculate the initial due time -- scheduleTime plus duration
auto initialDueTime = scheduledTime + period;

if (scheduledTime + period <= now_ms && !repeat) {
m_reactContext.CallJSFunction(
L"JSTimers",
L"callTimers",
winrt::Microsoft::ReactNative::JSValueArray{winrt::Microsoft::ReactNative::JSValueArray{id}});
return;
}

// Make sure duration is always larger than 16ms to avoid unnecessary wakeups.
period = TimeSpan{duration < 16 ? 16 : (int64_t)duration};
m_timerQueue.Push(Timer{id, initialDueTime, period, repeat});

TimersChanged();
}

void Timing::TimersChanged() noexcept {
if (m_timerQueue.IsEmpty()) {
// TimerQueue is empty.
// Stop the kernel timer only when it is about to fire
if (KernelTimerIsAboutToFire()) {
StopKernelTimer();
}
return;
}
// If front timer has the same target time as ThreadpoolTimer,
// we will keep ThreadpoolTimer unchanged.
if (m_timerQueue.Front().DueTime == m_dueTime) {
// do nothing
}
// If current front timer's due time is earlier than current
// ThreadpoolTimer's, we need to reset the ThreadpoolTimer to current front
// timer
else if (m_timerQueue.Front().DueTime < m_dueTime) {
SetKernelTimer(m_timerQueue.Front().DueTime);
}
// If current front timer's due time is later than current kernel timer's,
// we will reset kernel timer only when it is about to fire
else if (KernelTimerIsAboutToFire()) {
SetKernelTimer(m_timerQueue.Front().DueTime);
}
}

bool Timing::KernelTimerIsAboutToFire() noexcept {
// Here we assume if kernel timer is going to fire within 2 frames (about
// 33ms), we return true I am not sure the 2 frames assumption is good enough.
// We may need adjustment after performance analysis.
auto now = std::chrono::system_clock::now();
auto now_ms = std::chrono::time_point_cast<std::chrono::milliseconds>(now);
if (m_dueTime - now_ms <= 33ms)
return true;
return false;
}

void Timing::SetKernelTimer(DateTime dueTime) noexcept {
m_dueTime = dueTime;
FILETIME FileDueTime;
ULARGE_INTEGER ulDueTime;
auto now = std::chrono::system_clock::now();
auto now_ms = std::chrono::time_point_cast<std::chrono::milliseconds>(now);
TimeSpan period = dueTime - now_ms;
ulDueTime.QuadPart = (ULONGLONG) - (period.count() * 10000);
FileDueTime.dwHighDateTime = ulDueTime.HighPart;
FileDueTime.dwLowDateTime = ulDueTime.LowPart;

if (!m_threadpoolTimer) {
InitializeKernelTimer();
}

SetThreadpoolTimer(m_threadpoolTimer, &FileDueTime, 0, 0);
}

void Timing::InitializeKernelTimer() noexcept {
// Create ThreadPoolTimer
m_threadpoolTimer = CreateThreadpoolTimer(&Timing::ThreadpoolTimerCallback, static_cast<PVOID>(this), NULL);
assert(m_threadpoolTimer && "CreateThreadpoolTimer failed.");
}

void Timing::deleteTimer(uint64_t id) noexcept {
if (m_timerQueue.IsEmpty())
return;
if (m_timerQueue.Remove(id)) {
TimersChanged();
}
}

void Timing::StopKernelTimer() noexcept {
// Cancel pending callbacks
SetThreadpoolTimer(m_threadpoolTimer, NULL, 0, 0);
m_dueTime = DateTime::max();
}

void Timing::setSendIdleEvents(bool /*sendIdleEvents*/) noexcept {
// It seems we don't need this API. Leave it empty for now.
assert(false && "not implemented");
}

Timing::~Timing() {
if (m_threadpoolTimer) {
StopKernelTimer();
WaitForThreadpoolTimerCallbacks(m_threadpoolTimer, true);
CloseThreadpoolTimer(m_threadpoolTimer);
}
}

} // namespace facebook::react
Loading
Loading