@oasys/oecs 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (272) hide show
  1. package/CHANGELOG.md +553 -0
  2. package/README.md +42 -31
  3. package/dist/core/ecs/access_check.d.cts +77 -0
  4. package/dist/core/ecs/access_check.d.ts +8 -8
  5. package/dist/core/ecs/access_check.d.ts.map +1 -1
  6. package/dist/core/ecs/archetype.d.cts +486 -0
  7. package/dist/core/ecs/archetype.d.ts +23 -11
  8. package/dist/core/ecs/archetype.d.ts.map +1 -1
  9. package/dist/core/ecs/archetype_graph.d.cts +96 -0
  10. package/dist/core/ecs/archetype_graph.d.ts +96 -0
  11. package/dist/core/ecs/archetype_graph.d.ts.map +1 -0
  12. package/dist/core/ecs/builtin_relations.d.cts +43 -0
  13. package/dist/core/ecs/builtin_relations.d.ts +8 -8
  14. package/dist/core/ecs/builtin_relations.d.ts.map +1 -1
  15. package/dist/core/ecs/command_log.d.cts +116 -0
  16. package/dist/core/ecs/command_log.d.ts +13 -10
  17. package/dist/core/ecs/command_log.d.ts.map +1 -1
  18. package/dist/core/ecs/component.d.cts +176 -0
  19. package/dist/core/ecs/component.d.ts +75 -5
  20. package/dist/core/ecs/component.d.ts.map +1 -1
  21. package/dist/core/ecs/compute_backend.d.cts +37 -0
  22. package/dist/core/ecs/compute_backend.d.ts +2 -2
  23. package/dist/core/ecs/debug_names.d.cts +7 -0
  24. package/dist/core/ecs/debug_names.d.ts +7 -0
  25. package/dist/core/ecs/debug_names.d.ts.map +1 -0
  26. package/dist/core/ecs/deferred_commands.d.cts +55 -0
  27. package/dist/core/ecs/deferred_commands.d.ts +55 -0
  28. package/dist/core/ecs/deferred_commands.d.ts.map +1 -0
  29. package/dist/core/ecs/dispatch_trace.d.cts +93 -0
  30. package/dist/core/ecs/dispatch_trace.d.ts +3 -3
  31. package/dist/core/ecs/ecs.d.cts +562 -0
  32. package/dist/core/ecs/ecs.d.ts +314 -285
  33. package/dist/core/ecs/ecs.d.ts.map +1 -1
  34. package/dist/core/ecs/ecs_memory.d.cts +179 -0
  35. package/dist/core/ecs/ecs_memory.d.ts +1 -1
  36. package/dist/core/ecs/entity.d.cts +34 -0
  37. package/dist/core/ecs/entity.d.ts +7 -1
  38. package/dist/core/ecs/entity.d.ts.map +1 -1
  39. package/dist/core/ecs/entity_allocator.d.cts +59 -0
  40. package/dist/core/ecs/entity_allocator.d.ts +59 -0
  41. package/dist/core/ecs/entity_allocator.d.ts.map +1 -0
  42. package/dist/core/ecs/event.d.cts +79 -0
  43. package/dist/core/ecs/event.d.ts +36 -11
  44. package/dist/core/ecs/event.d.ts.map +1 -1
  45. package/dist/core/ecs/event_registry.d.cts +22 -0
  46. package/dist/core/ecs/event_registry.d.ts +22 -0
  47. package/dist/core/ecs/event_registry.d.ts.map +1 -0
  48. package/dist/core/ecs/facades.d.cts +158 -0
  49. package/dist/core/ecs/facades.d.ts +158 -0
  50. package/dist/core/ecs/facades.d.ts.map +1 -0
  51. package/dist/core/ecs/frame_stepper.d.cts +58 -0
  52. package/dist/core/ecs/frame_stepper.d.ts +58 -0
  53. package/dist/core/ecs/frame_stepper.d.ts.map +1 -0
  54. package/dist/core/ecs/frame_trace.d.cts +133 -0
  55. package/dist/core/ecs/frame_trace.d.ts +5 -5
  56. package/dist/core/ecs/frame_trace.d.ts.map +1 -1
  57. package/dist/core/ecs/host_commands.d.cts +274 -0
  58. package/dist/core/ecs/host_commands.d.ts +53 -31
  59. package/dist/core/ecs/host_commands.d.ts.map +1 -1
  60. package/dist/core/ecs/index.d.cts +46 -0
  61. package/dist/core/ecs/index.d.ts +45 -40
  62. package/dist/core/ecs/index.d.ts.map +1 -1
  63. package/dist/core/ecs/observer.d.cts +173 -0
  64. package/dist/core/ecs/observer.d.ts +43 -12
  65. package/dist/core/ecs/observer.d.ts.map +1 -1
  66. package/dist/core/ecs/query.d.cts +718 -0
  67. package/dist/core/ecs/query.d.ts +251 -90
  68. package/dist/core/ecs/query.d.ts.map +1 -1
  69. package/dist/core/ecs/ref.d.cts +31 -0
  70. package/dist/core/ecs/ref.d.ts +3 -3
  71. package/dist/core/ecs/relation.d.cts +243 -0
  72. package/dist/core/ecs/relation.d.ts +26 -14
  73. package/dist/core/ecs/relation.d.ts.map +1 -1
  74. package/dist/core/ecs/relation_service.d.cts +235 -0
  75. package/dist/core/ecs/relation_service.d.ts +235 -0
  76. package/dist/core/ecs/relation_service.d.ts.map +1 -0
  77. package/dist/core/ecs/resource.d.cts +53 -0
  78. package/dist/core/ecs/resource.d.ts +25 -5
  79. package/dist/core/ecs/resource.d.ts.map +1 -1
  80. package/dist/core/ecs/resource_registry.d.cts +20 -0
  81. package/dist/core/ecs/resource_registry.d.ts +20 -0
  82. package/dist/core/ecs/resource_registry.d.ts.map +1 -0
  83. package/dist/core/ecs/resume.d.cts +85 -0
  84. package/dist/core/ecs/resume.d.ts +6 -6
  85. package/dist/core/ecs/resume.d.ts.map +1 -1
  86. package/dist/core/ecs/run_condition.d.cts +81 -0
  87. package/dist/core/ecs/run_condition.d.ts +15 -9
  88. package/dist/core/ecs/run_condition.d.ts.map +1 -1
  89. package/dist/core/ecs/schedule.d.cts +142 -0
  90. package/dist/core/ecs/schedule.d.ts +15 -6
  91. package/dist/core/ecs/schedule.d.ts.map +1 -1
  92. package/dist/core/ecs/snapshot_service.d.cts +94 -0
  93. package/dist/core/ecs/snapshot_service.d.ts +94 -0
  94. package/dist/core/ecs/snapshot_service.d.ts.map +1 -0
  95. package/dist/core/ecs/sparse_store.d.cts +110 -0
  96. package/dist/core/ecs/sparse_store.d.ts +5 -2
  97. package/dist/core/ecs/sparse_store.d.ts.map +1 -1
  98. package/dist/core/ecs/store.d.cts +950 -0
  99. package/dist/core/ecs/store.d.ts +143 -342
  100. package/dist/core/ecs/store.d.ts.map +1 -1
  101. package/dist/core/ecs/store_layout_listener.d.cts +23 -0
  102. package/dist/core/ecs/system.d.cts +250 -0
  103. package/dist/core/ecs/system.d.ts +138 -22
  104. package/dist/core/ecs/system.d.ts.map +1 -1
  105. package/dist/{utils/arrays.d.ts → core/ecs/utils/arrays.d.cts} +1 -0
  106. package/dist/core/ecs/utils/error.d.cts +61 -0
  107. package/dist/core/ecs/utils/error.d.ts +12 -2
  108. package/dist/core/ecs/utils/error.d.ts.map +1 -1
  109. package/dist/core/store/action_ring.d.cts +136 -0
  110. package/dist/core/store/allocator.d.cts +238 -0
  111. package/dist/core/store/allocator.d.ts.map +1 -1
  112. package/dist/core/store/buffer_backed_column.d.cts +69 -0
  113. package/dist/core/store/buffer_backed_column.d.ts +2 -2
  114. package/dist/core/store/column_store.d.cts +273 -0
  115. package/dist/core/store/column_store.d.ts +18 -10
  116. package/dist/core/store/column_store.d.ts.map +1 -1
  117. package/dist/core/store/command_dispatch.d.cts +52 -0
  118. package/dist/core/store/command_ring.d.cts +107 -0
  119. package/dist/core/store/descriptor.d.cts +80 -0
  120. package/dist/core/store/descriptor.d.ts +1 -1
  121. package/dist/core/store/descriptor.d.ts.map +1 -1
  122. package/dist/core/store/entity_index.d.cts +108 -0
  123. package/dist/core/store/event_ring.d.cts +95 -0
  124. package/dist/core/store/extend.d.cts +39 -0
  125. package/dist/core/store/extend.d.ts +3 -73
  126. package/dist/core/store/extend.d.ts.map +1 -1
  127. package/dist/core/store/grow.d.cts +34 -0
  128. package/dist/core/store/grow.d.ts +4 -9
  129. package/dist/core/store/grow.d.ts.map +1 -1
  130. package/dist/core/store/header.d.cts +64 -0
  131. package/dist/core/store/header.d.ts +1 -1
  132. package/dist/core/store/header.d.ts.map +1 -1
  133. package/dist/core/store/index.d.cts +17 -0
  134. package/dist/core/store/index.d.ts +16 -15
  135. package/dist/core/store/index.d.ts.map +1 -1
  136. package/dist/core/store/layout_ops.d.cts +152 -0
  137. package/dist/core/store/layout_ops.d.ts +152 -0
  138. package/dist/core/store/layout_ops.d.ts.map +1 -0
  139. package/dist/core/store/region_table.d.cts +74 -0
  140. package/dist/core/store/region_table.d.ts +1 -1
  141. package/dist/core/store/region_table.d.ts.map +1 -1
  142. package/dist/core/store/snapshot.d.cts +43 -0
  143. package/dist/core/store/snapshot.d.ts +2 -2
  144. package/dist/core/store/state_hash.d.cts +38 -0
  145. package/dist/core/store/state_hash.d.ts +1 -1
  146. package/dist/core/store/store_regions.d.cts +38 -0
  147. package/dist/core/store/store_regions.d.ts +1 -1
  148. package/dist/core/store/vendored_abi/abi.d.ts +43 -0
  149. package/dist/core/store/{__generated__ → vendored_abi}/abi.d.ts.map +1 -1
  150. package/dist/dev_flag.d.cts +17 -0
  151. package/dist/dev_flag.d.ts +17 -0
  152. package/dist/dev_flag.d.ts.map +1 -0
  153. package/dist/extensions/editor/editor.d.cts +170 -0
  154. package/dist/extensions/editor/editor.d.ts +43 -22
  155. package/dist/extensions/editor/editor.d.ts.map +1 -1
  156. package/dist/extensions/editor/field_handle.d.cts +40 -0
  157. package/dist/extensions/editor/field_handle.d.ts +8 -3
  158. package/dist/extensions/editor/field_handle.d.ts.map +1 -1
  159. package/dist/extensions/editor/index.cjs +1 -1
  160. package/dist/extensions/editor/index.d.cts +21 -0
  161. package/dist/extensions/editor/index.d.ts +2 -2
  162. package/dist/extensions/editor/index.js +116 -88
  163. package/dist/extensions/reactive/ecs_sync.d.cts +214 -0
  164. package/dist/extensions/reactive/ecs_sync.d.ts +26 -22
  165. package/dist/extensions/reactive/ecs_sync.d.ts.map +1 -1
  166. package/dist/extensions/reactive/index.cjs +1 -1
  167. package/dist/extensions/reactive/index.d.cts +23 -0
  168. package/dist/extensions/reactive/index.d.ts +1 -1
  169. package/dist/extensions/reactive/index.js +106 -116
  170. package/dist/extensions/solid/index.cjs +1 -1
  171. package/dist/extensions/solid/index.d.cts +6 -0
  172. package/dist/extensions/solid/index.d.ts +2 -2
  173. package/dist/extensions/solid/index.js +8 -4
  174. package/dist/extensions/solid/kernel_solid.d.cts +45 -0
  175. package/dist/extensions/solid/kernel_solid.d.ts +6 -3
  176. package/dist/extensions/solid/kernel_solid.d.ts.map +1 -1
  177. package/dist/host_commands-BF8QMi3c.cjs +1 -0
  178. package/dist/host_commands-i4cAeyL5.js +2318 -0
  179. package/dist/index.cjs +1 -2
  180. package/dist/index.d.cts +66 -0
  181. package/dist/index.d.ts +50 -1
  182. package/dist/index.d.ts.map +1 -1
  183. package/dist/index.js +4469 -5933
  184. package/dist/internal.cjs +2 -0
  185. package/dist/internal.d.cts +20 -0
  186. package/dist/internal.d.ts +20 -0
  187. package/dist/internal.d.ts.map +1 -0
  188. package/dist/internal.js +141 -0
  189. package/dist/primitives.cjs +1 -1
  190. package/dist/primitives.d.cts +18 -0
  191. package/dist/primitives.d.ts +6 -6
  192. package/dist/primitives.js +24 -23
  193. package/dist/reactive/array.d.ts +24 -0
  194. package/dist/reactive/array.d.ts.map +1 -0
  195. package/dist/reactive/index.cjs +1 -0
  196. package/dist/reactive/index.d.cts +11 -0
  197. package/dist/reactive/index.d.ts +11 -0
  198. package/dist/reactive/index.d.ts.map +1 -0
  199. package/dist/reactive/index.js +18 -0
  200. package/dist/reactive/interop.d.cts +19 -0
  201. package/dist/{core/reactive → reactive}/interop.d.ts +1 -1
  202. package/dist/reactive/interop.d.ts.map +1 -0
  203. package/dist/reactive/kernel.d.cts +73 -0
  204. package/dist/{core/reactive → reactive}/kernel.d.ts +5 -3
  205. package/dist/reactive/kernel.d.ts.map +1 -0
  206. package/dist/reactive/map.d.ts +16 -0
  207. package/dist/reactive/map.d.ts.map +1 -0
  208. package/dist/reactive/shallow.d.cts +11 -0
  209. package/dist/reactive/shallow.d.ts +11 -0
  210. package/dist/reactive/shallow.d.ts.map +1 -0
  211. package/dist/reactive/struct.d.cts +10 -0
  212. package/dist/{core/reactive → reactive}/struct.d.ts +1 -1
  213. package/dist/reactive/struct.d.ts.map +1 -0
  214. package/dist/shallow-DQ8aG51_.cjs +1 -0
  215. package/dist/shallow-DfnAm9vG.js +150 -0
  216. package/dist/shared-BU1Cd40h.js +103 -0
  217. package/dist/shared-BymrGTyR.cjs +1 -0
  218. package/dist/shared.cjs +1 -1
  219. package/dist/shared.d.cts +22 -0
  220. package/dist/shared.d.ts +2 -2
  221. package/dist/shared.js +1 -1
  222. package/dist/topological_sort-BDvEyb9W.cjs +1 -0
  223. package/dist/topological_sort-DK6EjpWa.js +135 -0
  224. package/dist/type_primitives/assertions.d.cts +32 -0
  225. package/dist/type_primitives/assertions.d.ts +13 -1
  226. package/dist/type_primitives/assertions.d.ts.map +1 -1
  227. package/dist/type_primitives/binary_heap/binary_heap.d.cts +37 -0
  228. package/dist/type_primitives/bitset/bitset.d.cts +54 -0
  229. package/dist/type_primitives/brand.d.cts +23 -0
  230. package/dist/type_primitives/error.d.cts +16 -0
  231. package/dist/type_primitives/error.d.ts +4 -3
  232. package/dist/type_primitives/error.d.ts.map +1 -1
  233. package/dist/type_primitives/index.d.cts +13 -0
  234. package/dist/type_primitives/index.d.ts +9 -9
  235. package/dist/type_primitives/sparse_map/sparse_map.d.cts +30 -0
  236. package/dist/type_primitives/sparse_set/sparse_set.d.cts +28 -0
  237. package/dist/type_primitives/topological_sort/topological_sort.d.cts +29 -0
  238. package/dist/type_primitives/typed_arrays/typed_arrays.d.cts +127 -0
  239. package/dist/typed_arrays-BcXcnzB4.js +258 -0
  240. package/dist/typed_arrays-D3NmwtCG.cjs +1 -0
  241. package/dist/utils/error.d.cts +6 -0
  242. package/dist/version.d.cts +10 -0
  243. package/dist/version.d.ts +10 -0
  244. package/dist/version.d.ts.map +1 -0
  245. package/package.json +102 -19
  246. package/dist/array-CxbPyiHO.cjs +0 -1
  247. package/dist/array-uFR7Q8fU.js +0 -132
  248. package/dist/core/reactive/array.d.ts.map +0 -1
  249. package/dist/core/reactive/index.cjs +0 -1
  250. package/dist/core/reactive/index.d.ts +0 -10
  251. package/dist/core/reactive/index.d.ts.map +0 -1
  252. package/dist/core/reactive/index.js +0 -17
  253. package/dist/core/reactive/interop.d.ts.map +0 -1
  254. package/dist/core/reactive/kernel.d.ts.map +0 -1
  255. package/dist/core/reactive/map.d.ts.map +0 -1
  256. package/dist/core/reactive/struct.d.ts.map +0 -1
  257. package/dist/log/console_sink.d.ts +0 -4
  258. package/dist/log/console_sink.d.ts.map +0 -1
  259. package/dist/log/index.d.ts +0 -3
  260. package/dist/log/index.d.ts.map +0 -1
  261. package/dist/log/logger.d.ts +0 -27
  262. package/dist/log/logger.d.ts.map +0 -1
  263. package/dist/shared-BXSZnxx4.cjs +0 -1
  264. package/dist/shared-C678TAPY.js +0 -99
  265. package/dist/topological_sort-DlRpSrxu.js +0 -391
  266. package/dist/topological_sort-WAT-VHb-.cjs +0 -1
  267. package/dist/utils/arrays.d.ts.map +0 -1
  268. package/dist/utils/constants.d.ts.map +0 -1
  269. /package/dist/{utils/constants.d.ts → core/ecs/utils/constants.d.cts} +0 -0
  270. /package/dist/core/store/{__generated__/abi.d.ts → vendored_abi/abi.d.cts} +0 -0
  271. /package/dist/{core/reactive/array.d.ts → reactive/array.d.cts} +0 -0
  272. /package/dist/{core/reactive/map.d.ts → reactive/map.d.cts} +0 -0
