Global Metrics

path: .metrics.nexits.average
old: 0.0
new: 0.5714285714285714

path: .metrics.nexits.sum
old: 0.0
new: 4.0

path: .metrics.nargs.sum
old: 0.0
new: 3.0

path: .metrics.nargs.average
old: 0.0
new: 0.42857142857142855

path: .metrics.cyclomatic.average
old: 1.0
new: 1.2666666666666666

path: .metrics.cyclomatic.sum
old: 6.0
new: 19.0

path: .metrics.loc.cloc
old: 18.0
new: 85.0

path: .metrics.loc.blank
old: 8.0
new: 30.0

path: .metrics.loc.sloc
old: 49.0
new: 191.0

path: .metrics.loc.ploc
old: 23.0
new: 76.0

path: .metrics.loc.lloc
old: 0.0
new: 10.0

path: .metrics.mi.mi_original
old: 77.60984893600903
new: 41.67134000188257

path: .metrics.mi.mi_sei
old: 77.22441753244352
new: 29.30680521115906

path: .metrics.mi.mi_visual_studio
old: 45.38587657076551
new: 24.369204679463493

path: .metrics.nom.total
old: 2.0
new: 7.0

path: .metrics.nom.functions
old: 2.0
new: 7.0

path: .metrics.halstead.estimated_program_length
old: 81.83229392178727
new: 506.11891378608817

path: .metrics.halstead.N1
old: 38.0
new: 190.0

path: .metrics.halstead.volume
old: 262.36659345130676
new: 2138.071865768946

path: .metrics.halstead.length
old: 58.0
new: 331.0

path: .metrics.halstead.vocabulary
old: 23.0
new: 88.0

path: .metrics.halstead.purity_ratio
old: 1.41090161934116
new: 1.5290601624957347

path: .metrics.halstead.level
old: 0.15555555555555556
new: 0.0592407175636212

path: .metrics.halstead.effort
old: 1686.6423864726864
new: 36091.2553679448

path: .metrics.halstead.N2
old: 20.0
new: 141.0

path: .metrics.halstead.n1
old: 9.0
new: 17.0

path: .metrics.halstead.n2
old: 14.0
new: 71.0

path: .metrics.halstead.bugs
old: 0.04723103023209937
new: 0.3640380149770425

path: .metrics.halstead.time
old: 93.70235480403814
new: 2005.0697426636

path: .metrics.halstead.difficulty
old: 6.428571428571429
new: 16.880281690140844

Spaces Data

Minimal test - lines (18, 188)

path: .spaces[0].spaces[0].metrics.nom.functions
old: 2.0
new: 7.0

path: .spaces[0].spaces[0].metrics.nom.total
old: 2.0
new: 7.0

path: .spaces[0].spaces[0].metrics.nexits.sum
old: 0.0
new: 4.0

path: .spaces[0].spaces[0].metrics.nexits.average
old: 0.0
new: 0.5714285714285714

path: .spaces[0].spaces[0].metrics.halstead.volume
old: 232.7928234072743
new: 2065.5127757564037

path: .spaces[0].spaces[0].metrics.halstead.difficulty
old: 6.375
new: 17.386363636363637

path: .spaces[0].spaces[0].metrics.halstead.N2
old: 17.0
new: 135.0

path: .spaces[0].spaces[0].metrics.halstead.purity_ratio
old: 1.3499787739931073
new: 1.4457311116633138

path: .spaces[0].spaces[0].metrics.halstead.level
old: 0.1568627450980392
new: 0.05751633986928104

path: .spaces[0].spaces[0].metrics.halstead.n1
old: 9.0
new: 17.0

path: .spaces[0].spaces[0].metrics.halstead.vocabulary
old: 21.0
new: 83.0

path: .spaces[0].spaces[0].metrics.halstead.N1
old: 36.0
new: 189.0

path: .spaces[0].spaces[0].metrics.halstead.n2
old: 12.0
new: 66.0

path: .spaces[0].spaces[0].metrics.halstead.estimated_program_length
old: 71.54887502163469
new: 468.4168801789137

path: .spaces[0].spaces[0].metrics.halstead.effort
old: 1484.0542492213738
new: 35911.75621485565

path: .spaces[0].spaces[0].metrics.halstead.time
old: 82.44745829007633
new: 1995.0975674919807

