Global Metrics

path: .metrics.halstead.volume
old: 1124.7323510644935
new: 4963.955310300051

path: .metrics.halstead.N2
old: 66.0
new: 298.0

path: .metrics.halstead.difficulty
old: 16.076923076923077
new: 43.8235294117647

path: .metrics.halstead.purity_ratio
old: 1.4939651525575826
new: 0.9028615222897168

path: .metrics.halstead.time
old: 1004.568638343928
new: 12085.446752201104

path: .metrics.halstead.effort
old: 18082.235490190706
new: 217538.04153961985

path: .metrics.halstead.level
old: 0.06220095693779904
new: 0.022818791946308727

path: .metrics.halstead.n1
old: 19.0
new: 25.0

path: .metrics.halstead.n2
old: 39.0
new: 85.0

path: .metrics.halstead.N1
old: 126.0
new: 434.0

path: .metrics.halstead.bugs
old: 0.2296396233479145
new: 1.2056897111309235

path: .metrics.halstead.estimated_program_length
old: 286.84130929105584
new: 660.8946343160727

path: .metrics.halstead.length
old: 192.0
new: 732.0

path: .metrics.halstead.vocabulary
old: 58.0
new: 110.0

path: .metrics.mi.mi_sei
old: 43.85170420969874
new: 0.1975780724226368

path: .metrics.mi.mi_original
old: 65.04965547638089
new: 24.11929997220173

path: .metrics.mi.mi_visual_studio
old: 38.04073419671397
new: 14.104853784913292

path: .metrics.cognitive.sum
old: 2.0
new: 8.0

path: .metrics.cognitive.average
old: 0.25
new: 0.26666666666666666

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

path: .metrics.loc.lloc
old: 14.0
new: 55.0

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

path: .metrics.loc.ploc
old: 48.0
new: 179.0

path: .metrics.loc.sloc
old: 63.0
new: 302.0

path: .metrics.cyclomatic.sum
old: 10.0
new: 44.0

path: .metrics.cyclomatic.average
old: 1.25
new: 1.2571428571428571

path: .metrics.nexits.average
old: 0.875
new: 0.4

path: .metrics.nexits.sum
old: 7.0
new: 12.0

path: .metrics.nom.functions
old: 6.0
new: 28.0

path: .metrics.nom.total
old: 8.0
new: 30.0

path: .metrics.nargs.average
old: 1.625
new: 0.5666666666666667

path: .metrics.nargs.sum
old: 13.0
new: 17.0

Spaces Data

Minimal test - lines (61, 300)

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

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

path: .spaces[0].metrics.mi.mi_original
old: 84.51884826167563
new: 28.23481217871415

path: .spaces[0].metrics.mi.mi_sei
old: 46.74317055377577
new: -1.0320186661940127

path: .spaces[0].metrics.mi.mi_visual_studio
old: 49.42622705361148
new: 16.51158606942348

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

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

path: .spaces[0].metrics.nom.total
old: 4.0
new: 30.0

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

path: .spaces[0].metrics.nom.functions
old: 4.0
new: 28.0

path: .spaces[0].metrics.halstead.length
old: 93.0
new: 721.0

path: .spaces[0].metrics.halstead.n2
old: 21.0
new: 77.0

path: .spaces[0].metrics.halstead.time
old: 196.21031746031747
new: 12539.020527428376

path: .spaces[0].metrics.halstead.purity_ratio
old: 1.4009936955200455
new: 0.8302898313146678

path: .spaces[0].metrics.halstead.difficulty
old: 7.595238095238095
new: 46.91558441558441

path: .spaces[0].metrics.halstead.N1
old: 64.0
new: 432.0

path: .spaces[0].metrics.halstead.effort
old: 3531.785714285714
new: 225702.3694937108

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

path: .spaces[0].metrics.halstead.vocabulary
old: 32.0
new: 102.0

path: .spaces[0].metrics.halstead.volume
old: 465.0
new: 4810.818671561448

path: .spaces[0].metrics.halstead.bugs
old: 0.07730512895798068
new: 1.2356709144394495

path: .spaces[0].metrics.halstead.N2
old: 29.0
new: 289.0

path: .spaces[0].metrics.halstead.estimated_program_length
old: 130.29241368336423
new: 598.6389683778755

path: .spaces[0].metrics.halstead.level
old: 0.1316614420062696
new: 0.021314878892733567

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

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

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

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

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

