EntityStore.js

import { compose } from 'stampit';
import pickBy from 'lodash/pickBy';
import { isFunction } from './helpers';

import Entity from './Entity';
import Pool from './Pool';

/**
 * Centralized place for registering, creating and reusing entities.
 *
 * __For internal use only!__
 *
 * @class EntityStore
 * @param {Object}          opts
 * @param {Entropy}         opts.game
 * @param {ComponentStore}  opts.componentStore
 */
const EntityStore = compose({
  init({ game }) {
    /**
     * Object with different pool for each entity type.
     * Entity type is a key and {@link Pool} instance is a value.
     *
     * @private
     * @memberOf EntityStore#
     * @name _pools
     * @type Object
     */
    this._pools = {};

    /**
     * Object with factories for entity types.
     *
     * @private
     * @memberOf EntityStore#
     * @name _factories
     * @type Object
     */
    this._factories = {};

    /**
     * Game instance injected to constructor.
     *
     * @public
     * @memberOf EntityStore#
     * @name game
     * @type Entropy
     */
    this.game = game;
  },
  methods: {
    /**
     * Registers entity. Entity has to be registered before it can be created.
     *
     * @example
     * game.entity.register({
     *   type: 'Ball',
     *   onCreate(x, y, texture) {
     *     const sprite = Sprite(texture);
     *
     *     this.addComponent('Position', x, y);
     *     this.addComponent('Sprite', sprite);
     *
     *     // add sprite to stage for renderer (when using PIXI this has to be done)
     *     this.game.stage.addChild(sprite);
     *   },
     *   onRemove() {
     *     this.game.stage.removeChild(this.components.sprite.sprite);
     *   },
     * });
     *
     * const c = game.entity.create('Ball', 1, 2, 'ball.png');
     *
     * @public
     * @memberof EntityStore#
     * @method register
     * @param {Object}    descriptor            object describing entity
     * @param {String}    descriptor.type       type of entity
     * @param {Function}  descriptor.onCreate   method called when entity is created (may be recycled from pool)
     * @param {Function}  descriptor.onReuse    method called when entity is reused from pool (called before `onCreate`)
     * @param {Function}  descriptor.onRemove   method called when entity is removed (usually it returns to a pool)
     */
    register(descriptor) {
      this._factories[descriptor.type] = compose(Entity, {
        methods: pickBy(descriptor, value => isFunction(value)),
      });

      this._pools[descriptor.type] = Pool({
        _new: (...args) => {
          const entity = this._factories[descriptor.type]({
            game: this.game,
            type: descriptor.type,
          });

          entity.onCreate(...args);

          return entity;
        },
        _reuse: (entity, ...args) => {
          entity.onReuse();
          entity.onCreate(...args);

          return entity;
        },
      });
    },
    /**
     * Registeres many entities.
     *
     * @public
     * @memberof EntityStore#
     * @method registerMany
     * @param  {Array} descriptiors array of entity's descriptors (see {@link EntityStore#register})
     */
    registerMany(descriptors) {
      descriptors.forEach(descriptor => this.register(descriptor));
    },
    /**
     * Creates new entity instance or acquires one from pool.
     *
     * @public
     * @memberof EntityStore#
     * @method create
     * @param  {String} type    type of entity to create
     * @param  {...Any} ...args arguments passed to `onCreate` method
     * @return {Entity}         new (or reused) entity ready to be added to engine
     */
    create(type, ...args) {
      return this._pools[type].allocate(...args);
    },
    /**
     * Frees entity. Entity is added to the pool. Marks entity as recyceled.
     *
     * _This method is called internally by the engine, user should not call it._
     *
     * @public
     * @memberof EntityStore#
     * @method free
     * @param  {Entity} entity entity
     */
    free(entity) {
      entity.markAsRecycled();

      this._pools[entity.type].free(entity);
    },
  },
});

export default EntityStore;