path: .spaces[0].spaces[0].metrics.halstead.length
old: 53.0
new: 324.0

path: .spaces[0].spaces[0].metrics.halstead.bugs
old: 0.04336891892558609
new: 0.3628299882729645

path: .spaces[0].spaces[0].metrics.cyclomatic.sum
old: 4.0
new: 17.0

path: .spaces[0].spaces[0].metrics.cyclomatic.average
old: 1.0
new: 1.3076923076923077

path: .spaces[0].spaces[0].metrics.loc.blank
old: 4.0
new: 28.0

path: .spaces[0].spaces[0].metrics.loc.sloc
old: 25.0
new: 171.0

path: .spaces[0].spaces[0].metrics.loc.lloc
old: 0.0
new: 10.0

path: .spaces[0].spaces[0].metrics.loc.ploc
old: 17.0
new: 66.0

path: .spaces[0].spaces[0].metrics.loc.cloc
old: 4.0
new: 77.0

path: .spaces[0].spaces[0].metrics.nargs.average
old: 0.0
new: 0.42857142857142855

path: .spaces[0].spaces[0].metrics.nargs.sum
old: 0.0
new: 3.0

path: .spaces[0].spaces[0].metrics.mi.mi_sei
old: 83.00106133241832
new: 32.76618560127405

path: .spaces[0].spaces[0].metrics.mi.mi_visual_studio
old: 52.3938230520746
new: 25.791084599764005

path: .spaces[0].spaces[0].metrics.mi.mi_original
old: 89.59343741904756
new: 44.10275466559645

Code

namespace widget {

/**
 * De-jitters touch motions by resampling (interpolating or extrapolating) touch
 * positions for the vsync timestamp.
 *
 * Touch resampling improves the touch panning experience on devices where touch
 * positions are sampled at a rate that's not an integer multiple of the display
 * refresh rate, for example 100Hz touch sampling on a 60Hz display: Without
 * resampling, we would alternate between taking one touch sample or two touch
 * samples into account each frame, creating a jittery motion ("small step, big
 * step, small step, big step").
 * Intended for use on Android, where both touch events and vsync notifications
 * arrive on the same thread, the Java UI thread.
 * This class is not thread safe.
 *
 * TouchResampler operates in the following way:
 *
 * Original events are fed into ProcessEvent().
 * Outgoing events (potentially resampled for resampling) are added to a queue
 * and can be consumed by calling ConsumeOutgoingEvents(). Touch events which
 * are not touch move events are forwarded instantly and not resampled. Only
 * touch move events are resampled. Whenever a touch move event is received, it
 * gets delayed until NotifyFrame() is called, at which point it is resampled
 * into a resampled version for the given frame timestamp, and added to the
 * outgoing queue. If no touch move event is received between two consecutive
 * frames, this is treated as a stop in the touch motion. If the last outgoing
 * event was an resampled touch move event, we return back to the non-resampled
 * state by emitting a copy of the last original touch move event, which has
 * unmodified position data. Touch events which are not touch move events also
 * force a return to the non-resampled state before they are moved to the
 * outgoing queue.
 */
class TouchResampler final {
 public:
  // Feed a touch event into the interpolater. Returns an ID that can be used to
  // match outgoing events to this incoming event, to track data associated with
  // this event.
  uint64_t ProcessEvent(MultiTouchInput&& aInput);

  // Emit events, potentially resampled, for this timestamp. The events are put
  // into the outgoing queue. May not emit any events if there's no update.
  void NotifyFrame(const TimeStamp& aTimeStamp);

  // Returns true between the start and the end of a touch gesture. During this
  // time, the caller should keep itself registered with the system frame
  // callback mechanism, so that NotifyFrame() can be called on every frame.
  // (Otherwise, if we only registered the callback after receiving a touch move
  // event, the frame callback might be delayed by a full frame.)
  bool InTouchingState() const { return mCurrentTouches.HasTouch(); }

  struct OutgoingEvent {
    // The event, potentially modified from the original for resampling.
    MultiTouchInput mEvent;

    // Some(eventId) if this event is a modified version of an original event,
    // Nothing() if this is an extra event.
    Maybe mEventId;
  };

  // Returns the outgoing events that were produced since the last call.
  // No event IDs will be skipped. Returns at least one outgoing event for each
  // incoming event (possibly after a delay), and potential extra events with
  // no originating event ID.
  // Outgoing events should be consumed after every call to ProcessEvent() and
  // after every call to NotifyFrame().
  std::queue ConsumeOutgoingEvents() {
    return std::move(mOutgoingEvents);
  }

