<commit_before>/*
 * Copyright (c) 2016 Untu, Inc.
 * This code is licensed under Eclipse Public License - v 1.0.
 * The full license text can be found in LICENSE.txt file and
 * on the Eclipse official site (https://www.eclipse.org/legal/epl-v10.html).
 */

'use strict';

var Actor = require('./actor.js');
var net = require('net');
var P = require('bluebird');
var _ = require('underscore');

/**
 * An actor that is run in separate sub-process on local machine.
 */
class ForkedActor extends Actor {
  /**
   * @param {ActorSystem} system Actor system.
   * @param {Actor} parent Parent actor.
   * @param {Object} bus Message bus to send/receive messages.
   * @param {Actor} actor Wrapped actor.
   */
  constructor(system, parent, bus, actor) {
    super(system, parent, actor.getId(), actor.getName(), actor.getCustomParameters());

    this.system = system;
    this.bus = bus;
    this.actor = actor;
    this.idCounter = 1;
    this.responsePromises = {};
    this.timeouts = [];
    this.timeoutWorkerInterval = setInterval(() => {
      var now = _.now();

      while (this.timeouts.length > 0 && this.timeouts[0].timeout <= now) {
        var timeoutObject = this.timeouts.shift();
        var msgId = timeoutObject.msgId;
        var promise = this.responsePromises[msgId];
        delete this.responsePromises[msgId];

        promise && promise.reject(new Error('Response timed out.'));
      }
    }, 1000);
  }

  initialize() {
    return P.resolve()
      .then(() => this.actor.initialize(this))
      .then(() => {
        // Listen for message responses.
        this.bus.on('message', (msg, handle) => {
          var log = this.getLog();

          log.debug('Received message:', msg);

          if (!msg.id) return log.warn('Missing ID in actor message (ignoring):', msg);

          if (msg.type == 'actor-message') {
            if (!msg.body) return this._sendErrorResponse(msg.id, 'Missing message body');

            var topic = msg.body.topic;

            if (!topic) return this._sendErrorResponse(msg.id, 'Missing message topic');

            var actor = this.actor;

            if (msg.actorId != this.getId()) {
              var parent = this.getParent();

              // Check if we've got a message to a parent.
              if (parent && parent.getId() == msg.actorId) {
                actor = parent;
              }
              else {
                return this._sendErrorResponse(msg.id, 'Target actor ID doesn\'t match neither self nor parent');
              }
            }

            var message = handle || this._decodeMessageBody(msg.body);

            var sendPromise;

            if (msg.body.receive) {
              sendPromise = actor.sendAndReceive.apply(actor, [topic].concat(message))
                .then(resp => this._send0({
                  type: 'actor-response',
                  id: msg.id,
                  body: { response: resp }
                }));
            }
            else {
              sendPromise = actor.send.apply(actor, [topic].concat(message));
            }

            sendPromise.catch(err => this._sendErrorResponse(msg.id, err.message));

            return;
          }

          if (msg.actorId != this.getId()) return;

          if (msg.type == 'actor-tree') {
            this.tree()
              .then(tree => this._send0({
                type: 'actor-response',
                id: msg.id,
                body: { response: tree }
              }))
              .catch(err => this._sendErrorResponse(msg.id, err.message));
          }
          else if (msg.type == 'actor-metrics') {
            this.metrics()
              .then(metrics => this._send0({
                type: 'actor-response',
                id: msg.id,
                body: { response: metrics }
              }))
              .catch(err => this._sendErrorResponse(msg.id, err.message));
          }
          else if (msg.type == 'actor-response' || msg.type == 'parent-pong') {
            var respPromise = this.responsePromises[msg.id];

            if (respPromise) {
              delete this.responsePromises[msg.id];
            }

            if (!msg.id) return log.warn(`Ignoring "${msg.type}" message with absent ID.`);

            if (!respPromise) return log.warn(`No pending promise for "${msg.type}":`, msg);

            if (msg.body) {
              var body = msg.body;

              if (body.error) {
                respPromise.reject(new Error(body.error));
              }
              else {
                respPromise.resolve(body.response);
              }
            }
            else {
              respPromise.resolve();
            }
          }
          else if (msg.type == 'destroy-actor') {
            this.bus.removeAllListeners('message');

            this.actor.destroy().then(() => {
              this.bus.send({ type: 'actor-destroyed', actorId: this.getId(), id: msg.id }, () => {
                log.debug('Destroying forked system for ' + this);

                this.system.destroy().catch(err => {
                  log.error('Failed to destroy forked system: ' + err);
                });
              });
            });
          }
          else if (msg.type == 'actor-destroyed') {
            clearInterval(this.timeoutWorkerInterval);
            var respPromise0 = this.responsePromises[msg.id];

            if (respPromise0) {
              delete this.responsePromises[msg.id];

              respPromise0.resolve();
            }
          }
          else if (msg.type == 'parent-ping') {
            this.bus.send({ type: 'parent-pong', id: msg.id, actorId: this.getId() });
          }
          else {
            log.warn('Ignoring message of an unknown type:', msg);
          }
        });

        // Remove message listener once child process has exited or crashed.
        this.bus.once('exit', () => {
          this.bus.removeAllListeners('message');
        });
      });
  }

  send0(topic, ...message) {
    return this._sendActorMessage(topic, message, { receive: false });
  }

  sendAndReceive0(topic, ...message) {
    return this._sendActorMessage(topic, message, { receive: true });
  }

  metrics0() {
    return this.actor.metrics();
  }

  toString() {
    var name = this.getName();

    if (name) {
      return 'ForkedActor(' + this.id + ', ' + name + ')';
    }
    else {
      return 'ForkedActor(' + this.getId() + ')';
    }
  }

  /**
   * @returns {Actor} Wrapped actor.
   * @protected
   */
  _getActor() {
    return this.actor;
  }

  /**
   * Sends an actor message to a forked actor and returns a promise, which is resolved with message response.
   *
   * @param {String} topic Message topic.
   * @param {Array} message Message.
   * @param {Object} options Operation options.
   * - {Boolean} receive Receive flag.
   * - {String} [actorId] ID of a remote actor to send message to (either self or parent).
   * @returns {*} Promise that yields a message response promise, if a receive flag is on. A promise
   * yields undefined if a receive flag is off.
   * @protected
   */
  _sendActorMessage(topic, message, options) {
    var actorMessage = {
      type: 'actor-message',
      body: {
        topic: topic,
        message: message,
        receive: options.receive
      }
    };

    if (message instanceof net.Server) {
      delete actorMessage.body.message;

      return this._send0(actorMessage, _.extend({}, options, { socketHandle: message }));
    }

    var marshalledType = [];
    var message0 = _.map(message || [], (subMessage, idx) => {
      var marshaller = this.system.getMarshallerForMessage(subMessage);

      if (marshaller) {
        marshalledType[idx] = marshaller.type;

        return marshaller.marshall(subMessage);
      }

      return subMessage;
    });

    if (message0.length == 1) {
      message0 = message0[0];
      marshalledType = marshalledType[0];
    }

    actorMessage.body.message = message0;
    actorMessage.body.marshalledType = marshalledType;

    return this._send0(actorMessage, options);
  }

  /**
   * Sends an error response.
   *
   * @param {String} msgId Message ID.
   * @param {String} errorText Error text.
   * @returns {*} Operation promise.
   * @private
   */
  _sendErrorResponse(msgId, errorText) {
    return this._send0({
      type: 'actor-response',
      id: msgId,
      body: {
        error: errorText
      }
    });
  }

  /**
   * Sends an arbitrary message to a forked actor.
   *
   * @param {Object} msg Message to send.
   * @param {Object} [options] Operation options.
   * - {Boolean} receive Receive flag.
   * - {String} [actorId] ID of a remote actor to send message to (either self or parent).
   * @returns {*} Promise that yields a message response promise, if a receive flag is on. A promise
   * yields undefined if a receive flag is off.
   * @protected
   */
  _send0(msg, options) {
    options = options || {};

    return new P((resolve, reject) => {
      var msgId = msg.id;

      if (!msgId) {
        msg.id = msgId = this.idCounter++;
      }

      msg.actorId = options.actorId || this.getId();

      var ret;

      if (options.receive) {
        var pending = P.pending();

        this.responsePromises[msgId] = pending;

        // Await for message response.
        ret = pending.promise;

        // Set message timeout, if needed.
        if (options.timeout > 0) {
          var timeout = _.now() + options.timeout;
          var timeoutObject = { timeout: timeout, msgId: msgId };
          var idx = _.sortedIndex(this.timeouts, timeoutObject, 'timeout');

          this.timeouts.splice(idx, 0, timeoutObject);
        }
      }

      if (this.getLog().isDebug()) {
        this.getLog().debug('Sending message:', JSON.stringify(msg, null, 2));
      }

      var cb = err => {
        if (err) return reject(err);

        resolve(ret);
      };

      if (options.socketHandle) {
        this.bus.send(msg, options.socketHandle, cb);
      }
      else {
        this.bus.send(msg, cb);
      }
    });
  }

  /**
   * Decodes message body, received by this actor.
   *
   * @param {Object} body Message body.
   * @returns {Array} Decoded message in variable argument format.
   * @private
   */
  _decodeMessageBody(body) {
    var message = body.message;
    var marshalledTypes = body.marshalledType;

    if (!message) {
      message = [];
      marshalledTypes = [];
    }

    if (!_.isArray(message)) {
      message = [message];
      marshalledTypes = [marshalledTypes];
    }

    return _.map(message, (subMessage, idx) => {
      var type = marshalledTypes[idx];

      if (type) {
        var marshaller = this.system.getMarshaller(type);

        return marshaller.unmarshall(subMessage);
      }

      return subMessage;
    });
  }
}

module.exports = ForkedActor;<commit_msg>(saymon) comet-server-clustering: Fixed Server instance marshalling failure.<commit_after>/*
 * Copyright (c) 2016 Untu, Inc.
 * This code is licensed under Eclipse Public License - v 1.0.
 * The full license text can be found in LICENSE.txt file and
 * on the Eclipse official site (https://www.eclipse.org/legal/epl-v10.html).
 */

'use strict';

var Actor = require('./actor.js');
var net = require('net');
var P = require('bluebird');
var _ = require('underscore');

/**
 * An actor that is run in separate sub-process on local machine.
 */
class ForkedActor extends Actor {
  /**
   * @param {ActorSystem} system Actor system.
   * @param {Actor} parent Parent actor.
   * @param {Object} bus Message bus to send/receive messages.
   * @param {Actor} actor Wrapped actor.
   */
  constructor(system, parent, bus, actor) {
    super(system, parent, actor.getId(), actor.getName(), actor.getCustomParameters());

    this.system = system;
    this.bus = bus;
    this.actor = actor;
    this.idCounter = 1;
    this.responsePromises = {};
    this.timeouts = [];
    this.timeoutWorkerInterval = setInterval(() => {
      var now = _.now();

      while (this.timeouts.length > 0 && this.timeouts[0].timeout <= now) {
        var timeoutObject = this.timeouts.shift();
        var msgId = timeoutObject.msgId;
        var promise = this.responsePromises[msgId];
        delete this.responsePromises[msgId];

        promise && promise.reject(new Error('Response timed out.'));
      }
    }, 1000);
  }

