UNPKG

@byloth/micro-ecs

Version:

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

27 lines (14 loc) • 15.9 kB
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const i=require("@byloth/core");class p{static __μECS_nextId__=0;id;constructor(){this.id=p.__μECS_nextId__+=1}}class y{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 _ 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 p{_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 _("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 y(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 x extends p{_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 g extends p{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 p{_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 f{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=(...r)=>{o(r)},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 m{_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 r=this._views.get(o);if(r.has(e))continue;const c=this._keyTypes.get(o);if(!c)continue;const l=[];let d=!0,u=0;do{const b=c[u],w=e.components.get(b);if(!w||!w.isEnabled){d=!1;break}l.push(w),u+=1}while(u<c.length);d&&r.set(e,l)}}_onEntityComponentDisable(e,t){const n=t.constructor,s=this._typeKeys.get(n);if(s)for(const o of s){const r=this._views.get(o);r&&r.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 r=!0,c=0;do{const l=e[c],d=o.components.get(l);if(!d||!d.isEnabled){r=!1;break}s.push(d),c+=1}while(c<e.length);if(r)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 r=!0,c=0;do{const l=e[c],d=s.components.get(l);if(!d||!d.isEnabled){r=!1;break}o.push(d),c+=1}while(c<e.length);if(r)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 r=!0,c=0;do{const l=e[c],d=s.components.get(l);if(!d||!d.isEnabled){r=!1;break}o.push(d),c+=1}while(c<e.length);r&&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 m(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 _("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 _("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 _("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 f(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";exports.AttachmentException=_;exports.Component=x;exports.DependencyException=a;exports.Entity=E;exports.EntityContext=y;exports.QueryManager=m;exports.Resource=v;exports.System=g;exports.VERSION=T;exports.World=S;exports.WorldContext=f; //# sourceMappingURL=micro-ecs.cjs.map