 private:
  // Add the event to the outgoing queue.
  void EmitEvent(MultiTouchInput&& aInput, uint64_t aEventId) {
    mLastEmittedEventTime = aInput.mTimeStamp;
    mOutgoingEvents.push(OutgoingEvent{std::move(aInput), Some(aEventId)});
  }

  // Emit an event that does not correspond to an incoming event.
  void EmitExtraEvent(MultiTouchInput&& aInput) {
    mLastEmittedEventTime = aInput.mTimeStamp;
    mOutgoingEvents.push(OutgoingEvent{std::move(aInput), Nothing()});
  }

  // Move any touch move events that we deferred for resampling to the outgoing
  // queue unmodified, leaving mDeferredTouchMoveEvents empty.
  void FlushDeferredTouchMoveEventsUnresampled();

  // Must only be called if mInResampledState is true and
  // mDeferredTouchMoveEvents is empty. Emits mOriginalOfResampledTouchMove,
  // with a potentially adjusted timestamp for correct ordering.
  void ReturnToNonResampledState();

  // Takes historical touch data from mRemainingTouchData and prepends it to the
  // data in aInput.
  void PrependLeftoverHistoricalData(MultiTouchInput* aInput);

  struct DataPoint {
    TimeStamp mTimeStamp;
    ScreenIntPoint mPosition;
  };

  struct TouchInfo {
    void Update(const SingleTouchData& aTouch, const TimeStamp& aEventTime);
    ScreenIntPoint ResampleAtTime(const ScreenIntPoint& aLastObservedPosition,
                                  const TimeStamp& aTimeStamp);

    int32_t mIdentifier = 0;
    Maybe mBaseDataPoint;
    Maybe mLatestDataPoint;
  };

  struct CurrentTouches {
    void UpdateFromEvent(const MultiTouchInput& aInput);
    bool HasTouch() const { return !mTouches.IsEmpty(); }
    TimeStamp LatestDataPointTime() { return mLatestDataPointTime; }

    ScreenIntPoint ResampleTouchPositionAtTime(
        int32_t aIdentifier, const ScreenIntPoint& aLastObservedPosition,
        const TimeStamp& aTimeStamp);

    void ClearDataPoints() {
      for (auto& touch : mTouches) {
        touch.mBaseDataPoint = Nothing();
        touch.mLatestDataPoint = Nothing();
      }
    }

   private:
    nsTArray::iterator TouchByIdentifier(int32_t aIdentifier);

    nsTArray mTouches;
    TimeStamp mLatestDataPointTime;
  };

  // The current touch positions with historical data points. This data only
  // contains original non-resampled positions from the incoming touch events.
  CurrentTouches mCurrentTouches;

  // Incoming touch move events are stored here until NotifyFrame is called.
  std::queue> mDeferredTouchMoveEvents;

  // Stores any touch samples that were not included in the last emitted touch
  // move event because they were in the future compared to the emitted event's
  // timestamp. These data points should be prepended to the historical data of
  // the next emitted touch move evnt.
  // Can only be non-empty if mInResampledState is true.
  std::unordered_map>
      mRemainingTouchData;

  // If we're in an resampled state, because the last outgoing event was a
  // resampled touch move event, then this contains a copy of the unresampled,
  // original touch move event.
  // Some() iff mInResampledState is true.
  Maybe mOriginalOfResampledTouchMove;

  // The stream of outgoing events that can be consumed by our caller.
  std::queue mOutgoingEvents;

  // The timestamp of the event that was emitted most recently, or the null
  // timestamp if no event has been emitted yet.
  TimeStamp mLastEmittedEventTime;

  uint64_t mNextEventId = 0;

  // True if the last outgoing event was a touch move event with an resampled
  // position. We only want to stay in this state as long as a continuous stream
  // of touch move events is coming in.
  bool mInResampledState = false;
};

}  // namespace widget

Minimal test - lines (51, 186)

path: .spaces[0].spaces[0].spaces[0].metrics.nargs.sum
old: 0.0
new: 3.0

path: .spaces[0].spaces[0].spaces[0].metrics.nargs.average
old: 0.0
new: 0.42857142857142855

