@ecsia/core 0.1.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.
Files changed (191) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +29 -0
  3. package/dist/bitmask/bitmask.d.ts +21 -0
  4. package/dist/bitmask/bitmask.d.ts.map +1 -0
  5. package/dist/bitmask/bitmask.js +103 -0
  6. package/dist/bitmask/bitmask.js.map +1 -0
  7. package/dist/bitmask/index.d.ts +3 -0
  8. package/dist/bitmask/index.d.ts.map +1 -0
  9. package/dist/bitmask/index.js +2 -0
  10. package/dist/bitmask/index.js.map +1 -0
  11. package/dist/component/accessor.d.ts +40 -0
  12. package/dist/component/accessor.d.ts.map +1 -0
  13. package/dist/component/accessor.js +220 -0
  14. package/dist/component/accessor.js.map +1 -0
  15. package/dist/component/column-set.d.ts +20 -0
  16. package/dist/component/column-set.d.ts.map +1 -0
  17. package/dist/component/column-set.js +60 -0
  18. package/dist/component/column-set.js.map +1 -0
  19. package/dist/component/define.d.ts +23 -0
  20. package/dist/component/define.d.ts.map +1 -0
  21. package/dist/component/define.js +155 -0
  22. package/dist/component/define.js.map +1 -0
  23. package/dist/component/descriptors.d.ts +3 -0
  24. package/dist/component/descriptors.d.ts.map +1 -0
  25. package/dist/component/descriptors.js +147 -0
  26. package/dist/component/descriptors.js.map +1 -0
  27. package/dist/component/index.d.ts +10 -0
  28. package/dist/component/index.d.ts.map +1 -0
  29. package/dist/component/index.js +6 -0
  30. package/dist/component/index.js.map +1 -0
  31. package/dist/component/sidecar.d.ts +58 -0
  32. package/dist/component/sidecar.d.ts.map +1 -0
  33. package/dist/component/sidecar.js +136 -0
  34. package/dist/component/sidecar.js.map +1 -0
  35. package/dist/config.d.ts +55 -0
  36. package/dist/config.d.ts.map +1 -0
  37. package/dist/config.js +70 -0
  38. package/dist/config.js.map +1 -0
  39. package/dist/entity/codec.d.ts +45 -0
  40. package/dist/entity/codec.d.ts.map +1 -0
  41. package/dist/entity/codec.js +53 -0
  42. package/dist/entity/codec.js.map +1 -0
  43. package/dist/entity/index-allocator.d.ts +46 -0
  44. package/dist/entity/index-allocator.d.ts.map +1 -0
  45. package/dist/entity/index-allocator.js +121 -0
  46. package/dist/entity/index-allocator.js.map +1 -0
  47. package/dist/entity/index.d.ts +13 -0
  48. package/dist/entity/index.d.ts.map +1 -0
  49. package/dist/entity/index.js +7 -0
  50. package/dist/entity/index.js.map +1 -0
  51. package/dist/entity/record.d.ts +28 -0
  52. package/dist/entity/record.d.ts.map +1 -0
  53. package/dist/entity/record.js +42 -0
  54. package/dist/entity/record.js.map +1 -0
  55. package/dist/entity/ref.d.ts +70 -0
  56. package/dist/entity/ref.d.ts.map +1 -0
  57. package/dist/entity/ref.js +104 -0
  58. package/dist/entity/ref.js.map +1 -0
  59. package/dist/entity/reservation.d.ts +12 -0
  60. package/dist/entity/reservation.d.ts.map +1 -0
  61. package/dist/entity/reservation.js +28 -0
  62. package/dist/entity/reservation.js.map +1 -0
  63. package/dist/entity/store.d.ts +60 -0
  64. package/dist/entity/store.d.ts.map +1 -0
  65. package/dist/entity/store.js +193 -0
  66. package/dist/entity/store.js.map +1 -0
  67. package/dist/env.d.ts +2 -0
  68. package/dist/env.d.ts.map +1 -0
  69. package/dist/env.js +12 -0
  70. package/dist/env.js.map +1 -0
  71. package/dist/ids.d.ts +9 -0
  72. package/dist/ids.d.ts.map +1 -0
  73. package/dist/ids.js +8 -0
  74. package/dist/ids.js.map +1 -0
  75. package/dist/index.d.ts +29 -0
  76. package/dist/index.d.ts.map +1 -0
  77. package/dist/index.js +33 -0
  78. package/dist/index.js.map +1 -0
  79. package/dist/inspect-surface.d.ts +27 -0
  80. package/dist/inspect-surface.d.ts.map +1 -0
  81. package/dist/inspect-surface.js +14 -0
  82. package/dist/inspect-surface.js.map +1 -0
  83. package/dist/internal.d.ts +19 -0
  84. package/dist/internal.d.ts.map +1 -0
  85. package/dist/internal.js +19 -0
  86. package/dist/internal.js.map +1 -0
  87. package/dist/memory/allocU32.d.ts +25 -0
  88. package/dist/memory/allocU32.d.ts.map +1 -0
  89. package/dist/memory/allocU32.js +95 -0
  90. package/dist/memory/allocU32.js.map +1 -0
  91. package/dist/memory/buffers.d.ts +94 -0
  92. package/dist/memory/buffers.d.ts.map +1 -0
  93. package/dist/memory/buffers.js +308 -0
  94. package/dist/memory/buffers.js.map +1 -0
  95. package/dist/memory/index.d.ts +7 -0
  96. package/dist/memory/index.d.ts.map +1 -0
  97. package/dist/memory/index.js +4 -0
  98. package/dist/memory/index.js.map +1 -0
  99. package/dist/memory/layout.d.ts +37 -0
  100. package/dist/memory/layout.d.ts.map +1 -0
  101. package/dist/memory/layout.js +116 -0
  102. package/dist/memory/layout.js.map +1 -0
  103. package/dist/query/compile.d.ts +73 -0
  104. package/dist/query/compile.d.ts.map +1 -0
  105. package/dist/query/compile.js +158 -0
  106. package/dist/query/compile.js.map +1 -0
  107. package/dist/query/engine.d.ts +48 -0
  108. package/dist/query/engine.d.ts.map +1 -0
  109. package/dist/query/engine.js +230 -0
  110. package/dist/query/engine.js.map +1 -0
  111. package/dist/query/index.d.ts +8 -0
  112. package/dist/query/index.d.ts.map +1 -0
  113. package/dist/query/index.js +10 -0
  114. package/dist/query/index.js.map +1 -0
  115. package/dist/query/live-query.d.ts +122 -0
  116. package/dist/query/live-query.d.ts.map +1 -0
  117. package/dist/query/live-query.js +543 -0
  118. package/dist/query/live-query.js.map +1 -0
  119. package/dist/query/sparse-set.d.ts +18 -0
  120. package/dist/query/sparse-set.d.ts.map +1 -0
  121. package/dist/query/sparse-set.js +126 -0
  122. package/dist/query/sparse-set.js.map +1 -0
  123. package/dist/reactivity/change-version.d.ts +19 -0
  124. package/dist/reactivity/change-version.d.ts.map +1 -0
  125. package/dist/reactivity/change-version.js +76 -0
  126. package/dist/reactivity/change-version.js.map +1 -0
  127. package/dist/reactivity/index.d.ts +12 -0
  128. package/dist/reactivity/index.d.ts.map +1 -0
  129. package/dist/reactivity/index.js +12 -0
  130. package/dist/reactivity/index.js.map +1 -0
  131. package/dist/reactivity/log.d.ts +83 -0
  132. package/dist/reactivity/log.d.ts.map +1 -0
  133. package/dist/reactivity/log.js +260 -0
  134. package/dist/reactivity/log.js.map +1 -0
  135. package/dist/reactivity/observer-commands.d.ts +40 -0
  136. package/dist/reactivity/observer-commands.d.ts.map +1 -0
  137. package/dist/reactivity/observer-commands.js +111 -0
  138. package/dist/reactivity/observer-commands.js.map +1 -0
  139. package/dist/reactivity/observers.d.ts +50 -0
  140. package/dist/reactivity/observers.d.ts.map +1 -0
  141. package/dist/reactivity/observers.js +127 -0
  142. package/dist/reactivity/observers.js.map +1 -0
  143. package/dist/reactivity/reactivity.d.ts +141 -0
  144. package/dist/reactivity/reactivity.d.ts.map +1 -0
  145. package/dist/reactivity/reactivity.js +479 -0
  146. package/dist/reactivity/reactivity.js.map +1 -0
  147. package/dist/reactivity/structural-journal.d.ts +30 -0
  148. package/dist/reactivity/structural-journal.d.ts.map +1 -0
  149. package/dist/reactivity/structural-journal.js +77 -0
  150. package/dist/reactivity/structural-journal.js.map +1 -0
  151. package/dist/registry.d.ts +26 -0
  152. package/dist/registry.d.ts.map +1 -0
  153. package/dist/registry.js +58 -0
  154. package/dist/registry.js.map +1 -0
  155. package/dist/serialize-surface.d.ts +170 -0
  156. package/dist/serialize-surface.d.ts.map +1 -0
  157. package/dist/serialize-surface.js +6 -0
  158. package/dist/serialize-surface.js.map +1 -0
  159. package/dist/storage/archetype.d.ts +38 -0
  160. package/dist/storage/archetype.d.ts.map +1 -0
  161. package/dist/storage/archetype.js +47 -0
  162. package/dist/storage/archetype.js.map +1 -0
  163. package/dist/storage/cold-store.d.ts +41 -0
  164. package/dist/storage/cold-store.d.ts.map +1 -0
  165. package/dist/storage/cold-store.js +100 -0
  166. package/dist/storage/cold-store.js.map +1 -0
  167. package/dist/storage/index.d.ts +10 -0
  168. package/dist/storage/index.d.ts.map +1 -0
  169. package/dist/storage/index.js +5 -0
  170. package/dist/storage/index.js.map +1 -0
  171. package/dist/storage/signature.d.ts +27 -0
  172. package/dist/storage/signature.d.ts.map +1 -0
  173. package/dist/storage/signature.js +115 -0
  174. package/dist/storage/signature.js.map +1 -0
  175. package/dist/storage/storage.d.ts +72 -0
  176. package/dist/storage/storage.d.ts.map +1 -0
  177. package/dist/storage/storage.js +192 -0
  178. package/dist/storage/storage.js.map +1 -0
  179. package/dist/storage/store.d.ts +88 -0
  180. package/dist/storage/store.d.ts.map +1 -0
  181. package/dist/storage/store.js +473 -0
  182. package/dist/storage/store.js.map +1 -0
  183. package/dist/util/stable-index.d.ts +29 -0
  184. package/dist/util/stable-index.d.ts.map +1 -0
  185. package/dist/util/stable-index.js +51 -0
  186. package/dist/util/stable-index.js.map +1 -0
  187. package/dist/world.d.ts +262 -0
  188. package/dist/world.d.ts.map +1 -0
  189. package/dist/world.js +831 -0
  190. package/dist/world.js.map +1 -0
  191. package/package.json +52 -0
