diff --git a/packages/react-native/ReactCommon/react/timing/__docs__/README.md b/packages/react-native/ReactCommon/react/timing/__docs__/README.md new file mode 100644 index 00000000000000..4ded9ac8445b12 --- /dev/null +++ b/packages/react-native/ReactCommon/react/timing/__docs__/README.md @@ -0,0 +1,90 @@ +# React Native Timing + +[🏠 Home](../../../../../../__docs__/README.md) + +This directory contains the shared C++ implementation of high-resolution timing +primitives for React Native. These primitives provide precise time measurement +capabilities that align with Web standards while being optimized for the React +Native environment. + +## 🚀 Usage + +The timing primitives in this module are primarily used by internal React Native +systems that require precise time measurements. The monotonic clock is used for +these primitives: they should be used for measuring time intervals. These +primitives should not be used for wall times. They are not expected to be used +directly by application developers but serve as foundational components for +various React Native features. + +Key primitives include: + +- `HighResTimeStamp`: A class representing a specific point in time with high + precision. +- `HighResDuration`: A class representing a duration of time with high + precision. + +These primitives support various operations: + +```cpp +// Getting the current high-resolution timestamp +auto start = HighResTimeStamp::now(); + +// Creating durations +auto duration = HighResDuration::fromNanoseconds(100); +auto durationMs = HighResDuration::fromMilliseconds(100); + +// Arithmetic operations +auto later = start + duration; +auto elapsed = later - start; + +// Converting to absolute time units of highest precision +auto end = HighResTimeStamp::now(); +int64_t nanoseconds = (end - start).toNanoseconds(); + +// Converting to DOMHighResTimeStamp (for JavaScript interoperability) +double jsTimeValue = now.toDOMHighResTimeStamp(); +``` + +## 📐 Design + +The timing primitives are designed to align with Web standards while leveraging +C++'s type system and the performance characteristics of native code. The +implementation uses `std::chrono` internally but provides a more specialized +interface tailored to React Native's needs. + +### HighResTimeStamp + +This class represents a specific point in time with high precision. It +encapsulates a `std::chrono::steady_clock::time_point` and provides methods to: + +- Convert to `DOMHighResTimeStamp` for JavaScript interoperability. +- Perform arithmetic operations with durations. +- Compare with other timestamps. + +### HighResDuration + +This class represents a duration of time with high precision. It encapsulates a +`std::chrono::duration` and provides methods to: + +- Convert to `DOMHighResTimeStamp` for JavaScript interoperability. +- Convert to an absolute number of nanoseconds. +- Perform arithmetic operations. +- Compare with other durations. + +## 🔗 Relationship with other systems + +### Used by + +- [Event Loop](../../renderer/runtimescheduler/__docs__/README.md): Uses timing + primitives for measuring task execution times and scheduling. +- Web Performance API: Timing primitives are used to implement performance + measurement APIs like `PerformanceObserver` entries (e.g., `longtask` and + `event`). +- React Native DevTools: The timing primitives integrate with the React Native + DevTools tracing infrastructure to report the timing of tasks and events. + +### Related to + +- Web timing APIs: The timing primitives are designed to be compatible with Web + timing concepts, making it easier to implement Web-compatible APIs in React + Native. diff --git a/packages/react-native/ReactCommon/react/timing/primitives.h b/packages/react-native/ReactCommon/react/timing/primitives.h index c6b3c304635cdc..28347e867df3f4 100644 --- a/packages/react-native/ReactCommon/react/timing/primitives.h +++ b/packages/react-native/ReactCommon/react/timing/primitives.h @@ -30,4 +30,274 @@ inline DOMHighResTimeStamp chronoToDOMHighResTimeStamp( return chronoToDOMHighResTimeStamp(timePoint.time_since_epoch()); } +class HighResDuration; +class HighResTimeStamp; + +/* + * A class representing a duration of time with high precision. + * + * @see __docs__/README.md for more information. + */ +class HighResDuration { + friend class HighResTimeStamp; + friend constexpr HighResDuration operator-( + const HighResTimeStamp& lhs, + const HighResTimeStamp& rhs); + friend constexpr HighResTimeStamp operator+( + const HighResTimeStamp& lhs, + const HighResDuration& rhs); + friend constexpr HighResTimeStamp operator-( + const HighResTimeStamp& lhs, + const HighResDuration& rhs); + + public: + constexpr HighResDuration() + : chronoDuration_(std::chrono::steady_clock::duration()) {} + + static constexpr HighResDuration zero() { + return HighResDuration(std::chrono::steady_clock::duration::zero()); + } + + static constexpr HighResDuration fromChrono( + std::chrono::steady_clock::duration chronoDuration) { + return HighResDuration(chronoDuration); + } + + static constexpr HighResDuration fromNanoseconds(int64_t units) { + return HighResDuration(std::chrono::nanoseconds(units)); + } + + static constexpr HighResDuration fromMilliseconds(int64_t units) { + return HighResDuration(std::chrono::milliseconds(units)); + } + + constexpr int64_t toNanoseconds() const { + return std::chrono::duration_cast(chronoDuration_) + .count(); + } + + // @see https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp + static constexpr HighResDuration fromDOMHighResTimeStamp(double units) { + auto nanoseconds = static_cast(units * 1e6); + return fromNanoseconds(nanoseconds); + } + + // @see https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp + constexpr double toDOMHighResTimeStamp() const { + return static_cast>( + chronoDuration_) + .count(); + } + + constexpr HighResDuration operator+(const HighResDuration& rhs) const { + return HighResDuration(chronoDuration_ + rhs.chronoDuration_); + } + + constexpr HighResDuration operator+( + const std::chrono::steady_clock::duration& rhs) const { + return HighResDuration(chronoDuration_ + rhs); + } + + constexpr HighResDuration operator-(const HighResDuration& rhs) const { + return HighResDuration(chronoDuration_ - rhs.chronoDuration_); + } + + constexpr HighResDuration operator-( + const std::chrono::steady_clock::duration& rhs) const { + return HighResDuration(chronoDuration_ - rhs); + } + + constexpr HighResDuration& operator+=(const HighResDuration& rhs) { + chronoDuration_ += rhs.chronoDuration_; + return *this; + } + + constexpr HighResDuration& operator+=( + const std::chrono::steady_clock::duration& rhs) { + chronoDuration_ += rhs; + return *this; + } + + constexpr HighResDuration& operator-=(const HighResDuration& rhs) { + chronoDuration_ -= rhs.chronoDuration_; + return *this; + } + + constexpr HighResDuration& operator-=( + const std::chrono::steady_clock::duration& rhs) { + chronoDuration_ -= rhs; + return *this; + } + + constexpr bool operator==(const HighResDuration& rhs) const { + return chronoDuration_ == rhs.chronoDuration_; + } + + constexpr bool operator==( + const std::chrono::steady_clock::duration& rhs) const { + return chronoDuration_ == rhs; + } + + constexpr bool operator!=(const HighResDuration& rhs) const { + return chronoDuration_ != rhs.chronoDuration_; + } + + constexpr bool operator!=( + const std::chrono::steady_clock::duration& rhs) const { + return chronoDuration_ != rhs; + } + + constexpr bool operator<(const HighResDuration& rhs) const { + return chronoDuration_ < rhs.chronoDuration_; + } + + constexpr bool operator<( + const std::chrono::steady_clock::duration& rhs) const { + return chronoDuration_ < rhs; + } + + constexpr bool operator<=(const HighResDuration& rhs) const { + return chronoDuration_ <= rhs.chronoDuration_; + } + + constexpr bool operator<=( + const std::chrono::steady_clock::duration& rhs) const { + return chronoDuration_ <= rhs; + } + + constexpr bool operator>(const HighResDuration& rhs) const { + return chronoDuration_ > rhs.chronoDuration_; + } + + constexpr bool operator>( + const std::chrono::steady_clock::duration& rhs) const { + return chronoDuration_ > rhs; + } + + constexpr bool operator>=(const HighResDuration& rhs) const { + return chronoDuration_ >= rhs.chronoDuration_; + } + + constexpr bool operator>=( + const std::chrono::steady_clock::duration& rhs) const { + return chronoDuration_ >= rhs; + } + + constexpr operator std::chrono::steady_clock::duration() const { + return chronoDuration_; + } + + private: + explicit constexpr HighResDuration( + std::chrono::steady_clock::duration chronoDuration) + : chronoDuration_(chronoDuration) {} + + std::chrono::steady_clock::duration chronoDuration_; +}; + +/* + * A class representing a specific point in time with high precision. + * + * @see __docs__/README.md for more information. + */ +class HighResTimeStamp { + friend constexpr HighResDuration operator-( + const HighResTimeStamp& lhs, + const HighResTimeStamp& rhs); + friend constexpr HighResTimeStamp operator+( + const HighResTimeStamp& lhs, + const HighResDuration& rhs); + friend constexpr HighResTimeStamp operator-( + const HighResTimeStamp& lhs, + const HighResDuration& rhs); + + public: + HighResTimeStamp() noexcept + : chronoTimePoint_(std::chrono::steady_clock::now()) {} + + static constexpr HighResTimeStamp now() noexcept { + return HighResTimeStamp(std::chrono::steady_clock::now()); + } + + static constexpr HighResTimeStamp min() noexcept { + return HighResTimeStamp(std::chrono::steady_clock::time_point::min()); + } + + static constexpr HighResTimeStamp max() noexcept { + return HighResTimeStamp(std::chrono::steady_clock::time_point::max()); + } + + // @see https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp + static constexpr HighResTimeStamp fromDOMHighResTimeStamp(double units) { + auto nanoseconds = static_cast(units * 1e6); + return HighResTimeStamp(std::chrono::steady_clock::time_point( + std::chrono::nanoseconds(nanoseconds))); + } + + // @see https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp + constexpr double toDOMHighResTimeStamp() const { + return HighResDuration(chronoTimePoint_.time_since_epoch()) + .toDOMHighResTimeStamp(); + } + + constexpr bool operator==(const HighResTimeStamp& rhs) const { + return chronoTimePoint_ == rhs.chronoTimePoint_; + } + + constexpr bool operator!=(const HighResTimeStamp& rhs) const { + return chronoTimePoint_ != rhs.chronoTimePoint_; + } + + constexpr bool operator<(const HighResTimeStamp& rhs) const { + return chronoTimePoint_ < rhs.chronoTimePoint_; + } + + constexpr bool operator<=(const HighResTimeStamp& rhs) const { + return chronoTimePoint_ <= rhs.chronoTimePoint_; + } + + constexpr bool operator>(const HighResTimeStamp& rhs) const { + return chronoTimePoint_ > rhs.chronoTimePoint_; + } + + constexpr bool operator>=(const HighResTimeStamp& rhs) const { + return chronoTimePoint_ >= rhs.chronoTimePoint_; + } + + constexpr HighResTimeStamp& operator+=(const HighResDuration& rhs) { + chronoTimePoint_ += rhs.chronoDuration_; + return *this; + } + + constexpr HighResTimeStamp& operator-=(const HighResDuration& rhs) { + chronoTimePoint_ -= rhs.chronoDuration_; + return *this; + } + + private: + explicit constexpr HighResTimeStamp( + std::chrono::steady_clock::time_point chronoTimePoint) + : chronoTimePoint_(chronoTimePoint) {} + + std::chrono::steady_clock::time_point chronoTimePoint_; +}; + +inline constexpr HighResDuration operator-( + const HighResTimeStamp& lhs, + const HighResTimeStamp& rhs) { + return HighResDuration(lhs.chronoTimePoint_ - rhs.chronoTimePoint_); +} + +inline constexpr HighResTimeStamp operator+( + const HighResTimeStamp& lhs, + const HighResDuration& rhs) { + return HighResTimeStamp(lhs.chronoTimePoint_ + rhs.chronoDuration_); +} + +inline constexpr HighResTimeStamp operator-( + const HighResTimeStamp& lhs, + const HighResDuration& rhs) { + return HighResTimeStamp(lhs.chronoTimePoint_ - rhs.chronoDuration_); +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/timing/tests/PrimitivesTest.cpp b/packages/react-native/ReactCommon/react/timing/tests/PrimitivesTest.cpp index 80452a2f713d37..bde37c28551c45 100644 --- a/packages/react-native/ReactCommon/react/timing/tests/PrimitivesTest.cpp +++ b/packages/react-native/ReactCommon/react/timing/tests/PrimitivesTest.cpp @@ -44,4 +44,88 @@ TEST(chronoToDOMHighResTimeStamp, withTimePoints) { 1000.000020); } +TEST(HighResDuration, CorrectlyConvertsToDOMHighResTimeStamp) { + EXPECT_EQ( + HighResDuration::fromNanoseconds(10).toDOMHighResTimeStamp(), 0.00001); + EXPECT_EQ( + HighResDuration::fromNanoseconds(10 * 1e3).toDOMHighResTimeStamp(), 0.01); + EXPECT_EQ( + HighResDuration::fromNanoseconds(10 * 1e6).toDOMHighResTimeStamp(), 10.0); + EXPECT_EQ( + HighResDuration::fromNanoseconds(10 * 1e9).toDOMHighResTimeStamp(), + 10000.0); + EXPECT_EQ( + HighResDuration::fromNanoseconds(1e9 + 20).toDOMHighResTimeStamp(), + 1000.000020); + + EXPECT_EQ(HighResDuration::fromMilliseconds(0).toDOMHighResTimeStamp(), 0); + EXPECT_EQ( + HighResDuration::fromMilliseconds(10).toDOMHighResTimeStamp(), 10.0); +} + +TEST(HighResDuration, ComparisonOperators) { + auto duration1 = HighResDuration::fromNanoseconds(10); + auto duration2 = HighResDuration::fromNanoseconds(20); + auto duration3 = HighResDuration::fromNanoseconds(10); + + EXPECT_TRUE(duration1 == duration3); + EXPECT_FALSE(duration1 == duration2); + + EXPECT_TRUE(duration1 != duration2); + EXPECT_FALSE(duration1 != duration3); + + EXPECT_TRUE(duration1 < duration2); + EXPECT_FALSE(duration2 < duration1); + EXPECT_FALSE(duration1 < duration3); + + EXPECT_TRUE(duration1 <= duration2); + EXPECT_TRUE(duration1 <= duration3); + EXPECT_FALSE(duration2 <= duration1); + + EXPECT_TRUE(duration2 > duration1); + EXPECT_FALSE(duration1 > duration2); + EXPECT_FALSE(duration1 > duration3); + + EXPECT_TRUE(duration2 >= duration1); + EXPECT_TRUE(duration1 >= duration3); + EXPECT_FALSE(duration1 >= duration2); +} + +TEST(HighResDuration, ArithmeticOperators) { + auto duration1 = HighResDuration::fromChrono(std::chrono::nanoseconds(100)); + auto duration2 = HighResDuration::fromChrono(std::chrono::nanoseconds(50)); + + EXPECT_EQ(duration1 + duration2, std::chrono::nanoseconds(150)); + EXPECT_EQ(duration1 - duration2, std::chrono::nanoseconds(50)); + EXPECT_EQ(duration2 - duration1, std::chrono::nanoseconds(-50)); +} + +TEST(HighResTimeStamp, ComparisonOperators) { + auto now = HighResTimeStamp::now(); + auto later = now + HighResDuration::fromNanoseconds(1); + auto nowCopy = now; + + EXPECT_TRUE(now == nowCopy); + EXPECT_FALSE(now == later); + + EXPECT_TRUE(now != later); + EXPECT_FALSE(now != nowCopy); + + EXPECT_TRUE(now < later); + EXPECT_FALSE(later < now); + EXPECT_FALSE(now < nowCopy); + + EXPECT_TRUE(now <= later); + EXPECT_TRUE(now <= nowCopy); + EXPECT_FALSE(later <= now); + + EXPECT_TRUE(later > now); + EXPECT_FALSE(now > later); + EXPECT_FALSE(now > nowCopy); + + EXPECT_TRUE(later >= now); + EXPECT_TRUE(now >= nowCopy); + EXPECT_FALSE(now >= later); +} + } // namespace facebook::react