core/grid.js

// 'ESLint' configuration
/* global TypeGeneric */

import {AABB, Vector2} from '../index.js';

/**
 * Creates two-dimensional grids.
 * @template {any} TypeGeneric The generic type of the data stored.
 *
 * @example
 *
 * const grid = new Grid();
 * grid.set(position, data);
 *
 * grid.traverse(aabb, handler);
 */
class Grid {

    /**
     * @callback TypeHandlerTraverseTerminate A handler to execute when terminating the traversal of the grid.
     * @returns {void}
     * @protected
     *
     * @memberof Grid
     */

    /**
     * @callback TypeHandlerTraverse A handler to execute when traversal a cell.
     * @param {object} $parameters The given parameters.
     * @param {TypeGeneric} $parameters.$data The data of the cell.
     * @param {Grid<TypeGeneric>} $parameters.$grid The reference grid.
     * @param {Vector2} $parameters.$position The position of the cell.
     * @param {TypeHandlerTraverseTerminate} $parameters.$terminate Terminates the traversal of the grid.
     * @returns {void}
     * @protected
     *
     * @memberof Grid
     */

    /**
     * Stores the grid structure.
     * @type {Map<string, TypeGeneric>}
     * @private
     */
    $grid;

    /**
     * Creates a new two-dimensional grid.
     */
    constructor() {

        this.$grid = new Map();
    }

    /**
     * Clears the grid.
     * @returns {this}
     * @public
     */
    clear() {

        this.$grid.clear();

        return this;
    }

    /**
     * Deletes the data from the given position.
     * @param {Vector2} $position The position to delete the data from.
     * @returns {this}
     * @public
     */
    delete($position) {

        this.$grid.delete(Vector2.serialize($position));

        return this;
    }

    /**
     * Gets the data from the given position.
     * @param {Vector2} $position The position to get the data from.
     * @returns {TypeGeneric}
     * @public
     */
    get($position) {

        return this.$grid.get(Vector2.serialize($position));
    }

    /**
     * Checks if the given position stores any data.
     * @param {Vector2} $position The given position to check.
     * @returns {boolean}
     * @public
     */
    has($position) {

        return this.$grid.has(Vector2.serialize($position)) === true;
    }

    /**
     * Iterates through the grid applying the given handler.
     * @param {TypeHandlerTraverse} $handler The handler to apply to each cell in the sector.
     * @public
     */
    iterate($handler) {

        let terminated = Boolean(false);

        /**
         * @type {TypeHandlerTraverseTerminate}
         */
        const terminate = () => {

            terminated = true;
        };

        const entries = [...this.$grid.entries()];

        for (const [$serialized, $data] of entries) {

            const position = Vector2.deserialize($serialized);

            $handler({

                $data: $data,
                $grid: this,
                $position: position,
                $terminate: terminate
            });

            if (terminated === true) {

                break;
            }
        }
    }

    /**
     * Sets the given data to the given position.
     * @param {Vector2} $position The position to set the data to (with integer values).
     * @param {TypeGeneric} $data The data to set.
     * @returns {this}
     * @public
     */
    set($position, $data) {

        this.$grid.set(Vector2.serialize($position), $data);

        return this;
    }

    /**
     * Traverses the given sector applying the given handler.
     * @param {AABB} $aabb The sector to traverse (with integer boundaries).
     * @param {TypeHandlerTraverse} $handler The handler to apply to each cell in the sector.
     * @public
     */
    traverse($aabb, $handler) {

        const minimumX = $aabb.minimum.x;
        const minimumY = $aabb.minimum.y;
        const maximumX = $aabb.maximum.x;
        const maximumY = $aabb.maximum.y;

        let terminated = Boolean(false);

        /**
         * @type {TypeHandlerTraverseTerminate}
         */
        const terminate = () => {

            terminated = true;
        };

        for (let x = minimumX; x <= maximumX; x += 1) {

            for (let y = minimumY; y <= maximumY; y += 1) {

                const position = new Vector2(x, y);

                $handler({

                    $data: this.get(position),
                    $grid: this,
                    $position: position,
                    $terminate: terminate
                });

                if (terminated === true) {

                    break;
                }
            }

            if (terminated === true) {

                break;
            }
        }
    }
}

export {

    Grid
};

export default Grid;