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;