UNPKG

elics

Version:

lightweight, flexible ECS for web games

1,006 lines (991 loc) 40.1 kB
var Types; (function (Types) { Types["Int8"] = "Int8"; Types["Int16"] = "Int16"; Types["Entity"] = "Entity"; Types["Float32"] = "Float32"; Types["Float64"] = "Float64"; Types["Boolean"] = "Boolean"; Types["String"] = "String"; Types["Object"] = "Object"; Types["Vec2"] = "Vec2"; Types["Vec3"] = "Vec3"; Types["Vec4"] = "Vec4"; Types["Color"] = "Color"; Types["Enum"] = "Enum"; })(Types || (Types = {})); const TypedArrayMap = { Int8: { arrayConstructor: Int8Array, length: 1 }, Int16: { arrayConstructor: Int16Array, length: 1 }, Entity: { arrayConstructor: Int32Array, length: 1 }, Float32: { arrayConstructor: Float32Array, length: 1 }, Float64: { arrayConstructor: Float64Array, length: 1 }, Boolean: { arrayConstructor: Uint8Array, length: 1 }, String: { arrayConstructor: Array, length: 1 }, Object: { arrayConstructor: Array, length: 1 }, Vec2: { arrayConstructor: Float32Array, length: 2 }, Vec3: { arrayConstructor: Float32Array, length: 3 }, Vec4: { arrayConstructor: Float32Array, length: 4 }, Color: { arrayConstructor: Float32Array, length: 4 }, Enum: { arrayConstructor: Array, length: 1 }, }; let CHECKS_ON = true; function toggleChecks(value) { CHECKS_ON = value; } var ErrorMessages; (function (ErrorMessages) { ErrorMessages["TypeNotSupported"] = "Type not supported"; ErrorMessages["InvalidDefaultValue"] = "Invalid default value"; ErrorMessages["InvalidEnumValue"] = "Invalid enum value"; ErrorMessages["InvalidRangeValue"] = "Value out of range"; })(ErrorMessages || (ErrorMessages = {})); function assertCondition(condition, message, object) { if (CHECKS_ON && !condition) { throw new Error(`${message}: ${object}`); } } function assertValidEnumValue(value, enumObject, fieldName) { if (!CHECKS_ON) { return; } const enumValues = Object.values(enumObject); if (!enumValues.includes(value)) { throw new Error(`${ErrorMessages.InvalidEnumValue}: ${value} is not a valid value for enum ${fieldName}`); } } function assertValidRangeValue(value, min, max, fieldName) { if (!CHECKS_ON) { return; } if (min !== undefined && value < min) { throw new Error(`${ErrorMessages.InvalidRangeValue}: ${value} is below minimum ${min} for field ${fieldName}`); } if (max !== undefined && value > max) { throw new Error(`${ErrorMessages.InvalidRangeValue}: ${value} is above maximum ${max} for field ${fieldName}`); } } class BitSet { constructor(init = 0) { if (typeof init === 'number') { this.words = new Uint32Array(1); // treat number as initial lower 32-bit mask value this.words[0] = init >>> 0; } else { this.words = new Uint32Array(init.length); this.words.set(init); } } // Back-compat for tests referencing .bits get bits() { return this.words[0] >>> 0; } ensure(wordIndex) { if (wordIndex < this.words.length) { return; } const nextLen = Math.max(this.words.length << 1, wordIndex + 1); const n = new Uint32Array(nextLen); n.set(this.words); this.words = n; } set(bitIndex, value) { const w = (bitIndex / 32) | 0; const b = bitIndex & 31; this.ensure(w); const mask = 1 << b; if (value) { this.words[w] |= mask; } else { this.words[w] &= ~mask; } } or(other) { var _a, _b; const out = new BitSet(new Uint32Array(Math.max(this.words.length, other.words.length))); const a = this.words, b = other.words, o = out.words; const max = o.length; for (let i = 0; i < max; i++) { o[i] = ((_a = a[i]) !== null && _a !== void 0 ? _a : 0) | ((_b = b[i]) !== null && _b !== void 0 ? _b : 0); } return out; } and(other) { var _a, _b; const out = new BitSet(new Uint32Array(Math.max(this.words.length, other.words.length))); const a = this.words, b = other.words, o = out.words; const max = o.length; for (let i = 0; i < max; i++) { o[i] = ((_a = a[i]) !== null && _a !== void 0 ? _a : 0) & ((_b = b[i]) !== null && _b !== void 0 ? _b : 0); } return out; } andNot(other) { var _a, _b; const out = new BitSet(new Uint32Array(Math.max(this.words.length, other.words.length))); const a = this.words, b = other.words, o = out.words; const max = o.length; for (let i = 0; i < max; i++) { o[i] = ((_a = a[i]) !== null && _a !== void 0 ? _a : 0) & ~((_b = b[i]) !== null && _b !== void 0 ? _b : 0); } return out; } orInPlace(other) { this.ensure(other.words.length - 1); const a = this.words, b = other.words; for (let i = 0; i < b.length; i++) { a[i] |= b[i]; } } andNotInPlace(other) { const a = this.words, b = other.words; const max = Math.min(a.length, b.length); for (let i = 0; i < max; i++) { a[i] &= ~b[i]; } } equals(other) { var _a, _b; const max = Math.max(this.words.length, other.words.length); for (let i = 0; i < max; i++) { const a = (_a = this.words[i]) !== null && _a !== void 0 ? _a : 0; const b = (_b = other.words[i]) !== null && _b !== void 0 ? _b : 0; if (a !== b) { return false; } } return true; } isEmpty() { const w = this.words; for (let i = 0; i < w.length; i++) { if (w[i] !== 0) { return false; } } return true; } clear() { this.words.fill(0); } toArray() { const out = []; const w = this.words; for (let wi = 0; wi < w.length; wi++) { let word = w[wi]; while (word) { const t = word & -word; // lowbit const bit = 31 - Math.clz32(t); out.push((wi << 5) + bit); word &= word - 1; } } return out; } toString() { if (this.words.length === 1) { return this.words[0].toString(); } // Hex words from high->low to produce stable mask ids let s = ''; for (let i = this.words.length - 1; i >= 0; i--) { const hex = this.words[i].toString(16); s += i === this.words.length - 1 ? hex : '-' + hex; } return s; } contains(other) { var _a; const a = this.words, b = other.words; const n = b.length; for (let i = 0; i < n; i++) { const aw = ((_a = a[i]) !== null && _a !== void 0 ? _a : 0) >>> 0; const bw = b[i] >>> 0; if ((aw & bw) >>> 0 !== bw) { return false; } } return true; } intersects(other) { const a = this.words, b = other.words; const n = Math.min(a.length, b.length); for (let i = 0; i < n; i++) { if ((a[i] & b[i]) >>> 0 !== 0) { return true; } } return false; } } class Entity { constructor(entityManager, queryManager, componentManager, index) { this.entityManager = entityManager; this.queryManager = queryManager; this.componentManager = componentManager; this.index = index; this.bitmask = new BitSet(); this.active = true; this.vectorViews = new Map(); } addComponent(component, initialData = {}) { if (!this.active) { console.warn(`Entity ${this.index} is destroyed, cannot add component ${component.schema}`); } else { if (component.bitmask === null) { this.componentManager.registerComponent(component); } this.bitmask.orInPlace(component.bitmask); this.componentManager.attachComponentToEntity(this.index, component, initialData); this.queryManager.updateEntity(this, component); } return this; } removeComponent(component) { if (!this.active) { console.warn(`Entity ${this.index} is destroyed, cannot remove component ${component.schema}`); } else if (component.bitmask) { this.bitmask.andNotInPlace(component.bitmask); this.vectorViews.delete(component); this.queryManager.updateEntity(this, component); } return this; } hasComponent(component) { return this.bitmask.intersects(component.bitmask); } getComponents() { const bitArray = this.bitmask.toArray(); return bitArray.map((typeId) => this.componentManager.getComponentByTypeId(typeId)); } getValue(component, key) { // allow runtime access with invalid keys, return null const schemaEntry = component.schema[key]; if (!schemaEntry) { return null; } const dataContainer = component.data[key]; const indexed = dataContainer; const data = indexed ? indexed[this.index] : undefined; const type = schemaEntry.type; switch (type) { case Types.Vec2: case Types.Vec3: case Types.Vec4: case Types.Color: throw new Error('Array/vector types must be read via getVectorView(component, key).'); case Types.Boolean: return Boolean(data); case Types.Entity: return data === -1 ? null : this.entityManager.getEntityByIndex(data); default: return data; } } setValue(component, key, value) { const componentData = component.data[key]; const schemaField = component.schema[key]; const type = schemaField.type; switch (type) { case Types.Vec2: case Types.Vec3: case Types.Vec4: case Types.Color: throw new Error('Array/vector types must be written via getVectorView(component, key).'); case Types.Enum: // enum property is guaranteed to exist due to initialization validation assertValidEnumValue(value, schemaField.enum, key); componentData[this.index] = value; break; case Types.Int8: case Types.Int16: case Types.Float32: case Types.Float64: // For numeric types, validate range constraints if present if ('min' in schemaField || 'max' in schemaField) { assertValidRangeValue(value, schemaField.min, schemaField.max, key); } componentData[this.index] = value; break; case Types.Entity: componentData[this.index] = value === null ? -1 : value.index; break; default: componentData[this.index] = value; break; } // Notify only queries that depend on this component's values this.queryManager.updateEntityValue(this, component); } getVectorView(component, key) { var _a; const keyStr = key; const cachedVectorView = (_a = this.vectorViews.get(component)) === null || _a === void 0 ? void 0 : _a.get(keyStr); if (cachedVectorView) { return cachedVectorView; } else { const componentData = component.data[key]; const type = component.schema[key].type; const length = TypedArrayMap[type].length; const offset = this.index * length; const vectorView = componentData.subarray(offset, offset + length); if (!this.vectorViews.has(component)) { this.vectorViews.set(component, new Map()); } this.vectorViews.get(component).set(keyStr, vectorView); return vectorView; } } destroy() { if (this.active) { this.active = false; this.bitmask.clear(); this.vectorViews.clear(); this.queryManager.resetEntity(this); this.entityManager.releaseEntityInstance(this); } } } class ComponentRegistry { static record(component) { if (this.components.has(component.id)) { throw new Error(`Component with id '${component.id}' already exists. Each component must have a unique identifier.`); } this.components.set(component.id, component); } static getById(id) { return this.components.get(id); } static getAllComponents() { return Array.from(this.components.values()); } static has(id) { return this.components.has(id); } static clear() { this.components.clear(); } } ComponentRegistry.components = new Map(); function createComponent(id, schema, description) { const component = { id, description, schema, data: {}, bitmask: null, typeId: -1, }; ComponentRegistry.record(component); return component; } function initializeComponentStorage(component, entityCapacity) { const s = component.schema; component.data = {}; for (const key in s) { const schemaField = s[key]; const { type, default: defaultValue } = schemaField; let { arrayConstructor, length } = TypedArrayMap[type]; // For Enum types, validate enum property exists if (type === Types.Enum) { assertCondition('enum' in schemaField, ErrorMessages.InvalidDefaultValue, `Enum type requires 'enum' property for field ${key}`); } assertCondition(!!arrayConstructor, ErrorMessages.TypeNotSupported, type); component.data[key] = new arrayConstructor(entityCapacity * length); assertCondition(length === 1 || (Array.isArray(defaultValue) && defaultValue.length === length), ErrorMessages.InvalidDefaultValue, key); } } function assignInitialComponentData(component, index, initialData) { var _a, _b, _c; const s = component.schema; for (const key in s) { const schemaField = s[key]; const { type, default: defaultValue } = schemaField; const length = TypedArrayMap[type].length; const dataRef = component.data[key]; const input = (_a = initialData[key]) !== null && _a !== void 0 ? _a : defaultValue; switch (type) { case Types.Entity: dataRef[index] = input ? input.index : -1; break; case Types.Enum: assertValidEnumValue(input, schemaField.enum, key); dataRef[index] = input; break; case Types.Int8: case Types.Int16: case Types.Float32: case Types.Float64: // For numeric types, validate range constraints if present if ('min' in schemaField || 'max' in schemaField) { assertValidRangeValue(input, schemaField.min, schemaField.max, key); } dataRef[index] = input; break; case Types.String: dataRef[index] = input; break; case Types.Object: dataRef[index] = input; break; case Types.Color: { const length = TypedArrayMap[type].length; const arr = (_b = input) !== null && _b !== void 0 ? _b : [1, 1, 1, 1]; let clamped = false; for (let i = 0; i < 4; i++) { let v = (_c = arr[i]) !== null && _c !== void 0 ? _c : 1; if (v < 0) { v = 0; clamped = true; } else if (v > 1) { v = 1; clamped = true; } dataRef[index * length + i] = v; } if (clamped) { console.warn(`Color values out of range for ${component.id}.${key}; clamped to [0,1].`); } break; } default: if (length === 1) { dataRef[index] = input; } else { dataRef.set(input, index * length); } break; } } } class ComponentManager { constructor(entityCapacity) { this.entityCapacity = entityCapacity; this.nextComponentTypeId = 0; this.componentsByTypeId = []; } hasComponent(component) { return (component.typeId !== -1 && this.componentsByTypeId[component.typeId] === component); } registerComponent(component) { if (this.hasComponent(component)) { return; } const typeId = this.nextComponentTypeId++; component.bitmask = new BitSet(); component.bitmask.set(typeId, 1); component.typeId = typeId; initializeComponentStorage(component, this.entityCapacity); this.componentsByTypeId[typeId] = component; } attachComponentToEntity(entityIndex, component, initialData) { assignInitialComponentData(component, entityIndex, initialData); } getComponentByTypeId(typeId) { var _a; return (_a = this.componentsByTypeId[typeId]) !== null && _a !== void 0 ? _a : null; } } class EntityManager { constructor(queryManager, componentManager, entityReleaseCallback) { this.queryManager = queryManager; this.componentManager = componentManager; this.entityReleaseCallback = entityReleaseCallback; this.pool = []; this.entityIndex = 0; this.indexLookup = []; this.poolSize = 0; } requestEntityInstance() { let entity; if (this.poolSize > 0) { entity = this.pool[--this.poolSize]; entity.active = true; } else { entity = new Entity(this, this.queryManager, this.componentManager, this.entityIndex++); } this.indexLookup[entity.index] = entity; return entity; } releaseEntityInstance(entity) { var _a; (_a = this.entityReleaseCallback) === null || _a === void 0 ? void 0 : _a.call(this, entity); this.indexLookup[entity.index] = null; this.pool[this.poolSize++] = entity; } getEntityByIndex(index) { var _a; if (index === -1) { return null; } return (_a = this.indexLookup[index]) !== null && _a !== void 0 ? _a : null; } } class Query { constructor(requiredMask, excludedMask, queryId, valuePredicates = []) { this.requiredMask = requiredMask; this.excludedMask = excludedMask; this.queryId = queryId; this.subscribers = { qualify: new Set(), disqualify: new Set(), }; this.entities = new Set(); this.valuePredicates = []; this.valuePredicates = valuePredicates; } matches(entity) { // Excluded: if any excluded bit is present on entity -> no match if (!this.excludedMask.isEmpty() && this.excludedMask.intersects(entity.bitmask)) { return false; } // Required: entity must contain all required bits if (!entity.bitmask.contains(this.requiredMask)) { return false; } // Value predicates: verify values if (this.valuePredicates.length > 0) { for (const p of this.valuePredicates) { const v = entity.getValue(p.component, p.key); switch (p.op) { case 'eq': if (v !== p.value) { return false; } break; case 'ne': if (v === p.value) { return false; } break; case 'lt': if (!(typeof v === 'number' && typeof p.value === 'number' && v < p.value)) { return false; } break; case 'le': if (!(typeof v === 'number' && typeof p.value === 'number' && v <= p.value)) { return false; } break; case 'gt': if (!(typeof v === 'number' && typeof p.value === 'number' && v > p.value)) { return false; } break; case 'ge': if (!(typeof v === 'number' && typeof p.value === 'number' && v >= p.value)) { return false; } break; case 'in': if (!p.valueSet || !p.valueSet.has(v)) { return false; } break; case 'nin': if (p.valueSet && p.valueSet.has(v)) { return false; } break; } } } return true; } subscribe(event, callback, replayExisting = false) { this.subscribers[event].add(callback); if (event === 'qualify' && replayExisting) { for (const e of this.entities) { callback(e); } } return () => { this.subscribers[event].delete(callback); }; } static generateQueryInfo(queryConfig) { var _a; const requiredMask = new BitSet(); const excludedMask = new BitSet(); for (const c of queryConfig.required) { requiredMask.orInPlace(c.bitmask); } if (queryConfig.excluded) { for (const c of queryConfig.excluded) { excludedMask.orInPlace(c.bitmask); } } // Normalize and validate predicates const rawPreds = (_a = queryConfig.where) !== null && _a !== void 0 ? _a : []; const preds = rawPreds.map((p) => { const schema = p.component.schema[p.key]; if (!schema) { throw new Error(`Predicate key '${p.key}' not found on component ${p.component.id}`); } const t = schema.type; // Validate operator applicability switch (p.op) { case 'lt': case 'le': case 'gt': case 'ge': if (!(t === Types.Int8 || t === Types.Int16 || t === Types.Float32 || t === Types.Float64)) { throw new Error(`Operator '${p.op}' only valid for numeric fields: ${p.component.id}.${p.key}`); } break; case 'in': case 'nin': if (!Array.isArray(p.value)) { throw new Error(`Operator '${p.op}' requires array value for ${p.component.id}.${p.key}`); } break; } const np = { ...p }; if (p.op === 'in' || p.op === 'nin') { np.valueSet = new Set(p.value); } return np; }); for (const p of preds) { requiredMask.orInPlace(p.component.bitmask); } const whereStr = preds .map((p) => `${p.component.typeId}:${p.key}:${p.op}=${Array.isArray(p.value) ? 'arr' : String(p.value)}`) .join(','); return { requiredMask, excludedMask, queryId: `required:${requiredMask.toString()}|excluded:${excludedMask.toString()}|where:${whereStr}`, valuePredicates: preds, }; } } class QueryManager { constructor(componentManager) { this.componentManager = componentManager; this.queries = new Map(); this.trackedEntities = new Set(); this.queriesByComponent = new Map(); this.queriesByValueComponent = new Map(); } registerQuery(query) { var _a, _b, _c; for (const component of query.required) { if (component.bitmask === null) { this.componentManager.registerComponent(component); } } if (query.excluded) { for (const component of query.excluded) { if (component.bitmask === null) { this.componentManager.registerComponent(component); } } } if (query.where) { for (const p of query.where) { if (p.component.bitmask === null) { this.componentManager.registerComponent(p.component); } } } const { requiredMask, excludedMask, queryId, valuePredicates } = Query.generateQueryInfo(query); if (!this.queries.has(queryId)) { const newQuery = new Query(requiredMask, excludedMask, queryId, valuePredicates); for (const entity of this.trackedEntities) { if (newQuery.matches(entity)) { newQuery.entities.add(entity); } } const comps = [ ...query.required, ...((_a = query.excluded) !== null && _a !== void 0 ? _a : []), ...((_b = query.where) !== null && _b !== void 0 ? _b : []).map((p) => p.component), ]; for (const c of comps) { let set = this.queriesByComponent.get(c); if (!set) { set = new Set(); this.queriesByComponent.set(c, set); } set.add(newQuery); } // Track value-predicate components separately for efficient value updates for (const p of (_c = query.where) !== null && _c !== void 0 ? _c : []) { let set = this.queriesByValueComponent.get(p.component); if (!set) { set = new Set(); this.queriesByValueComponent.set(p.component, set); } set.add(newQuery); } this.queries.set(queryId, newQuery); } return this.queries.get(queryId); } updateEntity(entity, changedComponent) { var _a; this.trackedEntities.add(entity); if (entity.bitmask.isEmpty()) { for (const query of this.queries.values()) { if (query.entities.delete(entity)) { for (const cb of query.subscribers.disqualify) { cb(entity); } } } return; } const queries = changedComponent ? ((_a = this.queriesByComponent.get(changedComponent)) !== null && _a !== void 0 ? _a : []) : this.queries.values(); for (const query of queries) { const matches = query.matches(entity); const inSet = query.entities.has(entity); if (matches && !inSet) { query.entities.add(entity); for (const cb of query.subscribers.qualify) { cb(entity); } } else if (!matches && inSet) { query.entities.delete(entity); for (const cb of query.subscribers.disqualify) { cb(entity); } } } } // Optimized path for value changes: only check queries that have value predicates on this component updateEntityValue(entity, component) { this.trackedEntities.add(entity); const queries = this.queriesByValueComponent.get(component); if (!queries || queries.size === 0) { return; } for (const query of queries) { const matches = query.matches(entity); const inSet = query.entities.has(entity); if (matches && !inSet) { query.entities.add(entity); for (const cb of query.subscribers.qualify) { cb(entity); } } else if (!matches && inSet) { query.entities.delete(entity); for (const cb of query.subscribers.disqualify) { cb(entity); } } } } resetEntity(entity) { this.trackedEntities.delete(entity); if (entity.bitmask.isEmpty()) { for (const query of this.queries.values()) { if (query.entities.delete(entity)) { for (const cb of query.subscribers.disqualify) { cb(entity); } } } return; } const processed = new Set(); for (const i of entity.bitmask.toArray()) { const component = this.componentManager.getComponentByTypeId(i); const qs = this.queriesByComponent.get(component); if (!qs) { continue; } for (const q of qs) { if (processed.has(q)) { continue; } if (q.entities.delete(entity)) { for (const cb of q.subscribers.disqualify) { cb(entity); } } processed.add(q); } } } } class World { constructor({ entityCapacity = 1000, checksOn = true, entityReleaseCallback, } = {}) { this.systems = []; this.globals = {}; this.componentManager = new ComponentManager(entityCapacity); this.queryManager = new QueryManager(this.componentManager); this.entityManager = new EntityManager(this.queryManager, this.componentManager, entityReleaseCallback); toggleChecks(checksOn); } registerComponent(component) { this.componentManager.registerComponent(component); return this; } hasComponent(component) { return this.componentManager.hasComponent(component); } createEntity() { return this.entityManager.requestEntityInstance(); } registerSystem(systemClass, options = {}) { if (this.hasSystem(systemClass)) { console.warn(`System ${systemClass.name} is already registered, skipping registration.`); return this; } const { configData = {}, priority = 0 } = options; const queries = {}; Object.entries(systemClass.queries).forEach(([queryName, queryConfig]) => { queries[queryName] = this.queryManager.registerQuery(queryConfig); }); const systemInstance = new systemClass(this, this.queryManager, priority); systemInstance.queries = queries; Object.entries(configData).forEach(([key, value]) => { if (key in systemInstance.config) { const cfg = systemInstance.config; cfg[key].value = value; } }); systemInstance.init(); // Determine the correct position for the new system based on priority const insertIndex = this.systems.findIndex((s) => s.priority > systemInstance.priority); if (insertIndex === -1) { this.systems.push(systemInstance); } else { this.systems.splice(insertIndex, 0, systemInstance); } return this; } unregisterSystem(systemClass) { const systemInstance = this.getSystem(systemClass); if (systemInstance) { systemInstance.destroy(); this.systems = this.systems.filter((system) => !(system instanceof systemClass)); } } registerQuery(queryConfig) { this.queryManager.registerQuery(queryConfig); return this; } update(delta, time) { for (const system of this.systems) { if (!system.isPaused) { system.update(delta, time); } } } getSystem(systemClass) { for (const system of this.systems) { if (system instanceof systemClass) { return system; } } return undefined; } getSystems() { return this.systems; } hasSystem(systemClass) { return this.systems.some((system) => system instanceof systemClass); } } const i=Symbol.for("preact-signals");function t(){if(r>1){r--;return}let i,t=false;while(void 0!==s){let o=s;s=void 0;f++;while(void 0!==o){const n=o.o;o.o=void 0;o.f&=-3;if(!(8&o.f)&&v(o))try{o.c();}catch(o){if(!t){i=o;t=true;}}o=n;}}f=0;r--;if(t)throw i}let n,s;function h(i){const t=n;n=void 0;try{return i()}finally{n=t;}}let r=0,f=0,e=0;function c(i){if(void 0===n)return;let t=i.n;if(void 0===t||t.t!==n){t={i:0,S:i,p:n.s,n:void 0,t:n,e:void 0,x:void 0,r:t};if(void 0!==n.s)n.s.n=t;n.s=t;i.n=t;if(32&n.f)i.S(t);return t}else if(-1===t.i){t.i=0;if(void 0!==t.n){t.n.p=t.p;if(void 0!==t.p)t.p.n=t.n;t.p=n.s;t.n=void 0;n.s.n=t;n.s=t;}return t}}function u(i,t){this.v=i;this.i=0;this.n=void 0;this.t=void 0;this.W=null==t?void 0:t.watched;this.Z=null==t?void 0:t.unwatched;}u.prototype.brand=i;u.prototype.h=function(){return true};u.prototype.S=function(i){const t=this.t;if(t!==i&&void 0===i.e){i.x=t;this.t=i;if(void 0!==t)t.e=i;else h(()=>{var i;null==(i=this.W)||i.call(this);});}};u.prototype.U=function(i){if(void 0!==this.t){const t=i.e,o=i.x;if(void 0!==t){t.x=o;i.e=void 0;}if(void 0!==o){o.e=t;i.x=void 0;}if(i===this.t){this.t=o;if(void 0===o)h(()=>{var i;null==(i=this.Z)||i.call(this);});}}};u.prototype.subscribe=function(i){return E(()=>{const t=this.value,o=n;n=void 0;try{i(t);}finally{n=o;}})};u.prototype.valueOf=function(){return this.value};u.prototype.toString=function(){return this.value+""};u.prototype.toJSON=function(){return this.value};u.prototype.peek=function(){const i=n;n=void 0;try{return this.value}finally{n=i;}};Object.defineProperty(u.prototype,"value",{get(){const i=c(this);if(void 0!==i)i.i=this.i;return this.v},set(i){if(i!==this.v){if(f>100)throw new Error("Cycle detected");this.v=i;this.i++;e++;r++;try{for(let i=this.t;void 0!==i;i=i.x)i.t.N();}finally{t();}}}});function d(i,t){return new u(i,t)}function v(i){for(let t=i.s;void 0!==t;t=t.n)if(t.S.i!==t.i||!t.S.h()||t.S.i!==t.i)return true;return false}function l(i){for(let t=i.s;void 0!==t;t=t.n){const o=t.S.n;if(void 0!==o)t.r=o;t.S.n=t;t.i=-1;if(void 0===t.n){i.s=t;break}}}function y(i){let t,o=i.s;while(void 0!==o){const i=o.p;if(-1===o.i){o.S.U(o);if(void 0!==i)i.n=o.n;if(void 0!==o.n)o.n.p=i;}else t=o;o.S.n=o.r;if(void 0!==o.r)o.r=void 0;o=i;}i.s=t;}function a(i,t){u.call(this,void 0);this.x=i;this.s=void 0;this.g=e-1;this.f=4;this.W=null==t?void 0:t.watched;this.Z=null==t?void 0:t.unwatched;}a.prototype=new u;a.prototype.h=function(){this.f&=-3;if(1&this.f)return false;if(32==(36&this.f))return true;this.f&=-5;if(this.g===e)return true;this.g=e;this.f|=1;if(this.i>0&&!v(this)){this.f&=-2;return true}const i=n;try{l(this);n=this;const i=this.x();if(16&this.f||this.v!==i||0===this.i){this.v=i;this.f&=-17;this.i++;}}catch(i){this.v=i;this.f|=16;this.i++;}n=i;y(this);this.f&=-2;return true};a.prototype.S=function(i){if(void 0===this.t){this.f|=36;for(let i=this.s;void 0!==i;i=i.n)i.S.S(i);}u.prototype.S.call(this,i);};a.prototype.U=function(i){if(void 0!==this.t){u.prototype.U.call(this,i);if(void 0===this.t){this.f&=-33;for(let i=this.s;void 0!==i;i=i.n)i.S.U(i);}}};a.prototype.N=function(){if(!(2&this.f)){this.f|=6;for(let i=this.t;void 0!==i;i=i.x)i.t.N();}};Object.defineProperty(a.prototype,"value",{get(){if(1&this.f)throw new Error("Cycle detected");const i=c(this);this.h();if(void 0!==i)i.i=this.i;if(16&this.f)throw this.v;return this.v}});function _(i){const o=i.u;i.u=void 0;if("function"==typeof o){r++;const s=n;n=void 0;try{o();}catch(t){i.f&=-2;i.f|=8;g(i);throw t}finally{n=s;t();}}}function g(i){for(let t=i.s;void 0!==t;t=t.n)t.S.U(t);i.x=void 0;i.s=void 0;_(i);}function p(i){if(n!==this)throw new Error("Out-of-order effect");y(this);n=i;this.f&=-2;if(8&this.f)g(this);t();}function b(i){this.x=i;this.u=void 0;this.s=void 0;this.o=void 0;this.f=32;}b.prototype.c=function(){const i=this.S();try{if(8&this.f)return;if(void 0===this.x)return;const t=this.x();if("function"==typeof t)this.u=t;}finally{i();}};b.prototype.S=function(){if(1&this.f)throw new Error("Cycle detected");this.f|=1;this.f&=-9;_(this);l(this);r++;const i=n;n=this;return p.bind(this,i)};b.prototype.N=function(){if(!(2&this.f)){this.f|=2;this.o=s;s=this;}};b.prototype.d=function(){this.f|=8;if(!(1&this.f))g(this);};function E(i){const t=new b(i);try{t.c();}catch(i){t.d();throw i}return t.d.bind(t)} function createSystem(queries = {}, schema = {}) { var _a; return _a = class { constructor(world, queryManager, priority) { this.world = world; this.queryManager = queryManager; this.priority = priority; this.isPaused = false; this.config = {}; for (const key in schema) { const k = key; // Initialize config signals with strongly-typed defaults this.config[k] = d(schema[k].default); } } get globals() { return this.world.globals; } createEntity() { return this.world.createEntity(); } init() { } update(_delta, _time) { } play() { this.isPaused = false; } stop() { this.isPaused = true; } destroy() { } }, _a.schema = schema, _a.isSystem = true, _a.queries = queries, _a; } const VERSION = '3.3.0'; function eq(component, key, value) { return { component, key: key, op: 'eq', value }; } function ne(component, key, value) { return { component, key: key, op: 'ne', value }; } function lt(component, key, value) { return { component, key: key, op: 'lt', value }; } function le(component, key, value) { return { component, key: key, op: 'le', value }; } function gt(component, key, value) { return { component, key: key, op: 'gt', value }; } function ge(component, key, value) { return { component, key: key, op: 'ge', value }; } function isin(component, key, values) { return { component, key: key, op: 'in', value: values }; } function nin(component, key, values) { return { component, key: key, op: 'nin', value: values }; } export { ComponentRegistry, Entity, Query, TypedArrayMap, Types, VERSION, World, assignInitialComponentData, createComponent, createSystem, eq, ge, gt, initializeComponentStorage, isin, le, lt, ne, nin };