path: .spaces[0].metrics.loc.ploc
old: 25.0
new: 166.0

path: .spaces[0].metrics.loc.sloc
old: 27.0
new: 240.0

path: .spaces[0].metrics.nexits.average
old: 0.75
new: 0.4

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

Code

namespace mozilla {

extern LazyLogModule gStateWatchingLog;

#  define WATCH_LOG(x, ...) \
    MOZ_LOG(gStateWatchingLog, LogLevel::Debug, (x, ##__VA_ARGS__))

/*
 * AbstractWatcher is a superclass from which all watchers must inherit.
 */
class AbstractWatcher {
 public:
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractWatcher)
  AbstractWatcher() : mDestroyed(false) {}
  bool IsDestroyed() { return mDestroyed; }
  virtual void Notify() = 0;

 protected:
  virtual ~AbstractWatcher() { MOZ_ASSERT(mDestroyed); }
  bool mDestroyed;
};

/*
 * WatchTarget is a superclass from which all watchable things must inherit.
 * Unlike AbstractWatcher, it is a fully-implemented Mix-in, and the subclass
 * needs only to invoke NotifyWatchers when something changes.
 *
 * The functionality that this class provides is not threadsafe, and should only
 * be used on the thread that owns that WatchTarget.
 */
class WatchTarget {
 public:
  explicit WatchTarget(const char* aName) : mName(aName) {}

  void AddWatcher(AbstractWatcher* aWatcher) {
    MOZ_ASSERT(!mWatchers.Contains(aWatcher));
    mWatchers.AppendElement(aWatcher);
  }

  void RemoveWatcher(AbstractWatcher* aWatcher) {
    MOZ_ASSERT(mWatchers.Contains(aWatcher));
    mWatchers.RemoveElement(aWatcher);
  }

 protected:
  void NotifyWatchers() {
    WATCH_LOG("%s[%p] notifying watchers\n", mName, this);
    PruneWatchers();
    for (size_t i = 0; i < mWatchers.Length(); ++i) {
      mWatchers[i]->Notify();
    }
  }

 private:
  // We don't have Watchers explicitly unregister themselves when they die,
  // because then they'd need back-references to all the WatchTargets they're
  // subscribed to, and WatchTargets aren't reference-counted. So instead we
  // just prune dead ones at appropriate times, which works just fine.
  void PruneWatchers() {
    mWatchers.RemoveElementsBy(
        [](const auto& watcher) { return watcher->IsDestroyed(); });
  }

  nsTArray> mWatchers;

 protected:
  const char* mName;
};

/*
 * Watchable is a wrapper class that turns any primitive into a WatchTarget.
 */
template 
class Watchable : public WatchTarget {
 public:
  Watchable(const T& aInitialValue, const char* aName)
      : WatchTarget(aName), mValue(aInitialValue) {}

  const T& Ref() const { return mValue; }
  operator const T&() const { return Ref(); }
  Watchable& operator=(const T& aNewValue) {
    if (aNewValue != mValue) {
      mValue = aNewValue;
      NotifyWatchers();
    }

    return *this;
  }

 private:
  Watchable(const Watchable& aOther) = delete;
  Watchable& operator=(const Watchable& aOther) = delete;

  T mValue;
};

// Manager class for state-watching. Declare one of these in any class for which
// you want to invoke method callbacks.
//
// Internally, WatchManager maintains one AbstractWatcher per callback method.
// Consumers invoke Watch/Unwatch on a particular (WatchTarget, Callback) tuple.
// This causes an AbstractWatcher for |Callback| to be instantiated if it
// doesn't already exist, and registers it with |WatchTarget|.
//
// Using Direct Tasks on the TailDispatcher, WatchManager ensures that we fire
// watch callbacks no more than once per task, once all other operations for
// that task have been completed.
//
// WatchManager is intended to be declared as a member of |OwnerType|
// objects. Given that, it and its owned objects can't hold permanent strong
// refs to the owner, since that would keep the owner alive indefinitely.
// Instead, it _only_ holds strong refs while waiting for Direct Tasks to fire.
// This ensures that everything is kept alive just long enough.
template 
class WatchManager {
 public:
  typedef void (OwnerType::*CallbackMethod)();
  explicit WatchManager(OwnerType* aOwner, AbstractThread* aOwnerThread)
      : mOwner(aOwner), mOwnerThread(aOwnerThread) {}

  ~WatchManager() {
    if (!IsShutdown()) {
      Shutdown();
    }
  }

  bool IsShutdown() const { return !mOwner; }

  // Shutdown needs to happen on mOwnerThread. If the WatchManager will be
  // destroyed on a different thread, Shutdown() must be called manually.
  void Shutdown() {
    MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
    for (auto& watcher : mWatchers) {
      watcher->Destroy();
    }
    mWatchers.Clear();
    mOwner = nullptr;
  }

  void Watch(WatchTarget& aTarget, CallbackMethod aMethod) {
    MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
    aTarget.AddWatcher(&EnsureWatcher(aMethod));
  }

  void Unwatch(WatchTarget& aTarget, CallbackMethod aMethod) {
    MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
    PerCallbackWatcher* watcher = GetWatcher(aMethod);
    MOZ_ASSERT(watcher);
    aTarget.RemoveWatcher(watcher);
  }

  void ManualNotify(CallbackMethod aMethod) {
    MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
    PerCallbackWatcher* watcher = GetWatcher(aMethod);
    MOZ_ASSERT(watcher);
    watcher->Notify();
  }

 private:
  class PerCallbackWatcher : public AbstractWatcher {
   public:
    PerCallbackWatcher(OwnerType* aOwner, AbstractThread* aOwnerThread,
                       CallbackMethod aMethod)
        : mOwner(aOwner),
          mOwnerThread(aOwnerThread),
          mCallbackMethod(aMethod) {}

    void Destroy() {
      MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
      mDestroyed = true;
      mOwner = nullptr;
    }

    void Notify() override {
      MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
      MOZ_DIAGNOSTIC_ASSERT(mOwner,
                            "mOwner is only null after destruction, "
                            "at which point we shouldn't be notified");
      if (mNotificationPending) {
        // We've already got a notification job in the pipe.
        return;
      }
      mNotificationPending = true;

      // Queue up our notification jobs to run in a stable state.
      AbstractThread::DispatchDirectTask(
          NS_NewRunnableFunction("WatchManager::PerCallbackWatcher::Notify",
                                 [self = RefPtr(this),
                                  owner = RefPtr(mOwner)]() {
                                   if (!self->mDestroyed) {
                                     ((*owner).*(self->mCallbackMethod))();
                                   }
                                   self->mNotificationPending = false;
                                 }));
    }

    bool CallbackMethodIs(CallbackMethod aMethod) const {
      return mCallbackMethod == aMethod;
    }

   private:
    ~PerCallbackWatcher() = default;

    OwnerType* mOwner;  // Never null.
    bool mNotificationPending = false;
    RefPtr mOwnerThread;
    CallbackMethod mCallbackMethod;
  };

  PerCallbackWatcher* GetWatcher(CallbackMethod aMethod) {
    MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
    for (auto& watcher : mWatchers) {
      if (watcher->CallbackMethodIs(aMethod)) {
        return watcher;
      }
    }
    return nullptr;
  }

  PerCallbackWatcher& EnsureWatcher(CallbackMethod aMethod) {
    MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
    PerCallbackWatcher* watcher = GetWatcher(aMethod);
    if (watcher) {
      return *watcher;
    }
    watcher = mWatchers
                  .AppendElement(MakeAndAddRef(
                      mOwner, mOwnerThread, aMethod))
                  ->get();
    return *watcher;
  }

  nsTArray> mWatchers;
  OwnerType* mOwner;
  RefPtr mOwnerThread;
};

#  undef WATCH_LOG

}  // namespace mozilla

Minimal test - lines (71, 81)

path: .spaces[0].spaces[0].metrics.mi.mi_original
old: 106.6746999031816
new: 104.97484534296882

path: .spaces[0].spaces[0].metrics.mi.mi_sei
old: 78.30002840603022
new: 76.15311623999212

path: .spaces[0].spaces[0].metrics.mi.mi_visual_studio
old: 62.38286544045708
new: 61.388798446180594

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

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

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

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

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

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

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

path: .spaces[0].spaces[0].metrics.halstead.level
old: 0.25
new: 0.14285714285714285

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

path: .spaces[0].spaces[0].metrics.halstead.bugs
old: 0.026066162105227123
new: 0.03534764658819855

path: .spaces[0].spaces[0].metrics.halstead.effort
old: 691.508495181978
new: 1092.0

path: .spaces[0].spaces[0].metrics.halstead.estimated_program_length
old: 67.01955000865388
new: 48.0

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

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

path: .spaces[0].spaces[0].metrics.halstead.N1
old: 28.0
new: 25.0

path: .spaces[0].spaces[0].metrics.halstead.volume
old: 172.8771237954945
new: 156.0

path: .spaces[0].spaces[0].metrics.halstead.difficulty
old: 4.0
new: 7.0

path: .spaces[0].spaces[0].metrics.halstead.length
old: 40.0
new: 39.0

path: .spaces[0].spaces[0].metrics.halstead.purity_ratio
old: 1.675488750216347
new: 1.2307692307692308

path: .spaces[0].spaces[0].metrics.halstead.time
old: 38.417138621221
new: 60.666666666666664

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

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

Code

class AbstractWatcher {
 public:
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractWatcher)
  AbstractWatcher() : mDestroyed(false) {}
  bool IsDestroyed() { return mDestroyed; }
  virtual void Notify() = 0;

