mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 13:21:05 +00:00
55681e438e
Differential Revision: https://phabricator.services.mozilla.com/D72954
236 lines
9.1 KiB
C++
236 lines
9.1 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#ifndef SystemTimeConverter_h
|
|
#define SystemTimeConverter_h
|
|
|
|
#include <limits>
|
|
#include <type_traits>
|
|
#include "mozilla/TimeStamp.h"
|
|
|
|
namespace mozilla {
|
|
|
|
// Utility class that converts time values represented as an unsigned integral
|
|
// number of milliseconds from one time source (e.g. a native event time) to
|
|
// corresponding mozilla::TimeStamp objects.
|
|
//
|
|
// This class handles wrapping of integer values and skew between the time
|
|
// source and mozilla::TimeStamp values.
|
|
//
|
|
// It does this by using an historical reference time recorded in both time
|
|
// scales (i.e. both as a numerical time value and as a TimeStamp).
|
|
//
|
|
// For performance reasons, this class is careful to minimize calls to the
|
|
// native "current time" function (e.g. gdk_x11_server_get_time) since this can
|
|
// be slow.
|
|
template <typename Time, typename TimeStampNowProvider = TimeStamp>
|
|
class SystemTimeConverter {
|
|
public:
|
|
SystemTimeConverter()
|
|
: mReferenceTime(Time(0)),
|
|
mReferenceTimeStamp() // Initializes to the null timestamp
|
|
,
|
|
mLastBackwardsSkewCheck(Time(0)),
|
|
kTimeRange(std::numeric_limits<Time>::max()),
|
|
kTimeHalfRange(kTimeRange / 2),
|
|
kBackwardsSkewCheckInterval(Time(2000)) {
|
|
static_assert(!std::is_signed_v<Time>, "Expected Time to be unsigned");
|
|
}
|
|
|
|
template <typename CurrentTimeGetter>
|
|
mozilla::TimeStamp GetTimeStampFromSystemTime(
|
|
Time aTime, CurrentTimeGetter& aCurrentTimeGetter) {
|
|
TimeStamp roughlyNow = TimeStampNowProvider::Now();
|
|
|
|
// If the reference time is not set, use the current time value to fill
|
|
// it in.
|
|
if (mReferenceTimeStamp.IsNull()) {
|
|
// This sometimes happens when ::GetMessageTime returns 0 for the first
|
|
// message on Windows.
|
|
if (!aTime) return roughlyNow;
|
|
UpdateReferenceTime(aTime, aCurrentTimeGetter);
|
|
}
|
|
|
|
// Check for skew between the source of Time values and TimeStamp values.
|
|
// We do this by comparing two durations (both in ms):
|
|
//
|
|
// i. The duration from the reference time to the passed-in time.
|
|
// (timeDelta in the diagram below)
|
|
// ii. The duration from the reference timestamp to the current time
|
|
// based on TimeStamp::Now.
|
|
// (timeStampDelta in the diagram below)
|
|
//
|
|
// Normally, we'd expect (ii) to be slightly larger than (i) to account
|
|
// for the time taken between generating the event and processing it.
|
|
//
|
|
// If (ii) - (i) is negative then the source of Time values is getting
|
|
// "ahead" of TimeStamp. We call this "forwards" skew below.
|
|
//
|
|
// For the reverse case, if (ii) - (i) is positive (and greater than some
|
|
// tolerance factor), then we may have "backwards" skew. This is often
|
|
// the case when we have a backlog of events and by the time we process
|
|
// them, the time given by the system is comparatively "old".
|
|
//
|
|
// The IsNewerThanTimestamp function computes the equivalent of |aTime| in
|
|
// the TimeStamp scale and returns that in |timeAsTimeStamp|.
|
|
//
|
|
// Graphically:
|
|
//
|
|
// mReferenceTime aTime
|
|
// Time scale: ........+.......................*........
|
|
// |--------timeDelta------|
|
|
//
|
|
// mReferenceTimeStamp roughlyNow
|
|
// TimeStamp scale: ........+...........................*....
|
|
// |------timeStampDelta-------|
|
|
//
|
|
// |---|
|
|
// roughlyNow-timeAsTimeStamp
|
|
//
|
|
TimeStamp timeAsTimeStamp;
|
|
bool newer = IsTimeNewerThanTimestamp(aTime, roughlyNow, &timeAsTimeStamp);
|
|
|
|
// Tolerance when detecting clock skew.
|
|
static const TimeDuration kTolerance = TimeDuration::FromMilliseconds(30.0);
|
|
|
|
// Check for forwards skew
|
|
if (newer) {
|
|
// Make aTime correspond to roughlyNow
|
|
UpdateReferenceTime(aTime, roughlyNow);
|
|
|
|
// We didn't have backwards skew so don't bother checking for
|
|
// backwards skew again for a little while.
|
|
mLastBackwardsSkewCheck = aTime;
|
|
|
|
return roughlyNow;
|
|
}
|
|
|
|
if (roughlyNow - timeAsTimeStamp <= kTolerance) {
|
|
// If the time between event times and TimeStamp values is within
|
|
// the tolerance then assume we don't have clock skew so we can
|
|
// avoid checking for backwards skew for a while.
|
|
mLastBackwardsSkewCheck = aTime;
|
|
} else if (aTime - mLastBackwardsSkewCheck > kBackwardsSkewCheckInterval) {
|
|
aCurrentTimeGetter.GetTimeAsyncForPossibleBackwardsSkew(roughlyNow);
|
|
mLastBackwardsSkewCheck = aTime;
|
|
}
|
|
|
|
// Finally, calculate the timestamp
|
|
return timeAsTimeStamp;
|
|
}
|
|
|
|
void CompensateForBackwardsSkew(Time aReferenceTime,
|
|
const TimeStamp& aLowerBound) {
|
|
// Check if we actually have backwards skew. Backwards skew looks like
|
|
// the following:
|
|
//
|
|
// mReferenceTime
|
|
// Time: ..+...a...b...c..........................
|
|
//
|
|
// mReferenceTimeStamp
|
|
// TimeStamp: ..+.....a.....b.....c....................
|
|
//
|
|
// Converted
|
|
// time: ......a'..b'..c'.........................
|
|
//
|
|
// What we need to do is bring mReferenceTime "forwards".
|
|
//
|
|
// Suppose when we get (c), we detect possible backwards skew and trigger
|
|
// an async request for the current time (which is passed in here as
|
|
// aReferenceTime).
|
|
//
|
|
// We end up with something like the following:
|
|
//
|
|
// mReferenceTime aReferenceTime
|
|
// Time: ..+...a...b...c...v......................
|
|
//
|
|
// mReferenceTimeStamp
|
|
// TimeStamp: ..+.....a.....b.....c..........x.........
|
|
// ^ ^
|
|
// aLowerBound TimeStamp::Now()
|
|
//
|
|
// If the duration (aLowerBound - mReferenceTimeStamp) is greater than
|
|
// (aReferenceTime - mReferenceTime) then we know we have backwards skew.
|
|
//
|
|
// If that's not the case, then we probably just got caught behind
|
|
// temporarily.
|
|
if (IsTimeNewerThanTimestamp(aReferenceTime, aLowerBound, nullptr)) {
|
|
return;
|
|
}
|
|
|
|
// We have backwards skew; the equivalent TimeStamp for aReferenceTime lies
|
|
// somewhere between aLowerBound (which was the TimeStamp when we triggered
|
|
// the async request for the current time) and TimeStamp::Now().
|
|
//
|
|
// If aReferenceTime was waiting in the event queue for a long time, the
|
|
// equivalent TimeStamp might be much closer to aLowerBound than
|
|
// TimeStamp::Now() so for now we just set it to aLowerBound. That's
|
|
// guaranteed to be at least somewhat of an improvement.
|
|
UpdateReferenceTime(aReferenceTime, aLowerBound);
|
|
}
|
|
|
|
private:
|
|
template <typename CurrentTimeGetter>
|
|
void UpdateReferenceTime(Time aReferenceTime,
|
|
const CurrentTimeGetter& aCurrentTimeGetter) {
|
|
Time currentTime = aCurrentTimeGetter.GetCurrentTime();
|
|
TimeStamp currentTimeStamp = TimeStampNowProvider::Now();
|
|
Time timeSinceReference = currentTime - aReferenceTime;
|
|
TimeStamp referenceTimeStamp =
|
|
currentTimeStamp - TimeDuration::FromMilliseconds(timeSinceReference);
|
|
UpdateReferenceTime(aReferenceTime, referenceTimeStamp);
|
|
}
|
|
|
|
void UpdateReferenceTime(Time aReferenceTime,
|
|
const TimeStamp& aReferenceTimeStamp) {
|
|
mReferenceTime = aReferenceTime;
|
|
mReferenceTimeStamp = aReferenceTimeStamp;
|
|
}
|
|
|
|
bool IsTimeNewerThanTimestamp(Time aTime, TimeStamp aTimeStamp,
|
|
TimeStamp* aTimeAsTimeStamp) {
|
|
Time timeDelta = aTime - mReferenceTime;
|
|
|
|
// Cast the result to signed 64-bit integer first since that should be
|
|
// enough to hold the range of values returned by ToMilliseconds() and
|
|
// the result of converting from double to an integer-type when the value
|
|
// is outside the integer range is undefined.
|
|
// Then we do an implicit cast to Time (typically an unsigned 32-bit
|
|
// integer) which wraps times outside that range.
|
|
TimeDuration timeStampDelta = (aTimeStamp - mReferenceTimeStamp);
|
|
int64_t wholeMillis = static_cast<int64_t>(timeStampDelta.ToMilliseconds());
|
|
Time wrappedTimeStampDelta = wholeMillis; // truncate to unsigned
|
|
|
|
Time timeToTimeStamp = wrappedTimeStampDelta - timeDelta;
|
|
bool isNewer = false;
|
|
if (timeToTimeStamp == 0) {
|
|
// wholeMillis needs no adjustment
|
|
} else if (timeToTimeStamp < kTimeHalfRange) {
|
|
wholeMillis -= timeToTimeStamp;
|
|
} else {
|
|
isNewer = true;
|
|
wholeMillis += (-timeToTimeStamp);
|
|
}
|
|
if (aTimeAsTimeStamp) {
|
|
*aTimeAsTimeStamp =
|
|
mReferenceTimeStamp + TimeDuration::FromMilliseconds(wholeMillis);
|
|
}
|
|
|
|
return isNewer;
|
|
}
|
|
|
|
Time mReferenceTime;
|
|
TimeStamp mReferenceTimeStamp;
|
|
Time mLastBackwardsSkewCheck;
|
|
|
|
const Time kTimeRange;
|
|
const Time kTimeHalfRange;
|
|
const Time kBackwardsSkewCheckInterval;
|
|
};
|
|
|
|
} // namespace mozilla
|
|
|
|
#endif /* SystemTimeConverter_h */
|