path: .spaces[0].spaces[0].spaces[0].metrics.cyclomatic.average
old: 1.0
new: 1.3333333333333333

path: .spaces[0].spaces[0].spaces[0].metrics.cyclomatic.sum
old: 2.0
new: 16.0

path: .spaces[0].spaces[0].spaces[0].metrics.loc.ploc
old: 11.0
new: 64.0

path: .spaces[0].spaces[0].spaces[0].metrics.loc.lloc
old: 0.0
new: 10.0

path: .spaces[0].spaces[0].spaces[0].metrics.loc.cloc
old: 2.0
new: 46.0

path: .spaces[0].spaces[0].spaces[0].metrics.loc.blank
old: 2.0
new: 26.0

path: .spaces[0].spaces[0].spaces[0].metrics.loc.sloc
old: 15.0
new: 136.0

path: .spaces[0].spaces[0].spaces[0].metrics.nexits.average
old: 0.0
new: 0.5714285714285714

path: .spaces[0].spaces[0].spaces[0].metrics.nexits.sum
old: 0.0
new: 4.0

path: .spaces[0].spaces[0].spaces[0].metrics.nom.total
old: 1.0
new: 7.0

path: .spaces[0].spaces[0].spaces[0].metrics.nom.functions
old: 1.0
new: 7.0

path: .spaces[0].spaces[0].spaces[0].metrics.halstead.length
old: 35.0
new: 321.0

path: .spaces[0].spaces[0].spaces[0].metrics.halstead.estimated_program_length
old: 57.05865002596162
new: 460.9407761481053

path: .spaces[0].spaces[0].spaces[0].metrics.halstead.level
old: 0.18181818181818185
new: 0.057067603160667245

path: .spaces[0].spaces[0].spaces[0].metrics.halstead.volume
old: 145.94737505048093
new: 2040.7741934824048

path: .spaces[0].spaces[0].spaces[0].metrics.halstead.n2
old: 9.0
new: 65.0

path: .spaces[0].spaces[0].spaces[0].metrics.halstead.vocabulary
old: 18.0
new: 82.0

path: .spaces[0].spaces[0].spaces[0].metrics.halstead.effort
old: 802.7105627776451
new: 35760.64317502245

path: .spaces[0].spaces[0].spaces[0].metrics.halstead.N2
old: 11.0
new: 134.0

path: .spaces[0].spaces[0].spaces[0].metrics.halstead.purity_ratio
old: 1.6302471435989034
new: 1.43595257367011

path: .spaces[0].spaces[0].spaces[0].metrics.halstead.difficulty
old: 5.5
new: 17.523076923076925

path: .spaces[0].spaces[0].spaces[0].metrics.halstead.bugs
old: 0.028790645175253947
new: 0.36181143850239467

path: .spaces[0].spaces[0].spaces[0].metrics.halstead.n1
old: 9.0
new: 17.0

path: .spaces[0].spaces[0].spaces[0].metrics.halstead.N1
old: 24.0
new: 187.0

path: .spaces[0].spaces[0].spaces[0].metrics.halstead.time
old: 44.59503126542473
new: 1986.7023986123584

path: .spaces[0].spaces[0].spaces[0].metrics.mi.mi_sei
old: 96.66371880954054
new: 34.526398748934426

path: .spaces[0].spaces[0].spaces[0].metrics.mi.mi_visual_studio
old: 58.92205085403852
new: 28.131784409076637

path: .spaces[0].spaces[0].spaces[0].metrics.mi.mi_original
old: 100.75670696040586
new: 48.10535133952105

Code

class TouchResampler final {
 public:
  // Feed a touch event into the interpolater. Returns an ID that can be used to
  // match outgoing events to this incoming event, to track data associated with
  // this event.
  uint64_t ProcessEvent(MultiTouchInput&& aInput);

  // Emit events, potentially resampled, for this timestamp. The events are put
  // into the outgoing queue. May not emit any events if there's no update.
  void NotifyFrame(const TimeStamp& aTimeStamp);

  // Returns true between the start and the end of a touch gesture. During this
  // time, the caller should keep itself registered with the system frame
  // callback mechanism, so that NotifyFrame() can be called on every frame.
  // (Otherwise, if we only registered the callback after receiving a touch move
  // event, the frame callback might be delayed by a full frame.)
  bool InTouchingState() const { return mCurrentTouches.HasTouch(); }