 protected:
  virtual ~AbstractWatcher() { MOZ_ASSERT(mDestroyed); }
  bool mDestroyed;
};

Minimal test - lines (175, 296)

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

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

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

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

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

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

path: .spaces[0].spaces[3].metrics.cyclomatic.sum
old: 1.0
new: 23.0

path: .spaces[0].spaces[3].metrics.mi.mi_sei
old: 102.58917596407876
new: 9.3912101094776

path: .spaces[0].spaces[3].metrics.mi.mi_original
old: 123.51065405124744
new: 46.66596931013636

path: .spaces[0].spaces[3].metrics.mi.mi_visual_studio
old: 72.2284526615482
new: 27.290040532243484

path: .spaces[0].spaces[3].metrics.loc.ploc
old: 5.0
new: 102.0

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

path: .spaces[0].spaces[3].metrics.loc.blank
old: 0.0
new: 15.0

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

path: .spaces[0].spaces[3].metrics.loc.sloc
old: 5.0
new: 122.0

path: .spaces[0].spaces[3].metrics.cognitive.sum
old: 0.0
new: 6.0

path: .spaces[0].spaces[3].metrics.cognitive.average
old: 0.0
new: 0.4

path: .spaces[0].spaces[3].metrics.halstead.volume
old: 58.81033751683405
new: 2770.296325647598

