UNPKG

@cesium/engine

Version:

CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.

206 lines (183 loc) 5.47 kB
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) { if (!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()) { if (!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;