Source: state/States.js

//------------------------------------------------------------------------------
// Constructor scope
//------------------------------------------------------------------------------

/**
 * Creates a new instance of the States class, ie a state machine.
 *
 * @constructor
 *
 * @param {Object} owner The state owner.
 * 
 * @class
 * @classdesc
 * 
 * The States class represents a finite-state machine (FSM) that can handle 
 * multiple logical states simultaneously. Although several states can be 
 * allocated simultaneously, only one state can be active at a time. Switch 
 * between states to quickly switch between an object's behavioral logic.
 */
rune.state.States = function(owner) {

    //--------------------------------------------------------------------------
    // Private properties
    //--------------------------------------------------------------------------

    /**
     * Reference to the object that is in the current state.
     *
     * @type {Object}
     * @private
     */
    this.m_owner = owner || null;
    
    /**
     * Index of selected state.
     *
     * @type {number}
     * @private
     */
    this.m_selected = 0;

    /**
     * List of initiated and available States.
     *
     * @type {Array.<rune.state.State>}
     * @private
     */
    this.m_states = [];

    /**
     * List containing states to be activated at the next frame.
     *
     * @type {Array.<rune.state.State>}
     * @private
     */
    this.m_swap = null;
};

//------------------------------------------------------------------------------
// Public prototype getter and setter methods
//------------------------------------------------------------------------------

/**
 * The state that is currently selected and thus activated.
 *
 * @member {rune.state.State} selected
 * @memberof rune.state.States
 * @instance
 * @readonly
 */
Object.defineProperty(rune.state.States.prototype, "selected", {
    /**
     * @this rune.state.States
     * @ignore
     */
    get : function() {
        return this.m_states[this.m_selected];
    }
});

//------------------------------------------------------------------------------
// Public prototype methods (API)
//------------------------------------------------------------------------------

/**
 * Load a new batch of states. A "batch" is represented by a list.
 *
 * @param {Array.<rune.state.State>} states List of instantiated states.
 *
 * @throws {Error} If invalid or empty batch.
 *
 * @returns {undefined}
 */
rune.state.States.prototype.load = function(states) {
    if (Array.isArray(states) === true && states.length > 0) {
        this.m_swap = states;
    } else throw new Error();
};

/**
 * Selects and activates a state based on its name.
 *
 * @param {string} name Name of state to be selected.
 *
 * @returns {boolean} Whether a state could be selected.
 */
rune.state.States.prototype.select = function(name) {
    for (var i = 0; i < this.m_states.length; i++) {
        if (this.m_states[i]['name'].toUpperCase() == name.toUpperCase()) {
            if (this.m_selected != i) {
                var a = i;
                var b = this.m_selected;
                this.m_states[this.m_selected]['onExit'](this.m_states[a]);
                this.m_selected = a;
                this.m_states[this.m_selected]['onEnter'](this.m_states[b]);
            }
            
            return true;
        }
    }

    return false;
};

//------------------------------------------------------------------------------
// Public prototype methods (ENGINE)
//------------------------------------------------------------------------------

/**
 * Updates all states.
 *
 * @param {number} step Current time step.
 *
 * @returns {undefined}
 * @ignore
 */
rune.state.States.prototype.update = function(step) {
    this.m_updateSwap(step);
    this.m_updateStates(step);
};

/**
 * Renders all states.
 *
 * @returns {undefined}
 * @ignore
 */
rune.state.States.prototype.render = function() {
    this.m_renderStates();
};

/**
 * Destroys all states.
 *
 * @returns {undefined}
 * @ignore
 */
rune.state.States.prototype.dispose = function() {
    this.m_disposeStates();
};

//------------------------------------------------------------------------------
// Private prototype methods
//------------------------------------------------------------------------------

/**
 * Initiates current states.
 *
 * @throws {Error} In case of invalid state swap.
 *
 * @returns {undefined}
 * @private
 */
rune.state.States.prototype.m_initStates = function() {
    this.m_disposeStates();
    if (this.m_swap != null && this.m_swap.length > 0) {
        this.m_states = this.m_swap;
        this.m_selected = 0;
        for (var i = 0; i < this.m_states.length; i++) {
            this.m_states[i].setOwner(this.m_owner);
            this.m_states[i].init();
        }
        
        this.m_swap = null;
    } else throw new Error();
};

/**
 * Update swap.
 *
 * @param {number} step Current time step.
 *
 * @returns {undefined}
 * @private
 */
rune.state.States.prototype.m_updateSwap = function(step) {
    if (this.m_swap != null) {
        this.m_initStates();
    }
};

/**
 * Update states.
 *
 * @param {number} step Current time step.
 *
 * @returns {undefined}
 * @private
 */
rune.state.States.prototype.m_updateStates = function(step) {
    if (this.m_states != null && this.m_states.length > 0) {
        this.m_states[this.m_selected].update(step);
    }
};

/**
 * Render states.
 *
 * @returns {undefined}
 * @private
 */
rune.state.States.prototype.m_renderStates = function() {
    if (this.m_states != null && this.m_states.length > 0) {
        this.m_states[this.m_selected].render();
    }
};

/**
 * Destroys all states.
 *
 * @returns {undefined}
 * @private
 */
rune.state.States.prototype.m_disposeStates = function() {
    if (this.m_states != null) {
        for (var i = 0; i < this.m_states.length; i++) {
            this.m_states[i].dispose();
            this.m_states[i] = null;
        }
        
        this.m_states = null;
        this.m_selected = 0;
    }
};