  initialize() {
    return P.resolve()
      .then(() => this.actor.initialize(this))
      .then(() => {
        // Listen for message responses.
        this.bus.on('message', (msg, handle) => {
          var log = this.getLog();

          log.debug('Received message:', msg);

          if (!msg.id) return log.warn('Missing ID in actor message (ignoring):', msg);

          if (msg.type == 'actor-message') {
            if (!msg.body) return this._sendErrorResponse(msg.id, 'Missing message body');

            var topic = msg.body.topic;

            if (!topic) return this._sendErrorResponse(msg.id, 'Missing message topic');

            var actor = this.actor;

            if (msg.actorId != this.getId()) {
              var parent = this.getParent();

              // Check if we've got a message to a parent.
              if (parent && parent.getId() == msg.actorId) {
                actor = parent;
              }
              else {
                return this._sendErrorResponse(msg.id, 'Target actor ID doesn\'t match neither self nor parent');
              }
            }

            var message = handle || this._decodeMessageBody(msg.body);

            var sendPromise;

            if (msg.body.receive) {
              sendPromise = actor.sendAndReceive.apply(actor, [topic].concat(message))
                .then(resp => this._send0({
                  type: 'actor-response',
                  id: msg.id,
                  body: { response: resp }
                }));
            }
            else {
              sendPromise = actor.send.apply(actor, [topic].concat(message));
            }

            sendPromise.catch(err => this._sendErrorResponse(msg.id, err.message));

            return;
          }

          if (msg.actorId != this.getId()) return;

          if (msg.type == 'actor-tree') {
            this.tree()
              .then(tree => this._send0({
                type: 'actor-response',
                id: msg.id,
                body: { response: tree }
              }))
              .catch(err => this._sendErrorResponse(msg.id, err.message));
          }
          else if (msg.type == 'actor-metrics') {
            this.metrics()
              .then(metrics => this._send0({
                type: 'actor-response',
                id: msg.id,
                body: { response: metrics }
              }))
              .catch(err => this._sendErrorResponse(msg.id, err.message));
          }
          else if (msg.type == 'actor-response' || msg.type == 'parent-pong') {
            var respPromise = this.responsePromises[msg.id];

            if (respPromise) {
              delete this.responsePromises[msg.id];
            }

            if (!msg.id) return log.warn(`Ignoring "${msg.type}" message with absent ID.`);

            if (!respPromise) return log.warn(`No pending promise for "${msg.type}":`, msg);

            if (msg.body) {
              var body = msg.body;

              if (body.error) {
                respPromise.reject(new Error(body.error));
              }
              else {
                respPromise.resolve(body.response);
              }
            }
            else {
              respPromise.resolve();
            }
          }
          else if (msg.type == 'destroy-actor') {
            this.bus.removeAllListeners('message');

            this.actor.destroy().then(() => {
              this.bus.send({ type: 'actor-destroyed', actorId: this.getId(), id: msg.id }, () => {
                log.debug('Destroying forked system for ' + this);

                this.system.destroy().catch(err => {
                  log.error('Failed to destroy forked system: ' + err);
                });
              });
            });
          }
          else if (msg.type == 'actor-destroyed') {
            clearInterval(this.timeoutWorkerInterval);
            var respPromise0 = this.responsePromises[msg.id];

            if (respPromise0) {
              delete this.responsePromises[msg.id];

              respPromise0.resolve();
            }
          }
          else if (msg.type == 'parent-ping') {
            this.bus.send({ type: 'parent-pong', id: msg.id, actorId: this.getId() });
          }
          else {
            log.warn('Ignoring message of an unknown type:', msg);
          }
        });

        // Remove message listener once child process has exited or crashed.
        this.bus.once('exit', () => {
          this.bus.removeAllListeners('message');
        });
      });
  }

  send0(topic, ...message) {
    return this._sendActorMessage(topic, message, { receive: false });
  }

  sendAndReceive0(topic, ...message) {
    return this._sendActorMessage(topic, message, { receive: true });
  }

  metrics0() {
    return this.actor.metrics();
  }

  toString() {
    var name = this.getName();

    if (name) {
      return 'ForkedActor(' + this.id + ', ' + name + ')';
    }
    else {
      return 'ForkedActor(' + this.getId() + ')';
    }
  }

  /**
   * @returns {Actor} Wrapped actor.
   * @protected
   */
  _getActor() {
    return this.actor;
  }

  /**
   * Sends an actor message to a forked actor and returns a promise, which is resolved with message response.
   *
   * @param {String} topic Message topic.
   * @param {Array} message Message.
   * @param {Object} options Operation options.
   * - {Boolean} receive Receive flag.
   * - {String} [actorId] ID of a remote actor to send message to (either self or parent).
   * @returns {*} Promise that yields a message response promise, if a receive flag is on. A promise
   * yields undefined if a receive flag is off.
   * @protected
   */
  _sendActorMessage(topic, message, options) {
    var actorMessage = {
      type: 'actor-message',
      body: {
        topic: topic,
        message: message,
        receive: options.receive
      }
    };

    if (message.length == 1 && message[0] instanceof net.Server) {
      delete actorMessage.body.message;

      return this._send0(actorMessage, _.extend({}, options, { socketHandle: message[0] }));
    }

    var marshalledType = [];
    var message0 = _.map(message || [], (subMessage, idx) => {
      var marshaller = this.system.getMarshallerForMessage(subMessage);

      if (marshaller) {
        marshalledType[idx] = marshaller.type;

        return marshaller.marshall(subMessage);
      }

      return subMessage;
    });

    if (message0.length == 1) {
      message0 = message0[0];
      marshalledType = marshalledType[0];
    }

    actorMessage.body.message = message0;
    actorMessage.body.marshalledType = marshalledType;

    return this._send0(actorMessage, options);
  }

  /**
   * Sends an error response.
   *
   * @param {String} msgId Message ID.
   * @param {String} errorText Error text.
   * @returns {*} Operation promise.
   * @private
   */
  _sendErrorResponse(msgId, errorText) {
    return this._send0({
      type: 'actor-response',
      id: msgId,
      body: {
        error: errorText
      }
    });
  }

  /**
   * Sends an arbitrary message to a forked actor.
   *
   * @param {Object} msg Message to send.
   * @param {Object} [options] Operation options.
   * - {Boolean} receive Receive flag.
   * - {String} [actorId] ID of a remote actor to send message to (either self or parent).
   * @returns {*} Promise that yields a message response promise, if a receive flag is on. A promise
   * yields undefined if a receive flag is off.
   * @protected
   */
  _send0(msg, options) {
    options = options || {};

    return new P((resolve, reject) => {
      var msgId = msg.id;

      if (!msgId) {
        msg.id = msgId = this.idCounter++;
      }

      msg.actorId = options.actorId || this.getId();

      var ret;

      if (options.receive) {
        var pending = P.pending();

        this.responsePromises[msgId] = pending;

        // Await for message response.
        ret = pending.promise;

        // Set message timeout, if needed.
        if (options.timeout > 0) {
          var timeout = _.now() + options.timeout;
          var timeoutObject = { timeout: timeout, msgId: msgId };
          var idx = _.sortedIndex(this.timeouts, timeoutObject, 'timeout');

          this.timeouts.splice(idx, 0, timeoutObject);
        }
      }

      if (this.getLog().isDebug()) {
        this.getLog().debug('Sending message:', JSON.stringify(msg, null, 2));
      }

      var cb = err => {
        if (err) return reject(err);

        resolve(ret);
      };

      if (options.socketHandle) {
        this.bus.send(msg, options.socketHandle, cb);
      }
      else {
        this.bus.send(msg, cb);
      }
    });
  }

  /**
   * Decodes message body, received by this actor.
   *
   * @param {Object} body Message body.
   * @returns {Array} Decoded message in variable argument format.
   * @private
   */
  _decodeMessageBody(body) {
    var message = body.message;
    var marshalledTypes = body.marshalledType;

    if (!message) {
      message = [];
      marshalledTypes = [];
    }

    if (!_.isArray(message)) {
      message = [message];
      marshalledTypes = [marshalledTypes];
    }

    return _.map(message, (subMessage, idx) => {
      var type = marshalledTypes[idx];

      if (type) {
        var marshaller = this.system.getMarshaller(type);

        return marshaller.unmarshall(subMessage);
      }

      return subMessage;
    });
  }
}

module.exports = ForkedActor;<|endoftext|>


<commit_before>"use strict";

// Pre-create a plain Collection so it can be used in the model's relations.
var Tasks = Backbone.Collection.extend({

	initialize: function () {

		// After the first fetch...
		this.once('loadProject', function () {

			// Recurse.
			this.each(function (model) {

				model.get('tasks').trigger('loadProject');
			});

			// Track relative order.
			this.on('add',    this.updateOrdering, this);
			this.on('remove', this.updateOrdering, this);

			// Track parent.

			this.on('add', function (model) {

				// New place in the the project tree.
				model.save({parentId: this.parent.id});

			}.bind(this));

			this.on('remove', function (model) {

				// No longer part of the project tree.
				model.save({parentId: null});
			});

		}.bind(this));
	},

	create: function () {

		// Same as default, but auto-create the guid locally, and trigger 'focus' event.

		var topCollection = this;
		while (topCollection.parent.collection) {
			topCollection = topCollection.parent.collection;
		}

		var _arguments = _.toArray(arguments);
		_arguments[0] = _arguments[0] || {
			id: makeGuid(),
			parentId: this.parent.id,
			projectId: topCollection.parent.id
		};
		Backbone.Collection.prototype.create.apply(this, _arguments).trigger('focus');
	},

	updateOrdering: function () {

		this.models.forEach(function (model, index) {

			model.save({ordering: index});
		});
	}
});

var Task = Backbone.Model.extend({

	defaults: {
		color: 'white',
		tasks: []
	},

	relations: {
		tasks: Tasks
	},

	url: function () {

		return '/api/tasks/' + this.id;
	},

	save: function(changedAttributes, options) {

		// The model data as they would look after saving.
		var attributes = _.extend(_.clone(this.attributes), changedAttributes);

		// But some stuff shouldn't be sent.
		delete attributes.tasks;
		delete attributes.projection;

		// Override what gets sent to the server.
		options = options || {};
		options.data = JSON.stringify(attributes);
		options.contentType = "application/json"

		// Proxy the call to the original save function. (Actually saving the model data, doing server requests, triggering events, etc.)
		Backbone.Model.prototype.save.call(this, changedAttributes, options);
	},

	initialize: function () {

		// So the subtasks can find their parentId.
		this.get('tasks').parent = this;

		// Replace save with a locally debounced version for each instance.
		// Like _.debounce, but uses the *last* arguments.
		// Also, *set* the data immediately.
		var timer = null;
		var originalSave = this.save;
		var THIS = this;
		this.save = function () {

			var outerArguments = arguments;

			THIS.set.apply(THIS, outerArguments);

			if (timer) {

				clearTimeout(timer);
				timer = null;
			}

			timer = setTimeout(function () {

				timer = null;
				originalSave.apply(THIS, outerArguments);

			}, 3000);
		};
		// Prevent debounce from re-saving after delete.
		this.on('destroy', function () {

			// The collection listens to this event and removes as well,
			// but the remove event triggers a save...
			THIS.collection.remove(THIS);

			clearTimeout(timer);
			timer = null;
		});

		this.on('change:from change:to change:actual', function (model) {

			// Recalculate all projections from the top.
			model.projectRoot().calculateProjection();
		});
	},

	index: function () {

		// If it doesn't belong to a collection.
		if (!this.collection) {

			return 0;
		}

		var indexOf = this.collection.models.indexOf(this);

		// If it hasn't been added just yet. (During creation.)
		if (indexOf === -1) {

			return undefined;
		}

		return indexOf;
	},

	numTasksRecursive: function () {

		return this.get('tasks')
			.map(function (child) { return child.numTasksRecursive(); })
			.reduce(function (soFar, next) { return soFar + next; }, 0)
			+ 1;
	},

	createProject: function () {

		if (!this.isNew()) {

			throw new Error('You need an unused Taks to create a project.');
		}

		var projectId = makeGuid();

		this.set({
			id: projectId,
			projectId: projectId,
			parentId: null
		});

		this.trigger('focus');
	},

	projectRoot: function () {

		var currentTask = this;

		while (true) {

			if (!currentTask.collection) {

				break;
			}

			currentTask = currentTask.collection.parent;
		}

		return currentTask;
	},

	getDefaultEstimateMax: function () {

		return this.get('from') * 1.5;
	},

	getEstimate: function () {

		return this.get('from') ? {
			min: this.get('from'),
			max: this.get('to') || this.getDefaultEstimateMax()
		} : null;
	},

	calculateProjection: function () {

		// Calculate the projections of the child tasks.
		this.get('tasks').each(function (child) {

			child.calculateProjection();
		});

		// Sum up the projections.
		var childProjections = _.filter(this.get('tasks').pluck('projection'), function (projection) { return !!projection; });
		var childProjectionSum = childProjections.length ? {
			min: _.pluck(childProjections, 'min').reduce(function (a, b) { return a + b; }, 0),
			max: _.pluck(childProjections, 'max').reduce(function (a, b) { return a + b; }, 0)
		} : null;

		if (childProjectionSum) {

			// Calculate un-projected missing time in children.
			var numProjected = childProjections.length;
			var factorProjected = numProjected / this.get('tasks').length;

			// Add the extrapolated time from un-estimated siblings.
			childProjectionSum.min /= factorProjected;
			childProjectionSum.max /= factorProjected;
		}

		var estimate = this.getEstimate();

		var actual = this.get('actual');

		// Use actual data, otherwise the largest numbers of whatever information is available.
		var projection;
		if (actual !== null) {

			projection = {
				min: actual,
				max: actual
			};

		} else {

			if (childProjectionSum && estimate) {

				projection = {
					min: Math.max(estimate.min, childSumMin),
					max: Math.max(estimate.min, childSumMax)
				};

			} else if (estimate) {

				projection = estimate;

			} else {

				projection = childProjectionSum;
			}
		}
		this.set('projection', projection);

		// Set the projections for the undefined children.
		this.calculateProjectionDown();
	},

	calculateProjectionDown: function () {

		// TODO:

		// Count un-estimated and sum partial estimates.

		// Calculate un-estimated missing time in children.

		// Spread equally.
	}
});

