@adobe/ecs 0.2.1 → 0.2.3

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 (213) hide show
  1. package/README.md +2 -2
  2. package/dist/implementation/compiler/describe.d.ts +1 -1
  3. package/dist/implementation/compiler/describe.js +3 -3
  4. package/dist/implementation/compiler/describe.js.map +1 -1
  5. package/dist/implementation/compiler/signature.js +1 -1
  6. package/dist/implementation/compiler/transform.js +3 -3
  7. package/dist/implementation/compiler/transform.js.map +1 -1
  8. package/dist/implementation/world/commands.js +50 -1
  9. package/dist/implementation/world/commands.js.map +1 -1
  10. package/dist/implementation/world/entities.d.ts +2 -0
  11. package/dist/implementation/world/entities.js +6 -0
  12. package/dist/implementation/world/entities.js.map +1 -1
  13. package/dist/implementation/world/join.js +10 -2
  14. package/dist/implementation/world/join.js.map +1 -1
  15. package/dist/implementation/world/register.js +2 -2
  16. package/dist/implementation/world/rows.d.ts +1 -1
  17. package/dist/implementation/world/rows.js +5 -0
  18. package/dist/implementation/world/rows.js.map +1 -1
  19. package/dist/implementation/world/runner.js +3 -3
  20. package/dist/implementation/world/runner.js.map +1 -1
  21. package/dist/implementation/world/sharing.js +1 -1
  22. package/dist/implementation/world/staging.d.ts +4 -0
  23. package/dist/implementation/world/staging.js +15 -3
  24. package/dist/implementation/world/staging.js.map +1 -1
  25. package/dist/implementation/world/tables-read.d.ts +7 -0
  26. package/dist/implementation/world/tables-read.js +60 -0
  27. package/dist/implementation/world/tables-read.js.map +1 -0
  28. package/dist/implementation/world/tables.d.ts +9 -0
  29. package/dist/implementation/world/tables.js +355 -0
  30. package/dist/implementation/world/tables.js.map +1 -0
  31. package/dist/implementation/world/thread-observer.js +1 -1
  32. package/dist/implementation/world/thread-observer.js.map +1 -1
  33. package/dist/implementation/world/thread-systems.js +1 -0
  34. package/dist/implementation/world/thread-systems.js.map +1 -1
  35. package/dist/index.d.ts +8 -28
  36. package/dist/index.js +4 -9
  37. package/dist/index.js.map +1 -1
  38. package/dist/interface/component/declarations.d.ts +4 -0
  39. package/dist/interface/component/declarations.js +8 -0
  40. package/dist/interface/component/declarations.js.map +1 -0
  41. package/dist/interface/component/declared.d.ts +12 -0
  42. package/dist/interface/component/declared.js +2 -0
  43. package/dist/interface/component/declared.js.map +1 -0
  44. package/dist/interface/component/index.d.ts +2 -0
  45. package/dist/interface/component/index.js +1 -1
  46. package/dist/interface/component/index.js.map +1 -1
  47. package/dist/interface/registry/names.d.ts +2 -0
  48. package/dist/interface/registry/names.js +9 -0
  49. package/dist/interface/registry/names.js.map +1 -1
  50. package/dist/interface/registry/schedule.d.ts +3 -3
  51. package/dist/interface/registry/schedule.js +3 -3
  52. package/dist/interface/registry/schedule.js.map +1 -1
  53. package/dist/interface/resource/declarations.d.ts +4 -0
  54. package/dist/interface/resource/declarations.js +8 -0
  55. package/dist/interface/resource/declarations.js.map +1 -0
  56. package/dist/interface/resource/index.d.ts +2 -0
  57. package/dist/interface/resource/index.js +1 -1
  58. package/dist/interface/resource/index.js.map +1 -1
  59. package/dist/interface/system/body.d.ts +11 -6
  60. package/dist/interface/system/check.d.ts +1 -1
  61. package/dist/interface/system/check.js +25 -9
  62. package/dist/interface/system/check.js.map +1 -1
  63. package/dist/interface/system/commands.d.ts +16 -0
  64. package/dist/interface/system/create.d.ts +21 -9
  65. package/dist/interface/system/create.js +3 -3
  66. package/dist/interface/system/create.js.map +1 -1
  67. package/dist/interface/system/declaration.d.ts +9 -2
  68. package/dist/interface/system/tables.d.ts +20 -0
  69. package/dist/interface/system/tables.js +2 -0
  70. package/dist/interface/system/tables.js.map +1 -0
  71. package/dist/interface/world/index.d.ts +1 -1
  72. package/dist/interface/world/table/index.d.ts +26 -0
  73. package/dist/interface/world/table/index.js +3 -0
  74. package/dist/interface/world/table/index.js.map +1 -0
  75. package/dist/interface/world/table/table.d.ts +11 -0
  76. package/dist/interface/world/table/table.js +2 -0
  77. package/dist/interface/world/table/table.js.map +1 -0
  78. package/dist/interface/world/world.d.ts +10 -10
  79. package/dist/world.d.ts +27 -0
  80. package/dist/world.js +27 -0
  81. package/dist/world.js.map +1 -0
  82. package/package.json +1 -1
  83. package/src/exports.test.ts +1 -1
  84. package/src/guide/00-keys.ts +1 -1
  85. package/src/guide/01-resources.md +1 -1
  86. package/src/guide/01-resources.ts +1 -1
  87. package/src/guide/02-entities.ts +2 -2
  88. package/src/guide/03-systems.md +2 -2
  89. package/src/guide/03-systems.out +1 -1
  90. package/src/guide/03-systems.ts +3 -3
  91. package/src/guide/04-ticks.md +1 -1
  92. package/src/guide/04-ticks.ts +3 -3
  93. package/src/guide/05-scheduling.ts +1 -1
  94. package/src/guide/06-sync-points.md +9 -2
  95. package/src/guide/06-sync-points.ts +2 -2
  96. package/src/guide/07-change-filters.ts +2 -2
  97. package/src/guide/08-input.ts +1 -1
  98. package/src/guide/09-rules.ts +1 -1
  99. package/src/guide/11-traits.ts +3 -3
  100. package/src/guide/12-lookups.ts +2 -2
  101. package/src/guide/13-indexes.ts +4 -4
  102. package/src/guide/14-kernels.ts +1 -1
  103. package/src/guide/15-reductions.ts +1 -1
  104. package/src/guide/16-gpu.ts +1 -1
  105. package/src/guide/17-hierarchies.ts +1 -1
  106. package/src/guide/18-identity.out +1 -1
  107. package/src/guide/19-threads.md +4 -2
  108. package/src/guide/19-threads.ts +2 -2
  109. package/src/guide/20-shared.ts +3 -3
  110. package/src/guide/21-dom.md +2 -2
  111. package/src/guide/21-dom.ts +1 -1
  112. package/src/guide/22-canvas.md +1 -1
  113. package/src/guide/22-canvas.ts +1 -1
  114. package/src/guide/CLAUDE.md +1 -1
  115. package/src/guide/guide.test.ts +7 -5
  116. package/src/implementation/compiler/describe.test-helper.ts +1 -1
  117. package/src/implementation/compiler/describe.test.ts +1 -1
  118. package/src/implementation/compiler/describe.ts +3 -3
  119. package/src/implementation/compiler/hygiene.test.ts +1 -1
  120. package/src/implementation/compiler/plugin.test.ts +2 -2
  121. package/src/implementation/compiler/signature.ts +1 -1
  122. package/src/implementation/compiler/transform.test.ts +3 -3
  123. package/src/implementation/compiler/transform.ts +3 -3
  124. package/src/implementation/world/change-blocks.test.ts +1 -1
  125. package/src/implementation/world/commands.ts +44 -1
  126. package/src/implementation/world/defaults.test.ts +1 -1
  127. package/src/implementation/world/entities.ts +6 -0
  128. package/src/implementation/world/finalizers.test.ts +1 -1
  129. package/src/implementation/world/index-changed.test.ts +2 -2
  130. package/src/implementation/world/index-joined.test.ts +5 -5
  131. package/src/implementation/world/index-order.test.ts +1 -1
  132. package/src/implementation/world/join.test.ts +4 -4
  133. package/src/implementation/world/join.ts +10 -2
  134. package/src/implementation/world/joined-observer.test.ts +1 -1
  135. package/src/implementation/world/joining-worker.fixture.ts +1 -1
  136. package/src/implementation/world/joining.fixture.ts +1 -1
  137. package/src/implementation/world/joining.test-helper.ts +1 -1
  138. package/src/implementation/world/kernel-spawns.test.ts +1 -1
  139. package/src/implementation/world/observe-factories.test.ts +2 -2
  140. package/src/implementation/world/observe-ticks-cases.test.ts +1 -1
  141. package/src/implementation/world/observe.test-helper.ts +1 -1
  142. package/src/implementation/world/observer-contained-worker.fixture.ts +1 -1
  143. package/src/implementation/world/observer-contained.test-helper.ts +1 -1
  144. package/src/implementation/world/observer-contained.test.ts +2 -2
  145. package/src/implementation/world/observer-joined.test-helper.ts +4 -4
  146. package/src/implementation/world/observer-joined.test.ts +23 -15
  147. package/src/implementation/world/observer.browser.test.ts +2 -2
  148. package/src/implementation/world/observer.test.ts +8 -8
  149. package/src/implementation/world/optional-workers.fixture.ts +1 -1
  150. package/src/implementation/world/optional.test.ts +7 -7
  151. package/src/implementation/world/reclaim.test.ts +3 -3
  152. package/src/implementation/world/register.ts +2 -2
  153. package/src/implementation/world/remote-objects.test-helper.ts +1 -1
  154. package/src/implementation/world/rows.ts +5 -1
  155. package/src/implementation/world/run-results.test.ts +10 -10
  156. package/src/implementation/world/runner.ts +3 -3
  157. package/src/implementation/world/shared-gpu.browser.test.ts +2 -2
  158. package/src/implementation/world/shared-kernels.test.ts +2 -2
  159. package/src/implementation/world/shared-workers.fixture.ts +2 -2
  160. package/src/implementation/world/shared.test.ts +8 -8
  161. package/src/implementation/world/sharing.ts +1 -1
  162. package/src/implementation/world/staging.ts +16 -3
  163. package/src/implementation/world/tables-read.ts +59 -0
  164. package/src/implementation/world/tables.test.ts +267 -0
  165. package/src/implementation/world/tables.ts +330 -0
  166. package/src/implementation/world/tails.test.ts +1 -1
  167. package/src/implementation/world/target.test.ts +2 -2
  168. package/src/implementation/world/thread-observer.ts +1 -1
  169. package/src/implementation/world/thread-systems.test-helper.ts +3 -3
  170. package/src/implementation/world/thread-systems.ts +1 -0
  171. package/src/implementation/world/where.test.ts +4 -4
  172. package/src/implementation/world/workers.fixture.ts +2 -2
  173. package/src/index.ts +4 -29
  174. package/src/interface/component/declarations.ts +11 -0
  175. package/src/interface/component/declared.ts +9 -0
  176. package/src/interface/component/index.ts +2 -0
  177. package/src/interface/index.type-test.ts +2 -2
  178. package/src/interface/indexes/index.type-test.ts +1 -1
  179. package/src/interface/registry/names.ts +8 -0
  180. package/src/interface/registry/schedule.ts +4 -4
  181. package/src/interface/resource/declarations.ts +11 -0
  182. package/src/interface/resource/index.ts +2 -0
  183. package/src/interface/system/body.ts +7 -5
  184. package/src/interface/system/check.ts +22 -8
  185. package/src/interface/system/commands.ts +14 -0
  186. package/src/interface/system/create.test.ts +13 -13
  187. package/src/interface/system/create.ts +15 -10
  188. package/src/interface/system/declaration.ts +9 -2
  189. package/src/interface/system/lists.type-test.ts +1 -1
  190. package/src/interface/system/optional.type-test.ts +1 -1
  191. package/src/interface/system/shared.type-test.ts +1 -1
  192. package/src/interface/system/system.type-test.ts +26 -20
  193. package/src/interface/system/tables.ts +21 -0
  194. package/src/interface/system/up.type-test.ts +3 -3
  195. package/src/interface/world/index.ts +2 -2
  196. package/src/interface/world/run.type-test.ts +3 -3
  197. package/src/interface/world/table/index.ts +19 -0
  198. package/src/interface/world/table/table.ts +12 -0
  199. package/src/interface/world/world.ts +10 -10
  200. package/src/observe/entity.ts +2 -2
  201. package/src/observe/resource.ts +2 -2
  202. package/src/observe/select.ts +4 -4
  203. package/src/observe/world-state.ts +3 -3
  204. package/src/perftest/benchmarks.ts +1 -1
  205. package/src/perftest/indexes.ts +3 -3
  206. package/src/perftest/loop.ts +1 -1
  207. package/src/perftest/observer.ts +4 -4
  208. package/src/perftest/reads.ts +2 -2
  209. package/src/perftest/remote.ts +5 -5
  210. package/src/perftest/strings.ts +1 -1
  211. package/src/perftest/suite.ts +1 -1
  212. package/src/world.test.ts +2 -2
  213. package/src/world.ts +32 -0
