Global Metrics

path: .metrics.nargs.sum
old: 77.0
new: 12.0

path: .metrics.nargs.average
old: 2.2
new: 0.8571428571428571

path: .metrics.halstead.estimated_program_length
old: 2745.5915000448863
new: 1173.1417466270589

path: .metrics.halstead.level
old: 0.011320199941193768
new: 0.015452901528850895

path: .metrics.halstead.vocabulary
old: 346.0
new: 174.0

path: .metrics.halstead.length
old: 3485.0
new: 1334.0

path: .metrics.halstead.volume
old: 29394.67937331399
new: 9928.886623462204

path: .metrics.halstead.time
old: 144258.7367224255
new: 35695.87312678521

path: .metrics.halstead.difficulty
old: 88.33766233766234
new: 64.7127659574468

path: .metrics.halstead.n2
old: 308.0
new: 141.0

path: .metrics.halstead.N1
old: 2053.0
new: 781.0

path: .metrics.halstead.N2
old: 1432.0
new: 553.0

path: .metrics.halstead.bugs
old: 6.297311336541657
new: 2.4820227329793325

path: .metrics.halstead.effort
old: 2596657.261003659
new: 642525.7162821338

path: .metrics.halstead.purity_ratio
old: 0.7878311334418612
new: 0.8794166016694595

path: .metrics.halstead.n1
old: 38.0
new: 33.0

path: .metrics.loc.lloc
old: 244.0
new: 106.0

path: .metrics.loc.cloc
old: 133.0
new: 86.0

path: .metrics.loc.ploc
old: 618.0
new: 250.0

path: .metrics.loc.sloc
old: 887.0
new: 381.0

path: .metrics.loc.blank
old: 136.0
new: 45.0

path: .metrics.nexits.sum
old: 32.0
new: 11.0

path: .metrics.nexits.average
old: 0.9142857142857144
new: 0.7857142857142857

path: .metrics.cognitive.average
old: 4.6
new: 2.642857142857143

path: .metrics.cognitive.sum
old: 161.0
new: 37.0

path: .metrics.cyclomatic.sum
old: 152.0
new: 52.0

path: .metrics.cyclomatic.average
old: 3.619047619047619
new: 4.0

path: .metrics.mi.mi_sei
old: -71.56072270358925
new: -15.32830288907909

path: .metrics.mi.mi_original
old: -27.4236473223269
new: 14.909991256090803

path: .metrics.mi.mi_visual_studio
old: 0.0
new: 8.719293132216844

path: .metrics.nom.total
old: 35.0
new: 14.0

path: .metrics.nom.functions
old: 35.0
new: 10.0

path: .metrics.nom.closures
old: 0.0
new: 4.0

Spaces Data

Minimal test - lines (16, 380)

path: .spaces[0].spaces[0].metrics.mi.mi_original
old: -26.473616939307604
new: 16.098179018225906

path: .spaces[0].spaces[0].metrics.mi.mi_sei
old: -70.63924346987328
new: -14.910614678451132

path: .spaces[0].spaces[0].metrics.mi.mi_visual_studio
old: 0.0
new: 9.414139776740296

path: .spaces[0].spaces[0].metrics.nargs.average
old: 2.2
new: 0.8571428571428571

path: .spaces[0].spaces[0].metrics.nargs.sum
old: 77.0
new: 12.0

path: .spaces[0].spaces[0].metrics.nexits.average
old: 0.9142857142857144
new: 0.7857142857142857

path: .spaces[0].spaces[0].metrics.nexits.sum
old: 32.0
new: 11.0

path: .spaces[0].spaces[0].metrics.cyclomatic.sum
old: 150.0
new: 50.0

path: .spaces[0].spaces[0].metrics.cyclomatic.average
old: 3.75
new: 4.545454545454546

path: .spaces[0].spaces[0].metrics.loc.lloc
old: 244.0
new: 106.0

path: .spaces[0].spaces[0].metrics.loc.ploc
old: 603.0
new: 246.0

path: .spaces[0].spaces[0].metrics.loc.blank
old: 134.0
new: 43.0

path: .spaces[0].spaces[0].metrics.loc.sloc
old: 864.0
new: 365.0

path: .spaces[0].spaces[0].metrics.loc.cloc
old: 127.0
new: 76.0

path: .spaces[0].spaces[0].metrics.cognitive.sum
old: 161.0
new: 37.0

path: .spaces[0].spaces[0].metrics.cognitive.average
old: 4.6
new: 2.642857142857143

path: .spaces[0].spaces[0].metrics.nom.closures
old: 0.0
new: 4.0

path: .spaces[0].spaces[0].metrics.nom.total
old: 35.0
new: 14.0

path: .spaces[0].spaces[0].metrics.nom.functions
old: 35.0
new: 10.0

path: .spaces[0].spaces[0].metrics.halstead.time
old: 146602.4003990225
new: 36043.32745231243

path: .spaces[0].spaces[0].metrics.halstead.vocabulary
old: 334.0
new: 171.0

path: .spaces[0].spaces[0].metrics.halstead.length
old: 3463.0
new: 1330.0

path: .spaces[0].spaces[0].metrics.halstead.volume
old: 29032.767964837643
new: 9865.743844798244

path: .spaces[0].spaces[0].metrics.halstead.effort
old: 2638843.207182405
new: 648779.8941416236

path: .spaces[0].spaces[0].metrics.halstead.N2
old: 1416.0
new: 550.0

path: .spaces[0].spaces[0].metrics.halstead.level
old: 0.011002081474873623
new: 0.015206611570247934

path: .spaces[0].spaces[0].metrics.halstead.n1
old: 38.0
new: 33.0

path: .spaces[0].spaces[0].metrics.halstead.purity_ratio
old: 0.7592894720580495
new: 0.8627378804317416

path: .spaces[0].spaces[0].metrics.halstead.bugs
old: 6.365333112534401
new: 2.498102957977555

path: .spaces[0].spaces[0].metrics.halstead.estimated_program_length
old: 2629.4194417370254
new: 1147.4413809742164

path: .spaces[0].spaces[0].metrics.halstead.n2
old: 296.0
new: 138.0

path: .spaces[0].spaces[0].metrics.halstead.N1
old: 2047.0
new: 780.0

path: .spaces[0].spaces[0].metrics.halstead.difficulty
old: 90.89189189189187
new: 65.76086956521739

Code

