ActionState.js

import Data from './Data.js';
import StateMap from './StateMap.js';
import {arrayCache} from './utils/array.js';
import config from 'config';
import recycle from 'recycle';

const
    /**
     * This class defines an action state based on one or more inputs. This is used by [EntityController](platypus.components.EntityController.html) to produce event messages listing whether a particular action is "triggered", "pressed", and/or "released".
     *
     * @memberof platypus
     * @class ActionState
     * @param event {string} The name of the event to trigger on the Entity.
     * @param states {Object} A list of key/value pairs describing what states should be `true` or `false` on the Entity for this action to be triggered.
     * @param trigger {Function} The function to be called if one or more inputs are active and the current state of the Entity is valid.
     * @property {string} [event] The name of the event to trigger on the Entity.
     * @property {Function} [trigger] The function to call if the ActionState is active.
     * @property {boolean} [active] Whether any of the ActionState's inputs are active.
     * @property {boolean} [wasActive] Whether any of the ActionState's inputs were active last update.
     * @property {boolean} [valid] Whether the Entity's state is valid for this ActionState.
     * @property {boolean} [wasValid] Whether the Entity's state was valid for this ActionState last update.
     * @property {platypus.StateMap} [states] The state of the Entity that is valid for this ActionState.
     * @property {Array} [inputs] The list of input toggles to track control input.
     * @property {platypus.Data} [stateSummary] The message that is passed to the Entity if the ActionState is active.
     * @return {ActionState} Returns the new ActionState object.
     */
    ActionState = function (event, states, trigger) {
        this.event     = event;
        this.trigger   = trigger;
        this.active    = false;
        this.wasActive = false;
        this.valid     = true;
        this.wasValid  = true;
        this.states = StateMap.setUp(states);
        this.inputs = arrayCache.setUp();

        this.stateSummary = Data.setUp(
            "pressed",   false,
            "released",  false,
            "triggered", false
        );
    },
    orArray = function (element) {
        return element;
    },
    proto = ActionState.prototype;

/**
 * Updates the state of the action by checking the state of the Entity and whether any inputs are active.
 *
 * @method platypus.ActionState#update
 * @param state {Object} The Entity's `state` property to compare against the ActionState's valid state.
 * @return {Boolean} Whether the ActionState is triggered, pressed, or released.
 */
proto.update = function (state) {
    var ss = this.stateSummary;
    
    this.valid     = state.includes(this.states);
    this.active    = this.inputs.some(orArray);
    
    ss.pressed     = this.valid && this.active;
    ss.released    = this.wasActive && ((!this.valid && this.wasValid) || (this.valid && !this.active));
    ss.triggered   = this.valid && this.active && !this.wasActive;
    
    this.wasValid  = this.valid;
    this.wasActive = this.active;
    
    return ss.pressed || ss.released || ss.triggered;
};

/**
 * Triggers events on the Entity related to the ActionState's state. This is necessarily separate from the `update` method since triggered events could affect entity state. The messages have the following form and are only triggered if one of the values is `true`:
 *
 *     {
 *         "triggered": true,
 *         "pressed": true,
 *         "released": false
 *     }
 *
 * Here is a mapping of the various event messages depending on the ActionState's state.
 *
 *     ActionState State:
 *          wasValid:  0 0 0 0  0 0 0 0  1 1 1 1  1 1 1 1
 *             valid:  0 0 0 0  1 1 1 1  0 0 0 0  1 1 1 1
 *         wasActive:  0 0 1 1  0 0 1 1  0 0 1 1  0 0 1 1
 *            active:  0 1 0 1  0 1 0 1  0 1 0 1  0 1 0 1
 *     Events:
 *         triggered:  0 0 0 0  0 1 0 0  0 0 0 0  0 1 0 0
 *           pressed:  0 0 0 0  0 1 0 1  0 0 0 0  0 1 0 1
 *          released:  0 0 0 0  0 0 1 0  0 0 1 1  0 0 1 0
 *
 * @method platypus.ActionState#resolve
 */
proto.resolve = function () {
    this.trigger(this.event, this.stateSummary);
};

/**
 * Returns an ActionState from cache or creates a new one if none are available.
 *
 * @method platypus.ActionState.setUp
 * @return {platypus.ActionState} The instantiated ActionState.
 */
/**
 * Returns an ActionState back to the cache. Prefer the ActionState's recycle method since it recycles property objects as well.
 *
 * @method platypus.ActionState.recycle
 * @param {platypus.ActionState} actionState The ActionState to be recycled.
 */
/**
 * Relinquishes properties of the ActionState and recycles it.
 *
 * @method platypus.ActionState#recycle
 */
recycle.add(ActionState, 'ActionState', ActionState, function () {
    this.states.recycle();
    this.stateSummary.recycle();
    arrayCache.recycle(this.inputs);
}, true, config.dev);

export default ActionState;