// src/core/utils/defineHiddenProperty.ts var defineHiddenProperty = (obj, key, value) => Object.defineProperty(obj, key, { value, enumerable: false, writable: true, configurable: true }); // src/core/EntityIndex.ts var getId = (index, id) => id & index.entityMask; var getVersion = (index, id) => id >>> index.versionShift & (1 << index.versionBits) - 1; var incrementVersion = (index, id) => { const currentVersion = getVersion(index, id); const newVersion = currentVersion + 1 & (1 << index.versionBits) - 1; return id & index.entityMask | newVersion << index.versionShift; }; var withVersioning = (versionBits) => ({ versioning: true, versionBits }); var createEntityIndex = (options) => { const config = options ? typeof options === "function" ? options() : options : { versioning: false, versionBits: 8 }; const versionBits = config.versionBits ?? 8; const versioning = config.versioning ?? false; const entityBits = 32 - versionBits; const entityMask = (1 << entityBits) - 1; const versionShift = entityBits; const versionMask = (1 << versionBits) - 1 << versionShift; return { aliveCount: 0, dense: [], sparse: [], maxId: 0, versioning, versionBits, entityMask, versionShift, versionMask }; }; var addEntityId = (index) => { if (index.aliveCount < index.dense.length) { const recycledId = index.dense[index.aliveCount]; const entityId = recycledId; index.sparse[entityId] = index.aliveCount; index.aliveCount++; return recycledId; } const id = ++index.maxId; index.dense.push(id); index.sparse[id] = index.aliveCount; index.aliveCount++; return id; }; var removeEntityId = (index, id) => { const denseIndex = index.sparse[id]; if (denseIndex === void 0 || denseIndex >= index.aliveCount) { return; } const lastIndex = index.aliveCount - 1; const lastId = index.dense[lastIndex]; index.sparse[lastId] = denseIndex; index.dense[denseIndex] = lastId; index.sparse[id] = lastIndex; index.dense[lastIndex] = id; if (index.versioning) { const newId = incrementVersion(index, id); index.dense[lastIndex] = newId; } index.aliveCount--; }; var isEntityIdAlive = (index, id) => { const entityId = getId(index, id); const denseIndex = index.sparse[entityId]; return denseIndex !== void 0 && denseIndex < index.aliveCount && index.dense[denseIndex] === id; }; // src/core/World.ts var $internal = Symbol.for("bitecs_internal"); var createBaseWorld = (context, entityIndex) => defineHiddenProperty(context || {}, $internal, { entityIndex: entityIndex || createEntityIndex(), entityMasks: [[]], entityComponents: /* @__PURE__ */ new Map(), bitflag: 1, componentMap: /* @__PURE__ */ new Map(), componentCount: 0, queries: /* @__PURE__ */ new Set(), queriesHashMap: /* @__PURE__ */ new Map(), notQueries: /* @__PURE__ */ new Set(), dirtyQueries: /* @__PURE__ */ new Set(), entitiesWithRelations: /* @__PURE__ */ new Set() }); function createWorld(...args) { let entityIndex; let context; args.forEach((arg) => { if (typeof arg === "object" && "add" in arg && "remove" in arg) { entityIndex = arg; } else if (typeof arg === "object") { context = arg; } }); return createBaseWorld(context, entityIndex); } var resetWorld = (world) => { const ctx = world[$internal]; ctx.entityIndex = createEntityIndex(); ctx.entityMasks = [[]]; ctx.entityComponents = /* @__PURE__ */ new Map(); ctx.bitflag = 1; ctx.componentMap = /* @__PURE__ */ new Map(); ctx.componentCount = 0; ctx.queries = /* @__PURE__ */ new Set(); ctx.queriesHashMap = /* @__PURE__ */ new Map(); ctx.notQueries = /* @__PURE__ */ new Set(); ctx.dirtyQueries = /* @__PURE__ */ new Set(); ctx.entitiesWithRelations = /* @__PURE__ */ new Set(); return world; }; var deleteWorld = (world) => { delete world[$internal]; }; var getWorldComponents = (world) => Object.keys(world[$internal].componentMap); var getAllEntities = (world) => Array.from(world[$internal].entityComponents.keys()); // src/core/utils/SparseSet.ts var createSparseSet = () => { const dense = []; const sparse = []; const has = (val) => dense[sparse[val]] === val; const add = (val) => { if (has(val)) return; sparse[val] = dense.push(val) - 1; }; const remove = (val) => { if (!has(val)) return; const index = sparse[val]; const swapped = dense.pop(); if (swapped !== val) { dense[index] = swapped; sparse[swapped] = index; } }; const reset = () => { dense.length = 0; sparse.length = 0; }; return { add, remove, has, sparse, dense, reset }; }; var SharedArrayBufferOrArrayBuffer = typeof SharedArrayBuffer !== "undefined" ? SharedArrayBuffer : ArrayBuffer; var createUint32SparseSet = (initialCapacity = 1e3) => { const sparse = []; let length = 0; let dense = new Uint32Array(new SharedArrayBufferOrArrayBuffer(initialCapacity * 4)); const has = (val) => val < sparse.length && sparse[val] < length && dense[sparse[val]] === val; const add = (val) => { if (has(val)) return; if (length >= dense.length) { const newDense = new Uint32Array(new SharedArrayBufferOrArrayBuffer(dense.length * 2 * 4)); newDense.set(dense); dense = newDense; } dense[length] = val; sparse[val] = length; length++; }; const remove = (val) => { if (!has(val)) return; length--; const index = sparse[val]; const swapped = dense[length]; dense[index] = swapped; sparse[swapped] = index; }; const reset = () => { length = 0; sparse.length = 0; }; return { add, remove, has, sparse, get dense() { return new Uint32Array(dense.buffer, 0, length); }, reset }; }; // src/core/utils/Observer.ts var createObservable = () => { const observers = /* @__PURE__ */ new Set(); const subscribe = (observer) => { observers.add(observer); return () => { observers.delete(observer); }; }; const notify = (entity, ...args) => { return Array.from(observers).reduce((acc, listener) => { const result = listener(entity, ...args); return result && typeof result === "object" ? { ...acc, ...result } : acc; }, {}); }; return { subscribe, notify }; }; // src/core/Query.ts var $opType = Symbol.for("bitecs-opType"); var $opTerms = Symbol.for("bitecs-opTerms"); var Or = (...components) => ({ [$opType]: "Or", [$opTerms]: components }); var And = (...components) => ({ [$opType]: "And", [$opTerms]: components }); var Not = (...components) => ({ [$opType]: "Not", [$opTerms]: components }); var Any = Or; var All = And; var None = Not; var onAdd = (...terms) => ({ [$opType]: "add", [$opTerms]: terms }); var onRemove = (...terms) => ({ [$opType]: "remove", [$opTerms]: terms }); var onSet = (component) => ({ [$opType]: "set", [$opTerms]: [component] }); var onGet = (component) => ({ [$opType]: "get", [$opTerms]: [component] }); function observe(world, hook, callback) { const ctx = world[$internal]; const { [$opType]: type, [$opTerms]: components } = hook; if (type === "add" || type === "remove") { const hash = queryHash(world, components); let queryData = ctx.queriesHashMap.get(hash); if (!queryData) { queryData = registerQuery(world, components); } const observableKey = type === "add" ? "addObservable" : "removeObservable"; return queryData[observableKey].subscribe(callback); } else if (type === "set" || type === "get") { if (components.length !== 1) { throw new Error("Set and Get hooks can only observe a single component"); } const component = components[0]; let componentData = ctx.componentMap.get(component); if (!componentData) { componentData = registerComponent(world, component); } const observableKey = type === "set" ? "setObservable" : "getObservable"; return componentData[observableKey].subscribe(callback); } throw new Error(`Invalid hook type: ${type}`); } var queryHash = (world, terms) => { const ctx = world[$internal]; const getComponentId = (component) => { if (!ctx.componentMap.has(component)) { registerComponent(world, component); } return ctx.componentMap.get(component).id; }; const termToString = (term) => { if ($opType in term) { const componentIds = term[$opTerms].map(getComponentId); const sortedComponentIds = componentIds.sort((a, b) => a - b); const sortedType = term[$opType].toLowerCase(); return `${sortedType}(${sortedComponentIds.join(",")})`; } else { return getComponentId(term).toString(); } }; return terms.map(termToString).sort().join("-"); }; var registerQuery = (world, terms, options = {}) => { const ctx = world[$internal]; const hash = queryHash(world, terms); const components = []; const notComponents = []; const orComponents = []; const processComponents = (comps, targetArray) => { comps.forEach((comp) => { if (!ctx.componentMap.has(comp)) registerComponent(world, comp); targetArray.push(comp); }); }; terms.forEach((term) => { if ($opType in term) { if (term[$opType] === "Not") { processComponents(term[$opTerms], notComponents); } else if (term[$opType] === "Or") { processComponents(term[$opTerms], orComponents); } } else { if (!ctx.componentMap.has(term)) registerComponent(world, term); components.push(term); } }); const mapComponents = (c) => ctx.componentMap.get(c); const allComponents = components.concat(notComponents.flat()).concat(orComponents.flat()).map(mapComponents); const sparseSet = options.buffered ? createUint32SparseSet() : createSparseSet(); const toRemove = createSparseSet(); const generations = allComponents.map((c) => c.generationId).reduce((a, v) => { if (a.includes(v)) return a; a.push(v); return a; }, []); const reduceBitflags = (a, c) => { if (!a[c.generationId]) a[c.generationId] = 0; a[c.generationId] |= c.bitflag; return a; }; const masks = components.map(mapComponents).reduce(reduceBitflags, {}); const notMasks = notComponents.map(mapComponents).reduce(reduceBitflags, {}); const orMasks = orComponents.map(mapComponents).reduce(reduceBitflags, {}); const hasMasks = allComponents.reduce(reduceBitflags, {}); const addObservable = createObservable(); const removeObservable = createObservable(); const query2 = Object.assign(sparseSet, { components, notComponents, orComponents, allComponents, masks, notMasks, orMasks, hasMasks, generations, toRemove, addObservable, removeObservable, queues: {} }); ctx.queries.add(query2); ctx.queriesHashMap.set(hash, query2); allComponents.forEach((c) => { c.queries.add(query2); }); if (notComponents.length) ctx.notQueries.add(query2); const entityIndex = ctx.entityIndex; for (let i = 0; i < entityIndex.aliveCount; i++) { const eid = entityIndex.dense[i]; if (hasComponent(world, eid, Prefab)) continue; const match = queryCheckEntity(world, query2, eid); if (match) { queryAddEntity(query2, eid); } } return query2; }; function innerQuery(world, terms, options = {}) { const ctx = world[$internal]; const hash = queryHash(world, terms); let queryData = ctx.queriesHashMap.get(hash); if (!queryData) { queryData = registerQuery(world, terms, options); } else if (options.buffered && !("buffer" in queryData.dense)) { queryData = registerQuery(world, terms, { buffered: true }); } return queryData.dense; } function query(world, terms) { commitRemovals(world); return innerQuery(world, terms); } function queryCheckEntity(world, query2, eid) { const ctx = world[$internal]; const { masks, notMasks, orMasks, generations } = query2; for (let i = 0; i < generations.length; i++) { const generationId = generations[i]; const qMask = masks[generationId]; const qNotMask = notMasks[generationId]; const qOrMask = orMasks[generationId]; const eMask = ctx.entityMasks[generationId][eid]; if (qNotMask && (eMask & qNotMask) !== 0) { return false; } if (qMask && (eMask & qMask) !== qMask) { return false; } if (qOrMask && (eMask & qOrMask) === 0) { return false; } } return true; } var queryAddEntity = (query2, eid) => { query2.toRemove.remove(eid); query2.addObservable.notify(eid); query2.add(eid); }; var queryCommitRemovals = (query2) => { for (let i = 0; i < query2.toRemove.dense.length; i++) { const eid = query2.toRemove.dense[i]; query2.remove(eid); } query2.toRemove.reset(); }; var commitRemovals = (world) => { const ctx = world[$internal]; if (!ctx.dirtyQueries.size) return; ctx.dirtyQueries.forEach(queryCommitRemovals); ctx.dirtyQueries.clear(); }; var queryRemoveEntity = (world, query2, eid) => { const ctx = world[$internal]; const has = query2.has(eid); if (!has || query2.toRemove.has(eid)) return; query2.toRemove.add(eid); ctx.dirtyQueries.add(query2); query2.removeObservable.notify(eid); }; var removeQuery = (world, terms) => { const ctx = world[$internal]; const hash = queryHash(world, terms); const query2 = ctx.queriesHashMap.get(hash); if (query2) { ctx.queries.delete(query2); ctx.queriesHashMap.delete(hash); } }; // src/core/Relation.ts var $relation = Symbol.for("bitecs-relation"); var $pairTarget = Symbol.for("bitecs-pairTarget"); var $isPairComponent = Symbol.for("bitecs-isPairComponent"); var $relationData = Symbol.for("bitecs-relationData"); var createBaseRelation = () => { const data = { pairsMap: /* @__PURE__ */ new Map(), initStore: void 0, exclusiveRelation: false, autoRemoveSubject: false, onTargetRemoved: void 0 }; const relation = (target) => { if (target === void 0) throw Error("Relation target is undefined"); const normalizedTarget = target === "*" ? Wildcard : target; if (!data.pairsMap.has(normalizedTarget)) { const component = data.initStore ? data.initStore(target) : {}; defineHiddenProperty(component, $relation, relation); defineHiddenProperty(component, $pairTarget, normalizedTarget); defineHiddenProperty(component, $isPairComponent, true); data.pairsMap.set(normalizedTarget, component); } return data.pairsMap.get(normalizedTarget); }; defineHiddenProperty(relation, $relationData, data); return relation; }; var withStore = (createStore) => (relation) => { const ctx = relation[$relationData]; ctx.initStore = createStore; return relation; }; var makeExclusive = (relation) => { const ctx = relation[$relationData]; ctx.exclusiveRelation = true; return relation; }; var withAutoRemoveSubject = (relation) => { const ctx = relation[$relationData]; ctx.autoRemoveSubject = true; return relation; }; var withOnTargetRemoved = (onRemove2) => (relation) => { const ctx = relation[$relationData]; ctx.onTargetRemoved = onRemove2; return relation; }; var Pair = (relation, target) => { if (relation === void 0) throw Error("Relation is undefined"); return relation(target); }; var getRelationTargets = (world, eid, relation) => { const components = getEntityComponents(world, eid); const targets = []; for (const c of components) { if (c[$relation] === relation && c[$pairTarget] !== Wildcard && !isRelation(c[$pairTarget])) { targets.push(c[$pairTarget]); } } return targets; }; function createRelation(...args) { if (args.length === 1 && typeof args[0] === "object") { const { store, exclusive, autoRemoveSubject, onTargetRemoved } = args[0]; const modifiers = [ store && withStore(store), exclusive && makeExclusive, autoRemoveSubject && withAutoRemoveSubject, onTargetRemoved && withOnTargetRemoved(onTargetRemoved) ].filter(Boolean); return modifiers.reduce((acc, modifier) => modifier(acc), createBaseRelation()); } else { const modifiers = args; return modifiers.reduce((acc, modifier) => modifier(acc), createBaseRelation()); } } var $wildcard = Symbol.for("bitecs-wildcard"); function createWildcardRelation() { const relation = createBaseRelation(); Object.defineProperty(relation, $wildcard, { value: true, enumerable: false, writable: false, configurable: false }); return relation; } function getWildcard() { const GLOBAL_WILDCARD = Symbol.for("bitecs-global-wildcard"); if (!globalThis[GLOBAL_WILDCARD]) { globalThis[GLOBAL_WILDCARD] = createWildcardRelation(); } return globalThis[GLOBAL_WILDCARD]; } var Wildcard = getWildcard(); function createIsARelation() { return createBaseRelation(); } function getIsA() { const GLOBAL_ISA = Symbol.for("bitecs-global-isa"); if (!globalThis[GLOBAL_ISA]) { globalThis[GLOBAL_ISA] = createIsARelation(); } return globalThis[GLOBAL_ISA]; } var IsA = getIsA(); function isWildcard(relation) { if (!relation) return false; const symbols = Object.getOwnPropertySymbols(relation); return symbols.includes($wildcard); } function isRelation(component) { if (!component) return false; const symbols = Object.getOwnPropertySymbols(component); return symbols.includes($relationData); } // src/core/Component.ts var registerComponent = (world, component) => { if (!component) { throw new Error(`bitECS - Cannot register null or undefined component`); } const ctx = world[$internal]; const queries = /* @__PURE__ */ new Set(); const data = { id: ctx.componentCount++, generationId: ctx.entityMasks.length - 1, bitflag: ctx.bitflag, ref: component, queries, setObservable: createObservable(), getObservable: createObservable() }; ctx.componentMap.set(component, data); ctx.bitflag *= 2; if (ctx.bitflag >= 2 ** 31) { ctx.bitflag = 1; ctx.entityMasks.push([]); } return data; }; var registerComponents = (world, components) => { components.forEach((component) => registerComponent(world, component)); }; var hasComponent = (world, eid, component) => { const ctx = world[$internal]; const registeredComponent = ctx.componentMap.get(component); if (!registeredComponent) return false; const { generationId, bitflag } = registeredComponent; const mask = ctx.entityMasks[generationId][eid]; return (mask & bitflag) === bitflag; }; var getComponentData = (world, eid, component) => { const ctx = world[$internal]; const componentData = ctx.componentMap.get(component); if (!componentData) { return void 0; } if (!hasComponent(world, eid, component)) { return void 0; } return componentData.getObservable.notify(eid); }; var set = (component, data) => ({ component, data }); var recursivelyInherit = (ctx, world, baseEid, inheritedEid, visited = /* @__PURE__ */ new Set()) => { if (visited.has(inheritedEid)) return; visited.add(inheritedEid); addComponent(world, baseEid, IsA(inheritedEid)); for (const component of getEntityComponents(world, inheritedEid)) { if (component === Prefab) continue; if (!hasComponent(world, baseEid, component)) { addComponent(world, baseEid, component); const componentData = ctx.componentMap.get(component); if (componentData?.setObservable) { const data = getComponentData(world, inheritedEid, component); componentData.setObservable.notify(baseEid, data); } } } for (const parentEid of getRelationTargets(world, inheritedEid, IsA)) { recursivelyInherit(ctx, world, baseEid, parentEid, visited); } }; var addComponent = (world, eid, ...components) => { if (!entityExists(world, eid)) { throw new Error(`Cannot add component - entity ${eid} does not exist in the world.`); } const ctx = world[$internal]; components.forEach((componentOrSet) => { const component = "component" in componentOrSet ? componentOrSet.component : componentOrSet; const data = "data" in componentOrSet ? componentOrSet.data : void 0; if (!ctx.componentMap.has(component)) registerComponent(world, component); const componentData = ctx.componentMap.get(component); if (data !== void 0) { componentData.setObservable.notify(eid, data); } if (hasComponent(world, eid, component)) return; const { generationId, bitflag, queries } = componentData; ctx.entityMasks[generationId][eid] |= bitflag; if (!hasComponent(world, eid, Prefab)) { queries.forEach((queryData) => { queryData.toRemove.remove(eid); const match = queryCheckEntity(world, queryData, eid); if (match) queryAddEntity(queryData, eid); else queryRemoveEntity(world, queryData, eid); }); } ctx.entityComponents.get(eid).add(component); if (component[$isPairComponent]) { const relation = component[$relation]; const target = component[$pairTarget]; addComponent(world, eid, Pair(relation, Wildcard)); addComponent(world, eid, Pair(Wildcard, target)); if (typeof target === "number") { addComponent(world, target, Pair(Wildcard, eid)); addComponent(world, target, Pair(Wildcard, relation)); ctx.entitiesWithRelations.add(target); ctx.entitiesWithRelations.add(eid); } ctx.entitiesWithRelations.add(target); const relationData = relation[$relationData]; if (relationData.exclusiveRelation === true && target !== Wildcard) { const oldTarget = getRelationTargets(world, eid, relation)[0]; if (oldTarget !== void 0 && oldTarget !== null && oldTarget !== target) { removeComponent(world, eid, relation(oldTarget)); } } if (relation === IsA) { const inheritedTargets = getRelationTargets(world, eid, IsA); for (const inherited of inheritedTargets) { recursivelyInherit(ctx, world, eid, inherited); } } } }); }; var addComponents = addComponent; var removeComponent = (world, eid, ...components) => { const ctx = world[$internal]; if (!entityExists(world, eid)) { throw new Error(`Cannot remove component - entity ${eid} does not exist in the world.`); } components.forEach((component) => { if (!hasComponent(world, eid, component)) return; const componentNode = ctx.componentMap.get(component); const { generationId, bitflag, queries } = componentNode; ctx.entityMasks[generationId][eid] &= ~bitflag; queries.forEach((queryData) => { queryData.toRemove.remove(eid); const match = queryCheckEntity(world, queryData, eid); if (match) queryAddEntity(queryData, eid); else queryRemoveEntity(world, queryData, eid); }); ctx.entityComponents.get(eid).delete(component); if (component[$isPairComponent]) { const target = component[$pairTarget]; removeComponent(world, eid, Pair(Wildcard, target)); const relation = component[$relation]; const otherTargets = getRelationTargets(world, eid, relation); if (otherTargets.length === 0) { removeComponent(world, eid, Pair(relation, Wildcard)); } } }); }; var removeComponents = removeComponent; // src/core/Entity.ts var Prefab = {}; var addPrefab = (world) => { const eid = addEntity(world); addComponent(world, eid, Prefab); return eid; }; var addEntity = (world) => { const ctx = world[$internal]; const eid = addEntityId(ctx.entityIndex); ctx.notQueries.forEach((q) => { const match = queryCheckEntity(world, q, eid); if (match) queryAddEntity(q, eid); }); ctx.entityComponents.set(eid, /* @__PURE__ */ new Set()); return eid; }; var removeEntity = (world, eid) => { const ctx = world[$internal]; if (!isEntityIdAlive(ctx.entityIndex, eid)) return; const removalQueue = [eid]; const processedEntities = /* @__PURE__ */ new Set(); while (removalQueue.length > 0) { const currentEid = removalQueue.shift(); if (processedEntities.has(currentEid)) continue; processedEntities.add(currentEid); const componentRemovalQueue = []; if (ctx.entitiesWithRelations.has(currentEid)) { for (const subject of innerQuery(world, [Wildcard(currentEid)])) { if (!entityExists(world, subject)) { continue; } for (const component of ctx.entityComponents.get(subject)) { if (!component[$isPairComponent]) { continue; } const relation = component[$relation]; const relationData = relation[$relationData]; componentRemovalQueue.push(() => removeComponent(world, subject, Pair(Wildcard, currentEid))); if (component[$pairTarget] === currentEid) { componentRemovalQueue.push(() => removeComponent(world, subject, component)); if (relationData.autoRemoveSubject) { removalQueue.push(subject); } if (relationData.onTargetRemoved) { componentRemovalQueue.push(() => relationData.onTargetRemoved(world, subject, currentEid)); } } } } ctx.entitiesWithRelations.delete(currentEid); } for (const removeOperation of componentRemovalQueue) { removeOperation(); } for (const eid2 of removalQueue) { removeEntity(world, eid2); } for (const query2 of ctx.queries) { queryRemoveEntity(world, query2, currentEid); } removeEntityId(ctx.entityIndex, currentEid); ctx.entityComponents.delete(currentEid); for (let i = 0; i < ctx.entityMasks.length; i++) { ctx.entityMasks[i][currentEid] = 0; } } }; var getEntityComponents = (world, eid) => { const ctx = world[$internal]; if (eid === void 0) throw new Error(`getEntityComponents: entity id is undefined.`); if (!isEntityIdAlive(ctx.entityIndex, eid)) throw new Error(`getEntityComponents: entity ${eid} does not exist in the world.`); return Array.from(ctx.entityComponents.get(eid)); }; var entityExists = (world, eid) => isEntityIdAlive(world[$internal].entityIndex, eid); // src/core/utils/pipe.ts var pipe = (...functions) => { return (...args) => functions.reduce((result, fn) => [fn(...result)], args)[0]; }; export { $internal, All, And, Any, IsA, None, Not, Or, Pair, Prefab, Wildcard, addComponent, addComponents, addEntity, addPrefab, commitRemovals, createEntityIndex, createRelation, createWorld, deleteWorld, entityExists, getAllEntities, getComponentData, getEntityComponents, getId, getRelationTargets, getVersion, getWorldComponents, hasComponent, innerQuery, isRelation, isWildcard, observe, onAdd, onGet, onRemove, onSet, pipe, query, registerComponent, registerComponents, registerQuery, removeComponent, removeComponents, removeEntity, removeQuery, resetWorld, set, withAutoRemoveSubject, withOnTargetRemoved, withStore, withVersioning }; //# sourceMappingURL=index.mjs.map