  struct OutgoingEvent {
    // The event, potentially modified from the original for resampling.
    MultiTouchInput mEvent;

    // Some(eventId) if this event is a modified version of an original event,
    // Nothing() if this is an extra event.
    Maybe mEventId;
  };

  // Returns the outgoing events that were produced since the last call.
  // No event IDs will be skipped. Returns at least one outgoing event for each
  // incoming event (possibly after a delay), and potential extra events with
  // no originating event ID.
  // Outgoing events should be consumed after every call to ProcessEvent() and
  // after every call to NotifyFrame().
  std::queue ConsumeOutgoingEvents() {
    return std::move(mOutgoingEvents);
  }

 private:
  // Add the event to the outgoing queue.
  void EmitEvent(MultiTouchInput&& aInput, uint64_t aEventId) {
    mLastEmittedEventTime = aInput.mTimeStamp;
    mOutgoingEvents.push(OutgoingEvent{std::move(aInput), Some(aEventId)});
  }

  // Emit an event that does not correspond to an incoming event.
  void EmitExtraEvent(MultiTouchInput&& aInput) {
    mLastEmittedEventTime = aInput.mTimeStamp;
    mOutgoingEvents.push(OutgoingEvent{std::move(aInput), Nothing()});
  }

  // Move any touch move events that we deferred for resampling to the outgoing
  // queue unmodified, leaving mDeferredTouchMoveEvents empty.
  void FlushDeferredTouchMoveEventsUnresampled();

  // Must only be called if mInResampledState is true and
  // mDeferredTouchMoveEvents is empty. Emits mOriginalOfResampledTouchMove,
  // with a potentially adjusted timestamp for correct ordering.
  void ReturnToNonResampledState();

  // Takes historical touch data from mRemainingTouchData and prepends it to the
  // data in aInput.
  void PrependLeftoverHistoricalData(MultiTouchInput* aInput);

  struct DataPoint {
    TimeStamp mTimeStamp;
    ScreenIntPoint mPosition;
  };

  struct TouchInfo {
    void Update(const SingleTouchData& aTouch, const TimeStamp& aEventTime);
    ScreenIntPoint ResampleAtTime(const ScreenIntPoint& aLastObservedPosition,
                                  const TimeStamp& aTimeStamp);

    int32_t mIdentifier = 0;
    Maybe mBaseDataPoint;
    Maybe mLatestDataPoint;
  };

  struct CurrentTouches {
    void UpdateFromEvent(const MultiTouchInput& aInput);
    bool HasTouch() const { return !mTouches.IsEmpty(); }
    TimeStamp LatestDataPointTime() { return mLatestDataPointTime; }

    ScreenIntPoint ResampleTouchPositionAtTime(
        int32_t aIdentifier, const ScreenIntPoint& aLastObservedPosition,
        const TimeStamp& aTimeStamp);

    void ClearDataPoints() {
      for (auto& touch : mTouches) {
        touch.mBaseDataPoint = Nothing();
        touch.mLatestDataPoint = Nothing();
      }
    }

   private:
    nsTArray::iterator TouchByIdentifier(int32_t aIdentifier);

    nsTArray mTouches;
    TimeStamp mLatestDataPointTime;
  };

  // The current touch positions with historical data points. This data only
  // contains original non-resampled positions from the incoming touch events.
  CurrentTouches mCurrentTouches;

  // Incoming touch move events are stored here until NotifyFrame is called.
  std::queue> mDeferredTouchMoveEvents;

  // Stores any touch samples that were not included in the last emitted touch
  // move event because they were in the future compared to the emitted event's
  // timestamp. These data points should be prepended to the historical data of
  // the next emitted touch move evnt.
  // Can only be non-empty if mInResampledState is true.
  std::unordered_map>
      mRemainingTouchData;

  // If we're in an resampled state, because the last outgoing event was a
  // resampled touch move event, then this contains a copy of the unresampled,
  // original touch move event.
  // Some() iff mInResampledState is true.
  Maybe mOriginalOfResampledTouchMove;

  // The stream of outgoing events that can be consumed by our caller.
  std::queue mOutgoingEvents;

  // The timestamp of the event that was emitted most recently, or the null
  // timestamp if no event has been emitted yet.
  TimeStamp mLastEmittedEventTime;

  uint64_t mNextEventId = 0;

