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 "mozilla/TimeStamp.h"
11 #include "mozilla/TypeTraits.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
>
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(!IsSigned
<Time
>::value
, "Expected Time to be unsigned");
42 template <typename CurrentTimeGetter
>
43 mozilla::TimeStamp
GetTimeStampFromSystemTime(
44 Time aTime
, CurrentTimeGetter
& aCurrentTimeGetter
) {
45 TimeStamp roughlyNow
= TimeStamp::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 // We call the absolute difference between (i) and (ii), "deltaFromNow".
80 // mReferenceTime aTime
81 // Time scale: ........+.......................*........
82 // |--------timeDelta------|
84 // mReferenceTimeStamp roughlyNow
85 // TimeStamp scale: ........+...........................*....
86 // |------timeStampDelta-------|
92 bool newer
= IsTimeNewerThanTimestamp(aTime
, roughlyNow
, &deltaFromNow
);
94 // Tolerance when detecting clock skew.
95 static const Time kTolerance
= 30;
97 // Check for forwards skew
99 // Make aTime correspond to roughlyNow
100 UpdateReferenceTime(aTime
, roughlyNow
);
102 // We didn't have backwards skew so don't bother checking for
103 // backwards skew again for a little while.
104 mLastBackwardsSkewCheck
= aTime
;
109 if (deltaFromNow
<= kTolerance
) {
110 // If the time between event times and TimeStamp values is within
111 // the tolerance then assume we don't have clock skew so we can
112 // avoid checking for backwards skew for a while.
113 mLastBackwardsSkewCheck
= aTime
;
114 } else if (aTime
- mLastBackwardsSkewCheck
> kBackwardsSkewCheckInterval
) {
115 aCurrentTimeGetter
.GetTimeAsyncForPossibleBackwardsSkew(roughlyNow
);
116 mLastBackwardsSkewCheck
= aTime
;
119 // Finally, calculate the timestamp
120 return roughlyNow
- TimeDuration::FromMilliseconds(deltaFromNow
);
123 void CompensateForBackwardsSkew(Time aReferenceTime
,
124 const TimeStamp
& aLowerBound
) {
125 // Check if we actually have backwards skew. Backwards skew looks like
129 // Time: ..+...a...b...c..........................
131 // mReferenceTimeStamp
132 // TimeStamp: ..+.....a.....b.....c....................
135 // time: ......a'..b'..c'.........................
137 // What we need to do is bring mReferenceTime "forwards".
139 // Suppose when we get (c), we detect possible backwards skew and trigger
140 // an async request for the current time (which is passed in here as
143 // We end up with something like the following:
145 // mReferenceTime aReferenceTime
146 // Time: ..+...a...b...c...v......................
148 // mReferenceTimeStamp
149 // TimeStamp: ..+.....a.....b.....c..........x.........
151 // aLowerBound TimeStamp::Now()
153 // If the duration (aLowerBound - mReferenceTimeStamp) is greater than
154 // (aReferenceTime - mReferenceTime) then we know we have backwards skew.
156 // If that's not the case, then we probably just got caught behind
159 if (IsTimeNewerThanTimestamp(aReferenceTime
, aLowerBound
, &delta
)) {
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
= TimeStamp::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
,
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 Time timeStampDelta
= static_cast<int64_t>(
203 (aTimeStamp
- mReferenceTimeStamp
).ToMilliseconds());
205 Time timeToTimeStamp
= timeStampDelta
- timeDelta
;
206 bool isNewer
= false;
207 if (timeToTimeStamp
== 0) {
209 } else if (timeToTimeStamp
< kTimeHalfRange
) {
210 *aDelta
= timeToTimeStamp
;
213 *aDelta
= timeDelta
- timeStampDelta
;
220 TimeStamp mReferenceTimeStamp
;
221 Time mLastBackwardsSkewCheck
;
223 const Time kTimeRange
;
224 const Time kTimeHalfRange
;
225 const Time kBackwardsSkewCheckInterval
;
228 } // namespace mozilla
230 #endif /* SystemTimeConverter_h */