// Complete the collection after the model's relations has used it.
Tasks.prototype.model = Task;
<commit_msg>Fixed broken variable name.<commit_after>"use strict";

// Pre-create a plain Collection so it can be used in the model's relations.
var Tasks = Backbone.Collection.extend({

	initialize: function () {

		// After the first fetch...
		this.once('loadProject', function () {

			// Recurse.
			this.each(function (model) {

				model.get('tasks').trigger('loadProject');
			});

			// Track relative order.
			this.on('add',    this.updateOrdering, this);
			this.on('remove', this.updateOrdering, this);

			// Track parent.

			this.on('add', function (model) {

				// New place in the the project tree.
				model.save({parentId: this.parent.id});

			}.bind(this));

			this.on('remove', function (model) {

				// No longer part of the project tree.
				model.save({parentId: null});
			});

		}.bind(this));
	},

	create: function () {

		// Same as default, but auto-create the guid locally, and trigger 'focus' event.

		var topCollection = this;
		while (topCollection.parent.collection) {
			topCollection = topCollection.parent.collection;
		}

		var _arguments = _.toArray(arguments);
		_arguments[0] = _arguments[0] || {
			id: makeGuid(),
			parentId: this.parent.id,
			projectId: topCollection.parent.id
		};
		Backbone.Collection.prototype.create.apply(this, _arguments).trigger('focus');
	},

	updateOrdering: function () {

		this.models.forEach(function (model, index) {

			model.save({ordering: index});
		});
	}
});

var Task = Backbone.Model.extend({

	defaults: {
		color: 'white',
		tasks: []
	},

	relations: {
		tasks: Tasks
	},

	url: function () {

		return '/api/tasks/' + this.id;
	},

	save: function(changedAttributes, options) {

		// The model data as they would look after saving.
		var attributes = _.extend(_.clone(this.attributes), changedAttributes);

		// But some stuff shouldn't be sent.
		delete attributes.tasks;
		delete attributes.projection;

		// Override what gets sent to the server.
		options = options || {};
		options.data = JSON.stringify(attributes);
		options.contentType = "application/json"

		// Proxy the call to the original save function. (Actually saving the model data, doing server requests, triggering events, etc.)
		Backbone.Model.prototype.save.call(this, changedAttributes, options);
	},

	initialize: function () {

		// So the subtasks can find their parentId.
		this.get('tasks').parent = this;

		// Replace save with a locally debounced version for each instance.
		// Like _.debounce, but uses the *last* arguments.
		// Also, *set* the data immediately.
		var timer = null;
		var originalSave = this.save;
		var THIS = this;
		this.save = function () {

			var outerArguments = arguments;

			THIS.set.apply(THIS, outerArguments);

			if (timer) {

				clearTimeout(timer);
				timer = null;
			}

			timer = setTimeout(function () {

				timer = null;
				originalSave.apply(THIS, outerArguments);

			}, 3000);
		};
		// Prevent debounce from re-saving after delete.
		this.on('destroy', function () {

			// The collection listens to this event and removes as well,
			// but the remove event triggers a save...
			THIS.collection.remove(THIS);

			clearTimeout(timer);
			timer = null;
		});

		this.on('change:from change:to change:actual', function (model) {

			// Recalculate all projections from the top.
			model.projectRoot().calculateProjection();
		});
	},

	index: function () {

		// If it doesn't belong to a collection.
		if (!this.collection) {

			return 0;
		}

		var indexOf = this.collection.models.indexOf(this);

		// If it hasn't been added just yet. (During creation.)
		if (indexOf === -1) {

			return undefined;
		}

		return indexOf;
	},

	numTasksRecursive: function () {

		return this.get('tasks')
			.map(function (child) { return child.numTasksRecursive(); })
			.reduce(function (soFar, next) { return soFar + next; }, 0)
			+ 1;
	},

	createProject: function () {

		if (!this.isNew()) {

			throw new Error('You need an unused Taks to create a project.');
		}

		var projectId = makeGuid();

		this.set({
			id: projectId,
			projectId: projectId,
			parentId: null
		});

		this.trigger('focus');
	},

	projectRoot: function () {

		var currentTask = this;

		while (true) {

			if (!currentTask.collection) {

				break;
			}

			currentTask = currentTask.collection.parent;
		}

		return currentTask;
	},

	getDefaultEstimateMax: function () {

		return this.get('from') * 1.5;
	},

	getEstimate: function () {

		return this.get('from') ? {
			min: this.get('from'),
			max: this.get('to') || this.getDefaultEstimateMax()
		} : null;
	},

	calculateProjection: function () {

		// Calculate the projections of the child tasks.
		this.get('tasks').each(function (child) {

			child.calculateProjection();
		});

		// Sum up the projections.
		var childProjections = _.filter(this.get('tasks').pluck('projection'), function (projection) { return !!projection; });
		var childProjectionSum = childProjections.length ? {
			min: _.pluck(childProjections, 'min').reduce(function (a, b) { return a + b; }, 0),
			max: _.pluck(childProjections, 'max').reduce(function (a, b) { return a + b; }, 0)
		} : null;

		if (childProjectionSum) {

			// Calculate un-projected missing time in children.
			var numProjected = childProjections.length;
			var factorProjected = numProjected / this.get('tasks').length;

			// Add the extrapolated time from un-estimated siblings.
			childProjectionSum.min /= factorProjected;
			childProjectionSum.max /= factorProjected;
		}

		var estimate = this.getEstimate();

		var actual = this.get('actual');

		// Use actual data, otherwise the largest numbers of whatever information is available.
		var projection;
		if (actual !== null) {

			projection = {
				min: actual,
				max: actual
			};

		} else {

			if (childProjectionSum && estimate) {

				projection = {
					min: Math.max(estimate.min, childProjectionSum.min),
					max: Math.max(estimate.min, childProjectionSum.max)
				};

			} else if (estimate) {

				projection = estimate;

			} else {

				projection = childProjectionSum;
			}
		}
		this.set('projection', projection);

		// Set the projections for the undefined children.
		this.calculateProjectionDown();
	},

	calculateProjectionDown: function () {

		// TODO:

		// Count un-estimated and sum partial estimates.

		// Calculate un-estimated missing time in children.

		// Spread equally.
	}
});

// Complete the collection after the model's relations has used it.
Tasks.prototype.model = Task;
<|endoftext|>


