import Entity from './Entity';
import Manager from './Manager';
import System from './System';
import EntitySystem from './System/EntitySystem';
import VoidSystem from './System/VoidSystem';
/**
* The world contains all entities, systems and managers
*/
class World {
constructor() {
/** @var {Entity[]}*/
this.entities = [];
/** @var {VoidSystem[]} */
this.voidSystems = [];
/** @var {EntitySystem[]} */
this.entitySystems = [];
/** @var {Manager[]} */
this.managers = [];
/** @var { boolean} */
this.initialized = false;
/** @var {number} */
this.ticks = 0;
}
/**
* Creates and adds a new entity
* @param {Component[]} components
* @return {Entity}
*/
createEntity(components) {
let slot = this._getFreeEntitySlot();
const entity = new Entity();
if (slot != null) {
this.entities[slot] = entity;
} else {
slot = this.entities.length;
this.entities.push(entity);
}
components.forEach(c => entity.addComponent(c));
entity.setIndex(slot);
this._entityAdded(entity);
return entity;
}
/**
* Add entity to the world
* @param { Entity} entity
* @returns { World }
*/
addEntity(entity) {
let slot = this._getFreeEntitySlot();
if (slot != null) {
this.entities[slot] = entity;
} else {
slot = this.entities.length;
this.entities.push(entity);
}
entity.setIndex(slot);
this._entityAdded(entity);
return this;
}
/**
* remove entity from the world
* @param {Entity} entity
* @returns {World}
* @private
* @throws Error
*/
_removeEntity(entity) {
const index = entity.getIndex();
if (index === null) {
throw new Error('Entity has no index');
}
this._entityDeleted(entity);
return this;
}
/**
* Get entity from world by its id
* @param {number} index
* @returns {Entity}
* @throws Error
*/
getEntity(index) {
if (this.entities[index] == undefined) {
throw new Error('No entity found');
}
return this.entities[index];
}
/**
* Get all entities from wold
* @returns {Entity[]}
*/
getEntities() {
return this.entities;
}
/**
* Add new void system to the world
* @param {VoidSystem} system
*/
addVoidSystem(system) {
system.setWorld(this);
let slot = this._getFreeEntitySystemSlot();
if (slot != null) {
this.voidSystems[slot] = system;
} else {
this.voidSystems.push(system);
}
return this;
}
/**
* Add new entity system to the world
* @param {EntitySystem} system
*/
addEntitySystem(system) {
system.setWorld(this);
let slot = this._getFreeEntitySystemSlot();
if (slot != null) {
this.entitySystems[slot] = system;
} else {
this.entitySystems.push(system);
}
return this;
}
/**
* After all systems has been added, this should be called to initiate them
* @return {World}
*/
init() {
for (let i = 0, len = this.entitySystems.length; i < len; i++) {
let system = this.entitySystems[i];
system.addedToWorld();
}
for (let i = 0, len = this.voidSystems.length; i < len; i++) {
let system = this.voidSystems[i];
system.addedToWorld();
}
this.initialized = true;
return this;
}
/**
* get a System
* @param {Function} systemClass Class constructor
* @return {System}
* @throws Error
*/
getSystem(systemClass) {
for (let i = 0, len = this.entitySystems.length; i < len; i++) {
let s = this.entitySystems[i];
if (s instanceof systemClass) {
return s;
}
}
for (let i = 0, len = this.voidSystems.length; i < len; i++) {
let s = this.voidSystems[i];
if (s instanceof systemClass) {
return s;
}
}
throw 'System ' + systemClass + ' not found';
}
/**
* Remove System
* @param {Function} systemClass Class constructor
* @returns {World}
*/
removeSystem(systemClass) {
const entitySystems = this.entitySystems;
const eLen = entitySystems.length;
const newESystems = [];
for (let i = 0; i < eLen; i++) {
const system = entitySystems[i];
if (!system instanceof systemClass) {
newESystems.push(system);
}
}
this.entitySystems = newESystems;
const voidSystems = this.voidSystems;
const vLen = voidSystems.length;
const newVSystems = [];
for (let i = 0; i < vLen; i++) {
const system = voidSystems[i];
if (!system instanceof systemClass) {
newVSystems.push(system);
}
}
this.voidSystems = newVSystems;
return this;
}
/**
* Add Manager
* @param {Manager} manager Manager instance
* @returns {World}
*/
addManager(manager) {
manager.setWorld(this);
this.managers.push(manager);
return this;
}
/**
* Get a manager
* @param { Function} managerClass Manager class constructor
* @returns {Manager}
* @throws Error
*/
getManager(managerClass) {
for (let i = 0, len = this.managers.length; i < len; i++) {
let manager = this.managers[i];
if (manager instanceof managerClass) {
return this.managers[i];
}
}
throw 'Manager ' + name + ' not found';
}
/**
* Update the world
* @throws Error
*/
update() {
if (this.initialized === false) {
throw new Error('Not initialized');
}
this._updateEntities()
._updateVoidSystem()
._updateEntitySystem();
this.ticks++;
}
/**
* Update void systems
* @private
* @returns {World}
*/
_updateVoidSystem() {
for (let s = 0, sLen = this.voidSystems.length; s < sLen; s++) {
let system = this.voidSystems[s];
system.update();
}
return this;
}
/**
* Update entity systems
* @private
* @returns {World}
*/
_updateEntitySystem() {
for (let s = 0, sLen = this.entitySystems.length; s < sLen; s++) {
const system = this.entitySystems[s];
const entities = this.getEntitiesWithComponents(system.components);
system.processEntities(entities);
}
}
/**
*
* @param {Entity} entity
* @param components
* @private
*/
_checkIfEntityHasComponents(entity, components) {
for (let i = 0; i < components.length; i++) {
if (!entity.hasComponent(components[i])) {
return false;
}
}
return true;
}
/**
* Get a list of entities that match a component
* @param {Function} component
* @returns {Entity[]}
*/
getEntitiesWithComponent(component) {
const entities = this.entities;
const entitiesLength = entities.length;
const filteredComponents = [];
for (let i = 0; i < entitiesLength; i++) {
const entity = entities[i];
if (entity.hasComponent(component)) {
filteredComponents.push(entity);
}
}
return filteredComponents;
}
/**
* Get a list of entities that match a component
* @param {Function[]} components
* @returns {Entity[]}
*/
getEntitiesWithComponents(components) {
const matchingEntities = [];
for (let i = 0; i < this.entities.length; i++) {
const entity = this.entities[i];
if (this._checkIfEntityHasComponents(entity, components)) {
matchingEntities.push(entity);
}
}
return matchingEntities;
}
/**
* Update all entities state
* @private
* @returns {World}
*/
_updateEntities() {
this.entities = this.entities.filter((entity) => {
if (!entity.isAlive() && entity.isRemove()) {
this._removeEntity(entity);
return false;
}
return true;
});
return this;
}
/**
* recycle entityslots
* @private
* @returns {number | null}
*/
_getFreeEntitySlot() {
for (let e = 0, len = this.entities.length; e < len; e++) {
let entity = this.entities[e];
if (entity === null || entity === undefined) {
return e;
}
}
return null;
}
/**
* recycle systemslot
* @private
* @return {number | null}
*/
_getFreeEntitySystemSlot() {
for (let s = 0, len = this.entitySystems.length; s < len; s++) {
let system = this.entitySystems[s];
if (system === undefined || system === null) {
return s;
}
}
return null;
}
/**
* notify systems for new entity
* @param {Entity} entity
* @private
* @returns {World}
*/
_entityAdded(entity) {
for (let s = 0, len = this.voidSystems.length; s < len; s++) {
let system = this.voidSystems[s];
system.added(entity);
}
for (let s = 0, len = this.entitySystems.length; s < len; s++) {
let system = this.entitySystems[s];
system.added(entity);
}
return this;
}
/**
* notify systems for deleted entity
* @param {Entity} entity
* @private
* @returns {World}
*/
_entityDeleted(entity) {
for (let s = 0, len = this.voidSystems.length; s < len; s++) {
let system = this.voidSystems[s];
system.removed(entity);
}
for (let s = 0, len = this.entitySystems.length; s < len; s++) {
let system = this.entitySystems[s];
system.removed(entity);
}
}
/**
* @return {EntitySystem[]}
*/
getEntitySystems() {
return this.entitySystems;
}
/**
* @returns {VoidSystem[]}
*/
getVoidSystems() {
return this.voidSystems;
}
/**
* @return {Manager[]}
*/
getManagers() {
return this.managers;
}
}
export default World;