diff options
Diffstat (limited to 'widget/SystemTimeConverter.h')
-rw-r--r-- | widget/SystemTimeConverter.h | 231 |
1 files changed, 231 insertions, 0 deletions
diff --git a/widget/SystemTimeConverter.h b/widget/SystemTimeConverter.h new file mode 100644 index 000000000..5eb201fc8 --- /dev/null +++ b/widget/SystemTimeConverter.h @@ -0,0 +1,231 @@ +/* -*- 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 "mozilla/TimeStamp.h" +#include "mozilla/TypeTraits.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> +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(!IsSigned<Time>::value, "Expected Time to be unsigned"); + } + + template <typename CurrentTimeGetter> + mozilla::TimeStamp + GetTimeStampFromSystemTime(Time aTime, + CurrentTimeGetter& aCurrentTimeGetter) { + // If the reference time is not set, use the current time value to fill + // it in. + if (mReferenceTimeStamp.IsNull()) { + UpdateReferenceTime(aTime, aCurrentTimeGetter); + } + TimeStamp roughlyNow = TimeStamp::Now(); + + // 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". + // + // We call the absolute difference between (i) and (ii), "deltaFromNow". + // + // Graphically: + // + // mReferenceTime aTime + // Time scale: ........+.......................*........ + // |--------timeDelta------| + // + // mReferenceTimeStamp roughlyNow + // TimeStamp scale: ........+...........................*.... + // |------timeStampDelta-------| + // + // |---| + // deltaFromNow + // + Time deltaFromNow; + bool newer = IsTimeNewerThanTimestamp(aTime, roughlyNow, &deltaFromNow); + + // Tolerance when detecting clock skew. + static const Time kTolerance = 30; + + // 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 (deltaFromNow <= 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 roughlyNow - TimeDuration::FromMilliseconds(deltaFromNow); + } + + 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. + Time delta; + if (IsTimeNewerThanTimestamp(aReferenceTime, aLowerBound, &delta)) { + 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 = TimeStamp::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, Time* aDelta) + { + 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. + Time timeStampDelta = + static_cast<int64_t>((aTimeStamp - mReferenceTimeStamp).ToMilliseconds()); + + Time timeToTimeStamp = timeStampDelta - timeDelta; + bool isNewer = false; + if (timeToTimeStamp == 0) { + *aDelta = 0; + } else if (timeToTimeStamp < kTimeHalfRange) { + *aDelta = timeToTimeStamp; + } else { + isNewer = true; + *aDelta = timeDelta - timeStampDelta; + } + + return isNewer; + } + + Time mReferenceTime; + TimeStamp mReferenceTimeStamp; + Time mLastBackwardsSkewCheck; + + const Time kTimeRange; + const Time kTimeHalfRange; + const Time kBackwardsSkewCheckInterval; +}; + +} // namespace mozilla + +#endif /* SystemTimeConverter_h */ |