Bug 1746870: part 1) Minorly extend documentation in <jsactors.rst>. r=hsivonen
[gecko.git] / widget / SystemTimeConverter.h
blob4a074d573fecd28fc70663b859ec8f69c15066a0
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
9 #include <limits>
10 #include <type_traits>
11 #include "mozilla/TimeStamp.h"
13 namespace mozilla {
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
27 // be slow.
28 template <typename Time, typename TimeStampNowProvider = TimeStamp>
29 class SystemTimeConverter {
30 public:
31 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
48 // it in.
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|.
79 // Graphically:
81 // mReferenceTime aTime
82 // Time scale: ........+.......................*........
83 // |--------timeDelta------|
85 // mReferenceTimeStamp roughlyNow
86 // TimeStamp scale: ........+...........................*....
87 // |------timeStampDelta-------|
89 // |---|
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
99 if (newer) {
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;
107 return roughlyNow;
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
127 // the following:
129 // mReferenceTime
130 // Time: ..+...a...b...c..........................
132 // mReferenceTimeStamp
133 // TimeStamp: ..+.....a.....b.....c....................
135 // Converted
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
142 // aReferenceTime).
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.........
151 // ^ ^
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
158 // temporarily.
159 if (IsTimeNewerThanTimestamp(aReferenceTime, aLowerBound, nullptr)) {
160 return;
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);
174 private:
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;
212 } else {
213 isNewer = true;
214 wholeMillis += (-timeToTimeStamp);
216 if (aTimeAsTimeStamp) {
217 *aTimeAsTimeStamp =
218 mReferenceTimeStamp + TimeDuration::FromMilliseconds(wholeMillis);
221 return isNewer;
224 Time mReferenceTime;
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 */