systems/input.system.js

import {EVENT_CODES, EVENT_TYPES, EventGamepadAnalog, EventGamepadDigital, EventGravityAnalog, EventGravityDigital, EventGyroscopeAnalog, EventGyroscopeDigital, EventPointerAnalog, EventPointerDigital, Stage, System} from '../index.js';

/**
 * Creates input systems.
 *
 * @example
 *
 * const system = new SystemInput({$container});
 * system.initiate();
 * system.tick();
 */
class SystemInput extends System {

    /**
     * Stores the container.
     * @type {HTMLElement}
     * @private
     */
    $container;

    /**
     * Stores the input events.
     * @type {Array<Event>}
     * @private
     */
    $events;

    /**
     * Stores the state of the accepted inputs.
     * @type {Object<string, boolean>}
     * @private
     */
    $inputs;

    /**
     * Stores the state of the accepted analog inputs.
     * @type {Object<string, number>}
     * @private
     */
    $inputsAnalog;

    /**
     * Creates a new input system.
     * @param {object} $parameters The given parameters.
     * @param {HTMLElement} $parameters.$container The container on which to attach input events.
     */
    constructor({$container}) {

        super();

        this.$container = $container;
    }

    /**
     * Stacks the input events for the next tick.
     * @param {Event} $event The input event to stack.
     * @private
     */
    $stack($event) {

        $event.preventDefault();

        this.$events.push($event);
    }

    /**
     * Gets the current input state value of the given digital input.
     * @param {string} $input The event code of the given digital input.
     * @returns {boolean}
     * @public
     */
    getInput($input) {

        const input = this.$inputs[$input];

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

            return false;
        }

