Source: Entity.js

goog.provide('entitas.Entity');
goog.require('entitas.utils.Signal');
goog.require('entitas.exceptions.EntityIsNotEnabledException');
goog.require('entitas.exceptions.EntityIsAlreadyReleasedException');
goog.require('entitas.exceptions.EntityAlreadyHasComponentException');
goog.require('entitas.exceptions.EntityDoesNotHaveComponentException');


/**
 * The basic game object. Everything is an entity with components that
 * are added / removed as needed.
 *
 * @param {Object} componentsEnum
 * @param {number} totalComponents
 * @constructor
 */
entitas.Entity = function(componentsEnum, totalComponents) {
  if (totalComponents === void 0) { totalComponents = 16; }
  /**
   * Subscribe to Entity Released Event
   * @type {entitas.ISignal} */
  this.onEntityReleased = null;
  /**
   * Subscribe to Component Added Event
   * @type {entitas.ISignal} */
  this.onComponentAdded = null;
  /**
   * Subscribe to Component Removed Event
   * @type {entitas.ISignal} */
  this.onComponentRemoved = null;
  /**
   * Subscribe to Component Replaced Event
   * @type {entitas.ISignal} */
  this.onComponentReplaced = null;
  /**
   * Entity name
   * @type {string} */
  this.name = '';
  /**
   * Entity Id
   * @type {string} */
  this.id = '';
  /**
   * Instance index
   * @type {number} */
  this.instanceIndex = 0;
  this._creationIndex = 0;
  this._isEnabled = true;
  this._components = null;
  this._componentsCache = null;
  this._componentIndicesCache = null;
  this._toStringCache = '';
  this._refCount = 0;
  this._pool = null;
  this._componentsEnum = null;
  this.onEntityReleased = new entitas.utils.Signal(this);
  this.onComponentAdded = new entitas.utils.Signal(this);
  this.onComponentRemoved = new entitas.utils.Signal(this);
  this.onComponentReplaced = new entitas.utils.Signal(this);
  this._componentsEnum = componentsEnum;
  this._pool = entitas.Pool.instance;
  this._components = this.initialize(totalComponents);
}
Object.defineProperty(entitas.Entity.prototype, "creationIndex", {
  /**
   * A unique sequential index number assigned to each entity at creation
   * @type {number}
   * @name entitas.Entity#creationIndex */
  get: function () { return this._creationIndex; },
  enumerable: true,
  configurable: true
});
Entity.initialize = function (totalComponents, options) {
  Entity.size = options.entities || 100;
};
/**
 * allocate entity pool
 *
 * @param count number of components
 * @param size max number of entities
 */
Entity.dim = function (count, size) {
  Entity.alloc = new Array(size);
  for (var e = 0; e < size; e++) {
    Entity.alloc[e] = new Array(count);
    for (var k = 0; k < count; k++) {
      Entity.alloc[e][k] = null;
    }
  }
};
/**
 * Initialize
 * allocate the entity pool.
 *
 * @param {number} totalComponents
 * @returns {Array<entitas.IComponent>}
 */
entitas.Entity.prototype.initialize = function (totalComponents) {
  var mem;
  var size = Entity.size;
  if (Entity.alloc == null)
    Entity.dim(totalComponents, size);
  var alloc = Entity.alloc;
  this.instanceIndex = Entity.instanceIndex++;
  if (mem = alloc[this.instanceIndex])
    return mem;
  console.log('Insufficient memory allocation at ', this.instanceIndex, '. Allocating ', size, ' entities.');
  for (var i = this.instanceIndex, l = i + size; i < l; i++) {
    alloc[i] = new Array(totalComponents);
    for (var k = 0; k < totalComponents; k++) {
      alloc[i][k] = null;
    }
  }
  mem = alloc[this.instanceIndex];
  return mem;
};
;
/**
 * AddComponent
 *
 * @param {number} index
 * @param {entitas.IComponent} component
 * @returns {entitas.Entity}
 */
entitas.Entity.prototype.addComponent = function (index, component) {
  if (!this._isEnabled) {
    throw new entitas.exceptions.EntityIsNotEnabledException("Cannot add component!");
  }
  if (this.hasComponent(index)) {
    var errorMsg = "Cannot add component at index " + index + " to " + this;
    throw new entitas.exceptions.EntityAlreadyHasComponentException(errorMsg, index);
  }
  this._components[index] = component;
  this._componentsCache = null;
  this._componentIndicesCache = null;
  this._toStringCache = null;
  var onComponentAdded = this.onComponentAdded;
  if (onComponentAdded.active)
    onComponentAdded.dispatch(this, index, component);
  return this;
};
/**
 * RemoveComponent
 *
 * @param {number} index
 * @returns {entitas.Entity}
 */
entitas.Entity.prototype.removeComponent = function (index) {
  if (!this._isEnabled) {
    throw new entitas.exceptions.EntityIsNotEnabledException("Cannot remove component!");
  }
  if (!this.hasComponent(index)) {
    var errorMsg = "Cannot remove component at index " + index + " from " + this;
    throw new entitas.exceptions.EntityDoesNotHaveComponentException(errorMsg, index);
  }
  this._replaceComponent(index, null);
  return this;
};
/**
 * ReplaceComponent
 *
 * @param {number} index
 * @param {entitas.IComponent} component
 * @returns {entitas.Entity}
 */
