Comparing version 0.2.15 to 0.2.16
@@ -1,2 +0,773 @@ | ||
const e={bool:"Uint8",i8:"Int8",ui8:"Uint8",ui8c:"Uint8Clamped",i16:"Int16",ui16:"Uint16",i32:"Int32",ui32:"Uint32",f32:"Float32",f64:"Float64"},t={bool:"bool",i8:Int8Array,ui8:Uint8Array,ui8c:Uint8ClampedArray,i16:Int16Array,ui16:Uint16Array,i32:Int32Array,ui32:Uint32Array,f32:Float32Array,f64:Float64Array},n=256,r=65536,o=e=>4*Math.ceil(e/4),s=Symbol("storeRef"),i=Symbol("storeSize"),a=Symbol("storeMaps"),c=Symbol("storeFlattened"),l=Symbol("storeArrayCount"),u=Symbol("storeSubarrays"),f=Symbol("storeCursor"),y=Symbol("subarrayCursors"),d=Symbol("subarray"),g=Symbol("queryShadow"),b=Symbol("serializeShadow"),h=Symbol("indexType"),p=Symbol("indexBytes"),m={},E=(e,t)=>{const n=new ArrayBuffer(t*e.BYTES_PER_ELEMENT),r=new e.constructor(n);return r.set(e,0),r},S=(e,t)=>{Object.keys(e).forEach((n=>{const r=e[n];r[d]||(ArrayBuffer.isView(r)?(e[n]=E(r,t),e[n][g]=E(r[g],t),e[n][b]=E(r[b],t)):"object"==typeof r&&S(e[n],t))}))},A=(e,n)=>{e[i]=n,S(e,n),(e=>{const n=e[i],r=e[y]={};Object.keys(e[u]).forEach((s=>{const i=e[l],a=e[0].length,c=Array(i).fill(0).reduce(((e,n)=>e+t[s].BYTES_PER_ELEMENT),0),f=Array(i).fill(0).reduce(((e,t)=>e+a),0),y=new ArrayBuffer(o(c*f*n)),g=new t[s](y);g.set(e[u][s].buffer,0),e[u][s]=g;for(let t=0;t<n;t++){const n=r[s]+t*a,o=n+a;e[t]=e[u][s].subarray(n,o),e[t][d]=!0}}))})(e)},w=(e,n)=>{const r=n*t[e].BYTES_PER_ELEMENT,o=new ArrayBuffer(r);return new t[e](o)},_=(s,a,c)=>{const f=s[i],m=s[y],E=c<n?"ui8":c<r?"ui16":"ui32";if(!c)throw new Error("❌ Must define a length for component array.");if(!t[a])throw new Error(`❌ Invalid component array property type ${a}.`);if(!s[u][a]){const n=s[l],r=Array(n).fill(0).reduce(((e,n)=>e+t[a].BYTES_PER_ELEMENT),0),i=Array(n).fill(0).reduce(((e,t)=>e+c),0),y=o(r*i*f),d=new ArrayBuffer(y),m=new t[a](d);s[u][a]=m,s[u][a][g]=m.slice(0),s[u][a][b]=m.slice(0),m[h]=e[E],m[p]=t[E].BYTES_PER_ELEMENT}let S=0;for(let e=0;e<f;e++){const t=m[a]+e*c,n=t+c;s[e]=s[u][a].subarray(t,n),s[e][d]=!0,S=n}return m[a]=S,s},B=e=>{e[g]=e.slice(0),e[b]=e.slice(0)},M=e=>Array.isArray(e)&&"object"==typeof e[0]&&e[0].hasOwnProperty("type")&&e[0].hasOwnProperty("length"),T=Symbol("entityMasks"),U=Symbol("entityEnabled"),I=Symbol("deferredEntityRemovals"),j=Symbol("removedEntities");let O=0;const R=e=>{const t=e[j],n=e[ae],r=e[U];if(O>=n-n/5){const t=4*Math.ceil(n/2/4);e[ae]+=t,e[Z].forEach((t=>{A(t.store,e[ae])})),e[Y].forEach((t=>{t.indices=E(t.indices,e[ae]),t.enabled=E(t.enabled,e[ae])}))}const o=t.length>0?t.pop():O;return r[o]=1,O++,o},k=(e,t)=>{const n=e[L],r=e[j],o=e[U];if(0!==o[t]){n.forEach((n=>{X(e,n,t)})),r.push(t),o[t]=0;for(let n=0;n<e[T].length;n++)e[T][n][t]=0}},P=e=>{let t,n=new Set;return Array.isArray(e)?t=e.map((e=>{if(e._flatten)return e._flatten();if("function"==typeof e&&"QueryChanged"===e.name){if((e=e())._flatten){let t=e._flatten();return t.forEach((e=>n.add(e))),t}return n.add(e),[e]}})).reduce(((e,t)=>e.concat(t)),[]):e[Z].forEach((e=>{t=t.concat(e._flatten())})),[t,n]},C=(e,t=5e6)=>{const n=new ArrayBuffer(t),r=new DataView(n),[o,s]=P(e);return e=>{if(!e.length)return;let t=0;for(let n=0;n<o.length;n++){const i=o[n],a=s.has(i);r.setUint8(t,n),t+=1;const c=t;t+=4;let l=0;for(let n=0;n<e.length;n++){const o=e[n];if(!a||i[o]!==i[b][o])if(l++,r.setUint32(t,o),t+=4,ArrayBuffer.isView(i[o])){const e=i[o].constructor.name.replace("Array",""),n=i[o]._indexType,s=i[o]._indexBytes,a=t;t+=1;let c=0;for(let a=0;a<i[o].length;a++){const l=i[o][a];r["set"+n](t,a),t+=s,r["set"+e](t,l),t+=i[o].BYTES_PER_ELEMENT,c++}r["set"+n](a,c)}else{const e=i.constructor.name.replace("Array","");r["set"+e](t,i[o]),t+=i.BYTES_PER_ELEMENT,i[b]||console.log(i),i[b][o]=i[o]}}r.setUint32(c,l)}return n.slice(0,t)}},v=e=>{const[t]=P(e);return e=>{const n=new DataView(e);let r=0;const o=n.getUint8(r);r+=1;const s=n.getUint32(r);r+=4;const i=t[o];for(let e=0;e<s;e++){const e=n.getUint32(r);if(r+=4,ArrayBuffer.isView(i[e])){const t=i[e],o=n["get"+t._indexType];r+=t._indexBytes;for(let s=0;s<o;s++){const o=n["get"+t.constructor.name.replace("Array","")](r);r+=t.BYTES_PER_ELEMENT,i[e][s]=o}}else{let t=n["get"+i.constructor.name.replace("Array","")](r);r+=i.BYTES_PER_ELEMENT,i[e]=t}}}};function x(e){return function(){return e}}function N(e){return function(){return e}}const L=Symbol("queries"),Y=Symbol("queryMap"),V=Symbol("$dirtyQueries"),F=Symbol("queryComponents"),q=2**32,Q=(e,t,n)=>{e[Y].has(t)||$(e,t),e[Y].get(t).enter=n},z=(e,t,n)=>{e[Y].has(t)||$(e,t),e[Y].get(t).exit=n},$=(e,t)=>{e[Y].has(t)||e[Y].set(t,{});let n=[],r=[],o=[];t[F].forEach((e=>{"function"==typeof e?("QueryNot"===e.name&&r.push(e()),"QueryChanged"===e.name&&(o.push(e()),n.push(e()))):n.push(e)}));const s=t=>e[Z].get(t),a=n.reduce(((e,t)=>t[i]>e?t[i]:e),0),c=new Uint32Array(a).fill(q),l=new Uint8Array(a),u=n.concat(r).map((t=>(e[Z].has(t)||ne(e,t),t))).map(s).map((e=>e.generationId)).reduce(((e,t)=>(e.includes(t)||e.push(t),e)),[]),f=n.map(s).reduce(((e,t)=>(e[t.generationId]||(e[t.generationId]=0),e[t.generationId]|=t.bitflag,e)),{}),y=r.map(s).reduce(((e,t)=>(e[t.generationId]||(e[t.generationId]=0,e[t.generationId]|=t.bitflag),e)),{}),d=n.map((e=>e._flatten?e._flatten():[e])).reduce(((e,t)=>e.concat(t)),[]);Object.assign(e[Y].get(t),{entities:[],changed:[],enabled:l,components:n,notComponents:r,changedComponents:o,masks:f,notMasks:y,generations:u,indices:c,flatProps:d,toRemove:[]}),e[L].add(t);for(let n=0;n<O;n++)e[U][n]&&G(e,t,n)&&J(e,t,n)},D=e=>{const t=function(e){e[Y].has(t)||$(e,t),K(e,t);const n=e[Y].get(t);return n.changedComponents.length?((e,t)=>{const n=e[Y].get(t);n.changed.length=0;const r=n.flatProps;for(let e=0;e<n.entities.length;e++){const t=n.entities[e];let o=!1;for(let e=0;e<r.length;e++){const n=r[e];if(ArrayBuffer.isView(n[t]))for(let e=0;e<n[t].length;e++)n[t][e]!==n[t][g][e]&&(o=!0,n[t][g][e]=n[t][e]);else n[t]!==n[g][t]&&(o=!0,n[g][t]=n[t])}o&&n.changed.push(t)}return n.changed})(e,t):n.entities};return t[F]=e,t},G=(e,t,n)=>{const{masks:r,notMasks:o,generations:s}=e[Y].get(t);for(let t=0;t<s.length;t++){const i=s[t],a=r[i],c=o[i],l=e[T][i][n];if(c&&0!=(l&c))return!1;if(a&&(l&a)!==a)return!1}return!0},H=(e,t,n)=>n.every((n=>((e,t,n)=>{const{generationId:r,bitflag:o}=e[Z].get(n),{masks:s}=e[Y].get(t);return(s[r]&o)===o})(e,t,n))),J=(e,t,n)=>{const r=e[Y].get(t);r.enabled[n]||(r.enabled[n]=!0,r.entities.push(n),r.indices[n]=r.entities.length-1,r.enter&&r.enter(n))},K=(e,t)=>{const n=e[Y].get(t);for(;n.toRemove.length;){const e=n.toRemove.pop(),t=n.indices[e];if(t===q)continue;const r=n.entities.pop();r!==e&&(n.entities[t]=r,n.indices[r]=t),n.indices[e]=q}e[V].delete(n)},W=e=>{e[V].forEach((e=>{K(e)}))},X=(e,t,n)=>{const r=e[Y].get(t);r.enabled[n]&&(r.enabled[n]=!1,r.exit&&r.exit(n),r.toRemove.push(n),e[V].add(r))},Z=Symbol("componentMap"),ee=Symbol("de$deferredComponentRemovals"),te=e=>((e,n=1e6)=>{const r=Symbol("store");if("Map"===e.constructor.name)return e[i]=n,e;const o=(t,n)=>(M(e[n])?t++:e[n]instanceof Object&&(t+=Object.keys(e[n]).reduce(o,0)),t),d=M(e)?1:Object.keys(e).reduce(o,0),g={[i]:n,[a]:{},[u]:{},[s]:r,[f]:0,[y]:Object.keys(t).reduce(((e,t)=>({...e,[t]:0})),{}),[l]:d,[c]:[]};if("string"==typeof e)return m[r]=Object.assign(w(e,n),g),g[c].push(m[r]),B(m[r]),m[r];if(M(e)){const{type:t,length:n}=e[0];return m[r]=Object.assign(_(g,t,n),g),g[c].push(m[r]),m[r]}if(e instanceof Object&&Object.keys(e).length){const t=(e,r)=>{if("string"==typeof e[r])e[r]=w(e[r],n),g[c].push(e[r]),B(e[r]);else if(M(e[r])){const{type:t,length:n}=e[r][0];e[r]=_(g,t,n)}else e[r]instanceof Object&&(e[r]=Object.keys(e[r]).reduce(t,e[r]));return e};return m[r]=Object.assign(Object.keys(e).reduce(t,e),g),m[r]}return{}})(e),ne=(e,t)=>{e[Z].set(t,{generationId:e[T].length-1,bitflag:e[ce],store:t}),(e=>{e[ce]*=2,e[ce]>=2**32&&(e[ce]=1,e[T].push(new Uint32Array(e[ae])))})(e)},re=(e,t)=>{t.forEach((t=>ne(e,t)))},oe=(e,t,n)=>{const{generationId:r,bitflag:o}=e[Z].get(t);return(e[T][r][n]&o)===o},se=(e,t,n)=>{if(e[Z].has(t)||ne(e,t),oe(e,t,n))return;const{generationId:r,bitflag:o}=e[Z].get(t);e[T][r][n]|=o;e[L].forEach((t=>{const r=t[F];if(!H(e,t,r))return;G(e,t,n)&&J(e,t,n)}))},ie=(e,t,n)=>{const{generationId:r,bitflag:o}=e[Z].get(t);if(!(e[T][r][n]&o))return;e[L].forEach((t=>{const r=t[F];if(!H(e,t,r))return;G(e,t,n)&&X(e,t,n)})),e[T][r][n]&=~o},ae=Symbol("size"),ce=Symbol("bitflag"),le=(e=1e6)=>{const t={};return t[ae]=e,t[U]=new Uint8Array(e),t[T]=[new Uint32Array(e)],t[j]=[],t[ce]=1,t[Z]=new Map,t[Y]=new Map,t[L]=new Set,t[V]=new Set,t[ee]=[],t[I]=[],t},ue=e=>{const t=t=>{e(t),W(t)};return Object.defineProperty(t,"name",{value:(e.name||"AnonymousSystem")+"_internal",configurable:!0}),t},fe=(...e)=>t=>{e=Array.isArray(e[0])?e[0]:e;for(let n=0;n<e.length;n++){(0,e[n])(t)}},ye={bool:"bool",i8:"i8",ui8:"ui8",ui8c:"ui8c",i16:"i16",ui16:"ui16",i32:"i32",ui32:"ui32",f32:"f32",f64:"f64"};export{N as Changed,x as Not,ye as Types,se as addComponent,R as addEntity,W as commitRemovals,le as createWorld,te as defineComponent,v as defineDeserializer,D as defineQuery,C as defineSerializer,ue as defineSystem,Q as enterQuery,z as exitQuery,oe as hasComponent,fe as pipe,ne as registerComponent,re as registerComponents,ie as removeComponent,k as removeEntity}; | ||
const TYPES_ENUM = { | ||
bool: 'bool', | ||
i8: 'i8', | ||
ui8: 'ui8', | ||
ui8c: 'ui8c', | ||
i16: 'i16', | ||
ui16: 'ui16', | ||
i32: 'i32', | ||
ui32: 'ui32', | ||
f32: 'f32', | ||
f64: 'f64' | ||
}; | ||
const TYPES_NAMES = { | ||
bool: 'Uint8', | ||
i8: 'Int8', | ||
ui8: 'Uint8', | ||
ui8c: 'Uint8Clamped', | ||
i16: 'Int16', | ||
ui16: 'Uint16', | ||
i32: 'Int32', | ||
ui32: 'Uint32', | ||
f32: 'Float32', | ||
f64: 'Float64' | ||
}; | ||
const TYPES = { | ||
bool: 'bool', | ||
i8: Int8Array, | ||
ui8: Uint8Array, | ||
ui8c: Uint8ClampedArray, | ||
i16: Int16Array, | ||
ui16: Uint16Array, | ||
i32: Int32Array, | ||
ui32: Uint32Array, | ||
f32: Float32Array, | ||
f64: Float64Array | ||
}; | ||
const UNSIGNED_MAX = { | ||
uint8: 2 ** 8, | ||
uint16: 2 ** 16, | ||
uint32: 2 ** 32 | ||
}; | ||
const roundToMultiple4 = x => Math.ceil(x / 4) * 4; | ||
const $storeRef = Symbol('storeRef'); | ||
const $storeSize = Symbol('storeSize'); | ||
const $storeMaps = Symbol('storeMaps'); | ||
const $storeFlattened = Symbol('storeFlattened'); | ||
const $storeArrayCount = Symbol('storeArrayCount'); | ||
const $storeSubarrays = Symbol('storeSubarrays'); | ||
const $storeCursor = Symbol('storeCursor'); | ||
const $subarrayCursors = Symbol('subarrayCursors'); | ||
const $subarray = Symbol('subarray'); | ||
const $queryShadow = Symbol('queryShadow'); | ||
const $serializeShadow = Symbol('serializeShadow'); | ||
const $indexType = Symbol('indexType'); | ||
const $indexBytes = Symbol('indexBytes'); | ||
const stores = {}; | ||
const resize = (ta, size) => { | ||
const newBuffer = new ArrayBuffer(size * ta.BYTES_PER_ELEMENT); | ||
const newTa = new ta.constructor(newBuffer); | ||
newTa.set(ta, 0); | ||
return newTa; | ||
}; | ||
const resizeRecursive = (store, size) => { | ||
Object.keys(store).forEach(key => { | ||
const ta = store[key]; | ||
if (ta[$subarray]) return;else if (ArrayBuffer.isView(ta)) { | ||
store[key] = resize(ta, size); | ||
store[key][$queryShadow] = resize(ta[$queryShadow], size); | ||
store[key][$serializeShadow] = resize(ta[$serializeShadow], size); | ||
} else if (typeof ta === 'object') { | ||
resizeRecursive(store[key], size); | ||
} | ||
}); | ||
}; | ||
const resizeSubarrays = (store, size) => { | ||
const cursors = store[$subarrayCursors] = {}; | ||
Object.keys(store[$storeSubarrays]).forEach(type => { | ||
const arrayCount = store[$storeArrayCount]; | ||
const length = store[0].length; | ||
const summedBytesPerElement = Array(arrayCount).fill(0).reduce((a, p) => a + TYPES[type].BYTES_PER_ELEMENT, 0); | ||
const summedLength = Array(arrayCount).fill(0).reduce((a, p) => a + length, 0); | ||
const buffer = new ArrayBuffer(roundToMultiple4(summedBytesPerElement * summedLength * size)); | ||
const array = new TYPES[type](buffer); | ||
array.set(store[$storeSubarrays][type].buffer, 0); | ||
store[$storeSubarrays][type] = array; | ||
store[$storeSubarrays][type][$queryShadow] = array.slice(0); | ||
store[$storeSubarrays][type][$serializeShadow] = array.slice(0); | ||
for (let eid = 0; eid < size; eid++) { | ||
const from = cursors[type] + eid * length; | ||
const to = from + length; | ||
store[eid] = store[$storeSubarrays][type].subarray(from, to); | ||
store[eid][$queryShadow] = store[$storeSubarrays][type][$queryShadow].subarray(from, to); | ||
store[eid][$serializeShadow] = store[$storeSubarrays][type][$serializeShadow].subarray(from, to); | ||
store[eid][$subarray] = true; | ||
store[eid][$indexType] = array[$indexType]; | ||
store[eid][$indexBytes] = array[$indexBytes]; | ||
} | ||
}); | ||
}; | ||
const resizeStore = (store, size) => { | ||
store[$storeSize] = size; | ||
resizeRecursive(store, size); | ||
resizeSubarrays(store, size); | ||
}; | ||
const createTypeStore = (type, length) => { | ||
const totalBytes = length * TYPES[type].BYTES_PER_ELEMENT; | ||
const buffer = new ArrayBuffer(totalBytes); | ||
return new TYPES[type](buffer); | ||
}; | ||
const createArrayStore = (store, type, length) => { | ||
const size = store[$storeSize]; | ||
const cursors = store[$subarrayCursors]; | ||
const indexType = length < UNSIGNED_MAX.uint8 ? 'ui8' : length < UNSIGNED_MAX.uint16 ? 'ui16' : 'ui32'; | ||
if (!length) throw new Error('❌ Must define a length for component array.'); | ||
if (!TYPES[type]) throw new Error(`❌ Invalid component array property type ${type}.`); // create buffer for type if it does not already exist | ||
if (!store[$storeSubarrays][type]) { | ||
const arrayCount = store[$storeArrayCount]; | ||
const summedBytesPerElement = Array(arrayCount).fill(0).reduce((a, p) => a + TYPES[type].BYTES_PER_ELEMENT, 0); | ||
const summedLength = Array(arrayCount).fill(0).reduce((a, p) => a + length, 0); | ||
const totalBytes = roundToMultiple4(summedBytesPerElement * summedLength * size); | ||
const buffer = new ArrayBuffer(totalBytes); | ||
const array = new TYPES[type](buffer); | ||
store[$storeSubarrays][type] = array; | ||
store[$storeSubarrays][type][$queryShadow] = array.slice(0); | ||
store[$storeSubarrays][type][$serializeShadow] = array.slice(0); | ||
array[$indexType] = TYPES_NAMES[indexType]; | ||
array[$indexBytes] = TYPES[indexType].BYTES_PER_ELEMENT; | ||
} // pre-generate subarrays for each eid | ||
let end = 0; | ||
for (let eid = 0; eid < size; eid++) { | ||
const from = cursors[type] + eid * length; | ||
const to = from + length; | ||
store[eid] = store[$storeSubarrays][type].subarray(from, to); | ||
store[eid][$queryShadow] = store[$storeSubarrays][type][$queryShadow].subarray(from, to); | ||
store[eid][$serializeShadow] = store[$storeSubarrays][type][$serializeShadow].subarray(from, to); | ||
store[eid][$subarray] = true; | ||
store[eid][$indexType] = TYPES_NAMES[indexType]; | ||
store[eid][$indexBytes] = TYPES[indexType].BYTES_PER_ELEMENT; | ||
end = to; | ||
} | ||
cursors[type] = end; | ||
return store; | ||
}; | ||
const createShadows = store => { | ||
store[$queryShadow] = store.slice(0); | ||
store[$serializeShadow] = store.slice(0); | ||
}; | ||
const isArrayType = x => Array.isArray(x) && typeof x[0] === 'object' && x[0].hasOwnProperty('type') && x[0].hasOwnProperty('length'); | ||
const createStore = (schema, size = 1000000) => { | ||
const $store = Symbol('store'); | ||
schema = JSON.parse(JSON.stringify(schema)); | ||
if (schema.constructor.name === 'Map') { | ||
schema[$storeSize] = size; | ||
return schema; | ||
} | ||
const collectArrayCount = (count, key) => { | ||
if (isArrayType(schema[key])) { | ||
count++; | ||
} else if (schema[key] instanceof Object) { | ||
count += Object.keys(schema[key]).reduce(collectArrayCount, 0); | ||
} | ||
return count; | ||
}; | ||
const arrayCount = isArrayType(schema) ? 1 : Object.keys(schema).reduce(collectArrayCount, 0); | ||
const metadata = { | ||
[$storeSize]: size, | ||
[$storeMaps]: {}, | ||
[$storeSubarrays]: {}, | ||
[$storeRef]: $store, | ||
[$storeCursor]: 0, | ||
[$subarrayCursors]: Object.keys(TYPES).reduce((a, type) => ({ ...a, | ||
[type]: 0 | ||
}), {}), | ||
[$storeArrayCount]: arrayCount, | ||
[$storeFlattened]: [] | ||
}; | ||
if (typeof schema === 'string') { | ||
stores[$store] = Object.assign(createTypeStore(schema, size), metadata); | ||
metadata[$storeFlattened].push(stores[$store]); | ||
createShadows(stores[$store]); | ||
return stores[$store]; | ||
} else if (isArrayType(schema)) { | ||
const { | ||
type, | ||
length | ||
} = schema[0]; | ||
stores[$store] = Object.assign(createArrayStore(metadata, type, length), metadata); | ||
metadata[$storeFlattened].push(stores[$store]); | ||
return stores[$store]; | ||
} else if (schema instanceof Object && Object.keys(schema).length) { | ||
const recursiveTransform = (a, k) => { | ||
if (typeof a[k] === 'string') { | ||
a[k] = createTypeStore(a[k], size); | ||
metadata[$storeFlattened].push(a[k]); | ||
createShadows(a[k]); | ||
} else if (isArrayType(a[k])) { | ||
const { | ||
type, | ||
length | ||
} = a[k][0]; | ||
a[k] = createArrayStore(metadata, type, length); | ||
metadata[$storeFlattened].push(a[k]); | ||
} else if (a[k] instanceof Object) { | ||
a[k] = Object.keys(a[k]).reduce(recursiveTransform, a[k]); | ||
} | ||
return a; | ||
}; | ||
stores[$store] = Object.assign(Object.keys(schema).reduce(recursiveTransform, schema), metadata); | ||
return stores[$store]; | ||
} | ||
return {}; | ||
}; | ||
const $entityMasks = Symbol('entityMasks'); | ||
const $entityEnabled = Symbol('entityEnabled'); | ||
const $deferredEntityRemovals = Symbol('deferredEntityRemovals'); | ||
const $removedEntities = Symbol('removedEntities'); // need a global EID cursor which all worlds and all components know about | ||
// so that world entities can posess entire rows spanning all component tables | ||
let globalEntityCursor = 0; | ||
const getEntityCursor = () => globalEntityCursor; | ||
const resizeWorld = (world, size) => { | ||
world[$size] = size; | ||
world[$componentMap].forEach(c => { | ||
resizeStore(c.store, size); | ||
}); | ||
world[$queryMap].forEach(q => { | ||
q.indices = resize(q.indices, size); | ||
q.enabled = resize(q.enabled, size); | ||
}); | ||
world[$entityEnabled] = resize(world[$entityEnabled], size); | ||
for (let i = 0; i < world[$entityMasks].length; i++) { | ||
const masks = world[$entityMasks][i]; | ||
world[$entityMasks][i] = resize(masks, size); | ||
} | ||
}; | ||
const addEntity = world => { | ||
const removed = world[$removedEntities]; | ||
const size = world[$size]; | ||
const enabled = world[$entityEnabled]; // if data stores are 80% full | ||
if (globalEntityCursor >= size - size / 5) { | ||
// grow by half the original size rounded up to a multiple of 4 | ||
const amount = Math.ceil(size / 2 / 4) * 4; | ||
resizeWorld(world, size + amount); | ||
} | ||
const eid = removed.length > 0 ? removed.pop() : globalEntityCursor; | ||
enabled[eid] = 1; | ||
globalEntityCursor++; | ||
return eid; | ||
}; | ||
const removeEntity = (world, eid) => { | ||
const queries = world[$queries]; | ||
const removed = world[$removedEntities]; | ||
const enabled = world[$entityEnabled]; // Check if entity is already removed | ||
if (enabled[eid] === 0) return; // Remove entity from all queries | ||
// TODO: archetype graph | ||
queries.forEach(query => { | ||
queryRemoveEntity(world, query, eid); | ||
}); // Free the entity | ||
removed.push(eid); | ||
enabled[eid] = 0; // Clear entity bitmasks | ||
for (let i = 0; i < world[$entityMasks].length; i++) world[$entityMasks][i][eid] = 0; | ||
}; | ||
const diff = (world, query) => { | ||
const q = world[$queryMap].get(query); | ||
q.changed.length = 0; | ||
const flat = q.flatProps; | ||
for (let i = 0; i < q.entities.length; i++) { | ||
const eid = q.entities[i]; | ||
let dirty = false; | ||
for (let pid = 0; pid < flat.length; pid++) { | ||
const prop = flat[pid]; | ||
if (ArrayBuffer.isView(prop[eid])) { | ||
for (let i = 0; i < prop[eid].length; i++) { | ||
if (prop[eid][i] !== prop[eid][$queryShadow][i]) { | ||
dirty = true; | ||
prop[eid][$queryShadow][i] = prop[eid][i]; | ||
} | ||
} | ||
} else { | ||
if (prop[eid] !== prop[$queryShadow][eid]) { | ||
dirty = true; | ||
prop[$queryShadow][eid] = prop[eid]; | ||
} | ||
} | ||
} | ||
if (dirty) q.changed.push(eid); | ||
} | ||
return q.changed; | ||
}; | ||
const canonicalize = target => { | ||
let componentProps; | ||
let changedProps = new Set(); | ||
if (Array.isArray(target)) { | ||
componentProps = target.map(p => { | ||
if (typeof p === 'function' && p.name === 'QueryChanged') { | ||
p()[$storeFlattened].forEach(prop => { | ||
changedProps.add(prop); | ||
}); | ||
return p()[$storeFlattened]; | ||
} | ||
return p[$storeFlattened]; | ||
}).reduce((a, v) => a.concat(v), []); | ||
} else { | ||
target[$componentMap].forEach(c => { | ||
componentProps = componentProps.concat(c[$storeFlattened]); | ||
}); | ||
} | ||
return [componentProps, changedProps]; | ||
}; | ||
const defineSerializer = (target, maxBytes = 20_000_000) => { | ||
const [componentProps, changedProps] = canonicalize(target); // TODO: calculate max bytes based on target | ||
const buffer = new ArrayBuffer(maxBytes); | ||
const view = new DataView(buffer); | ||
return ents => { | ||
if (!ents.length) return; | ||
let where = 0; // iterate over component props | ||
for (let pid = 0; pid < componentProps.length; pid++) { | ||
const prop = componentProps[pid]; | ||
const diff = changedProps.has(prop); // write pid | ||
view.setUint8(where, pid); | ||
where += 1; // save space for entity count | ||
const countWhere = where; | ||
where += 4; | ||
let count = 0; // write eid,val | ||
for (let i = 0; i < ents.length; i++) { | ||
const eid = ents[i]; // skip if diffing and no change | ||
if (diff && prop[eid] === prop[$serializeShadow][eid]) { | ||
continue; | ||
} | ||
count++; // write eid | ||
view.setUint32(where, eid); | ||
where += 4; // if property is an array | ||
if (ArrayBuffer.isView(prop[eid])) { | ||
const type = prop[eid].constructor.name.replace('Array', ''); | ||
const indexType = prop[eid][$indexType]; | ||
const indexBytes = prop[eid][$indexBytes]; // add space for count of dirty array elements | ||
const countWhere2 = where; | ||
where += 1; | ||
let count2 = 0; // write array values | ||
for (let i = 0; i < prop[eid].length; i++) { | ||
const value = prop[eid][i]; | ||
if (diff && prop[eid][i] === prop[eid][$serializeShadow][i]) { | ||
continue; | ||
} // write array index | ||
view[`set${indexType}`](where, i); | ||
where += indexBytes; // write value at that index | ||
view[`set${type}`](where, value); | ||
where += prop[eid].BYTES_PER_ELEMENT; | ||
count2++; | ||
} | ||
view[`set${indexType}`](countWhere2, count2); | ||
} else { | ||
// regular property values | ||
const type = prop.constructor.name.replace('Array', ''); // set value next [type] bytes | ||
view[`set${type}`](where, prop[eid]); | ||
where += prop.BYTES_PER_ELEMENT; // sync shadow state | ||
prop[$serializeShadow][eid] = prop[eid]; | ||
} | ||
} | ||
view.setUint32(countWhere, count); | ||
} | ||
return buffer.slice(0, where); | ||
}; | ||
}; | ||
const defineDeserializer = target => { | ||
const [componentProps] = canonicalize(target); | ||
return packet => { | ||
const view = new DataView(packet); | ||
let where = 0; | ||
while (where < packet.byteLength) { | ||
// pid | ||
const pid = view.getUint8(where); | ||
where += 1; // entity count | ||
const entityCount = view.getUint32(where); | ||
where += 4; // typed array | ||
const ta = componentProps[pid]; // Get the properties and set the new state | ||
for (let i = 0; i < entityCount; i++) { | ||
const eid = view.getUint32(where); | ||
where += 4; | ||
if (ArrayBuffer.isView(ta[eid])) { | ||
const array = ta[eid]; | ||
const count = view[`get${array[$indexType]}`](where); | ||
where += array[$indexBytes]; // iterate over count | ||
for (let i = 0; i < count; i++) { | ||
const index = view[`get${array[$indexType]}`](where); | ||
where += array[$indexBytes]; | ||
const value = view[`get${array.constructor.name.replace('Array', '')}`](where); | ||
where += array.BYTES_PER_ELEMENT; | ||
ta[eid][index] = value; | ||
} | ||
} else { | ||
let value = view[`get${ta.constructor.name.replace('Array', '')}`](where); | ||
where += ta.BYTES_PER_ELEMENT; | ||
ta[eid] = value; | ||
} | ||
} | ||
} | ||
}; | ||
}; | ||
function Not(c) { | ||
return function QueryNot() { | ||
return c; | ||
}; | ||
} | ||
function Changed(c) { | ||
return function QueryChanged() { | ||
return c; | ||
}; | ||
} | ||
const $queries = Symbol('queries'); | ||
const $queryMap = Symbol('queryMap'); | ||
const $dirtyQueries = Symbol('$dirtyQueries'); | ||
const $queryComponents = Symbol('queryComponents'); | ||
const NONE = 2 ** 32; | ||
const enterQuery = (world, query, fn) => { | ||
if (!world[$queryMap].has(query)) registerQuery(world, query); | ||
world[$queryMap].get(query).enter = fn; | ||
}; | ||
const exitQuery = (world, query, fn) => { | ||
if (!world[$queryMap].has(query)) registerQuery(world, query); | ||
world[$queryMap].get(query).exit = fn; | ||
}; | ||
const registerQuery = (world, query) => { | ||
if (!world[$queryMap].has(query)) world[$queryMap].set(query, {}); | ||
let components = []; | ||
let notComponents = []; | ||
let changedComponents = []; | ||
query[$queryComponents].forEach(c => { | ||
if (typeof c === 'function') { | ||
if (c.name === 'QueryNot') { | ||
notComponents.push(c()); | ||
} | ||
if (c.name === 'QueryChanged') { | ||
changedComponents.push(c()); | ||
components.push(c()); | ||
} | ||
} else { | ||
components.push(c); | ||
} | ||
}); | ||
const mapComponents = c => world[$componentMap].get(c); | ||
const size = components.reduce((a, c) => c[$storeSize] > a ? c[$storeSize] : a, 0); | ||
const entities = []; | ||
const changed = []; | ||
const indices = new Uint32Array(size).fill(NONE); | ||
const enabled = new Uint8Array(size); | ||
const generations = components.concat(notComponents).map(c => { | ||
if (!world[$componentMap].has(c)) registerComponent(world, c); | ||
return c; | ||
}).map(mapComponents).map(c => c.generationId).reduce((a, v) => { | ||
if (a.includes(v)) return a; | ||
a.push(v); | ||
return a; | ||
}, []); | ||
const reduceBitmasks = (a, c) => { | ||
if (!a[c.generationId]) a[c.generationId] = 0; | ||
a[c.generationId] |= c.bitflag; | ||
return a; | ||
}; | ||
const masks = components.map(mapComponents).reduce(reduceBitmasks, {}); | ||
const notMasks = notComponents.map(mapComponents).reduce((a, c) => { | ||
if (!a[c.generationId]) { | ||
a[c.generationId] = 0; | ||
a[c.generationId] |= c.bitflag; | ||
} | ||
return a; | ||
}, {}); | ||
const flatProps = components.map(c => c._flatten ? c._flatten() : [c]).reduce((a, v) => a.concat(v), []); | ||
const toRemove = []; | ||
Object.assign(world[$queryMap].get(query), { | ||
entities, | ||
changed, | ||
enabled, | ||
components, | ||
notComponents, | ||
changedComponents, | ||
masks, | ||
notMasks, | ||
generations, | ||
indices, | ||
flatProps, | ||
toRemove | ||
}); | ||
world[$queries].add(query); | ||
for (let eid = 0; eid < getEntityCursor(); eid++) { | ||
if (!world[$entityEnabled][eid]) continue; | ||
if (queryCheckEntity(world, query, eid)) { | ||
queryAddEntity(world, query, eid); | ||
} | ||
} | ||
}; | ||
const defineQuery = components => { | ||
const query = function (world) { | ||
if (!world[$queryMap].has(query)) registerQuery(world, query); | ||
queryCommitRemovals(world, query); | ||
const q = world[$queryMap].get(query); | ||
if (q.changedComponents.length) return diff(world, query); | ||
return q.entities; | ||
}; | ||
query[$queryComponents] = components; | ||
return query; | ||
}; // TODO: archetype graph | ||
const queryCheckEntity = (world, query, eid) => { | ||
const { | ||
masks, | ||
notMasks, | ||
generations | ||
} = world[$queryMap].get(query); | ||
for (let i = 0; i < generations.length; i++) { | ||
const generationId = generations[i]; | ||
const qMask = masks[generationId]; | ||
const qNotMask = notMasks[generationId]; | ||
const eMask = world[$entityMasks][generationId][eid]; | ||
if (qNotMask && (eMask & qNotMask) !== 0) { | ||
return false; | ||
} | ||
if (qMask && (eMask & qMask) !== qMask) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
}; | ||
const queryCheckComponent = (world, query, component) => { | ||
const { | ||
generationId, | ||
bitflag | ||
} = world[$componentMap].get(component); | ||
const { | ||
masks | ||
} = world[$queryMap].get(query); | ||
const mask = masks[generationId]; | ||
return (mask & bitflag) === bitflag; | ||
}; | ||
const queryCheckComponents = (world, query, components) => { | ||
return components.every(c => queryCheckComponent(world, query, c)); | ||
}; | ||
const queryAddEntity = (world, query, eid) => { | ||
const q = world[$queryMap].get(query); | ||
if (q.enabled[eid]) return; | ||
q.enabled[eid] = true; | ||
q.entities.push(eid); | ||
q.indices[eid] = q.entities.length - 1; | ||
if (q.enter) q.enter(eid); | ||
}; | ||
const queryCommitRemovals = (world, query) => { | ||
const q = world[$queryMap].get(query); | ||
while (q.toRemove.length) { | ||
const eid = q.toRemove.pop(); | ||
const index = q.indices[eid]; | ||
if (index === NONE) continue; | ||
const swapped = q.entities.pop(); | ||
if (swapped !== eid) { | ||
q.entities[index] = swapped; | ||
q.indices[swapped] = index; | ||
} | ||
q.indices[eid] = NONE; | ||
} | ||
world[$dirtyQueries].delete(q); | ||
}; | ||
const commitRemovals = world => { | ||
world[$dirtyQueries].forEach(q => { | ||
queryCommitRemovals(q); | ||
}); | ||
}; | ||
const queryRemoveEntity = (world, query, eid) => { | ||
const q = world[$queryMap].get(query); | ||
if (!q.enabled[eid]) return; | ||
q.enabled[eid] = false; | ||
if (q.exit) q.exit(eid); | ||
q.toRemove.push(eid); | ||
world[$dirtyQueries].add(q); | ||
}; | ||
const $componentMap = Symbol('componentMap'); | ||
const $deferredComponentRemovals = Symbol('de$deferredComponentRemovals'); | ||
const defineComponent = schema => createStore(schema); | ||
const incrementBitflag = world => { | ||
world[$bitflag] *= 2; | ||
if (world[$bitflag] >= 2 ** 32) { | ||
world[$bitflag] = 1; | ||
world[$entityMasks].push(new Uint32Array(world[$size])); | ||
} | ||
}; | ||
const registerComponent = (world, component) => { | ||
world[$componentMap].set(component, { | ||
generationId: world[$entityMasks].length - 1, | ||
bitflag: world[$bitflag], | ||
store: component | ||
}); | ||
incrementBitflag(world); | ||
}; | ||
const registerComponents = (world, components) => { | ||
components.forEach(c => registerComponent(world, c)); | ||
}; | ||
const hasComponent = (world, component, eid) => { | ||
const { | ||
generationId, | ||
bitflag | ||
} = world[$componentMap].get(component); | ||
const mask = world[$entityMasks][generationId][eid]; | ||
return (mask & bitflag) === bitflag; | ||
}; | ||
const addComponent = (world, component, eid) => { | ||
if (!world[$componentMap].has(component)) registerComponent(world, component); | ||
if (hasComponent(world, component, eid)) return; // Add bitflag to entity bitmask | ||
const { | ||
generationId, | ||
bitflag | ||
} = world[$componentMap].get(component); | ||
world[$entityMasks][generationId][eid] |= bitflag; // Zero out each property value | ||
// component._reset(eid) | ||
// todo: archetype graph | ||
const queries = world[$queries]; | ||
queries.forEach(query => { | ||
const components = query[$queryComponents]; | ||
if (!queryCheckComponents(world, query, components)) return; | ||
const match = queryCheckEntity(world, query, eid); | ||
if (match) queryAddEntity(world, query, eid); | ||
}); | ||
}; | ||
const removeComponent = (world, component, eid) => { | ||
const { | ||
generationId, | ||
bitflag | ||
} = world[$componentMap].get(component); | ||
if (!(world[$entityMasks][generationId][eid] & bitflag)) return; // todo: archetype graph | ||
const queries = world[$queries]; | ||
queries.forEach(query => { | ||
const components = query[$queryComponents]; | ||
if (!queryCheckComponents(world, query, components)) return; | ||
const match = queryCheckEntity(world, query, eid); | ||
if (match) queryRemoveEntity(world, query, eid); | ||
}); // Remove flag from entity bitmask | ||
world[$entityMasks][generationId][eid] &= ~bitflag; | ||
}; | ||
const $size = Symbol('size'); | ||
const $bitflag = Symbol('bitflag'); | ||
const createWorld = (size = 1000000) => { | ||
const world = {}; | ||
world[$size] = size; | ||
world[$entityEnabled] = new Uint8Array(size); | ||
world[$entityMasks] = [new Uint32Array(size)]; | ||
world[$removedEntities] = []; | ||
world[$bitflag] = 1; | ||
world[$componentMap] = new Map(); | ||
world[$queryMap] = new Map(); | ||
world[$queries] = new Set(); | ||
world[$dirtyQueries] = new Set(); | ||
world[$deferredComponentRemovals] = []; | ||
world[$deferredEntityRemovals] = []; | ||
return world; | ||
}; | ||
const defineSystem = update => { | ||
const system = world => { | ||
update(world); | ||
commitRemovals(world); | ||
}; | ||
Object.defineProperty(system, 'name', { | ||
value: (update.name || "AnonymousSystem") + "_internal", | ||
configurable: true | ||
}); | ||
return system; | ||
}; | ||
const pipe = (...fns) => world => { | ||
fns = Array.isArray(fns[0]) ? fns[0] : fns; | ||
for (let i = 0; i < fns.length; i++) { | ||
const fn = fns[i]; | ||
fn(world); | ||
} | ||
}; | ||
const Types = TYPES_ENUM; | ||
export { Changed, Not, Types, addComponent, addEntity, commitRemovals, createWorld, defineComponent, defineDeserializer, defineQuery, defineSerializer, defineSystem, enterQuery, exitQuery, hasComponent, pipe, registerComponent, registerComponents, removeComponent, removeEntity }; | ||
//# sourceMappingURL=index.es.js.map |
@@ -1,2 +0,796 @@ | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});const e={bool:"Uint8",i8:"Int8",ui8:"Uint8",ui8c:"Uint8Clamped",i16:"Int16",ui16:"Uint16",i32:"Int32",ui32:"Uint32",f32:"Float32",f64:"Float64"},t={bool:"bool",i8:Int8Array,ui8:Uint8Array,ui8c:Uint8ClampedArray,i16:Int16Array,ui16:Uint16Array,i32:Int32Array,ui32:Uint32Array,f32:Float32Array,f64:Float64Array},n=256,r=65536,o=e=>4*Math.ceil(e/4),s=Symbol("storeRef"),i=Symbol("storeSize"),a=Symbol("storeMaps"),c=Symbol("storeFlattened"),l=Symbol("storeArrayCount"),u=Symbol("storeSubarrays"),f=Symbol("storeCursor"),y=Symbol("subarrayCursors"),p=Symbol("subarray"),d=Symbol("queryShadow"),g=Symbol("serializeShadow"),m=Symbol("indexType"),b=Symbol("indexBytes"),h={},E=(e,t)=>{const n=new ArrayBuffer(t*e.BYTES_PER_ELEMENT),r=new e.constructor(n);return r.set(e,0),r},S=(e,t)=>{Object.keys(e).forEach((n=>{const r=e[n];r[p]||(ArrayBuffer.isView(r)?(e[n]=E(r,t),e[n][d]=E(r[d],t),e[n][g]=E(r[g],t)):"object"==typeof r&&S(e[n],t))}))},A=(e,n)=>{e[i]=n,S(e,n),(e=>{const n=e[i],r=e[y]={};Object.keys(e[u]).forEach((s=>{const i=e[l],a=e[0].length,c=Array(i).fill(0).reduce(((e,n)=>e+t[s].BYTES_PER_ELEMENT),0),f=Array(i).fill(0).reduce(((e,t)=>e+a),0),y=new ArrayBuffer(o(c*f*n)),d=new t[s](y);d.set(e[u][s].buffer,0),e[u][s]=d;for(let t=0;t<n;t++){const n=r[s]+t*a,o=n+a;e[t]=e[u][s].subarray(n,o),e[t][p]=!0}}))})(e)},w=(e,n)=>{const r=n*t[e].BYTES_PER_ELEMENT,o=new ArrayBuffer(r);return new t[e](o)},_=(s,a,c)=>{const f=s[i],h=s[y],E=c<n?"ui8":c<r?"ui16":"ui32";if(!c)throw new Error("❌ Must define a length for component array.");if(!t[a])throw new Error(`❌ Invalid component array property type ${a}.`);if(!s[u][a]){const n=s[l],r=Array(n).fill(0).reduce(((e,n)=>e+t[a].BYTES_PER_ELEMENT),0),i=Array(n).fill(0).reduce(((e,t)=>e+c),0),y=o(r*i*f),p=new ArrayBuffer(y),h=new t[a](p);s[u][a]=h,s[u][a][d]=h.slice(0),s[u][a][g]=h.slice(0),h[m]=e[E],h[b]=t[E].BYTES_PER_ELEMENT}let S=0;for(let e=0;e<f;e++){const t=h[a]+e*c,n=t+c;s[e]=s[u][a].subarray(t,n),s[e][p]=!0,S=n}return h[a]=S,s},x=e=>{e[d]=e.slice(0),e[g]=e.slice(0)},M=e=>Array.isArray(e)&&"object"==typeof e[0]&&e[0].hasOwnProperty("type")&&e[0].hasOwnProperty("length"),T=Symbol("entityMasks"),B=Symbol("entityEnabled"),U=Symbol("deferredEntityRemovals"),j=Symbol("removedEntities");let C=0;const I=e=>{let t,n=new Set;return Array.isArray(e)?t=e.map((e=>{if(e._flatten)return e._flatten();if("function"==typeof e&&"QueryChanged"===e.name){if((e=e())._flatten){let t=e._flatten();return t.forEach((e=>n.add(e))),t}return n.add(e),[e]}})).reduce(((e,t)=>e.concat(t)),[]):e[q].forEach((e=>{t=t.concat(e._flatten())})),[t,n]};const O=Symbol("queries"),R=Symbol("queryMap"),P=Symbol("$dirtyQueries"),k=Symbol("queryComponents"),v=2**32,N=(e,t)=>{e[R].has(t)||e[R].set(t,{});let n=[],r=[],o=[];t[k].forEach((e=>{"function"==typeof e?("QueryNot"===e.name&&r.push(e()),"QueryChanged"===e.name&&(o.push(e()),n.push(e()))):n.push(e)}));const s=t=>e[q].get(t),a=n.reduce(((e,t)=>t[i]>e?t[i]:e),0),c=new Uint32Array(a).fill(v),l=new Uint8Array(a),u=n.concat(r).map((t=>(e[q].has(t)||$(e,t),t))).map(s).map((e=>e.generationId)).reduce(((e,t)=>(e.includes(t)||e.push(t),e)),[]),f=n.map(s).reduce(((e,t)=>(e[t.generationId]||(e[t.generationId]=0),e[t.generationId]|=t.bitflag,e)),{}),y=r.map(s).reduce(((e,t)=>(e[t.generationId]||(e[t.generationId]=0,e[t.generationId]|=t.bitflag),e)),{}),p=n.map((e=>e._flatten?e._flatten():[e])).reduce(((e,t)=>e.concat(t)),[]);Object.assign(e[R].get(t),{entities:[],changed:[],enabled:l,components:n,notComponents:r,changedComponents:o,masks:f,notMasks:y,generations:u,indices:c,flatProps:p,toRemove:[]}),e[O].add(t);for(let n=0;n<C;n++)e[B][n]&&L(e,t,n)&&Q(e,t,n)},L=(e,t,n)=>{const{masks:r,notMasks:o,generations:s}=e[R].get(t);for(let t=0;t<s.length;t++){const i=s[t],a=r[i],c=o[i],l=e[T][i][n];if(c&&0!=(l&c))return!1;if(a&&(l&a)!==a)return!1}return!0},Y=(e,t,n)=>n.every((n=>((e,t,n)=>{const{generationId:r,bitflag:o}=e[q].get(n),{masks:s}=e[R].get(t);return(s[r]&o)===o})(e,t,n))),Q=(e,t,n)=>{const r=e[R].get(t);r.enabled[n]||(r.enabled[n]=!0,r.entities.push(n),r.indices[n]=r.entities.length-1,r.enter&&r.enter(n))},V=(e,t)=>{const n=e[R].get(t);for(;n.toRemove.length;){const e=n.toRemove.pop(),t=n.indices[e];if(t===v)continue;const r=n.entities.pop();r!==e&&(n.entities[t]=r,n.indices[r]=t),n.indices[e]=v}e[P].delete(n)},z=e=>{e[P].forEach((e=>{V(e)}))},F=(e,t,n)=>{const r=e[R].get(t);r.enabled[n]&&(r.enabled[n]=!1,r.exit&&r.exit(n),r.toRemove.push(n),e[P].add(r))},q=Symbol("componentMap"),D=Symbol("de$deferredComponentRemovals"),$=(e,t)=>{e[q].set(t,{generationId:e[T].length-1,bitflag:e[H],store:t}),(e=>{e[H]*=2,e[H]>=2**32&&(e[H]=1,e[T].push(new Uint32Array(e[G])))})(e)},W=(e,t,n)=>{const{generationId:r,bitflag:o}=e[q].get(t);return(e[T][r][n]&o)===o},G=Symbol("size"),H=Symbol("bitflag"),J={bool:"bool",i8:"i8",ui8:"ui8",ui8c:"ui8c",i16:"i16",ui16:"ui16",i32:"i32",ui32:"ui32",f32:"f32",f64:"f64"};exports.Changed=function(e){return function(){return e}},exports.Not=function(e){return function(){return e}},exports.Types=J,exports.addComponent=(e,t,n)=>{if(e[q].has(t)||$(e,t),W(e,t,n))return;const{generationId:r,bitflag:o}=e[q].get(t);e[T][r][n]|=o;e[O].forEach((t=>{const r=t[k];if(!Y(e,t,r))return;L(e,t,n)&&Q(e,t,n)}))},exports.addEntity=e=>{const t=e[j],n=e[G],r=e[B];if(C>=n-n/5){const t=4*Math.ceil(n/2/4);e[G]+=t,e[q].forEach((t=>{A(t.store,e[G])})),e[R].forEach((t=>{t.indices=E(t.indices,e[G]),t.enabled=E(t.enabled,e[G])}))}const o=t.length>0?t.pop():C;return r[o]=1,C++,o},exports.commitRemovals=z,exports.createWorld=(e=1e6)=>{const t={};return t[G]=e,t[B]=new Uint8Array(e),t[T]=[new Uint32Array(e)],t[j]=[],t[H]=1,t[q]=new Map,t[R]=new Map,t[O]=new Set,t[P]=new Set,t[D]=[],t[U]=[],t},exports.defineComponent=e=>((e,n=1e6)=>{const r=Symbol("store");if("Map"===e.constructor.name)return e[i]=n,e;const o=(t,n)=>(M(e[n])?t++:e[n]instanceof Object&&(t+=Object.keys(e[n]).reduce(o,0)),t),p=M(e)?1:Object.keys(e).reduce(o,0),d={[i]:n,[a]:{},[u]:{},[s]:r,[f]:0,[y]:Object.keys(t).reduce(((e,t)=>({...e,[t]:0})),{}),[l]:p,[c]:[]};if("string"==typeof e)return h[r]=Object.assign(w(e,n),d),d[c].push(h[r]),x(h[r]),h[r];if(M(e)){const{type:t,length:n}=e[0];return h[r]=Object.assign(_(d,t,n),d),d[c].push(h[r]),h[r]}if(e instanceof Object&&Object.keys(e).length){const t=(e,r)=>{if("string"==typeof e[r])e[r]=w(e[r],n),d[c].push(e[r]),x(e[r]);else if(M(e[r])){const{type:t,length:n}=e[r][0];e[r]=_(d,t,n)}else e[r]instanceof Object&&(e[r]=Object.keys(e[r]).reduce(t,e[r]));return e};return h[r]=Object.assign(Object.keys(e).reduce(t,e),d),h[r]}return{}})(e),exports.defineDeserializer=e=>{const[t]=I(e);return e=>{const n=new DataView(e);let r=0;const o=n.getUint8(r);r+=1;const s=n.getUint32(r);r+=4;const i=t[o];for(let e=0;e<s;e++){const e=n.getUint32(r);if(r+=4,ArrayBuffer.isView(i[e])){const t=i[e],o=n["get"+t._indexType];r+=t._indexBytes;for(let s=0;s<o;s++){const o=n["get"+t.constructor.name.replace("Array","")](r);r+=t.BYTES_PER_ELEMENT,i[e][s]=o}}else{let t=n["get"+i.constructor.name.replace("Array","")](r);r+=i.BYTES_PER_ELEMENT,i[e]=t}}}},exports.defineQuery=e=>{const t=function(e){e[R].has(t)||N(e,t),V(e,t);const n=e[R].get(t);return n.changedComponents.length?((e,t)=>{const n=e[R].get(t);n.changed.length=0;const r=n.flatProps;for(let e=0;e<n.entities.length;e++){const t=n.entities[e];let o=!1;for(let e=0;e<r.length;e++){const n=r[e];if(ArrayBuffer.isView(n[t]))for(let e=0;e<n[t].length;e++)n[t][e]!==n[t][d][e]&&(o=!0,n[t][d][e]=n[t][e]);else n[t]!==n[d][t]&&(o=!0,n[d][t]=n[t])}o&&n.changed.push(t)}return n.changed})(e,t):n.entities};return t[k]=e,t},exports.defineSerializer=(e,t=5e6)=>{const n=new ArrayBuffer(t),r=new DataView(n),[o,s]=I(e);return e=>{if(!e.length)return;let t=0;for(let n=0;n<o.length;n++){const i=o[n],a=s.has(i);r.setUint8(t,n),t+=1;const c=t;t+=4;let l=0;for(let n=0;n<e.length;n++){const o=e[n];if(!a||i[o]!==i[g][o])if(l++,r.setUint32(t,o),t+=4,ArrayBuffer.isView(i[o])){const e=i[o].constructor.name.replace("Array",""),n=i[o]._indexType,s=i[o]._indexBytes,a=t;t+=1;let c=0;for(let a=0;a<i[o].length;a++){const l=i[o][a];r["set"+n](t,a),t+=s,r["set"+e](t,l),t+=i[o].BYTES_PER_ELEMENT,c++}r["set"+n](a,c)}else{const e=i.constructor.name.replace("Array","");r["set"+e](t,i[o]),t+=i.BYTES_PER_ELEMENT,i[g]||console.log(i),i[g][o]=i[o]}}r.setUint32(c,l)}return n.slice(0,t)}},exports.defineSystem=e=>{const t=t=>{e(t),z(t)};return Object.defineProperty(t,"name",{value:(e.name||"AnonymousSystem")+"_internal",configurable:!0}),t},exports.enterQuery=(e,t,n)=>{e[R].has(t)||N(e,t),e[R].get(t).enter=n},exports.exitQuery=(e,t,n)=>{e[R].has(t)||N(e,t),e[R].get(t).exit=n},exports.hasComponent=W,exports.pipe=(...e)=>t=>{e=Array.isArray(e[0])?e[0]:e;for(let n=0;n<e.length;n++){(0,e[n])(t)}},exports.registerComponent=$,exports.registerComponents=(e,t)=>{t.forEach((t=>$(e,t)))},exports.removeComponent=(e,t,n)=>{const{generationId:r,bitflag:o}=e[q].get(t);if(!(e[T][r][n]&o))return;e[O].forEach((t=>{const r=t[k];if(!Y(e,t,r))return;L(e,t,n)&&F(e,t,n)})),e[T][r][n]&=~o},exports.removeEntity=(e,t)=>{const n=e[O],r=e[j],o=e[B];if(0!==o[t]){n.forEach((n=>{F(e,n,t)})),r.push(t),o[t]=0;for(let n=0;n<e[T].length;n++)e[T][n][t]=0}}; | ||
'use strict'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
const TYPES_ENUM = { | ||
bool: 'bool', | ||
i8: 'i8', | ||
ui8: 'ui8', | ||
ui8c: 'ui8c', | ||
i16: 'i16', | ||
ui16: 'ui16', | ||
i32: 'i32', | ||
ui32: 'ui32', | ||
f32: 'f32', | ||
f64: 'f64' | ||
}; | ||
const TYPES_NAMES = { | ||
bool: 'Uint8', | ||
i8: 'Int8', | ||
ui8: 'Uint8', | ||
ui8c: 'Uint8Clamped', | ||
i16: 'Int16', | ||
ui16: 'Uint16', | ||
i32: 'Int32', | ||
ui32: 'Uint32', | ||
f32: 'Float32', | ||
f64: 'Float64' | ||
}; | ||
const TYPES = { | ||
bool: 'bool', | ||
i8: Int8Array, | ||
ui8: Uint8Array, | ||
ui8c: Uint8ClampedArray, | ||
i16: Int16Array, | ||
ui16: Uint16Array, | ||
i32: Int32Array, | ||
ui32: Uint32Array, | ||
f32: Float32Array, | ||
f64: Float64Array | ||
}; | ||
const UNSIGNED_MAX = { | ||
uint8: 2 ** 8, | ||
uint16: 2 ** 16, | ||
uint32: 2 ** 32 | ||
}; | ||
const roundToMultiple4 = x => Math.ceil(x / 4) * 4; | ||
const $storeRef = Symbol('storeRef'); | ||
const $storeSize = Symbol('storeSize'); | ||
const $storeMaps = Symbol('storeMaps'); | ||
const $storeFlattened = Symbol('storeFlattened'); | ||
const $storeArrayCount = Symbol('storeArrayCount'); | ||
const $storeSubarrays = Symbol('storeSubarrays'); | ||
const $storeCursor = Symbol('storeCursor'); | ||
const $subarrayCursors = Symbol('subarrayCursors'); | ||
const $subarray = Symbol('subarray'); | ||
const $queryShadow = Symbol('queryShadow'); | ||
const $serializeShadow = Symbol('serializeShadow'); | ||
const $indexType = Symbol('indexType'); | ||
const $indexBytes = Symbol('indexBytes'); | ||
const stores = {}; | ||
const resize = (ta, size) => { | ||
const newBuffer = new ArrayBuffer(size * ta.BYTES_PER_ELEMENT); | ||
const newTa = new ta.constructor(newBuffer); | ||
newTa.set(ta, 0); | ||
return newTa; | ||
}; | ||
const resizeRecursive = (store, size) => { | ||
Object.keys(store).forEach(key => { | ||
const ta = store[key]; | ||
if (ta[$subarray]) return;else if (ArrayBuffer.isView(ta)) { | ||
store[key] = resize(ta, size); | ||
store[key][$queryShadow] = resize(ta[$queryShadow], size); | ||
store[key][$serializeShadow] = resize(ta[$serializeShadow], size); | ||
} else if (typeof ta === 'object') { | ||
resizeRecursive(store[key], size); | ||
} | ||
}); | ||
}; | ||
const resizeSubarrays = (store, size) => { | ||
const cursors = store[$subarrayCursors] = {}; | ||
Object.keys(store[$storeSubarrays]).forEach(type => { | ||
const arrayCount = store[$storeArrayCount]; | ||
const length = store[0].length; | ||
const summedBytesPerElement = Array(arrayCount).fill(0).reduce((a, p) => a + TYPES[type].BYTES_PER_ELEMENT, 0); | ||
const summedLength = Array(arrayCount).fill(0).reduce((a, p) => a + length, 0); | ||
const buffer = new ArrayBuffer(roundToMultiple4(summedBytesPerElement * summedLength * size)); | ||
const array = new TYPES[type](buffer); | ||
array.set(store[$storeSubarrays][type].buffer, 0); | ||
store[$storeSubarrays][type] = array; | ||
store[$storeSubarrays][type][$queryShadow] = array.slice(0); | ||
store[$storeSubarrays][type][$serializeShadow] = array.slice(0); | ||
for (let eid = 0; eid < size; eid++) { | ||
const from = cursors[type] + eid * length; | ||
const to = from + length; | ||
store[eid] = store[$storeSubarrays][type].subarray(from, to); | ||
store[eid][$queryShadow] = store[$storeSubarrays][type][$queryShadow].subarray(from, to); | ||
store[eid][$serializeShadow] = store[$storeSubarrays][type][$serializeShadow].subarray(from, to); | ||
store[eid][$subarray] = true; | ||
store[eid][$indexType] = array[$indexType]; | ||
store[eid][$indexBytes] = array[$indexBytes]; | ||
} | ||
}); | ||
}; | ||
const resizeStore = (store, size) => { | ||
store[$storeSize] = size; | ||
resizeRecursive(store, size); | ||
resizeSubarrays(store, size); | ||
}; | ||
const createTypeStore = (type, length) => { | ||
const totalBytes = length * TYPES[type].BYTES_PER_ELEMENT; | ||
const buffer = new ArrayBuffer(totalBytes); | ||
return new TYPES[type](buffer); | ||
}; | ||
const createArrayStore = (store, type, length) => { | ||
const size = store[$storeSize]; | ||
const cursors = store[$subarrayCursors]; | ||
const indexType = length < UNSIGNED_MAX.uint8 ? 'ui8' : length < UNSIGNED_MAX.uint16 ? 'ui16' : 'ui32'; | ||
if (!length) throw new Error('❌ Must define a length for component array.'); | ||
if (!TYPES[type]) throw new Error(`❌ Invalid component array property type ${type}.`); // create buffer for type if it does not already exist | ||
if (!store[$storeSubarrays][type]) { | ||
const arrayCount = store[$storeArrayCount]; | ||
const summedBytesPerElement = Array(arrayCount).fill(0).reduce((a, p) => a + TYPES[type].BYTES_PER_ELEMENT, 0); | ||
const summedLength = Array(arrayCount).fill(0).reduce((a, p) => a + length, 0); | ||
const totalBytes = roundToMultiple4(summedBytesPerElement * summedLength * size); | ||
const buffer = new ArrayBuffer(totalBytes); | ||
const array = new TYPES[type](buffer); | ||
store[$storeSubarrays][type] = array; | ||
store[$storeSubarrays][type][$queryShadow] = array.slice(0); | ||
store[$storeSubarrays][type][$serializeShadow] = array.slice(0); | ||
array[$indexType] = TYPES_NAMES[indexType]; | ||
array[$indexBytes] = TYPES[indexType].BYTES_PER_ELEMENT; | ||
} // pre-generate subarrays for each eid | ||
let end = 0; | ||
for (let eid = 0; eid < size; eid++) { | ||
const from = cursors[type] + eid * length; | ||
const to = from + length; | ||
store[eid] = store[$storeSubarrays][type].subarray(from, to); | ||
store[eid][$queryShadow] = store[$storeSubarrays][type][$queryShadow].subarray(from, to); | ||
store[eid][$serializeShadow] = store[$storeSubarrays][type][$serializeShadow].subarray(from, to); | ||
store[eid][$subarray] = true; | ||
store[eid][$indexType] = TYPES_NAMES[indexType]; | ||
store[eid][$indexBytes] = TYPES[indexType].BYTES_PER_ELEMENT; | ||
end = to; | ||
} | ||
cursors[type] = end; | ||
return store; | ||
}; | ||
const createShadows = store => { | ||
store[$queryShadow] = store.slice(0); | ||
store[$serializeShadow] = store.slice(0); | ||
}; | ||
const isArrayType = x => Array.isArray(x) && typeof x[0] === 'object' && x[0].hasOwnProperty('type') && x[0].hasOwnProperty('length'); | ||
const createStore = (schema, size = 1000000) => { | ||
const $store = Symbol('store'); | ||
schema = JSON.parse(JSON.stringify(schema)); | ||
if (schema.constructor.name === 'Map') { | ||
schema[$storeSize] = size; | ||
return schema; | ||
} | ||
const collectArrayCount = (count, key) => { | ||
if (isArrayType(schema[key])) { | ||
count++; | ||
} else if (schema[key] instanceof Object) { | ||
count += Object.keys(schema[key]).reduce(collectArrayCount, 0); | ||
} | ||
return count; | ||
}; | ||
const arrayCount = isArrayType(schema) ? 1 : Object.keys(schema).reduce(collectArrayCount, 0); | ||
const metadata = { | ||
[$storeSize]: size, | ||
[$storeMaps]: {}, | ||
[$storeSubarrays]: {}, | ||
[$storeRef]: $store, | ||
[$storeCursor]: 0, | ||
[$subarrayCursors]: Object.keys(TYPES).reduce((a, type) => ({ ...a, | ||
[type]: 0 | ||
}), {}), | ||
[$storeArrayCount]: arrayCount, | ||
[$storeFlattened]: [] | ||
}; | ||
if (typeof schema === 'string') { | ||
stores[$store] = Object.assign(createTypeStore(schema, size), metadata); | ||
metadata[$storeFlattened].push(stores[$store]); | ||
createShadows(stores[$store]); | ||
return stores[$store]; | ||
} else if (isArrayType(schema)) { | ||
const { | ||
type, | ||
length | ||
} = schema[0]; | ||
stores[$store] = Object.assign(createArrayStore(metadata, type, length), metadata); | ||
metadata[$storeFlattened].push(stores[$store]); | ||
return stores[$store]; | ||
} else if (schema instanceof Object && Object.keys(schema).length) { | ||
const recursiveTransform = (a, k) => { | ||
if (typeof a[k] === 'string') { | ||
a[k] = createTypeStore(a[k], size); | ||
metadata[$storeFlattened].push(a[k]); | ||
createShadows(a[k]); | ||
} else if (isArrayType(a[k])) { | ||
const { | ||
type, | ||
length | ||
} = a[k][0]; | ||
a[k] = createArrayStore(metadata, type, length); | ||
metadata[$storeFlattened].push(a[k]); | ||
} else if (a[k] instanceof Object) { | ||
a[k] = Object.keys(a[k]).reduce(recursiveTransform, a[k]); | ||
} | ||
return a; | ||
}; | ||
stores[$store] = Object.assign(Object.keys(schema).reduce(recursiveTransform, schema), metadata); | ||
return stores[$store]; | ||
} | ||
return {}; | ||
}; | ||
const $entityMasks = Symbol('entityMasks'); | ||
const $entityEnabled = Symbol('entityEnabled'); | ||
const $deferredEntityRemovals = Symbol('deferredEntityRemovals'); | ||
const $removedEntities = Symbol('removedEntities'); // need a global EID cursor which all worlds and all components know about | ||
// so that world entities can posess entire rows spanning all component tables | ||
let globalEntityCursor = 0; | ||
const getEntityCursor = () => globalEntityCursor; | ||
const resizeWorld = (world, size) => { | ||
world[$size] = size; | ||
world[$componentMap].forEach(c => { | ||
resizeStore(c.store, size); | ||
}); | ||
world[$queryMap].forEach(q => { | ||
q.indices = resize(q.indices, size); | ||
q.enabled = resize(q.enabled, size); | ||
}); | ||
world[$entityEnabled] = resize(world[$entityEnabled], size); | ||
for (let i = 0; i < world[$entityMasks].length; i++) { | ||
const masks = world[$entityMasks][i]; | ||
world[$entityMasks][i] = resize(masks, size); | ||
} | ||
}; | ||
const addEntity = world => { | ||
const removed = world[$removedEntities]; | ||
const size = world[$size]; | ||
const enabled = world[$entityEnabled]; // if data stores are 80% full | ||
if (globalEntityCursor >= size - size / 5) { | ||
// grow by half the original size rounded up to a multiple of 4 | ||
const amount = Math.ceil(size / 2 / 4) * 4; | ||
resizeWorld(world, size + amount); | ||
} | ||
const eid = removed.length > 0 ? removed.pop() : globalEntityCursor; | ||
enabled[eid] = 1; | ||
globalEntityCursor++; | ||
return eid; | ||
}; | ||
const removeEntity = (world, eid) => { | ||
const queries = world[$queries]; | ||
const removed = world[$removedEntities]; | ||
const enabled = world[$entityEnabled]; // Check if entity is already removed | ||
if (enabled[eid] === 0) return; // Remove entity from all queries | ||
// TODO: archetype graph | ||
queries.forEach(query => { | ||
queryRemoveEntity(world, query, eid); | ||
}); // Free the entity | ||
removed.push(eid); | ||
enabled[eid] = 0; // Clear entity bitmasks | ||
for (let i = 0; i < world[$entityMasks].length; i++) world[$entityMasks][i][eid] = 0; | ||
}; | ||
const diff = (world, query) => { | ||
const q = world[$queryMap].get(query); | ||
q.changed.length = 0; | ||
const flat = q.flatProps; | ||
for (let i = 0; i < q.entities.length; i++) { | ||
const eid = q.entities[i]; | ||
let dirty = false; | ||
for (let pid = 0; pid < flat.length; pid++) { | ||
const prop = flat[pid]; | ||
if (ArrayBuffer.isView(prop[eid])) { | ||
for (let i = 0; i < prop[eid].length; i++) { | ||
if (prop[eid][i] !== prop[eid][$queryShadow][i]) { | ||
dirty = true; | ||
prop[eid][$queryShadow][i] = prop[eid][i]; | ||
} | ||
} | ||
} else { | ||
if (prop[eid] !== prop[$queryShadow][eid]) { | ||
dirty = true; | ||
prop[$queryShadow][eid] = prop[eid]; | ||
} | ||
} | ||
} | ||
if (dirty) q.changed.push(eid); | ||
} | ||
return q.changed; | ||
}; | ||
const canonicalize = target => { | ||
let componentProps; | ||
let changedProps = new Set(); | ||
if (Array.isArray(target)) { | ||
componentProps = target.map(p => { | ||
if (typeof p === 'function' && p.name === 'QueryChanged') { | ||
p()[$storeFlattened].forEach(prop => { | ||
changedProps.add(prop); | ||
}); | ||
return p()[$storeFlattened]; | ||
} | ||
return p[$storeFlattened]; | ||
}).reduce((a, v) => a.concat(v), []); | ||
} else { | ||
target[$componentMap].forEach(c => { | ||
componentProps = componentProps.concat(c[$storeFlattened]); | ||
}); | ||
} | ||
return [componentProps, changedProps]; | ||
}; | ||
const defineSerializer = (target, maxBytes = 20_000_000) => { | ||
const [componentProps, changedProps] = canonicalize(target); // TODO: calculate max bytes based on target | ||
const buffer = new ArrayBuffer(maxBytes); | ||
const view = new DataView(buffer); | ||
return ents => { | ||
if (!ents.length) return; | ||
let where = 0; // iterate over component props | ||
for (let pid = 0; pid < componentProps.length; pid++) { | ||
const prop = componentProps[pid]; | ||
const diff = changedProps.has(prop); // write pid | ||
view.setUint8(where, pid); | ||
where += 1; // save space for entity count | ||
const countWhere = where; | ||
where += 4; | ||
let count = 0; // write eid,val | ||
for (let i = 0; i < ents.length; i++) { | ||
const eid = ents[i]; // skip if diffing and no change | ||
if (diff && prop[eid] === prop[$serializeShadow][eid]) { | ||
continue; | ||
} | ||
count++; // write eid | ||
view.setUint32(where, eid); | ||
where += 4; // if property is an array | ||
if (ArrayBuffer.isView(prop[eid])) { | ||
const type = prop[eid].constructor.name.replace('Array', ''); | ||
const indexType = prop[eid][$indexType]; | ||
const indexBytes = prop[eid][$indexBytes]; // add space for count of dirty array elements | ||
const countWhere2 = where; | ||
where += 1; | ||
let count2 = 0; // write array values | ||
for (let i = 0; i < prop[eid].length; i++) { | ||
const value = prop[eid][i]; | ||
if (diff && prop[eid][i] === prop[eid][$serializeShadow][i]) { | ||
continue; | ||
} // write array index | ||
view[`set${indexType}`](where, i); | ||
where += indexBytes; // write value at that index | ||
view[`set${type}`](where, value); | ||
where += prop[eid].BYTES_PER_ELEMENT; | ||
count2++; | ||
} | ||
view[`set${indexType}`](countWhere2, count2); | ||
} else { | ||
// regular property values | ||
const type = prop.constructor.name.replace('Array', ''); // set value next [type] bytes | ||
view[`set${type}`](where, prop[eid]); | ||
where += prop.BYTES_PER_ELEMENT; // sync shadow state | ||
prop[$serializeShadow][eid] = prop[eid]; | ||
} | ||
} | ||
view.setUint32(countWhere, count); | ||
} | ||
return buffer.slice(0, where); | ||
}; | ||
}; | ||
const defineDeserializer = target => { | ||
const [componentProps] = canonicalize(target); | ||
return packet => { | ||
const view = new DataView(packet); | ||
let where = 0; | ||
while (where < packet.byteLength) { | ||
// pid | ||
const pid = view.getUint8(where); | ||
where += 1; // entity count | ||
const entityCount = view.getUint32(where); | ||
where += 4; // typed array | ||
const ta = componentProps[pid]; // Get the properties and set the new state | ||
for (let i = 0; i < entityCount; i++) { | ||
const eid = view.getUint32(where); | ||
where += 4; | ||
if (ArrayBuffer.isView(ta[eid])) { | ||
const array = ta[eid]; | ||
const count = view[`get${array[$indexType]}`](where); | ||
where += array[$indexBytes]; // iterate over count | ||
for (let i = 0; i < count; i++) { | ||
const index = view[`get${array[$indexType]}`](where); | ||
where += array[$indexBytes]; | ||
const value = view[`get${array.constructor.name.replace('Array', '')}`](where); | ||
where += array.BYTES_PER_ELEMENT; | ||
ta[eid][index] = value; | ||
} | ||
} else { | ||
let value = view[`get${ta.constructor.name.replace('Array', '')}`](where); | ||
where += ta.BYTES_PER_ELEMENT; | ||
ta[eid] = value; | ||
} | ||
} | ||
} | ||
}; | ||
}; | ||
function Not(c) { | ||
return function QueryNot() { | ||
return c; | ||
}; | ||
} | ||
function Changed(c) { | ||
return function QueryChanged() { | ||
return c; | ||
}; | ||
} | ||
const $queries = Symbol('queries'); | ||
const $queryMap = Symbol('queryMap'); | ||
const $dirtyQueries = Symbol('$dirtyQueries'); | ||
const $queryComponents = Symbol('queryComponents'); | ||
const NONE = 2 ** 32; | ||
const enterQuery = (world, query, fn) => { | ||
if (!world[$queryMap].has(query)) registerQuery(world, query); | ||
world[$queryMap].get(query).enter = fn; | ||
}; | ||
const exitQuery = (world, query, fn) => { | ||
if (!world[$queryMap].has(query)) registerQuery(world, query); | ||
world[$queryMap].get(query).exit = fn; | ||
}; | ||
const registerQuery = (world, query) => { | ||
if (!world[$queryMap].has(query)) world[$queryMap].set(query, {}); | ||
let components = []; | ||
let notComponents = []; | ||
let changedComponents = []; | ||
query[$queryComponents].forEach(c => { | ||
if (typeof c === 'function') { | ||
if (c.name === 'QueryNot') { | ||
notComponents.push(c()); | ||
} | ||
if (c.name === 'QueryChanged') { | ||
changedComponents.push(c()); | ||
components.push(c()); | ||
} | ||
} else { | ||
components.push(c); | ||
} | ||
}); | ||
const mapComponents = c => world[$componentMap].get(c); | ||
const size = components.reduce((a, c) => c[$storeSize] > a ? c[$storeSize] : a, 0); | ||
const entities = []; | ||
const changed = []; | ||
const indices = new Uint32Array(size).fill(NONE); | ||
const enabled = new Uint8Array(size); | ||
const generations = components.concat(notComponents).map(c => { | ||
if (!world[$componentMap].has(c)) registerComponent(world, c); | ||
return c; | ||
}).map(mapComponents).map(c => c.generationId).reduce((a, v) => { | ||
if (a.includes(v)) return a; | ||
a.push(v); | ||
return a; | ||
}, []); | ||
const reduceBitmasks = (a, c) => { | ||
if (!a[c.generationId]) a[c.generationId] = 0; | ||
a[c.generationId] |= c.bitflag; | ||
return a; | ||
}; | ||
const masks = components.map(mapComponents).reduce(reduceBitmasks, {}); | ||
const notMasks = notComponents.map(mapComponents).reduce((a, c) => { | ||
if (!a[c.generationId]) { | ||
a[c.generationId] = 0; | ||
a[c.generationId] |= c.bitflag; | ||
} | ||
return a; | ||
}, {}); | ||
const flatProps = components.map(c => c._flatten ? c._flatten() : [c]).reduce((a, v) => a.concat(v), []); | ||
const toRemove = []; | ||
Object.assign(world[$queryMap].get(query), { | ||
entities, | ||
changed, | ||
enabled, | ||
components, | ||
notComponents, | ||
changedComponents, | ||
masks, | ||
notMasks, | ||
generations, | ||
indices, | ||
flatProps, | ||
toRemove | ||
}); | ||
world[$queries].add(query); | ||
for (let eid = 0; eid < getEntityCursor(); eid++) { | ||
if (!world[$entityEnabled][eid]) continue; | ||
if (queryCheckEntity(world, query, eid)) { | ||
queryAddEntity(world, query, eid); | ||
} | ||
} | ||
}; | ||
const defineQuery = components => { | ||
const query = function (world) { | ||
if (!world[$queryMap].has(query)) registerQuery(world, query); | ||
queryCommitRemovals(world, query); | ||
const q = world[$queryMap].get(query); | ||
if (q.changedComponents.length) return diff(world, query); | ||
return q.entities; | ||
}; | ||
query[$queryComponents] = components; | ||
return query; | ||
}; // TODO: archetype graph | ||
const queryCheckEntity = (world, query, eid) => { | ||
const { | ||
masks, | ||
notMasks, | ||
generations | ||
} = world[$queryMap].get(query); | ||
for (let i = 0; i < generations.length; i++) { | ||
const generationId = generations[i]; | ||
const qMask = masks[generationId]; | ||
const qNotMask = notMasks[generationId]; | ||
const eMask = world[$entityMasks][generationId][eid]; | ||
if (qNotMask && (eMask & qNotMask) !== 0) { | ||
return false; | ||
} | ||
if (qMask && (eMask & qMask) !== qMask) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
}; | ||
const queryCheckComponent = (world, query, component) => { | ||
const { | ||
generationId, | ||
bitflag | ||
} = world[$componentMap].get(component); | ||
const { | ||
masks | ||
} = world[$queryMap].get(query); | ||
const mask = masks[generationId]; | ||
return (mask & bitflag) === bitflag; | ||
}; | ||
const queryCheckComponents = (world, query, components) => { | ||
return components.every(c => queryCheckComponent(world, query, c)); | ||
}; | ||
const queryAddEntity = (world, query, eid) => { | ||
const q = world[$queryMap].get(query); | ||
if (q.enabled[eid]) return; | ||
q.enabled[eid] = true; | ||
q.entities.push(eid); | ||
q.indices[eid] = q.entities.length - 1; | ||
if (q.enter) q.enter(eid); | ||
}; | ||
const queryCommitRemovals = (world, query) => { | ||
const q = world[$queryMap].get(query); | ||
while (q.toRemove.length) { | ||
const eid = q.toRemove.pop(); | ||
const index = q.indices[eid]; | ||
if (index === NONE) continue; | ||
const swapped = q.entities.pop(); | ||
if (swapped !== eid) { | ||
q.entities[index] = swapped; | ||
q.indices[swapped] = index; | ||
} | ||
q.indices[eid] = NONE; | ||
} | ||
world[$dirtyQueries].delete(q); | ||
}; | ||
const commitRemovals = world => { | ||
world[$dirtyQueries].forEach(q => { | ||
queryCommitRemovals(q); | ||
}); | ||
}; | ||
const queryRemoveEntity = (world, query, eid) => { | ||
const q = world[$queryMap].get(query); | ||
if (!q.enabled[eid]) return; | ||
q.enabled[eid] = false; | ||
if (q.exit) q.exit(eid); | ||
q.toRemove.push(eid); | ||
world[$dirtyQueries].add(q); | ||
}; | ||
const $componentMap = Symbol('componentMap'); | ||
const $deferredComponentRemovals = Symbol('de$deferredComponentRemovals'); | ||
const defineComponent = schema => createStore(schema); | ||
const incrementBitflag = world => { | ||
world[$bitflag] *= 2; | ||
if (world[$bitflag] >= 2 ** 32) { | ||
world[$bitflag] = 1; | ||
world[$entityMasks].push(new Uint32Array(world[$size])); | ||
} | ||
}; | ||
const registerComponent = (world, component) => { | ||
world[$componentMap].set(component, { | ||
generationId: world[$entityMasks].length - 1, | ||
bitflag: world[$bitflag], | ||
store: component | ||
}); | ||
incrementBitflag(world); | ||
}; | ||
const registerComponents = (world, components) => { | ||
components.forEach(c => registerComponent(world, c)); | ||
}; | ||
const hasComponent = (world, component, eid) => { | ||
const { | ||
generationId, | ||
bitflag | ||
} = world[$componentMap].get(component); | ||
const mask = world[$entityMasks][generationId][eid]; | ||
return (mask & bitflag) === bitflag; | ||
}; | ||
const addComponent = (world, component, eid) => { | ||
if (!world[$componentMap].has(component)) registerComponent(world, component); | ||
if (hasComponent(world, component, eid)) return; // Add bitflag to entity bitmask | ||
const { | ||
generationId, | ||
bitflag | ||
} = world[$componentMap].get(component); | ||
world[$entityMasks][generationId][eid] |= bitflag; // Zero out each property value | ||
// component._reset(eid) | ||
// todo: archetype graph | ||
const queries = world[$queries]; | ||
queries.forEach(query => { | ||
const components = query[$queryComponents]; | ||
if (!queryCheckComponents(world, query, components)) return; | ||
const match = queryCheckEntity(world, query, eid); | ||
if (match) queryAddEntity(world, query, eid); | ||
}); | ||
}; | ||
const removeComponent = (world, component, eid) => { | ||
const { | ||
generationId, | ||
bitflag | ||
} = world[$componentMap].get(component); | ||
if (!(world[$entityMasks][generationId][eid] & bitflag)) return; // todo: archetype graph | ||
const queries = world[$queries]; | ||
queries.forEach(query => { | ||
const components = query[$queryComponents]; | ||
if (!queryCheckComponents(world, query, components)) return; | ||
const match = queryCheckEntity(world, query, eid); | ||
if (match) queryRemoveEntity(world, query, eid); | ||
}); // Remove flag from entity bitmask | ||
world[$entityMasks][generationId][eid] &= ~bitflag; | ||
}; | ||
const $size = Symbol('size'); | ||
const $bitflag = Symbol('bitflag'); | ||
const createWorld = (size = 1000000) => { | ||
const world = {}; | ||
world[$size] = size; | ||
world[$entityEnabled] = new Uint8Array(size); | ||
world[$entityMasks] = [new Uint32Array(size)]; | ||
world[$removedEntities] = []; | ||
world[$bitflag] = 1; | ||
world[$componentMap] = new Map(); | ||
world[$queryMap] = new Map(); | ||
world[$queries] = new Set(); | ||
world[$dirtyQueries] = new Set(); | ||
world[$deferredComponentRemovals] = []; | ||
world[$deferredEntityRemovals] = []; | ||
return world; | ||
}; | ||
const defineSystem = update => { | ||
const system = world => { | ||
update(world); | ||
commitRemovals(world); | ||
}; | ||
Object.defineProperty(system, 'name', { | ||
value: (update.name || "AnonymousSystem") + "_internal", | ||
configurable: true | ||
}); | ||
return system; | ||
}; | ||
const pipe = (...fns) => world => { | ||
fns = Array.isArray(fns[0]) ? fns[0] : fns; | ||
for (let i = 0; i < fns.length; i++) { | ||
const fn = fns[i]; | ||
fn(world); | ||
} | ||
}; | ||
const Types = TYPES_ENUM; | ||
exports.Changed = Changed; | ||
exports.Not = Not; | ||
exports.Types = Types; | ||
exports.addComponent = addComponent; | ||
exports.addEntity = addEntity; | ||
exports.commitRemovals = commitRemovals; | ||
exports.createWorld = createWorld; | ||
exports.defineComponent = defineComponent; | ||
exports.defineDeserializer = defineDeserializer; | ||
exports.defineQuery = defineQuery; | ||
exports.defineSerializer = defineSerializer; | ||
exports.defineSystem = defineSystem; | ||
exports.enterQuery = enterQuery; | ||
exports.exitQuery = exitQuery; | ||
exports.hasComponent = hasComponent; | ||
exports.pipe = pipe; | ||
exports.registerComponent = registerComponent; | ||
exports.registerComponents = registerComponents; | ||
exports.removeComponent = removeComponent; | ||
exports.removeEntity = removeEntity; | ||
//# sourceMappingURL=index.min.js.map |
{ | ||
"name": "bitecs", | ||
"version": "0.2.15", | ||
"version": "0.2.16", | ||
"description": "Tiny, data-driven, high performance ECS library written in Javascript", | ||
@@ -5,0 +5,0 @@ "license": "MPL-2.0", |
21
test.js
@@ -30,7 +30,7 @@ import { performance} from'perf_hooks' | ||
const Position = defineComponent(Vector2) | ||
const Velocity = defineComponent({x: [{type:f32,length:2}]}) | ||
const Velocity = defineComponent(Vector2) | ||
// registerComponents(world, [Position, Velocity]) | ||
const query = defineQuery([Position,Velocity]) | ||
const serialize = defineSerializer([Changed(Position), Velocity]) | ||
const serialize = defineSerializer([Position, Velocity]) | ||
const deserialize = defineDeserializer([Position,Velocity]) | ||
@@ -47,4 +47,4 @@ | ||
Position.y[eid]++ | ||
Velocity.x[eid].fill(1) | ||
// Velocity.y[eid].fill(1) | ||
Velocity.x[eid]++ | ||
Velocity.y[eid]++ | ||
} | ||
@@ -74,13 +74,2 @@ | ||
for (let i = 0; i < n; i++) { | ||
Position.x[i]++ | ||
Position.y[i]++ | ||
Velocity.x[i].fill(2) | ||
// Velocity.y[eid].fill(2) | ||
} | ||
// console.log('before',Velocity.x[0]) | ||
then = now() | ||
@@ -90,3 +79,1 @@ deserialize(packet) | ||
// console.log('after',Velocity.x[0]) | ||
console.log(Velocity.x[0][0]) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
218608
1477