@oasys/oecs 0.3.1 → 0.5.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/CHANGELOG.md +553 -0
- package/README.md +207 -267
- package/dist/core/ecs/access_check.d.cts +77 -0
- package/dist/core/ecs/access_check.d.ts +77 -0
- package/dist/core/ecs/access_check.d.ts.map +1 -0
- package/dist/core/ecs/archetype.d.cts +486 -0
- package/dist/core/ecs/archetype.d.ts +486 -0
- package/dist/core/ecs/archetype.d.ts.map +1 -0
- package/dist/core/ecs/archetype_graph.d.cts +96 -0
- package/dist/core/ecs/archetype_graph.d.ts +96 -0
- package/dist/core/ecs/archetype_graph.d.ts.map +1 -0
- package/dist/core/ecs/builtin_relations.d.cts +43 -0
- package/dist/core/ecs/builtin_relations.d.ts +43 -0
- package/dist/core/ecs/builtin_relations.d.ts.map +1 -0
- package/dist/core/ecs/command_log.d.cts +116 -0
- package/dist/core/ecs/command_log.d.ts +116 -0
- package/dist/core/ecs/command_log.d.ts.map +1 -0
- package/dist/core/ecs/component.d.cts +176 -0
- package/dist/core/ecs/component.d.ts +176 -0
- package/dist/core/ecs/component.d.ts.map +1 -0
- package/dist/core/ecs/compute_backend.d.cts +37 -0
- package/dist/core/ecs/compute_backend.d.ts +37 -0
- package/dist/core/ecs/compute_backend.d.ts.map +1 -0
- package/dist/core/ecs/debug_names.d.cts +7 -0
- package/dist/core/ecs/debug_names.d.ts +7 -0
- package/dist/core/ecs/debug_names.d.ts.map +1 -0
- package/dist/core/ecs/deferred_commands.d.cts +55 -0
- package/dist/core/ecs/deferred_commands.d.ts +55 -0
- package/dist/core/ecs/deferred_commands.d.ts.map +1 -0
- package/dist/core/ecs/dispatch_trace.d.cts +93 -0
- package/dist/core/ecs/dispatch_trace.d.ts +93 -0
- package/dist/core/ecs/dispatch_trace.d.ts.map +1 -0
- package/dist/core/ecs/ecs.d.cts +562 -0
- package/dist/core/ecs/ecs.d.ts +562 -0
- package/dist/core/ecs/ecs.d.ts.map +1 -0
- package/dist/core/ecs/ecs_memory.d.cts +179 -0
- package/dist/core/ecs/ecs_memory.d.ts +179 -0
- package/dist/core/ecs/ecs_memory.d.ts.map +1 -0
- package/dist/core/ecs/entity.d.cts +34 -0
- package/dist/core/ecs/entity.d.ts +34 -0
- package/dist/core/ecs/entity.d.ts.map +1 -0
- package/dist/core/ecs/entity_allocator.d.cts +59 -0
- package/dist/core/ecs/entity_allocator.d.ts +59 -0
- package/dist/core/ecs/entity_allocator.d.ts.map +1 -0
- package/dist/core/ecs/event.d.cts +79 -0
- package/dist/core/ecs/event.d.ts +79 -0
- package/dist/core/ecs/event.d.ts.map +1 -0
- package/dist/core/ecs/event_registry.d.cts +22 -0
- package/dist/core/ecs/event_registry.d.ts +22 -0
- package/dist/core/ecs/event_registry.d.ts.map +1 -0
- package/dist/core/ecs/facades.d.cts +158 -0
- package/dist/core/ecs/facades.d.ts +158 -0
- package/dist/core/ecs/facades.d.ts.map +1 -0
- package/dist/core/ecs/frame_stepper.d.cts +58 -0
- package/dist/core/ecs/frame_stepper.d.ts +58 -0
- package/dist/core/ecs/frame_stepper.d.ts.map +1 -0
- package/dist/core/ecs/frame_trace.d.cts +133 -0
- package/dist/core/ecs/frame_trace.d.ts +133 -0
- package/dist/core/ecs/frame_trace.d.ts.map +1 -0
- package/dist/core/ecs/host_commands.d.cts +274 -0
- package/dist/core/ecs/host_commands.d.ts +274 -0
- package/dist/core/ecs/host_commands.d.ts.map +1 -0
- package/dist/core/ecs/index.d.cts +46 -0
- package/dist/core/ecs/index.d.ts +46 -0
- package/dist/core/ecs/index.d.ts.map +1 -0
- package/dist/core/ecs/observer.d.cts +173 -0
- package/dist/core/ecs/observer.d.ts +173 -0
- package/dist/core/ecs/observer.d.ts.map +1 -0
- package/dist/core/ecs/query.d.cts +718 -0
- package/dist/core/ecs/query.d.ts +718 -0
- package/dist/core/ecs/query.d.ts.map +1 -0
- package/dist/core/ecs/ref.d.cts +31 -0
- package/dist/core/ecs/ref.d.ts +31 -0
- package/dist/core/ecs/ref.d.ts.map +1 -0
- package/dist/core/ecs/relation.d.cts +243 -0
- package/dist/core/ecs/relation.d.ts +243 -0
- package/dist/core/ecs/relation.d.ts.map +1 -0
- package/dist/core/ecs/relation_service.d.cts +235 -0
- package/dist/core/ecs/relation_service.d.ts +235 -0
- package/dist/core/ecs/relation_service.d.ts.map +1 -0
- package/dist/core/ecs/resource.d.cts +53 -0
- package/dist/core/ecs/resource.d.ts +53 -0
- package/dist/core/ecs/resource.d.ts.map +1 -0
- package/dist/core/ecs/resource_registry.d.cts +20 -0
- package/dist/core/ecs/resource_registry.d.ts +20 -0
- package/dist/core/ecs/resource_registry.d.ts.map +1 -0
- package/dist/core/ecs/resume.d.cts +85 -0
- package/dist/core/ecs/resume.d.ts +85 -0
- package/dist/core/ecs/resume.d.ts.map +1 -0
- package/dist/core/ecs/run_condition.d.cts +81 -0
- package/dist/core/ecs/run_condition.d.ts +81 -0
- package/dist/core/ecs/run_condition.d.ts.map +1 -0
- package/dist/core/ecs/schedule.d.cts +142 -0
- package/dist/core/ecs/schedule.d.ts +142 -0
- package/dist/core/ecs/schedule.d.ts.map +1 -0
- package/dist/core/ecs/snapshot_service.d.cts +94 -0
- package/dist/core/ecs/snapshot_service.d.ts +94 -0
- package/dist/core/ecs/snapshot_service.d.ts.map +1 -0
- package/dist/core/ecs/sparse_store.d.cts +110 -0
- package/dist/core/ecs/sparse_store.d.ts +110 -0
- package/dist/core/ecs/sparse_store.d.ts.map +1 -0
- package/dist/core/ecs/store.d.cts +950 -0
- package/dist/core/ecs/store.d.ts +950 -0
- package/dist/core/ecs/store.d.ts.map +1 -0
- package/dist/core/ecs/store_layout_listener.d.cts +23 -0
- package/dist/core/ecs/store_layout_listener.d.ts +23 -0
- package/dist/core/ecs/store_layout_listener.d.ts.map +1 -0
- package/dist/core/ecs/system.d.cts +250 -0
- package/dist/core/ecs/system.d.ts +250 -0
- package/dist/core/ecs/system.d.ts.map +1 -0
- package/dist/core/ecs/utils/arrays.d.cts +7 -0
- package/dist/{utils → core/ecs/utils}/arrays.d.ts +2 -1
- package/dist/core/ecs/utils/arrays.d.ts.map +1 -0
- package/dist/core/ecs/utils/constants.d.ts +12 -0
- package/dist/core/ecs/utils/constants.d.ts.map +1 -0
- package/dist/core/ecs/utils/error.d.cts +61 -0
- package/dist/core/ecs/utils/error.d.ts +61 -0
- package/dist/core/ecs/utils/error.d.ts.map +1 -0
- package/dist/core/store/action_ring.d.cts +136 -0
- package/dist/core/store/action_ring.d.ts +136 -0
- package/dist/core/store/action_ring.d.ts.map +1 -0
- package/dist/core/store/allocator.d.cts +238 -0
- package/dist/core/store/allocator.d.ts +238 -0
- package/dist/core/store/allocator.d.ts.map +1 -0
- package/dist/core/store/buffer_backed_column.d.cts +69 -0
- package/dist/core/store/buffer_backed_column.d.ts +69 -0
- package/dist/core/store/buffer_backed_column.d.ts.map +1 -0
- package/dist/core/store/column_store.d.cts +273 -0
- package/dist/core/store/column_store.d.ts +273 -0
- package/dist/core/store/column_store.d.ts.map +1 -0
- package/dist/core/store/command_dispatch.d.cts +52 -0
- package/dist/core/store/command_dispatch.d.ts +52 -0
- package/dist/core/store/command_dispatch.d.ts.map +1 -0
- package/dist/core/store/command_ring.d.cts +107 -0
- package/dist/core/store/command_ring.d.ts +107 -0
- package/dist/core/store/command_ring.d.ts.map +1 -0
- package/dist/core/store/descriptor.d.cts +80 -0
- package/dist/core/store/descriptor.d.ts +80 -0
- package/dist/core/store/descriptor.d.ts.map +1 -0
- package/dist/core/store/entity_index.d.cts +108 -0
- package/dist/core/store/entity_index.d.ts +108 -0
- package/dist/core/store/entity_index.d.ts.map +1 -0
- package/dist/core/store/event_ring.d.cts +95 -0
- package/dist/core/store/event_ring.d.ts +95 -0
- package/dist/core/store/event_ring.d.ts.map +1 -0
- package/dist/core/store/extend.d.cts +39 -0
- package/dist/core/store/extend.d.ts +39 -0
- package/dist/core/store/extend.d.ts.map +1 -0
- package/dist/core/store/grow.d.cts +34 -0
- package/dist/core/store/grow.d.ts +34 -0
- package/dist/core/store/grow.d.ts.map +1 -0
- package/dist/core/store/header.d.cts +64 -0
- package/dist/core/store/header.d.ts +64 -0
- package/dist/core/store/header.d.ts.map +1 -0
- package/dist/core/store/index.d.cts +17 -0
- package/dist/core/store/index.d.ts +17 -0
- package/dist/core/store/index.d.ts.map +1 -0
- package/dist/core/store/layout_ops.d.cts +152 -0
- package/dist/core/store/layout_ops.d.ts +152 -0
- package/dist/core/store/layout_ops.d.ts.map +1 -0
- package/dist/core/store/region_table.d.cts +74 -0
- package/dist/core/store/region_table.d.ts +74 -0
- package/dist/core/store/region_table.d.ts.map +1 -0
- package/dist/core/store/snapshot.d.cts +43 -0
- package/dist/core/store/snapshot.d.ts +43 -0
- package/dist/core/store/snapshot.d.ts.map +1 -0
- package/dist/core/store/state_hash.d.cts +38 -0
- package/dist/core/store/state_hash.d.ts +38 -0
- package/dist/core/store/state_hash.d.ts.map +1 -0
- package/dist/core/store/store_regions.d.cts +38 -0
- package/dist/core/store/store_regions.d.ts +38 -0
- package/dist/core/store/store_regions.d.ts.map +1 -0
- package/dist/core/store/vendored_abi/abi.d.cts +43 -0
- package/dist/core/store/vendored_abi/abi.d.ts +43 -0
- package/dist/core/store/vendored_abi/abi.d.ts.map +1 -0
- package/dist/dev_flag.d.cts +17 -0
- package/dist/dev_flag.d.ts +17 -0
- package/dist/dev_flag.d.ts.map +1 -0
- package/dist/extensions/editor/editor.d.cts +170 -0
- package/dist/extensions/editor/editor.d.ts +170 -0
- package/dist/extensions/editor/editor.d.ts.map +1 -0
- package/dist/extensions/editor/field_handle.d.cts +40 -0
- package/dist/extensions/editor/field_handle.d.ts +40 -0
- package/dist/extensions/editor/field_handle.d.ts.map +1 -0
- package/dist/extensions/editor/index.cjs +1 -0
- package/dist/extensions/editor/index.d.cts +21 -0
- package/dist/extensions/editor/index.d.ts +21 -0
- package/dist/extensions/editor/index.d.ts.map +1 -0
- package/dist/extensions/editor/index.js +237 -0
- package/dist/extensions/reactive/ecs_sync.d.cts +214 -0
- package/dist/extensions/reactive/ecs_sync.d.ts +214 -0
- package/dist/extensions/reactive/ecs_sync.d.ts.map +1 -0
- package/dist/extensions/reactive/index.cjs +1 -0
- package/dist/extensions/reactive/index.d.cts +23 -0
- package/dist/extensions/reactive/index.d.ts +23 -0
- package/dist/extensions/reactive/index.d.ts.map +1 -0
- package/dist/extensions/reactive/index.js +215 -0
- package/dist/extensions/solid/index.cjs +1 -0
- package/dist/extensions/solid/index.d.cts +6 -0
- package/dist/extensions/solid/index.d.ts +6 -0
- package/dist/extensions/solid/index.d.ts.map +1 -0
- package/dist/extensions/solid/index.js +36 -0
- package/dist/extensions/solid/kernel_solid.d.cts +45 -0
- package/dist/extensions/solid/kernel_solid.d.ts +45 -0
- package/dist/extensions/solid/kernel_solid.d.ts.map +1 -0
- package/dist/host_commands-BF8QMi3c.cjs +1 -0
- package/dist/host_commands-i4cAeyL5.js +2318 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +66 -0
- package/dist/index.d.ts +65 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7080 -1289
- package/dist/internal.cjs +2 -0
- package/dist/internal.d.cts +20 -0
- package/dist/internal.d.ts +20 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +141 -0
- package/dist/interop-CT-REx0W.cjs +1 -0
- package/dist/interop-CcY6ASQc.js +18 -0
- package/dist/kernel-DgyrLFjW.js +227 -0
- package/dist/kernel-yWV3XnAb.cjs +1 -0
- package/dist/primitives.cjs +1 -0
- package/dist/primitives.d.cts +18 -0
- package/dist/primitives.d.ts +18 -0
- package/dist/primitives.d.ts.map +1 -0
- package/dist/primitives.js +45 -0
- package/dist/reactive/array.d.cts +24 -0
- package/dist/reactive/array.d.ts +24 -0
- package/dist/reactive/array.d.ts.map +1 -0
- package/dist/reactive/index.cjs +1 -0
- package/dist/reactive/index.d.cts +11 -0
- package/dist/reactive/index.d.ts +11 -0
- package/dist/reactive/index.d.ts.map +1 -0
- package/dist/reactive/index.js +18 -0
- package/dist/reactive/interop.d.cts +19 -0
- package/dist/reactive/interop.d.ts +19 -0
- package/dist/reactive/interop.d.ts.map +1 -0
- package/dist/reactive/kernel.d.cts +73 -0
- package/dist/reactive/kernel.d.ts +73 -0
- package/dist/reactive/kernel.d.ts.map +1 -0
- package/dist/reactive/map.d.cts +16 -0
- package/dist/reactive/map.d.ts +16 -0
- package/dist/reactive/map.d.ts.map +1 -0
- package/dist/reactive/shallow.d.cts +11 -0
- package/dist/reactive/shallow.d.ts +11 -0
- package/dist/reactive/shallow.d.ts.map +1 -0
- package/dist/reactive/struct.d.cts +10 -0
- package/dist/reactive/struct.d.ts +10 -0
- package/dist/reactive/struct.d.ts.map +1 -0
- package/dist/shallow-DQ8aG51_.cjs +1 -0
- package/dist/shallow-DfnAm9vG.js +150 -0
- package/dist/shared-BU1Cd40h.js +103 -0
- package/dist/shared-BymrGTyR.cjs +1 -0
- package/dist/shared.cjs +1 -0
- package/dist/shared.d.cts +22 -0
- package/dist/shared.d.ts +22 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +7 -0
- package/dist/topological_sort-BDvEyb9W.cjs +1 -0
- package/dist/topological_sort-DK6EjpWa.js +135 -0
- package/dist/type_primitives/assertions.d.cts +32 -0
- package/dist/type_primitives/assertions.d.ts +25 -9
- package/dist/type_primitives/assertions.d.ts.map +1 -1
- package/dist/type_primitives/binary_heap/binary_heap.d.cts +37 -0
- package/dist/type_primitives/binary_heap/binary_heap.d.ts +6 -2
- package/dist/type_primitives/binary_heap/binary_heap.d.ts.map +1 -1
- package/dist/type_primitives/bitset/bitset.d.cts +54 -0
- package/dist/type_primitives/bitset/bitset.d.ts +16 -4
- package/dist/type_primitives/bitset/bitset.d.ts.map +1 -1
- package/dist/type_primitives/brand.d.cts +23 -0
- package/dist/type_primitives/brand.d.ts +6 -1
- package/dist/type_primitives/brand.d.ts.map +1 -1
- package/dist/type_primitives/error.d.cts +16 -0
- package/dist/type_primitives/error.d.ts +8 -3
- package/dist/type_primitives/error.d.ts.map +1 -1
- package/dist/type_primitives/index.d.cts +13 -0
- package/dist/type_primitives/index.d.ts +12 -9
- package/dist/type_primitives/index.d.ts.map +1 -1
- package/dist/type_primitives/sparse_map/sparse_map.d.cts +30 -0
- package/dist/type_primitives/sparse_map/sparse_map.d.ts +7 -3
- package/dist/type_primitives/sparse_map/sparse_map.d.ts.map +1 -1
- package/dist/type_primitives/sparse_set/sparse_set.d.cts +28 -0
- package/dist/type_primitives/sparse_set/sparse_set.d.ts +4 -0
- package/dist/type_primitives/sparse_set/sparse_set.d.ts.map +1 -1
- package/dist/type_primitives/topological_sort/topological_sort.d.cts +29 -0
- package/dist/type_primitives/topological_sort/topological_sort.d.ts +7 -3
- package/dist/type_primitives/topological_sort/topological_sort.d.ts.map +1 -1
- package/dist/type_primitives/typed_arrays/typed_arrays.d.cts +127 -0
- package/dist/type_primitives/typed_arrays/typed_arrays.d.ts +53 -16
- package/dist/type_primitives/typed_arrays/typed_arrays.d.ts.map +1 -1
- package/dist/typed_arrays-BcXcnzB4.js +258 -0
- package/dist/typed_arrays-D3NmwtCG.cjs +1 -0
- package/dist/utils/error.d.cts +6 -0
- package/dist/utils/error.d.ts +2 -20
- package/dist/utils/error.d.ts.map +1 -1
- package/dist/version.d.cts +10 -0
- package/dist/version.d.ts +10 -0
- package/dist/version.d.ts.map +1 -0
- package/package.json +115 -5
- package/dist/archetype.d.ts +0 -108
- package/dist/archetype.d.ts.map +0 -1
- package/dist/component.d.ts +0 -45
- package/dist/component.d.ts.map +0 -1
- package/dist/ecs.d.ts +0 -104
- package/dist/ecs.d.ts.map +0 -1
- package/dist/entity.d.ts +0 -11
- package/dist/entity.d.ts.map +0 -1
- package/dist/event.d.ts +0 -30
- package/dist/event.d.ts.map +0 -1
- package/dist/query.d.ts +0 -94
- package/dist/query.d.ts.map +0 -1
- package/dist/ref.d.ts +0 -23
- package/dist/ref.d.ts.map +0 -1
- package/dist/resource.d.ts +0 -23
- package/dist/resource.d.ts.map +0 -1
- package/dist/schedule.d.ts +0 -45
- package/dist/schedule.d.ts.map +0 -1
- package/dist/store.d.ts +0 -118
- package/dist/store.d.ts.map +0 -1
- package/dist/system.d.ts +0 -16
- package/dist/system.d.ts.map +0 -1
- package/dist/utils/arrays.d.ts.map +0 -1
- package/dist/utils/constants.d.ts.map +0 -1
- /package/dist/{utils/constants.d.ts → core/ecs/utils/constants.d.cts} +0 -0
|
@@ -0,0 +1,950 @@
|
|
|
1
|
+
import { EntityID } from './entity.js';
|
|
2
|
+
import { FrameTraceSink } from './frame_trace.js';
|
|
3
|
+
import { ComponentDef, ComponentHandle, ComponentSchema, FieldValues } from './component.js';
|
|
4
|
+
import { SparseComponentDef, SparseComponentID } from './sparse_store.js';
|
|
5
|
+
import { RelationDef, RelationOptions } from './relation.js';
|
|
6
|
+
import { EmptyEventSchema, EventDef, EventReader, EventShape } from './event.js';
|
|
7
|
+
import { BitSet, TypedArrayTag } from '../../type_primitives/index.js';
|
|
8
|
+
import { Archetype, ArchetypeID } from './archetype.js';
|
|
9
|
+
import { Query, QueryHost } from './query.js';
|
|
10
|
+
import { ObserverHost } from './observer.js';
|
|
11
|
+
import { InPlaceBufferAllocator, ColumnStoreRegionHandle, StoreRegionSpec, ColumnStore } from '../store/index.js';
|
|
12
|
+
import { ECSMemoryCapContext } from './ecs_memory.js';
|
|
13
|
+
export interface ComponentMeta {
|
|
14
|
+
/** Optional debug name from `registerComponent(schema, { name })` —
|
|
15
|
+
* diagnostic messages only, never behaviour. */
|
|
16
|
+
name?: string;
|
|
17
|
+
fieldNames: string[];
|
|
18
|
+
fieldIndex: Record<string, number>;
|
|
19
|
+
fieldTypes: TypedArrayTag[];
|
|
20
|
+
/** Has an onAdd observer — collect effective adds for this component. */
|
|
21
|
+
obsAdd: boolean;
|
|
22
|
+
/** Has an onRemove observer — collect effective removes for this component. */
|
|
23
|
+
obsRem: boolean;
|
|
24
|
+
/** Has an onDisable observer (#677) — collect effective disables for this
|
|
25
|
+
* component at the toggle drain. */
|
|
26
|
+
obsDisable: boolean;
|
|
27
|
+
/** Has an onEnable observer (#677) — collect effective enables for this
|
|
28
|
+
* component at the toggle drain. */
|
|
29
|
+
obsEnable: boolean;
|
|
30
|
+
/** Has a per-entity onSet observer — record dirty rows on the write path
|
|
31
|
+
* (the ADR-0012 opt-in dirty list). */
|
|
32
|
+
trackDirty: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Effective `(component, entity)` structural events for one fixed-point round,
|
|
36
|
+
* collected during `_flushAdds` / `_flushRemoves` and handed to the observer
|
|
37
|
+
* dispatch hook. Flat parallel arrays, count-bounded (`*_len`), reused across
|
|
38
|
+
* rounds — never reallocated in the flush. This is a scheduling artifact: it is
|
|
39
|
+
* NOT part of `stateHash` or snapshot. See `observer.ts`.
|
|
40
|
+
*/
|
|
41
|
+
export interface StructuralObserverEvents {
|
|
42
|
+
addComp: number[];
|
|
43
|
+
addEid: number[];
|
|
44
|
+
addLen: number;
|
|
45
|
+
remComp: number[];
|
|
46
|
+
remEid: number[];
|
|
47
|
+
remLen: number;
|
|
48
|
+
/** Effective disable events (#677) — collected during the toggle drain
|
|
49
|
+
* (`_flushToggles`), one per `(component, entity)` of each net-disabled
|
|
50
|
+
* entity's mask. Empty on a structural (add/remove/destroy) round. */
|
|
51
|
+
disComp: number[];
|
|
52
|
+
disEid: number[];
|
|
53
|
+
disLen: number;
|
|
54
|
+
/** Effective enable events (#677), symmetric with the disable arrays. */
|
|
55
|
+
enaComp: number[];
|
|
56
|
+
enaEid: number[];
|
|
57
|
+
enaLen: number;
|
|
58
|
+
}
|
|
59
|
+
/** Extracts the schema out of a `ComponentDef` handle. */
|
|
60
|
+
type SchemaOf<D extends ComponentDef> = D extends ComponentDef<infer S> ? S : ComponentSchema;
|
|
61
|
+
/** One typed template entry: `values` is checked against the def's schema —
|
|
62
|
+
* a misspelled field is a compile error. Fields may be omitted (they default
|
|
63
|
+
* to 0), so the map is `Partial`. Tag defs refuse `values` entirely — their
|
|
64
|
+
* `FieldValues` would otherwise degenerate to `Record<string, number>` (same
|
|
65
|
+
* pitfall as `ValuesArg`); a schema-erased `ComponentDef` falls into the
|
|
66
|
+
* valued branch, so untyped call sites keep the loose shape. */
|
|
67
|
+
export type TemplateEntry<D extends ComponentDef = ComponentDef> = SchemaOf<D> extends Record<string, never> ? {
|
|
68
|
+
readonly def: D;
|
|
69
|
+
readonly values?: undefined;
|
|
70
|
+
} : {
|
|
71
|
+
readonly def: D;
|
|
72
|
+
readonly values?: Partial<FieldValues<SchemaOf<D>>>;
|
|
73
|
+
};
|
|
74
|
+
/** The entries tuple for `ECS.template` — each element's `values` is checked
|
|
75
|
+
* against its own `def`'s schema. */
|
|
76
|
+
export type TemplateEntries<Defs extends readonly ComponentDef[]> = readonly [
|
|
77
|
+
...{
|
|
78
|
+
[K in keyof Defs]: TemplateEntry<Defs[K]>;
|
|
79
|
+
}
|
|
80
|
+
];
|
|
81
|
+
/** Union of every field name owned by a component in `Defs` (distributes
|
|
82
|
+
* over the def list). */
|
|
83
|
+
type TemplateFieldNames<Defs extends readonly ComponentDef[]> = Defs[number] extends ComponentDef<infer S> ? keyof S & string : never;
|
|
84
|
+
/** Flat per-instance override map for `ECS.spawn`: any field of any
|
|
85
|
+
* component in the template, each optional. A misspelled field is a compile
|
|
86
|
+
* error (and still a `DEV` throw at runtime for untyped call sites). */
|
|
87
|
+
export type TemplateOverrides<Defs extends readonly ComponentDef[]> = {
|
|
88
|
+
readonly [K in TemplateFieldNames<Defs>]?: number;
|
|
89
|
+
};
|
|
90
|
+
declare const __templateDefs: unique symbol;
|
|
91
|
+
/** A resolved template (#462) — an archetype template produced by
|
|
92
|
+
* `ECS.template(...)`. **Opaque** apart from `defs`: callers hold it and pass
|
|
93
|
+
* it to `ECS.spawn` / `ECS.spawnMany` (and may reference it in a system's
|
|
94
|
+
* `spawns` / `despawns` access declaration — the scheduler expands it to
|
|
95
|
+
* `defs`); the remaining fields are engine-internal and may change. `spawn`
|
|
96
|
+
* lands an entity directly in `archetype_id` with zero archetype transitions,
|
|
97
|
+
* writing `flatValues` (defaults in `_flatColumns` order) in one append
|
|
98
|
+
* pass. See ADR-0010. */
|
|
99
|
+
export interface Template<Defs extends readonly ComponentDef[] = readonly ComponentDef[]> {
|
|
100
|
+
readonly archetypeId: ArchetypeID;
|
|
101
|
+
readonly flatValues: number[];
|
|
102
|
+
readonly overrideIndex: Map<string, number>;
|
|
103
|
+
/** The component set this template spawns into, in entry order. */
|
|
104
|
+
readonly defs: readonly ComponentDef[];
|
|
105
|
+
readonly [__templateDefs]?: Defs;
|
|
106
|
+
}
|
|
107
|
+
export interface StoreOptions {
|
|
108
|
+
initialCapacity?: number;
|
|
109
|
+
/** Pluggable SAB buffer source. When provided, `createColumnStore`,
|
|
110
|
+
* `extendColumnStore`, and `growColumnStore` route through it. Default is
|
|
111
|
+
* `growableSabAllocator()`. Typed `InPlaceBufferAllocator` (#682): a live
|
|
112
|
+
* Store's flush loops hoist entity-index views across grows, so only
|
|
113
|
+
* in-place allocators may back one (ADR-0008) — the constructor also
|
|
114
|
+
* runtime-asserts the marker for untyped JS callers. Consumers normally
|
|
115
|
+
* don't touch this directly; `ECSOptions.memory` resolves to it. */
|
|
116
|
+
bufferAllocator?: InPlaceBufferAllocator;
|
|
117
|
+
/** Sizing intent the world was constructed with (#682), used to phrase
|
|
118
|
+
* allocator-cap and entity-index-overflow errors in the caller's own
|
|
119
|
+
* terms ("3.2× the declared budget") instead of raw bytes. Wired by
|
|
120
|
+
* `ECS` from `resolveECSMemory`; absent for bare test Stores. */
|
|
121
|
+
capContext?: ECSMemoryCapContext;
|
|
122
|
+
/** Fired after every SAB resize (extend or grow). The new SAB has
|
|
123
|
+
* already been built and archetypes have already refreshed their
|
|
124
|
+
* views by the time this fires. Used by ECS to call
|
|
125
|
+
* `sim.setLayout(0)` so WASM-side cached pointers re-walk. */
|
|
126
|
+
onBufferResized?: () => void;
|
|
127
|
+
/** Max live entities the SAB entity-index region holds (#245 / PR 4B).
|
|
128
|
+
* Default `ENTITY_INDEX_DEFAULT_CAPACITY` (`1 << 20` — the full EntityID
|
|
129
|
+
* index space). Exceeding this at runtime throws `EID_MAX_INDEX_OVERFLOW`.
|
|
130
|
+
* Tests with small entity counts may set lower to bench the SAB region size
|
|
131
|
+
* or to make index exhaustion reachable; a 1000-entity workload fits
|
|
132
|
+
* comfortably in the default. */
|
|
133
|
+
entityIndexCapacity?: number;
|
|
134
|
+
/** Consumer-declared SAB regions (#623), forwarded verbatim to
|
|
135
|
+
* `createColumnStore`. Each `StoreRegionSpec` carries an opaque `region_id`,
|
|
136
|
+
* a precomputed byte size, and an `init` closure; the engine lays them out
|
|
137
|
+
* generically and exposes them via `regionHandle(id)` / `regionOffset(id)`.
|
|
138
|
+
* A game (e.g. `@internal/sim`'s region specs) supplies these — the engine
|
|
139
|
+
* ships no game regions of its own. Omitted ⇒ none. */
|
|
140
|
+
regions?: readonly StoreRegionSpec[];
|
|
141
|
+
/** Byte size of the opt-in sim-bindings region (#625), forwarded verbatim to
|
|
142
|
+
* `createColumnStore`. A consumer that attaches a WASM backend passes its own
|
|
143
|
+
* size (`@internal/sim`'s `SIM_BINDINGS_BYTES`, computed from the binding
|
|
144
|
+
* manifest); the host then writes the `(component_id, field_id)` IDs into the
|
|
145
|
+
* region. Omitted / 0 ⇒ no region (a pure-TS game pays nothing for the WASM
|
|
146
|
+
* seam). De-welded from the engine ABI in #625 so a manifest edit doesn't
|
|
147
|
+
* drift an engine golden. */
|
|
148
|
+
bindingsRegionBytes?: number;
|
|
149
|
+
/** Opt into the **determinism surface** (#626 / ADR-0020). Default `false`.
|
|
150
|
+
* Gates the three methods that fold/serialize state in canonical (sorted)
|
|
151
|
+
* order: `stateHash`, `snapshotSparse`, `restoreSparse`. When `false`
|
|
152
|
+
* those throw `DETERMINISM_DISABLED` — the canonical-ordering tax (sparse
|
|
153
|
+
* `canonicalIndices` sort + relation target-set sort) is never paid, and a
|
|
154
|
+
* consumer can't accidentally read a non-canonical digest. When `true`,
|
|
155
|
+
* today's behavior is reproduced bit-for-bit. This is the ONLY effect of the
|
|
156
|
+
* flag: it does not touch the per-tick path, the in-place-allocator invariant
|
|
157
|
+
* (ADR-0008, a memory-safety requirement that holds regardless), or the
|
|
158
|
+
* always-on `enabled_count` partition maintenance. The flag's value is a
|
|
159
|
+
* capability gate, not a hot-path switch — `stateHash`/snapshot are never
|
|
160
|
+
* called per tick. */
|
|
161
|
+
deterministic?: boolean;
|
|
162
|
+
}
|
|
163
|
+
export declare class Store implements ObserverHost, QueryHost {
|
|
164
|
+
private readonly entityAllocator;
|
|
165
|
+
private readonly componentMetas;
|
|
166
|
+
private componentCount;
|
|
167
|
+
private readonly sparseStores;
|
|
168
|
+
/** Debug names parallel to `sparseStores` — diagnostics only. */
|
|
169
|
+
private readonly sparseNames;
|
|
170
|
+
private readonly relationService;
|
|
171
|
+
private readonly events;
|
|
172
|
+
private readonly archGraph;
|
|
173
|
+
private readonly registeredQueries;
|
|
174
|
+
private emptyArchetypeId;
|
|
175
|
+
private entityArchetype;
|
|
176
|
+
private entityRow;
|
|
177
|
+
private readonly _deferred;
|
|
178
|
+
private readonly _snapshots;
|
|
179
|
+
_tick: number;
|
|
180
|
+
/** Per-world frame-trace sink (ADR-0030), installed via `ECS.setTrace`.
|
|
181
|
+
* `null` unless a consumer attaches a recorder. Every call site is
|
|
182
|
+
* `if (DEV) store._trace?.…`, so production builds dead-code-eliminate
|
|
183
|
+
* the seam and pay only this one nullable field. The sink observes; it never
|
|
184
|
+
* folds into `stateHash` (a scheduling artifact, like `_changedTick` / the
|
|
185
|
+
* observer state below). */
|
|
186
|
+
_trace: FrameTraceSink | null;
|
|
187
|
+
private _structuralObserverCount;
|
|
188
|
+
/** Count of components with any onDisable/onEnable observer (#677). While 0
|
|
189
|
+
* (with `_structuralObserverCount` also 0), `flushStructural` takes the
|
|
190
|
+
* byte-for-byte fast path and the toggle drain skips event collection. */
|
|
191
|
+
private _toggleObserverCount;
|
|
192
|
+
/** Reused effective-event scratch for the current flush round. */
|
|
193
|
+
private readonly _obsEvents;
|
|
194
|
+
/** Installed via `setStructuralObserverHook` — dispatches a round's collected
|
|
195
|
+
* events to the observer registry (ordering + callbacks), which may enqueue
|
|
196
|
+
* further structural ops. */
|
|
197
|
+
private _structuralObserverHook;
|
|
198
|
+
/** Install the structural-observer dispatch hook (called once by `ECS`
|
|
199
|
+
* during construction) — the named seam replacing direct writes to the
|
|
200
|
+
* previously-public field (M1). */
|
|
201
|
+
setStructuralObserverHook(fn: (ev: StructuralObserverEvents) => void): void;
|
|
202
|
+
private _collectDestroyEid;
|
|
203
|
+
private readonly _collectDestroyRemoveBit;
|
|
204
|
+
private _collectToggleEid;
|
|
205
|
+
private readonly _collectDisableBit;
|
|
206
|
+
private readonly _collectEnableBit;
|
|
207
|
+
/** Net-transition snapshot for the toggle drain (#677): entity → its disabled
|
|
208
|
+
* state at the START of the drain. Reused, cleared each drain. Lets
|
|
209
|
+
* `_flushToggles` emit one event per *net* transition (disable→enable→disable
|
|
210
|
+
* within a tick = a single onDisable) instead of one per buffered op — required
|
|
211
|
+
* because the radix canonical-order pass would otherwise reorder duplicate eids
|
|
212
|
+
* and mis-sequence a consumer's delete/republish. */
|
|
213
|
+
private readonly _toggleInitial;
|
|
214
|
+
_anyDirtyTracked: boolean;
|
|
215
|
+
private readonly _dirtyTrackedCids;
|
|
216
|
+
private readonly _dirtyLists;
|
|
217
|
+
private readonly _dirtyMarks;
|
|
218
|
+
/** Set by any path that changes a SAB-backed archetype's live row count
|
|
219
|
+
* (`flushStructural`/`flushDestroyed` when they did work; immediate
|
|
220
|
+
* `destroyEntity`, `addComponent(s)`, `removeComponent(s)` on the
|
|
221
|
+
* Store). Cleared by `publishRowCountsToDescriptor`. Lets read-only
|
|
222
|
+
* phases' `ctx.flush()` skip the descriptor walk entirely (#324). */
|
|
223
|
+
private _rowCountsDirty;
|
|
224
|
+
/** Monotonic counter bumped by every membership-changing path (immediate
|
|
225
|
+
* `addComponent(s)`, `removeComponent(s)`, `destroyEntity`,
|
|
226
|
+
* `batchAddComponent`, `batchRemoveComponent`, `flushStructural`,
|
|
227
|
+
* `flushDestroyed`, and new-archetype installs in `ArchetypeGraph.install`).
|
|
228
|
+
* Read by `Query._nonEmpty()` via `QueryResolver._getQueryDirtyEpoch`
|
|
229
|
+
* — a query whose stored `_lastSeenEpoch` matches the current epoch
|
|
230
|
+
* reuses its cached non-empty list (#327). Replaces the previous walk
|
|
231
|
+
* over `registeredQueries` that wrote one dirty bit per query per
|
|
232
|
+
* mutation; 5000 startup adds × Q queries used to be 5000×Q writes,
|
|
233
|
+
* now it's 5000 integer increments. Public so ECS can forward through
|
|
234
|
+
* its `QueryResolver` impl; not part of the user-facing API. */
|
|
235
|
+
_queryDirtyEpoch: number;
|
|
236
|
+
private readonly initialCapacity;
|
|
237
|
+
private readonly _scratchTargetMask;
|
|
238
|
+
private _columnStore;
|
|
239
|
+
/** Installed on every SAB-backed Archetype so the Archetype can
|
|
240
|
+
* request a SAB grow when an insertion would exceed its column
|
|
241
|
+
* capacity. Doubles the offending archetype's row capacity (or jumps
|
|
242
|
+
* to whatever fits `arch.length + additional`, whichever is larger),
|
|
243
|
+
* reallocs the SAB via `growColumnStore` (live rows of every archetype
|
|
244
|
+
* are carried forward), and republishes column views to every
|
|
245
|
+
* SAB-backed archetype. Plan §8.1 / §8.3 / §8.4. (#171 §6.1.9 Phase 3) */
|
|
246
|
+
private readonly _growHandler;
|
|
247
|
+
/** Build the intent-aware fatal for an allocator cap hit (#682). The
|
|
248
|
+
* allocator can only name raw bytes; the Store knows what the caller
|
|
249
|
+
* declared (`capContext`) and how many entities are live, so the error
|
|
250
|
+
* says "3.2× the declared budget — runaway creation upstream?" instead
|
|
251
|
+
* of leaving the caller to reverse-engineer byte counts. Fatality is
|
|
252
|
+
* unchanged (#380: no grow-beyond-cap fallback). */
|
|
253
|
+
private _capExceededError;
|
|
254
|
+
private readonly _bufferAllocator;
|
|
255
|
+
private readonly _capContext;
|
|
256
|
+
private readonly _onBufferResized;
|
|
257
|
+
/** Construct with an `initialCapacity` number (legacy form) or an
|
|
258
|
+
* options object (new in PR 3D — adds `bufferAllocator` and
|
|
259
|
+
* `onBufferResized` callback). Both signatures coexist so test fixtures
|
|
260
|
+
* that pass `new Store(4)` keep working. */
|
|
261
|
+
constructor(arg?: number | StoreOptions);
|
|
262
|
+
/** Capacity of the entity-index SAB region (max slots ≈ max live
|
|
263
|
+
* entities). Fixed at construction in #245's PR 4B scope; a future
|
|
264
|
+
* follow-up will grow it via `growColumnStore` when `entityHighWater`
|
|
265
|
+
* hits the cap. */
|
|
266
|
+
private readonly _entityIndexCapacity;
|
|
267
|
+
/** Consumer-declared SAB regions (#623), captured so the realloc path
|
|
268
|
+
* re-lays them out. `undefined` when no consumer regions were declared.
|
|
269
|
+
* The region contents survive a grow via the self-describing region table
|
|
270
|
+
* (`extend.ts` snapshot/restore), so this is only the layout recipe. */
|
|
271
|
+
private readonly _regions;
|
|
272
|
+
/** Byte size of the opt-in sim-bindings region (#625). 0 ⇒ no region (the
|
|
273
|
+
* pure-TS default). Captured so the initial `createColumnStore` reserves it;
|
|
274
|
+
* across a realloc the size is re-derived from the old header by
|
|
275
|
+
* `optionsFromOld`, so it is not threaded through the grow/extend path. */
|
|
276
|
+
private readonly _bindingsRegionBytes;
|
|
277
|
+
/** Determinism opt-in (#626 / ADR-0020). When `false` (the default), the
|
|
278
|
+
* canonical-ordering determinism surface (`stateHash` / `snapshotSparse` /
|
|
279
|
+
* `restoreSparse`) throws `DETERMINISM_DISABLED` rather than running its
|
|
280
|
+
* sort. Memory-safety invariants (the in-place allocator, ADR-0008) and the
|
|
281
|
+
* `enabled_count` partition are unaffected — they hold regardless. */
|
|
282
|
+
private readonly _deterministic;
|
|
283
|
+
/** Whether the determinism surface is enabled (#626). `false` ⇒ `stateHash`
|
|
284
|
+
* / `snapshotSparse` / `restoreSparse` throw `DETERMINISM_DISABLED`. */
|
|
285
|
+
get deterministic(): boolean;
|
|
286
|
+
/** Guard the canonical-ordering determinism surface. Throws
|
|
287
|
+
* `DETERMINISM_DISABLED` when determinism wasn't opted into, naming the
|
|
288
|
+
* method so the caller knows to pass `{ deterministic: true }`. Always on
|
|
289
|
+
* (not `DEV`-gated): the surface is cold (never per-tick) so one boolean
|
|
290
|
+
* check is free, and a silent non-canonical digest is the failure mode we're
|
|
291
|
+
* preventing. */
|
|
292
|
+
private _requireDeterministic;
|
|
293
|
+
/** Reject `f32`/`f64` fields on a `deterministic: true` world at registration
|
|
294
|
+
* (#777). IEEE-754 rounds differently across V8 / Bun / Zig at the 1-ULP
|
|
295
|
+
* level, so a float column in a fixed-update path is a silent per-tick
|
|
296
|
+
* `stateHash` divergence between client and server — the one thing the
|
|
297
|
+
* determinism opt-in (ADR-0020) exists to prevent. Non-deterministic worlds
|
|
298
|
+
* skip this entirely (floats stay allowed), so it costs the default path
|
|
299
|
+
* nothing. `kind` names the storage class in the error ("component" /
|
|
300
|
+
* "sparse component"); the array shorthand's `f64` default lands here too, so
|
|
301
|
+
* a deterministic world must pass an explicit integer type. */
|
|
302
|
+
private _rejectNonDeterministicFields;
|
|
303
|
+
/** Rebuild the Int32Array views over the SAB entity-index region
|
|
304
|
+
* after a host-side SAB realloc (extend / grow). Called from
|
|
305
|
+
* `_handleBufferResized` BEFORE the user-supplied `onBufferResized`
|
|
306
|
+
* callback fires so any downstream reader sees coherent views. */
|
|
307
|
+
private _refreshEntityIndexViews;
|
|
308
|
+
/** Centralised "SAB was just reallocated" handler. Refreshes the
|
|
309
|
+
* Int32Array views FIRST (so user callbacks observe valid views),
|
|
310
|
+
* then mirrors `entityHighWater` into the region's length header,
|
|
311
|
+
* then fires the user-supplied callback. */
|
|
312
|
+
private _handleBufferResized;
|
|
313
|
+
/** SAB backing every archetype's column views. Read-only handle; the
|
|
314
|
+
* live mutation happens through `archGetOrCreateFromMask`.
|
|
315
|
+
* Exposed for tests, snapshot/restore, and the upcoming
|
|
316
|
+
* `columnStoreStateHash` wire-up. Production reads of column data should
|
|
317
|
+
* still go through `Archetype.getColumnRead` (which sources from this
|
|
318
|
+
* SAB under the hood). */
|
|
319
|
+
get columnStore(): ColumnStore;
|
|
320
|
+
/** Resolve a consumer-declared SAB region's byte offset by `region_id`, or
|
|
321
|
+
* 0 when the region is absent (no region was declared with that id). The
|
|
322
|
+
* generic, de-gamed replacement (#623) for the removed game-named accessors
|
|
323
|
+
* (`terrain_view` / `spatial_grid_view` / … ); a consumer pairs this with
|
|
324
|
+
* its own region module (e.g. `@internal/sim`'s region helpers) to
|
|
325
|
+
* materialise a typed view. TS twin of Zig `abi.find_region`. */
|
|
326
|
+
regionOffset(regionId: number): number;
|
|
327
|
+
/** A handle to a consumer-declared SAB region resolved by `region_id`, or
|
|
328
|
+
* `null` when absent. Carries the live `buffer`/`view` plus the region's byte
|
|
329
|
+
* `offset` and `bytes`, so a consumer's region module can build a TypedArray
|
|
330
|
+
* view over exactly the region's span without re-reading the directory.
|
|
331
|
+
* Re-fetch after a SAB grow (the offset/view may have moved). (#623) */
|
|
332
|
+
regionHandle(regionId: number): ColumnStoreRegionHandle | null;
|
|
333
|
+
/**
|
|
334
|
+
* Stamp every SAB-backed archetype's live `length` into its descriptor's
|
|
335
|
+
* `row_count` field (#252 / Phase 4 PR 4E). `extendColumnStore` /
|
|
336
|
+
* `growColumnStore` are the only other writers of `row_count`, and they
|
|
337
|
+
* record the count at the moment of the resize — `Archetype.addEntity`
|
|
338
|
+
* does not update it, so any insertion after the most recent resize
|
|
339
|
+
* leaves the descriptor stale. Zig systems that drive their per-row loop
|
|
340
|
+
* off `arch_hdr.row_count` (every `tick_*` export added since PR 3B)
|
|
341
|
+
* read those stale bytes and silently skip the just-spawned rows.
|
|
342
|
+
*
|
|
343
|
+
* Lockstep walk: SAB descriptors are written by `extendColumnStore` in
|
|
344
|
+
* the order non-SAB archetypes are promoted, which is the same id-order
|
|
345
|
+
* those archetypes occupy in `this.archGraph.archetypes`. Iterating that array
|
|
346
|
+
* once, skipping non-SAB entries, and advancing an `archAddr` cursor
|
|
347
|
+
* by the descriptor's `column_count` lets us write `row_count` without
|
|
348
|
+
* the throwaway `Map<archId, length>` the previous version allocated
|
|
349
|
+
* on every call (#323). Cheap: descriptor-region seeks only, no column
|
|
350
|
+
* I/O.
|
|
351
|
+
*
|
|
352
|
+
* Gated by `_rowCountsDirty` (#324) — mutation paths
|
|
353
|
+
* (`flushStructural`, `flushDestroyed`, immediate `destroyEntity`,
|
|
354
|
+
* `addComponent(s)`, `removeComponent(s)`) set the flag; this method
|
|
355
|
+
* clears it. Read-only phases that flush only to drain empty buffers
|
|
356
|
+
* pay nothing. */
|
|
357
|
+
publishRowCountsToDescriptor(): void;
|
|
358
|
+
/** FNV-1a-style 32-bit digest over (archetype_id, live_row_count, live
|
|
359
|
+
* column bytes) for each archetype in id order, followed by the sparse
|
|
360
|
+
* stores (out-of-identity components, ADR-0011) in registration order.
|
|
361
|
+
* Replaces the per-networked-component fold that compute_state_hash used
|
|
362
|
+
* pre-#171 §6.1.9 Phase 5 — this is the canonical "live ECS state digest"
|
|
363
|
+
* for cross-replay determinism.
|
|
364
|
+
*
|
|
365
|
+
* **Sparse coverage (#470).** Sparse data lives outside the archetype
|
|
366
|
+
* graph, so it is folded separately after the archetype loop — per store:
|
|
367
|
+
* the sparse-component id, the member count, then each member's source
|
|
368
|
+
* entity index + f64 field words, walked in CANONICAL ascending-index order
|
|
369
|
+
* (`SparseComponentStore.canonicalIndices`). Canonical order is what makes
|
|
370
|
+
* the digest insertion-order-independent: two worlds with identical sparse
|
|
371
|
+
* contents built by different add/remove sequences agree. Keyed by entity
|
|
372
|
+
* index, and destruction purges the slot, so a recycled index never carries
|
|
373
|
+
* a stale occupant's data into the hash.
|
|
374
|
+
*
|
|
375
|
+
* It is strictly broader than the prior per-networked-component fold
|
|
376
|
+
* (covers every column, not just a hand-picked subset of networked
|
|
377
|
+
* components), and strictly tighter than `columnStoreStateHash(...)`
|
|
378
|
+
* which scans the full SAB including trailing unused capacity.
|
|
379
|
+
*
|
|
380
|
+
* **Per-word fold (#326).** The inner column loop folds one 32-bit
|
|
381
|
+
* word at a time using FNV-1a's `xor + imul(PRIME)` step. This is NOT
|
|
382
|
+
* byte-for-byte FNV-1a-32 of the column bytes — it's a deterministic
|
|
383
|
+
* digest with the same equality semantics, ~4× faster than the per-
|
|
384
|
+
* byte loop it replaces. Trailing 0–3 tail bytes (only possible for
|
|
385
|
+
* u8/u16 columns at odd row counts) are folded together as a single
|
|
386
|
+
* little-endian word so the algorithm stays branch-free in the inner
|
|
387
|
+
* loop. The 4-byte `id` and `len` headers are folded as words for the
|
|
388
|
+
* same reason. Byte order is little-endian to match the platform's
|
|
389
|
+
* native TypedArray layout; the digest is opaque (no consumer compares
|
|
390
|
+
* against a literal value), so endianness is an implementation detail
|
|
391
|
+
* rather than wire contract.
|
|
392
|
+
*
|
|
393
|
+
* Determinism: same store ⇒ same digest within a process, and across
|
|
394
|
+
* processes on the same architecture (which is all `replay_match`
|
|
395
|
+
* needs — both replays run the same algorithm on the same words).
|
|
396
|
+
*
|
|
397
|
+
* **Opt-in (#626 / ADR-0020).** Throws `DETERMINISM_DISABLED` unless the
|
|
398
|
+
* Store was constructed with `{ deterministic: true }`. The canonical
|
|
399
|
+
* ordering this fold relies on (sparse `canonicalIndices`, sorted relation
|
|
400
|
+
* target sets) is the determinism tax the flag gates. */
|
|
401
|
+
stateHash(): number;
|
|
402
|
+
private archGet;
|
|
403
|
+
/** Look up the `EntityID` at `row` in archetype `archetype_id`. Used
|
|
404
|
+
* by a WASM system to resolve an
|
|
405
|
+
* `EntityID` from an event-ring payload — Zig writes
|
|
406
|
+
* `(archId, row, …)` to the event ring,
|
|
407
|
+
* and TS bridges it back through `ctx.emit(...)` via this method. (#250 /
|
|
408
|
+
* Phase 4 PR 4D)
|
|
409
|
+
*
|
|
410
|
+
* Throws `ECSError` if `archetype_id` is out of range or `row` is
|
|
411
|
+
* past the archetype's live row count — these would indicate a
|
|
412
|
+
* ring-payload corruption or a stale row index (extend / grow
|
|
413
|
+
* happened mid-tick), both of which are bugs the parity test would
|
|
414
|
+
* surface. */
|
|
415
|
+
entityIdAtRow(archetypeId: number, row: number): EntityID;
|
|
416
|
+
/** Find or create an archetype for the given component mask — see
|
|
417
|
+
* `ArchetypeGraph.getOrCreateFromMask`. */
|
|
418
|
+
private archGetOrCreateFromMask;
|
|
419
|
+
/** Bulk variant of `archGetOrCreateFromMask` — one `extendColumnStore`
|
|
420
|
+
* call for the whole batch (Phase C prewarm, #213). See
|
|
421
|
+
* `ArchetypeGraph.createManyFromMasks`. */
|
|
422
|
+
archCreateManyFromMasks(masks: readonly BitSet[]): ArchetypeID[];
|
|
423
|
+
/** Snapshot every existing archetype's SAB rows, call `extendColumnStore`
|
|
424
|
+
* once with `newSpecs`, then refresh every pre-existing SAB-backed
|
|
425
|
+
* Archetype's TypedArray views. The single `existing` snapshot is the
|
|
426
|
+
* key win in the bulk variant — single-mask creation rebuilds it per
|
|
427
|
+
* call (i.e. N times for N new archetypes). */
|
|
428
|
+
private _archExtendStoreWithNewSpecs;
|
|
429
|
+
/** Materialise the `Archetype` object for a freshly-minted graph node —
|
|
430
|
+
* binds the graph's topology to THIS store's column backing and grow
|
|
431
|
+
* handler (`ArchetypeGraphHost.materialize`). Store-owned so the graph
|
|
432
|
+
* never touches `_columnStore`. */
|
|
433
|
+
private _materializeArchetype;
|
|
434
|
+
/** Push a newly-installed archetype into every registered query whose masks
|
|
435
|
+
* it satisfies (`ArchetypeGraphHost.fanIntoQueries`; the query registry
|
|
436
|
+
* stays on Store). No epoch bump — see the note in `ArchetypeGraph.install`. */
|
|
437
|
+
private _fanIntoQueries;
|
|
438
|
+
/** Resolve "add component_id to archetype_id" → target ArchetypeID (edge-cached). */
|
|
439
|
+
private archResolveAdd;
|
|
440
|
+
/** Resolve "remove component_id from archetype_id" → target ArchetypeID (edge-cached). */
|
|
441
|
+
private archResolveRemove;
|
|
442
|
+
createEntity(): EntityID;
|
|
443
|
+
/** Allocate an entity slot WITHOUT placing it in the empty archetype, for
|
|
444
|
+
* the template spawn paths. Returns the packed `EntityID`; the slot index
|
|
445
|
+
* is left in `entityAllocator.lastIndex`. Skips the empty-archetype
|
|
446
|
+
* membership write `createEntity` performs (the caller installs the real
|
|
447
|
+
* archetype + row). This *commits* the slot (bumps counts, stamps the
|
|
448
|
+
* generation so `isAlive` is already true), so the caller MUST have
|
|
449
|
+
* reserved the column capacity for the row first
|
|
450
|
+
* (`Archetype.ensureRowCapacity`) — otherwise a cap throw from the
|
|
451
|
+
* subsequent append leaves the slot phantom-alive (#775). */
|
|
452
|
+
private _allocEntity;
|
|
453
|
+
/** Pre-check that `count` fresh entity slots can be allocated without
|
|
454
|
+
* exhausting the entity-index space, so `spawnMany` commits all-or-nothing
|
|
455
|
+
* (#775). `_allocEntity`'s own per-call high-water guard would otherwise
|
|
456
|
+
* throw `EID_MAX_INDEX_OVERFLOW` partway through the alloc loop, leaving the
|
|
457
|
+
* slots it already committed phantom-alive. Free-list reuse covers the first
|
|
458
|
+
* `entityFreeIndices.length` slots; only the remainder draws down the
|
|
459
|
+
* high-water headroom. */
|
|
460
|
+
private _ensureEntityIndexCapacity;
|
|
461
|
+
/** Resolve a template: compute the target archetype (creating it if absent —
|
|
462
|
+
* fits the prewarm model), pre-flatten default field values into
|
|
463
|
+
* `_flatColumns` order, and build the override index (field name → flat
|
|
464
|
+
* column index; `TEMPLATE_OVERRIDE_AMBIGUOUS` for a name shared by more than
|
|
465
|
+
* one component, which a flat override cannot target). */
|
|
466
|
+
resolveTemplate<Defs extends readonly ComponentDef[]>(entries: TemplateEntries<Defs>): Template<Defs>;
|
|
467
|
+
/** Resolve an override key to its flat column index, with the DEV guards
|
|
468
|
+
* for unknown and ambiguous field names; `-1` means skip (the production
|
|
469
|
+
* fallback where DEV would have thrown). */
|
|
470
|
+
private _resolveOverrideColumn;
|
|
471
|
+
/** Apply per-instance overrides to the freshly-spawned row. Each key is a
|
|
472
|
+
* field name resolved through the template's override index. */
|
|
473
|
+
private _applyOverrides;
|
|
474
|
+
/** Bulk variant of `_applyOverrides`: one `fill` per overridden column
|
|
475
|
+
* across the contiguous rows `[start, start + count)`. */
|
|
476
|
+
private _applyOverridesRange;
|
|
477
|
+
/** Spawn one entity directly into the template's archetype (zero archetype
|
|
478
|
+
* transitions). Writes the template defaults in a single append pass, then
|
|
479
|
+
* applies any per-instance overrides. */
|
|
480
|
+
spawn(p: Template, overrides?: Record<string, number | undefined>): EntityID;
|
|
481
|
+
/** Bulk-spawn `count` identical entities into the template's archetype. The
|
|
482
|
+
* field writes are O(columns) — one `TypedArray.fill` per column via
|
|
483
|
+
* `addEntitiesWithValues` — not O(count×columns). Returns the new ids in
|
|
484
|
+
* spawn order. */
|
|
485
|
+
spawnMany(p: Template, count: number, overrides?: Record<string, number | undefined>): EntityID[];
|
|
486
|
+
/** Immediately destroy an entity, removing it from its archetype.
|
|
487
|
+
*
|
|
488
|
+
* With no `delete`/`clear` target-cleanup policy registered (the common
|
|
489
|
+
* case) this tears the one entity down and returns — no allocation. When a
|
|
490
|
+
* policy is in play, a `delete`-target's sources are appended to a local
|
|
491
|
+
* work-list this method then drains in the same iterative pass (#473, #492):
|
|
492
|
+
* the `work.length` re-read drives chains and trees out without recursion, so
|
|
493
|
+
* depth is bounded by entity count, not tree depth. This mirrors the deferred
|
|
494
|
+
* `flushDestroyed` buffer mechanism — both paths are iterative and reach the
|
|
495
|
+
* identical end state; the only difference is the shared `pendingDestroy`
|
|
496
|
+
* buffer there vs. a local work-list here. `isAlive` dedups a source reached
|
|
497
|
+
* twice (diamonds) and terminates cycles, exactly as the generation guard does
|
|
498
|
+
* in the deferred loop. */
|
|
499
|
+
destroyEntity(id: EntityID): void;
|
|
500
|
+
/** Tear a single entity out of its archetype, relation, and sparse stores,
|
|
501
|
+
* then recycle (or retire) its slot. Shared by both immediate-destroy entry
|
|
502
|
+
* points (the fast no-cascade path and the work-list driver in
|
|
503
|
+
* `destroyEntity`). When `cascade` is non-null, a `delete`-policy target's
|
|
504
|
+
* surviving sources are appended to it for the driver to drain (#473, #492);
|
|
505
|
+
* `null` skips that collection for callers that cannot cascade. The caller
|
|
506
|
+
* must have already confirmed `id` is alive. */
|
|
507
|
+
private _destroyOne;
|
|
508
|
+
/**
|
|
509
|
+
* Liveness check, **fail-closed** against forged / retired / out-of-bounds
|
|
510
|
+
* handles (#778). For a general-purpose engine that may receive a handle from
|
|
511
|
+
* serialization, IPC, or any untrusted caller, three malformed inputs must read
|
|
512
|
+
* dead rather than alias a slot:
|
|
513
|
+
* - **Out of range** — an `id` outside the 31-bit packed space (`< 0` or
|
|
514
|
+
* `> MAX_ENTITY_ID`). Without this, the 20-bit index mask below silently
|
|
515
|
+
* folds garbage high bits onto a valid slot. (Same bound the snapshot /
|
|
516
|
+
* postMessage decode applies, #723.)
|
|
517
|
+
* - **Tombstone generation** — a handle carrying `RETIRED_GENERATION`, which
|
|
518
|
+
* the allocator stamps into a retired slot and never issues to a live
|
|
519
|
+
* entity, would otherwise match a retired slot's parked generation and read
|
|
520
|
+
* alive (the ABA tombstone, previously documented as a known gap).
|
|
521
|
+
* Both guards are comparisons predicted not-taken on the live path, so a
|
|
522
|
+
* well-formed handle pays two branches and nothing else (#778 measured).
|
|
523
|
+
*/
|
|
524
|
+
isAlive(id: EntityID): boolean;
|
|
525
|
+
get entityCount(): number;
|
|
526
|
+
/** An archetype's row count moved from `preLen` to its current
|
|
527
|
+
* `arch.length` on a **shrink** (rows removed: the source of a transition, a
|
|
528
|
+
* destroy, a batch-source drain). Always marks SAB row counts dirty (#324 —
|
|
529
|
+
* the descriptor walk just needs "something moved"); bumps the query-dirty
|
|
530
|
+
* epoch (#327) only on a `length` 0/non-zero crossing, the only case where
|
|
531
|
+
* `Query._nonEmptyArchetypes` can change on a shrink (#328). Mutations that
|
|
532
|
+
* move row counts within the same side (6→5) leave the non-empty set unchanged
|
|
533
|
+
* and skip the bump.
|
|
534
|
+
*
|
|
535
|
+
* A shrink does **not** need the `enabledCount` crossing test (#812): the
|
|
536
|
+
* only enabled-count move it can make is 1→0 (the last enabled row leaves an
|
|
537
|
+
* archetype that keeps disabled rows), which leaves the archetype in a default
|
|
538
|
+
* query's non-empty list as a harmless stale *inclusion* — `count`/`forEach`
|
|
539
|
+
* bound on `enabledCount` (now 0) iterate it zero times. Only a **grow** into
|
|
540
|
+
* an all-disabled archetype can stale-*exclude* a live row, so the enabled
|
|
541
|
+
* crossing lives in `_onArchGrow`, off this path.
|
|
542
|
+
*
|
|
543
|
+
* **Inlining-sensitive — keep the body tiny.** This function is called
|
|
544
|
+
* once or twice per immediate-mode `addComponent` / `removeComponent` and the
|
|
545
|
+
* mutation hot path depends on it being inlined at every call site.
|
|
546
|
+
* #351 / PR #353 added an `if (registeredQueries.length === 0) return;`
|
|
547
|
+
* gate to skip the bump for no-query workloads — bench showed it
|
|
548
|
+
* regressed `mutation: churn_loop` 16-18% because the extra statement
|
|
549
|
+
* pushed the function past V8's per-call inlining budget. Reverted in
|
|
550
|
+
* PR #355. Any future change here needs a `bench-vs-commit` run before
|
|
551
|
+
* merging, not just code review. */
|
|
552
|
+
private _onArchLenChange;
|
|
553
|
+
/** An archetype **grew** — rows were appended (the target of a transition, a
|
|
554
|
+
* spawn, a batch-target fill). Like `_onArchLenChange` it marks row counts
|
|
555
|
+
* dirty and bumps the query-dirty epoch on a `length` 0/non-zero crossing
|
|
556
|
+
* (`includeDisabled` membership), but it *also* bumps on an `enabledCount`
|
|
557
|
+
* 0→1 crossing (#812). The non-empty filter is field-split (#577): a default
|
|
558
|
+
* query keeps archetypes with `enabledCount > 0`. An enabled row appended to
|
|
559
|
+
* an archetype that is non-empty but all-disabled (`length > 0,
|
|
560
|
+
* enabledCount == 0`) crosses `enabledCount` 0→1 without touching `length`,
|
|
561
|
+
* so the `preLen` test alone (the valid-while-`enabledCount === length`
|
|
562
|
+
* pre-#577 proxy) misses it and a cached default query keeps a stale
|
|
563
|
+
* `_nonEmpty` list. Only grows can do this, so only grow sites carry the test.
|
|
564
|
+
*
|
|
565
|
+
* **Precondition: ≥1 row was appended** (every caller adds at least one row),
|
|
566
|
+
* so `arch.length > 0` afterward — which is why the crossings simplify and the
|
|
567
|
+
* body stays inlinable (the inlining caveat on `_onArchLenChange` applies
|
|
568
|
+
* here too; verified with `bench-vs-commit mutation`). The general
|
|
569
|
+
* `(pre === 0) !== (post === 0)` boundary test collapses given the post side:
|
|
570
|
+
* - `length`: post > 0 always ⇒ a crossing iff `preLen === 0`.
|
|
571
|
+
* - `enabledCount`: non-decreasing on a grow ⇒ a 0-crossing iff it was 0
|
|
572
|
+
* before and is non-zero now (`preEnabled === 0 && enabledCount !== 0`);
|
|
573
|
+
* a disabled-row append leaves it 0 and correctly skips. The `enabledCount`
|
|
574
|
+
* read is short-circuited away on the hot path (`preLen` or `preEnabled`
|
|
575
|
+
* non-zero), so a no-disabled workload pays only two scalar compares. */
|
|
576
|
+
private _onArchGrow;
|
|
577
|
+
/** Dirty bookkeeping for an enable/disable toggle (#577). `length` is
|
|
578
|
+
* unchanged (no row added/removed) but `enabled_count` moved, so: republish
|
|
579
|
+
* row counts (the descriptor's `enabled_count` changed, so the WASM sim and
|
|
580
|
+
* snapshot see the new partition), and bump the query epoch only when the
|
|
581
|
+
* *enabled* count crossed 0 — the boundary at which an archetype enters/leaves
|
|
582
|
+
* a query's non-empty set (`Query._nonEmpty` filters on `entityCount`, which
|
|
583
|
+
* is now `enabled_count`). */
|
|
584
|
+
private _onArchEnabledChange;
|
|
585
|
+
/** Immediately disable an entity (idempotent). The entity must hold at least
|
|
586
|
+
* one component — a component-less entity occupies no archetype row, so it
|
|
587
|
+
* cannot be partitioned (a `DEV` error; prod no-op). */
|
|
588
|
+
disableEntity(id: EntityID): void;
|
|
589
|
+
/** Immediately enable an entity (idempotent). */
|
|
590
|
+
enableEntity(id: EntityID): void;
|
|
591
|
+
/** Whether `id` is currently disabled. A component-less entity is never
|
|
592
|
+
* disabled (it has no row to partition). */
|
|
593
|
+
isDisabled(id: EntityID): boolean;
|
|
594
|
+
/** 0-crossing detection for the per-entity flush paths (`_flushAdds`,
|
|
595
|
+
* `_flushRemoves`) without per-entity Map traffic — the same cost the
|
|
596
|
+
* destroy drain shed in #457. Each touched archetype is stamped with the
|
|
597
|
+
* current flush epoch (`Archetype._flushSeenEpoch`), its pre-length and
|
|
598
|
+
* pre-enabled-count recorded on first sight (`_flushPreLen` /
|
|
599
|
+
* `_flushPreEnabled`), and pushed onto this scratch list;
|
|
600
|
+
* `_settleFlushDirty` walks the list once after the loop. The field
|
|
601
|
+
* accesses per entity replace a `Map.has` + `Map.set` hash probe pair. The
|
|
602
|
+
* epoch is bumped at settle so the next flush re-records. */
|
|
603
|
+
private _flushEpoch;
|
|
604
|
+
private readonly _flushTouched;
|
|
605
|
+
/** Resolve dirty flags for a per-entity batch flush from the captured
|
|
606
|
+
* pre-counts. Marks row counts dirty if any archetype was touched; bumps
|
|
607
|
+
* the query epoch once if any touched archetype crossed the 0 boundary on
|
|
608
|
+
* *either* `length` (includeDisabled membership) or `enabledCount`
|
|
609
|
+
* (default-query membership) — the deferred analog of the immediate
|
|
610
|
+
* `_onArchLenChange` two-field check (#812). A single bump is sufficient
|
|
611
|
+
* (queries only need to know "something changed"). Clears the touched list
|
|
612
|
+
* and advances the flush epoch on exit. */
|
|
613
|
+
private _settleFlushDirty;
|
|
614
|
+
destroyEntityDeferred(id: EntityID): void;
|
|
615
|
+
/** Buffer an enable/disable toggle for the phase flush (#577). The row swap a
|
|
616
|
+
* toggle performs would corrupt a `forEach` over that archetype if applied
|
|
617
|
+
* mid-system, so it is deferred like add/remove. */
|
|
618
|
+
disableEntityDeferred(id: EntityID): void;
|
|
619
|
+
enableEntityDeferred(id: EntityID): void;
|
|
620
|
+
/** Drain buffered enable/disable toggles, applying each in operation order via
|
|
621
|
+
* the immediate path (which is idempotent and updates dirty flags). Called at
|
|
622
|
+
* the flush boundary after structural adds/removes settle, so a toggle sees the
|
|
623
|
+
* entity's final archetype placement for the tick.
|
|
624
|
+
*
|
|
625
|
+
* When an onDisable/onEnable observer is registered (`_toggleObserverCount >
|
|
626
|
+
* 0`, #677) this also collects effective toggle events into `_obsEvents` for
|
|
627
|
+
* the dispatch hook, collapsed to one event per *net* transition across the
|
|
628
|
+
* drain (see `_toggleInitial`). The no-observer path is byte-for-byte the
|
|
629
|
+
* pre-#677 drain. */
|
|
630
|
+
private _flushToggles;
|
|
631
|
+
/** Fan one entity's net toggle transition out to an onDisable / onEnable event
|
|
632
|
+
* per carried component (#677). Walks the entity's archetype mask through the
|
|
633
|
+
* matching pre-bound bit visitor; a component-less entity (no row) carries
|
|
634
|
+
* nothing and is skipped. */
|
|
635
|
+
private _collectToggle;
|
|
636
|
+
get pendingToggleCount(): number;
|
|
637
|
+
/** Flush all buffered entity destructions in batch.
|
|
638
|
+
*
|
|
639
|
+
* When onRemove observers are registered (`_structuralObserverCount > 0`),
|
|
640
|
+
* a destroy fires onRemove for every component the entity carried — a destroy
|
|
641
|
+
* *is* a remove of the whole mask — collected here and dispatched by the
|
|
642
|
+
* `flushStructural` fixed-point loop, the only caller in that mode (it drains
|
|
643
|
+
* `pendingDestroy` each round so the trailing `ctx.flush()` call is a no-op).
|
|
644
|
+
* Same commit-then-observe discipline as `_flushRemoves`: the entity is fully
|
|
645
|
+
* freed before the callback runs, so onRemove receives the (now dead) eid as
|
|
646
|
+
* the identity of what was destroyed, not a live handle to read. The
|
|
647
|
+
* no-observer path is byte-for-byte unchanged (`collecting` gate). #531.
|
|
648
|
+
*
|
|
649
|
+
* Re-entrancy: while the observed fixed point owns the flush, the loop
|
|
650
|
+
* drains destroys itself via `_drainDestroyed`, so a re-entrant
|
|
651
|
+
* `ctx.flush()` from a callback no-ops (the guard lives in
|
|
652
|
+
* `DeferredCommandBuffer.flushDestroyed`) — otherwise it would collect
|
|
653
|
+
* into the shared `_obsEvents` scratch mid-dispatch and corrupt it. */
|
|
654
|
+
flushDestroyed(): void;
|
|
655
|
+
private _drainDestroyed;
|
|
656
|
+
get pendingDestroyCount(): number;
|
|
657
|
+
addComponentDeferred(entityId: EntityID, def: ComponentDef<Record<string, never>>): void;
|
|
658
|
+
addComponentDeferred<S extends ComponentSchema>(entityId: EntityID, def: ComponentDef<S>, values: FieldValues<S>): void;
|
|
659
|
+
removeComponentDeferred(entityId: EntityID, def: ComponentDef): void;
|
|
660
|
+
/** Phase-boundary structural flush. The drain policy — no-observer fast
|
|
661
|
+
* path, observed fixed point (adds/removes → destroys → toggles),
|
|
662
|
+
* convergence guard, re-entrancy — lives in `DeferredCommandBuffer`
|
|
663
|
+
* (H1 step 4); the batch appliers it drives are the `_flush*` /
|
|
664
|
+
* `_drainDestroyed` methods below. */
|
|
665
|
+
flushStructural(): void;
|
|
666
|
+
/** Batch-apply all deferred component additions. */
|
|
667
|
+
private _flushAdds;
|
|
668
|
+
/** Batch-apply all deferred component removals. */
|
|
669
|
+
private _flushRemoves;
|
|
670
|
+
get pendingStructuralCount(): number;
|
|
671
|
+
/** Set the per-component observation flags from the registry's aggregate of
|
|
672
|
+
* live observers for `cid`. Maintains `_structuralObserverCount` and
|
|
673
|
+
* `_toggleObserverCount` (#677) (the fast-path gates) and lazily allocates the
|
|
674
|
+
* dirty list when per-entity onSet tracking turns on. */
|
|
675
|
+
_configureComponentObservation(cid: number, hasAdd: boolean, hasRem: boolean, hasDisable: boolean, hasEnable: boolean, trackDirty: boolean): void;
|
|
676
|
+
/** Record a per-entity onSet "changed" event for the entity. Called from the
|
|
677
|
+
* field-write path (`SystemContext.setField` / `markChanged`) and gated by
|
|
678
|
+
* the caller on `_anyDirtyTracked`. Appends to the dirty list only if the
|
|
679
|
+
* dedup bit was clear (the ADR-0012 list + dedup-bit mechanism). */
|
|
680
|
+
_noteSet(def: ComponentHandle, eid: EntityID): void;
|
|
681
|
+
private _growDirtyMarks;
|
|
682
|
+
/** Detach and return the dirty-row list for `cid`, clearing its dedup bits and
|
|
683
|
+
* leaving the store with a fresh empty list (so re-dirties during the drain
|
|
684
|
+
* accumulate for the NEXT tick, not this one). Returns a shared empty array
|
|
685
|
+
* when nothing is dirty. Caller owns the returned array. */
|
|
686
|
+
_takeDirty(cid: number): EntityID[];
|
|
687
|
+
/** Clear any dirty dedup bits for a freed entity index across every tracked
|
|
688
|
+
* component, so a recycled slot at the same index can be marked afresh. Gated
|
|
689
|
+
* by `_anyDirtyTracked` at the destroy call sites. */
|
|
690
|
+
private _clearDirtyForIndex;
|
|
691
|
+
/** Visit every non-empty archetype containing `cid` whose component-column
|
|
692
|
+
* changed at or after `baseline`, in canonical (ascending archetype-id) order
|
|
693
|
+
* — the archetype-granular onSet detection point. Reuses the existing
|
|
694
|
+
* per-archetype change tick (free; no write-path cost). */
|
|
695
|
+
_forEachChangedArchetype(cid: number, baseline: number, cb: (arch: Archetype) => void): void;
|
|
696
|
+
/** Enabled live entities currently carrying `cid`, used by `yieldExisting` to
|
|
697
|
+
* replay onAdd on registration. Bounded by `enabled_count` (#677): a disabled
|
|
698
|
+
* entity is excluded from default queries, so seeding it via onAdd would
|
|
699
|
+
* publish a row that an immediate onDisable should have removed — it is simply
|
|
700
|
+
* absent at seed (the "delete on disable" semantics). Unordered here — the
|
|
701
|
+
* registry radix-sorts. */
|
|
702
|
+
_collectEntitiesWithComponent(cid: number): EntityID[];
|
|
703
|
+
registerComponent<S extends Record<string, TypedArrayTag>>(schema: S, name?: string): ComponentDef<S>;
|
|
704
|
+
/** `'Pos' (component 5)` when the component was registered with a debug
|
|
705
|
+
* name, else `component 5` — the label diagnostics interpolate. */
|
|
706
|
+
componentLabel(cid: number): string;
|
|
707
|
+
/** Return the field index assigned to `(def, fieldName)` at component
|
|
708
|
+
* registration. Indexes are insertion-order, zero-based, and stable for
|
|
709
|
+
* the lifetime of the ECS. Used by systems that pass `(component_id,
|
|
710
|
+
* field_id)` pairs across the WASM FFI (PR 3C / #231). */
|
|
711
|
+
fieldIdOf(def: ComponentHandle, fieldName: string): number;
|
|
712
|
+
/** Register a sparse component or tag. Unlike `registerComponent`, this
|
|
713
|
+
* allocates from a separate id space and never touches the archetype mask,
|
|
714
|
+
* so it does **not** count against `STORE_DESCRIPTOR_COMPONENT_LIMIT`. See
|
|
715
|
+
* ADR-0011 and `sparse_store.ts`. */
|
|
716
|
+
registerSparseComponent<S extends Record<string, TypedArrayTag>>(schema: S, name?: string): SparseComponentDef<S>;
|
|
717
|
+
/** Sparse sibling of `componentLabel` — sparse ids are a separate id space. */
|
|
718
|
+
sparseLabel(sid: number): string;
|
|
719
|
+
/** Allocate the backing sparse store WITHOUT the #777 float guard, for
|
|
720
|
+
* engine-internal backings whose `f64` holds an EXACT integer rather than a
|
|
721
|
+
* user quantity: the exclusive-relation `{ target }` slot stores an `EntityID`
|
|
722
|
+
* (≤ 2^53, so f64 is bit-exact and cross-host identical — the ban targets float
|
|
723
|
+
* *arithmetic* rounding, which a target slot never undergoes). User schemas go
|
|
724
|
+
* through `registerSparseComponent`, which guards first. */
|
|
725
|
+
private _pushSparseStore;
|
|
726
|
+
private sparseStoreOf;
|
|
727
|
+
/** Add (or overwrite) a sparse component on an entity. No archetype
|
|
728
|
+
* transition, no row copy — the entity's `archetype_id` is unchanged. */
|
|
729
|
+
addSparse(entityId: EntityID, def: SparseComponentDef, values?: Record<string, number>): void;
|
|
730
|
+
/** Remove a sparse component from an entity. No-op if absent. */
|
|
731
|
+
removeSparse(entityId: EntityID, def: SparseComponentDef): void;
|
|
732
|
+
/** Total, like `hasComponent` — `false` for a dead entity, never a throw. */
|
|
733
|
+
hasSparse(entityId: EntityID, def: SparseComponentDef): boolean;
|
|
734
|
+
getSparseField(entityId: EntityID, def: SparseComponentDef, field: string): number;
|
|
735
|
+
setSparseField(entityId: EntityID, def: SparseComponentDef, field: string, value: number): void;
|
|
736
|
+
/** Drop all sparse data for a destroyed entity index so a recycled slot
|
|
737
|
+
* can't inherit it. Gated by the caller on `sparseStores.length > 0` to
|
|
738
|
+
* keep the destroy hot path free when sparse storage is unused. */
|
|
739
|
+
private _purgeSparse;
|
|
740
|
+
/** Serialize the sparse stores **and** relation side data to a self-contained
|
|
741
|
+
* byte buffer — the sparse half of a world snapshot (the dense half is the
|
|
742
|
+
* SAB snapshot). Two framed sections: the sparse stores (`snapshot_sparse_-
|
|
743
|
+
* stores` — exclusive relation targets + multi membership ride here) followed
|
|
744
|
+
* by the relation side data (`snapshotRelations` — multi forward target
|
|
745
|
+
* sets, which live outside the sparse store). Both are written in canonical
|
|
746
|
+
* entity-index order, so two worlds with identical contents inserted in
|
|
747
|
+
* different orders snapshot byte-for-byte the same (#470). The reverse index
|
|
748
|
+
* is derived and never serialized — `restoreSparse` rebuilds it. Pairs with
|
|
749
|
+
* `restoreSparse`.
|
|
750
|
+
*
|
|
751
|
+
* **Opt-in (#626 / ADR-0020).** Throws `DETERMINISM_DISABLED` unless the
|
|
752
|
+
* Store was constructed with `{ deterministic: true }` — the canonical
|
|
753
|
+
* entity-index ordering is the determinism tax the flag gates. */
|
|
754
|
+
snapshotSparse(): Uint8Array;
|
|
755
|
+
/** Repopulate the sparse stores from `snapshotSparse` bytes, replacing all
|
|
756
|
+
* current sparse data (full-equality round-trip of membership + data), then
|
|
757
|
+
* rebuild every relation's derived side indices: multi forward sets from the
|
|
758
|
+
* relation section, and the reverse index for both cardinalities (exclusive
|
|
759
|
+
* from the just-restored sparse target field, multi from the rebuilt forward
|
|
760
|
+
* sets). The sparse components and relations must already be registered in
|
|
761
|
+
* the same order — restore carries data, not the registration (which is
|
|
762
|
+
* code). Throws `SparseRestoreError` if the snapshot's shape, field identity,
|
|
763
|
+
* entity-index bounds, or frame length don't validate.
|
|
764
|
+
*
|
|
765
|
+
* **Opt-in (#626 / ADR-0020).** Throws `DETERMINISM_DISABLED` unless the
|
|
766
|
+
* Store was constructed with `{ deterministic: true }`; paired with
|
|
767
|
+
* `snapshotSparse`, which produces the canonical bytes restore consumes. */
|
|
768
|
+
restoreSparse(bytes: Uint8Array): void;
|
|
769
|
+
/**
|
|
770
|
+
* Capture the full live world to one self-contained byte buffer that
|
|
771
|
+
* `restoreInto` can mount back onto a live, ticking world ("rewind a running
|
|
772
|
+
* world and keep ticking"). Three sections (see `resume.ts`): the dense SAB
|
|
773
|
+
* column bytes (`snapshotColumnStore`), the sparse + relation bytes
|
|
774
|
+
* (`snapshotSparse`), and the host-side bookkeeping the SAB omits — the world
|
|
775
|
+
* tick, the entity recycle free-list (in live order; no byte source, and its
|
|
776
|
+
* order is load-bearing for byte-identical resume), the alive count, and each
|
|
777
|
+
* archetype's `length` / `enabledCount`.
|
|
778
|
+
*
|
|
779
|
+
* **Opt-in (ADR-0020).** Throws `DETERMINISM_DISABLED` unless constructed with
|
|
780
|
+
* `{ deterministic: true }` — the sparse section rides the canonical-ordering
|
|
781
|
+
* surface and byte-identical resume is a determinism property. Pairs with
|
|
782
|
+
* `restoreInto`.
|
|
783
|
+
*
|
|
784
|
+
* **v1 scope.** Resources + events are NOT captured (resume requires
|
|
785
|
+
* resource-free per-tick state; events are tick-cleared). Change-detection /
|
|
786
|
+
* scheduler baselines (`changed()` queries) are likewise not captured — they
|
|
787
|
+
* are scheduling artifacts, never folded into `stateHash`. Take the snapshot
|
|
788
|
+
* at a tick boundary (between `update()`s). See the ADR. */
|
|
789
|
+
snapshot(): Uint8Array;
|
|
790
|
+
/**
|
|
791
|
+
* Mount a `snapshot()` buffer onto this live world and leave it ready to keep
|
|
792
|
+
* ticking. Fails closed on a malformed frame or a registration mismatch
|
|
793
|
+
* BEFORE any live state is touched (the archetype/component graph is rebuilt
|
|
794
|
+
* from code, not the snapshot — same contract as `restoreSparse`). On
|
|
795
|
+
* success the world's dense + sparse state, entity allocator, and tick are
|
|
796
|
+
* exactly the captured world's.
|
|
797
|
+
*
|
|
798
|
+
* Requires a world whose SAB-backed archetype set + column layout match the
|
|
799
|
+
* snapshot's exactly (prewarm so the archetype set is stable) and the same
|
|
800
|
+
* entity-index capacity. **Opt-in (ADR-0020):** throws `DETERMINISM_DISABLED`
|
|
801
|
+
* unless `{ deterministic: true }`. See `snapshot()` for the v1 scope. */
|
|
802
|
+
restoreInto(bytes: Uint8Array): void;
|
|
803
|
+
/** Adopt a restored dense store (`SnapshotService.restoreInto`'s mount
|
|
804
|
+
* step): swap the live backing, refresh every buffer-backed archetype's
|
|
805
|
+
* views, recover the allocator high-water from the restored region, and
|
|
806
|
+
* republish (the grow tail). Store-owned because it assigns
|
|
807
|
+
* `_columnStore` — the service never writes Store fields. */
|
|
808
|
+
private _mountRestoredDense;
|
|
809
|
+
/** Rebuild each SAB-backed archetype's host-side `length` / `enabledCount` /
|
|
810
|
+
* `_entityIds` after the dense backing was swapped in `restoreInto`. `length`
|
|
811
|
+
* + the per-row entity-id back-reference come from a scan of the restored
|
|
812
|
+
* entity-index region (which entity occupies which row); `enabledCount` comes
|
|
813
|
+
* from the captured host-state (the #577 partition boundary is positional only
|
|
814
|
+
* — it has no per-entity byte source). */
|
|
815
|
+
private _reconstructHostRows;
|
|
816
|
+
registerRelation(opts?: RelationOptions): RelationDef;
|
|
817
|
+
/** Number of registered relations. Visible to tests asserting the
|
|
818
|
+
* no-transition invariant alongside `archetype_count`. */
|
|
819
|
+
get relationCount(): number;
|
|
820
|
+
addRelation(src: EntityID, def: RelationDef, tgt: EntityID): void;
|
|
821
|
+
removeRelation(src: EntityID, def: RelationDef, tgt?: EntityID): void;
|
|
822
|
+
targetOf(src: EntityID, def: RelationDef): EntityID | undefined;
|
|
823
|
+
targetsOf(src: EntityID, def: RelationDef): EntityID[];
|
|
824
|
+
sourcesOf(tgt: EntityID, def: RelationDef): EntityID[];
|
|
825
|
+
hasRelation(src: EntityID, def: RelationDef): boolean;
|
|
826
|
+
pairsOf(def: RelationDef): readonly (readonly [EntityID, EntityID])[];
|
|
827
|
+
sourcesOfAny(tgt: EntityID): readonly (readonly [RelationDef, EntityID])[];
|
|
828
|
+
relationBackingSparseId(def: RelationDef): SparseComponentID;
|
|
829
|
+
/** Drive a `(*, T)` wildcard query (`Query.forEachRelatedTo`) — see
|
|
830
|
+
* `RelationService.forEachRelationTargetMatch`. */
|
|
831
|
+
_forEachRelationTargetMatch(target: EntityID, include: BitSet, exclude: BitSet | null, anyOf: BitSet | null, sparseInclude: readonly SparseComponentID[], sparseExclude: readonly SparseComponentID[], includeDisabled: boolean, cb: (entityId: EntityID) => void): void;
|
|
832
|
+
compactRelations(): number;
|
|
833
|
+
ancestorsOf(src: EntityID, def: RelationDef): EntityID[];
|
|
834
|
+
rootOf(src: EntityID, def: RelationDef): EntityID;
|
|
835
|
+
cascadeOf(root: EntityID, def: RelationDef): EntityID[];
|
|
836
|
+
/** Second query-match path (#469 / ADR-0011): iterate entities matching a
|
|
837
|
+
* dense mask **and** sparse-membership terms, invoking `cb` per entity.
|
|
838
|
+
* Yields `EntityID`s, not archetype spans — sparse members are scattered
|
|
839
|
+
* across archetypes, so there is no SoA column to hand back. Driven by the
|
|
840
|
+
* cheapest candidate set:
|
|
841
|
+
*
|
|
842
|
+
* - **sparse require present** → walk the *smallest* required store's
|
|
843
|
+
* `indices` (an upper bound on the result), filtering each by the other
|
|
844
|
+
* required stores, the excluded stores, and the dense mask resolved from
|
|
845
|
+
* the entity's own archetype. Independent of archetype count.
|
|
846
|
+
* - **sparse exclude only** → walk `denseArchetypes` (already dense-mask-
|
|
847
|
+
* matched and non-empty by the caller), skipping rows in any excluded
|
|
848
|
+
* store.
|
|
849
|
+
* - **neither** → walk `denseArchetypes`' entity ids (dense-only fallback).
|
|
850
|
+
*
|
|
851
|
+
* Only reached via `Query.forEachEntity`; dense `forEach` never consults
|
|
852
|
+
* the sparse stores, so dense-only queries are unaffected (#469 AC). */
|
|
853
|
+
_forEachSparseMatch(include: BitSet, exclude: BitSet | null, anyOf: BitSet | null, sparseInclude: readonly SparseComponentID[], sparseExclude: readonly SparseComponentID[], denseArchetypes: readonly Archetype[], cb: (entityId: EntityID) => void, includeDisabled: boolean): void;
|
|
854
|
+
/** Fourth query-match path (#581): the matched set in hierarchy depth order
|
|
855
|
+
* (parents before children) — see `RelationService.forEachHierarchyMatch`. */
|
|
856
|
+
_forEachHierarchyMatch(include: BitSet, exclude: BitSet | null, anyOf: BitSet | null, sparseInclude: readonly SparseComponentID[], sparseExclude: readonly SparseComponentID[], denseArchetypes: readonly Archetype[], relation: RelationDef, maxDepth: number, includeDisabled: boolean, cb: (entityId: EntityID) => void): void;
|
|
857
|
+
addComponent(entityId: EntityID, def: ComponentDef<Record<string, never>>): void;
|
|
858
|
+
addComponent<S extends ComponentSchema>(entityId: EntityID, def: ComponentDef<S>, values: FieldValues<S>): void;
|
|
859
|
+
/** Add multiple components in one transition (resolves final archetype, then moves once).
|
|
860
|
+
*
|
|
861
|
+
* Final-mask resolve, not graph walk. The previous implementation called
|
|
862
|
+
* `archResolveAdd` once per entry, which threaded through every
|
|
863
|
+
* intermediate archetype on the path — and each unseen intermediate
|
|
864
|
+
* triggered a fresh `extendColumnStore` even though no entity ever lived
|
|
865
|
+
* there. Computing the union mask up front and resolving once via
|
|
866
|
+
* `archGetOrCreateFromMask` collapses N-1 intermediate-archetype
|
|
867
|
+
* creations into zero for the batched case (#211 follow-up). The lazy
|
|
868
|
+
* single-mask path remains the same; this just avoids feeding it
|
|
869
|
+
* archetypes the entity never visits.
|
|
870
|
+
*
|
|
871
|
+
* #659 — composite-add edge cache. The final-mask resolve, unlike the
|
|
872
|
+
* single-add `edges[]` walk, re-pays a per-call `mask.hash()`, `ArchetypeGraph.lookup`
|
|
873
|
+
* (the Map-of-buckets + `equals` scan), and `getBatchTransitionMap` on
|
|
874
|
+
* every call — a ~2× gap vs a cached edge walk that the decomposition probe
|
|
875
|
+
* pinned on the two `Map.get`s, not the hash. So a repeated (source, added-
|
|
876
|
+
* set) add now resolves through `currentArch`'s composite-add cache: one
|
|
877
|
+
* `Map.get` on an exact packed key yields the target + transition map, and we
|
|
878
|
+
* skip the union-mask build entirely. First call per key still resolves via
|
|
879
|
+
* the final-mask path below (no intermediate planting) and plants the edge.
|
|
880
|
+
* See docs/reports/bench/regressions/add-components-composite-edge.md. */
|
|
881
|
+
addComponents<Defs extends readonly ComponentDef[]>(entityId: EntityID, entries: TemplateEntries<Defs>): void;
|
|
882
|
+
/** Shared move+write tail of `addComponents` (#659): place the entity into
|
|
883
|
+
* the already-resolved `targetArch` — a `moveEntityFrom` along the cached
|
|
884
|
+
* `map` when it has a row, else a fresh append (the rowless empty-archetype
|
|
885
|
+
* source ignores `map`) — then write every entry's fields. Both the
|
|
886
|
+
* composite-edge-cache hit and the final-mask cold path funnel through here so
|
|
887
|
+
* the placement logic lives once. */
|
|
888
|
+
private _addComponentsInto;
|
|
889
|
+
removeComponent(entityId: EntityID, def: ComponentDef): void;
|
|
890
|
+
/** Remove multiple components in one transition (resolves final archetype, then moves once).
|
|
891
|
+
*
|
|
892
|
+
* Final-mask resolve, not graph walk. Same rationale as `addComponents`
|
|
893
|
+
* above — the previous per-step path threaded `archResolveRemove`
|
|
894
|
+
* once per def, which materialised every intermediate archetype on the
|
|
895
|
+
* removal path. Computing the difference mask up front and resolving
|
|
896
|
+
* once avoids planting N-1 intermediates the entity never lives in. */
|
|
897
|
+
removeComponents(entityId: EntityID, defs: ComponentDef[]): void;
|
|
898
|
+
/** Total (POLISH_AUDIT #9): a dead/stale `entityId` returns `false` rather
|
|
899
|
+
* than throwing — a "has" probe is exactly what callers reach for to avoid
|
|
900
|
+
* touching dead entities, so it must be safe to ask. */
|
|
901
|
+
hasComponent(entityId: EntityID, def: ComponentHandle): boolean;
|
|
902
|
+
/**
|
|
903
|
+
* Bulk add a component to ALL entities in the given archetype.
|
|
904
|
+
* Uses TypedArray.set() for O(columns) instead of O(N×columns).
|
|
905
|
+
* The archetype must not already contain this component.
|
|
906
|
+
*/
|
|
907
|
+
batchAddComponent(src: ArchetypeID, def: ComponentDef, values?: Record<string, number>): void;
|
|
908
|
+
/**
|
|
909
|
+
* Bulk remove a component from ALL entities in the given archetype.
|
|
910
|
+
* Uses TypedArray.set() for O(columns) instead of O(N×columns).
|
|
911
|
+
* The archetype must contain this component.
|
|
912
|
+
*/
|
|
913
|
+
batchRemoveComponent(src: ArchetypeID, def: ComponentDef): void;
|
|
914
|
+
getEntityArchetype(entityId: EntityID): Archetype;
|
|
915
|
+
getEntityRow(entityId: EntityID): number;
|
|
916
|
+
/**
|
|
917
|
+
* Find all archetypes matching the given masks.
|
|
918
|
+
* Uses the inverted componentIndex to start from the component with the
|
|
919
|
+
* fewest archetypes, minimizing the number of superset checks.
|
|
920
|
+
*/
|
|
921
|
+
getMatchingArchetypes(required: BitSet, excluded?: BitSet, anyOf?: BitSet): readonly Archetype[];
|
|
922
|
+
/**
|
|
923
|
+
* Register a live query. Returns a mutable Archetype[] that this Store will
|
|
924
|
+
* push newly-created matching archetypes into, keeping the query always up-to-date.
|
|
925
|
+
*/
|
|
926
|
+
registerQuery(include: BitSet, exclude?: BitSet, anyOf?: BitSet): Archetype[];
|
|
927
|
+
updateQueryRef(result: Archetype[], query: Query<any>): void;
|
|
928
|
+
get archetypeCount(): number;
|
|
929
|
+
registerEvent<S extends EventShape<S>>(fields: readonly (keyof S & string)[]): EventDef<S>;
|
|
930
|
+
emitEvent(def: EventDef<any>, values: Record<string, number>): void;
|
|
931
|
+
emitSignal(def: EventDef<EmptyEventSchema>): void;
|
|
932
|
+
getEventReader<S extends EventShape<S>>(def: EventDef<S>): EventReader<S>;
|
|
933
|
+
clearEvents(): void;
|
|
934
|
+
/** `DEV`-only mid-update emit detection — see
|
|
935
|
+
* `EventRegistry.devBufferedEventCount`. */
|
|
936
|
+
_devBufferedEventCount(): number;
|
|
937
|
+
registerEventByKey<S extends EventShape<S>>(key: symbol, fields: readonly (keyof S & string)[]): EventDef<S>;
|
|
938
|
+
getEventDefByKey(key: symbol): EventDef<any>;
|
|
939
|
+
hasEventKey(key: symbol): boolean;
|
|
940
|
+
private readonly resources;
|
|
941
|
+
registerResource(key: symbol, value: unknown): void;
|
|
942
|
+
getResource(key: symbol): unknown;
|
|
943
|
+
setResource(key: symbol, value: unknown): void;
|
|
944
|
+
/** Fails closed on a missing key; the present → absent → present
|
|
945
|
+
* lifecycle (#798) — see `ResourceRegistry.remove`. */
|
|
946
|
+
removeResource(key: symbol): void;
|
|
947
|
+
hasResource(key: symbol): boolean;
|
|
948
|
+
}
|
|
949
|
+
export {};
|
|
950
|
+
//# sourceMappingURL=store.d.ts.map
|