EventEmitter

Event emitter engine for nodeGame

Copyright(c) 2012 Stefano Balietti MIT Licensed

Keeps a register of events and function listeners.


(function (exports, node) {
  

Global scope

  
var NDDB = node.NDDB;

exports.EventEmitter = EventEmitter;

EventEmitter constructor

Creates a new instance of EventEmitter

function EventEmitter() {

Public properties

  

EventEmitter.global

Global listeners always active during the game

    this.global = this._listeners = {};
    

EventEmitter.local

Local listeners erased after every state update

    this.local = this._localListeners = {};

EventEmitter.history

Database of emitted events

@see NDDB @see EventEmitter.EventHistory @see EventEmitter.store

    this.history = new EventHistory();
}

EventEmitter methods

EventEmitter.prototype = {

    constructor: EventEmitter,
  

EventEmitter.add

Registers a global listener for an event

Listeners registered with this method are valid for the whole length of the game

Params
type string The event name
listener function The function to fire
See
EventEmitter.addLocal
    add: function (type, listener) {
      if (!type || !listener) return;
      if ('undefined' === typeof this.global[type]){
        this.global[type] = [];
      }
        node.log('Added Listener: ' + type + ' ' + listener, 'DEBUG');
        this.global[type].push(listener);
    },
    

EventEmitter.addLocal

Registers a local listener for an event

Listeners registered with this method are valid only for the same game state (step) in which they have been registered

Params
type string The event name
listener function The function to fire
See
EventEmitter.add
    addLocal: function (type, listener) {
      if (!type || !listener) return;
      if ('undefined' === typeof this.local[type]){
            this.local[type] = [];
        }
      node.log('Added Local Listener: ' + type + ' ' + listener, 'DEBUG');
        this.local[type].push(listener);
    },

EventEmitter.emit

Fires all the listeners associated with an event

Params
event {string|object} The event name or an object of the type
{ type: 'myEvent',
  target: this, } // optional
p1 object Optional. A parameter to be passed to the listener
p2 object Optional. A parameter to be passed to the listener
p3 object Optional. A parameter to be passed to the listener
    emit: function(event, p1, p2, p3) { // Up to 3 parameters
      if (!event) return;
      
      if ('string' === typeof event) {
            event = { type: event };
        }
        if (!event.target){
            event.target = this;
        }
        
        if (!event.type) {  //falsy
            throw new Error("Event object missing 'type' property.");
        }
      
        

Log the event into node.history object, if present

        if (node.conf && node.conf.events) {
            
          if (node.conf.events.history) {
            var o = {
                event: event.type,

target: node.game,

                state: node.game.state,
                p1: p1,
                p2: p2,
                p3: p3
              };
            
            this.history.insert(o);
          }
          

        
          if (list[type] instanceof Array) {
            if (!listener) {
              delete list[type];

console.log('Removed listener ' + type);

              return true;
            }
            
              var listeners = list[type];
              var len=listeners.length;
              for (var i=0; i < len; i++) {

console.log(listeners[i]);

                
                  if (listeners[i] == listener) {
                      listeners.splice(i, 1);
                      node.log('Removed listener ' + type + ' ' + listener, 'DEBUG');
                      return true;
                  }
              }
          }
          
          return false;
    }
    
    var r1 = removeFromList(type, listener, this.global);
    var r2 = removeFromList(type, listener, this.local);
  
    return r1 || r2;
  },
    

EventEmitter.clearState

Undocumented (for now)

  clearState: function(state) {
    this.clearLocal();
    return true;
  },
    

EventEmitter.clearLocalListeners

Removes all entries from the local listeners register

  clearLocal: function() {
    node.log('Cleaning Local Listeners', 'DEBUG');
    for (var key in this.local) {
      if (this.local.hasOwnProperty(key)) {
        this.remove(key, this.local[key]);
      }
    }
    
    this.local = {};
  },
    

EventEmitter.printAll

Prints to console all the registered functions

  printAll: function() {
    node.log('nodeGame:\tPRINTING ALL LISTENERS', 'DEBUG');
      
    for (var i in this.global){
        if (this.global.hasOwnProperty(i)){
          console.log(i + ' ' + i.length);
        }
      }
    
    for (var i in this.local){
        if (this.local.hasOwnProperty(i)){
          console.log(i + ' ' + i.length);
        }
      }
      
  }
  
};

EventHistory

function EventHistory() {
  

EventHistory.history

Database of emitted events

@see NDDB @see EventEmitter.store

  this.history = new NDDB();
      
    this.history.h('state', function(e) {
      if (!e) return;
      var state = ('object' === typeof e.state) ? e.state
                            : node.game.state;
      return node.GameState.toHash(state, 'S.s.r');
    });
      
}

EventHistory.prototype.remit = function(state, discard, keep) {

  if (!this.history.count()) {
    node.log('no event history was found to remit', 'WARN');
    return false;
  }
      
  node.log('remitting ' + node.events.history.count() + ' events', 'DEBUG');
      
  var hash, db;
  
  if (state) {
    
    this.history.rebuildIndexes();
    
    hash = new GameState(session.state).toHash('S.s.r'); 
    
    if (!this.history.state) {
      node.log('no old events to re-emit were found during session recovery', 'DEBUG');
      return false; 
    }
    if (!this.history.state[hash]){
      node.log('the current state ' + hash + ' has no events to re-emit', 'DEBUG');
      return false; 
    }
    
    db = this.history.state[hash];
  }
  else {
    db = this.history;
  }
  

cleaning up the events to remit

  
  if (discard) {
    db.select('event', 'in', discard).remove();
  }
  
  if (keep) {
    db = db.select('event', 'in', keep);
  }
    
  if (!db.count()){
    node.log('no valid events to re-emit after cleanup', 'DEBUG');
    return false;
  }
  
  var remit = function () {
    node.log('re-emitting ' + db.count() + ' events', 'DEBUG');

We have events that were fired at the state when disconnection happened. Let's fire them again

    db.each(function(e) {
      node.emit(e.event, e.p1, e.p2, e.p3);
    });
  };
  
  if (node.game.isReady()) {
    remit.call(node.game);
  }
  else {
    node.on('LOADED', function(){
      remit.call(node.game);
    });
  }
  
  return true;
};

Listener

Undocumented (for now)

function Listener (o) {
  var o = o || {};
  

event name

  this.event = o.event;           
  

callback function

  this.listener = o.listener;       
  

events with higher priority are executed first

  this.priority = o.priority || 0;  
  

the state in which the listener is allowed to be executed

  this.state = o.state || node.game.state;  
  

for how many extra steps is the event still valid. -1 = always valid

  this.ttl = ('undefined' !== typeof o.ttl) ? o.ttl : -1; 
  

function will be called with target as 'this'

  this.target = o.target || undefined;  
};
   

Closure

})(
  'undefined' != typeof node ? node : module.exports
  , 'undefined' != typeof node ? node : module.parent.exports
);