Skip to content

Commit

Permalink
[0.74] Use Turbo Modules for Desktop instead of CxxModules (#13602)
Browse files Browse the repository at this point in the history
* Use Turbo Modules for Desktop instead of CxxModules (#13265)

* fix

* format

* fabric should always set useTurboModulesOnly

---------

Co-authored-by: Vladimir Morozov <vmorozov@microsoft.com>
  • Loading branch information
acoates-ms and vmoroz authored Aug 26, 2024
1 parent e0b7309 commit 003e703
Show file tree
Hide file tree
Showing 9 changed files with 343 additions and 71 deletions.
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

0 comments on commit 003e703

Please sign in to comment.