@@ -0,0 +1,27 @@
1
+ import type { Create } from './interface/world/create.js';
2
+ import type { Join } from './interface/world/join.js';
3
+ export type { Table } from './interface/world/table/table.js';
4
+ /**
5
+ * Creates a world on this thread, synchronously, with resources starting at the given values in place of their
6
+ * defaults. This thread is its `"world"` thread for the world's life: it runs the sync points, the ticks and the GPU
7
+ * device, and fills every logical thread no joined thread claims. Keys are the resources' symbols, built-in ones
8
+ * included (`{ [workers]: 0 }`); string keys are never a resource's, so they're ignored. Throws for a symbol that isn't
9
+ * a resource, or is one only the world writes. While a declaration's trait or default doesn't hold, each call fails to
10
+ * compile, once, naming each such key, its members and what's wrong with them.
11
+ * @function
12
+ */
13
+ export declare const create: Create;
14
+ /**
15
+ * Joins, from another thread, a world that `share` let join through the other end of `port`, claiming the logical
16
+ * `threads` this thread fills: their objects live here, and the systems and finalizers reading them run here. Resolves,
17
+ * once the world accepted the claim, to the same world: its `run` runs systems there, and its `add` and `remove` take
18
+ * readonly systems, which run here after the world's sync points. Add them before awaiting anything else, so the
19
+ * world's `share` resolves with them in place. Its `dispose` disconnects this thread. Rejects with an `Error` saying
20
+ * why it didn't join: the world refused the claim, was disposed, or its thread ended first, as the platform reports it:
21
+ * the world's `Worker` failing to start or throwing first, a port closed, as in node, or, in a browser, the world's
22
+ * thread ending once it was shared through a port this thread listened on. A browser world's worker that the page ended
23
+ * before this thread heard from it isn't reported, so the join stays pending. Rejects too where memory can't be shared,
24
+ * as without cross-origin isolation.
25
+ * @function
26
+ */
27
+ export declare const join: Join;
package/dist/world.js ADDED
@@ -0,0 +1,27 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ import { create as createWorld, join as joinWorld } from './implementation/index.js';
3
+ /**
4
+ * Creates a world on this thread, synchronously, with resources starting at the given values in place of their
5
+ * defaults. This thread is its `"world"` thread for the world's life: it runs the sync points, the ticks and the GPU
6
+ * device, and fills every logical thread no joined thread claims. Keys are the resources' symbols, built-in ones
7
+ * included (`{ [workers]: 0 }`); string keys are never a resource's, so they're ignored. Throws for a symbol that isn't
8
+ * a resource, or is one only the world writes. While a declaration's trait or default doesn't hold, each call fails to
9
+ * compile, once, naming each such key, its members and what's wrong with them.
10
+ * @function
11
+ */
12
+ export const create = createWorld;
13
+ /**
14
+ * Joins, from another thread, a world that `share` let join through the other end of `port`, claiming the logical
15
+ * `threads` this thread fills: their objects live here, and the systems and finalizers reading them run here. Resolves,
16
+ * once the world accepted the claim, to the same world: its `run` runs systems there, and its `add` and `remove` take
17
+ * readonly systems, which run here after the world's sync points. Add them before awaiting anything else, so the
18
+ * world's `share` resolves with them in place. Its `dispose` disconnects this thread. Rejects with an `Error` saying
19
+ * why it didn't join: the world refused the claim, was disposed, or its thread ended first, as the platform reports it:
20
+ * the world's `Worker` failing to start or throwing first, a port closed, as in node, or, in a browser, the world's
21
+ * thread ending once it was shared through a port this thread listened on. A browser world's worker that the page ended
22
+ * before this thread heard from it isn't reported, so the join stays pending. Rejects too where memory can't be shared,
23
+ * as without cross-origin isolation.
24
+ * @function
25
+ */
26
+ export const join = joinWorld;
27
+ //# sourceMappingURL=world.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"world.js","sourceRoot":"","sources":["../src/world.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,OAAO,EAAE,MAAM,IAAI,WAAW,EAAE,IAAI,IAAI,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAMrF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,MAAM,GAAW,WAAW,CAAC;AAE1C;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,IAAI,GAAS,SAAS,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/ecs",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Adobe ECS",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -50,7 +50,7 @@ export const world: World = World.create({ [workers]: 0 });
50
50
  // Another thread joins it through the other end of a channel the world shares.
