@oasys/oecs 0.3.0 → 0.4.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 (204) hide show
  1. package/README.md +196 -267
  2. package/dist/array-CxbPyiHO.cjs +1 -0
  3. package/dist/array-uFR7Q8fU.js +132 -0
  4. package/dist/core/ecs/access_check.d.ts +77 -0
  5. package/dist/core/ecs/access_check.d.ts.map +1 -0
  6. package/dist/core/ecs/archetype.d.ts +474 -0
  7. package/dist/core/ecs/archetype.d.ts.map +1 -0
  8. package/dist/core/ecs/builtin_relations.d.ts +43 -0
  9. package/dist/core/ecs/builtin_relations.d.ts.map +1 -0
  10. package/dist/core/ecs/command_log.d.ts +113 -0
  11. package/dist/core/ecs/command_log.d.ts.map +1 -0
  12. package/dist/core/ecs/component.d.ts +106 -0
  13. package/dist/core/ecs/component.d.ts.map +1 -0
  14. package/dist/core/ecs/compute_backend.d.ts +37 -0
  15. package/dist/core/ecs/compute_backend.d.ts.map +1 -0
  16. package/dist/core/ecs/dispatch_trace.d.ts +93 -0
  17. package/dist/core/ecs/dispatch_trace.d.ts.map +1 -0
  18. package/dist/core/ecs/ecs.d.ts +533 -0
  19. package/dist/core/ecs/ecs.d.ts.map +1 -0
  20. package/dist/core/ecs/ecs_memory.d.ts +179 -0
  21. package/dist/core/ecs/ecs_memory.d.ts.map +1 -0
  22. package/dist/core/ecs/entity.d.ts +28 -0
  23. package/dist/core/ecs/entity.d.ts.map +1 -0
  24. package/dist/core/ecs/event.d.ts +54 -0
  25. package/dist/core/ecs/event.d.ts.map +1 -0
  26. package/dist/core/ecs/frame_trace.d.ts +133 -0
  27. package/dist/core/ecs/frame_trace.d.ts.map +1 -0
  28. package/dist/core/ecs/host_commands.d.ts +252 -0
  29. package/dist/core/ecs/host_commands.d.ts.map +1 -0
  30. package/dist/core/ecs/index.d.ts +41 -0
  31. package/dist/core/ecs/index.d.ts.map +1 -0
  32. package/dist/core/ecs/observer.d.ts +142 -0
  33. package/dist/core/ecs/observer.d.ts.map +1 -0
  34. package/dist/core/ecs/query.d.ts +557 -0
  35. package/dist/core/ecs/query.d.ts.map +1 -0
  36. package/dist/core/ecs/ref.d.ts +31 -0
  37. package/dist/core/ecs/ref.d.ts.map +1 -0
  38. package/dist/core/ecs/relation.d.ts +231 -0
  39. package/dist/core/ecs/relation.d.ts.map +1 -0
  40. package/dist/core/ecs/resource.d.ts +33 -0
  41. package/dist/core/ecs/resource.d.ts.map +1 -0
  42. package/dist/core/ecs/resume.d.ts +85 -0
  43. package/dist/core/ecs/resume.d.ts.map +1 -0
  44. package/dist/core/ecs/run_condition.d.ts +75 -0
  45. package/dist/core/ecs/run_condition.d.ts.map +1 -0
  46. package/dist/core/ecs/schedule.d.ts +133 -0
  47. package/dist/core/ecs/schedule.d.ts.map +1 -0
  48. package/dist/core/ecs/sparse_store.d.ts +107 -0
  49. package/dist/core/ecs/sparse_store.d.ts.map +1 -0
  50. package/dist/core/ecs/store.d.ts +1149 -0
  51. package/dist/core/ecs/store.d.ts.map +1 -0
  52. package/dist/core/ecs/store_layout_listener.d.ts +23 -0
  53. package/dist/core/ecs/store_layout_listener.d.ts.map +1 -0
  54. package/dist/core/ecs/system.d.ts +134 -0
  55. package/dist/core/ecs/system.d.ts.map +1 -0
  56. package/dist/core/ecs/utils/arrays.d.ts +7 -0
  57. package/dist/core/ecs/utils/arrays.d.ts.map +1 -0
  58. package/dist/core/ecs/utils/constants.d.ts +12 -0
  59. package/dist/core/ecs/utils/constants.d.ts.map +1 -0
  60. package/dist/core/ecs/utils/error.d.ts +51 -0
  61. package/dist/core/ecs/utils/error.d.ts.map +1 -0
  62. package/dist/core/reactive/array.d.ts +24 -0
  63. package/dist/core/reactive/array.d.ts.map +1 -0
  64. package/dist/core/reactive/index.cjs +1 -0
  65. package/dist/core/reactive/index.d.ts +10 -0
  66. package/dist/core/reactive/index.d.ts.map +1 -0
  67. package/dist/core/reactive/index.js +17 -0
  68. package/dist/core/reactive/interop.d.ts +19 -0
  69. package/dist/core/reactive/interop.d.ts.map +1 -0
  70. package/dist/core/reactive/kernel.d.ts +71 -0
  71. package/dist/core/reactive/kernel.d.ts.map +1 -0
  72. package/dist/core/reactive/map.d.ts +16 -0
  73. package/dist/core/reactive/map.d.ts.map +1 -0
  74. package/dist/core/reactive/struct.d.ts +10 -0
  75. package/dist/core/reactive/struct.d.ts.map +1 -0
  76. package/dist/core/store/__generated__/abi.d.ts +43 -0
  77. package/dist/core/store/__generated__/abi.d.ts.map +1 -0
  78. package/dist/core/store/action_ring.d.ts +136 -0
  79. package/dist/core/store/action_ring.d.ts.map +1 -0
  80. package/dist/core/store/allocator.d.ts +238 -0
  81. package/dist/core/store/allocator.d.ts.map +1 -0
  82. package/dist/core/store/buffer_backed_column.d.ts +69 -0
  83. package/dist/core/store/buffer_backed_column.d.ts.map +1 -0
  84. package/dist/core/store/column_store.d.ts +265 -0
  85. package/dist/core/store/column_store.d.ts.map +1 -0
  86. package/dist/core/store/command_dispatch.d.ts +52 -0
  87. package/dist/core/store/command_dispatch.d.ts.map +1 -0
  88. package/dist/core/store/command_ring.d.ts +107 -0
  89. package/dist/core/store/command_ring.d.ts.map +1 -0
  90. package/dist/core/store/descriptor.d.ts +80 -0
  91. package/dist/core/store/descriptor.d.ts.map +1 -0
  92. package/dist/core/store/entity_index.d.ts +108 -0
  93. package/dist/core/store/entity_index.d.ts.map +1 -0
  94. package/dist/core/store/event_ring.d.ts +95 -0
  95. package/dist/core/store/event_ring.d.ts.map +1 -0
  96. package/dist/core/store/extend.d.ts +109 -0
  97. package/dist/core/store/extend.d.ts.map +1 -0
  98. package/dist/core/store/grow.d.ts +39 -0
  99. package/dist/core/store/grow.d.ts.map +1 -0
  100. package/dist/core/store/header.d.ts +64 -0
  101. package/dist/core/store/header.d.ts.map +1 -0
  102. package/dist/core/store/index.d.ts +16 -0
  103. package/dist/core/store/index.d.ts.map +1 -0
  104. package/dist/core/store/region_table.d.ts +74 -0
  105. package/dist/core/store/region_table.d.ts.map +1 -0
  106. package/dist/core/store/snapshot.d.ts +43 -0
  107. package/dist/core/store/snapshot.d.ts.map +1 -0
  108. package/dist/core/store/state_hash.d.ts +38 -0
  109. package/dist/core/store/state_hash.d.ts.map +1 -0
  110. package/dist/core/store/store_regions.d.ts +38 -0
  111. package/dist/core/store/store_regions.d.ts.map +1 -0
  112. package/dist/extensions/editor/editor.d.ts +149 -0
  113. package/dist/extensions/editor/editor.d.ts.map +1 -0
  114. package/dist/extensions/editor/field_handle.d.ts +35 -0
  115. package/dist/extensions/editor/field_handle.d.ts.map +1 -0
  116. package/dist/extensions/editor/index.cjs +1 -0
  117. package/dist/extensions/editor/index.d.ts +21 -0
  118. package/dist/extensions/editor/index.d.ts.map +1 -0
  119. package/dist/extensions/editor/index.js +209 -0
  120. package/dist/extensions/reactive/ecs_sync.d.ts +210 -0
  121. package/dist/extensions/reactive/ecs_sync.d.ts.map +1 -0
  122. package/dist/extensions/reactive/index.cjs +1 -0
  123. package/dist/extensions/reactive/index.d.ts +23 -0
  124. package/dist/extensions/reactive/index.d.ts.map +1 -0
  125. package/dist/extensions/reactive/index.js +225 -0
  126. package/dist/extensions/solid/index.cjs +1 -0
  127. package/dist/extensions/solid/index.d.ts +6 -0
  128. package/dist/extensions/solid/index.d.ts.map +1 -0
  129. package/dist/extensions/solid/index.js +32 -0
  130. package/dist/extensions/solid/kernel_solid.d.ts +42 -0
  131. package/dist/extensions/solid/kernel_solid.d.ts.map +1 -0
  132. package/dist/index.cjs +2 -1
  133. package/dist/index.d.ts +16 -12
  134. package/dist/index.d.ts.map +1 -1
  135. package/dist/index.js +8563 -1233
  136. package/dist/interop-CT-REx0W.cjs +1 -0
  137. package/dist/interop-CcY6ASQc.js +18 -0
  138. package/dist/kernel-DgyrLFjW.js +227 -0
  139. package/dist/kernel-yWV3XnAb.cjs +1 -0
  140. package/dist/log/console_sink.d.ts +4 -0
  141. package/dist/log/console_sink.d.ts.map +1 -0
  142. package/dist/log/index.d.ts +3 -0
  143. package/dist/log/index.d.ts.map +1 -0
  144. package/dist/log/logger.d.ts +27 -0
  145. package/dist/log/logger.d.ts.map +1 -0
  146. package/dist/primitives.cjs +1 -0
  147. package/dist/primitives.d.ts +18 -0
  148. package/dist/primitives.d.ts.map +1 -0
  149. package/dist/primitives.js +44 -0
  150. package/dist/shared-BXSZnxx4.cjs +1 -0
  151. package/dist/shared-C678TAPY.js +99 -0
  152. package/dist/shared.cjs +1 -0
  153. package/dist/shared.d.ts +22 -0
  154. package/dist/shared.d.ts.map +1 -0
  155. package/dist/shared.js +7 -0
  156. package/dist/topological_sort-DlRpSrxu.js +391 -0
  157. package/dist/topological_sort-WAT-VHb-.cjs +1 -0
  158. package/dist/type_primitives/assertions.d.ts +12 -8
  159. package/dist/type_primitives/assertions.d.ts.map +1 -1
  160. package/dist/type_primitives/binary_heap/binary_heap.d.ts +6 -2
  161. package/dist/type_primitives/binary_heap/binary_heap.d.ts.map +1 -1
  162. package/dist/type_primitives/bitset/bitset.d.ts +16 -4
  163. package/dist/type_primitives/bitset/bitset.d.ts.map +1 -1
  164. package/dist/type_primitives/brand.d.ts +6 -1
  165. package/dist/type_primitives/brand.d.ts.map +1 -1
  166. package/dist/type_primitives/error.d.ts +4 -0
  167. package/dist/type_primitives/error.d.ts.map +1 -1
  168. package/dist/type_primitives/index.d.ts +3 -0
  169. package/dist/type_primitives/index.d.ts.map +1 -1
  170. package/dist/type_primitives/sparse_map/sparse_map.d.ts +7 -3
  171. package/dist/type_primitives/sparse_map/sparse_map.d.ts.map +1 -1
  172. package/dist/type_primitives/sparse_set/sparse_set.d.ts +4 -0
  173. package/dist/type_primitives/sparse_set/sparse_set.d.ts.map +1 -1
  174. package/dist/type_primitives/topological_sort/topological_sort.d.ts +7 -3
  175. package/dist/type_primitives/topological_sort/topological_sort.d.ts.map +1 -1
  176. package/dist/type_primitives/typed_arrays/typed_arrays.d.ts +53 -16
  177. package/dist/type_primitives/typed_arrays/typed_arrays.d.ts.map +1 -1
  178. package/dist/utils/arrays.d.ts +1 -1
  179. package/dist/utils/arrays.d.ts.map +1 -1
  180. package/dist/utils/error.d.ts +2 -20
  181. package/dist/utils/error.d.ts.map +1 -1
  182. package/package.json +36 -9
  183. package/dist/archetype.d.ts +0 -101
  184. package/dist/archetype.d.ts.map +0 -1
  185. package/dist/component.d.ts +0 -45
  186. package/dist/component.d.ts.map +0 -1
  187. package/dist/ecs.d.ts +0 -104
  188. package/dist/ecs.d.ts.map +0 -1
  189. package/dist/entity.d.ts +0 -11
  190. package/dist/entity.d.ts.map +0 -1
  191. package/dist/event.d.ts +0 -30
  192. package/dist/event.d.ts.map +0 -1
  193. package/dist/query.d.ts +0 -90
  194. package/dist/query.d.ts.map +0 -1
  195. package/dist/ref.d.ts +0 -23
  196. package/dist/ref.d.ts.map +0 -1
  197. package/dist/resource.d.ts +0 -23
  198. package/dist/resource.d.ts.map +0 -1
  199. package/dist/schedule.d.ts +0 -45
  200. package/dist/schedule.d.ts.map +0 -1
  201. package/dist/store.d.ts +0 -118
  202. package/dist/store.d.ts.map +0 -1
  203. package/dist/system.d.ts +0 -16
  204. package/dist/system.d.ts.map +0 -1
