core/stage.js

// 'ESLint' configuration
/* global TypeGenericAction */
/* global TypeGenericState */

import {Actor, Engine, Preloadable, UTILS} from '../index.js';

/**
 * The identifier of the 'origin' actor.
 * @type {string}
 * @constant
 * @private
 */
const $IDENTIFIER_ACTOR_ORIGIN = '$origin';

/**
 * Abstract stages.
 *
 * @example
 *
 * class StageExample extends Stage {}
 */
class Stage extends Preloadable {

    /**
     * Stores the current actors.
     * @type {Array<Actor>}
     * @private
     */
    $actors;

    /**
     * Stores the current engine.
     * @type {Engine}
     * @private
     */
    $engine;

    /**
     * Stores the 'origin' actor (not attached).
     * @type {Actor}
     * @private
     */
    $origin;

    /**
     * Stores the point of view.
     * @type {Actor}
     * @private
     */
    $pointOfView;

    /**
     * Stores the uuid.
     * @type {string}
     * @private
     */
    $uuid;

    /**
     * Gets the current actors.
     * @type {Array<Actor>}
     * @public
     */
    get actors() {

        return this.$actors;
    }

    /**
     * Gets the current engine.
     * @type {Engine}
     * @public
     */
    get engine() {

        return this.$engine;
    }

    /**
     * Gets the 'origin' actor (not attached).
     * @type {Actor}
     * @public
     */
    get origin() {

        return this.$origin;
    }

    /**
     * Gets the point of view.
     * @type {Actor}
     * @public
     */
    get pointOfView() {

        return this.$pointOfView;
    }

    /**
     * Gets the uuid.
     * @type {string}
     * @public
     */
    get uuid() {

        return this.$uuid;
    }

    /**
     * Creates a new stage.
     * @param {Engine} $engine The engine on which to create the stage.
     */
    constructor($engine) {

        super();

        this.$engine = $engine;

        this.$actors = [];
        this.$origin = this.$createActorOrigin();
        this.$pointOfView = this.$origin;
        this.$uuid = UTILS.uuid();
    }

    /**
     * Creates the 'origin' actor.
     * @returns {Actor}
     * @private
     */
    $createActorOrigin() {

        const actor = new Actor(this)
        .setIdentifier($IDENTIFIER_ACTOR_ORIGIN);

        actor.onCreate();

        return actor;
    }

    /**
     * Removes the given actor.
     * @param {Actor} $actor The actor to remove.
     * @private
     */
    $removeActor($actor) {

        $actor.onBeforeRemove();

        if (this.$pointOfView === $actor) {

            this.$pointOfView = this.$origin;
        }

        const index = this.$actors.indexOf($actor);
        this.$actors.splice(index, 1);

        $actor.onAfterRemove();
    }

    /**
     * Creates the given actor.
     * @template {string} [TypeGenericAction=string] The generic type of the actions.
     * @template {string} [TypeGenericState=string] The generic type of the states.
     * @param {typeof Actor<TypeGenericAction, TypeGenericState>} [$actor] The actor to create.
     * @returns {Actor<TypeGenericAction, TypeGenericState>}
     * @public
     */
    createActor($actor = Actor) {

        const actor = new $actor(this);

        this.$actors.push(actor);

        actor.onCreate();

        return actor;
    }

    /**
     * Gets the first actor with the given identifier.
     * @param {string} $identifier The identifier of the actor to get.
     * @returns {Actor}
     * @public
     */
    getActorWithIdentifier($identifier) {

        return this.$actors.find(($actor) => ($actor.identifier === $identifier));
    }

    /**
     * Gets the actors with the given identifier.
     * @param {string} $identifier The identifier of the actors to get.
     * @returns {Array<Actor>}
     * @public
     */
    getActorsWithIdentifier($identifier) {

        return this.$actors.filter(($actor) => ($actor.identifier === $identifier));
    }

    /**
     * Checks if the stage has the given actor.
     * @param {Actor} $actor The actor to check.
     * @returns {boolean}
     * @public
     */
    hasActor($actor) {

        return this.$actors.indexOf($actor) !== -1;
    }

    /**
     * Checks if the stage has an actor with the given identifier.
     * @param {string} $identifier The identifier of the actor to check.
     * @returns {boolean}
     * @public
     */
    hasActorWithIdentifier($identifier) {

        return this.$actors.some(($actor) => ($actor.identifier === $identifier)) === true;
    }

    /**
     * Called when the stage is being created.
     * @public
     */
    onCreate() {}

    /**
     * Removes the given actor.
     * @param {Actor} $actor The actor to remove.
     * @public
     */
    removeActor($actor) {

        if (this.$actors.indexOf($actor) === -1) {

            return;
        }

        this.$removeActor($actor);
    }

    /**
     * Removes the first actor with the given identifier.
     * @param {string} $identifier The identifier of the actor to remove.
     * @public
     */
    removeActorWithIdentifier($identifier) {

        const actor = this.$actors.find(($actor) => ($actor.identifier === $identifier));

        if (typeof actor === 'undefined') {

            return;
        }

        this.$removeActor(actor);
    }

    /**
     * Removes all actors.
     * @param {boolean} $force If the removal should also be applied to all actors created during this removal.
     * @public
     */
    removeActors($force = false) {

        if ($force === true) {

            while (this.$actors.length > 0) {

                const [actor] = this.$actors;

                this.$removeActor(actor);
            }

            return;
        }

        [...this.$actors].forEach(($actor) => {

            this.$removeActor($actor);
        });
    }

    /**
     * Removes the actors with the given identifier.
     * @param {string} $identifier The identifier of the actors to remove.
     * @param {boolean} $force If the removal should also be applied to the actors with the given identifier created during this removal.
     * @public
     */
    removeActorsWithIdentifier($identifier, $force = false) {

        if ($force === true) {

            while (this.$actors.some(($actor) => ($actor.identifier === $identifier)) === true) {

                const actor = this.$actors.find(($actor) => ($actor.identifier === $identifier));

                this.$removeActor(actor);
            }

            return;
        }

        [...this.$actors.filter(($actor) => ($actor.identifier === $identifier))].forEach(($actor) => {

            this.$removeActor($actor);
        });
    }

    /**
     * Sets the given actor as the point of view.
     * @param {Actor} $actor The actor to set as the point of view.
     * @public
     */
    setPointOfView($actor) {

        this.$pointOfView = $actor;
    }
}

export {

    Stage
};

export default Stage;