<commit_before>var View = (function () {
  var container;

  var camera, cameraControls, scene, renderer, mesh, id;

  var clock = new THREE.Clock();

  var userPositions = [];

  function init() {

    renderer = new THREE.WebGLRenderer({
      antialias: true,
      alpha: true
    });

    renderer.setSize(window.innerWidth, window.innerHeight);

    container = document.createElement('div');

    container.appendChild(renderer.domElement);
    document.body.appendChild(container);

    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 10000);
    camera.position.set(1000, 1000, 0);

    cameraControls = new THREE.TrackballControls(camera, renderer.domElement);
    cameraControls.target.set(0, 0, 0);
    cameraControls.maxDistance = 3000;

    scene = new THREE.Scene();
    scene.fog = new THREE.FogExp2( 0x000000, 0.0005 );

  	var ambientLight	= new THREE.AmbientLight( 0x888888 );
  	scene.add( ambientLight );
    var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 );
    directionalLight.position.set( 0, 1, 0 );
    scene.add( directionalLight );

    window.addEventListener('resize', onWindowResize, false);

    // var sphere = new THREE.Mesh(
    //   new THREE.SphereGeometry(100, 32, 32),
    //   new THREE.MeshBasicMaterial({
    //     map: THREE.ImageUtils.loadTexture('assets/wallpaper.jpg')
    //   })
    // );
    // sphere.scale.x = -1;
    // scene.add(sphere);
  }

  function displayData(user, data) {

        var textureLoader = new THREE.TextureLoader();
        textureLoader.crossOrigin = 'Anonymous';

        var smallGeometry = new THREE.SphereGeometry(75, 32, 32);
        var mainGeometry = new THREE.SphereGeometry(250, 32, 32);
        var basicMaterial = new THREE.MeshBasicMaterial({
          color: 'white'
        });

        var main;
        // Draw main user
        textureLoader.load(user.profile_image_url, function(texture) {
          var mainMaterial = new THREE.MeshPhongMaterial({
            map: texture
          });
          main = new THREE.Mesh(mainGeometry, mainMaterial);
          main.quaternion.copy(camera.quaternion);
          scene.add(main);
          userPositions.push(main.position);

          for (var i = 0; i < data.length; i++) {
            (function(userData) {
              textureLoader.load(userData.profile_image_url, function(texture) {
                generateSphere(userData, texture);
              });
            })(data[i]);
          }
        });

        var ballTexture = new THREE.TextureLoader().load("/assets/ball.png" );

        function getRandomInt(min, max) {
          return Math.floor(Math.random() * (max - min + 1)) + min;
        }
        function getUserPosition() {
          var v = new THREE.Vector3(getRandomInt(-1000, 1000), getRandomInt(-1000, 1000), getRandomInt(-1000, 1000));
          var valid = true;
          for(var i = 0; i < userPositions.length; i++) {
            if (Math.abs(v.distanceTo(userPositions[i])) < 200) {
              return getUserPosition();
            }
          }
          return v;
        }
        // Draw other users
        var generateSphere = function (userData, texture) {
          var userMaterial = new THREE.MeshPhongMaterial({
            map: texture
          });
          var user = new THREE.Mesh(smallGeometry, userMaterial);
          user.quaternion.copy(camera.quaternion);

          var posVector = getUserPosition();

          user.position.set(posVector.x, posVector.y, posVector.z);

          userPositions.push(user.position);

          scene.add(user);
          if(userData.from_freq > 0) {
            createLine(user.position, main.position, userData.from_freq);
          }
          if(userData.to_freq > 0) {
            createLine(main.position, user.position, userData.to_freq);
          }
        };

        var createLine = function (v1, v2, freq) {

          var particleGeometry = new THREE.Geometry();

          //First create the line that we want to animate the particles along
          var geometry = new THREE.Geometry();
          geometry.vertices.push(v1);
          geometry.vertices.push(v2);

          var line = new THREE.Line(geometry, basicMaterial);
          var startPoint = line.geometry.vertices[0];
          var endPoint = line.geometry.vertices[1];
          scene.add(line);
          var pMaterial = new THREE.PointsMaterial({
            color: 0x00FF00,
            map: ballTexture,
            size: 30,
            blending: THREE.AdditiveBlending,
            transparent: true
          });
          pMaterial.color.setHSL( 1.0, 0.2, 0.7 );

          //next create a set of about 30 animation points along the line
          var animationPoints = createLinePoints(startPoint, endPoint);

          //add particles to scene
          var numParticles = 4 * freq;
          for (i = 0; i < numParticles; i++) {
            var desiredIndex = i / numParticles * animationPoints.length;
            var rIndex = constrain(Math.floor(desiredIndex), 0, animationPoints.length - 1);
            var particle = animationPoints[rIndex].clone();
            particle.moveIndex = rIndex;
            particle.nextIndex = rIndex + 1;
            if (particle.nextIndex >= animationPoints.length)
              particle.nextIndex = 0;
            particle.lerpN = 0;
            particle.path = animationPoints;
            particleGeometry.vertices.push(particle);
          }

          var particles = new THREE.Points(particleGeometry, pMaterial);
          scene.add(particles);
          particles.update = updateParticles;
        };

        //update function for each particle animation
        var updateParticles = function () {
          // var time = Date.now()
          for (var i in this.geometry.vertices) {
            var particle = this.geometry.vertices[i];
            var path = particle.path;
            particle.lerpN += 0.05;
            if (particle.lerpN > 1) {
              particle.lerpN = 0;
              particle.moveIndex = particle.nextIndex;
              particle.nextIndex++;
              if (particle.nextIndex >= path.length) {
                particle.moveIndex = 0;
                particle.nextIndex = 1;
              }
            }

            var currentPoint = path[particle.moveIndex];
            var nextPoint = path[particle.nextIndex];

            particle.copy(currentPoint);
            particle.lerp(nextPoint, particle.lerpN);
          }
          this.geometry.verticesNeedUpdate = true;
        };

        function createLinePoints(startPoint, endPoint) {
          var numPoints = 30;
          var returnPoints = [];
          for (i = 0; i <= numPoints; i++) {
            var thisPoint = startPoint.clone().lerp(endPoint, i / numPoints);
            returnPoints.push(thisPoint);
          }
          return returnPoints;
        }

        function constrain(v, min, max) {
          if (v < min)
            v = min;
          else
          if (v > max)
            v = max;
          return v;
        }
  }

  function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    renderer.setSize(window.innerWidth, window.innerHeight);

    render();
  }

  function animate() {
    var delta = clock.getDelta();

    id = requestAnimationFrame(animate);

    cameraControls.update(delta);

    for (var i = 0; i < scene.children.length; i++) {
      var child = scene.children[i];
      if (child.type === 'Mesh') {
        child.quaternion.copy(camera.quaternion);
      } else if (child.type === 'Points') {
        child.update();
      }
    }

    renderer.render(scene, camera);
  }

  function clear() {
    console.log('clearing');
    if(id) {
      cancelAnimationFrame( id );
    }
    renderer.clear();
    var clearTypes = ['Mesh', 'Points', 'Line'];
    if(scene) {
      for( var i = scene.children.length - 1; i >= 0; i--) {
        var obj = scene.children[i];
        var type = obj.type;
        if(clearTypes.indexOf(type) !== -1) {
          console.log('removing');
          scene.remove(obj);
        }
      }
    }
    console.log(scene);
    userPositions = [];
  }

  return {
    init: init,
    animate: animate,
    clear: clear,
    displayData: displayData
  };
}());
<commit_msg>add camera min distance<commit_after>var View = (function () {
  var container;

  var camera, cameraControls, scene, renderer, mesh, id;

  var clock = new THREE.Clock();

  var userPositions = [];

  function init() {

    renderer = new THREE.WebGLRenderer({
      antialias: true,
      alpha: true
    });

    renderer.setSize(window.innerWidth, window.innerHeight);

    container = document.createElement('div');

    container.appendChild(renderer.domElement);
    document.body.appendChild(container);

    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 10000);
    camera.position.set(1000, 1000, 0);

    cameraControls = new THREE.TrackballControls(camera, renderer.domElement);
    cameraControls.target.set(0, 0, 0);
    cameraControls.minDistance = 300;
    cameraControls.maxDistance = 3000;

    scene = new THREE.Scene();
    scene.fog = new THREE.FogExp2( 0x000000, 0.0005 );

  	var ambientLight	= new THREE.AmbientLight( 0x888888 );
  	scene.add( ambientLight );
    var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 );
    directionalLight.position.set( 0, 1, 0 );
    scene.add( directionalLight );

    window.addEventListener('resize', onWindowResize, false);

    // var sphere = new THREE.Mesh(
    //   new THREE.SphereGeometry(100, 32, 32),
    //   new THREE.MeshBasicMaterial({
    //     map: THREE.ImageUtils.loadTexture('assets/wallpaper.jpg')
    //   })
    // );
    // sphere.scale.x = -1;
    // scene.add(sphere);
  }

  function displayData(user, data) {

        var textureLoader = new THREE.TextureLoader();
        textureLoader.crossOrigin = 'Anonymous';

        var smallGeometry = new THREE.SphereGeometry(75, 32, 32);
        var mainGeometry = new THREE.SphereGeometry(250, 32, 32);
        var basicMaterial = new THREE.MeshBasicMaterial({
          color: 'white'
        });

        var main;
        // Draw main user
        textureLoader.load(user.profile_image_url, function(texture) {
          var mainMaterial = new THREE.MeshPhongMaterial({
            map: texture
          });
          main = new THREE.Mesh(mainGeometry, mainMaterial);
          main.quaternion.copy(camera.quaternion);
          scene.add(main);
          userPositions.push(main.position);

          for (var i = 0; i < data.length; i++) {
            (function(userData) {
              textureLoader.load(userData.profile_image_url, function(texture) {
                generateSphere(userData, texture);
              });
            })(data[i]);
          }
        });

        var ballTexture = new THREE.TextureLoader().load("/assets/ball.png" );

        function getRandomInt(min, max) {
          return Math.floor(Math.random() * (max - min + 1)) + min;
        }

        function getUserPosition() {
          var v = new THREE.Vector3(getRandomInt(-1000, 1000), getRandomInt(-1000, 1000), getRandomInt(-1000, 1000));
          var valid = true;
          for(var i = 0; i < userPositions.length; i++) {
            if (Math.abs(v.distanceTo(userPositions[i])) < 500) {
              return getUserPosition();
            }
          }
          return v;
        }
        // Draw other users
        var generateSphere = function (userData, texture) {
          var userMaterial = new THREE.MeshPhongMaterial({
            map: texture
          });
          var user = new THREE.Mesh(smallGeometry, userMaterial);
          user.quaternion.copy(camera.quaternion);

          var posVector = getUserPosition();

          user.position.set(posVector.x, posVector.y, posVector.z);

          userPositions.push(user.position);

          scene.add(user);
          if(userData.from_freq > 0) {
            createLine(user.position, main.position, userData.from_freq);
          }
          if(userData.to_freq > 0) {
            createLine(main.position, user.position, userData.to_freq);
          }
        };

        var createLine = function (v1, v2, freq) {

          var particleGeometry = new THREE.Geometry();

          //First create the line that we want to animate the particles along
          var geometry = new THREE.Geometry();
          geometry.vertices.push(v1);
          geometry.vertices.push(v2);

          var line = new THREE.Line(geometry, basicMaterial);
          var startPoint = line.geometry.vertices[0];
          var endPoint = line.geometry.vertices[1];
          scene.add(line);
          var pMaterial = new THREE.PointsMaterial({
            color: 0x00FF00,
            map: ballTexture,
            size: 30,
            blending: THREE.AdditiveBlending,
            transparent: true
          });
          pMaterial.color.setHSL( 1.0, 0.2, 0.7 );

          //next create a set of about 30 animation points along the line
          var animationPoints = createLinePoints(startPoint, endPoint);

          //add particles to scene
          var numParticles = 4 * freq;
          for (i = 0; i < numParticles; i++) {
            var desiredIndex = i / numParticles * animationPoints.length;
            var rIndex = constrain(Math.floor(desiredIndex), 0, animationPoints.length - 1);
            var particle = animationPoints[rIndex].clone();
            particle.moveIndex = rIndex;
            particle.nextIndex = rIndex + 1;
            if (particle.nextIndex >= animationPoints.length)
              particle.nextIndex = 0;
            particle.lerpN = 0;
            particle.path = animationPoints;
            particleGeometry.vertices.push(particle);
          }

          var particles = new THREE.Points(particleGeometry, pMaterial);
          scene.add(particles);
          particles.update = updateParticles;
        };

        //update function for each particle animation
        var updateParticles = function () {
          // var time = Date.now()
          for (var i in this.geometry.vertices) {
            var particle = this.geometry.vertices[i];
            var path = particle.path;
            particle.lerpN += 0.05;
            if (particle.lerpN > 1) {
              particle.lerpN = 0;
              particle.moveIndex = particle.nextIndex;
              particle.nextIndex++;
              if (particle.nextIndex >= path.length) {
                particle.moveIndex = 0;
                particle.nextIndex = 1;
              }
            }

            var currentPoint = path[particle.moveIndex];
            var nextPoint = path[particle.nextIndex];

            particle.copy(currentPoint);
            particle.lerp(nextPoint, particle.lerpN);
          }
          this.geometry.verticesNeedUpdate = true;
        };

        function createLinePoints(startPoint, endPoint) {
          var numPoints = 30;
          var returnPoints = [];
          for (i = 0; i <= numPoints; i++) {
            var thisPoint = startPoint.clone().lerp(endPoint, i / numPoints);
            returnPoints.push(thisPoint);
          }
          return returnPoints;
        }

        function constrain(v, min, max) {
          if (v < min)
            v = min;
          else
          if (v > max)
            v = max;
          return v;
        }
  }

  function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    renderer.setSize(window.innerWidth, window.innerHeight);

    render();
  }

  function animate() {
    var delta = clock.getDelta();

    id = requestAnimationFrame(animate);

    cameraControls.update(delta);

    for (var i = 0; i < scene.children.length; i++) {
      var child = scene.children[i];
      if (child.type === 'Mesh') {
        child.quaternion.copy(camera.quaternion);
      } else if (child.type === 'Points') {
        child.update();
      }
    }

    renderer.render(scene, camera);
  }

  function clear() {
    console.log('clearing');
    if(id) {
      cancelAnimationFrame( id );
    }
    renderer.clear();
    var clearTypes = ['Mesh', 'Points', 'Line'];
    if(scene) {
      for( var i = scene.children.length - 1; i >= 0; i--) {
        var obj = scene.children[i];
        var type = obj.type;
        if(clearTypes.indexOf(type) !== -1) {
          console.log('removing');
          scene.remove(obj);
        }
      }
    }
    console.log(scene);
    userPositions = [];
  }

  return {
    init: init,
    animate: animate,
    clear: clear,
    displayData: displayData
  };
}());
<|endoftext|>


