Define HighResTimeStamp, HighResDuration (#51254)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/51254

# Changelog: [Internal]

Defines single timestamp abstraction that will be used in C++ layer of React Native core:
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.

Right now, all these just encapsulate `std::chrono::steady_clock` with no extra custom logic. We might revisit this in a future, once we decide to add support for [`Performance.timeOrigin`](https://developer.mozilla.org/en-US/docs/Web/API/Performance/timeOrigin).

This diff also adds a `README.md` documentation file with a brief summary of the `react/timing` module.

Reviewed By: rubennorte

Differential Revision: D72796412

fbshipit-source-id: 7a92b61202f5e527e020edea2b756b0063971242
This commit is contained in:
Ruslan Lesiutin
2025-05-16 12:20:42 -07:00
committed by Facebook GitHub Bot
parent 3e1b9a9182
commit a882f19974
3 changed files with 444 additions and 0 deletions
@@ -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.
@@ -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<std::chrono::nanoseconds>(chronoDuration_)
.count();
}
// @see https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp
static constexpr HighResDuration fromDOMHighResTimeStamp(double units) {
auto nanoseconds = static_cast<int64_t>(units * 1e6);
return fromNanoseconds(nanoseconds);
}
// @see https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp
constexpr double toDOMHighResTimeStamp() const {
return static_cast<std::chrono::duration<double, std::milli>>(
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<int64_t>(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
@@ -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