path: .spaces[0].spaces[3].metrics.halstead.purity_ratio
old: 1.6265579090825428
new: 0.8469939979927766

path: .spaces[0].spaces[3].metrics.halstead.N2
old: 5.0
new: 186.0

path: .spaces[0].spaces[3].metrics.halstead.vocabulary
old: 11.0
new: 72.0

path: .spaces[0].spaces[3].metrics.halstead.difficulty
old: 4.375
new: 40.92

path: .spaces[0].spaces[3].metrics.halstead.length
old: 17.0
new: 449.0

path: .spaces[0].spaces[3].metrics.halstead.level
old: 0.22857142857142856
new: 0.02443792766373411

path: .spaces[0].spaces[3].metrics.halstead.n1
old: 7.0
new: 22.0

path: .spaces[0].spaces[3].metrics.halstead.N1
old: 12.0
new: 263.0

path: .spaces[0].spaces[3].metrics.halstead.bugs
old: 0.013484449818562038
new: 0.7807643576375038

path: .spaces[0].spaces[3].metrics.halstead.n2
old: 4.0
new: 50.0

path: .spaces[0].spaces[3].metrics.halstead.effort
old: 257.295226636149
new: 113360.52564549971

path: .spaces[0].spaces[3].metrics.halstead.estimated_program_length
old: 27.651484454403228
new: 380.3003050987567

path: .spaces[0].spaces[3].metrics.halstead.time
old: 14.294179257563831
new: 6297.8069803055405

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

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

Code

class WatchManager {
 public:
  typedef void (OwnerType::*CallbackMethod)();
  explicit WatchManager(OwnerType* aOwner, AbstractThread* aOwnerThread)
      : mOwner(aOwner), mOwnerThread(aOwnerThread) {}

  ~WatchManager() {
    if (!IsShutdown()) {
      Shutdown();
    }
  }

  bool IsShutdown() const { return !mOwner; }

  // Shutdown needs to happen on mOwnerThread. If the WatchManager will be
  // destroyed on a different thread, Shutdown() must be called manually.
  void Shutdown() {
    MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
    for (auto& watcher : mWatchers) {
      watcher->Destroy();
    }
    mWatchers.Clear();
    mOwner = nullptr;
  }