namespace widget {

// The values below have been tested and found to be acceptable on a device
// with a display refresh rate of 60Hz and touch sampling rate of 100Hz.
// While their "ideal" values are dependent on the exact rates of each device,
// the values we've picked below should be somewhat robust across a variation of
// different rates. They mostly aim to avoid making predictions that are too far
// away (in terms of distance) from the finger, and to detect pauses in the
// finger motion without too much delay.

// Maximum time between two consecutive data points to consider resampling
// between them.
// Values between 1x and 5x of the touch sampling interval are reasonable.
static const double kTouchResampleWindowSize = 40.0;

// These next two values constrain the sampling timestamp.
// Our caller will usually adjust frame timestamps to be slightly in the past,
// for example by 5ms. This means that, during normal operation, we will
// maximally need to predict by [touch sampling rate] minus 5ms.
// So we would like kTouchResampleMaxPredictMs to satisfy the following:
// kTouchResampleMaxPredictMs + [frame time adjust] > [touch sampling rate]
static const double kTouchResampleMaxPredictMs = 8.0;
// This one is a protection against very outdated frame timestamps.
// Values larger than the touch sampling interval and less than 3x of the vsync
// interval are reasonable.
static const double kTouchResampleMaxBacksampleMs = 20.0;

// The maximum age of the most recent data point to consider resampling.
// Should be between 1x and 3x of the touch sampling interval.
static const double kTouchResampleOldTouchThresholdMs = 17.0;

uint64_t TouchResampler::ProcessEvent(MultiTouchInput&& aInput) {
  mCurrentTouches.UpdateFromEvent(aInput);

  uint64_t eventId = mNextEventId;
  mNextEventId++;

  if (aInput.mType == MultiTouchInput::MULTITOUCH_MOVE) {
    // Touch move events are deferred until NotifyFrame.
    mDeferredTouchMoveEvents.push({std::move(aInput), eventId});
  } else {
    // Non-move events are transferred to the outgoing queue unmodified.
    // If there are pending touch move events, flush those out first, so that
    // events are emitted in the right order.
    FlushDeferredTouchMoveEventsUnresampled();
    if (mInResampledState) {
      // Return to a non-resampled state before emitting a non-move event.
      ReturnToNonResampledState();
    }
    EmitEvent(std::move(aInput), eventId);
  }

  return eventId;
}

void TouchResampler::NotifyFrame(const TimeStamp& aTimeStamp) {
  TimeStamp lastTouchTime = mCurrentTouches.LatestDataPointTime();
  if (mDeferredTouchMoveEvents.empty() ||
      (lastTouchTime &&
       lastTouchTime < aTimeStamp - TimeDuration::FromMilliseconds(
                                        kTouchResampleOldTouchThresholdMs))) {
    // We haven't received a touch move event in a while, so the fingers must
    // have stopped moving. Flush any old touch move events.
    FlushDeferredTouchMoveEventsUnresampled();

    if (mInResampledState) {
      // Make sure we pause at the resting position that we actually observed,
      // and not at a resampled position.
      ReturnToNonResampledState();
    }

    // Clear touch location history so that we don't resample across a pause.
    mCurrentTouches.ClearDataPoints();
    return;
  }

  MOZ_RELEASE_ASSERT(lastTouchTime);
  TimeStamp lowerBound = lastTouchTime - TimeDuration::FromMilliseconds(
                                             kTouchResampleMaxBacksampleMs);
  TimeStamp upperBound = lastTouchTime + TimeDuration::FromMilliseconds(
                                             kTouchResampleMaxPredictMs);
  TimeStamp sampleTime = clamped(aTimeStamp, lowerBound, upperBound);

  if (mLastEmittedEventTime && sampleTime < mLastEmittedEventTime) {
    // Keep emitted timestamps in order.
    sampleTime = mLastEmittedEventTime;
  }

  // We have at least one pending touch move event. Pick one of the events from
  // mDeferredTouchMoveEvents as the base event for the resampling adjustment.
  // We want to produce an event stream whose timestamps are in the right order.
  // As the base event, use the first event that's at or after sampleTime,
  // unless there is no such event, in that case use the last one we have. We
  // will set the timestamp on the resampled event to sampleTime later.
  // Flush out any older events so that everything remains in the right order.
  MultiTouchInput input;
  uint64_t eventId;
  while (true) {
    MOZ_RELEASE_ASSERT(!mDeferredTouchMoveEvents.empty());

    std::tie(input, eventId) = std::move(mDeferredTouchMoveEvents.front());
    mDeferredTouchMoveEvents.pop();
    if (mDeferredTouchMoveEvents.empty() || input.mTimeStamp >= sampleTime) {
      break;
    }

    // Flush this event to the outgoing queue without resampling. What ends up
    // on the screen will still be smooth because we will proceed to emit a
    // resampled event before the paint for this frame starts.
    PrependLeftoverHistoricalData(&input);
    MOZ_RELEASE_ASSERT(input.mTimeStamp < sampleTime);
    EmitEvent(std::move(input), eventId);
  }

  mOriginalOfResampledTouchMove = Nothing();

  // Compute the resampled touch positions.
  nsTArray resampledPositions;
  bool anyPositionDifferentFromOriginal = false;
  for (const auto& touch : input.mTouches) {
    ScreenIntPoint resampledPosition =
        mCurrentTouches.ResampleTouchPositionAtTime(
            touch.mIdentifier, touch.mScreenPoint, sampleTime);
    if (resampledPosition != touch.mScreenPoint) {
      anyPositionDifferentFromOriginal = true;
    }
    resampledPositions.AppendElement(resampledPosition);
  }

  if (anyPositionDifferentFromOriginal) {
    // Store a copy of the original event, so that we can return to an
    // non-resampled position later, if necessary.
    mOriginalOfResampledTouchMove = Some(input);

    // Add the original observed position to the historical data, as well as any
    // leftover historical positions from the previous touch move event, and
    // store the resampled values in the "final" position of the event.
    PrependLeftoverHistoricalData(&input);
    for (size_t i = 0; i < input.mTouches.Length(); i++) {
      auto& touch = input.mTouches[i];
      touch.mHistoricalData.AppendElement(SingleTouchData::HistoricalTouchData{
          input.mTimeStamp,
          touch.mScreenPoint,
          touch.mLocalScreenPoint,
          touch.mRadius,
          touch.mRotationAngle,
          touch.mForce,
      });

      // Remove any historical touch data that's in the future, compared to
      // sampleTime. This data will be included by upcoming touch move
      // events. This only happens if the frame timestamp can be older than the
      // event timestamp, i.e. if interpolation occurs (rather than
      // extrapolation).
      auto futureDataStart = std::find_if(
          touch.mHistoricalData.begin(), touch.mHistoricalData.end(),
          [sampleTime](
              const SingleTouchData::HistoricalTouchData& aHistoricalData) {
            return aHistoricalData.mTimeStamp > sampleTime;
          });
      if (futureDataStart != touch.mHistoricalData.end()) {
        nsTArray futureData(
            Span(touch.mHistoricalData)
                .From(futureDataStart.GetIndex()));
        touch.mHistoricalData.TruncateLength(futureDataStart.GetIndex());
        mRemainingTouchData.insert({touch.mIdentifier, std::move(futureData)});
      }

      touch.mScreenPoint = resampledPositions[i];
    }
    input.mTimeStamp = sampleTime;
  }

  EmitEvent(std::move(input), eventId);
  mInResampledState = anyPositionDifferentFromOriginal;
}

void TouchResampler::PrependLeftoverHistoricalData(MultiTouchInput* aInput) {
  for (auto& touch : aInput->mTouches) {
    auto leftoverData = mRemainingTouchData.find(touch.mIdentifier);
    if (leftoverData != mRemainingTouchData.end()) {
      nsTArray data =
          std::move(leftoverData->second);
      mRemainingTouchData.erase(leftoverData);
      touch.mHistoricalData.InsertElementsAt(0, data);
    }

    if (TimeStamp cutoffTime = mLastEmittedEventTime) {
      // If we received historical touch data that was further in the past than
      // the last resampled event, discard that data so that the touch data
      // points are emitted in order.
      touch.mHistoricalData.RemoveElementsBy(
          [cutoffTime](const SingleTouchData::HistoricalTouchData& aTouchData) {
            return aTouchData.mTimeStamp < cutoffTime;
          });
    }
  }
  mRemainingTouchData.clear();
}

void TouchResampler::FlushDeferredTouchMoveEventsUnresampled() {
  while (!mDeferredTouchMoveEvents.empty()) {
    MultiTouchInput input;
    uint64_t eventId;
    std::tie(input, eventId) = std::move(mDeferredTouchMoveEvents.front());
    mDeferredTouchMoveEvents.pop();
    PrependLeftoverHistoricalData(&input);
    EmitEvent(std::move(input), eventId);
    mInResampledState = false;
    mOriginalOfResampledTouchMove = Nothing();
  }
}

void TouchResampler::ReturnToNonResampledState() {
  MOZ_RELEASE_ASSERT(mInResampledState);
  MOZ_RELEASE_ASSERT(mDeferredTouchMoveEvents.empty(),
                     "Don't call this if there is a deferred touch move event. "
                     "We can return to the non-resampled state by sending that "
                     "event, rather than a copy of a previous event.");

  // The last outgoing event was a resampled touch move event.
  // Return to the non-resampled state, by sending a touch move event to
  // "overwrite" any resampled positions with the original observed positions.
  MultiTouchInput input = std::move(*mOriginalOfResampledTouchMove);
  mOriginalOfResampledTouchMove = Nothing();

  // For the event's timestamp, we want to backdate the correction as far as we
  // can, while still preserving timestamp ordering. But we also don't want to
  // backdate it to be older than it was originally.
  if (mLastEmittedEventTime > input.mTimeStamp) {
    input.mTimeStamp = mLastEmittedEventTime;
  }

  // Assemble the correct historical touch data for this event.
  // We don't want to include data points that we've already sent out with the
  // resampled event. And from the leftover data points, we only want those that
  // don't duplicate the final time + position of this event.
  for (auto& touch : input.mTouches) {
    touch.mHistoricalData.Clear();
  }
  PrependLeftoverHistoricalData(&input);
  for (auto& touch : input.mTouches) {
    touch.mHistoricalData.RemoveElementsBy([&](const auto& histData) {
      return histData.mTimeStamp >= input.mTimeStamp;
    });
  }

  EmitExtraEvent(std::move(input));
  mInResampledState = false;
}

void TouchResampler::TouchInfo::Update(const SingleTouchData& aTouch,
                                       const TimeStamp& aEventTime) {
  for (const auto& historicalData : aTouch.mHistoricalData) {
    mBaseDataPoint = mLatestDataPoint;
    mLatestDataPoint =
        Some(DataPoint{historicalData.mTimeStamp, historicalData.mScreenPoint});
  }
  mBaseDataPoint = mLatestDataPoint;
  mLatestDataPoint = Some(DataPoint{aEventTime, aTouch.mScreenPoint});
}

ScreenIntPoint TouchResampler::TouchInfo::ResampleAtTime(
    const ScreenIntPoint& aLastObservedPosition, const TimeStamp& aTimeStamp) {
  TimeStamp cutoff =
      aTimeStamp - TimeDuration::FromMilliseconds(kTouchResampleWindowSize);
  if (!mBaseDataPoint || !mLatestDataPoint ||
      !(mBaseDataPoint->mTimeStamp < mLatestDataPoint->mTimeStamp) ||
      mBaseDataPoint->mTimeStamp < cutoff) {
    return aLastObservedPosition;
  }

  // For the actual resampling, connect the last two data points with a line and
  // sample along that line.
  TimeStamp t1 = mBaseDataPoint->mTimeStamp;
  TimeStamp t2 = mLatestDataPoint->mTimeStamp;
  double t = (aTimeStamp - t1) / (t2 - t1);

  double x1 = mBaseDataPoint->mPosition.x;
  double x2 = mLatestDataPoint->mPosition.x;
  double y1 = mBaseDataPoint->mPosition.y;
  double y2 = mLatestDataPoint->mPosition.y;

  int32_t resampledX = round(x1 + t * (x2 - x1));
  int32_t resampledY = round(y1 + t * (y2 - y1));
  return ScreenIntPoint(resampledX, resampledY);
}

void TouchResampler::CurrentTouches::UpdateFromEvent(
    const MultiTouchInput& aInput) {
  switch (aInput.mType) {
    case MultiTouchInput::MULTITOUCH_START: {
      // A new touch has been added; make sure mTouches reflects the current
      // touches in the event.
      nsTArray newTouches;
      for (const auto& touch : aInput.mTouches) {
        const auto touchInfo = TouchByIdentifier(touch.mIdentifier);
        if (touchInfo != mTouches.end()) {
          // This is one of the existing touches.
          newTouches.AppendElement(std::move(*touchInfo));
          mTouches.RemoveElementAt(touchInfo);
        } else {
          // This is the new touch.
          newTouches.AppendElement(TouchInfo{
              touch.mIdentifier, Nothing(),
              Some(DataPoint{aInput.mTimeStamp, touch.mScreenPoint})});
        }
      }
      MOZ_ASSERT(mTouches.IsEmpty(), "Missing touch end before touch start?");
      mTouches = std::move(newTouches);
      break;
    }

    case MultiTouchInput::MULTITOUCH_MOVE: {
      // The touches have moved.
      // Add position information to the history data points.
      for (const auto& touch : aInput.mTouches) {
        const auto touchInfo = TouchByIdentifier(touch.mIdentifier);
        MOZ_ASSERT(touchInfo != mTouches.end());
        if (touchInfo != mTouches.end()) {
          touchInfo->Update(touch, aInput.mTimeStamp);
        }
      }
      mLatestDataPointTime = aInput.mTimeStamp;
      break;
    }

    case MultiTouchInput::MULTITOUCH_END: {
      // A touch has been removed.
      MOZ_RELEASE_ASSERT(aInput.mTouches.Length() == 1);
      const auto touchInfo = TouchByIdentifier(aInput.mTouches[0].mIdentifier);
      MOZ_ASSERT(touchInfo != mTouches.end());
      if (touchInfo != mTouches.end()) {
        mTouches.RemoveElementAt(touchInfo);
      }
      break;
    }

    case MultiTouchInput::MULTITOUCH_CANCEL:
      // All touches are canceled.
      mTouches.Clear();
      break;
  }
}

nsTArray::iterator
TouchResampler::CurrentTouches::TouchByIdentifier(int32_t aIdentifier) {
  return std::find_if(mTouches.begin(), mTouches.end(),
                      [aIdentifier](const TouchInfo& info) {
                        return info.mIdentifier == aIdentifier;
                      });
}

ScreenIntPoint TouchResampler::CurrentTouches::ResampleTouchPositionAtTime(
    int32_t aIdentifier, const ScreenIntPoint& aLastObservedPosition,
    const TimeStamp& aTimeStamp) {
  const auto touchInfo = TouchByIdentifier(aIdentifier);
  MOZ_ASSERT(touchInfo != mTouches.end());
  if (touchInfo != mTouches.end()) {
    return touchInfo->ResampleAtTime(aLastObservedPosition, aTimeStamp);
  }
  return aLastObservedPosition;
}

}  // namespace widget