<commit_before>var display = require('./display');
var gamejs = require('../gamejs');
/**
 * @fileoverview Methods for polling mouse and keyboard.
 *
 * Call `gamejs.event.get()` in your main loop to get a list of events that happend
 * since your last call.
 *
 * Note that some events, which would trigger a default browser action, are prevented
 * from triggering their default behaviour if and only if the game's display canvas has
 * focus (the game gets focus if the user clicked on the game).
 *
 * All events have a type identifier. This event type is in between the values
 * of NOEVENT and NUMEVENTS. Each event has a constant in `gamejs.event.*`
 * All user defined events can have the value of USEREVENT or higher.
 * Make sure your custom event ids* follow this system.
 *
 * A pattern for using the event loop: your main game function (tick in this example)
 * is being called by [gamejs.time.interval()](../time/#interval).
 * Inside tick we call [gamejs.event.get()](#get) for a list of events that happened since the last
 * tick and we loop over each event and act on the event properties.
 *
 * @example
 *     var events = gamejs.event.get()
 *     events.forEach(function(event) {
 *        if (event.type === gamejs.event.MOUSE_UP) {
 *          gamejs.log(event.pos, event.button);
 *        } else if (event.type === gamejs.event.KEY_UP) {
 *          gamejs.log(event.key);
 *        }
 *     });
 *
 */
// key constants
exports.K_UP = 38;
exports.K_DOWN = 40;
exports.K_RIGHT = 39;
exports.K_LEFT = 37;

exports.K_SPACE = 32;
exports.K_BACKSPACE = 8;
exports.K_TAB = 9;
exports.K_ENTER = 13;
exports.K_SHIFT = 16;
exports.K_CTRL = 17;
exports.K_ALT = 18;
exports.K_ESC = 27;

exports.K_0 = 48;
exports.K_1 = 49;
exports.K_2 = 50;
exports.K_3 = 51;
exports.K_4 = 52;
exports.K_5 = 53;
exports.K_6 = 54;
exports.K_7 = 55;
exports.K_8 = 56;
exports.K_9 = 57;
exports.K_a = 65;
exports.K_b = 66;
exports.K_c = 67;
exports.K_d = 68;
exports.K_e = 69;
exports.K_f = 70;
exports.K_g = 71;
exports.K_h = 72;
exports.K_i = 73;
exports.K_j = 74;
exports.K_k = 75;
exports.K_l = 76;
exports.K_m = 77;
exports.K_n = 78;
exports.K_o = 79;
exports.K_p = 80;
exports.K_q = 81;
exports.K_r = 82;
exports.K_s = 83;
exports.K_t = 84;
exports.K_u = 85;
exports.K_v = 86;
exports.K_w = 87;
exports.K_x = 88;
exports.K_y = 89;
exports.K_z = 90;

exports.K_KP1 = 97;
exports.K_KP2 = 98;
exports.K_KP3 = 99;
exports.K_KP4 = 100;
exports.K_KP5 = 101;
exports.K_KP6 = 102;
exports.K_KP7 = 103;
exports.K_KP8 = 104;
exports.K_KP9 = 105;

// event type constants
exports.NOEVENT = 0
exports.NUMEVENTS = 32000

exports.QUIT = 0;
exports.KEY_DOWN = 1;
exports.KEY_UP = 2;
exports.MOUSE_MOTION = 3;
exports.MOUSE_UP = 4;
exports.MOUSE_DOWN = 5;
exports.MOUSE_WHEEL = 6;
exports.USEREVENT = 2000;

exports.WORKER = 1000;
exports.WORKER_RESULT = 1001;
/** @ignore **/
exports.WORKER_ERROR = 1002;
/** @ignore **/
exports.WORKER_ALIVE = 1003;
/** @ignore **/
exports.WORKER_LOG = 1004;

var QUEUE = [];

/**
 * Get all events from the event queue
 * @returns {Array}
 */
exports.get = function() {
   return QUEUE.splice(0, QUEUE.length);
};

/**
 * Get the newest event of the event queue
 * @returns {gamejs.event.Event}
 */
exports.poll = function() {
   return QUEUE.pop();
};

/**
 * Post an event to the event queue.
 * @param {gamejs.event.Event} userEvent the event to post to the queue
 */
exports.post = function(userEvent) {
   if (userEvent.type === exports.WORKER_RESULT && gamejs.worker.inWorker === true) {
      gamejs.worker._messageMain(userEvent);
   } else if (userEvent.type === exports.WORKER && gamejs.worker.inWorker === false) {
      if (!userEvent.worker || !userEvent.worker.post) {
         throw new Error('Missing "worker" property on event');
      }
      userEvent.worker.post(userEvent.data);
   } else {
      QUEUE.push(userEvent);
   }
   return;
};

/**
 * Remove all events from the queue
 */
exports.clear = function() {
   QUEUE = [];
};

/**
 * Holds all information about an event.
 * @class
 */

exports.Event = function() {
    /**
     * The type of the event. e.g., gamejs.event.QUIT, KEYDOWN, MOUSEUP.
     */
    this.type = null;
    /**
     * key the keyCode of the key. compare with gamejs.event.K_a, gamejs.event.K_b,...
     */
    this.key = null;
    /**
     * relative movement for a mousemove event
     */
    this.rel = null;
    /**
     * the number of the mousebutton pressed
     */
    this.button = null;
    /**
     * pos the position of the event for mouse events
     */
    this.pos = null;
};

/**
 * @ignore
 */
exports.init = function() {

   var lastPos = [];

   // anonymous functions as event handlers = memory leak, see MDC:elementAddEventListener

   function onMouseDown (ev) {
      var canvasOffset = display._getCanvasOffset();
      QUEUE.push({
         'type': gamejs.event.MOUSE_DOWN,
         'pos': [ev.clientX - canvasOffset[0], ev.clientY - canvasOffset[1]],
         'button': ev.button,
         'shiftKey': ev.shiftKey,
         'ctrlKey': ev.ctrlKey,
         'metaKey': ev.metaKey
      });
   }

   function onMouseUp (ev) {
      var canvasOffset = display._getCanvasOffset();
      QUEUE.push({
         'type':gamejs.event.MOUSE_UP,
         'pos': [ev.clientX - canvasOffset[0], ev.clientY - canvasOffset[1]],
         'button': ev.button,
         'shiftKey': ev.shiftKey,
         'ctrlKey': ev.ctrlKey,
         'metaKey': ev.metaKey
      });
   }

   function onKeyDown (ev) {
      var key = ev.keyCode || ev.which;
      QUEUE.push({
         'type': gamejs.event.KEY_DOWN,
         'key': key,
         'shiftKey': ev.shiftKey,
         'ctrlKey': ev.ctrlKey,
         'metaKey': ev.metaKey
      });

      // if the display has focus, we surpress default action
      // for most keys
      if (display._hasFocus() && (!ev.ctrlKey && !ev.metaKey &&
         ((key >= exports.K_LEFT && key <= exports.K_DOWN) ||
         (key >= exports.K_0    && key <= exports.K_z) ||
         (key >= exports.K_KP1  && key <= exports.K_KP9) ||
         key === exports.K_SPACE ||
         key === exports.K_TAB ||
         key === exports.K_ENTER)) ||
         key === exports.K_ALT ||
         key === exports.K_BACKSPACE) {
        ev.preventDefault();
      }
   }

   function onKeyUp (ev) {
      QUEUE.push({
         'type': gamejs.event.KEY_UP,
         'key': ev.keyCode,
         'shiftKey': ev.shiftKey,
         'ctrlKey': ev.ctrlKey,
         'metaKey': ev.metaKey
      });
   }

   function onMouseMove (ev) {
      var canvasOffset = display._getCanvasOffset();
      var currentPos = [ev.clientX - canvasOffset[0], ev.clientY - canvasOffset[1]];
      var relativePos = [];
      if (lastPos.length) {
         relativePos = [
            lastPos[0] - currentPos[0],
            lastPos[1] - currentPos[1]
         ];
      }
      QUEUE.push({
         'type': gamejs.event.MOUSE_MOTION,
         'pos': currentPos,
         'rel': relativePos,
         'buttons': null, // FIXME, fixable?
         'timestamp': ev.timeStamp
      });
      lastPos = currentPos;
      return;
   }

   function onMouseScroll(ev) {
      var canvasOffset = display._getCanvasOffset();
      var currentPos = [ev.clientX - canvasOffset[0], ev.clientY - canvasOffset[1]];
      QUEUE.push({
         type: gamejs.event.MOUSE_WHEEL,
         pos: currentPos,
         delta: ev.detail || (- ev.wheelDeltaY / 40)
      });
      return;
   }

   function onBeforeUnload (ev) {
      QUEUE.push({
         'type': gamejs.event.QUIT
      });
      return;
   }

   // IEFIX does not support addEventListener on document itself
   // MOZFIX but in moz & opera events don't reach body if mouse outside window or on menubar
   var canvas = display.getSurface()._canvas;
   canvas.addEventListener('mousedown', onMouseDown, false);
   canvas.addEventListener('mouseup', onMouseUp, false);
   document.addEventListener('keydown', onKeyDown, false);
   document.addEventListener('keyup', onKeyUp, false);
   canvas.addEventListener('mousemove', onMouseMove, false);
   canvas.addEventListener('mousewheel', onMouseScroll, false);
   // MOZFIX
   // https://developer.mozilla.org/en/Code_snippets/Miscellaneous#Detecting_mouse_wheel_events
   canvas.addEventListener('DOMMouseScroll', onMouseScroll, false);
   canvas.addEventListener('beforeunload', onBeforeUnload, false);

};
<commit_msg>implement event.get(eventTypes) argument<commit_after>var display = require('./display');
var gamejs = require('../gamejs');
/**
 * @fileoverview Methods for polling mouse and keyboard.
 *
 * Call `gamejs.event.get()` in your main loop to get a list of events that happend
 * since your last call.
 *
 * Note that some events, which would trigger a default browser action, are prevented
 * from triggering their default behaviour if and only if the game's display canvas has
 * focus (the game gets focus if the user clicked on the game).
 *
 * All events have a type identifier. This event type is in between the values
 * of NOEVENT and NUMEVENTS. Each event has a constant in `gamejs.event.*`
 * All user defined events can have the value of USEREVENT or higher.
 * Make sure your custom event ids* follow this system.
 *
 * A pattern for using the event loop: your main game function (tick in this example)
 * is being called by [gamejs.time.interval()](../time/#interval).
 * Inside tick we call [gamejs.event.get()](#get) for a list of events that happened since the last
 * tick and we loop over each event and act on the event properties.
 *
 * @example
 *     var events = gamejs.event.get()
 *     events.forEach(function(event) {
 *        if (event.type === gamejs.event.MOUSE_UP) {
 *          gamejs.log(event.pos, event.button);
 *        } else if (event.type === gamejs.event.KEY_UP) {
 *          gamejs.log(event.key);
 *        }
 *     });
 *
 */
// key constants
exports.K_UP = 38;
exports.K_DOWN = 40;
exports.K_RIGHT = 39;
exports.K_LEFT = 37;

exports.K_SPACE = 32;
exports.K_BACKSPACE = 8;
exports.K_TAB = 9;
exports.K_ENTER = 13;
exports.K_SHIFT = 16;
exports.K_CTRL = 17;
exports.K_ALT = 18;
exports.K_ESC = 27;