  // True if the last outgoing event was a touch move event with an resampled
  // position. We only want to stay in this state as long as a continuous stream
  // of touch move events is coming in.
  bool mInResampledState = false;
};

Minimal test - lines (67, 67)

path: .spaces[0].spaces[0].spaces[0].spaces[0].metrics.loc.lloc
old: 0.0
new: 1.0

path: .spaces[0].spaces[0].spaces[0].spaces[0].metrics.mi.mi_sei
old: 155.17000000000002
new: 144.8408406278255

path: .spaces[0].spaces[0].spaces[0].spaces[0].metrics.mi.mi_visual_studio
old: 93.54204911302038
new: 89.35513233151332

path: .spaces[0].spaces[0].spaces[0].spaces[0].metrics.mi.mi_original
old: 159.95690398326485
new: 152.79727628688778

path: .spaces[0].spaces[0].spaces[0].spaces[0].metrics.halstead.estimated_program_length
old: 4.754887502163468
new: 20.264662506490403

path: .spaces[0].spaces[0].spaces[0].spaces[0].metrics.halstead.level
old: 0.6666666666666666
new: 0.3333333333333333

path: .spaces[0].spaces[0].spaces[0].spaces[0].metrics.halstead.time
old: 0.6666666666666666
new: 5.2832083357371875

path: .spaces[0].spaces[0].spaces[0].spaces[0].metrics.halstead.volume
old: 8.0
new: 31.69925001442312

path: .spaces[0].spaces[0].spaces[0].spaces[0].metrics.halstead.length
old: 4.0
new: 10.0

path: .spaces[0].spaces[0].spaces[0].spaces[0].metrics.halstead.vocabulary
old: 4.0
new: 9.0

path: .spaces[0].spaces[0].spaces[0].spaces[0].metrics.halstead.N1
old: 3.0
new: 7.0

path: .spaces[0].spaces[0].spaces[0].spaces[0].metrics.halstead.n2
old: 1.0
new: 3.0

path: .spaces[0].spaces[0].spaces[0].spaces[0].metrics.halstead.difficulty
old: 1.5
new: 3.0

path: .spaces[0].spaces[0].spaces[0].spaces[0].metrics.halstead.bugs
old: 0.0017471609294725976
new: 0.006944786620971931

path: .spaces[0].spaces[0].spaces[0].spaces[0].metrics.halstead.n1
old: 3.0
new: 6.0

path: .spaces[0].spaces[0].spaces[0].spaces[0].metrics.halstead.purity_ratio
old: 1.188721875540867
new: 2.0264662506490403

path: .spaces[0].spaces[0].spaces[0].spaces[0].metrics.halstead.N2
old: 1.0
new: 3.0

path: .spaces[0].spaces[0].spaces[0].spaces[0].metrics.halstead.effort
old: 12.0
new: 95.09775004326936

path: .spaces[0].spaces[0].spaces[0].spaces[0].metrics.nexits.sum
old: 0.0
new: 1.0

path: .spaces[0].spaces[0].spaces[0].spaces[0].metrics.nexits.average
old: 0.0
new: 1.0

Code

  bool InTouchingState() const { return mCurrentTouches.HasTouch(); }

Minimal test - lines (17, 189)

path: .spaces[0].metrics.nexits.sum
old: 0.0
new: 4.0

path: .spaces[0].metrics.nexits.average
old: 0.0
new: 0.5714285714285714

path: .spaces[0].metrics.mi.mi_sei
old: 80.57671602890764
new: 32.21473506783513

path: .spaces[0].metrics.mi.mi_original
old: 84.9991387284759
new: 43.63830607963422

path: .spaces[0].metrics.mi.mi_visual_studio
old: 49.707098671623335
new: 25.519477239552177

path: .spaces[0].metrics.cyclomatic.average
old: 1.0
new: 1.2857142857142858

path: .spaces[0].metrics.cyclomatic.sum
old: 5.0
new: 18.0

path: .spaces[0].metrics.loc.lloc
old: 0.0
new: 10.0

path: .spaces[0].metrics.loc.sloc
old: 32.0
new: 173.0

path: .spaces[0].metrics.loc.blank
old: 6.0
new: 27.0

path: .spaces[0].metrics.loc.ploc
old: 19.0
new: 68.0

path: .spaces[0].metrics.loc.cloc
old: 7.0
new: 78.0

