{
  "version": 3,
  "sources": ["../../../../src/binding/internal/binding-impl.ts"],
  "sourcesContent": ["import type { DoubleLinkedListNode } from 'doublell';\nimport { DoubleLinkedList } from 'doublell';\n\nimport { areEqual as globalAreEqual } from '../../config/are-equal.mjs';\nimport { getLogger, isSpecialLoggingEnabledFor } from '../../config/logging.mjs';\nimport { getStatsHandler } from '../../config/stats-handler.mjs';\nimport { makeUID } from '../../internal-utils/uid.mjs';\nimport type { Binding } from '../types/binding';\nimport type { BindingConstructorArgs } from '../types/binding-args';\nimport type { BindingInitializer } from '../types/binding-initializer';\nimport type { ChangeListener } from '../types/change-listener';\nimport type { SetValueTransformer } from '../types/set-value-transformer';\nimport { LOCK_DURATION_WARNING_INTERVAL_MSEC } from './consts.mjs';\n\n/** A pending update on a binding */\ntype PendingUpdate<GetType = any> =\n  | { type: 'set-raw'; value: GetType }\n  | { type: 'set'; value: GetType }\n  | { type: 'reset'; value?: undefined };\n\n/** The standard implementation of a read-write binding */\nexport class BindingImpl<GetType = any> implements Binding<GetType> {\n  // Public Fields\n\n  public readonly isBinding = true;\n  public readonly id: string;\n  public readonly uid = makeUID();\n\n  public readonly setValueTransformer: SetValueTransformer<GetType> | undefined;\n\n  // Private Fields\n\n  /** The stored value of the binding */\n  private value_: GetType;\n\n  /** An ID updated every time the value is changed.  This value is unique to this runtime. */\n  private changeUid_ = this.uid; // Using the binding's uid as the initial change ID for convenience\n  /** Registered change listeners */\n  private onChangeListeners_?: DoubleLinkedList<ChangeListener>;\n\n  /**\n   * A flag indicating whether or not this binding was modified.  This is initially `false` when the binding is created and set to `true`\n   * when `set` is called (even if the underlying value doesn't actually change).\n   *\n   * This can also be set manually using `setIsModified`.\n   */\n  private isModified_ = false;\n\n  /** This binding is considered to be locked if `> 0` */\n  private lockedCount_ = 0;\n  /**\n   * We keep track of the most-recent mutating call made while this binding is locked.  When it becomes unlocked, we apply the requested\n   * change.\n   */\n  private pending_: PendingUpdate<GetType> | undefined = undefined;\n  /**\n   * In addition to tracking the most-recent mutating call made while this binding is locked, we also track if any of those mutating calls\n   * were resets.  If they were, when this binding becomes unlocked, we first reset and then apply the pending update.\n   */\n  private hasPendingReset_ = false;\n\n  /** The value equality checker function */\n  private areEqual_?: (a: GetType, b: GetType) => boolean;\n  /** If `true`, `areEqual_` (or the default) is used to determine if the value has changed */\n  private detectChanges_: boolean;\n\n  /** The initializer function, which can be used to reset the binding */\n  private initializer_: BindingInitializer<GetType>;\n\n  /**\n   * @param initializer - A function called to initialize this binding's value, which can also be called to reset its value.\n   * @param args - Additional arguments for configuring this binding.\n   */\n  constructor(initializer: BindingInitializer<GetType>, args: BindingConstructorArgs<GetType>) {\n    const theInitialValue = initializer(false);\n\n    this.id = args.id;\n    this.value_ = theInitialValue;\n\n    this.setValueTransformer = args.setValueTransformer;\n\n    this.initializer_ = initializer;\n\n    this.areEqual_ = args.areEqual;\n    this.detectChanges_ = args.detectChanges ?? false;\n  }\n\n  // Getters\n\n  public readonly get = () => this.value_;\n\n  public readonly getChangeUid = () => this.changeUid_;\n\n  // Setters\n\n  public readonly reset = () => {\n    if (this.lockedCount_ > 0) {\n      getLogger().debug?.(\n        `Attempted to change locked binding ${this.id}.  The most recently set value will be restored once this binding is unlocked`\n      );\n      this.pending_ = { type: 'reset' };\n      this.hasPendingReset_ = true;\n\n      return;\n    }\n\n    this.setRaw(this.initializer_(true));\n    this.setIsModified(false);\n  };\n\n  public readonly setRaw = (newValue: GetType) => {\n    if (this.lockedCount_ > 0) {\n      getLogger().debug?.(\n        `Attempted to change locked binding ${this.id}.  The most recently set value will be restored once this binding is unlocked`\n      );\n      this.pending_ = { type: 'set-raw', value: newValue };\n      return;\n    }\n\n    if (this.detectChanges_) {\n      const shouldChangeValue = !(this.areEqual_ ?? globalAreEqual)(this.value_, newValue);\n      if (!shouldChangeValue) {\n        return; // No change\n      }\n    }\n\n    const startMSec = performance.now();\n\n    this.value_ = newValue;\n    this.changeUid_ = makeUID();\n\n    const numListeners = this.triggerChangeListeners();\n\n    getStatsHandler().trackBindingDidSetRaw?.({\n      binding: this,\n      durationMSec: performance.now() - startMSec,\n      numListeners\n    });\n  };\n\n  public readonly set = (newValue: GetType) => {\n    if (this.lockedCount_ > 0) {\n      getLogger().debug?.(\n        `Attempted to change locked binding ${this.id}.  The most recently set value will be restored once this binding is unlocked`\n      );\n      this.pending_ = { type: 'set', value: newValue };\n      return;\n    }\n\n    this.isModified_ = true;\n    this.setRaw(this.setValueTransformer !== undefined ? this.setValueTransformer(newValue, this) : newValue);\n  };\n\n  // Modified\n\n  public readonly isModified = () => this.isModified_;\n\n  public readonly setIsModified = (newIsModified: boolean) => {\n    this.isModified_ = newIsModified;\n  };\n\n  // Locked\n\n  public readonly isLocked = () => this.lockedCount_ > 0;\n\n  public readonly lock = () => {\n    this.lockedCount_ += 1;\n\n    const shouldLogBindingLockDurationWarnings = isSpecialLoggingEnabledFor('binding-lock-duration-warnings');\n    const warningTimeout: ReturnType<typeof setTimeout> | undefined = shouldLogBindingLockDurationWarnings\n      ? setTimeout(() => {\n          getLogger().debug?.(\n            `A lock for binding ${this.id} wasn't released after more than ${LOCK_DURATION_WARNING_INTERVAL_MSEC / 1000} seconds`\n          );\n        }, LOCK_DURATION_WARNING_INTERVAL_MSEC)\n      : undefined;\n\n    let wasUnlocked = false;\n    return () => {\n      if (wasUnlocked) {\n        getLogger().debug?.(`A lock for binding ${this.id} was released more than once`);\n        return;\n      }\n      wasUnlocked = true;\n\n      if (warningTimeout !== undefined) {\n        clearTimeout(warningTimeout);\n      }\n\n      this.lockedCount_ -= 1;\n\n      // Restoring any pending values that were set while locked (mostly for bindings with memories)\n      if (this.lockedCount_ === 0 && this.pending_ !== undefined) {\n        const hasPendingReset = this.hasPendingReset_;\n        this.hasPendingReset_ = false;\n\n        const pending = this.pending_;\n        this.pending_ = undefined;\n\n        if (hasPendingReset) {\n          this.reset();\n        }\n\n        switch (pending.type) {\n          case 'set':\n            this.set(pending.value);\n            break;\n          case 'set-raw':\n            this.setRaw(pending.value);\n            break;\n          case 'reset':\n            // Nothing to do here, handled above\n            break;\n        }\n      }\n    };\n  };\n\n  // Change Listener\n\n  public readonly addChangeListener = (listener: ChangeListener) => {\n    getStatsHandler().trackBindingDidAddChangeListener?.({ binding: this });\n\n    if (this.onChangeListeners_ === undefined) {\n      this.onChangeListeners_ = new DoubleLinkedList<ChangeListener>();\n    }\n    let newNode: DoubleLinkedListNode<ChangeListener> | undefined = this.onChangeListeners_.append(listener);\n\n    // Returning function that can be used to remove the same change listener;\n    return () => {\n      if (newNode === undefined) {\n        getLogger().debug?.(`A change listener for binding ${this.id} was removed more than once`);\n        return;\n      }\n\n      getStatsHandler().trackBindingDidRemoveChangeListener?.({ binding: this });\n      this.onChangeListeners_!.remove(newNode);\n      newNode = undefined;\n    };\n  };\n\n  public readonly triggerChangeListeners = () => {\n    if (this.onChangeListeners_ === undefined) {\n      return 0;\n    }\n\n    const listeners = this.onChangeListeners_.toArray();\n    for (const listener of listeners) {\n      listener();\n    }\n\n    return listeners?.length;\n  };\n}\n"],
  "mappings": "AACA,SAAS,wBAAwB;AAEjC,SAAS,YAAY,sBAAsB;AAC3C,SAAS,WAAW,kCAAkC;AACtD,SAAS,uBAAuB;AAChC,SAAS,eAAe;AAMxB,SAAS,2CAA2C;AAS7C,MAAM,YAAuD;AAAA;AAAA;AAAA;AAAA;AAAA,EAoDlE,YAAY,aAA0C,MAAuC;AAjD7F;AAAA,SAAgB,YAAY;AAE5B,SAAgB,MAAM,QAAQ;AAU9B;AAAA,SAAQ,aAAa,KAAK;AAU1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAQ,cAAc;AAGtB;AAAA,SAAQ,eAAe;AAKvB;AAAA;AAAA;AAAA;AAAA,SAAQ,WAA+C;AAKvD;AAAA;AAAA;AAAA;AAAA,SAAQ,mBAAmB;AA8B3B;AAAA,SAAgB,MAAM,MAAM,KAAK;AAEjC,SAAgB,eAAe,MAAM,KAAK;AAI1C;AAAA,SAAgB,QAAQ,MAAM;AAC5B,UAAI,KAAK,eAAe,GAAG;AACzB,kBAAU,EAAE;AAAA,UACV,sCAAsC,KAAK,EAAE;AAAA,QAC/C;AACA,aAAK,WAAW,EAAE,MAAM,QAAQ;AAChC,aAAK,mBAAmB;AAExB;AAAA,MACF;AAEA,WAAK,OAAO,KAAK,aAAa,IAAI,CAAC;AACnC,WAAK,cAAc,KAAK;AAAA,IAC1B;AAEA,SAAgB,SAAS,CAAC,aAAsB;AAC9C,UAAI,KAAK,eAAe,GAAG;AACzB,kBAAU,EAAE;AAAA,UACV,sCAAsC,KAAK,EAAE;AAAA,QAC/C;AACA,aAAK,WAAW,EAAE,MAAM,WAAW,OAAO,SAAS;AACnD;AAAA,MACF;AAEA,UAAI,KAAK,gBAAgB;AACvB,cAAM,oBAAoB,EAAE,KAAK,aAAa,gBAAgB,KAAK,QAAQ,QAAQ;AACnF,YAAI,CAAC,mBAAmB;AACtB;AAAA,QACF;AAAA,MACF;AAEA,YAAM,YAAY,YAAY,IAAI;AAElC,WAAK,SAAS;AACd,WAAK,aAAa,QAAQ;AAE1B,YAAM,eAAe,KAAK,uBAAuB;AAEjD,sBAAgB,EAAE,wBAAwB;AAAA,QACxC,SAAS;AAAA,QACT,cAAc,YAAY,IAAI,IAAI;AAAA,QAClC;AAAA,MACF,CAAC;AAAA,IACH;AAEA,SAAgB,MAAM,CAAC,aAAsB;AAC3C,UAAI,KAAK,eAAe,GAAG;AACzB,kBAAU,EAAE;AAAA,UACV,sCAAsC,KAAK,EAAE;AAAA,QAC/C;AACA,aAAK,WAAW,EAAE,MAAM,OAAO,OAAO,SAAS;AAC/C;AAAA,MACF;AAEA,WAAK,cAAc;AACnB,WAAK,OAAO,KAAK,wBAAwB,SAAY,KAAK,oBAAoB,UAAU,IAAI,IAAI,QAAQ;AAAA,IAC1G;AAIA;AAAA,SAAgB,aAAa,MAAM,KAAK;AAExC,SAAgB,gBAAgB,CAAC,kBAA2B;AAC1D,WAAK,cAAc;AAAA,IACrB;AAIA;AAAA,SAAgB,WAAW,MAAM,KAAK,eAAe;AAErD,SAAgB,OAAO,MAAM;AAC3B,WAAK,gBAAgB;AAErB,YAAM,uCAAuC,2BAA2B,gCAAgC;AACxG,YAAM,iBAA4D,uCAC9D,WAAW,MAAM;AACf,kBAAU,EAAE;AAAA,UACV,sBAAsB,KAAK,EAAE,oCAAoC,sCAAsC,GAAI;AAAA,QAC7G;AAAA,MACF,GAAG,mCAAmC,IACtC;AAEJ,UAAI,cAAc;AAClB,aAAO,MAAM;AACX,YAAI,aAAa;AACf,oBAAU,EAAE,QAAQ,sBAAsB,KAAK,EAAE,8BAA8B;AAC/E;AAAA,QACF;AACA,sBAAc;AAEd,YAAI,mBAAmB,QAAW;AAChC,uBAAa,cAAc;AAAA,QAC7B;AAEA,aAAK,gBAAgB;AAGrB,YAAI,KAAK,iBAAiB,KAAK,KAAK,aAAa,QAAW;AAC1D,gBAAM,kBAAkB,KAAK;AAC7B,eAAK,mBAAmB;AAExB,gBAAM,UAAU,KAAK;AACrB,eAAK,WAAW;AAEhB,cAAI,iBAAiB;AACnB,iBAAK,MAAM;AAAA,UACb;AAEA,kBAAQ,QAAQ,MAAM;AAAA,YACpB,KAAK;AACH,mBAAK,IAAI,QAAQ,KAAK;AACtB;AAAA,YACF,KAAK;AACH,mBAAK,OAAO,QAAQ,KAAK;AACzB;AAAA,YACF,KAAK;AAEH;AAAA,UACJ;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAIA;AAAA,SAAgB,oBAAoB,CAAC,aAA6B;AAChE,sBAAgB,EAAE,mCAAmC,EAAE,SAAS,KAAK,CAAC;AAEtE,UAAI,KAAK,uBAAuB,QAAW;AACzC,aAAK,qBAAqB,IAAI,iBAAiC;AAAA,MACjE;AACA,UAAI,UAA4D,KAAK,mBAAmB,OAAO,QAAQ;AAGvG,aAAO,MAAM;AACX,YAAI,YAAY,QAAW;AACzB,oBAAU,EAAE,QAAQ,iCAAiC,KAAK,EAAE,6BAA6B;AACzF;AAAA,QACF;AAEA,wBAAgB,EAAE,sCAAsC,EAAE,SAAS,KAAK,CAAC;AACzE,aAAK,mBAAoB,OAAO,OAAO;AACvC,kBAAU;AAAA,MACZ;AAAA,IACF;AAEA,SAAgB,yBAAyB,MAAM;AAC7C,UAAI,KAAK,uBAAuB,QAAW;AACzC,eAAO;AAAA,MACT;AAEA,YAAM,YAAY,KAAK,mBAAmB,QAAQ;AAClD,iBAAW,YAAY,WAAW;AAChC,iBAAS;AAAA,MACX;AAEA,aAAO,WAAW;AAAA,IACpB;AAlLE,UAAM,kBAAkB,YAAY,KAAK;AAEzC,SAAK,KAAK,KAAK;AACf,SAAK,SAAS;AAEd,SAAK,sBAAsB,KAAK;AAEhC,SAAK,eAAe;AAEpB,SAAK,YAAY,KAAK;AACtB,SAAK,iBAAiB,KAAK,iBAAiB;AAAA,EAC9C;AAwKF;",
  "names": []
}
