hook up events (fixes #44); improve singleton pattern (#79)

This commit is contained in:
Christopher Van 2014-10-02 23:47:28 -07:00
Родитель 9e0946df76
Коммит 2e8345db5f
3 изменённых файлов: 91 добавлений и 83 удалений

Просмотреть файл

@ -52,6 +52,8 @@ var paths = {
}
};
var jsHostGlobal = 'MobileGamepad';
function bundlify(files, standalone) {
return browserify({
@ -76,7 +78,7 @@ gulp.task('js-build', [
gulp.task('js-build-host', function () {
// Write to `src/js/host.bundle.js`.
return bundlify(paths.build.src.jsHost, 'gamepad')
return bundlify(paths.build.src.jsHost, jsHostGlobal)
.bundle()
.pipe(source(paths.build.dest.jsHost))
.pipe(gulp.dest(paths.build.dest.js));
@ -111,7 +113,7 @@ gulp.task('js-dist', [
gulp.task('js-dist-host-raw', ['js-build'], function () {
// // Write to `dist/js/gamepad-host.js`.
return bundlify(paths.build.src.jsHost, 'gamepad')
return bundlify(paths.build.src.jsHost, jsHostGlobal)
.bundle()
.pipe(source(paths.dist.raw.jsHost))
.pipe(gulp.dest(paths.dist.raw.js));
@ -127,7 +129,7 @@ gulp.task('js-dist-client-raw', function () {
gulp.task('js-dist-host-min', function () {
// Write to `dist/js/gamepad-host.min.js`.
var hostBundle = bundlify(paths.build.src.jsHost, 'gamepad');
var hostBundle = bundlify(paths.build.src.jsHost, jsHostGlobal);
// Let's uglify before we browserify.
hostBundle.transform({

Просмотреть файл

@ -8,7 +8,7 @@
<script src="../js/external/promise-1.0.0.js"></script>
<script src="../js/host.bundle.js"></script>
<script>
var pad = gamepad.init();
var pad = MobileGamepad.create();
pad.pair().then(function (controller) {
console.log('Connected to controller');

Просмотреть файл

@ -1,6 +1,8 @@
(function (window, document) {
'use strict';
var Emitter = require('events').EventEmitter;
var Plink = require('plink');
// var Promise = require('./external/promise-1.0.0.js'); // jshint ignore:line
@ -16,47 +18,54 @@ var warn = utils.warn;
utils.polyfill();
var _instance;
var GAMEPAD_DEFAULT_OPTIONS = {
// Which transport protocol to try first (choices: 'webrtc' or 'websocket').
protocol: 'webrtc'
};
var gamepad = {
get: function () {
return _instance;
},
init: function (protocol) {
return _instance || new Gamepad({
protocol: protocol
});
}
};
/**
* A library to control an HTML5 game using WebRTC or WebSocket.
*
* @param {String} emmiter Event emmiter.
* @param {String} opts Options for gamepad (e.g., protocol).
* @exports Gamepad
* @namespace Gamepad
*/
function Gamepad(opts) {
_instance = this;
if (!opts) {
opts = {};
function MobileGamepad(emitter, opts) {
if (!emitter) {
throw 'Emitter required!';
}
this.emitter = emitter;
// Set properties based on options passed in, using defaults if missing.
Object.keys(GAMEPAD_DEFAULT_OPTIONS).forEach(function (key) {
this[key] = key in opts ? opts[key] : GAMEPAD_DEFAULT_OPTIONS[key];
}.bind(this));
this.connected = false;
this.state = {};
}
/**
* A library to control an HTML5 game using WebRTC or WebSocket.
*
* @param {String} opts Options for gamepad (e.g., protocol).
* @memberOf Gamepad
*/
MobileGamepad.create = function (opts) {
if (!opts) {
opts = {};
}
var emitter = new Emitter();
return new MobileGamepad(emitter, opts);
};
/**
* Retrieve the version of the Gamepad.
*
@ -65,7 +74,7 @@ function Gamepad(opts) {
* @returns {String}
* @memberOf Gamepad
*/
Gamepad.prototype.version = gamepad.version = settings.VERSION;
MobileGamepad.prototype.version = settings.VERSION;
/**
@ -77,7 +86,7 @@ Gamepad.prototype.version = gamepad.version = settings.VERSION;
* @returns {String}
* @memberOf Gamepad
*/
Gamepad.prototype.getGamepadOrigin = gamepad.getGamepadOrigin = function () {
MobileGamepad.prototype.getGamepadOrigin = function () {
// This is a function instead of a property, because this element could be
// injected after this script is loaded but before the origin is retrieved.
var dataOrigin = document.querySelector('[data-gamepad-origin]');
@ -99,7 +108,7 @@ Gamepad.prototype.getGamepadOrigin = gamepad.getGamepadOrigin = function () {
* @returns {Promise}
* @memberOf Gamepad
*/
Gamepad.prototype._reverse_url = function (routeName) {
MobileGamepad.prototype._reverse_url = function (routeName) {
if (!(routeName in routes)) {
throw 'No route matches for that name: ' + routeName;
}
@ -121,7 +130,7 @@ Gamepad.prototype._reverse_url = function (routeName) {
* @returns {Promise}
* @memberOf Gamepad
*/
Gamepad.prototype._pair = function (peerKey) {
MobileGamepad.prototype._pair = function (peerKey) {
return new Promise(function (resolve, reject) {
if (!peerKey) {
peerKey = utils.getPeerKey(); // The host key.
@ -212,6 +221,7 @@ Gamepad.prototype._pair = function (peerKey) {
trace('[' + peer.address + '] Received new controller state ' +
'from peer: ' +
(typeof msg === 'object' ? JSON.stringify(msg) : msg));
// TODO: Emit new `statechange` event!
return this._updateState(msg.data);
default:
return warn('[' + peer.address + '] Received peer message of ' +
@ -268,7 +278,7 @@ Gamepad.prototype._pair = function (peerKey) {
* @returns {Promise}
* @memberOf Gamepad
*/
Gamepad.prototype.pair = function (peerKey) {
MobileGamepad.prototype.pair = function (peerKey) {
return new Promise(function (resolve, reject) {
trace('Pairing started');
@ -312,6 +322,7 @@ Gamepad.prototype.pair = function (peerKey) {
return this._pair(peerKey).then(function (peer) {
trace('[' + peer.address + '] Paired to controller');
this.connected = true;
}.bind(this)).then(function () {
this.modal.close();
}.bind(this)).catch(function (err) {
@ -327,20 +338,33 @@ Gamepad.prototype.pair = function (peerKey) {
* @private
* @memberOf Gamepad
*/
Gamepad.prototype._updateState = function (data) {
this.state = data;
MobileGamepad.prototype._updateState = function (data) {
Object.keys(data || {}).forEach(function (key) {
if (!this.state[key] && data[key]) {
// Button pushed.
this._emit('buttondown', key);
this._emit('buttondown.' + key, key);
this.emitter.emit('buttondown', key);
this.emitter.emit('buttondown.' + key, key); // TODO: Don't send two
// Button pressed.
this.emitter.emit('buttonpress', key);
this.emitter.emit('buttonpress.' + key, key);
// Button changed.
this.emitter.emit('buttonchange', key);
this.emitter.emit('buttonchange.' + key, true);
} else if (this.state[key] && !data[key]) {
// Button released.
this._emit('buttonup', key);
this._emit('buttonup.' + key, key);
this.emitter.emit('buttonup', key);
this.emitter.emit('buttonup.' + key, key);
// Button changed.
this.emitter.emit('buttonchange', key);
this.emitter.emit('buttonchange.' + key, false);
}
}.bind(this));
this.state = data;
this.emitter.emit('statechange');
};
@ -350,30 +374,13 @@ Gamepad.prototype._updateState = function (data) {
* @returns {Promise}
* @memberOf Gamepad
*/
Gamepad.prototype.hidePairingScreen = function () {
MobileGamepad.prototype.hidePairingScreen = function () {
this.modal.close();
};
/**
* Fire an internal event with given data.
*
* @private
* @method _fire
* @param {String} eventName Name of event to fire (e.g., `buttondown`).
* @param {*} data Data to pass to the listener.
*/
Gamepad.prototype._emit = function (eventName, data) {
// TODO: Handle proper event emission (#44).
trace('Emit "' + eventName + '": ' + data);
// (this.listeners[eventName] || []).forEach(function (listener) {
// listener.apply(listener, [data]);
// });
};
/**
* Bind a listener to a gamepad event.
* Bind an event listener listener to a gamepad event.
*
* @private
* @method bind
@ -381,57 +388,56 @@ Gamepad.prototype._emit = function (eventName, data) {
* @param {Function} listener Listener to call when given event occurs.
* @return {Gamepad} Self
*/
Gamepad.prototype._bind = function (eventName, listener) {
if (typeof(this.listeners[event]) === 'undefined') {
this.listeners[event] = [];
}
this.listeners[event].push(listener);
MobileGamepad.prototype.on = function () {
this.emitter.on.apply(this.emitter, arguments);
return this;
};
MobileGamepad.prototype.addListener = MobileGamepad.prototype.on;
/**
* Remove listener of given type.
* Remove an event listener of a given type.
*
* If no type is given, all listeners are removed. If no listener is given, all
* listeners of given type are removed.
*
* @method unbind
* @param {String} [type] Type of listener to remove.
* @param {String} [eventName] Event name of listener to remove.
* @param {Function} [listener] (Optional) The listener function to remove.
* @return {Boolean} Was unbinding the listener successful.
*/
Gamepad.prototype.unbind = function (eventName, listener) {
// Remove everything for all event types.
if (typeof eventName === 'undefined') {
this.listeners = {};
return;
MobileGamepad.prototype.off = function () {
if (arguments.length < 2) {
// If no listener function is provided, remove all events of this type.
this.removeAllListeners.apply(this, arguments);
return this;
}
// Remove all listener functions for that event type.
if (typeof listener === 'undefined') {
this.listeners[eventName] = [];
return;
}
this.emitter.removeListener.apply(this.emitter, arguments);
return this;
};
if (typeof this.listeners[eventName] === 'undefined') {
return false;
}
MobileGamepad.prototype.removeListener = MobileGamepad.prototype.off;
this.listeners[eventName].forEach(function (value, idx) {
// Remove only the listener function passed to this method.
if (value === listener) {
this.listeners[eventName].splice(idx, 1);
return true;
}
});
return false;
/**
* Remove all event listeners.
*
* If no type is given, all listeners are removed. If no listener is given, all
* listeners of given type are removed.
*
* @method unbind
* @param {String} [eventName] Event name of listener to remove.
* @param {Function} [listener] (Optional) The listener function to remove.
* @return {Boolean} Was unbinding the listener successful.
*/
MobileGamepad.prototype.removeAllListeners = function () {
this.emitter.removeAllListeners.apply(this.emitter, arguments);
return this;
};
module.exports = gamepad;
module.exports = MobileGamepad;
})(window, document);