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;