/**
* Entity Component System module
*
* @module ecs
*/
import Entity from './entity';
import System from './system';
import performance from './performance';
import uid from './uid';
/**
* @class ECS
*/
class ECS {
/**
* @constructor
* @class ECS
*/
constructor() {
/**
* Store all entities of the ECS.
*
* @property entities
* @type {Array}
*/
this.entities = [];
/**
* Store entities which need to be tested at beginning of next tick.
*
* @property entitiesSystemsDirty
* @type {Array}
*/
this.entitiesSystemsDirty = [];
/**
* Store all systems of the ECS.
*
* @property systems
* @type {Array}
*/
this.systems = [];
/**
* Count how many updates have been done.
*
* @property updateCounter
* @type {Number}
*/
this.updateCounter = 0;
this.lastUpdate = performance.now();
}
/**
* Retrieve an entity by id
* @param {Number} id id of the entity to retrieve
* @return {Entity} The entity if found null otherwise
*/
getEntityById(id) {
for (let i = 0, entity; entity = this.entities[i]; i += 1) {
if (entity.id === id) {
return entity;
}
}
return null;
}
/**
* Add an entity to the ecs.
*
* @method addEntity
* @param {Entity} entity The entity to add.
*/
addEntity(entity) {
this.entities.push(entity);
entity.addToECS(this);
}
/**
* Remove an entity from the ecs by reference.
*
* @method removeEntity
* @param {Entity} entity reference of the entity to remove
* @return {Entity} the remove entity if any
*/
removeEntity(entity) {
let index = this.entities.indexOf(entity);
let entityRemoved = null;
// if the entity is not found do nothing
if (index !== -1) {
entityRemoved = this.entities[index];
entity.dispose();
this.removeEntityIfDirty(entityRemoved);
this.entities.splice(index, 1);
}
return entityRemoved;
}
/**
* Remove an entity from the ecs by entity id.
*
* @method removeEntityById
* @param {Entity} entityId id of the entity to remove
* @return {Entity} removed entity if any
*/
removeEntityById(entityId) {
for (let i = 0, entity; entity = this.entities[i]; i += 1) {
if (entity.id === entityId) {
entity.dispose();
this.removeEntityIfDirty(entity);
this.entities.splice(i, 1);
return entity;
}
}
}
/**
* Remove an entity from dirty entities by reference.
*
* @private
* @method removeEntityIfDirty
* @param {[type]} entity entity to remove
*/
removeEntityIfDirty(entity) {
let index = this.entitiesSystemsDirty.indexOf(entity);
if (index !== -1) {
this.entitiesSystemsDirty.splice(index, 1);
}
}
/**
* Add a system to the ecs.
*
* @method addSystem
* @param {System} system system to add
*/
addSystem(system) {
this.systems.push(system);
// iterate over all entities to eventually add system
for (let i = 0, entity; entity = this.entities[i]; i += 1) {
if (system.test(entity)) {
system.addEntity(entity);
}
}
}
/**
* Remove a system from the ecs.
*
* @method removeSystem
* @param {System} system system reference
*/
removeSystem(system) {
let index = this.systems.indexOf(system);
if (index !== -1) {
this.systems.splice(index, 1);
system.dispose();
}
}
/**
* "Clean" entities flagged as dirty by removing unecessary systems and
* adding missing systems.
*
* @private
* @method cleanDirtyEntities
*/
cleanDirtyEntities() {
// jshint maxdepth: 4
for (let i = 0, entity; entity = this.entitiesSystemsDirty[i]; i += 1) {
for (let s = 0, system; system = this.systems[s]; s += 1) {
// for each dirty entity for each system
let index = entity.systems.indexOf(system);
let entityTest = system.test(entity);
if (index === -1 && entityTest) {
// if the entity is not added to the system yet and should be, add it
system.addEntity(entity);
} else if (index !== -1 && !entityTest) {
// if the entity is added to the system but should not be, remove it
system.removeEntity(entity);
}
// else we do nothing the current state is OK
}
entity.systemsDirty = false;
}
// jshint maxdepth: 3
this.entitiesSystemsDirty = [];
}
/**
* Update the ecs.
*
* @method update
*/
update() {
let now = performance.now();
let elapsed = now - this.lastUpdate;
for (let i = 0, system; system = this.systems[i]; i += 1) {
if (this.updateCounter % system.frequency > 0) {
break;
}
if (this.entitiesSystemsDirty.length) {
// if the last system flagged some entities as dirty check that case
this.cleanDirtyEntities();
}
system.updateAll(elapsed);
}
this.updateCounter += 1;
this.lastUpdate = now;
}
}
// expose user stuff
ECS.Entity = Entity;
ECS.System = System;
ECS.uid = uid;
export default ECS;