mirror of
https://github.com/photonstorm/phaser
synced 2024-11-24 05:33:35 +00:00
Big refactoring to allow for multi-bindings from a single dispatcher.
This commit is contained in:
parent
f3f6c98896
commit
6aa620a853
4 changed files with 335 additions and 209 deletions
233
v3/src/events/EventBinding.js
Normal file
233
v3/src/events/EventBinding.js
Normal file
|
@ -0,0 +1,233 @@
|
||||||
|
var CONST = require('./const');
|
||||||
|
var EventListener = require('./EventListener');
|
||||||
|
|
||||||
|
var EventBinding = function (dispatcher, type)
|
||||||
|
{
|
||||||
|
this.dispatcher = dispatcher;
|
||||||
|
this.type = type;
|
||||||
|
this.state = CONST.DISPATCHER_IDLE;
|
||||||
|
this.active = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
EventBinding.prototype.constructor = EventBinding;
|
||||||
|
|
||||||
|
EventBinding.prototype = {
|
||||||
|
|
||||||
|
get: function (callback)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < this.active.length; i++)
|
||||||
|
{
|
||||||
|
if (this.active[i].callback === callback)
|
||||||
|
{
|
||||||
|
return this.active[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getIndex: function (callback)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < this.active.length; i++)
|
||||||
|
{
|
||||||
|
if (this.active[i].callback === callback)
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
has: function (callback)
|
||||||
|
{
|
||||||
|
return (this.get(callback));
|
||||||
|
},
|
||||||
|
|
||||||
|
add: function (callback, priority, once)
|
||||||
|
{
|
||||||
|
var listener = this.get(callback);
|
||||||
|
|
||||||
|
if (!listener)
|
||||||
|
{
|
||||||
|
// The listener doesn't exist, so create one
|
||||||
|
listener = EventListener(this.type, callback, priority, once);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Listener already exists, abort
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state === CONST.DISPATCHER_IDLE)
|
||||||
|
{
|
||||||
|
// The Dispatcher isn't doing anything, so we don't need a pending state
|
||||||
|
listener.state = CONST.LISTENER_ACTIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.active.push(listener);
|
||||||
|
|
||||||
|
this.active.sort(this.sortHandler);
|
||||||
|
},
|
||||||
|
|
||||||
|
sortHandler: function (listenerA, listenerB)
|
||||||
|
{
|
||||||
|
if (listenerB.priority < listenerA.priority)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else if (listenerB.priority > listenerA.priority)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
remove: function (callback)
|
||||||
|
{
|
||||||
|
if (this.state === CONST.DISPATCHER_IDLE)
|
||||||
|
{
|
||||||
|
// The Dispatcher isn't doing anything, so we can remove right away
|
||||||
|
var i = this.getIndex(callback);
|
||||||
|
|
||||||
|
if (i !== null)
|
||||||
|
{
|
||||||
|
this.active.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (this.state === CONST.DISPATCHER_DISPATCHING)
|
||||||
|
{
|
||||||
|
// The Dispatcher is working, so we flag the listener for removal at the end
|
||||||
|
var listener = this.get(callback);
|
||||||
|
|
||||||
|
if (listener)
|
||||||
|
{
|
||||||
|
listener.state = CONST.LISTENER_REMOVING;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
dispatch: function (event)
|
||||||
|
{
|
||||||
|
if (this.state !== CONST.DISPATCHER_IDLE)
|
||||||
|
{
|
||||||
|
throw new Error('Error: Failed to execute \'EventDispatcher.dispatch\' on \'' + this.type + '\': The event is already being dispatched.');
|
||||||
|
}
|
||||||
|
else if (this.active.length === 0)
|
||||||
|
{
|
||||||
|
// This was a valid dispatch call, we just had nothing to do ...
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state = CONST.DISPATCHER_DISPATCHING;
|
||||||
|
|
||||||
|
console.log('Dispatching', this.active.length, 'listeners');
|
||||||
|
|
||||||
|
var listener;
|
||||||
|
|
||||||
|
event.reset(this.dispatcher);
|
||||||
|
|
||||||
|
for (var i = 0; i < this.active.length; i++)
|
||||||
|
{
|
||||||
|
listener = this.active[i];
|
||||||
|
|
||||||
|
if (listener.state !== CONST.LISTENER_ACTIVE)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
listener.callback.call(this.dispatcher, event);
|
||||||
|
|
||||||
|
// Has the callback changed the state of this binding?
|
||||||
|
if (this.state !== CONST.DISPATCHER_DISPATCHING)
|
||||||
|
{
|
||||||
|
// Yup! Let's break out
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Was it a 'once' listener?
|
||||||
|
if (listener.once)
|
||||||
|
{
|
||||||
|
listener.state = CONST.LISTENER_REMOVING;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has the event been halted by the callback?
|
||||||
|
if (!event._propagate)
|
||||||
|
{
|
||||||
|
// Break out, a listener has called Event.stopPropagation
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispatch over, or aborted
|
||||||
|
if (this.state === CONST.DISPATCHER_REMOVING)
|
||||||
|
{
|
||||||
|
this.removeAll();
|
||||||
|
}
|
||||||
|
else if (this.state === CONST.DISPATCHER_DESTROYED)
|
||||||
|
{
|
||||||
|
this.dispatcher.delete(this.type);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// All done, just purge the list
|
||||||
|
this.tidy();
|
||||||
|
|
||||||
|
this.state = CONST.DISPATCHER_IDLE;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Removes all listeners
|
||||||
|
// If this is currently being dispatched then don't remove 'pending' listeners
|
||||||
|
// (i.e. ones that were added during the dispatch), only active ones
|
||||||
|
removeAll: function ()
|
||||||
|
{
|
||||||
|
if (this.state === CONST.DISPATCHER_IDLE)
|
||||||
|
{
|
||||||
|
this.active.length = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var i = this.active.length;
|
||||||
|
|
||||||
|
while (i--)
|
||||||
|
{
|
||||||
|
if (this.active[i].state !== CONST.LISTENER_PENDING)
|
||||||
|
{
|
||||||
|
this.active.slice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state = CONST.DISPATCHER_IDLE;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
tidy: function ()
|
||||||
|
{
|
||||||
|
var i = this.active.length;
|
||||||
|
|
||||||
|
while (i--)
|
||||||
|
{
|
||||||
|
if (this.active[i].state === CONST.LISTENER_REMOVING)
|
||||||
|
{
|
||||||
|
this.active.slice(i, 1);
|
||||||
|
}
|
||||||
|
else if (this.active[i].state === CONST.LISTENER_PENDING)
|
||||||
|
{
|
||||||
|
this.active[i].state === CONST.LISTENER_ACTIVE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy: function ()
|
||||||
|
{
|
||||||
|
this.active.length = 0;
|
||||||
|
this.dispatcher = undefined;
|
||||||
|
this.type = '';
|
||||||
|
this.state = CONST.DISPATCHER_DESTROYED;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = EventBinding;
|
|
@ -1,88 +1,42 @@
|
||||||
|
var EventBinding = require('./EventBinding');
|
||||||
|
|
||||||
var EventDispatcher = function ()
|
var EventDispatcher = function ()
|
||||||
{
|
{
|
||||||
this.listeners = {};
|
this.bindings = {};
|
||||||
|
|
||||||
this._state = EventDispatcher.STATE_PENDING;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
EventDispatcher.STATE_PENDING = 0;
|
|
||||||
EventDispatcher.STATE_DISPATCHING = 1;
|
|
||||||
EventDispatcher.STATE_REMOVING_ALL = 2;
|
|
||||||
EventDispatcher.STATE_DESTROYED = 3;
|
|
||||||
|
|
||||||
EventDispatcher.prototype.constructor = EventDispatcher;
|
EventDispatcher.prototype.constructor = EventDispatcher;
|
||||||
|
|
||||||
EventDispatcher.prototype = {
|
EventDispatcher.prototype = {
|
||||||
|
|
||||||
// Private
|
getBinding: function (type)
|
||||||
add: function (type, listener, priority, isOnce)
|
|
||||||
{
|
{
|
||||||
if (this.listeners === undefined)
|
if (this.bindings.hasOwnProperty(type))
|
||||||
{
|
{
|
||||||
// Has the EventDispatcher been destroyed?
|
return this.bindings[type];
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// console.log('Add listener', type, listener);
|
|
||||||
|
|
||||||
if (!this.listeners[type])
|
|
||||||
{
|
|
||||||
this.listeners[type] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
var listeners = this.listeners[type];
|
|
||||||
|
|
||||||
if (this.has(type, listener))
|
|
||||||
{
|
|
||||||
this.update(type, listener, priority, isOnce);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
listeners.push({ listener: listener, priority: priority, isOnce: isOnce, toRemove: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
listeners.sort(this.sortHandler);
|
|
||||||
},
|
|
||||||
|
|
||||||
sortHandler: function (listenerA, listenerB)
|
|
||||||
{
|
|
||||||
if (listenerB.priority < listenerA.priority)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
else if (listenerB.priority > listenerA.priority)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
update: function (type, listener, priority, isOnce)
|
createBinding: function (type)
|
||||||
{
|
{
|
||||||
var listeners = this.listeners[type];
|
if (!this.getBinding(type))
|
||||||
|
|
||||||
for (var i = 0; i < listeners.length; i++)
|
|
||||||
{
|
{
|
||||||
if (listeners[i].listener === listener)
|
this.bindings[type] = new EventBinding(this, type);
|
||||||
{
|
|
||||||
// They're trying to add the same listener again, so just update the priority + once
|
|
||||||
listeners[i].priority = priority;
|
|
||||||
listeners[i].isOnce = isOnce;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return this.bindings[type];
|
||||||
},
|
},
|
||||||
|
|
||||||
// Need to test what happens if array is sorted during DISPATCH phase (does it screw it all up?)
|
|
||||||
|
|
||||||
on: function (type, listener, priority)
|
on: function (type, listener, priority)
|
||||||
{
|
{
|
||||||
if (priority === undefined) { priority = 0; }
|
if (priority === undefined) { priority = 0; }
|
||||||
|
|
||||||
this.add(type, listener, priority, false);
|
var binding = this.createBinding(type);
|
||||||
|
|
||||||
|
if (binding)
|
||||||
|
{
|
||||||
|
binding.add(type, listener, priority, false);
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
@ -91,69 +45,39 @@ EventDispatcher.prototype = {
|
||||||
{
|
{
|
||||||
if (priority === undefined) { priority = 0; }
|
if (priority === undefined) { priority = 0; }
|
||||||
|
|
||||||
this.add(type, listener, priority, true);
|
var binding = this.createBinding(type);
|
||||||
|
|
||||||
|
if (binding)
|
||||||
|
{
|
||||||
|
binding.add(type, listener, priority, true);
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
total: function (type)
|
|
||||||
{
|
|
||||||
if (!this.listeners || !this.listeners[type])
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.listeners[type].length;
|
|
||||||
},
|
|
||||||
|
|
||||||
has: function (type, listener)
|
has: function (type, listener)
|
||||||
{
|
{
|
||||||
if (!this.listeners || !this.listeners[type])
|
var binding = this.getBinding(type);
|
||||||
|
|
||||||
|
if (binding)
|
||||||
|
{
|
||||||
|
return binding.has(listener);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var listeners = this.listeners[type];
|
|
||||||
|
|
||||||
for (var i = 0; i < listeners.length; i++)
|
|
||||||
{
|
|
||||||
if (listeners[i].listener === listener)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Removes an event listener.
|
// Removes an event listener.
|
||||||
// If there is no matching listener registered with the EventDispatcher, a call to this method has no effect.
|
// If there is no matching listener registered with the EventDispatcher, a call to this method has no effect.
|
||||||
off: function (type, listener)
|
off: function (type, listener)
|
||||||
{
|
{
|
||||||
if (!this.listeners || !this.listeners[type])
|
var binding = this.getBinding(type);
|
||||||
{
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
var listeners = this.listeners[type];
|
if (binding)
|
||||||
|
|
||||||
for (var i = 0; i < listeners.length; i++)
|
|
||||||
{
|
{
|
||||||
if (listeners[i].listener === listener)
|
binding.remove(listener);
|
||||||
{
|
|
||||||
if (this._state === EventDispatcher.STATE_DISPATCHING)
|
|
||||||
{
|
|
||||||
console.log('Flag listener for removal', type);
|
|
||||||
listeners[i].toRemove = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
console.log('Remove listener', type);
|
|
||||||
listeners.splice(i, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
@ -161,130 +85,71 @@ EventDispatcher.prototype = {
|
||||||
|
|
||||||
dispatch: function (event)
|
dispatch: function (event)
|
||||||
{
|
{
|
||||||
// Add in a dispatch lock, to stop the dispatcher from being invoked during an event callback
|
var binding;
|
||||||
|
|
||||||
if (this._state !== EventDispatcher.STATE_PENDING || !this.listeners[event.type])
|
if (Array.isArray(event))
|
||||||
{
|
{
|
||||||
return false;
|
for (var i = 0; i < event.length; i++)
|
||||||
}
|
|
||||||
|
|
||||||
var listeners = this.listeners[event.type];
|
|
||||||
|
|
||||||
// This was a valid dispatch call, we just had nothing to do ...
|
|
||||||
if (listeners.length === 0)
|
|
||||||
{
|
{
|
||||||
return true;
|
binding = this.getBinding(event[i].type);
|
||||||
}
|
|
||||||
|
|
||||||
this._state = EventDispatcher.STATE_DISPATCHING;
|
if (binding)
|
||||||
|
|
||||||
event.reset(this);
|
|
||||||
|
|
||||||
var toRemove = [];
|
|
||||||
|
|
||||||
var entry;
|
|
||||||
var entries = listeners.slice();
|
|
||||||
// var entries = listeners;
|
|
||||||
|
|
||||||
console.log('Dispatching', entries.length, 'listeners');
|
|
||||||
|
|
||||||
for (var i = 0; i < entries.length; i++)
|
|
||||||
{
|
{
|
||||||
entry = entries[i];
|
return binding.dispatch(event[i]);
|
||||||
|
|
||||||
if (entry.toRemove)
|
|
||||||
{
|
|
||||||
toRemove.push(entry);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add Custom Events
|
|
||||||
// If this adjusts the entries.length for any reason, the reference is still valid
|
|
||||||
entry.listener.call(this, event);
|
|
||||||
|
|
||||||
// Has the callback done something disastrous? Like called removeAll, or nuked the dispatcher?
|
|
||||||
if (this._state !== EventDispatcher.STATE_DISPATCHING)
|
|
||||||
{
|
|
||||||
// Yup! Let's get out of here ...
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Was a 'once' or was removed during the callback
|
|
||||||
if (entry.isOnce || entry.toRemove)
|
|
||||||
{
|
|
||||||
toRemove.push(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Has the event been halted?
|
|
||||||
if (!event._propagate)
|
|
||||||
{
|
|
||||||
// Break out, a listener has called Event.stopPropagation
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._state === EventDispatcher.STATE_REMOVING_ALL)
|
|
||||||
{
|
|
||||||
this.removeAll();
|
|
||||||
}
|
|
||||||
else if (this._state === EventDispatcher.STATE_DESTROYED)
|
|
||||||
{
|
|
||||||
this.destroy();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Anything in the toRemove list?
|
binding = this.getBinding(event.type);
|
||||||
|
|
||||||
console.log('Cleaning out', toRemove.length, 'listeners');
|
if (binding)
|
||||||
|
|
||||||
for (i = 0; i < toRemove.length; i++)
|
|
||||||
{
|
{
|
||||||
this.off(event.type, toRemove[i].listener);
|
return binding.dispatch(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
toRemove.length = 0;
|
|
||||||
|
|
||||||
this._state = EventDispatcher.STATE_PENDING;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Removes all listeners, but retains the event type entries
|
// Removes all listeners, but retains the event type entries
|
||||||
removeAll: function ()
|
removeAll: function (type)
|
||||||
{
|
{
|
||||||
if (this._state === EventDispatcher.STATE_DISPATCHING)
|
var binding = this.getBinding(type);
|
||||||
{
|
|
||||||
this._state = EventDispatcher.STATE_REMOVING_ALL;
|
|
||||||
|
|
||||||
return;
|
if (binding)
|
||||||
}
|
|
||||||
|
|
||||||
for (var eventType in this.listeners)
|
|
||||||
{
|
{
|
||||||
this.listeners[eventType].length = 0;
|
binding.removeAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
delete: function (type)
|
||||||
|
{
|
||||||
|
var binding = this.getBinding(type);
|
||||||
|
|
||||||
|
if (binding)
|
||||||
|
{
|
||||||
|
binding.destroy();
|
||||||
|
|
||||||
|
delete this.bindings[type];
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteAll: function ()
|
||||||
|
{
|
||||||
|
for (var binding in this.bindings)
|
||||||
|
{
|
||||||
|
binding.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bindings = {};
|
||||||
|
},
|
||||||
|
|
||||||
destroy: function ()
|
destroy: function ()
|
||||||
{
|
{
|
||||||
if (this._state === EventDispatcher.STATE_DISPATCHING)
|
// What would it do any differently to deleteAll?
|
||||||
{
|
|
||||||
this._state = EventDispatcher.STATE_DESTROYED;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var eventType in this.listeners)
|
|
||||||
{
|
|
||||||
this.listeners[eventType].length = 0;
|
|
||||||
|
|
||||||
delete this.listeners[eventType];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.listeners = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
14
v3/src/events/EventListener.js
Normal file
14
v3/src/events/EventListener.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
var CONST = require('./const');
|
||||||
|
|
||||||
|
var EventListener = function (type, callback, priority, once)
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
type: type,
|
||||||
|
callback: callback,
|
||||||
|
priority: priority,
|
||||||
|
once: once,
|
||||||
|
state: CONST.LISTENER_PENDING
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = EventListener;
|
14
v3/src/events/const.js
Normal file
14
v3/src/events/const.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
var EVENT_CONST = {
|
||||||
|
|
||||||
|
DISPATCHER_IDLE: 0,
|
||||||
|
DISPATCHER_DISPATCHING: 1,
|
||||||
|
DISPATCHER_REMOVING: 2,
|
||||||
|
DISPATCHER_DESTROYED: 3,
|
||||||
|
|
||||||
|
LISTENER_PENDING: 4,
|
||||||
|
LISTENER_ACTIVE: 5,
|
||||||
|
LISTENER_REMOVING: 6
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = EVENT_CONST;
|
Loading…
Reference in a new issue