From 18c1382bf809eeb04bdd1921f7169681d217a351 Mon Sep 17 00:00:00 2001 From: Darren Date: Thu, 20 Nov 2014 00:49:29 +0000 Subject: [PATCH] Restoring PluginInterface away from Promises --- client/src/helpers/plugininterface.js | 317 +++++++++++++++---------- server/plugininterface.js | 318 ++++++++++++++++---------- 2 files changed, 403 insertions(+), 232 deletions(-) diff --git a/client/src/helpers/plugininterface.js b/client/src/helpers/plugininterface.js index 44f1598..c218d21 100644 --- a/client/src/helpers/plugininterface.js +++ b/client/src/helpers/plugininterface.js @@ -1,155 +1,242 @@ -function PluginInterface() { - this.listeners = []; +/* + * The same functionality as EventEmitter but with the inclusion of callbacks + */ + + + +function PluginInterface () { + // Holder for all the bound listeners by this module + this._listeners = {}; } -PluginInterface.prototype.on = PluginInterface.prototype.addListener = function addListener(type, listener) { - if (typeof listener !== 'function') { - throw new TypeError('listener must be a function'); - } - if (this.listeners[type]) { - if (!_.contains(this.listeners[type], listener)) { - this.listeners[type].push(listener); + +PluginInterface.prototype.on = function (event_name, fn, scope) { + this._listeners[event_name] = this._listeners[event_name] || []; + this._listeners[event_name].push(['on', fn, scope]); +}; + + + +PluginInterface.prototype.once = function (event_name, fn, scope) { + this._listeners[event_name] = this._listeners[event_name] || []; + this._listeners[event_name].push(['once', fn, scope]); +}; + + + +PluginInterface.prototype.off = function (event_name, fn, scope) { + var idx; + + if (typeof event_name === 'undefined') { + // Remove all listeners + this._listeners = {}; + + } else if (typeof fn === 'undefined') { + // Remove all of 1 event type + delete this._listeners[event_name]; + + } else if (typeof scope === 'undefined') { + // Remove a single event type + callback + for (idx in (this._listeners[event_name] || [])) { + if (this._listeners[event_name][idx][1] === fn) { + delete this._listeners[event_name][idx]; + } } } else { - this.listeners[type] = [listener]; + // Remove a single event type + callback + scope + for (idx in (this._listeners[event_name] || [])) { + if (this._listeners[event_name][idx][1] === fn && this._listeners[event_name][idx][2] === scope) { + delete this._listeners[event_name][idx]; + } + } } +}; + - return this; + +// Call all the listeners for a certain event, passing them some event data that may be changed +PluginInterface.prototype.emit = function (event_name, event_data) { + var emitter = new this.EmitCall(event_name, event_data); + var listeners = this._listeners[event_name] || []; + + // Once emitted, remove any 'once' bound listeners + emitter.then(function () { + var len = listeners.length, + idx; + + for(idx = 0; idx < len; idx++) { + if (listeners[idx][0] === 'once') { + listeners[idx] = undefined; + } + } + }); + + // Emit the event to the listeners and return + emitter.callListeners(listeners); + return emitter; }; -PluginInterface.prototype.once = function once(type, listener) { - if (typeof listener !== 'function') { - throw new TypeError('listener must be a function'); + + +// Promise style object to emit events to listeners +PluginInterface.prototype.EmitCall = function EmitCall (event_name, event_data) { + var that = this, + completed = false, + completed_fn = [], + + // Has event.preventDefault() been called + prevented = false, + prevented_fn = []; + + + // Emit this event to an array of listeners + function callListeners(listeners) { + var current_event_idx = -1; + + // Make sure we have some data to pass to the listeners + event_data = event_data || undefined; + + // If no bound listeners for this event, leave now + if (listeners.length === 0) { + emitComplete(); + return; + } + + + // Call the next listener in our array + function nextListener() { + var listener, event_obj; + + // We want the next listener + current_event_idx++; + + // If we've ran out of listeners end this emit call + if (!listeners[current_event_idx]) { + emitComplete(); + return; + } + + // Object the listener ammends to tell us what it's going to do + event_obj = { + // If changed to true, expect this listener is going to callback + wait: false, + + // If wait is true, this callback must be called to continue running listeners + callback: function () { + // Invalidate this callback incase a listener decides to call it again + event_obj.callback = undefined; + + nextListener.apply(that); + }, + + // Prevents the default 'done' functions from executing + preventDefault: function () { + prevented = true; + } + }; + + + listener = listeners[current_event_idx]; + listener[1].call(listener[2] || that, event_obj, event_data); + + // If the listener hasn't signalled it's going to wait, proceed to next listener + if (!event_obj.wait) { + // Invalidate the callback just incase a listener decides to call it anyway + event_obj.callback = undefined; + + nextListener(); + } + } + + nextListener(); } - var fired = false; - function g() { - this.removeListener(type, g); - if (!fired) { - fired = true; - listener.apply(this, arguments); + function emitComplete() { + completed = true; + + var funcs = prevented ? prevented_fn : completed_fn; + funcs = funcs || []; + + // Call the completed/prevented functions + for (var idx = 0; idx < funcs.length; idx++) { + if (typeof funcs[idx] === 'function') funcs[idx](); } } - g.listener = listener; - this.on(type, g); - return this; -}; -PluginInterface.prototype.off = PluginInterface.prototype.removeListener = function removeListener(type, listener) { - if (!this.listeners[type]) { + function addCompletedFunc(fn) { + // Only accept functions + if (typeof fn !== 'function') return false; + + completed_fn.push(fn); + + // If we have already completed the emits, call this now + if (completed && !prevented) fn(); + return this; } - this.listeners[type] = _.without(this.listeners[type], listener); - return this; -}; -PluginInterface.prototype.emit = function emit(type, data) { - var that = this; + function addPreventedFunc(fn) { + // Only accept functions + if (typeof fn !== 'function') return false; - return new Promise(function (emit_resolve, emit_reject) { - var rejected = false, - rejected_reasons = [], - listeners_promise; + prevented_fn.push(fn); - if (!that.listeners[type]) { - return emit_resolve(data); - } + // If we have already completed the emits, call this now + if (completed && prevented) fn(); - // Add each listener as a promise .then() - listeners_promise = that.listeners[type].reduce(function (listener_promise, listener) { - return listener_promise.then(function (data) { - return new Promise(function (resolve) { - var event_data = { - callback: function () { - resolve(data); - event_data.callback = null; - }, - preventDefault: function (reason) { - rejected = true; - if (reason) { - rejected_reasons.push(reason); - } - }, - wait: false - }; - - listener(event_data, data); - - // If the module has not specified that we should wait, callback now - if (!event_data.wait && event_data.callback) { - event_data.callback(); - } - }); - }); - }, Promise.resolve(data)); - - // After all the listeners have been called, resolve back with any modified data - listeners_promise.then(function (data) { - if (rejected) { - emit_reject({data: data, reasons: rejected_reasons}); - } else { - emit_resolve(data); - } - }); - }); + return this; + } + + + return { + callListeners: callListeners, + then: addCompletedFunc, + catch: addPreventedFunc + }; }; + + // If running a node module, set the exports if (typeof module === 'object' && typeof module.exports !== 'undefined') { module.exports = PluginInterface; } -/* Test cases -var p = new PluginInterface(); -p.on('test', function (event, data) { - data.a += '!'; -}); -p.on('test', function (event, data) { - data.wait = true; - setTimeout(function() { - data.a += '!'; - event.callback(); - }, 1000); -}); +/* + * Example usage + */ -p.emit('test', {a: 'hello world'}).then(function (data) { - if (data.a === 'hello world!') { - console.log('Test passed'); - } else { - console.error('Test failed!'); - } -}, function (err) { - console.error('Test failed!'); -}); +/* +var modules = new PluginInterface(); -var p = new PluginInterface(); -p.on('test', function (event, data) { - data.a += '!'; - event.callback(); -}); -p.on('test', function (event) { - event.preventDefault('testing'); - event.callback(); -}) - -p.emit('test', {a:'hello world'}).then(function (){ - console.error('Test failed!'); -}, function (data, reasons) { - if ((data.data.a === 'hello world!') && (data.reasons.length === 1 && data.reasons[0] === 'testing')) { - console.log('Test passed'); - } else { - console.error('Test failed!'); - } + + +// A plugin +modules.on('irc message', function (event, data) { + //event.wait = true; + setTimeout(event.callback, 2000); }); -*/ + + + +// Core code that is being extended by plugins +var data = { + nick: 'prawnsalald', + command: '/dothis' +}; + +modules.emit('irc message', data).done(function () { + console.log('Your command is: ' + data.command); +}); +*/ \ No newline at end of file diff --git a/server/plugininterface.js b/server/plugininterface.js index 9bfed5f..c218d21 100644 --- a/server/plugininterface.js +++ b/server/plugininterface.js @@ -1,158 +1,242 @@ -var _ = require('lodash'), - Promise = require('es6-promise').Promise; +/* + * The same functionality as EventEmitter but with the inclusion of callbacks + */ -function PluginInterface() { - this.listeners = []; + + +function PluginInterface () { + // Holder for all the bound listeners by this module + this._listeners = {}; } -PluginInterface.prototype.on = PluginInterface.prototype.addListener = function addListener(type, listener) { - if (typeof listener !== 'function') { - throw new TypeError('listener must be a function'); - } - if (this.listeners[type]) { - if (!_.contains(this.listeners[type], listener)) { - this.listeners[type].push(listener); + +PluginInterface.prototype.on = function (event_name, fn, scope) { + this._listeners[event_name] = this._listeners[event_name] || []; + this._listeners[event_name].push(['on', fn, scope]); +}; + + + +PluginInterface.prototype.once = function (event_name, fn, scope) { + this._listeners[event_name] = this._listeners[event_name] || []; + this._listeners[event_name].push(['once', fn, scope]); +}; + + + +PluginInterface.prototype.off = function (event_name, fn, scope) { + var idx; + + if (typeof event_name === 'undefined') { + // Remove all listeners + this._listeners = {}; + + } else if (typeof fn === 'undefined') { + // Remove all of 1 event type + delete this._listeners[event_name]; + + } else if (typeof scope === 'undefined') { + // Remove a single event type + callback + for (idx in (this._listeners[event_name] || [])) { + if (this._listeners[event_name][idx][1] === fn) { + delete this._listeners[event_name][idx]; + } } } else { - this.listeners[type] = [listener]; + // Remove a single event type + callback + scope + for (idx in (this._listeners[event_name] || [])) { + if (this._listeners[event_name][idx][1] === fn && this._listeners[event_name][idx][2] === scope) { + delete this._listeners[event_name][idx]; + } + } } +}; + + - return this; +// Call all the listeners for a certain event, passing them some event data that may be changed +PluginInterface.prototype.emit = function (event_name, event_data) { + var emitter = new this.EmitCall(event_name, event_data); + var listeners = this._listeners[event_name] || []; + + // Once emitted, remove any 'once' bound listeners + emitter.then(function () { + var len = listeners.length, + idx; + + for(idx = 0; idx < len; idx++) { + if (listeners[idx][0] === 'once') { + listeners[idx] = undefined; + } + } + }); + + // Emit the event to the listeners and return + emitter.callListeners(listeners); + return emitter; }; -PluginInterface.prototype.once = function once(type, listener) { - if (typeof listener !== 'function') { - throw new TypeError('listener must be a function'); + + +// Promise style object to emit events to listeners +PluginInterface.prototype.EmitCall = function EmitCall (event_name, event_data) { + var that = this, + completed = false, + completed_fn = [], + + // Has event.preventDefault() been called + prevented = false, + prevented_fn = []; + + + // Emit this event to an array of listeners + function callListeners(listeners) { + var current_event_idx = -1; + + // Make sure we have some data to pass to the listeners + event_data = event_data || undefined; + + // If no bound listeners for this event, leave now + if (listeners.length === 0) { + emitComplete(); + return; + } + + + // Call the next listener in our array + function nextListener() { + var listener, event_obj; + + // We want the next listener + current_event_idx++; + + // If we've ran out of listeners end this emit call + if (!listeners[current_event_idx]) { + emitComplete(); + return; + } + + // Object the listener ammends to tell us what it's going to do + event_obj = { + // If changed to true, expect this listener is going to callback + wait: false, + + // If wait is true, this callback must be called to continue running listeners + callback: function () { + // Invalidate this callback incase a listener decides to call it again + event_obj.callback = undefined; + + nextListener.apply(that); + }, + + // Prevents the default 'done' functions from executing + preventDefault: function () { + prevented = true; + } + }; + + + listener = listeners[current_event_idx]; + listener[1].call(listener[2] || that, event_obj, event_data); + + // If the listener hasn't signalled it's going to wait, proceed to next listener + if (!event_obj.wait) { + // Invalidate the callback just incase a listener decides to call it anyway + event_obj.callback = undefined; + + nextListener(); + } + } + + nextListener(); } - var fired = false; - function g() { - this.removeListener(type, g); - if (!fired) { - fired = true; - listener.apply(this, arguments); + function emitComplete() { + completed = true; + + var funcs = prevented ? prevented_fn : completed_fn; + funcs = funcs || []; + + // Call the completed/prevented functions + for (var idx = 0; idx < funcs.length; idx++) { + if (typeof funcs[idx] === 'function') funcs[idx](); } } - g.listener = listener; - this.on(type, g); - return this; -}; -PluginInterface.prototype.off = PluginInterface.prototype.removeListener = function removeListener(type, listener) { - if (!this.listeners[type]) { + function addCompletedFunc(fn) { + // Only accept functions + if (typeof fn !== 'function') return false; + + completed_fn.push(fn); + + // If we have already completed the emits, call this now + if (completed && !prevented) fn(); + return this; } - this.listeners[type] = _.without(this.listeners[type], listener); - return this; -}; -PluginInterface.prototype.emit = function emit(type, data) { - var that = this; + function addPreventedFunc(fn) { + // Only accept functions + if (typeof fn !== 'function') return false; - return new Promise(function (emit_resolve, emit_reject) { - var rejected = false, - rejected_reasons = [], - listeners_promise; + prevented_fn.push(fn); - if (!that.listeners[type]) { - return emit_resolve(data); - } + // If we have already completed the emits, call this now + if (completed && prevented) fn(); - // Add each listener as a promise .then() - listeners_promise = that.listeners[type].reduce(function (listener_promise, listener) { - return listener_promise.then(function (data) { - return new Promise(function (resolve) { - var event_data = { - callback: function () { - resolve(data); - event_data.callback = null; - }, - preventDefault: function (reason) { - rejected = true; - if (reason) { - rejected_reasons.push(reason); - } - }, - wait: false - }; - - listener(event_data, data); - - // If the module has not specified that we should wait, callback now - if (!event_data.wait && event_data.callback) { - event_data.callback(); - } - }); - }); - }, Promise.resolve(data)); - - // After all the listeners have been called, resolve back with any modified data - listeners_promise.then(function (data) { - if (rejected) { - emit_reject({data: data, reasons: rejected_reasons}); - } else { - emit_resolve(data); - } - }); - }); + return this; + } + + + return { + callListeners: callListeners, + then: addCompletedFunc, + catch: addPreventedFunc + }; }; + + // If running a node module, set the exports if (typeof module === 'object' && typeof module.exports !== 'undefined') { module.exports = PluginInterface; } -/* Test cases -var p = new PluginInterface(); -p.on('test', function (event, data) { - data.a += '!'; -}); -p.on('test', function (event, data) { - data.wait = true; - setTimeout(function() { - data.a += '!'; - event.callback(); - }, 1000); -}); +/* + * Example usage + */ -p.emit('test', {a: 'hello world'}).then(function (data) { - if (data.a === 'hello world!') { - console.log('Test passed'); - } else { - console.error('Test failed!'); - } -}, function (err) { - console.error('Test failed!'); -}); +/* +var modules = new PluginInterface(); -var p = new PluginInterface(); -p.on('test', function (event, data) { - data.a += '!'; - event.callback(); -}); -p.on('test', function (event) { - event.preventDefault('testing'); - event.callback(); -}) - -p.emit('test', {a:'hello world'}).then(function (){ - console.error('Test failed!'); -}, function (data, reasons) { - if ((data.data.a === 'hello world!') && (data.reasons.length === 1 && data.reasons[0] === 'testing')) { - console.log('Test passed'); - } else { - console.error('Test failed!'); - } + + +// A plugin +modules.on('irc message', function (event, data) { + //event.wait = true; + setTimeout(event.callback, 2000); }); -*/ + + + +// Core code that is being extended by plugins +var data = { + nick: 'prawnsalald', + command: '/dothis' +}; + +modules.emit('irc message', data).done(function () { + console.log('Your command is: ' + data.command); +}); +*/ \ No newline at end of file -- 2.25.1