Minimal test - lines (193, 214)

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

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

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

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

path: .spaces[0].spaces[0].spaces[2].metrics.loc.cloc
old: 0.0
new: 3.0

path: .spaces[0].spaces[0].spaces[2].metrics.loc.sloc
old: 7.0
new: 22.0

path: .spaces[0].spaces[0].spaces[2].metrics.loc.ploc
old: 7.0
new: 18.0

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

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

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

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

path: .spaces[0].spaces[0].spaces[2].metrics.nom.closures
old: 0.0
new: 1.0

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

path: .spaces[0].spaces[0].spaces[2].metrics.halstead.estimated_program_length
old: 59.71535681027101
new: 202.9220554270965

path: .spaces[0].spaces[0].spaces[2].metrics.halstead.effort
old: 291.89475010096186
new: 8427.882251675155

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

path: .spaces[0].spaces[0].spaces[2].metrics.halstead.purity_ratio
old: 2.132691314652536
new: 2.029220554270965

path: .spaces[0].spaces[0].spaces[2].metrics.halstead.time
old: 16.21637500560899
new: 468.21568064861975

path: .spaces[0].spaces[0].spaces[2].metrics.halstead.N1
old: 15.0
new: 58.0

path: .spaces[0].spaces[0].spaces[2].metrics.halstead.level
old: 0.4
new: 0.06516290726817042

path: .spaces[0].spaces[0].spaces[2].metrics.halstead.bugs
old: 0.01466773093381915
new: 0.1380461772802505

path: .spaces[0].spaces[0].spaces[2].metrics.halstead.volume
old: 116.75790004038474
new: 549.1853096329675

path: .spaces[0].spaces[0].spaces[2].metrics.halstead.length
old: 28.0
new: 100.0

path: .spaces[0].spaces[0].spaces[2].metrics.halstead.difficulty
old: 2.5
new: 15.346153846153848

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

path: .spaces[0].spaces[0].spaces[2].metrics.halstead.n1
old: 5.0
new: 19.0

path: .spaces[0].spaces[0].spaces[2].metrics.halstead.N2
old: 13.0
new: 42.0

path: .spaces[0].spaces[0].spaces[2].metrics.mi.mi_visual_studio
old: 66.9553931405845
new: 50.99488037793438

path: .spaces[0].spaces[0].spaces[2].metrics.mi.mi_sei
old: 89.58049319981535
new: 77.58023067257346

path: .spaces[0].spaces[0].spaces[2].metrics.mi.mi_original
old: 114.4937222703995
new: 87.20124544626779

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

path: .spaces[0].spaces[0].spaces[2].metrics.cognitive.sum
old: 0.0
new: 2.0

Code

void TouchResampler::PrependLeftoverHistoricalData(MultiTouchInput* aInput) {
  for (auto& touch : aInput->mTouches) {
    auto leftoverData = mRemainingTouchData.find(touch.mIdentifier);
    if (leftoverData != mRemainingTouchData.end()) {
      nsTArray data =
          std::move(leftoverData->second);
      mRemainingTouchData.erase(leftoverData);
      touch.mHistoricalData.InsertElementsAt(0, data);
    }

    if (TimeStamp cutoffTime = mLastEmittedEventTime) {
      // If we received historical touch data that was further in the past than
      // the last resampled event, discard that data so that the touch data
      // points are emitted in order.
      touch.mHistoricalData.RemoveElementsBy(
          [cutoffTime](const SingleTouchData::HistoricalTouchData& aTouchData) {
            return aTouchData.mTimeStamp < cutoffTime;
          });
    }
  }
  mRemainingTouchData.clear();
}

Minimal test - lines (304, 359)

path: .spaces[0].spaces[0].spaces[7].metrics.cognitive.average
old: 5.0
new: 8.0

path: .spaces[0].spaces[0].spaces[7].metrics.cognitive.sum
old: 5.0
new: 8.0

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

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

path: .spaces[0].spaces[0].spaces[7].metrics.cyclomatic.sum
old: 6.0
new: 10.0

path: .spaces[0].spaces[0].spaces[7].metrics.cyclomatic.average
old: 6.0
new: 10.0

path: .spaces[0].spaces[0].spaces[7].metrics.loc.lloc
old: 7.0
new: 24.0

path: .spaces[0].spaces[0].spaces[7].metrics.loc.cloc
old: 0.0
new: 8.0

path: .spaces[0].spaces[0].spaces[7].metrics.loc.sloc
old: 23.0
new: 56.0

path: .spaces[0].spaces[0].spaces[7].metrics.loc.ploc
old: 20.0
new: 45.0

path: .spaces[0].spaces[0].spaces[7].metrics.nargs.sum
old: 6.0
new: 1.0

path: .spaces[0].spaces[0].spaces[7].metrics.nargs.average
old: 6.0
new: 1.0

path: .spaces[0].spaces[0].spaces[7].metrics.mi.mi_sei
old: 46.9360149436372
new: 48.00148527986839

path: .spaces[0].spaces[0].spaces[7].metrics.mi.mi_visual_studio
old: 49.46312377679747
new: 38.529117662237184

path: .spaces[0].spaces[0].spaces[7].metrics.mi.mi_original
old: 84.58194165832367
new: 65.88479120242559

path: .spaces[0].spaces[0].spaces[7].metrics.halstead.level
old: 0.057416267942583726
new: 0.03272557269752221

path: .spaces[0].spaces[0].spaces[7].metrics.halstead.estimated_program_length
old: 227.91734062368369
new: 283.56683058238514

path: .spaces[0].spaces[0].spaces[7].metrics.halstead.purity_ratio
old: 1.7668010901060751
new: 1.2015543668745132

path: .spaces[0].spaces[0].spaces[7].metrics.halstead.vocabulary
old: 49.0
new: 58.0

path: .spaces[0].spaces[0].spaces[7].metrics.halstead.difficulty
old: 17.416666666666668
new: 30.557142857142857

path: .spaces[0].spaces[0].spaces[7].metrics.halstead.volume
old: 724.2975698908618
new: 1382.483514850107

path: .spaces[0].spaces[0].spaces[7].metrics.halstead.length
old: 129.0
new: 236.0

path: .spaces[0].spaces[0].spaces[7].metrics.halstead.bugs
old: 0.1806342599124885
new: 0.4043218538754398

path: .spaces[0].spaces[0].spaces[7].metrics.halstead.n1
old: 19.0
new: 23.0

path: .spaces[0].spaces[0].spaces[7].metrics.halstead.N1
old: 74.0
new: 143.0

path: .spaces[0].spaces[0].spaces[7].metrics.halstead.time
old: 700.8249634592136
new: 2346.930347828872

path: .spaces[0].spaces[0].spaces[7].metrics.halstead.N2
old: 55.0
new: 93.0

path: .spaces[0].spaces[0].spaces[7].metrics.halstead.effort
old: 12614.849342265845
new: 42244.746260919695

path: .spaces[0].spaces[0].spaces[7].metrics.halstead.n2
old: 30.0
new: 35.0

Code

void TouchResampler::CurrentTouches::UpdateFromEvent(
    const MultiTouchInput& aInput) {
  switch (aInput.mType) {
    case MultiTouchInput::MULTITOUCH_START: {
      // A new touch has been added; make sure mTouches reflects the current
      // touches in the event.
      nsTArray newTouches;
      for (const auto& touch : aInput.mTouches) {
        const auto touchInfo = TouchByIdentifier(touch.mIdentifier);
        if (touchInfo != mTouches.end()) {
          // This is one of the existing touches.
          newTouches.AppendElement(std::move(*touchInfo));
          mTouches.RemoveElementAt(touchInfo);
        } else {
          // This is the new touch.
          newTouches.AppendElement(TouchInfo{
              touch.mIdentifier, Nothing(),
              Some(DataPoint{aInput.mTimeStamp, touch.mScreenPoint})});
        }
      }
      MOZ_ASSERT(mTouches.IsEmpty(), "Missing touch end before touch start?");
      mTouches = std::move(newTouches);
      break;
    }

    case MultiTouchInput::MULTITOUCH_MOVE: {
      // The touches have moved.
      // Add position information to the history data points.
      for (const auto& touch : aInput.mTouches) {
        const auto touchInfo = TouchByIdentifier(touch.mIdentifier);
        MOZ_ASSERT(touchInfo != mTouches.end());
        if (touchInfo != mTouches.end()) {
          touchInfo->Update(touch, aInput.mTimeStamp);
        }
      }
      mLatestDataPointTime = aInput.mTimeStamp;
      break;
    }

    case MultiTouchInput::MULTITOUCH_END: {
      // A touch has been removed.
      MOZ_RELEASE_ASSERT(aInput.mTouches.Length() == 1);
      const auto touchInfo = TouchByIdentifier(aInput.mTouches[0].mIdentifier);
      MOZ_ASSERT(touchInfo != mTouches.end());
      if (touchInfo != mTouches.end()) {
        mTouches.RemoveElementAt(touchInfo);
      }
      break;
    }

    case MultiTouchInput::MULTITOUCH_CANCEL:
      // All touches are canceled.
      mTouches.Clear();
      break;
  }
}

Minimal test - lines (369, 378)

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

path: .spaces[0].spaces[0].spaces[9].metrics.nargs.average
old: 2.0
new: 3.0

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

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

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

path: .spaces[0].spaces[0].spaces[9].metrics.loc.ploc
old: 7.0
new: 10.0

path: .spaces[0].spaces[0].spaces[9].metrics.loc.sloc
old: 9.0
new: 10.0

path: .spaces[0].spaces[0].spaces[9].metrics.loc.lloc
old: 2.0
new: 4.0

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