exports.K_0 = 48;
exports.K_1 = 49;
exports.K_2 = 50;
exports.K_3 = 51;
exports.K_4 = 52;
exports.K_5 = 53;
exports.K_6 = 54;
exports.K_7 = 55;
exports.K_8 = 56;
exports.K_9 = 57;
exports.K_a = 65;
exports.K_b = 66;
exports.K_c = 67;
exports.K_d = 68;
exports.K_e = 69;
exports.K_f = 70;
exports.K_g = 71;
exports.K_h = 72;
exports.K_i = 73;
exports.K_j = 74;
exports.K_k = 75;
exports.K_l = 76;
exports.K_m = 77;
exports.K_n = 78;
exports.K_o = 79;
exports.K_p = 80;
exports.K_q = 81;
exports.K_r = 82;
exports.K_s = 83;
exports.K_t = 84;
exports.K_u = 85;
exports.K_v = 86;
exports.K_w = 87;
exports.K_x = 88;
exports.K_y = 89;
exports.K_z = 90;

exports.K_KP1 = 97;
exports.K_KP2 = 98;
exports.K_KP3 = 99;
exports.K_KP4 = 100;
exports.K_KP5 = 101;
exports.K_KP6 = 102;
exports.K_KP7 = 103;
exports.K_KP8 = 104;
exports.K_KP9 = 105;

// event type constants
exports.NOEVENT = 0
exports.NUMEVENTS = 32000

exports.QUIT = 0;
exports.KEY_DOWN = 1;
exports.KEY_UP = 2;
exports.MOUSE_MOTION = 3;
exports.MOUSE_UP = 4;
exports.MOUSE_DOWN = 5;
exports.MOUSE_WHEEL = 6;
exports.USEREVENT = 2000;

exports.WORKER = 1000;
exports.WORKER_RESULT = 1001;
/** @ignore **/
exports.WORKER_ERROR = 1002;
/** @ignore **/
exports.WORKER_ALIVE = 1003;
/** @ignore **/
exports.WORKER_LOG = 1004;

var QUEUE = [];

/**
 * Get all events from the event queue
 * @returns {Array}
 */
exports.get = function(eventTypes) {
  if (eventTypes === undefined) {
    return QUEUE.splice(0, QUEUE.length);
  } else {
    if (! (eventTypes instanceof Array)) {
      eventTypes = [eventTypes];
    }
    var result = [];
    QUEUE = QUEUE.filter(function(event) {
      if (eventTypes.indexOf(event.type) === -1) {
        return true;
      }
      result.push(event)
      return false;
    })
    return result;
  }
};

/**
 * Get the newest event of the event queue
 * @returns {gamejs.event.Event}
 */
exports.poll = function() {
   return QUEUE.pop();
};

/**
 * Post an event to the event queue.
 * @param {gamejs.event.Event} userEvent the event to post to the queue
 */
exports.post = function(userEvent) {
   if (userEvent.type === exports.WORKER_RESULT && gamejs.worker.inWorker === true) {
      gamejs.worker._messageMain(userEvent);
   } else if (userEvent.type === exports.WORKER && gamejs.worker.inWorker === false) {
      if (!userEvent.worker || !userEvent.worker.post) {
         throw new Error('Missing "worker" property on event');
      }
      userEvent.worker.post(userEvent.data);
   } else {
      QUEUE.push(userEvent);
   }
   return;
};

/**
 * Remove all events from the queue
 */
exports.clear = function() {
   QUEUE = [];
};

/**
 * Holds all information about an event.
 * @class
 */

exports.Event = function() {
    /**
     * The type of the event. e.g., gamejs.event.QUIT, KEYDOWN, MOUSEUP.
     */
    this.type = null;
    /**
     * key the keyCode of the key. compare with gamejs.event.K_a, gamejs.event.K_b,...
     */
    this.key = null;
    /**
     * relative movement for a mousemove event
     */
    this.rel = null;
    /**
     * the number of the mousebutton pressed
     */
    this.button = null;
    /**
     * pos the position of the event for mouse events
     */
    this.pos = null;
};

/**
 * @ignore
 */
exports.init = function() {

   var lastPos = [];

   // anonymous functions as event handlers = memory leak, see MDC:elementAddEventListener

   function onMouseDown (ev) {
      var canvasOffset = display._getCanvasOffset();
      QUEUE.push({
         'type': gamejs.event.MOUSE_DOWN,
         'pos': [ev.clientX - canvasOffset[0], ev.clientY - canvasOffset[1]],
         'button': ev.button,
         'shiftKey': ev.shiftKey,
         'ctrlKey': ev.ctrlKey,
         'metaKey': ev.metaKey
      });
   }

   function onMouseUp (ev) {
      var canvasOffset = display._getCanvasOffset();
      QUEUE.push({
         'type':gamejs.event.MOUSE_UP,
         'pos': [ev.clientX - canvasOffset[0], ev.clientY - canvasOffset[1]],
         'button': ev.button,
         'shiftKey': ev.shiftKey,
         'ctrlKey': ev.ctrlKey,
         'metaKey': ev.metaKey
      });
   }

   function onKeyDown (ev) {
      var key = ev.keyCode || ev.which;
      QUEUE.push({
         'type': gamejs.event.KEY_DOWN,
         'key': key,
         'shiftKey': ev.shiftKey,
         'ctrlKey': ev.ctrlKey,
         'metaKey': ev.metaKey
      });

      // if the display has focus, we surpress default action
      // for most keys
      if (display._hasFocus() && (!ev.ctrlKey && !ev.metaKey &&
         ((key >= exports.K_LEFT && key <= exports.K_DOWN) ||
         (key >= exports.K_0    && key <= exports.K_z) ||
         (key >= exports.K_KP1  && key <= exports.K_KP9) ||
         key === exports.K_SPACE ||
         key === exports.K_TAB ||
         key === exports.K_ENTER)) ||
         key === exports.K_ALT ||
         key === exports.K_BACKSPACE) {
        ev.preventDefault();
      }
   }

   function onKeyUp (ev) {
      QUEUE.push({
         'type': gamejs.event.KEY_UP,
         'key': ev.keyCode,
         'shiftKey': ev.shiftKey,
         'ctrlKey': ev.ctrlKey,
         'metaKey': ev.metaKey
      });
   }

   function onMouseMove (ev) {
      var canvasOffset = display._getCanvasOffset();
      var currentPos = [ev.clientX - canvasOffset[0], ev.clientY - canvasOffset[1]];
      var relativePos = [];
      if (lastPos.length) {
         relativePos = [
            lastPos[0] - currentPos[0],
            lastPos[1] - currentPos[1]
         ];
      }
      QUEUE.push({
         'type': gamejs.event.MOUSE_MOTION,
         'pos': currentPos,
         'rel': relativePos,
         'buttons': null, // FIXME, fixable?
         'timestamp': ev.timeStamp
      });
      lastPos = currentPos;
      return;
   }

   function onMouseScroll(ev) {
      var canvasOffset = display._getCanvasOffset();
      var currentPos = [ev.clientX - canvasOffset[0], ev.clientY - canvasOffset[1]];
      QUEUE.push({
         type: gamejs.event.MOUSE_WHEEL,
         pos: currentPos,
         delta: ev.detail || (- ev.wheelDeltaY / 40)
      });
      return;
   }

   function onBeforeUnload (ev) {
      QUEUE.push({
         'type': gamejs.event.QUIT
      });
      return;
   }

   // IEFIX does not support addEventListener on document itself
   // MOZFIX but in moz & opera events don't reach body if mouse outside window or on menubar
   var canvas = display.getSurface()._canvas;
   document.addEventListener('mousedown', onMouseDown, false);
   document.addEventListener('mouseup', onMouseUp, false);
   document.addEventListener('keydown', onKeyDown, false);
   document.addEventListener('keyup', onKeyUp, false);
   canvas.addEventListener('mousemove', onMouseMove, false);
   canvas.addEventListener('mousewheel', onMouseScroll, false);
   // MOZFIX
   // https://developer.mozilla.org/en/Code_snippets/Miscellaneous#Detecting_mouse_wheel_events
   canvas.addEventListener('DOMMouseScroll', onMouseScroll, false);
   canvas.addEventListener('beforeunload', onBeforeUnload, false);

};
<|endoftext|>


<commit_before>/*
 * Copyright 2009-2011 Mozilla Foundation and contributors
 * Licensed under the New BSD license. See LICENSE.txt or:
 * http://opensource.org/licenses/BSD-3-Clause
 */

define(function(require, exports, module) {

/**
 * Create an unfulfilled promise
 */
var Promise = function () {
  this._status = Promise.PENDING;
  this._value = undefined;
  this._onSuccessHandlers = [];
  this._onErrorHandlers = [];

  // Debugging help
  this._id = Promise._nextId++;
  Promise._outstanding[this._id] = this;
};

/**
 * We give promises and ID so we can track which are outstanding
 */
Promise._nextId = 0;

/**
 * Outstanding promises. Handy list for debugging only.
 */
Promise._outstanding = [];

/**
 * Recently resolved promises. Also for debugging only.
 */
Promise._recent = [];

/**
 * A promise can be in one of 2 states.
 * The ERROR and SUCCESS states are terminal, the PENDING state is the only
 * start state.
 */
Promise.ERROR = -1;
Promise.PENDING = 0;
Promise.SUCCESS = 1;

/**
 * Yeay for RTTI.
 */
Promise.prototype.isPromise = true;

/**
 * Have we either been resolve()ed or reject()ed?
 */
Promise.prototype.isComplete = function() {
  return this._status != PENDING;
};

/**
 * Have we resolve()ed?
 */
Promise.prototype.isResolved = function() {
  return this._status == Promise.SUCCESS;
};

/**
 * Have we reject()ed?
 */
Promise.prototype.isRejected = function() {
  return this._status == Promise.ERROR;
};

/**
 * Take the specified action of fulfillment of a promise, and (optionally)
 * a different action on promise rejection.
 */
Promise.prototype.then = function(onSuccess, onError) {
  if (typeof onSuccess === 'function') {
    if (this._status === Promise.SUCCESS) {
      onSuccess.call(null, this._value);
    } else if (this._status === Promise.PENDING) {
      this._onSuccessHandlers.push(onSuccess);
    }
  }

  if (typeof onError === 'function') {
    if (this._status === Promise.ERROR) {
      onError.call(null, this._value);
    } else if (this._status === Promise.PENDING) {
      this._onErrorHandlers.push(onError);
    }
  }

  return this;
};

/**
 * Like then() except that rather than returning <tt>this</tt> we return
 * a promise which resolves when the original promise resolves.
 */
Promise.prototype.chainPromise = function(onSuccess) {
  var chain = new Promise();
  chain._chainedFrom = this;
  this.then(function(data) {
    try {
      chain.resolve(onSuccess(data));
    } catch (ex) {
      chain.reject(ex);
    }
  }, function(ex) {
    chain.reject(ex);
  });
  return chain;
};

/**
 * Supply the fulfillment of a promise
 */
Promise.prototype.resolve = function(data) {
  return this._complete(this._onSuccessHandlers, Promise.SUCCESS, data, 'resolve');
};

/**
 * Renege on a promise
 */
Promise.prototype.reject = function(data) {
  return this._complete(this._onErrorHandlers, Promise.ERROR, data, 'reject');
};

/**
 * Internal method to be called on resolve() or reject().
 * @private
 */
Promise.prototype._complete = function(list, status, data, name) {
  // Complain if we've already been completed
  if (this._status != Promise.PENDING) {
    console.group('Promise already closed');
    console.error('Attempted ' + name + '() with ', data);
    console.error('Previous status = ', this._status,
        ', previous value = ', this._value);
    console.trace();

    console.groupEnd();
    return this;
  }

  this._status = status;
  this._value = data;

  // Call all the handlers, and then delete them
  list.forEach(function(handler) {
    handler.call(null, this._value);
  }, this);
  this._onSuccessHandlers.length = 0;
  this._onErrorHandlers.length = 0;

  // Remove the given {promise} from the _outstanding list, and add it to the
  // _recent list, pruning more than 20 recent promises from that list.
  delete Promise._outstanding[this._id];
  Promise._recent.push(this);
  while (Promise._recent.length > 20) {
    Promise._recent.shift();
  }

  return this;
};

/**
 * Takes an array of promises and returns a promise that that is fulfilled once
 * all the promises in the array are fulfilled
 * @param promiseList The array of promises
 * @return the promise that is fulfilled when all the array is fulfilled
 */
Promise.group = function(promiseList) {
  if (!(promiseList instanceof Array)) {
    promiseList = Array.prototype.slice.call(arguments);
  }

  // If the original array has nothing in it, return now to avoid waiting
  if (promiseList.length === 0) {
    return new Promise().resolve([]);
  }

  var groupPromise = new Promise();
  var results = [];
  var fulfilled = 0;

  var onSuccessFactory = function(index) {
    return function(data) {
      results[index] = data;
      fulfilled++;
      // If the group has already failed, silently drop extra results
      if (groupPromise._status !== Promise.ERROR) {
        if (fulfilled === promiseList.length) {
          groupPromise.resolve(results);
        }
      }
    };
  };

  promiseList.forEach(function(promise, index) {
    var onSuccess = onSuccessFactory(index);
    var onError = groupPromise.reject.bind(groupPromise);
    promise.then(onSuccess, onError);
  });

  return groupPromise;
};

exports.Promise = Promise;

});
<commit_msg>Bug 692424 (promise): Tidyup. The bug is question is about adding promises to central, but this made me review the code and make these tweaks.<commit_after>/*
 * Copyright 2009-2011 Mozilla Foundation and contributors
 * Licensed under the New BSD license. See LICENSE.txt or:
 * http://opensource.org/licenses/BSD-3-Clause
 */