path: .spaces[0].metrics.halstead.time
old: 86.44436676127685
new: 1997.4984864665944

path: .spaces[0].metrics.halstead.vocabulary
old: 22.0
new: 84.0

path: .spaces[0].metrics.halstead.bugs
old: 0.044759464313189494
new: 0.363121018576748

path: .spaces[0].metrics.halstead.estimated_program_length
old: 76.63504134881501
new: 475.9148440619265

path: .spaces[0].metrics.halstead.length
old: 56.0
new: 326.0

path: .spaces[0].metrics.halstead.level
old: 0.16049382716049382
new: 0.05795847750865052

path: .spaces[0].metrics.halstead.purity_ratio
old: 1.3684828812288394
new: 1.4598614848525353

path: .spaces[0].metrics.halstead.N1
old: 38.0
new: 190.0

path: .spaces[0].metrics.halstead.volume
old: 249.72817064368863
new: 2083.895479825876

path: .spaces[0].metrics.halstead.N2
old: 18.0
new: 136.0

path: .spaces[0].metrics.halstead.n2
old: 13.0
new: 67.0

path: .spaces[0].metrics.halstead.effort
old: 1555.9986017029833
new: 35954.972756398696

path: .spaces[0].metrics.halstead.n1
old: 9.0
new: 17.0

path: .spaces[0].metrics.halstead.difficulty
old: 6.230769230769231
new: 17.253731343283583

path: .spaces[0].metrics.nargs.sum
old: 0.0
new: 3.0

path: .spaces[0].metrics.nargs.average
old: 0.0
new: 0.42857142857142855

path: .spaces[0].metrics.nom.functions
old: 2.0
new: 7.0

path: .spaces[0].metrics.nom.total
old: 2.0
new: 7.0

Code

namespace mozilla {
namespace widget {

/**
 * De-jitters touch motions by resampling (interpolating or extrapolating) touch
 * positions for the vsync timestamp.
 *
 * Touch resampling improves the touch panning experience on devices where touch
 * positions are sampled at a rate that's not an integer multiple of the display
 * refresh rate, for example 100Hz touch sampling on a 60Hz display: Without
 * resampling, we would alternate between taking one touch sample or two touch
 * samples into account each frame, creating a jittery motion ("small step, big
 * step, small step, big step").
 * Intended for use on Android, where both touch events and vsync notifications
 * arrive on the same thread, the Java UI thread.
 * This class is not thread safe.
 *
 * TouchResampler operates in the following way:
 *
 * Original events are fed into ProcessEvent().
 * Outgoing events (potentially resampled for resampling) are added to a queue
 * and can be consumed by calling ConsumeOutgoingEvents(). Touch events which
 * are not touch move events are forwarded instantly and not resampled. Only
 * touch move events are resampled. Whenever a touch move event is received, it
 * gets delayed until NotifyFrame() is called, at which point it is resampled
 * into a resampled version for the given frame timestamp, and added to the
 * outgoing queue. If no touch move event is received between two consecutive
 * frames, this is treated as a stop in the touch motion. If the last outgoing
 * event was an resampled touch move event, we return back to the non-resampled
 * state by emitting a copy of the last original touch move event, which has
 * unmodified position data. Touch events which are not touch move events also
 * force a return to the non-resampled state before they are moved to the
 * outgoing queue.
 */
class TouchResampler final {
 public:
  // Feed a touch event into the interpolater. Returns an ID that can be used to
  // match outgoing events to this incoming event, to track data associated with
  // this event.
  uint64_t ProcessEvent(MultiTouchInput&& aInput);

  // Emit events, potentially resampled, for this timestamp. The events are put
  // into the outgoing queue. May not emit any events if there's no update.
  void NotifyFrame(const TimeStamp& aTimeStamp);

  // Returns true between the start and the end of a touch gesture. During this
  // time, the caller should keep itself registered with the system frame
  // callback mechanism, so that NotifyFrame() can be called on every frame.
  // (Otherwise, if we only registered the callback after receiving a touch move
  // event, the frame callback might be delayed by a full frame.)
  bool InTouchingState() const { return mCurrentTouches.HasTouch(); }

  struct OutgoingEvent {
    // The event, potentially modified from the original for resampling.
    MultiTouchInput mEvent;

    // Some(eventId) if this event is a modified version of an original event,
    // Nothing() if this is an extra event.
    Maybe mEventId;
  };