path: .spaces[0].spaces[0].spaces[9].metrics.mi.mi_sei
old: 103.133039588922
new: 75.56279719065977

path: .spaces[0].spaces[0].spaces[9].metrics.mi.mi_original
old: 106.70495514208774
new: 104.70681965523568

path: .spaces[0].spaces[0].spaces[9].metrics.mi.mi_visual_studio
old: 62.40055856262441
new: 61.232058277915606

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

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

path: .spaces[0].spaces[0].spaces[9].metrics.halstead.vocabulary
old: 29.0
new: 25.0

path: .spaces[0].spaces[0].spaces[9].metrics.halstead.purity_ratio
old: 2.380985698785835
new: 1.752408968163232

path: .spaces[0].spaces[0].spaces[9].metrics.halstead.volume
old: 228.3251067709959
new: 241.48052186828568

path: .spaces[0].spaces[0].spaces[9].metrics.halstead.N1
old: 26.0
new: 30.0

path: .spaces[0].spaces[0].spaces[9].metrics.halstead.level
old: 0.1020408163265306
new: 0.08391608391608392

path: .spaces[0].spaces[0].spaces[9].metrics.halstead.effort
old: 2237.5860463557597
new: 2877.6428855970707

path: .spaces[0].spaces[0].spaces[9].metrics.halstead.length
old: 47.0
new: 52.0

path: .spaces[0].spaces[0].spaces[9].metrics.halstead.n1
old: 14.0
new: 13.0

path: .spaces[0].spaces[0].spaces[9].metrics.halstead.estimated_program_length
old: 111.90632784293425
new: 91.12526634448808

path: .spaces[0].spaces[0].spaces[9].metrics.halstead.bugs
old: 0.057024993180707015
new: 0.06743779213953927

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

path: .spaces[0].spaces[0].spaces[9].metrics.halstead.N2
old: 21.0
new: 22.0

path: .spaces[0].spaces[0].spaces[9].metrics.halstead.difficulty
old: 9.8
new: 11.916666666666666

path: .spaces[0].spaces[0].spaces[9].metrics.halstead.time
old: 124.31033590865331
new: 159.86904919983726

Code

ScreenIntPoint TouchResampler::CurrentTouches::ResampleTouchPositionAtTime(
    int32_t aIdentifier, const ScreenIntPoint& aLastObservedPosition,
    const TimeStamp& aTimeStamp) {
  const auto touchInfo = TouchByIdentifier(aIdentifier);
  MOZ_ASSERT(touchInfo != mTouches.end());
  if (touchInfo != mTouches.end()) {
    return touchInfo->ResampleAtTime(aLastObservedPosition, aTimeStamp);
  }
  return aLastObservedPosition;
}

Minimal test - lines (229, 265)

path: .spaces[0].spaces[0].spaces[4].metrics.mi.mi_visual_studio
old: 65.81687303745743
new: 45.827681614794784

path: .spaces[0].spaces[0].spaces[4].metrics.mi.mi_sei
old: 86.77175440530047
new: 73.8186588141208

path: .spaces[0].spaces[0].spaces[4].metrics.mi.mi_original
old: 112.5468528940522
new: 78.36533556129908

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

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

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

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

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

path: .spaces[0].spaces[0].spaces[4].metrics.loc.ploc
old: 6.0
new: 23.0

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

path: .spaces[0].spaces[0].spaces[4].metrics.loc.lloc
old: 3.0
new: 11.0

path: .spaces[0].spaces[0].spaces[4].metrics.loc.sloc
old: 8.0
new: 37.0

path: .spaces[0].spaces[0].spaces[4].metrics.nom.closures
old: 0.0
new: 1.0

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

path: .spaces[0].spaces[0].spaces[4].metrics.halstead.estimated_program_length
old: 48.18080946738404
new: 179.5259683185635

path: .spaces[0].spaces[0].spaces[4].metrics.halstead.difficulty
old: 7.714285714285714
new: 15.9375

path: .spaces[0].spaces[0].spaces[4].metrics.halstead.vocabulary
old: 16.0
new: 41.0

path: .spaces[0].spaces[0].spaces[4].metrics.halstead.N2
old: 12.0
new: 45.0

path: .spaces[0].spaces[0].spaces[4].metrics.halstead.volume
old: 112.0
new: 594.6882725126073

path: .spaces[0].spaces[0].spaces[4].metrics.halstead.effort
old: 864.0
new: 9477.84434316968

path: .spaces[0].spaces[0].spaces[4].metrics.halstead.purity_ratio
old: 1.7207431952637156
new: 1.6173510659330046

path: .spaces[0].spaces[0].spaces[4].metrics.halstead.N1
old: 16.0
new: 66.0

path: .spaces[0].spaces[0].spaces[4].metrics.halstead.bugs
old: 0.03023810519747695
new: 0.14928578628259745

path: .spaces[0].spaces[0].spaces[4].metrics.halstead.length
old: 28.0
new: 111.0

path: .spaces[0].spaces[0].spaces[4].metrics.halstead.n2
old: 7.0
new: 24.0

path: .spaces[0].spaces[0].spaces[4].metrics.halstead.level
old: 0.12962962962962962
new: 0.06274509803921569

path: .spaces[0].spaces[0].spaces[4].metrics.halstead.time
old: 48.0
new: 526.5469079538711

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

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

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

path: .spaces[0].spaces[0].spaces[4].metrics.cognitive.average
old: 0.0
new: 0.5

Code

void TouchResampler::ReturnToNonResampledState() {
  MOZ_RELEASE_ASSERT(mInResampledState);
  MOZ_RELEASE_ASSERT(mDeferredTouchMoveEvents.empty(),
                     "Don't call this if there is a deferred touch move event. "
                     "We can return to the non-resampled state by sending that "
                     "event, rather than a copy of a previous event.");

  // The last outgoing event was a resampled touch move event.
  // Return to the non-resampled state, by sending a touch move event to
  // "overwrite" any resampled positions with the original observed positions.
  MultiTouchInput input = std::move(*mOriginalOfResampledTouchMove);
  mOriginalOfResampledTouchMove = Nothing();

  // For the event's timestamp, we want to backdate the correction as far as we
  // can, while still preserving timestamp ordering. But we also don't want to
  // backdate it to be older than it was originally.
  if (mLastEmittedEventTime > input.mTimeStamp) {
    input.mTimeStamp = mLastEmittedEventTime;
  }

  // Assemble the correct historical touch data for this event.
  // We don't want to include data points that we've already sent out with the
  // resampled event. And from the leftover data points, we only want those that
  // don't duplicate the final time + position of this event.
  for (auto& touch : input.mTouches) {
    touch.mHistoricalData.Clear();
  }
  PrependLeftoverHistoricalData(&input);
  for (auto& touch : input.mTouches) {
    touch.mHistoricalData.RemoveElementsBy([&](const auto& histData) {
      return histData.mTimeStamp >= input.mTimeStamp;
    });
  }

  EmitExtraEvent(std::move(input));
  mInResampledState = false;
}

Minimal test - lines (216, 227)

path: .spaces[0].spaces[0].spaces[3].metrics.mi.mi_original
old: 105.04724328551264
new: 100.71571367949426

path: .spaces[0].spaces[0].spaces[3].metrics.mi.mi_sei
old: 99.58071679280668
new: 69.8048483917953

path: .spaces[0].spaces[0].spaces[3].metrics.mi.mi_visual_studio
old: 61.4311364242764
new: 58.89807817514284

path: .spaces[0].spaces[0].spaces[3].metrics.halstead.bugs
old: 0.05599031818965022
new: 0.06028408425377481

path: .spaces[0].spaces[0].spaces[3].metrics.halstead.difficulty
old: 9.625
new: 8.25

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

path: .spaces[0].spaces[0].spaces[3].metrics.halstead.N2
old: 21.0
new: 24.0

path: .spaces[0].spaces[0].spaces[3].metrics.halstead.vocabulary
old: 23.0
new: 27.0

path: .spaces[0].spaces[0].spaces[3].metrics.halstead.estimated_program_length
old: 81.07329781366414
new: 102.05374780501026

path: .spaces[0].spaces[0].spaces[3].metrics.halstead.length
old: 50.0
new: 62.0

path: .spaces[0].spaces[0].spaces[3].metrics.halstead.volume
old: 226.17809780285063
new: 294.80302513413505

path: .spaces[0].spaces[0].spaces[3].metrics.halstead.level
old: 0.1038961038961039
new: 0.12121212121212122

path: .spaces[0].spaces[0].spaces[3].metrics.halstead.purity_ratio
old: 1.6214659562732827
new: 1.646028190403391

path: .spaces[0].spaces[0].spaces[3].metrics.halstead.effort
old: 2176.9641913524374
new: 2432.1249573566142

path: .spaces[0].spaces[0].spaces[3].metrics.halstead.time
old: 120.9424550751354
new: 135.11805318647856

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

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

path: .spaces[0].spaces[0].spaces[3].metrics.loc.sloc
old: 10.0
new: 12.0

path: .spaces[0].spaces[0].spaces[3].metrics.loc.ploc
old: 9.0
new: 12.0

path: .spaces[0].spaces[0].spaces[3].metrics.loc.lloc
old: 5.0
new: 7.0

Code

void TouchResampler::FlushDeferredTouchMoveEventsUnresampled() {
  while (!mDeferredTouchMoveEvents.empty()) {
    MultiTouchInput input;
    uint64_t eventId;
    std::tie(input, eventId) = std::move(mDeferredTouchMoveEvents.front());
    mDeferredTouchMoveEvents.pop();
    PrependLeftoverHistoricalData(&input);
    EmitEvent(std::move(input), eventId);
    mInResampledState = false;
    mOriginalOfResampledTouchMove = Nothing();
  }
}

Minimal test - lines (71, 191)

path: .spaces[0].spaces[0].spaces[1].metrics.cognitive.average
old: 1.0
new: 9.0

path: .spaces[0].spaces[0].spaces[1].metrics.cognitive.sum
old: 1.0
new: 18.0

path: .spaces[0].spaces[0].spaces[1].metrics.halstead.volume
old: 243.00301253822127
new: 2844.7474784073006

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

path: .spaces[0].spaces[0].spaces[1].metrics.halstead.n1
old: 14.0
new: 26.0

path: .spaces[0].spaces[0].spaces[1].metrics.halstead.n2
old: 10.0
new: 69.0