define(function(require, exports, module) {

/**
 * Create an unfulfilled promise
 * @constructor
 */
function Promise() {
  this._status = Promise.PENDING;
  this._value = undefined;
  this._onSuccessHandlers = [];
  this._onErrorHandlers = [];

  // Debugging help
  this._id = Promise._nextId++;
  Promise._outstanding[this._id] = this;
};

/**
 * We give promises and ID so we can track which are outstanding
 */
Promise._nextId = 0;

/**
 * Outstanding promises. Handy list for debugging only.
 */
Promise._outstanding = [];

/**
 * Recently resolved promises. Also for debugging only.
 */
Promise._recent = [];

/**
 * A promise can be in one of 2 states.
 * The ERROR and SUCCESS states are terminal, the PENDING state is the only
 * start state.
 */
Promise.ERROR = -1;
Promise.PENDING = 0;
Promise.SUCCESS = 1;

/**
 * Yeay for RTTI.
 */
Promise.prototype.isPromise = true;

/**
 * Have we either been resolve()ed or reject()ed?
 */
Promise.prototype.isComplete = function() {
  return this._status != Promise.PENDING;
};

/**
 * Have we resolve()ed?
 */
Promise.prototype.isResolved = function() {
  return this._status == Promise.SUCCESS;
};

/**
 * Have we reject()ed?
 */
Promise.prototype.isRejected = function() {
  return this._status == Promise.ERROR;
};

/**
 * Take the specified action of fulfillment of a promise, and (optionally)
 * a different action on promise rejection.
 */
Promise.prototype.then = function(onSuccess, onError) {
  if (typeof onSuccess === 'function') {
    if (this._status === Promise.SUCCESS) {
      onSuccess.call(null, this._value);
    } else if (this._status === Promise.PENDING) {
      this._onSuccessHandlers.push(onSuccess);
    }
  }

  if (typeof onError === 'function') {
    if (this._status === Promise.ERROR) {
      onError.call(null, this._value);
    } else if (this._status === Promise.PENDING) {
      this._onErrorHandlers.push(onError);
    }
  }

  return this;
};

/**
 * Like then() except that rather than returning <tt>this</tt> we return
 * a promise which resolves when the original promise resolves.
 */
Promise.prototype.chainPromise = function(onSuccess) {
  var chain = new Promise();
  chain._chainedFrom = this;
  this.then(function(data) {
    try {
      chain.resolve(onSuccess(data));
    } catch (ex) {
      chain.reject(ex);
    }
  }, function(ex) {
    chain.reject(ex);
  });
  return chain;
};

/**
 * Supply the fulfillment of a promise
 */
Promise.prototype.resolve = function(data) {
  return this._complete(this._onSuccessHandlers, Promise.SUCCESS, data, 'resolve');
};

/**
 * Renege on a promise
 */
Promise.prototype.reject = function(data) {
  return this._complete(this._onErrorHandlers, Promise.ERROR, data, 'reject');
};

/**
 * Internal method to be called on resolve() or reject().
 * @private
 */
Promise.prototype._complete = function(list, status, data, name) {
  // Complain if we've already been completed
  if (this._status != Promise.PENDING) {
    console.group('Promise already closed');
    console.error('Attempted ' + name + '() with ', data);
    console.error('Previous status = ', this._status,
        ', previous value = ', this._value);
    console.trace();

    console.groupEnd();
    return this;
  }

  this._status = status;
  this._value = data;

  // Call all the handlers, and then delete them
  list.forEach(function(handler) {
    handler.call(null, this._value);
  }, this);
  delete this._onSuccessHandlers;
  delete this._onErrorHandlers;

  // Remove the given {promise} from the _outstanding list, and add it to the
  // _recent list, pruning more than 20 recent promises from that list.
  delete Promise._outstanding[this._id];
  Promise._recent.push(this);
  while (Promise._recent.length > 20) {
    Promise._recent.shift();
  }

  return this;
};

/**
 * Takes an array of promises and returns a promise that that is fulfilled once
 * all the promises in the array are fulfilled
 * @param promiseList The array of promises
 * @return the promise that is fulfilled when all the array is fulfilled
 */
Promise.group = function(promiseList) {
  if (!(promiseList instanceof Array)) {
    promiseList = Array.prototype.slice.call(arguments);
  }

  // If the original array has nothing in it, return now to avoid waiting
  if (promiseList.length === 0) {
    return new Promise().resolve([]);
  }

  var groupPromise = new Promise();
  var results = [];
  var fulfilled = 0;

  var onSuccessFactory = function(index) {
    return function(data) {
      results[index] = data;
      fulfilled++;
      // If the group has already failed, silently drop extra results
      if (groupPromise._status !== Promise.ERROR) {
        if (fulfilled === promiseList.length) {
          groupPromise.resolve(results);
        }
      }
    };
  };

  promiseList.forEach(function(promise, index) {
    var onSuccess = onSuccessFactory(index);
    var onError = groupPromise.reject.bind(groupPromise);
    promise.then(onSuccess, onError);
  });

  return groupPromise;
};

exports.Promise = Promise;

});
<|endoftext|>


<commit_before>const Queue = require('promise-queue')

const env = require('./env')
const dbs = require('../lib/dbs')
const Log = require('gk-log')
const statsd = require('./statsd')
const getToken = require('./get-token')
const Github = require('../lib/github')

const readQueues = {}
const writeQueues = {}

function getWriteQueue (installationId) {
  // requests per user should be serial
  // hence the queue concurrency of 1
  const writeQueue = writeQueues[installationId] || new Queue(1, Infinity)
  writeQueues[installationId] = writeQueue
  return writeQueue
}

function getReadQueue (installationId) {
  // requests per user should be serial
  // hence the queue concurrency of 1
  const readQueue = readQueues[installationId] || new Queue(1, Infinity)
  readQueues[installationId] = readQueue
  return readQueue
}

module.exports = function (installationId) {
  return {
    write: write.bind(null, installationId),
    read: read.bind(null, installationId)
  }
}

function setupLog () {
  const logs = dbs.getLogsDb()
  const log = Log({logsDb: logs, context: 'github-queue'})
  return log
}

function reportStats (stats) {
  const waitingInQueue = stats.started - stats.queued
  const waitingForToken = stats.tokenized - stats.started
  const waitingForResponse = stats.done - stats.tokenized
  const waitingForNetwork = stats.done - stats.started
  const total = stats.done - stats.queued
  statsd.gauge(`queues.github_${stats.type}_requests_time_in_queue`, waitingInQueue)
  statsd.gauge(`queues.github_${stats.type}_requests_time_to_token`, waitingForToken)
  statsd.gauge(`queues.github_${stats.type}_requests_time_to_response`, waitingForResponse)
  statsd.gauge(`queues.github_${stats.type}_requests_time_in_network`, waitingForNetwork)
  statsd.gauge(`queues.github_${stats.type}_requests_time_total`, total)
}

