import type { ComponentId, ComponentDef, Schema } from '@ecsia/schema'; import type { ArchetypeId } from '@ecsia/schema'; import type { Buffers } from '../memory/index.js'; import type { AccessorWorld } from '../component/index.js'; import type { Bitmask } from '../bitmask/index.js'; import type { Signature } from './signature.js'; import type { Archetype } from './archetype.js'; import type { ColdStore } from './cold-store.js'; export declare const EMPTY_ARCHETYPE_ID: ArchetypeId; export declare const ARCHETYPE_NONE: ArchetypeId; /** The two-word record surface the store commits through. */ export interface RecordSurface { commitRecord(index: number, archetypeId: number, row: number): void; archetypeIdOf(index: number): number; rowOf(index: number): number; } export interface StorageDeps { readonly buffers: Buffers; readonly accessorWorld: AccessorWorld; readonly bitmask: Bitmask; readonly record: RecordSurface; readonly maxHotArchetypes: number; /** Bitmask/sigWords stride = ceil(N/32) ( C4). */ readonly stride: number; /** Mint ceiling; clamps a new column's initial reservation so it never exceeds the world cap. */ readonly maxEntities: number; /** Removal-reactivity hook for components in fromArch \ toArch ( fills the body). */ enqueueRemoveLog(index: number, c: ComponentId): void; /** * Shape-log hook: emit a structural entry at the commit point. Optional so a * query-less harness (unit tests) constructs the store directly; the world wires it to reactivity. */ trackShape?(index: number, c: ComponentId, kind: number): void; /** * Single-entity incremental query maintenance: called once per component in the * symmetric difference of a migration AFTER the bitmask delta is applied, so matchesEntityNow reads * a coherent shape. Optional so a query-less harness ( unit tests) constructs the store directly. * re-routes this through reactivity's MAINTAIN_STRUCTURAL shape-log drain. */ maintainEntity?(index: number, c: ComponentId): void; tick(): number; defOf(c: ComponentId): ComponentDef | undefined; handleIndex(handle: number): number; /** * A held deferred-dead row was relocated (allocRow evicting it to keep [0,count) dense): tell the * observer-window location stash its new row, so a dead-tenant ref still resolves the right slot. * Optional — a query-less harness omits it (no observer window to update). */ relocateHeld?(handle: number, archId: number, newRow: number): void; } export declare class ArchetypeStore { #private; readonly byId: Archetype[]; readonly cold: ColdStore; constructor(deps: StorageDeps); get hotCount(): number; get emptyArchetype(): Archetype; getOrCreateArchetype(sig: Signature): Archetype; /** Subscribe to archetypeCreated. Serial-phase only. */ onArchetypeCreated(fn: (arch: Archetype) => void): void; edgeAdd(arch: Archetype, c: ComponentId): Archetype; edgeRemove(arch: Archetype, c: ComponentId): Archetype; /** Reserve a row in `arch` for `handle`; records the occupant. Caller writes column values. */ allocRow(arch: Archetype, handle: number): number; /** * Release every archetype's held deferred-dead rows back to the allocatable region. Called at the * observer drain's flushPending (after onRemove handlers have read the dying values): held → 0, so * the next allocRow reuses [count, …) and overwrites the abandoned dead data. Iteration is * unaffected (the rows were always above count); only allocation reclaims them. */ releaseHeldRows(): void; /** * ROLLBACK-ONLY (@ecsia/rollback restore): set an archetype's occupancy directly, keeping * the held-release set coherent. The caller writes the row list + columns itself; this is the one * place the two occupancy words may be assigned outside alloc/remove. */ rollbackSetOccupancy(arch: Archetype, count: number, held: number): void; /** * Swap-pop removal: move the last live row into `row`, then fix the moved sibling's record via * the callback. `fixSibling` fires exactly once iff row !== count-1 (I6). Serial only. * *: when `relocateDying` is supplied (a remove-observer subscribes to a * held component), the dying entity's column data must survive intact until after observerDrain so * an onRemove handler can read its last value. Instead of a one-way overwrite of the dying row, we * SWAP it with the last live row: the sibling's data lands in `row` (record fixed), and the dying * entity's data lands at the now-excluded `last` slot (record relocated via `relocateDying`). The * dying row is naturally reclaimed — it sits at/above `count`, outside every `[0,count)` iteration, * and is overwritten by the next allocRow (which only happens next frame, since observer mutations * stage to command buffers). For hot rows this needs no per-frame stale-row list. */ removeRow(arch: Archetype, row: number, fixSibling: (movedIndex: number, newRow: number) => void, relocateDying?: (newRow: number) => void): void; /** Move one entity from fromArch to toArch: K-shared copy + init-added + shuffle-pop + commit. */ migrate(handle: number, fromArch: Archetype, toArch: Archetype): number; /** entity.add(C): single-id add via the cached edge. */ migrateAdding(handle: number, c: ComponentId): number; /** entity.remove(C): single-id remove via the cached edge. */ migrateRemoving(handle: number, c: ComponentId): number; /** Multi-id atomic add — ONE target signature, one migration (relations atomicity). */ migrateAddingMany(handle: number, addIds: readonly ComponentId[]): number; /** Multi-id atomic remove — symmetric to migrateAddingMany. */ migrateRemovingMany(handle: number, removeIds: readonly ComponentId[]): number; /** spawnWith fast path: compute the target signature up front and migrate ONCE. */ spawnWith(handle: number, defs: readonly ComponentDef[]): number; /** Promote a cold archetype (by signature) to hot: allocate columns, migrate its rows out. */ warm(sig: Signature): void; } //# sourceMappingURL=store.d.ts.map