51
51
  declare const port: MessagePort;
52
52
  export const joined: Promise<World> = World.join(port, { threads: ["main"] });
53
- export const read = System.create({ observer: true, components: { with: [position] }, lookup: { at: { with: [position] } } }, () => {});
53
+ export const read = System.create({ readonly: true, components: { with: [position] }, lookup: { at: { with: [position] } } }, () => {});
54
54
  export const added = world.add(read);
55
55
  `;
56
56
 
@@ -23,7 +23,7 @@ const award = System.create({ components: { with: [score] } }, (row, { resources
23
23
  row[score] += resources[bonus];
24
24
  });
25
25
 
26
- const read = System.create({ observer: true, components: { with: [score] } }, row => {
26
+ const read = System.create({ readonly: true, components: { with: [score] } }, row => {
27
27
  console.log("score", row[score]);
28
28
  });
29
29
 
@@ -17,6 +17,6 @@ that isn't data, such as an `AudioContext`, is stored as it is; a change inside
17
17
  written back. An `Entity` resource with no entity holds `Entity.none`, which is `0` rather than `null`, so it stays data
18
18
  every thread can read.
19
19
 
20
- A system reads resources by key. With `observer: true` it only reads, and runs after the world's changes apply.
20
+ A system reads resources by key. With `readonly: true` it only reads, and runs after the world's changes apply.
21
21
  [`run`](01-resources.ts#L31-L32) runs a system once, and resolves to what the body returns, for a system without
22
22
  components.
@@ -22,7 +22,7 @@ declare module "@adobe/ecs" {
22
22
  }
23
23
  }
24
24
 
25
- const read = System.create({ observer: true }, ({ resources }) =>
25
+ const read = System.create({ readonly: true }, ({ resources }) =>
26
26
  [resources[gravity], resources[floor], resources[names], resources[audio], resources[focused]]);
27
27
 
28
28
  export const main = async (): Promise<void> => {
@@ -25,10 +25,10 @@ export const defeat = System.create({ components: { with: [enemy] } }, (row, { c
25
25
  commands.remove(row.entity, enemy);
26
26
  });
27
27
 
28
- const readFoes = System.create({ observer: true, components: { with: [position, enemy] } }, row => {
28
+ const readFoes = System.create({ readonly: true, components: { with: [position, enemy] } }, row => {
29
29
  console.log("a foe at", row[position]);
30
30
  });
31
- const readParticles = System.create({ observer: true, components: { with: [...Particle] } }, row => {
31
+ const readParticles = System.create({ readonly: true, components: { with: [...Particle] } }, row => {
32
32
  console.log("a particle moving at", row[velocity]);
33
33
  });
34
34
 
@@ -8,8 +8,8 @@ it in parallel.
8
8
  component in `with` and none in `not`, so the `frozen` one stays put. Values are replaced whole, never edited in
9
9
  place.
10
10
  - **Systems without components**, such as [`count`](03-systems.ts#L35-L37), run once per run.
11
- - **Observer systems**, `observer: true`, only read. They see what the runs before them left. Run one to read the
12
- world, or add one to react to it.
11
+ - **Readonly systems**, `readonly: true`, only read. Run one to read the world, as [`read`](03-systems.ts#L40-L42)
12
+ does, or add one to react to it. [Sync points](06-sync-points.md) says when and where they run.
13
13
  - **Optional components**, `optional: [velocity]`, are in the row where the entity has them and `undefined` where it
14
14
  doesn't. They never narrow which entities a system runs on; [`readSpeeds`](03-systems.ts#L44-L47) still reads the
15
15
  entity without a velocity. Only commands add and remove them.
@@ -1,5 +1,5 @@
1
1
  at [ 0, 1, 5 ]
2
2
  moving [ 'at 2', 'at 2', 'still' ]
3
3
  frames 1
4
- Error: System "fail" (@adobe/ecs@0.2.1/src/guide/03-systems.ts#fail) failed: broken
4
+ Error: System "fail" (@adobe/ecs@0.2.3/src/guide/03-systems.ts#fail) failed: broken
5
5
  frames 2
@@ -37,16 +37,16 @@ const count = System.create({}, ({ resources }) => {
37
37
  });
38
38
 
39
39
  const places: number[] = [];
40
- const read = System.create({ observer: true, components: { with: [position] } }, row => {
40
+ const read = System.create({ readonly: true, components: { with: [position] } }, row => {
41
41
  places.push(row[position][0]);
42
42
  });
43
43
 
44
44
  const speeds: string[] = [];
45
- const readSpeeds = System.create({ observer: true, components: { with: [position], optional: [velocity] } }, row => {
45
+ const readSpeeds = System.create({ readonly: true, components: { with: [position], optional: [velocity] } }, row => {
46
46
  speeds.push(row[velocity] === undefined ? "still" : `at ${row[velocity][0]}`);
47
47
  });
48
48
 
49
- const frameCount = System.create({ observer: true }, ({ resources }) => resources[frames]);
49
+ const frameCount = System.create({ readonly: true }, ({ resources }) => resources[frames]);
50
50
 
51
51
  const fail = System.create(() => {
52
52
  throw new Error("broken");
@@ -9,7 +9,7 @@ every tick from then on, and [`world.remove`](04-ticks.ts#L41) takes it off.
9
9
  `step` milliseconds, each with that `delta`.
10
10
  - A world ticks only after a run arrives, unless `always` is set, as for [the first world](04-ticks.ts#L36).
11
11
 
12
- An observer with `resources: { changed: [elapsed] }` runs after each tick that changed `elapsed`:
12
+ A readonly system with `resources: { changed: [elapsed] }` runs after each tick that changed `elapsed`:
13
13
  [`watch`](04-ticks.ts#L18-L21) resolves once 30 ms have passed. Once `advance` is removed, `elapsed` stays put.
14
14
 
15
15
  **Asking for a tick.** A system that needs another tick contributes a time to the `wake` reduction:
@@ -16,11 +16,11 @@ const advance = System.create({}, ({ resources }) => {
16
16
  });
17
17
 
18
18
  let reached: (elapsed: number) => void = () => {};
19
- const watch = System.create({ observer: true, resources: { changed: [elapsed] } }, ({ resources }) => {
19
+ const watch = System.create({ readonly: true, resources: { changed: [elapsed] } }, ({ resources }) => {
20
20
  if (resources[elapsed] >= 30) reached(resources[elapsed]);
21
21
  });
22
22
 
23
- const readTime = System.create({ observer: true }, ({ resources }) => ({ elapsed: resources[elapsed], delta: resources[clock].delta }));
23
+ const readTime = System.create({ readonly: true }, ({ resources }) => ({ elapsed: resources[elapsed], delta: resources[clock].delta }));
24
24
 
25
25
  const step = System.create({}, ({ resources, reduce }) => {
26
26
  resources[steps] += 1;
@@ -28,7 +28,7 @@ const step = System.create({}, ({ resources, reduce }) => {
28
28
  });
29
29
 
30
30
  let finished: (steps: number) => void = () => {};
31
- const watchSteps = System.create({ observer: true, resources: { changed: [steps] } }, ({ resources }) => {
31
+ const watchSteps = System.create({ readonly: true, resources: { changed: [steps] } }, ({ resources }) => {
32
32
  if (resources[steps] === 3) finished(resources[steps]);
33
33
  });
34
34
 
@@ -20,7 +20,7 @@ const heal = System.create({ components: { with: [health] }, after: [hit] }, row
20
20
  });
21
21
  const frame = System.create([heal, hit]);
22
22
 
23
- const read = System.create({ observer: true, components: { with: [health] } }, row => {
23
+ const read = System.create({ readonly: true, components: { with: [health] } }, row => {
24
24
  console.log("health", row[health]);
25
25
  });
26
26
 
@@ -10,5 +10,12 @@ A run ends at a *sync point*, where its commands apply.
10
10
  A composite is one run with one sync point. In [`frame`](06-sync-points.ts#L25), `count` runs `after: [spawn]` but
11
11
  before the spawned entities exist, so it counts none; run alone, after the sync point, it counts the hero.
12
12
 
13
- An observer added to a world runs after the sync point of every run and tick, reading what it left:
14
- [`watch`](06-sync-points.ts#L39-L41) logs the hero after the run that spawned it.
13
+ **Readonly systems.** `readonly: true` places a system after the sync point, so it reads what the commands did. Added
14
+ to a world, [`watch`](06-sync-points.ts#L27-L29) runs after the sync point of every run and tick, and logs the hero
15
+ after the run that spawned it; `count`, which isn't readonly, runs before the sync point, in its tier.
16
+
17
+ - **Where:** a readonly system runs on the thread that added or ran it, so a [joined thread](19-threads.md) adds only
18
+ readonly systems.
19
+ - **Order:** none. It can't declare `before`, `after` or `during`.
20
+ - **Limits:** it can't write, use `commands` or `reduce`, or take input. Its row and resources are read-only, so each
21
+ is a type error.
@@ -24,10 +24,10 @@ const count = System.create({ components: { with: [health] }, after: [spawn] },
24
24
  });
25
25
  const frame = System.create([spawn, count]);
26
26
 
27
- const watch = System.create({ observer: true, components: { with: [health] } }, row => {
27
+ const watch = System.create({ readonly: true, components: { with: [health] } }, row => {
28
28
  console.log("health", row[health]);
29
29
  });
30
- const readSeen = System.create({ observer: true }, ({ resources }) => resources[seen]);
30
+ const readSeen = System.create({ readonly: true }, ({ resources }) => resources[seen]);
31
31
 
32
32
  export const main = async (): Promise<void> => {
33
33
  await using world = World.create();
@@ -36,7 +36,7 @@ const bump = System.create({ components: { with: [score, boss] } }, row => {
36
36
  });
37
37
 
38
38
  let loaded = 0;
39
- const countLoaded = System.create({ observer: true, components: { with: [shown] } }, () => {
39
+ const countLoaded = System.create({ readonly: true, components: { with: [shown] } }, () => {
40
40
  loaded += 1;
41
41
  });
42
42
 
@@ -45,7 +45,7 @@ const step = System.create({ resources: { is: [map], not: [paused] } }, ({ resou
45
45
  });
46
46
  const pause = System.create({}, ({ resources }) => { resources[paused] = true; });
47
47
  const open = System.create({}, ({ resources }) => { resources[map] = { width: 4 }; });
48
- const readSteps = System.create({ observer: true }, ({ resources }) => resources[steps]);
48
+ const readSteps = System.create({ readonly: true }, ({ resources }) => resources[steps]);
49
49
 
50
50
  export const main = async (): Promise<void> => {
51
51
  await using world = World.create();
@@ -30,7 +30,7 @@ const heal = System.create({
30
30
  });
31
31
 
32
32
  const healths: number[] = [];
33
- const read = System.create({ observer: true, components: { with: [health] } }, row => {
33
+ const read = System.create({ readonly: true, components: { with: [health] } }, row => {
34
34
  healths.push(row[health]);
35
35
  });
36
36
 
@@ -31,7 +31,7 @@ const hide = System.create({ components: { with: [sprite] } }, (row, { commands
31
31
  });
32
32
 
33
33
  const drawing: string[] = [];
34
- const read = System.create({ observer: true, components: { with: [layer, drawn], optional: [mesh, sprite] } }, row => {
34
+ const read = System.create({ readonly: true, components: { with: [layer, drawn], optional: [mesh, sprite] } }, row => {
35
35
  drawing.push(`${row[mesh] === undefined ? `sprite ${row[sprite]}` : `mesh ${row[mesh]}`} on layer ${row[layer]}`);
36
36
  });
37
37
  /** Logs what's drawn, in order. */
@@ -33,9 +33,9 @@ const destroy = System.create({ components: { with: [ship] } }, (row, { commands
33
33
  });
34
34
 
35
35
  const counts = { outlined: 0, targeting: 0, entities: 0 };
36
- const countOutlined = System.create({ observer: true, components: { with: [outline] } }, () => { counts.outlined += 1; });
37
- const countTargeting = System.create({ observer: true, components: { with: [target] } }, () => { counts.targeting += 1; });
38
- const countAll = System.create({ observer: true, components: {} }, () => { counts.entities += 1; });
36
+ const countOutlined = System.create({ readonly: true, components: { with: [outline] } }, () => { counts.outlined += 1; });
37
+ const countTargeting = System.create({ readonly: true, components: { with: [target] } }, () => { counts.targeting += 1; });
38
+ const countAll = System.create({ readonly: true, components: {} }, () => { counts.entities += 1; });
39
39
  const count = System.create([countOutlined, countTargeting, countAll]);
40
40
 
41
41
  export const main = async (): Promise<void> => {
@@ -25,7 +25,7 @@ const track = System.create({
25
25
 
26
26
  const refreshed: boolean[] = [];
27
27
  const watch = System.create({
28
- observer: true,
28
+ readonly: true,
29
29
  components: { with: [target] },
30
30
  lookup: { enemy: { with: [health], changed: [health] } },
31
31
  }, (row, { lookup }) => {
@@ -34,7 +34,7 @@ const watch = System.create({
34
34
  });
35
35
 
36
36
  const aims: number[] = [];
37
- const readAims = System.create({ observer: true, components: { with: [aim] } }, row => {
37
+ const readAims = System.create({ readonly: true, components: { with: [aim] } }, row => {
38
38
  aims.push(row[aim]);
39
39
  });
40
40
 
@@ -26,14 +26,14 @@ const count = System.create({ components: { with: [cell, crowded] }, lookup: { a
26
26
 
27
27
  const byScore = Index.create({ key: [score], sorted: true, components: { with: [player] } });
28
28
 
29
- const leaders = System.create({ observer: true, lookup: { scores: { index: byScore } } }, ({ lookup }) => {
29
+ const leaders = System.create({ readonly: true, lookup: { scores: { index: byScore } } }, ({ lookup }) => {
30
30
  const winners: string[] = [];
31
31
  const high = lookup.scores({ ">=": 90 });
32
32
  for (let i = 0; i < high.length; i++) winners.push(high.at(i)[player]);
33
33
  return winners;
34
34
  });
35
35
 
36
- const list = System.create({ observer: true, components: { with: [player, score] }, order: byScore }, row => {
36
+ const list = System.create({ readonly: true, components: { with: [player, score] }, order: byScore }, row => {
37
37
  console.log(row[player], row[score]);
38
38
  });
39
39
 
@@ -42,12 +42,12 @@ const find = System.create({ lookup: { byId: { index: byServerId } } }, ({ looku
42
42
  lookup.byId(input.id)?.[player] ?? null);
43
43
 
44
44
  const players = Index.create({ key: [], components: { with: [player] } });
45
- const countPlayers = System.create({ observer: true, lookup: { players: { index: players, changed: true } } }, ({ lookup }) => {
45
+ const countPlayers = System.create({ readonly: true, lookup: { players: { index: players, changed: true } } }, ({ lookup }) => {
46
46
  console.log("players", lookup.players().length);
47
47
  });
48
48
 
49
49
  const crowds: number[] = [];
50
- const readCrowds = System.create({ observer: true, components: { with: [crowded] } }, row => {
50
+ const readCrowds = System.create({ readonly: true, components: { with: [crowded] } }, row => {
51
51
  crowds.push(row[crowded]);
52
52
  });
53
53
 
@@ -29,7 +29,7 @@ const slow = System.create({ kernel: false, components: { with: [velocity] } },
29
29
  });
30
30
 
31
31
  const moved: string[] = [];
32
- const read = System.create({ observer: true, components: { with: [position, velocity] } }, row => {
32
+ const read = System.create({ readonly: true, components: { with: [position, velocity] } }, row => {
33
33
  moved.push(`at ${row[position][0]}, moving at ${row[velocity][0]}`);
34
34
  });
35
35
 
@@ -23,7 +23,7 @@ const weigh = System.create({ components: { with: [mass] } }, (row, { reduce })
23
23
  reduce[count](1);
24
24
  });
25
25
 
26
- const report = System.create({ observer: true }, ({ resources }) =>
26
+ const report = System.create({ readonly: true }, ({ resources }) =>
27
27
  `${resources[count]} weigh ${resources[total]}, at most ${resources[heaviest]}`);
28
28
 
29
29
  const spawn = System.create(({ commands }) => {
@@ -32,7 +32,7 @@ const draw = System.render({ shader: { accessors: { dotPosition: position, dotSi
32
32
  });
33
33
  const shaderCode = Gpu.wgsl(draw);
34
34
 
35
- const read = System.create({ observer: true, components: { with: [position] } }, row => {
35
+ const read = System.create({ readonly: true, components: { with: [position] } }, row => {
36
36
  console.log("at", row[position]);
37
37
  });
38
38
 
@@ -21,7 +21,7 @@ const place = System.create({ components: { with: [offset, placed] }, up: { thro
21
21
  });
22
22
 
23
23
  const where: string[] = [];
24
- const read = System.create({ observer: true, components: { with: [label, placed] } }, row => {
24
+ const read = System.create({ readonly: true, components: { with: [label, placed] } }, row => {
25
25
  where.push(`${row[label]} at ${row[placed]}`);
26
26
  });
27
27
 
@@ -1 +1 @@
1
- Error: System "explode" (@adobe/ecs@0.2.1/src/guide/18-identity.ts#explode) failed: boom
1
+ Error: System "explode" (@adobe/ecs@0.2.3/src/guide/18-identity.ts#explode) failed: boom
@@ -11,6 +11,8 @@ A *logical thread* is where objects live and the code touching them runs: the pa
11
11
  `World.join(port, { threads: ["audio"] })`, claiming the threads it fills. Usually the other end of the channel goes
12
12
  to a worker; [here both ends](19-threads.ts#L44-L54) are on one thread.
13
13
 
14
- - `share` resolves once the thread joined, with the observer systems it added as `join` resolved, before awaiting
15
- anything else. Those observers run there after the world's sync points: `listen` hears what `mix` wrote.
14
+ - `share` resolves once the thread joined, with the readonly systems it added as `join` resolved, before awaiting
15
+ anything else. Those readonly systems run there after the world's sync points: `listen` hears what `mix` wrote.
16
+ - A joined thread adds only [readonly systems](03-systems.md), which run on it; a system that writes is added on the
17
+ world's thread.
16
18
  - A joined world runs systems as the world does: [`audio.run`](19-threads.ts#L60-L61) spawns and mixes too.
@@ -22,7 +22,7 @@ const mix = System.create({ components: { with: [volume] } }, (row, { resources
22
22
  });
23
23
 
24
24
  let heard = 0;
25
- const listen = System.create({ observer: true }, ({ resources }) => {
25
+ const listen = System.create({ readonly: true }, ({ resources }) => {
26
26
  heard = resources[levels].size;
27
27
  });
28
28
 
@@ -31,7 +31,7 @@ const spawn = System.create(({ commands }) => {
31
31
  commands.spawn([volume])({ [volume]: 0.3 });
32
32
  });
33
33
 
34
- const countVoices = System.create({ observer: true }, ({ resources }) => resources[levels].size);
34
+ const countVoices = System.create({ readonly: true }, ({ resources }) => resources[levels].size);
35
35
 
36
36
  /** Waits a turn of the event loop at a time until `done`, for two seconds at most. */
37
37
  const until = async (done: () => boolean): Promise<void> => {
@@ -56,7 +56,7 @@ let drawn = 0;
56
56
  let left = Infinity;
57
57
  let right = -Infinity;
58
58
  const draw = System.create({
59
- observer: true,
59
+ readonly: true,
60
60
  components: { with: [position], where: { [cell]: (at, { resources }) => resources[camera]?.sees(at) === true } },
61
61
  }, row => {
62
62
  drawn += 1;
@@ -66,7 +66,7 @@ const draw = System.create({
66
66
 
67
67
  let refreshed = 0;
68
68
  const refresh = System.create({
69
- observer: true,
69
+ readonly: true,
70
70
  components: { with: [position], changed: [position], where: { [cell]: (at, { resources }) => resources[camera]?.sees(at) === true } },
71
71
  }, () => {
72
72
  refreshed += 1;
@@ -74,7 +74,7 @@ const refresh = System.create({
74
74
 
75
75
  // Only chunk [4, 0], which no unit is in until one crosses into it.
76
76
  let east = 0;
77
- const countEast = System.create({ observer: true, components: { with: [position], where: { [cell]: [4, 0] } } }, () => {
77
+ const countEast = System.create({ readonly: true, components: { with: [position], where: { [cell]: [4, 0] } } }, () => {
78
78
  east += 1;
79
79
  });
80
80
 
@@ -13,9 +13,9 @@ same.
13
13
  Each tick runs one composite:
14
14
 
15
15
  1. [`move`](21-dom.ts#L38-L44) moves every particle and turns it back from the walls.
16
- 2. [`create`](21-dom.ts#L46-L51) gives each particle without a dot an element, with `commands.add`. Observers can't
16
+ 2. [`create`](21-dom.ts#L46-L51) gives each particle without a dot an element, with `commands.add`. Readonly systems can't
17
17
  issue commands, so it ticks, and its condition `is: [stage]` waits for a stage.
18
- 3. [`draw`](21-dom.ts#L52-L60), an observer, sets each moved dot's `style.translate`, a transform, which the page
18
+ 3. [`draw`](21-dom.ts#L52-L60), a readonly system, sets each moved dot's `style.translate`, a transform, which the page
19
19
  doesn't lay out again. `changed: [position]` skips the dots that didn't move.
20
20
 
21
21
  [A finalizer](21-dom.ts#L61-L63) removes a particle's element when the particle goes, and every element when the
@@ -51,7 +51,7 @@ const create = System.create({ components: { with: [position], not: [dot] }, res
51
51
  });
52
52
  // Moves each dot whose particle moved, by a transform, which the page doesn't lay out again.
53
53
  const draw = System.create({
54
- observer: true,
54
+ readonly: true,
55
55
  components: { with: [position, dot], optional: [traced], changed: [position] },
56
56
  }, (row, { resources }) => {
57
57
  const [x, y] = row[position];
@@ -2,7 +2,7 @@
2
2
 
3
3
  The same particles, drawn in immediate mode: each frame, the canvas is cleared and drawn again whole.
4
4
 
5
- [`draw`](22-canvas.ts#L52-L80) is one observer without components, so it runs once per tick, not per particle.
5
+ [`draw`](22-canvas.ts#L52-L80) is one readonly system without components, so it runs once per tick, not per particle.
6
6
 
7
7
  - `changed: [clock]` runs it after every tick, and `is: [pen]` waits for a 2D context.
8
8
  - It reads every particle through [an index without a key](22-canvas.ts#L50-L51), `key: []`, and the traced one
@@ -51,7 +51,7 @@ const particles = Index.create({ key: [], components: { with: [position] } });
51
51
  const tracedOnes = Index.create({ key: [], components: { with: [position, traced] } });
52
52
  // Clears the canvas and draws every particle, after each tick.
53
53
  const draw = System.create({
54
- observer: true,
54
+ readonly: true,
55
55
  resources: { is: [pen], changed: [clock] },
56
56
  lookup: { particles: { index: particles }, traced: { index: tracedOnes } },
57
57
  }, ({ resources, lookup }) => {
@@ -45,5 +45,5 @@ it in `NN-name.type-test.ts`, under a `// @ts-expect-error` that the package's b
45
45
  before they're printed.