function getGitHubMethod (gen) {
  const [ match ] = gen.toString().match(/github\.[^(]+/)
  return match || ''
}

function write (installationId, gen) {
  statsd.increment('queues.github_write_requests')
  statsd.increment('queues.github_write_requests_tagged', [installationId, getGitHubMethod(gen)])
  const stats = {
    type: 'write',
    queued: Date.now()
  }
  try {
    const writeQueue = getWriteQueue(installationId)
    return writeQueue.add(() => {
      return Promise.delay(env.NODE_ENV === 'testing' ? 0 : 1000)
        .then(() => {
          stats.started = Date.now()
          return getToken(installationId)
        })
        .then(({token}) => {
          stats.tokenized = Date.now()
          const github = Github()
          github.authenticate({ type: 'token', token })
          return gen(github)
        })
        .then(response => {
          stats.done = Date.now()
          reportStats(stats)
          return response.data
        })
    })
  } catch (e) {
    statsd.increment('queues.github_write_failures')
    const log = setupLog()
    log.warn('github: write exception', { installationId, exception: e })
    throw e
  }
}

function read (installationId, gen) {
  statsd.increment('queues.github_read_requests')
  statsd.increment('queues.github_read_requests_tagged', [installationId, getGitHubMethod(gen)])
  const stats = {
    type: 'read',
    queued: Date.now()
  }
  try {
    const readQueue = getReadQueue(installationId)
    return readQueue.add(() => {
      stats.started = Date.now()
      return getToken(installationId)
        .then(({token}) => {
          stats.tokenized = Date.now()
          const github = Github()
          github.authenticate({ type: 'token', token })
          return gen(github)
        })
        .then(response => {
          stats.done = Date.now()
          reportStats(stats)
          if (response.data) {
            return response.data
          }

          // some responses don’t have .data, let’s find out which
          // https://rollbar.com/neighbourhoodie/gk-jobs/items/2969/

          var logs = dbs.getLogsDB()
          const log = Log({logsDb: logs, accountId: 'github', repoSlug: 'queue', context: 'github-queue'})
          log.warn('github: response.data is `undefined`', { installationId, response })
        })
    })
  } catch (e) {
    statsd.increment('queues.github_read_failures')
    const log = setupLog()
    log.warn('github: read exception', { installationId, exception: e })
    throw e
  }
}

if (env.NODE_ENV !== 'testing') {
  setInterval(
    function collectGitHubQueueStats () {
      const readQueueKeys = Object.keys(readQueues)
      const writeQueueKeys = Object.keys(writeQueues)
      statsd.gauge('queues.github.read.queueCount', readQueueKeys.length)
      statsd.gauge('queues.github.write.queueCount', writeQueueKeys.length)
      readQueueKeys.forEach((installationId) => {
        statsd.gauge('queues.github.installation.read', readQueues[installationId].length, {tag: installationId})
      })
      writeQueueKeys.forEach((installationId) => {
        statsd.gauge('queues.github.installation.write', writeQueues[installationId].length, {tag: installationId})
      })
    },
    1000
  )
}
<commit_msg>fix: metric name<commit_after>const Queue = require('promise-queue')

const env = require('./env')
const dbs = require('../lib/dbs')
const Log = require('gk-log')
const statsd = require('./statsd')
const getToken = require('./get-token')
const Github = require('../lib/github')

const readQueues = {}
const writeQueues = {}

function getWriteQueue (installationId) {
  // requests per user should be serial
  // hence the queue concurrency of 1
  const writeQueue = writeQueues[installationId] || new Queue(1, Infinity)
  writeQueues[installationId] = writeQueue
  return writeQueue
}

function getReadQueue (installationId) {
  // requests per user should be serial
  // hence the queue concurrency of 1
  const readQueue = readQueues[installationId] || new Queue(1, Infinity)
  readQueues[installationId] = readQueue
  return readQueue
}

module.exports = function (installationId) {
  return {
    write: write.bind(null, installationId),
    read: read.bind(null, installationId)
  }
}

function setupLog () {
  const logs = dbs.getLogsDb()
  const log = Log({logsDb: logs, context: 'github-queue'})
  return log
}

function reportStats (stats) {
  const waitingInQueue = stats.started - stats.queued
  const waitingForToken = stats.tokenized - stats.started
  const waitingForResponse = stats.done - stats.tokenized
  const waitingForNetwork = stats.done - stats.started
  const total = stats.done - stats.queued
  statsd.gauge(`queues.github_${stats.type}_requests_time_in_queue`, waitingInQueue)
  statsd.gauge(`queues.github_${stats.type}_requests_time_to_token`, waitingForToken)
  statsd.gauge(`queues.github_${stats.type}_requests_time_to_response`, waitingForResponse)
  statsd.gauge(`queues.github_${stats.type}_requests_time_in_network`, waitingForNetwork)
  statsd.gauge(`queues.github_${stats.type}_requests_time_total`, total)
}

function getGitHubMethod (gen) {
  const [ match ] = gen.toString().match(/github\.[^(]+/)
  return match || ''
}

function write (installationId, gen) {
  statsd.increment('queues.github_write_requests')
  statsd.increment('queues.github.write.tagged', [installationId, getGitHubMethod(gen)])
  const stats = {
    type: 'write',
    queued: Date.now()
  }
  try {
    const writeQueue = getWriteQueue(installationId)
    return writeQueue.add(() => {
      return Promise.delay(env.NODE_ENV === 'testing' ? 0 : 1000)
        .then(() => {
          stats.started = Date.now()
          return getToken(installationId)
        })
        .then(({token}) => {
          stats.tokenized = Date.now()
          const github = Github()
          github.authenticate({ type: 'token', token })
          return gen(github)
        })
        .then(response => {
          stats.done = Date.now()
          reportStats(stats)
          return response.data
        })
    })
  } catch (e) {
    statsd.increment('queues.github_write_failures')
    const log = setupLog()
    log.warn('github: write exception', { installationId, exception: e })
    throw e
  }
}

function read (installationId, gen) {
  statsd.increment('queues.github_read_requests')
  statsd.increment('queues.github.read.tagged', [installationId, getGitHubMethod(gen)])
  const stats = {
    type: 'read',
    queued: Date.now()
  }
  try {
    const readQueue = getReadQueue(installationId)
    return readQueue.add(() => {
      stats.started = Date.now()
      return getToken(installationId)
        .then(({token}) => {
          stats.tokenized = Date.now()
          const github = Github()
          github.authenticate({ type: 'token', token })
          return gen(github)
        })
        .then(response => {
          stats.done = Date.now()
          reportStats(stats)
          if (response.data) {
            return response.data
          }

          // some responses don’t have .data, let’s find out which
          // https://rollbar.com/neighbourhoodie/gk-jobs/items/2969/

          var logs = dbs.getLogsDB()
          const log = Log({logsDb: logs, accountId: 'github', repoSlug: 'queue', context: 'github-queue'})
          log.warn('github: response.data is `undefined`', { installationId, response })
        })
    })
  } catch (e) {
    statsd.increment('queues.github_read_failures')
    const log = setupLog()
    log.warn('github: read exception', { installationId, exception: e })
    throw e
  }
}

if (env.NODE_ENV !== 'testing') {
  setInterval(
    function collectGitHubQueueStats () {
      const readQueueKeys = Object.keys(readQueues)
      const writeQueueKeys = Object.keys(writeQueues)
      statsd.gauge('queues.github.read.queueCount', readQueueKeys.length)
      statsd.gauge('queues.github.write.queueCount', writeQueueKeys.length)
      readQueueKeys.forEach((installationId) => {
        statsd.gauge('queues.github.installation.read', readQueues[installationId].length, {tag: installationId})
      })
      writeQueueKeys.forEach((installationId) => {
        statsd.gauge('queues.github.installation.write', writeQueues[installationId].length, {tag: installationId})
      })
    },
    1000
  )
}
<|endoftext|>


<commit_before>'use strict';

var request = require('request');
var q = require('q');
var GnAuth = require('./gn-auth');

function GnEndpoints(options, constants) {
  options.baseUrl = options.sandbox ? constants.URL.sandbox : constants.URL.production;
  this.options = options;
  this.gnAuth = new GnAuth(options, constants);
  this.accessToken = null;
  this.constants = constants;
}

GnEndpoints.prototype.run = function (name, params, body) {
  var self = this;
  self.defer = q.defer();
  self.endpoint = self.constants.ENDPOINTS[name];
  self.body = body;
  self.params = params;

  if (!self.accessToken) {
    self.getAccessToken()
      .then(self.directReq.bind(self));
  } else {
    self.withTokenReq.call(self);
  }

  return self.defer.promise;
};

GnEndpoints.prototype.getAccessToken = function () {
  var self = this;
  return this.gnAuth.getAccessToken()
    .then(function (response) {
      self.accessToken = response.access_token;
      return self.accessToken;
    }).catch(function (err) {
      return err;
    });
};

GnEndpoints.prototype.getResponse = function (response, body) {
  return this.options
    .raw_response ? response : body;
};

GnEndpoints.prototype.req = function (callback) {
  request[this.endpoint.method](this.getParams.call(this,
    this.endpoint.route), callback);
}

GnEndpoints.prototype.directReq = function () {
  this.req(this.directReqCallback.bind(this));
};

GnEndpoints.prototype.withTokenReq = function () {
  this.req(this.withTokenReqCallback.bind(this));
};

GnEndpoints.prototype.getParams = function (route) {
  var self = this;
  var regex = /\:(\w+)/g;
  var query = '';
  var placeholders = route.match(regex) || {};
  var params = {};

  for (var prop in self.params) {
    params[prop] = self.params[prop];
  }

  var getVariables = function () {
    return placeholders.map(function (item) {
      return item.replace(':', '');
    });
  };

  var updateRoute = function () {
    var variables = getVariables();
    variables.forEach(function (value, index) {
      if (params[value]) {
        route = route.replace(placeholders[index], params[value]);
        delete params[value];
      }
    });
  };

  var getQueryString = function () {
    var keys = Object.keys(params);
    var initial = keys.length >= 1 ? '?' : '';
    return keys.reduce(function (previous, current, index, array) {
      var next = (index === array.length - 1) ? '' : '&';
      return [previous, current, '=',
        params[current], next
      ].join('');
    }, initial);
  };

  updateRoute();
  query = getQueryString();

  var req = {
    url: [this.options.baseUrl, route, query].join(''),
    json: true,
    body: this.body
  };

  if (this.accessToken) {
    req.auth = {
      bearer: this.accessToken
    };
  }

  return req;
};

GnEndpoints.prototype.withTokenReqCallback = function (err, httpResponse, httpResponseBody) {
  var self = this;
  var response = self.getResponse(httpResponse, httpResponseBody);

  if (err) {
    self.defer.reject(err);
  } else if (httpResponse.statusCode === 401) {
    self.getAccessToken().then(self.directReq.bind(self));
  } else if (httpResponse.statusCode !== 200) {
    self.defer.reject(response);
  } else {
    self.defer.resolve(response);
  }
};

GnEndpoints.prototype.directReqCallback = function (err, httpResponse, bodyResponse) {
  var response = this.getResponse(httpResponse, bodyResponse);

  if (err) {
    this.defer.reject(err);
  } else if (httpResponse.statusCode !== 200) {
    this.defer.reject(response);
  } else {
    this.defer.resolve(response);
  }
};

module.exports = GnEndpoints;<commit_msg>Fix tests<commit_after>'use strict';

var request = require('request');
var q = require('q');
var GnAuth = require('./gn-auth');

function GnEndpoints(options, constants) {
  options.baseUrl = options.sandbox ? constants.URL.sandbox : constants.URL.production;
  this.options = options;
  this.gnAuth = new GnAuth(options, constants);
  this.accessToken = null;
  this.constants = constants;
}

GnEndpoints.prototype.run = function (name, params, body) {
  var self = this;
  self.defer = q.defer();
  self.endpoint = self.constants.ENDPOINTS[name];
  self.body = body;
  self.params = params;

  if (!self.accessToken) {
    self.getAccessToken()
      .then(self.directReq.bind(self));
  } else {
    self.withTokenReq.call(self);
  }

  return self.defer.promise;
};

GnEndpoints.prototype.getAccessToken = function () {
  var self = this;
  return this.gnAuth.getAccessToken()
    .then(function (response) {
      self.accessToken = response.access_token;
      return self.accessToken;
    }).catch(function (err) {
      return err;
    });
};

GnEndpoints.prototype.getResponse = function (response, body) {
  return this.options
    .raw_response ? response : body;
};

GnEndpoints.prototype.req = function (callback) {
  request[this.endpoint.method](this.getParams.call(this,
    this.endpoint.route), callback);
}

GnEndpoints.prototype.directReq = function () {
  this.req(this.directReqCallback.bind(this));
};

GnEndpoints.prototype.withTokenReq = function () {
  this.req(this.withTokenReqCallback.bind(this));
};

GnEndpoints.prototype.getParams = function (route) {
  var self = this;
  var regex = /\:(\w+)/g;
  var query = '';
  var placeholders = route.match(regex) || [];
  var params = {};

  for (var prop in self.params) {
    params[prop] = self.params[prop];
  }

  var getVariables = function () {
    return placeholders.map(function (item) {
      return item.replace(':', '');
    });
  };

  var updateRoute = function () {
    var variables = getVariables();
    variables.forEach(function (value, index) {
      if (params[value]) {
        route = route.replace(placeholders[index], params[value]);
        delete params[value];
      }
    });
  };

  var getQueryString = function () {
    var keys = Object.keys(params);
    var initial = keys.length >= 1 ? '?' : '';
    return keys.reduce(function (previous, current, index, array) {
      var next = (index === array.length - 1) ? '' : '&';
      return [previous, current, '=',
        params[current], next
      ].join('');
    }, initial);
  };

  updateRoute();
  query = getQueryString();

  var req = {
    url: [this.options.baseUrl, route, query].join(''),
    json: true,
    body: this.body
  };

  if (this.accessToken) {
    req.auth = {
      bearer: this.accessToken
    };
  }

  return req;
};

GnEndpoints.prototype.withTokenReqCallback = function (err, httpResponse, httpResponseBody) {
  var self = this;
  var response = self.getResponse(httpResponse, httpResponseBody);

  if (err) {
    self.defer.reject(err);
  } else if (httpResponse.statusCode === 401) {
    self.getAccessToken().then(self.directReq.bind(self));
  } else if (httpResponse.statusCode !== 200) {
    self.defer.reject(response);
  } else {
    self.defer.resolve(response);
  }
};

GnEndpoints.prototype.directReqCallback = function (err, httpResponse, bodyResponse) {
  var response = this.getResponse(httpResponse, bodyResponse);

  if (err) {
    this.defer.reject(err);
  } else if (httpResponse.statusCode !== 200) {
    this.defer.reject(response);
  } else {
    this.defer.resolve(response);
  }
};

module.exports = GnEndpoints;<|endoftext|>