Comparing version 0.2.8 to 0.2.9
@@ -1,2 +0,792 @@ | ||
const e={bool:"bool",i8:Int8Array,ui8:Uint8Array,ui8c:Uint8ClampedArray,i16:Int16Array,ui16:Uint16Array,i32:Int32Array,ui32:Uint32Array,f32:Float32Array,f64:Float64Array},t=255,r=65535,n=(e,t)=>{const r=new e.constructor(new ArrayBuffer(e.buffer.byteLength+t*e.BYTES_PER_ELEMENT));return r.set(e.buffer),r},o=e=>4*Math.ceil(e/4),i={},a=Symbol("managerRef"),s=Symbol("managerSize"),f=Symbol("maps"),c=Symbol("subarrays"),y=Symbol("managerCursor"),u=Symbol("managerRemoved"),l=Symbol("shadow"),p=Symbol("entityMasks"),g=Symbol("entityEnabled"),d=Symbol("deferredEntityRemovals"),E=Symbol("removedEntities");let b=0;const m=e=>{const t=e[E],r=e[k],n=e[g];if(b>=r-r/5){const t=4*Math.ceil(r/2/4);e[x].forEach((e=>{e.manager._grow(t)})),e[k]+=t}const o=t.length>0?t.pop():b;return n[o]=1,b++,o},h=(e,t)=>e[d].push(t);function w(e){return function(){return e}}function _(e){return function(){return e}}const A=Symbol("queries"),S=Symbol("queryMap"),B=Symbol("queryComponents"),M=(e,t,r)=>{e[S].get(t)||P(e,t),e[S].get(t).enter=r},T=(e,t,r)=>{e[S].get(t)||P(e,t),e[S].get(t).exit=r},P=(e,t)=>{e[S].get(t)||e[S].set(t,{});let r=[],n=[],o=[];t[B].forEach((e=>{"function"==typeof e?("QueryNot"===e.name&&n.push(e()),"QueryChanged"===e.name&&(o.push(e()),r.push(e()))):r.push(e)}));const i=r.reduce(((e,t)=>t[s]>e?t[s]:e),0),a=new Uint32Array(i),f=new Uint8Array(i),c=r.map((t=>e[x].get(t).generationId)),y=(e,t)=>(e[t.generationId]||(e[t.generationId]=0),e[t.generationId]|=t.bitflag,e),u=r.map((t=>e[x].get(t))).reduce(y,{}),l=n.map((t=>e[x].get(t))).reduce(y,{});Object.assign(e[S].get(t),{entities:[],changed:[],enabled:f,components:r,notComponents:n,changedComponents:o,masks:u,notMasks:l,generations:c,indices:a}),e[A].add(t);for(let r=0;r<b;r++)e[g][r]&&v(e,t,r)&&R(e,t,r)},j=e=>{const t=function(e){e[S].has(t)||P(e,t);const r=e[S].get(t);return r.changedComponents.length?((e,t)=>{const r=e[S].get(t);return r.changed.length=0,r.changedComponents.forEach((e=>{const t=e._flatten();for(let e=0;e<t.length;e++){const n=t[e];for(let e=0;e<r.entities.length;e++){const t=r.entities[e];n[t]!==n[l][t]&&(r.changed.push(t),n[l][t]=n[t])}}})),r.changed})(e,t):r.entities};return t[B]=e,t},v=(e,t,r)=>{const{masks:n,notMasks:o,generations:i}=e[S].get(t);for(let t=0;t<i.length;t++){const a=i[t],s=n[a];o[a];if((e[p][a][r]&s)!==s)return!1}return!0},I=(e,t,r)=>r.every((r=>((e,t,r)=>{const{generationId:n,bitflag:o}=e[x].get(r),{masks:i}=e[S].get(t);return(i[n]&o)===o})(e,t,r))),R=(e,t,r)=>{const n=e[S].get(t);n.enabled[r]||(n.enabled[r]=!0,n.entities.push(r),n.indices[r]=n.entities.length-1,n.enter&&n.enter(r))},O=(e,t,r)=>{const n=e[S].get(t);n.enabled[r]&&(n.enabled[r]=!1,n.entities.splice(n.indices[r]),n.exit&&n.exit(r))},x=Symbol("componentMap"),L=Symbol("de$deferredComponentRemovals"),N=(p,g)=>((p,g=1e5)=>{const d=Symbol("manager");i[d]={[s]:g,[f]:{},[c]:{},[a]:d,[y]:0,[u]:[]};const E=p?Object.keys(p):[];let b=E.filter((e=>Array.isArray(p[e])&&"object"==typeof p[e][0]));const m=Object.keys(e).reduce(((e,t)=>({...e,[t]:0})),{});if("string"==typeof p){const t=p,r=g*e[t].BYTES_PER_ELEMENT,n=new ArrayBuffer(r);i[d]=new e[t](n)}else if(Array.isArray(p)){b=p;const{type:n,length:a}=p[0],s=a<t?"ui8":a<r?"ui16":"ui32";if(!a)throw new Error("❌ Must define a length for component array.");if(!e[n])throw new Error(`❌ Invalid component array property type ${n}.`);if(!i[d][c][n]){const t=b,r=t.reduce(((t,r)=>t+e[n].BYTES_PER_ELEMENT),0),f=t.reduce(((e,t)=>e+a),0),y=new ArrayBuffer(o(r*f*g)),u=new e[n](y);u._indexType=s,u._indexBytes=e[s].BYTES_PER_ELEMENT,i[d][c][n]=u}let f=0;for(let e=0;e<g;e++){const t=m[n]+e*a,r=t+a;i[d][e]=i[d][c][n].subarray(t,r),f=r}m[n]=f,i[d]._reset=e=>i[d][e].fill(0),i[d]._set=(e,t)=>i[d][e].set(t,0)}else E.forEach((t=>{if("bool"===p[t]){const r=e.uint8,n=g*e.uint8.BYTES_PER_ELEMENT,o=new ArrayBuffer(n);i[d][f][t]=p[t],i[d][t]=new r(o),i[d][t]._boolType=!0}else if(Array.isArray(p[t])&&"string"==typeof p[t][0]){const r=e.uint8,n=g*e.uint8.BYTES_PER_ELEMENT,o=new ArrayBuffer(n);i[d][f][t]=p[t],i[d][t]=new r(o)}else if(Array.isArray(p[t])&&"object"==typeof p[t][0]){const{type:r,length:n}=p[0];if(!n)throw new Error("❌ Must define a length for component array.");if(!e[r])throw new Error(`❌ Invalid component array property type ${r}.`);if(!i[d][c][r]){const t=b.filter((e=>p[e][0].type===r)),a=t.reduce(((t,n)=>t+e[r].BYTES_PER_ELEMENT),0),s=t.reduce(((e,t)=>e+n),0),f=new ArrayBuffer(o(a*s*g)),y=new e[r](f);y._indexType=index,y._indexBytes=e[index].BYTES_PER_ELEMENT,i[d][c][r]=y}i[d][t]={};let a=0;for(let e=0;e<g;e++){const o=m[r]+e*n,s=o+n;i[d][t][e]=i[d][c][r].subarray(o,s),a=s}m[r]=a,i[d][t]._reset=e=>i[d][t][e].fill(0),i[d][t]._set=(e,r)=>i[d][t][e].set(r,0)}else if("object"==typeof p[t])i[d][t]=Manager(g,p[t],!1);else if("string"==typeof p[t]){const r=p[t],n=g*e[r].BYTES_PER_ELEMENT,o=new ArrayBuffer(n),a=new ArrayBuffer(n);i[d][t]=new e[r](o),i[d][t][l]=new e[r](a)}else{if("function"!=typeof p[t])throw new Error("ECS Error: invalid property type "+p[t]);{const e=p[t],r=g*e.BYTES_PER_ELEMENT,n=new ArrayBuffer(r);i[d][t]=new e(n)}}}));let h;return Object.defineProperty(i[d],"_schema",{value:p}),Object.defineProperty(i[d],"_mapping",{value:e=>i[d][f][e]}),Object.defineProperty(i[d],"_reset",{value:e=>{for(const t of i[d]._props)ArrayBuffer.isView(i[d][t])?ArrayBuffer.isView(i[d][t][e])?i[d][t][e].fill(0):i[d][t][e]=0:i[d][t]._reset(e)}}),Object.defineProperty(i[d],"_set",{value:(e,t)=>{for(const r in t)i[d]._mapping(r)&&"string"==typeof t[r]?i[d].enum(r,e,t[r]):ArrayBuffer.isView(i[d][r])?i[d][r][e]=t[r]:Array.isArray(t[r])&&ArrayBuffer.isView(i[d][r][e])?i[d][r][e].set(t[r],0):"object"==typeof i[d][r]&&i[d][r]._set(e,t[r])}}),Object.defineProperty(i[d],"_get",{value:e=>{const t={};for(const r of i[d]._props)i[d]._mapping(r)?t[r]=i[d].enum(r,e):ArrayBuffer.isView(i[d][r])?t[r]=i[d][r][e]:"object"==typeof i[d][r]&&(ArrayBuffer.isView(i[d][r][e])?t[r]=Array.from(i[d][r][e]):t[r]=i[d][r]._get(e));return t}}),Object.defineProperty(i[d],"_props",{value:E}),Object.defineProperty(i[d],"_flatten",{value:(e=[])=>{if(h)return h;for(const t of i[d]._props)ArrayBuffer.isView(i[d][t])?e.push(i[d][t]):"object"==typeof i[d][t]&&i[d][t]._flatten(e);return h=e,e}}),Object.defineProperty(i[d],"enum",{value:(e,t,r)=>{const n=i[d]._mapping(e);if(n){if(!r)return n[i[d][e][t]];{const o=n.indexOf(r);if(-1===o)return void console.warn(`Value '${r}' is not part of enum.`);i[d][e][t]=o}}else console.warn("Property is not an enum.")}}),Object.defineProperty(i[d],"_grow",{value:e=>{i[d][s]+=e;for(const t of i[d]._props)ArrayBuffer.isView(i[d][t])?(i[d][t]=n(i[d][t],e),i[d][t][l]=n(i[d][t],e)):"object"==typeof i[d][t]&&(ArrayBuffer.isView(i[d][t][eid])||i[d][t]._grow())}}),i[d]})(p,g),C=(e,t)=>{e[x].set(t,{generationId:e[p].length-1,bitflag:e[$],manager:t}),(e=>{e[$]*=2,e[$]>=Math.pow(2,32)&&(e[$]=1,e[p].push(new Uint32Array(e[k])))})(e)},V=(e,t)=>{t.forEach((t=>C(e,t)))},Y=(e,t,r)=>{if(((e,t,r)=>{const{generationId:n,bitflag:o}=e[x].get(t);return(e[p][n][r]&o)===o})(e,t,r))return;const{generationId:n,bitflag:o}=e[x].get(t);e[p][n][r]|=o;e[A].forEach((t=>{const n=t[B];if(!I(e,t,n))return;v(e,t,r)&&R(e,t,r)}))},U=(e,t,r)=>e[L].push(t,r),k=Symbol("size"),$=Symbol("bitflag"),q=(e=1e5)=>{const t={};return t[k]=e,t[g]=new Uint8Array(t[k]),t[p]=[new Uint32Array(e)],t[E]=[],t[$]=1,t[x]=new Map,t[S]=new Map,t[A]=new Set,t[L]=[],t[d]=[],t},z=(e,t)=>r=>{t(e(r)),(e=>{const t=e[L];for(let r=0;r<t.length;r+=2){const n=t[r],o=t[r+1],{generationId:i,bitflag:a}=e[x].get(n);if(!(e[p][i][o]&a))return;e[p][i][o]&=~a,e[A].forEach((t=>{const r=t[B];I(e,t,r)&&v(e,t,o)&&O(e,t,o)}))}t.length=0})(r),(e=>{const t=e[d],r=e[A],n=e[E],o=e[g];for(let i=0;i<t.length;i++){const a=t[i];if(0!==o[a]){r.forEach((e=>{O(e,a)})),n.push(a),o[a]=0;for(let t=0;t<e[p].length;t++)e[p][t][a]=0}}t.length=0})(r)},F=e=>t=>{for(let r=0;r<e.length;r++){(0,e[r])(t)}},Q={bool:"bool",i8:"i8",ui8:"ui8",ui8c:"ui8c",i16:"i16",ui16:"ui16",i32:"i32",ui32:"ui32",f32:"f32",f64:"f64"};export{_ as Changed,w as Not,Q as Types,Y as addComponent,m as addEntity,q as createWorld,N as defineComponent,j as defineQuery,z as defineSystem,M as enterQuery,T as exitQuery,F as pipe,C as registerComponent,V as registerComponents,U as removeComponent,h 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 = { | ||
bool: 'bool', | ||
i8: Int8Array, | ||
ui8: Uint8Array, | ||
ui8c: Uint8ClampedArray, | ||
i16: Int16Array, | ||
ui16: Uint16Array, | ||
i32: Int32Array, | ||
ui32: Uint32Array, | ||
f32: Float32Array, | ||
f64: Float64Array | ||
}; | ||
const UNSIGNED_MAX = { | ||
uint8: 255, | ||
uint16: 65535, | ||
uint32: 4294967295 | ||
}; | ||
const grow = (ta, amount) => { | ||
const newTa = new ta.constructor(new ArrayBuffer(ta.buffer.byteLength + amount * ta.BYTES_PER_ELEMENT)); | ||
newTa.set(ta.buffer); | ||
return newTa; | ||
}; | ||
const roundToMultiple4 = x => Math.ceil(x / 4) * 4; | ||
const managers = {}; | ||
const $managerRef = Symbol('managerRef'); | ||
const $managerSize = Symbol('managerSize'); | ||
const $managerMaps = Symbol('maps'); | ||
const $managerSubarrays = Symbol('subarrays'); | ||
const $managerCursor = Symbol('managerCursor'); | ||
const $managerRemoved = Symbol('managerRemoved'); | ||
const $queryShadow = Symbol('queryShadow'); | ||
const $serializeShadow = Symbol('$serializeShadow'); | ||
const alloc = (schema, size = 1000000) => { | ||
const $manager = Symbol('manager'); | ||
managers[$manager] = { | ||
[$managerSize]: size, | ||
[$managerMaps]: {}, | ||
[$managerSubarrays]: {}, | ||
[$managerRef]: $manager, | ||
[$managerCursor]: 0, | ||
[$managerRemoved]: [] | ||
}; | ||
const props = schema ? Object.keys(schema) : []; | ||
let arrays = props.filter(p => Array.isArray(schema[p]) && typeof schema[p][0] === 'object'); | ||
const cursors = Object.keys(TYPES).reduce((a, type) => ({ ...a, | ||
[type]: 0 | ||
}), {}); | ||
if (typeof schema === 'string') { | ||
const type = schema; | ||
const totalBytes = size * TYPES[type].BYTES_PER_ELEMENT; | ||
const buffer = new ArrayBuffer(totalBytes); | ||
managers[$manager] = new TYPES[type](buffer); | ||
} else if (Array.isArray(schema)) { | ||
arrays = schema; | ||
const { | ||
type, | ||
length | ||
} = schema[0]; | ||
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 (!managers[$manager][$managerSubarrays][type]) { | ||
const relevantArrays = arrays; | ||
const summedBytesPerElement = relevantArrays.reduce((a, p) => a + TYPES[type].BYTES_PER_ELEMENT, 0); | ||
const summedLength = relevantArrays.reduce((a, p) => a + length, 0); | ||
const buffer = new ArrayBuffer(roundToMultiple4(summedBytesPerElement * summedLength * size)); | ||
const array = new TYPES[type](buffer); | ||
array._indexType = indexType; | ||
array._indexBytes = TYPES[indexType].BYTES_PER_ELEMENT; | ||
managers[$manager][$managerSubarrays][type] = array; | ||
} // 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; | ||
managers[$manager][eid] = managers[$manager][$managerSubarrays][type].subarray(from, to); | ||
end = to; | ||
} | ||
cursors[type] = end; | ||
managers[$manager]._reset = eid => managers[$manager][eid].fill(0); | ||
managers[$manager]._set = (eid, values) => managers[$manager][eid].set(values, 0); | ||
} else props.forEach(prop => { | ||
// Boolean Type | ||
if (schema[prop] === 'bool') { | ||
const Type = TYPES.uint8; | ||
const totalBytes = size * TYPES.uint8.BYTES_PER_ELEMENT; | ||
const buffer = new ArrayBuffer(totalBytes); | ||
managers[$manager][$managerMaps][prop] = schema[prop]; | ||
managers[$manager][prop] = new Type(buffer); | ||
managers[$manager][prop]._boolType = true; // Enum Type | ||
} else if (Array.isArray(schema[prop]) && typeof schema[prop][0] === 'string') { | ||
const Type = TYPES.uint8; | ||
const totalBytes = size * TYPES.uint8.BYTES_PER_ELEMENT; | ||
const buffer = new ArrayBuffer(totalBytes); | ||
managers[$manager][$managerMaps][prop] = schema[prop]; | ||
managers[$manager][prop] = new Type(buffer); // Array Type | ||
} else if (Array.isArray(schema[prop]) && typeof schema[prop][0] === 'object') { | ||
const { | ||
type, | ||
length | ||
} = schema[0]; | ||
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 (!managers[$manager][$managerSubarrays][type]) { | ||
const relevantArrays = arrays.filter(p => schema[p][0].type === type); | ||
const summedBytesPerElement = relevantArrays.reduce((a, p) => a + TYPES[type].BYTES_PER_ELEMENT, 0); | ||
const summedLength = relevantArrays.reduce((a, p) => a + length, 0); | ||
const buffer = new ArrayBuffer(roundToMultiple4(summedBytesPerElement * summedLength * size)); | ||
const array = new TYPES[type](buffer); | ||
array._indexType = index; | ||
array._indexBytes = TYPES[index].BYTES_PER_ELEMENT; | ||
managers[$manager][$managerSubarrays][type] = array; | ||
} // pre-generate subarrays for each eid | ||
managers[$manager][prop] = {}; | ||
let end = 0; | ||
for (let eid = 0; eid < size; eid++) { | ||
const from = cursors[type] + eid * length; | ||
const to = from + length; | ||
managers[$manager][prop][eid] = managers[$manager][$managerSubarrays][type].subarray(from, to); | ||
end = to; | ||
} | ||
cursors[type] = end; | ||
managers[$manager][prop]._reset = eid => managers[$manager][prop][eid].fill(0); | ||
managers[$manager][prop]._set = (eid, values) => managers[$manager][prop][eid].set(values, 0); // Object Type | ||
} else if (typeof schema[prop] === 'object') { | ||
managers[$manager][prop] = Manager(size, schema[prop], false); // String Type | ||
} else if (typeof schema[prop] === 'string') { | ||
const type = schema[prop]; | ||
const totalBytes = size * TYPES[type].BYTES_PER_ELEMENT; | ||
const buffer = new ArrayBuffer(totalBytes); | ||
const queryShadowBuffer = new ArrayBuffer(totalBytes); | ||
const serializeShadowBuffer = new ArrayBuffer(totalBytes); | ||
managers[$manager][prop] = new TYPES[type](buffer); | ||
managers[$manager][prop][$queryShadow] = new TYPES[type](queryShadowBuffer); | ||
managers[$manager][prop][$serializeShadow] = new TYPES[type](serializeShadowBuffer); // TypedArray Type | ||
} else if (typeof schema[prop] === 'function') { | ||
const Type = schema[prop]; | ||
const totalBytes = size * Type.BYTES_PER_ELEMENT; | ||
const buffer = new ArrayBuffer(totalBytes); | ||
managers[$manager][prop] = new Type(buffer); | ||
} else { | ||
throw new Error(`ECS Error: invalid property type ${schema[prop]}`); | ||
} | ||
}); // methods | ||
Object.defineProperty(managers[$manager], '_schema', { | ||
value: schema | ||
}); | ||
Object.defineProperty(managers[$manager], '_mapping', { | ||
value: prop => managers[$manager][$managerMaps][prop] | ||
}); // Recursively set all values to 0 | ||
Object.defineProperty(managers[$manager], '_reset', { | ||
value: eid => { | ||
for (const prop of managers[$manager]._props) { | ||
if (ArrayBuffer.isView(managers[$manager][prop])) { | ||
if (ArrayBuffer.isView(managers[$manager][prop][eid])) { | ||
managers[$manager][prop][eid].fill(0); | ||
} else { | ||
managers[$manager][prop][eid] = 0; | ||
} | ||
} else { | ||
managers[$manager][prop]._reset(eid); | ||
} | ||
} | ||
} | ||
}); // Recursively set all values from a supplied object | ||
Object.defineProperty(managers[$manager], '_set', { | ||
value: (eid, values) => { | ||
for (const prop in values) { | ||
const mapping = managers[$manager]._mapping(prop); | ||
if (mapping && typeof values[prop] === 'string') { | ||
managers[$manager].enum(prop, eid, values[prop]); | ||
} else if (ArrayBuffer.isView(managers[$manager][prop])) { | ||
managers[$manager][prop][eid] = values[prop]; | ||
} else if (Array.isArray(values[prop]) && ArrayBuffer.isView(managers[$manager][prop][eid])) { | ||
managers[$manager][prop][eid].set(values[prop], 0); | ||
} else if (typeof managers[$manager][prop] === 'object') { | ||
managers[$manager][prop]._set(eid, values[prop]); | ||
} | ||
} | ||
} | ||
}); | ||
Object.defineProperty(managers[$manager], '_get', { | ||
value: eid => { | ||
const obj = {}; | ||
for (const prop of managers[$manager]._props) { | ||
const mapping = managers[$manager]._mapping(prop); | ||
if (mapping) { | ||
obj[prop] = managers[$manager].enum(prop, eid); | ||
} else if (ArrayBuffer.isView(managers[$manager][prop])) { | ||
obj[prop] = managers[$manager][prop][eid]; | ||
} else if (typeof managers[$manager][prop] === 'object') { | ||
if (ArrayBuffer.isView(managers[$manager][prop][eid])) { | ||
obj[prop] = Array.from(managers[$manager][prop][eid]); | ||
} else { | ||
obj[prop] = managers[$manager][prop]._get(eid); | ||
} | ||
} | ||
} | ||
return obj; | ||
} | ||
}); | ||
Object.defineProperty(managers[$manager], '_props', { | ||
value: props | ||
}); // Aggregate all typedArrays into single kvp array (memoized) | ||
let flattened; | ||
Object.defineProperty(managers[$manager], '_flatten', { | ||
value: (flat = []) => { | ||
if (flattened) return flattened; | ||
for (const prop of managers[$manager]._props) { | ||
if (ArrayBuffer.isView(managers[$manager][prop])) { | ||
flat.push(managers[$manager][prop]); | ||
} else if (typeof managers[$manager][prop] === 'object') { | ||
managers[$manager][prop]._flatten(flat); | ||
} | ||
} | ||
flattened = flat; | ||
return flat; | ||
} | ||
}); | ||
Object.defineProperty(managers[$manager], 'enum', { | ||
value: (prop, eid, value) => { | ||
const mapping = managers[$manager]._mapping(prop); | ||
if (!mapping) { | ||
console.warn('Property is not an enum.'); | ||
return undefined; | ||
} | ||
if (value) { | ||
const index = mapping.indexOf(value); | ||
if (index === -1) { | ||
console.warn(`Value '${value}' is not part of enum.`); | ||
return undefined; | ||
} | ||
managers[$manager][prop][eid] = index; | ||
} else { | ||
return mapping[managers[$manager][prop][eid]]; | ||
} | ||
} | ||
}); | ||
Object.defineProperty(managers[$manager], '_grow', { | ||
value: amount => { | ||
managers[$manager][$managerSize] += amount; | ||
for (const prop of managers[$manager]._props) { | ||
if (ArrayBuffer.isView(managers[$manager][prop])) { | ||
managers[$manager][prop] = grow(managers[$manager][prop], amount); | ||
managers[$manager][prop][$queryShadow] = grow(managers[$manager][prop], amount); | ||
} else if (typeof managers[$manager][prop] === 'object') { | ||
if (ArrayBuffer.isView(managers[$manager][prop][eid])) ; else { | ||
managers[$manager][prop]._grow(); | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
return managers[$manager]; | ||
}; | ||
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 addEntity = world => { | ||
const removed = world[$removedEntities]; | ||
const size = world[$size]; | ||
const enabled = world[$entityEnabled]; | ||
if (globalEntityCursor >= size - size / 5) { | ||
// if 80% full | ||
const amount = Math.ceil(size / 2 / 4) * 4; // grow by half the original size rounded up to a multiple of 4 | ||
// grow data stores | ||
world[$componentMap].forEach(component => { | ||
component.manager._grow(amount); | ||
}); | ||
world[$size] += amount; // TODO: grow metadata on world mappings for queries/components enabled/etc | ||
} | ||
const eid = removed.length > 0 ? removed.pop() : globalEntityCursor; | ||
enabled[eid] = 1; | ||
globalEntityCursor++; | ||
return eid; | ||
}; | ||
const removeEntity = (world, eid) => world[$deferredEntityRemovals].push(eid); | ||
const commitEntityRemovals = world => { | ||
const deferred = world[$deferredEntityRemovals]; | ||
const queries = world[$queries]; | ||
const removed = world[$removedEntities]; | ||
const enabled = world[$entityEnabled]; | ||
for (let i = 0; i < deferred.length; i++) { | ||
const eid = deferred[i]; // Check if entity is already removed | ||
if (enabled[eid] === 0) continue; // Remove entity from all queries | ||
// TODO: archetype graph | ||
queries.forEach(query => { | ||
queryRemoveEntity(query, eid); | ||
}); // Free the entity | ||
removed.push(eid); | ||
enabled[eid] = 0; // Clear component bitmasks | ||
for (let i = 0; i < world[$entityMasks].length; i++) world[$entityMasks][i][eid] = 0; | ||
} | ||
deferred.length = 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 (p._flatten) { | ||
return p._flatten(); | ||
} else if (typeof p === 'function' && p.name === 'QueryChanged') { | ||
p = p(); | ||
if (p._flatten) { | ||
let props = p._flatten(); | ||
props.forEach(x => changedProps.add(x)); | ||
return props; | ||
} | ||
changedProps.add(p); | ||
return [p]; | ||
} | ||
}).reduce((a, v) => a.concat(v), []); | ||
} else { | ||
target[$componentMap].forEach(c => { | ||
componentProps = componentProps.concat(c._flatten()); | ||
}); | ||
} | ||
return [componentProps, changedProps]; | ||
}; | ||
const defineSerializer = (target, maxBytes = 5000000) => { | ||
const buffer = new ArrayBuffer(maxBytes); | ||
const view = new DataView(buffer); | ||
const [componentProps, changedProps] = canonicalize(target); | ||
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; | ||
} | ||
prop[$serializeShadow][eid] = prop[eid]; | ||
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 val = prop[eid][i]; // write array index | ||
view[`set${indexType}`](where, i); | ||
where += indexBytes; // write value at that index | ||
view[`set${type}`](where, val); | ||
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; | ||
} | ||
} | ||
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; // 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 += array._indexBytes; // iterate over count | ||
for (let i = 0; i < count; i++) { | ||
const value = view[`get${array.constructor.name.replace('Array', '')}`](where); | ||
where += array.BYTES_PER_ELEMENT; | ||
ta[eid][i] = 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 $queryComponents = Symbol('queryComponents'); | ||
const enterQuery = (world, query, fn) => { | ||
if (!world[$queryMap].get(query)) registerQuery(world, query); | ||
world[$queryMap].get(query).enter = fn; | ||
}; | ||
const exitQuery = (world, query, fn) => { | ||
if (!world[$queryMap].get(query)) registerQuery(world, query); | ||
world[$queryMap].get(query).exit = fn; | ||
}; | ||
const registerQuery = (world, query) => { | ||
if (!world[$queryMap].get(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 size = components.reduce((a, c) => c[$managerSize] > a ? c[$managerSize] : a, 0); | ||
const entities = []; | ||
const changed = []; | ||
const indices = new Uint32Array(size); | ||
const enabled = new Uint8Array(size); | ||
const generations = components.map(c => world[$componentMap].get(c).generationId); | ||
const mapComponents = c => world[$componentMap].get(c); | ||
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(c => world[$componentMap].get(c)).reduce(reduceBitmasks, {}); | ||
const flatProps = components.map(c => !ArrayBuffer.isView(c) ? c._flatten() : [c]).reduce((a, v) => a.concat(v), []); | ||
Object.assign(world[$queryMap].get(query), { | ||
entities, | ||
changed, | ||
enabled, | ||
components, | ||
notComponents, | ||
changedComponents, | ||
masks, | ||
notMasks, | ||
generations, | ||
indices, | ||
flatProps | ||
}); | ||
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); | ||
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 ((eMask & qMask) !== qMask) { | ||
return false; | ||
} // if ((eMask | ~qNotMask) === ~qNotMask) { | ||
// 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 queryRemoveEntity = (world, query, eid) => { | ||
const q = world[$queryMap].get(query); | ||
if (!q.enabled[eid]) return; | ||
q.enabled[eid] = false; | ||
q.entities.splice(q.indices[eid]); | ||
if (q.exit) q.exit(eid); | ||
}; | ||
const $componentMap = Symbol('componentMap'); | ||
const $deferredComponentRemovals = Symbol('de$deferredComponentRemovals'); | ||
const defineComponent = (schema, n) => alloc(schema, n); | ||
const incrementBitflag = world => { | ||
world[$bitflag] *= 2; | ||
if (world[$bitflag] >= Math.pow(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], | ||
manager: 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 (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) => world[$deferredComponentRemovals].push(component, eid); | ||
const commitComponentRemovals = world => { | ||
const deferredComponentRemovals = world[$deferredComponentRemovals]; | ||
for (let i = 0; i < deferredComponentRemovals.length; i += 2) { | ||
const component = deferredComponentRemovals[i]; | ||
const eid = deferredComponentRemovals[i + 1]; | ||
const { | ||
generationId, | ||
bitflag | ||
} = world[$componentMap].get(component); | ||
if (!(world[$entityMasks][generationId][eid] & bitflag)) return; // Remove flag from entity bitmask | ||
world[$entityMasks][generationId][eid] &= ~bitflag; // 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); | ||
}); | ||
} | ||
deferredComponentRemovals.length = 0; | ||
}; | ||
const $size = Symbol('size'); | ||
const $bitflag = Symbol('bitflag'); | ||
const createWorld = (size = 1000000) => { | ||
const world = {}; | ||
world[$size] = size; | ||
world[$entityEnabled] = new Uint8Array(world[$size]); | ||
world[$entityMasks] = [new Uint32Array(size)]; | ||
world[$removedEntities] = []; | ||
world[$bitflag] = 1; | ||
world[$componentMap] = new Map(); | ||
world[$queryMap] = new Map(); | ||
world[$queries] = new Set(); | ||
world[$deferredComponentRemovals] = []; | ||
world[$deferredEntityRemovals] = []; | ||
return world; | ||
}; | ||
const defineSystem = update => { | ||
const system = world => { | ||
update(world); | ||
commitComponentRemovals(world); | ||
commitEntityRemovals(world); | ||
}; | ||
Object.defineProperty(system, 'name', { | ||
value: (update.name || "AnonymousSystem") + "_internal", | ||
configurable: true | ||
}); | ||
return system; | ||
}; | ||
const pipe = fns => world => { | ||
for (let i = 0; i < fns.length; i++) { | ||
const fn = fns[i]; | ||
fn(world); | ||
} | ||
}; | ||
const Types = TYPES_ENUM; | ||
export { Changed, Not, Types, addComponent, addEntity, createWorld, defineComponent, defineDeserializer, defineQuery, defineSerializer, defineSystem, enterQuery, exitQuery, pipe, registerComponent, registerComponents, removeComponent, removeEntity }; | ||
//# sourceMappingURL=index.es.js.map |
@@ -1,2 +0,813 @@ | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});const e={bool:"bool",i8:Int8Array,ui8:Uint8Array,ui8c:Uint8ClampedArray,i16:Int16Array,ui16:Uint16Array,i32:Int32Array,ui32:Uint32Array,f32:Float32Array,f64:Float64Array},t=255,r=65535,n=(e,t)=>{const r=new e.constructor(new ArrayBuffer(e.buffer.byteLength+t*e.BYTES_PER_ELEMENT));return r.set(e.buffer),r},o=e=>4*Math.ceil(e/4),i={},s=Symbol("managerRef"),a=Symbol("managerSize"),f=Symbol("maps"),y=Symbol("subarrays"),c=Symbol("managerCursor"),u=Symbol("managerRemoved"),p=Symbol("shadow"),l=Symbol("entityMasks"),g=Symbol("entityEnabled"),d=Symbol("deferredEntityRemovals"),m=Symbol("removedEntities");let E=0;const b=Symbol("queries"),h=Symbol("queryMap"),_=Symbol("queryComponents"),w=(e,t)=>{e[h].get(t)||e[h].set(t,{});let r=[],n=[],o=[];t[_].forEach((e=>{"function"==typeof e?("QueryNot"===e.name&&n.push(e()),"QueryChanged"===e.name&&(o.push(e()),r.push(e()))):r.push(e)}));const i=r.reduce(((e,t)=>t[a]>e?t[a]:e),0),s=new Uint32Array(i),f=new Uint8Array(i),y=r.map((t=>e[M].get(t).generationId)),c=(e,t)=>(e[t.generationId]||(e[t.generationId]=0),e[t.generationId]|=t.bitflag,e),u=r.map((t=>e[M].get(t))).reduce(c,{}),p=n.map((t=>e[M].get(t))).reduce(c,{});Object.assign(e[h].get(t),{entities:[],changed:[],enabled:f,components:r,notComponents:n,changedComponents:o,masks:u,notMasks:p,generations:y,indices:s}),e[b].add(t);for(let r=0;r<E;r++)e[g][r]&&A(e,t,r)&&B(e,t,r)},A=(e,t,r)=>{const{masks:n,notMasks:o,generations:i}=e[h].get(t);for(let t=0;t<i.length;t++){const s=i[t],a=n[s];o[s];if((e[l][s][r]&a)!==a)return!1}return!0},S=(e,t,r)=>r.every((r=>((e,t,r)=>{const{generationId:n,bitflag:o}=e[M].get(r),{masks:i}=e[h].get(t);return(i[n]&o)===o})(e,t,r))),B=(e,t,r)=>{const n=e[h].get(t);n.enabled[r]||(n.enabled[r]=!0,n.entities.push(r),n.indices[r]=n.entities.length-1,n.enter&&n.enter(r))},x=(e,t,r)=>{const n=e[h].get(t);n.enabled[r]&&(n.enabled[r]=!1,n.entities.splice(n.indices[r]),n.exit&&n.exit(r))},M=Symbol("componentMap"),T=Symbol("de$deferredComponentRemovals"),v=(e,t)=>{e[M].set(t,{generationId:e[l].length-1,bitflag:e[j],manager:t}),(e=>{e[j]*=2,e[j]>=Math.pow(2,32)&&(e[j]=1,e[l].push(new Uint32Array(e[P])))})(e)},P=Symbol("size"),j=Symbol("bitflag"),C={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=C,exports.addComponent=(e,t,r)=>{if(((e,t,r)=>{const{generationId:n,bitflag:o}=e[M].get(t);return(e[l][n][r]&o)===o})(e,t,r))return;const{generationId:n,bitflag:o}=e[M].get(t);e[l][n][r]|=o;e[b].forEach((t=>{const n=t[_];if(!S(e,t,n))return;A(e,t,r)&&B(e,t,r)}))},exports.addEntity=e=>{const t=e[m],r=e[P],n=e[g];if(E>=r-r/5){const t=4*Math.ceil(r/2/4);e[M].forEach((e=>{e.manager._grow(t)})),e[P]+=t}const o=t.length>0?t.pop():E;return n[o]=1,E++,o},exports.createWorld=(e=1e5)=>{const t={};return t[P]=e,t[g]=new Uint8Array(t[P]),t[l]=[new Uint32Array(e)],t[m]=[],t[j]=1,t[M]=new Map,t[h]=new Map,t[b]=new Set,t[T]=[],t[d]=[],t},exports.defineComponent=(l,g)=>((l,g=1e5)=>{const d=Symbol("manager");i[d]={[a]:g,[f]:{},[y]:{},[s]:d,[c]:0,[u]:[]};const m=l?Object.keys(l):[];let E=m.filter((e=>Array.isArray(l[e])&&"object"==typeof l[e][0]));const b=Object.keys(e).reduce(((e,t)=>({...e,[t]:0})),{});if("string"==typeof l){const t=l,r=g*e[t].BYTES_PER_ELEMENT,n=new ArrayBuffer(r);i[d]=new e[t](n)}else if(Array.isArray(l)){E=l;const{type:n,length:s}=l[0],a=s<t?"ui8":s<r?"ui16":"ui32";if(!s)throw new Error("❌ Must define a length for component array.");if(!e[n])throw new Error(`❌ Invalid component array property type ${n}.`);if(!i[d][y][n]){const t=E,r=t.reduce(((t,r)=>t+e[n].BYTES_PER_ELEMENT),0),f=t.reduce(((e,t)=>e+s),0),c=new ArrayBuffer(o(r*f*g)),u=new e[n](c);u._indexType=a,u._indexBytes=e[a].BYTES_PER_ELEMENT,i[d][y][n]=u}let f=0;for(let e=0;e<g;e++){const t=b[n]+e*s,r=t+s;i[d][e]=i[d][y][n].subarray(t,r),f=r}b[n]=f,i[d]._reset=e=>i[d][e].fill(0),i[d]._set=(e,t)=>i[d][e].set(t,0)}else m.forEach((t=>{if("bool"===l[t]){const r=e.uint8,n=g*e.uint8.BYTES_PER_ELEMENT,o=new ArrayBuffer(n);i[d][f][t]=l[t],i[d][t]=new r(o),i[d][t]._boolType=!0}else if(Array.isArray(l[t])&&"string"==typeof l[t][0]){const r=e.uint8,n=g*e.uint8.BYTES_PER_ELEMENT,o=new ArrayBuffer(n);i[d][f][t]=l[t],i[d][t]=new r(o)}else if(Array.isArray(l[t])&&"object"==typeof l[t][0]){const{type:r,length:n}=l[0];if(!n)throw new Error("❌ Must define a length for component array.");if(!e[r])throw new Error(`❌ Invalid component array property type ${r}.`);if(!i[d][y][r]){const t=E.filter((e=>l[e][0].type===r)),s=t.reduce(((t,n)=>t+e[r].BYTES_PER_ELEMENT),0),a=t.reduce(((e,t)=>e+n),0),f=new ArrayBuffer(o(s*a*g)),c=new e[r](f);c._indexType=index,c._indexBytes=e[index].BYTES_PER_ELEMENT,i[d][y][r]=c}i[d][t]={};let s=0;for(let e=0;e<g;e++){const o=b[r]+e*n,a=o+n;i[d][t][e]=i[d][y][r].subarray(o,a),s=a}b[r]=s,i[d][t]._reset=e=>i[d][t][e].fill(0),i[d][t]._set=(e,r)=>i[d][t][e].set(r,0)}else if("object"==typeof l[t])i[d][t]=Manager(g,l[t],!1);else if("string"==typeof l[t]){const r=l[t],n=g*e[r].BYTES_PER_ELEMENT,o=new ArrayBuffer(n),s=new ArrayBuffer(n);i[d][t]=new e[r](o),i[d][t][p]=new e[r](s)}else{if("function"!=typeof l[t])throw new Error("ECS Error: invalid property type "+l[t]);{const e=l[t],r=g*e.BYTES_PER_ELEMENT,n=new ArrayBuffer(r);i[d][t]=new e(n)}}}));let h;return Object.defineProperty(i[d],"_schema",{value:l}),Object.defineProperty(i[d],"_mapping",{value:e=>i[d][f][e]}),Object.defineProperty(i[d],"_reset",{value:e=>{for(const t of i[d]._props)ArrayBuffer.isView(i[d][t])?ArrayBuffer.isView(i[d][t][e])?i[d][t][e].fill(0):i[d][t][e]=0:i[d][t]._reset(e)}}),Object.defineProperty(i[d],"_set",{value:(e,t)=>{for(const r in t)i[d]._mapping(r)&&"string"==typeof t[r]?i[d].enum(r,e,t[r]):ArrayBuffer.isView(i[d][r])?i[d][r][e]=t[r]:Array.isArray(t[r])&&ArrayBuffer.isView(i[d][r][e])?i[d][r][e].set(t[r],0):"object"==typeof i[d][r]&&i[d][r]._set(e,t[r])}}),Object.defineProperty(i[d],"_get",{value:e=>{const t={};for(const r of i[d]._props)i[d]._mapping(r)?t[r]=i[d].enum(r,e):ArrayBuffer.isView(i[d][r])?t[r]=i[d][r][e]:"object"==typeof i[d][r]&&(ArrayBuffer.isView(i[d][r][e])?t[r]=Array.from(i[d][r][e]):t[r]=i[d][r]._get(e));return t}}),Object.defineProperty(i[d],"_props",{value:m}),Object.defineProperty(i[d],"_flatten",{value:(e=[])=>{if(h)return h;for(const t of i[d]._props)ArrayBuffer.isView(i[d][t])?e.push(i[d][t]):"object"==typeof i[d][t]&&i[d][t]._flatten(e);return h=e,e}}),Object.defineProperty(i[d],"enum",{value:(e,t,r)=>{const n=i[d]._mapping(e);if(n){if(!r)return n[i[d][e][t]];{const o=n.indexOf(r);if(-1===o)return void console.warn(`Value '${r}' is not part of enum.`);i[d][e][t]=o}}else console.warn("Property is not an enum.")}}),Object.defineProperty(i[d],"_grow",{value:e=>{i[d][a]+=e;for(const t of i[d]._props)ArrayBuffer.isView(i[d][t])?(i[d][t]=n(i[d][t],e),i[d][t][p]=n(i[d][t],e)):"object"==typeof i[d][t]&&(ArrayBuffer.isView(i[d][t][eid])||i[d][t]._grow())}}),i[d]})(l,g),exports.defineQuery=e=>{const t=function(e){e[h].has(t)||w(e,t);const r=e[h].get(t);return r.changedComponents.length?((e,t)=>{const r=e[h].get(t);return r.changed.length=0,r.changedComponents.forEach((e=>{const t=e._flatten();for(let e=0;e<t.length;e++){const n=t[e];for(let e=0;e<r.entities.length;e++){const t=r.entities[e];n[t]!==n[p][t]&&(r.changed.push(t),n[p][t]=n[t])}}})),r.changed})(e,t):r.entities};return t[_]=e,t},exports.defineSystem=(e,t)=>r=>{t(e(r)),(e=>{const t=e[T];for(let r=0;r<t.length;r+=2){const n=t[r],o=t[r+1],{generationId:i,bitflag:s}=e[M].get(n);if(!(e[l][i][o]&s))return;e[l][i][o]&=~s,e[b].forEach((t=>{const r=t[_];S(e,t,r)&&A(e,t,o)&&x(e,t,o)}))}t.length=0})(r),(e=>{const t=e[d],r=e[b],n=e[m],o=e[g];for(let i=0;i<t.length;i++){const s=t[i];if(0!==o[s]){r.forEach((e=>{x(e,s)})),n.push(s),o[s]=0;for(let t=0;t<e[l].length;t++)e[l][t][s]=0}}t.length=0})(r)},exports.enterQuery=(e,t,r)=>{e[h].get(t)||w(e,t),e[h].get(t).enter=r},exports.exitQuery=(e,t,r)=>{e[h].get(t)||w(e,t),e[h].get(t).exit=r},exports.pipe=e=>t=>{for(let r=0;r<e.length;r++){(0,e[r])(t)}},exports.registerComponent=v,exports.registerComponents=(e,t)=>{t.forEach((t=>v(e,t)))},exports.removeComponent=(e,t,r)=>e[T].push(t,r),exports.removeEntity=(e,t)=>e[d].push(t); | ||
'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 = { | ||
bool: 'bool', | ||
i8: Int8Array, | ||
ui8: Uint8Array, | ||
ui8c: Uint8ClampedArray, | ||
i16: Int16Array, | ||
ui16: Uint16Array, | ||
i32: Int32Array, | ||
ui32: Uint32Array, | ||
f32: Float32Array, | ||
f64: Float64Array | ||
}; | ||
const UNSIGNED_MAX = { | ||
uint8: 255, | ||
uint16: 65535, | ||
uint32: 4294967295 | ||
}; | ||
const grow = (ta, amount) => { | ||
const newTa = new ta.constructor(new ArrayBuffer(ta.buffer.byteLength + amount * ta.BYTES_PER_ELEMENT)); | ||
newTa.set(ta.buffer); | ||
return newTa; | ||
}; | ||
const roundToMultiple4 = x => Math.ceil(x / 4) * 4; | ||
const managers = {}; | ||
const $managerRef = Symbol('managerRef'); | ||
const $managerSize = Symbol('managerSize'); | ||
const $managerMaps = Symbol('maps'); | ||
const $managerSubarrays = Symbol('subarrays'); | ||
const $managerCursor = Symbol('managerCursor'); | ||
const $managerRemoved = Symbol('managerRemoved'); | ||
const $queryShadow = Symbol('queryShadow'); | ||
const $serializeShadow = Symbol('$serializeShadow'); | ||
const alloc = (schema, size = 1000000) => { | ||
const $manager = Symbol('manager'); | ||
managers[$manager] = { | ||
[$managerSize]: size, | ||
[$managerMaps]: {}, | ||
[$managerSubarrays]: {}, | ||
[$managerRef]: $manager, | ||
[$managerCursor]: 0, | ||
[$managerRemoved]: [] | ||
}; | ||
const props = schema ? Object.keys(schema) : []; | ||
let arrays = props.filter(p => Array.isArray(schema[p]) && typeof schema[p][0] === 'object'); | ||
const cursors = Object.keys(TYPES).reduce((a, type) => ({ ...a, | ||
[type]: 0 | ||
}), {}); | ||
if (typeof schema === 'string') { | ||
const type = schema; | ||
const totalBytes = size * TYPES[type].BYTES_PER_ELEMENT; | ||
const buffer = new ArrayBuffer(totalBytes); | ||
managers[$manager] = new TYPES[type](buffer); | ||
} else if (Array.isArray(schema)) { | ||
arrays = schema; | ||
const { | ||
type, | ||
length | ||
} = schema[0]; | ||
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 (!managers[$manager][$managerSubarrays][type]) { | ||
const relevantArrays = arrays; | ||
const summedBytesPerElement = relevantArrays.reduce((a, p) => a + TYPES[type].BYTES_PER_ELEMENT, 0); | ||
const summedLength = relevantArrays.reduce((a, p) => a + length, 0); | ||
const buffer = new ArrayBuffer(roundToMultiple4(summedBytesPerElement * summedLength * size)); | ||
const array = new TYPES[type](buffer); | ||
array._indexType = indexType; | ||
array._indexBytes = TYPES[indexType].BYTES_PER_ELEMENT; | ||
managers[$manager][$managerSubarrays][type] = array; | ||
} // 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; | ||
managers[$manager][eid] = managers[$manager][$managerSubarrays][type].subarray(from, to); | ||
end = to; | ||
} | ||
cursors[type] = end; | ||
managers[$manager]._reset = eid => managers[$manager][eid].fill(0); | ||
managers[$manager]._set = (eid, values) => managers[$manager][eid].set(values, 0); | ||
} else props.forEach(prop => { | ||
// Boolean Type | ||
if (schema[prop] === 'bool') { | ||
const Type = TYPES.uint8; | ||
const totalBytes = size * TYPES.uint8.BYTES_PER_ELEMENT; | ||
const buffer = new ArrayBuffer(totalBytes); | ||
managers[$manager][$managerMaps][prop] = schema[prop]; | ||
managers[$manager][prop] = new Type(buffer); | ||
managers[$manager][prop]._boolType = true; // Enum Type | ||
} else if (Array.isArray(schema[prop]) && typeof schema[prop][0] === 'string') { | ||
const Type = TYPES.uint8; | ||
const totalBytes = size * TYPES.uint8.BYTES_PER_ELEMENT; | ||
const buffer = new ArrayBuffer(totalBytes); | ||
managers[$manager][$managerMaps][prop] = schema[prop]; | ||
managers[$manager][prop] = new Type(buffer); // Array Type | ||
} else if (Array.isArray(schema[prop]) && typeof schema[prop][0] === 'object') { | ||
const { | ||
type, | ||
length | ||
} = schema[0]; | ||
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 (!managers[$manager][$managerSubarrays][type]) { | ||
const relevantArrays = arrays.filter(p => schema[p][0].type === type); | ||
const summedBytesPerElement = relevantArrays.reduce((a, p) => a + TYPES[type].BYTES_PER_ELEMENT, 0); | ||
const summedLength = relevantArrays.reduce((a, p) => a + length, 0); | ||
const buffer = new ArrayBuffer(roundToMultiple4(summedBytesPerElement * summedLength * size)); | ||
const array = new TYPES[type](buffer); | ||
array._indexType = index; | ||
array._indexBytes = TYPES[index].BYTES_PER_ELEMENT; | ||
managers[$manager][$managerSubarrays][type] = array; | ||
} // pre-generate subarrays for each eid | ||
managers[$manager][prop] = {}; | ||
let end = 0; | ||
for (let eid = 0; eid < size; eid++) { | ||
const from = cursors[type] + eid * length; | ||
const to = from + length; | ||
managers[$manager][prop][eid] = managers[$manager][$managerSubarrays][type].subarray(from, to); | ||
end = to; | ||
} | ||
cursors[type] = end; | ||
managers[$manager][prop]._reset = eid => managers[$manager][prop][eid].fill(0); | ||
managers[$manager][prop]._set = (eid, values) => managers[$manager][prop][eid].set(values, 0); // Object Type | ||
} else if (typeof schema[prop] === 'object') { | ||
managers[$manager][prop] = Manager(size, schema[prop], false); // String Type | ||
} else if (typeof schema[prop] === 'string') { | ||
const type = schema[prop]; | ||
const totalBytes = size * TYPES[type].BYTES_PER_ELEMENT; | ||
const buffer = new ArrayBuffer(totalBytes); | ||
const queryShadowBuffer = new ArrayBuffer(totalBytes); | ||
const serializeShadowBuffer = new ArrayBuffer(totalBytes); | ||
managers[$manager][prop] = new TYPES[type](buffer); | ||
managers[$manager][prop][$queryShadow] = new TYPES[type](queryShadowBuffer); | ||
managers[$manager][prop][$serializeShadow] = new TYPES[type](serializeShadowBuffer); // TypedArray Type | ||
} else if (typeof schema[prop] === 'function') { | ||
const Type = schema[prop]; | ||
const totalBytes = size * Type.BYTES_PER_ELEMENT; | ||
const buffer = new ArrayBuffer(totalBytes); | ||
managers[$manager][prop] = new Type(buffer); | ||
} else { | ||
throw new Error(`ECS Error: invalid property type ${schema[prop]}`); | ||
} | ||
}); // methods | ||
Object.defineProperty(managers[$manager], '_schema', { | ||
value: schema | ||
}); | ||
Object.defineProperty(managers[$manager], '_mapping', { | ||
value: prop => managers[$manager][$managerMaps][prop] | ||
}); // Recursively set all values to 0 | ||
Object.defineProperty(managers[$manager], '_reset', { | ||
value: eid => { | ||
for (const prop of managers[$manager]._props) { | ||
if (ArrayBuffer.isView(managers[$manager][prop])) { | ||
if (ArrayBuffer.isView(managers[$manager][prop][eid])) { | ||
managers[$manager][prop][eid].fill(0); | ||
} else { | ||
managers[$manager][prop][eid] = 0; | ||
} | ||
} else { | ||
managers[$manager][prop]._reset(eid); | ||
} | ||
} | ||
} | ||
}); // Recursively set all values from a supplied object | ||
Object.defineProperty(managers[$manager], '_set', { | ||
value: (eid, values) => { | ||
for (const prop in values) { | ||
const mapping = managers[$manager]._mapping(prop); | ||
if (mapping && typeof values[prop] === 'string') { | ||
managers[$manager].enum(prop, eid, values[prop]); | ||
} else if (ArrayBuffer.isView(managers[$manager][prop])) { | ||
managers[$manager][prop][eid] = values[prop]; | ||
} else if (Array.isArray(values[prop]) && ArrayBuffer.isView(managers[$manager][prop][eid])) { | ||
managers[$manager][prop][eid].set(values[prop], 0); | ||
} else if (typeof managers[$manager][prop] === 'object') { | ||
managers[$manager][prop]._set(eid, values[prop]); | ||
} | ||
} | ||
} | ||
}); | ||
Object.defineProperty(managers[$manager], '_get', { | ||
value: eid => { | ||
const obj = {}; | ||
for (const prop of managers[$manager]._props) { | ||
const mapping = managers[$manager]._mapping(prop); | ||
if (mapping) { | ||
obj[prop] = managers[$manager].enum(prop, eid); | ||
} else if (ArrayBuffer.isView(managers[$manager][prop])) { | ||
obj[prop] = managers[$manager][prop][eid]; | ||
} else if (typeof managers[$manager][prop] === 'object') { | ||
if (ArrayBuffer.isView(managers[$manager][prop][eid])) { | ||
obj[prop] = Array.from(managers[$manager][prop][eid]); | ||
} else { | ||
obj[prop] = managers[$manager][prop]._get(eid); | ||
} | ||
} | ||
} | ||
return obj; | ||
} | ||
}); | ||
Object.defineProperty(managers[$manager], '_props', { | ||
value: props | ||
}); // Aggregate all typedArrays into single kvp array (memoized) | ||
let flattened; | ||
Object.defineProperty(managers[$manager], '_flatten', { | ||
value: (flat = []) => { | ||
if (flattened) return flattened; | ||
for (const prop of managers[$manager]._props) { | ||
if (ArrayBuffer.isView(managers[$manager][prop])) { | ||
flat.push(managers[$manager][prop]); | ||
} else if (typeof managers[$manager][prop] === 'object') { | ||
managers[$manager][prop]._flatten(flat); | ||
} | ||
} | ||
flattened = flat; | ||
return flat; | ||
} | ||
}); | ||
Object.defineProperty(managers[$manager], 'enum', { | ||
value: (prop, eid, value) => { | ||
const mapping = managers[$manager]._mapping(prop); | ||
if (!mapping) { | ||
console.warn('Property is not an enum.'); | ||
return undefined; | ||
} | ||
if (value) { | ||
const index = mapping.indexOf(value); | ||
if (index === -1) { | ||
console.warn(`Value '${value}' is not part of enum.`); | ||
return undefined; | ||
} | ||
managers[$manager][prop][eid] = index; | ||
} else { | ||
return mapping[managers[$manager][prop][eid]]; | ||
} | ||
} | ||
}); | ||
Object.defineProperty(managers[$manager], '_grow', { | ||
value: amount => { | ||
managers[$manager][$managerSize] += amount; | ||
for (const prop of managers[$manager]._props) { | ||
if (ArrayBuffer.isView(managers[$manager][prop])) { | ||
managers[$manager][prop] = grow(managers[$manager][prop], amount); | ||
managers[$manager][prop][$queryShadow] = grow(managers[$manager][prop], amount); | ||
} else if (typeof managers[$manager][prop] === 'object') { | ||
if (ArrayBuffer.isView(managers[$manager][prop][eid])) ; else { | ||
managers[$manager][prop]._grow(); | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
return managers[$manager]; | ||
}; | ||
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 addEntity = world => { | ||
const removed = world[$removedEntities]; | ||
const size = world[$size]; | ||
const enabled = world[$entityEnabled]; | ||
if (globalEntityCursor >= size - size / 5) { | ||
// if 80% full | ||
const amount = Math.ceil(size / 2 / 4) * 4; // grow by half the original size rounded up to a multiple of 4 | ||
// grow data stores | ||
world[$componentMap].forEach(component => { | ||
component.manager._grow(amount); | ||
}); | ||
world[$size] += amount; // TODO: grow metadata on world mappings for queries/components enabled/etc | ||
} | ||
const eid = removed.length > 0 ? removed.pop() : globalEntityCursor; | ||
enabled[eid] = 1; | ||
globalEntityCursor++; | ||
return eid; | ||
}; | ||
const removeEntity = (world, eid) => world[$deferredEntityRemovals].push(eid); | ||
const commitEntityRemovals = world => { | ||
const deferred = world[$deferredEntityRemovals]; | ||
const queries = world[$queries]; | ||
const removed = world[$removedEntities]; | ||
const enabled = world[$entityEnabled]; | ||
for (let i = 0; i < deferred.length; i++) { | ||
const eid = deferred[i]; // Check if entity is already removed | ||
if (enabled[eid] === 0) continue; // Remove entity from all queries | ||
// TODO: archetype graph | ||
queries.forEach(query => { | ||
queryRemoveEntity(query, eid); | ||
}); // Free the entity | ||
removed.push(eid); | ||
enabled[eid] = 0; // Clear component bitmasks | ||
for (let i = 0; i < world[$entityMasks].length; i++) world[$entityMasks][i][eid] = 0; | ||
} | ||
deferred.length = 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 (p._flatten) { | ||
return p._flatten(); | ||
} else if (typeof p === 'function' && p.name === 'QueryChanged') { | ||
p = p(); | ||
if (p._flatten) { | ||
let props = p._flatten(); | ||
props.forEach(x => changedProps.add(x)); | ||
return props; | ||
} | ||
changedProps.add(p); | ||
return [p]; | ||
} | ||
}).reduce((a, v) => a.concat(v), []); | ||
} else { | ||
target[$componentMap].forEach(c => { | ||
componentProps = componentProps.concat(c._flatten()); | ||
}); | ||
} | ||
return [componentProps, changedProps]; | ||
}; | ||
const defineSerializer = (target, maxBytes = 5000000) => { | ||
const buffer = new ArrayBuffer(maxBytes); | ||
const view = new DataView(buffer); | ||
const [componentProps, changedProps] = canonicalize(target); | ||
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; | ||
} | ||
prop[$serializeShadow][eid] = prop[eid]; | ||
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 val = prop[eid][i]; // write array index | ||
view[`set${indexType}`](where, i); | ||
where += indexBytes; // write value at that index | ||
view[`set${type}`](where, val); | ||
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; | ||
} | ||
} | ||
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; // 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 += array._indexBytes; // iterate over count | ||
for (let i = 0; i < count; i++) { | ||
const value = view[`get${array.constructor.name.replace('Array', '')}`](where); | ||
where += array.BYTES_PER_ELEMENT; | ||
ta[eid][i] = 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 $queryComponents = Symbol('queryComponents'); | ||
const enterQuery = (world, query, fn) => { | ||
if (!world[$queryMap].get(query)) registerQuery(world, query); | ||
world[$queryMap].get(query).enter = fn; | ||
}; | ||
const exitQuery = (world, query, fn) => { | ||
if (!world[$queryMap].get(query)) registerQuery(world, query); | ||
world[$queryMap].get(query).exit = fn; | ||
}; | ||
const registerQuery = (world, query) => { | ||
if (!world[$queryMap].get(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 size = components.reduce((a, c) => c[$managerSize] > a ? c[$managerSize] : a, 0); | ||
const entities = []; | ||
const changed = []; | ||
const indices = new Uint32Array(size); | ||
const enabled = new Uint8Array(size); | ||
const generations = components.map(c => world[$componentMap].get(c).generationId); | ||
const mapComponents = c => world[$componentMap].get(c); | ||
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(c => world[$componentMap].get(c)).reduce(reduceBitmasks, {}); | ||
const flatProps = components.map(c => !ArrayBuffer.isView(c) ? c._flatten() : [c]).reduce((a, v) => a.concat(v), []); | ||
Object.assign(world[$queryMap].get(query), { | ||
entities, | ||
changed, | ||
enabled, | ||
components, | ||
notComponents, | ||
changedComponents, | ||
masks, | ||
notMasks, | ||
generations, | ||
indices, | ||
flatProps | ||
}); | ||
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); | ||
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 ((eMask & qMask) !== qMask) { | ||
return false; | ||
} // if ((eMask | ~qNotMask) === ~qNotMask) { | ||
// 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 queryRemoveEntity = (world, query, eid) => { | ||
const q = world[$queryMap].get(query); | ||
if (!q.enabled[eid]) return; | ||
q.enabled[eid] = false; | ||
q.entities.splice(q.indices[eid]); | ||
if (q.exit) q.exit(eid); | ||
}; | ||
const $componentMap = Symbol('componentMap'); | ||
const $deferredComponentRemovals = Symbol('de$deferredComponentRemovals'); | ||
const defineComponent = (schema, n) => alloc(schema, n); | ||
const incrementBitflag = world => { | ||
world[$bitflag] *= 2; | ||
if (world[$bitflag] >= Math.pow(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], | ||
manager: 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 (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) => world[$deferredComponentRemovals].push(component, eid); | ||
const commitComponentRemovals = world => { | ||
const deferredComponentRemovals = world[$deferredComponentRemovals]; | ||
for (let i = 0; i < deferredComponentRemovals.length; i += 2) { | ||
const component = deferredComponentRemovals[i]; | ||
const eid = deferredComponentRemovals[i + 1]; | ||
const { | ||
generationId, | ||
bitflag | ||
} = world[$componentMap].get(component); | ||
if (!(world[$entityMasks][generationId][eid] & bitflag)) return; // Remove flag from entity bitmask | ||
world[$entityMasks][generationId][eid] &= ~bitflag; // 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); | ||
}); | ||
} | ||
deferredComponentRemovals.length = 0; | ||
}; | ||
const $size = Symbol('size'); | ||
const $bitflag = Symbol('bitflag'); | ||
const createWorld = (size = 1000000) => { | ||
const world = {}; | ||
world[$size] = size; | ||
world[$entityEnabled] = new Uint8Array(world[$size]); | ||
world[$entityMasks] = [new Uint32Array(size)]; | ||
world[$removedEntities] = []; | ||
world[$bitflag] = 1; | ||
world[$componentMap] = new Map(); | ||
world[$queryMap] = new Map(); | ||
world[$queries] = new Set(); | ||
world[$deferredComponentRemovals] = []; | ||
world[$deferredEntityRemovals] = []; | ||
return world; | ||
}; | ||
const defineSystem = update => { | ||
const system = world => { | ||
update(world); | ||
commitComponentRemovals(world); | ||
commitEntityRemovals(world); | ||
}; | ||
Object.defineProperty(system, 'name', { | ||
value: (update.name || "AnonymousSystem") + "_internal", | ||
configurable: true | ||
}); | ||
return system; | ||
}; | ||
const pipe = fns => world => { | ||
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.createWorld = createWorld; | ||
exports.defineComponent = defineComponent; | ||
exports.defineDeserializer = defineDeserializer; | ||
exports.defineQuery = defineQuery; | ||
exports.defineSerializer = defineSerializer; | ||
exports.defineSystem = defineSystem; | ||
exports.enterQuery = enterQuery; | ||
exports.exitQuery = exitQuery; | ||
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.8", | ||
"version": "0.2.9", | ||
"description": "Tiny, data-driven, high performance ECS library written in Javascript", | ||
@@ -5,0 +5,0 @@ "license": "MPL-2.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
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
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
222951
9
1434
2
1