        return input;
    }

    /**
     * Gets the current input state value of the given analog input.
     * @param {string} $input The event code of the given analog input.
     * @returns {number}
     * @public
     */
    getInputAnalog($input) {

        const input = this.$inputsAnalog[$input];

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

            return 0;
        }

        return input;
    }

    /**
     * Called when the system is being initiated.
     * @public
     */
    onInitiate() {

        this.$events = [];
        this.$inputs = {};
        this.$inputsAnalog = {};

        window.addEventListener(EVENT_TYPES.NATIVE.BLUR, this.$stack.bind(this));
        window.addEventListener(EVENT_TYPES.NATIVE.CONTEXT_MENU, this.$stack.bind(this));

        window.addEventListener(EVENT_TYPES.GAMEPAD.GAMEPAD_ANALOG, this.$stack.bind(this));
        window.addEventListener(EVENT_TYPES.GAMEPAD.GAMEPAD_CONNECT, this.$stack.bind(this));
        window.addEventListener(EVENT_TYPES.GAMEPAD.GAMEPAD_DOWN, this.$stack.bind(this));
        window.addEventListener(EVENT_TYPES.GAMEPAD.GAMEPAD_UP, this.$stack.bind(this));

        window.addEventListener(EVENT_TYPES.GRAVITY.GRAVITY_ANALOG, this.$stack.bind(this));
        window.addEventListener(EVENT_TYPES.GRAVITY.GRAVITY_DOWN, this.$stack.bind(this));
        window.addEventListener(EVENT_TYPES.GRAVITY.GRAVITY_UP, this.$stack.bind(this));

        window.addEventListener(EVENT_TYPES.GYROSCOPE.GYROSCOPE_ANALOG, this.$stack.bind(this));
        window.addEventListener(EVENT_TYPES.GYROSCOPE.GYROSCOPE_DOWN, this.$stack.bind(this));
        window.addEventListener(EVENT_TYPES.GYROSCOPE.GYROSCOPE_UP, this.$stack.bind(this));

        window.addEventListener(EVENT_TYPES.POINTER.POINTER_ANALOG, this.$stack.bind(this));
        window.addEventListener(EVENT_TYPES.POINTER.POINTER_DOWN, this.$stack.bind(this));
        window.addEventListener(EVENT_TYPES.POINTER.POINTER_UP, this.$stack.bind(this));

        this.$container.addEventListener(EVENT_TYPES.KEYBOARD.KEY_DOWN, this.$stack.bind(this));
        this.$container.addEventListener(EVENT_TYPES.KEYBOARD.KEY_UP, this.$stack.bind(this));
    }

    /**
     * Called when the system is being terminated.
     * @returns {(undefined | Promise<void>)}
     * @public
     */
    onTerminate() {

        window.removeEventListener(EVENT_TYPES.NATIVE.BLUR, this.$stack.bind(this));
        window.removeEventListener(EVENT_TYPES.NATIVE.CONTEXT_MENU, this.$stack.bind(this));

        window.removeEventListener(EVENT_TYPES.GAMEPAD.GAMEPAD_ANALOG, this.$stack.bind(this));
        window.removeEventListener(EVENT_TYPES.GAMEPAD.GAMEPAD_CONNECT, this.$stack.bind(this));
        window.removeEventListener(EVENT_TYPES.GAMEPAD.GAMEPAD_DOWN, this.$stack.bind(this));
        window.removeEventListener(EVENT_TYPES.GAMEPAD.GAMEPAD_UP, this.$stack.bind(this));

        window.removeEventListener(EVENT_TYPES.GRAVITY.GRAVITY_ANALOG, this.$stack.bind(this));
        window.removeEventListener(EVENT_TYPES.GRAVITY.GRAVITY_DOWN, this.$stack.bind(this));
        window.removeEventListener(EVENT_TYPES.GRAVITY.GRAVITY_UP, this.$stack.bind(this));

        window.removeEventListener(EVENT_TYPES.GYROSCOPE.GYROSCOPE_ANALOG, this.$stack.bind(this));
        window.removeEventListener(EVENT_TYPES.GYROSCOPE.GYROSCOPE_DOWN, this.$stack.bind(this));
        window.removeEventListener(EVENT_TYPES.GYROSCOPE.GYROSCOPE_UP, this.$stack.bind(this));

        window.removeEventListener(EVENT_TYPES.POINTER.POINTER_ANALOG, this.$stack.bind(this));
        window.removeEventListener(EVENT_TYPES.POINTER.POINTER_DOWN, this.$stack.bind(this));
        window.removeEventListener(EVENT_TYPES.POINTER.POINTER_UP, this.$stack.bind(this));

        this.$container.removeEventListener(EVENT_TYPES.KEYBOARD.KEY_DOWN, this.$stack.bind(this));
        this.$container.removeEventListener(EVENT_TYPES.KEYBOARD.KEY_UP, this.$stack.bind(this));

        return undefined;
    }

    /**
     * Called when the system is being updated by one tick update.
     * @param {object} $parameters The given parameters.
     * @param {Stage} $parameters.$stage The stage on which to execute the system.
     * @param {number} $parameters.$timetick The tick duration (in ms).
     * @public
     */
    onTick({$stage, $timetick}) {

        void $stage;
        void $timetick;

        if (typeof this.$inputs[EVENT_CODES.GAMEPAD_STANDARD.CONNECTED] !== 'undefined') {

            delete this.$inputs[EVENT_CODES.GAMEPAD_STANDARD.CONNECTED];
        }

        if (typeof this.$inputs[EVENT_CODES.GAMEPAD_STANDARD.DISCONNECTED] !== 'undefined') {

            delete this.$inputs[EVENT_CODES.GAMEPAD_STANDARD.DISCONNECTED];
        }

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

            const $event = this.$events.shift();

            if ($event.type === EVENT_TYPES.NATIVE.BLUR) {

                this.$inputs = {};
                this.$inputsAnalog = {};
            }

            else if ($event instanceof EventGamepadAnalog
            && $event.type === EVENT_TYPES.GAMEPAD.GAMEPAD_ANALOG) {

                this.$inputsAnalog[$event.code] = $event.value;
            }

            else if ($event instanceof EventGamepadDigital
            && $event.type === EVENT_TYPES.GAMEPAD.GAMEPAD_CONNECT) {

                this.$inputs[$event.code] = true;
            }

            else if ($event instanceof EventGamepadDigital
            && $event.type === EVENT_TYPES.GAMEPAD.GAMEPAD_DOWN) {

                if (typeof this.$inputs[$event.code] === 'undefined') {

                    this.$inputs[$event.code] = true;
                }
            }

            else if ($event instanceof EventGamepadDigital
            && $event.type === EVENT_TYPES.GAMEPAD.GAMEPAD_UP) {

                if (typeof this.$inputs[$event.code] !== 'undefined') {

                    delete this.$inputs[$event.code];
                }
            }

            else if ($event instanceof EventGravityAnalog
            && $event.type === EVENT_TYPES.GRAVITY.GRAVITY_ANALOG) {

                this.$inputsAnalog[$event.code] = $event.value;
            }

            else if ($event instanceof EventGravityDigital
            && $event.type === EVENT_TYPES.GRAVITY.GRAVITY_DOWN) {

                if (typeof this.$inputs[$event.code] === 'undefined') {

                    this.$inputs[$event.code] = true;
                }
            }

            else if ($event instanceof EventGravityDigital
            && $event.type === EVENT_TYPES.GRAVITY.GRAVITY_UP) {

                if (typeof this.$inputs[$event.code] !== 'undefined') {

                    delete this.$inputs[$event.code];
                }
            }

            else if ($event instanceof EventGyroscopeAnalog
            && $event.type === EVENT_TYPES.GYROSCOPE.GYROSCOPE_ANALOG) {

                this.$inputsAnalog[$event.code] = $event.value;
            }

            else if ($event instanceof EventGyroscopeDigital
            && $event.type === EVENT_TYPES.GYROSCOPE.GYROSCOPE_DOWN) {

                if (typeof this.$inputs[$event.code] === 'undefined') {

                    this.$inputs[$event.code] = true;
                }
            }

            else if ($event instanceof EventGyroscopeDigital
            && $event.type === EVENT_TYPES.GYROSCOPE.GYROSCOPE_UP) {

                if (typeof this.$inputs[$event.code] !== 'undefined') {

                    delete this.$inputs[$event.code];
                }
            }

            else if ($event instanceof KeyboardEvent
            && $event.type === EVENT_TYPES.KEYBOARD.KEY_DOWN) {

                if (typeof this.$inputs[$event.code] === 'undefined') {

                    this.$inputs[$event.code] = true;
                }
            }

            else if ($event instanceof KeyboardEvent
            && $event.type === EVENT_TYPES.KEYBOARD.KEY_UP) {

                if (typeof this.$inputs[$event.code] !== 'undefined') {

                    delete this.$inputs[$event.code];
                }
            }

            else if ($event instanceof EventPointerAnalog
            && $event.type === EVENT_TYPES.POINTER.POINTER_ANALOG) {

                this.$inputsAnalog[$event.code] = $event.value;
            }

            else if ($event instanceof EventPointerDigital
            && $event.type === EVENT_TYPES.POINTER.POINTER_DOWN) {

                if (typeof this.$inputs[$event.code] === 'undefined') {

                    this.$inputs[$event.code] = true;
                }
            }

            else if ($event instanceof EventPointerDigital
            && $event.type === EVENT_TYPES.POINTER.POINTER_UP) {

                if (typeof this.$inputs[$event.code] !== 'undefined') {

                    delete this.$inputs[$event.code];
                }
            }
        }
    }
}

export {

    SystemInput
};

export default SystemInput;