Source: Pool.js

goog.provide('entitas.Pool');
goog.require('entitas.utils.UUID');
goog.require('entitas.utils.Bag');
goog.require('entitas.Group');
goog.require('entitas.Entity');
goog.require('entitas.utils.Signal');
goog.require('entitas.exceptions.EntityIsNotDestroyedException');
goog.require('entitas.exceptions.PoolDoesNotContainEntityException');


/**
 * @constructor
 * @param {Object} components
 * @param {number} totalComponents
 * @param {number} startCreationIndex
 */
entitas.Pool = function(components, totalComponents, startCreationIndex) {
  var _this = this;
  if (startCreationIndex === void 0) { startCreationIndex = 0; }
  /**
   * Subscribe to Entity Created Event
   * @type {entitas.utils.ISignal} */
  this.onEntityCreated = null;
  /**
   * Subscribe to Entity Will Be Destroyed Event
   * @type {entitas.utils.ISignal} */
  this.onEntityWillBeDestroyed = null;
  /**
   * Subscribe to Entity Destroyed Event
   * @type {entitas.utils.ISignal} */
  this.onEntityDestroyed = null;
  /**
   * Subscribe to Group Created Event
   * @type {entitas.utils.ISignal} */
  this.onGroupCreated = null;
  /**
   * Entity name for debugging
   * @type {string} */
  this.name = '';
  this._entities = {};
  this._groups = {};
  this._groupsForIndex = null;
  this._reusableEntities = new entitas.utils.Bag();
  this._retainedEntities = {};
  this._componentsEnum = null;
  this._totalComponents = 0;
  this._creationIndex = 0;
  this._entitiesCache = null;
  /**
   * @param {entitas.Entity} entity
   * @param {number} index
   * @param {entitas.IComponent} component
   */
  this.updateGroupsComponentAddedOrRemoved = function (entity, index, component) {
    var groups = _this._groupsForIndex[index];
    if (groups != null) {
      for (var i = 0, groupsCount = groups.size(); i < groupsCount; i++) {
        groups[i].handleEntity(entity, index, component);
      }
    }
  };
  /**
   * @param {entitas.Entity} entity
   * @param {number} index
   * @param {entitas.IComponent} previousComponent
   * @param {entitas.IComponent} newComponent
   */
  this.updateGroupsComponentReplaced = function (entity, index, previousComponent, newComponent) {
    var groups = _this._groupsForIndex[index];
    if (groups != null) {
      for (var i = 0, groupsCount = groups.size(); i < groupsCount; i++) {
        groups[i].updateEntity(entity, index, previousComponent, newComponent);
      }
    }
  };
  /**
   * @param {entitas.Entity} entity
   */
  this.onEntityReleased = function (entity) {
    if (entity._isEnabled) {
      throw new entitas.exceptions.EntityIsNotDestroyedException("Cannot release entity.");
    }
    entity.onEntityReleased.remove(_this._cachedOnEntityReleased);
    delete _this._retainedEntities[entity.id];
    _this._reusableEntities.add(entity);
  };
  Pool.instance = this;
  this.onGroupCreated = new entitas.utils.Signal(this);
  this.onEntityCreated = new entitas.utils.Signal(this);
  this.onEntityDestroyed = new entitas.utils.Signal(this);
  this.onEntityWillBeDestroyed = new entitas.utils.Signal(this);
  this._componentsEnum = components;
  this._totalComponents = totalComponents;
  this._creationIndex = startCreationIndex;
  this._groupsForIndex = new entitas.utils.Bag();
  this._cachedUpdateGroupsComponentAddedOrRemoved = this.updateGroupsComponentAddedOrRemoved;
  this._cachedUpdateGroupsComponentReplaced = this.updateGroupsComponentReplaced;
  this._cachedOnEntityReleased = this.onEntityReleased;
  Pool.componentsEnum = components;
  Pool.totalComponents = totalComponents;
}
Object.defineProperty(entitas.Pool.prototype, "totalComponents", {
  /**
   * The total number of components in this pool
   * @type {number}
   * @name entitas.Pool#totalComponents */
  get: function () { return this._totalComponents; },
  enumerable: true,
  configurable: true
});
Object.defineProperty(entitas.Pool.prototype, "count", {
  /**
   * Count of active entities
   * @type {number}
   * @name entitas.Pool#count */
  get: function () { return Object.keys(this._entities).length; },
  enumerable: true,
  configurable: true
});
Object.defineProperty(entitas.Pool.prototype, "reusableEntitiesCount", {
  /**
   * Count of entities waiting to be recycled
   * @type {number}
   * @name entitas.Pool#reusableEntitiesCount */
  get: function () { return this._reusableEntities.size(); },
  enumerable: true,
  configurable: true
});
Object.defineProperty(entitas.Pool.prototype, "retainedEntitiesCount", {
  /**
   * Count of entities that sill have references
   * @type {number}
   * @name entitas.Pool#retainedEntitiesCount */
  get: function () { return Object.keys(this._retainedEntities).length; },
  enumerable: true,
  configurable: true
});
/**
 * Set the system pool if supported
 *
 * @static
 * @param {entitas.ISystem} system
 * @param {entitas.Pool} pool
 */
Pool.setPool = function (system, pool) {
  var poolSystem = as(system, 'setPool');
  if (poolSystem != null) {
    poolSystem.setPool(pool);
  }
};
/**
 * Create a new entity
 * @param {string} name
 * @returns {entitas.Entity}
 */
