@oasys/oecs 0.5.2 → 0.5.4
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 +208 -14
- package/README.md +198 -145
- package/dist/core/ecs/access_check.d.cts +3 -3
- package/dist/core/ecs/access_check.d.ts +3 -3
- package/dist/core/ecs/archetype.d.cts +103 -43
- package/dist/core/ecs/archetype.d.ts +103 -43
- package/dist/core/ecs/archetype.d.ts.map +1 -1
- package/dist/core/ecs/archetype_graph.d.cts +8 -8
- package/dist/core/ecs/archetype_graph.d.ts +8 -8
- package/dist/core/ecs/archetype_graph.d.ts.map +1 -1
- package/dist/core/ecs/builtin_relations.d.cts +1 -1
- package/dist/core/ecs/builtin_relations.d.ts +1 -1
- package/dist/core/ecs/command_log.d.cts +4 -4
- package/dist/core/ecs/command_log.d.ts +4 -4
- package/dist/core/ecs/command_log.d.ts.map +1 -1
- package/dist/core/ecs/component.d.cts +4 -4
- package/dist/core/ecs/component.d.ts +4 -4
- package/dist/core/ecs/deferred_commands.d.cts +1 -1
- package/dist/core/ecs/deferred_commands.d.ts +1 -1
- package/dist/core/ecs/deferred_commands.d.ts.map +1 -1
- package/dist/core/ecs/ecs.d.cts +85 -43
- package/dist/core/ecs/ecs.d.ts +85 -43
- package/dist/core/ecs/ecs.d.ts.map +1 -1
- package/dist/core/ecs/ecs_memory.d.cts +15 -15
- package/dist/core/ecs/ecs_memory.d.ts +15 -15
- package/dist/core/ecs/ecs_memory.d.ts.map +1 -1
- package/dist/core/ecs/entity_allocator.d.cts +6 -6
- package/dist/core/ecs/entity_allocator.d.ts +6 -6
- package/dist/core/ecs/entity_allocator.d.ts.map +1 -1
- package/dist/core/ecs/event.d.cts +2 -2
- package/dist/core/ecs/event.d.ts +2 -2
- package/dist/core/ecs/event_registry.d.cts +1 -1
- package/dist/core/ecs/event_registry.d.ts +1 -1
- package/dist/core/ecs/event_registry.d.ts.map +1 -1
- package/dist/core/ecs/facades.d.cts +15 -15
- package/dist/core/ecs/facades.d.ts +15 -15
- package/dist/core/ecs/facades.d.ts.map +1 -1
- package/dist/core/ecs/frame_trace.d.cts +2 -2
- package/dist/core/ecs/frame_trace.d.ts +2 -2
- package/dist/core/ecs/host_commands.d.cts +12 -12
- package/dist/core/ecs/host_commands.d.ts +12 -12
- package/dist/core/ecs/host_commands.d.ts.map +1 -1
- package/dist/core/ecs/index.d.cts +1 -1
- package/dist/core/ecs/index.d.ts +1 -1
- package/dist/core/ecs/index.d.ts.map +1 -1
- package/dist/core/ecs/observer.d.cts +9 -9
- package/dist/core/ecs/observer.d.ts +9 -9
- package/dist/core/ecs/observer.d.ts.map +1 -1
- package/dist/core/ecs/query.d.cts +91 -70
- package/dist/core/ecs/query.d.ts +91 -70
- package/dist/core/ecs/query.d.ts.map +1 -1
- package/dist/core/ecs/ref.d.cts +53 -1
- package/dist/core/ecs/ref.d.ts +53 -1
- package/dist/core/ecs/ref.d.ts.map +1 -1
- package/dist/core/ecs/relation.d.cts +15 -15
- package/dist/core/ecs/relation.d.ts +15 -15
- package/dist/core/ecs/relation.d.ts.map +1 -1
- package/dist/core/ecs/relation_service.d.cts +14 -14
- package/dist/core/ecs/relation_service.d.ts +14 -14
- package/dist/core/ecs/relation_service.d.ts.map +1 -1
- package/dist/core/ecs/resource.d.cts +1 -1
- package/dist/core/ecs/resource.d.ts +1 -1
- package/dist/core/ecs/resource_registry.d.cts +2 -2
- package/dist/core/ecs/resource_registry.d.ts +2 -2
- package/dist/core/ecs/resume.d.cts +3 -3
- package/dist/core/ecs/resume.d.ts +3 -3
- package/dist/core/ecs/resume.d.ts.map +1 -1
- package/dist/core/ecs/schedule.d.cts +22 -9
- package/dist/core/ecs/schedule.d.ts +22 -9
- package/dist/core/ecs/schedule.d.ts.map +1 -1
- package/dist/core/ecs/snapshot_service.d.cts +3 -3
- package/dist/core/ecs/snapshot_service.d.ts +3 -3
- package/dist/core/ecs/sparse_store.d.cts +6 -6
- package/dist/core/ecs/sparse_store.d.ts +6 -6
- package/dist/core/ecs/sparse_store.d.ts.map +1 -1
- package/dist/core/ecs/store.d.cts +189 -110
- package/dist/core/ecs/store.d.ts +189 -110
- package/dist/core/ecs/store.d.ts.map +1 -1
- package/dist/core/ecs/store_layout_listener.d.cts +2 -2
- package/dist/core/ecs/store_layout_listener.d.ts +2 -2
- package/dist/core/ecs/system.d.cts +8 -8
- package/dist/core/ecs/system.d.ts +8 -8
- package/dist/core/ecs/system.d.ts.map +1 -1
- package/dist/core/ecs/utils/error.d.cts +7 -0
- package/dist/core/ecs/utils/error.d.ts +7 -0
- package/dist/core/ecs/utils/error.d.ts.map +1 -1
- package/dist/core/store/action_ring.d.cts +7 -8
- package/dist/core/store/action_ring.d.ts +7 -8
- package/dist/core/store/action_ring.d.ts.map +1 -1
- package/dist/core/store/allocator.d.cts +58 -35
- package/dist/core/store/allocator.d.ts +58 -35
- package/dist/core/store/allocator.d.ts.map +1 -1
- package/dist/core/store/buffer_backed_column.d.cts +5 -6
- package/dist/core/store/buffer_backed_column.d.ts +5 -6
- package/dist/core/store/buffer_backed_column.d.ts.map +1 -1
- package/dist/core/store/column_store.d.cts +23 -23
- package/dist/core/store/column_store.d.ts +23 -23
- package/dist/core/store/column_store.d.ts.map +1 -1
- package/dist/core/store/command_dispatch.d.cts +1 -1
- package/dist/core/store/command_dispatch.d.ts +1 -1
- package/dist/core/store/command_ring.d.cts +6 -7
- package/dist/core/store/command_ring.d.ts +6 -7
- package/dist/core/store/command_ring.d.ts.map +1 -1
- package/dist/core/store/descriptor.d.cts +2 -2
- package/dist/core/store/descriptor.d.ts +2 -2
- package/dist/core/store/entity_index.d.cts +3 -3
- package/dist/core/store/entity_index.d.ts +3 -3
- package/dist/core/store/event_ring.d.cts +7 -7
- package/dist/core/store/event_ring.d.ts +7 -7
- package/dist/core/store/extend.d.cts +2 -2
- package/dist/core/store/extend.d.ts +2 -2
- package/dist/core/store/extend.d.ts.map +1 -1
- package/dist/core/store/grow.d.cts +1 -1
- package/dist/core/store/grow.d.ts +1 -1
- package/dist/core/store/grow.d.ts.map +1 -1
- package/dist/core/store/header.d.cts +9 -9
- package/dist/core/store/header.d.ts +9 -9
- package/dist/core/store/header.d.ts.map +1 -1
- package/dist/core/store/layout_ops.d.cts +25 -7
- package/dist/core/store/layout_ops.d.ts +25 -7
- package/dist/core/store/layout_ops.d.ts.map +1 -1
- package/dist/core/store/state_hash.d.cts +3 -3
- package/dist/core/store/state_hash.d.ts +3 -3
- package/dist/extensions/editor/editor.d.cts +4 -4
- package/dist/extensions/editor/editor.d.ts +4 -4
- package/dist/extensions/editor/editor.d.ts.map +1 -1
- package/dist/extensions/editor/field_handle.d.cts +1 -1
- package/dist/extensions/editor/field_handle.d.ts +1 -1
- package/dist/extensions/editor/index.d.cts +2 -2
- package/dist/extensions/editor/index.d.ts +2 -2
- package/dist/extensions/editor/index.development.js +4 -4
- package/dist/extensions/editor/index.js +4 -4
- package/dist/extensions/reactive/ecs_sync.d.cts +4 -4
- package/dist/extensions/reactive/ecs_sync.d.ts +4 -4
- package/dist/extensions/reactive/index.d.cts +4 -4
- package/dist/extensions/reactive/index.d.ts +4 -4
- package/dist/extensions/reactive/index.development.js +3 -3
- package/dist/extensions/reactive/index.js +3 -3
- package/dist/extensions/solid/index.d.cts +1 -1
- package/dist/extensions/solid/index.d.ts +1 -1
- package/dist/extensions/solid/index.development.js +1 -1
- package/dist/extensions/solid/index.js +1 -1
- package/dist/extensions/solid/kernel_solid.d.cts +1 -1
- package/dist/extensions/solid/kernel_solid.d.ts +1 -1
- package/dist/extensions/solid/kernel_solid.d.ts.map +1 -1
- package/dist/{host_commands-BI8pEmjH.js → host_commands-BlR3oZ3x.js} +473 -393
- package/dist/host_commands-C2cpU3yC.cjs +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.development.cjs +1 -1
- package/dist/index.development.js +3345 -2963
- package/dist/index.js +1496 -1131
- package/dist/{internal-BMDgWZbt.js → internal-BoxWl4dG.js} +410 -330
- package/dist/internal-CGBEwfeB.cjs +2 -0
- package/dist/internal.cjs +1 -1
- package/dist/internal.development.cjs +1 -1
- package/dist/internal.development.js +1 -1
- package/dist/internal.js +1 -1
- package/dist/primitives.development.js +1 -1
- package/dist/primitives.js +1 -1
- package/dist/reactive/index.d.cts +2 -2
- package/dist/reactive/index.d.ts +2 -2
- package/dist/reactive/index.development.js +1 -1
- package/dist/reactive/index.js +1 -1
- package/dist/reactive/kernel.d.cts +5 -5
- package/dist/reactive/kernel.d.ts +5 -5
- package/dist/reactive/shallow.d.cts +1 -1
- package/dist/reactive/shallow.d.ts +1 -1
- package/dist/{shallow-DfnAm9vG.js → shallow-Bs39EPh1.js} +1 -1
- package/dist/{shallow-9S1pY_Iw.js → shallow-CiFC70Dz.js} +1 -1
- package/dist/{shared-BU1Cd40h.js → shared-B4v0L1cw.js} +41 -35
- package/dist/shared-DU78k0pU.cjs +1 -0
- package/dist/shared.cjs +1 -1
- package/dist/shared.d.cts +1 -1
- package/dist/shared.d.ts +1 -1
- package/dist/shared.development.cjs +1 -1
- package/dist/shared.development.js +1 -1
- package/dist/shared.js +1 -1
- package/dist/type_primitives/typed_arrays/typed_arrays.d.cts +2 -2
- package/dist/type_primitives/typed_arrays/typed_arrays.d.ts +2 -2
- package/dist/{typed_arrays-BcXcnzB4.js → typed_arrays-DIVwVy5_.js} +1 -1
- package/dist/version.d.cts +2 -2
- package/dist/version.d.ts +2 -2
- package/package.json +1 -1
- package/dist/host_commands-CxhpzMx9.cjs +0 -1
- package/dist/internal-LiTSB_tG.cjs +0 -2
- package/dist/shared-BymrGTyR.cjs +0 -1
package/dist/core/ecs/store.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { EntityID } from './entity.js';
|
|
2
|
+
import { CursorBinder } from './ref.js';
|
|
2
3
|
import { FrameTraceSink } from './frame_trace.js';
|
|
3
4
|
import { ComponentDef, ComponentHandle, ComponentSchema, FieldValues } from './component.js';
|
|
4
5
|
import { SparseComponentDef, SparseComponentID } from './sparse_store.js';
|
|
@@ -21,14 +22,14 @@ export interface ComponentMeta {
|
|
|
21
22
|
obsAdd: boolean;
|
|
22
23
|
/** Has an onRemove observer — collect effective removes for this component. */
|
|
23
24
|
obsRem: boolean;
|
|
24
|
-
/** Has an onDisable observer
|
|
25
|
+
/** Has an onDisable observer — collect effective disables for this
|
|
25
26
|
* component at the toggle drain. */
|
|
26
27
|
obsDisable: boolean;
|
|
27
|
-
/** Has an onEnable observer
|
|
28
|
+
/** Has an onEnable observer — collect effective enables for this
|
|
28
29
|
* component at the toggle drain. */
|
|
29
30
|
obsEnable: boolean;
|
|
30
31
|
/** Has a per-entity onSet observer — record dirty rows on the write path
|
|
31
|
-
* (the
|
|
32
|
+
* (the opt-in dirty list). */
|
|
32
33
|
trackDirty: boolean;
|
|
33
34
|
}
|
|
34
35
|
/**
|
|
@@ -45,13 +46,13 @@ export interface StructuralObserverEvents {
|
|
|
45
46
|
remComp: number[];
|
|
46
47
|
remEid: number[];
|
|
47
48
|
remLen: number;
|
|
48
|
-
/** Effective disable events
|
|
49
|
+
/** Effective disable events — collected during the toggle drain
|
|
49
50
|
* (`_flushToggles`), one per `(component, entity)` of each net-disabled
|
|
50
51
|
* entity's mask. Empty on a structural (add/remove/destroy) round. */
|
|
51
52
|
disComp: number[];
|
|
52
53
|
disEid: number[];
|
|
53
54
|
disLen: number;
|
|
54
|
-
/** Effective enable events
|
|
55
|
+
/** Effective enable events, symmetric with the disable arrays. */
|
|
55
56
|
enaComp: number[];
|
|
56
57
|
enaEid: number[];
|
|
57
58
|
enaLen: number;
|
|
@@ -75,14 +76,14 @@ export type TemplateOverrides<Defs extends readonly ComponentDef[]> = {
|
|
|
75
76
|
readonly [K in TemplateFieldNames<Defs>]?: number;
|
|
76
77
|
};
|
|
77
78
|
declare const __templateDefs: unique symbol;
|
|
78
|
-
/** A resolved template
|
|
79
|
+
/** A resolved template — an archetype template produced by
|
|
79
80
|
* `ECS.template(...)`. **Opaque** apart from `defs`: callers hold it and pass
|
|
80
81
|
* it to `ECS.spawn` / `ECS.spawnMany` (and may reference it in a system's
|
|
81
82
|
* `spawns` / `despawns` access declaration — the scheduler expands it to
|
|
82
83
|
* `defs`); the remaining fields are engine-internal and may change. `spawn`
|
|
83
84
|
* lands an entity directly in `archetype_id` with zero archetype transitions,
|
|
84
85
|
* writing `flatValues` (defaults in `_flatColumns` order) in one append
|
|
85
|
-
* pass.
|
|
86
|
+
* pass. */
|
|
86
87
|
export interface Template<Defs extends readonly ComponentDef[] = readonly ComponentDef[]> {
|
|
87
88
|
readonly archetypeId: ArchetypeID;
|
|
88
89
|
readonly flatValues: number[];
|
|
@@ -95,13 +96,13 @@ export interface StoreOptions {
|
|
|
95
96
|
initialCapacity?: number;
|
|
96
97
|
/** Pluggable SAB buffer source. When provided, `createColumnStore`,
|
|
97
98
|
* `extendColumnStore`, and `growColumnStore` route through it. Default is
|
|
98
|
-
* `growableSabAllocator
|
|
99
|
+
* `growableSabAllocator`. Typed `InPlaceBufferAllocator`: a live
|
|
99
100
|
* Store's flush loops hoist entity-index views across grows, so only
|
|
100
|
-
* in-place allocators may back one
|
|
101
|
+
* in-place allocators may back one — the constructor also
|
|
101
102
|
* runtime-asserts the marker for untyped JS callers. Consumers normally
|
|
102
103
|
* don't touch this directly; `ECSOptions.memory` resolves to it. */
|
|
103
104
|
bufferAllocator?: InPlaceBufferAllocator;
|
|
104
|
-
/** Sizing intent the world was constructed with
|
|
105
|
+
/** Sizing intent the world was constructed with, used to phrase
|
|
105
106
|
* allocator-cap and entity-index-overflow errors in the caller's own
|
|
106
107
|
* terms ("3.2× the declared budget") instead of raw bytes. Wired by
|
|
107
108
|
* `ECS` from `resolveECSMemory`; absent for bare test Stores. */
|
|
@@ -111,29 +112,29 @@ export interface StoreOptions {
|
|
|
111
112
|
* views by the time this fires. Used by ECS to call
|
|
112
113
|
* `sim.setLayout(0)` so WASM-side cached pointers re-walk. */
|
|
113
114
|
onBufferResized?: () => void;
|
|
114
|
-
/** Max live entities the SAB entity-index region holds
|
|
115
|
+
/** Max live entities the SAB entity-index region holds.
|
|
115
116
|
* Default `ENTITY_INDEX_DEFAULT_CAPACITY` (`1 << 20` — the full EntityID
|
|
116
117
|
* index space). Exceeding this at runtime throws `EID_MAX_INDEX_OVERFLOW`.
|
|
117
118
|
* Tests with small entity counts may set lower to bench the SAB region size
|
|
118
119
|
* or to make index exhaustion reachable; a 1000-entity workload fits
|
|
119
120
|
* comfortably in the default. */
|
|
120
121
|
entityIndexCapacity?: number;
|
|
121
|
-
/** Consumer-declared SAB regions
|
|
122
|
+
/** Consumer-declared SAB regions, forwarded verbatim to
|
|
122
123
|
* `createColumnStore`. Each `StoreRegionSpec` carries an opaque `region_id`,
|
|
123
124
|
* a precomputed byte size, and an `init` closure; the engine lays them out
|
|
124
125
|
* generically and exposes them via `regionHandle(id)` / `regionOffset(id)`.
|
|
125
126
|
* A game (e.g. `@internal/sim`'s region specs) supplies these — the engine
|
|
126
127
|
* ships no game regions of its own. Omitted ⇒ none. */
|
|
127
128
|
regions?: readonly StoreRegionSpec[];
|
|
128
|
-
/** Byte size of the opt-in sim-bindings region
|
|
129
|
+
/** Byte size of the opt-in sim-bindings region, forwarded verbatim to
|
|
129
130
|
* `createColumnStore`. A consumer that attaches a WASM backend passes its own
|
|
130
131
|
* size (`@internal/sim`'s `SIM_BINDINGS_BYTES`, computed from the binding
|
|
131
132
|
* manifest); the host then writes the `(component_id, field_id)` IDs into the
|
|
132
133
|
* region. Omitted / 0 ⇒ no region (a pure-TS game pays nothing for the WASM
|
|
133
|
-
* seam). De-welded from the engine ABI
|
|
134
|
+
* seam). De-welded from the engine ABI so a manifest edit doesn't
|
|
134
135
|
* drift an engine golden. */
|
|
135
136
|
bindingsRegionBytes?: number;
|
|
136
|
-
/** Opt into the **determinism surface
|
|
137
|
+
/** Opt into the **determinism surface**. Default `false`.
|
|
137
138
|
* Gates the three methods that fold/serialize state in canonical (sorted)
|
|
138
139
|
* order: `stateHash`, `snapshotSparse`, `restoreSparse`. When `false`
|
|
139
140
|
* those throw `DETERMINISM_DISABLED` — the canonical-ordering tax (sparse
|
|
@@ -141,7 +142,7 @@ export interface StoreOptions {
|
|
|
141
142
|
* consumer can't accidentally read a non-canonical digest. When `true`,
|
|
142
143
|
* today's behavior is reproduced bit-for-bit. This is the ONLY effect of the
|
|
143
144
|
* flag: it does not touch the per-tick path, the in-place-allocator invariant
|
|
144
|
-
* (
|
|
145
|
+
* (a memory-safety requirement that holds regardless), or the
|
|
145
146
|
* always-on `enabled_count` partition maintenance. The flag's value is a
|
|
146
147
|
* capability gate, not a hot-path switch — `stateHash`/snapshot are never
|
|
147
148
|
* called per tick. */
|
|
@@ -164,7 +165,7 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
164
165
|
private readonly _deferred;
|
|
165
166
|
private readonly _snapshots;
|
|
166
167
|
_tick: number;
|
|
167
|
-
/** Per-world frame-trace sink
|
|
168
|
+
/** Per-world frame-trace sink, installed via `ECS.setTrace`.
|
|
168
169
|
* `null` unless a consumer attaches a recorder. Every call site is
|
|
169
170
|
* `if (DEV) store._trace?.…`, so production builds dead-code-eliminate
|
|
170
171
|
* the seam and pay only this one nullable field. The sink observes; it never
|
|
@@ -172,7 +173,7 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
172
173
|
* observer state below). */
|
|
173
174
|
_trace: FrameTraceSink | null;
|
|
174
175
|
private _structuralObserverCount;
|
|
175
|
-
/** Count of components with any onDisable/onEnable observer
|
|
176
|
+
/** Count of components with any onDisable/onEnable observer. While 0
|
|
176
177
|
* (with `_structuralObserverCount` also 0), `flushStructural` takes the
|
|
177
178
|
* byte-for-byte fast path and the toggle drain skips event collection. */
|
|
178
179
|
private _toggleObserverCount;
|
|
@@ -184,14 +185,14 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
184
185
|
private _structuralObserverHook;
|
|
185
186
|
/** Install the structural-observer dispatch hook (called once by `ECS`
|
|
186
187
|
* during construction) — the named seam replacing direct writes to the
|
|
187
|
-
* previously-public field
|
|
188
|
+
* previously-public field. */
|
|
188
189
|
setStructuralObserverHook(fn: (ev: StructuralObserverEvents) => void): void;
|
|
189
190
|
private _collectDestroyEid;
|
|
190
191
|
private readonly _collectDestroyRemoveBit;
|
|
191
192
|
private _collectToggleEid;
|
|
192
193
|
private readonly _collectDisableBit;
|
|
193
194
|
private readonly _collectEnableBit;
|
|
194
|
-
/** Net-transition snapshot for the toggle drain
|
|
195
|
+
/** Net-transition snapshot for the toggle drain: entity → its disabled
|
|
195
196
|
* state at the START of the drain. Reused, cleared each drain. Lets
|
|
196
197
|
* `_flushToggles` emit one event per *net* transition (disable→enable→disable
|
|
197
198
|
* within a tick = a single onDisable) instead of one per buffered op — required
|
|
@@ -206,7 +207,7 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
206
207
|
* (`flushStructural`/`flushDestroyed` when they did work; immediate
|
|
207
208
|
* `destroyEntity`, `addComponent(s)`, `removeComponent(s)` on the
|
|
208
209
|
* Store). Cleared by `publishRowCountsToDescriptor`. Lets read-only
|
|
209
|
-
* phases' `ctx.flush
|
|
210
|
+
* phases' `ctx.flush` skip the descriptor walk entirely. */
|
|
210
211
|
private _rowCountsDirty;
|
|
211
212
|
/** Monotonic counter bumped by every membership-changing path (immediate
|
|
212
213
|
* `addComponent(s)`, `removeComponent(s)`, `destroyEntity`,
|
|
@@ -214,7 +215,7 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
214
215
|
* `flushDestroyed`, and new-archetype installs in `ArchetypeGraph.install`).
|
|
215
216
|
* Read by `Query._nonEmpty()` via `QueryResolver._getQueryDirtyEpoch`
|
|
216
217
|
* — a query whose stored `_lastSeenEpoch` matches the current epoch
|
|
217
|
-
* reuses its cached non-empty list
|
|
218
|
+
* reuses its cached non-empty list. Replaces the previous walk
|
|
218
219
|
* over `registeredQueries` that wrote one dirty bit per query per
|
|
219
220
|
* mutation; 5000 startup adds × Q queries used to be 5000×Q writes,
|
|
220
221
|
* now it's 5000 integer increments. Public so ECS can forward through
|
|
@@ -229,45 +230,45 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
229
230
|
* to whatever fits `arch.length + additional`, whichever is larger),
|
|
230
231
|
* reallocs the SAB via `growColumnStore` (live rows of every archetype
|
|
231
232
|
* are carried forward), and republishes column views to every
|
|
232
|
-
* SAB-backed archetype.
|
|
233
|
+
* SAB-backed archetype. */
|
|
233
234
|
private readonly _growHandler;
|
|
234
|
-
/** Build the intent-aware fatal for an allocator cap hit
|
|
235
|
+
/** Build the intent-aware fatal for an allocator cap hit. The
|
|
235
236
|
* allocator can only name raw bytes; the Store knows what the caller
|
|
236
237
|
* declared (`capContext`) and how many entities are live, so the error
|
|
237
238
|
* says "3.2× the declared budget — runaway creation upstream?" instead
|
|
238
239
|
* of leaving the caller to reverse-engineer byte counts. Fatality is
|
|
239
|
-
* unchanged (
|
|
240
|
+
* unchanged (no grow-beyond-cap fallback). */
|
|
240
241
|
private _capExceededError;
|
|
241
242
|
private readonly _bufferAllocator;
|
|
242
243
|
private readonly _capContext;
|
|
243
244
|
private readonly _onBufferResized;
|
|
244
245
|
/** Construct with an `initialCapacity` number (legacy form) or an
|
|
245
|
-
* options object (
|
|
246
|
+
* options object (adds `bufferAllocator` and
|
|
246
247
|
* `onBufferResized` callback). Both signatures coexist so test fixtures
|
|
247
248
|
* that pass `new Store(4)` keep working. */
|
|
248
249
|
constructor(arg?: number | StoreOptions);
|
|
249
250
|
/** Capacity of the entity-index SAB region (max slots ≈ max live
|
|
250
|
-
* entities). Fixed at construction
|
|
251
|
+
* entities). Fixed at construction; a future
|
|
251
252
|
* follow-up will grow it via `growColumnStore` when `entityHighWater`
|
|
252
253
|
* hits the cap. */
|
|
253
254
|
private readonly _entityIndexCapacity;
|
|
254
|
-
/** Consumer-declared SAB regions
|
|
255
|
+
/** Consumer-declared SAB regions, captured so the realloc path
|
|
255
256
|
* re-lays them out. `undefined` when no consumer regions were declared.
|
|
256
257
|
* The region contents survive a grow via the self-describing region table
|
|
257
258
|
* (`extend.ts` snapshot/restore), so this is only the layout recipe. */
|
|
258
259
|
private readonly _regions;
|
|
259
|
-
/** Byte size of the opt-in sim-bindings region
|
|
260
|
+
/** Byte size of the opt-in sim-bindings region. 0 ⇒ no region (the
|
|
260
261
|
* pure-TS default). Captured so the initial `createColumnStore` reserves it;
|
|
261
262
|
* across a realloc the size is re-derived from the old header by
|
|
262
263
|
* `optionsFromOld`, so it is not threaded through the grow/extend path. */
|
|
263
264
|
private readonly _bindingsRegionBytes;
|
|
264
|
-
/** Determinism opt-in
|
|
265
|
+
/** Determinism opt-in. When `false` (the default), the
|
|
265
266
|
* canonical-ordering determinism surface (`stateHash` / `snapshotSparse` /
|
|
266
267
|
* `restoreSparse`) throws `DETERMINISM_DISABLED` rather than running its
|
|
267
|
-
* sort. Memory-safety invariants (the in-place allocator
|
|
268
|
+
* sort. Memory-safety invariants (the in-place allocator) and the
|
|
268
269
|
* `enabled_count` partition are unaffected — they hold regardless. */
|
|
269
270
|
private readonly _deterministic;
|
|
270
|
-
/** Whether the determinism surface is enabled
|
|
271
|
+
/** Whether the determinism surface is enabled. `false` ⇒ `stateHash`
|
|
271
272
|
* / `snapshotSparse` / `restoreSparse` throw `DETERMINISM_DISABLED`. */
|
|
272
273
|
get deterministic(): boolean;
|
|
273
274
|
/** Guard the canonical-ordering determinism surface. Throws
|
|
@@ -277,11 +278,11 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
277
278
|
* check is free, and a silent non-canonical digest is the failure mode we're
|
|
278
279
|
* preventing. */
|
|
279
280
|
private _requireDeterministic;
|
|
280
|
-
/** Reject `f32`/`f64` fields on a `deterministic: true` world at registration
|
|
281
|
-
*
|
|
281
|
+
/** Reject `f32`/`f64` fields on a `deterministic: true` world at registration.
|
|
282
|
+
* IEEE-754 rounds differently across V8 / Bun / Zig at the 1-ULP
|
|
282
283
|
* level, so a float column in a fixed-update path is a silent per-tick
|
|
283
284
|
* `stateHash` divergence between client and server — the one thing the
|
|
284
|
-
* determinism opt-in
|
|
285
|
+
* determinism opt-in exists to prevent. Non-deterministic worlds
|
|
285
286
|
* skip this entirely (floats stay allowed), so it costs the default path
|
|
286
287
|
* nothing. `kind` names the storage class in the error ("component" /
|
|
287
288
|
* "sparse component"); the array shorthand's `f64` default lands here too, so
|
|
@@ -306,7 +307,7 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
306
307
|
get columnStore(): ColumnStore;
|
|
307
308
|
/** Resolve a consumer-declared SAB region's byte offset by `region_id`, or
|
|
308
309
|
* 0 when the region is absent (no region was declared with that id). The
|
|
309
|
-
* generic, de-gamed replacement
|
|
310
|
+
* generic, de-gamed replacement for the removed game-named accessors
|
|
310
311
|
* (`terrain_view` / `spatial_grid_view` / … ); a consumer pairs this with
|
|
311
312
|
* its own region module (e.g. `@internal/sim`'s region helpers) to
|
|
312
313
|
* materialise a typed view. TS twin of Zig `abi.find_region`. */
|
|
@@ -315,16 +316,16 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
315
316
|
* `null` when absent. Carries the live `buffer`/`view` plus the region's byte
|
|
316
317
|
* `offset` and `bytes`, so a consumer's region module can build a TypedArray
|
|
317
318
|
* view over exactly the region's span without re-reading the directory.
|
|
318
|
-
* Re-fetch after a SAB grow (the offset/view may have moved).
|
|
319
|
+
* Re-fetch after a SAB grow (the offset/view may have moved). */
|
|
319
320
|
regionHandle(regionId: number): ColumnStoreRegionHandle | null;
|
|
320
321
|
/**
|
|
321
322
|
* Stamp every SAB-backed archetype's live `length` into its descriptor's
|
|
322
|
-
* `row_count` field
|
|
323
|
+
* `row_count` field. `extendColumnStore` /
|
|
323
324
|
* `growColumnStore` are the only other writers of `row_count`, and they
|
|
324
325
|
* record the count at the moment of the resize — `Archetype.addEntity`
|
|
325
326
|
* does not update it, so any insertion after the most recent resize
|
|
326
327
|
* leaves the descriptor stale. Zig systems that drive their per-row loop
|
|
327
|
-
* off `arch_hdr.row_count` (every `tick_*` export
|
|
328
|
+
* off `arch_hdr.row_count` (every `tick_*` export)
|
|
328
329
|
* read those stale bytes and silently skip the just-spawned rows.
|
|
329
330
|
*
|
|
330
331
|
* Lockstep walk: SAB descriptors are written by `extendColumnStore` in
|
|
@@ -333,10 +334,10 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
333
334
|
* once, skipping non-SAB entries, and advancing an `archAddr` cursor
|
|
334
335
|
* by the descriptor's `column_count` lets us write `row_count` without
|
|
335
336
|
* the throwaway `Map<archId, length>` the previous version allocated
|
|
336
|
-
* on every call
|
|
337
|
+
* on every call. Cheap: descriptor-region seeks only, no column
|
|
337
338
|
* I/O.
|
|
338
339
|
*
|
|
339
|
-
* Gated by `_rowCountsDirty`
|
|
340
|
+
* Gated by `_rowCountsDirty` — mutation paths
|
|
340
341
|
* (`flushStructural`, `flushDestroyed`, immediate `destroyEntity`,
|
|
341
342
|
* `addComponent(s)`, `removeComponent(s)`) set the flag; this method
|
|
342
343
|
* clears it. Read-only phases that flush only to drain empty buffers
|
|
@@ -344,12 +345,11 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
344
345
|
publishRowCountsToDescriptor(): void;
|
|
345
346
|
/** FNV-1a-style 32-bit digest over (archetype_id, live_row_count, live
|
|
346
347
|
* column bytes) for each archetype in id order, followed by the sparse
|
|
347
|
-
* stores (out-of-identity components
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
* for cross-replay determinism.
|
|
348
|
+
* stores (out-of-identity components) in registration order.
|
|
349
|
+
* This is the canonical "live ECS state digest" for cross-replay
|
|
350
|
+
* determinism. It replaces the earlier per-networked-component fold.
|
|
351
351
|
*
|
|
352
|
-
* **Sparse coverage
|
|
352
|
+
* **Sparse coverage.** Sparse data lives outside the archetype
|
|
353
353
|
* graph, so it is folded separately after the archetype loop — per store:
|
|
354
354
|
* the sparse-component id, the member count, then each member's source
|
|
355
355
|
* entity index + f64 field words, walked in CANONICAL ascending-index order
|
|
@@ -364,11 +364,11 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
364
364
|
* components), and strictly tighter than `columnStoreStateHash(...)`
|
|
365
365
|
* which scans the full SAB including trailing unused capacity.
|
|
366
366
|
*
|
|
367
|
-
* **Per-word fold
|
|
367
|
+
* **Per-word fold.** The inner column loop folds one 32-bit
|
|
368
368
|
* word at a time using FNV-1a's `xor + imul(PRIME)` step. This is NOT
|
|
369
369
|
* byte-for-byte FNV-1a-32 of the column bytes — it's a deterministic
|
|
370
|
-
* digest with the same equality semantics,
|
|
371
|
-
* byte loop it replaces. Trailing 0–3 tail bytes (only possible for
|
|
370
|
+
* digest with the same equality semantics, and much quicker than the
|
|
371
|
+
* per-byte loop it replaces. Trailing 0–3 tail bytes (only possible for
|
|
372
372
|
* u8/u16 columns at odd row counts) are folded together as a single
|
|
373
373
|
* little-endian word so the algorithm stays branch-free in the inner
|
|
374
374
|
* loop. The 4-byte `id` and `len` headers are folded as words for the
|
|
@@ -381,7 +381,7 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
381
381
|
* processes on the same architecture (which is all `replay_match`
|
|
382
382
|
* needs — both replays run the same algorithm on the same words).
|
|
383
383
|
*
|
|
384
|
-
* **Opt-in
|
|
384
|
+
* **Opt-in.** Throws `DETERMINISM_DISABLED` unless the
|
|
385
385
|
* Store was constructed with `{ deterministic: true }`. The canonical
|
|
386
386
|
* ordering this fold relies on (sparse `canonicalIndices`, sorted relation
|
|
387
387
|
* target sets) is the determinism tax the flag gates. */
|
|
@@ -391,8 +391,7 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
391
391
|
* by a WASM system to resolve an
|
|
392
392
|
* `EntityID` from an event-ring payload — Zig writes
|
|
393
393
|
* `(archId, row, …)` to the event ring,
|
|
394
|
-
* and TS bridges it back through `ctx.emit(...)` via this method.
|
|
395
|
-
* Phase 4 PR 4D)
|
|
394
|
+
* and TS bridges it back through `ctx.emit(...)` via this method.
|
|
396
395
|
*
|
|
397
396
|
* Throws `ECSError` if `archetype_id` is out of range or `row` is
|
|
398
397
|
* past the archetype's live row count — these would indicate a
|
|
@@ -404,7 +403,7 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
404
403
|
* `ArchetypeGraph.getOrCreateFromMask`. */
|
|
405
404
|
private archGetOrCreateFromMask;
|
|
406
405
|
/** Bulk variant of `archGetOrCreateFromMask` — one `extendColumnStore`
|
|
407
|
-
* call for the whole batch (
|
|
406
|
+
* call for the whole batch (the prewarm pass). See
|
|
408
407
|
* `ArchetypeGraph.createManyFromMasks`. */
|
|
409
408
|
archCreateManyFromMasks(masks: readonly BitSet[]): ArchetypeID[];
|
|
410
409
|
/** Snapshot every existing archetype's SAB rows, call `extendColumnStore`
|
|
@@ -435,11 +434,11 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
435
434
|
* generation so `isAlive` is already true), so the caller MUST have
|
|
436
435
|
* reserved the column capacity for the row first
|
|
437
436
|
* (`Archetype.ensureRowCapacity`) — otherwise a cap throw from the
|
|
438
|
-
* subsequent append leaves the slot phantom-alive
|
|
437
|
+
* subsequent append leaves the slot phantom-alive. */
|
|
439
438
|
private _allocEntity;
|
|
440
439
|
/** Pre-check that `count` fresh entity slots can be allocated without
|
|
441
|
-
* exhausting the entity-index space, so `spawnMany` commits all-or-nothing
|
|
442
|
-
*
|
|
440
|
+
* exhausting the entity-index space, so `spawnMany` commits all-or-nothing.
|
|
441
|
+
* `_allocEntity`'s own per-call high-water guard would otherwise
|
|
443
442
|
* throw `EID_MAX_INDEX_OVERFLOW` partway through the alloc loop, leaving the
|
|
444
443
|
* slots it already committed phantom-alive. Free-list reuse covers the first
|
|
445
444
|
* `entityFreeIndices.length` slots; only the remainder draws down the
|
|
@@ -475,7 +474,7 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
475
474
|
* With no `delete`/`clear` target-cleanup policy registered (the common
|
|
476
475
|
* case) this tears the one entity down and returns — no allocation. When a
|
|
477
476
|
* policy is in play, a `delete`-target's sources are appended to a local
|
|
478
|
-
* work-list this method then drains in the same iterative pass
|
|
477
|
+
* work-list this method then drains in the same iterative pass:
|
|
479
478
|
* the `work.length` re-read drives chains and trees out without recursion, so
|
|
480
479
|
* depth is bounded by entity count, not tree depth. This mirrors the deferred
|
|
481
480
|
* `flushDestroyed` buffer mechanism — both paths are iterative and reach the
|
|
@@ -488,38 +487,65 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
488
487
|
* then recycle (or retire) its slot. Shared by both immediate-destroy entry
|
|
489
488
|
* points (the fast no-cascade path and the work-list driver in
|
|
490
489
|
* `destroyEntity`). When `cascade` is non-null, a `delete`-policy target's
|
|
491
|
-
* surviving sources are appended to it for the driver to drain
|
|
490
|
+
* surviving sources are appended to it for the driver to drain;
|
|
492
491
|
* `null` skips that collection for callers that cannot cascade. The caller
|
|
493
492
|
* must have already confirmed `id` is alive. */
|
|
494
493
|
private _destroyOne;
|
|
495
494
|
/**
|
|
496
495
|
* Liveness check, **fail-closed** against forged / retired / out-of-bounds
|
|
497
|
-
* handles
|
|
496
|
+
* handles. For a general-purpose engine that may receive a handle from
|
|
498
497
|
* serialization, IPC, or any untrusted caller, three malformed inputs must read
|
|
499
498
|
* dead rather than alias a slot:
|
|
500
499
|
* - **Out of range** — an `id` outside the 31-bit packed space (`< 0` or
|
|
501
500
|
* `> MAX_ENTITY_ID`). Without this, the 20-bit index mask below silently
|
|
502
501
|
* folds garbage high bits onto a valid slot. (Same bound the snapshot /
|
|
503
|
-
* postMessage decode applies
|
|
502
|
+
* postMessage decode applies.)
|
|
504
503
|
* - **Tombstone generation** — a handle carrying `RETIRED_GENERATION`, which
|
|
505
504
|
* the allocator stamps into a retired slot and never issues to a live
|
|
506
505
|
* entity, would otherwise match a retired slot's parked generation and read
|
|
507
506
|
* alive (the ABA tombstone, previously documented as a known gap).
|
|
508
507
|
* Both guards are comparisons predicted not-taken on the live path, so a
|
|
509
|
-
* well-formed handle pays two branches and nothing else
|
|
508
|
+
* well-formed handle pays two branches and nothing else.
|
|
510
509
|
*/
|
|
511
510
|
isAlive(id: EntityID): boolean;
|
|
511
|
+
/**
|
|
512
|
+
* Liveness and the packed index in one result: the entity index if `id` is
|
|
513
|
+
* live, else `-1`. Same three fail-closed guards as `isAlive` (documented
|
|
514
|
+
* above), and the sole implementation of them — `isAlive` is a comparison on
|
|
515
|
+
* top of this.
|
|
516
|
+
*
|
|
517
|
+
* Why it returns the index instead of a boolean: every by-id caller needs
|
|
518
|
+
* BOTH answers, and the pair used to cost two derivations of the same index.
|
|
519
|
+
* `hasComponent` called `isAlive(id)` — which computed `getEntityIndex(id)`
|
|
520
|
+
* internally — and then computed `getEntityIndex(id)` again to reach
|
|
521
|
+
* `entityArchetype`. The generational check has already touched the index; a
|
|
522
|
+
* caller that is about to index a parallel array with it should be handed the
|
|
523
|
+
* one that was computed, not re-derive it. The generations read is inlined
|
|
524
|
+
* here for the same reason, rather than delegated to
|
|
525
|
+
* `entityAllocator.isAliveIndex` — which stays as the index-domain entry point
|
|
526
|
+
* for callers that already hold an index.
|
|
527
|
+
*
|
|
528
|
+
* **This gives much less than it appears to give.** The estimate for this
|
|
529
|
+
* change was a large decrease. The measurement shows that `has` and `isAlive`
|
|
530
|
+
* are only a little faster. The decrease is real, but it is much smaller than
|
|
531
|
+
* the estimate. The work that we removed was truly not necessary. But V8
|
|
532
|
+
* already made both one-line functions inline, and it already removed most of
|
|
533
|
+
* the duplicated calculation. Therefore "the work is plainly not necessary" is
|
|
534
|
+
* not an argument about performance for a JIT compiler. This note stops the
|
|
535
|
+
* next reader from making the same estimate again.
|
|
536
|
+
*/
|
|
537
|
+
private _liveIndex;
|
|
512
538
|
get entityCount(): number;
|
|
513
539
|
/** An archetype's row count moved from `preLen` to its current
|
|
514
540
|
* `arch.length` on a **shrink** (rows removed: the source of a transition, a
|
|
515
|
-
* destroy, a batch-source drain). Always marks SAB row counts dirty
|
|
516
|
-
* the descriptor walk just needs "something moved"); bumps the query-dirty
|
|
517
|
-
* epoch
|
|
518
|
-
* `Query._nonEmptyArchetypes` can change on a shrink
|
|
541
|
+
* destroy, a batch-source drain). Always marks SAB row counts dirty
|
|
542
|
+
* (the descriptor walk just needs "something moved"); bumps the query-dirty
|
|
543
|
+
* epoch only on a `length` 0/non-zero crossing, the only case where
|
|
544
|
+
* `Query._nonEmptyArchetypes` can change on a shrink. Mutations that
|
|
519
545
|
* move row counts within the same side (6→5) leave the non-empty set unchanged
|
|
520
546
|
* and skip the bump.
|
|
521
547
|
*
|
|
522
|
-
* A shrink does **not** need the `enabledCount` crossing test
|
|
548
|
+
* A shrink does **not** need the `enabledCount` crossing test: the
|
|
523
549
|
* only enabled-count move it can make is 1→0 (the last enabled row leaves an
|
|
524
550
|
* archetype that keeps disabled rows), which leaves the archetype in a default
|
|
525
551
|
* query's non-empty list as a harmless stale *inclusion* — `count`/`forEach`
|
|
@@ -530,29 +556,29 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
530
556
|
* **Inlining-sensitive — keep the body tiny.** This function is called
|
|
531
557
|
* once or twice per immediate-mode `addComponent` / `removeComponent` and the
|
|
532
558
|
* mutation hot path depends on it being inlined at every call site.
|
|
533
|
-
*
|
|
534
|
-
* gate to skip the bump for no-query workloads
|
|
535
|
-
*
|
|
536
|
-
*
|
|
537
|
-
*
|
|
538
|
-
*
|
|
559
|
+
* An earlier change added an `if (registeredQueries.length === 0) return;`
|
|
560
|
+
* gate to skip the bump for no-query workloads. The bench showed a large
|
|
561
|
+
* regression of the mutation churn loop, because the extra statement pushed
|
|
562
|
+
* the function past V8's per-call inlining budget. The gate is no longer in
|
|
563
|
+
* the code. Do a bench run before you merge a change here. Code review
|
|
564
|
+
* alone is not sufficient. */
|
|
539
565
|
private _onArchLenChange;
|
|
540
566
|
/** An archetype **grew** — rows were appended (the target of a transition, a
|
|
541
567
|
* spawn, a batch-target fill). Like `_onArchLenChange` it marks row counts
|
|
542
568
|
* dirty and bumps the query-dirty epoch on a `length` 0/non-zero crossing
|
|
543
569
|
* (`includeDisabled` membership), but it *also* bumps on an `enabledCount`
|
|
544
|
-
* 0→1 crossing
|
|
570
|
+
* 0→1 crossing. The non-empty filter is field-split: a default
|
|
545
571
|
* query keeps archetypes with `enabledCount > 0`. An enabled row appended to
|
|
546
572
|
* an archetype that is non-empty but all-disabled (`length > 0,
|
|
547
573
|
* enabledCount == 0`) crosses `enabledCount` 0→1 without touching `length`,
|
|
548
|
-
* so the `preLen` test alone (the valid
|
|
549
|
-
*
|
|
574
|
+
* so the `preLen` test alone (the earlier proxy, valid only while
|
|
575
|
+
* `enabledCount === length`) misses it and a cached default query keeps a stale
|
|
550
576
|
* `_nonEmpty` list. Only grows can do this, so only grow sites carry the test.
|
|
551
577
|
*
|
|
552
578
|
* **Precondition: ≥1 row was appended** (every caller adds at least one row),
|
|
553
579
|
* so `arch.length > 0` afterward — which is why the crossings simplify and the
|
|
554
580
|
* body stays inlinable (the inlining caveat on `_onArchLenChange` applies
|
|
555
|
-
* here too;
|
|
581
|
+
* here too; this is bench-verified). The general
|
|
556
582
|
* `(pre === 0) !== (post === 0)` boundary test collapses given the post side:
|
|
557
583
|
* - `length`: post > 0 always ⇒ a crossing iff `preLen === 0`.
|
|
558
584
|
* - `enabledCount`: non-decreasing on a grow ⇒ a 0-crossing iff it was 0
|
|
@@ -561,7 +587,7 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
561
587
|
* read is short-circuited away on the hot path (`preLen` or `preEnabled`
|
|
562
588
|
* non-zero), so a no-disabled workload pays only two scalar compares. */
|
|
563
589
|
private _onArchGrow;
|
|
564
|
-
/** Dirty bookkeeping for an enable/disable toggle
|
|
590
|
+
/** Dirty bookkeeping for an enable/disable toggle. `length` is
|
|
565
591
|
* unchanged (no row added/removed) but `enabled_count` moved, so: republish
|
|
566
592
|
* row counts (the descriptor's `enabled_count` changed, so the WASM sim and
|
|
567
593
|
* snapshot see the new partition), and bump the query epoch only when the
|
|
@@ -580,7 +606,7 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
580
606
|
isDisabled(id: EntityID): boolean;
|
|
581
607
|
/** 0-crossing detection for the per-entity flush paths (`_flushAdds`,
|
|
582
608
|
* `_flushRemoves`) without per-entity Map traffic — the same cost the
|
|
583
|
-
* destroy drain
|
|
609
|
+
* destroy drain also avoids. Each touched archetype is stamped with the
|
|
584
610
|
* current flush epoch (`Archetype._flushSeenEpoch`), its pre-length and
|
|
585
611
|
* pre-enabled-count recorded on first sight (`_flushPreLen` /
|
|
586
612
|
* `_flushPreEnabled`), and pushed onto this scratch list;
|
|
@@ -594,12 +620,12 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
594
620
|
* the query epoch once if any touched archetype crossed the 0 boundary on
|
|
595
621
|
* *either* `length` (includeDisabled membership) or `enabledCount`
|
|
596
622
|
* (default-query membership) — the deferred analog of the immediate
|
|
597
|
-
* `_onArchLenChange` two-field check
|
|
623
|
+
* `_onArchLenChange` two-field check. A single bump is sufficient
|
|
598
624
|
* (queries only need to know "something changed"). Clears the touched list
|
|
599
625
|
* and advances the flush epoch on exit. */
|
|
600
626
|
private _settleFlushDirty;
|
|
601
627
|
destroyEntityDeferred(id: EntityID): void;
|
|
602
|
-
/** Buffer an enable/disable toggle for the phase flush
|
|
628
|
+
/** Buffer an enable/disable toggle for the phase flush. The row swap a
|
|
603
629
|
* toggle performs would corrupt a `forEach` over that archetype if applied
|
|
604
630
|
* mid-system, so it is deferred like add/remove. */
|
|
605
631
|
disableEntityDeferred(id: EntityID): void;
|
|
@@ -610,13 +636,13 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
610
636
|
* entity's final archetype placement for the tick.
|
|
611
637
|
*
|
|
612
638
|
* When an onDisable/onEnable observer is registered (`_toggleObserverCount >
|
|
613
|
-
* 0
|
|
639
|
+
* 0`) this also collects effective toggle events into `_obsEvents` for
|
|
614
640
|
* the dispatch hook, collapsed to one event per *net* transition across the
|
|
615
641
|
* drain (see `_toggleInitial`). The no-observer path is byte-for-byte the
|
|
616
|
-
*
|
|
642
|
+
* earlier drain. */
|
|
617
643
|
private _flushToggles;
|
|
618
644
|
/** Fan one entity's net toggle transition out to an onDisable / onEnable event
|
|
619
|
-
* per carried component
|
|
645
|
+
* per carried component. Walks the entity's archetype mask through the
|
|
620
646
|
* matching pre-bound bit visitor; a component-less entity (no row) carries
|
|
621
647
|
* nothing and is skipped. */
|
|
622
648
|
private _collectToggle;
|
|
@@ -631,7 +657,7 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
631
657
|
* Same commit-then-observe discipline as `_flushRemoves`: the entity is fully
|
|
632
658
|
* freed before the callback runs, so onRemove receives the (now dead) eid as
|
|
633
659
|
* the identity of what was destroyed, not a live handle to read. The
|
|
634
|
-
* no-observer path is byte-for-byte unchanged (`collecting` gate).
|
|
660
|
+
* no-observer path is byte-for-byte unchanged (`collecting` gate).
|
|
635
661
|
*
|
|
636
662
|
* Re-entrancy: while the observed fixed point owns the flush, the loop
|
|
637
663
|
* drains destroys itself via `_drainDestroyed`, so a re-entrant
|
|
@@ -647,7 +673,7 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
647
673
|
/** Phase-boundary structural flush. The drain policy — no-observer fast
|
|
648
674
|
* path, observed fixed point (adds/removes → destroys → toggles),
|
|
649
675
|
* convergence guard, re-entrancy — lives in `DeferredCommandBuffer`
|
|
650
|
-
|
|
676
|
+
*; the batch appliers it drives are the `_flush*` /
|
|
651
677
|
* `_drainDestroyed` methods below. */
|
|
652
678
|
flushStructural(): void;
|
|
653
679
|
/** Batch-apply all deferred component additions. */
|
|
@@ -657,13 +683,13 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
657
683
|
get pendingStructuralCount(): number;
|
|
658
684
|
/** Set the per-component observation flags from the registry's aggregate of
|
|
659
685
|
* live observers for `cid`. Maintains `_structuralObserverCount` and
|
|
660
|
-
* `_toggleObserverCount` (
|
|
686
|
+
* `_toggleObserverCount` (the fast-path gates) and lazily allocates the
|
|
661
687
|
* dirty list when per-entity onSet tracking turns on. */
|
|
662
688
|
_configureComponentObservation(cid: number, hasAdd: boolean, hasRem: boolean, hasDisable: boolean, hasEnable: boolean, trackDirty: boolean): void;
|
|
663
689
|
/** Record a per-entity onSet "changed" event for the entity. Called from the
|
|
664
690
|
* field-write path (`SystemContext.setField` / `markChanged`) and gated by
|
|
665
691
|
* the caller on `_anyDirtyTracked`. Appends to the dirty list only if the
|
|
666
|
-
* dedup bit was clear (the
|
|
692
|
+
* dedup bit was clear (the dirty list + dedup-bit mechanism). */
|
|
667
693
|
_noteSet(def: ComponentHandle, eid: EntityID): void;
|
|
668
694
|
private _growDirtyMarks;
|
|
669
695
|
/** Detach and return the dirty-row list for `cid`, clearing its dedup bits and
|
|
@@ -681,7 +707,7 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
681
707
|
* per-archetype change tick (free; no write-path cost). */
|
|
682
708
|
_forEachChangedArchetype(cid: number, baseline: number, cb: (arch: Archetype) => void): void;
|
|
683
709
|
/** Enabled live entities currently carrying `cid`, used by `yieldExisting` to
|
|
684
|
-
* replay onAdd on registration. Bounded by `enabled_count
|
|
710
|
+
* replay onAdd on registration. Bounded by `enabled_count`: a disabled
|
|
685
711
|
* entity is excluded from default queries, so seeding it via onAdd would
|
|
686
712
|
* publish a row that an immediate onDisable should have removed — it is simply
|
|
687
713
|
* absent at seed (the "delete on disable" semantics). Unordered here — the
|
|
@@ -694,16 +720,16 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
694
720
|
/** Return the field index assigned to `(def, fieldName)` at component
|
|
695
721
|
* registration. Indexes are insertion-order, zero-based, and stable for
|
|
696
722
|
* the lifetime of the ECS. Used by systems that pass `(component_id,
|
|
697
|
-
* field_id)` pairs across the WASM FFI
|
|
723
|
+
* field_id)` pairs across the WASM FFI. */
|
|
698
724
|
fieldIdOf(def: ComponentHandle, fieldName: string): number;
|
|
699
725
|
/** Register a sparse component or tag. Unlike `registerComponent`, this
|
|
700
726
|
* allocates from a separate id space and never touches the archetype mask,
|
|
701
727
|
* so it does **not** count against `STORE_DESCRIPTOR_COMPONENT_LIMIT`. See
|
|
702
|
-
*
|
|
728
|
+
* `sparse_store.ts`. */
|
|
703
729
|
registerSparseComponent<S extends Record<string, TypedArrayTag>>(schema: S, name?: string): SparseComponentDef<S>;
|
|
704
730
|
/** Sparse sibling of `componentLabel` — sparse ids are a separate id space. */
|
|
705
731
|
sparseLabel(sid: number): string;
|
|
706
|
-
/** Allocate the backing sparse store WITHOUT the
|
|
732
|
+
/** Allocate the backing sparse store WITHOUT the float guard, for
|
|
707
733
|
* engine-internal backings whose `f64` holds an EXACT integer rather than a
|
|
708
734
|
* user quantity: the exclusive-relation `{ target }` slot stores an `EntityID`
|
|
709
735
|
* (≤ 2^53, so f64 is bit-exact and cross-host identical — the ban targets float
|
|
@@ -731,11 +757,11 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
731
757
|
* by the relation side data (`snapshotRelations` — multi forward target
|
|
732
758
|
* sets, which live outside the sparse store). Both are written in canonical
|
|
733
759
|
* entity-index order, so two worlds with identical contents inserted in
|
|
734
|
-
* different orders snapshot byte-for-byte the same
|
|
760
|
+
* different orders snapshot byte-for-byte the same. The reverse index
|
|
735
761
|
* is derived and never serialized — `restoreSparse` rebuilds it. Pairs with
|
|
736
762
|
* `restoreSparse`.
|
|
737
763
|
*
|
|
738
|
-
* **Opt-in
|
|
764
|
+
* **Opt-in.** Throws `DETERMINISM_DISABLED` unless the
|
|
739
765
|
* Store was constructed with `{ deterministic: true }` — the canonical
|
|
740
766
|
* entity-index ordering is the determinism tax the flag gates. */
|
|
741
767
|
snapshotSparse(): Uint8Array;
|
|
@@ -749,7 +775,7 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
749
775
|
* code). Throws `SparseRestoreError` if the snapshot's shape, field identity,
|
|
750
776
|
* entity-index bounds, or frame length don't validate.
|
|
751
777
|
*
|
|
752
|
-
* **Opt-in
|
|
778
|
+
* **Opt-in.** Throws `DETERMINISM_DISABLED` unless the
|
|
753
779
|
* Store was constructed with `{ deterministic: true }`; paired with
|
|
754
780
|
* `snapshotSparse`, which produces the canonical bytes restore consumes. */
|
|
755
781
|
restoreSparse(bytes: Uint8Array): void;
|
|
@@ -763,7 +789,7 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
763
789
|
* order is load-bearing for byte-identical resume), the alive count, and each
|
|
764
790
|
* archetype's `length` / `enabledCount`.
|
|
765
791
|
*
|
|
766
|
-
* **Opt-in
|
|
792
|
+
* **Opt-in.** Throws `DETERMINISM_DISABLED` unless constructed with
|
|
767
793
|
* `{ deterministic: true }` — the sparse section rides the canonical-ordering
|
|
768
794
|
* surface and byte-identical resume is a determinism property. Pairs with
|
|
769
795
|
* `restoreInto`.
|
|
@@ -784,7 +810,7 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
784
810
|
*
|
|
785
811
|
* Requires a world whose SAB-backed archetype set + column layout match the
|
|
786
812
|
* snapshot's exactly (prewarm so the archetype set is stable) and the same
|
|
787
|
-
* entity-index capacity. **Opt-in
|
|
813
|
+
* entity-index capacity. **Opt-in:** throws `DETERMINISM_DISABLED`
|
|
788
814
|
* unless `{ deterministic: true }`. See `snapshot()` for the v1 scope. */
|
|
789
815
|
restoreInto(bytes: Uint8Array): void;
|
|
790
816
|
/** Adopt a restored dense store (`SnapshotService.restoreInto`'s mount
|
|
@@ -797,7 +823,7 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
797
823
|
* `_entityIds` after the dense backing was swapped in `restoreInto`. `length`
|
|
798
824
|
* + the per-row entity-id back-reference come from a scan of the restored
|
|
799
825
|
* entity-index region (which entity occupies which row); `enabledCount` comes
|
|
800
|
-
* from the captured host-state (the
|
|
826
|
+
* from the captured host-state (the partition boundary is positional only
|
|
801
827
|
* — it has no per-entity byte source). */
|
|
802
828
|
private _reconstructHostRows;
|
|
803
829
|
registerRelation(opts?: RelationOptions): RelationDef;
|
|
@@ -820,7 +846,7 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
820
846
|
ancestorsOf(src: EntityID, def: RelationDef): EntityID[];
|
|
821
847
|
rootOf(src: EntityID, def: RelationDef): EntityID;
|
|
822
848
|
cascadeOf(root: EntityID, def: RelationDef): EntityID[];
|
|
823
|
-
/** Second query-match path
|
|
849
|
+
/** Second query-match path: iterate entities matching a
|
|
824
850
|
* dense mask **and** sparse-membership terms, invoking `cb` per entity.
|
|
825
851
|
* Yields `EntityID`s, not archetype spans — sparse members are scattered
|
|
826
852
|
* across archetypes, so there is no SoA column to hand back. Driven by the
|
|
@@ -836,13 +862,20 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
836
862
|
* - **neither** → walk `denseArchetypes`' entity ids (dense-only fallback).
|
|
837
863
|
*
|
|
838
864
|
* Only reached via `Query.forEachEntity`; dense `forEach` never consults
|
|
839
|
-
* the sparse stores, so dense-only queries are unaffected
|
|
865
|
+
* the sparse stores, so dense-only queries are unaffected. */
|
|
840
866
|
_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;
|
|
841
|
-
/** Fourth query-match path
|
|
867
|
+
/** Fourth query-match path: the matched set in hierarchy depth order
|
|
842
868
|
* (parents before children) — see `RelationService.forEachHierarchyMatch`. */
|
|
843
869
|
_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;
|
|
844
870
|
addComponent(entityId: EntityID, def: ComponentDef<Record<string, never>>): void;
|
|
845
871
|
addComponent<S extends ComponentSchema>(entityId: EntityID, def: ComponentDef<S>, values: FieldValues<S>): void;
|
|
872
|
+
/** @internal — cold tail of `addComponent`: the entity already holds `def`
|
|
873
|
+
* (overwrite in place, no transition), or the (source, component) add edge
|
|
874
|
+
* has not been cached yet (first time this pair is seen — resolve, which
|
|
875
|
+
* plants the edge, then re-enter the hot body). Split out so the edge-hit
|
|
876
|
+
* path above stays a straight line with one holey-array probe; this runs at
|
|
877
|
+
* most once per (archetype, component) pair plus on in-place overwrites. */
|
|
878
|
+
private _addComponentCold;
|
|
846
879
|
/** Add multiple components in one transition (resolves final archetype, then moves once).
|
|
847
880
|
*
|
|
848
881
|
* Final-mask resolve, not graph walk. The previous implementation called
|
|
@@ -851,22 +884,21 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
851
884
|
* triggered a fresh `extendColumnStore` even though no entity ever lived
|
|
852
885
|
* there. Computing the union mask up front and resolving once via
|
|
853
886
|
* `archGetOrCreateFromMask` collapses N-1 intermediate-archetype
|
|
854
|
-
* creations into zero for the batched case
|
|
887
|
+
* creations into zero for the batched case. The lazy
|
|
855
888
|
* single-mask path remains the same; this just avoids feeding it
|
|
856
889
|
* archetypes the entity never visits.
|
|
857
890
|
*
|
|
858
|
-
*
|
|
891
|
+
* Composite-add edge cache. The final-mask resolve, unlike the
|
|
859
892
|
* single-add `edges[]` walk, re-pays a per-call `mask.hash()`, `ArchetypeGraph.lookup`
|
|
860
893
|
* (the Map-of-buckets + `equals` scan), and `getBatchTransitionMap` on
|
|
861
|
-
* every call
|
|
862
|
-
*
|
|
863
|
-
* set) add now resolves through `currentArch`'s composite-add cache: one
|
|
894
|
+
* every call. That is much slower than a cached edge walk, and a probe put the
|
|
895
|
+
* cost on the two `Map.get` calls, and not on the hash. So a repeated (source,
|
|
896
|
+
* added-set) add now resolves through `currentArch`'s composite-add cache: one
|
|
864
897
|
* `Map.get` on an exact packed key yields the target + transition map, and we
|
|
865
898
|
* skip the union-mask build entirely. First call per key still resolves via
|
|
866
|
-
* the final-mask path below (no intermediate planting) and plants the edge.
|
|
867
|
-
* See docs/reports/bench/regressions/add-components-composite-edge.md. */
|
|
899
|
+
* the final-mask path below (no intermediate planting) and plants the edge. */
|
|
868
900
|
addComponents(entityId: EntityID, entries: readonly TemplateEntryData[]): void;
|
|
869
|
-
/** Shared move+write tail of `addComponents
|
|
901
|
+
/** Shared move+write tail of `addComponents`: place the entity into
|
|
870
902
|
* the already-resolved `targetArch` — a `moveEntityFrom` along the cached
|
|
871
903
|
* `map` when it has a row, else a fresh append (the rowless empty-archetype
|
|
872
904
|
* source ignores `map`) — then write every entry's fields. Both the
|
|
@@ -874,6 +906,10 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
874
906
|
* the placement logic lives once. */
|
|
875
907
|
private _addComponentsInto;
|
|
876
908
|
removeComponent(entityId: EntityID, def: ComponentDef): void;
|
|
909
|
+
/** @internal — cold tail of `removeComponent`: the entity doesn't hold `def`
|
|
910
|
+
* (no-op), or the (source, component) remove edge has not been cached yet.
|
|
911
|
+
* Mirror of `_addComponentCold`; same depth-1 re-entry argument. */
|
|
912
|
+
private _removeComponentCold;
|
|
877
913
|
/** Remove multiple components in one transition (resolves final archetype, then moves once).
|
|
878
914
|
*
|
|
879
915
|
* Final-mask resolve, not graph walk. Same rationale as `addComponents`
|
|
@@ -882,7 +918,7 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
882
918
|
* removal path. Computing the difference mask up front and resolving
|
|
883
919
|
* once avoids planting N-1 intermediates the entity never lives in. */
|
|
884
920
|
removeComponents(entityId: EntityID, defs: ComponentDef[]): void;
|
|
885
|
-
/** Total
|
|
921
|
+
/** Total: a dead/stale `entityId` returns `false` rather
|
|
886
922
|
* than throwing — a "has" probe is exactly what callers reach for to avoid
|
|
887
923
|
* touching dead entities, so it must be safe to ask. */
|
|
888
924
|
hasComponent(entityId: EntityID, def: ComponentHandle): boolean;
|
|
@@ -900,6 +936,49 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
900
936
|
batchRemoveComponent(src: ArchetypeID, def: ComponentDef): void;
|
|
901
937
|
getEntityArchetype(entityId: EntityID): Archetype;
|
|
902
938
|
getEntityRow(entityId: EntityID): number;
|
|
939
|
+
/**
|
|
940
|
+
* The row `resolveEntity` placed the entity at — the alloc-free second
|
|
941
|
+
* return value of a resolve, read immediately after the call. Same out-param
|
|
942
|
+
* pattern as `EntityAllocator.lastIndex` and `_moveResult`; returning a
|
|
943
|
+
* `{ arch, row }` pair instead would allocate on every by-id read.
|
|
944
|
+
*/
|
|
945
|
+
resolvedRow: number;
|
|
946
|
+
/**
|
|
947
|
+
* (archetype, row) for a by-id access, derived from ONE index computation.
|
|
948
|
+
*
|
|
949
|
+
* `getEntityArchetype` and `getEntityRow` are each one line, and every by-id
|
|
950
|
+
* caller needs both — so the pair cost two derivations of the same packed
|
|
951
|
+
* index and two call frames to read two elements of two parallel arrays
|
|
952
|
+
* addressed identically. This is that pair, fused: index once, publish the
|
|
953
|
+
* row on `resolvedRow`, return the archetype.
|
|
954
|
+
*
|
|
955
|
+
* The two single-purpose accessors stay above — tests reach for one half at a
|
|
956
|
+
* time — but no runtime path uses them in a pair any more.
|
|
957
|
+
*/
|
|
958
|
+
resolveEntity(entityId: EntityID): Archetype;
|
|
959
|
+
/**
|
|
960
|
+
* Build the `at(entity)` binder a `ComponentCursor` repoints itself through
|
|
961
|
+
* (ref.ts). Handing the cursor a closure rather than the Store keeps the
|
|
962
|
+
* mutation surface out of a value that user code holds onto.
|
|
963
|
+
*
|
|
964
|
+
* `stampTick` distinguishes the two variants once, here, instead of per
|
|
965
|
+
* `at()`: a mutable cursor bumps the component's change tick on every
|
|
966
|
+
* repoint (matching `ctx.ref`), a read-only one never does.
|
|
967
|
+
*
|
|
968
|
+
* The access check lives HERE, in the binder, and not only at the call that
|
|
969
|
+
* creates the cursor. A cursor is made one time and then kept, so it outlives
|
|
970
|
+
* the span that made it: a cursor made at host level (where no system is
|
|
971
|
+
* active, and the check at creation therefore passes) writes an undeclared
|
|
972
|
+
* component when a system body uses it, and a `ctx.cursor` that a system
|
|
973
|
+
* stores in an outer variable does the same in the NEXT system. Both slip
|
|
974
|
+
* past a check that only runs at creation. `at()` is the point of use, so the
|
|
975
|
+
* check belongs on it. The creation-site check stays as well: it fails early,
|
|
976
|
+
* and its stack names the line that made the cursor.
|
|
977
|
+
*/
|
|
978
|
+
cursorBinder(def: ComponentHandle, stampTick: boolean): CursorBinder;
|
|
979
|
+
/** A component's field names in schema order — the cursor prototype key and
|
|
980
|
+
* ordinal source (ref.ts). One array per component, owned by its meta. */
|
|
981
|
+
componentFieldNames(def: ComponentHandle): readonly string[];
|
|
903
982
|
/**
|
|
904
983
|
* Find all archetypes matching the given masks.
|
|
905
984
|
* Uses the inverted componentIndex to start from the component with the
|
|
@@ -929,7 +1008,7 @@ export declare class Store implements ObserverHost, QueryHost {
|
|
|
929
1008
|
getResource(key: symbol): unknown;
|
|
930
1009
|
setResource(key: symbol, value: unknown): void;
|
|
931
1010
|
/** Fails closed on a missing key; the present → absent → present
|
|
932
|
-
* lifecycle
|
|
1011
|
+
* lifecycle — see `ResourceRegistry.remove`. */
|
|
933
1012
|
removeResource(key: symbol): void;
|
|
934
1013
|
hasResource(key: symbol): boolean;
|
|
935
1014
|
}
|