UNPKG

elics

Version:

lightweight, flexible ECS for web games

2 lines (1 loc) 19.5 kB
var t;!function(t){t.Int8="Int8",t.Int16="Int16",t.Entity="Entity",t.Float32="Float32",t.Float64="Float64",t.Boolean="Boolean",t.String="String",t.Object="Object",t.Vec2="Vec2",t.Vec3="Vec3",t.Vec4="Vec4",t.Enum="Enum"}(t||(t={}));const e={Int8:{arrayConstructor:Int8Array,length:1},Int16:{arrayConstructor:Int16Array,length:1},Entity:{arrayConstructor:Int32Array,length:1},Float32:{arrayConstructor:Float32Array,length:1},Float64:{arrayConstructor:Float64Array,length:1},Boolean:{arrayConstructor:Uint8Array,length:1},String:{arrayConstructor:Array,length:1},Object:{arrayConstructor:Array,length:1},Vec2:{arrayConstructor:Float32Array,length:2},Vec3:{arrayConstructor:Float32Array,length:3},Vec4:{arrayConstructor:Float32Array,length:4},Enum:{arrayConstructor:Array,length:1}};let n=!0;var i;function s(t,e,i){if(n&&!t)throw new Error(`${e}: ${i}`)}function r(t,e,s){if(!n)return;if(!Object.values(e).includes(t))throw new Error(`${i.InvalidEnumValue}: ${t} is not a valid value for enum ${s}`)}function o(t,e,s,r){if(n){if(void 0!==e&&t<e)throw new Error(`${i.InvalidRangeValue}: ${t} is below minimum ${e} for field ${r}`);if(void 0!==s&&t>s)throw new Error(`${i.InvalidRangeValue}: ${t} is above maximum ${s} for field ${r}`)}}!function(t){t.TypeNotSupported="Type not supported",t.InvalidDefaultValue="Invalid default value",t.InvalidEnumValue="Invalid enum value",t.InvalidRangeValue="Value out of range"}(i||(i={}));class a{constructor(t=0){"number"==typeof t?(this.words=new Uint32Array(1),this.words[0]=t>>>0):(this.words=new Uint32Array(t.length),this.words.set(t))}get bits(){return this.words[0]>>>0}ensure(t){if(t<this.words.length)return;const e=Math.max(this.words.length<<1,t+1),n=new Uint32Array(e);n.set(this.words),this.words=n}set(t,e){const n=t/32|0,i=31&t;this.ensure(n);const s=1<<i;e?this.words[n]|=s:this.words[n]&=~s}or(t){var e,n;const i=new a(new Uint32Array(Math.max(this.words.length,t.words.length))),s=this.words,r=t.words,o=i.words,c=o.length;for(let t=0;t<c;t++)o[t]=(null!==(e=s[t])&&void 0!==e?e:0)|(null!==(n=r[t])&&void 0!==n?n:0);return i}and(t){var e,n;const i=new a(new Uint32Array(Math.max(this.words.length,t.words.length))),s=this.words,r=t.words,o=i.words,c=o.length;for(let t=0;t<c;t++)o[t]=(null!==(e=s[t])&&void 0!==e?e:0)&(null!==(n=r[t])&&void 0!==n?n:0);return i}andNot(t){var e,n;const i=new a(new Uint32Array(Math.max(this.words.length,t.words.length))),s=this.words,r=t.words,o=i.words,c=o.length;for(let t=0;t<c;t++)o[t]=(null!==(e=s[t])&&void 0!==e?e:0)&~(null!==(n=r[t])&&void 0!==n?n:0);return i}orInPlace(t){this.ensure(t.words.length-1);const e=this.words,n=t.words;for(let t=0;t<n.length;t++)e[t]|=n[t]}andNotInPlace(t){const e=this.words,n=t.words,i=Math.min(e.length,n.length);for(let t=0;t<i;t++)e[t]&=~n[t]}equals(t){var e,n;const i=Math.max(this.words.length,t.words.length);for(let s=0;s<i;s++){if((null!==(e=this.words[s])&&void 0!==e?e:0)!==(null!==(n=t.words[s])&&void 0!==n?n:0))return!1}return!0}isEmpty(){const t=this.words;for(let e=0;e<t.length;e++)if(0!==t[e])return!1;return!0}clear(){this.words.fill(0)}toArray(){const t=[],e=this.words;for(let n=0;n<e.length;n++){let i=e[n];for(;i;){const e=i&-i,s=31-Math.clz32(e);t.push((n<<5)+s),i&=i-1}}return t}toString(){if(1===this.words.length)return this.words[0].toString();let t="";for(let e=this.words.length-1;e>=0;e--){const n=this.words[e].toString(16);t+=e===this.words.length-1?n:"-"+n}return t}contains(t){var e;const n=this.words,i=t.words,s=i.length;for(let t=0;t<s;t++){const s=(null!==(e=n[t])&&void 0!==e?e:0)>>>0,r=i[t]>>>0;if((s&r)>>>0!==r)return!1}return!0}intersects(t){const e=this.words,n=t.words,i=Math.min(e.length,n.length);for(let t=0;t<i;t++)if((e[t]&n[t])>>>0!=0)return!0;return!1}}class c{constructor(t,e,n,i){this.entityManager=t,this.queryManager=e,this.componentManager=n,this.index=i,this.bitmask=new a,this.active=!0,this.vectorViews=new Map}addComponent(t,e={}){return this.active?(null===t.bitmask&&this.componentManager.registerComponent(t),this.bitmask.orInPlace(t.bitmask),this.componentManager.attachComponentToEntity(this.index,t,e),this.queryManager.updateEntity(this,t)):console.warn(`Entity ${this.index} is destroyed, cannot add component ${t.schema}`),this}removeComponent(t){return this.active?t.bitmask&&(this.bitmask.andNotInPlace(t.bitmask),this.vectorViews.delete(t),this.queryManager.updateEntity(this,t)):console.warn(`Entity ${this.index} is destroyed, cannot remove component ${t.schema}`),this}hasComponent(t){return this.bitmask.intersects(t.bitmask)}getComponents(){return this.bitmask.toArray().map((t=>this.componentManager.getComponentByTypeId(t)))}getValue(e,n){var i;const s=e.schema[n];if(!s)return null;const r=null===(i=e.data[n])||void 0===i?void 0:i[this.index];switch(s.type){case t.Boolean:return Boolean(r);case t.Entity:return-1===r?null:this.entityManager.getEntityByIndex(r);default:return r}}setValue(e,n,i){const s=e.data[n],a=e.schema[n];switch(a.type){case t.Enum:r(i,a.enum,n),s[this.index]=i;break;case t.Int8:case t.Int16:case t.Float32:case t.Float64:("min"in a||"max"in a)&&o(i,a.min,a.max,n),s[this.index]=i;break;case t.Entity:s[this.index]=null===i?-1:i.index;break;default:s[this.index]=i}this.queryManager.updateEntityValue(this,e)}getVectorView(t,n){var i;const s=n,r=null===(i=this.vectorViews.get(t))||void 0===i?void 0:i.get(s);if(r)return r;{const i=t.data[n],r=t.schema[n].type,o=e[r].length,a=this.index*o,c=i.subarray(a,a+o);return this.vectorViews.has(t)||this.vectorViews.set(t,new Map),this.vectorViews.get(t).set(s,c),c}}destroy(){this.active&&(this.active=!1,this.bitmask.clear(),this.vectorViews.clear(),this.queryManager.resetEntity(this),this.entityManager.releaseEntityInstance(this))}}class u{static record(t){if(this.components.has(t.id))throw new Error(`Component with id '${t.id}' already exists. Each component must have a unique identifier.`);this.components.set(t.id,t)}static getById(t){return this.components.get(t)}static getAllComponents(){return Array.from(this.components.values())}static has(t){return this.components.has(t)}static clear(){this.components.clear()}}function h(t,e,n){const i={id:t,description:n,schema:e,data:{},bitmask:null,typeId:-1};return u.record(i),i}function l(n,r){const o=n.schema;n.data={};for(const a in o){const c=o[a],{type:u,default:h}=c;let{arrayConstructor:l,length:d}=e[u];u===t.Enum&&s("enum"in c,i.InvalidDefaultValue,`Enum type requires 'enum' property for field ${a}`),s(!!l,i.TypeNotSupported,u),n.data[a]=new l(r*d),s(1===d||Array.isArray(h)&&h.length===d,i.InvalidDefaultValue,a)}}function d(n,i,s){var a;const c=n.schema;for(const u in c){const h=c[u],{type:l,default:d}=h,f=e[l].length,y=n.data[u],p=null!==(a=s[u])&&void 0!==a?a:d;switch(l){case t.Entity:y[i]=p?p.index:-1;break;case t.Enum:r(p,h.enum,u),y[i]=p;break;case t.Int8:case t.Int16:case t.Float32:case t.Float64:("min"in h||"max"in h)&&o(p,h.min,h.max,u),y[i]=p;break;case t.String:case t.Object:y[i]=p;break;default:1===f?y[i]=p:y.set(p,i*f)}}}u.components=new Map;class f{constructor(t){this.entityCapacity=t,this.nextComponentTypeId=0,this.componentsByTypeId=[]}hasComponent(t){return-1!==t.typeId&&this.componentsByTypeId[t.typeId]===t}registerComponent(t){if(this.hasComponent(t))return;const e=this.nextComponentTypeId++;t.bitmask=new a,t.bitmask.set(e,1),t.typeId=e,l(t,this.entityCapacity),this.componentsByTypeId[e]=t}attachComponentToEntity(t,e,n){d(e,t,n)}getComponentByTypeId(t){var e;return null!==(e=this.componentsByTypeId[t])&&void 0!==e?e:null}}class y{constructor(t,e,n){this.queryManager=t,this.componentManager=e,this.entityReleaseCallback=n,this.pool=[],this.entityIndex=0,this.indexLookup=[],this.poolSize=0}requestEntityInstance(){let t;return this.poolSize>0?(t=this.pool[--this.poolSize],t.active=!0):t=new c(this,this.queryManager,this.componentManager,this.entityIndex++),this.indexLookup[t.index]=t,t}releaseEntityInstance(t){var e;null===(e=this.entityReleaseCallback)||void 0===e||e.call(this,t),this.indexLookup[t.index]=null,this.pool[this.poolSize++]=t}getEntityByIndex(t){var e;return-1===t?null:null!==(e=this.indexLookup[t])&&void 0!==e?e:null}}class p{constructor(t,e,n,i=[]){this.requiredMask=t,this.excludedMask=e,this.queryId=n,this.subscribers={qualify:new Set,disqualify:new Set},this.entities=new Set,this.valuePredicates=[],this.valuePredicates=i}matches(t){if(!this.excludedMask.isEmpty()&&this.excludedMask.intersects(t.bitmask))return!1;if(!t.bitmask.contains(this.requiredMask))return!1;if(this.valuePredicates.length>0)for(const e of this.valuePredicates){const n=t.getValue(e.component,e.key);switch(e.op){case"eq":if(n!==e.value)return!1;break;case"ne":if(n===e.value)return!1;break;case"lt":if(!("number"==typeof n&&"number"==typeof e.value&&n<e.value))return!1;break;case"le":if(!("number"==typeof n&&"number"==typeof e.value&&n<=e.value))return!1;break;case"gt":if(!("number"==typeof n&&"number"==typeof e.value&&n>e.value))return!1;break;case"ge":if(!("number"==typeof n&&"number"==typeof e.value&&n>=e.value))return!1;break;case"in":if(!e.valueSet||!e.valueSet.has(n))return!1;break;case"nin":if(e.valueSet&&e.valueSet.has(n))return!1}}return!0}subscribe(t,e){return this.subscribers[t].add(e),()=>{this.subscribers[t].delete(e)}}static generateQueryInfo(e){var n;const i=new a,s=new a;for(const t of e.required)i.orInPlace(t.bitmask);if(e.excluded)for(const t of e.excluded)s.orInPlace(t.bitmask);const r=(null!==(n=e.where)&&void 0!==n?n:[]).map((e=>{const n=e.component.schema[e.key];if(!n)throw new Error(`Predicate key '${e.key}' not found on component ${e.component.id}`);const i=n.type;switch(e.op){case"lt":case"le":case"gt":case"ge":if(i!==t.Int8&&i!==t.Int16&&i!==t.Float32&&i!==t.Float64)throw new Error(`Operator '${e.op}' only valid for numeric fields: ${e.component.id}.${e.key}`);break;case"in":case"nin":if(!Array.isArray(e.value))throw new Error(`Operator '${e.op}' requires array value for ${e.component.id}.${e.key}`)}const s={...e};return"in"!==e.op&&"nin"!==e.op||(s.valueSet=new Set(e.value)),s}));for(const t of r)i.orInPlace(t.component.bitmask);const o=r.map((t=>`${t.component.typeId}:${t.key}:${t.op}=${Array.isArray(t.value)?"arr":String(t.value)}`)).join(",");return{requiredMask:i,excludedMask:s,queryId:`required:${i.toString()}|excluded:${s.toString()}|where:${o}`,valuePredicates:r}}}class m{constructor(t){this.componentManager=t,this.queries=new Map,this.trackedEntities=new Set,this.queriesByComponent=new Map,this.queriesByValueComponent=new Map}registerQuery(t){var e,n,i;for(const e of t.required)null===e.bitmask&&this.componentManager.registerComponent(e);if(t.excluded)for(const e of t.excluded)null===e.bitmask&&this.componentManager.registerComponent(e);if(t.where)for(const e of t.where)null===e.component.bitmask&&this.componentManager.registerComponent(e.component);const{requiredMask:s,excludedMask:r,queryId:o,valuePredicates:a}=p.generateQueryInfo(t);if(!this.queries.has(o)){const c=new p(s,r,o,a);for(const t of this.trackedEntities)c.matches(t)&&c.entities.add(t);const u=[...t.required,...null!==(e=t.excluded)&&void 0!==e?e:[],...(null!==(n=t.where)&&void 0!==n?n:[]).map((t=>t.component))];for(const t of u){let e=this.queriesByComponent.get(t);e||(e=new Set,this.queriesByComponent.set(t,e)),e.add(c)}for(const e of null!==(i=t.where)&&void 0!==i?i:[]){let t=this.queriesByValueComponent.get(e.component);t||(t=new Set,this.queriesByValueComponent.set(e.component,t)),t.add(c)}this.queries.set(o,c)}return this.queries.get(o)}updateEntity(t,e){var n;if(this.trackedEntities.add(t),t.bitmask.isEmpty()){for(const e of this.queries.values())if(e.entities.delete(t))for(const n of e.subscribers.disqualify)n(t);return}const i=e?null!==(n=this.queriesByComponent.get(e))&&void 0!==n?n:[]:this.queries.values();for(const e of i){const n=e.matches(t),i=e.entities.has(t);if(n&&!i){e.entities.add(t);for(const n of e.subscribers.qualify)n(t)}else if(!n&&i){e.entities.delete(t);for(const n of e.subscribers.disqualify)n(t)}}}updateEntityValue(t,e){this.trackedEntities.add(t);const n=this.queriesByValueComponent.get(e);if(n&&0!==n.size)for(const e of n){const n=e.matches(t),i=e.entities.has(t);if(n&&!i){e.entities.add(t);for(const n of e.subscribers.qualify)n(t)}else if(!n&&i){e.entities.delete(t);for(const n of e.subscribers.disqualify)n(t)}}}resetEntity(t){if(this.trackedEntities.delete(t),t.bitmask.isEmpty()){for(const e of this.queries.values())if(e.entities.delete(t))for(const n of e.subscribers.disqualify)n(t);return}const e=new Set;for(const n of t.bitmask.toArray()){const i=this.componentManager.getComponentByTypeId(n),s=this.queriesByComponent.get(i);if(s)for(const n of s)if(!e.has(n)){if(n.entities.delete(t))for(const e of n.subscribers.disqualify)e(t);e.add(n)}}}}class v{constructor({entityCapacity:t=1e3,checksOn:e=!0,entityReleaseCallback:i}={}){this.systems=[],this.globals={},this.componentManager=new f(t),this.queryManager=new m(this.componentManager),this.entityManager=new y(this.queryManager,this.componentManager,i),n=e}registerComponent(t){return this.componentManager.registerComponent(t),this}hasComponent(t){return this.componentManager.hasComponent(t)}createEntity(){return this.entityManager.requestEntityInstance()}registerSystem(t,e={}){if(this.hasSystem(t))return console.warn(`System ${t.name} is already registered, skipping registration.`),this;const{configData:n={},priority:i=0}=e,s={};Object.entries(t.queries).forEach((([t,e])=>{s[t]=this.queryManager.registerQuery(e)}));const r=new t(this,this.queryManager,i);r.queries=s,Object.keys(n).forEach((t=>{t in r.config&&(r.config[t].value=n[t])})),r.init();const o=this.systems.findIndex((t=>t.priority>r.priority));return-1===o?this.systems.push(r):this.systems.splice(o,0,r),this}unregisterSystem(t){const e=this.getSystem(t);e&&(e.destroy(),this.systems=this.systems.filter((e=>!(e instanceof t))))}registerQuery(t){return this.queryManager.registerQuery(t),this}update(t,e){for(const n of this.systems)n.isPaused||n.update(t,e)}getSystem(t){for(const e of this.systems)if(e instanceof t)return e}getSystems(){return this.systems}hasSystem(t){return this.systems.some((e=>e instanceof t))}}const g=Symbol.for("preact-signals");function w(){if(I>1)return void I--;let t,e=!1;for(;void 0!==k;){let n=k;for(k=void 0,q++;void 0!==n;){const i=n.o;if(n.o=void 0,n.f&=-3,!(8&n.f)&&V(n))try{n.c()}catch(n){e||(t=n,e=!0)}n=i}}if(q=0,I--,e)throw t}let b,k;function x(t){const e=b;b=void 0;try{return t()}finally{b=e}}let I=0,q=0,E=0;function S(t){if(void 0===b)return;let e=t.n;return void 0===e||e.t!==b?(e={i:0,S:t,p:b.s,n:void 0,t:b,e:void 0,x:void 0,r:e},void 0!==b.s&&(b.s.n=e),b.s=e,t.n=e,32&b.f&&t.S(e),e):-1===e.i?(e.i=0,void 0!==e.n&&(e.n.p=e.p,void 0!==e.p&&(e.p.n=e.n),e.p=b.s,e.n=void 0,b.s.n=e,b.s=e),e):void 0}function M(t,e){this.v=t,this.i=0,this.n=void 0,this.t=void 0,this.W=null==e?void 0:e.watched,this.Z=null==e?void 0:e.unwatched}function C(t,e){return new M(t,e)}function V(t){for(let e=t.s;void 0!==e;e=e.n)if(e.S.i!==e.i||!e.S.h()||e.S.i!==e.i)return!0;return!1}function $(t){for(let e=t.s;void 0!==e;e=e.n){const n=e.S.n;if(void 0!==n&&(e.r=n),e.S.n=e,e.i=-1,void 0===e.n){t.s=e;break}}}function A(t){let e,n=t.s;for(;void 0!==n;){const t=n.p;-1===n.i?(n.S.U(n),void 0!==t&&(t.n=n.n),void 0!==n.n&&(n.n.p=t)):e=n,n.S.n=n.r,void 0!==n.r&&(n.r=void 0),n=t}t.s=e}function B(t,e){M.call(this,void 0),this.x=t,this.s=void 0,this.g=E-1,this.f=4,this.W=null==e?void 0:e.watched,this.Z=null==e?void 0:e.unwatched}function P(t){const e=t.u;if(t.u=void 0,"function"==typeof e){I++;const n=b;b=void 0;try{e()}catch(e){throw t.f&=-2,t.f|=8,F(t),e}finally{b=n,w()}}}function F(t){for(let e=t.s;void 0!==e;e=e.n)e.S.U(e);t.x=void 0,t.s=void 0,P(t)}function O(t){if(b!==this)throw new Error("Out-of-order effect");A(this),b=t,this.f&=-2,8&this.f&&F(this),w()}function T(t){this.x=t,this.u=void 0,this.s=void 0,this.o=void 0,this.f=32}function U(t={},e={}){var n;return(n=class{constructor(t,n,i){this.world=t,this.queryManager=n,this.priority=i,this.isPaused=!1,this.config={};for(const t in e)this.config[t]=C(e[t].default)}get globals(){return this.world.globals}createEntity(){return this.world.createEntity()}init(){}update(t,e){}play(){this.isPaused=!1}stop(){this.isPaused=!0}destroy(){}}).schema=e,n.isSystem=!0,n.queries=t,n}M.prototype.brand=g,M.prototype.h=function(){return!0},M.prototype.S=function(t){const e=this.t;e!==t&&void 0===t.e&&(t.x=e,this.t=t,void 0!==e?e.e=t:x((()=>{var t;null==(t=this.W)||t.call(this)})))},M.prototype.U=function(t){if(void 0!==this.t){const e=t.e,n=t.x;void 0!==e&&(e.x=n,t.e=void 0),void 0!==n&&(n.e=e,t.x=void 0),t===this.t&&(this.t=n,void 0===n&&x((()=>{var t;null==(t=this.Z)||t.call(this)})))}},M.prototype.subscribe=function(t){return function(t){const e=new T(t);try{e.c()}catch(t){throw e.d(),t}return e.d.bind(e)}((()=>{const e=this.value,n=b;b=void 0;try{t(e)}finally{b=n}}))},M.prototype.valueOf=function(){return this.value},M.prototype.toString=function(){return this.value+""},M.prototype.toJSON=function(){return this.value},M.prototype.peek=function(){const t=b;b=void 0;try{return this.value}finally{b=t}},Object.defineProperty(M.prototype,"value",{get(){const t=S(this);return void 0!==t&&(t.i=this.i),this.v},set(t){if(t!==this.v){if(q>100)throw new Error("Cycle detected");this.v=t,this.i++,E++,I++;try{for(let t=this.t;void 0!==t;t=t.x)t.t.N()}finally{w()}}}}),B.prototype=new M,B.prototype.h=function(){if(this.f&=-3,1&this.f)return!1;if(32==(36&this.f))return!0;if(this.f&=-5,this.g===E)return!0;if(this.g=E,this.f|=1,this.i>0&&!V(this))return this.f&=-2,!0;const t=b;try{$(this),b=this;const t=this.x();(16&this.f||this.v!==t||0===this.i)&&(this.v=t,this.f&=-17,this.i++)}catch(t){this.v=t,this.f|=16,this.i++}return b=t,A(this),this.f&=-2,!0},B.prototype.S=function(t){if(void 0===this.t){this.f|=36;for(let t=this.s;void 0!==t;t=t.n)t.S.S(t)}M.prototype.S.call(this,t)},B.prototype.U=function(t){if(void 0!==this.t&&(M.prototype.U.call(this,t),void 0===this.t)){this.f&=-33;for(let t=this.s;void 0!==t;t=t.n)t.S.U(t)}},B.prototype.N=function(){if(!(2&this.f)){this.f|=6;for(let t=this.t;void 0!==t;t=t.x)t.t.N()}},Object.defineProperty(B.prototype,"value",{get(){if(1&this.f)throw new Error("Cycle detected");const t=S(this);if(this.h(),void 0!==t&&(t.i=this.i),16&this.f)throw this.v;return this.v}}),T.prototype.c=function(){const t=this.S();try{if(8&this.f)return;if(void 0===this.x)return;const t=this.x();"function"==typeof t&&(this.u=t)}finally{t()}},T.prototype.S=function(){if(1&this.f)throw new Error("Cycle detected");this.f|=1,this.f&=-9,P(this),$(this),I++;const t=b;return b=this,O.bind(this,t)},T.prototype.N=function(){2&this.f||(this.f|=2,this.o=k,k=this)},T.prototype.d=function(){this.f|=8,1&this.f||F(this)};const j="3.2.0";function N(t,e,n){return{component:t,key:e,op:"eq",value:n}}function z(t,e,n){return{component:t,key:e,op:"ne",value:n}}function Q(t,e,n){return{component:t,key:e,op:"lt",value:n}}function R(t,e,n){return{component:t,key:e,op:"le",value:n}}function D(t,e,n){return{component:t,key:e,op:"gt",value:n}}function L(t,e,n){return{component:t,key:e,op:"ge",value:n}}function W(t,e,n){return{component:t,key:e,op:"in",value:n}}function Z(t,e,n){return{component:t,key:e,op:"nin",value:n}}export{u as ComponentRegistry,c as Entity,p as Query,e as TypedArrayMap,t as Types,j as VERSION,v as World,d as assignInitialComponentData,h as createComponent,U as createSystem,N as eq,L as ge,D as gt,l as initializeComponentStorage,W as isin,R as le,Q as lt,z as ne,Z as nin};