aiecsjs 0.5.8 → 0.6.0
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 +46 -12
- package/README_ZHTW.md +46 -12
- package/api.json +171 -48
- package/dist/chunk-27JX7WHY.js +2 -0
- package/dist/chunk-27JX7WHY.js.map +1 -0
- package/dist/chunk-GJ6U2TIU.js +2 -0
- package/dist/chunk-GJ6U2TIU.js.map +1 -0
- package/dist/chunk-LNZZ4WWC.js +2 -0
- package/dist/chunk-LNZZ4WWC.js.map +1 -0
- package/dist/chunk-MVBFYRHY.cjs +2 -0
- package/dist/chunk-MVBFYRHY.cjs.map +1 -0
- package/dist/chunk-NPNWRCB5.cjs +2 -0
- package/dist/chunk-NPNWRCB5.cjs.map +1 -0
- package/dist/chunk-O4UVAWFU.cjs +2 -0
- package/dist/chunk-O4UVAWFU.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 +79 -13
- package/dist/index.d.ts +79 -13
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/loop.cjs +1 -1
- package/dist/loop.cjs.map +1 -1
- package/dist/loop.d.cts +12 -0
- package/dist/loop.d.ts +12 -0
- package/dist/loop.js +1 -1
- package/dist/loop.js.map +1 -1
- package/dist/observers.cjs +1 -1
- package/dist/observers.cjs.map +1 -1
- package/dist/observers.d.cts +7 -1
- package/dist/observers.d.ts +7 -1
- package/dist/observers.js +1 -1
- package/dist/observers.js.map +1 -1
- package/dist/relations.cjs +1 -1
- package/dist/relations.cjs.map +1 -1
- package/dist/relations.d.cts +18 -9
- package/dist/relations.d.ts +18 -9
- package/dist/relations.js +1 -1
- package/dist/relations.js.map +1 -1
- package/dist/serialize.cjs +1 -1
- package/dist/serialize.d.cts +30 -5
- package/dist/serialize.d.ts +30 -5
- package/dist/serialize.js +1 -1
- package/dist/{types-BeVLA7xG.d.cts → types-C6TkQ7HW.d.cts} +33 -0
- package/dist/{types-BeVLA7xG.d.ts → types-C6TkQ7HW.d.ts} +33 -0
- package/dist/worker.cjs +1 -1
- package/dist/worker.cjs.map +1 -1
- package/dist/worker.d.cts +10 -3
- package/dist/worker.d.ts +10 -3
- package/dist/worker.js +1 -1
- package/dist/worker.js.map +1 -1
- package/llms-full.txt +120 -18
- package/llms.txt +1 -0
- package/package.json +57 -22
- package/dist/chunk-3FV6UMUS.cjs +0 -2
- package/dist/chunk-3FV6UMUS.cjs.map +0 -1
- package/dist/chunk-3ZLNR4JX.cjs +0 -2
- package/dist/chunk-3ZLNR4JX.cjs.map +0 -1
- package/dist/chunk-N5OETPTK.js +0 -2
- package/dist/chunk-N5OETPTK.js.map +0 -1
- package/dist/chunk-TNZMD7E5.js +0 -2
- package/dist/chunk-TNZMD7E5.js.map +0 -1
- package/dist/chunk-YFTCD2UG.js +0 -2
- package/dist/chunk-YFTCD2UG.js.map +0 -1
- package/dist/chunk-ZIZSWIHW.cjs +0 -2
- package/dist/chunk-ZIZSWIHW.cjs.map +0 -1
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 /**\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<unknown>\n exclusive: Int32Array | null // [srcEid] → tgtEid; -1 means none\n // Reverse index for exclusive relations: tgtEid → set of srcEids currently\n // pointing at it. Lets destroy cleanup clear incoming edges in O(incoming)\n // instead of scanning the whole `exclusive` capacity. null for non-exclusive\n // storage. Kept in lockstep with every write to `exclusive`.\n incoming: Map<number, Set<number>> | null\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"]}
|
|
1
|
+
{"version":3,"sources":["../src/internal/types.ts","../src/internal/pipe.ts","../src/ref.ts","../src/index.ts"],"names":["Types","pipe","systems","s","EcsError","w","world","ctx","EntityNotAliveError","eid","refOf","entity","state","getWorldState","isAliveInternal","deref","ref","idx","unpackIdx","archId","arch","storedGen","refGen","unpackGen","aliveRef","IS_SAB_SUPPORTED"],"mappings":"qHAKO,IAAMA,CAAAA,CAAQ,CACnB,EAAA,CAAI,IAAA,CACJ,GAAI,IAAA,CACJ,GAAA,CAAK,KAAA,CACL,GAAA,CAAK,KAAA,CACL,GAAA,CAAK,MACL,GAAA,CAAK,KAAA,CACL,GAAA,CAAK,KAAA,CACL,GAAA,CAAK,KAAA,CACL,IAAK,KAAA,CACL,IAAA,CAAM,MACR,ECRO,SAASC,CAAAA,CAAAA,GACXC,EACa,CAChB,IAAA,IAAWC,CAAAA,IAAKD,CAAAA,CACd,GAAI,OAAOC,GAAM,UAAA,CAAY,MAAM,IAAIC,mBAAAA,CAAS,0CAA0C,CAAA,CAE5F,OAAIF,CAAAA,CAAQ,MAAA,GAAW,CAAA,CAAWG,CAAAA,EAASA,CAAAA,CACvCH,CAAAA,CAAQ,SAAW,CAAA,CAAUA,CAAAA,CAAQ,CAAC,CAAA,CACnC,CAACI,CAAAA,CAAUC,IAAa,CAC7B,IAAIF,CAAAA,CAAIC,CAAAA,CACR,IAAA,IAAWH,CAAAA,IAAKD,EAASG,CAAAA,CAAIF,CAAAA,CAAEE,CAAAA,CAAGE,CAAG,CAAA,CACrC,OAAOF,CACT,CACF,CCwBO,IAAMG,CAAAA,CAAN,cAAkC,KAAM,CACpC,GAAA,CACT,WAAA,CAAYC,CAAAA,CAAa,CACvB,KAAA,CAAM,CAAA,gBAAA,EAAmBA,CAAG,CAAA,aAAA,CAAe,CAAA,CAC3C,IAAA,CAAK,IAAA,CAAO,qBAAA,CACZ,IAAA,CAAK,IAAMA,EACb,CACF,EAaO,SAASC,CAAAA,CAAmBJ,CAAAA,CAAcK,EAAgC,CAC/E,IAAMC,CAAAA,CAAQC,mBAAAA,CAAcP,CAAK,CAAA,CACjC,GAAI,CAACQ,mBAAAA,CAAgBF,CAAAA,CAAOD,CAAgB,CAAA,CAC1C,MAAM,IAAIH,CAAAA,CAAoBG,CAAgB,CAAA,CAEhD,OAAO,MAAA,CAAO,MAAA,CAAO,CAAE,EAAA,CAAIA,CAAAA,CAAQ,OAAA,CAASC,CAAAA,CAAM,EAAG,CAAC,CACxD,CAgBO,SAASG,CAAAA,CAAmBT,CAAAA,CAAcU,CAAAA,CAAoC,CACnF,IAAMJ,CAAAA,CAAQC,mBAAAA,CAAcP,CAAK,CAAA,CAGjC,GAAIU,CAAAA,CAAI,UAAYJ,CAAAA,CAAM,EAAA,CAAI,OAAO,IAAA,CAErC,IAAMH,CAAAA,CAAMO,EAAI,EAAA,CACVC,CAAAA,CAAMC,mBAAAA,CAAUT,CAAAA,CAAKG,CAAAA,CAAM,OAAO,EAIxC,GADIK,CAAAA,EAAO,CAAA,EACPA,CAAAA,EAAOL,CAAAA,CAAM,QAAA,CAAU,OAAO,IAAA,CAGlC,IAAMO,CAAAA,CAASP,CAAAA,CAAM,eAAA,CAAgBK,CAAG,GAAK,CAAA,CACvCG,CAAAA,CAAOR,CAAAA,CAAM,UAAA,CAAWO,CAAM,CAAA,CAEpC,GADI,CAACC,CAAAA,EACD,CAACA,CAAAA,CAAK,SAAA,CAAU,GAAA,CAAIJ,EAAI,EAAE,CAAA,CAAG,OAAO,IAAA,CAGxC,IAAMK,CAAAA,CAAYT,EAAM,WAAA,CAAYK,CAAG,CAAA,EAAK,CAAA,CACtCK,CAAAA,CAASC,mBAAAA,CAAUd,EAAKG,CAAAA,CAAM,OAAO,CAAA,CAC3C,OAAIS,CAAAA,GAAcC,CAAAA,CAAe,KAE1BN,CAAAA,CAAI,EACb,CAQO,SAASQ,CAAAA,CAAsBlB,CAAAA,CAAcU,EAA4B,CAC9E,OAAOD,CAAAA,CAAMT,CAAAA,CAAOU,CAAG,CAAA,GAAM,IAC/B,CC3BO,IAAMS,EAAAA,CAA4B,OAAO,iBAAA,CAAsB","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}\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 // Stable serialization key from `defineComponent(..., { key })`; null when keyless.\n key: string | null\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}\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 /**\n * `'throw'` (default) rejects a snapshot whose format version is not 2 —\n * including every 0.5.x snapshot — with `EcsError`. `'best-effort'` loads it\n * anyway; a snapshot without a format 2 component table is then resolved by\n * creation-order id with a `kind` check only (0.5.x behaviour).\n */\n onUnknownVersion?: 'throw' | 'best-effort'\n /**\n * `'throw'` (default) rejects a snapshot that references a component this\n * process has not defined with `EcsError`; `'skip'` drops that component's\n * data and loads the rest.\n */\n onUnknownComponent?: 'throw' | 'skip'\n}\n\nexport interface WorldSnapshot {\n /** Snapshot format; 0.6.0 writes 2. 0.5.x snapshots have no `formatVersion`. */\n formatVersion: 2\n version: string\n capacity: number\n // Source world layout / limits. Absent in snapshots from older versions, in\n // which case the restored world uses the createWorld defaults.\n maxEntities?: number\n indexBits?: number\n generationBits?: number\n /**\n * One entry per component referenced by `entities`: the source process's\n * component `id`, its stable `key` (null when keyless), its `kind`, and for\n * SoA its fields in declaration order. A loader resolves each entry by `key`\n * (by `id` when keyless) and rejects a kind or field mismatch.\n */\n components: Array<{\n id: number\n key: string | null\n kind: 'soa' | 'aos' | 'tag'\n fields: Array<{ name: string; type: SoAFieldType; vectorLen: number }> | null\n }>\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<unknown>\n exclusive: Int32Array | null // [srcEid] → tgtEid; -1 means none\n // Reverse index for exclusive relations: tgtEid → set of srcEids currently\n // pointing at it. Lets destroy cleanup clear incoming edges in O(incoming)\n // instead of scanning the whole `exclusive` capacity. null for non-exclusive\n // storage. Kept in lockstep with every write to `exclusive`.\n incoming: Map<number, Set<number>> | null\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 destroying: Set<number> // packed eids mid-destroyEntity (reentrancy guard)\n removing: Set<number> // idx * maxComponents + bit mid-removeComponent dispatch\n visitStamp: Uint32Array // [idx] → last forEachEntity pass that visited it (lazy)\n visitEpoch: number // current forEachEntity pass stamp\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 { EcsError } from './errors.js'\nimport type { System, World } from './types.js'\n\n/**\n * Compose systems left to right: the returned system threads the world through\n * each one with the same `ctx`. Every system must be a function (`EcsError`\n * otherwise, checked here rather than on the first tick).\n */\nexport function pipe<W extends World = World, Ctx = unknown>(\n ...systems: System<W, Ctx>[]\n): System<W, Ctx> {\n for (const s of systems) {\n if (typeof s !== 'function') throw new EcsError('aiecsjs: every system must be a function')\n }\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 for a live (registered, non-disposed)\n * world — they return null/false. Both still throw `EcsError` if `world`\n * itself has already been disposed (`disposeWorld`), same as any other call\n * that resolves world state.\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 for a live world. Throws `EcsError` if `world` has already\n * been disposed via `disposeWorld` — call this before disposal, or catch it\n * during teardown, if you need this check to run past a world's lifetime.\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 for a live world; see `deref` for the disposed-world case.\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.\n\nexport { VERSION } from './version.js'\n\nimport { registerObserverDispatch } from './internal/component.js'\nimport { registerObserversAPI, registerRelationsCleanup } from './internal/entity.js'\nimport type { EntityId, WorldState } from './internal/types.js'\n\n// Component lookup and mask-change → reactive-query wiring live in\n// internal/query.ts, so they survive a bundler dropping this entry module\n// (`sideEffects: false`) when a consumer imports only re-exported names.\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,10 +1,23 @@
|
|
|
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-C6TkQ7HW.cjs';
|
|
2
|
+
export { d as ComponentView, g as SoAColumns, i as SoAFieldDecl, j as SoAFieldType, n as Types } from './types-C6TkQ7HW.cjs';
|
|
3
3
|
|
|
4
|
-
declare const VERSION = "0.
|
|
4
|
+
declare const VERSION = "0.6.0";
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Create an ECS world. `initialCapacity`, `maxEntities`, `indexBits` and
|
|
8
|
+
* `generationBits` must be integers when given (`EcsError` otherwise); they are
|
|
9
|
+
* then range-checked (`indexBits` 1–24, `generationBits` 0–16, sum <= 32) and
|
|
10
|
+
* clamped (`initialCapacity` to [1, 2^indexBits], `maxEntities` to
|
|
11
|
+
* [initialCapacity, 2^indexBits]).
|
|
12
|
+
*/
|
|
6
13
|
declare function createWorld(options?: WorldOptions): World;
|
|
7
14
|
declare function destroyWorld(world: World): void;
|
|
15
|
+
/**
|
|
16
|
+
* Wipe every entity, its component data and relation edges while keeping the
|
|
17
|
+
* world's capacity and registered components (e.g. for hot module reload).
|
|
18
|
+
* Throws `EcsError` on a read-only (worker-attached) world, like every other
|
|
19
|
+
* mutator.
|
|
20
|
+
*/
|
|
8
21
|
declare function resetWorld(world: World): void;
|
|
9
22
|
declare function getWorldSize(world: World): number;
|
|
10
23
|
declare function getWorldCapacity(world: World): number;
|
|
@@ -37,9 +50,30 @@ declare function getEntityGeneration(eid: EntityId): number;
|
|
|
37
50
|
declare function packEntity(index: number, generation: number): EntityId;
|
|
38
51
|
declare function isEntity(world: World, x: unknown): x is EntityId;
|
|
39
52
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Declare a Structure-of-Arrays component.
|
|
55
|
+
*
|
|
56
|
+
* `options.key` gives the component a stable identity for snapshots: loaders
|
|
57
|
+
* match snapshot data to components by key (by creation-order id when keyless),
|
|
58
|
+
* so a keyed component survives a different definition order in the loading
|
|
59
|
+
* process. The key must be a non-empty string. Defining the same key again with
|
|
60
|
+
* the same kind and field layout (a module re-run by HMR or an editor reload)
|
|
61
|
+
* returns the existing component; a different layout throws `EcsError`.
|
|
62
|
+
*/
|
|
63
|
+
declare function defineComponent<S extends SoASchema>(schema: S, options?: {
|
|
64
|
+
key?: string;
|
|
65
|
+
}): SoAComponent<S>;
|
|
66
|
+
/** Declare a zero-byte tag component. `options.key`: see {@link defineComponent}. */
|
|
67
|
+
declare function defineTag(options?: {
|
|
68
|
+
key?: string;
|
|
69
|
+
}): TagComponent;
|
|
70
|
+
/**
|
|
71
|
+
* Declare an Array-of-Structures component (one JS object per entity).
|
|
72
|
+
* `options.key`: see {@link defineComponent}.
|
|
73
|
+
*/
|
|
74
|
+
declare function defineObjectComponent<T>(factory?: () => T, options?: {
|
|
75
|
+
key?: string;
|
|
76
|
+
}): AoSComponent<T>;
|
|
43
77
|
declare function addComponent<C extends ComponentLike>(world: World, eid: EntityId, component: C, initial?: ComponentInit<C>): void;
|
|
44
78
|
declare function removeComponent<C extends ComponentLike>(world: World, eid: EntityId, component: C): void;
|
|
45
79
|
declare function hasComponent<C extends ComponentLike>(world: World, eid: EntityId, component: C): boolean;
|
|
@@ -107,12 +141,37 @@ declare function forEachEntity(world: World, query: Query, fn: (eid: EntityId, .
|
|
|
107
141
|
*/
|
|
108
142
|
declare function forEachEntityIndexed(world: World, query: Query, fn: (e: EntityId, i: number, ...cols: any[]) => void): void;
|
|
109
143
|
|
|
144
|
+
/**
|
|
145
|
+
* Compose systems left to right: the returned system threads the world through
|
|
146
|
+
* each one with the same `ctx`. Every system must be a function (`EcsError`
|
|
147
|
+
* otherwise, checked here rather than on the first tick).
|
|
148
|
+
*/
|
|
110
149
|
declare function pipe<W extends World = World, Ctx = unknown>(...systems: System<W, Ctx>[]): System<W, Ctx>;
|
|
111
150
|
|
|
112
151
|
/**
|
|
113
|
-
* Thrown by
|
|
114
|
-
*
|
|
115
|
-
*
|
|
152
|
+
* Thrown by aiecsjs for invariant violations and misuse, with an
|
|
153
|
+
* `aiecsjs: `-prefixed message:
|
|
154
|
+
*
|
|
155
|
+
* - world: bad or non-integer world options, a destroyed/unknown/missing world,
|
|
156
|
+
* exhausted component slots, capacity overflow, `maxEntities` reached;
|
|
157
|
+
* - read-only (worker-attached) worlds: every mutator — `createEntity`,
|
|
158
|
+
* `destroyEntity`, `addComponent`/`removeComponent`/`setComponent`,
|
|
159
|
+
* `resetWorld`, `addRelation`/`removeRelation`, delta `apply()`;
|
|
160
|
+
* - entities/components: `addComponent`/`setComponent` on a dead entity, an
|
|
161
|
+
* unregistered component (or query component id), an invalid or duplicate
|
|
162
|
+
* component `key`, a non-function or nullish-returning AoS factory;
|
|
163
|
+
* - relations: `addRelation` on a dead source or target, a value that is not a
|
|
164
|
+
* relation handle;
|
|
165
|
+
* - callbacks: a non-function observer handler, `forEachEntity` /
|
|
166
|
+
* `forEachEntityIndexed` / `withCommandBuffer` callback or `pipe` system;
|
|
167
|
+
* - command buffers: an unknown buffer or an unresolved placeholder;
|
|
168
|
+
* - snapshots (`aiecsjs/serialize`, `aiecsjs/worker`): malformed binary
|
|
169
|
+
* headers or bodies, invalid worker meta, an unsupported format version, an
|
|
170
|
+
* unknown component or a component kind/field mismatch.
|
|
171
|
+
*
|
|
172
|
+
* `defineComponent` field-declaration errors and non-Query input to the query
|
|
173
|
+
* functions throw `TypeError` instead; `aiecsjs/loop` throws `TypeError` /
|
|
174
|
+
* `RangeError` (it exports no error class).
|
|
116
175
|
*
|
|
117
176
|
* Catch this to distinguish an aiecsjs-originated failure from an unrelated
|
|
118
177
|
* runtime error, instead of string-matching on the message. Mirrors the
|
|
@@ -128,7 +187,9 @@ declare function pipe<W extends World = World, Ctx = unknown>(...systems: System
|
|
|
128
187
|
* }
|
|
129
188
|
*/
|
|
130
189
|
declare class EcsError extends Error {
|
|
131
|
-
constructor(message: string
|
|
190
|
+
constructor(message: string, options?: {
|
|
191
|
+
cause?: unknown;
|
|
192
|
+
});
|
|
132
193
|
}
|
|
133
194
|
|
|
134
195
|
/**
|
|
@@ -156,7 +217,10 @@ interface EntityRef<T = unknown> {
|
|
|
156
217
|
}
|
|
157
218
|
/**
|
|
158
219
|
* Thrown by `refOf` when the entity is not alive.
|
|
159
|
-
* `deref` and `aliveRef` never throw
|
|
220
|
+
* `deref` and `aliveRef` never throw for a live (registered, non-disposed)
|
|
221
|
+
* world — they return null/false. Both still throw `EcsError` if `world`
|
|
222
|
+
* itself has already been disposed (`disposeWorld`), same as any other call
|
|
223
|
+
* that resolves world state.
|
|
160
224
|
*/
|
|
161
225
|
declare class EntityNotAliveError extends Error {
|
|
162
226
|
readonly eid: number;
|
|
@@ -184,14 +248,16 @@ declare function refOf<T = unknown>(world: World, entity: EntityId): EntityRef<T
|
|
|
184
248
|
* 4. entity is not in any archetype's entityRow (slot released)
|
|
185
249
|
* 5. generation mismatch → ABA: old ref points at recycled slot
|
|
186
250
|
*
|
|
187
|
-
* Never throws.
|
|
251
|
+
* Never throws for a live world. Throws `EcsError` if `world` has already
|
|
252
|
+
* been disposed via `disposeWorld` — call this before disposal, or catch it
|
|
253
|
+
* during teardown, if you need this check to run past a world's lifetime.
|
|
188
254
|
*/
|
|
189
255
|
declare function deref<T = unknown>(world: World, ref: EntityRef<T>): EntityId | null;
|
|
190
256
|
/**
|
|
191
257
|
* Boolean guard form of `deref`. Equivalent to `deref(world, ref) !== null`.
|
|
192
258
|
* Use in guard clauses when you do not need the resolved EntityId.
|
|
193
259
|
*
|
|
194
|
-
* Never throws.
|
|
260
|
+
* Never throws for a live world; see `deref` for the disposed-world case.
|
|
195
261
|
*/
|
|
196
262
|
declare function aliveRef<T = unknown>(world: World, ref: EntityRef<T>): boolean;
|
|
197
263
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,23 @@
|
|
|
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-C6TkQ7HW.js';
|
|
2
|
+
export { d as ComponentView, g as SoAColumns, i as SoAFieldDecl, j as SoAFieldType, n as Types } from './types-C6TkQ7HW.js';
|
|
3
3
|
|
|
4
|
-
declare const VERSION = "0.
|
|
4
|
+
declare const VERSION = "0.6.0";
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Create an ECS world. `initialCapacity`, `maxEntities`, `indexBits` and
|
|
8
|
+
* `generationBits` must be integers when given (`EcsError` otherwise); they are
|
|
9
|
+
* then range-checked (`indexBits` 1–24, `generationBits` 0–16, sum <= 32) and
|
|
10
|
+
* clamped (`initialCapacity` to [1, 2^indexBits], `maxEntities` to
|
|
11
|
+
* [initialCapacity, 2^indexBits]).
|
|
12
|
+
*/
|
|
6
13
|
declare function createWorld(options?: WorldOptions): World;
|
|
7
14
|
declare function destroyWorld(world: World): void;
|
|
15
|
+
/**
|
|
16
|
+
* Wipe every entity, its component data and relation edges while keeping the
|
|
17
|
+
* world's capacity and registered components (e.g. for hot module reload).
|
|
18
|
+
* Throws `EcsError` on a read-only (worker-attached) world, like every other
|
|
19
|
+
* mutator.
|
|
20
|
+
*/
|
|
8
21
|
declare function resetWorld(world: World): void;
|
|
9
22
|
declare function getWorldSize(world: World): number;
|
|
10
23
|
declare function getWorldCapacity(world: World): number;
|
|
@@ -37,9 +50,30 @@ declare function getEntityGeneration(eid: EntityId): number;
|
|
|
37
50
|
declare function packEntity(index: number, generation: number): EntityId;
|
|
38
51
|
declare function isEntity(world: World, x: unknown): x is EntityId;
|
|
39
52
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Declare a Structure-of-Arrays component.
|
|
55
|
+
*
|
|
56
|
+
* `options.key` gives the component a stable identity for snapshots: loaders
|
|
57
|
+
* match snapshot data to components by key (by creation-order id when keyless),
|
|
58
|
+
* so a keyed component survives a different definition order in the loading
|
|
59
|
+
* process. The key must be a non-empty string. Defining the same key again with
|
|
60
|
+
* the same kind and field layout (a module re-run by HMR or an editor reload)
|
|
61
|
+
* returns the existing component; a different layout throws `EcsError`.
|
|
62
|
+
*/
|
|
63
|
+
declare function defineComponent<S extends SoASchema>(schema: S, options?: {
|
|
64
|
+
key?: string;
|
|
65
|
+
}): SoAComponent<S>;
|
|
66
|
+
/** Declare a zero-byte tag component. `options.key`: see {@link defineComponent}. */
|
|
67
|
+
declare function defineTag(options?: {
|
|
68
|
+
key?: string;
|
|
69
|
+
}): TagComponent;
|
|
70
|
+
/**
|
|
71
|
+
* Declare an Array-of-Structures component (one JS object per entity).
|
|
72
|
+
* `options.key`: see {@link defineComponent}.
|
|
73
|
+
*/
|
|
74
|
+
declare function defineObjectComponent<T>(factory?: () => T, options?: {
|
|
75
|
+
key?: string;
|
|
76
|
+
}): AoSComponent<T>;
|
|
43
77
|
declare function addComponent<C extends ComponentLike>(world: World, eid: EntityId, component: C, initial?: ComponentInit<C>): void;
|
|
44
78
|
declare function removeComponent<C extends ComponentLike>(world: World, eid: EntityId, component: C): void;
|
|
45
79
|
declare function hasComponent<C extends ComponentLike>(world: World, eid: EntityId, component: C): boolean;
|
|
@@ -107,12 +141,37 @@ declare function forEachEntity(world: World, query: Query, fn: (eid: EntityId, .
|
|
|
107
141
|
*/
|
|
108
142
|
declare function forEachEntityIndexed(world: World, query: Query, fn: (e: EntityId, i: number, ...cols: any[]) => void): void;
|
|
109
143
|
|
|
144
|
+
/**
|
|
145
|
+
* Compose systems left to right: the returned system threads the world through
|
|
146
|
+
* each one with the same `ctx`. Every system must be a function (`EcsError`
|
|
147
|
+
* otherwise, checked here rather than on the first tick).
|
|
148
|
+
*/
|
|
110
149
|
declare function pipe<W extends World = World, Ctx = unknown>(...systems: System<W, Ctx>[]): System<W, Ctx>;
|
|
111
150
|
|
|
112
151
|
/**
|
|
113
|
-
* Thrown by
|
|
114
|
-
*
|
|
115
|
-
*
|
|
152
|
+
* Thrown by aiecsjs for invariant violations and misuse, with an
|
|
153
|
+
* `aiecsjs: `-prefixed message:
|
|
154
|
+
*
|
|
155
|
+
* - world: bad or non-integer world options, a destroyed/unknown/missing world,
|
|
156
|
+
* exhausted component slots, capacity overflow, `maxEntities` reached;
|
|
157
|
+
* - read-only (worker-attached) worlds: every mutator — `createEntity`,
|
|
158
|
+
* `destroyEntity`, `addComponent`/`removeComponent`/`setComponent`,
|
|
159
|
+
* `resetWorld`, `addRelation`/`removeRelation`, delta `apply()`;
|
|
160
|
+
* - entities/components: `addComponent`/`setComponent` on a dead entity, an
|
|
161
|
+
* unregistered component (or query component id), an invalid or duplicate
|
|
162
|
+
* component `key`, a non-function or nullish-returning AoS factory;
|
|
163
|
+
* - relations: `addRelation` on a dead source or target, a value that is not a
|
|
164
|
+
* relation handle;
|
|
165
|
+
* - callbacks: a non-function observer handler, `forEachEntity` /
|
|
166
|
+
* `forEachEntityIndexed` / `withCommandBuffer` callback or `pipe` system;
|
|
167
|
+
* - command buffers: an unknown buffer or an unresolved placeholder;
|
|
168
|
+
* - snapshots (`aiecsjs/serialize`, `aiecsjs/worker`): malformed binary
|
|
169
|
+
* headers or bodies, invalid worker meta, an unsupported format version, an
|
|
170
|
+
* unknown component or a component kind/field mismatch.
|
|
171
|
+
*
|
|
172
|
+
* `defineComponent` field-declaration errors and non-Query input to the query
|
|
173
|
+
* functions throw `TypeError` instead; `aiecsjs/loop` throws `TypeError` /
|
|
174
|
+
* `RangeError` (it exports no error class).
|
|
116
175
|
*
|
|
117
176
|
* Catch this to distinguish an aiecsjs-originated failure from an unrelated
|
|
118
177
|
* runtime error, instead of string-matching on the message. Mirrors the
|
|
@@ -128,7 +187,9 @@ declare function pipe<W extends World = World, Ctx = unknown>(...systems: System
|
|
|
128
187
|
* }
|
|
129
188
|
*/
|
|
130
189
|
declare class EcsError extends Error {
|
|
131
|
-
constructor(message: string
|
|
190
|
+
constructor(message: string, options?: {
|
|
191
|
+
cause?: unknown;
|
|
192
|
+
});
|
|
132
193
|
}
|
|
133
194
|
|
|
134
195
|
/**
|
|
@@ -156,7 +217,10 @@ interface EntityRef<T = unknown> {
|
|
|
156
217
|
}
|
|
157
218
|
/**
|
|
158
219
|
* Thrown by `refOf` when the entity is not alive.
|
|
159
|
-
* `deref` and `aliveRef` never throw
|
|
220
|
+
* `deref` and `aliveRef` never throw for a live (registered, non-disposed)
|
|
221
|
+
* world — they return null/false. Both still throw `EcsError` if `world`
|
|
222
|
+
* itself has already been disposed (`disposeWorld`), same as any other call
|
|
223
|
+
* that resolves world state.
|
|
160
224
|
*/
|
|
161
225
|
declare class EntityNotAliveError extends Error {
|
|
162
226
|
readonly eid: number;
|
|
@@ -184,14 +248,16 @@ declare function refOf<T = unknown>(world: World, entity: EntityId): EntityRef<T
|
|
|
184
248
|
* 4. entity is not in any archetype's entityRow (slot released)
|
|
185
249
|
* 5. generation mismatch → ABA: old ref points at recycled slot
|
|
186
250
|
*
|
|
187
|
-
* Never throws.
|
|
251
|
+
* Never throws for a live world. Throws `EcsError` if `world` has already
|
|
252
|
+
* been disposed via `disposeWorld` — call this before disposal, or catch it
|
|
253
|
+
* during teardown, if you need this check to run past a world's lifetime.
|
|
188
254
|
*/
|
|
189
255
|
declare function deref<T = unknown>(world: World, ref: EntityRef<T>): EntityId | null;
|
|
190
256
|
/**
|
|
191
257
|
* Boolean guard form of `deref`. Equivalent to `deref(world, ref) !== null`.
|
|
192
258
|
* Use in guard clauses when you do not need the resolved EntityId.
|
|
193
259
|
*
|
|
194
|
-
* Never throws.
|
|
260
|
+
* Never throws for a live world; see `deref` for the disposed-world case.
|
|
195
261
|
*/
|
|
196
262
|
declare function aliveRef<T = unknown>(world: World, ref: EntityRef<T>): boolean;
|
|
197
263
|
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
export{a as defineQuery,b as enterQuery,c as exitQuery,i as forEachEntity,j as forEachEntityIndexed,h as iterQuery,f as queryArchetypes,g as runQuery}from'./chunk-LNZZ4WWC.js';import {f,i as i$1,z,t,u}from'./chunk-27JX7WHY.js';export{f 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-27JX7WHY.js';var H={i8:"i8",u8:"u8",i16:"i16",u16:"u16",i32:"i32",u32:"u32",f32:"f32",f64:"f64",eid:"eid",bool:"bool"};function N(...n){for(let e of n)if(typeof e!="function")throw new f("aiecsjs: every system must be a function");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 K(n,e){let r=i$1(n);if(!z(r,e))throw new i(e);return Object.freeze({id:e,worldId:r.id})}function l(n,e){let r=i$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 c=r.entityArchetype[o]??0,u$1=r.archetypes[c];if(!u$1||!u$1.entityRow.has(e.id))return null;let f=r.generations[o]??0,b=u(t$1,r.options);return f!==b?null:e.id}function $(n,e){return l(n,e)!==null}var re=typeof SharedArrayBuffer<"u";export{i as EntityNotAliveError,re as IS_SAB_SUPPORTED,H as Types,$ as aliveRef,l as deref,N as pipe,K 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 /**\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<unknown>\n exclusive: Int32Array | null // [srcEid] → tgtEid; -1 means none\n // Reverse index for exclusive relations: tgtEid → set of srcEids currently\n // pointing at it. Lets destroy cleanup clear incoming edges in O(incoming)\n // instead of scanning the whole `exclusive` capacity. null for non-exclusive\n // storage. Kept in lockstep with every write to `exclusive`.\n incoming: Map<number, Set<number>> | null\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"]}
|
|
1
|
+
{"version":3,"sources":["../src/internal/types.ts","../src/internal/pipe.ts","../src/ref.ts","../src/index.ts"],"names":["Types","pipe","systems","s","EcsError","w","world","ctx","EntityNotAliveError","eid","refOf","entity","state","getWorldState","isAliveInternal","deref","ref","idx","unpackIdx","archId","arch","storedGen","refGen","unpackGen","aliveRef","IS_SAB_SUPPORTED"],"mappings":"urBAKO,IAAMA,CAAAA,CAAQ,CACnB,EAAA,CAAI,IAAA,CACJ,GAAI,IAAA,CACJ,GAAA,CAAK,KAAA,CACL,GAAA,CAAK,KAAA,CACL,GAAA,CAAK,MACL,GAAA,CAAK,KAAA,CACL,GAAA,CAAK,KAAA,CACL,GAAA,CAAK,KAAA,CACL,IAAK,KAAA,CACL,IAAA,CAAM,MACR,ECRO,SAASC,CAAAA,CAAAA,GACXC,EACa,CAChB,IAAA,IAAWC,CAAAA,IAAKD,CAAAA,CACd,GAAI,OAAOC,GAAM,UAAA,CAAY,MAAM,IAAIC,CAAAA,CAAS,0CAA0C,CAAA,CAE5F,OAAIF,CAAAA,CAAQ,MAAA,GAAW,CAAA,CAAWG,CAAAA,EAASA,CAAAA,CACvCH,CAAAA,CAAQ,SAAW,CAAA,CAAUA,CAAAA,CAAQ,CAAC,CAAA,CACnC,CAACI,CAAAA,CAAUC,IAAa,CAC7B,IAAIF,CAAAA,CAAIC,CAAAA,CACR,IAAA,IAAWH,CAAAA,IAAKD,EAASG,CAAAA,CAAIF,CAAAA,CAAEE,CAAAA,CAAGE,CAAG,CAAA,CACrC,OAAOF,CACT,CACF,CCwBO,IAAMG,CAAAA,CAAN,cAAkC,KAAM,CACpC,GAAA,CACT,WAAA,CAAYC,CAAAA,CAAa,CACvB,KAAA,CAAM,CAAA,gBAAA,EAAmBA,CAAG,CAAA,aAAA,CAAe,CAAA,CAC3C,IAAA,CAAK,IAAA,CAAO,qBAAA,CACZ,IAAA,CAAK,IAAMA,EACb,CACF,EAaO,SAASC,CAAAA,CAAmBJ,CAAAA,CAAcK,EAAgC,CAC/E,IAAMC,CAAAA,CAAQC,GAAAA,CAAcP,CAAK,CAAA,CACjC,GAAI,CAACQ,CAAAA,CAAgBF,CAAAA,CAAOD,CAAgB,CAAA,CAC1C,MAAM,IAAIH,CAAAA,CAAoBG,CAAgB,CAAA,CAEhD,OAAO,MAAA,CAAO,MAAA,CAAO,CAAE,EAAA,CAAIA,CAAAA,CAAQ,OAAA,CAASC,CAAAA,CAAM,EAAG,CAAC,CACxD,CAgBO,SAASG,CAAAA,CAAmBT,CAAAA,CAAcU,CAAAA,CAAoC,CACnF,IAAMJ,CAAAA,CAAQC,GAAAA,CAAcP,CAAK,CAAA,CAGjC,GAAIU,CAAAA,CAAI,UAAYJ,CAAAA,CAAM,EAAA,CAAI,OAAO,IAAA,CAErC,IAAMH,GAAAA,CAAMO,EAAI,EAAA,CACVC,CAAAA,CAAMC,CAAAA,CAAUT,GAAAA,CAAKG,CAAAA,CAAM,OAAO,EAIxC,GADIK,CAAAA,EAAO,CAAA,EACPA,CAAAA,EAAOL,CAAAA,CAAM,QAAA,CAAU,OAAO,IAAA,CAGlC,IAAMO,CAAAA,CAASP,CAAAA,CAAM,eAAA,CAAgBK,CAAG,GAAK,CAAA,CACvCG,GAAAA,CAAOR,CAAAA,CAAM,UAAA,CAAWO,CAAM,CAAA,CAEpC,GADI,CAACC,GAAAA,EACD,CAACA,GAAAA,CAAK,SAAA,CAAU,GAAA,CAAIJ,EAAI,EAAE,CAAA,CAAG,OAAO,IAAA,CAGxC,IAAMK,CAAAA,CAAYT,EAAM,WAAA,CAAYK,CAAG,CAAA,EAAK,CAAA,CACtCK,CAAAA,CAASC,CAAAA,CAAUd,IAAKG,CAAAA,CAAM,OAAO,CAAA,CAC3C,OAAIS,CAAAA,GAAcC,CAAAA,CAAe,KAE1BN,CAAAA,CAAI,EACb,CAQO,SAASQ,CAAAA,CAAsBlB,CAAAA,CAAcU,EAA4B,CAC9E,OAAOD,CAAAA,CAAMT,CAAAA,CAAOU,CAAG,CAAA,GAAM,IAC/B,CC3BO,IAAMS,EAAAA,CAA4B,OAAO,iBAAA,CAAsB","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}\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 // Stable serialization key from `defineComponent(..., { key })`; null when keyless.\n key: string | null\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}\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 /**\n * `'throw'` (default) rejects a snapshot whose format version is not 2 —\n * including every 0.5.x snapshot — with `EcsError`. `'best-effort'` loads it\n * anyway; a snapshot without a format 2 component table is then resolved by\n * creation-order id with a `kind` check only (0.5.x behaviour).\n */\n onUnknownVersion?: 'throw' | 'best-effort'\n /**\n * `'throw'` (default) rejects a snapshot that references a component this\n * process has not defined with `EcsError`; `'skip'` drops that component's\n * data and loads the rest.\n */\n onUnknownComponent?: 'throw' | 'skip'\n}\n\nexport interface WorldSnapshot {\n /** Snapshot format; 0.6.0 writes 2. 0.5.x snapshots have no `formatVersion`. */\n formatVersion: 2\n version: string\n capacity: number\n // Source world layout / limits. Absent in snapshots from older versions, in\n // which case the restored world uses the createWorld defaults.\n maxEntities?: number\n indexBits?: number\n generationBits?: number\n /**\n * One entry per component referenced by `entities`: the source process's\n * component `id`, its stable `key` (null when keyless), its `kind`, and for\n * SoA its fields in declaration order. A loader resolves each entry by `key`\n * (by `id` when keyless) and rejects a kind or field mismatch.\n */\n components: Array<{\n id: number\n key: string | null\n kind: 'soa' | 'aos' | 'tag'\n fields: Array<{ name: string; type: SoAFieldType; vectorLen: number }> | null\n }>\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<unknown>\n exclusive: Int32Array | null // [srcEid] → tgtEid; -1 means none\n // Reverse index for exclusive relations: tgtEid → set of srcEids currently\n // pointing at it. Lets destroy cleanup clear incoming edges in O(incoming)\n // instead of scanning the whole `exclusive` capacity. null for non-exclusive\n // storage. Kept in lockstep with every write to `exclusive`.\n incoming: Map<number, Set<number>> | null\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 destroying: Set<number> // packed eids mid-destroyEntity (reentrancy guard)\n removing: Set<number> // idx * maxComponents + bit mid-removeComponent dispatch\n visitStamp: Uint32Array // [idx] → last forEachEntity pass that visited it (lazy)\n visitEpoch: number // current forEachEntity pass stamp\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 { EcsError } from './errors.js'\nimport type { System, World } from './types.js'\n\n/**\n * Compose systems left to right: the returned system threads the world through\n * each one with the same `ctx`. Every system must be a function (`EcsError`\n * otherwise, checked here rather than on the first tick).\n */\nexport function pipe<W extends World = World, Ctx = unknown>(\n ...systems: System<W, Ctx>[]\n): System<W, Ctx> {\n for (const s of systems) {\n if (typeof s !== 'function') throw new EcsError('aiecsjs: every system must be a function')\n }\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 for a live (registered, non-disposed)\n * world — they return null/false. Both still throw `EcsError` if `world`\n * itself has already been disposed (`disposeWorld`), same as any other call\n * that resolves world state.\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 for a live world. Throws `EcsError` if `world` has already\n * been disposed via `disposeWorld` — call this before disposal, or catch it\n * during teardown, if you need this check to run past a world's lifetime.\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 for a live world; see `deref` for the disposed-world case.\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.\n\nexport { VERSION } from './version.js'\n\nimport { registerObserverDispatch } from './internal/component.js'\nimport { registerObserversAPI, registerRelationsCleanup } from './internal/entity.js'\nimport type { EntityId, WorldState } from './internal/types.js'\n\n// Component lookup and mask-change → reactive-query wiring live in\n// internal/query.ts, so they survive a bundler dropping this entry module\n// (`sideEffects: false`) when a consumer imports only re-exported names.\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/loop.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
'use strict';var
|
|
1
|
+
'use strict';var d=typeof globalThis.requestAnimationFrame=="function",h=()=>typeof performance<"u"?performance.now():Date.now();function b(e){return d?globalThis.requestAnimationFrame(e):setTimeout(()=>e(h()),16)}function x(e){d&&typeof globalThis.cancelAnimationFrame=="function"?globalThis.cancelAnimationFrame(e):clearTimeout(e);}function R(e){if(!e||typeof e!="object")throw new TypeError("aiecsjs: options must be an object");let n=e.fixed??1/60,i=e.maxSubSteps??5,f=e.onUpdate,a=e.onRender;if(typeof f!="function")throw new TypeError("aiecsjs: onUpdate must be a function");if(a!=null&&typeof a!="function")throw new TypeError("aiecsjs: onRender must be a function");if(!(Number.isFinite(n)&&n>0))throw new RangeError("aiecsjs: fixed must be a finite number > 0");if(!(Number.isFinite(i)&&i>=1))throw new RangeError("aiecsjs: maxSubSteps must be a finite number >= 1");let t=false,o=0,s=0,r=0,c=0;return {start(){if(t)return;t=true;let w=++c;s=h(),r=0;let u=()=>t&&w===c,m=p=>{if(!u())return;let T=p-s;s=p,r+=Math.min(Math.max(0,T)/1e3,n*i);let l=0;for(;r>=n&&l<i;){if(f(n),!u())return;r-=n,l++;}if(a){let y=r/n;if(a(y),!u())return}o=b(m);};o=b(m);},stop(){t&&(t=false,o&&x(o),o=0);}}}exports.createLoop=R;//# sourceMappingURL=loop.cjs.map
|
|
2
2
|
//# sourceMappingURL=loop.cjs.map
|
package/dist/loop.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/loop.ts"],"names":["hasRAF","now","raf","cb","cancelRaf","handle","createLoop","options","fixed","maxSubSteps","onUpdate","onRender","running","lastT","accumulator","tick","t","dtMs","steps","alpha"],"mappings":"aAcA,IAAMA,
|
|
1
|
+
{"version":3,"sources":["../src/loop.ts"],"names":["hasRAF","now","raf","cb","cancelRaf","handle","createLoop","options","fixed","maxSubSteps","onUpdate","onRender","running","lastT","accumulator","epoch","token","live","tick","t","dtMs","steps","alpha"],"mappings":"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","file":"loop.cjs","sourcesContent":["// aiecsjs/loop — fixed-timestep accumulator loop.\n\ninterface LoopOptions {\n fixed?: number\n maxSubSteps?: number\n onUpdate: (dt: number) => void\n onRender?: (alpha: number) => void\n}\n\ninterface Loop {\n start(): void\n stop(): void\n}\n\nconst hasRAF = typeof globalThis.requestAnimationFrame === 'function'\nconst now = () => (typeof performance !== 'undefined' ? performance.now() : Date.now())\n\nfunction raf(cb: (t: number) => void): number {\n if (hasRAF) return globalThis.requestAnimationFrame(cb)\n return setTimeout(() => cb(now()), 16) as unknown as number\n}\nfunction cancelRaf(handle: number): void {\n if (hasRAF && typeof globalThis.cancelAnimationFrame === 'function') {\n globalThis.cancelAnimationFrame(handle)\n } else {\n clearTimeout(handle as unknown as ReturnType<typeof setTimeout>)\n }\n}\n\n/**\n * Create a fixed-timestep loop driven by `requestAnimationFrame` (a 16 ms\n * `setTimeout` outside browsers). `onUpdate(fixed)` runs up to `maxSubSteps`\n * times per frame; `onRender(alpha)` runs once per frame with the leftover\n * fraction of a step.\n *\n * Arguments are validated before anything is scheduled. This subpath exports\n * no error class, so misuse throws built-in errors with an `aiecsjs: ` prefix:\n * `TypeError` for a missing options object or a non-function `onUpdate` /\n * `onRender`, `RangeError` for a `fixed` that is not a finite number > 0 or a\n * `maxSubSteps` that is not a finite number >= 1.\n */\nexport function createLoop(options: LoopOptions): Loop {\n if (!options || typeof options !== 'object') {\n throw new TypeError('aiecsjs: options must be an object')\n }\n const fixed = options.fixed ?? 1 / 60\n const maxSubSteps = options.maxSubSteps ?? 5\n const onUpdate = options.onUpdate\n const onRender = options.onRender\n if (typeof onUpdate !== 'function') throw new TypeError('aiecsjs: onUpdate must be a function')\n if (onRender != null && typeof onRender !== 'function') {\n throw new TypeError('aiecsjs: onRender must be a function')\n }\n // NaN / Infinity / <= 0 would stall the loop, run zero-length steps every\n // frame, or disable the spiral-of-death clamp.\n if (!(Number.isFinite(fixed) && fixed > 0)) {\n throw new RangeError('aiecsjs: fixed must be a finite number > 0')\n }\n if (!(Number.isFinite(maxSubSteps) && maxSubSteps >= 1)) {\n throw new RangeError('aiecsjs: maxSubSteps must be a finite number >= 1')\n }\n\n let running = false\n let handle = 0\n let lastT = 0\n let accumulator = 0\n // Bumped by every start(). A tick chain belongs to the start() that created\n // it; it stops as soon as the loop is stopped or restarted — even from\n // inside onUpdate / onRender — so it never keeps stepping, renders after\n // stop(), or runs alongside a newer chain.\n let epoch = 0\n\n return {\n start() {\n if (running) return\n running = true\n const token = ++epoch\n lastT = now()\n accumulator = 0\n const live = (): boolean => running && token === epoch\n const tick = (t: number): void => {\n if (!live()) return\n const dtMs = t - lastT\n lastT = t\n // Clamp to non-negative: the first rAF callback can receive a\n // frame-begin timestamp earlier than the `performance.now()` sampled\n // by `start()`, which would otherwise drive the accumulator (and thus\n // `onRender`'s alpha) negative.\n accumulator += Math.min(Math.max(0, dtMs) / 1000, fixed * maxSubSteps)\n let steps = 0\n while (accumulator >= fixed && steps < maxSubSteps) {\n onUpdate(fixed)\n if (!live()) return\n accumulator -= fixed\n steps++\n }\n if (onRender) {\n const alpha = accumulator / fixed\n onRender(alpha)\n if (!live()) return\n }\n handle = raf(tick)\n }\n handle = raf(tick)\n },\n stop() {\n if (!running) return\n running = false\n if (handle) cancelRaf(handle)\n handle = 0\n },\n }\n}\n"]}
|
package/dist/loop.d.cts
CHANGED
|
@@ -8,6 +8,18 @@ interface Loop {
|
|
|
8
8
|
start(): void;
|
|
9
9
|
stop(): void;
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Create a fixed-timestep loop driven by `requestAnimationFrame` (a 16 ms
|
|
13
|
+
* `setTimeout` outside browsers). `onUpdate(fixed)` runs up to `maxSubSteps`
|
|
14
|
+
* times per frame; `onRender(alpha)` runs once per frame with the leftover
|
|
15
|
+
* fraction of a step.
|
|
16
|
+
*
|
|
17
|
+
* Arguments are validated before anything is scheduled. This subpath exports
|
|
18
|
+
* no error class, so misuse throws built-in errors with an `aiecsjs: ` prefix:
|
|
19
|
+
* `TypeError` for a missing options object or a non-function `onUpdate` /
|
|
20
|
+
* `onRender`, `RangeError` for a `fixed` that is not a finite number > 0 or a
|
|
21
|
+
* `maxSubSteps` that is not a finite number >= 1.
|
|
22
|
+
*/
|
|
11
23
|
declare function createLoop(options: LoopOptions): Loop;
|
|
12
24
|
|
|
13
25
|
export { createLoop };
|
package/dist/loop.d.ts
CHANGED
|
@@ -8,6 +8,18 @@ interface Loop {
|
|
|
8
8
|
start(): void;
|
|
9
9
|
stop(): void;
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Create a fixed-timestep loop driven by `requestAnimationFrame` (a 16 ms
|
|
13
|
+
* `setTimeout` outside browsers). `onUpdate(fixed)` runs up to `maxSubSteps`
|
|
14
|
+
* times per frame; `onRender(alpha)` runs once per frame with the leftover
|
|
15
|
+
* fraction of a step.
|
|
16
|
+
*
|
|
17
|
+
* Arguments are validated before anything is scheduled. This subpath exports
|
|
18
|
+
* no error class, so misuse throws built-in errors with an `aiecsjs: ` prefix:
|
|
19
|
+
* `TypeError` for a missing options object or a non-function `onUpdate` /
|
|
20
|
+
* `onRender`, `RangeError` for a `fixed` that is not a finite number > 0 or a
|
|
21
|
+
* `maxSubSteps` that is not a finite number >= 1.
|
|
22
|
+
*/
|
|
11
23
|
declare function createLoop(options: LoopOptions): Loop;
|
|
12
24
|
|
|
13
25
|
export { createLoop };
|