entitas.Pool.prototype.createEntity = function (name) {
  var entity = this._reusableEntities.size() > 0 ? this._reusableEntities.removeLast() : new entitas.Entity(this._componentsEnum, this._totalComponents);
  entity._isEnabled = true;
  entity.name = name;
  entity._creationIndex = this._creationIndex++;
  entity.id = UUID.randomUUID();
  entity.addRef();
  this._entities[entity.id] = entity;
  this._entitiesCache = null;
  entity.onComponentAdded.add(this._cachedUpdateGroupsComponentAddedOrRemoved);
  entity.onComponentRemoved.add(this._cachedUpdateGroupsComponentAddedOrRemoved);
  entity.onComponentReplaced.add(this._cachedUpdateGroupsComponentReplaced);
  entity.onEntityReleased.add(this._cachedOnEntityReleased);
  var onEntityCreated = this.onEntityCreated;
  if (onEntityCreated.active)
    onEntityCreated.dispatch(this, entity);
  return entity;
};
/**
 * Destroy an entity
 * @param {entitas.Entity} entity
 */
entitas.Pool.prototype.destroyEntity = function (entity) {
  if (!(entity.id in this._entities)) {
    throw new entitas.exceptions.PoolDoesNotContainEntityException(entity, "Could not destroy entity!");
  }
  delete this._entities[entity.id];
  this._entitiesCache = null;
  var onEntityWillBeDestroyed = this.onEntityWillBeDestroyed;
  if (onEntityWillBeDestroyed.active)
    onEntityWillBeDestroyed.dispatch(this, entity);
  entity.destroy();
  var onEntityDestroyed = this.onEntityDestroyed;
  if (onEntityDestroyed.active)
    onEntityDestroyed.dispatch(this, entity);
  if (entity._refCount === 1) {
    entity.onEntityReleased.remove(this._cachedOnEntityReleased);
    this._reusableEntities.add(entity);
  }
  else {
    this._retainedEntities[entity.id] = entity;
  }
  entity.release();
};
/**
 * Destroy All Entities
 */
entitas.Pool.prototype.destroyAllEntities = function () {
  var entities = this.getEntities();
  for (var i = 0, entitiesLength = entities.length; i < entitiesLength; i++) {
    this.destroyEntity(entities[i]);
  }
};
/**
 * Check if pool has this entity
 *
 * @param {entitas.Entity} entity
 * @returns {boolean}
 */
entitas.Pool.prototype.hasEntity = function (entity) {
  return entity.id in this._entities;
};
/**
 * Gets all of the entities
 *
 * @returns {Array<entitas.Entity>}
 */
entitas.Pool.prototype.getEntities = function (matcher) {
  if (matcher) {
    /** PoolExtension::getEntities */
    return this.getGroup(matcher).getEntities();
  }
  else {
    if (this._entitiesCache == null) {
      var entities = this._entities;
      var keys = Object.keys(entities);
      var length = keys.length;
      var entitiesCache = this._entitiesCache = new Array(length);
      for (var i = 0; i < length; i++) {
        entitiesCache[i] = entities[keys[i]];
      }
    }
    return this._entitiesCache;
  }
  // if (this._entitiesCache == null) {
  //  var entities = this._entities;
  //  var keys = Object.keys(entities);
  //  var length = keys.length;
  //  var entitiesCache = this._entitiesCache = new Array(length);
  //  for (var i = 0; i < length; i++) {
  //   var k = keys[i];
  //   entitiesCache[i] = entities[k];
  //  }
  // }
  // return entitiesCache;
};
/**
 * Create System
 * @param {entitas.ISystem|Function}
 * @returns {entitas.ISystem}
 */
entitas.Pool.prototype.createSystem = function (system) {
  if ('function' === typeof system) {
    var Klass = system;
    system = new Klass();
  }
  Pool.setPool(system, this);
  var reactiveSystem = as(system, 'trigger');
  if (reactiveSystem != null) {
    return new entitas.ReactiveSystem(this, reactiveSystem);
  }
  var multiReactiveSystem = as(system, 'triggers');
  if (multiReactiveSystem != null) {
    return new entitas.ReactiveSystem(this, multiReactiveSystem);
  }
  return system;
};
/**
 * Gets all of the entities that match
 *
 * @param {entias.IMatcher} matcher
 * @returns {entitas.Group}
 */
entitas.Pool.prototype.getGroup = function (matcher) {
  var group;
  if (matcher.id in this._groups) {
    group = this._groups[matcher.id];
  }
  else {
    group = new entitas.Group(matcher);
    var entities = this.getEntities();
    for (var i = 0, entitiesLength = entities.length; i < entitiesLength; i++) {
      group.handleEntitySilently(entities[i]);
    }
    this._groups[matcher.id] = group;
    for (var i = 0, indicesLength = matcher.indices.length; i < indicesLength; i++) {
      var index = matcher.indices[i];
      if (this._groupsForIndex[index] == null) {
        this._groupsForIndex[index] = new entitas.utils.Bag();
      }
      this._groupsForIndex[index].add(group);
    }
    var onGroupCreated = this.onGroupCreated;
    if (onGroupCreated.active)
      onGroupCreated.dispatch(this, group);
  }
  return group;
};
/**
 * An enum of valid component types
 * @type {Object<string,number>} */
Pool.componentsEnum = null;
/**
 * Count of components
 * @type {number} */
Pool.totalComponents = 0;
/**
 * Global reference to pool instance
 * @type {entitas.Pool} */
Pool.instance = null;