components/Node.js

/**
## JSON Definition
    {
      "type": "NodeResident",
      
      "nodeId": "city-hall",
      // Optional. The id of the node that this entity should start on. Uses the entity's nodeId property if not set here.
      
      "nodes": ['path','sidewalk','road'],
      // Optional. This is a list of node types that this entity can reside on. If not set, entity can reside on any type of node.
      
      "shares": ['friends','neighbors','city-council-members'],
      // Optional. This is a list of entities that this entity can reside with on the same node. If not set, this entity cannot reside with any entities on the same node.
      
      "speed": 5,
      // Optional. Sets the speed with which the entity moves along an edge to an adjacent node. Default is 0 (instantaneous movement).
      
      "updateOrientation": true
      // Optional. Determines whether the entity's orientation is updated by movement across the NodeMap. Default is false.
    }
*/
import {arrayCache, greenSplice} from '../utils/array.js';
import Vector from '../Vector.js';
import createComponentClass from '../factory.js';

export default createComponentClass(/** @lends platypus.components.Node.prototype */{
    
    id: 'Node',

    properties: {
        /**
         * If provided, treats these property names as neighbors, assigning them to the neighbors object. For example, ["east", "west"] creates `entity.east` and `entity.west` entity properties that are pointers to those neighbors.
         *
         * @property neighborProperties
         * @type Array
         * @default null
         */
        neighborProperties: null
    },
    
    publicProperties: {
        x: 0,
        y: 0,
        z: 0
    },
    
    /**
     * This component causes an entity to be a position on a [[NodeMap]]. This component should not be confused with `NodeResident` which should be used on entities that move around on a NodeMap: `Node` simply represents a non-moving location on the NodeMap.
     * 
     * @memberof platypus.components
     * @uses platypus.Component
     * @constructs
     * @param {*} definition 
     */
    initialize: function (definition) {
        const owner = this.owner;

        this.nodeId = definition.nodeId || owner.nodeId || owner.id || String(Math.random());
        
        if ((typeof this.nodeId !== 'string') && (this.nodeId.length)) {
            this.nodeId = definition.nodeId.join('|');
        }
        
        owner.nodeId = this.nodeId;
        
        owner.isNode = true;
        this.map = owner.map = owner.map || owner.parent || null;
        this.contains = owner.contains = arrayCache.setUp();
        this.edgesContain = owner.edgesContain = arrayCache.setUp();
        
        Vector.assign(owner, 'position', 'x', 'y', 'z');
        
        this.neighbors = owner.neighbors = definition.neighbors || owner.neighbors || {};
        
        if (this.neighborProperties) {
            const properties = this.neighborProperties;

            for (let i = 0; i < properties.length; i++) {
                const
                    propertyName = properties[i],
                    value = owner[propertyName];

                if (value) {
                    this.neighbors[propertyName] = value;
                }
                Object.defineProperty(owner, propertyName, {
                    get: () => this.neighbors[propertyName],
                    set: (value) => {
                        if (value !== this.neighbors[propertyName]) {
                            this.neighbors[propertyName] = value;
                            for (let i = 0; i < this.contains.length; i++) {
                                this.contains[i].triggerEvent('set-directions');
                            }
                        }
                    }
                });
            }
        }
    },
    
    events: {
        "add-neighbors": function (neighbors) {
            for (const direction in neighbors) {
                if (neighbors.hasOwnProperty(direction)) {
                    this.neighbors[direction] = neighbors[direction];
                }
            }
            
            for (let i = 0; i < this.contains.length; i++) {
                this.contains[i].triggerEvent('set-directions');
            }
        },
        "remove-neighbor": function (nodeOrNodeId) {
            var i  = null,
                id = nodeOrNodeId;
            
            if (typeof id !== 'string') {
                id = id.nodeId;
            }

            for (i in this.neighbors) {
                if (this.neighbors.hasOwnProperty(i)) {
                    if (typeof this.neighbors[i] === 'string') {
                        if (this.neighbors[i] === id) {
                            delete this.neighbors[i];
                            break;
                        }
                    } else if (this.neighbors[i].nodeId === id) {
                        delete this.neighbors[i];
                        break;
                    }
                }
            }
        }
    },
    
    methods: {
        destroy: function () {
            arrayCache.recycle(this.contains);
            this.contains = this.owner.contains = null;
            arrayCache.recycle(this.edgesContain);
            this.edgesContain = this.owner.edgesContain = null;
        }
    },
    
    publicMethods: {
        /**
         * Gets a neighboring node Entity.
         * 
         * @memberof Node.prototype
         * @param {String} desc Describes the direction to check.
         * @returns {platypus.Entity}
         */
        getNode: function (desc) {
            var neighbor = null;
            
            //map check
            if (!this.map && this.owner.map) {
                this.map = this.owner.map;
            }
            
            if (this.neighbors[desc]) {
                neighbor = this.neighbors[desc];
                if (neighbor.isNode) {
                    return neighbor;
                } else if (typeof neighbor === 'string') {
                    neighbor = this.map.getNode(neighbor);
                    if (neighbor) {
                        this.neighbors[desc] = neighbor;
                        return neighbor;
                    }
                } else if (neighbor.length) {
                    neighbor = this.map.getNode(neighbor.join('|'));
                    if (neighbor) {
                        this.neighbors[desc] = neighbor;
                        return neighbor;
                    }
                }
                return null;
            } else {
                return null;
            }
        },

        /**
         * Puts an entity on this node.
         * 
         * @memberof Node.prototype
         * @param {platypus.Entity} entity
         * @returns {platypus.Entity}
         */
        addToNode: function (entity) {
            var i = 0;
            
            for (i = 0; i < this.contains.length; i++) {
                if (this.contains[i] === entity) {
                    return false;
                }
            }
            this.contains.push(entity);
            return entity;
        },

        /**
         * Removes an entity from this node.
         * 
         * @memberof Node.prototype
         * @param {platypus.Entity} entity
         * @returns {platypus.Entity}
         */
        removeFromNode: function (entity) {
            var i = 0;
            
            for (i = 0; i < this.contains.length; i++) {
                if (this.contains[i] === entity) {
                    return greenSplice(this.contains, i);
                }
            }
            return false;
        },

        /**
         * Adds an entity to this node's edges.
         * 
         * @memberof Node.prototype
         * @param {platypus.Entity} entity
         * @returns {platypus.Entity}
         */
        addToEdge: function (entity) {
            var i = 0;
            
            for (i = 0; i < this.edgesContain.length; i++) {
                if (this.edgesContain[i] === entity) {
                    return false;
                }
            }
            this.edgesContain.push(entity);
            return entity;
        },

        /**
         * Removes an entity from this node's edges.
         * 
         * @memberof Node.prototype
         * @param {platypus.Entity} entity
         * @returns {platypus.Entity}
         */
        removeFromEdge: function (entity) {
            var i = 0;
            
            for (i = 0; i < this.edgesContain.length; i++) {
                if (this.edgesContain[i] === entity) {
                    return greenSplice(this.edgesContain, i);
                }
            }
            return false;
        }
    }
});