aiecsjs 0.5.6 → 0.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -12
- package/README_ZHTW.md +21 -12
- package/api.json +23 -0
- package/dist/chunk-22ICWJ5O.js +2 -0
- package/dist/chunk-22ICWJ5O.js.map +1 -0
- package/dist/chunk-2KCN5RVK.js +2 -0
- package/dist/chunk-2KCN5RVK.js.map +1 -0
- package/dist/chunk-5QWEV4VJ.cjs +2 -0
- package/dist/chunk-5QWEV4VJ.cjs.map +1 -0
- package/dist/chunk-AGWUE6JB.js +2 -0
- package/dist/chunk-AGWUE6JB.js.map +1 -0
- package/dist/chunk-P5GW7GKY.cjs +2 -0
- package/dist/chunk-P5GW7GKY.cjs.map +1 -0
- package/dist/chunk-SRX2MZPX.cjs +2 -0
- package/dist/chunk-SRX2MZPX.cjs.map +1 -0
- package/dist/commands.cjs +1 -1
- package/dist/commands.cjs.map +1 -1
- package/dist/commands.d.cts +1 -1
- package/dist/commands.d.ts +1 -1
- package/dist/commands.js +1 -1
- package/dist/commands.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +49 -4
- package/dist/index.d.ts +49 -4
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/observers.cjs +1 -1
- package/dist/observers.d.cts +1 -1
- package/dist/observers.d.ts +1 -1
- package/dist/observers.js +1 -1
- package/dist/observers.js.map +1 -1
- package/dist/relations.cjs +1 -1
- package/dist/relations.d.cts +1 -1
- package/dist/relations.d.ts +1 -1
- package/dist/relations.js +1 -1
- package/dist/relations.js.map +1 -1
- package/dist/serialize.cjs +1 -1
- package/dist/serialize.d.cts +1 -1
- package/dist/serialize.d.ts +1 -1
- package/dist/serialize.js +1 -1
- package/dist/{types-BGEeHad-.d.cts → types-BeVLA7xG.d.cts} +11 -1
- package/dist/{types-BGEeHad-.d.ts → types-BeVLA7xG.d.ts} +11 -1
- package/dist/worker.cjs +1 -1
- package/dist/worker.cjs.map +1 -1
- package/dist/worker.d.cts +2 -2
- package/dist/worker.d.ts +2 -2
- package/dist/worker.js +1 -1
- package/dist/worker.js.map +1 -1
- package/llms-full.txt +50 -12
- package/package.json +7 -2
- package/dist/chunk-FZ5DSL4K.cjs +0 -2
- package/dist/chunk-FZ5DSL4K.cjs.map +0 -1
- package/dist/chunk-H3E365XN.cjs +0 -2
- package/dist/chunk-H3E365XN.cjs.map +0 -1
- package/dist/chunk-IFA7MVBB.js +0 -2
- package/dist/chunk-IFA7MVBB.js.map +0 -1
- package/dist/chunk-L3H4HVUJ.cjs +0 -2
- package/dist/chunk-L3H4HVUJ.cjs.map +0 -1
- package/dist/chunk-R67UJPKI.js +0 -2
- package/dist/chunk-R67UJPKI.js.map +0 -1
- package/dist/chunk-TKDLUVJR.js +0 -2
- package/dist/chunk-TKDLUVJR.js.map +0 -1
package/dist/commands.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands.ts"],"names":["createCommandBuffer","world","buf","getWorldState","makeApi","flush","cb","state","stateOf","lookupWorld","placeholders","op","createEntity","resolve","eid","num","real","addComponent","removeComponent","destroyEntity","withCommandBuffer","fn","result","cbStateMap","api","component","initial","ph","s","worldId","lookupState"],"mappings":"4CAeO,SAASA,EAAoBC,CAAAA,CAA6B,CAE/D,IAAMC,CAAAA,CAA0B,CAC9B,OAAA,CAFYC,CAAAA,CAAcF,CAAK,CAAA,CAEhB,GACf,GAAA,CAAK,EAAC,CACN,eAAA,CAAiB,GACjB,QAAA,CAAU,KACZ,CAAA,CACA,
|
|
1
|
+
{"version":3,"sources":["../src/commands.ts"],"names":["createCommandBuffer","world","buf","getWorldState","makeApi","flush","cb","state","stateOf","lookupWorld","placeholders","op","createEntity","resolve","eid","num","real","addComponent","removeComponent","destroyEntity","withCommandBuffer","fn","result","cbStateMap","api","component","initial","ph","s","worldId","lookupState"],"mappings":"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","file":"commands.js","sourcesContent":["// aiecsjs/commands — deferred structural mutations during iteration.\n\nimport { addComponent, removeComponent } from './internal/component.js'\nimport { createEntity, destroyEntity } from './internal/entity.js'\nimport type {\n CommandBuffer,\n CommandBufferState,\n CommandOp,\n ComponentInit,\n ComponentLike,\n EntityId,\n World,\n} from './internal/types.js'\nimport { getWorldState } from './internal/world.js'\n\nexport function createCommandBuffer(world: World): CommandBuffer {\n const state = getWorldState(world)\n const buf: CommandBufferState = {\n worldId: state.id,\n ops: [],\n nextPlaceholder: -1,\n flushing: false,\n }\n return makeApi(buf)\n}\n\nexport function flush(cb: CommandBuffer): void {\n const state = stateOf(cb)\n if (state.flushing) return\n state.flushing = true\n try {\n const world = lookupWorld(state.worldId)\n\n // Phase 1: resolve placeholders by creating real entities first\n const placeholders = new Map<number, EntityId>()\n for (const op of state.ops) {\n if (op.kind === 'create') {\n placeholders.set(op.placeholder, createEntity(world))\n }\n }\n\n const resolve = (eid: EntityId): EntityId => {\n const num = eid as number\n if (num < 0) {\n const real = placeholders.get(num)\n if (real === undefined) throw new Error(`aiecsjs: unresolved placeholder ${num}`)\n return real\n }\n return eid\n }\n\n // Phase 2: add/remove in queue order\n for (const op of state.ops) {\n if (op.kind === 'add') {\n addComponent(\n world,\n resolve(op.eid),\n op.component,\n op.initial as ComponentInit<ComponentLike>,\n )\n } else if (op.kind === 'remove') {\n removeComponent(world, resolve(op.eid), op.component)\n }\n }\n\n // Phase 3: destroy last\n for (const op of state.ops) {\n if (op.kind === 'destroy') {\n destroyEntity(world, resolve(op.eid))\n }\n }\n\n state.ops.length = 0\n state.nextPlaceholder = -1\n } finally {\n state.flushing = false\n }\n}\n\nexport function withCommandBuffer<R>(world: World, fn: (cb: CommandBuffer) => R): R {\n const cb = createCommandBuffer(world)\n const result = fn(cb)\n flush(cb)\n return result\n}\n\n// --- Internals ---\n\nconst cbStateMap = new WeakMap<object, CommandBufferState>()\n\nfunction makeApi(state: CommandBufferState): CommandBuffer {\n const api: CommandBuffer = {\n add(eid, component, initial) {\n state.ops.push({ kind: 'add', eid, component, initial })\n },\n remove(eid, component) {\n state.ops.push({ kind: 'remove', eid, component })\n },\n destroy(eid) {\n state.ops.push({ kind: 'destroy', eid })\n },\n create() {\n const ph = state.nextPlaceholder--\n state.ops.push({ kind: 'create', placeholder: ph })\n return ph as EntityId\n },\n }\n cbStateMap.set(api, state)\n return api\n}\n\nfunction stateOf(cb: CommandBuffer): CommandBufferState {\n const s = cbStateMap.get(cb)\n if (!s) throw new Error('aiecsjs: unknown CommandBuffer')\n return s\n}\n\n// Look up a public World from its state id. We need a registry — re-import lazily.\nfunction lookupWorld(worldId: number): World {\n // The world registry is in internal/world.ts. We need to construct a public reference.\n // Since destroy clears state, we look up afresh by id via the worldRegistry.\n const state = lookupState(worldId)\n return { id: state.id, capacity: state.capacity, version: state.version } as World\n}\n\nfunction lookupState(worldId: number): import('./internal/types.js').WorldState {\n // Access worldRegistry indirectly via getWorldState requires a World object.\n // Use a small workaround: construct a minimal World-like and call getWorldState.\n return getWorldState({ id: worldId, capacity: 0, version: '0.0.0' } as World)\n}\n"]}
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
'use strict';var
|
|
1
|
+
'use strict';var chunkSRX2MZPX_cjs=require('./chunk-SRX2MZPX.cjs'),chunk5QWEV4VJ_cjs=require('./chunk-5QWEV4VJ.cjs');var J={i8:"i8",u8:"u8",i16:"i16",u16:"u16",i32:"i32",u32:"u32",f32:"f32",f64:"f64",eid:"eid",bool:"bool"};function X(...n){return n.length===0?e=>e:n.length===1?n[0]:(e,r)=>{let t=e;for(let o of n)t=o(t,r);return t}}var i=class extends Error{eid;constructor(e){super(`aiecsjs: entity ${e} is not alive`),this.name="EntityNotAliveError",this.eid=e;}};function Y(n,e){let r=chunk5QWEV4VJ_cjs.j(n);if(!chunk5QWEV4VJ_cjs.z(r,e))throw new i(e);return Object.freeze({id:e,worldId:r.id})}function b(n,e){let r=chunk5QWEV4VJ_cjs.j(n);if(e.worldId!==r.id)return null;let t=e.id,o=chunk5QWEV4VJ_cjs.t(t,r.options);if(o<=0||o>=r.capacity)return null;let x=r.entityArchetype[o]??0,d=r.archetypes[x];if(!d||!d.entityRow.has(e.id))return null;let C=r.generations[o]??0,A=chunk5QWEV4VJ_cjs.u(t,r.options);return C!==A?null:e.id}function Z(n,e){return b(n,e)!==null}chunkSRX2MZPX_cjs.j(n=>chunk5QWEV4VJ_cjs.R(n));chunk5QWEV4VJ_cjs.P((n,e,r,t,o)=>{chunkSRX2MZPX_cjs.i(n,e,r,t,o);});var pe=typeof SharedArrayBuffer<"u";Object.defineProperty(exports,"defineQuery",{enumerable:true,get:function(){return chunkSRX2MZPX_cjs.a}});Object.defineProperty(exports,"enterQuery",{enumerable:true,get:function(){return chunkSRX2MZPX_cjs.b}});Object.defineProperty(exports,"exitQuery",{enumerable:true,get:function(){return chunkSRX2MZPX_cjs.c}});Object.defineProperty(exports,"forEachEntity",{enumerable:true,get:function(){return chunkSRX2MZPX_cjs.g}});Object.defineProperty(exports,"forEachEntityIndexed",{enumerable:true,get:function(){return chunkSRX2MZPX_cjs.h}});Object.defineProperty(exports,"iterQuery",{enumerable:true,get:function(){return chunkSRX2MZPX_cjs.f}});Object.defineProperty(exports,"queryArchetypes",{enumerable:true,get:function(){return chunkSRX2MZPX_cjs.d}});Object.defineProperty(exports,"runQuery",{enumerable:true,get:function(){return chunkSRX2MZPX_cjs.e}});Object.defineProperty(exports,"EcsError",{enumerable:true,get:function(){return chunk5QWEV4VJ_cjs.i}});Object.defineProperty(exports,"VERSION",{enumerable:true,get:function(){return chunk5QWEV4VJ_cjs.a}});Object.defineProperty(exports,"addComponent",{enumerable:true,get:function(){return chunk5QWEV4VJ_cjs.K}});Object.defineProperty(exports,"createEntity",{enumerable:true,get:function(){return chunk5QWEV4VJ_cjs.v}});Object.defineProperty(exports,"createWorld",{enumerable:true,get:function(){return chunk5QWEV4VJ_cjs.l}});Object.defineProperty(exports,"defineComponent",{enumerable:true,get:function(){return chunk5QWEV4VJ_cjs.G}});Object.defineProperty(exports,"defineObjectComponent",{enumerable:true,get:function(){return chunk5QWEV4VJ_cjs.I}});Object.defineProperty(exports,"defineTag",{enumerable:true,get:function(){return chunk5QWEV4VJ_cjs.H}});Object.defineProperty(exports,"destroyEntity",{enumerable:true,get:function(){return chunk5QWEV4VJ_cjs.x}});Object.defineProperty(exports,"destroyWorld",{enumerable:true,get:function(){return chunk5QWEV4VJ_cjs.m}});Object.defineProperty(exports,"disposeWorld",{enumerable:true,get:function(){return chunk5QWEV4VJ_cjs.m}});Object.defineProperty(exports,"entityExists",{enumerable:true,get:function(){return chunk5QWEV4VJ_cjs.y}});Object.defineProperty(exports,"getComponent",{enumerable:true,get:function(){return chunk5QWEV4VJ_cjs.N}});Object.defineProperty(exports,"getEntityGeneration",{enumerable:true,get:function(){return chunk5QWEV4VJ_cjs.B}});Object.defineProperty(exports,"getEntityIndex",{enumerable:true,get:function(){return chunk5QWEV4VJ_cjs.A}});Object.defineProperty(exports,"getWorldCapacity",{enumerable:true,get:function(){return chunk5QWEV4VJ_cjs.p}});Object.defineProperty(exports,"getWorldSize",{enumerable:true,get:function(){return chunk5QWEV4VJ_cjs.o}});Object.defineProperty(exports,"hasComponent",{enumerable:true,get:function(){return chunk5QWEV4VJ_cjs.M}});Object.defineProperty(exports,"isEntity",{enumerable:true,get:function(){return chunk5QWEV4VJ_cjs.D}});Object.defineProperty(exports,"isWorld",{enumerable:true,get:function(){return chunk5QWEV4VJ_cjs.r}});Object.defineProperty(exports,"packEntity",{enumerable:true,get:function(){return chunk5QWEV4VJ_cjs.C}});Object.defineProperty(exports,"removeComponent",{enumerable:true,get:function(){return chunk5QWEV4VJ_cjs.L}});Object.defineProperty(exports,"resetWorld",{enumerable:true,get:function(){return chunk5QWEV4VJ_cjs.n}});Object.defineProperty(exports,"setComponent",{enumerable:true,get:function(){return chunk5QWEV4VJ_cjs.O}});exports.EntityNotAliveError=i;exports.IS_SAB_SUPPORTED=pe;exports.Types=J;exports.aliveRef=Z;exports.deref=b;exports.pipe=X;exports.refOf=Y;//# sourceMappingURL=index.cjs.map
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/internal/types.ts","../src/internal/pipe.ts","../src/ref.ts","../src/index.ts"],"names":["Types","pipe","systems","w","world","ctx","s","EntityNotAliveError","eid","refOf","entity","state","getWorldState","isAliveInternal","deref","ref","idx","unpackIdx","archId","arch","storedGen","refGen","unpackGen","aliveRef","registerComponentLookup","id","getComponentByInternalId","registerMaskChangeDispatch","bit","prev","next","recordEntityMaskChange","IS_SAB_SUPPORTED"],"mappings":"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","file":"index.cjs","sourcesContent":["// Shared type definitions for aiecsjs internals.\n// Public-facing types are re-exported from src/index.ts.\n\nexport type EntityId = number & { readonly __brand: 'EntityId' }\n\nexport const Types = {\n i8: 'i8',\n u8: 'u8',\n i16: 'i16',\n u16: 'u16',\n i32: 'i32',\n u32: 'u32',\n f32: 'f32',\n f64: 'f64',\n eid: 'eid',\n bool: 'bool',\n} as const\n\nexport type SoAFieldType =\n | 'i8'\n | 'u8'\n | 'i16'\n | 'u16'\n | 'i32'\n | 'u32'\n | 'f32'\n | 'f64'\n | 'eid'\n | 'bool'\n\nexport type SoAFieldDecl = SoAFieldType | readonly [SoAFieldType, number]\nexport type SoASchema = Readonly<Record<string, SoAFieldDecl>>\n\nexport interface SoAComponent<S extends SoASchema = SoASchema> {\n readonly __kind: 'soa'\n readonly __id: number\n readonly __schema: S\n}\n\nexport interface AoSComponent<T = unknown> {\n readonly __kind: 'aos'\n readonly __id: number\n readonly __factory: () => T\n}\n\nexport interface TagComponent {\n readonly __kind: 'tag'\n readonly __id: number\n}\n\nexport type ComponentLike = SoAComponent<any> | AoSComponent<any> | TagComponent\n\nexport type ColumnArray =\n | Int8Array\n | Uint8Array\n | Int16Array\n | Uint16Array\n | Int32Array\n | Uint32Array\n | Float32Array\n | Float64Array\n\nexport interface SoAColumns {\n [field: string]: ColumnArray\n}\n\nexport type ComponentView<C extends ComponentLike> = C extends SoAComponent<infer _S>\n ? SoAColumns\n : C extends AoSComponent<infer T>\n ? T | undefined\n : C extends TagComponent\n ? boolean\n : never\n\nexport type ComponentInit<C extends ComponentLike> = C extends SoAComponent<infer _S>\n ? Record<string, unknown>\n : C extends AoSComponent<infer T>\n ? Partial<T>\n : undefined\n\nexport interface QueryDescriptor {\n all?: ComponentLike[]\n any?: ComponentLike[]\n none?: ComponentLike[]\n}\n\nexport interface Query {\n readonly id: number\n readonly mask: ReadonlyArray<number>\n}\n\nexport interface Archetype {\n readonly id: number\n readonly mask: ReadonlyArray<number>\n readonly size: number\n}\n\nexport interface WorldOptions {\n initialCapacity?: number\n maxEntities?: number\n indexBits?: number\n generationBits?: number\n buffer?: SharedArrayBuffer\n bufferByteOffset?: number\n}\n\nexport interface World {\n readonly id: number\n readonly capacity: number\n readonly version: string\n}\n\nexport type System<W extends World = World, Ctx = unknown> = (world: W, ctx: Ctx) => W\n\n// --- Internal-only types ---\n\nexport interface ResolvedWorldOptions {\n initialCapacity: number\n maxEntities: number\n indexBits: number\n generationBits: number\n indexMask: number\n generationMask: number\n maxComponents: number\n maskWordCount: number\n buffer: SharedArrayBuffer | null\n bufferByteOffset: number\n}\n\nexport interface FieldInfo {\n name: string\n type: SoAFieldType\n vectorLen: number // 1 for scalar\n ctor: TypedArrayConstructor\n bytesPerElement: number\n}\n\nexport type TypedArrayConstructor =\n | Int8ArrayConstructor\n | Uint8ArrayConstructor\n | Int16ArrayConstructor\n | Uint16ArrayConstructor\n | Int32ArrayConstructor\n | Uint32ArrayConstructor\n | Float32ArrayConstructor\n | Float64ArrayConstructor\n\nexport interface ComponentInfo {\n id: number\n kind: 'soa' | 'aos' | 'tag'\n schema: SoASchema | null\n fields: FieldInfo[]\n factory: (() => unknown) | null\n}\n\nexport interface WorldComponentStorage {\n // For each component bit in this world: storage backed by a column map (SoA), object array (AoS), or null (tag)\n kind: 'soa' | 'aos' | 'tag'\n componentId: number\n bit: number\n // SoA: field name → TypedArray of size `worldCapacity`\n soa?: SoAColumns\n // AoS: per-entity object array, sparse, indexed by eid; undefined for unowned\n aos?: (unknown | undefined)[]\n}\n\nexport interface ArchetypeState {\n id: number\n mask: Uint32Array\n size: number\n capacity: number\n entities: Uint32Array // packed eids in row order\n entityRow: Map<number, number> // eid → row (small archetype-local lookup)\n componentBits: number[] // sorted\n edgeAdd: Int32Array // [bit] → archetype id; -1 unknown\n edgeRemove: Int32Array // [bit] → archetype id; -1 unknown\n}\n\nexport interface QueryInternal extends Query {\n id: number\n all: number[]\n any: number[]\n none: number[]\n // Cache of column references per matched archetype (per query-call)\n columnViewCache: ComponentLike[] // the requested components in their declared order\n reactiveKind: 'normal' | 'enter' | 'exit'\n sourceQueryId: number // -1 for normal\n sourceQuery: QueryInternal | null // back-ref for reactive registration\n}\n\n// Per-world resolved bitmasks for a query. Component bits are assigned per\n// world, so the masks must live in WorldState — keeping them on the shared\n// QueryInternal would silently cross-contaminate worlds whose component\n// registration order differs.\nexport interface QueryMaskBundle {\n withMask: Uint32Array\n anyMask: Uint32Array\n noneMask: Uint32Array\n anyHasBits: boolean\n}\n\nexport interface ReactiveBuffer {\n entered: number[]\n exited: number[]\n}\n\nexport type ObserverEvent = 'add' | 'remove' | 'set'\n\nexport interface ObserverEntry {\n event: ObserverEvent\n componentBit: number // -1 means \"any component\" (used by query observe)\n queryId: number // -1 means component-only\n handler: (eid: EntityId, value?: unknown) => void\n}\n\nexport interface CommandBufferState {\n worldId: number\n ops: CommandOp[]\n nextPlaceholder: number\n flushing: boolean\n}\n\nexport type CommandOp =\n | { kind: 'create'; placeholder: number }\n | { kind: 'add'; eid: EntityId; component: ComponentLike; initial?: unknown }\n | { kind: 'remove'; eid: EntityId; component: ComponentLike }\n | { kind: 'destroy'; eid: EntityId }\n\nexport interface CommandBuffer {\n add<C extends ComponentLike>(eid: EntityId, component: C, initial?: ComponentInit<C>): void\n remove<C extends ComponentLike>(eid: EntityId, component: C): void\n destroy(eid: EntityId): void\n create(): EntityId\n}\n\nexport interface WorldMeta {\n magic: number\n formatVersion: number\n aiecsjsVersion: string\n indexBits: number\n generationBits: number\n maxComponents: number\n maskWordCount: number\n capacity: number\n componentSchemas: Array<{ id: number; kind: 'soa' | 'aos' | 'tag'; schema: SoASchema | null }>\n}\n\nexport interface TransferableSnapshot {\n buffer: SharedArrayBuffer\n meta: WorldMeta\n}\n\nexport interface SerializeOptions {\n components?: ComponentLike[]\n}\n\nexport interface DeserializeOptions {\n components?: ComponentLike[]\n onUnknownVersion?: 'throw' | 'best-effort'\n}\n\nexport interface WorldSnapshot {\n version: string\n capacity: number\n entities: Array<{\n eid: number\n components: Array<{\n kind: 'soa' | 'aos' | 'tag'\n id: number\n data: unknown\n }>\n }>\n}\n\nexport interface DeltaSerializer {\n capture(): Uint8Array\n apply(world: World, delta: Uint8Array): void\n reset(): void\n}\n\nexport interface Relation<T = void> {\n readonly __kind: 'relation'\n readonly __id: number\n readonly __exclusive: boolean\n readonly __hasData: boolean\n}\n\nexport interface RelationStorage {\n rel: Relation<any>\n exclusive: Int32Array | null // [srcEid] → tgtEid; -1 means none\n outgoing: Map<number, number[]> // srcEid → tgtEid[]\n data: Map<number, Map<number, unknown>> // srcEid → (tgtEid → data); nested to stay correct across capacity growth\n}\n\nexport interface WorldState {\n // --- public view ---\n readonly id: number\n capacity: number\n readonly version: string\n\n // --- options ---\n options: ResolvedWorldOptions\n\n // --- entity allocation ---\n size: number // alive count\n nextFreshIndex: number // never-used index\n freeList: number[] // recycled indices\n generations: Uint8Array | Uint16Array // [eid] → version\n destroyed: boolean\n\n // --- component registry (per-world) ---\n componentBitFor: Map<number, number> // global component id → bit pos in this world\n componentInfoByBit: (ComponentInfo | null)[] // bit pos → ComponentInfo\n componentStorageByBit: (WorldComponentStorage | null)[]\n nextComponentBit: number\n\n // --- sparse entity state ---\n entityArchetype: Uint32Array // [eid] → archetype id (0 = unattached)\n // entityMask flattened: word w of mask of eid is at entityMask[eid * maskWordCount + w]\n entityMask: Uint32Array\n\n // --- archetype registry ---\n archetypes: ArchetypeState[]\n archetypeByMaskHash: Map<string, number>\n queryVersion: number\n\n // --- query cache ---\n queries: QueryInternal[] // by id\n queryMasks: Map<number, QueryMaskBundle> // queryId → per-world resolved bitmasks\n queryArchetypeCache: (number[] | null)[]\n queryArchetypeStamp: number[]\n bitToQueries: Map<number, Set<number>> // bit → queryIds that mention this bit\n\n // --- reactive query state ---\n reactiveBuffers: Map<number, ReactiveBuffer> // queryId → { entered, exited }\n\n // --- observers ---\n observers: ObserverEntry[]\n\n // --- relations ---\n relationStorage: Map<number, RelationStorage> // relation id → storage\n\n // --- worker / SAB ---\n sab: SharedArrayBuffer | null\n readOnly: boolean\n}\n","import type { System, World } from './types.js'\n\nexport function pipe<W extends World = World, Ctx = unknown>(\n ...systems: System<W, Ctx>[]\n): System<W, Ctx> {\n if (systems.length === 0) return (w: W) => w\n if (systems.length === 1) return systems[0]!\n return (world: W, ctx: Ctx) => {\n let w = world\n for (const s of systems) w = s(w, ctx)\n return w\n }\n}\n","// aiecsjs/ref — ABA-safe entity references.\n//\n// EntityRef<T> wraps a packed EntityId with a world identity check so that\n// stale references (pointing at a recycled slot with a different generation)\n// safely return null rather than silently accessing the wrong entity.\n//\n// EntityRef is in-memory only. Do not pass across worker boundaries or persist\n// to disk. The packed id field has meaning only within the world that issued it.\n// Generation counters reset on resetWorld / deserializeWorld / adoptSnapshot.\n\nimport { isAliveInternal, unpackGen, unpackIdx } from './internal/entity.js'\nimport type { EntityId, World } from './internal/types.js'\nimport { getWorldState } from './internal/world.js'\n\n/**\n * ABA-safe entity reference.\n *\n * Holds the packed EntityId and the id of the world that issued it.\n * Use `deref(world, ref)` to validate and retrieve the live entity id.\n *\n * The phantom type `T` allows callers to distinguish reference kinds at the\n * type system level — e.g. `EntityRef<'bullet'>` vs `EntityRef<'player'>` —\n * without any runtime overhead.\n *\n * @example\n * const ref = refOf<'bullet'>(world, e)\n * // later, in a system:\n * const live = deref(world, ref)\n * if (live !== null) {\n * // entity is still alive with the same generation\n * }\n */\nexport interface EntityRef<T = unknown> {\n readonly id: EntityId // packed value (index + generation)\n readonly worldId: number // world.id — guards against cross-world deref\n readonly __phantom?: T // phantom type tag; zero runtime size\n}\n\n/**\n * Thrown by `refOf` when the entity is not alive.\n * `deref` and `aliveRef` never throw — they return null/false.\n */\nexport class EntityNotAliveError extends Error {\n readonly eid: number\n constructor(eid: number) {\n super(`aiecsjs: entity ${eid} is not alive`)\n this.name = 'EntityNotAliveError'\n this.eid = eid\n }\n}\n\n/**\n * Create an ABA-safe reference to a live entity.\n *\n * @throws {EntityNotAliveError} if the entity is not alive (dead, never created, or eid === 0).\n *\n * The returned object is frozen. `id` is the packed EntityId; `worldId` is\n * the world's numeric id used by `deref` to reject cross-world lookups.\n *\n * The function may be called on the hot path — each call returns a new object\n * but performs no allocation beyond that.\n */\nexport function refOf<T = unknown>(world: World, entity: EntityId): EntityRef<T> {\n const state = getWorldState(world)\n if (!isAliveInternal(state, entity as number)) {\n throw new EntityNotAliveError(entity as number)\n }\n return Object.freeze({ id: entity, worldId: state.id }) as EntityRef<T>\n}\n\n/**\n * Resolve a ref to its live EntityId, or return null if stale.\n *\n * Returns null when any of the following hold:\n * 1. `ref.worldId !== state.id` — cross-world ref\n * 2. `getEntityIndex(ref.id) >= state.capacity` — index out-of-bounds\n * 3. `getEntityIndex(ref.id) === 0` — sentinel slot\n * 4. entity is not in any archetype's entityRow (slot released)\n * 5. generation mismatch → ABA: old ref points at recycled slot\n *\n * Never throws.\n */\nexport function deref<T = unknown>(world: World, ref: EntityRef<T>): EntityId | null {\n const state = getWorldState(world)\n\n // Cross-world guard\n if (ref.worldId !== state.id) return null\n\n const eid = ref.id as number\n const idx = unpackIdx(eid, state.options)\n\n // Sentinel and OOB guards\n if (idx <= 0) return null\n if (idx >= state.capacity) return null\n\n // Archetype membership check\n const archId = state.entityArchetype[idx] ?? 0\n const arch = state.archetypes[archId]\n if (!arch) return null\n if (!arch.entityRow.has(ref.id)) return null\n\n // Generation match — the core ABA protection\n const storedGen = state.generations[idx] ?? 0\n const refGen = unpackGen(eid, state.options)\n if (storedGen !== refGen) return null\n\n return ref.id\n}\n\n/**\n * Boolean guard form of `deref`. Equivalent to `deref(world, ref) !== null`.\n * Use in guard clauses when you do not need the resolved EntityId.\n *\n * Never throws.\n */\nexport function aliveRef<T = unknown>(world: World, ref: EntityRef<T>): boolean {\n return deref(world, ref) !== null\n}\n","// aiecsjs — public root entry.\n// Re-exports the core API and wires up the lazy cross-module references.\n\nexport { VERSION } from './version.js'\n\nimport {\n getComponentByInternalId,\n registerMaskChangeDispatch,\n registerObserverDispatch,\n} from './internal/component.js'\nimport { registerObserversAPI, registerRelationsCleanup } from './internal/entity.js'\nimport { recordEntityMaskChange, registerComponentLookup } from './internal/query.js'\nimport type { EntityId, WorldState } from './internal/types.js'\n\n// Wire query → component lookup\nregisterComponentLookup((id) => getComponentByInternalId(id))\n\n// Wire component mask-change → query reactive update\nregisterMaskChangeDispatch((state, eid, bit, prev, next) => {\n recordEntityMaskChange(state, eid, bit, prev, next)\n})\n\n// Observer dispatch is registered lazily by observers.ts on first import.\n// Relations cleanup is registered lazily by relations.ts on first import.\n\n// --- Type re-exports ---\nexport type {\n EntityId,\n World,\n WorldOptions,\n System,\n SoAComponent,\n AoSComponent,\n TagComponent,\n ComponentLike,\n ComponentInit,\n ComponentView,\n SoAColumns,\n SoASchema,\n SoAFieldType,\n SoAFieldDecl,\n Query,\n QueryDescriptor,\n Archetype,\n} from './internal/types.js'\n\n// --- World ---\nexport {\n createWorld,\n destroyWorld,\n resetWorld,\n getWorldSize,\n getWorldCapacity,\n isWorld,\n} from './internal/world.js'\n\n// `disposeWorld` is the new preferred name (added 0.2.0). It is the same\n// function as `destroyWorld`, exported under a name that aligns with the ai*js\n// ecosystem `dispose()` convention (aifsmjs Runtime, aibridgejs Bridge).\n//\n// `destroyWorld` is the original 0.1.x export. It is now deprecated as of 0.2.0\n// and is scheduled for removal in 1.0. New code should import `disposeWorld`.\nexport { destroyWorld as disposeWorld } from './internal/world.js'\n\n// --- Entity ---\nexport {\n createEntity,\n destroyEntity,\n entityExists,\n getEntityIndex,\n getEntityGeneration,\n packEntity,\n isEntity,\n} from './internal/entity.js'\n\n// --- Component ---\nexport {\n defineComponent,\n defineTag,\n defineObjectComponent,\n addComponent,\n removeComponent,\n hasComponent,\n getComponent,\n setComponent,\n} from './internal/component.js'\n\nexport { Types } from './internal/types.js'\n\n// --- Query ---\nexport {\n defineQuery,\n runQuery,\n iterQuery,\n forEachEntity,\n forEachEntityIndexed,\n enterQuery,\n exitQuery,\n queryArchetypes,\n} from './internal/query.js'\n\n// --- System ---\nexport { pipe } from './internal/pipe.js'\n\n// --- Utility ---\nexport const IS_SAB_SUPPORTED: boolean = typeof SharedArrayBuffer !== 'undefined'\n\n// --- EntityRef (ABA-safe references) ---\nexport type { EntityRef } from './ref.js'\nexport { refOf, deref, aliveRef, EntityNotAliveError } from './ref.js'\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/internal/types.ts","../src/internal/pipe.ts","../src/ref.ts","../src/index.ts"],"names":["Types","pipe","systems","w","world","ctx","s","EntityNotAliveError","eid","refOf","entity","state","getWorldState","isAliveInternal","deref","ref","idx","unpackIdx","archId","arch","storedGen","refGen","unpackGen","aliveRef","registerComponentLookup","id","getComponentByInternalId","registerMaskChangeDispatch","bit","prev","next","recordEntityMaskChange","IS_SAB_SUPPORTED"],"mappings":"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","file":"index.cjs","sourcesContent":["// Shared type definitions for aiecsjs internals.\n// Public-facing types are re-exported from src/index.ts.\n\nexport type EntityId = number & { readonly __brand: 'EntityId' }\n\nexport const Types = {\n i8: 'i8',\n u8: 'u8',\n i16: 'i16',\n u16: 'u16',\n i32: 'i32',\n u32: 'u32',\n f32: 'f32',\n f64: 'f64',\n eid: 'eid',\n bool: 'bool',\n} as const\n\nexport type SoAFieldType =\n | 'i8'\n | 'u8'\n | 'i16'\n | 'u16'\n | 'i32'\n | 'u32'\n | 'f32'\n | 'f64'\n | 'eid'\n | 'bool'\n\nexport type SoAFieldDecl = SoAFieldType | readonly [SoAFieldType, number]\nexport type SoASchema = Readonly<Record<string, SoAFieldDecl>>\n\nexport interface SoAComponent<S extends SoASchema = SoASchema> {\n readonly __kind: 'soa'\n readonly __id: number\n readonly __schema: S\n}\n\nexport interface AoSComponent<T = unknown> {\n readonly __kind: 'aos'\n readonly __id: number\n readonly __factory: () => T\n}\n\nexport interface TagComponent {\n readonly __kind: 'tag'\n readonly __id: number\n}\n\nexport type ComponentLike = SoAComponent<any> | AoSComponent<any> | TagComponent\n\nexport type ColumnArray =\n | Int8Array\n | Uint8Array\n | Int16Array\n | Uint16Array\n | Int32Array\n | Uint32Array\n | Float32Array\n | Float64Array\n\nexport interface SoAColumns {\n [field: string]: ColumnArray\n}\n\nexport type ComponentView<C extends ComponentLike> = C extends SoAComponent<infer _S>\n ? SoAColumns\n : C extends AoSComponent<infer T>\n ? T | undefined\n : C extends TagComponent\n ? boolean\n : never\n\nexport type ComponentInit<C extends ComponentLike> = C extends SoAComponent<infer _S>\n ? Record<string, unknown>\n : C extends AoSComponent<infer T>\n ? Partial<T>\n : undefined\n\nexport interface QueryDescriptor {\n all?: ComponentLike[]\n any?: ComponentLike[]\n none?: ComponentLike[]\n}\n\nexport interface Query {\n readonly id: number\n readonly mask: ReadonlyArray<number>\n}\n\nexport interface Archetype {\n readonly id: number\n readonly mask: ReadonlyArray<number>\n readonly size: number\n}\n\nexport interface WorldOptions {\n initialCapacity?: number\n maxEntities?: number\n indexBits?: number\n generationBits?: number\n /**\n * RESERVED / UNIMPLEMENTED (0.x). Setting this has **no effect** today: the\n * world always allocates its own column storage and never reads or writes a\n * caller-supplied SAB. The 0.x Worker handoff is a snapshot-copy transport —\n * post `transferableSnapshot(world)` and rebuild via `adoptSnapshot` from\n * `aiecsjs/worker`; do not pre-allocate a SAB here. The field is kept so the\n * shape is forward-compatible with the true shared-column backing targeted\n * for 0.3+ (see STABILITY.md, `aiecsjs/worker`).\n */\n buffer?: SharedArrayBuffer\n /** RESERVED / UNIMPLEMENTED (0.x). Paired with {@link WorldOptions.buffer}; no effect today. */\n bufferByteOffset?: number\n}\n\nexport interface World {\n readonly id: number\n readonly capacity: number\n readonly version: string\n}\n\nexport type System<W extends World = World, Ctx = unknown> = (world: W, ctx: Ctx) => W\n\n// --- Internal-only types ---\n\nexport interface ResolvedWorldOptions {\n initialCapacity: number\n maxEntities: number\n indexBits: number\n generationBits: number\n indexMask: number\n generationMask: number\n maxComponents: number\n maskWordCount: number\n buffer: SharedArrayBuffer | null\n bufferByteOffset: number\n}\n\nexport interface FieldInfo {\n name: string\n type: SoAFieldType\n vectorLen: number // 1 for scalar\n ctor: TypedArrayConstructor\n bytesPerElement: number\n}\n\nexport type TypedArrayConstructor =\n | Int8ArrayConstructor\n | Uint8ArrayConstructor\n | Int16ArrayConstructor\n | Uint16ArrayConstructor\n | Int32ArrayConstructor\n | Uint32ArrayConstructor\n | Float32ArrayConstructor\n | Float64ArrayConstructor\n\nexport interface ComponentInfo {\n id: number\n kind: 'soa' | 'aos' | 'tag'\n schema: SoASchema | null\n fields: FieldInfo[]\n factory: (() => unknown) | null\n}\n\nexport interface WorldComponentStorage {\n // For each component bit in this world: storage backed by a column map (SoA), object array (AoS), or null (tag)\n kind: 'soa' | 'aos' | 'tag'\n componentId: number\n bit: number\n // SoA: field name → TypedArray of size `worldCapacity`\n soa?: SoAColumns\n // AoS: per-entity object array, sparse, indexed by eid; undefined for unowned\n aos?: (unknown | undefined)[]\n}\n\nexport interface ArchetypeState {\n id: number\n mask: Uint32Array\n size: number\n capacity: number\n entities: Uint32Array // packed eids in row order\n entityRow: Map<number, number> // eid → row (small archetype-local lookup)\n componentBits: number[] // sorted\n edgeAdd: Int32Array // [bit] → archetype id; -1 unknown\n edgeRemove: Int32Array // [bit] → archetype id; -1 unknown\n}\n\nexport interface QueryInternal extends Query {\n id: number\n all: number[]\n any: number[]\n none: number[]\n // Cache of column references per matched archetype (per query-call)\n columnViewCache: ComponentLike[] // the requested components in their declared order\n reactiveKind: 'normal' | 'enter' | 'exit'\n sourceQueryId: number // -1 for normal\n sourceQuery: QueryInternal | null // back-ref for reactive registration\n}\n\n// Per-world resolved bitmasks for a query. Component bits are assigned per\n// world, so the masks must live in WorldState — keeping them on the shared\n// QueryInternal would silently cross-contaminate worlds whose component\n// registration order differs.\nexport interface QueryMaskBundle {\n withMask: Uint32Array\n anyMask: Uint32Array\n noneMask: Uint32Array\n anyHasBits: boolean\n}\n\nexport interface ReactiveBuffer {\n entered: number[]\n exited: number[]\n}\n\nexport type ObserverEvent = 'add' | 'remove' | 'set'\n\nexport interface ObserverEntry {\n event: ObserverEvent\n componentBit: number // -1 means \"any component\" (used by query observe)\n queryId: number // -1 means component-only\n handler: (eid: EntityId, value?: unknown) => void\n}\n\nexport interface CommandBufferState {\n worldId: number\n ops: CommandOp[]\n nextPlaceholder: number\n flushing: boolean\n}\n\nexport type CommandOp =\n | { kind: 'create'; placeholder: number }\n | { kind: 'add'; eid: EntityId; component: ComponentLike; initial?: unknown }\n | { kind: 'remove'; eid: EntityId; component: ComponentLike }\n | { kind: 'destroy'; eid: EntityId }\n\nexport interface CommandBuffer {\n add<C extends ComponentLike>(eid: EntityId, component: C, initial?: ComponentInit<C>): void\n remove<C extends ComponentLike>(eid: EntityId, component: C): void\n destroy(eid: EntityId): void\n create(): EntityId\n}\n\nexport interface WorldMeta {\n magic: number\n formatVersion: number\n aiecsjsVersion: string\n indexBits: number\n generationBits: number\n maxComponents: number\n maskWordCount: number\n capacity: number\n componentSchemas: Array<{ id: number; kind: 'soa' | 'aos' | 'tag'; schema: SoASchema | null }>\n}\n\nexport interface TransferableSnapshot {\n // Union, not bare SharedArrayBuffer: in a non-cross-origin-isolated environment\n // `SharedArrayBuffer` is undefined, so transferableSnapshot falls back to a\n // plain ArrayBuffer (worker.ts). Declaring the honest union lets consumers\n // branch on `instanceof SharedArrayBuffer` instead of trusting a type that\n // would otherwise lie. Experimental subpath — the union is allowed to land in 0.x.\n buffer: SharedArrayBuffer | ArrayBuffer\n meta: WorldMeta\n}\n\nexport interface SerializeOptions {\n components?: ComponentLike[]\n}\n\nexport interface DeserializeOptions {\n components?: ComponentLike[]\n onUnknownVersion?: 'throw' | 'best-effort'\n}\n\nexport interface WorldSnapshot {\n version: string\n capacity: number\n entities: Array<{\n eid: number\n components: Array<{\n kind: 'soa' | 'aos' | 'tag'\n id: number\n data: unknown\n }>\n }>\n}\n\nexport interface DeltaSerializer {\n capture(): Uint8Array\n apply(world: World, delta: Uint8Array): void\n reset(): void\n}\n\nexport interface Relation<T = void> {\n readonly __kind: 'relation'\n readonly __id: number\n readonly __exclusive: boolean\n readonly __hasData: boolean\n}\n\nexport interface RelationStorage {\n rel: Relation<any>\n exclusive: Int32Array | null // [srcEid] → tgtEid; -1 means none\n outgoing: Map<number, number[]> // srcEid → tgtEid[]\n data: Map<number, Map<number, unknown>> // srcEid → (tgtEid → data); nested to stay correct across capacity growth\n}\n\nexport interface WorldState {\n // --- public view ---\n readonly id: number\n capacity: number\n readonly version: string\n\n // --- options ---\n options: ResolvedWorldOptions\n\n // --- entity allocation ---\n size: number // alive count\n nextFreshIndex: number // never-used index\n freeList: number[] // recycled indices\n generations: Uint8Array | Uint16Array // [eid] → version\n destroyed: boolean\n\n // --- component registry (per-world) ---\n componentBitFor: Map<number, number> // global component id → bit pos in this world\n componentInfoByBit: (ComponentInfo | null)[] // bit pos → ComponentInfo\n componentStorageByBit: (WorldComponentStorage | null)[]\n nextComponentBit: number\n\n // --- sparse entity state ---\n entityArchetype: Uint32Array // [eid] → archetype id (0 = unattached)\n // entityMask flattened: word w of mask of eid is at entityMask[eid * maskWordCount + w]\n entityMask: Uint32Array\n\n // --- archetype registry ---\n archetypes: ArchetypeState[]\n archetypeByMaskHash: Map<string, number>\n queryVersion: number\n\n // --- query cache ---\n queries: QueryInternal[] // by id\n queryMasks: Map<number, QueryMaskBundle> // queryId → per-world resolved bitmasks\n queryArchetypeCache: (number[] | null)[]\n queryArchetypeStamp: number[]\n bitToQueries: Map<number, Set<number>> // bit → queryIds that mention this bit\n\n // --- reactive query state ---\n reactiveBuffers: Map<number, ReactiveBuffer> // queryId → { entered, exited }\n\n // --- observers ---\n observers: ObserverEntry[]\n\n // --- relations ---\n relationStorage: Map<number, RelationStorage> // relation id → storage\n\n // --- worker / SAB ---\n sab: SharedArrayBuffer | null\n readOnly: boolean\n}\n","import type { System, World } from './types.js'\n\nexport function pipe<W extends World = World, Ctx = unknown>(\n ...systems: System<W, Ctx>[]\n): System<W, Ctx> {\n if (systems.length === 0) return (w: W) => w\n if (systems.length === 1) return systems[0]!\n return (world: W, ctx: Ctx) => {\n let w = world\n for (const s of systems) w = s(w, ctx)\n return w\n }\n}\n","// aiecsjs/ref — ABA-safe entity references.\n//\n// EntityRef<T> wraps a packed EntityId with a world identity check so that\n// stale references (pointing at a recycled slot with a different generation)\n// safely return null rather than silently accessing the wrong entity.\n//\n// EntityRef is in-memory only. Do not pass across worker boundaries or persist\n// to disk. The packed id field has meaning only within the world that issued it.\n// Generation counters reset on resetWorld / deserializeWorld / adoptSnapshot.\n\nimport { isAliveInternal, unpackGen, unpackIdx } from './internal/entity.js'\nimport type { EntityId, World } from './internal/types.js'\nimport { getWorldState } from './internal/world.js'\n\n/**\n * ABA-safe entity reference.\n *\n * Holds the packed EntityId and the id of the world that issued it.\n * Use `deref(world, ref)` to validate and retrieve the live entity id.\n *\n * The phantom type `T` allows callers to distinguish reference kinds at the\n * type system level — e.g. `EntityRef<'bullet'>` vs `EntityRef<'player'>` —\n * without any runtime overhead.\n *\n * @example\n * const ref = refOf<'bullet'>(world, e)\n * // later, in a system:\n * const live = deref(world, ref)\n * if (live !== null) {\n * // entity is still alive with the same generation\n * }\n */\nexport interface EntityRef<T = unknown> {\n readonly id: EntityId // packed value (index + generation)\n readonly worldId: number // world.id — guards against cross-world deref\n readonly __phantom?: T // phantom type tag; zero runtime size\n}\n\n/**\n * Thrown by `refOf` when the entity is not alive.\n * `deref` and `aliveRef` never throw — they return null/false.\n */\nexport class EntityNotAliveError extends Error {\n readonly eid: number\n constructor(eid: number) {\n super(`aiecsjs: entity ${eid} is not alive`)\n this.name = 'EntityNotAliveError'\n this.eid = eid\n }\n}\n\n/**\n * Create an ABA-safe reference to a live entity.\n *\n * @throws {EntityNotAliveError} if the entity is not alive (dead, never created, or eid === 0).\n *\n * The returned object is frozen. `id` is the packed EntityId; `worldId` is\n * the world's numeric id used by `deref` to reject cross-world lookups.\n *\n * The function may be called on the hot path — each call returns a new object\n * but performs no allocation beyond that.\n */\nexport function refOf<T = unknown>(world: World, entity: EntityId): EntityRef<T> {\n const state = getWorldState(world)\n if (!isAliveInternal(state, entity as number)) {\n throw new EntityNotAliveError(entity as number)\n }\n return Object.freeze({ id: entity, worldId: state.id }) as EntityRef<T>\n}\n\n/**\n * Resolve a ref to its live EntityId, or return null if stale.\n *\n * Returns null when any of the following hold:\n * 1. `ref.worldId !== state.id` — cross-world ref\n * 2. `getEntityIndex(ref.id) >= state.capacity` — index out-of-bounds\n * 3. `getEntityIndex(ref.id) === 0` — sentinel slot\n * 4. entity is not in any archetype's entityRow (slot released)\n * 5. generation mismatch → ABA: old ref points at recycled slot\n *\n * Never throws.\n */\nexport function deref<T = unknown>(world: World, ref: EntityRef<T>): EntityId | null {\n const state = getWorldState(world)\n\n // Cross-world guard\n if (ref.worldId !== state.id) return null\n\n const eid = ref.id as number\n const idx = unpackIdx(eid, state.options)\n\n // Sentinel and OOB guards\n if (idx <= 0) return null\n if (idx >= state.capacity) return null\n\n // Archetype membership check\n const archId = state.entityArchetype[idx] ?? 0\n const arch = state.archetypes[archId]\n if (!arch) return null\n if (!arch.entityRow.has(ref.id)) return null\n\n // Generation match — the core ABA protection\n const storedGen = state.generations[idx] ?? 0\n const refGen = unpackGen(eid, state.options)\n if (storedGen !== refGen) return null\n\n return ref.id\n}\n\n/**\n * Boolean guard form of `deref`. Equivalent to `deref(world, ref) !== null`.\n * Use in guard clauses when you do not need the resolved EntityId.\n *\n * Never throws.\n */\nexport function aliveRef<T = unknown>(world: World, ref: EntityRef<T>): boolean {\n return deref(world, ref) !== null\n}\n","// aiecsjs — public root entry.\n// Re-exports the core API and wires up the lazy cross-module references.\n\nexport { VERSION } from './version.js'\n\nimport {\n getComponentByInternalId,\n registerMaskChangeDispatch,\n registerObserverDispatch,\n} from './internal/component.js'\nimport { registerObserversAPI, registerRelationsCleanup } from './internal/entity.js'\nimport { recordEntityMaskChange, registerComponentLookup } from './internal/query.js'\nimport type { EntityId, WorldState } from './internal/types.js'\n\n// Wire query → component lookup\nregisterComponentLookup((id) => getComponentByInternalId(id))\n\n// Wire component mask-change → query reactive update\nregisterMaskChangeDispatch((state, eid, bit, prev, next) => {\n recordEntityMaskChange(state, eid, bit, prev, next)\n})\n\n// Observer dispatch is registered lazily by observers.ts on first import.\n// Relations cleanup is registered lazily by relations.ts on first import.\n\n// --- Type re-exports ---\nexport type {\n EntityId,\n World,\n WorldOptions,\n System,\n SoAComponent,\n AoSComponent,\n TagComponent,\n ComponentLike,\n ComponentInit,\n ComponentView,\n SoAColumns,\n SoASchema,\n SoAFieldType,\n SoAFieldDecl,\n Query,\n QueryDescriptor,\n Archetype,\n} from './internal/types.js'\n\n// --- World ---\nexport {\n createWorld,\n destroyWorld,\n resetWorld,\n getWorldSize,\n getWorldCapacity,\n isWorld,\n} from './internal/world.js'\n\n// `disposeWorld` is the new preferred name (added 0.2.0). It is the same\n// function as `destroyWorld`, exported under a name that aligns with the ai*js\n// ecosystem `dispose()` convention (aifsmjs Runtime, aibridgejs Bridge).\n//\n// `destroyWorld` is the original 0.1.x export. It is now deprecated as of 0.2.0\n// and is scheduled for removal in 1.0. New code should import `disposeWorld`.\nexport { destroyWorld as disposeWorld } from './internal/world.js'\n\n// --- Entity ---\nexport {\n createEntity,\n destroyEntity,\n entityExists,\n getEntityIndex,\n getEntityGeneration,\n packEntity,\n isEntity,\n} from './internal/entity.js'\n\n// --- Component ---\nexport {\n defineComponent,\n defineTag,\n defineObjectComponent,\n addComponent,\n removeComponent,\n hasComponent,\n getComponent,\n setComponent,\n} from './internal/component.js'\n\nexport { Types } from './internal/types.js'\n\n// --- Query ---\nexport {\n defineQuery,\n runQuery,\n iterQuery,\n forEachEntity,\n forEachEntityIndexed,\n enterQuery,\n exitQuery,\n queryArchetypes,\n} from './internal/query.js'\n\n// --- System ---\nexport { pipe } from './internal/pipe.js'\n\n// --- Utility ---\nexport const IS_SAB_SUPPORTED: boolean = typeof SharedArrayBuffer !== 'undefined'\n\n// --- Errors ---\nexport { EcsError } from './internal/errors.js'\n\n// --- EntityRef (ABA-safe references) ---\nexport type { EntityRef } from './ref.js'\nexport { refOf, deref, aliveRef, EntityNotAliveError } from './ref.js'\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { o as WorldOptions, W as World, E as EntityId, c as ComponentLike, b as ComponentInit, k as SoASchema, h as SoAComponent, A as AoSComponent, T as TagComponent, f as QueryDescriptor, Q as Query, a as Archetype, l as System } from './types-
|
|
2
|
-
export { d as ComponentView, g as SoAColumns, i as SoAFieldDecl, j as SoAFieldType, n as Types } from './types-
|
|
1
|
+
import { o as WorldOptions, W as World, E as EntityId, c as ComponentLike, b as ComponentInit, k as SoASchema, h as SoAComponent, A as AoSComponent, T as TagComponent, f as QueryDescriptor, Q as Query, a as Archetype, l as System } from './types-BeVLA7xG.cjs';
|
|
2
|
+
export { d as ComponentView, g as SoAColumns, i as SoAFieldDecl, j as SoAFieldType, n as Types } from './types-BeVLA7xG.cjs';
|
|
3
3
|
|
|
4
|
-
declare const VERSION = "0.5.
|
|
4
|
+
declare const VERSION = "0.5.7";
|
|
5
5
|
|
|
6
6
|
declare function createWorld(options?: WorldOptions): World;
|
|
7
7
|
declare function destroyWorld(world: World): void;
|
|
@@ -47,7 +47,30 @@ declare function getComponent<C extends ComponentLike>(world: World, eid: Entity
|
|
|
47
47
|
declare function setComponent<C extends ComponentLike, V>(world: World, eid: EntityId, component: C, value: V): void;
|
|
48
48
|
|
|
49
49
|
declare function defineQuery(arg: ComponentLike[] | QueryDescriptor): Query;
|
|
50
|
+
/**
|
|
51
|
+
* Reactive "entered" view of `query`: each read drains the entities that began
|
|
52
|
+
* matching since the last read (via `runQuery` / `iterQuery` / `forEachEntity`).
|
|
53
|
+
*
|
|
54
|
+
* **MUST-DRAIN CONTRACT (unbounded buffer).** The enter buffer has **no cap**.
|
|
55
|
+
* Every structural change that makes an entity newly match pushes one id; the
|
|
56
|
+
* buffer only shrinks when you read the reactive view. A view that is created
|
|
57
|
+
* but never read — e.g. a disabled system, or reading only the {@link exitQuery}
|
|
58
|
+
* twin — accumulates one number per event **forever**, an unbounded memory leak
|
|
59
|
+
* at churn rates (e.g. ~1k spawns/frame). Read every enter view you create, once
|
|
60
|
+
* per frame. Dropping ids silently would corrupt enter/exit symmetry, so capping
|
|
61
|
+
* is intentionally not done — draining is the caller's responsibility.
|
|
62
|
+
*/
|
|
50
63
|
declare function enterQuery(query: Query): Query;
|
|
64
|
+
/**
|
|
65
|
+
* Reactive "exited" view of `query`: each read drains the entities that stopped
|
|
66
|
+
* matching since the last read (via `runQuery` / `iterQuery` / `forEachEntity`).
|
|
67
|
+
*
|
|
68
|
+
* **MUST-DRAIN CONTRACT (unbounded buffer).** The exit buffer has **no cap** —
|
|
69
|
+
* same discipline as {@link enterQuery}. An unread exit view accumulates one id
|
|
70
|
+
* per matching structural change (including every `destroyEntity` of a matching
|
|
71
|
+
* entity) without bound. Read every exit view you create, once per frame. Capping
|
|
72
|
+
* is intentionally not done because dropping ids would break enter/exit symmetry.
|
|
73
|
+
*/
|
|
51
74
|
declare function exitQuery(query: Query): Query;
|
|
52
75
|
declare function queryArchetypes(world: World, query: Query): readonly Archetype[];
|
|
53
76
|
declare function runQuery(world: World, query: Query): readonly EntityId[];
|
|
@@ -86,6 +109,28 @@ declare function forEachEntityIndexed(world: World, query: Query, fn: (e: Entity
|
|
|
86
109
|
|
|
87
110
|
declare function pipe<W extends World = World, Ctx = unknown>(...systems: System<W, Ctx>[]): System<W, Ctx>;
|
|
88
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Thrown by core world/entity/component operations for invariant violations and
|
|
114
|
+
* misuse (bad world options, a destroyed/unknown world, exhausted component
|
|
115
|
+
* slots, capacity overflow). Carries the existing `aiecsjs: `-prefixed message.
|
|
116
|
+
*
|
|
117
|
+
* Catch this to distinguish an aiecsjs-originated failure from an unrelated
|
|
118
|
+
* runtime error, instead of string-matching on the message. Mirrors the
|
|
119
|
+
* {@link EntityNotAliveError} class style.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* try {
|
|
123
|
+
* createWorld({ indexBits: 99 })
|
|
124
|
+
* } catch (err) {
|
|
125
|
+
* if (err instanceof EcsError) {
|
|
126
|
+
* // an aiecsjs invariant was violated
|
|
127
|
+
* }
|
|
128
|
+
* }
|
|
129
|
+
*/
|
|
130
|
+
declare class EcsError extends Error {
|
|
131
|
+
constructor(message: string);
|
|
132
|
+
}
|
|
133
|
+
|
|
89
134
|
/**
|
|
90
135
|
* ABA-safe entity reference.
|
|
91
136
|
*
|
|
@@ -152,4 +197,4 @@ declare function aliveRef<T = unknown>(world: World, ref: EntityRef<T>): boolean
|
|
|
152
197
|
|
|
153
198
|
declare const IS_SAB_SUPPORTED: boolean;
|
|
154
199
|
|
|
155
|
-
export { AoSComponent, Archetype, ComponentInit, ComponentLike, EntityId, EntityNotAliveError, type EntityRef, IS_SAB_SUPPORTED, Query, QueryDescriptor, SoAComponent, SoASchema, System, TagComponent, VERSION, World, WorldOptions, addComponent, aliveRef, createEntity, createWorld, defineComponent, defineObjectComponent, defineQuery, defineTag, deref, destroyEntity, destroyWorld, destroyWorld as disposeWorld, enterQuery, entityExists, exitQuery, forEachEntity, forEachEntityIndexed, getComponent, getEntityGeneration, getEntityIndex, getWorldCapacity, getWorldSize, hasComponent, isEntity, isWorld, iterQuery, packEntity, pipe, queryArchetypes, refOf, removeComponent, resetWorld, runQuery, setComponent };
|
|
200
|
+
export { AoSComponent, Archetype, ComponentInit, ComponentLike, EcsError, EntityId, EntityNotAliveError, type EntityRef, IS_SAB_SUPPORTED, Query, QueryDescriptor, SoAComponent, SoASchema, System, TagComponent, VERSION, World, WorldOptions, addComponent, aliveRef, createEntity, createWorld, defineComponent, defineObjectComponent, defineQuery, defineTag, deref, destroyEntity, destroyWorld, destroyWorld as disposeWorld, enterQuery, entityExists, exitQuery, forEachEntity, forEachEntityIndexed, getComponent, getEntityGeneration, getEntityIndex, getWorldCapacity, getWorldSize, hasComponent, isEntity, isWorld, iterQuery, packEntity, pipe, queryArchetypes, refOf, removeComponent, resetWorld, runQuery, setComponent };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { o as WorldOptions, W as World, E as EntityId, c as ComponentLike, b as ComponentInit, k as SoASchema, h as SoAComponent, A as AoSComponent, T as TagComponent, f as QueryDescriptor, Q as Query, a as Archetype, l as System } from './types-
|
|
2
|
-
export { d as ComponentView, g as SoAColumns, i as SoAFieldDecl, j as SoAFieldType, n as Types } from './types-
|
|
1
|
+
import { o as WorldOptions, W as World, E as EntityId, c as ComponentLike, b as ComponentInit, k as SoASchema, h as SoAComponent, A as AoSComponent, T as TagComponent, f as QueryDescriptor, Q as Query, a as Archetype, l as System } from './types-BeVLA7xG.js';
|
|
2
|
+
export { d as ComponentView, g as SoAColumns, i as SoAFieldDecl, j as SoAFieldType, n as Types } from './types-BeVLA7xG.js';
|
|
3
3
|
|
|
4
|
-
declare const VERSION = "0.5.
|
|
4
|
+
declare const VERSION = "0.5.7";
|
|
5
5
|
|
|
6
6
|
declare function createWorld(options?: WorldOptions): World;
|
|
7
7
|
declare function destroyWorld(world: World): void;
|
|
@@ -47,7 +47,30 @@ declare function getComponent<C extends ComponentLike>(world: World, eid: Entity
|
|
|
47
47
|
declare function setComponent<C extends ComponentLike, V>(world: World, eid: EntityId, component: C, value: V): void;
|
|
48
48
|
|
|
49
49
|
declare function defineQuery(arg: ComponentLike[] | QueryDescriptor): Query;
|
|
50
|
+
/**
|
|
51
|
+
* Reactive "entered" view of `query`: each read drains the entities that began
|
|
52
|
+
* matching since the last read (via `runQuery` / `iterQuery` / `forEachEntity`).
|
|
53
|
+
*
|
|
54
|
+
* **MUST-DRAIN CONTRACT (unbounded buffer).** The enter buffer has **no cap**.
|
|
55
|
+
* Every structural change that makes an entity newly match pushes one id; the
|
|
56
|
+
* buffer only shrinks when you read the reactive view. A view that is created
|
|
57
|
+
* but never read — e.g. a disabled system, or reading only the {@link exitQuery}
|
|
58
|
+
* twin — accumulates one number per event **forever**, an unbounded memory leak
|
|
59
|
+
* at churn rates (e.g. ~1k spawns/frame). Read every enter view you create, once
|
|
60
|
+
* per frame. Dropping ids silently would corrupt enter/exit symmetry, so capping
|
|
61
|
+
* is intentionally not done — draining is the caller's responsibility.
|
|
62
|
+
*/
|
|
50
63
|
declare function enterQuery(query: Query): Query;
|
|
64
|
+
/**
|
|
65
|
+
* Reactive "exited" view of `query`: each read drains the entities that stopped
|
|
66
|
+
* matching since the last read (via `runQuery` / `iterQuery` / `forEachEntity`).
|
|
67
|
+
*
|
|
68
|
+
* **MUST-DRAIN CONTRACT (unbounded buffer).** The exit buffer has **no cap** —
|
|
69
|
+
* same discipline as {@link enterQuery}. An unread exit view accumulates one id
|
|
70
|
+
* per matching structural change (including every `destroyEntity` of a matching
|
|
71
|
+
* entity) without bound. Read every exit view you create, once per frame. Capping
|
|
72
|
+
* is intentionally not done because dropping ids would break enter/exit symmetry.
|
|
73
|
+
*/
|
|
51
74
|
declare function exitQuery(query: Query): Query;
|
|
52
75
|
declare function queryArchetypes(world: World, query: Query): readonly Archetype[];
|
|
53
76
|
declare function runQuery(world: World, query: Query): readonly EntityId[];
|
|
@@ -86,6 +109,28 @@ declare function forEachEntityIndexed(world: World, query: Query, fn: (e: Entity
|
|
|
86
109
|
|
|
87
110
|
declare function pipe<W extends World = World, Ctx = unknown>(...systems: System<W, Ctx>[]): System<W, Ctx>;
|
|
88
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Thrown by core world/entity/component operations for invariant violations and
|
|
114
|
+
* misuse (bad world options, a destroyed/unknown world, exhausted component
|
|
115
|
+
* slots, capacity overflow). Carries the existing `aiecsjs: `-prefixed message.
|
|
116
|
+
*
|
|
117
|
+
* Catch this to distinguish an aiecsjs-originated failure from an unrelated
|
|
118
|
+
* runtime error, instead of string-matching on the message. Mirrors the
|
|
119
|
+
* {@link EntityNotAliveError} class style.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* try {
|
|
123
|
+
* createWorld({ indexBits: 99 })
|
|
124
|
+
* } catch (err) {
|
|
125
|
+
* if (err instanceof EcsError) {
|
|
126
|
+
* // an aiecsjs invariant was violated
|
|
127
|
+
* }
|
|
128
|
+
* }
|
|
129
|
+
*/
|
|
130
|
+
declare class EcsError extends Error {
|
|
131
|
+
constructor(message: string);
|
|
132
|
+
}
|
|
133
|
+
|
|
89
134
|
/**
|
|
90
135
|
* ABA-safe entity reference.
|
|
91
136
|
*
|
|
@@ -152,4 +197,4 @@ declare function aliveRef<T = unknown>(world: World, ref: EntityRef<T>): boolean
|
|
|
152
197
|
|
|
153
198
|
declare const IS_SAB_SUPPORTED: boolean;
|
|
154
199
|
|
|
155
|
-
export { AoSComponent, Archetype, ComponentInit, ComponentLike, EntityId, EntityNotAliveError, type EntityRef, IS_SAB_SUPPORTED, Query, QueryDescriptor, SoAComponent, SoASchema, System, TagComponent, VERSION, World, WorldOptions, addComponent, aliveRef, createEntity, createWorld, defineComponent, defineObjectComponent, defineQuery, defineTag, deref, destroyEntity, destroyWorld, destroyWorld as disposeWorld, enterQuery, entityExists, exitQuery, forEachEntity, forEachEntityIndexed, getComponent, getEntityGeneration, getEntityIndex, getWorldCapacity, getWorldSize, hasComponent, isEntity, isWorld, iterQuery, packEntity, pipe, queryArchetypes, refOf, removeComponent, resetWorld, runQuery, setComponent };
|
|
200
|
+
export { AoSComponent, Archetype, ComponentInit, ComponentLike, EcsError, EntityId, EntityNotAliveError, type EntityRef, IS_SAB_SUPPORTED, Query, QueryDescriptor, SoAComponent, SoASchema, System, TagComponent, VERSION, World, WorldOptions, addComponent, aliveRef, createEntity, createWorld, defineComponent, defineObjectComponent, defineQuery, defineTag, deref, destroyEntity, destroyWorld, destroyWorld as disposeWorld, enterQuery, entityExists, exitQuery, forEachEntity, forEachEntityIndexed, getComponent, getEntityGeneration, getEntityIndex, getWorldCapacity, getWorldSize, hasComponent, isEntity, isWorld, iterQuery, packEntity, pipe, queryArchetypes, refOf, removeComponent, resetWorld, runQuery, setComponent };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {j,i as i$1}from'./chunk-
|
|
1
|
+
import {j,i as i$1}from'./chunk-22ICWJ5O.js';export{a as defineQuery,b as enterQuery,c as exitQuery,g as forEachEntity,h as forEachEntityIndexed,f as iterQuery,d as queryArchetypes,e as runQuery}from'./chunk-22ICWJ5O.js';import {R,P,j as j$1,z,t,u}from'./chunk-2KCN5RVK.js';export{i as EcsError,a as VERSION,K as addComponent,v as createEntity,l as createWorld,G as defineComponent,I as defineObjectComponent,H as defineTag,x as destroyEntity,m as destroyWorld,m as disposeWorld,y as entityExists,N as getComponent,B as getEntityGeneration,A as getEntityIndex,p as getWorldCapacity,o as getWorldSize,M as hasComponent,D as isEntity,r as isWorld,C as packEntity,L as removeComponent,n as resetWorld,O as setComponent}from'./chunk-2KCN5RVK.js';var J={i8:"i8",u8:"u8",i16:"i16",u16:"u16",i32:"i32",u32:"u32",f32:"f32",f64:"f64",eid:"eid",bool:"bool"};function X(...n){return n.length===0?e=>e:n.length===1?n[0]:(e,r)=>{let t=e;for(let o of n)t=o(t,r);return t}}var i=class extends Error{eid;constructor(e){super(`aiecsjs: entity ${e} is not alive`),this.name="EntityNotAliveError",this.eid=e;}};function Y(n,e){let r=j$1(n);if(!z(r,e))throw new i(e);return Object.freeze({id:e,worldId:r.id})}function b(n,e){let r=j$1(n);if(e.worldId!==r.id)return null;let t$1=e.id,o=t(t$1,r.options);if(o<=0||o>=r.capacity)return null;let x=r.entityArchetype[o]??0,d=r.archetypes[x];if(!d||!d.entityRow.has(e.id))return null;let C=r.generations[o]??0,A=u(t$1,r.options);return C!==A?null:e.id}function Z(n,e){return b(n,e)!==null}j(n=>R(n));P((n,e,r,t,o)=>{i$1(n,e,r,t,o);});var pe=typeof SharedArrayBuffer<"u";export{i as EntityNotAliveError,pe as IS_SAB_SUPPORTED,J as Types,Z as aliveRef,b as deref,X as pipe,Y as refOf};//# sourceMappingURL=index.js.map
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/internal/types.ts","../src/internal/pipe.ts","../src/ref.ts","../src/index.ts"],"names":["Types","pipe","systems","w","world","ctx","s","EntityNotAliveError","eid","refOf","entity","state","getWorldState","isAliveInternal","deref","ref","idx","unpackIdx","archId","arch","storedGen","refGen","unpackGen","aliveRef","registerComponentLookup","id","getComponentByInternalId","registerMaskChangeDispatch","bit","prev","next","recordEntityMaskChange","IS_SAB_SUPPORTED"],"mappings":"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","file":"index.js","sourcesContent":["// Shared type definitions for aiecsjs internals.\n// Public-facing types are re-exported from src/index.ts.\n\nexport type EntityId = number & { readonly __brand: 'EntityId' }\n\nexport const Types = {\n i8: 'i8',\n u8: 'u8',\n i16: 'i16',\n u16: 'u16',\n i32: 'i32',\n u32: 'u32',\n f32: 'f32',\n f64: 'f64',\n eid: 'eid',\n bool: 'bool',\n} as const\n\nexport type SoAFieldType =\n | 'i8'\n | 'u8'\n | 'i16'\n | 'u16'\n | 'i32'\n | 'u32'\n | 'f32'\n | 'f64'\n | 'eid'\n | 'bool'\n\nexport type SoAFieldDecl = SoAFieldType | readonly [SoAFieldType, number]\nexport type SoASchema = Readonly<Record<string, SoAFieldDecl>>\n\nexport interface SoAComponent<S extends SoASchema = SoASchema> {\n readonly __kind: 'soa'\n readonly __id: number\n readonly __schema: S\n}\n\nexport interface AoSComponent<T = unknown> {\n readonly __kind: 'aos'\n readonly __id: number\n readonly __factory: () => T\n}\n\nexport interface TagComponent {\n readonly __kind: 'tag'\n readonly __id: number\n}\n\nexport type ComponentLike = SoAComponent<any> | AoSComponent<any> | TagComponent\n\nexport type ColumnArray =\n | Int8Array\n | Uint8Array\n | Int16Array\n | Uint16Array\n | Int32Array\n | Uint32Array\n | Float32Array\n | Float64Array\n\nexport interface SoAColumns {\n [field: string]: ColumnArray\n}\n\nexport type ComponentView<C extends ComponentLike> = C extends SoAComponent<infer _S>\n ? SoAColumns\n : C extends AoSComponent<infer T>\n ? T | undefined\n : C extends TagComponent\n ? boolean\n : never\n\nexport type ComponentInit<C extends ComponentLike> = C extends SoAComponent<infer _S>\n ? Record<string, unknown>\n : C extends AoSComponent<infer T>\n ? Partial<T>\n : undefined\n\nexport interface QueryDescriptor {\n all?: ComponentLike[]\n any?: ComponentLike[]\n none?: ComponentLike[]\n}\n\nexport interface Query {\n readonly id: number\n readonly mask: ReadonlyArray<number>\n}\n\nexport interface Archetype {\n readonly id: number\n readonly mask: ReadonlyArray<number>\n readonly size: number\n}\n\nexport interface WorldOptions {\n initialCapacity?: number\n maxEntities?: number\n indexBits?: number\n generationBits?: number\n buffer?: SharedArrayBuffer\n bufferByteOffset?: number\n}\n\nexport interface World {\n readonly id: number\n readonly capacity: number\n readonly version: string\n}\n\nexport type System<W extends World = World, Ctx = unknown> = (world: W, ctx: Ctx) => W\n\n// --- Internal-only types ---\n\nexport interface ResolvedWorldOptions {\n initialCapacity: number\n maxEntities: number\n indexBits: number\n generationBits: number\n indexMask: number\n generationMask: number\n maxComponents: number\n maskWordCount: number\n buffer: SharedArrayBuffer | null\n bufferByteOffset: number\n}\n\nexport interface FieldInfo {\n name: string\n type: SoAFieldType\n vectorLen: number // 1 for scalar\n ctor: TypedArrayConstructor\n bytesPerElement: number\n}\n\nexport type TypedArrayConstructor =\n | Int8ArrayConstructor\n | Uint8ArrayConstructor\n | Int16ArrayConstructor\n | Uint16ArrayConstructor\n | Int32ArrayConstructor\n | Uint32ArrayConstructor\n | Float32ArrayConstructor\n | Float64ArrayConstructor\n\nexport interface ComponentInfo {\n id: number\n kind: 'soa' | 'aos' | 'tag'\n schema: SoASchema | null\n fields: FieldInfo[]\n factory: (() => unknown) | null\n}\n\nexport interface WorldComponentStorage {\n // For each component bit in this world: storage backed by a column map (SoA), object array (AoS), or null (tag)\n kind: 'soa' | 'aos' | 'tag'\n componentId: number\n bit: number\n // SoA: field name → TypedArray of size `worldCapacity`\n soa?: SoAColumns\n // AoS: per-entity object array, sparse, indexed by eid; undefined for unowned\n aos?: (unknown | undefined)[]\n}\n\nexport interface ArchetypeState {\n id: number\n mask: Uint32Array\n size: number\n capacity: number\n entities: Uint32Array // packed eids in row order\n entityRow: Map<number, number> // eid → row (small archetype-local lookup)\n componentBits: number[] // sorted\n edgeAdd: Int32Array // [bit] → archetype id; -1 unknown\n edgeRemove: Int32Array // [bit] → archetype id; -1 unknown\n}\n\nexport interface QueryInternal extends Query {\n id: number\n all: number[]\n any: number[]\n none: number[]\n // Cache of column references per matched archetype (per query-call)\n columnViewCache: ComponentLike[] // the requested components in their declared order\n reactiveKind: 'normal' | 'enter' | 'exit'\n sourceQueryId: number // -1 for normal\n sourceQuery: QueryInternal | null // back-ref for reactive registration\n}\n\n// Per-world resolved bitmasks for a query. Component bits are assigned per\n// world, so the masks must live in WorldState — keeping them on the shared\n// QueryInternal would silently cross-contaminate worlds whose component\n// registration order differs.\nexport interface QueryMaskBundle {\n withMask: Uint32Array\n anyMask: Uint32Array\n noneMask: Uint32Array\n anyHasBits: boolean\n}\n\nexport interface ReactiveBuffer {\n entered: number[]\n exited: number[]\n}\n\nexport type ObserverEvent = 'add' | 'remove' | 'set'\n\nexport interface ObserverEntry {\n event: ObserverEvent\n componentBit: number // -1 means \"any component\" (used by query observe)\n queryId: number // -1 means component-only\n handler: (eid: EntityId, value?: unknown) => void\n}\n\nexport interface CommandBufferState {\n worldId: number\n ops: CommandOp[]\n nextPlaceholder: number\n flushing: boolean\n}\n\nexport type CommandOp =\n | { kind: 'create'; placeholder: number }\n | { kind: 'add'; eid: EntityId; component: ComponentLike; initial?: unknown }\n | { kind: 'remove'; eid: EntityId; component: ComponentLike }\n | { kind: 'destroy'; eid: EntityId }\n\nexport interface CommandBuffer {\n add<C extends ComponentLike>(eid: EntityId, component: C, initial?: ComponentInit<C>): void\n remove<C extends ComponentLike>(eid: EntityId, component: C): void\n destroy(eid: EntityId): void\n create(): EntityId\n}\n\nexport interface WorldMeta {\n magic: number\n formatVersion: number\n aiecsjsVersion: string\n indexBits: number\n generationBits: number\n maxComponents: number\n maskWordCount: number\n capacity: number\n componentSchemas: Array<{ id: number; kind: 'soa' | 'aos' | 'tag'; schema: SoASchema | null }>\n}\n\nexport interface TransferableSnapshot {\n buffer: SharedArrayBuffer\n meta: WorldMeta\n}\n\nexport interface SerializeOptions {\n components?: ComponentLike[]\n}\n\nexport interface DeserializeOptions {\n components?: ComponentLike[]\n onUnknownVersion?: 'throw' | 'best-effort'\n}\n\nexport interface WorldSnapshot {\n version: string\n capacity: number\n entities: Array<{\n eid: number\n components: Array<{\n kind: 'soa' | 'aos' | 'tag'\n id: number\n data: unknown\n }>\n }>\n}\n\nexport interface DeltaSerializer {\n capture(): Uint8Array\n apply(world: World, delta: Uint8Array): void\n reset(): void\n}\n\nexport interface Relation<T = void> {\n readonly __kind: 'relation'\n readonly __id: number\n readonly __exclusive: boolean\n readonly __hasData: boolean\n}\n\nexport interface RelationStorage {\n rel: Relation<any>\n exclusive: Int32Array | null // [srcEid] → tgtEid; -1 means none\n outgoing: Map<number, number[]> // srcEid → tgtEid[]\n data: Map<number, Map<number, unknown>> // srcEid → (tgtEid → data); nested to stay correct across capacity growth\n}\n\nexport interface WorldState {\n // --- public view ---\n readonly id: number\n capacity: number\n readonly version: string\n\n // --- options ---\n options: ResolvedWorldOptions\n\n // --- entity allocation ---\n size: number // alive count\n nextFreshIndex: number // never-used index\n freeList: number[] // recycled indices\n generations: Uint8Array | Uint16Array // [eid] → version\n destroyed: boolean\n\n // --- component registry (per-world) ---\n componentBitFor: Map<number, number> // global component id → bit pos in this world\n componentInfoByBit: (ComponentInfo | null)[] // bit pos → ComponentInfo\n componentStorageByBit: (WorldComponentStorage | null)[]\n nextComponentBit: number\n\n // --- sparse entity state ---\n entityArchetype: Uint32Array // [eid] → archetype id (0 = unattached)\n // entityMask flattened: word w of mask of eid is at entityMask[eid * maskWordCount + w]\n entityMask: Uint32Array\n\n // --- archetype registry ---\n archetypes: ArchetypeState[]\n archetypeByMaskHash: Map<string, number>\n queryVersion: number\n\n // --- query cache ---\n queries: QueryInternal[] // by id\n queryMasks: Map<number, QueryMaskBundle> // queryId → per-world resolved bitmasks\n queryArchetypeCache: (number[] | null)[]\n queryArchetypeStamp: number[]\n bitToQueries: Map<number, Set<number>> // bit → queryIds that mention this bit\n\n // --- reactive query state ---\n reactiveBuffers: Map<number, ReactiveBuffer> // queryId → { entered, exited }\n\n // --- observers ---\n observers: ObserverEntry[]\n\n // --- relations ---\n relationStorage: Map<number, RelationStorage> // relation id → storage\n\n // --- worker / SAB ---\n sab: SharedArrayBuffer | null\n readOnly: boolean\n}\n","import type { System, World } from './types.js'\n\nexport function pipe<W extends World = World, Ctx = unknown>(\n ...systems: System<W, Ctx>[]\n): System<W, Ctx> {\n if (systems.length === 0) return (w: W) => w\n if (systems.length === 1) return systems[0]!\n return (world: W, ctx: Ctx) => {\n let w = world\n for (const s of systems) w = s(w, ctx)\n return w\n }\n}\n","// aiecsjs/ref — ABA-safe entity references.\n//\n// EntityRef<T> wraps a packed EntityId with a world identity check so that\n// stale references (pointing at a recycled slot with a different generation)\n// safely return null rather than silently accessing the wrong entity.\n//\n// EntityRef is in-memory only. Do not pass across worker boundaries or persist\n// to disk. The packed id field has meaning only within the world that issued it.\n// Generation counters reset on resetWorld / deserializeWorld / adoptSnapshot.\n\nimport { isAliveInternal, unpackGen, unpackIdx } from './internal/entity.js'\nimport type { EntityId, World } from './internal/types.js'\nimport { getWorldState } from './internal/world.js'\n\n/**\n * ABA-safe entity reference.\n *\n * Holds the packed EntityId and the id of the world that issued it.\n * Use `deref(world, ref)` to validate and retrieve the live entity id.\n *\n * The phantom type `T` allows callers to distinguish reference kinds at the\n * type system level — e.g. `EntityRef<'bullet'>` vs `EntityRef<'player'>` —\n * without any runtime overhead.\n *\n * @example\n * const ref = refOf<'bullet'>(world, e)\n * // later, in a system:\n * const live = deref(world, ref)\n * if (live !== null) {\n * // entity is still alive with the same generation\n * }\n */\nexport interface EntityRef<T = unknown> {\n readonly id: EntityId // packed value (index + generation)\n readonly worldId: number // world.id — guards against cross-world deref\n readonly __phantom?: T // phantom type tag; zero runtime size\n}\n\n/**\n * Thrown by `refOf` when the entity is not alive.\n * `deref` and `aliveRef` never throw — they return null/false.\n */\nexport class EntityNotAliveError extends Error {\n readonly eid: number\n constructor(eid: number) {\n super(`aiecsjs: entity ${eid} is not alive`)\n this.name = 'EntityNotAliveError'\n this.eid = eid\n }\n}\n\n/**\n * Create an ABA-safe reference to a live entity.\n *\n * @throws {EntityNotAliveError} if the entity is not alive (dead, never created, or eid === 0).\n *\n * The returned object is frozen. `id` is the packed EntityId; `worldId` is\n * the world's numeric id used by `deref` to reject cross-world lookups.\n *\n * The function may be called on the hot path — each call returns a new object\n * but performs no allocation beyond that.\n */\nexport function refOf<T = unknown>(world: World, entity: EntityId): EntityRef<T> {\n const state = getWorldState(world)\n if (!isAliveInternal(state, entity as number)) {\n throw new EntityNotAliveError(entity as number)\n }\n return Object.freeze({ id: entity, worldId: state.id }) as EntityRef<T>\n}\n\n/**\n * Resolve a ref to its live EntityId, or return null if stale.\n *\n * Returns null when any of the following hold:\n * 1. `ref.worldId !== state.id` — cross-world ref\n * 2. `getEntityIndex(ref.id) >= state.capacity` — index out-of-bounds\n * 3. `getEntityIndex(ref.id) === 0` — sentinel slot\n * 4. entity is not in any archetype's entityRow (slot released)\n * 5. generation mismatch → ABA: old ref points at recycled slot\n *\n * Never throws.\n */\nexport function deref<T = unknown>(world: World, ref: EntityRef<T>): EntityId | null {\n const state = getWorldState(world)\n\n // Cross-world guard\n if (ref.worldId !== state.id) return null\n\n const eid = ref.id as number\n const idx = unpackIdx(eid, state.options)\n\n // Sentinel and OOB guards\n if (idx <= 0) return null\n if (idx >= state.capacity) return null\n\n // Archetype membership check\n const archId = state.entityArchetype[idx] ?? 0\n const arch = state.archetypes[archId]\n if (!arch) return null\n if (!arch.entityRow.has(ref.id)) return null\n\n // Generation match — the core ABA protection\n const storedGen = state.generations[idx] ?? 0\n const refGen = unpackGen(eid, state.options)\n if (storedGen !== refGen) return null\n\n return ref.id\n}\n\n/**\n * Boolean guard form of `deref`. Equivalent to `deref(world, ref) !== null`.\n * Use in guard clauses when you do not need the resolved EntityId.\n *\n * Never throws.\n */\nexport function aliveRef<T = unknown>(world: World, ref: EntityRef<T>): boolean {\n return deref(world, ref) !== null\n}\n","// aiecsjs — public root entry.\n// Re-exports the core API and wires up the lazy cross-module references.\n\nexport { VERSION } from './version.js'\n\nimport {\n getComponentByInternalId,\n registerMaskChangeDispatch,\n registerObserverDispatch,\n} from './internal/component.js'\nimport { registerObserversAPI, registerRelationsCleanup } from './internal/entity.js'\nimport { recordEntityMaskChange, registerComponentLookup } from './internal/query.js'\nimport type { EntityId, WorldState } from './internal/types.js'\n\n// Wire query → component lookup\nregisterComponentLookup((id) => getComponentByInternalId(id))\n\n// Wire component mask-change → query reactive update\nregisterMaskChangeDispatch((state, eid, bit, prev, next) => {\n recordEntityMaskChange(state, eid, bit, prev, next)\n})\n\n// Observer dispatch is registered lazily by observers.ts on first import.\n// Relations cleanup is registered lazily by relations.ts on first import.\n\n// --- Type re-exports ---\nexport type {\n EntityId,\n World,\n WorldOptions,\n System,\n SoAComponent,\n AoSComponent,\n TagComponent,\n ComponentLike,\n ComponentInit,\n ComponentView,\n SoAColumns,\n SoASchema,\n SoAFieldType,\n SoAFieldDecl,\n Query,\n QueryDescriptor,\n Archetype,\n} from './internal/types.js'\n\n// --- World ---\nexport {\n createWorld,\n destroyWorld,\n resetWorld,\n getWorldSize,\n getWorldCapacity,\n isWorld,\n} from './internal/world.js'\n\n// `disposeWorld` is the new preferred name (added 0.2.0). It is the same\n// function as `destroyWorld`, exported under a name that aligns with the ai*js\n// ecosystem `dispose()` convention (aifsmjs Runtime, aibridgejs Bridge).\n//\n// `destroyWorld` is the original 0.1.x export. It is now deprecated as of 0.2.0\n// and is scheduled for removal in 1.0. New code should import `disposeWorld`.\nexport { destroyWorld as disposeWorld } from './internal/world.js'\n\n// --- Entity ---\nexport {\n createEntity,\n destroyEntity,\n entityExists,\n getEntityIndex,\n getEntityGeneration,\n packEntity,\n isEntity,\n} from './internal/entity.js'\n\n// --- Component ---\nexport {\n defineComponent,\n defineTag,\n defineObjectComponent,\n addComponent,\n removeComponent,\n hasComponent,\n getComponent,\n setComponent,\n} from './internal/component.js'\n\nexport { Types } from './internal/types.js'\n\n// --- Query ---\nexport {\n defineQuery,\n runQuery,\n iterQuery,\n forEachEntity,\n forEachEntityIndexed,\n enterQuery,\n exitQuery,\n queryArchetypes,\n} from './internal/query.js'\n\n// --- System ---\nexport { pipe } from './internal/pipe.js'\n\n// --- Utility ---\nexport const IS_SAB_SUPPORTED: boolean = typeof SharedArrayBuffer !== 'undefined'\n\n// --- EntityRef (ABA-safe references) ---\nexport type { EntityRef } from './ref.js'\nexport { refOf, deref, aliveRef, EntityNotAliveError } from './ref.js'\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/internal/types.ts","../src/internal/pipe.ts","../src/ref.ts","../src/index.ts"],"names":["Types","pipe","systems","w","world","ctx","s","EntityNotAliveError","eid","refOf","entity","state","getWorldState","isAliveInternal","deref","ref","idx","unpackIdx","archId","arch","storedGen","refGen","unpackGen","aliveRef","registerComponentLookup","id","getComponentByInternalId","registerMaskChangeDispatch","bit","prev","next","recordEntityMaskChange","IS_SAB_SUPPORTED"],"mappings":"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","file":"index.js","sourcesContent":["// Shared type definitions for aiecsjs internals.\n// Public-facing types are re-exported from src/index.ts.\n\nexport type EntityId = number & { readonly __brand: 'EntityId' }\n\nexport const Types = {\n i8: 'i8',\n u8: 'u8',\n i16: 'i16',\n u16: 'u16',\n i32: 'i32',\n u32: 'u32',\n f32: 'f32',\n f64: 'f64',\n eid: 'eid',\n bool: 'bool',\n} as const\n\nexport type SoAFieldType =\n | 'i8'\n | 'u8'\n | 'i16'\n | 'u16'\n | 'i32'\n | 'u32'\n | 'f32'\n | 'f64'\n | 'eid'\n | 'bool'\n\nexport type SoAFieldDecl = SoAFieldType | readonly [SoAFieldType, number]\nexport type SoASchema = Readonly<Record<string, SoAFieldDecl>>\n\nexport interface SoAComponent<S extends SoASchema = SoASchema> {\n readonly __kind: 'soa'\n readonly __id: number\n readonly __schema: S\n}\n\nexport interface AoSComponent<T = unknown> {\n readonly __kind: 'aos'\n readonly __id: number\n readonly __factory: () => T\n}\n\nexport interface TagComponent {\n readonly __kind: 'tag'\n readonly __id: number\n}\n\nexport type ComponentLike = SoAComponent<any> | AoSComponent<any> | TagComponent\n\nexport type ColumnArray =\n | Int8Array\n | Uint8Array\n | Int16Array\n | Uint16Array\n | Int32Array\n | Uint32Array\n | Float32Array\n | Float64Array\n\nexport interface SoAColumns {\n [field: string]: ColumnArray\n}\n\nexport type ComponentView<C extends ComponentLike> = C extends SoAComponent<infer _S>\n ? SoAColumns\n : C extends AoSComponent<infer T>\n ? T | undefined\n : C extends TagComponent\n ? boolean\n : never\n\nexport type ComponentInit<C extends ComponentLike> = C extends SoAComponent<infer _S>\n ? Record<string, unknown>\n : C extends AoSComponent<infer T>\n ? Partial<T>\n : undefined\n\nexport interface QueryDescriptor {\n all?: ComponentLike[]\n any?: ComponentLike[]\n none?: ComponentLike[]\n}\n\nexport interface Query {\n readonly id: number\n readonly mask: ReadonlyArray<number>\n}\n\nexport interface Archetype {\n readonly id: number\n readonly mask: ReadonlyArray<number>\n readonly size: number\n}\n\nexport interface WorldOptions {\n initialCapacity?: number\n maxEntities?: number\n indexBits?: number\n generationBits?: number\n /**\n * RESERVED / UNIMPLEMENTED (0.x). Setting this has **no effect** today: the\n * world always allocates its own column storage and never reads or writes a\n * caller-supplied SAB. The 0.x Worker handoff is a snapshot-copy transport —\n * post `transferableSnapshot(world)` and rebuild via `adoptSnapshot` from\n * `aiecsjs/worker`; do not pre-allocate a SAB here. The field is kept so the\n * shape is forward-compatible with the true shared-column backing targeted\n * for 0.3+ (see STABILITY.md, `aiecsjs/worker`).\n */\n buffer?: SharedArrayBuffer\n /** RESERVED / UNIMPLEMENTED (0.x). Paired with {@link WorldOptions.buffer}; no effect today. */\n bufferByteOffset?: number\n}\n\nexport interface World {\n readonly id: number\n readonly capacity: number\n readonly version: string\n}\n\nexport type System<W extends World = World, Ctx = unknown> = (world: W, ctx: Ctx) => W\n\n// --- Internal-only types ---\n\nexport interface ResolvedWorldOptions {\n initialCapacity: number\n maxEntities: number\n indexBits: number\n generationBits: number\n indexMask: number\n generationMask: number\n maxComponents: number\n maskWordCount: number\n buffer: SharedArrayBuffer | null\n bufferByteOffset: number\n}\n\nexport interface FieldInfo {\n name: string\n type: SoAFieldType\n vectorLen: number // 1 for scalar\n ctor: TypedArrayConstructor\n bytesPerElement: number\n}\n\nexport type TypedArrayConstructor =\n | Int8ArrayConstructor\n | Uint8ArrayConstructor\n | Int16ArrayConstructor\n | Uint16ArrayConstructor\n | Int32ArrayConstructor\n | Uint32ArrayConstructor\n | Float32ArrayConstructor\n | Float64ArrayConstructor\n\nexport interface ComponentInfo {\n id: number\n kind: 'soa' | 'aos' | 'tag'\n schema: SoASchema | null\n fields: FieldInfo[]\n factory: (() => unknown) | null\n}\n\nexport interface WorldComponentStorage {\n // For each component bit in this world: storage backed by a column map (SoA), object array (AoS), or null (tag)\n kind: 'soa' | 'aos' | 'tag'\n componentId: number\n bit: number\n // SoA: field name → TypedArray of size `worldCapacity`\n soa?: SoAColumns\n // AoS: per-entity object array, sparse, indexed by eid; undefined for unowned\n aos?: (unknown | undefined)[]\n}\n\nexport interface ArchetypeState {\n id: number\n mask: Uint32Array\n size: number\n capacity: number\n entities: Uint32Array // packed eids in row order\n entityRow: Map<number, number> // eid → row (small archetype-local lookup)\n componentBits: number[] // sorted\n edgeAdd: Int32Array // [bit] → archetype id; -1 unknown\n edgeRemove: Int32Array // [bit] → archetype id; -1 unknown\n}\n\nexport interface QueryInternal extends Query {\n id: number\n all: number[]\n any: number[]\n none: number[]\n // Cache of column references per matched archetype (per query-call)\n columnViewCache: ComponentLike[] // the requested components in their declared order\n reactiveKind: 'normal' | 'enter' | 'exit'\n sourceQueryId: number // -1 for normal\n sourceQuery: QueryInternal | null // back-ref for reactive registration\n}\n\n// Per-world resolved bitmasks for a query. Component bits are assigned per\n// world, so the masks must live in WorldState — keeping them on the shared\n// QueryInternal would silently cross-contaminate worlds whose component\n// registration order differs.\nexport interface QueryMaskBundle {\n withMask: Uint32Array\n anyMask: Uint32Array\n noneMask: Uint32Array\n anyHasBits: boolean\n}\n\nexport interface ReactiveBuffer {\n entered: number[]\n exited: number[]\n}\n\nexport type ObserverEvent = 'add' | 'remove' | 'set'\n\nexport interface ObserverEntry {\n event: ObserverEvent\n componentBit: number // -1 means \"any component\" (used by query observe)\n queryId: number // -1 means component-only\n handler: (eid: EntityId, value?: unknown) => void\n}\n\nexport interface CommandBufferState {\n worldId: number\n ops: CommandOp[]\n nextPlaceholder: number\n flushing: boolean\n}\n\nexport type CommandOp =\n | { kind: 'create'; placeholder: number }\n | { kind: 'add'; eid: EntityId; component: ComponentLike; initial?: unknown }\n | { kind: 'remove'; eid: EntityId; component: ComponentLike }\n | { kind: 'destroy'; eid: EntityId }\n\nexport interface CommandBuffer {\n add<C extends ComponentLike>(eid: EntityId, component: C, initial?: ComponentInit<C>): void\n remove<C extends ComponentLike>(eid: EntityId, component: C): void\n destroy(eid: EntityId): void\n create(): EntityId\n}\n\nexport interface WorldMeta {\n magic: number\n formatVersion: number\n aiecsjsVersion: string\n indexBits: number\n generationBits: number\n maxComponents: number\n maskWordCount: number\n capacity: number\n componentSchemas: Array<{ id: number; kind: 'soa' | 'aos' | 'tag'; schema: SoASchema | null }>\n}\n\nexport interface TransferableSnapshot {\n // Union, not bare SharedArrayBuffer: in a non-cross-origin-isolated environment\n // `SharedArrayBuffer` is undefined, so transferableSnapshot falls back to a\n // plain ArrayBuffer (worker.ts). Declaring the honest union lets consumers\n // branch on `instanceof SharedArrayBuffer` instead of trusting a type that\n // would otherwise lie. Experimental subpath — the union is allowed to land in 0.x.\n buffer: SharedArrayBuffer | ArrayBuffer\n meta: WorldMeta\n}\n\nexport interface SerializeOptions {\n components?: ComponentLike[]\n}\n\nexport interface DeserializeOptions {\n components?: ComponentLike[]\n onUnknownVersion?: 'throw' | 'best-effort'\n}\n\nexport interface WorldSnapshot {\n version: string\n capacity: number\n entities: Array<{\n eid: number\n components: Array<{\n kind: 'soa' | 'aos' | 'tag'\n id: number\n data: unknown\n }>\n }>\n}\n\nexport interface DeltaSerializer {\n capture(): Uint8Array\n apply(world: World, delta: Uint8Array): void\n reset(): void\n}\n\nexport interface Relation<T = void> {\n readonly __kind: 'relation'\n readonly __id: number\n readonly __exclusive: boolean\n readonly __hasData: boolean\n}\n\nexport interface RelationStorage {\n rel: Relation<any>\n exclusive: Int32Array | null // [srcEid] → tgtEid; -1 means none\n outgoing: Map<number, number[]> // srcEid → tgtEid[]\n data: Map<number, Map<number, unknown>> // srcEid → (tgtEid → data); nested to stay correct across capacity growth\n}\n\nexport interface WorldState {\n // --- public view ---\n readonly id: number\n capacity: number\n readonly version: string\n\n // --- options ---\n options: ResolvedWorldOptions\n\n // --- entity allocation ---\n size: number // alive count\n nextFreshIndex: number // never-used index\n freeList: number[] // recycled indices\n generations: Uint8Array | Uint16Array // [eid] → version\n destroyed: boolean\n\n // --- component registry (per-world) ---\n componentBitFor: Map<number, number> // global component id → bit pos in this world\n componentInfoByBit: (ComponentInfo | null)[] // bit pos → ComponentInfo\n componentStorageByBit: (WorldComponentStorage | null)[]\n nextComponentBit: number\n\n // --- sparse entity state ---\n entityArchetype: Uint32Array // [eid] → archetype id (0 = unattached)\n // entityMask flattened: word w of mask of eid is at entityMask[eid * maskWordCount + w]\n entityMask: Uint32Array\n\n // --- archetype registry ---\n archetypes: ArchetypeState[]\n archetypeByMaskHash: Map<string, number>\n queryVersion: number\n\n // --- query cache ---\n queries: QueryInternal[] // by id\n queryMasks: Map<number, QueryMaskBundle> // queryId → per-world resolved bitmasks\n queryArchetypeCache: (number[] | null)[]\n queryArchetypeStamp: number[]\n bitToQueries: Map<number, Set<number>> // bit → queryIds that mention this bit\n\n // --- reactive query state ---\n reactiveBuffers: Map<number, ReactiveBuffer> // queryId → { entered, exited }\n\n // --- observers ---\n observers: ObserverEntry[]\n\n // --- relations ---\n relationStorage: Map<number, RelationStorage> // relation id → storage\n\n // --- worker / SAB ---\n sab: SharedArrayBuffer | null\n readOnly: boolean\n}\n","import type { System, World } from './types.js'\n\nexport function pipe<W extends World = World, Ctx = unknown>(\n ...systems: System<W, Ctx>[]\n): System<W, Ctx> {\n if (systems.length === 0) return (w: W) => w\n if (systems.length === 1) return systems[0]!\n return (world: W, ctx: Ctx) => {\n let w = world\n for (const s of systems) w = s(w, ctx)\n return w\n }\n}\n","// aiecsjs/ref — ABA-safe entity references.\n//\n// EntityRef<T> wraps a packed EntityId with a world identity check so that\n// stale references (pointing at a recycled slot with a different generation)\n// safely return null rather than silently accessing the wrong entity.\n//\n// EntityRef is in-memory only. Do not pass across worker boundaries or persist\n// to disk. The packed id field has meaning only within the world that issued it.\n// Generation counters reset on resetWorld / deserializeWorld / adoptSnapshot.\n\nimport { isAliveInternal, unpackGen, unpackIdx } from './internal/entity.js'\nimport type { EntityId, World } from './internal/types.js'\nimport { getWorldState } from './internal/world.js'\n\n/**\n * ABA-safe entity reference.\n *\n * Holds the packed EntityId and the id of the world that issued it.\n * Use `deref(world, ref)` to validate and retrieve the live entity id.\n *\n * The phantom type `T` allows callers to distinguish reference kinds at the\n * type system level — e.g. `EntityRef<'bullet'>` vs `EntityRef<'player'>` —\n * without any runtime overhead.\n *\n * @example\n * const ref = refOf<'bullet'>(world, e)\n * // later, in a system:\n * const live = deref(world, ref)\n * if (live !== null) {\n * // entity is still alive with the same generation\n * }\n */\nexport interface EntityRef<T = unknown> {\n readonly id: EntityId // packed value (index + generation)\n readonly worldId: number // world.id — guards against cross-world deref\n readonly __phantom?: T // phantom type tag; zero runtime size\n}\n\n/**\n * Thrown by `refOf` when the entity is not alive.\n * `deref` and `aliveRef` never throw — they return null/false.\n */\nexport class EntityNotAliveError extends Error {\n readonly eid: number\n constructor(eid: number) {\n super(`aiecsjs: entity ${eid} is not alive`)\n this.name = 'EntityNotAliveError'\n this.eid = eid\n }\n}\n\n/**\n * Create an ABA-safe reference to a live entity.\n *\n * @throws {EntityNotAliveError} if the entity is not alive (dead, never created, or eid === 0).\n *\n * The returned object is frozen. `id` is the packed EntityId; `worldId` is\n * the world's numeric id used by `deref` to reject cross-world lookups.\n *\n * The function may be called on the hot path — each call returns a new object\n * but performs no allocation beyond that.\n */\nexport function refOf<T = unknown>(world: World, entity: EntityId): EntityRef<T> {\n const state = getWorldState(world)\n if (!isAliveInternal(state, entity as number)) {\n throw new EntityNotAliveError(entity as number)\n }\n return Object.freeze({ id: entity, worldId: state.id }) as EntityRef<T>\n}\n\n/**\n * Resolve a ref to its live EntityId, or return null if stale.\n *\n * Returns null when any of the following hold:\n * 1. `ref.worldId !== state.id` — cross-world ref\n * 2. `getEntityIndex(ref.id) >= state.capacity` — index out-of-bounds\n * 3. `getEntityIndex(ref.id) === 0` — sentinel slot\n * 4. entity is not in any archetype's entityRow (slot released)\n * 5. generation mismatch → ABA: old ref points at recycled slot\n *\n * Never throws.\n */\nexport function deref<T = unknown>(world: World, ref: EntityRef<T>): EntityId | null {\n const state = getWorldState(world)\n\n // Cross-world guard\n if (ref.worldId !== state.id) return null\n\n const eid = ref.id as number\n const idx = unpackIdx(eid, state.options)\n\n // Sentinel and OOB guards\n if (idx <= 0) return null\n if (idx >= state.capacity) return null\n\n // Archetype membership check\n const archId = state.entityArchetype[idx] ?? 0\n const arch = state.archetypes[archId]\n if (!arch) return null\n if (!arch.entityRow.has(ref.id)) return null\n\n // Generation match — the core ABA protection\n const storedGen = state.generations[idx] ?? 0\n const refGen = unpackGen(eid, state.options)\n if (storedGen !== refGen) return null\n\n return ref.id\n}\n\n/**\n * Boolean guard form of `deref`. Equivalent to `deref(world, ref) !== null`.\n * Use in guard clauses when you do not need the resolved EntityId.\n *\n * Never throws.\n */\nexport function aliveRef<T = unknown>(world: World, ref: EntityRef<T>): boolean {\n return deref(world, ref) !== null\n}\n","// aiecsjs — public root entry.\n// Re-exports the core API and wires up the lazy cross-module references.\n\nexport { VERSION } from './version.js'\n\nimport {\n getComponentByInternalId,\n registerMaskChangeDispatch,\n registerObserverDispatch,\n} from './internal/component.js'\nimport { registerObserversAPI, registerRelationsCleanup } from './internal/entity.js'\nimport { recordEntityMaskChange, registerComponentLookup } from './internal/query.js'\nimport type { EntityId, WorldState } from './internal/types.js'\n\n// Wire query → component lookup\nregisterComponentLookup((id) => getComponentByInternalId(id))\n\n// Wire component mask-change → query reactive update\nregisterMaskChangeDispatch((state, eid, bit, prev, next) => {\n recordEntityMaskChange(state, eid, bit, prev, next)\n})\n\n// Observer dispatch is registered lazily by observers.ts on first import.\n// Relations cleanup is registered lazily by relations.ts on first import.\n\n// --- Type re-exports ---\nexport type {\n EntityId,\n World,\n WorldOptions,\n System,\n SoAComponent,\n AoSComponent,\n TagComponent,\n ComponentLike,\n ComponentInit,\n ComponentView,\n SoAColumns,\n SoASchema,\n SoAFieldType,\n SoAFieldDecl,\n Query,\n QueryDescriptor,\n Archetype,\n} from './internal/types.js'\n\n// --- World ---\nexport {\n createWorld,\n destroyWorld,\n resetWorld,\n getWorldSize,\n getWorldCapacity,\n isWorld,\n} from './internal/world.js'\n\n// `disposeWorld` is the new preferred name (added 0.2.0). It is the same\n// function as `destroyWorld`, exported under a name that aligns with the ai*js\n// ecosystem `dispose()` convention (aifsmjs Runtime, aibridgejs Bridge).\n//\n// `destroyWorld` is the original 0.1.x export. It is now deprecated as of 0.2.0\n// and is scheduled for removal in 1.0. New code should import `disposeWorld`.\nexport { destroyWorld as disposeWorld } from './internal/world.js'\n\n// --- Entity ---\nexport {\n createEntity,\n destroyEntity,\n entityExists,\n getEntityIndex,\n getEntityGeneration,\n packEntity,\n isEntity,\n} from './internal/entity.js'\n\n// --- Component ---\nexport {\n defineComponent,\n defineTag,\n defineObjectComponent,\n addComponent,\n removeComponent,\n hasComponent,\n getComponent,\n setComponent,\n} from './internal/component.js'\n\nexport { Types } from './internal/types.js'\n\n// --- Query ---\nexport {\n defineQuery,\n runQuery,\n iterQuery,\n forEachEntity,\n forEachEntityIndexed,\n enterQuery,\n exitQuery,\n queryArchetypes,\n} from './internal/query.js'\n\n// --- System ---\nexport { pipe } from './internal/pipe.js'\n\n// --- Utility ---\nexport const IS_SAB_SUPPORTED: boolean = typeof SharedArrayBuffer !== 'undefined'\n\n// --- Errors ---\nexport { EcsError } from './internal/errors.js'\n\n// --- EntityRef (ABA-safe references) ---\nexport type { EntityRef } from './ref.js'\nexport { refOf, deref, aliveRef, EntityNotAliveError } from './ref.js'\n"]}
|
package/dist/observers.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
'use strict';var
|
|
1
|
+
'use strict';var chunkSRX2MZPX_cjs=require('./chunk-SRX2MZPX.cjs'),chunk5QWEV4VJ_cjs=require('./chunk-5QWEV4VJ.cjs');function f(n,e){if(!e)return n;if(e.aborted)return n(),()=>{};let o=()=>{n();};e.addEventListener("abort",o,{once:true});let t=false;return ()=>{t||(t=true,e.removeEventListener("abort",o),n());}}function w(n,e,o,t){return f(b(n,e,"add",o),t?.signal)}function x(n,e,o,t){return f(b(n,e,"remove",o),t?.signal)}function C(n,e,o,t){return f(b(n,e,"set",o),t?.signal)}function L(n,e,o,t,r){let s=chunk5QWEV4VJ_cjs.j(n);chunkSRX2MZPX_cjs.e(n,e);let i={event:o,componentBit:-1,queryId:e.id,handler:t};return s.observers.push(i),f(()=>{let d=s.observers.indexOf(i);d>=0&&s.observers.splice(d,1);},r?.signal)}function b(n,e,o,t){let r=chunk5QWEV4VJ_cjs.j(n),s=chunk5QWEV4VJ_cjs.J(e),i=r.componentBitFor.get(s.id);i===void 0&&(i=chunk5QWEV4VJ_cjs.q(r,s));let u={event:o,componentBit:i,queryId:-1,handler:t};return r.observers.push(u),()=>{let d=r.observers.indexOf(u);d>=0&&r.observers.splice(d,1);}}function E(n,e,o){let t=Array.from(n.observers);for(let r of t)r.event==="add"&&r.componentBit===o&&n.observers.includes(r)&&r.handler(e);O(n,e,"add");}function M(n,e,o){let t=Array.from(n.observers);for(let r of t)r.event==="remove"&&r.componentBit===o&&n.observers.includes(r)&&r.handler(e);O(n,e,"remove");}function g(n,e,o,t){let r=Array.from(n.observers);for(let s of r)s.event==="set"&&n.observers.includes(s)&&(s.componentBit===o||s.queryId!==-1&&n.queries[s.queryId]?.all.includes(n.componentInfoByBit[o]?.id??-1))&&s.handler(e,t);}function O(n,e,o,t,r){let s=n.options.maskWordCount,i=(e&n.options.indexMask)*s,u=Array.from(n.observers);for(let d of u){if(d.event!==o||d.queryId===-1||!n.observers.includes(d))continue;let c=n.queryMasks.get(d.queryId);if(!c)continue;let l=chunk5QWEV4VJ_cjs.h(n.entityMask,i,s,c.withMask,c.anyMask,c.noneMask,c.anyHasBits);o==="add"&&l&&d.handler(e),o==="remove"&&!l&&d.handler(e);}}chunk5QWEV4VJ_cjs.Q({fireAdd:E,fireRemove:M,fireSet:g});chunk5QWEV4VJ_cjs.E({dispatchDestroyObservers(n,e){let o=n.options.maskWordCount,t=(e&n.options.indexMask)*o,r=new Uint32Array(o);for(let i=0;i<o;i++)r[i]=n.entityMask[t+i]??0;chunk5QWEV4VJ_cjs.g(r,0,o,i=>{let u=Array.from(n.observers);for(let d of u)d.event==="remove"&&d.componentBit===i&&n.observers.includes(d)&&d.handler(e);});let s=Array.from(n.observers);for(let i of s){if(i.event!=="remove"||i.queryId===-1||!n.observers.includes(i))continue;let u=n.queryMasks.get(i.queryId);if(!u)continue;chunk5QWEV4VJ_cjs.h(r,0,o,u.withMask,u.anyMask,u.noneMask,u.anyHasBits)&&i.handler(e);}}});exports.observe=L;exports.onAdd=w;exports.onRemove=x;exports.onSet=C;//# sourceMappingURL=observers.cjs.map
|
|
2
2
|
//# sourceMappingURL=observers.cjs.map
|
package/dist/observers.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { W as World, Q as Query, O as ObserverEvent, E as EntityId, c as ComponentLike } from './types-
|
|
1
|
+
import { W as World, Q as Query, O as ObserverEvent, E as EntityId, c as ComponentLike } from './types-BeVLA7xG.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Options accepted by every observer registration. `signal` aborts the
|
package/dist/observers.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { W as World, Q as Query, O as ObserverEvent, E as EntityId, c as ComponentLike } from './types-
|
|
1
|
+
import { W as World, Q as Query, O as ObserverEvent, E as EntityId, c as ComponentLike } from './types-BeVLA7xG.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Options accepted by every observer registration. `signal` aborts the
|
package/dist/observers.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {e}from'./chunk-
|
|
1
|
+
import {e}from'./chunk-22ICWJ5O.js';import {Q,E as E$1,g as g$1,h,j,J,q}from'./chunk-2KCN5RVK.js';function f(n,e){if(!e)return n;if(e.aborted)return n(),()=>{};let o=()=>{n();};e.addEventListener("abort",o,{once:true});let t=false;return ()=>{t||(t=true,e.removeEventListener("abort",o),n());}}function w(n,e,o,t){return f(b(n,e,"add",o),t?.signal)}function x(n,e,o,t){return f(b(n,e,"remove",o),t?.signal)}function C(n,e,o,t){return f(b(n,e,"set",o),t?.signal)}function L(n,e$1,o,t,r){let s=j(n);e(n,e$1);let i={event:o,componentBit:-1,queryId:e$1.id,handler:t};return s.observers.push(i),f(()=>{let d=s.observers.indexOf(i);d>=0&&s.observers.splice(d,1);},r?.signal)}function b(n,e,o,t){let r=j(n),s=J(e),i=r.componentBitFor.get(s.id);i===void 0&&(i=q(r,s));let u={event:o,componentBit:i,queryId:-1,handler:t};return r.observers.push(u),()=>{let d=r.observers.indexOf(u);d>=0&&r.observers.splice(d,1);}}function E(n,e,o){let t=Array.from(n.observers);for(let r of t)r.event==="add"&&r.componentBit===o&&n.observers.includes(r)&&r.handler(e);O(n,e,"add");}function M(n,e,o){let t=Array.from(n.observers);for(let r of t)r.event==="remove"&&r.componentBit===o&&n.observers.includes(r)&&r.handler(e);O(n,e,"remove");}function g(n,e,o,t){let r=Array.from(n.observers);for(let s of r)s.event==="set"&&n.observers.includes(s)&&(s.componentBit===o||s.queryId!==-1&&n.queries[s.queryId]?.all.includes(n.componentInfoByBit[o]?.id??-1))&&s.handler(e,t);}function O(n,e,o,t,r){let s=n.options.maskWordCount,i=(e&n.options.indexMask)*s,u=Array.from(n.observers);for(let d of u){if(d.event!==o||d.queryId===-1||!n.observers.includes(d))continue;let c=n.queryMasks.get(d.queryId);if(!c)continue;let l=h(n.entityMask,i,s,c.withMask,c.anyMask,c.noneMask,c.anyHasBits);o==="add"&&l&&d.handler(e),o==="remove"&&!l&&d.handler(e);}}Q({fireAdd:E,fireRemove:M,fireSet:g});E$1({dispatchDestroyObservers(n,e){let o=n.options.maskWordCount,t=(e&n.options.indexMask)*o,r=new Uint32Array(o);for(let i=0;i<o;i++)r[i]=n.entityMask[t+i]??0;g$1(r,0,o,i=>{let u=Array.from(n.observers);for(let d of u)d.event==="remove"&&d.componentBit===i&&n.observers.includes(d)&&d.handler(e);});let s=Array.from(n.observers);for(let i of s){if(i.event!=="remove"||i.queryId===-1||!n.observers.includes(i))continue;let u=n.queryMasks.get(i.queryId);if(!u)continue;h(r,0,o,u.withMask,u.anyMask,u.noneMask,u.anyHasBits)&&i.handler(e);}}});export{L as observe,w as onAdd,x as onRemove,C as onSet};//# sourceMappingURL=observers.js.map
|
|
2
2
|
//# sourceMappingURL=observers.js.map
|
package/dist/observers.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/observers.ts"],"names":["bindAbortSignal","unsubscribe","signal","onAbort","detached","onAdd","world","component","handler","opts","registerComponentObserver","onRemove","onSet","observe","query","event","state","getWorldState","runQuery","entry","idx","info","getComponentInfo","bit","getOrRegisterComponentBit","fireAdd","eid","snapshot","obs","dispatchQueryObservers","fireRemove","fireSet","value","_prev","_current","w","base","bundle","isMatch","matchesEntityMask","registerObserverDispatch","registerObserversAPI","preMask","forEachSetBit","querySnapshot"],"mappings":"2FA0BA,SAASA,CAAAA,CAAgBC,CAAAA,CAAyBC,CAAAA,CAA6C,CAC7F,GAAI,CAACA,CAAAA,CAAQ,OAAOD,CAAAA,CACpB,GAAIC,CAAAA,CAAO,OAAA,CACT,OAAAD,GAAY,CACL,IAAM,CAAC,CAAA,CAEhB,IAAME,CAAAA,CAAU,IAAY,CAC1BF,IACF,CAAA,CACAC,CAAAA,CAAO,gBAAA,CAAiB,QAASC,CAAAA,CAAS,CAAE,IAAA,CAAM,IAAK,CAAC,CAAA,CACxD,IAAIC,CAAAA,CAAW,KAAA,CACf,OAAO,IAAM,CACPA,CAAAA,GACJA,EAAW,IAAA,CACXF,CAAAA,CAAO,mBAAA,CAAoB,OAAA,CAASC,CAAO,CAAA,CAC3CF,CAAAA,EAAY,EACd,CACF,CAEO,SAASI,CAAAA,CACdC,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACY,CACZ,OAAOT,CAAAA,CAAgBU,CAAAA,CAA0BJ,CAAAA,CAAOC,CAAAA,CAAW,MAAOC,CAAO,CAAA,CAAGC,CAAAA,EAAM,MAAM,CAClG,CAEO,SAASE,CAAAA,CACdL,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACY,CACZ,OAAOT,CAAAA,CACLU,CAAAA,CAA0BJ,CAAAA,CAAOC,CAAAA,CAAW,QAAA,CAAUC,CAAO,CAAA,CAC7DC,CAAAA,EAAM,MACR,CACF,CAyBO,SAASG,CAAAA,CACdN,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACY,CACZ,OAAOT,CAAAA,CAAgBU,CAAAA,CAA0BJ,CAAAA,CAAOC,EAAW,KAAA,CAAOC,CAAO,CAAA,CAAGC,CAAAA,EAAM,MAAM,CAClG,CAEO,SAASI,CAAAA,CACdP,CAAAA,CACAQ,GAAAA,CACAC,CAAAA,CACAP,CAAAA,CACAC,EACY,CACZ,IAAMO,CAAAA,CAAQC,CAAAA,CAAcX,CAAK,CAAA,CAEjCY,CAAAA,CAASZ,CAAAA,CAAOQ,GAAK,CAAA,CACrB,IAAMK,GAAAA,CAAuB,CAC3B,KAAA,CAAAJ,CAAAA,CACA,YAAA,CAAc,EAAA,CACd,QAAUD,GAAAA,CAAwB,EAAA,CAClC,OAAA,CAASN,CACX,EACA,OAAAQ,CAAAA,CAAM,SAAA,CAAU,IAAA,CAAKG,GAAK,CAAA,CAKnBnB,CAAAA,CAJa,IAAY,CAC9B,IAAMoB,CAAAA,CAAMJ,CAAAA,CAAM,SAAA,CAAU,QAAQG,GAAK,CAAA,CACrCC,CAAAA,EAAO,CAAA,EAAGJ,CAAAA,CAAM,SAAA,CAAU,MAAA,CAAOI,CAAAA,CAAK,CAAC,EAC7C,CAAA,CACoCX,CAAAA,EAAM,MAAM,CAClD,CAEA,SAASC,CAAAA,CACPJ,EACAC,CAAAA,CACAQ,CAAAA,CACAP,CAAAA,CACY,CACZ,IAAMQ,CAAAA,CAAQC,CAAAA,CAAcX,CAAK,CAAA,CAC3Be,EAAOC,CAAAA,CAAiBf,CAAS,CAAA,CACnCgB,GAAAA,CAAMP,CAAAA,CAAM,eAAA,CAAgB,GAAA,CAAIK,CAAAA,CAAK,EAAE,CAAA,CACvCE,GAAAA,GAAQ,MAAA,GACVA,GAAAA,CAAMC,CAAAA,CAA0BR,CAAAA,CAAOK,CAAI,CAAA,CAAA,CAE7C,IAAMF,CAAAA,CAAuB,CAAE,KAAA,CAAAJ,CAAAA,CAAO,YAAA,CAAcQ,GAAAA,CAAK,OAAA,CAAS,EAAA,CAAI,QAAAf,CAAQ,CAAA,CAC9E,OAAAQ,CAAAA,CAAM,UAAU,IAAA,CAAKG,CAAK,CAAA,CACnB,IAAM,CACX,IAAMC,CAAAA,CAAMJ,CAAAA,CAAM,SAAA,CAAU,OAAA,CAAQG,CAAK,CAAA,CACrCC,CAAAA,EAAO,GAAGJ,CAAAA,CAAM,SAAA,CAAU,MAAA,CAAOI,CAAAA,CAAK,CAAC,EAC7C,CACF,CAWA,SAASK,CAAAA,CAAQT,CAAAA,CAAmBU,CAAAA,CAAeH,CAAAA,CAAmB,CACpE,IAAMI,CAAAA,CAAW,KAAA,CAAM,KAAKX,CAAAA,CAAM,SAAS,CAAA,CAC3C,IAAA,IAAWY,KAAOD,CAAAA,CACZC,CAAAA,CAAI,KAAA,GAAU,KAAA,EACdA,EAAI,YAAA,GAAiBL,CAAAA,EACpBP,CAAAA,CAAM,SAAA,CAAU,QAAA,CAASY,CAAG,CAAA,EACjCA,CAAAA,CAAI,QAAQF,CAAG,CAAA,CAIjBG,CAAAA,CAAuBb,CAAAA,CAAOU,CAAAA,CAAK,KAAsC,EAC3E,CAEA,SAASI,CAAAA,CAAWd,CAAAA,CAAmBU,CAAAA,CAAeH,CAAAA,CAAmB,CACvE,IAAMI,EAAW,KAAA,CAAM,IAAA,CAAKX,CAAAA,CAAM,SAAS,EAC3C,IAAA,IAAWY,CAAAA,IAAOD,CAAAA,CACZC,CAAAA,CAAI,QAAU,QAAA,EACdA,CAAAA,CAAI,YAAA,GAAiBL,CAAAA,EACpBP,CAAAA,CAAM,SAAA,CAAU,QAAA,CAASY,CAAG,GACjCA,CAAAA,CAAI,OAAA,CAAQF,CAAG,CAAA,CAEjBG,CAAAA,CAAuBb,CAAAA,CAAOU,CAAAA,CAAK,QAAyC,EAC9E,CAEA,SAASK,CAAAA,CAAQf,CAAAA,CAAmBU,CAAAA,CAAeH,CAAAA,CAAaS,EAAsB,CACpF,IAAML,CAAAA,CAAW,KAAA,CAAM,KAAKX,CAAAA,CAAM,SAAS,CAAA,CAC3C,IAAA,IAAWY,KAAOD,CAAAA,CACZC,CAAAA,CAAI,KAAA,GAAU,KAAA,EACbZ,CAAAA,CAAM,SAAA,CAAU,QAAA,CAASY,CAAG,IAC7BA,CAAAA,CAAI,YAAA,GAAiBL,CAAAA,EAChBK,CAAAA,CAAI,OAAA,GAAY,EAAA,EACbZ,CAAAA,CAAM,OAAA,CAAQY,EAAI,OAAO,CAAA,EAC5B,GAAA,CAAI,QAAA,CAASZ,CAAAA,CAAM,kBAAA,CAAmBO,CAAG,CAAA,EAAG,IAAM,EAAE,CAAA,CAAA,EACzDK,CAAAA,CAAI,OAAA,CAAQF,EAAKM,CAAK,EAI9B,CAIA,SAASH,EACPb,CAAAA,CACAU,CAAAA,CACAX,CAAAA,CACAkB,CAAAA,CACAC,CAAAA,CACM,CACN,IAAMC,CAAAA,CAAInB,EAAM,OAAA,CAAQ,aAAA,CAClBoB,CAAAA,CAAAA,CAASV,CAAAA,CAAiBV,CAAAA,CAAM,OAAA,CAAQ,SAAA,EAAamB,CAAAA,CACrDR,EAAW,KAAA,CAAM,IAAA,CAAKX,CAAAA,CAAM,SAAS,CAAA,CAC3C,IAAA,IAAWY,CAAAA,IAAOD,CAAAA,CAAU,CAG1B,GAFIC,CAAAA,CAAI,KAAA,GAAUb,CAAAA,EACda,EAAI,OAAA,GAAY,EAAA,EAChB,CAACZ,CAAAA,CAAM,UAAU,QAAA,CAASY,CAAG,CAAA,CAAG,SACpC,IAAMS,CAAAA,CAASrB,CAAAA,CAAM,UAAA,CAAW,IAAIY,CAAAA,CAAI,OAAO,CAAA,CAC/C,GAAI,CAACS,CAAAA,CAAQ,SACb,IAAMC,EAAUC,CAAAA,CACdvB,CAAAA,CAAM,UAAA,CACNoB,CAAAA,CACAD,CAAAA,CACAE,CAAAA,CAAO,QAAA,CACPA,CAAAA,CAAO,QACPA,CAAAA,CAAO,QAAA,CACPA,CAAAA,CAAO,UACT,EAIItB,CAAAA,GAAU,KAAA,EAASuB,CAAAA,EAASV,CAAAA,CAAI,QAAQF,CAAG,CAAA,CAC3CX,CAAAA,GAAU,QAAA,EAAY,CAACuB,CAAAA,EAASV,CAAAA,CAAI,OAAA,CAAQF,CAAG,EACrD,CACF,CAGAc,CAAAA,CAAyB,CAAE,OAAA,CAAAf,CAAAA,CAAS,UAAA,CAAAK,EAAY,OAAA,CAAAC,CAAQ,CAAC,CAAA,CAIzDU,CAAAA,CAAqB,CACnB,wBAAA,CAAyBzB,CAAAA,CAAmBU,EAAqB,CAC/D,IAAMS,CAAAA,CAAInB,CAAAA,CAAM,QAAQ,aAAA,CAClBoB,CAAAA,CAAAA,CAASV,CAAAA,CAAiBV,CAAAA,CAAM,QAAQ,SAAA,EAAamB,CAAAA,CAOrDO,CAAAA,CAAU,IAAI,WAAA,CAAYP,CAAC,CAAA,CACjC,IAAA,IAAS,EAAI,CAAA,CAAG,CAAA,CAAIA,CAAAA,CAAG,CAAA,EAAA,CAAKO,CAAAA,CAAQ,CAAC,CAAA,CAAI1B,CAAAA,CAAM,WAAWoB,CAAAA,CAAO,CAAC,CAAA,EAAK,CAAA,CAGvEO,GAAAA,CAAcD,CAAAA,CAAS,CAAA,CAAGP,CAAAA,CAAIZ,GAAQ,CACpC,IAAMI,CAAAA,CAAW,KAAA,CAAM,KAAKX,CAAAA,CAAM,SAAS,CAAA,CAC3C,IAAA,IAAWY,KAAOD,CAAAA,CACZC,CAAAA,CAAI,KAAA,GAAU,QAAA,EACdA,CAAAA,CAAI,YAAA,GAAiBL,CAAAA,EACpBP,CAAAA,CAAM,UAAU,QAAA,CAASY,CAAG,CAAA,EACjCA,CAAAA,CAAI,OAAA,CAAQF,CAAG,EAEnB,CAAC,EAKD,IAAMkB,CAAAA,CAAgB,KAAA,CAAM,IAAA,CAAK5B,CAAAA,CAAM,SAAS,CAAA,CAChD,IAAA,IAAWY,KAAOgB,CAAAA,CAAe,CAG/B,GAFIhB,CAAAA,CAAI,QAAU,QAAA,EACdA,CAAAA,CAAI,OAAA,GAAY,EAAA,EAChB,CAACZ,CAAAA,CAAM,SAAA,CAAU,QAAA,CAASY,CAAG,CAAA,CAAG,SACpC,IAAMS,CAAAA,CAASrB,EAAM,UAAA,CAAW,GAAA,CAAIY,CAAAA,CAAI,OAAO,CAAA,CAC/C,GAAI,CAACS,CAAAA,CAAQ,SACIE,CAAAA,CACfG,CAAAA,CACA,CAAA,CACAP,CAAAA,CACAE,CAAAA,CAAO,QAAA,CACPA,CAAAA,CAAO,OAAA,CACPA,EAAO,QAAA,CACPA,CAAAA,CAAO,UACT,CAAA,EACcT,EAAI,OAAA,CAAQF,CAAG,EAC/B,CACF,CACF,CAAC,CAAA","file":"observers.js","sourcesContent":["// aiecsjs/observers — observe component add/remove/set events.\n\nimport { forEachSetBit, matchesEntityMask } from './internal/bitmask.js'\nimport { getComponentInfo, registerObserverDispatch } from './internal/component.js'\nimport { registerObserversAPI } from './internal/entity.js'\nimport { runQuery } from './internal/query.js'\nimport type {\n ComponentLike,\n EntityId,\n ObserverEntry,\n ObserverEvent,\n Query,\n QueryInternal,\n World,\n WorldState,\n} from './internal/types.js'\nimport { getOrRegisterComponentBit, getWorldState } from './internal/world.js'\n\n/**\n * Options accepted by every observer registration. `signal` aborts the\n * subscription when fired; the returned unsubscribe is still safe to call.\n */\nexport interface ObserverOptions {\n signal?: AbortSignal\n}\n\nfunction bindAbortSignal(unsubscribe: () => void, signal: AbortSignal | undefined): () => void {\n if (!signal) return unsubscribe\n if (signal.aborted) {\n unsubscribe()\n return () => {}\n }\n const onAbort = (): void => {\n unsubscribe()\n }\n signal.addEventListener('abort', onAbort, { once: true })\n let detached = false\n return () => {\n if (detached) return\n detached = true\n signal.removeEventListener('abort', onAbort)\n unsubscribe()\n }\n}\n\nexport function onAdd(\n world: World,\n component: ComponentLike,\n handler: (eid: EntityId) => void,\n opts?: ObserverOptions,\n): () => void {\n return bindAbortSignal(registerComponentObserver(world, component, 'add', handler), opts?.signal)\n}\n\nexport function onRemove(\n world: World,\n component: ComponentLike,\n handler: (eid: EntityId) => void,\n opts?: ObserverOptions,\n): () => void {\n return bindAbortSignal(\n registerComponentObserver(world, component, 'remove', handler),\n opts?.signal,\n )\n}\n\n/**\n * Low-level mutation hook. Fires after `setComponent(world, eid, comp, value)`\n * when the component is already present on the entity.\n *\n * Does NOT fire for:\n * - `addComponent` (use `onAdd` for that path; `addComponent + setComponent`\n * fires `onAdd` then `onSet`)\n * - Direct writes to a column view returned by `getComponent`. The column\n * array is the raw `TypedArray` / object — mutations bypass observer\n * dispatch.\n *\n * @example\n * // ❌ Anti-pattern: no `onSet` callback fires.\n * const col = getComponent(world, eid, Position) // raw column object\n * col.x[getEntityIndex(eid)] = 5\n *\n * // ✅ Correct: triggers `onSet`.\n * setComponent(world, eid, Position, { x: 5 })\n *\n * This is NOT a reactive value-predicate query — see `enterQuery` /\n * `exitQuery` for structural change tracking, and validate value predicates\n * in app code if you need them.\n */\nexport function onSet<C extends ComponentLike>(\n world: World,\n component: C,\n handler: (eid: EntityId, value: unknown) => void,\n opts?: ObserverOptions,\n): () => void {\n return bindAbortSignal(registerComponentObserver(world, component, 'set', handler), opts?.signal)\n}\n\nexport function observe(\n world: World,\n query: Query,\n event: ObserverEvent,\n handler: (eid: EntityId) => void,\n opts?: ObserverOptions,\n): () => void {\n const state = getWorldState(world)\n // Force registration of the query into this world so dispatch can find it.\n runQuery(world, query)\n const entry: ObserverEntry = {\n event,\n componentBit: -1,\n queryId: (query as QueryInternal).id,\n handler: handler as (eid: EntityId, value?: unknown) => void,\n }\n state.observers.push(entry)\n const unsubscribe = (): void => {\n const idx = state.observers.indexOf(entry)\n if (idx >= 0) state.observers.splice(idx, 1)\n }\n return bindAbortSignal(unsubscribe, opts?.signal)\n}\n\nfunction registerComponentObserver(\n world: World,\n component: ComponentLike,\n event: ObserverEvent,\n handler: (eid: EntityId, value?: unknown) => void,\n): () => void {\n const state = getWorldState(world)\n const info = getComponentInfo(component)\n let bit = state.componentBitFor.get(info.id)\n if (bit === undefined) {\n bit = getOrRegisterComponentBit(state, info)\n }\n const entry: ObserverEntry = { event, componentBit: bit, queryId: -1, handler }\n state.observers.push(entry)\n return () => {\n const idx = state.observers.indexOf(entry)\n if (idx >= 0) state.observers.splice(idx, 1)\n }\n}\n\n// --- Dispatch impls (wired into component.ts) ---\n//\n// CORRECTNESS: every dispatch loop snapshots `state.observers` via `Array.from`\n// before iterating. A handler may call its returned unsubscribe (which splices\n// `state.observers`), and mutating the backing array while iterating with a\n// for-of would skip the next sibling observer. The snapshot pins the visit\n// list; already-removed entries are filtered via `state.observers.includes`\n// so an in-flight unsubscribe also skips subsequent fires of the same dispatch.\n\nfunction fireAdd(state: WorldState, eid: EntityId, bit: number): void {\n const snapshot = Array.from(state.observers)\n for (const obs of snapshot) {\n if (obs.event !== 'add') continue\n if (obs.componentBit !== bit) continue\n if (!state.observers.includes(obs)) continue\n obs.handler(eid)\n }\n // Query-targeted observers: check if the entity's mask now matches the query\n // (uses the post-mutation mask, which the caller already wrote)\n dispatchQueryObservers(state, eid, 'add', /*prev*/ null, /*current*/ true)\n}\n\nfunction fireRemove(state: WorldState, eid: EntityId, bit: number): void {\n const snapshot = Array.from(state.observers)\n for (const obs of snapshot) {\n if (obs.event !== 'remove') continue\n if (obs.componentBit !== bit) continue\n if (!state.observers.includes(obs)) continue\n obs.handler(eid)\n }\n dispatchQueryObservers(state, eid, 'remove', /*prev*/ true, /*current*/ null)\n}\n\nfunction fireSet(state: WorldState, eid: EntityId, bit: number, value: unknown): void {\n const snapshot = Array.from(state.observers)\n for (const obs of snapshot) {\n if (obs.event !== 'set') continue\n if (!state.observers.includes(obs)) continue\n if (obs.componentBit === bit) obs.handler(eid, value)\n else if (obs.queryId !== -1) {\n const q = state.queries[obs.queryId]\n if (q?.all.includes(state.componentInfoByBit[bit]?.id ?? -1)) {\n obs.handler(eid, value)\n }\n }\n }\n}\n\n// Helper: when a component changes, walk query observers to see if their query\n// match status changed. Reads state.entityMask in place; no per-call allocation.\nfunction dispatchQueryObservers(\n state: WorldState,\n eid: EntityId,\n event: 'add' | 'remove',\n _prev: unknown,\n _current: unknown,\n): void {\n const w = state.options.maskWordCount\n const base = ((eid as number) & state.options.indexMask) * w\n const snapshot = Array.from(state.observers)\n for (const obs of snapshot) {\n if (obs.event !== event) continue\n if (obs.queryId === -1) continue\n if (!state.observers.includes(obs)) continue\n const bundle = state.queryMasks.get(obs.queryId)\n if (!bundle) continue\n const isMatch = matchesEntityMask(\n state.entityMask,\n base,\n w,\n bundle.withMask,\n bundle.anyMask,\n bundle.noneMask,\n bundle.anyHasBits,\n )\n // For 'add' event, fire if newly matched. For 'remove', fire if newly unmatched.\n // We approximate \"newly\" by always firing on event when match status agrees;\n // duplicates are acceptable for 0.1 query observers.\n if (event === 'add' && isMatch) obs.handler(eid)\n if (event === 'remove' && !isMatch) obs.handler(eid)\n }\n}\n\n// Wire the dispatch into component.ts\nregisterObserverDispatch({ fireAdd, fireRemove, fireSet })\n\n// Register destroy hook so onRemove fires for every component on destroy\n// AND so query-targeted observers see the entity exit any matched query.\nregisterObserversAPI({\n dispatchDestroyObservers(state: WorldState, eid: EntityId): void {\n const w = state.options.maskWordCount\n const base = ((eid as number) & state.options.indexMask) * w\n\n // Snapshot the pre-destroy mask so Phase 2's `wasMatch` is computed\n // against the state at destroy entry — Phase 1 handlers might reentrant-\n // mutate `state.entityMask` (e.g. by calling removeComponent on a\n // sibling), and we still want query observers to fire for queries the\n // entity was matching *before* the destroy began.\n const preMask = new Uint32Array(w)\n for (let i = 0; i < w; i++) preMask[i] = state.entityMask[base + i] ?? 0\n\n // Phase 1: component-level remove for every bit set at destroy entry.\n forEachSetBit(preMask, 0, w, (bit) => {\n const snapshot = Array.from(state.observers)\n for (const obs of snapshot) {\n if (obs.event !== 'remove') continue\n if (obs.componentBit !== bit) continue\n if (!state.observers.includes(obs)) continue\n obs.handler(eid)\n }\n })\n\n // Phase 2: query-level remove for any query that was matching this entity.\n // Read the pre-destroy mask snapshot (not live state.entityMask) so this\n // phase is decoupled from Phase 1 reentrant mutations.\n const querySnapshot = Array.from(state.observers)\n for (const obs of querySnapshot) {\n if (obs.event !== 'remove') continue\n if (obs.queryId === -1) continue\n if (!state.observers.includes(obs)) continue\n const bundle = state.queryMasks.get(obs.queryId)\n if (!bundle) continue\n const wasMatch = matchesEntityMask(\n preMask,\n 0,\n w,\n bundle.withMask,\n bundle.anyMask,\n bundle.noneMask,\n bundle.anyHasBits,\n )\n if (wasMatch) obs.handler(eid)\n }\n },\n})\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/observers.ts"],"names":["bindAbortSignal","unsubscribe","signal","onAbort","detached","onAdd","world","component","handler","opts","registerComponentObserver","onRemove","onSet","observe","query","event","state","getWorldState","runQuery","entry","idx","info","getComponentInfo","bit","getOrRegisterComponentBit","fireAdd","eid","snapshot","obs","dispatchQueryObservers","fireRemove","fireSet","value","_prev","_current","w","base","bundle","isMatch","matchesEntityMask","registerObserverDispatch","registerObserversAPI","preMask","forEachSetBit","querySnapshot"],"mappings":"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","file":"observers.js","sourcesContent":["// aiecsjs/observers — observe component add/remove/set events.\n\nimport { forEachSetBit, matchesEntityMask } from './internal/bitmask.js'\nimport { getComponentInfo, registerObserverDispatch } from './internal/component.js'\nimport { registerObserversAPI } from './internal/entity.js'\nimport { runQuery } from './internal/query.js'\nimport type {\n ComponentLike,\n EntityId,\n ObserverEntry,\n ObserverEvent,\n Query,\n QueryInternal,\n World,\n WorldState,\n} from './internal/types.js'\nimport { getOrRegisterComponentBit, getWorldState } from './internal/world.js'\n\n/**\n * Options accepted by every observer registration. `signal` aborts the\n * subscription when fired; the returned unsubscribe is still safe to call.\n */\nexport interface ObserverOptions {\n signal?: AbortSignal\n}\n\nfunction bindAbortSignal(unsubscribe: () => void, signal: AbortSignal | undefined): () => void {\n if (!signal) return unsubscribe\n if (signal.aborted) {\n unsubscribe()\n return () => {}\n }\n const onAbort = (): void => {\n unsubscribe()\n }\n signal.addEventListener('abort', onAbort, { once: true })\n let detached = false\n return () => {\n if (detached) return\n detached = true\n signal.removeEventListener('abort', onAbort)\n unsubscribe()\n }\n}\n\nexport function onAdd(\n world: World,\n component: ComponentLike,\n handler: (eid: EntityId) => void,\n opts?: ObserverOptions,\n): () => void {\n return bindAbortSignal(registerComponentObserver(world, component, 'add', handler), opts?.signal)\n}\n\nexport function onRemove(\n world: World,\n component: ComponentLike,\n handler: (eid: EntityId) => void,\n opts?: ObserverOptions,\n): () => void {\n return bindAbortSignal(\n registerComponentObserver(world, component, 'remove', handler),\n opts?.signal,\n )\n}\n\n/**\n * Low-level mutation hook. Fires after `setComponent(world, eid, comp, value)`\n * when the component is already present on the entity.\n *\n * Does NOT fire for:\n * - `addComponent` (use `onAdd` for that path; `addComponent + setComponent`\n * fires `onAdd` then `onSet`)\n * - Direct writes to a column view returned by `getComponent`. The column\n * array is the raw `TypedArray` / object — mutations bypass observer\n * dispatch.\n *\n * @example\n * // ❌ Anti-pattern: no `onSet` callback fires.\n * const col = getComponent(world, eid, Position) // raw column object\n * col.x[getEntityIndex(eid)] = 5\n *\n * // ✅ Correct: triggers `onSet`.\n * setComponent(world, eid, Position, { x: 5 })\n *\n * This is NOT a reactive value-predicate query — see `enterQuery` /\n * `exitQuery` for structural change tracking, and validate value predicates\n * in app code if you need them.\n */\nexport function onSet<C extends ComponentLike>(\n world: World,\n component: C,\n handler: (eid: EntityId, value: unknown) => void,\n opts?: ObserverOptions,\n): () => void {\n return bindAbortSignal(registerComponentObserver(world, component, 'set', handler), opts?.signal)\n}\n\nexport function observe(\n world: World,\n query: Query,\n event: ObserverEvent,\n handler: (eid: EntityId) => void,\n opts?: ObserverOptions,\n): () => void {\n const state = getWorldState(world)\n // Force registration of the query into this world so dispatch can find it.\n runQuery(world, query)\n const entry: ObserverEntry = {\n event,\n componentBit: -1,\n queryId: (query as QueryInternal).id,\n handler: handler as (eid: EntityId, value?: unknown) => void,\n }\n state.observers.push(entry)\n const unsubscribe = (): void => {\n const idx = state.observers.indexOf(entry)\n if (idx >= 0) state.observers.splice(idx, 1)\n }\n return bindAbortSignal(unsubscribe, opts?.signal)\n}\n\nfunction registerComponentObserver(\n world: World,\n component: ComponentLike,\n event: ObserverEvent,\n handler: (eid: EntityId, value?: unknown) => void,\n): () => void {\n const state = getWorldState(world)\n const info = getComponentInfo(component)\n let bit = state.componentBitFor.get(info.id)\n if (bit === undefined) {\n bit = getOrRegisterComponentBit(state, info)\n }\n const entry: ObserverEntry = { event, componentBit: bit, queryId: -1, handler }\n state.observers.push(entry)\n return () => {\n const idx = state.observers.indexOf(entry)\n if (idx >= 0) state.observers.splice(idx, 1)\n }\n}\n\n// --- Dispatch impls (wired into component.ts) ---\n//\n// CORRECTNESS: every dispatch loop snapshots `state.observers` via `Array.from`\n// before iterating. A handler may call its returned unsubscribe (which splices\n// `state.observers`), and mutating the backing array while iterating with a\n// for-of would skip the next sibling observer. The snapshot pins the visit\n// list; already-removed entries are filtered via `state.observers.includes`\n// so an in-flight unsubscribe also skips subsequent fires of the same dispatch.\n\nfunction fireAdd(state: WorldState, eid: EntityId, bit: number): void {\n const snapshot = Array.from(state.observers)\n for (const obs of snapshot) {\n if (obs.event !== 'add') continue\n if (obs.componentBit !== bit) continue\n if (!state.observers.includes(obs)) continue\n obs.handler(eid)\n }\n // Query-targeted observers: check if the entity's mask now matches the query\n // (uses the post-mutation mask, which the caller already wrote)\n dispatchQueryObservers(state, eid, 'add', /*prev*/ null, /*current*/ true)\n}\n\nfunction fireRemove(state: WorldState, eid: EntityId, bit: number): void {\n const snapshot = Array.from(state.observers)\n for (const obs of snapshot) {\n if (obs.event !== 'remove') continue\n if (obs.componentBit !== bit) continue\n if (!state.observers.includes(obs)) continue\n obs.handler(eid)\n }\n dispatchQueryObservers(state, eid, 'remove', /*prev*/ true, /*current*/ null)\n}\n\nfunction fireSet(state: WorldState, eid: EntityId, bit: number, value: unknown): void {\n const snapshot = Array.from(state.observers)\n for (const obs of snapshot) {\n if (obs.event !== 'set') continue\n if (!state.observers.includes(obs)) continue\n if (obs.componentBit === bit) obs.handler(eid, value)\n else if (obs.queryId !== -1) {\n const q = state.queries[obs.queryId]\n if (q?.all.includes(state.componentInfoByBit[bit]?.id ?? -1)) {\n obs.handler(eid, value)\n }\n }\n }\n}\n\n// Helper: when a component changes, walk query observers to see if their query\n// match status changed. Reads state.entityMask in place; no per-call allocation.\nfunction dispatchQueryObservers(\n state: WorldState,\n eid: EntityId,\n event: 'add' | 'remove',\n _prev: unknown,\n _current: unknown,\n): void {\n const w = state.options.maskWordCount\n const base = ((eid as number) & state.options.indexMask) * w\n const snapshot = Array.from(state.observers)\n for (const obs of snapshot) {\n if (obs.event !== event) continue\n if (obs.queryId === -1) continue\n if (!state.observers.includes(obs)) continue\n const bundle = state.queryMasks.get(obs.queryId)\n if (!bundle) continue\n const isMatch = matchesEntityMask(\n state.entityMask,\n base,\n w,\n bundle.withMask,\n bundle.anyMask,\n bundle.noneMask,\n bundle.anyHasBits,\n )\n // For 'add' event, fire if newly matched. For 'remove', fire if newly unmatched.\n // We approximate \"newly\" by always firing on event when match status agrees;\n // duplicates are acceptable for 0.1 query observers.\n if (event === 'add' && isMatch) obs.handler(eid)\n if (event === 'remove' && !isMatch) obs.handler(eid)\n }\n}\n\n// Wire the dispatch into component.ts\nregisterObserverDispatch({ fireAdd, fireRemove, fireSet })\n\n// Register destroy hook so onRemove fires for every component on destroy\n// AND so query-targeted observers see the entity exit any matched query.\nregisterObserversAPI({\n dispatchDestroyObservers(state: WorldState, eid: EntityId): void {\n const w = state.options.maskWordCount\n const base = ((eid as number) & state.options.indexMask) * w\n\n // Snapshot the pre-destroy mask so Phase 2's `wasMatch` is computed\n // against the state at destroy entry — Phase 1 handlers might reentrant-\n // mutate `state.entityMask` (e.g. by calling removeComponent on a\n // sibling), and we still want query observers to fire for queries the\n // entity was matching *before* the destroy began.\n const preMask = new Uint32Array(w)\n for (let i = 0; i < w; i++) preMask[i] = state.entityMask[base + i] ?? 0\n\n // Phase 1: component-level remove for every bit set at destroy entry.\n forEachSetBit(preMask, 0, w, (bit) => {\n const snapshot = Array.from(state.observers)\n for (const obs of snapshot) {\n if (obs.event !== 'remove') continue\n if (obs.componentBit !== bit) continue\n if (!state.observers.includes(obs)) continue\n obs.handler(eid)\n }\n })\n\n // Phase 2: query-level remove for any query that was matching this entity.\n // Read the pre-destroy mask snapshot (not live state.entityMask) so this\n // phase is decoupled from Phase 1 reentrant mutations.\n const querySnapshot = Array.from(state.observers)\n for (const obs of querySnapshot) {\n if (obs.event !== 'remove') continue\n if (obs.queryId === -1) continue\n if (!state.observers.includes(obs)) continue\n const bundle = state.queryMasks.get(obs.queryId)\n if (!bundle) continue\n const wasMatch = matchesEntityMask(\n preMask,\n 0,\n w,\n bundle.withMask,\n bundle.anyMask,\n bundle.noneMask,\n bundle.anyHasBits,\n )\n if (wasMatch) obs.handler(eid)\n }\n },\n})\n"]}
|