entitas.Entity.prototype.replaceComponent = function (index, component) {
  if (!this._isEnabled) {
    throw new entitas.exceptions.EntityIsNotEnabledException("Cannot replace component!");
  }
  if (this.hasComponent(index)) {
    this._replaceComponent(index, component);
  }
  else if (component != null) {
    this.addComponent(index, component);
  }
  return this;
};
entitas.Entity.prototype._replaceComponent = function (index, replacement) {
  var components = this._components;
  var previousComponent = components[index];
  if (previousComponent === replacement) {
    var onComponentReplaced = this.onComponentReplaced;
    if (onComponentReplaced.active)
      onComponentReplaced.dispatch(this, index, previousComponent, replacement);
  }
  else {
    components[index] = replacement;
    this._componentsCache = null;
    if (replacement == null) {
      //delete components[index];
      components[index] = null;
      this._componentIndicesCache = null;
      this._toStringCache = null;
      var onComponentRemoved = this.onComponentRemoved;
      if (onComponentRemoved.active)
        onComponentRemoved.dispatch(this, index, previousComponent);
    }
    else {
      var onComponentReplaced = this.onComponentReplaced;
      if (onComponentReplaced.active)
        onComponentReplaced.dispatch(this, index, previousComponent, replacement);
    }
  }
};
/**
 * GetComponent
 *
 * @param {number} index
 * @param {entitas.IComponent} component
 */
entitas.Entity.prototype.getComponent = function (index) {
  if (!this.hasComponent(index)) {
    var errorMsg = "Cannot get component at index " + index + " from " + this;
    throw new entitas.exceptions.EntityDoesNotHaveComponentException(errorMsg, index);
  }
  return this._components[index];
};
/**
 * GetComponents
 *
 * @returns {Array<entitas.IComponent>}
 */
entitas.Entity.prototype.getComponents = function () {
  if (this._componentsCache == null) {
    var components = [];
    var _components = this._components;
    for (var i = 0, j = 0, componentsLength = _components.length; i < componentsLength; i++) {
      var component = _components[i];
      if (component != null) {
        components[j++] = component;
      }
    }
    this._componentsCache = components;
  }
  return this._componentsCache;
};
/**
 * GetComponentIndices
 *
 * @returns {Array<number>}
 */
entitas.Entity.prototype.getComponentIndices = function () {
  if (this._componentIndicesCache == null) {
    var indices = [];
    var _components = this._components;
    for (var i = 0, j = 0, componentsLength = _components.length; i < componentsLength; i++) {
      if (_components[i] != null) {
        indices[j++] = i;
      }
    }
    this._componentIndicesCache = indices;
  }
  return this._componentIndicesCache;
};
/**
 * HasComponent
 *
 * @param {number} index
 * @returns {boolean}
 */
entitas.Entity.prototype.hasComponent = function (index) {
  return this._components[index] != null;
};
/**
 * HasComponents
 *
 * @param {Array<number>} indices
 * @returns {boolean}
 */
entitas.Entity.prototype.hasComponents = function (indices) {
  var _components = this._components;
  for (var i = 0, indicesLength = indices.length; i < indicesLength; i++) {
    if (_components[indices[i]] == null) {
      return false;
    }
  }
  return true;
};
/**
 * HasAnyComponent
 *
 * @param {Array<number>} indices
 * @returns {boolean}
 */
entitas.Entity.prototype.hasAnyComponent = function (indices) {
  var _components = this._components;
  for (var i = 0, indicesLength = indices.length; i < indicesLength; i++) {
    if (_components[indices[i]] != null) {
      return true;
    }
  }
  return false;
};
/**
 * RemoveAllComponents
 *
 */
entitas.Entity.prototype.removeAllComponents = function () {
  this._toStringCache = null;
  var _components = this._components;
  for (var i = 0, componentsLength = _components.length; i < componentsLength; i++) {
    if (_components[i] != null) {
      this._replaceComponent(i, null);
    }
  }
};
/**
 * Destroy
 *
 */
entitas.Entity.prototype.destroy = function () {
  this.removeAllComponents();
  this.onComponentAdded.clear();
  this.onComponentReplaced.clear();
  this.onComponentRemoved.clear();
  this._isEnabled = false;
};
/**
 * ToString
 *
 * @returns {string}
 */
entitas.Entity.prototype.toString = function () {
  if (this._toStringCache == null) {
    var sb = [];
    var seperator = ", ";
    var components = this.getComponents();
    var lastSeperator = components.length - 1;
    for (var i = 0, j = 0, componentsLength = components.length; i < componentsLength; i++) {
      sb[j++] = components[i].constructor['name'].replace('Component', '') || i + '';
      if (i < lastSeperator) {
        sb[j++] = seperator;
      }
    }
    this._toStringCache = sb.join('');
  }
  return this._toStringCache;
};
/**
 * AddRef
 *
 * @returns {entitas.Entity}
 */
entitas.Entity.prototype.addRef = function () {
  this._refCount += 1;
  return this;
};
/**
 * Release
 *
 */
entitas.Entity.prototype.release = function () {
  this._refCount -= 1;
  if (this._refCount === 0) {
    var onEntityReleased = this.onEntityReleased;
    if (onEntityReleased.active)
      onEntityReleased.dispatch(this);
  }
  else if (this._refCount < 0) {
    throw new entitas.exceptions.EntityIsAlreadyReleasedException();
  }
};
/**
 * @static
 * @type {number} */
Entity.instanceIndex = 0;
/**
 * @static
 * @type {Array<Array<IComponent>>} */
Entity.alloc = null;
/**
 * @static
 * @type {number} */
Entity.size = 0;