All files / engine/Source/Core Event.js

96.49% Statements 55/57
91.66% Branches 22/24
100% Functions 8/8
96.49% Lines 55/57

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206                                                        76802x         76802x         76802x 76802x 76802x     1x                 12109x                                   1x   40325x   40322x   40322x     40322x 40322x 40317x     40322x 16693x         40538x 39472x   40538x 40538x 40533x 40533x     5x                         1x   20968x     20966x           20966x             20966x 20966x 20963x     20966x       42132x 42132x 20969x     21163x 204x         20959x 20959x 20929x       21163x                     1x 118868x   118868x 76657x       76657x 77807x       118867x     118867x 12x 12x     118867x     118867x 200x 200x     118867x                  
import Check from "./Check.js";
import defined from "./defined.js";
 
/**
 * A generic utility class for managing subscribers for a particular event.
 * This class is usually instantiated inside of a container class and
 * exposed as a property for others to subscribe to.
 *
 * @alias Event
 * @template Listener extends (...args: any[]) => void = (...args: any[]) => void
 * @constructor
 * @example
 * MyObject.prototype.myListener = function(arg1, arg2) {
 *     this.myArg1Copy = arg1;
 *     this.myArg2Copy = arg2;
 * }
 *
 * const myObjectInstance = new MyObject();
 * const evt = new Cesium.Event();
 * evt.addEventListener(MyObject.prototype.myListener, myObjectInstance);
 * evt.raiseEvent('1', '2');
 * evt.removeEventListener(MyObject.prototype.myListener);
 */
function Event() {
  /**
   * @type {Map<Listener,Set<object>>}
   * @private
   */
  this._listeners = new Map();
  /**
   * @type {Map<Listener,Set<object>>}
   * @private
   */
  this._toRemove = new Map();
  /**
   * @type {Map<Listener,Set<object>>}
   * @private
   */
  this._toAdd = new Map();
  this._invokingListeners = false;
  this._listenerCount = 0; // Tracks number of listener + scope pairs
}
 
Object.defineProperties(Event.prototype, {
  /**
   * The number of listeners currently subscribed to the event.
   * @memberof Event.prototype
   * @type {number}
   * @readonly
   */
  numberOfListeners: {
    get: function () {
      return this._listenerCount;
    },
  },
});
 
/**
 * Registers a callback function to be executed whenever the event is raised.
 * An optional scope can be provided to serve as the <code>this</code> pointer
 * in which the function will execute.
 *
 * @param {Listener} listener The function to be executed when the event is raised.
 * @param {object} [scope] An optional object scope to serve as the <code>this</code>
 *        pointer in which the listener function will execute.
 * @returns {Event.RemoveCallback} A function that will remove this event listener when invoked.
 *
 * @see Event#raiseEvent
 * @see Event#removeEventListener
 */
Event.prototype.addEventListener = function (listener, scope) {
  //>>includeStart('debug', pragmas.debug);
  Check.typeOf.func("listener", listener);
  //>>includeEnd('debug');
  const event = this;
 
  const listenerMap = event._invokingListeners
    ? event._toAdd
    : event._listeners;
  const added = addEventListener(this, listenerMap, listener, scope);
  if (added) {
    event._listenerCount++;
  }
 
  return function () {
    event.removeEventListener(listener, scope);
  };
};
 
function addEventListener(event, listenerMap, listener, scope) {
  if (!listenerMap.has(listener)) {
    listenerMap.set(listener, new Set());
  }
  const scopes = listenerMap.get(listener);
  if (!scopes.has(scope)) {
    scopes.add(scope);
    return true;
  }
 
  return false;
}
 
/**
 * Unregisters a previously registered callback.
 *
 * @param {Listener} listener The function to be unregistered.
 * @param {object} [scope] The scope that was originally passed to addEventListener.
 * @returns {boolean} <code>true</code> if the listener was removed; <code>false</code> if the listener and scope are not registered with the event.
 *
 * @see Event#addEventListener
 * @see Event#raiseEvent
 */
Event.prototype.removeEventListener = function (listener, scope) {
  //>>includeStart('debug', pragmas.debug);
  Check.typeOf.func("listener", listener);
  //>>includeEnd('debug');
 
  const removedFromListeners = removeEventListener(
    this,
    this._listeners,
    listener,
    scope,
  );
  const removedFromToAdd = removeEventListener(
    this,
    this._toAdd,
    listener,
    scope,
  );
 
  const removed = removedFromListeners || removedFromToAdd;
  if (removed) {
    this._listenerCount--;
  }
 
  return removed;
};
 
function removeEventListener(event, listenerMap, listener, scope) {
  const scopes = listenerMap.get(listener);
  if (!scopes || !scopes.has(scope)) {
    return false;
  }
 
  if (event._invokingListeners) {
    Iif (!addEventListener(event, event._toRemove, listener, scope)) {
      // Already marked for removal
      return false;
    }
  } else {
    scopes.delete(scope);
    if (scopes.size === 0) {
      listenerMap.delete(listener);
    }
  }
 
  return true;
}
 
/**
 * Raises the event by calling each registered listener with all supplied arguments.
 *
 * @param {...Parameters<Listener>} arguments This method takes any number of parameters and passes them through to the listener functions.
 *
 * @see Event#addEventListener
 * @see Event#removeEventListener
 */
Event.prototype.raiseEvent = function () {
  this._invokingListeners = true;
 
  for (const [listener, scopes] of this._listeners.entries()) {
    Iif (!defined(listener)) {
      continue;
    }
 
    for (const scope of scopes) {
      listener.apply(scope, arguments);
    }
  }
 
  this._invokingListeners = false;
 
  // Actually add items marked for addition
  for (const [listener, scopes] of this._toAdd.entries()) {
    for (const scope of scopes) {
      addEventListener(this, this._listeners, listener, scope);
    }
  }
  this._toAdd.clear();
 
  // Actually remove items marked for removal
  for (const [listener, scopes] of this._toRemove.entries()) {
    for (const scope of scopes) {
      removeEventListener(this, this._listeners, listener, scope);
    }
  }
  this._toRemove.clear();
};
 
/**
 * A function that removes a listener.
 * @callback Event.RemoveCallback
 */
 
export default Event;