import stampit from 'stampit';
import { isObject, toLowerFirstCase } from './helpers';
import FastBitset from 'fastbitset';
import FastArray from 'fast-array';
import EventEmitter from './EventEmitter';
/**
* Entity factory.
*
* @class Entity
*/
const Entity = stampit({
init(opts) {
this._modifications = FastArray();
this._used = false;
this.game = opts.game;
this.type = opts.type;
this.id = 0;
this.bitset = new FastBitset();
this.components = this.cs = {};
},
methods: {
/**
* Called when entity is created. Could be overriden (see {@link EntityStore#register}).
*
* @memberof Entity#
*/
onCreate() {},
/**
* Called when entity is removed. Could be overriden (see {@link EntityStore#register}).
*
* @memberof Entity#
*/
onRemove() {},
/**
* Called when entity is reused from pool. Could be overriden (see {@link EntityStore#register}).
*
* @memberof Entity#
*/
onReuse() {},
onComponentRemove() {},
/**
* Adds new component to entity.
*
* If first argument is component type, component is either created from scratch or reused from pool. In latter case, component's pattern `onReuse` method is called (if present).
* Component patterns `onCreate` method is called with additional arguments passed to `add` method.
* If entity is already added to the system (has id greater than 0) addition doesn't happen immediately, but is postponed to nearest update cycle.
*
* @example
* // `this` is a reference to Entity instance
* // code like this can be seen in entity's `onCreate` method
* this.add('Position', 1, 1);
*
* // or
*
* this.add(game.createComponent('Position', 1, 1));
*
* @memberof Entity#
* @param {String|Component} componentTypeOrComponent component instance or component type
* @param {...Any} args arguments passed to `onCreate` method of component
* @return {Entity} Entity instance
*/
addComponent(componentTypeOrComponent, ...args) {
const componentToAdd = isObject(componentTypeOrComponent) ?
componentTypeOrComponent : this.game.component.create(componentTypeOrComponent, ...args);
// if entity id equals 0, it has not yet been added to the system, so we can safely modify it
if (this.id === 0) {
this._addComponent(componentToAdd);
} else {
this._modifications.push({
fn: () => {
this._addComponent(componentToAdd);
},
});
this.emit('queuedModification', this);
}
// return `this` for easy chaining of `add` calls
return this;
},
_addComponent(componentToAdd) {
this.components[componentToAdd._propName] = componentToAdd;
this.bitset.add(componentToAdd._id);
this.emit('componentAdd', this, componentToAdd);
},
/**
* Removes component.
*
* @memberof Entity#
*/
removeComponent(componentType) {
const componentPropName = toLowerFirstCase(componentType);
const componentToRemove = this.components[componentPropName];
if (this.id === 0) {
this._removeComponent(componentToRemove);
} else {
this._modifications.push({
fn: () => {
this._removeComponent(componentToRemove);
},
});
this.emit('queueModification', this);
}
return this;
},
removeAllComponents() {
Object.keys(this.components).forEach(propName =>
this.removeComponent(propName)
);
},
_removeComponent(componentToRemove) {
this.game.component.free(componentToRemove);
this.components[componentToRemove._propName] = null;
this.bitset.remove(componentToRemove._id);
this.onComponentRemove(componentToRemove);
},
applyModifications() {
while (this._modifications.length) {
const modification = this._modifications.shift();
modification.fn();
}
},
clearModifications() {
this._modifications.clear();
},
is(type) {
return this.type === type;
},
markAsRecycled() {
this._used = true;
},
isRecycled() {
return this._used;
},
},
})
.compose(EventEmitter);
export default Entity;