  void Watch(WatchTarget& aTarget, CallbackMethod aMethod) {
    MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
    aTarget.AddWatcher(&EnsureWatcher(aMethod));
  }

  void Unwatch(WatchTarget& aTarget, CallbackMethod aMethod) {
    MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
    PerCallbackWatcher* watcher = GetWatcher(aMethod);
    MOZ_ASSERT(watcher);
    aTarget.RemoveWatcher(watcher);
  }

  void ManualNotify(CallbackMethod aMethod) {
    MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
    PerCallbackWatcher* watcher = GetWatcher(aMethod);
    MOZ_ASSERT(watcher);
    watcher->Notify();
  }

 private:
  class PerCallbackWatcher : public AbstractWatcher {
   public:
    PerCallbackWatcher(OwnerType* aOwner, AbstractThread* aOwnerThread,
                       CallbackMethod aMethod)
        : mOwner(aOwner),
          mOwnerThread(aOwnerThread),
          mCallbackMethod(aMethod) {}

    void Destroy() {
      MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
      mDestroyed = true;
      mOwner = nullptr;
    }

    void Notify() override {
      MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
      MOZ_DIAGNOSTIC_ASSERT(mOwner,
                            "mOwner is only null after destruction, "
                            "at which point we shouldn't be notified");
      if (mNotificationPending) {
        // We've already got a notification job in the pipe.
        return;
      }
      mNotificationPending = true;

      // Queue up our notification jobs to run in a stable state.
      AbstractThread::DispatchDirectTask(
          NS_NewRunnableFunction("WatchManager::PerCallbackWatcher::Notify",
                                 [self = RefPtr(this),
                                  owner = RefPtr(mOwner)]() {
                                   if (!self->mDestroyed) {
                                     ((*owner).*(self->mCallbackMethod))();
                                   }
                                   self->mNotificationPending = false;
                                 }));
    }

    bool CallbackMethodIs(CallbackMethod aMethod) const {
      return mCallbackMethod == aMethod;
    }

   private:
    ~PerCallbackWatcher() = default;

    OwnerType* mOwner;  // Never null.
    bool mNotificationPending = false;
    RefPtr mOwnerThread;
    CallbackMethod mCallbackMethod;
  };

  PerCallbackWatcher* GetWatcher(CallbackMethod aMethod) {
    MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
    for (auto& watcher : mWatchers) {
      if (watcher->CallbackMethodIs(aMethod)) {
        return watcher;
      }
    }
    return nullptr;
  }

  PerCallbackWatcher& EnsureWatcher(CallbackMethod aMethod) {
    MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
    PerCallbackWatcher* watcher = GetWatcher(aMethod);
    if (watcher) {
      return *watcher;
    }
    watcher = mWatchers
                  .AppendElement(MakeAndAddRef(
                      mOwner, mOwnerThread, aMethod))
                  ->get();
    return *watcher;
  }

  nsTArray> mWatchers;
  OwnerType* mOwner;
  RefPtr mOwnerThread;
};

Minimal test - lines (134, 155)

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

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

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

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

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

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

path: .spaces[0].spaces[2].metrics.halstead.purity_ratio
old: 1.8823529411764703
new: 0.97981924439407

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

path: .spaces[0].spaces[2].metrics.halstead.effort
old: 304.72181256129824
new: 7565.43119340755

path: .spaces[0].spaces[2].metrics.halstead.bugs
old: 0.015094350855465146
new: 0.1284599856398078

path: .spaces[0].spaces[2].metrics.halstead.volume
old: 60.94436251225965
new: 375.45564235273207

path: .spaces[0].spaces[2].metrics.halstead.n1
old: 8.0
new: 13.0

path: .spaces[0].spaces[2].metrics.halstead.N1
old: 12.0
new: 52.0

path: .spaces[0].spaces[2].metrics.halstead.N2
old: 5.0
new: 31.0

path: .spaces[0].spaces[2].metrics.halstead.estimated_program_length
old: 32.0
new: 81.32499728470782

path: .spaces[0].spaces[2].metrics.halstead.length
old: 17.0
new: 83.0

path: .spaces[0].spaces[2].metrics.halstead.level
old: 0.2
new: 0.04962779156327544

path: .spaces[0].spaces[2].metrics.halstead.difficulty
old: 5.0
new: 20.15

path: .spaces[0].spaces[2].metrics.halstead.time
old: 16.92898958673879
new: 420.30173296708614

