mirror of
https://github.com/photonstorm/phaser
synced 2024-12-19 17:44:45 +00:00
426 lines
12 KiB
JavaScript
426 lines
12 KiB
JavaScript
/**
|
|
* @author Miller Medeiros http://millermedeiros.github.com/js-signals/
|
|
* @author Richard Davey <rich@photonstorm.com>
|
|
* @copyright 2014 Photon Storm Ltd.
|
|
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
|
|
*/
|
|
|
|
/**
|
|
* A Signal is an event dispatch mechansim than supports broadcasting to multiple listeners.
|
|
*
|
|
* Event listeners are uniquely identified by the listener/callback function and the context.
|
|
*
|
|
* @class Phaser.Signal
|
|
* @constructor
|
|
*/
|
|
Phaser.Signal = function () {
|
|
};
|
|
|
|
Phaser.Signal.prototype = {
|
|
|
|
/**
|
|
* @property {?Array.<Phaser.SignalBinding>} _bindings - Internal variable.
|
|
* @private
|
|
*/
|
|
_bindings: null,
|
|
|
|
/**
|
|
* @property {any} _prevParams - Internal variable.
|
|
* @private
|
|
*/
|
|
_prevParams: null,
|
|
|
|
/**
|
|
* Memorize the previously dispatched event?
|
|
*
|
|
* If an event has been memorized it is automatically dispatched when a new listener is added with {@link #add} or {@link #addOnce}.
|
|
* Use {@link #forget} to clear any currently memorized event.
|
|
*
|
|
* @property {boolean} memorize
|
|
*/
|
|
memorize: false,
|
|
|
|
/**
|
|
* @property {boolean} _shouldPropagate
|
|
* @private
|
|
*/
|
|
_shouldPropagate: true,
|
|
|
|
/**
|
|
* Is the Signal active? Only active signal will broadcast dispatched events.
|
|
*
|
|
* Setting this property during a dispatch will only affect the next dispatch. To stop the propagation of a signal from a listener use {@link #halt}.
|
|
*
|
|
* @property {boolean} active
|
|
* @default
|
|
*/
|
|
active: true,
|
|
|
|
/**
|
|
* @property {function} _boundDispatch - The bound dispatch function, if any.
|
|
* @private
|
|
*/
|
|
_boundDispatch: true,
|
|
|
|
/**
|
|
* @method Phaser.Signal#validateListener
|
|
* @param {function} listener - Signal handler function.
|
|
* @param {string} fnName - Function name.
|
|
* @private
|
|
*/
|
|
validateListener: function (listener, fnName) {
|
|
|
|
if (typeof listener !== 'function')
|
|
{
|
|
throw new Error('Phaser.Signal: listener is a required param of {fn}() and should be a Function.'.replace('{fn}', fnName));
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
* @method Phaser.Signal#_registerListener
|
|
* @private
|
|
* @param {function} listener - Signal handler function.
|
|
* @param {boolean} isOnce - Should the listener only be called once?
|
|
* @param {object} [listenerContext] - The context under which the listener is invoked.
|
|
* @param {number} [priority] - The priority level of the event listener. Listeners with higher priority will be executed before listeners with lower priority. Listeners with same priority level will be executed at the same order as they were added. (default = 0).
|
|
* @return {Phaser.SignalBinding} An Object representing the binding between the Signal and listener.
|
|
*/
|
|
_registerListener: function (listener, isOnce, listenerContext, priority) {
|
|
|
|
var prevIndex = this._indexOfListener(listener, listenerContext);
|
|
var binding;
|
|
|
|
if (prevIndex !== -1)
|
|
{
|
|
binding = this._bindings[prevIndex];
|
|
|
|
if (binding.isOnce() !== isOnce)
|
|
{
|
|
throw new Error('You cannot add' + (isOnce ? '' : 'Once') + '() then add' + (!isOnce ? '' : 'Once') + '() the same listener without removing the relationship first.');
|
|
}
|
|
}
|
|
else
|
|
{
|
|
binding = new Phaser.SignalBinding(this, listener, isOnce, listenerContext, priority);
|
|
this._addBinding(binding);
|
|
}
|
|
|
|
if (this.memorize && this._prevParams)
|
|
{
|
|
binding.execute(this._prevParams);
|
|
}
|
|
|
|
return binding;
|
|
|
|
},
|
|
|
|
/**
|
|
* @method Phaser.Signal#_addBinding
|
|
* @private
|
|
* @param {Phaser.SignalBinding} binding - An Object representing the binding between the Signal and listener.
|
|
*/
|
|
_addBinding: function (binding) {
|
|
|
|
if (!this._bindings)
|
|
{
|
|
this._bindings = [];
|
|
}
|
|
|
|
// Simplified insertion sort
|
|
var n = this._bindings.length;
|
|
|
|
do {
|
|
n--;
|
|
}
|
|
while (this._bindings[n] && binding._priority <= this._bindings[n]._priority);
|
|
|
|
this._bindings.splice(n + 1, 0, binding);
|
|
|
|
},
|
|
|
|
/**
|
|
* @method Phaser.Signal#_indexOfListener
|
|
* @private
|
|
* @param {function} listener - Signal handler function.
|
|
* @param {object} [context=null] - Signal handler function.
|
|
* @return {number} The index of the listener within the private bindings array.
|
|
*/
|
|
_indexOfListener: function (listener, context) {
|
|
|
|
if (!this._bindings)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
if (typeof context === 'undefined') { context = null; }
|
|
|
|
var n = this._bindings.length;
|
|
var cur;
|
|
|
|
while (n--)
|
|
{
|
|
cur = this._bindings[n];
|
|
|
|
if (cur._listener === listener && cur.context === context)
|
|
{
|
|
return n;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
|
|
},
|
|
|
|
/**
|
|
* Check if a specific listener is attached.
|
|
*
|
|
* @method Phaser.Signal#has
|
|
* @param {function} listener - Signal handler function.
|
|
* @param {object} [context] - Context on which listener will be executed (object that should represent the `this` variable inside listener function).
|
|
* @return {boolean} If Signal has the specified listener.
|
|
*/
|
|
has: function (listener, context) {
|
|
|
|
return this._indexOfListener(listener, context) !== -1;
|
|
|
|
},
|
|
|
|
/**
|
|
* Add an event listener.
|
|
*
|
|
* @method Phaser.Signal#add
|
|
* @param {function} listener - The function to call when this Signal is dispatched.
|
|
* @param {object} [listenerContext] - The context under which the listener will be executed (i.e. the object that should represent the `this` variable).
|
|
* @param {number} [priority] - The priority level of the event listener. Listeners with higher priority will be executed before listeners with lower priority. Listeners with same priority level will be executed at the same order as they were added (default = 0)
|
|
* @return {Phaser.SignalBinding} An Object representing the binding between the Signal and listener.
|
|
*/
|
|
add: function (listener, listenerContext, priority) {
|
|
|
|
this.validateListener(listener, 'add');
|
|
|
|
return this._registerListener(listener, false, listenerContext, priority);
|
|
|
|
},
|
|
|
|
/**
|
|
* Add a one-time listener - the listener is automatically removed after the first execution.
|
|
*
|
|
* If there is as {@link Phaser.Signal#memorize memorized} event then it will be dispatched and
|
|
* the listener will be removed immediately.
|
|
*
|
|
* @method Phaser.Signal#addOnce
|
|
* @param {function} listener - The function to call when this Signal is dispatched.
|
|
* @param {object} [listenerContext] - The context under which the listener will be executed (i.e. the object that should represent the `this` variable).
|
|
* @param {number} [priority] - The priority level of the event listener. Listeners with higher priority will be executed before listeners with lower priority. Listeners with same priority level will be executed at the same order as they were added (default = 0)
|
|
* @return {Phaser.SignalBinding} An Object representing the binding between the Signal and listener.
|
|
*/
|
|
addOnce: function (listener, listenerContext, priority) {
|
|
|
|
this.validateListener(listener, 'addOnce');
|
|
|
|
return this._registerListener(listener, true, listenerContext, priority);
|
|
|
|
},
|
|
|
|
/**
|
|
* Remove a single event listener.
|
|
*
|
|
* @method Phaser.Signal#remove
|
|
* @param {function} listener - Handler function that should be removed.
|
|
* @param {object} [context=null] - Execution context (since you can add the same handler multiple times if executing in a different context).
|
|
* @return {function} Listener handler function.
|
|
*/
|
|
remove: function (listener, context) {
|
|
|
|
this.validateListener(listener, 'remove');
|
|
|
|
var i = this._indexOfListener(listener, context);
|
|
|
|
if (i !== -1)
|
|
{
|
|
this._bindings[i]._destroy(); //no reason to a Phaser.SignalBinding exist if it isn't attached to a signal
|
|
this._bindings.splice(i, 1);
|
|
}
|
|
|
|
return listener;
|
|
|
|
},
|
|
|
|
/**
|
|
* Remove all event listeners.
|
|
*
|
|
* @method Phaser.Signal#removeAll
|
|
* @param {object} [context=null] - If specified only listeners for the given context will be removed.
|
|
*/
|
|
removeAll: function (context) {
|
|
|
|
if (typeof context === 'undefined') { context = null; }
|
|
|
|
if (!this._bindings)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var n = this._bindings.length;
|
|
|
|
while (n--)
|
|
{
|
|
if (context)
|
|
{
|
|
if (this._bindings[n].context === context)
|
|
{
|
|
this._bindings[n]._destroy();
|
|
this._bindings.splice(n, 1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this._bindings[n]._destroy();
|
|
}
|
|
}
|
|
|
|
if (!context)
|
|
{
|
|
this._bindings.length = 0;
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
* Gets the total number of listeners attached to this Signal.
|
|
*
|
|
* @method Phaser.Signal#getNumListeners
|
|
* @return {integer} Number of listeners attached to the Signal.
|
|
*/
|
|
getNumListeners: function () {
|
|
|
|
return this._bindings ? this._bindings.length : 0;
|
|
|
|
},
|
|
|
|
/**
|
|
* Stop propagation of the event, blocking the dispatch to next listener on the queue.
|
|
*
|
|
* This should be called only during event dispatch as calling it before/after dispatch won't affect other broadcast.
|
|
* See {@link #active} to enable/disable the signal entirely.
|
|
*
|
|
* @method Phaser.Signal#halt
|
|
*/
|
|
halt: function () {
|
|
|
|
this._shouldPropagate = false;
|
|
|
|
},
|
|
|
|
/**
|
|
* Dispatch / broadcast the event to all listeners.
|
|
*
|
|
* To create an instance-bound dispatch for this Signal, use {@link #boundDispatch}.
|
|
*
|
|
* @method Phaser.Signal#dispatch
|
|
* @param {any} [params] - Parameters that should be passed to each handler.
|
|
*/
|
|
dispatch: function () {
|
|
|
|
if (!this.active || !this._bindings)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var paramsArr = Array.prototype.slice.call(arguments);
|
|
var n = this._bindings.length;
|
|
var bindings;
|
|
|
|
if (this.memorize)
|
|
{
|
|
this._prevParams = paramsArr;
|
|
}
|
|
|
|
if (!n)
|
|
{
|
|
// Should come after memorize
|
|
return;
|
|
}
|
|
|
|
bindings = this._bindings.slice(); //clone array in case add/remove items during dispatch
|
|
this._shouldPropagate = true; //in case `halt` was called before dispatch or during the previous dispatch.
|
|
|
|
//execute all callbacks until end of the list or until a callback returns `false` or stops propagation
|
|
//reverse loop since listeners with higher priority will be added at the end of the list
|
|
do {
|
|
n--;
|
|
}
|
|
while (bindings[n] && this._shouldPropagate && bindings[n].execute(paramsArr) !== false);
|
|
|
|
},
|
|
|
|
/**
|
|
* Forget the currently {@link Phaser.Signal#memorize memorized} event, if any.
|
|
*
|
|
* @method Phaser.Signal#forget
|
|
*/
|
|
forget: function() {
|
|
|
|
if (this._prevParams)
|
|
{
|
|
this._prevParams = null;
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
* Dispose the signal - no more events can be dispatched.
|
|
*
|
|
* This removes all event listeners and clears references to external objects.
|
|
* Calling methods on a disposed objects results in undefined behavior.
|
|
*
|
|
* @method Phaser.Signal#dispose
|
|
*/
|
|
dispose: function () {
|
|
|
|
this.removeAll();
|
|
|
|
this._bindings = null;
|
|
if (this._prevParams)
|
|
{
|
|
this._prevParams = null;
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
* A string representation of the object.
|
|
*
|
|
* @method Phaser.Signal#toString
|
|
* @return {string} String representation of the object.
|
|
*/
|
|
toString: function () {
|
|
|
|
return '[Phaser.Signal active:'+ this.active +' numListeners:'+ this.getNumListeners() +']';
|
|
|
|
}
|
|
|
|
};
|
|
|
|
/**
|
|
* Create a `dispatch` function that maintains a binding to the original Signal context.
|
|
*
|
|
* Use the resulting value if the dispatch function needs to be passed somewhere
|
|
* or called independently of the Signal object.
|
|
*
|
|
* @memberof Phaser.Signal
|
|
* @property {function} boundDispatch
|
|
*/
|
|
Object.defineProperty(Phaser.Signal.prototype, "boundDispatch", {
|
|
|
|
get: function () {
|
|
var _this = this;
|
|
return this._boundDispatch || (this._boundDispatch = function () {
|
|
return _this.dispatch.apply(_this, arguments);
|
|
});
|
|
}
|
|
|
|
});
|
|
|
|
Phaser.Signal.prototype.constructor = Phaser.Signal;
|