UNPKG

sam-ecs

Version:

A specialized entity component system

983 lines (834 loc) 31.2 kB
'use strict'; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } //StateManager.js// /** * @description - Manager that manages one thing specifically: state * @author - Sam Faulkner */ //node imports var Dict = require('collections/dict.js'), Set = require('collections/set.js'), SortedArray = require('collections/sorted-array.js'); //user imports var Entity = require('./Entity.js'); var isEqual = require('lodash/isEqual.js'); var clone = require('lodash/cloneDeep.js'); //constants var MAXIMUM_BUFFER_LENGTH = 8; var StateManager = function () { function StateManager(bufferSize) { _classCallCheck(this, StateManager); this._entities = new Dict(); this._entitiesByComponent = new Dict(); this._subStates = new Dict({ 'default': new Set() }); this._stateBuffer = new SortedArray([], function (first, second) { return first.tick == second.tick; }, function (first, second) { return first.tick - second.tick; }); this._maxBufferSize = bufferSize || MAXIMUM_BUFFER_LENGTH; } _createClass(StateManager, [{ key: 'setMaxBufferSize', value: function setMaxBufferSize(value) { this._maxBufferSize = value; } /** * @description - Returns the entities * @returns {Dict} the entities in the state manager */ }, { key: 'getEntities', value: function getEntities() { return this._entities; } /** * @description - Helper method for returning all of the entities in a * set * @returns {Set} set of all the entities hashes */ }, { key: 'getEntitySet', value: function getEntitySet() { return new Set(this._entities.keys()); } /** * @description - Returns the state for a single entity * @param {String} hash - the hash of the entity to return the state of * @returns {Dict} the dict of components from the given entity */ }, { key: 'getEntityState', value: function getEntityState(hash) { if (!this._entities.has(hash)) { throw new TypeError(hash + " isn't in state manager!"); } return this._entities.get(hash).get('components'); } /** * @description - Helper method (particularly for reducers) that * returns the state of the given component on the given entity, * denoted by the given hash * @param {String} hash - the hash of the entity to retrieve state from * @param {String} component - the name of the component to retrieve the state * from */ }, { key: 'getEntityComponent', value: function getEntityComponent(hash, component) { if (!hash) { return undefined; } try { var entityComponents = this.getEntityState(hash); return entityComponents.get(component).get('state'); } catch (e) { return undefined; } } /** * @description - Returns the entity object itself * @param {String} hash - the hash of the entity to return * @returns {Entity} the entity object from the given hash */ }, { key: 'getEntity', value: function getEntity(hash) { if (!this._entities.has(hash)) { throw new TypeError(hash + " isn't in the state manager!"); } return this._entities.get(hash).get('object'); } /** * @description - Returns the set of entity hashes that have the given component * @param {String} componentName - the component that the returned entities must carry * @returns {Set} the set of entities that have the given component */ }, { key: 'getEntitiesByComponent', value: function getEntitiesByComponent(componentName) { if (!this._entitiesByComponent.has(componentName)) { throw new TypeError(componentName + " doesn't have any entities listed!"); } return this._entitiesByComponent.get(componentName); } /** * @description - Returns a boolean if there are any entities that have the * given component * @param {String} componentName - the component to be tested for * @returns {Boolean} whether or not any entity has the given component */ }, { key: 'hasComponent', value: function hasComponent(componentName) { return this._entitiesByComponent.has(componentName); } /** * @description - Adds an entity to the state manager * @param {Entity} entity - the entity object to add * @param {String} subState - optional parameter to add * this entity to a substate */ }, { key: 'addEntity', value: function addEntity(entity, subState) { if (this._entities.has(entity.hash())) { this.removeEntity(this._entities.get(entity.hash()).get('object'), true); } var entitySubState = subState || 'default'; var components = entity.getComponents(); var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = components.keys()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var componentName = _step.value; this._addEntityToComponentList(entity.hash(), componentName); } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } this._entities.set(entity.hash(), new Dict({ 'components': components, 'object': entity, 'subState': entitySubState })); this.addEntityToSubState(entitySubState, entity.hash()); entity.initializeComponents(); entity.setManager(this); return entity.hash(); } /** * @description - Removes an entity from the state manager * @param {Entity} entity - the entity object to be removed */ }, { key: 'removeEntity', value: function removeEntity(entity) { var removeComponents = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; if (!this._entities.has(entity.hash())) { throw new TypeError(entity.hash() + " isn't being tracked by the manager!"); } var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = entity.getComponents().keys()[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var componentName = _step2.value; this._removeEntityFromComponentList(entity.hash(), componentName); } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2.return) { _iterator2.return(); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } this.removeEntityFromSubState(this._entities.get(entity.hash()).get('subState'), entity.hash()); if (removeComponents) entity.removeComponents(false); this._entities.delete(entity.hash()); entity.clearManager(); } /** * @description - Adds the given entity hash to the given component set * @param {String} entityHash - the hash to add to the set * @param {String} componentName - the component set to add it to */ }, { key: '_addEntityToComponentList', value: function _addEntityToComponentList(entityHash, componentName) { if (!this.hasComponent(componentName)) { this._entitiesByComponent.set(componentName, new Set()); } this._entitiesByComponent.get(componentName).add(entityHash); } /** * @description - Removes the given entityHash from the given component set * @param {String} entityHash - the hash to remove from the set * @param {String} componentName - denotes the set to remove it from */ }, { key: '_removeEntityFromComponentList', value: function _removeEntityFromComponentList(entityHash, componentName) { if (!this.hasComponent(componentName)) { throw new TypeError("Can't remove " + entityHash + " from component " + componentName); } var componentSet = this._entitiesByComponent.get(componentName); componentSet.delete(entityHash); if (componentSet.length <= 0) { this._entitiesByComponent.delete(componentName); } } /** * @description - Invalidates the processor's cached list of entities * @param {String} entity - the hash of the entity to invalidate * @param {String} componentName - the name of the component */ }, { key: '_invalidateProcessorListsByEntityComponent', value: function _invalidateProcessorListsByEntityComponent(entity, componentName) { // this should trigger the listener for the ProcessorManager this._entities.dispatchMapChange(entity, this._entities.get(entity)); } /** * @description - Adds a new substate within the state manager. * @param {String} name - the name of the new substate * @param {Set} entities - optional, but if defined will add these entities * to the state */ }, { key: 'addSubState', value: function addSubState(name, entities) { if (!this._subStates.has(name)) { this._subStates.set(name, new Set()); // throw new TypeError("Attempting to override substate: " + name + "!"); } if (entities) { this.addEntitiesToSubState(name, entities.toArray()); } } /** * @description - Adds an iterable object of entities to the given substate * @param {String} name - the name of the substate that will be added to * @param {Iterable} entities - the entity hashes that will be added */ }, { key: 'addEntitiesToSubState', value: function addEntitiesToSubState(name, entities) { if (!this._subStates.has(name)) { // throw new TypeError("Can't add entities to an unknown substate: " + name); this.addSubState(name); } var subState = this._subStates.get(name); var _iteratorNormalCompletion3 = true; var _didIteratorError3 = false; var _iteratorError3 = undefined; try { for (var _iterator3 = entities[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { var entityHash = _step3.value; if (!this._entities.has(entityHash)) { throw new TypeError("'" + entityHash + "' isn't being tracked by the State Manager!"); } var entityObject = this._entities.get(entityHash); var previousSubState = entityObject.get('subState'); if (this._subStates.get(previousSubState).has(entityObject.get('object').hash())) { this.removeEntityFromSubState(entityObject.get('subState'), entityObject.get('object').hash()); } subState.add(entityHash); this._entities.get(entityHash).set('subState', name); } } catch (err) { _didIteratorError3 = true; _iteratorError3 = err; } finally { try { if (!_iteratorNormalCompletion3 && _iterator3.return) { _iterator3.return(); } } finally { if (_didIteratorError3) { throw _iteratorError3; } } } } /** * @description - Adds a single entity to a substate * @param {String} name - the substate that will be addeded to * @param {String} entityHash - the hash of the entity that will be added */ }, { key: 'addEntityToSubState', value: function addEntityToSubState(name, entity) { this.addEntitiesToSubState(name, [entity]); } /** * @description - Removes the given entities from the given substate * @param {String} name - the name of the substate to remove from * @param {Array} entities - the array of entity hashes to remove */ }, { key: 'removeEntitiesFromSubState', value: function removeEntitiesFromSubState(name, entities) { if (!this._subStates.has(name)) { throw new TypeError("Can't remove entities from an unknown substate: " + name); } var subState = this._subStates.get(name); var _iteratorNormalCompletion4 = true; var _didIteratorError4 = false; var _iteratorError4 = undefined; try { for (var _iterator4 = entities[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { var entityHash = _step4.value; if (!this._entities.has(entityHash)) { throw new TypeError("'" + entityHash + "' isn't being tracked by the State Manager!"); } if (!subState.has(entityHash)) { throw new TypeError("'" + entityHash + "' isn't in substate: '" + name + "'!"); } subState.delete(entityHash); this._entities.get(entityHash).set('subState', 'default'); } } catch (err) { _didIteratorError4 = true; _iteratorError4 = err; } finally { try { if (!_iteratorNormalCompletion4 && _iterator4.return) { _iterator4.return(); } } finally { if (_didIteratorError4) { throw _iteratorError4; } } } } /** * @description - Updates the state manager. Currently only updates the buffer */ }, { key: 'update', value: function update(currentTick) { this.bufferState(currentTick); } /** * @description - Buffers the current state into the state buffer * @param {Number} current tick - the tick to save the state under for * keeping track of the different buffered states */ }, { key: 'bufferState', value: function bufferState(currentTick) { while (this._stateBuffer.length >= this._maxBufferSize) { this._stateBuffer.shift(); } this._stateBuffer.push({ 'tick': currentTick, 'subStates': this._subStates.clone(), 'state': this.cloneEntities() }); } /** * @description - Returns a clone of the _entities dict * @returns {Dict} a perfect clone of the _entities dict */ }, { key: 'cloneEntities', value: function cloneEntities() { var clonedDict = new Dict(); this._entities.forEach(function (value, key, dict) { var clonedEntity = value.get('object').clone(); clonedDict.set(key, new Dict({ 'object': clonedEntity, 'components': clonedEntity.getComponents(), 'subState': value.get('subState') })); }); return clonedDict; } /** * @description - Restores the state from a state that is * currently in the buffer * @param {Number} tick - the state to restore to */ }, { key: 'restoreState', value: function restoreState(tick) { var bufferedState = this.getBufferedState(tick); if (!bufferedState) { // throw new TypeError("No state listed under tick: '" + tick.toString() + "'"); bufferedState = this._stateBuffer.min(); if (!bufferedState) { throw new TypeError("No state listed under tick: '" + tick.toString() + "'"); } } var _this = this; this._entities.forEach(function (value, key, dict) { value.get('object').removeComponentsMethod(); }); this._entities = bufferedState.state; this._entities.forEach(function (value, key, dict) { value.get('object').initializeComponents(); }); this._subStates = bufferedState.subStates; } /** * @description - Returns the state within the buffer, if available * @param {Number} tick - the state to retrieve * @returns {Object} the object that was placed into the buffer * in {@link bufferState} */ }, { key: 'getBufferedState', value: function getBufferedState(tick) { var _iteratorNormalCompletion5 = true; var _didIteratorError5 = false; var _iteratorError5 = undefined; try { for (var _iterator5 = this._stateBuffer.toArray()[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { var bufferedState = _step5.value; if (bufferedState.tick == tick) { return bufferedState; } } } catch (err) { _didIteratorError5 = true; _iteratorError5 = err; } finally { try { if (!_iteratorNormalCompletion5 && _iterator5.return) { _iterator5.return(); } } finally { if (_didIteratorError5) { throw _iteratorError5; } } } } /** * @description - Returns the state buffer * @returns {Array} the array of state buffer objects */ }, { key: 'getStateBuffer', value: function getStateBuffer() { return this._stateBuffer; } /** * @description - Static method for comparing two serialized states * for equality * @param {Object} stateA - the first state to compare with * @param {Object} stateB - the second state to compare against * @returns {Boolean} are the states equal? */ }, { key: 'serializedStateEquality', value: function serializedStateEquality(stateA, stateB) { return isEqual(stateA, stateB); } /** * @description - Removes a single entity from a substate * @param {String} name - the substate that will be removed from * @param {String} entityHash - the hash of the entity that will be removed */ }, { key: 'removeEntityFromSubState', value: function removeEntityFromSubState(name, entityHash) { this.removeEntitiesFromSubState(name, [entityHash]); } /** * @description - Returns the this._entities containing * the state of all of those entities from the given substate * @param {String} name - the name of the substate to be returned * @returns {Dict} the dict of hashes to objects containing the entities state */ }, { key: 'getSubState', value: function getSubState() { var name = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'default'; if (!this._subStates.has(name)) { throw new TypeError(name + " substate doesn't exist!"); } var subState = this._subStates.get(name); var entities = this._entities.filter(function (value, key, dict) { return subState.has(key); }); var returnDict = new Dict(); var _iteratorNormalCompletion6 = true; var _didIteratorError6 = false; var _iteratorError6 = undefined; try { for (var _iterator6 = entities.keys()[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) { var entity = _step6.value; returnDict.set(entity, this._entities.get(entity)); } } catch (err) { _didIteratorError6 = true; _iteratorError6 = err; } finally { try { if (!_iteratorNormalCompletion6 && _iterator6.return) { _iterator6.return(); } } finally { if (_didIteratorError6) { throw _iteratorError6; } } } return returnDict; } /** * @description - Returns the entities belonging to a substate within a * given, buffered state object * @param {Dict} bufferedStateObject - the given buffered state object * @param {String} name - the name of the substate to return */ }, { key: '_getSubState', value: function _getSubState(bufferedStateObject) { var name = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'default'; if (!bufferedStateObject.subStates.has(name)) { throw new TypeError(name + " substate doesn't exist in this buffered state!"); } var subState = bufferedStateObject.subStates.get(name); var entities = bufferedStateObject.entities.filter(function (value, key, dict) { return subState.has(key); }); var returnDict = new Dict(); var _iteratorNormalCompletion7 = true; var _didIteratorError7 = false; var _iteratorError7 = undefined; try { for (var _iterator7 = entities.keys()[Symbol.iterator](), _step7; !(_iteratorNormalCompletion7 = (_step7 = _iterator7.next()).done); _iteratorNormalCompletion7 = true) { var entity = _step7.value; returnDict.set(entity, entities.get(entity)); } } catch (err) { _didIteratorError7 = true; _iteratorError7 = err; } finally { try { if (!_iteratorNormalCompletion7 && _iterator7.return) { _iterator7.return(); } } finally { if (_didIteratorError7) { throw _iteratorError7; } } } return returnDict; } /** * @description - Saves the given substate to a regular js Object * @param {String} subState - the substate to serialize * @returns {Object} the state of the given substate */ }, { key: 'serializeState', value: function serializeState() { var subState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'default'; return this._serializeState(this.getSubState(subState)); } }, { key: '_serializeState', value: function _serializeState(stateObject) { var entitiesList = []; stateObject.forEach(function (value, key, dict) { var entityObject = value.get('object').serialize(); entityObject.subState = value.get('subState'); entitiesList.push(entityObject); }); return { 'entities': entitiesList }; } /** * @description - Generates a checksum of a serialized state object * @returns {int} */ }, { key: 'generateCheckSum', value: function generateCheckSum(serializedStateObject) { var entityList = serializedStateObject.entities; var sum = 0; var _iteratorNormalCompletion8 = true; var _didIteratorError8 = false; var _iteratorError8 = undefined; try { for (var _iterator8 = entityList[Symbol.iterator](), _step8; !(_iteratorNormalCompletion8 = (_step8 = _iterator8.next()).done); _iteratorNormalCompletion8 = true) { var entity = _step8.value; sum = crc.crc32(entity.hash, sum); for (var componentName in entity.components) { var component = entity.components[componentName]; var _iteratorNormalCompletion9 = true; var _didIteratorError9 = false; var _iteratorError9 = undefined; try { for (var _iterator9 = Object.keys(component)[Symbol.iterator](), _step9; !(_iteratorNormalCompletion9 = (_step9 = _iterator9.next()).done); _iteratorNormalCompletion9 = true) { var key = _step9.value; sum = crc.crc32(key, sum); sum = crc.crc32(component[key].toString(), sum); } } catch (err) { _didIteratorError9 = true; _iteratorError9 = err; } finally { try { if (!_iteratorNormalCompletion9 && _iterator9.return) { _iterator9.return(); } } finally { if (_didIteratorError9) { throw _iteratorError9; } } } } } } catch (err) { _didIteratorError8 = true; _iteratorError8 = err; } finally { try { if (!_iteratorNormalCompletion8 && _iterator8.return) { _iterator8.return(); } } finally { if (_didIteratorError8) { throw _iteratorError8; } } } return sum; } /** * @description - Merges the entities in the stateObject with * the current entities in the given substate. Conflicts favor the * given stateObject * @param {Object} stateObject - state object most likely returned * from {@link serializeState} * @param {ComponentManager} componentManager - the component manager, * used for [re]constructing entities * @param {String} subStateName - denotes the substate to merge with */ }, { key: 'mergeState', value: function mergeState(stateObject, componentManager) { var subStateName = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'default'; var entityList = stateObject.entities; var _this = this; entityList.forEach(function (value, index, array) { var hash = value.hash; if (_this._entities.has(hash)) { var entityObject = _this._entities.get(hash).get('object').clone(); entityObject.deserialize(value, componentManager); //this should erase the old one and replace it with a new one _this.addEntity(entityObject, value.subState); } // create the entity else { var componentList = []; for (var componentKey in value.components) { componentList.push({ 'name': componentKey, 'args': value.components[componentKey] }); } _this.addEntity(componentManager.createEntityFromComponents(componentList, hash), value.subState); } }); } /** * @description - Gets the differing state between * the given substate and the substate denoted by the * string * @param {Dict} subState - the substate to compare against * @param {String} subStateName - denotes the substate within this * manager to compare with * @returns {Dict} the differing state dict */ }, { key: 'getDeltaState', value: function getDeltaState(subState) { var subStateName = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'default'; var thisSubState = this.getSubState(subStateName); var deltaDict = new Dict(); thisSubState.forEach(function (value, key, dict) { if (!subState.has(key)) { deltaDict.set(key, value); } else { var thisValue = subState.get(key); if (!thisValue.get('object').equals(value.get('object'))) { var entity = value.get('object').clone(); deltaDict.set(key, new Dict({ 'components': entity.getComponents(), 'object': entity, 'subState': subStateName })); } } }); return deltaDict; } /** * @description - Clears out the entire state */ }, { key: 'clear', value: function clear() { var _iteratorNormalCompletion10 = true; var _didIteratorError10 = false; var _iteratorError10 = undefined; try { for (var _iterator10 = this._entities.values()[Symbol.iterator](), _step10; !(_iteratorNormalCompletion10 = (_step10 = _iterator10.next()).done); _iteratorNormalCompletion10 = true) { var entityObject = _step10.value; var entity = entityObject.get('object'); this.removeEntity(entity); } } catch (err) { _didIteratorError10 = true; _iteratorError10 = err; } finally { try { if (!_iteratorNormalCompletion10 && _iterator10.return) { _iterator10.return(); } } finally { if (_didIteratorError10) { throw _iteratorError10; } } } } /** * @description - clears out all the state for the entities in the given * substate * @param {String} subState - denotes the substate to clear */ }, { key: 'clearSubState', value: function clearSubState(subState) { if (!this._subStates.has(subState)) { throw new TypeError("Can't clear a non existing substate!"); } var _this = this, subStateSet = this._subStates.get(subState); this._entities.forEach(function (value, key, dict) { if (subStateSet.has(key)) { _this.removeEntity(value.get('object')); } }); subStateSet.clear(); } /** * @description - Returns the entire state of the StateManager */ }, { key: 'getState', value: function getState() { var state = {}; var _iteratorNormalCompletion11 = true; var _didIteratorError11 = false; var _iteratorError11 = undefined; try { for (var _iterator11 = this._subStates.keys()[Symbol.iterator](), _step11; !(_iteratorNormalCompletion11 = (_step11 = _iterator11.next()).done); _iteratorNormalCompletion11 = true) { var subState = _step11.value; state[subState] = this.serializeState(subState); } } catch (err) { _didIteratorError11 = true; _iteratorError11 = err; } finally { try { if (!_iteratorNormalCompletion11 && _iterator11.return) { _iterator11.return(); } } finally { if (_didIteratorError11) { throw _iteratorError11; } } } return state; } /** * @description - Takes in an entire state generated from * {@link getState} and merges it in * @param {Object} state - the whole state object generated from * 'getState' * @param {ComponentManager} componentManager - the componentManager * for {@link mergeState} */ }, { key: 'mergeEntireState', value: function mergeEntireState(state, componentManager) { for (var subState in state) { this.mergeState(state[subState], componentManager, subState); } } }]); return StateManager; }(); module.exports = StateManager;