path: .spaces[0].spaces[0].spaces[1].metrics.halstead.N1
old: 31.0
new: 249.0

path: .spaces[0].spaces[0].spaces[1].metrics.halstead.vocabulary
old: 24.0
new: 95.0

path: .spaces[0].spaces[0].spaces[1].metrics.halstead.N2
old: 22.0
new: 184.0

path: .spaces[0].spaces[0].spaces[1].metrics.halstead.bugs
old: 0.08034650638737752
new: 0.7115126335112582

path: .spaces[0].spaces[0].spaces[1].metrics.halstead.estimated_program_length
old: 86.52224985768008
new: 543.6996201893621

path: .spaces[0].spaces[0].spaces[1].metrics.halstead.level
old: 0.06493506493506493
new: 0.028846153846153848

path: .spaces[0].spaces[0].spaces[1].metrics.halstead.purity_ratio
old: 1.6324952803335864
new: 1.2556573214534923

path: .spaces[0].spaces[0].spaces[1].metrics.halstead.time
old: 207.90257739381155
new: 5478.7729213770235

path: .spaces[0].spaces[0].spaces[1].metrics.halstead.difficulty
old: 15.4
new: 34.666666666666664

path: .spaces[0].spaces[0].spaces[1].metrics.halstead.effort
old: 3742.246393088608
new: 98617.91258478642

path: .spaces[0].spaces[0].spaces[1].metrics.nom.closures
old: 0.0
new: 1.0

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

path: .spaces[0].spaces[0].spaces[1].metrics.mi.mi_sei
old: 75.51564684010567
new: 29.20981880895701

path: .spaces[0].spaces[0].spaces[1].metrics.mi.mi_visual_studio
old: 61.21294591969983
new: 28.36339142139961

path: .spaces[0].spaces[0].spaces[1].metrics.mi.mi_original
old: 104.6741375226867
new: 48.50139933059333

path: .spaces[0].spaces[0].spaces[1].metrics.nargs.average
old: 1.0
new: 0.5

path: .spaces[0].spaces[0].spaces[1].metrics.cyclomatic.average
old: 2.0
new: 15.0

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

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

path: .spaces[0].spaces[0].spaces[1].metrics.loc.ploc
old: 8.0
new: 80.0

path: .spaces[0].spaces[0].spaces[1].metrics.loc.sloc
old: 10.0
new: 121.0

path: .spaces[0].spaces[0].spaces[1].metrics.loc.cloc
old: 0.0
new: 27.0

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

path: .spaces[0].spaces[0].spaces[1].metrics.loc.lloc
old: 4.0
new: 35.0

Code

void TouchResampler::NotifyFrame(const TimeStamp& aTimeStamp) {
  TimeStamp lastTouchTime = mCurrentTouches.LatestDataPointTime();
  if (mDeferredTouchMoveEvents.empty() ||
      (lastTouchTime &&
       lastTouchTime < aTimeStamp - TimeDuration::FromMilliseconds(
                                        kTouchResampleOldTouchThresholdMs))) {
    // We haven't received a touch move event in a while, so the fingers must
    // have stopped moving. Flush any old touch move events.
    FlushDeferredTouchMoveEventsUnresampled();

    if (mInResampledState) {
      // Make sure we pause at the resting position that we actually observed,
      // and not at a resampled position.
      ReturnToNonResampledState();
    }

    // Clear touch location history so that we don't resample across a pause.
    mCurrentTouches.ClearDataPoints();
    return;
  }

  MOZ_RELEASE_ASSERT(lastTouchTime);
  TimeStamp lowerBound = lastTouchTime - TimeDuration::FromMilliseconds(
                                             kTouchResampleMaxBacksampleMs);
  TimeStamp upperBound = lastTouchTime + TimeDuration::FromMilliseconds(
                                             kTouchResampleMaxPredictMs);
  TimeStamp sampleTime = clamped(aTimeStamp, lowerBound, upperBound);

  if (mLastEmittedEventTime && sampleTime < mLastEmittedEventTime) {
    // Keep emitted timestamps in order.
    sampleTime = mLastEmittedEventTime;
  }

  // We have at least one pending touch move event. Pick one of the events from
  // mDeferredTouchMoveEvents as the base event for the resampling adjustment.
  // We want to produce an event stream whose timestamps are in the right order.
  // As the base event, use the first event that's at or after sampleTime,
  // unless there is no such event, in that case use the last one we have. We
  // will set the timestamp on the resampled event to sampleTime later.
  // Flush out any older events so that everything remains in the right order.
  MultiTouchInput input;
  uint64_t eventId;
  while (true) {
    MOZ_RELEASE_ASSERT(!mDeferredTouchMoveEvents.empty());

    std::tie(input, eventId) = std::move(mDeferredTouchMoveEvents.front());
    mDeferredTouchMoveEvents.pop();
    if (mDeferredTouchMoveEvents.empty() || input.mTimeStamp >= sampleTime) {
      break;
    }

    // Flush this event to the outgoing queue without resampling. What ends up
    // on the screen will still be smooth because we will proceed to emit a
    // resampled event before the paint for this frame starts.
    PrependLeftoverHistoricalData(&input);
    MOZ_RELEASE_ASSERT(input.mTimeStamp < sampleTime);
    EmitEvent(std::move(input), eventId);
  }

  mOriginalOfResampledTouchMove = Nothing();

  // Compute the resampled touch positions.
  nsTArray resampledPositions;
  bool anyPositionDifferentFromOriginal = false;
  for (const auto& touch : input.mTouches) {
    ScreenIntPoint resampledPosition =
        mCurrentTouches.ResampleTouchPositionAtTime(
            touch.mIdentifier, touch.mScreenPoint, sampleTime);
    if (resampledPosition != touch.mScreenPoint) {
      anyPositionDifferentFromOriginal = true;
    }
    resampledPositions.AppendElement(resampledPosition);
  }

  if (anyPositionDifferentFromOriginal) {
    // Store a copy of the original event, so that we can return to an
    // non-resampled position later, if necessary.
    mOriginalOfResampledTouchMove = Some(input);

    // Add the original observed position to the historical data, as well as any
    // leftover historical positions from the previous touch move event, and
    // store the resampled values in the "final" position of the event.
    PrependLeftoverHistoricalData(&input);
    for (size_t i = 0; i < input.mTouches.Length(); i++) {
      auto& touch = input.mTouches[i];
      touch.mHistoricalData.AppendElement(SingleTouchData::HistoricalTouchData{
          input.mTimeStamp,
          touch.mScreenPoint,
          touch.mLocalScreenPoint,
          touch.mRadius,
          touch.mRotationAngle,
          touch.mForce,
      });

      // Remove any historical touch data that's in the future, compared to
      // sampleTime. This data will be included by upcoming touch move
      // events. This only happens if the frame timestamp can be older than the
      // event timestamp, i.e. if interpolation occurs (rather than
      // extrapolation).
      auto futureDataStart = std::find_if(
          touch.mHistoricalData.begin(), touch.mHistoricalData.end(),
          [sampleTime](
              const SingleTouchData::HistoricalTouchData& aHistoricalData) {
            return aHistoricalData.mTimeStamp > sampleTime;
          });
      if (futureDataStart != touch.mHistoricalData.end()) {
        nsTArray futureData(
            Span(touch.mHistoricalData)
                .From(futureDataStart.GetIndex()));
        touch.mHistoricalData.TruncateLength(futureDataStart.GetIndex());
        mRemainingTouchData.insert({touch.mIdentifier, std::move(futureData)});
      }

      touch.mScreenPoint = resampledPositions[i];
    }
    input.mTimeStamp = sampleTime;
  }

  EmitEvent(std::move(input), eventId);
  mInResampledState = anyPositionDifferentFromOriginal;
}

Minimal test - lines (361, 367)

path: .spaces[0].spaces[0].spaces[8].metrics.cognitive.average
old: 11.0
new: 0.0

path: .spaces[0].spaces[0].spaces[8].metrics.cognitive.sum
old: 11.0
new: 0.0

path: .spaces[0].spaces[0].spaces[8].metrics.nom.closures
old: 0.0
new: 1.0

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

path: .spaces[0].spaces[0].spaces[8].metrics.cyclomatic.sum
old: 8.0
new: 1.0

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

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

path: .spaces[0].spaces[0].spaces[8].metrics.nargs.average
old: 3.0
new: 0.5

path: .spaces[0].spaces[0].spaces[8].metrics.mi.mi_sei
old: 69.38777452819193
new: 85.6503418792255

path: .spaces[0].spaces[0].spaces[8].metrics.mi.mi_visual_studio
old: 45.8825438709911
new: 65.3623093352975

path: .spaces[0].spaces[0].spaces[8].metrics.mi.mi_original
old: 78.45915001939478
new: 111.76954896335872

path: .spaces[0].spaces[0].spaces[8].metrics.loc.blank
old: 3.0
new: 0.0

path: .spaces[0].spaces[0].spaces[8].metrics.loc.cloc
old: 6.0
new: 0.0

path: .spaces[0].spaces[0].spaces[8].metrics.loc.lloc
old: 9.0
new: 2.0

path: .spaces[0].spaces[0].spaces[8].metrics.loc.ploc
old: 23.0
new: 7.0

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

path: .spaces[0].spaces[0].spaces[8].metrics.nexits.sum
old: 3.0
new: 2.0

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

path: .spaces[0].spaces[0].spaces[8].metrics.halstead.vocabulary
old: 49.0
new: 24.0

path: .spaces[0].spaces[0].spaces[8].metrics.halstead.difficulty
old: 17.93103448275862
new: 9.454545454545457

path: .spaces[0].spaces[0].spaces[8].metrics.halstead.length
old: 137.0
new: 43.0

path: .spaces[0].spaces[0].spaces[8].metrics.halstead.N1
old: 85.0
new: 27.0

path: .spaces[0].spaces[0].spaces[8].metrics.halstead.bugs
old: 0.191711235362376
new: 0.0504865092661884

path: .spaces[0].spaces[0].spaces[8].metrics.halstead.level
old: 0.05576923076923077
new: 0.10576923076923075

path: .spaces[0].spaces[0].spaces[8].metrics.halstead.effort
old: 13792.825148095428
new: 1863.9956639295465

path: .spaces[0].spaces[0].spaces[8].metrics.halstead.n2
old: 29.0
new: 11.0