@@ -0,0 +1,40 @@
1
+ import type { ComponentDef, EntityHandle, RelationDef, RelationId, Schema } from '@ecsia/schema';
2
+ /** The world verbs the deferred buffer replays at flush time (all serial / main-thread). */
3
+ export interface ObserverCommandApply {
4
+ /** Place an already-minted (alive) handle into its target signature. The handle was reserved when
5
+ * the observer called spawn (so the observer could configure it); placement is deferred to flush. */
6
+ placeReserved(handle: EntityHandle, defs: readonly ComponentDef<Schema>[]): void;
7
+ add(handle: EntityHandle, def: ComponentDef<Schema>): void;
8
+ remove(handle: EntityHandle, def: ComponentDef<Schema>): void;
9
+ despawn(handle: EntityHandle): void;
10
+ isAlive(handle: EntityHandle): boolean;
11
+ /** Write initializer values through the tracked accessor path (value-carrying spawnWith, Item 8). */
12
+ writePayload(handle: EntityHandle, def: ComponentDef<Schema>, values: Record<string, unknown>): void;
13
+ /** Relation apply seams (undefined in a relation-free world). */
14
+ addPair?(subject: EntityHandle, relationId: RelationId, target: EntityHandle, payload: Record<string, unknown> | undefined): void;
15
+ removePair?(subject: EntityHandle, relationId: RelationId, target: EntityHandle): void;
16
+ }
17
+ export declare class ObserverCommandBuffer {
18
+ #private;
19
+ get deferring(): boolean;
20
+ get isDraining(): boolean;
21
+ get pendingCount(): number;
22
+ beginDeferring(): void;
23
+ endDeferring(): void;
24
+ enterDrain(): boolean;
25
+ exitDrain(): void;
26
+ stageSpawnWith(handle: EntityHandle, defs: readonly ComponentDef<Schema>[], values?: readonly (readonly [ComponentDef<Schema>, Record<string, unknown>])[]): void;
27
+ stageAdd(handle: EntityHandle, def: ComponentDef<Schema>): void;
28
+ stageRemove(handle: EntityHandle, def: ComponentDef<Schema>): void;
29
+ stageDespawn(handle: EntityHandle): void;
30
+ stageAddPair(subject: EntityHandle, relation: RelationDef<Schema | void>, relationId: RelationId, target: EntityHandle, payload: Record<string, unknown> | undefined): void;
31
+ stageRemovePair(subject: EntityHandle, relation: RelationDef<Schema | void>, relationId: RelationId, target: EntityHandle): void;
32
+ /**
33
+ * Apply every staged op in FIFO order (deterministic — staging order is the observers' fire order,
34
+ * which is itself deterministic merge order). Drop-if-dead is honored: a staged op whose subject is
35
+ * no longer alive at flush time is skipped (the entity was despawned by an earlier staged op or the
36
+ * intervening frame). Called at the start of the next drain — i.e. the next serial flush.
37
+ */
38
+ flush(apply: ObserverCommandApply): void;
39
+ }
40
+ //# sourceMappingURL=observer-commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observer-commands.d.ts","sourceRoot":"","sources":["../../src/reactivity/observer-commands.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AA6BhG,4FAA4F;AAC5F,MAAM,WAAW,oBAAoB;IACnC;yGACqG;IACrG,aAAa,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,YAAY,CAAC,MAAM,CAAC,EAAE,GAAG,IAAI,CAAA;IAChF,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,YAAY,CAAC,MAAM,CAAC,GAAG,IAAI,CAAA;IAC1D,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,YAAY,CAAC,MAAM,CAAC,GAAG,IAAI,CAAA;IAC7D,OAAO,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAAA;IACnC,OAAO,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAA;IACtC,qGAAqG;IACrG,YAAY,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IACpG,iEAAiE;IACjE,OAAO,CAAC,CAAC,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,IAAI,CAAA;IACjI,UAAU,CAAC,CAAC,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAAA;CACvF;AAED,qBAAa,qBAAqB;;IAOhC,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,IAAI,UAAU,IAAI,OAAO,CAExB;IAED,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,cAAc,IAAI,IAAI;IAItB,YAAY,IAAI,IAAI;IAIpB,UAAU,IAAI,OAAO;IAMrB,SAAS,IAAI,IAAI;IAIjB,cAAc,CACZ,MAAM,EAAE,YAAY,EACpB,IAAI,EAAE,SAAS,YAAY,CAAC,MAAM,CAAC,EAAE,EACrC,MAAM,GAAE,SAAS,CAAC,SAAS,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,EAAO,GACjF,IAAI;IAGP,QAAQ,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,YAAY,CAAC,MAAM,CAAC,GAAG,IAAI;IAG/D,WAAW,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,YAAY,CAAC,MAAM,CAAC,GAAG,IAAI;IAGlE,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI;IAGxC,YAAY,CACV,OAAO,EAAE,YAAY,EACrB,QAAQ,EAAE,WAAW,CAAC,MAAM,GAAG,IAAI,CAAC,EACpC,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAC3C,IAAI;IAGP,eAAe,CACb,OAAO,EAAE,YAAY,EACrB,QAAQ,EAAE,WAAW,CAAC,MAAM,GAAG,IAAI,CAAC,EACpC,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,YAAY,GACnB,IAAI;IAIP;;;;;OAKG;IACH,KAAK,CAAC,KAAK,EAAE,oBAAoB,GAAG,IAAI;CAuCzC"}
@@ -0,0 +1,111 @@
1
+ // Deferred observer command buffer. An observer handler MAY call
2
+ // world.spawn()/despawn()/add()/remove()/addPair()/removePair() during observerDrain. Because the
3
+ // drain runs at a serial slot — iterating a FROZEN log snapshot — those structural ops must NOT be
4
+ // direct-applied mid-drain (a synchronous despawn shuffle-pops a row a later observer in the same
5
+ // drain still needs to read, and a synchronous spawn would extend the wave the drain is replaying).
6
+ //
7
+ // Instead they are STAGED here and applied at the NEXT serial flush (the start of the next drain).
8
+ // This is the main-thread analogue of the worker command buffer: the observer sees a quiescent world,
9
+ // stages intent, and the intent lands deterministically one flush later. Consequence: an
10
+ // entity spawned inside an onChange handler is observed by onAdd observers NEXT frame, never
11
+ // re-entrantly this frame.
12
+ export class ObserverCommandBuffer {
13
+ #pending = [];
14
+ /** Set while observerDrain is executing — structural verbs route here instead of direct-applying. */
15
+ #deferring = false;
16
+ /** Re-entrancy guard: observerDrain must never re-enter itself (a flush could trigger a drain). */
17
+ #draining = false;
18
+ get deferring() {
19
+ return this.#deferring;
20
+ }
21
+ get isDraining() {
22
+ return this.#draining;
23
+ }
24
+ get pendingCount() {
25
+ return this.#pending.length;
26
+ }
27
+ beginDeferring() {
28
+ this.#deferring = true;
29
+ }
30
+ endDeferring() {
31
+ this.#deferring = false;
32
+ }
33
+ enterDrain() {
34
+ if (this.#draining)
35
+ return false;
36
+ this.#draining = true;
37
+ return true;
38
+ }
39
+ exitDrain() {
40
+ this.#draining = false;
41
+ }
42
+ stageSpawnWith(handle, defs, values = []) {
43
+ this.#pending.push({ kind: 'spawnWith', handle, defs: defs.slice(), values: values.slice() });
44
+ }
45
+ stageAdd(handle, def) {
46
+ this.#pending.push({ kind: 'add', handle, def });
47
+ }
48
+ stageRemove(handle, def) {
49
+ this.#pending.push({ kind: 'remove', handle, def });
50
+ }
51
+ stageDespawn(handle) {
52
+ this.#pending.push({ kind: 'despawn', handle });
53
+ }
54
+ stageAddPair(subject, relation, relationId, target, payload) {
55
+ this.#pending.push({ kind: 'addPair', subject, relation, relationId, target, payload });
56
+ }
57
+ stageRemovePair(subject, relation, relationId, target) {
58
+ this.#pending.push({ kind: 'removePair', subject, relation, relationId, target });
59
+ }
60
+ /**
61
+ * Apply every staged op in FIFO order (deterministic — staging order is the observers' fire order,
62
+ * which is itself deterministic merge order). Drop-if-dead is honored: a staged op whose subject is
63
+ * no longer alive at flush time is skipped (the entity was despawned by an earlier staged op or the
64
+ * intervening frame). Called at the start of the next drain — i.e. the next serial flush.
65
+ */
66
+ flush(apply) {
67
+ if (this.#pending.length === 0)
68
+ return;
69
+ // Snapshot + clear FIRST so a re-entrant stage during apply (defensive) lands in the next batch,
70
+ // never extends this loop.
71
+ const ops = this.#pending;
72
+ this.#pending = [];
73
+ for (const op of ops) {
74
+ switch (op.kind) {
75
+ case 'spawnWith': {
76
+ // The handle was reserved-alive when the observer called spawn; place it now, then write any
77
+ // value-carrying initializers through the tracked path (Item 8).
78
+ if (apply.isAlive(op.handle)) {
79
+ apply.placeReserved(op.handle, op.defs);
80
+ for (const [def, values] of op.values)
81
+ apply.writePayload(op.handle, def, values);
82
+ }
83
+ break;
84
+ }
85
+ case 'add':
86
+ if (apply.isAlive(op.handle))
87
+ apply.add(op.handle, op.def);
88
+ break;
89
+ case 'remove':
90
+ if (apply.isAlive(op.handle))
91
+ apply.remove(op.handle, op.def);
92
+ break;
93
+ case 'despawn':
94
+ if (apply.isAlive(op.handle))
95
+ apply.despawn(op.handle);
96
+ break;
97
+ case 'addPair':
98
+ if (apply.addPair !== undefined && apply.isAlive(op.subject) && apply.isAlive(op.target)) {
99
+ apply.addPair(op.subject, op.relationId, op.target, op.payload);
100
+ }
101
+ break;
102
+ case 'removePair':
103
+ if (apply.removePair !== undefined && apply.isAlive(op.subject)) {
104
+ apply.removePair(op.subject, op.relationId, op.target);
105
+ }
106
+ break;
107
+ }
108
+ }
109
+ }
110
+ }
111
+ //# sourceMappingURL=observer-commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observer-commands.js","sourceRoot":"","sources":["../../src/reactivity/observer-commands.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import type { ComponentDef, ComponentId, Schema } from '@ecsia/schema';
2
+ import type { EntityRef } from '../entity/index.js';
3
+ export type ObserverKind = 'add' | 'remove' | 'change';
4
+ export interface ObserverHandle {
5
+ readonly id: number;
6
+ dispose(): void;
7
+ }
8
+ /** A typed observer subscription term (the component set + the kind), mirroring the query DSL. */
9
+ export interface ObserverTerm {
10
+ readonly kind: ObserverKind;
11
+ readonly components: readonly ComponentDef<Schema>[];
12
+ }
13
+ export interface ObserverContext {
14
+ readonly kind: ObserverKind;
15
+ readonly component: ComponentId;
16
+ readonly tick: number;
17
+ }
18
+ export type ObserverHandler = (e: EntityRef, ctx: ObserverContext) => void;
19
+ export declare function onAdd(...components: ComponentDef<Schema>[]): ObserverTerm;
20
+ export declare function onRemove(...components: ComponentDef<Schema>[]): ObserverTerm;
21
+ export declare function onChange(...components: ComponentDef<Schema>[]): ObserverTerm;
22
+ export interface ObserverDeps {
23
+ /** Resolve a registered def's dense id (throws if not registered with this world). */
24
+ idOf(def: ComponentDef<Schema>): ComponentId;
25
+ /** Does `index` currently hold ALL of `componentIds`? (multi-component add satisfaction). */
26
+ holdsAll(index: number, componentIds: readonly ComponentId[]): boolean;
27
+ /** The pooled EntityRef bound to the current (index, generation) for `index`. */
28
+ refOf(index: number): EntityRef;
29
+ /** The current frame tick. */
30
+ tick(): number;
31
+ }
32
+ export declare class ObserverRegistry {
33
+ #private;
34
+ constructor(deps: ObserverDeps);
35
+ get hasObservers(): boolean;
36
+ /** True iff any `change`-kind observer is registered — gates the write-log push fast-out. */
37
+ get hasChangeObservers(): boolean;
38
+ /** True iff any observer subscribes to `kind` events on `componentId` — gates deferred-row reclaim. */
39
+ hasKindFor(kind: ObserverKind, componentId: number): boolean;
40
+ observe(term: ObserverTerm, handler: ObserverHandler): ObserverHandle;
41
+ /** Fire a structural (add/remove) event for one (index, componentId). */
42
+ dispatchStructural(kind: 'add' | 'remove', index: number, componentId: number): void;
43
+ /** Fire a change event for one (index, componentId), deduped per frame. */
44
+ dispatchChange(index: number, componentId: number): void;
45
+ /**: an overflow forces every change observer to assume worst-case. */
46
+ fireAllChangeConservatively(current: Iterable<number>): void;
47
+ /** Clear per-frame change dedup state (called at the start of each observer drain). */
48
+ resetChangeDedup(): void;
49
+ }
50
+ //# sourceMappingURL=observers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observers.d.ts","sourceRoot":"","sources":["../../src/reactivity/observers.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AACtE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAEnD,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAEtD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,OAAO,IAAI,IAAI,CAAA;CAChB;AAED,kGAAkG;AAClG,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAA;IAC3B,QAAQ,CAAC,UAAU,EAAE,SAAS,YAAY,CAAC,MAAM,CAAC,EAAE,CAAA;CACrD;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAA;IAC3B,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAA;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,eAAe,KAAK,IAAI,CAAA;AAY1E,wBAAgB,KAAK,CAAC,GAAG,UAAU,EAAE,YAAY,CAAC,MAAM,CAAC,EAAE,GAAG,YAAY,CAEzE;AACD,wBAAgB,QAAQ,CAAC,GAAG,UAAU,EAAE,YAAY,CAAC,MAAM,CAAC,EAAE,GAAG,YAAY,CAE5E;AACD,wBAAgB,QAAQ,CAAC,GAAG,UAAU,EAAE,YAAY,CAAC,MAAM,CAAC,EAAE,GAAG,YAAY,CAE5E;AAED,MAAM,WAAW,YAAY;IAC3B,sFAAsF;IACtF,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,MAAM,CAAC,GAAG,WAAW,CAAA;IAC5C,6FAA6F;IAC7F,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,SAAS,WAAW,EAAE,GAAG,OAAO,CAAA;IACtE,iFAAiF;IACjF,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;IAC/B,8BAA8B;IAC9B,IAAI,IAAI,MAAM,CAAA;CACf;AAED,qBAAa,gBAAgB;;gBAQf,IAAI,EAAE,YAAY;IAI9B,IAAI,YAAY,IAAI,OAAO,CAE1B;IAED,6FAA6F;IAC7F,IAAI,kBAAkB,IAAI,OAAO,CAEhC;IAED,uGAAuG;IACvG,UAAU,CAAC,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO;IAK5D,OAAO,CAAC,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,eAAe,GAAG,cAAc;IA8BrE,yEAAyE;IACzE,kBAAkB,CAAC,IAAI,EAAE,KAAK,GAAG,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI;IAepF,2EAA2E;IAC3E,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI;IAYxD,sEAAsE;IACtE,2BAA2B,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,IAAI;IAgB5D,uFAAuF;IACvF,gBAAgB,IAAI,IAAI;CAOzB"}
@@ -0,0 +1,127 @@
1
+ // Deferred observers. onAdd/onRemove/onChange register a handler in a
2
+ // (kind, componentId) dispatch table. They fire ONLY from observerDrain at a serial slot —
3
+ // NEVER synchronously from a setter or a migration. The drain walks the shape log (add/remove) and
4
+ // the write log (change) once, looking up the bucket per entry (no per-event Array.from/reduce).
5
+ export function onAdd(...components) {
6
+ return { kind: 'add', components };
7
+ }
8
+ export function onRemove(...components) {
9
+ return { kind: 'remove', components };
10
+ }
11
+ export function onChange(...components) {
12
+ return { kind: 'change', components };
13
+ }
14
+ export class ObserverRegistry {
15
+ #deps;
16
+ /** (kind, componentId) → observers. Key is `${kind}:${componentId}`. */
17
+ #table = new Map();
18
+ #seq = 0;
19
+ #count = 0;
20
+ #changeCount = 0;
21
+ constructor(deps) {
22
+ this.#deps = deps;
23
+ }
24
+ get hasObservers() {
25
+ return this.#count > 0;
26
+ }
27
+ /** True iff any `change`-kind observer is registered — gates the write-log push fast-out. */
28
+ get hasChangeObservers() {
29
+ return this.#changeCount > 0;
30
+ }
31
+ /** True iff any observer subscribes to `kind` events on `componentId` — gates deferred-row reclaim. */
32
+ hasKindFor(kind, componentId) {
33
+ const bucket = this.#table.get(`${kind}:${componentId}`);
34
+ return bucket !== undefined && bucket.length > 0;
35
+ }
36
+ observe(term, handler) {
37
+ const componentIds = term.components.map((d) => this.#deps.idOf(d));
38
+ const obs = { id: this.#seq++, term, handler, componentIds, dedup: new Set() };
39
+ for (const cid of componentIds) {
40
+ const key = `${term.kind}:${cid}`;
41
+ let bucket = this.#table.get(key);
42
+ if (bucket === undefined) {
43
+ bucket = [];
44
+ this.#table.set(key, bucket);
45
+ }
46
+ bucket.push(obs);
47
+ }
48
+ this.#count += 1;
49
+ if (term.kind === 'change')
50
+ this.#changeCount += 1;
51
+ return {
52
+ id: obs.id,
53
+ dispose: () => {
54
+ for (const cid of componentIds) {
55
+ const key = `${term.kind}:${cid}`;
56
+ const bucket = this.#table.get(key);
57
+ if (bucket === undefined)
58
+ continue;
59
+ const i = bucket.indexOf(obs);
60
+ if (i >= 0)
61
+ bucket.splice(i, 1);
62
+ }
63
+ this.#count -= 1;
64
+ if (term.kind === 'change')
65
+ this.#changeCount -= 1;
66
+ },
67
+ };
68
+ }
69
+ /** Fire a structural (add/remove) event for one (index, componentId). */
70
+ dispatchStructural(kind, index, componentId) {
71
+ const bucket = this.#table.get(`${kind}:${componentId}`);
72
+ if (bucket === undefined)
73
+ return;
74
+ const tick = this.#deps.tick();
75
+ for (const obs of bucket) {
76
+ // Multi-component terms: fire only if the entity now satisfies the whole term (add) — a remove
77
+ // fires per just-removed component (the entity no longer satisfies, by construction).
78
+ if (kind === 'add' && obs.componentIds.length > 1 && !this.#deps.holdsAll(index, obs.componentIds)) {
79
+ continue;
80
+ }
81
+ const ref = this.#deps.refOf(index);
82
+ obs.handler(ref, { kind, component: componentId, tick });
83
+ }
84
+ }
85
+ /** Fire a change event for one (index, componentId), deduped per frame. */
86
+ dispatchChange(index, componentId) {
87
+ const bucket = this.#table.get(`change:${componentId}`);
88
+ if (bucket === undefined)
89
+ return;
90
+ const tick = this.#deps.tick();
91
+ for (const obs of bucket) {
92
+ if (obs.dedup.has(index))
93
+ continue;
94
+ obs.dedup.add(index);
95
+ const ref = this.#deps.refOf(index);
96
+ obs.handler(ref, { kind: 'change', component: componentId, tick });
97
+ }
98
+ }
99
+ /**: an overflow forces every change observer to assume worst-case. */
100
+ fireAllChangeConservatively(current) {
101
+ const tick = this.#deps.tick();
102
+ for (const [key, bucket] of this.#table) {
103
+ if (!key.startsWith('change:'))
104
+ continue;
105
+ const componentId = Number(key.slice('change:'.length));
106
+ for (const index of current) {
107
+ for (const obs of bucket) {
108
+ if (obs.dedup.has(index))
109
+ continue;
110
+ obs.dedup.add(index);
111
+ const ref = this.#deps.refOf(index);
112
+ obs.handler(ref, { kind: 'change', component: componentId, tick });
113
+ }
114
+ }
115
+ }
116
+ }
117
+ /** Clear per-frame change dedup state (called at the start of each observer drain). */
118
+ resetChangeDedup() {
119
+ for (const bucket of this.#table.values()) {
120
+ for (const obs of bucket) {
121
+ if (obs.term.kind === 'change')
122
+ obs.dedup.clear();
123
+ }
124
+ }
125
+ }
126
+ }
127
+ //# sourceMappingURL=observers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observers.js","sourceRoot":"","sources":["../../src/reactivity/observers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,141 @@
1
+ import type { Buffers } from '../memory/index.js';
2
+ import type { ComponentDef, ComponentId, EntityHandle, Schema } from '@ecsia/schema';
3
+ import type { EntityRef } from '../entity/index.js';
4
+ import type { LiveQuery } from '../query/index.js';
5
+ import { ShapeKind } from './log.js';
6
+ import type { StructuralRecord } from './structural-journal.js';
7
+ import type { ObserverHandle, ObserverHandler, ObserverTerm } from './observers.js';
8
+ export interface ReactivityDeps {
9
+ readonly buffers: Buffers;
10
+ readonly maxEntities: number;
11
+ readonly indexBits: number;
12
+ readonly logEntryWords: 1 | 2;
13
+ readonly maxWritesPerFrame: number;
14
+ readonly maxShapeChangesPerFrame: number;
15
+ readonly shrinkRings: boolean;
16
+ readonly dev: boolean;
17
+ /** index → its (archetypeId, row) — for changeVersion stamping + the public predicate. */
18
+ resolveLocation(index: number): {
19
+ archetypeId: number;
20
+ row: number;
21
+ };
22
+ /** The world frame tick (world owns it; reactivity reads it, never holds it). */
23
+ tick(): number;
24
+ /** Advance the world frame tick (world.advanceTick). Called at frameReset. */
25
+ advanceTick(): void;
26
+ /** Resolve a registered def's dense id. */
27
+ idOf(def: ComponentDef<Schema>): ComponentId;
28
+ /** Does `index` currently hold ALL of `componentIds`? */
29
+ holdsAll(index: number, componentIds: readonly ComponentId[]): boolean;
30
+ /** The pooled EntityRef bound to the current (index, generation) for `index` (observer dispatch). */
31
+ refOf(index: number): EntityRef;
32
+ /** index → its current FULL (generational) handle — for the structural journal's portable handles. */
33
+ resolveHandle(index: number): number;
34
+ /** The accessor's shared write-path fast-out cell. Reactivity flips `.active` whenever a write consumer
35
+ * (changed-flavor pointer / change observer) or changeVersion stamping (de)registers, so the setter's
36
+ * `track()` can skip the handleIndex decode + closure hops when no consumer is present. */
37
+ readonly tracking: {
38
+ active: boolean;
39
+ };
40
+ }
41
+ export declare class Reactivity {
42
+ #private;
43
+ constructor(deps: ReactivityDeps);
44
+ /** Late-bind the single-entity maintenance hook (the query engine's maintainEntity). */
45
+ setMaintainHook(fn: (index: number, componentId: number) => void): void;
46
+ /** Late-bind a "current matching members across all queries" source for the conservative path. */
47
+ setCurrentMembersSource(fn: () => Iterable<number>): void;
48
+ /** +: push the write entry (when a consumer exists) and stamp changeVersion (when enabled).
49
+ * Single-thread. The write log is read ONLY by changed-flavor query pointers and change observers; with
50
+ * neither present every appended word is dead (rewound at frame-recycle), so the push is a pure cost on
51
+ * the iteration hot path. Gate it on `#writeLogActive` (recomputed on flavor/observer (de)registration) —
52
+ * a semantics-preserving fast-out, since a later-attached changed flavor's pointer starts at the live head
53
+ * and sees only writes after it attaches. Pack the word inline (no per-write array). */
54
+ trackWrite(index: number, componentId: ComponentId, fieldIndex?: number): void;
55
+ /**: append one shape entry. Main thread only, O(1). */
56
+ trackShape(index: number, componentId: ComponentId, kind: ShapeKind): void;
57
+ /**: carries the target index in word B/C. */
58
+ trackShapePair(index: number, pairId: ComponentId, targetIndex: number, kind: ShapeKind.AddPair | ShapeKind.RemovePair): void;
59
+ /**
60
+ * _PAYLOAD: a non-exclusive overflow pair's payload changed on an already-live pair. This is
61
+ * NOT a membership change (no shape-log entry, no add/remove observer), but it IS a structural delta
62
+ * the since-T stream must carry, so we record it in the persistent journal only
63
+ * (overflow payload changes are explicit OP_PAIR_PAYLOAD records).
64
+ */
65
+ trackShapeSetPayload(index: number, pairId: ComponentId, targetIndex: number): void;
66
+ /**
67
+ * The enqueueRemoveLog stub body: storage calls this for each component in fromArch \ toArch at
68
+ * a migration, and for each held component at despawn (BEFORE removeRow + identity invalidation,
69
+ * ). It emits a shape-log Remove entry — the single source for onRemove dispatch + Removed
70
+ * delta maintenance.
71
+ */
72
+ enqueueRemoveLog(index: number, componentId: ComponentId): void;
73
+ /** Enable per-row changeVersion stamping (a `.changed` predicate consumer / serializer exists). */
74
+ enableChangeVersion(): void;
75
+ /**
76
+ * Enable the persistent structural journal (the since-T STRUCTURAL source). A delta serializer
77
+ * that includes the structural section calls this once at construction — it is the structural twin of
78
+ * `enableChangeVersion`. Until then, zero record cost.
79
+ */
80
+ enableStructuralJournal(): void;
81
+ /**
82
+ * /: the structural ops committed with tick > since, in commit order, as portable full-handle
83
+ * records. `gap` is true when `since` predates the bounded journal's live window (the caller must
84
+ * resync from a fresh snapshot — the no-partial-apply delta-gap rule).
85
+ */
86
+ drainStructuralSince(since: number): {
87
+ records: StructuralRecord[];
88
+ gap: boolean;
89
+ };
90
+ /**: "did any component on `handle` change since tick `since`?" (strict >). */
91
+ changedSince(handle: EntityHandle, since: number): boolean;
92
+ /**
93
+ * /: rows of `archetypeId` whose ENTITY's stamp is > since (the delta-serializer scan).
94
+ * `indexOfRow` maps a live row of the archetype to its entity index — the stamp is keyed by entity
95
+ * index (it follows the entity across relocations), so we resolve each row's current occupant.
96
+ */
97
+ changedRows(_archetypeId: number, since: number, count: number, indexOfRow: (row: number) => number): Iterable<number>;
98
+ currentTick(): number;
99
+ observe(term: ObserverTerm, handler: ObserverHandler): ObserverHandle;
100
+ /**: is there a remove-observer on `componentId` (gates deferred row reclaim)? */
101
+ hasRemoveObserver(componentId: number): boolean;
102
+ /**
103
+ *: allocate the `changed` flavor's LogPointer + dedup bitset for `q`. `added`/`removed` lists
104
+ * are owned by the LiveQuery itself and filled by maintenance; this hook wires only `changed`.
105
+ */
106
+ attachChangedFlavor(q: LiveQuery, componentIds: Iterable<number>): void;
107
+ /**
108
+ * _CHANGED: drain `q`'s write-log pointer, returning this frame's changed indices (deduped,
109
+ * intersected with `q.current` and the query's filtered components). Write-log driven — never
110
+ * consults changeVersion.
111
+ */
112
+ drainChanged(q: LiveQuery): Uint32Array;
113
+ /**: start of frame — advance the world tick, snapshot peak, recycle the rings. */
114
+ frameReset(): void;
115
+ /**: merge per-worker write corrals into the shared ring (deterministic). No-op single-thread. */
116
+ mergeCorrals(): void;
117
+ /**
118
+ * /+: merge ONE worker's staged value writes into the shared write log. `pairs` is a
119
+ * flat `[index, componentId, index, componentId, …]` buffer (the worker's raw corral payload); the
120
+ * caller drives this in ASCENDING worker-index order so the merged stream is deterministic. We
121
+ * (re)pack each pair through the module's own packWrite so single/wide layout stays the single
122
+ * source of truth — the worker never duplicates the packing scheme. Writes flow into the SAME ring
123
+ * the main thread appends to, so `.changed` filters and onChange observers fire for worker writes
124
+ * exactly as for single-thread writes.: when changeVersion is enabled we also
125
+ * stamp each row here (the worker hot path stays atomic-free; the stamp lands at the serial merge).
126
+ */
127
+ mergeWorkerWrites(pairs: Int32Array | Uint32Array, count: number): void;
128
+ /**
129
+ * _STRUCTURAL: drain the shape log, re-testing each affected entity against the queries
130
+ * referencing the changed component. In single-thread mode already maintains `current`
131
+ * synchronously at the commit point, so this re-runs the same idempotent re-test off the log (the
132
+ * drain is the spec's serial mechanism; the synchronous path is the optimization that agrees with
133
+ * it). Add/remove coalesce within the frame because the drain happens once.
134
+ */
135
+ maintainStructural(): void;
136
+ /** _DRAIN: fire deferred observers from the saved shape/write pointers. */
137
+ observerDrain(): void;
138
+ /** _LOGS: drain/merge spill (consumers already drained it), schedule next-frame resize. */
139
+ flushLogs(): void;
140
+ }
141
+ //# sourceMappingURL=reactivity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reactivity.d.ts","sourceRoot":"","sources":["../../src/reactivity/reactivity.ts"],"names":[],"mappings":"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"}