46
46
  - Keys are `Symbol()` with no description: the build's id names each by its binding.
47
47
  - Archetypes are types, so their names are PascalCase: `const Particle = [position, velocity] as const`.
48
- - Use the public API only, as a consumer imports it, not internal modules. Read a world through observer systems.
48
+ - Use the public API only, as a consumer imports it, not internal modules. Read a world through readonly systems.
49
49
  - Lines change as code does: the test of links fails on any that no longer covers whole code, so move them with it.
@@ -69,12 +69,12 @@ const listed = [...read("README.md").matchAll(/^(?:- |(\d+)\. )\[([^\]]+)\]\(([^
69
69
  /** What each chapter teaches, which its prose names as code and its program does; 0 is the unnumbered keys chapter's. */
70
70
  const teaches: { readonly [chapter: number]: readonly string[] } = {
71
71
  0: ["Symbol()", "declare module \"@adobe/ecs\"", "Components", "Resources", "row[score]", "resources[bonus]", "default"],
72
- 1: ["Resources", "default", "World.create", "observer: true", "run", "ReadonlyMap", "Entity"],
72
+ 1: ["Resources", "default", "World.create", "readonly: true", "run", "ReadonlyMap", "Entity"],
73
73
  2: ["Components", "type: true", "as const", "commands.spawn", "commands.remove", "with: [...Particle]"],
74
- 3: ["System.create", "with", "not", "observer: true", "optional: [velocity]"],
74
+ 3: ["System.create", "with", "not", "readonly: true", "optional: [velocity]"],
75
75
  4: ["clock", "delta", "cadence", "always", "resources: { changed: [elapsed] }", "wake", "reduce[wake]", "world.add", "world.remove"],
76
76
  5: ["after", "during", "System.create([heal, hit])", "during: [update]"],
77
- 6: ["commands.spawn", "commands.add", "commands.despawn", "after: [spawn]"],
77
+ 6: ["commands.spawn", "commands.add", "commands.despawn", "after: [spawn]", "readonly: true"],
78
78
  7: ["resources: { changed: [level] }", "changed: [score]", "resources: { is: [map], not: [paused] }", "resources[map]"],
79
79
  8: ["world.run(hit, { amount: 3 })", "target: \"entity\""],
80
80
  9: ["Rule.create", "derived: true"],
@@ -106,10 +106,12 @@ const mutations: { readonly [chapter: number]: readonly (readonly [string, strin
106
106
  ],
107
107
  1: [["World.create({ [gravity]: -1.6 })", "World.create()"], ["default: -9.8 ", ""], ["{ default: -Infinity }", "{}"]],
108
108
  2: [["commands.remove(row.entity, enemy);", ""], ["{ [position]: [0, 10, 0] }", "{}"]],
109
- 3: [["not: [frozen]", "not: []"], ["System.create([count, fail])", "System.create([fail])"]],
109
+ 3: [
110
+ ["not: [frozen]", "not: []"], ["System.create([count, fail])", "System.create([fail])"],
111
+ ],
110
112
  4: [["world.remove(advance);", ""]],
111
113
  5: [["after: [hit] ", ""], ["during: [update] ", ""]],
112
- 6: [["commands.add(hero, health, 5);", ""], ["commands.despawn(commands.spawn([health])({ [health]: 1 }));", "commands.spawn([health])({ [health]: 1 });"], ["watched.add(watch);", ""]],
114
+ 6: [["commands.add(hero, health, 5);", ""], ["commands.despawn(commands.spawn([health])({ [health]: 1 }));", "commands.spawn([health])({ [health]: 1 });"], ["watched.add(watch);", ""], ["const watch = System.create({ readonly: true, ", "const watch = System.create({ "]],
113
115
  7: [["resources: { changed: [level] } ", ""], [", changed: [score]", ""], [", not: [paused]", ""], ["is: [map], ", ""]],
114
116
  8: [["target: \"entity\",", ""]],
115
117
  9: [["with: [mesh], not: [hidden]", "with: [mesh]"], ["Rule.create({ components: { with: [sprite], not: [hidden] }, derive: [drawn] });", ""], ["Rule.create({ components: { with: [drawn] }, derive: [layer] });", ""]],
@@ -51,7 +51,7 @@ export const spawn = System.create(({ commands }) => {
51
51
  commands.spawn([position, velocity])({ [position]: [0, 0, 0], [velocity]: [1, 0, 0] });
52
52
  });
53
53
 
54
- export const observe = System.create({ observer: true, components: { with: [position] } }, row => {
54
+ export const observe = System.create({ readonly: true, components: { with: [position] } }, row => {
55
55
  console.log(row[position]);
56
56
  });
57
57
 
@@ -109,7 +109,7 @@ describe("a module's systems described", () => {
109
109
  it("says why systems that run as written have no code: per-run systems, observers, render systems, kernel: false and one declared in a function", () => {
110
110
  expect(described("spawn")).toMatchObject({ kind: "per-run", runs: `Its body as written, on the "world" thread` });
111
111
  for (const backend of ["js", "wasm", "wgsl"] as const) expect(reasonOf(described("spawn")[backend])).toBe("per-run systems run as written; only row systems compile");
112
- expect(described("observe")).toMatchObject({ kind: "observer", js: { kind: "none", reason: expect.stringMatching(/^an observer runs as written/) } });
112
+ expect(described("observe")).toMatchObject({ kind: "readonly", js: { kind: "none", reason: expect.stringMatching(/^a readonly system runs as written/) } });
113
113
  expect(described("draw")).toMatchObject({ kind: "render", runs: expect.stringContaining("while the world has a GPU device"), wgsl: { kind: "none", reason: expect.stringMatching(/^a render system runs as written/) } });
114
114
  const unflagged = described("unflagged");
115
115
  expect(unflagged).toMatchObject({ kernel: false, js: { kind: "none", reason: "as written, because `kernel: false`" }, wasm: { kind: "none", reason: "as written, because `kernel: false`" }, wgsl: { kind: "eligible" } });
@@ -30,7 +30,7 @@ export type Backend =
30
30
  * What runs a system: over every matched row, once per run, as an observer, on one entity its input names (`target`),
31
31
  * or as a render system.
32
32
  */
33
- export type SystemKind = "row" | "per-run" | "observer" | "target" | "render";
33
+ export type SystemKind = "row" | "per-run" | "readonly" | "target" | "render";
34
34
 
35
35
  /**
36
36
  * A system of a module as the build compiles it: its call's offsets in the code transformed, the binding it's
@@ -427,7 +427,7 @@ export const describeSystems = async (sites: TransformedSites, format: (code: st
427
427
  const kernel: boolean | "gpu" | null = literal === true || literal === false ? literal : literal === "gpu" ? "gpu" : null;
428
428
  const kind: SystemKind = created === "render" ? "render"
429
429
  : declaration === undefined ? "per-run"
430
- : propertyOf(declaration, "observer") !== null ? "observer"
430
+ : propertyOf(declaration, "readonly") !== null ? "readonly"
431
431
  : propertyOf(declaration, "target") !== null ? "target"
432
432
  : propertyOf(declaration, "components") !== null ? "row" : "per-run";
433
433
  const base = { start: node.start, end: node.end, binding: bindings.get(node) ?? null, kind, kernel };
@@ -442,7 +442,7 @@ export const describeSystems = async (sites: TransformedSites, format: (code: st
442
442
  ? threadOf(declaration, body, kind === "render" ? positionsOf("render", declaration) : positionsOf("system", declaration)) : "world";
443
443
  if (kind === "render") return asWritten("a render system runs as written, recording GPU work with shaders of your own; `Gpu.wgsl` gives them their accessors", "world");
444
444
  if (kind === "per-run") return asWritten("per-run systems run as written; only row systems compile", written());
445
- if (kind === "observer") return asWritten("an observer runs as written, reading the settled state after the run's last sync point; only row systems that aren't observers compile", written());
445
+ if (kind === "readonly") return asWritten("a readonly system runs as written, reading the settled state after the run's last sync point; only row systems that aren't readonly compile", written());
446
446
  if (kind === "target") return asWritten("a system with `target` runs as written, once, on the entity its input names; only systems over every matched row compile", written());
447
447
  if (declaration === undefined || body === undefined || body.type === "SpreadElement" || !isFunction(body)) {
448
448
  return asWritten(declaration === undefined ? "its declaration isn't written in its call, so the build can't read whether it's a kernel" : "its body isn't a function written in place or bound at module scope, so the build can't read it", "world");
@@ -34,7 +34,7 @@ const spawn = System.create(({ commands }) => {
34
34
  });
35
35
 
36
36
  const read: number[][] = [];
37
- const readAll = System.create({ observer: true, components: { with: [p, c, l, s, e, v0, v1] } }, row => {
37
+ const readAll = System.create({ readonly: true, components: { with: [p, c, l, s, e, v0, v1] } }, row => {
38
38
  read.push([row[p], row[c], row[l], row[s], row[e], row[v0], row[v1]]);
39
39
  });
40
40