1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #ifndef SystemTimeConverter_h
7 #define SystemTimeConverter_h
10 #include <type_traits>
11 #include "mozilla/TimeStamp.h"
15 // Utility class that converts time values represented as an unsigned integral
16 // number of milliseconds from one time source (e.g. a native event time) to
17 // corresponding mozilla::TimeStamp objects.
19 // This class handles wrapping of integer values and skew between the time
20 // source and mozilla::TimeStamp values.
22 // It does this by using an historical reference time recorded in both time
23 // scales (i.e. both as a numerical time value and as a TimeStamp).
25 // For performance reasons, this class is careful to minimize calls to the
26 // native "current time" function (e.g. gdk_x11_server_get_time) since this can
28 template <typename Time
, typename TimeStampNowProvider
= TimeStamp
>
29 class SystemTimeConverter
{
32 : mReferenceTime(Time(0)),
33 mReferenceTimeStamp() // Initializes to the null timestamp
35 mLastBackwardsSkewCheck(Time(0)),
36 kTimeRange(std::numeric_limits
<Time
>::max()),
37 kTimeHalfRange(kTimeRange
/ 2),
38 kBackwardsSkewCheckInterval(Time(2000)) {
39 static_assert(!std::is_signed_v
<Time
>, "Expected Time to be unsigned");
42 template <typename CurrentTimeGetter
>
43 mozilla::TimeStamp
GetTimeStampFromSystemTime(
44 Time aTime
, CurrentTimeGetter
& aCurrentTimeGetter
) {
45 TimeStamp roughlyNow
= TimeStampNowProvider::Now();
47 // If the reference time is not set, use the current time value to fill
49 if (mReferenceTimeStamp
.IsNull()) {
50 // This sometimes happens when ::GetMessageTime returns 0 for the first
51 // message on Windows.
52 if (!aTime
) return roughlyNow
;
53 UpdateReferenceTime(aTime
, aCurrentTimeGetter
);
56 // Check for skew between the source of Time values and TimeStamp values.
57 // We do this by comparing two durations (both in ms):
59 // i. The duration from the reference time to the passed-in time.
60 // (timeDelta in the diagram below)
61 // ii. The duration from the reference timestamp to the current time
62 // based on TimeStamp::Now.
63 // (timeStampDelta in the diagram below)
65 // Normally, we'd expect (ii) to be slightly larger than (i) to account
66 // for the time taken between generating the event and processing it.
68 // If (ii) - (i) is negative then the source of Time values is getting
69 // "ahead" of TimeStamp. We call this "forwards" skew below.
71 // For the reverse case, if (ii) - (i) is positive (and greater than some
72 // tolerance factor), then we may have "backwards" skew. This is often
73 // the case when we have a backlog of events and by the time we process
74 // them, the time given by the system is comparatively "old".
76 // The IsNewerThanTimestamp function computes the equivalent of |aTime| in
77 // the TimeStamp scale and returns that in |timeAsTimeStamp|.
81 // mReferenceTime aTime
82 // Time scale: ........+.......................*........
83 // |--------timeDelta------|
85 // mReferenceTimeStamp roughlyNow
86 // TimeStamp scale: ........+...........................*....
87 // |------timeStampDelta-------|
90 // roughlyNow-timeAsTimeStamp
92 TimeStamp timeAsTimeStamp
;
93 bool newer
= IsTimeNewerThanTimestamp(aTime
, roughlyNow
, &timeAsTimeStamp
);
95 // Tolerance when detecting clock skew.
96 static const TimeDuration kTolerance
= TimeDuration::FromMilliseconds(30.0);
98 // Check for forwards skew
100 // Make aTime correspond to roughlyNow
101 UpdateReferenceTime(aTime
, roughlyNow
);
103 // We didn't have backwards skew so don't bother checking for
104 // backwards skew again for a little while.
105 mLastBackwardsSkewCheck
= aTime
;
110 if (roughlyNow
- timeAsTimeStamp
<= kTolerance
) {
111 // If the time between event times and TimeStamp values is within
112 // the tolerance then assume we don't have clock skew so we can
113 // avoid checking for backwards skew for a while.
114 mLastBackwardsSkewCheck
= aTime
;
115 } else if (aTime
- mLastBackwardsSkewCheck
> kBackwardsSkewCheckInterval
) {
116 aCurrentTimeGetter
.GetTimeAsyncForPossibleBackwardsSkew(roughlyNow
);
117 mLastBackwardsSkewCheck
= aTime
;
120 // Finally, calculate the timestamp
121 return timeAsTimeStamp
;
124 void CompensateForBackwardsSkew(Time aReferenceTime
,
125 const TimeStamp
& aLowerBound
) {
126 // Check if we actually have backwards skew. Backwards skew looks like
130 // Time: ..+...a...b...c..........................
132 // mReferenceTimeStamp
133 // TimeStamp: ..+.....a.....b.....c....................
136 // time: ......a'..b'..c'.........................
138 // What we need to do is bring mReferenceTime "forwards".
140 // Suppose when we get (c), we detect possible backwards skew and trigger
141 // an async request for the current time (which is passed in here as
144 // We end up with something like the following:
146 // mReferenceTime aReferenceTime
147 // Time: ..+...a...b...c...v......................
149 // mReferenceTimeStamp
150 // TimeStamp: ..+.....a.....b.....c..........x.........
152 // aLowerBound TimeStamp::Now()
154 // If the duration (aLowerBound - mReferenceTimeStamp) is greater than
155 // (aReferenceTime - mReferenceTime) then we know we have backwards skew.
157 // If that's not the case, then we probably just got caught behind
159 if (IsTimeNewerThanTimestamp(aReferenceTime
, aLowerBound
, nullptr)) {
163 // We have backwards skew; the equivalent TimeStamp for aReferenceTime lies
164 // somewhere between aLowerBound (which was the TimeStamp when we triggered
165 // the async request for the current time) and TimeStamp::Now().
167 // If aReferenceTime was waiting in the event queue for a long time, the
168 // equivalent TimeStamp might be much closer to aLowerBound than
169 // TimeStamp::Now() so for now we just set it to aLowerBound. That's
170 // guaranteed to be at least somewhat of an improvement.
171 UpdateReferenceTime(aReferenceTime
, aLowerBound
);
175 template <typename CurrentTimeGetter
>
176 void UpdateReferenceTime(Time aReferenceTime
,
177 const CurrentTimeGetter
& aCurrentTimeGetter
) {
178 Time currentTime
= aCurrentTimeGetter
.GetCurrentTime();
179 TimeStamp currentTimeStamp
= TimeStampNowProvider::Now();
180 Time timeSinceReference
= currentTime
- aReferenceTime
;
181 TimeStamp referenceTimeStamp
=
182 currentTimeStamp
- TimeDuration::FromMilliseconds(timeSinceReference
);
183 UpdateReferenceTime(aReferenceTime
, referenceTimeStamp
);
186 void UpdateReferenceTime(Time aReferenceTime
,
187 const TimeStamp
& aReferenceTimeStamp
) {
188 mReferenceTime
= aReferenceTime
;
189 mReferenceTimeStamp
= aReferenceTimeStamp
;
192 bool IsTimeNewerThanTimestamp(Time aTime
, TimeStamp aTimeStamp
,
193 TimeStamp
* aTimeAsTimeStamp
) {
194 Time timeDelta
= aTime
- mReferenceTime
;
196 // Cast the result to signed 64-bit integer first since that should be
197 // enough to hold the range of values returned by ToMilliseconds() and
198 // the result of converting from double to an integer-type when the value
199 // is outside the integer range is undefined.
200 // Then we do an implicit cast to Time (typically an unsigned 32-bit
201 // integer) which wraps times outside that range.
202 TimeDuration timeStampDelta
= (aTimeStamp
- mReferenceTimeStamp
);
203 int64_t wholeMillis
= static_cast<int64_t>(timeStampDelta
.ToMilliseconds());
204 Time wrappedTimeStampDelta
= wholeMillis
; // truncate to unsigned
206 Time timeToTimeStamp
= wrappedTimeStampDelta
- timeDelta
;
207 bool isNewer
= false;
208 if (timeToTimeStamp
== 0) {
209 // wholeMillis needs no adjustment
210 } else if (timeToTimeStamp
< kTimeHalfRange
) {
211 wholeMillis
-= timeToTimeStamp
;
214 wholeMillis
+= (-timeToTimeStamp
);
216 if (aTimeAsTimeStamp
) {
218 mReferenceTimeStamp
+ TimeDuration::FromMilliseconds(wholeMillis
);
225 TimeStamp mReferenceTimeStamp
;
226 Time mLastBackwardsSkewCheck
;
228 const Time kTimeRange
;
229 const Time kTimeHalfRange
;
230 const Time kBackwardsSkewCheckInterval
;
233 } // namespace mozilla
235 #endif /* SystemTimeConverter_h */