@@ -1,72 +1,68 @@
1
- function o(i, t, n) {
2
- return `${i}:${t.id}:${n}`;
1
+ function a(r, t, e) {
2
+ return `${r}:${t.id}:${e}`;
3
3
  }
4
- class d {
5
- /** @internal */
6
- constructor(t, n) {
7
- this.readField = t, this.shadow = n;
8
- }
4
+ const d = /* @__PURE__ */ new WeakMap();
5
+ class h {
9
6
  /** @internal */
10
- _txn = { forward: [], inverse: [] };
11
- /**
12
- * Spawn an entity carrying `components`. Inverse: despawn the created entity,
13
- * finalized once the spawn applies (the id only exists post-flush). `onSpawned`
14
- * also forwards the new id to the caller. The finalizer re-fires on redo, so the
15
- * inverse tracks the current id.
16
- */
17
- spawn(t, n) {
18
- const e = {
7
+ constructor(t, e) {
8
+ this.readField = t, this.shadow = e, d.set(this, { forward: [], inverse: [] });
9
+ }
10
+ /** Values staged by THIS build, layered over the editor's shared shadow. Kept
11
+ * transaction-local so an aborted build (the callback throws before commit)
12
+ * leaves the shared shadow untouched — a phantom staged value would poison
13
+ * `pendingField` and seed the NEXT edit's inverse with a value the world never
14
+ * held. The one merge point into the shared shadow is commit's `applyShadow`. */
15
+ staged = /* @__PURE__ */ new Map();
16
+ get _txn() {
17
+ return d.get(this);
18
+ }
19
+ spawn(t, e) {
20
+ const n = {
19
21
  kind: "despawn",
20
22
  eid: 0
21
23
  };
22
- return this._txn.inverse.push(e), this._txn.forward.push({
24
+ return this._txn.inverse.push(n), this._txn.forward.push({
23
25
  kind: "spawn",
24
26
  components: t,
25
27
  onSpawned: (s) => {
26
- e.eid = s, n?.(s);
28
+ n.eid = s, e?.(s);
27
29
  }
28
30
  }), this;
29
31
  }
30
- /**
31
- * Despawn `eid`. Inverse: respawn from `restore` (the components+values to
32
- * recreate — read them from the channel before despawning). Undo respawns the
33
- * DATA, not the identity: the new entity gets a fresh id, and the respawn's
34
- * `onSpawned` rewrites this despawn's target so redo removes the respawned
35
- * entity rather than the dead original.
36
- */
37
- despawn(t, n) {
38
- const e = { kind: "despawn", eid: t };
39
- return this._txn.forward.push(e), this._txn.inverse.push({
32
+ despawn(t, e) {
33
+ const n = { kind: "despawn", eid: t };
34
+ return this._txn.forward.push(n), this._txn.inverse.push({
40
35
  kind: "spawn",
41
- components: n,
36
+ components: e,
42
37
  onSpawned: (s) => {
43
- e.eid = s;
38
+ n.eid = s;
44
39
  }
45
40
  }), this;
46
41
  }
47
42
  /**
48
43
  * Set `field` of `def` on `eid` to `value`. Inverse: set it back to the value
49
- * this edit replaced — read from the shadow (so stacked edits before a commit
50
- * invert correctly) or, failing that, the read channel (`0` if unknown).
44
+ * this edit replaced — read from the staged overlay / shadow (so stacked edits,
45
+ * within one build or before a commit, invert correctly) or, failing that, the
46
+ * read channel (`0` if unknown).
51
47
  */
52
- setField(t, n, e, s) {
53
- const r = o(t, n, e), a = this.shadow.get(r) ?? this.readField(t, n, e) ?? 0;
54
- return this.shadow.set(r, s), this._txn.forward.push({ kind: "set_field", eid: t, def: n, field: e, value: s }), this._txn.inverse.push({ kind: "set_field", eid: t, def: n, field: e, value: a }), this;
48
+ setField(t, e, n, s) {
49
+ const i = a(t, e, n), o = this.staged.get(i) ?? this.shadow.get(i) ?? this.readField(t, e, n) ?? 0;
50
+ return this.staged.set(i, s), this._txn.forward.push({ kind: "set_field", eid: t, def: e, field: n, value: s }), this._txn.inverse.push({ kind: "set_field", eid: t, def: e, field: n, value: o }), this;
55
51
  }
56
52
  /** Add `def` (with complete `values`) to `eid`. Inverse: remove it. */
57
- addComponent(t, n, e) {
58
- return this._txn.forward.push({ kind: "add_component", eid: t, def: n, values: e }), this._txn.inverse.push({ kind: "remove_component", eid: t, def: n }), this;
53
+ addComponent(t, e, n) {
54
+ return this._txn.forward.push({ kind: "add_component", eid: t, def: e, values: n }), this._txn.inverse.push({ kind: "remove_component", eid: t, def: e }), this;
59
55
  }
60
56
  /**
61
57
  * Remove `def` from `eid`. Inverse: re-add it from `restore` (the field values
62
58
  * to recreate — read them from the channel before removing).
63
59
  */
64
- removeComponent(t, n, e) {
65
- return this._txn.forward.push({ kind: "remove_component", eid: t, def: n }), this._txn.inverse.push({
60
+ removeComponent(t, e, n) {
61
+ return this._txn.forward.push({ kind: "remove_component", eid: t, def: e }), this._txn.inverse.push({
66
62
  kind: "add_component",
67
63
  eid: t,
68
- def: n,
69
- values: e
64
+ def: e,
65
+ values: n
70
66
  }), this;
71
67
  }
72
68
  /** Disable `eid`. Inverse: enable it. */
@@ -78,12 +74,14 @@ class d {
78
74
  return this._txn.forward.push({ kind: "enable", eid: t }), this._txn.inverse.push({ kind: "disable", eid: t }), this;
79
75
  }
80
76
  }
81
- class h {
82
- constructor(t, n) {
83
- this.queue = t, this.readField = n;
77
+ class u {
78
+ constructor(t, e) {
79
+ this.queue = t, this.readField = e;
84
80
  }
85
81
  undoStack = [];
86
82
  redoStack = [];
83
+ /** onChange subscribers — see {@link onChange}. */
84
+ listeners = [];
87
85
  /** Per-`(entity, component, field)` shadow of edited values, for inverse correctness. */
88
86
  shadow = /* @__PURE__ */ new Map();
89
87
  /**
@@ -92,36 +90,34 @@ class h {
92
90
  * commands) and lands on the undo stack atomically, clearing the redo stack.
93
91
  */
94
92
  transaction(t) {
95
- const n = new d(this.readField, this.shadow);
96
- return t(n), this.commit(n._txn);
93
+ const e = new h(this.readField, this.shadow);
94
+ return t(e), this.commit(d.get(e));
97
95
  }
98
- /** Spawn `components` as its own undo entry. `onSpawned` reports the new id. */
99
- spawn(t, n) {
100
- return this.transaction((e) => e.spawn(t, n));
96
+ spawn(t, e) {
97
+ return this.transaction((n) => n.spawn(t, e));
101
98
  }
102
- /** Despawn `eid` as its own undo entry; `restore` recreates it on undo. */
103
- despawn(t, n) {
104
- return this.transaction((e) => e.despawn(t, n));
99
+ despawn(t, e) {
100
+ return this.transaction((n) => n.despawn(t, e));
105
101
  }
106
102
  /** Set one field as its own undo entry. */
107
- setField(t, n, e, s) {
108
- return this.transaction((r) => r.setField(t, n, e, s));
103
+ setField(t, e, n, s) {
104
+ return this.transaction((i) => i.setField(t, e, n, s));
109
105
  }
110
106
  /** Add a component as its own undo entry. */
111
- addComponent(t, n, e) {
112
- return this.transaction((s) => s.addComponent(t, n, e));
107
+ addComponent(t, e, n) {
108
+ return this.transaction((s) => s.addComponent(t, e, n));
113
109
  }
114
110
  /** Remove a component as its own undo entry; `restore` re-adds it on undo. */
115
- removeComponent(t, n, e) {
116
- return this.transaction((s) => s.removeComponent(t, n, e));
111
+ removeComponent(t, e, n) {
112
+ return this.transaction((s) => s.removeComponent(t, e, n));
117
113
  }
118
114
  /** Disable `eid` as its own undo entry. */
119
115
  disable(t) {
120
- return this.transaction((n) => n.disable(t));
116
+ return this.transaction((e) => e.disable(t));
121
117
  }
122
118
  /** Enable `eid` as its own undo entry. */
123
119
  enable(t) {
124
- return this.transaction((n) => n.enable(t));
120
+ return this.transaction((e) => e.enable(t));
125
121
  }
126
122
  /**
127
123
  * Undo the most recent transaction: enqueue its inverse commands (in reverse
@@ -131,9 +127,9 @@ class h {
131
127
  undo() {
132
128
  const t = this.undoStack.pop();
133
129
  if (t === void 0) return !1;
134
- const n = t.inverse.slice().reverse();
135
- for (const e of n) this.queue.push(e);
136
- return this.applyShadow(n), this.redoStack.push(t), !0;
130
+ const e = t.inverse.slice().reverse();
131
+ for (const n of e) this.queue.push(n);
132
+ return this.applyShadow(e), this.redoStack.push(t), this.notify(), !0;
137
133
  }
138
134
  /**
139
135
  * Redo the most recently undone transaction: re-enqueue its forward commands on
@@ -143,67 +139,99 @@ class h {
143
139
  redo() {
144
140
  const t = this.redoStack.pop();
145
141
  if (t === void 0) return !1;
146
- for (const n of t.forward) this.queue.push(n);
147
- return this.applyShadow(t.forward), this.undoStack.push(t), !0;
142
+ for (const e of t.forward) this.queue.push(e);
143
+ return this.applyShadow(t.forward), this.undoStack.push(t), this.notify(), !0;
148
144
  }
149
145
  /** Drop both stacks (e.g. on load). Does not touch the world. */
150
146
  clear() {
151
- this.undoStack.length = 0, this.redoStack.length = 0, this.shadow.clear();
147
+ this.undoStack.length = 0, this.redoStack.length = 0, this.shadow.clear(), this.notify();
152
148
  }
153
149
  /** Current stack depths — for an "Undo (3)" / "Redo" affordance. */
154
150
  depths() {
155
151
  return { undo: this.undoStack.length, redo: this.redoStack.length };
156
152
  }
153
+ /** `true` when `undo()` would do something — allocation-free (M10). */
154
+ get canUndo() {
155
+ return this.undoStack.length > 0;
156
+ }
157
+ /** `true` when `redo()` would do something — allocation-free (M10). */
158
+ get canRedo() {
159
+ return this.redoStack.length > 0;
160
+ }
161
+ /**
162
+ * Subscribe to undo/redo-stack changes: fires after every commit, undo,
163
+ * redo, and clear — the push signal an "Undo (3)" affordance needs instead
164
+ * of polling `depths()` per frame (M10). Returns an unsubscribe function.
165
+ * Callbacks run synchronously in subscription order; read `canUndo` /
166
+ * `canRedo` / `depths()` inside.
167
+ */
168
+ onChange(t) {
169
+ return this.listeners.push(t), () => {
170
+ const e = this.listeners.indexOf(t);
171
+ e !== -1 && this.listeners.splice(e, 1);
172
+ };
173
+ }
174
+ notify() {
175
+ for (const t of this.listeners.slice()) t();
176
+ }
177
+ /** Read one committed `(entity, component, field)` slot through the reader
178
+ * this editor was constructed with — the default read for `fieldHandle`
179
+ * when no channel thunk is supplied (M11). */
180
+ committedField(t, e, n) {
181
+ return this.readField(t, e, n);
182
+ }
157
183
  /**
158
184
  * The pending value the editor believes for a field that the committed read
159
185
  * channel has NOT caught up to yet (the shadow), or `undefined` if none. Lets an
160
186
  * inspector echo an edit between the `set` and the tick that commits it.
161
187
  *
162
188
  * Self-resolving: once the read channel reports the shadowed value (the edit
163
- * landed), the entry is dropped and this returns `undefined` so `pending` does
164
- * not outlive its set→commit window and shadow a later external write. The one
189
+ * landed) or reports `undefined` (the slot is gone: entity despawned or
190
+ * component removed) the entry is dropped and this returns `undefined`, so
191
+ * `pending` does not outlive its set→commit window and shadow a later external
192
+ * write, nor a dead slot's lifetime. The one
165
193
  * residual: if an external write changes the field to a *different* value within
166
194
  * the same window before this is next consulted, `pending` can read stale until
167
195
  * the next edit to the slot. `value` (the read channel) is always the source of
168
196
  * truth; `pending` is only the optimistic bridge.
169
197
  */
170
- pendingField(t, n, e) {
171
- const s = o(t, n, e), r = this.shadow.get(s);
172
- if (r !== void 0) {
173
- if (this.readField(t, n, e) === r) {
174
- this.shadow.delete(s);
175
- return;
176
- }
177
- return r;
198
+ pendingField(t, e, n) {
199
+ const s = a(t, e, n), i = this.shadow.get(s);
200
+ if (i === void 0) return;
201
+ const o = this.readField(t, e, n);
202
+ if (o === i || o === void 0) {
203
+ this.shadow.delete(s);
204
+ return;
178
205
  }
206
+ return i;
179
207
  }
180
208
  /** Enqueue forward commands, record the transaction, clear redo, sync shadow. */
181
209
  commit(t) {
182
210
  if (t.forward.length === 0) return t;
183
- for (const n of t.forward) this.queue.push(n);
184
- return this.applyShadow(t.forward), this.undoStack.push(t), this.redoStack.length = 0, t;
211
+ for (const e of t.forward) this.queue.push(e);
212
+ return this.applyShadow(t.forward), this.undoStack.push(t), this.redoStack.length = 0, this.notify(), t;
185
213
  }
186
214
  /** Keep the setField shadow in step with the commands just enqueued. */
187
215
  applyShadow(t) {
188
- for (const n of t)
189
- n.kind === "set_field" && this.shadow.set(o(n.eid, n.def, n.field), n.value);
216
+ for (const e of t)
217
+ e.kind === "set_field" && this.shadow.set(a(e.eid, e.def, e.field), e.value);
190
218
  }
191
219
  }
192
- function u(i, t, n, e, s) {
220
+ function c(r, t, e, n, s) {
193
221
  return {
194
222
  get value() {
195
- return s();
223
+ return s !== void 0 ? s() : r.committedField(t, e, n);
196
224
  },
197
- set(r) {
198
- i.setField(t, n, e, r);
225
+ set(i) {
226
+ r.setField(t, e, n, i);
199
227
  },
200
228
  get pending() {
201
- return i.pendingField(t, n, e);
229
+ return r.pendingField(t, e, n);
202
230
  }
203
231
  };
204
232
  }
205
233
  export {
206
- h as Editor,
207
- d as TransactionBuilder,
208
- u as fieldHandle
234
+ u as Editor,
235
+ h as TransactionBuilder,
236
+ c as fieldHandle
209
237
  };
@@ -0,0 +1,214 @@
1
+ import { ReactiveArray, ReactiveMap, StructSetters, StructEq } from '../../reactive/index.cjs';
2
+ import { ComponentDef, ComponentSchema, ECS, EntityID, SystemAccessDeclaration } from '../../core/ecs/index.cjs';
3
+ import { shallow } from '../../reactive/shallow.cjs';
4
+ export { shallow };
5
+ /**
6
+ * A read cursor over one entity's single-component state, handed to a single-
7
+ * component `Projection`. `field` is column-backed under "column" grain
8
+ * (sequential), random-access (`getField`) under "entity" grain.
9
+ *
10
+ * Lifetime: the cursor is a reused, mutable singleton valid ONLY during the
11
+ * synchronous `project` call for the current row. Read what you need and return —
12
+ * never capture `row` (or stash its `field` reads keyed off a later closure): the
13
+ * next row/tick mutates it in place, so a captured cursor reads stale data.
14
+ */
15
+ export interface RowReader<S extends ComponentSchema> {
16
+ /** The current entity (the row being projected). */
17
+ readonly eid: EntityID;
18
+ /** Read a field of the synced component for the current row. */
19
+ field<K extends string & keyof S>(name: K): number;
20
+ }
21
+ /**
22
+ * A read cursor over a JOINED entity (it carries every component in the join), for
23
+ * a `JoinProjection`. `field(def, name)` reads any field of any joined component
24
+ * for the current entity (random-access; the join subscribes to all of them so the
25
+ * read can never go stale).
26
+ *
27
+ * Lifetime: like `RowReader`, a reused mutable singleton valid ONLY during the
28
+ * synchronous `project` call — never capture it; the next dispatch mutates it.
29
+ */
30
+ export interface JoinReader<Schemas extends readonly ComponentSchema[] = readonly ComponentSchema[]> {
31
+ /** The current entity (it has all joined components). */
32
+ readonly eid: EntityID;
33
+ /** Read a field of one joined component for the current entity. `def` is
34
+ * constrained to the join's own component set — reading a def outside the
35
+ * join is the stale-read footgun the module header warns about (its changes
36
+ * aren't subscribed), so it's a compile error. */
37
+ field<S extends Schemas[number], K extends string & keyof S>(def: ComponentDef<S>, name: K): number;
38
+ }
39
+ /** Map one single-component row to the value a UI cell reads. */
40
+ export type Projection<S extends ComponentSchema, V> = (row: RowReader<S>) => V;
41
+ /** Map one joined entity to the value a UI cell reads. */
42
+ export type JoinProjection<V, Schemas extends readonly ComponentSchema[] = readonly ComponentSchema[]> = (row: JoinReader<Schemas>) => V;
43
+ /** Per-component dirty grain. See the module header for the measured crossover. */
44
+ export type SyncGrain = "entity" | "column";
45
+ export interface EcsMapSyncOptions<V> {
46
+ /** Dirty grain (default `"entity"`). Use `"column"` for high-churn components. */
47
+ grain?: SyncGrain;
48
+ /**
49
+ * Value equality for the map's per-key no-op skip (default `Object.is`). Pass
50
+ * `shallow` for object projections, or a content comparator. A fresh object
51
+ * every tick under the default reference eq wakes the row every frame.
52
+ */
53
+ eq?: (a: V, b: V) => boolean;
54
+ /**
55
+ * Access surface the projection touches, merged over the synced components'
56
+ * `reads`. `DEV` access-checks the observer callbacks exactly like a system.
57
+ */
58
+ access?: Partial<SystemAccessDeclaration>;
59
+ /**
60
+ * Replay current matches on registration (default `true`), so a bridge attached
61
+ * to an already-populated world seeds the map with present state instead of
62
+ * waking only on the next change. flecs `yieldExisting`.
63
+ */
64
+ seedExisting?: boolean;
65
+ }
66
+ export interface EcsMapSync<V> {
67
+ /** The live channel: a reader of key `eid` subscribes to that entity alone. */
68
+ readonly map: ReactiveMap<EntityID, V>;
69
+ /** Unregister the observer(s) and stop publishing. Safe to call more than once. */
70
+ dispose(): void;
71
+ }
72
+ /**
73
+ * Sync ONE component's change detection into a `reactiveMap`, keyed by `EntityID`.
74
+ * A value change publishes the changed rows; a spawn inserts a row and a despawn
75
+ * deletes it. Returns the map + a disposer. Drive with `batchedUpdate(world, dt)`.
76
+ *
77
+ * To read more than one component per row, use `syncJoinToMap` — reading a second
78
+ * component here goes stale (its changes aren't subscribed).
79
+ */
80
+ export declare function syncComponentToMap<S extends ComponentSchema, V>(ecs: ECS, def: ComponentDef<S>, project: Projection<S, V>, opts?: EcsMapSyncOptions<V>): EcsMapSync<V>;
81
+ /**
82
+ * Sugar over `syncComponentToMap`: project a fixed FIELD LIST into a `{ field:
83
+ * value }` snapshot, with an automatic `shallow` eq so an unchanged row wakes
84
+ * nobody. The declarative-field-list model from WatermelonDB `observeWithColumns` /
85
+ * TanStack `select` — the common "mirror these fields" case without hand-writing
86
+ * the projection + comparator.
87
+ *
88
+ * Allocation note: this builds a fresh object per dirty row and `shallow` does two
89
+ * `Object.keys` per compare — fine for low-churn. For a HIGH-churn component (the
90
+ * case you'd pick `grain:"column"` for), prefer `syncComponentToMap` with a scalar
91
+ * or hand-written-`eq` projection, so the hot path doesn't allocate exactly where
92
+ * the column grain is meant to keep it cheap.
93
+ */
94
+ export declare function syncFieldsToMap<S extends ComponentSchema, const F extends readonly (string & keyof S)[]>(ecs: ECS, def: ComponentDef<S>, fields: F, opts?: Omit<EcsMapSyncOptions<{
95
+ [K in F[number]]: number;
96
+ }>, "eq">): EcsMapSync<{
97
+ [K in F[number]]: number;
98
+ }>;
99
+ /**
100
+ * Sync a multi-component JOIN into a `reactiveMap`. An entity is a member iff it
101
+ * has EVERY component in `defs`; the projection reads any of them via
102
+ * `row.field(def, name)`. Change detection subscribes to ALL joined components, so
103
+ * a write to ANY of them republishes the row — the join can never go stale (the
104
+ * bug a single-component sync + manual secondary read would have). Spawns/removes
105
+ * of any joined component re-evaluate membership.
106
+ *
107
+ * Entity grain only: a join spans archetypes, so there is no single column to
108
+ * sweep. Drive with `batchedUpdate(world, dt)`.
109
+ */
110
+ export declare function syncJoinToMap<Schemas extends readonly ComponentSchema[], V>(ecs: ECS, defs: readonly [...{
111
+ [I in keyof Schemas]: ComponentDef<Schemas[I]>;
112
+ }], project: JoinProjection<V, Schemas>, opts?: Omit<EcsMapSyncOptions<V>, "grain">): EcsMapSync<V>;
113
+ export interface SingletonStructSync<V extends object> {
114
+ /** The live per-field channel: reading `struct.field` in a tracked scope
115
+ * subscribes to that field alone (pair with `fromKernelStruct` to render). */
116
+ readonly struct: V;
117
+ /** Unregister the observer and stop publishing. Safe to call more than once. */
118
+ dispose(): void;
119
+ }
120
+ export interface SingletonSyncOptions<V extends object = Record<string, number>> {
121
+ /**
122
+ * Access surface the publish touches, merged over the synced def's read.
123
+ * `DEV` access-checks the observer callbacks exactly like a system.
124
+ */
125
+ access?: Partial<SystemAccessDeclaration>;
126
+ /** Replay current state on registration (default `true`). flecs `yieldExisting`. */
127
+ seedExisting?: boolean;
128
+ /**
129
+ * Drive a PRE-CREATED `reactiveStruct` (its `[proxy, set]` pair) instead of
130
+ * creating a fresh one. Lets the consumer keep the struct at module scope as a
131
+ * stable "channel" with initial values present before the sync attaches (the
132
+ * client UI seam: panels import the eager proxy; the sync, set up at world build,
133
+ * republishes into it via the observer). The seed (`yieldExisting`) overwrites
134
+ * the eager initials with the entity's current values on registration.
135
+ */
136
+ into?: readonly [V, StructSetters<V>];
137
+ /** Per-field equality overrides for the created struct (parity with
138
+ * `SingletonArraySyncOptions.eq`). Ignored when `into` supplies a
139
+ * pre-created struct — that struct already carries its own `eq`. */
140
+ eq?: StructEq<V>;
141
+ }
142
+ /**
143
+ * Sync ONE singleton entity's single-component state into a `reactiveStruct`,
144
+ * keyless — the singleton/resource shape (ADR-0024). This is how heterogeneous
145
+ * ephemeral UI state (net status + latency, FPS/mem, wave timer, hovered hex) joins
146
+ * the same reactive view as per-entity components: model it as components on a
147
+ * reserved singleton entity (the flecs / Unity-DOTS "singleton-as-entity" model)
148
+ * and read it through per-field channels — NOT a separate reactive-resource
149
+ * subsystem. Resources stay non-reactive internal singletons.
150
+ *
151
+ * It reuses the entity-grain ADR-0013 component observer verbatim, filtered to the
152
+ * one target eid: an `onSet` (or seed `onAdd`) republishes the entity's field values
153
+ * into the struct via per-field setters. The per-field `Object.is` eq means an
154
+ * unchanged field writes nothing — "equal write → 0 renders" — and per-field signals
155
+ * give "1-of-N FIELD → 1 re-render"; drive with `batchedUpdate` for "one tick → one
156
+ * commit". Because the component change-detection fires on `ctx.setField`, the
157
+ * in-place write pattern that defeats a `setResource`-keyed resource observer is
158
+ * handled for free.
159
+ *
160
+ * `fields` is explicit: a `ComponentDef` is a branded number at runtime (its schema
161
+ * is a phantom type), so the field names must be passed — same contract as
162
+ * `syncFieldsToMap`. Project a subset to channel only what the UI reads.
163
+ *
164
+ * A `reactiveStruct` has a fixed field set and no `delete`, so the map adapter's
165
+ * delete-on-disable (#677 / ADR-0023) has no struct analog: `onRemove`/`onDisable`
166
+ * **reset the fields to the channel's declared initial values** (`onSet` skips
167
+ * disabled entities), `onEnable` republishes. The defaults are the channel's own
168
+ * initials — the eager `into` struct's declared values (e.g. `NetStats.latency = -1`),
169
+ * or the zeros of a freshly-created one — captured (untracked) at registration, NOT a
170
+ * blind 0. A singleton is rarely despawned; this keeps the channel well-defined if it is.
171
+ */
172
+ export declare function syncSingletonToStruct<S extends ComponentSchema, const F extends readonly (string & keyof S)[]>(ecs: ECS, def: ComponentDef<S>, eid: EntityID, fields: F, opts?: SingletonSyncOptions<{
173
+ [K in F[number]]: number;
174
+ }>): SingletonStructSync<{
175
+ [K in F[number]]: number;
176
+ }>;
177
+ export interface SingletonArraySync<T> {
178
+ /** The live ordered channel: a reader of slot `i` subscribes to that slot alone. */
179
+ readonly array: ReactiveArray<T>;
180
+ /** Unregister the observer and stop publishing. Safe to call more than once. */
181
+ dispose(): void;
182
+ }
183
+ export interface SingletonArraySyncOptions<T> {
184
+ access?: Partial<SystemAccessDeclaration>;
185
+ seedExisting?: boolean;
186
+ /** Drive a PRE-CREATED `reactiveArray` (the module-scope channel) instead of a fresh one. */
187
+ into?: ReactiveArray<T>;
188
+ /** Per-slot equality for the array's no-op skip (default `Object.is`). */
189
+ eq?: (a: T, b: T) => boolean;
190
+ }
191
+ /**
192
+ * Sync ONE singleton entity's component into a `reactiveArray`, one slot per field
193
+ * in `fields` order (#685 / ADR-0024) — the ORDERED sibling of `syncSingletonToStruct`,
194
+ * for positional UI state (the army slots). On every change to the component it
195
+ * `reconcile`s the array from the field values: a slot whose value is unchanged keeps
196
+ * its reference (structural sharing), only changed slots wake. `onRemove`/`onDisable`
197
+ * reset the slots to the channel's DECLARED initial slots (a reactiveArray has no row
198
+ * delete) — captured (untracked) at registration, so the army channel resets to its
199
+ * empty sentinel (`EMPTY_SLOT`), NOT a blind 0 that would read as unit type 0.
200
+ *
201
+ * `fields` is explicit (a `ComponentDef` is a branded number at runtime). Pair with
202
+ * `@oasys/oecs/solid`'s `fromKernelArray` + a Solid `<Index>`.
203
+ */
204
+ export declare function syncSingletonToArray<S extends ComponentSchema>(ecs: ECS, def: ComponentDef<S>, eid: EntityID, fields: readonly (string & keyof S)[], opts?: SingletonArraySyncOptions<number>): SingletonArraySync<number>;
205
+ /**
206
+ * Advance the world one tick with every bridge publish coalesced into a single UI
207
+ * flush. onAdd/onRemove fire mid-tick at flush boundaries and onSet fires at the
208
+ * tick tail; wrapping the whole update in `batch` defers the effect flush until the
209
+ * tick completes, so a frame that touched K entities (across any number of syncs on
210
+ * this world) wakes its readers once, not once per observer dispatch point.
211
+ * Equivalent to `batch(() => ecs.update(dt))`.
212
+ */
213
+ export declare function batchedUpdate(ecs: ECS, dt: number): void;
214
+ //# sourceMappingURL=ecs_sync.d.ts.map
@@ -1,12 +1,7 @@
1
- import { ReactiveArray, ReactiveMap, StructSetters } from '../../core/reactive';
2
- import { ComponentDef, ComponentSchema, ECS, EntityID, SystemAccessDeclaration } from '../../core/ecs';
3
- /**
4
- * Shallow (one-level) value equality — the recommended `eq` for object-valued
5
- * projections. Mirrors zustand `useShallow` / MobX `comparer.shallow`: two objects
6
- * are equal iff they have the same own keys with `Object.is`-equal values. Restores
7
- * "equal write wakes nobody" for projections that build a fresh object each tick.
8
- */
9
- export declare function shallow(a: object, b: object): boolean;
1
+ import { ReactiveArray, ReactiveMap, StructSetters, StructEq } from '../../reactive/index.js';
2
+ import { ComponentDef, ComponentSchema, ECS, EntityID, SystemAccessDeclaration } from '../../core/ecs/index.js';
3
+ import { shallow } from '../../reactive/shallow.js';
4
+ export { shallow };
10
5
  /**
11
6
  * A read cursor over one entity's single-component state, handed to a single-
12
7
  * component `Projection`. `field` is column-backed under "column" grain
@@ -32,16 +27,19 @@ export interface RowReader<S extends ComponentSchema> {
32
27
  * Lifetime: like `RowReader`, a reused mutable singleton valid ONLY during the
33
28
  * synchronous `project` call — never capture it; the next dispatch mutates it.
34
29
  */
35
- export interface JoinReader {
30
+ export interface JoinReader<Schemas extends readonly ComponentSchema[] = readonly ComponentSchema[]> {
36
31
  /** The current entity (it has all joined components). */
37
32
  readonly eid: EntityID;
38
- /** Read a field of one joined component for the current entity. */
39
- field<S extends ComponentSchema, K extends string & keyof S>(def: ComponentDef<S>, name: K): number;
33
+ /** Read a field of one joined component for the current entity. `def` is
34
+ * constrained to the join's own component set reading a def outside the
35
+ * join is the stale-read footgun the module header warns about (its changes
36
+ * aren't subscribed), so it's a compile error. */
37
+ field<S extends Schemas[number], K extends string & keyof S>(def: ComponentDef<S>, name: K): number;
40
38
  }
41
39
  /** Map one single-component row to the value a UI cell reads. */
42
40
  export type Projection<S extends ComponentSchema, V> = (row: RowReader<S>) => V;
43
41
  /** Map one joined entity to the value a UI cell reads. */
44
- export type JoinProjection<V> = (row: JoinReader) => V;
42
+ export type JoinProjection<V, Schemas extends readonly ComponentSchema[] = readonly ComponentSchema[]> = (row: JoinReader<Schemas>) => V;
45
43
  /** Per-component dirty grain. See the module header for the measured crossover. */
46
44
  export type SyncGrain = "entity" | "column";
47
45
  export interface EcsMapSyncOptions<V> {
@@ -55,7 +53,7 @@ export interface EcsMapSyncOptions<V> {
55
53
  eq?: (a: V, b: V) => boolean;
56
54
  /**
57
55
  * Access surface the projection touches, merged over the synced components'
58
- * `reads`. `__DEV__` access-checks the observer callbacks exactly like a system.
56
+ * `reads`. `DEV` access-checks the observer callbacks exactly like a system.
59
57
  */
60
58
  access?: Partial<SystemAccessDeclaration>;
61
59
  /**
@@ -79,7 +77,7 @@ export interface EcsMapSync<V> {
79
77
  * To read more than one component per row, use `syncJoinToMap` — reading a second
80
78
  * component here goes stale (its changes aren't subscribed).
81
79
  */
82
- export declare function syncComponentToMap<S extends ComponentSchema, V>(world: ECS, def: ComponentDef<S>, project: Projection<S, V>, opts?: EcsMapSyncOptions<V>): EcsMapSync<V>;
80
+ export declare function syncComponentToMap<S extends ComponentSchema, V>(ecs: ECS, def: ComponentDef<S>, project: Projection<S, V>, opts?: EcsMapSyncOptions<V>): EcsMapSync<V>;
83
81
  /**
84
82
  * Sugar over `syncComponentToMap`: project a fixed FIELD LIST into a `{ field:
85
83
  * value }` snapshot, with an automatic `shallow` eq so an unchanged row wakes
@@ -93,7 +91,7 @@ export declare function syncComponentToMap<S extends ComponentSchema, V>(world:
93
91
  * or hand-written-`eq` projection, so the hot path doesn't allocate exactly where
94
92
  * the column grain is meant to keep it cheap.
95
93
  */
96
- export declare function syncFieldsToMap<S extends ComponentSchema, const F extends readonly (string & keyof S)[]>(world: ECS, def: ComponentDef<S>, fields: F, opts?: Omit<EcsMapSyncOptions<{
94
+ export declare function syncFieldsToMap<S extends ComponentSchema, const F extends readonly (string & keyof S)[]>(ecs: ECS, def: ComponentDef<S>, fields: F, opts?: Omit<EcsMapSyncOptions<{
97
95
  [K in F[number]]: number;
98
96
  }>, "eq">): EcsMapSync<{
99
97
  [K in F[number]]: number;
@@ -109,7 +107,9 @@ export declare function syncFieldsToMap<S extends ComponentSchema, const F exten
109
107
  * Entity grain only: a join spans archetypes, so there is no single column to
110
108
  * sweep. Drive with `batchedUpdate(world, dt)`.
111
109
  */
112
- export declare function syncJoinToMap<V>(world: ECS, defs: readonly ComponentDef[], project: JoinProjection<V>, opts?: Omit<EcsMapSyncOptions<V>, "grain">): EcsMapSync<V>;
110
+ export declare function syncJoinToMap<Schemas extends readonly ComponentSchema[], V>(ecs: ECS, defs: readonly [...{
111
+ [I in keyof Schemas]: ComponentDef<Schemas[I]>;
112
+ }], project: JoinProjection<V, Schemas>, opts?: Omit<EcsMapSyncOptions<V>, "grain">): EcsMapSync<V>;
113
113
  export interface SingletonStructSync<V extends object> {
114
114
  /** The live per-field channel: reading `struct.field` in a tracked scope
115
115
  * subscribes to that field alone (pair with `fromKernelStruct` to render). */
@@ -120,7 +120,7 @@ export interface SingletonStructSync<V extends object> {
120
120
  export interface SingletonSyncOptions<V extends object = Record<string, number>> {
121
121
  /**
122
122
  * Access surface the publish touches, merged over the synced def's read.
123
- * `__DEV__` access-checks the observer callbacks exactly like a system.
123
+ * `DEV` access-checks the observer callbacks exactly like a system.
124
124
  */
125
125
  access?: Partial<SystemAccessDeclaration>;
126
126
  /** Replay current state on registration (default `true`). flecs `yieldExisting`. */
@@ -134,6 +134,10 @@ export interface SingletonSyncOptions<V extends object = Record<string, number>>
134
134
  * the eager initials with the entity's current values on registration.
135
135
  */
136
136
  into?: readonly [V, StructSetters<V>];
137
+ /** Per-field equality overrides for the created struct (parity with
138
+ * `SingletonArraySyncOptions.eq`). Ignored when `into` supplies a
139
+ * pre-created struct — that struct already carries its own `eq`. */
140
+ eq?: StructEq<V>;
137
141
  }
138
142
  /**
139
143
  * Sync ONE singleton entity's single-component state into a `reactiveStruct`,
@@ -165,7 +169,7 @@ export interface SingletonSyncOptions<V extends object = Record<string, number>>
165
169
  * or the zeros of a freshly-created one — captured (untracked) at registration, NOT a
166
170
  * blind 0. A singleton is rarely despawned; this keeps the channel well-defined if it is.
167
171
  */
168
- export declare function syncSingletonToStruct<S extends ComponentSchema, const F extends readonly (string & keyof S)[]>(world: ECS, def: ComponentDef<S>, eid: EntityID, fields: F, opts?: SingletonSyncOptions<{
172
+ export declare function syncSingletonToStruct<S extends ComponentSchema, const F extends readonly (string & keyof S)[]>(ecs: ECS, def: ComponentDef<S>, eid: EntityID, fields: F, opts?: SingletonSyncOptions<{
169
173
  [K in F[number]]: number;
170
174
  }>): SingletonStructSync<{
171
175
  [K in F[number]]: number;
@@ -197,14 +201,14 @@ export interface SingletonArraySyncOptions<T> {
197
201
  * `fields` is explicit (a `ComponentDef` is a branded number at runtime). Pair with
198
202
  * `@oasys/oecs/solid`'s `fromKernelArray` + a Solid `<Index>`.
199
203
  */
200
- export declare function syncSingletonToArray<S extends ComponentSchema>(world: ECS, def: ComponentDef<S>, eid: EntityID, fields: readonly (string & keyof S)[], opts?: SingletonArraySyncOptions<number>): SingletonArraySync<number>;
204
+ export declare function syncSingletonToArray<S extends ComponentSchema>(ecs: ECS, def: ComponentDef<S>, eid: EntityID, fields: readonly (string & keyof S)[], opts?: SingletonArraySyncOptions<number>): SingletonArraySync<number>;
201
205
  /**
202
206
  * Advance the world one tick with every bridge publish coalesced into a single UI
203
207
  * flush. onAdd/onRemove fire mid-tick at flush boundaries and onSet fires at the
204
208
  * tick tail; wrapping the whole update in `batch` defers the effect flush until the
205
209
  * tick completes, so a frame that touched K entities (across any number of syncs on
206
210
  * this world) wakes its readers once, not once per observer dispatch point.
207
- * Equivalent to `batch(() => world.update(dt))`.
211
+ * Equivalent to `batch(() => ecs.update(dt))`.
208
212
  */
209
- export declare function batchedUpdate(world: ECS, dt: number): void;
213
+ export declare function batchedUpdate(ecs: ECS, dt: number): void;
210
214
  //# sourceMappingURL=ecs_sync.d.ts.map