path: .spaces[0].spaces[0].spaces[8].metrics.halstead.time
old: 766.2680637830794
new: 103.55531466275258

path: .spaces[0].spaces[0].spaces[8].metrics.halstead.estimated_program_length
old: 227.32001075644683
new: 86.15946414084446

path: .spaces[0].spaces[0].spaces[8].metrics.halstead.n1
old: 20.0
new: 13.0

path: .spaces[0].spaces[0].spaces[8].metrics.halstead.purity_ratio
old: 1.6592701515069113
new: 2.0037084683917317

path: .spaces[0].spaces[0].spaces[8].metrics.halstead.volume
old: 769.2152486437835
new: 197.1533875310097

path: .spaces[0].spaces[0].spaces[8].metrics.halstead.N2
old: 52.0
new: 16.0

Code

nsTArray::iterator
TouchResampler::CurrentTouches::TouchByIdentifier(int32_t aIdentifier) {
  return std::find_if(mTouches.begin(), mTouches.end(),
                      [aIdentifier](const TouchInfo& info) {
                        return info.mIdentifier == aIdentifier;
                      });
}

Minimal test - lines (15, 381)

path: .spaces[0].metrics.halstead.time
old: 146371.6782975398
new: 35943.70910277471

path: .spaces[0].metrics.halstead.length
old: 3465.0
new: 1332.0

path: .spaces[0].metrics.halstead.N2
old: 1417.0
new: 551.0

path: .spaces[0].metrics.halstead.vocabulary
old: 335.0
new: 172.0

path: .spaces[0].metrics.halstead.N1
old: 2048.0
new: 781.0

path: .spaces[0].metrics.halstead.level
old: 0.01103146008988597
new: 0.015289006214596052

path: .spaces[0].metrics.halstead.difficulty
old: 90.64983164983164
new: 65.40647482014388

path: .spaces[0].metrics.halstead.n1
old: 38.0
new: 33.0

path: .spaces[0].metrics.halstead.n2
old: 297.0
new: 139.0

path: .spaces[0].metrics.halstead.effort
old: 2634690.209355716
new: 646986.7638499448

path: .spaces[0].metrics.halstead.volume
old: 29064.479893720894
new: 9891.784653263194

path: .spaces[0].metrics.halstead.estimated_program_length
old: 2639.074024388684
new: 1155.9978150473964

path: .spaces[0].metrics.halstead.purity_ratio
old: 0.7616375250760993
new: 0.8678662275130604

path: .spaces[0].metrics.halstead.bugs
old: 6.358652873936142
new: 2.4934979138794

path: .spaces[0].metrics.cognitive.sum
old: 161.0
new: 37.0

path: .spaces[0].metrics.cognitive.average
old: 4.6
new: 2.642857142857143

path: .spaces[0].metrics.cyclomatic.sum
old: 151.0
new: 51.0

path: .spaces[0].metrics.cyclomatic.average
old: 3.682926829268293
new: 4.25

path: .spaces[0].metrics.loc.lloc
old: 244.0
new: 106.0

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

path: .spaces[0].metrics.loc.blank
old: 133.0
new: 42.0

path: .spaces[0].metrics.loc.ploc
old: 605.0
new: 248.0

path: .spaces[0].metrics.loc.sloc
old: 866.0
new: 367.0

path: .spaces[0].metrics.nom.functions
old: 35.0
new: 10.0

path: .spaces[0].metrics.nom.total
old: 35.0
new: 14.0

path: .spaces[0].metrics.nom.closures
old: 0.0
new: 4.0

path: .spaces[0].metrics.mi.mi_original
old: -26.74675036271151
new: 15.765946796707766

path: .spaces[0].metrics.mi.mi_visual_studio
old: 0.0
new: 9.21985192789928

path: .spaces[0].metrics.mi.mi_sei
old: -70.86335215696016
new: -15.185797941514052

path: .spaces[0].metrics.nargs.average
old: 2.2
new: 0.8571428571428571

path: .spaces[0].metrics.nargs.sum
old: 77.0
new: 12.0

path: .spaces[0].metrics.nexits.average
old: 0.9142857142857144
new: 0.7857142857142857

path: .spaces[0].metrics.nexits.sum
old: 32.0
new: 11.0

Code

