UNPKG

@byloth/micro-ecs

Version:

A simple & lightweight ECS (Entity Component System) library for JavaScript and TypeScript. 🕹

27 lines (14 loc) • 16.1 kB
(function(r,i){typeof exports=="object"&&typeof module<"u"?i(exports,require("@byloth/core")):typeof define=="function"&&define.amd?define(["exports","@byloth/core"],i):(r=typeof globalThis<"u"?globalThis:r||self,i(r.MicroECS={},r.Core))})(this,(function(r,i){"use strict";class _{static __μECS_nextId__=0;id;constructor(){this.id=_.__μECS_nextId__+=1}}class f{get _entity(){return this._component.entity}_component;_dependencies;get dependencies(){return this._dependencies}_onDispose;constructor(e){this._component=e,this._dependencies=new Set}useComponent(e){const t=this._entity._addDependency(this._component,e);return this._dependencies.add(t),t}releaseComponent(e){const t=typeof e=="function"?e:e.constructor,n=this._entity._removeDependency(this._component,t);this._dependencies.delete(n)}dispose(){this._onDispose&&(this._onDispose(this),this._onDispose=void 0),this._dependencies.clear()}}class u extends i.RuntimeException{constructor(e,t,n="AttachmentException"){super(e,t,n)}[Symbol.toStringTag]="AttachmentException"}class a extends i.ReferenceException{constructor(e,t,n="DependencyException"){super(e,t,n)}[Symbol.toStringTag]="DependencyException"}class E extends _{_isEnabled;get isEnabled(){return this._isEnabled}_components;get components(){return this._components}_world;get world(){return this._world}_contexts;_dependencies;_onContextDispose=e=>{const t=e._component;for(const n of e.dependencies){const s=this._dependencies.get(n);s.delete(t),s.size===0&&this._dependencies.delete(n)}this._contexts.delete(t)};constructor(e=!0){super(),this._isEnabled=e,this._components=new Map,this._world=null,this._contexts=new Map,this._dependencies=new Map}_addDependency(e,t){const n=this._components.get(t);if(!n)throw new a("The dependency doesn't exist in the entity.");const s=this._dependencies.get(n);if(s){if(s.has(e))throw new a("The dependant already depends on this component.");s.add(e)}else this._dependencies.set(n,new Set([e]));return n}_removeDependency(e,t){const n=this._components.get(t),s=this._dependencies.get(n);if(!s?.delete(e))throw new a("The dependant doesn't depend on this component.");return s.size===0&&this._dependencies.delete(n),n}_enableComponent(e){this._isEnabled&&this._world?._enableEntityComponent(this,e)}_disableComponent(e){this._isEnabled&&this._world?._disableEntityComponent(this,e)}addComponent(e){const t=e.constructor;if(this._components.has(t))throw new i.ReferenceException("The component already exists in the entity.");try{e.onAttach(this)}catch(n){throw new u("It wasn't possible to attach this component to the entity.",n)}return this._components.set(t,e),e.isEnabled&&this._enableComponent(e),e}getComponent(e){const t=this._components.get(e);if(!t)throw new i.ReferenceException("The component doesn't exist in the entity.");return t}hasComponent(e){return this._components.has(e)}removeComponent(e){const t=typeof e=="function"?e:e.constructor,n=this._components.get(t);if(!n)throw new i.ReferenceException("The component doesn't exist in the entity.");if(this._dependencies.has(n))throw new a("The component has dependants and cannot be removed. Remove them first.");const s=this._contexts.get(n);if(s){try{s.dispose()}catch(o){console.warn(`An error occurred while disposing the context of the component. Suppressed`,o)}this._contexts.delete(n)}n.isEnabled&&this._disableComponent(n),this._components.delete(n.constructor);try{n.onDetach()}catch(o){console.warn(`An error occurred while detaching this component from the entity. Suppressed`,o)}return n}getContext(e){let t=this._contexts.get(e);return t||(t=new f(e),t._onDispose=this._onContextDispose,this._contexts.set(e,t),t)}enable(){if(this._isEnabled)throw new i.RuntimeException("The entity is already enabled.");this._isEnabled=!0,this._world?._enableEntity(this)}disable(){if(!this._isEnabled)throw new i.RuntimeException("The entity is already disabled.");this._isEnabled=!1,this._world?._disableEntity(this)}onAttach(e){if(this._world)throw new i.ReferenceException("The entity is already attached to a world.");this._world=e}onDetach(){if(!this._world)throw new i.ReferenceException("The entity isn't attached to any world.");this._world=null}dispose(){if(this._world)throw new i.RuntimeException("The entity must be detached from the world before being disposed.");try{for(const e of this._components.values())e.onDetach(),e.dispose()}catch(e){console.warn(`An error occurred while disposing components of the entity. Suppressed`,e)}this._components.clear();try{for(const e of this._contexts.values())e.dispose()}catch(e){console.warn(`An error occurred while disposing contexts of the entity. Suppressed`,e)}this._contexts.clear(),this._dependencies.clear()}}class g extends _{_isEnabled;get isEnabled(){return this._isEnabled}_entity;get entity(){return this._entity}constructor(e=!0){super(),this._isEnabled=e,this._entity=null}enable(){if(this._isEnabled)throw new i.RuntimeException("The component is already enabled.");this._isEnabled=!0,this._entity?._enableComponent(this)}disable(){if(!this._isEnabled)throw new i.RuntimeException("The component is already disabled.");this._isEnabled=!1,this._entity?._disableComponent(this)}onAttach(e){if(this._entity)throw new i.ReferenceException("The component is already attached to an entity.");this._entity=e}onDetach(){if(!this._entity)throw new i.ReferenceException("The component isn't attached to any entity.");this._entity=null}dispose(){if(this._entity)throw new i.RuntimeException("The component must be detached from the entity before disposing it.")}}class x extends _{static Sort(e,t){return e.priority-t.priority}priority;_isEnabled;get isEnabled(){return this._isEnabled}_world;get world(){return this._world}constructor(e=0,t=!0){super(),this.priority=e,this._isEnabled=t,this._world=null}enable(){if(this._isEnabled)throw new i.RuntimeException("The system is already enabled.");this._isEnabled=!0,this._world?._enableSystem(this)}disable(){if(!this._isEnabled)throw new i.RuntimeException("The system is already disabled.");this._isEnabled=!1,this._world?._disableSystem(this)}onAttach(e){if(this._world)throw new i.ReferenceException("The system is already attached to a world.");this._world=e}onDetach(){if(!this._world)throw new i.ReferenceException("The system isn't attached to any world.");this._world=null}update(e){}dispose(){if(this._world)throw new i.RuntimeException("The system must be detached from the world before disposing it.")}}class v extends _{_world;get world(){return this._world}constructor(){super(),this._world=null}onAttach(e){if(this._world)throw new i.ReferenceException("The resource is already attached to a world.");this._world=e}onDetach(){if(!this._world)throw new i.ReferenceException("The resource isn't attached to any world.");this._world=null}dispose(){if(this._world)throw new i.RuntimeException("The resource must be detached from the world before disposing it.")}}class m{get _world(){return this._system.world}_system;_publisher;_dependencies;get dependencies(){return this._dependencies}_onDispose;constructor(e,t){this._system=e,this._publisher=t,this._dependencies=new Set}emit(e,...t){return this._publisher.publish(e,...t)}on(e,t){return this._publisher.subscribe(e,t)}once(e,t){const n=(...s)=>(this._publisher.unsubscribe(e,n),t(...s));return this._publisher.subscribe(e,n)}async wait(e,t){let n;const s=o=>{n=(...d)=>{o(d)},this._publisher.subscribe(e,n)};try{return t?await new i.TimedPromise(s,t):await new Promise(s)}finally{this._publisher.unsubscribe(e,n)}}off(e,t){this._publisher.unsubscribe(e,t)}useResource(e){const t=this._world._addDependency(this._system,e);return this._dependencies.add(t),t}releaseResource(e){const t=typeof e=="function"?e:e.constructor,n=this._world._removeDependency(this._system,t);this._dependencies.delete(n)}dispose(){this._onDispose&&(this._onDispose(this),this._onDispose=void 0),this._dependencies.clear(),this._publisher.clear()}}class b{_typeKeys;_keyTypes;_views;_entities;constructor(e){this._typeKeys=new Map,this._keyTypes=new Map,this._views=new Map,this._entities=e}_onEntityComponentEnable(e,t){const n=t.constructor,s=this._typeKeys.get(n);if(s)for(const o of s){const d=this._views.get(o);if(d.has(e))continue;const h=this._keyTypes.get(o);if(!h)continue;const p=[];let c=!0,w=0;do{const C=h[w],y=e.components.get(C);if(!y||!y.isEnabled){c=!1;break}p.push(y),w+=1}while(w<h.length);c&&d.set(e,p)}}_onEntityComponentDisable(e,t){const n=t.constructor,s=this._typeKeys.get(n);if(s)for(const o of s){const d=this._views.get(o);d&&d.delete(e)}}_addComponentKeys(e,t){for(const n of e){const s=this._typeKeys.get(n);s?s.add(t):this._typeKeys.set(n,new Set([t]))}}_addKeyComponents(e,t){if(this._keyTypes.has(e))throw new i.KeyException(`The key "${e}" is already registered.`);this._keyTypes.set(e,t)}pickOne(e){const t=this._views.get(e.name);if(t){const{value:n}=t.values().next();return n}for(const n of this._entities.values()){if(!n.isEnabled)continue;const s=n.components.get(e);if(s?.isEnabled)return s}}findFirst(...e){if(!e.length)throw new i.ValueException("At least one type must be provided.");const t=e.map(o=>o.name).sort().join(","),n=this._views.get(t);if(n){const{value:o}=n.values().next();return o}const s=[];for(const o of this._entities.values()){if(!o.isEnabled)continue;let d=!0,h=0;do{const p=e[h],c=o.components.get(p);if(!c||!c.isEnabled){d=!1;break}s.push(c),h+=1}while(h<e.length);if(d)return s;s.length=0}}findAll(...e){if(!e.length)throw new i.ValueException("At least one type must be provided.");const t=e.map(s=>s.name).sort().join(","),n=this._views.get(t);return n?new i.SmartIterator(n.values()):new i.SmartIterator(this._entities.values()).filter(s=>s.isEnabled).map(s=>{const o=[];let d=!0,h=0;do{const p=e[h],c=s.components.get(p);if(!c||!c.isEnabled){d=!1;break}o.push(c),h+=1}while(h<e.length);if(d)return o}).filter(s=>s!==void 0)}getView(...e){if(!e.length)throw new i.ValueException("At least one type must be provided.");const t=e.map(s=>s.name).sort().join(",");let n=this._views.get(t);if(n)return n;n=new i.MapView;for(const s of this._entities.values()){if(!s.isEnabled)continue;const o=[];let d=!0,h=0;do{const p=e[h],c=s.components.get(p);if(!c||!c.isEnabled){d=!1;break}o.push(c),h+=1}while(h<e.length);d&&n.set(s,o)}return this._views.set(t,n),this._addComponentKeys(e,t),this._addKeyComponents(t,e),n}dispose(){for(const e of this._views.values())e.clear();this._views.clear(),this._keyTypes.clear(),this._typeKeys.clear()}}class S{_entities;get entities(){return this._entities}_resources;get resources(){return this._resources}_systems;_enabledSystems;get systems(){return this._systems}_contexts;_dependencies;_queryManager;_publisher;_onContextDispose=e=>{const t=e._system;for(const n of e.dependencies){const s=this._dependencies.get(n);s.delete(t),s.size===0&&this._dependencies.delete(n)}this._contexts.delete(t)};constructor(){this._entities=new Map,this._resources=new Map,this._systems=new Map,this._enabledSystems=[],this._contexts=new Map,this._dependencies=new Map,this._queryManager=new b(this._entities),this._publisher=new i.Publisher}_enableEntity(e){for(const t of e.components.values())t.isEnabled&&this._enableEntityComponent(e,t)}_disableEntity(e){for(const t of e.components.values())t.isEnabled&&this._disableEntityComponent(e,t)}_enableEntityComponent(e,t){this._queryManager._onEntityComponentEnable(e,t)}_disableEntityComponent(e,t){this._queryManager._onEntityComponentDisable(e,t)}_enableSystem(e){let t=0,n=this._enabledSystems.length;for(;t<n;){const s=Math.floor((t+n)/2),o=this._enabledSystems[s];e.priority<o.priority?n=s:t=s+1}this._enabledSystems.splice(t,0,e)}_disableSystem(e){const t=this._enabledSystems.indexOf(e);t!==-1&&this._enabledSystems.splice(t,1)}_addDependency(e,t){const n=this._resources.get(t);if(!n)throw new a("The dependency doesn't exist in the world.");const s=this._dependencies.get(n);if(s){if(s.has(e))throw new a("The dependant already depends on this resource.");s.add(e)}else this._dependencies.set(n,new Set([e]));return n}_removeDependency(e,t){const n=this._resources.get(t),s=this._dependencies.get(n);if(!s?.delete(e))throw new a("The dependant doesn't depend on this resource.");return s.size===0&&this._dependencies.delete(n),n}addEntity(e){if(this._entities.has(e.id))throw new i.ReferenceException("The entity already exists in the world.");try{e.onAttach(this)}catch(t){throw new u("It wasn't possible to attach this entity to the world.",t)}return this._entities.set(e.id,e),e.isEnabled&&this._enableEntity(e),e}removeEntity(e){const t=typeof e=="number"?e:e.id,n=this._entities.get(t);if(!n)throw new i.ReferenceException("The entity doesn't exist in the world.");n.isEnabled&&this._disableEntity(n),this._entities.delete(n.id);try{n.onDetach()}catch(s){console.warn(`An error occurred while detaching this entity from the world. Suppressed`,s)}return n}getFirstComponent(e){return this._queryManager.pickOne(e)}getFirstComponents(...e){return this._queryManager.findFirst(...e)}findAllComponents(...e){return this._queryManager.findAll(...e)}getComponentView(...e){return this._queryManager.getView(...e)}addResource(e){const t=e.constructor;if(this._resources.has(t))throw new i.ReferenceException("The resource already exists in the world.");try{e.onAttach(this)}catch(n){throw new u("It wasn't possible to attach this resource to the world.",n)}return this._resources.set(t,e),e}removeResource(e){const t=typeof e=="function"?e:e.constructor,n=this._resources.get(t);if(!n)throw new i.ReferenceException("The resource doesn't exist in the world.");if(this._dependencies.has(n))throw new a("The resource has dependants and cannot be removed. Remove them first.");this._resources.delete(n.constructor);try{n.onDetach()}catch(s){console.warn(`An error occurred while detaching this resource from the world. Suppressed`,s)}return n}addSystem(e){const t=e.constructor;if(this._systems.has(t))throw new i.ReferenceException("The system already exists in the world.");try{e.onAttach(this)}catch(n){throw new u("It wasn't possible to attach this system to the world.",n)}return this._systems.set(t,e),e.isEnabled&&this._enableSystem(e),e}removeSystem(e){const t=typeof e=="function"?e:e.constructor,n=this._systems.get(t);if(!n)throw new i.ReferenceException("The system doesn't exist in the world.");const s=this._contexts.get(n);if(s){try{s.dispose()}catch(o){console.warn(`An error occurred while disposing the context of the system. Suppressed`,o)}this._contexts.delete(n)}n.isEnabled&&this._disableSystem(n),this._systems.delete(n.constructor);try{n.onDetach()}catch(o){console.warn(`An error occurred while detaching this system from the world. Suppressed`,o)}return n}getContext(e){let t=this._contexts.get(e);return t||(t=new m(e,this._publisher.createScope()),t._onDispose=this._onContextDispose,this._contexts.set(e,t),t)}emit(e,...t){return this._publisher.publish(e,...t)}update(e){for(const t of this._enabledSystems)t.update(e)}dispose(){this._queryManager.dispose();try{for(const e of this._systems.values())e.onDetach(),e.dispose()}catch(e){console.warn(`An error occurred while disposing systems of the world. Suppressed`,e)}this._systems.clear(),this._enabledSystems.length=0;try{for(const e of this._resources.values())e.onDetach(),e.dispose()}catch(e){console.warn(`An error occurred while disposing resources of the world. Suppressed`,e)}this._resources.clear();try{for(const e of this._entities.values())e.onDetach(),e.dispose()}catch(e){console.warn(`An error occurred while disposing entities of the world. Suppressed`,e)}this._entities.clear();try{for(const e of this._contexts.values())e.dispose()}catch(e){console.warn(`An error occurred while disposing contexts of the world. Suppressed`,e)}this._contexts.clear(),this._publisher.clear()}}const T="1.0.24";r.AttachmentException=u,r.Component=g,r.DependencyException=a,r.Entity=E,r.EntityContext=f,r.QueryManager=b,r.Resource=v,r.System=x,r.VERSION=T,r.World=S,r.WorldContext=m,Object.defineProperty(r,Symbol.toStringTag,{value:"Module"})})); //# sourceMappingURL=micro-ecs.umd.cjs.map