UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

268 lines (267 loc) 9.81 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); import { EventHandle } from "./event-handle.js"; class EventHandler { constructor() { /** * @type {Map<string,Array<EventHandle>>} * @private */ __publicField(this, "_callbacks", /* @__PURE__ */ new Map()); /** * @type {Map<string,Array<EventHandle>>} * @private */ __publicField(this, "_callbackActive", /* @__PURE__ */ new Map()); } /** * Reinitialize the event handler. * @ignore */ initEventHandler() { this._callbacks = /* @__PURE__ */ new Map(); this._callbackActive = /* @__PURE__ */ new Map(); } /** * Registers a new event handler. * * @param {string} name - Name of the event to bind the callback to. * @param {HandleEventCallback} callback - Function that is called when event is fired. Note * the callback is limited to 8 arguments. * @param {object} scope - Object to use as 'this' when the event is fired, defaults to * current this. * @param {boolean} once - If true, the callback will be unbound after being fired once. * @returns {EventHandle} Created {@link EventHandle}. * @ignore */ _addCallback(name, callback, scope, once) { if (!name || typeof name !== "string" || !callback) { console.warn(`EventHandler: subscribing to an event (${name}) with missing arguments`, callback); } if (!this._callbacks.has(name)) { this._callbacks.set(name, []); } if (this._callbackActive.has(name)) { const callbackActive = this._callbackActive.get(name); if (callbackActive && callbackActive === this._callbacks.get(name)) { this._callbackActive.set(name, callbackActive.slice()); } } const evt = new EventHandle(this, name, callback, scope, once); this._callbacks.get(name).push(evt); return evt; } /** * Attach an event handler to an event. * * @param {string} name - Name of the event to bind the callback to. * @param {HandleEventCallback} callback - Function that is called when event is fired. Note * the callback is limited to 8 arguments. * @param {object} [scope] - Object to use as 'this' when the event is fired, defaults to * current this. * @returns {EventHandle} Can be used for removing event in the future. * @example * obj.on('test', (a, b) => { * console.log(a + b); * }); * obj.fire('test', 1, 2); // prints 3 to the console * @example * const evt = obj.on('test', (a, b) => { * console.log(a + b); * }); * // some time later * evt.off(); */ on(name, callback, scope = this) { return this._addCallback(name, callback, scope, false); } /** * Attach an event handler to an event. This handler will be removed after being fired once. * * @param {string} name - Name of the event to bind the callback to. * @param {HandleEventCallback} callback - Function that is called when event is fired. Note * the callback is limited to 8 arguments. * @param {object} [scope] - Object to use as 'this' when the event is fired, defaults to * current this. * @returns {EventHandle} Can be used for removing event in the future. * @example * obj.once('test', (a, b) => { * console.log(a + b); * }); * obj.fire('test', 1, 2); // prints 3 to the console * obj.fire('test', 1, 2); // not going to get handled */ once(name, callback, scope = this) { return this._addCallback(name, callback, scope, true); } /** * Detach an event handler from an event. If callback is not provided then all callbacks are * unbound from the event, if scope is not provided then all events with the callback will be * unbound. * * @param {string} [name] - Name of the event to unbind. * @param {HandleEventCallback} [callback] - Function to be unbound. * @param {object} [scope] - Scope that was used as the this when the event is fired. * @returns {EventHandler} Self for chaining. * @example * const handler = () => {}; * obj.on('test', handler); * * obj.off(); // Removes all events * obj.off('test'); // Removes all events called 'test' * obj.off('test', handler); // Removes all handler functions, called 'test' * obj.off('test', handler, this); // Removes all handler functions, called 'test' with scope this */ off(name, callback, scope) { if (name) { if (this._callbackActive.has(name) && this._callbackActive.get(name) === this._callbacks.get(name)) { this._callbackActive.set(name, this._callbackActive.get(name).slice()); } } else { for (const [key, callbacks] of this._callbackActive) { if (!this._callbacks.has(key)) { continue; } if (this._callbacks.get(key) !== callbacks) { continue; } this._callbackActive.set(key, callbacks.slice()); } } if (!name) { for (const callbacks of this._callbacks.values()) { for (let i = 0; i < callbacks.length; i++) { callbacks[i].removed = true; } } this._callbacks.clear(); } else if (!callback) { const callbacks = this._callbacks.get(name); if (callbacks) { for (let i = 0; i < callbacks.length; i++) { callbacks[i].removed = true; } this._callbacks.delete(name); } } else { const callbacks = this._callbacks.get(name); if (!callbacks) { return this; } for (let i = 0; i < callbacks.length; i++) { if (callbacks[i].callback !== callback) { continue; } if (scope && callbacks[i].scope !== scope) { continue; } callbacks[i].removed = true; callbacks.splice(i, 1); i--; } if (callbacks.length === 0) { this._callbacks.delete(name); } } return this; } /** * Detach an event handler from an event using EventHandle instance. More optimal remove * as it does not have to scan callbacks array. * * @param {EventHandle} handle - Handle of event. * @ignore */ offByHandle(handle) { const name = handle.name; handle.removed = true; if (this._callbackActive.has(name) && this._callbackActive.get(name) === this._callbacks.get(name)) { this._callbackActive.set(name, this._callbackActive.get(name).slice()); } const callbacks = this._callbacks.get(name); if (!callbacks) { return this; } const ind = callbacks.indexOf(handle); if (ind !== -1) { callbacks.splice(ind, 1); if (callbacks.length === 0) { this._callbacks.delete(name); } } return this; } /** * Fire an event, all additional arguments are passed on to the event listener. * * @param {string} name - Name of event to fire. * @param {any} [arg1] - First argument that is passed to the event handler. * @param {any} [arg2] - Second argument that is passed to the event handler. * @param {any} [arg3] - Third argument that is passed to the event handler. * @param {any} [arg4] - Fourth argument that is passed to the event handler. * @param {any} [arg5] - Fifth argument that is passed to the event handler. * @param {any} [arg6] - Sixth argument that is passed to the event handler. * @param {any} [arg7] - Seventh argument that is passed to the event handler. * @param {any} [arg8] - Eighth argument that is passed to the event handler. * @returns {EventHandler} Self for chaining. * @example * obj.fire('test', 'This is the message'); */ fire(name, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) { if (!name) { return this; } const callbacksInitial = this._callbacks.get(name); if (!callbacksInitial) { return this; } let callbacks; if (!this._callbackActive.has(name)) { this._callbackActive.set(name, callbacksInitial); } else if (this._callbackActive.get(name) !== callbacksInitial) { callbacks = callbacksInitial.slice(); } for (let i = 0; (callbacks || this._callbackActive.get(name)) && i < (callbacks || this._callbackActive.get(name)).length; i++) { const evt = (callbacks || this._callbackActive.get(name))[i]; if (!evt.callback) continue; evt.callback.call(evt.scope, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); if (evt._once) { const existingCallback = this._callbacks.get(name); const ind = existingCallback ? existingCallback.indexOf(evt) : -1; if (ind !== -1) { if (this._callbackActive.get(name) === existingCallback) { this._callbackActive.set(name, this._callbackActive.get(name).slice()); } const callbacks2 = this._callbacks.get(name); if (!callbacks2) continue; callbacks2[ind].removed = true; callbacks2.splice(ind, 1); if (callbacks2.length === 0) { this._callbacks.delete(name); } } } } if (!callbacks) { this._callbackActive.delete(name); } return this; } /** * Test if there are any handlers bound to an event name. * * @param {string} name - The name of the event to test. * @returns {boolean} True if the object has handlers bound to the specified event name. * @example * obj.on('test', () => {}); // bind an event to 'test' * obj.hasEvent('test'); // returns true * obj.hasEvent('hello'); // returns false */ hasEvent(name) { return !!this._callbacks.get(name)?.length; } } export { EventHandler };