  // Returns the outgoing events that were produced since the last call.
  // No event IDs will be skipped. Returns at least one outgoing event for each
  // incoming event (possibly after a delay), and potential extra events with
  // no originating event ID.
  // Outgoing events should be consumed after every call to ProcessEvent() and
  // after every call to NotifyFrame().
  std::queue ConsumeOutgoingEvents() {
    return std::move(mOutgoingEvents);
  }

 private:
  // Add the event to the outgoing queue.
  void EmitEvent(MultiTouchInput&& aInput, uint64_t aEventId) {
    mLastEmittedEventTime = aInput.mTimeStamp;
    mOutgoingEvents.push(OutgoingEvent{std::move(aInput), Some(aEventId)});
  }

  // Emit an event that does not correspond to an incoming event.
  void EmitExtraEvent(MultiTouchInput&& aInput) {
    mLastEmittedEventTime = aInput.mTimeStamp;
    mOutgoingEvents.push(OutgoingEvent{std::move(aInput), Nothing()});
  }

  // Move any touch move events that we deferred for resampling to the outgoing
  // queue unmodified, leaving mDeferredTouchMoveEvents empty.
  void FlushDeferredTouchMoveEventsUnresampled();

  // Must only be called if mInResampledState is true and
  // mDeferredTouchMoveEvents is empty. Emits mOriginalOfResampledTouchMove,
  // with a potentially adjusted timestamp for correct ordering.
  void ReturnToNonResampledState();

  // Takes historical touch data from mRemainingTouchData and prepends it to the
  // data in aInput.
  void PrependLeftoverHistoricalData(MultiTouchInput* aInput);

  struct DataPoint {
    TimeStamp mTimeStamp;
    ScreenIntPoint mPosition;
  };

  struct TouchInfo {
    void Update(const SingleTouchData& aTouch, const TimeStamp& aEventTime);
    ScreenIntPoint ResampleAtTime(const ScreenIntPoint& aLastObservedPosition,
                                  const TimeStamp& aTimeStamp);

    int32_t mIdentifier = 0;
    Maybe mBaseDataPoint;
    Maybe mLatestDataPoint;
  };

  struct CurrentTouches {
    void UpdateFromEvent(const MultiTouchInput& aInput);
    bool HasTouch() const { return !mTouches.IsEmpty(); }
    TimeStamp LatestDataPointTime() { return mLatestDataPointTime; }

    ScreenIntPoint ResampleTouchPositionAtTime(
        int32_t aIdentifier, const ScreenIntPoint& aLastObservedPosition,
        const TimeStamp& aTimeStamp);

    void ClearDataPoints() {
      for (auto& touch : mTouches) {
        touch.mBaseDataPoint = Nothing();
        touch.mLatestDataPoint = Nothing();
      }
    }

   private:
    nsTArray::iterator TouchByIdentifier(int32_t aIdentifier);

    nsTArray mTouches;
    TimeStamp mLatestDataPointTime;
  };

  // The current touch positions with historical data points. This data only
  // contains original non-resampled positions from the incoming touch events.
  CurrentTouches mCurrentTouches;

  // Incoming touch move events are stored here until NotifyFrame is called.
  std::queue> mDeferredTouchMoveEvents;

  // Stores any touch samples that were not included in the last emitted touch
  // move event because they were in the future compared to the emitted event's
  // timestamp. These data points should be prepended to the historical data of
  // the next emitted touch move evnt.
  // Can only be non-empty if mInResampledState is true.
  std::unordered_map>
      mRemainingTouchData;

  // If we're in an resampled state, because the last outgoing event was a
  // resampled touch move event, then this contains a copy of the unresampled,
  // original touch move event.
  // Some() iff mInResampledState is true.
  Maybe mOriginalOfResampledTouchMove;

  // The stream of outgoing events that can be consumed by our caller.
  std::queue mOutgoingEvents;

  // The timestamp of the event that was emitted most recently, or the null
  // timestamp if no event has been emitted yet.
  TimeStamp mLastEmittedEventTime;

  uint64_t mNextEventId = 0;

  // True if the last outgoing event was a touch move event with an resampled
  // position. We only want to stay in this state as long as a continuous stream
  // of touch move events is coming in.
  bool mInResampledState = false;
};

}  // namespace widget
}  // namespace mozilla