Comparing version 0.2.3 to 0.2.4
@@ -12,2 +12,49 @@ (function (global, factory) { | ||
/** | ||
* Return the name of a component | ||
* @param {Component} Component | ||
* @private | ||
*/ | ||
function getName(Component) { | ||
return Component.name; | ||
} | ||
/** | ||
* Return a valid property name for the Component | ||
* @param {Component} Component | ||
* @private | ||
*/ | ||
function componentPropertyName(Component) { | ||
return getName(Component); | ||
} | ||
/** | ||
* Get a key from a list of components | ||
* @param {Array(Component)} Components Array of components to generate the key | ||
* @private | ||
*/ | ||
function queryKey(Components) { | ||
var names = []; | ||
for (var n = 0; n < Components.length; n++) { | ||
var T = Components[n]; | ||
if (typeof T === "object") { | ||
var operator = T.operator === "not" ? "!" : T.operator; | ||
names.push(operator + getName(T.Component)); | ||
} else { | ||
names.push(getName(T)); | ||
} | ||
} | ||
return names.sort().join("-"); | ||
} | ||
// Detector for browser's "window" | ||
const hasWindow = typeof window !== "undefined"; | ||
// performance.now() "polyfill" | ||
const now = | ||
hasWindow && typeof window.performance !== "undefined" | ||
? performance.now.bind(performance) | ||
: Date.now.bind(Date); | ||
class SystemManager { | ||
@@ -22,5 +69,3 @@ constructor(world) { | ||
registerSystem(System, attributes) { | ||
if ( | ||
this._systems.find(s => s.constructor.name === System.name) !== undefined | ||
) { | ||
if (this.getSystem(System) !== undefined) { | ||
console.warn(`System '${System.name}' already registered.`); | ||
@@ -41,2 +86,19 @@ return this; | ||
unregisterSystem(System) { | ||
let system = this.getSystem(System); | ||
if (system === undefined) { | ||
console.warn(`Can unregister system '${System.name}'. It doesn't exist.`); | ||
return this; | ||
} | ||
this._systems.splice(this._systems.indexOf(system), 1); | ||
if (system.execute) { | ||
this._executeSystems.splice(this._executeSystems.indexOf(system), 1); | ||
} | ||
// @todo Add system.unregister() call to free resources | ||
return this; | ||
} | ||
sortSystems() { | ||
@@ -66,5 +128,5 @@ this._executeSystems.sort((a, b) => { | ||
if (system.canExecute()) { | ||
let startTime = performance.now(); | ||
let startTime = now(); | ||
system.execute(delta, time); | ||
system.executeTime = performance.now() - startTime; | ||
system.executeTime = now() - startTime; | ||
this.lastExecutedSystem = system; | ||
@@ -96,3 +158,4 @@ system.clearEvents(); | ||
var systemStats = (stats.systems[system.constructor.name] = { | ||
queries: {} | ||
queries: {}, | ||
executeTime: system.executeTime | ||
}); | ||
@@ -191,40 +254,2 @@ for (var name in system.ctx) { | ||
/** | ||
* Return the name of a component | ||
* @param {Component} Component | ||
* @private | ||
*/ | ||
function getName(Component) { | ||
return Component.name; | ||
} | ||
/** | ||
* Return a valid property name for the Component | ||
* @param {Component} Component | ||
* @private | ||
*/ | ||
function componentPropertyName(Component) { | ||
return getName(Component); | ||
} | ||
/** | ||
* Get a key from a list of components | ||
* @param {Array(Component)} Components Array of components to generate the key | ||
* @private | ||
*/ | ||
function queryKey(Components) { | ||
var names = []; | ||
for (var n = 0; n < Components.length; n++) { | ||
var T = Components[n]; | ||
if (typeof T === "object") { | ||
var operator = T.operator === "not" ? "!" : T.operator; | ||
names.push(operator + getName(T.Component)); | ||
} else { | ||
names.push(getName(T)); | ||
} | ||
} | ||
return names.sort().join("-"); | ||
} | ||
class Query { | ||
@@ -358,2 +383,5 @@ /** | ||
this.alive = false; | ||
//if there are state components on a entity, it can't be removed completely | ||
this.numStateComponents = 0; | ||
} | ||
@@ -394,2 +422,3 @@ | ||
// @todo accelerate this check. Maybe having query._Components as an object | ||
// @todo add Not components | ||
if (query.reactive && query.Components.indexOf(Component) !== -1) { | ||
@@ -411,4 +440,4 @@ query.eventDispatcher.dispatchEvent( | ||
removeComponent(Component, forceRemove) { | ||
this._world.entityRemoveComponent(this, Component, forceRemove); | ||
removeComponent(Component, forceImmediate) { | ||
this._world.entityRemoveComponent(this, Component, forceImmediate); | ||
return this; | ||
@@ -442,4 +471,4 @@ } | ||
removeAllComponents(forceRemove) { | ||
return this._world.entityRemoveAllComponents(this, forceRemove); | ||
removeAllComponents(forceImmediate) { | ||
return this._world.entityRemoveAllComponents(this, forceImmediate); | ||
} | ||
@@ -458,4 +487,4 @@ | ||
remove(forceRemove) { | ||
return this._world.removeEntity(this, forceRemove); | ||
remove(forceImmediate) { | ||
return this._world.removeEntity(this, forceImmediate); | ||
} | ||
@@ -663,4 +692,2 @@ } | ||
this.deferredRemovalEnabled = true; | ||
this.numStateComponents = 0; | ||
} | ||
@@ -702,3 +729,11 @@ | ||
entityAddComponent(entity, Component, values) { | ||
if (~entity._ComponentTypes.indexOf(Component)) return; | ||
if (~entity._ComponentTypes.indexOf(Component)) { | ||
// @todo Just on debug mode | ||
console.warn( | ||
"Component type already exists on entity.", | ||
entity, | ||
Component.name | ||
); | ||
return; | ||
} | ||
@@ -708,3 +743,3 @@ entity._ComponentTypes.push(Component); | ||
if (Component.__proto__ === SystemStateComponent) { | ||
this.numStateComponents++; | ||
entity.numStateComponents++; | ||
} | ||
@@ -766,6 +801,6 @@ | ||
if (Component.__proto__ === SystemStateComponent) { | ||
this.numStateComponents--; | ||
entity.numStateComponents--; | ||
// Check if the entity was a ghost waiting for the last system state component to be removed | ||
if (this.numStateComponents === 0 && !entity.alive) { | ||
if (entity.numStateComponents === 0 && !entity.alive) { | ||
entity.remove(); | ||
@@ -812,3 +847,3 @@ } | ||
if (this.numStateComponents === 0) { | ||
if (entity.numStateComponents === 0) { | ||
// Remove from entity list | ||
@@ -1110,3 +1145,3 @@ this.eventDispatcher.dispatchEvent(ENTITY_REMOVED, entity); | ||
if (typeof CustomEvent !== "undefined") { | ||
if (hasWindow && typeof CustomEvent !== "undefined") { | ||
var event = new CustomEvent("ecsy-world-created", { | ||
@@ -1118,3 +1153,3 @@ detail: { world: this, version: Version } | ||
this.lastTime = performance.now(); | ||
this.lastTime = now(); | ||
} | ||
@@ -1132,2 +1167,7 @@ | ||
unregisterSystem(System) { | ||
this.systemManager.unregisterSystem(System); | ||
return this; | ||
} | ||
getSystem(SystemClass) { | ||
@@ -1143,3 +1183,3 @@ return this.systemManager.getSystem(SystemClass); | ||
if (!delta) { | ||
let time = performance.now(); | ||
time = now(); | ||
delta = time - this.lastTime; | ||
@@ -1239,2 +1279,12 @@ this.lastTime = time; | ||
validEvents.forEach(eventName => { | ||
if (!this.execute) { | ||
console.warn( | ||
`System '${ | ||
this.constructor.name | ||
}' has defined listen events (${validEvents.join( | ||
", " | ||
)}) for query '${queryName}' but it does not implement the 'execute' method.` | ||
); | ||
} | ||
// Is the event enabled on this system's query? | ||
@@ -1688,2 +1738,7 @@ if (queryConfig.listen[eventName]) { | ||
function enableRemoteDevtools(remoteId) { | ||
if (!hasWindow) { | ||
console.warn("Remote devtools not available outside the browser"); | ||
return; | ||
} | ||
window.generateNewCode = () => { | ||
@@ -1774,7 +1829,9 @@ window.localStorage.clear(); | ||
const urlParams = new URLSearchParams(window.location.search); | ||
if (hasWindow) { | ||
const urlParams = new URLSearchParams(window.location.search); | ||
// @todo Provide a way to disable it if needed | ||
if (urlParams.has("enable-remote-devtools")) { | ||
enableRemoteDevtools(); | ||
// @todo Provide a way to disable it if needed | ||
if (urlParams.has("enable-remote-devtools")) { | ||
enableRemoteDevtools(); | ||
} | ||
} | ||
@@ -1781,0 +1838,0 @@ |
@@ -1,1 +0,1 @@ | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):(e=e||self,function(){var n=e.ECSY,o=e.ECSY={};t(o),o.noConflict=function(){return e.ECSY=n,o}}())}(this,(function(exports){"use strict";class SystemManager{constructor(e){this._systems=[],this._executeSystems=[],this.world=e,this.lastExecutedSystem=null}registerSystem(e,t){if(void 0!==this._systems.find(t=>t.constructor.name===e.name))return console.warn(`System '${e.name}' already registered.`),this;var n=new e(this.world,t);return n.init&&n.init(),n.order=this._systems.length,this._systems.push(n),n.execute&&(this._executeSystems.push(n),this.sortSystems()),this}sortSystems(){this._executeSystems.sort((e,t)=>e.priority-t.priority||e.order-t.order)}getSystem(e){return this._systems.find(t=>t instanceof e)}getSystems(){return this._systems}removeSystem(e){var t=this._systems.indexOf(e);~t&&this._systems.splice(t,1)}executeSystem(e,t,n){if(e.initialized&&e.canExecute()){let o=performance.now();e.execute(t,n),e.executeTime=performance.now()-o,this.lastExecutedSystem=e,e.clearEvents()}}stop(){this._executeSystems.forEach(e=>e.stop())}execute(e,t,n){this._executeSystems.forEach(o=>(n||o.enabled)&&this.executeSystem(o,e,t))}stats(){for(var e={numSystems:this._systems.length,systems:{}},t=0;t<this._systems.length;t++){var n=this._systems[t],o=e.systems[n.constructor.name]={queries:{}};for(var s in n.ctx)o.queries[s]=n.ctx[s].stats()}return e}}class EventDispatcher{constructor(){this._listeners={},this.stats={fired:0,handled:0}}addEventListener(e,t){let n=this._listeners;void 0===n[e]&&(n[e]=[]),-1===n[e].indexOf(t)&&n[e].push(t)}hasEventListener(e,t){return void 0!==this._listeners[e]&&-1!==this._listeners[e].indexOf(t)}removeEventListener(e,t){var n=this._listeners[e];if(void 0!==n){var o=n.indexOf(t);-1!==o&&n.splice(o,1)}}dispatchEvent(e,t,n){this.stats.fired++;var o=this._listeners[e];if(void 0!==o)for(var s=o.slice(0),i=0;i<s.length;i++)s[i].call(this,t,n)}resetCounters(){this.stats.fired=this.stats.handled=0}}function getName(e){return e.name}function componentPropertyName(e){return getName(e)}function queryKey(e){for(var t=[],n=0;n<e.length;n++){var o=e[n];if("object"==typeof o){var s="not"===o.operator?"!":o.operator;t.push(s+getName(o.Component))}else t.push(getName(o))}return t.sort().join("-")}class Query{constructor(e,t){if(this.Components=[],this.NotComponents=[],e.forEach(e=>{"object"==typeof e?this.NotComponents.push(e.Component):this.Components.push(e)}),0===this.Components.length)throw new Error("Can't create a query without components");this.entities=[],this.eventDispatcher=new EventDispatcher,this.reactive=!1,this.key=queryKey(e);for(var n=0;n<t._entities.length;n++){var o=t._entities[n];this.match(o)&&(o.queries.push(this),this.entities.push(o))}}addEntity(e){e.queries.push(this),this.entities.push(e),this.eventDispatcher.dispatchEvent(Query.prototype.ENTITY_ADDED,e)}removeEntity(e){let t=this.entities.indexOf(e);~t&&(this.entities.splice(t,1),t=e.queries.indexOf(this),e.queries.splice(t,1),this.eventDispatcher.dispatchEvent(Query.prototype.ENTITY_REMOVED,e))}match(e){return e.hasAllComponents(this.Components)&&!e.hasAnyComponents(this.NotComponents)}toJSON(){return{key:this.key,reactive:this.reactive,components:{included:this.Components.map(e=>e.name),not:this.NotComponents.map(e=>e.name)},numEntities:this.entities.length}}stats(){return{numComponents:this.Components.length,numEntities:this.entities.length}}}Query.prototype.ENTITY_ADDED="Query#ENTITY_ADDED",Query.prototype.ENTITY_REMOVED="Query#ENTITY_REMOVED",Query.prototype.COMPONENT_CHANGED="Query#COMPONENT_CHANGED";var nextId=0;class Entity{constructor(e){this._world=e||null,this.id=nextId++,this._ComponentTypes=[],this._components={},this._componentsToRemove={},this.queries=[],this._ComponentTypesToRemove=[],this.alive=!1}getComponent(e,t){var n=this._components[e.name];return n||!0!==t||(n=this._componentsToRemove[e.name]),n}getRemovedComponent(e){return this._componentsToRemove[e.name]}getComponents(){return this._components}getComponentsToRemove(){return this._componentsToRemove}getComponentTypes(){return this._ComponentTypes}getMutableComponent(e){for(var t=this._components[e.name],n=0;n<this.queries.length;n++){var o=this.queries[n];o.reactive&&-1!==o.Components.indexOf(e)&&o.eventDispatcher.dispatchEvent(Query.prototype.COMPONENT_CHANGED,this,t)}return t}addComponent(e,t){return this._world.entityAddComponent(this,e,t),this}removeComponent(e,t){return this._world.entityRemoveComponent(this,e,t),this}hasComponent(e,t){return!!~this._ComponentTypes.indexOf(e)||!0===t&&this.hasRemovedComponent(e)}hasRemovedComponent(e){return!!~this._ComponentTypesToRemove.indexOf(e)}hasAllComponents(e){for(var t=0;t<e.length;t++)if(!this.hasComponent(e[t]))return!1;return!0}hasAnyComponents(e){for(var t=0;t<e.length;t++)if(this.hasComponent(e[t]))return!0;return!1}removeAllComponents(e){return this._world.entityRemoveAllComponents(this,e)}reset(){this.id=nextId++,this._world=null,this._ComponentTypes.length=0,this.queries.length=0,this._components={}}remove(e){return this._world.removeEntity(this,e)}}class ObjectPool{constructor(e,t){this.freeList=[],this.count=0,this.T=e,this.isObjectPool=!0;var n=null;arguments.length>1&&(n=Array.prototype.slice.call(arguments)).shift(),this.createElement=n?()=>new e(...n):()=>new e,void 0!==t&&this.expand(t)}aquire(){return this.freeList.length<=0&&this.expand(Math.round(.2*this.count)+1),this.freeList.pop()}release(e){e.reset(),this.freeList.push(e)}expand(e){for(var t=0;t<e;t++)this.freeList.push(this.createElement());this.count+=e}totalSize(){return this.count}totalFree(){return this.freeList.length}totalUsed(){return this.count-this.freeList.length}}class QueryManager{constructor(e){this._world=e,this._queries={}}onEntityRemoved(e){for(var t in this._queries){var n=this._queries[t];-1!==e.queries.indexOf(n)&&n.removeEntity(e)}}onEntityComponentAdded(e,t){for(var n in this._queries){var o=this._queries[n];~o.NotComponents.indexOf(t)&&~o.entities.indexOf(e)?o.removeEntity(e):~o.Components.indexOf(t)&&o.match(e)&&!~o.entities.indexOf(e)&&o.addEntity(e)}}onEntityComponentRemoved(e,t){for(var n in this._queries){var o=this._queries[n];~o.NotComponents.indexOf(t)&&!~o.entities.indexOf(e)&&o.match(e)?o.addEntity(e):~o.Components.indexOf(t)&&~o.entities.indexOf(e)&&!o.match(e)&&o.removeEntity(e)}}getQuery(e){var t=queryKey(e),n=this._queries[t];return n||(this._queries[t]=n=new Query(e,this._world)),n}stats(){var e={};for(var t in this._queries)e[t]=this._queries[t].stats();return e}}class SystemStateComponent{}SystemStateComponent.isSystemStateComponent=!0;class EntityManager{constructor(e){this.world=e,this.componentsManager=e.componentsManager,this._entities=[],this._entitiesByNames={},this._queryManager=new QueryManager(this),this.eventDispatcher=new EventDispatcher,this._entityPool=new ObjectPool(Entity),this.entitiesWithComponentsToRemove=[],this.entitiesToRemove=[],this.deferredRemovalEnabled=!0,this.numStateComponents=0}getEntityByName(e){return this._entitiesByNames[e]}createEntity(e){var t=this._entityPool.aquire();return t.alive=!0,t.name=e||"",e&&(this._entitiesByNames[e]?console.warn(`Entity name '${e}' already exist`):this._entitiesByNames[e]=t),t._world=this,this._entities.push(t),this.eventDispatcher.dispatchEvent(ENTITY_CREATED,t),t}entityAddComponent(e,t,n){if(!~e._ComponentTypes.indexOf(t)){e._ComponentTypes.push(t),t.__proto__===SystemStateComponent&&this.numStateComponents++;var o=this.world.componentsManager.getComponentsPool(t).aquire();if(e._components[t.name]=o,n)if(o.copy)o.copy(n);else for(var s in n)o[s]=n[s];this._queryManager.onEntityComponentAdded(e,t),this.world.componentsManager.componentAddedToEntity(t),this.eventDispatcher.dispatchEvent(COMPONENT_ADDED,e,t)}}entityRemoveComponent(e,t,n){var o=e._ComponentTypes.indexOf(t);if(~o){if(this.eventDispatcher.dispatchEvent(COMPONENT_REMOVE,e,t),n)this._entityRemoveComponentSync(e,t,o);else{0===e._ComponentTypesToRemove.length&&this.entitiesWithComponentsToRemove.push(e),e._ComponentTypes.splice(o,1),e._ComponentTypesToRemove.push(t);var s=getName(t);e._componentsToRemove[s]=e._components[s],delete e._components[s]}this._queryManager.onEntityComponentRemoved(e,t),t.__proto__===SystemStateComponent&&(this.numStateComponents--,0!==this.numStateComponents||e.alive||e.remove())}}_entityRemoveComponentSync(e,t,n){e._ComponentTypes.splice(n,1);var o=componentPropertyName(t),s=getName(t),i=e._components[s];delete e._components[s],this.componentsManager._componentPool[o].release(i),this.world.componentsManager.componentRemovedFromEntity(t)}entityRemoveAllComponents(e,t){let n=e._ComponentTypes;for(let o=n.length-1;o>=0;o--)n[o].__proto__!==SystemStateComponent&&this.entityRemoveComponent(e,n[o],t)}removeEntity(e,t){var n=this._entities.indexOf(e);if(!~n)throw new Error("Tried to remove entity not in list");e.alive=!1,0===this.numStateComponents&&(this.eventDispatcher.dispatchEvent(ENTITY_REMOVED,e),this._queryManager.onEntityRemoved(e),!0===t?this._releaseEntity(e,n):this.entitiesToRemove.push(e)),this.entityRemoveAllComponents(e,t)}_releaseEntity(e,t){this._entities.splice(t,1),e._world=null,this._entityPool.release(e)}removeAllEntities(){for(var e=this._entities.length-1;e>=0;e--)this.removeEntity(this._entities[e])}processDeferredRemoval(){if(this.deferredRemovalEnabled){for(let e=0;e<this.entitiesToRemove.length;e++){let t=this.entitiesToRemove[e],n=this._entities.indexOf(t);this._releaseEntity(t,n)}this.entitiesToRemove.length=0;for(let o=0;o<this.entitiesWithComponentsToRemove.length;o++){let s=this.entitiesWithComponentsToRemove[o];for(;s._ComponentTypesToRemove.length>0;){let o=s._ComponentTypesToRemove.pop();var e=componentPropertyName(o),t=getName(o),n=s._componentsToRemove[t];delete s._componentsToRemove[t],this.componentsManager._componentPool[e].release(n),this.world.componentsManager.componentRemovedFromEntity(o)}}this.entitiesWithComponentsToRemove.length=0}}queryComponents(e){return this._queryManager.getQuery(e)}count(){return this._entities.length}stats(){var e={numEntities:this._entities.length,numQueries:Object.keys(this._queryManager._queries).length,queries:this._queryManager.stats(),numComponentPool:Object.keys(this.componentsManager._componentPool).length,componentPool:{},eventDispatcher:this.eventDispatcher.stats};for(var t in this.componentsManager._componentPool){var n=this.componentsManager._componentPool[t];e.componentPool[t]={used:n.totalUsed(),size:n.count}}return e}}const ENTITY_CREATED="EntityManager#ENTITY_CREATE",ENTITY_REMOVED="EntityManager#ENTITY_REMOVED",COMPONENT_ADDED="EntityManager#COMPONENT_ADDED",COMPONENT_REMOVE="EntityManager#COMPONENT_REMOVE";class DummyObjectPool{constructor(e){this.isDummyObjectPool=!0,this.count=0,this.used=0,this.T=e}aquire(){return this.used++,this.count++,new this.T}release(){this.used--}totalSize(){return this.count}totalFree(){return 1/0}totalUsed(){return this.used}}class ComponentManager{constructor(){this.Components={},this._componentPool={},this.numComponents={}}registerComponent(e){this.Components[e.name]?console.warn(`Component type: '${e.name}' already registered.`):(this.Components[e.name]=e,this.numComponents[e.name]=0)}componentAddedToEntity(e){this.Components[e.name]||this.registerComponent(e),this.numComponents[e.name]++}componentRemovedFromEntity(e){this.numComponents[e.name]--}getComponentsPool(e){var t=componentPropertyName(e);return this._componentPool[t]||(e.prototype.reset?this._componentPool[t]=new ObjectPool(e):(console.warn(`Component '${e.name}' won't benefit from pooling because 'reset' method was not implemented.`),this._componentPool[t]=new DummyObjectPool(e))),this._componentPool[t]}}var name="ecsy",version="0.2.2",description="Entity Component System in JS",main="build/ecsy.js",module="build/ecsy.module.js",types="src/index.d.ts",scripts={build:"rollup -c && npm run docs",docs:"rm docs/api/_sidebar.md; typedoc --readme none --mode file --excludeExternals --plugin typedoc-plugin-markdown --theme docs/theme --hideSources --hideBreadcrumbs --out docs/api/ --includeDeclarations --includes 'src/**/*.d.ts' src; touch docs/api/_sidebar.md","dev:docs":"nodemon -e ts -x 'npm run docs' -w src",dev:"concurrently --names 'ROLLUP,DOCS,HTTP' -c 'bgBlue.bold,bgYellow.bold,bgGreen.bold' 'rollup -c -w -m inline' 'npm run dev:docs' 'npm run dev:server'","dev:server":"http-server -c-1 -p 8080 --cors",lint:"eslint src test examples",start:"npm run dev",test:"ava",travis:"npm run lint && npm run test && npm run build","watch:test":"ava --watch"},repository={type:"git",url:"git+https://github.com/fernandojsg/ecsy.git"},keywords=["ecs","entity component system"],author="Fernando Serrano <fernandojsg@gmail.com> (http://fernandojsg.com)",license="MIT",bugs={url:"https://github.com/fernandojsg/ecsy/issues"},ava={files:["test/**/*.test.js"],sources:["src/**/*.js"],require:["babel-register","esm"]},jspm={files:["package.json","LICENSE","README.md","build/ecsy.js","build/ecsy.min.js","build/ecsy.module.js"],directories:{}},homepage="https://github.com/fernandojsg/ecsy#readme",devDependencies={ava:"^1.4.1","babel-cli":"^6.26.0","babel-core":"^6.26.3","babel-eslint":"^10.0.3","babel-loader":"^8.0.6",concurrently:"^4.1.2","docsify-cli":"^4.4.0",eslint:"^5.16.0","eslint-config-prettier":"^4.3.0","eslint-plugin-prettier":"^3.1.2","http-server":"^0.11.1",nodemon:"^1.19.4",prettier:"^1.19.1",rollup:"^1.29.0","rollup-plugin-json":"^4.0.0","rollup-plugin-terser":"^5.2.0",typedoc:"^0.15.8","typedoc-plugin-markdown":"^2.2.16",typescript:"^3.7.5"},pjson={name:name,version:version,description:description,main:main,"jsnext:main":"build/ecsy.module.js",module:module,types:types,scripts:scripts,repository:repository,keywords:keywords,author:author,license:license,bugs:bugs,ava:ava,jspm:jspm,homepage:homepage,devDependencies:devDependencies};const Version=pjson.version;class World{constructor(){if(this.componentsManager=new ComponentManager(this),this.entityManager=new EntityManager(this),this.systemManager=new SystemManager(this),this.enabled=!0,this.eventQueues={},"undefined"!=typeof CustomEvent){var e=new CustomEvent("ecsy-world-created",{detail:{world:this,version:Version}});window.dispatchEvent(e)}this.lastTime=performance.now()}registerComponent(e){return this.componentsManager.registerComponent(e),this}registerSystem(e,t){return this.systemManager.registerSystem(e,t),this}getSystem(e){return this.systemManager.getSystem(e)}getSystems(){return this.systemManager.getSystems()}execute(e,t){if(!e){let t=performance.now();e=t-this.lastTime,this.lastTime=t}this.enabled&&(this.systemManager.execute(e,t),this.entityManager.processDeferredRemoval())}stop(){this.enabled=!1}play(){this.enabled=!0}createEntity(e){return this.entityManager.createEntity(e)}stats(){var e={entities:this.entityManager.stats(),system:this.systemManager.stats()};console.log(JSON.stringify(e,null,2))}}class System{canExecute(){if(0===this._mandatoryQueries.length)return!0;for(let e=0;e<this._mandatoryQueries.length;e++){if(0===this._mandatoryQueries[e].entities.length)return!1}return!0}constructor(e,t){if(this.world=e,this.enabled=!0,this._queries={},this.queries={},this.priority=0,this.executeTime=0,t&&t.priority&&(this.priority=t.priority),this._mandatoryQueries=[],this.initialized=!0,this.constructor.queries)for(var n in this.constructor.queries){var o=this.constructor.queries[n],s=o.components;if(!s||0===s.length)throw new Error("'components' attribute can't be empty in a query");var i=this.world.entityManager.queryComponents(s);this._queries[n]=i,!0===o.mandatory&&this._mandatoryQueries.push(i),this.queries[n]={results:i.entities};const e={added:Query.prototype.ENTITY_ADDED,removed:Query.prototype.ENTITY_REMOVED,changed:Query.prototype.COMPONENT_CHANGED};o.listen&&["added","removed","changed"].forEach(t=>{if(o.listen[t]){let s=o.listen[t];if("changed"===t){if(i.reactive=!0,!0===s){let e=this.queries[n][t]=[];i.eventDispatcher.addEventListener(Query.prototype.COMPONENT_CHANGED,t=>{-1===e.indexOf(t)&&e.push(t)})}else if(Array.isArray(s)){let e=this.queries[n][t]=[];i.eventDispatcher.addEventListener(Query.prototype.COMPONENT_CHANGED,(t,n)=>{-1!==s.indexOf(n.constructor)&&-1===e.indexOf(t)&&e.push(t)})}}else{let o=this.queries[n][t]=[];i.eventDispatcher.addEventListener(e[t],e=>{-1===o.indexOf(e)&&o.push(e)})}}})}}stop(){this.executeTime=0,this.enabled=!1}play(){this.enabled=!0}clearEvents(){for(let t in this.queries){var e=this.queries[t];if(e.added&&(e.added.length=0),e.removed&&(e.removed.length=0),e.changed)if(Array.isArray(e.changed))e.changed.length=0;else for(let t in e.changed)e.changed[t].length=0}}toJSON(){var e={name:this.constructor.name,enabled:this.enabled,executeTime:this.executeTime,priority:this.priority,queries:{}};if(this.constructor.queries){var t=this.constructor.queries;for(let n in t){let o=this.queries[n],s=t[n],i=e.queries[n]={key:this._queries[n].key};if(i.mandatory=!0===s.mandatory,i.reactive=s.listen&&(!0===s.listen.added||!0===s.listen.removed||!0===s.listen.changed||Array.isArray(s.listen.changed)),i.reactive){i.listen={},["added","removed","changed"].forEach(e=>{o[e]&&(i.listen[e]={entities:o[e].length})})}}}return e}}function Not(e){return{operator:"not",Component:e}}class Component{}Component.isComponent=!0;class TagComponent{reset(){}}function createType(e){var t=["create","reset","clear"].filter(t=>!e[t]);if(t.length>0)throw new Error(`createType expect type definition to implements the following functions: ${t.join(", ")}`);return e.isType=!0,e}TagComponent.isTagComponent=!0;var Types={};Types.Number=createType({baseType:Number,isSimpleType:!0,create:e=>void 0!==e?e:0,reset:(e,t,n)=>{e[t]=void 0!==n?n:0},clear:(e,t)=>{e[t]=0}}),Types.Boolean=createType({baseType:Boolean,isSimpleType:!0,create:e=>void 0!==e&&e,reset:(e,t,n)=>{e[t]=void 0!==n&&n},clear:(e,t)=>{e[t]=!1}}),Types.String=createType({baseType:String,isSimpleType:!0,create:e=>void 0!==e?e:"",reset:(e,t,n)=>{e[t]=void 0!==n?n:""},clear:(e,t)=>{e[t]=""}}),Types.Array=createType({baseType:Array,create:e=>void 0!==e?e.slice():[],reset:(e,t,n)=>{void 0!==n?e[t]=n.slice():e[t].length=0},clear:(e,t)=>{e[t].length=0},copy:(e,t,n)=>{e[n]=t[n].slice()}});var standardTypes={number:Types.Number,boolean:Types.Boolean,string:Types.String};function inferType(e){return Array.isArray(e)?Types.Array:standardTypes[typeof e]?standardTypes[typeof e]:null}function createComponentClass(e,t){for(let t in e){e[t].type||(e[t].type=inferType(e[t].default))}var n=function(){for(let n in e){var t=e[n];let o=t.type;o&&o.isType?this[n]=o.create(t.default):this[n]=t.default}};void 0!==t&&Object.defineProperty(n,"name",{value:t}),n.prototype.schema=e;var o=!0;for(let t in e){var s=e[t];s.type||(s.type=inferType(s.default)),s.type||(console.warn(`Unknown type definition for attribute '${t}'`),o=!1)}if(o){n.prototype.copy=function(t){for(let n in e)if(t[n]){let o=e[n].type;o.isSimpleType?this[n]=t[n]:o.copy?o.copy(this,t,n):console.warn(`Unknown copy function for attribute '${n}' data type`)}},n.prototype.reset=function(){for(let t in e){let n=e[t],o=n.type;o.reset&&o.reset(this,t,n.default)}},n.prototype.clear=function(){for(let t in e){let n=e[t].type;n.clear&&n.clear(this,t)}};for(let t in e){let o=e[t],s=o.type;n.prototype[t]=o.default,s.reset&&s.reset(n.prototype,t,o.default)}}else for(var i in console.warn("This component can't use pooling because some data types are not registered. Please provide a type created with 'createType'"),e){let t=e[i];n.prototype[i]=t.default}return n}function generateId(e){for(var t="",n="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",o=n.length,s=0;s<e;s++)t+=n.charAt(Math.floor(Math.random()*o));return t}function injectScript(e,t){var n=document.createElement("script");n.src=e,n.onload=t,(document.head||document.documentElement).appendChild(n)}function hookConsoleAndErrors(e){["error","warning","log"].forEach(t=>{if("function"==typeof console[t]){var n=console[t].bind(console);console[t]=(...o)=>(e.send({method:"console",type:t,args:JSON.stringify(o)}),n.apply(null,o))}}),window.addEventListener("error",t=>{e.send({method:"error",error:JSON.stringify({message:t.error.message,stack:t.error.stack})})})}function includeRemoteIdHTML(e){let t=document.createElement("div");return t.style.cssText="\n align-items: center;\n background-color: #333;\n color: #aaa;\n display:flex;\n font-family: Arial;\n font-size: 1.1em;\n height: 40px;\n justify-content: center;\n left: 0;\n opacity: 0.9;\n position: absolute;\n right: 0;\n text-align: center;\n top: 0;\n ",t.innerHTML=`Open ECSY devtools to connect to this page using the code: <b style="color: #fff">${e}</b> <button onClick="generateNewCode()">Generate new code</button>`,document.body.appendChild(t),t}function enableRemoteDevtools(remoteId){window.generateNewCode=()=>{window.localStorage.clear(),remoteId=generateId(6),window.localStorage.setItem("ecsyRemoteId",remoteId),window.location.reload(!1)},remoteId=remoteId||window.localStorage.getItem("ecsyRemoteId"),remoteId||(remoteId=generateId(6),window.localStorage.setItem("ecsyRemoteId",remoteId));let infoDiv=includeRemoteIdHTML(remoteId);window.__ECSY_REMOTE_DEVTOOLS_INJECTED=!0,window.__ECSY_REMOTE_DEVTOOLS={};let Version="",worldsBeforeLoading=[],onWorldCreated=e=>{var t=e.detail.world;Version=e.detail.version,worldsBeforeLoading.push(t)};window.addEventListener("ecsy-world-created",onWorldCreated);let onLoaded=()=>{var peer=new Peer(remoteId);peer.on("open",()=>{peer.on("connection",connection=>{window.__ECSY_REMOTE_DEVTOOLS.connection=connection,connection.on("open",(function(){infoDiv.innerHTML="Connected",connection.on("data",(function(data){if("init"===data.type){var script=document.createElement("script");script.setAttribute("type","text/javascript"),script.onload=()=>{script.parentNode.removeChild(script),window.removeEventListener("ecsy-world-created",onWorldCreated),worldsBeforeLoading.forEach(e=>{var t=new CustomEvent("ecsy-world-created",{detail:{world:e,version:Version}});window.dispatchEvent(t)})},script.innerHTML=data.script,(document.head||document.documentElement).appendChild(script),script.onload(),hookConsoleAndErrors(connection)}else if("executeScript"===data.type){let value=eval(data.script);data.returnEval&&connection.send({method:"evalReturn",value:value})}}))}))})})};injectScript("https://cdn.jsdelivr.net/npm/peerjs@0.3.20/dist/peer.min.js",onLoaded)}const urlParams=new URLSearchParams(window.location.search);urlParams.has("enable-remote-devtools")&&enableRemoteDevtools(),exports.Component=Component,exports.Not=Not,exports.System=System,exports.SystemStateComponent=SystemStateComponent,exports.TagComponent=TagComponent,exports.Types=Types,exports.Version=Version,exports.World=World,exports.createComponentClass=createComponentClass,exports.createType=createType,exports.enableRemoteDevtools=enableRemoteDevtools,Object.defineProperty(exports,"__esModule",{value:!0})})); | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):(e=e||self,function(){var n=e.ECSY,o=e.ECSY={};t(o),o.noConflict=function(){return e.ECSY=n,o}}())}(this,(function(exports){"use strict";function getName(e){return e.name}function componentPropertyName(e){return getName(e)}function queryKey(e){for(var t=[],n=0;n<e.length;n++){var o=e[n];if("object"==typeof o){var s="not"===o.operator?"!":o.operator;t.push(s+getName(o.Component))}else t.push(getName(o))}return t.sort().join("-")}const hasWindow="undefined"!=typeof window,now=hasWindow&&void 0!==window.performance?performance.now.bind(performance):Date.now.bind(Date);class SystemManager{constructor(e){this._systems=[],this._executeSystems=[],this.world=e,this.lastExecutedSystem=null}registerSystem(e,t){if(void 0!==this.getSystem(e))return console.warn(`System '${e.name}' already registered.`),this;var n=new e(this.world,t);return n.init&&n.init(),n.order=this._systems.length,this._systems.push(n),n.execute&&(this._executeSystems.push(n),this.sortSystems()),this}unregisterSystem(e){let t=this.getSystem(e);return void 0===t?(console.warn(`Can unregister system '${e.name}'. It doesn't exist.`),this):(this._systems.splice(this._systems.indexOf(t),1),t.execute&&this._executeSystems.splice(this._executeSystems.indexOf(t),1),this)}sortSystems(){this._executeSystems.sort((e,t)=>e.priority-t.priority||e.order-t.order)}getSystem(e){return this._systems.find(t=>t instanceof e)}getSystems(){return this._systems}removeSystem(e){var t=this._systems.indexOf(e);~t&&this._systems.splice(t,1)}executeSystem(e,t,n){if(e.initialized&&e.canExecute()){let o=now();e.execute(t,n),e.executeTime=now()-o,this.lastExecutedSystem=e,e.clearEvents()}}stop(){this._executeSystems.forEach(e=>e.stop())}execute(e,t,n){this._executeSystems.forEach(o=>(n||o.enabled)&&this.executeSystem(o,e,t))}stats(){for(var e={numSystems:this._systems.length,systems:{}},t=0;t<this._systems.length;t++){var n=this._systems[t],o=e.systems[n.constructor.name]={queries:{},executeTime:n.executeTime};for(var s in n.ctx)o.queries[s]=n.ctx[s].stats()}return e}}class EventDispatcher{constructor(){this._listeners={},this.stats={fired:0,handled:0}}addEventListener(e,t){let n=this._listeners;void 0===n[e]&&(n[e]=[]),-1===n[e].indexOf(t)&&n[e].push(t)}hasEventListener(e,t){return void 0!==this._listeners[e]&&-1!==this._listeners[e].indexOf(t)}removeEventListener(e,t){var n=this._listeners[e];if(void 0!==n){var o=n.indexOf(t);-1!==o&&n.splice(o,1)}}dispatchEvent(e,t,n){this.stats.fired++;var o=this._listeners[e];if(void 0!==o)for(var s=o.slice(0),i=0;i<s.length;i++)s[i].call(this,t,n)}resetCounters(){this.stats.fired=this.stats.handled=0}}class Query{constructor(e,t){if(this.Components=[],this.NotComponents=[],e.forEach(e=>{"object"==typeof e?this.NotComponents.push(e.Component):this.Components.push(e)}),0===this.Components.length)throw new Error("Can't create a query without components");this.entities=[],this.eventDispatcher=new EventDispatcher,this.reactive=!1,this.key=queryKey(e);for(var n=0;n<t._entities.length;n++){var o=t._entities[n];this.match(o)&&(o.queries.push(this),this.entities.push(o))}}addEntity(e){e.queries.push(this),this.entities.push(e),this.eventDispatcher.dispatchEvent(Query.prototype.ENTITY_ADDED,e)}removeEntity(e){let t=this.entities.indexOf(e);~t&&(this.entities.splice(t,1),t=e.queries.indexOf(this),e.queries.splice(t,1),this.eventDispatcher.dispatchEvent(Query.prototype.ENTITY_REMOVED,e))}match(e){return e.hasAllComponents(this.Components)&&!e.hasAnyComponents(this.NotComponents)}toJSON(){return{key:this.key,reactive:this.reactive,components:{included:this.Components.map(e=>e.name),not:this.NotComponents.map(e=>e.name)},numEntities:this.entities.length}}stats(){return{numComponents:this.Components.length,numEntities:this.entities.length}}}Query.prototype.ENTITY_ADDED="Query#ENTITY_ADDED",Query.prototype.ENTITY_REMOVED="Query#ENTITY_REMOVED",Query.prototype.COMPONENT_CHANGED="Query#COMPONENT_CHANGED";var nextId=0;class Entity{constructor(e){this._world=e||null,this.id=nextId++,this._ComponentTypes=[],this._components={},this._componentsToRemove={},this.queries=[],this._ComponentTypesToRemove=[],this.alive=!1,this.numStateComponents=0}getComponent(e,t){var n=this._components[e.name];return n||!0!==t||(n=this._componentsToRemove[e.name]),n}getRemovedComponent(e){return this._componentsToRemove[e.name]}getComponents(){return this._components}getComponentsToRemove(){return this._componentsToRemove}getComponentTypes(){return this._ComponentTypes}getMutableComponent(e){for(var t=this._components[e.name],n=0;n<this.queries.length;n++){var o=this.queries[n];o.reactive&&-1!==o.Components.indexOf(e)&&o.eventDispatcher.dispatchEvent(Query.prototype.COMPONENT_CHANGED,this,t)}return t}addComponent(e,t){return this._world.entityAddComponent(this,e,t),this}removeComponent(e,t){return this._world.entityRemoveComponent(this,e,t),this}hasComponent(e,t){return!!~this._ComponentTypes.indexOf(e)||!0===t&&this.hasRemovedComponent(e)}hasRemovedComponent(e){return!!~this._ComponentTypesToRemove.indexOf(e)}hasAllComponents(e){for(var t=0;t<e.length;t++)if(!this.hasComponent(e[t]))return!1;return!0}hasAnyComponents(e){for(var t=0;t<e.length;t++)if(this.hasComponent(e[t]))return!0;return!1}removeAllComponents(e){return this._world.entityRemoveAllComponents(this,e)}reset(){this.id=nextId++,this._world=null,this._ComponentTypes.length=0,this.queries.length=0,this._components={}}remove(e){return this._world.removeEntity(this,e)}}class ObjectPool{constructor(e,t){this.freeList=[],this.count=0,this.T=e,this.isObjectPool=!0;var n=null;arguments.length>1&&(n=Array.prototype.slice.call(arguments)).shift(),this.createElement=n?()=>new e(...n):()=>new e,void 0!==t&&this.expand(t)}aquire(){return this.freeList.length<=0&&this.expand(Math.round(.2*this.count)+1),this.freeList.pop()}release(e){e.reset(),this.freeList.push(e)}expand(e){for(var t=0;t<e;t++)this.freeList.push(this.createElement());this.count+=e}totalSize(){return this.count}totalFree(){return this.freeList.length}totalUsed(){return this.count-this.freeList.length}}class QueryManager{constructor(e){this._world=e,this._queries={}}onEntityRemoved(e){for(var t in this._queries){var n=this._queries[t];-1!==e.queries.indexOf(n)&&n.removeEntity(e)}}onEntityComponentAdded(e,t){for(var n in this._queries){var o=this._queries[n];~o.NotComponents.indexOf(t)&&~o.entities.indexOf(e)?o.removeEntity(e):~o.Components.indexOf(t)&&o.match(e)&&!~o.entities.indexOf(e)&&o.addEntity(e)}}onEntityComponentRemoved(e,t){for(var n in this._queries){var o=this._queries[n];~o.NotComponents.indexOf(t)&&!~o.entities.indexOf(e)&&o.match(e)?o.addEntity(e):~o.Components.indexOf(t)&&~o.entities.indexOf(e)&&!o.match(e)&&o.removeEntity(e)}}getQuery(e){var t=queryKey(e),n=this._queries[t];return n||(this._queries[t]=n=new Query(e,this._world)),n}stats(){var e={};for(var t in this._queries)e[t]=this._queries[t].stats();return e}}class SystemStateComponent{}SystemStateComponent.isSystemStateComponent=!0;class EntityManager{constructor(e){this.world=e,this.componentsManager=e.componentsManager,this._entities=[],this._entitiesByNames={},this._queryManager=new QueryManager(this),this.eventDispatcher=new EventDispatcher,this._entityPool=new ObjectPool(Entity),this.entitiesWithComponentsToRemove=[],this.entitiesToRemove=[],this.deferredRemovalEnabled=!0}getEntityByName(e){return this._entitiesByNames[e]}createEntity(e){var t=this._entityPool.aquire();return t.alive=!0,t.name=e||"",e&&(this._entitiesByNames[e]?console.warn(`Entity name '${e}' already exist`):this._entitiesByNames[e]=t),t._world=this,this._entities.push(t),this.eventDispatcher.dispatchEvent(ENTITY_CREATED,t),t}entityAddComponent(e,t,n){if(~e._ComponentTypes.indexOf(t))console.warn("Component type already exists on entity.",e,t.name);else{e._ComponentTypes.push(t),t.__proto__===SystemStateComponent&&e.numStateComponents++;var o=this.world.componentsManager.getComponentsPool(t).aquire();if(e._components[t.name]=o,n)if(o.copy)o.copy(n);else for(var s in n)o[s]=n[s];this._queryManager.onEntityComponentAdded(e,t),this.world.componentsManager.componentAddedToEntity(t),this.eventDispatcher.dispatchEvent(COMPONENT_ADDED,e,t)}}entityRemoveComponent(e,t,n){var o=e._ComponentTypes.indexOf(t);if(~o){if(this.eventDispatcher.dispatchEvent(COMPONENT_REMOVE,e,t),n)this._entityRemoveComponentSync(e,t,o);else{0===e._ComponentTypesToRemove.length&&this.entitiesWithComponentsToRemove.push(e),e._ComponentTypes.splice(o,1),e._ComponentTypesToRemove.push(t);var s=getName(t);e._componentsToRemove[s]=e._components[s],delete e._components[s]}this._queryManager.onEntityComponentRemoved(e,t),t.__proto__===SystemStateComponent&&(e.numStateComponents--,0!==e.numStateComponents||e.alive||e.remove())}}_entityRemoveComponentSync(e,t,n){e._ComponentTypes.splice(n,1);var o=componentPropertyName(t),s=getName(t),i=e._components[s];delete e._components[s],this.componentsManager._componentPool[o].release(i),this.world.componentsManager.componentRemovedFromEntity(t)}entityRemoveAllComponents(e,t){let n=e._ComponentTypes;for(let o=n.length-1;o>=0;o--)n[o].__proto__!==SystemStateComponent&&this.entityRemoveComponent(e,n[o],t)}removeEntity(e,t){var n=this._entities.indexOf(e);if(!~n)throw new Error("Tried to remove entity not in list");e.alive=!1,0===e.numStateComponents&&(this.eventDispatcher.dispatchEvent(ENTITY_REMOVED,e),this._queryManager.onEntityRemoved(e),!0===t?this._releaseEntity(e,n):this.entitiesToRemove.push(e)),this.entityRemoveAllComponents(e,t)}_releaseEntity(e,t){this._entities.splice(t,1),e._world=null,this._entityPool.release(e)}removeAllEntities(){for(var e=this._entities.length-1;e>=0;e--)this.removeEntity(this._entities[e])}processDeferredRemoval(){if(this.deferredRemovalEnabled){for(let e=0;e<this.entitiesToRemove.length;e++){let t=this.entitiesToRemove[e],n=this._entities.indexOf(t);this._releaseEntity(t,n)}this.entitiesToRemove.length=0;for(let o=0;o<this.entitiesWithComponentsToRemove.length;o++){let s=this.entitiesWithComponentsToRemove[o];for(;s._ComponentTypesToRemove.length>0;){let o=s._ComponentTypesToRemove.pop();var e=componentPropertyName(o),t=getName(o),n=s._componentsToRemove[t];delete s._componentsToRemove[t],this.componentsManager._componentPool[e].release(n),this.world.componentsManager.componentRemovedFromEntity(o)}}this.entitiesWithComponentsToRemove.length=0}}queryComponents(e){return this._queryManager.getQuery(e)}count(){return this._entities.length}stats(){var e={numEntities:this._entities.length,numQueries:Object.keys(this._queryManager._queries).length,queries:this._queryManager.stats(),numComponentPool:Object.keys(this.componentsManager._componentPool).length,componentPool:{},eventDispatcher:this.eventDispatcher.stats};for(var t in this.componentsManager._componentPool){var n=this.componentsManager._componentPool[t];e.componentPool[t]={used:n.totalUsed(),size:n.count}}return e}}const ENTITY_CREATED="EntityManager#ENTITY_CREATE",ENTITY_REMOVED="EntityManager#ENTITY_REMOVED",COMPONENT_ADDED="EntityManager#COMPONENT_ADDED",COMPONENT_REMOVE="EntityManager#COMPONENT_REMOVE";class DummyObjectPool{constructor(e){this.isDummyObjectPool=!0,this.count=0,this.used=0,this.T=e}aquire(){return this.used++,this.count++,new this.T}release(){this.used--}totalSize(){return this.count}totalFree(){return 1/0}totalUsed(){return this.used}}class ComponentManager{constructor(){this.Components={},this._componentPool={},this.numComponents={}}registerComponent(e){this.Components[e.name]?console.warn(`Component type: '${e.name}' already registered.`):(this.Components[e.name]=e,this.numComponents[e.name]=0)}componentAddedToEntity(e){this.Components[e.name]||this.registerComponent(e),this.numComponents[e.name]++}componentRemovedFromEntity(e){this.numComponents[e.name]--}getComponentsPool(e){var t=componentPropertyName(e);return this._componentPool[t]||(e.prototype.reset?this._componentPool[t]=new ObjectPool(e):(console.warn(`Component '${e.name}' won't benefit from pooling because 'reset' method was not implemented.`),this._componentPool[t]=new DummyObjectPool(e))),this._componentPool[t]}}var name="ecsy",version="0.2.2",description="Entity Component System in JS",main="build/ecsy.js",module="build/ecsy.module.js",types="src/index.d.ts",scripts={build:"rollup -c && npm run docs",docs:"rm docs/api/_sidebar.md; typedoc --readme none --mode file --excludeExternals --plugin typedoc-plugin-markdown --theme docs/theme --hideSources --hideBreadcrumbs --out docs/api/ --includeDeclarations --includes 'src/**/*.d.ts' src; touch docs/api/_sidebar.md","dev:docs":"nodemon -e ts -x 'npm run docs' -w src",dev:"concurrently --names 'ROLLUP,DOCS,HTTP' -c 'bgBlue.bold,bgYellow.bold,bgGreen.bold' 'rollup -c -w -m inline' 'npm run dev:docs' 'npm run dev:server'","dev:server":"http-server -c-1 -p 8080 --cors",lint:"eslint src test examples",start:"npm run dev",test:"ava",travis:"npm run lint && npm run test && npm run build","watch:test":"ava --watch"},repository={type:"git",url:"git+https://github.com/fernandojsg/ecsy.git"},keywords=["ecs","entity component system"],author="Fernando Serrano <fernandojsg@gmail.com> (http://fernandojsg.com)",license="MIT",bugs={url:"https://github.com/fernandojsg/ecsy/issues"},ava={files:["test/**/*.test.js"],sources:["src/**/*.js"],require:["babel-register","esm"]},jspm={files:["package.json","LICENSE","README.md","build/ecsy.js","build/ecsy.min.js","build/ecsy.module.js"],directories:{}},homepage="https://github.com/fernandojsg/ecsy#readme",devDependencies={ava:"^1.4.1","babel-cli":"^6.26.0","babel-core":"^6.26.3","babel-eslint":"^10.0.3","babel-loader":"^8.0.6",concurrently:"^4.1.2","docsify-cli":"^4.4.0",eslint:"^5.16.0","eslint-config-prettier":"^4.3.0","eslint-plugin-prettier":"^3.1.2","http-server":"^0.11.1",nodemon:"^1.19.4",prettier:"^1.19.1",rollup:"^1.29.0","rollup-plugin-json":"^4.0.0","rollup-plugin-terser":"^5.2.0",typedoc:"^0.15.8","typedoc-plugin-markdown":"^2.2.16",typescript:"^3.7.5"},pjson={name:name,version:version,description:description,main:main,"jsnext:main":"build/ecsy.module.js",module:module,types:types,scripts:scripts,repository:repository,keywords:keywords,author:author,license:license,bugs:bugs,ava:ava,jspm:jspm,homepage:homepage,devDependencies:devDependencies};const Version=pjson.version;class World{constructor(){if(this.componentsManager=new ComponentManager(this),this.entityManager=new EntityManager(this),this.systemManager=new SystemManager(this),this.enabled=!0,this.eventQueues={},hasWindow&&"undefined"!=typeof CustomEvent){var e=new CustomEvent("ecsy-world-created",{detail:{world:this,version:Version}});window.dispatchEvent(e)}this.lastTime=now()}registerComponent(e){return this.componentsManager.registerComponent(e),this}registerSystem(e,t){return this.systemManager.registerSystem(e,t),this}unregisterSystem(e){return this.systemManager.unregisterSystem(e),this}getSystem(e){return this.systemManager.getSystem(e)}getSystems(){return this.systemManager.getSystems()}execute(e,t){e||(e=(t=now())-this.lastTime,this.lastTime=t),this.enabled&&(this.systemManager.execute(e,t),this.entityManager.processDeferredRemoval())}stop(){this.enabled=!1}play(){this.enabled=!0}createEntity(e){return this.entityManager.createEntity(e)}stats(){var e={entities:this.entityManager.stats(),system:this.systemManager.stats()};console.log(JSON.stringify(e,null,2))}}class System{canExecute(){if(0===this._mandatoryQueries.length)return!0;for(let e=0;e<this._mandatoryQueries.length;e++){if(0===this._mandatoryQueries[e].entities.length)return!1}return!0}constructor(e,t){if(this.world=e,this.enabled=!0,this._queries={},this.queries={},this.priority=0,this.executeTime=0,t&&t.priority&&(this.priority=t.priority),this._mandatoryQueries=[],this.initialized=!0,this.constructor.queries)for(var n in this.constructor.queries){var o=this.constructor.queries[n],s=o.components;if(!s||0===s.length)throw new Error("'components' attribute can't be empty in a query");var i=this.world.entityManager.queryComponents(s);this._queries[n]=i,!0===o.mandatory&&this._mandatoryQueries.push(i),this.queries[n]={results:i.entities};var r=["added","removed","changed"];const e={added:Query.prototype.ENTITY_ADDED,removed:Query.prototype.ENTITY_REMOVED,changed:Query.prototype.COMPONENT_CHANGED};o.listen&&r.forEach(t=>{if(this.execute||console.warn(`System '${this.constructor.name}' has defined listen events (${r.join(", ")}) for query '${n}' but it does not implement the 'execute' method.`),o.listen[t]){let s=o.listen[t];if("changed"===t){if(i.reactive=!0,!0===s){let e=this.queries[n][t]=[];i.eventDispatcher.addEventListener(Query.prototype.COMPONENT_CHANGED,t=>{-1===e.indexOf(t)&&e.push(t)})}else if(Array.isArray(s)){let e=this.queries[n][t]=[];i.eventDispatcher.addEventListener(Query.prototype.COMPONENT_CHANGED,(t,n)=>{-1!==s.indexOf(n.constructor)&&-1===e.indexOf(t)&&e.push(t)})}}else{let o=this.queries[n][t]=[];i.eventDispatcher.addEventListener(e[t],e=>{-1===o.indexOf(e)&&o.push(e)})}}})}}stop(){this.executeTime=0,this.enabled=!1}play(){this.enabled=!0}clearEvents(){for(let t in this.queries){var e=this.queries[t];if(e.added&&(e.added.length=0),e.removed&&(e.removed.length=0),e.changed)if(Array.isArray(e.changed))e.changed.length=0;else for(let t in e.changed)e.changed[t].length=0}}toJSON(){var e={name:this.constructor.name,enabled:this.enabled,executeTime:this.executeTime,priority:this.priority,queries:{}};if(this.constructor.queries){var t=this.constructor.queries;for(let n in t){let o=this.queries[n],s=t[n],i=e.queries[n]={key:this._queries[n].key};if(i.mandatory=!0===s.mandatory,i.reactive=s.listen&&(!0===s.listen.added||!0===s.listen.removed||!0===s.listen.changed||Array.isArray(s.listen.changed)),i.reactive){i.listen={},["added","removed","changed"].forEach(e=>{o[e]&&(i.listen[e]={entities:o[e].length})})}}}return e}}function Not(e){return{operator:"not",Component:e}}class Component{}Component.isComponent=!0;class TagComponent{reset(){}}function createType(e){var t=["create","reset","clear"].filter(t=>!e[t]);if(t.length>0)throw new Error(`createType expect type definition to implements the following functions: ${t.join(", ")}`);return e.isType=!0,e}TagComponent.isTagComponent=!0;var Types={};Types.Number=createType({baseType:Number,isSimpleType:!0,create:e=>void 0!==e?e:0,reset:(e,t,n)=>{e[t]=void 0!==n?n:0},clear:(e,t)=>{e[t]=0}}),Types.Boolean=createType({baseType:Boolean,isSimpleType:!0,create:e=>void 0!==e&&e,reset:(e,t,n)=>{e[t]=void 0!==n&&n},clear:(e,t)=>{e[t]=!1}}),Types.String=createType({baseType:String,isSimpleType:!0,create:e=>void 0!==e?e:"",reset:(e,t,n)=>{e[t]=void 0!==n?n:""},clear:(e,t)=>{e[t]=""}}),Types.Array=createType({baseType:Array,create:e=>void 0!==e?e.slice():[],reset:(e,t,n)=>{void 0!==n?e[t]=n.slice():e[t].length=0},clear:(e,t)=>{e[t].length=0},copy:(e,t,n)=>{e[n]=t[n].slice()}});var standardTypes={number:Types.Number,boolean:Types.Boolean,string:Types.String};function inferType(e){return Array.isArray(e)?Types.Array:standardTypes[typeof e]?standardTypes[typeof e]:null}function createComponentClass(e,t){for(let t in e){e[t].type||(e[t].type=inferType(e[t].default))}var n=function(){for(let n in e){var t=e[n];let o=t.type;o&&o.isType?this[n]=o.create(t.default):this[n]=t.default}};void 0!==t&&Object.defineProperty(n,"name",{value:t}),n.prototype.schema=e;var o=!0;for(let t in e){var s=e[t];s.type||(s.type=inferType(s.default)),s.type||(console.warn(`Unknown type definition for attribute '${t}'`),o=!1)}if(o){n.prototype.copy=function(t){for(let n in e)if(t[n]){let o=e[n].type;o.isSimpleType?this[n]=t[n]:o.copy?o.copy(this,t,n):console.warn(`Unknown copy function for attribute '${n}' data type`)}},n.prototype.reset=function(){for(let t in e){let n=e[t],o=n.type;o.reset&&o.reset(this,t,n.default)}},n.prototype.clear=function(){for(let t in e){let n=e[t].type;n.clear&&n.clear(this,t)}};for(let t in e){let o=e[t],s=o.type;n.prototype[t]=o.default,s.reset&&s.reset(n.prototype,t,o.default)}}else for(var i in console.warn("This component can't use pooling because some data types are not registered. Please provide a type created with 'createType'"),e){let t=e[i];n.prototype[i]=t.default}return n}function generateId(e){for(var t="",n="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",o=n.length,s=0;s<e;s++)t+=n.charAt(Math.floor(Math.random()*o));return t}function injectScript(e,t){var n=document.createElement("script");n.src=e,n.onload=t,(document.head||document.documentElement).appendChild(n)}function hookConsoleAndErrors(e){["error","warning","log"].forEach(t=>{if("function"==typeof console[t]){var n=console[t].bind(console);console[t]=(...o)=>(e.send({method:"console",type:t,args:JSON.stringify(o)}),n.apply(null,o))}}),window.addEventListener("error",t=>{e.send({method:"error",error:JSON.stringify({message:t.error.message,stack:t.error.stack})})})}function includeRemoteIdHTML(e){let t=document.createElement("div");return t.style.cssText="\n align-items: center;\n background-color: #333;\n color: #aaa;\n display:flex;\n font-family: Arial;\n font-size: 1.1em;\n height: 40px;\n justify-content: center;\n left: 0;\n opacity: 0.9;\n position: absolute;\n right: 0;\n text-align: center;\n top: 0;\n ",t.innerHTML=`Open ECSY devtools to connect to this page using the code: <b style="color: #fff">${e}</b> <button onClick="generateNewCode()">Generate new code</button>`,document.body.appendChild(t),t}function enableRemoteDevtools(remoteId){if(!hasWindow)return void console.warn("Remote devtools not available outside the browser");window.generateNewCode=()=>{window.localStorage.clear(),remoteId=generateId(6),window.localStorage.setItem("ecsyRemoteId",remoteId),window.location.reload(!1)},remoteId=remoteId||window.localStorage.getItem("ecsyRemoteId"),remoteId||(remoteId=generateId(6),window.localStorage.setItem("ecsyRemoteId",remoteId));let infoDiv=includeRemoteIdHTML(remoteId);window.__ECSY_REMOTE_DEVTOOLS_INJECTED=!0,window.__ECSY_REMOTE_DEVTOOLS={};let Version="",worldsBeforeLoading=[],onWorldCreated=e=>{var t=e.detail.world;Version=e.detail.version,worldsBeforeLoading.push(t)};window.addEventListener("ecsy-world-created",onWorldCreated);let onLoaded=()=>{var peer=new Peer(remoteId);peer.on("open",()=>{peer.on("connection",connection=>{window.__ECSY_REMOTE_DEVTOOLS.connection=connection,connection.on("open",(function(){infoDiv.innerHTML="Connected",connection.on("data",(function(data){if("init"===data.type){var script=document.createElement("script");script.setAttribute("type","text/javascript"),script.onload=()=>{script.parentNode.removeChild(script),window.removeEventListener("ecsy-world-created",onWorldCreated),worldsBeforeLoading.forEach(e=>{var t=new CustomEvent("ecsy-world-created",{detail:{world:e,version:Version}});window.dispatchEvent(t)})},script.innerHTML=data.script,(document.head||document.documentElement).appendChild(script),script.onload(),hookConsoleAndErrors(connection)}else if("executeScript"===data.type){let value=eval(data.script);data.returnEval&&connection.send({method:"evalReturn",value:value})}}))}))})})};injectScript("https://cdn.jsdelivr.net/npm/peerjs@0.3.20/dist/peer.min.js",onLoaded)}if(hasWindow){const e=new URLSearchParams(window.location.search);e.has("enable-remote-devtools")&&enableRemoteDevtools()}exports.Component=Component,exports.Not=Not,exports.System=System,exports.SystemStateComponent=SystemStateComponent,exports.TagComponent=TagComponent,exports.Types=Types,exports.Version=Version,exports.World=World,exports.createComponentClass=createComponentClass,exports.createType=createType,exports.enableRemoteDevtools=enableRemoteDevtools,Object.defineProperty(exports,"__esModule",{value:!0})})); |
@@ -0,1 +1,48 @@ | ||
/** | ||
* Return the name of a component | ||
* @param {Component} Component | ||
* @private | ||
*/ | ||
function getName(Component) { | ||
return Component.name; | ||
} | ||
/** | ||
* Return a valid property name for the Component | ||
* @param {Component} Component | ||
* @private | ||
*/ | ||
function componentPropertyName(Component) { | ||
return getName(Component); | ||
} | ||
/** | ||
* Get a key from a list of components | ||
* @param {Array(Component)} Components Array of components to generate the key | ||
* @private | ||
*/ | ||
function queryKey(Components) { | ||
var names = []; | ||
for (var n = 0; n < Components.length; n++) { | ||
var T = Components[n]; | ||
if (typeof T === "object") { | ||
var operator = T.operator === "not" ? "!" : T.operator; | ||
names.push(operator + getName(T.Component)); | ||
} else { | ||
names.push(getName(T)); | ||
} | ||
} | ||
return names.sort().join("-"); | ||
} | ||
// Detector for browser's "window" | ||
const hasWindow = typeof window !== "undefined"; | ||
// performance.now() "polyfill" | ||
const now = | ||
hasWindow && typeof window.performance !== "undefined" | ||
? performance.now.bind(performance) | ||
: Date.now.bind(Date); | ||
class SystemManager { | ||
@@ -10,5 +57,3 @@ constructor(world) { | ||
registerSystem(System, attributes) { | ||
if ( | ||
this._systems.find(s => s.constructor.name === System.name) !== undefined | ||
) { | ||
if (this.getSystem(System) !== undefined) { | ||
console.warn(`System '${System.name}' already registered.`); | ||
@@ -29,2 +74,19 @@ return this; | ||
unregisterSystem(System) { | ||
let system = this.getSystem(System); | ||
if (system === undefined) { | ||
console.warn(`Can unregister system '${System.name}'. It doesn't exist.`); | ||
return this; | ||
} | ||
this._systems.splice(this._systems.indexOf(system), 1); | ||
if (system.execute) { | ||
this._executeSystems.splice(this._executeSystems.indexOf(system), 1); | ||
} | ||
// @todo Add system.unregister() call to free resources | ||
return this; | ||
} | ||
sortSystems() { | ||
@@ -54,5 +116,5 @@ this._executeSystems.sort((a, b) => { | ||
if (system.canExecute()) { | ||
let startTime = performance.now(); | ||
let startTime = now(); | ||
system.execute(delta, time); | ||
system.executeTime = performance.now() - startTime; | ||
system.executeTime = now() - startTime; | ||
this.lastExecutedSystem = system; | ||
@@ -84,3 +146,4 @@ system.clearEvents(); | ||
var systemStats = (stats.systems[system.constructor.name] = { | ||
queries: {} | ||
queries: {}, | ||
executeTime: system.executeTime | ||
}); | ||
@@ -179,40 +242,2 @@ for (var name in system.ctx) { | ||
/** | ||
* Return the name of a component | ||
* @param {Component} Component | ||
* @private | ||
*/ | ||
function getName(Component) { | ||
return Component.name; | ||
} | ||
/** | ||
* Return a valid property name for the Component | ||
* @param {Component} Component | ||
* @private | ||
*/ | ||
function componentPropertyName(Component) { | ||
return getName(Component); | ||
} | ||
/** | ||
* Get a key from a list of components | ||
* @param {Array(Component)} Components Array of components to generate the key | ||
* @private | ||
*/ | ||
function queryKey(Components) { | ||
var names = []; | ||
for (var n = 0; n < Components.length; n++) { | ||
var T = Components[n]; | ||
if (typeof T === "object") { | ||
var operator = T.operator === "not" ? "!" : T.operator; | ||
names.push(operator + getName(T.Component)); | ||
} else { | ||
names.push(getName(T)); | ||
} | ||
} | ||
return names.sort().join("-"); | ||
} | ||
class Query { | ||
@@ -346,2 +371,5 @@ /** | ||
this.alive = false; | ||
//if there are state components on a entity, it can't be removed completely | ||
this.numStateComponents = 0; | ||
} | ||
@@ -382,2 +410,3 @@ | ||
// @todo accelerate this check. Maybe having query._Components as an object | ||
// @todo add Not components | ||
if (query.reactive && query.Components.indexOf(Component) !== -1) { | ||
@@ -399,4 +428,4 @@ query.eventDispatcher.dispatchEvent( | ||
removeComponent(Component, forceRemove) { | ||
this._world.entityRemoveComponent(this, Component, forceRemove); | ||
removeComponent(Component, forceImmediate) { | ||
this._world.entityRemoveComponent(this, Component, forceImmediate); | ||
return this; | ||
@@ -430,4 +459,4 @@ } | ||
removeAllComponents(forceRemove) { | ||
return this._world.entityRemoveAllComponents(this, forceRemove); | ||
removeAllComponents(forceImmediate) { | ||
return this._world.entityRemoveAllComponents(this, forceImmediate); | ||
} | ||
@@ -446,4 +475,4 @@ | ||
remove(forceRemove) { | ||
return this._world.removeEntity(this, forceRemove); | ||
remove(forceImmediate) { | ||
return this._world.removeEntity(this, forceImmediate); | ||
} | ||
@@ -651,4 +680,2 @@ } | ||
this.deferredRemovalEnabled = true; | ||
this.numStateComponents = 0; | ||
} | ||
@@ -690,3 +717,11 @@ | ||
entityAddComponent(entity, Component, values) { | ||
if (~entity._ComponentTypes.indexOf(Component)) return; | ||
if (~entity._ComponentTypes.indexOf(Component)) { | ||
// @todo Just on debug mode | ||
console.warn( | ||
"Component type already exists on entity.", | ||
entity, | ||
Component.name | ||
); | ||
return; | ||
} | ||
@@ -696,3 +731,3 @@ entity._ComponentTypes.push(Component); | ||
if (Component.__proto__ === SystemStateComponent) { | ||
this.numStateComponents++; | ||
entity.numStateComponents++; | ||
} | ||
@@ -754,6 +789,6 @@ | ||
if (Component.__proto__ === SystemStateComponent) { | ||
this.numStateComponents--; | ||
entity.numStateComponents--; | ||
// Check if the entity was a ghost waiting for the last system state component to be removed | ||
if (this.numStateComponents === 0 && !entity.alive) { | ||
if (entity.numStateComponents === 0 && !entity.alive) { | ||
entity.remove(); | ||
@@ -800,3 +835,3 @@ } | ||
if (this.numStateComponents === 0) { | ||
if (entity.numStateComponents === 0) { | ||
// Remove from entity list | ||
@@ -1098,3 +1133,3 @@ this.eventDispatcher.dispatchEvent(ENTITY_REMOVED, entity); | ||
if (typeof CustomEvent !== "undefined") { | ||
if (hasWindow && typeof CustomEvent !== "undefined") { | ||
var event = new CustomEvent("ecsy-world-created", { | ||
@@ -1106,3 +1141,3 @@ detail: { world: this, version: Version } | ||
this.lastTime = performance.now(); | ||
this.lastTime = now(); | ||
} | ||
@@ -1120,2 +1155,7 @@ | ||
unregisterSystem(System) { | ||
this.systemManager.unregisterSystem(System); | ||
return this; | ||
} | ||
getSystem(SystemClass) { | ||
@@ -1131,3 +1171,3 @@ return this.systemManager.getSystem(SystemClass); | ||
if (!delta) { | ||
let time = performance.now(); | ||
time = now(); | ||
delta = time - this.lastTime; | ||
@@ -1227,2 +1267,12 @@ this.lastTime = time; | ||
validEvents.forEach(eventName => { | ||
if (!this.execute) { | ||
console.warn( | ||
`System '${ | ||
this.constructor.name | ||
}' has defined listen events (${validEvents.join( | ||
", " | ||
)}) for query '${queryName}' but it does not implement the 'execute' method.` | ||
); | ||
} | ||
// Is the event enabled on this system's query? | ||
@@ -1676,2 +1726,7 @@ if (queryConfig.listen[eventName]) { | ||
function enableRemoteDevtools(remoteId) { | ||
if (!hasWindow) { | ||
console.warn("Remote devtools not available outside the browser"); | ||
return; | ||
} | ||
window.generateNewCode = () => { | ||
@@ -1762,9 +1817,11 @@ window.localStorage.clear(); | ||
const urlParams = new URLSearchParams(window.location.search); | ||
if (hasWindow) { | ||
const urlParams = new URLSearchParams(window.location.search); | ||
// @todo Provide a way to disable it if needed | ||
if (urlParams.has("enable-remote-devtools")) { | ||
enableRemoteDevtools(); | ||
// @todo Provide a way to disable it if needed | ||
if (urlParams.has("enable-remote-devtools")) { | ||
enableRemoteDevtools(); | ||
} | ||
} | ||
export { Component, Not, System, SystemStateComponent, TagComponent, Types, Version, World, createComponentClass, createType, enableRemoteDevtools }; |
@@ -26,3 +26,3 @@ # ECSY Architecture | ||
- `Walker` and `Flyer` for entities that will walk and fly (resp.). | ||
- `Enemy` for enemy entitites. | ||
- `Enemy` for enemy entities. | ||
- `Model3D` for all the entities that will have a 3d Model. | ||
@@ -92,3 +92,3 @@ | ||
Components could be made of multiple attributes, but sometimes they just contain a single attribute. | ||
It these cases using the attribute's name to match the component's name may seem handy: | ||
In these cases using the attribute's name to match the component's name may seem handy: | ||
```javascript | ||
@@ -95,0 +95,0 @@ class Acceleration { |
@@ -83,3 +83,3 @@ # Getting started | ||
// Iterate through all the entities on the query | ||
this.queries.position.result.forEach(entity => { | ||
this.queries.position.results.forEach(entity => { | ||
// Access the component `Position` on the current entity | ||
@@ -111,3 +111,3 @@ let pos = entity.getComponent(Position); | ||
// Iterate through all the entities on the query | ||
this.queries.moving.result.forEach(entity => { | ||
this.queries.moving.results.forEach(entity => { | ||
@@ -146,4 +146,4 @@ // Get the `Acceleration` component as Read-only | ||
execute() { | ||
this.queries.boxes.result.forEach(entity => { /* do things */}); | ||
this.queries.balls.result.forEach(entity => { /* do things */}); | ||
this.queries.boxes.results.forEach(entity => { /* do things */}); | ||
this.queries.balls.results.forEach(entity => { /* do things */}); | ||
} | ||
@@ -150,0 +150,0 @@ } |
{ | ||
"name": "ecsy", | ||
"version": "0.2.3", | ||
"version": "0.2.4", | ||
"description": "Entity Component System in JS", | ||
@@ -5,0 +5,0 @@ "main": "build/ecsy.js", |
@@ -15,6 +15,17 @@ import { Component, ComponentConstructor } from "./Component"; | ||
* @param Component Type of component to get | ||
* @param includeRemoved Whether a component that is staled to be removed should be also considered | ||
*/ | ||
getComponent<T extends Component>(Component:ComponentConstructor<T>): T; | ||
getComponent<T extends Component>( | ||
Component: ComponentConstructor<T>, | ||
includeRemoved?: boolean | ||
): T; | ||
/** | ||
* Get a component that is slated to be removed from this entity. | ||
*/ | ||
getRemovedComponent<T extends Component>( | ||
Component: ComponentConstructor<T> | ||
): T; | ||
/** | ||
* Get an object containing all the components on this entity, where the object keys are the component types. | ||
@@ -25,12 +36,10 @@ */ | ||
/** | ||
* Get a list of component types that have been added to this entity. | ||
* Get an object containing all the components that are slated to be removed from this entity, where the object keys are the component types. | ||
*/ | ||
getComponentTypes<T extends Component>(): Array<T>; | ||
getComponentsToRemove(): object; | ||
/** | ||
* Get a component that is slated to be removed from this entity. | ||
* Get a list of component types that have been added to this entity. | ||
*/ | ||
getRemovedComponent<T extends Component>( | ||
Component: ComponentConstructor<T> | ||
): T; | ||
getComponentTypes<T extends Component>(): Array<T>; | ||
@@ -58,5 +67,7 @@ /** | ||
* @param Component Type of component to remove from this entity | ||
* @param forceImmediate Whether a component should be removed immediately | ||
*/ | ||
removeComponent<T extends Component>( | ||
Component: ComponentConstructor<T> | ||
Component: ComponentConstructor<T>, | ||
forceImmediate?: boolean | ||
): this; | ||
@@ -67,5 +78,7 @@ | ||
* @param Component Type of component | ||
* @param includeRemoved Whether a component that is staled to be removed should be also considered | ||
*/ | ||
hasComponent<T extends Component>( | ||
Component: ComponentConstructor<T> | ||
Component: ComponentConstructor<T>, | ||
includeRemoved?: boolean | ||
): boolean; | ||
@@ -99,9 +112,15 @@ | ||
* Remove all components on this entity. | ||
* @param forceImmediate Whether all components should be removed immediately | ||
*/ | ||
removeAllComponents():void | ||
removeAllComponents( | ||
forceImmediate?: boolean | ||
):void | ||
/** | ||
* Remove this entity from the world. | ||
* @param forceImmediate Whether this entity should be removed immediately | ||
*/ | ||
remove():void; | ||
remove( | ||
forceImmediate?: boolean | ||
):void; | ||
} |
@@ -31,2 +31,5 @@ import Query from "./Query.js"; | ||
this.alive = false; | ||
//if there are state components on a entity, it can't be removed completely | ||
this.numStateComponents = 0; | ||
} | ||
@@ -67,2 +70,3 @@ | ||
// @todo accelerate this check. Maybe having query._Components as an object | ||
// @todo add Not components | ||
if (query.reactive && query.Components.indexOf(Component) !== -1) { | ||
@@ -84,4 +88,4 @@ query.eventDispatcher.dispatchEvent( | ||
removeComponent(Component, forceRemove) { | ||
this._world.entityRemoveComponent(this, Component, forceRemove); | ||
removeComponent(Component, forceImmediate) { | ||
this._world.entityRemoveComponent(this, Component, forceImmediate); | ||
return this; | ||
@@ -115,4 +119,4 @@ } | ||
removeAllComponents(forceRemove) { | ||
return this._world.entityRemoveAllComponents(this, forceRemove); | ||
removeAllComponents(forceImmediate) { | ||
return this._world.entityRemoveAllComponents(this, forceImmediate); | ||
} | ||
@@ -131,5 +135,5 @@ | ||
remove(forceRemove) { | ||
return this._world.removeEntity(this, forceRemove); | ||
remove(forceImmediate) { | ||
return this._world.removeEntity(this, forceImmediate); | ||
} | ||
} |
@@ -30,4 +30,2 @@ import Entity from "./Entity.js"; | ||
this.deferredRemovalEnabled = true; | ||
this.numStateComponents = 0; | ||
} | ||
@@ -69,3 +67,11 @@ | ||
entityAddComponent(entity, Component, values) { | ||
if (~entity._ComponentTypes.indexOf(Component)) return; | ||
if (~entity._ComponentTypes.indexOf(Component)) { | ||
// @todo Just on debug mode | ||
console.warn( | ||
"Component type already exists on entity.", | ||
entity, | ||
Component.name | ||
); | ||
return; | ||
} | ||
@@ -75,3 +81,3 @@ entity._ComponentTypes.push(Component); | ||
if (Component.__proto__ === SystemStateComponent) { | ||
this.numStateComponents++; | ||
entity.numStateComponents++; | ||
} | ||
@@ -133,6 +139,6 @@ | ||
if (Component.__proto__ === SystemStateComponent) { | ||
this.numStateComponents--; | ||
entity.numStateComponents--; | ||
// Check if the entity was a ghost waiting for the last system state component to be removed | ||
if (this.numStateComponents === 0 && !entity.alive) { | ||
if (entity.numStateComponents === 0 && !entity.alive) { | ||
entity.remove(); | ||
@@ -179,3 +185,3 @@ } | ||
if (this.numStateComponents === 0) { | ||
if (entity.numStateComponents === 0) { | ||
// Remove from entity list | ||
@@ -182,0 +188,0 @@ this.eventDispatcher.dispatchEvent(ENTITY_REMOVED, entity); |
/* global Peer */ | ||
import { injectScript, generateId } from "./utils.js"; | ||
import { hasWindow } from "../Utils.js"; | ||
@@ -57,2 +58,7 @@ function hookConsoleAndErrors(connection) { | ||
export function enableRemoteDevtools(remoteId) { | ||
if (!hasWindow) { | ||
console.warn("Remote devtools not available outside the browser"); | ||
return; | ||
} | ||
window.generateNewCode = () => { | ||
@@ -143,7 +149,9 @@ window.localStorage.clear(); | ||
const urlParams = new URLSearchParams(window.location.search); | ||
if (hasWindow) { | ||
const urlParams = new URLSearchParams(window.location.search); | ||
// @todo Provide a way to disable it if needed | ||
if (urlParams.has("enable-remote-devtools")) { | ||
enableRemoteDevtools(); | ||
// @todo Provide a way to disable it if needed | ||
if (urlParams.has("enable-remote-devtools")) { | ||
enableRemoteDevtools(); | ||
} | ||
} |
@@ -65,2 +65,12 @@ import Query from "./Query.js"; | ||
validEvents.forEach(eventName => { | ||
if (!this.execute) { | ||
console.warn( | ||
`System '${ | ||
this.constructor.name | ||
}' has defined listen events (${validEvents.join( | ||
", " | ||
)}) for query '${queryName}' but it does not implement the 'execute' method.` | ||
); | ||
} | ||
// Is the event enabled on this system's query? | ||
@@ -67,0 +77,0 @@ if (queryConfig.listen[eventName]) { |
@@ -0,1 +1,3 @@ | ||
import { now } from "./Utils.js"; | ||
export class SystemManager { | ||
@@ -10,5 +12,3 @@ constructor(world) { | ||
registerSystem(System, attributes) { | ||
if ( | ||
this._systems.find(s => s.constructor.name === System.name) !== undefined | ||
) { | ||
if (this.getSystem(System) !== undefined) { | ||
console.warn(`System '${System.name}' already registered.`); | ||
@@ -29,2 +29,19 @@ return this; | ||
unregisterSystem(System) { | ||
let system = this.getSystem(System); | ||
if (system === undefined) { | ||
console.warn(`Can unregister system '${System.name}'. It doesn't exist.`); | ||
return this; | ||
} | ||
this._systems.splice(this._systems.indexOf(system), 1); | ||
if (system.execute) { | ||
this._executeSystems.splice(this._executeSystems.indexOf(system), 1); | ||
} | ||
// @todo Add system.unregister() call to free resources | ||
return this; | ||
} | ||
sortSystems() { | ||
@@ -54,5 +71,5 @@ this._executeSystems.sort((a, b) => { | ||
if (system.canExecute()) { | ||
let startTime = performance.now(); | ||
let startTime = now(); | ||
system.execute(delta, time); | ||
system.executeTime = performance.now() - startTime; | ||
system.executeTime = now() - startTime; | ||
this.lastExecutedSystem = system; | ||
@@ -84,3 +101,4 @@ system.clearEvents(); | ||
var systemStats = (stats.systems[system.constructor.name] = { | ||
queries: {} | ||
queries: {}, | ||
executeTime: system.executeTime | ||
}); | ||
@@ -87,0 +105,0 @@ for (var name in system.ctx) { |
@@ -38,1 +38,10 @@ /** | ||
} | ||
// Detector for browser's "window" | ||
export const hasWindow = typeof window !== "undefined"; | ||
// performance.now() "polyfill" | ||
export const now = | ||
hasWindow && typeof window.performance !== "undefined" | ||
? performance.now.bind(performance) | ||
: Date.now.bind(Date); |
@@ -62,3 +62,3 @@ import { Component, ComponentConstructor } from "./Component"; | ||
*/ | ||
createEntity():Entity | ||
createEntity(name: string?):Entity | ||
} |
@@ -5,2 +5,3 @@ import { SystemManager } from "./SystemManager.js"; | ||
import { Version } from "./Version.js"; | ||
import { hasWindow, now } from "./Utils.js"; | ||
@@ -17,3 +18,3 @@ export class World { | ||
if (typeof CustomEvent !== "undefined") { | ||
if (hasWindow && typeof CustomEvent !== "undefined") { | ||
var event = new CustomEvent("ecsy-world-created", { | ||
@@ -25,3 +26,3 @@ detail: { world: this, version: Version } | ||
this.lastTime = performance.now(); | ||
this.lastTime = now(); | ||
} | ||
@@ -39,2 +40,7 @@ | ||
unregisterSystem(System) { | ||
this.systemManager.unregisterSystem(System); | ||
return this; | ||
} | ||
getSystem(SystemClass) { | ||
@@ -50,3 +56,3 @@ return this.systemManager.getSystem(SystemClass); | ||
if (!delta) { | ||
let time = performance.now(); | ||
time = now(); | ||
delta = time - this.lastTime; | ||
@@ -53,0 +59,0 @@ this.lastTime = time; |
@@ -1,2 +0,1 @@ | ||
import "../helpers/common.js"; | ||
import test from "ava"; | ||
@@ -3,0 +2,0 @@ import { World } from "../../src/index.js"; |
@@ -1,2 +0,1 @@ | ||
import "../helpers/common.js"; | ||
import test from "ava"; | ||
@@ -3,0 +2,0 @@ import { World } from "../../src/index.js"; |
@@ -1,2 +0,1 @@ | ||
import "../helpers/common.js"; | ||
import test from "ava"; | ||
@@ -3,0 +2,0 @@ import { World } from "../../src"; |
@@ -1,2 +0,1 @@ | ||
import "../helpers/common.js"; | ||
import test from "ava"; | ||
@@ -885,1 +884,23 @@ import { World, System, Not } from "../../src/index.js"; | ||
}); | ||
test("Unregister systems", t => { | ||
class SystemA extends System {} | ||
class SystemB extends System { | ||
execute() {} | ||
} | ||
const world = new World(); | ||
world.registerSystem(SystemA).registerSystem(SystemB); | ||
t.is(world.systemManager._systems.length, 2); | ||
t.is(world.systemManager._executeSystems.length, 1); | ||
world.unregisterSystem(SystemA); | ||
t.is(world.systemManager._systems.length, 1); | ||
t.is(world.systemManager._executeSystems.length, 1); | ||
world.unregisterSystem(SystemB); | ||
t.is(world.systemManager._systems.length, 0); | ||
t.is(world.systemManager._executeSystems.length, 0); | ||
}); |
@@ -1,2 +0,1 @@ | ||
import "../helpers/common.js"; | ||
import test from "ava"; | ||
@@ -6,3 +5,3 @@ import { World, System } from "../../src/index.js"; | ||
test("registerSystems", t => { | ||
var world = new World(); | ||
let world = new World(); | ||
@@ -21,1 +20,26 @@ class SystemA extends System {} | ||
}); | ||
test("registerSystems with different systems matching names", t => { | ||
let world = new World(); | ||
function importSystemA() { | ||
class SystemWithCommonName extends System {} | ||
return SystemWithCommonName; | ||
} | ||
function importSystemB() { | ||
class SystemWithCommonName extends System {} | ||
return SystemWithCommonName; | ||
} | ||
let SystemA = importSystemA(); | ||
let SystemB = importSystemB(); | ||
world.registerSystem(SystemA); | ||
t.is(world.systemManager._systems.length, 1); | ||
world.registerSystem(SystemB); | ||
t.is(world.systemManager._systems.length, 2); | ||
// Can't register twice the same system | ||
world.registerSystem(SystemA); | ||
t.is(world.systemManager._systems.length, 2); | ||
}); |
// @todo Define this globally for all the test? | ||
import "../helpers/common.js"; | ||
import test from "ava"; | ||
@@ -4,0 +3,0 @@ import { World, Not, System, SystemStateComponent } from "../../src/index.js"; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
942644
7429