namespace mozilla {
namespace widget {

// The values below have been tested and found to be acceptable on a device
// with a display refresh rate of 60Hz and touch sampling rate of 100Hz.
// While their "ideal" values are dependent on the exact rates of each device,
// the values we've picked below should be somewhat robust across a variation of
// different rates. They mostly aim to avoid making predictions that are too far
// away (in terms of distance) from the finger, and to detect pauses in the
// finger motion without too much delay.

// Maximum time between two consecutive data points to consider resampling
// between them.
// Values between 1x and 5x of the touch sampling interval are reasonable.
static const double kTouchResampleWindowSize = 40.0;

// These next two values constrain the sampling timestamp.
// Our caller will usually adjust frame timestamps to be slightly in the past,
// for example by 5ms. This means that, during normal operation, we will
// maximally need to predict by [touch sampling rate] minus 5ms.
// So we would like kTouchResampleMaxPredictMs to satisfy the following:
// kTouchResampleMaxPredictMs + [frame time adjust] > [touch sampling rate]
static const double kTouchResampleMaxPredictMs = 8.0;
// This one is a protection against very outdated frame timestamps.
// Values larger than the touch sampling interval and less than 3x of the vsync
// interval are reasonable.
static const double kTouchResampleMaxBacksampleMs = 20.0;

// The maximum age of the most recent data point to consider resampling.
// Should be between 1x and 3x of the touch sampling interval.
static const double kTouchResampleOldTouchThresholdMs = 17.0;

uint64_t TouchResampler::ProcessEvent(MultiTouchInput&& aInput) {
  mCurrentTouches.UpdateFromEvent(aInput);

  uint64_t eventId = mNextEventId;
  mNextEventId++;

  if (aInput.mType == MultiTouchInput::MULTITOUCH_MOVE) {
    // Touch move events are deferred until NotifyFrame.
    mDeferredTouchMoveEvents.push({std::move(aInput), eventId});
  } else {
    // Non-move events are transferred to the outgoing queue unmodified.
    // If there are pending touch move events, flush those out first, so that
    // events are emitted in the right order.
    FlushDeferredTouchMoveEventsUnresampled();
    if (mInResampledState) {
      // Return to a non-resampled state before emitting a non-move event.
      ReturnToNonResampledState();
    }
    EmitEvent(std::move(aInput), eventId);
  }

  return eventId;
}

void TouchResampler::NotifyFrame(const TimeStamp& aTimeStamp) {
  TimeStamp lastTouchTime = mCurrentTouches.LatestDataPointTime();
  if (mDeferredTouchMoveEvents.empty() ||
      (lastTouchTime &&
       lastTouchTime < aTimeStamp - TimeDuration::FromMilliseconds(
                                        kTouchResampleOldTouchThresholdMs))) {
    // We haven't received a touch move event in a while, so the fingers must
    // have stopped moving. Flush any old touch move events.
    FlushDeferredTouchMoveEventsUnresampled();

    if (mInResampledState) {
      // Make sure we pause at the resting position that we actually observed,
      // and not at a resampled position.
      ReturnToNonResampledState();
    }

    // Clear touch location history so that we don't resample across a pause.
    mCurrentTouches.ClearDataPoints();
    return;
  }

  MOZ_RELEASE_ASSERT(lastTouchTime);
  TimeStamp lowerBound = lastTouchTime - TimeDuration::FromMilliseconds(
                                             kTouchResampleMaxBacksampleMs);
  TimeStamp upperBound = lastTouchTime + TimeDuration::FromMilliseconds(
                                             kTouchResampleMaxPredictMs);
  TimeStamp sampleTime = clamped(aTimeStamp, lowerBound, upperBound);

  if (mLastEmittedEventTime && sampleTime < mLastEmittedEventTime) {
    // Keep emitted timestamps in order.
    sampleTime = mLastEmittedEventTime;
  }

  // We have at least one pending touch move event. Pick one of the events from
  // mDeferredTouchMoveEvents as the base event for the resampling adjustment.
  // We want to produce an event stream whose timestamps are in the right order.
  // As the base event, use the first event that's at or after sampleTime,
  // unless there is no such event, in that case use the last one we have. We
  // will set the timestamp on the resampled event to sampleTime later.
  // Flush out any older events so that everything remains in the right order.
  MultiTouchInput input;
  uint64_t eventId;
  while (true) {
    MOZ_RELEASE_ASSERT(!mDeferredTouchMoveEvents.empty());

    std::tie(input, eventId) = std::move(mDeferredTouchMoveEvents.front());
    mDeferredTouchMoveEvents.pop();
    if (mDeferredTouchMoveEvents.empty() || input.mTimeStamp >= sampleTime) {
      break;
    }

    // Flush this event to the outgoing queue without resampling. What ends up
    // on the screen will still be smooth because we will proceed to emit a
    // resampled event before the paint for this frame starts.
    PrependLeftoverHistoricalData(&input);
    MOZ_RELEASE_ASSERT(input.mTimeStamp < sampleTime);
    EmitEvent(std::move(input), eventId);
  }

  mOriginalOfResampledTouchMove = Nothing();

  // Compute the resampled touch positions.
  nsTArray resampledPositions;
  bool anyPositionDifferentFromOriginal = false;
  for (const auto& touch : input.mTouches) {
    ScreenIntPoint resampledPosition =
        mCurrentTouches.ResampleTouchPositionAtTime(
            touch.mIdentifier, touch.mScreenPoint, sampleTime);
    if (resampledPosition != touch.mScreenPoint) {
      anyPositionDifferentFromOriginal = true;
    }
    resampledPositions.AppendElement(resampledPosition);
  }

  if (anyPositionDifferentFromOriginal) {
    // Store a copy of the original event, so that we can return to an
    // non-resampled position later, if necessary.
    mOriginalOfResampledTouchMove = Some(input);

    // Add the original observed position to the historical data, as well as any
    // leftover historical positions from the previous touch move event, and
    // store the resampled values in the "final" position of the event.
    PrependLeftoverHistoricalData(&input);
    for (size_t i = 0; i < input.mTouches.Length(); i++) {
      auto& touch = input.mTouches[i];
      touch.mHistoricalData.AppendElement(SingleTouchData::HistoricalTouchData{
          input.mTimeStamp,
          touch.mScreenPoint,
          touch.mLocalScreenPoint,
          touch.mRadius,
          touch.mRotationAngle,
          touch.mForce,
      });

      // Remove any historical touch data that's in the future, compared to
      // sampleTime. This data will be included by upcoming touch move
      // events. This only happens if the frame timestamp can be older than the
      // event timestamp, i.e. if interpolation occurs (rather than
      // extrapolation).
      auto futureDataStart = std::find_if(
          touch.mHistoricalData.begin(), touch.mHistoricalData.end(),
          [sampleTime](
              const SingleTouchData::HistoricalTouchData& aHistoricalData) {
            return aHistoricalData.mTimeStamp > sampleTime;
          });
      if (futureDataStart != touch.mHistoricalData.end()) {
        nsTArray futureData(
            Span(touch.mHistoricalData)
                .From(futureDataStart.GetIndex()));
        touch.mHistoricalData.TruncateLength(futureDataStart.GetIndex());
        mRemainingTouchData.insert({touch.mIdentifier, std::move(futureData)});
      }

      touch.mScreenPoint = resampledPositions[i];
    }
    input.mTimeStamp = sampleTime;
  }

  EmitEvent(std::move(input), eventId);
  mInResampledState = anyPositionDifferentFromOriginal;
}

void TouchResampler::PrependLeftoverHistoricalData(MultiTouchInput* aInput) {
  for (auto& touch : aInput->mTouches) {
    auto leftoverData = mRemainingTouchData.find(touch.mIdentifier);
    if (leftoverData != mRemainingTouchData.end()) {
      nsTArray data =
          std::move(leftoverData->second);
      mRemainingTouchData.erase(leftoverData);
      touch.mHistoricalData.InsertElementsAt(0, data);
    }

    if (TimeStamp cutoffTime = mLastEmittedEventTime) {
      // If we received historical touch data that was further in the past than
      // the last resampled event, discard that data so that the touch data
      // points are emitted in order.
      touch.mHistoricalData.RemoveElementsBy(
          [cutoffTime](const SingleTouchData::HistoricalTouchData& aTouchData) {
            return aTouchData.mTimeStamp < cutoffTime;
          });
    }
  }
  mRemainingTouchData.clear();
}

void TouchResampler::FlushDeferredTouchMoveEventsUnresampled() {
  while (!mDeferredTouchMoveEvents.empty()) {
    MultiTouchInput input;
    uint64_t eventId;
    std::tie(input, eventId) = std::move(mDeferredTouchMoveEvents.front());
    mDeferredTouchMoveEvents.pop();
    PrependLeftoverHistoricalData(&input);
    EmitEvent(std::move(input), eventId);
    mInResampledState = false;
    mOriginalOfResampledTouchMove = Nothing();
  }
}

void TouchResampler::ReturnToNonResampledState() {
  MOZ_RELEASE_ASSERT(mInResampledState);
  MOZ_RELEASE_ASSERT(mDeferredTouchMoveEvents.empty(),
                     "Don't call this if there is a deferred touch move event. "
                     "We can return to the non-resampled state by sending that "
                     "event, rather than a copy of a previous event.");

  // The last outgoing event was a resampled touch move event.
  // Return to the non-resampled state, by sending a touch move event to
  // "overwrite" any resampled positions with the original observed positions.
  MultiTouchInput input = std::move(*mOriginalOfResampledTouchMove);
  mOriginalOfResampledTouchMove = Nothing();

  // For the event's timestamp, we want to backdate the correction as far as we
  // can, while still preserving timestamp ordering. But we also don't want to
  // backdate it to be older than it was originally.
  if (mLastEmittedEventTime > input.mTimeStamp) {
    input.mTimeStamp = mLastEmittedEventTime;
  }

  // Assemble the correct historical touch data for this event.
  // We don't want to include data points that we've already sent out with the
  // resampled event. And from the leftover data points, we only want those that
  // don't duplicate the final time + position of this event.
  for (auto& touch : input.mTouches) {
    touch.mHistoricalData.Clear();
  }
  PrependLeftoverHistoricalData(&input);
  for (auto& touch : input.mTouches) {
    touch.mHistoricalData.RemoveElementsBy([&](const auto& histData) {
      return histData.mTimeStamp >= input.mTimeStamp;
    });
  }

  EmitExtraEvent(std::move(input));
  mInResampledState = false;
}

void TouchResampler::TouchInfo::Update(const SingleTouchData& aTouch,
                                       const TimeStamp& aEventTime) {
  for (const auto& historicalData : aTouch.mHistoricalData) {
    mBaseDataPoint = mLatestDataPoint;
    mLatestDataPoint =
        Some(DataPoint{historicalData.mTimeStamp, historicalData.mScreenPoint});
  }
  mBaseDataPoint = mLatestDataPoint;
  mLatestDataPoint = Some(DataPoint{aEventTime, aTouch.mScreenPoint});
}

ScreenIntPoint TouchResampler::TouchInfo::ResampleAtTime(
    const ScreenIntPoint& aLastObservedPosition, const TimeStamp& aTimeStamp) {
  TimeStamp cutoff =
      aTimeStamp - TimeDuration::FromMilliseconds(kTouchResampleWindowSize);
  if (!mBaseDataPoint || !mLatestDataPoint ||
      !(mBaseDataPoint->mTimeStamp < mLatestDataPoint->mTimeStamp) ||
      mBaseDataPoint->mTimeStamp < cutoff) {
    return aLastObservedPosition;
  }

  // For the actual resampling, connect the last two data points with a line and
  // sample along that line.
  TimeStamp t1 = mBaseDataPoint->mTimeStamp;
  TimeStamp t2 = mLatestDataPoint->mTimeStamp;
  double t = (aTimeStamp - t1) / (t2 - t1);

  double x1 = mBaseDataPoint->mPosition.x;
  double x2 = mLatestDataPoint->mPosition.x;
  double y1 = mBaseDataPoint->mPosition.y;
  double y2 = mLatestDataPoint->mPosition.y;

  int32_t resampledX = round(x1 + t * (x2 - x1));
  int32_t resampledY = round(y1 + t * (y2 - y1));
  return ScreenIntPoint(resampledX, resampledY);
}

void TouchResampler::CurrentTouches::UpdateFromEvent(
    const MultiTouchInput& aInput) {
  switch (aInput.mType) {
    case MultiTouchInput::MULTITOUCH_START: {
      // A new touch has been added; make sure mTouches reflects the current
      // touches in the event.
      nsTArray newTouches;
      for (const auto& touch : aInput.mTouches) {
        const auto touchInfo = TouchByIdentifier(touch.mIdentifier);
        if (touchInfo != mTouches.end()) {
          // This is one of the existing touches.
          newTouches.AppendElement(std::move(*touchInfo));
          mTouches.RemoveElementAt(touchInfo);
        } else {
          // This is the new touch.
          newTouches.AppendElement(TouchInfo{
              touch.mIdentifier, Nothing(),
              Some(DataPoint{aInput.mTimeStamp, touch.mScreenPoint})});
        }
      }
      MOZ_ASSERT(mTouches.IsEmpty(), "Missing touch end before touch start?");
      mTouches = std::move(newTouches);
      break;
    }

    case MultiTouchInput::MULTITOUCH_MOVE: {
      // The touches have moved.
      // Add position information to the history data points.
      for (const auto& touch : aInput.mTouches) {
        const auto touchInfo = TouchByIdentifier(touch.mIdentifier);
        MOZ_ASSERT(touchInfo != mTouches.end());
        if (touchInfo != mTouches.end()) {
          touchInfo->Update(touch, aInput.mTimeStamp);
        }
      }
      mLatestDataPointTime = aInput.mTimeStamp;
      break;
    }

    case MultiTouchInput::MULTITOUCH_END: {
      // A touch has been removed.
      MOZ_RELEASE_ASSERT(aInput.mTouches.Length() == 1);
      const auto touchInfo = TouchByIdentifier(aInput.mTouches[0].mIdentifier);
      MOZ_ASSERT(touchInfo != mTouches.end());
      if (touchInfo != mTouches.end()) {
        mTouches.RemoveElementAt(touchInfo);
      }
      break;
    }

    case MultiTouchInput::MULTITOUCH_CANCEL:
      // All touches are canceled.
      mTouches.Clear();
      break;
  }
}

nsTArray::iterator
TouchResampler::CurrentTouches::TouchByIdentifier(int32_t aIdentifier) {
  return std::find_if(mTouches.begin(), mTouches.end(),
                      [aIdentifier](const TouchInfo& info) {
                        return info.mIdentifier == aIdentifier;
                      });
}

ScreenIntPoint TouchResampler::CurrentTouches::ResampleTouchPositionAtTime(
    int32_t aIdentifier, const ScreenIntPoint& aLastObservedPosition,
    const TimeStamp& aTimeStamp) {
  const auto touchInfo = TouchByIdentifier(aIdentifier);
  MOZ_ASSERT(touchInfo != mTouches.end());
  if (touchInfo != mTouches.end()) {
    return touchInfo->ResampleAtTime(aLastObservedPosition, aTimeStamp);
  }
  return aLastObservedPosition;
}

}  // namespace widget
}  // namespace mozilla

Minimal test - lines (278, 302)

path: .spaces[0].spaces[0].spaces[6].metrics.nargs.sum
old: 1.0
new: 2.0

path: .spaces[0].spaces[0].spaces[6].metrics.nargs.average
old: 1.0
new: 2.0

path: .spaces[0].spaces[0].spaces[6].metrics.cognitive.average
old: 6.0
new: 2.0

path: .spaces[0].spaces[0].spaces[6].metrics.cognitive.sum
old: 6.0
new: 2.0

path: .spaces[0].spaces[0].spaces[6].metrics.loc.ploc
old: 13.0
new: 20.0

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

path: .spaces[0].spaces[0].spaces[6].metrics.loc.blank
old: 0.0
new: 3.0

path: .spaces[0].spaces[0].spaces[6].metrics.loc.lloc
old: 5.0
new: 3.0

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

path: .spaces[0].spaces[0].spaces[6].metrics.halstead.time
old: 349.1338056342785
new: 1147.9313331764463

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

path: .spaces[0].spaces[0].spaces[6].metrics.halstead.bugs
old: 0.11351546092344686
new: 0.25099843995294474

path: .spaces[0].spaces[0].spaces[6].metrics.halstead.purity_ratio
old: 1.9513692208539513
new: 1.2888494781941613