path: .spaces[0].spaces[2].metrics.halstead.vocabulary
old: 12.0
new: 23.0

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

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

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

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

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

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

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

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

path: .spaces[0].spaces[2].metrics.mi.mi_sei
old: 107.53701147893022
new: 52.444214732937326

path: .spaces[0].spaces[2].metrics.mi.mi_visual_studio
old: 74.23405396973165
new: 51.61332310835995

path: .spaces[0].spaces[2].metrics.mi.mi_original
old: 126.9402322882411
new: 88.25878251529551

Code

class Watchable : public WatchTarget {
 public:
  Watchable(const T& aInitialValue, const char* aName)
      : WatchTarget(aName), mValue(aInitialValue) {}

  const T& Ref() const { return mValue; }
  operator const T&() const { return Ref(); }
  Watchable& operator=(const T& aNewValue) {
    if (aNewValue != mValue) {
      mValue = aNewValue;
      NotifyWatchers();
    }

    return *this;
  }

 private:
  Watchable(const Watchable& aOther) = delete;
  Watchable& operator=(const Watchable& aOther) = delete;

  T mValue;
};

Minimal test - lines (91, 128)

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

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

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

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

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

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

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

path: .spaces[0].spaces[1].metrics.cognitive.average
old: 0.0
new: 0.16666666666666666

path: .spaces[0].spaces[1].metrics.cyclomatic.sum
old: 1.0
new: 7.0

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

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

path: .spaces[0].spaces[1].metrics.halstead.N1
old: 10.0
new: 80.0

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

path: .spaces[0].spaces[1].metrics.halstead.purity_ratio
old: 1.8079610319175832
new: 1.4704234982333058

path: .spaces[0].spaces[1].metrics.halstead.time
old: 8.648579046593243
new: 705.4144181112728

path: .spaces[0].spaces[1].metrics.halstead.estimated_program_length
old: 27.11941547876375
new: 191.15505477032977

path: .spaces[0].spaces[1].metrics.halstead.n2
old: 5.0
new: 25.0

path: .spaces[0].spaces[1].metrics.halstead.volume
old: 51.89147427955946
new: 705.4144181112728

path: .spaces[0].spaces[1].metrics.halstead.bugs
old: 0.009646214643915437
new: 0.18142200824967217

path: .spaces[0].spaces[1].metrics.halstead.N2
old: 5.0
new: 50.0

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

path: .spaces[0].spaces[1].metrics.halstead.effort
old: 155.67442283867837
new: 12697.45952600291

path: .spaces[0].spaces[1].metrics.halstead.vocabulary
old: 11.0
new: 43.0

path: .spaces[0].spaces[1].metrics.halstead.length
old: 15.0
new: 130.0

path: .spaces[0].spaces[1].metrics.mi.mi_sei
old: 108.74338677859146
new: 59.25583590014251

path: .spaces[0].spaces[1].metrics.mi.mi_original
old: 127.77642792589845
new: 76.35541983490009

path: .spaces[0].spaces[1].metrics.mi.mi_visual_studio
old: 74.72305726660727
new: 44.65229230111117

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

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

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

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

path: .spaces[0].spaces[1].metrics.loc.sloc
old: 4.0
new: 38.0

Code

class WatchTarget {
 public:
  explicit WatchTarget(const char* aName) : mName(aName) {}

  void AddWatcher(AbstractWatcher* aWatcher) {
    MOZ_ASSERT(!mWatchers.Contains(aWatcher));
    mWatchers.AppendElement(aWatcher);
  }

  void RemoveWatcher(AbstractWatcher* aWatcher) {
    MOZ_ASSERT(mWatchers.Contains(aWatcher));
    mWatchers.RemoveElement(aWatcher);
  }

 protected:
  void NotifyWatchers() {
    WATCH_LOG("%s[%p] notifying watchers\n", mName, this);
    PruneWatchers();
    for (size_t i = 0; i < mWatchers.Length(); ++i) {
      mWatchers[i]->Notify();
    }
  }

 private:
  // We don't have Watchers explicitly unregister themselves when they die,
  // because then they'd need back-references to all the WatchTargets they're
  // subscribed to, and WatchTargets aren't reference-counted. So instead we
  // just prune dead ones at appropriate times, which works just fine.
  void PruneWatchers() {
    mWatchers.RemoveElementsBy(
        [](const auto& watcher) { return watcher->IsDestroyed(); });
  }

  nsTArray> mWatchers;

 protected:
  const char* mName;
};