@@ -0,0 +1,149 @@
1
+ import { ComponentDef, ComponentHandle, ComponentSchema, EntityID, FieldValues, HostCommand, HostCommandQueue, SpawnEntry } from '../../core/ecs';
2
+ /**
3
+ * Reads the committed value of one `(entity, component, field)` slot — the editor
4
+ * uses it to seed a `setField` inverse with the value the edit replaced. Wire it
5
+ * to the reactive read channel (e.g. a `reactiveMap`/`reactiveStruct` projection)
6
+ * or to `world.getField`; `undefined` for an unknown slot falls back to `0`.
7
+ */
8
+ export type FieldReader = (eid: EntityID, def: ComponentDef, field: string) => number | undefined;
9
+ /**
10
+ * A reified, undoable unit of editor work: the `forward` commands and their
11
+ * `inverse`, both plain `HostCommand` data on the one bus. The arrays are mutable
12
+ * internally (the spawn/despawn id finalizers patch a slot at apply time) but the
13
+ * type handed back from {@link Editor.transaction} is read-only.
14
+ */
15
+ export interface EditorTransaction {
16
+ readonly forward: readonly HostCommand[];
17
+ readonly inverse: readonly HostCommand[];
18
+ }
19
+ /** Internal, mutable transaction the id-finalizers patch in place. */
20
+ interface MutableTxn {
21
+ forward: HostCommand[];
22
+ inverse: HostCommand[];
23
+ }
24
+ /**
25
+ * Accumulates the `forward`/`inverse` commands for ONE transaction. Each method
26
+ * appends a forward command and its inverse, computing the inverse from the
27
+ * shadow / read channel where needed. Spawn and despawn install id-finalizers
28
+ * (via `HostCommand.onSpawned`) that patch the apply-time id into the right slot.
29
+ *
30
+ * Obtained from {@link Editor.transaction}; not constructed directly.
31
+ */
32
+ export declare class TransactionBuilder {
33
+ private readonly readField;
34
+ private readonly shadow;
35
+ /** @internal */
36
+ readonly _txn: MutableTxn;
37
+ /** @internal */
38
+ constructor(readField: FieldReader, shadow: Map<string, number>);
39
+ /**
40
+ * Spawn an entity carrying `components`. Inverse: despawn the created entity,
41
+ * finalized once the spawn applies (the id only exists post-flush). `onSpawned`
42
+ * also forwards the new id to the caller. The finalizer re-fires on redo, so the
43
+ * inverse tracks the current id.
44
+ */
45
+ spawn(components: readonly SpawnEntry[], onSpawned?: (eid: EntityID) => void): this;
46
+ /**
47
+ * Despawn `eid`. Inverse: respawn from `restore` (the components+values to
48
+ * recreate — read them from the channel before despawning). Undo respawns the
49
+ * DATA, not the identity: the new entity gets a fresh id, and the respawn's
50
+ * `onSpawned` rewrites this despawn's target so redo removes the respawned
51
+ * entity rather than the dead original.
52
+ */
53
+ despawn(eid: EntityID, restore: readonly SpawnEntry[]): this;
54
+ /**
55
+ * Set `field` of `def` on `eid` to `value`. Inverse: set it back to the value
56
+ * this edit replaced — read from the shadow (so stacked edits before a commit
57
+ * invert correctly) or, failing that, the read channel (`0` if unknown).
58
+ */
59
+ setField<S extends ComponentSchema>(eid: EntityID, def: ComponentDef<S>, field: string & keyof S, value: number): this;
60
+ /** Add `def` (with complete `values`) to `eid`. Inverse: remove it. */
61
+ addComponent<S extends ComponentSchema>(eid: EntityID, def: ComponentDef<S>, values: FieldValues<S>): this;
62
+ /**
63
+ * Remove `def` from `eid`. Inverse: re-add it from `restore` (the field values
64
+ * to recreate — read them from the channel before removing).
65
+ */
66
+ removeComponent<S extends ComponentSchema>(eid: EntityID, def: ComponentDef<S>, restore: FieldValues<S>): this;
67
+ /** Disable `eid`. Inverse: enable it. */
68
+ disable(eid: EntityID): this;
69
+ /** Enable `eid`. Inverse: disable it. */
70
+ enable(eid: EntityID): this;
71
+ }
72
+ /**
73
+ * The editor's undo/redo manager over a {@link HostCommandQueue}. Each action is a
74
+ * reified {@link EditorTransaction} pushed onto the undo stack; `undo()`/`redo()`
75
+ * enqueue its inverse/forward on the SAME bus. The single-action methods
76
+ * (`spawn`, `setField`, …) are sugar for one-command transactions; `transaction`
77
+ * groups several into one undo entry.
78
+ *
79
+ * Construct with the queue and a {@link FieldReader} for the committed read
80
+ * channel (used to seed `setField` inverses).
81
+ */
82
+ export declare class Editor {
83
+ private readonly queue;
84
+ private readonly readField;
85
+ private readonly undoStack;
86
+ private readonly redoStack;
87
+ /** Per-`(entity, component, field)` shadow of edited values, for inverse correctness. */
88
+ private readonly shadow;
89
+ constructor(queue: HostCommandQueue, readField: FieldReader);
90
+ /**
91
+ * Group several actions into ONE undo entry. Build them on the passed
92
+ * {@link TransactionBuilder}; the whole group commits (enqueues its forward
93
+ * commands) and lands on the undo stack atomically, clearing the redo stack.
94
+ */
95
+ transaction(build: (tx: TransactionBuilder) => void): EditorTransaction;
96
+ /** Spawn `components` as its own undo entry. `onSpawned` reports the new id. */
97
+ spawn(components: readonly SpawnEntry[], onSpawned?: (eid: EntityID) => void): EditorTransaction;
98
+ /** Despawn `eid` as its own undo entry; `restore` recreates it on undo. */
99
+ despawn(eid: EntityID, restore: readonly SpawnEntry[]): EditorTransaction;
100
+ /** Set one field as its own undo entry. */
101
+ setField<S extends ComponentSchema>(eid: EntityID, def: ComponentDef<S>, field: string & keyof S, value: number): EditorTransaction;
102
+ /** Add a component as its own undo entry. */
103
+ addComponent<S extends ComponentSchema>(eid: EntityID, def: ComponentDef<S>, values: FieldValues<S>): EditorTransaction;
104
+ /** Remove a component as its own undo entry; `restore` re-adds it on undo. */
105
+ removeComponent<S extends ComponentSchema>(eid: EntityID, def: ComponentDef<S>, restore: FieldValues<S>): EditorTransaction;
106
+ /** Disable `eid` as its own undo entry. */
107
+ disable(eid: EntityID): EditorTransaction;
108
+ /** Enable `eid` as its own undo entry. */
109
+ enable(eid: EntityID): EditorTransaction;
110
+ /**
111
+ * Undo the most recent transaction: enqueue its inverse commands (in reverse
112
+ * order — a group unwinds last-action-first) on the bus and move it to the redo
113
+ * stack. Returns `false` if the undo stack is empty.
114
+ */
115
+ undo(): boolean;
116
+ /**
117
+ * Redo the most recently undone transaction: re-enqueue its forward commands on
118
+ * the bus and move it back to the undo stack. Returns `false` if the redo stack
119
+ * is empty.
120
+ */
121
+ redo(): boolean;
122
+ /** Drop both stacks (e.g. on load). Does not touch the world. */
123
+ clear(): void;
124
+ /** Current stack depths — for an "Undo (3)" / "Redo" affordance. */
125
+ depths(): {
126
+ undo: number;
127
+ redo: number;
128
+ };
129
+ /**
130
+ * The pending value the editor believes for a field that the committed read
131
+ * channel has NOT caught up to yet (the shadow), or `undefined` if none. Lets an
132
+ * inspector echo an edit between the `set` and the tick that commits it.
133
+ *
134
+ * Self-resolving: once the read channel reports the shadowed value (the edit
135
+ * landed), the entry is dropped and this returns `undefined` — so `pending` does
136
+ * not outlive its set→commit window and shadow a later external write. The one
137
+ * residual: if an external write changes the field to a *different* value within
138
+ * the same window before this is next consulted, `pending` can read stale until
139
+ * the next edit to the slot. `value` (the read channel) is always the source of
140
+ * truth; `pending` is only the optimistic bridge.
141
+ */
142
+ pendingField(eid: EntityID, def: ComponentHandle, field: string): number | undefined;
143
+ /** Enqueue forward commands, record the transaction, clear redo, sync shadow. */
144
+ private commit;
145
+ /** Keep the setField shadow in step with the commands just enqueued. */
146
+ private applyShadow;
147
+ }
148
+ export {};
149
+ //# sourceMappingURL=editor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../../../src/extensions/editor/editor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import { ComponentDef, ComponentSchema, EntityID } from '../../core/ecs';
2
+ import { Editor } from './editor';
3
+ /**
4
+ * A two-way handle on one `(entity, component, field)` slot. `value` reads the
5
+ * reactive channel (tracked, if read in a tracking scope); `set` enqueues an
6
+ * undoable `setField` command via the editor.
7
+ */
8
+ export interface FieldHandle {
9
+ /**
10
+ * The field's current value through the reactive read channel — `undefined`
11
+ * until the channel has it (e.g. before the spawn's first commit). Read inside
12
+ * a tracking scope to subscribe to the channel; the value reflects the last
13
+ * COMMITTED tick, so a fresh `set` shows up on the next tick.
14
+ */
15
+ readonly value: number | undefined;
16
+ /** Enqueue an undoable `setField` for this slot; applied at the next tick. */
17
+ set(value: number): void;
18
+ /**
19
+ * The editor's pending (not-yet-committed) value for this slot, or `undefined`
20
+ * if none — a NON-reactive read of the editor shadow, for an optimistic echo
21
+ * between the `set` and its commit. Self-resolves to `undefined` once the read
22
+ * channel catches up, so it does not outlive the edit. Not a substitute for
23
+ * `value` in a tracking scope (it does not subscribe); `value` is the source of
24
+ * truth.
25
+ */
26
+ readonly pending: number | undefined;
27
+ }
28
+ /**
29
+ * Build a {@link FieldHandle} for one `(eid, def, field)` slot. `read` is the
30
+ * tracked read of the reactive channel for this field (e.g.
31
+ * `() => sync.map.get(eid)?.x`); `set` routes through `editor.setField`, so the
32
+ * edit is queued, batched, and undoable.
33
+ */
34
+ export declare function fieldHandle<S extends ComponentSchema>(editor: Editor, eid: EntityID, def: ComponentDef<S>, field: string & keyof S, read: () => number | undefined): FieldHandle;
35
+ //# sourceMappingURL=field_handle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"field_handle.d.ts","sourceRoot":"","sources":["../../../src/extensions/editor/field_handle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC9E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC3B;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,8EAA8E;IAC9E,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB;;;;;;;OAOG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;CACrC;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,eAAe,EACpD,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,QAAQ,EACb,GAAG,EAAE,YAAY,CAAC,CAAC,CAAC,EACpB,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,EACvB,IAAI,EAAE,MAAM,MAAM,GAAG,SAAS,GAC5B,WAAW,CAYb"}
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function o(i,t,n){return`${i}:${t.id}:${n}`}class a{constructor(t,n){this.readField=t,this.shadow=n}_txn={forward:[],inverse:[]};spawn(t,n){const e={kind:"despawn",eid:0};return this._txn.inverse.push(e),this._txn.forward.push({kind:"spawn",components:t,onSpawned:s=>{e.eid=s,n?.(s)}}),this}despawn(t,n){const e={kind:"despawn",eid:t};return this._txn.forward.push(e),this._txn.inverse.push({kind:"spawn",components:n,onSpawned:s=>{e.eid=s}}),this}setField(t,n,e,s){const r=o(t,n,e),d=this.shadow.get(r)??this.readField(t,n,e)??0;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:d}),this}addComponent(t,n,e){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}removeComponent(t,n,e){return this._txn.forward.push({kind:"remove_component",eid:t,def:n}),this._txn.inverse.push({kind:"add_component",eid:t,def:n,values:e}),this}disable(t){return this._txn.forward.push({kind:"disable",eid:t}),this._txn.inverse.push({kind:"enable",eid:t}),this}enable(t){return this._txn.forward.push({kind:"enable",eid:t}),this._txn.inverse.push({kind:"disable",eid:t}),this}}class h{constructor(t,n){this.queue=t,this.readField=n}undoStack=[];redoStack=[];shadow=new Map;transaction(t){const n=new a(this.readField,this.shadow);return t(n),this.commit(n._txn)}spawn(t,n){return this.transaction(e=>e.spawn(t,n))}despawn(t,n){return this.transaction(e=>e.despawn(t,n))}setField(t,n,e,s){return this.transaction(r=>r.setField(t,n,e,s))}addComponent(t,n,e){return this.transaction(s=>s.addComponent(t,n,e))}removeComponent(t,n,e){return this.transaction(s=>s.removeComponent(t,n,e))}disable(t){return this.transaction(n=>n.disable(t))}enable(t){return this.transaction(n=>n.enable(t))}undo(){const t=this.undoStack.pop();if(t===void 0)return!1;const n=t.inverse.slice().reverse();for(const e of n)this.queue.push(e);return this.applyShadow(n),this.redoStack.push(t),!0}redo(){const t=this.redoStack.pop();if(t===void 0)return!1;for(const n of t.forward)this.queue.push(n);return this.applyShadow(t.forward),this.undoStack.push(t),!0}clear(){this.undoStack.length=0,this.redoStack.length=0,this.shadow.clear()}depths(){return{undo:this.undoStack.length,redo:this.redoStack.length}}pendingField(t,n,e){const s=o(t,n,e),r=this.shadow.get(s);if(r!==void 0){if(this.readField(t,n,e)===r){this.shadow.delete(s);return}return r}}commit(t){if(t.forward.length===0)return t;for(const n of t.forward)this.queue.push(n);return this.applyShadow(t.forward),this.undoStack.push(t),this.redoStack.length=0,t}applyShadow(t){for(const n of t)n.kind==="set_field"&&this.shadow.set(o(n.eid,n.def,n.field),n.value)}}function u(i,t,n,e,s){return{get value(){return s()},set(r){i.setField(t,n,e,r)},get pending(){return i.pendingField(t,n,e)}}}exports.Editor=h;exports.TransactionBuilder=a;exports.fieldHandle=u;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The host write seam's EDITOR layer (#701) — layer 2 of #681 / PATTERNS §85.
3
+ *
4
+ * Reified undo/redo + the inspector field-handle, built on the shipped typed
5
+ * `HostCommandQueue` (#698). Application policy, so it lives here in
6
+ * `engine-extensions`, not engine core — and it pulls NO third-party / framework
7
+ * dependency (the field-handle reads through a caller-supplied thunk).
8
+ *
9
+ * - `Editor` — reified `EditorTransaction`s on undo/redo stacks, with
10
+ * transaction grouping; `undo()`/`redo()` enqueue the inverse/forward on the
11
+ * SAME bus, applied at the next schedule head (undo is just another command).
12
+ * - `fieldHandle` — pairs a reactive-channel read with a `setField` command, so
13
+ * an inspector field feels two-way while staying safe and undoable.
14
+ *
15
+ * Reachable as `@oasys/oecs/editor`. Pair it with
16
+ * `@oasys/oecs/reactive-sync`'s `syncFieldsToMap` /
17
+ * `syncSingletonToStruct` for the read channel the field-handle reads through.
18
+ */
19
+ export { Editor, TransactionBuilder, type EditorTransaction, type FieldReader } from './editor';
20
+ export { fieldHandle, type FieldHandle } from './field_handle';
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/extensions/editor/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AAChG,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,209 @@
1
+ function o(i, t, n) {
2
+ return `${i}:${t.id}:${n}`;
3
+ }
4
+ class d {
5
+ /** @internal */
6
+ constructor(t, n) {
7
+ this.readField = t, this.shadow = n;
8
+ }
9
+ /** @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 = {
19
+ kind: "despawn",
20
+ eid: 0
21
+ };
22
+ return this._txn.inverse.push(e), this._txn.forward.push({
23
+ kind: "spawn",
24
+ components: t,
25
+ onSpawned: (s) => {
26
+ e.eid = s, n?.(s);
27
+ }
28
+ }), this;
29
+ }
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({
40
+ kind: "spawn",
41
+ components: n,
42
+ onSpawned: (s) => {
43
+ e.eid = s;
44
+ }
45
+ }), this;
46
+ }
47
+ /**
48
+ * 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).
51
+ */
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;
55
+ }
56
+ /** 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;
59
+ }
60
+ /**
61
+ * Remove `def` from `eid`. Inverse: re-add it from `restore` (the field values
62
+ * to recreate — read them from the channel before removing).
63
+ */
64
+ removeComponent(t, n, e) {
65
+ return this._txn.forward.push({ kind: "remove_component", eid: t, def: n }), this._txn.inverse.push({
66
+ kind: "add_component",
67
+ eid: t,
68
+ def: n,
69
+ values: e
70
+ }), this;
71
+ }
72
+ /** Disable `eid`. Inverse: enable it. */
73
+ disable(t) {
74
+ return this._txn.forward.push({ kind: "disable", eid: t }), this._txn.inverse.push({ kind: "enable", eid: t }), this;
75
+ }
76
+ /** Enable `eid`. Inverse: disable it. */
77
+ enable(t) {
78
+ return this._txn.forward.push({ kind: "enable", eid: t }), this._txn.inverse.push({ kind: "disable", eid: t }), this;
79
+ }
80
+ }
81
+ class h {
82
+ constructor(t, n) {
83
+ this.queue = t, this.readField = n;
84
+ }
85
+ undoStack = [];
86
+ redoStack = [];
87
+ /** Per-`(entity, component, field)` shadow of edited values, for inverse correctness. */
88
+ shadow = /* @__PURE__ */ new Map();
89
+ /**
90
+ * Group several actions into ONE undo entry. Build them on the passed
91
+ * {@link TransactionBuilder}; the whole group commits (enqueues its forward
92
+ * commands) and lands on the undo stack atomically, clearing the redo stack.
93
+ */
94
+ transaction(t) {
95
+ const n = new d(this.readField, this.shadow);
96
+ return t(n), this.commit(n._txn);
97
+ }
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));
101
+ }
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));
105
+ }
106
+ /** 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));
109
+ }
110
+ /** Add a component as its own undo entry. */
111
+ addComponent(t, n, e) {
112
+ return this.transaction((s) => s.addComponent(t, n, e));
113
+ }
114
+ /** 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));
117
+ }
118
+ /** Disable `eid` as its own undo entry. */
119
+ disable(t) {
120
+ return this.transaction((n) => n.disable(t));
121
+ }
122
+ /** Enable `eid` as its own undo entry. */
123
+ enable(t) {
124
+ return this.transaction((n) => n.enable(t));
125
+ }
126
+ /**
127
+ * Undo the most recent transaction: enqueue its inverse commands (in reverse
128
+ * order — a group unwinds last-action-first) on the bus and move it to the redo
129
+ * stack. Returns `false` if the undo stack is empty.
130
+ */
131
+ undo() {
132
+ const t = this.undoStack.pop();
133
+ 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;
137
+ }
138
+ /**
139
+ * Redo the most recently undone transaction: re-enqueue its forward commands on
140
+ * the bus and move it back to the undo stack. Returns `false` if the redo stack
141
+ * is empty.
142
+ */
143
+ redo() {
144
+ const t = this.redoStack.pop();
145
+ 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;
148
+ }
149
+ /** Drop both stacks (e.g. on load). Does not touch the world. */
150
+ clear() {
151
+ this.undoStack.length = 0, this.redoStack.length = 0, this.shadow.clear();
152
+ }
153
+ /** Current stack depths — for an "Undo (3)" / "Redo" affordance. */
154
+ depths() {
155
+ return { undo: this.undoStack.length, redo: this.redoStack.length };
156
+ }
157
+ /**
158
+ * The pending value the editor believes for a field that the committed read
159
+ * channel has NOT caught up to yet (the shadow), or `undefined` if none. Lets an
160
+ * inspector echo an edit between the `set` and the tick that commits it.
161
+ *
162
+ * 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
165
+ * residual: if an external write changes the field to a *different* value within
166
+ * the same window before this is next consulted, `pending` can read stale until
167
+ * the next edit to the slot. `value` (the read channel) is always the source of
168
+ * truth; `pending` is only the optimistic bridge.
169
+ */
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;
178
+ }
179
+ }
180
+ /** Enqueue forward commands, record the transaction, clear redo, sync shadow. */
181
+ commit(t) {
182
+ 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;
185
+ }
186
+ /** Keep the setField shadow in step with the commands just enqueued. */
187
+ 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);
190
+ }
191
+ }
192
+ function u(i, t, n, e, s) {
193
+ return {
194
+ get value() {
195
+ return s();
196
+ },
197
+ set(r) {
198
+ i.setField(t, n, e, r);
199
+ },
200
+ get pending() {
201
+ return i.pendingField(t, n, e);
202
+ }
203
+ };
204
+ }
205
+ export {
206
+ h as Editor,
207
+ d as TransactionBuilder,
208
+ u as fieldHandle
209
+ };
@@ -0,0 +1,210 @@
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;
10
+ /**
11
+ * A read cursor over one entity's single-component state, handed to a single-
12
+ * component `Projection`. `field` is column-backed under "column" grain
13
+ * (sequential), random-access (`getField`) under "entity" grain.
14
+ *
15
+ * Lifetime: the cursor is a reused, mutable singleton valid ONLY during the
16
+ * synchronous `project` call for the current row. Read what you need and return —
17
+ * never capture `row` (or stash its `field` reads keyed off a later closure): the
18
+ * next row/tick mutates it in place, so a captured cursor reads stale data.
19
+ */
20
+ export interface RowReader<S extends ComponentSchema> {
21
+ /** The current entity (the row being projected). */
22
+ readonly eid: EntityID;
23
+ /** Read a field of the synced component for the current row. */
24
+ field<K extends string & keyof S>(name: K): number;
25
+ }
26
+ /**
27
+ * A read cursor over a JOINED entity (it carries every component in the join), for
28
+ * a `JoinProjection`. `field(def, name)` reads any field of any joined component
29
+ * for the current entity (random-access; the join subscribes to all of them so the
30
+ * read can never go stale).
31
+ *
32
+ * Lifetime: like `RowReader`, a reused mutable singleton valid ONLY during the
33
+ * synchronous `project` call — never capture it; the next dispatch mutates it.
34
+ */
35
+ export interface JoinReader {
36
+ /** The current entity (it has all joined components). */
37
+ 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;
40
+ }
41
+ /** Map one single-component row to the value a UI cell reads. */
42
+ export type Projection<S extends ComponentSchema, V> = (row: RowReader<S>) => V;
43
+ /** Map one joined entity to the value a UI cell reads. */
44
+ export type JoinProjection<V> = (row: JoinReader) => V;
45
+ /** Per-component dirty grain. See the module header for the measured crossover. */
46
+ export type SyncGrain = "entity" | "column";
47
+ export interface EcsMapSyncOptions<V> {
48
+ /** Dirty grain (default `"entity"`). Use `"column"` for high-churn components. */
49
+ grain?: SyncGrain;
50
+ /**
51
+ * Value equality for the map's per-key no-op skip (default `Object.is`). Pass
52
+ * `shallow` for object projections, or a content comparator. A fresh object
53
+ * every tick under the default reference eq wakes the row every frame.
54
+ */
55
+ eq?: (a: V, b: V) => boolean;
56
+ /**
57
+ * Access surface the projection touches, merged over the synced components'
58
+ * `reads`. `__DEV__` access-checks the observer callbacks exactly like a system.
59
+ */
60
+ access?: Partial<SystemAccessDeclaration>;
61
+ /**
62
+ * Replay current matches on registration (default `true`), so a bridge attached
63
+ * to an already-populated world seeds the map with present state instead of
64
+ * waking only on the next change. flecs `yieldExisting`.
65
+ */
66
+ seedExisting?: boolean;
67
+ }
68
+ export interface EcsMapSync<V> {
69
+ /** The live channel: a reader of key `eid` subscribes to that entity alone. */
70
+ readonly map: ReactiveMap<EntityID, V>;
71
+ /** Unregister the observer(s) and stop publishing. Safe to call more than once. */
72
+ dispose(): void;
73
+ }
74
+ /**
75
+ * Sync ONE component's change detection into a `reactiveMap`, keyed by `EntityID`.
76
+ * A value change publishes the changed rows; a spawn inserts a row and a despawn
77
+ * deletes it. Returns the map + a disposer. Drive with `batchedUpdate(world, dt)`.
78
+ *
79
+ * To read more than one component per row, use `syncJoinToMap` — reading a second
80
+ * component here goes stale (its changes aren't subscribed).
81
+ */
82
+ export declare function syncComponentToMap<S extends ComponentSchema, V>(world: ECS, def: ComponentDef<S>, project: Projection<S, V>, opts?: EcsMapSyncOptions<V>): EcsMapSync<V>;
83
+ /**
84
+ * Sugar over `syncComponentToMap`: project a fixed FIELD LIST into a `{ field:
85
+ * value }` snapshot, with an automatic `shallow` eq so an unchanged row wakes
86
+ * nobody. The declarative-field-list model from WatermelonDB `observeWithColumns` /
87
+ * TanStack `select` — the common "mirror these fields" case without hand-writing
88
+ * the projection + comparator.
89
+ *
90
+ * Allocation note: this builds a fresh object per dirty row and `shallow` does two
91
+ * `Object.keys` per compare — fine for low-churn. For a HIGH-churn component (the
92
+ * case you'd pick `grain:"column"` for), prefer `syncComponentToMap` with a scalar
93
+ * or hand-written-`eq` projection, so the hot path doesn't allocate exactly where
94
+ * the column grain is meant to keep it cheap.
95
+ */
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<{
97
+ [K in F[number]]: number;
98
+ }>, "eq">): EcsMapSync<{
99
+ [K in F[number]]: number;
100
+ }>;
101
+ /**
102
+ * Sync a multi-component JOIN into a `reactiveMap`. An entity is a member iff it
103
+ * has EVERY component in `defs`; the projection reads any of them via
104
+ * `row.field(def, name)`. Change detection subscribes to ALL joined components, so
105
+ * a write to ANY of them republishes the row — the join can never go stale (the
106
+ * bug a single-component sync + manual secondary read would have). Spawns/removes
107
+ * of any joined component re-evaluate membership.
108
+ *
109
+ * Entity grain only: a join spans archetypes, so there is no single column to
110
+ * sweep. Drive with `batchedUpdate(world, dt)`.
111
+ */
112
+ export declare function syncJoinToMap<V>(world: ECS, defs: readonly ComponentDef[], project: JoinProjection<V>, 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
+ }
138
+ /**
139
+ * Sync ONE singleton entity's single-component state into a `reactiveStruct`,
140
+ * keyless — the singleton/resource shape (ADR-0024). This is how heterogeneous
141
+ * ephemeral UI state (net status + latency, FPS/mem, wave timer, hovered hex) joins
142
+ * the same reactive view as per-entity components: model it as components on a
143
+ * reserved singleton entity (the flecs / Unity-DOTS "singleton-as-entity" model)
144
+ * and read it through per-field channels — NOT a separate reactive-resource
145
+ * subsystem. Resources stay non-reactive internal singletons.
146
+ *
147
+ * It reuses the entity-grain ADR-0013 component observer verbatim, filtered to the
148
+ * one target eid: an `onSet` (or seed `onAdd`) republishes the entity's field values
149
+ * into the struct via per-field setters. The per-field `Object.is` eq means an
150
+ * unchanged field writes nothing — "equal write → 0 renders" — and per-field signals
151
+ * give "1-of-N FIELD → 1 re-render"; drive with `batchedUpdate` for "one tick → one
152
+ * commit". Because the component change-detection fires on `ctx.setField`, the
153
+ * in-place write pattern that defeats a `setResource`-keyed resource observer is
154
+ * handled for free.
155
+ *
156
+ * `fields` is explicit: a `ComponentDef` is a branded number at runtime (its schema
157
+ * is a phantom type), so the field names must be passed — same contract as
158
+ * `syncFieldsToMap`. Project a subset to channel only what the UI reads.
159
+ *
160
+ * A `reactiveStruct` has a fixed field set and no `delete`, so the map adapter's
161
+ * delete-on-disable (#677 / ADR-0023) has no struct analog: `onRemove`/`onDisable`
162
+ * **reset the fields to the channel's declared initial values** (`onSet` skips
163
+ * disabled entities), `onEnable` republishes. The defaults are the channel's own
164
+ * initials — the eager `into` struct's declared values (e.g. `NetStats.latency = -1`),
165
+ * or the zeros of a freshly-created one — captured (untracked) at registration, NOT a
166
+ * blind 0. A singleton is rarely despawned; this keeps the channel well-defined if it is.
167
+ */
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<{
169
+ [K in F[number]]: number;
170
+ }>): SingletonStructSync<{
171
+ [K in F[number]]: number;
172
+ }>;
173
+ export interface SingletonArraySync<T> {
174
+ /** The live ordered channel: a reader of slot `i` subscribes to that slot alone. */
175
+ readonly array: ReactiveArray<T>;
176
+ /** Unregister the observer and stop publishing. Safe to call more than once. */
177
+ dispose(): void;
178
+ }
179
+ export interface SingletonArraySyncOptions<T> {
180
+ access?: Partial<SystemAccessDeclaration>;
181
+ seedExisting?: boolean;
182
+ /** Drive a PRE-CREATED `reactiveArray` (the module-scope channel) instead of a fresh one. */
183
+ into?: ReactiveArray<T>;
184
+ /** Per-slot equality for the array's no-op skip (default `Object.is`). */
185
+ eq?: (a: T, b: T) => boolean;
186
+ }
187
+ /**
188
+ * Sync ONE singleton entity's component into a `reactiveArray`, one slot per field
189
+ * in `fields` order (#685 / ADR-0024) — the ORDERED sibling of `syncSingletonToStruct`,
190
+ * for positional UI state (the army slots). On every change to the component it
191
+ * `reconcile`s the array from the field values: a slot whose value is unchanged keeps
192
+ * its reference (structural sharing), only changed slots wake. `onRemove`/`onDisable`
193
+ * reset the slots to the channel's DECLARED initial slots (a reactiveArray has no row
194
+ * delete) — captured (untracked) at registration, so the army channel resets to its
195
+ * empty sentinel (`EMPTY_SLOT`), NOT a blind 0 that would read as unit type 0.
196
+ *
197
+ * `fields` is explicit (a `ComponentDef` is a branded number at runtime). Pair with
198
+ * `@oasys/oecs/solid`'s `fromKernelArray` + a Solid `<Index>`.
199
+ */
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>;
201
+ /**
202
+ * Advance the world one tick with every bridge publish coalesced into a single UI
203
+ * flush. onAdd/onRemove fire mid-tick at flush boundaries and onSet fires at the
204
+ * tick tail; wrapping the whole update in `batch` defers the effect flush until the
205
+ * tick completes, so a frame that touched K entities (across any number of syncs on
206
+ * this world) wakes its readers once, not once per observer dispatch point.
207
+ * Equivalent to `batch(() => world.update(dt))`.
208
+ */
209
+ export declare function batchedUpdate(world: ECS, dt: number): void;
210
+ //# sourceMappingURL=ecs_sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ecs_sync.d.ts","sourceRoot":"","sources":["../../../src/extensions/reactive/ecs_sync.ts"],"names":[],"mappings":"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"}