forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			231 lines
		
	
	
	
		
			8.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			231 lines
		
	
	
	
		
			8.5 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 "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 */
 |