path: .spaces[0].spaces[0].spaces[6].metrics.halstead.effort
old: 6284.408501417012
new: 20662.763997176033

path: .spaces[0].spaces[0].spaces[6].metrics.halstead.estimated_program_length
old: 179.5259683185635
new: 190.74972277273588

path: .spaces[0].spaces[0].spaces[6].metrics.halstead.volume
old: 492.8947844248637
new: 803.0871836959105

path: .spaces[0].spaces[0].spaces[6].metrics.halstead.N1
old: 56.0
new: 83.0

path: .spaces[0].spaces[0].spaces[6].metrics.halstead.level
old: 0.0784313725490196
new: 0.0388663967611336

path: .spaces[0].spaces[0].spaces[6].metrics.halstead.vocabulary
old: 41.0
new: 43.0

path: .spaces[0].spaces[0].spaces[6].metrics.halstead.N2
old: 36.0
new: 65.0

path: .spaces[0].spaces[0].spaces[6].metrics.halstead.length
old: 92.0
new: 148.0

path: .spaces[0].spaces[0].spaces[6].metrics.halstead.difficulty
old: 12.75
new: 25.729166666666668

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

path: .spaces[0].spaces[0].spaces[6].metrics.cyclomatic.average
old: 4.0
new: 5.0

path: .spaces[0].spaces[0].spaces[6].metrics.mi.mi_original
old: 95.08573345253748
new: 82.92420257839832

path: .spaces[0].spaces[0].spaces[6].metrics.mi.mi_sei
old: 82.00167479725492
new: 65.65710029021675

path: .spaces[0].spaces[0].spaces[6].metrics.mi.mi_visual_studio
old: 55.6056920775073
new: 48.4936857183616

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

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

Code

ScreenIntPoint TouchResampler::TouchInfo::ResampleAtTime(
    const ScreenIntPoint& aLastObservedPosition, const TimeStamp& aTimeStamp) {
  TimeStamp cutoff =
      aTimeStamp - TimeDuration::FromMilliseconds(kTouchResampleWindowSize);
  if (!mBaseDataPoint || !mLatestDataPoint ||
      !(mBaseDataPoint->mTimeStamp < mLatestDataPoint->mTimeStamp) ||
      mBaseDataPoint->mTimeStamp < cutoff) {
    return aLastObservedPosition;
  }

  // For the actual resampling, connect the last two data points with a line and
  // sample along that line.
  TimeStamp t1 = mBaseDataPoint->mTimeStamp;
  TimeStamp t2 = mLatestDataPoint->mTimeStamp;
  double t = (aTimeStamp - t1) / (t2 - t1);

  double x1 = mBaseDataPoint->mPosition.x;
  double x2 = mLatestDataPoint->mPosition.x;
  double y1 = mBaseDataPoint->mPosition.y;
  double y2 = mLatestDataPoint->mPosition.y;

  int32_t resampledX = round(x1 + t * (x2 - x1));
  int32_t resampledY = round(y1 + t * (y2 - y1));
  return ScreenIntPoint(resampledX, resampledY);
}

Minimal test - lines (47, 69)

path: .spaces[0].spaces[0].spaces[0].metrics.mi.mi_original
old: 60.7161384665593
new: 89.1492616824421

path: .spaces[0].spaces[0].spaces[0].metrics.mi.mi_visual_studio
old: 35.50651372313409
new: 52.134071159322865

path: .spaces[0].spaces[0].spaces[0].metrics.mi.mi_sei
old: 37.730768824375474
new: 86.37779630898021

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

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

path: .spaces[0].spaces[0].spaces[0].metrics.cognitive.average
old: 1.3333333333333333
new: 4.0

path: .spaces[0].spaces[0].spaces[0].metrics.cognitive.sum
old: 8.0
new: 4.0

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

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

path: .spaces[0].spaces[0].spaces[0].metrics.halstead.N2
old: 107.0
new: 25.0

path: .spaces[0].spaces[0].spaces[0].metrics.halstead.purity_ratio
old: 0.7956315720598552
new: 1.7507905807284547

path: .spaces[0].spaces[0].spaces[0].metrics.halstead.N1
old: 163.0
new: 42.0

path: .spaces[0].spaces[0].spaces[0].metrics.halstead.level
old: 0.025233644859813085
new: 0.09142857142857144

path: .spaces[0].spaces[0].spaces[0].metrics.halstead.time
old: 3301.894484052818
new: 199.7683758107982

path: .spaces[0].spaces[0].spaces[0].metrics.halstead.vocabulary
old: 47.0
new: 30.0

path: .spaces[0].spaces[0].spaces[0].metrics.halstead.bugs
old: 0.5076556326042772
new: 0.07823688927938995

path: .spaces[0].spaces[0].spaces[0].metrics.halstead.n1
old: 20.0
new: 14.0

path: .spaces[0].spaces[0].spaces[0].metrics.halstead.estimated_program_length
old: 214.8205244561609
new: 117.30296890880646

path: .spaces[0].spaces[0].spaces[0].metrics.halstead.effort
old: 59434.10071295072
new: 3595.830764594368

path: .spaces[0].spaces[0].spaces[0].metrics.halstead.length
old: 270.0
new: 67.0

path: .spaces[0].spaces[0].spaces[0].metrics.halstead.n2
old: 27.0
new: 16.0

path: .spaces[0].spaces[0].spaces[0].metrics.halstead.difficulty
old: 39.629629629629626
new: 10.9375

path: .spaces[0].spaces[0].spaces[0].metrics.halstead.volume
old: 1499.738989952962
new: 328.76166990577076

path: .spaces[0].spaces[0].spaces[0].metrics.loc.blank
old: 12.0
new: 3.0

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

path: .spaces[0].spaces[0].spaces[0].metrics.loc.ploc
old: 48.0
new: 15.0

path: .spaces[0].spaces[0].spaces[0].metrics.loc.sloc
old: 67.0
new: 23.0

path: .spaces[0].spaces[0].spaces[0].metrics.loc.lloc
old: 14.0
new: 9.0

path: .spaces[0].spaces[0].spaces[0].metrics.nargs.average
old: 1.8333333333333333
new: 1.0

path: .spaces[0].spaces[0].spaces[0].metrics.nargs.sum
old: 11.0
new: 1.0

path: .spaces[0].spaces[0].spaces[0].metrics.cyclomatic.average
old: 1.8
new: 4.0

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

Code

uint64_t TouchResampler::ProcessEvent(MultiTouchInput&& aInput) {
  mCurrentTouches.UpdateFromEvent(aInput);

  uint64_t eventId = mNextEventId;
  mNextEventId++;

  if (aInput.mType == MultiTouchInput::MULTITOUCH_MOVE) {
    // Touch move events are deferred until NotifyFrame.
    mDeferredTouchMoveEvents.push({std::move(aInput), eventId});
  } else {
    // Non-move events are transferred to the outgoing queue unmodified.
    // If there are pending touch move events, flush those out first, so that
    // events are emitted in the right order.
    FlushDeferredTouchMoveEventsUnresampled();
    if (mInResampledState) {
      // Return to a non-resampled state before emitting a non-move event.
      ReturnToNonResampledState();
    }
    EmitEvent(std::move(aInput), eventId);
  }

  return eventId;
}

Minimal test - lines (267, 276)

path: .spaces[0].spaces[0].spaces[5].metrics.mi.mi_visual_studio
old: 60.521643511003816
new: 61.04551267914326

path: .spaces[0].spaces[0].spaces[5].metrics.mi.mi_original
old: 103.49201040381654
new: 104.38782668133496

path: .spaces[0].spaces[0].spaces[5].metrics.mi.mi_sei
old: 73.91201776741578
new: 75.10258760913477

path: .spaces[0].spaces[0].spaces[5].metrics.halstead.n1
old: 15.0
new: 11.0

path: .spaces[0].spaces[0].spaces[5].metrics.halstead.bugs
old: 0.0936691449056419
new: 0.06488348759510171

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

path: .spaces[0].spaces[0].spaces[5].metrics.halstead.N2
old: 31.0
new: 25.0

path: .spaces[0].spaces[0].spaces[5].metrics.halstead.effort
old: 4710.6052906868335
new: 2715.708558119454

path: .spaces[0].spaces[0].spaces[5].metrics.halstead.estimated_program_length
old: 145.04192083187502
new: 86.15946414084446

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

path: .spaces[0].spaces[0].spaces[5].metrics.halstead.time
old: 261.7002939270463
new: 150.872697673303

path: .spaces[0].spaces[0].spaces[5].metrics.halstead.level
old: 0.08602150537634409
new: 0.09454545454545454

path: .spaces[0].spaces[0].spaces[5].metrics.halstead.purity_ratio
old: 1.8359736814161391
new: 1.538561859657937

path: .spaces[0].spaces[0].spaces[5].metrics.halstead.N1
old: 48.0
new: 31.0

path: .spaces[0].spaces[0].spaces[5].metrics.halstead.vocabulary
old: 35.0
new: 24.0

path: .spaces[0].spaces[0].spaces[5].metrics.halstead.volume
old: 405.2133583386524
new: 256.75790004038475

path: .spaces[0].spaces[0].spaces[5].metrics.halstead.difficulty
old: 11.625
new: 10.576923076923077

path: .spaces[0].spaces[0].spaces[5].metrics.cyclomatic.average
old: 3.0
new: 2.0

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

path: .spaces[0].spaces[0].spaces[5].metrics.nargs.average
old: 1.0
new: 2.0

path: .spaces[0].spaces[0].spaces[5].metrics.nargs.sum
old: 1.0
new: 2.0

path: .spaces[0].spaces[0].spaces[5].metrics.cognitive.average
old: 3.0
new: 0.0

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

path: .spaces[0].spaces[0].spaces[5].metrics.loc.sloc
old: 9.0
new: 10.0

path: .spaces[0].spaces[0].spaces[5].metrics.loc.ploc
old: 9.0
new: 10.0

Code

void TouchResampler::TouchInfo::Update(const SingleTouchData& aTouch,
                                       const TimeStamp& aEventTime) {
  for (const auto& historicalData : aTouch.mHistoricalData) {
    mBaseDataPoint = mLatestDataPoint;
    mLatestDataPoint =
        Some(DataPoint{historicalData.mTimeStamp, historicalData.mScreenPoint});
  }
  mBaseDataPoint = mLatestDataPoint;
  mLatestDataPoint = Some(DataPoint{aEventTime, aTouch.mScreenPoint});
}