@adobe/ecs 0.2.1 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (330) hide show
  1. package/README.md +2 -2
  2. package/dist/implementation/compiler/body-access.d.ts +18 -1
  3. package/dist/implementation/compiler/body-access.js +104 -15
  4. package/dist/implementation/compiler/body-access.js.map +1 -1
  5. package/dist/implementation/compiler/describe.d.ts +1 -1
  6. package/dist/implementation/compiler/describe.js +3 -3
  7. package/dist/implementation/compiler/describe.js.map +1 -1
  8. package/dist/implementation/compiler/estree.d.ts +5 -2
  9. package/dist/implementation/compiler/estree.js +8 -5
  10. package/dist/implementation/compiler/estree.js.map +1 -1
  11. package/dist/implementation/compiler/inferred.js +4 -2
  12. package/dist/implementation/compiler/inferred.js.map +1 -1
  13. package/dist/implementation/compiler/signature.js +1 -1
  14. package/dist/implementation/compiler/transform.js +6 -5
  15. package/dist/implementation/compiler/transform.js.map +1 -1
  16. package/dist/implementation/world/activate.js +14 -2
  17. package/dist/implementation/world/activate.js.map +1 -1
  18. package/dist/implementation/world/buffer.d.ts +7 -0
  19. package/dist/implementation/world/buffer.js +7 -0
  20. package/dist/implementation/world/buffer.js.map +1 -1
  21. package/dist/implementation/world/cascade.d.ts +10 -3
  22. package/dist/implementation/world/cascade.js +48 -23
  23. package/dist/implementation/world/cascade.js.map +1 -1
  24. package/dist/implementation/world/commands.js +86 -2
  25. package/dist/implementation/world/commands.js.map +1 -1
  26. package/dist/implementation/world/create.js +53 -2
  27. package/dist/implementation/world/create.js.map +1 -1
  28. package/dist/implementation/world/entities.d.ts +2 -0
  29. package/dist/implementation/world/entities.js +6 -0
  30. package/dist/implementation/world/entities.js.map +1 -1
  31. package/dist/implementation/world/execute.d.ts +4 -4
  32. package/dist/implementation/world/execute.js +132 -83
  33. package/dist/implementation/world/execute.js.map +1 -1
  34. package/dist/implementation/world/finalizers.d.ts +2 -0
  35. package/dist/implementation/world/finalizers.js +24 -3
  36. package/dist/implementation/world/finalizers.js.map +1 -1
  37. package/dist/implementation/world/join.js +22 -4
  38. package/dist/implementation/world/join.js.map +1 -1
  39. package/dist/implementation/world/register.js +2 -2
  40. package/dist/implementation/world/resource-changes.d.ts +18 -0
  41. package/dist/implementation/world/resource-changes.js +66 -0
  42. package/dist/implementation/world/resource-changes.js.map +1 -0
  43. package/dist/implementation/world/rows.d.ts +1 -1
  44. package/dist/implementation/world/rows.js +23 -13
  45. package/dist/implementation/world/rows.js.map +1 -1
  46. package/dist/implementation/world/run-changes.d.ts +38 -0
  47. package/dist/implementation/world/run-changes.js +138 -0
  48. package/dist/implementation/world/run-changes.js.map +1 -0
  49. package/dist/implementation/world/runner.js +19 -15
  50. package/dist/implementation/world/runner.js.map +1 -1
  51. package/dist/implementation/world/sharing.js +1 -1
  52. package/dist/implementation/world/staging.d.ts +4 -0
  53. package/dist/implementation/world/staging.js +15 -3
  54. package/dist/implementation/world/staging.js.map +1 -1
  55. package/dist/implementation/world/state.d.ts +13 -0
  56. package/dist/implementation/world/state.js +13 -1
  57. package/dist/implementation/world/state.js.map +1 -1
  58. package/dist/implementation/world/synchronize.js +72 -22
  59. package/dist/implementation/world/synchronize.js.map +1 -1
  60. package/dist/implementation/world/tables-read.d.ts +9 -0
  61. package/dist/implementation/world/tables-read.js +72 -0
  62. package/dist/implementation/world/tables-read.js.map +1 -0
  63. package/dist/implementation/world/tables.d.ts +10 -0
  64. package/dist/implementation/world/tables.js +401 -0
  65. package/dist/implementation/world/tables.js.map +1 -0
  66. package/dist/implementation/world/thread-observer.js +1 -1
  67. package/dist/implementation/world/thread-observer.js.map +1 -1
  68. package/dist/implementation/world/thread-systems.js +11 -6
  69. package/dist/implementation/world/thread-systems.js.map +1 -1
  70. package/dist/implementation/world/up.js +7 -1
  71. package/dist/implementation/world/up.js.map +1 -1
  72. package/dist/implementation/world/where.d.ts +8 -0
  73. package/dist/implementation/world/where.js +40 -11
  74. package/dist/implementation/world/where.js.map +1 -1
  75. package/dist/index.d.ts +8 -28
  76. package/dist/index.js +4 -9
  77. package/dist/index.js.map +1 -1
  78. package/dist/interface/component/declarations.d.ts +4 -0
  79. package/dist/interface/component/declarations.js +8 -0
  80. package/dist/interface/component/declarations.js.map +1 -0
  81. package/dist/interface/component/declared.d.ts +12 -0
  82. package/dist/interface/component/declared.js +2 -0
  83. package/dist/interface/component/declared.js.map +1 -0
  84. package/dist/interface/component/index.d.ts +2 -0
  85. package/dist/interface/component/index.js +1 -1
  86. package/dist/interface/component/index.js.map +1 -1
  87. package/dist/interface/finalizer/body.d.ts +18 -3
  88. package/dist/interface/finalizer/create.d.ts +7 -5
  89. package/dist/interface/finalizer/create.js +8 -5
  90. package/dist/interface/finalizer/create.js.map +1 -1
  91. package/dist/interface/finalizer/declaration.d.ts +4 -2
  92. package/dist/interface/indexes/declaration-of.d.ts +17 -0
  93. package/dist/interface/indexes/declaration-of.js +20 -0
  94. package/dist/interface/indexes/declaration-of.js.map +1 -0
  95. package/dist/interface/indexes/index.d.ts +1 -0
  96. package/dist/interface/indexes/index.js +1 -0
  97. package/dist/interface/indexes/index.js.map +1 -1
  98. package/dist/interface/registry/names.d.ts +2 -0
  99. package/dist/interface/registry/names.js +9 -0
  100. package/dist/interface/registry/names.js.map +1 -1
  101. package/dist/interface/registry/schedule.d.ts +31 -4
  102. package/dist/interface/registry/schedule.js +185 -24
  103. package/dist/interface/registry/schedule.js.map +1 -1
  104. package/dist/interface/resource/declarations.d.ts +4 -0
  105. package/dist/interface/resource/declarations.js +8 -0
  106. package/dist/interface/resource/declarations.js.map +1 -0
  107. package/dist/interface/resource/index.d.ts +2 -0
  108. package/dist/interface/resource/index.js +1 -1
  109. package/dist/interface/resource/index.js.map +1 -1
  110. package/dist/interface/system/access.d.ts +15 -0
  111. package/dist/interface/system/body.d.ts +11 -6
  112. package/dist/interface/system/check.d.ts +8 -1
  113. package/dist/interface/system/check.js +42 -12
  114. package/dist/interface/system/check.js.map +1 -1
  115. package/dist/interface/system/commands.d.ts +37 -0
  116. package/dist/interface/system/create-resolved.js +1 -1
  117. package/dist/interface/system/create-resolved.js.map +1 -1
  118. package/dist/interface/system/create.d.ts +31 -15
  119. package/dist/interface/system/create.js +8 -7
  120. package/dist/interface/system/create.js.map +1 -1
  121. package/dist/interface/system/declaration.d.ts +9 -2
  122. package/dist/interface/system/describe.d.ts +120 -0
  123. package/dist/interface/system/describe.js +116 -0
  124. package/dist/interface/system/describe.js.map +1 -0
  125. package/dist/interface/system/index.d.ts +1 -0
  126. package/dist/interface/system/index.js +1 -0
  127. package/dist/interface/system/index.js.map +1 -1
  128. package/dist/interface/system/leaf.d.ts +6 -0
  129. package/dist/interface/system/render.js +1 -0
  130. package/dist/interface/system/render.js.map +1 -1
  131. package/dist/interface/system/resolve.d.ts +5 -1
  132. package/dist/interface/system/resolve.js +19 -1
  133. package/dist/interface/system/resolve.js.map +1 -1
  134. package/dist/interface/system/shared-only.d.ts +7 -0
  135. package/dist/interface/system/shared-only.js +2 -0
  136. package/dist/interface/system/shared-only.js.map +1 -0
  137. package/dist/interface/system/tables.d.ts +28 -0
  138. package/dist/interface/system/tables.js +2 -0
  139. package/dist/interface/system/tables.js.map +1 -0
  140. package/dist/interface/world/index.d.ts +1 -1
  141. package/dist/interface/world/table/index.d.ts +27 -0
  142. package/dist/interface/world/table/index.js +3 -0
  143. package/dist/interface/world/table/index.js.map +1 -0
  144. package/dist/interface/world/table/table.d.ts +11 -0
  145. package/dist/interface/world/table/table.js +2 -0
  146. package/dist/interface/world/table/table.js.map +1 -0
  147. package/dist/interface/world/world.d.ts +25 -10
  148. package/dist/world.d.ts +27 -0
  149. package/dist/world.js +27 -0
  150. package/dist/world.js.map +1 -0
  151. package/package.json +11 -2
  152. package/src/exports.test.ts +1 -1
  153. package/src/guide/00-keys.ts +1 -1
  154. package/src/guide/01-resources.md +1 -1
  155. package/src/guide/01-resources.ts +1 -1
  156. package/src/guide/02-entities.ts +2 -2
  157. package/src/guide/03-systems.md +2 -2
  158. package/src/guide/03-systems.out +1 -1
  159. package/src/guide/03-systems.ts +3 -3
  160. package/src/guide/04-ticks.md +1 -1
  161. package/src/guide/04-ticks.ts +3 -3
  162. package/src/guide/05-scheduling.ts +1 -1
  163. package/src/guide/06-sync-points.md +18 -7
  164. package/src/guide/06-sync-points.out +1 -2
  165. package/src/guide/06-sync-points.ts +10 -6
  166. package/src/guide/07-change-filters.ts +2 -2
  167. package/src/guide/08-input.ts +1 -1
  168. package/src/guide/09-rules.ts +1 -1
  169. package/src/guide/11-traits.ts +3 -3
  170. package/src/guide/12-lookups.ts +2 -2
  171. package/src/guide/13-indexes.ts +4 -4
  172. package/src/guide/14-kernels.ts +1 -1
  173. package/src/guide/15-reductions.ts +1 -1
  174. package/src/guide/16-gpu.ts +1 -1
  175. package/src/guide/17-hierarchies.ts +1 -1
  176. package/src/guide/18-identity.out +1 -1
  177. package/src/guide/19-threads.md +4 -2
  178. package/src/guide/19-threads.ts +2 -2
  179. package/src/guide/20-shared.ts +3 -3
  180. package/src/guide/21-dom.md +2 -2
  181. package/src/guide/21-dom.ts +1 -1
  182. package/src/guide/22-canvas.md +1 -1
  183. package/src/guide/22-canvas.ts +1 -1
  184. package/src/guide/CLAUDE.md +1 -1
  185. package/src/guide/guide.test.ts +9 -154
  186. package/src/implementation/compiler/body-access.ts +102 -12
  187. package/src/implementation/compiler/describe.test-helper.ts +1 -1
  188. package/src/implementation/compiler/describe.test.ts +1 -1
  189. package/src/implementation/compiler/describe.ts +3 -3
  190. package/src/implementation/compiler/estree.ts +9 -6
  191. package/src/implementation/compiler/hygiene.test.ts +1 -1
  192. package/src/implementation/compiler/inferred.test.ts +1 -1
  193. package/src/implementation/compiler/inferred.ts +4 -2
  194. package/src/implementation/compiler/plugin.test.ts +2 -2
  195. package/src/implementation/compiler/signature.ts +1 -1
  196. package/src/implementation/compiler/transform.test.ts +43 -23
  197. package/src/implementation/compiler/transform.ts +6 -4
  198. package/src/implementation/world/activate.ts +11 -2
  199. package/src/implementation/world/batches.test.ts +3 -3
  200. package/src/implementation/world/buffer.ts +7 -0
  201. package/src/implementation/world/cascade.ts +46 -22
  202. package/src/implementation/world/change-blocks.test.ts +1 -1
  203. package/src/implementation/world/commands.ts +76 -2
  204. package/src/implementation/world/create.test.ts +8 -8
  205. package/src/implementation/world/create.ts +57 -2
  206. package/src/implementation/world/defaults.test.ts +1 -1
  207. package/src/implementation/world/despawn-where.test.ts +287 -0
  208. package/src/implementation/world/entities.ts +6 -0
  209. package/src/implementation/world/execute.ts +125 -78
  210. package/src/implementation/world/finalizers.test.ts +1 -1
  211. package/src/implementation/world/finalizers.ts +27 -4
  212. package/src/implementation/world/ids.test.ts +1 -1
  213. package/src/implementation/world/index-changed.test.ts +2 -2
  214. package/src/implementation/world/index-joined.test.ts +5 -5
  215. package/src/implementation/world/index-order.test.ts +1 -1
  216. package/src/implementation/world/indexes.test.ts +2 -1
  217. package/src/implementation/world/join.test.ts +23 -4
  218. package/src/implementation/world/join.ts +26 -4
  219. package/src/implementation/world/joined-observer.test.ts +1 -1
  220. package/src/implementation/world/joining-worker.fixture.ts +1 -1
  221. package/src/implementation/world/joining.fixture.ts +1 -1
  222. package/src/implementation/world/joining.test-helper.ts +1 -1
  223. package/src/implementation/world/kernel-spawns.test.ts +1 -1
  224. package/src/implementation/world/names.test.ts +1 -2
  225. package/src/implementation/world/observe-factories.test.ts +2 -2
  226. package/src/implementation/world/observe-ticks-cases.test.ts +1 -1
  227. package/src/implementation/world/observe.test-helper.ts +1 -1
  228. package/src/implementation/world/observer-contained-worker.fixture.ts +1 -1
  229. package/src/implementation/world/observer-contained.test-helper.ts +1 -1
  230. package/src/implementation/world/observer-contained.test.ts +2 -2
  231. package/src/implementation/world/observer-joined.test-helper.ts +4 -4
  232. package/src/implementation/world/observer-joined.test.ts +23 -15
  233. package/src/implementation/world/observer.browser.test.ts +2 -2
  234. package/src/implementation/world/observer.test.ts +8 -8
  235. package/src/implementation/world/optional-workers.fixture.ts +1 -1
  236. package/src/implementation/world/optional.test.ts +7 -7
  237. package/src/implementation/world/perf.md +3 -1
  238. package/src/implementation/world/reclaim.test.ts +3 -3
  239. package/src/implementation/world/register.ts +2 -2
  240. package/src/implementation/world/remote-objects.test-helper.ts +1 -1
  241. package/src/implementation/world/resource-changes.test.ts +61 -0
  242. package/src/implementation/world/resource-changes.ts +67 -0
  243. package/src/implementation/world/rows.ts +24 -13
  244. package/src/implementation/world/rules.test.ts +37 -0
  245. package/src/implementation/world/run-changes.test.ts +88 -0
  246. package/src/implementation/world/run-changes.ts +160 -0
  247. package/src/implementation/world/run-results.test.ts +10 -10
  248. package/src/implementation/world/runner.ts +20 -16
  249. package/src/implementation/world/shared-gpu.browser.test.ts +2 -2
  250. package/src/implementation/world/shared-kernels.test.ts +2 -2
  251. package/src/implementation/world/shared-workers.fixture.ts +2 -2
  252. package/src/implementation/world/shared.test.ts +10 -10
  253. package/src/implementation/world/sharing.ts +1 -1
  254. package/src/implementation/world/staging.ts +16 -3
  255. package/src/implementation/world/state.ts +19 -0
  256. package/src/implementation/world/sync-points.test.ts +135 -0
  257. package/src/implementation/world/synchronize.ts +65 -18
  258. package/src/implementation/world/tables-read.ts +70 -0
  259. package/src/implementation/world/tables.test.ts +369 -0
  260. package/src/implementation/world/tables.ts +368 -0
  261. package/src/implementation/world/tails.test.ts +1 -1
  262. package/src/implementation/world/target.test.ts +2 -2
  263. package/src/implementation/world/thread-observer.ts +1 -1
  264. package/src/implementation/world/thread-systems.test-helper.ts +3 -3
  265. package/src/implementation/world/thread-systems.ts +11 -6
  266. package/src/implementation/world/up.test.ts +13 -1
  267. package/src/implementation/world/up.ts +6 -1
  268. package/src/implementation/world/where.test.ts +4 -4
  269. package/src/implementation/world/where.ts +38 -10
  270. package/src/implementation/world/workers.fixture.ts +2 -2
  271. package/src/index.ts +4 -29
  272. package/src/interface/component/declarations.ts +11 -0
  273. package/src/interface/component/declared.ts +9 -0
  274. package/src/interface/component/index.ts +2 -0
  275. package/src/interface/finalizer/body.ts +13 -3
  276. package/src/interface/finalizer/create.test.ts +3 -1
  277. package/src/interface/finalizer/create.ts +12 -7
  278. package/src/interface/finalizer/declaration.ts +4 -2
  279. package/src/interface/index.type-test.ts +2 -2
  280. package/src/interface/indexes/create.test.ts +10 -0
  281. package/src/interface/indexes/declaration-of.ts +34 -0
  282. package/src/interface/indexes/index.ts +1 -0
  283. package/src/interface/indexes/index.type-test.ts +1 -1
  284. package/src/interface/registry/names.ts +8 -0
  285. package/src/interface/registry/schedule.ts +200 -27
  286. package/src/interface/resource/declarations.ts +11 -0
  287. package/src/interface/resource/index.ts +2 -0
  288. package/src/interface/system/access.ts +16 -0
  289. package/src/interface/system/body.ts +7 -5
  290. package/src/interface/system/check.ts +37 -10
  291. package/src/interface/system/commands.ts +33 -0
  292. package/src/interface/system/commands.type-test.ts +31 -0
  293. package/src/interface/system/create-resolved.ts +1 -1
  294. package/src/interface/system/create.test.ts +21 -13
  295. package/src/interface/system/create.ts +24 -15
  296. package/src/interface/system/declaration.ts +9 -2
  297. package/src/interface/system/describe.test.ts +79 -0
  298. package/src/interface/system/describe.ts +122 -0
  299. package/src/interface/system/index.ts +1 -0
  300. package/src/interface/system/leaf.ts +6 -0
  301. package/src/interface/system/lists.type-test.ts +1 -1
  302. package/src/interface/system/optional.type-test.ts +1 -1
  303. package/src/interface/system/render.ts +1 -0
  304. package/src/interface/system/resolve.ts +25 -2
  305. package/src/interface/system/shared-only.ts +5 -0
  306. package/src/interface/system/shared.type-test.ts +1 -1
  307. package/src/interface/system/system.type-test.ts +26 -20
  308. package/src/interface/system/tables.ts +27 -0
  309. package/src/interface/system/up.type-test.ts +3 -3
  310. package/src/interface/world/index.ts +2 -2
  311. package/src/interface/world/run.type-test.ts +14 -4
  312. package/src/interface/world/table/index.ts +20 -0
  313. package/src/interface/world/table/table.ts +12 -0
  314. package/src/interface/world/world.ts +23 -10
  315. package/src/observe/entity.ts +2 -2
  316. package/src/observe/resource.ts +2 -2
  317. package/src/observe/select.ts +4 -4
  318. package/src/observe/world-state.ts +3 -3
  319. package/src/perftest/benchmarks.ts +19 -1
  320. package/src/perftest/indexes.ts +3 -3
  321. package/src/perftest/loop.ts +1 -1
  322. package/src/perftest/observer.ts +4 -4
  323. package/src/perftest/reads.ts +2 -2
  324. package/src/perftest/remote.ts +5 -5
  325. package/src/perftest/strings.ts +1 -1
  326. package/src/perftest/structural.ts +20 -0
  327. package/src/perftest/suite.ts +1 -1
  328. package/src/perftest/tables.ts +107 -0
  329. package/src/world.test.ts +2 -2
  330. package/src/world.ts +32 -0
@@ -35,6 +35,7 @@ import { Observe } from "../observe/index.js";
35
35
  import { entitiesWith } from "./observer.js";
36
36
  import { invariantBenchmarks } from "./invariants.js";
37
37
  import { journalBenchmarks } from "./journal.js";
38
+ import { tableBenchmarks } from "./tables.js";
38
39
 
39
40
  /** A world whose kernels run on this thread, which the benchmarks without a placement measure; `workers` ones use the pool. */
40
41
  const inline = (): World => createWorld({ [workers]: 0 });
@@ -505,7 +506,7 @@ export const benchmarks: readonly Benchmark[] = [
505
506
  run: async () => {
506
507
  const { world, roots } = await filteredForest();
507
508
  const found: { child: Entity | null } = { child: null };
508
- await world.run(System.create({ observer: true, components: { with: [hierarchyParent] } }, row => {
509
+ await world.run(System.create({ readonly: true, components: { with: [hierarchyParent] } }, row => {
509
510
  if (found.child === null && row[hierarchyParent] === roots[0]) found.child = row.entity;
510
511
  }));
511
512
  const { child } = found;
@@ -648,6 +649,22 @@ export const benchmarks: readonly Benchmark[] = [
648
649
  return best;
649
650
  },
650
651
  },
652
+ ...[10, 1_000, 100_000].flatMap(n => ([
653
+ ["rows", "a command per row", structural.despawnDoomedRows, 24],
654
+ ["where", "one `despawnWhere`", structural.despawnDoomedWhere, 16],
655
+ ] as const).map(([how, by, system, bytes]): Benchmark => {
656
+ const name = `despawn tagged ${n >= 1_000 ? `${n / 1_000}K` : n} ${how}`;
657
+ return {
658
+ name,
659
+ operation: `A run despawning the ${n >= 1_000 ? `${n / 1_000}K` : n} entities of a tag beside 100K others, by ${by}: clearing one kind, such as expired effects.`,
660
+ per: "despawns",
661
+ n,
662
+ typical: [10, 100_000],
663
+ best: { bytes, basis: bytes === 16 ? "Each despawn frees its 8-byte location and entity; the archetype empties, so no row moves." : "Each row reads its entity; each despawn frees its location and entity." },
664
+ run: () => structuralBest(1, world => world.run(structural.spawnDoomed, { count: n }), world => world.run(system),
665
+ async world => await countOf(world, [structural.doomed]) === 0, name),
666
+ };
667
+ })),
651
668
  ...([
652
669
  ["add tag", "Adding a tag to 100K entities of one archetype: a state flag set on many at once.", 1, structural.removeFlag, structural.addFlag, [structural.flag], 16],
653
670
  ["add tag kernel", "The same adds from a kernel.", 1, structural.removeFlagKernel, structural.addFlagKernel, [structural.flag], 20],
@@ -705,4 +722,5 @@ export const benchmarks: readonly Benchmark[] = [
705
722
  ...sharedBenchmarks,
706
723
  ...invariantBenchmarks,
707
724
  ...journalBenchmarks,
725
+ ...tableBenchmarks,
708
726
  ];
@@ -307,13 +307,13 @@ const seedFlags = System.create({}, ({ commands }) => {
307
307
  for (let i = 0; i < flagged; i++) commands.spawn([flag])({ [flag]: i % 2 === 0 });
308
308
  });
309
309
  /** A run of observer systems alone, which passes every one added. */
310
- const pass = System.create({ observer: true }, () => {});
310
+ const pass = System.create({ readonly: true }, () => {});
311
311
  /** A count of each bucket, written only when the index's entries changed: by `changed: true`, or by its lists' `changed`. */
312
312
  const countsWhenChanged = (filtered: boolean): System => filtered
313
- ? System.create({ observer: true, lookup: { byFlag: { index: byFlag, changed: true } } }, ({ lookup }) => {
313
+ ? System.create({ readonly: true, lookup: { byFlag: { index: byFlag, changed: true } } }, ({ lookup }) => {
314
314
  found += lookup.byFlag(false).length + lookup.byFlag(true).length;
315
315
  })
316
- : System.create({ observer: true, lookup: { byFlag: { index: byFlag } } }, ({ lookup }) => {
316
+ : System.create({ readonly: true, lookup: { byFlag: { index: byFlag } } }, ({ lookup }) => {
317
317
  const off = lookup.byFlag(false);
318
318
  const on = lookup.byFlag(true);
319
319
  if (!off.changed && !on.changed) return;
@@ -12,7 +12,7 @@ export const loopTicks = 10_000;
12
12
  export const observerIdles = 100;
13
13
 
14
14
  /** Observer systems no entity matches, each run after every tick. */
15
- const idleObserver = Array.from({ length: observerIdles }, (_, i) => System.create({ observer: true, components: { with: [unused] } }, () => {}));
15
+ const idleObserver = Array.from({ length: observerIdles }, (_, i) => System.create({ readonly: true, components: { with: [unused] } }, () => {}));
16
16
 
17
17
  /**
18
18
  * The fastest of `stretches` stretches of `loopTicks` ticks with nothing changed, after one to warm up: a world with
@@ -14,14 +14,14 @@ import type { World } from "../interface/world/world.js";
14
14
  * first call only starts the stretch that warms up.
15
15
  */
16
16
  export const eachTick = (world: World, notify: () => void): (() => void) => {
17
- const counter = System.create({ observer: true, resources: { changed: [clock] } }, () => { notify(); });
17
+ const counter = System.create({ readonly: true, resources: { changed: [clock] } }, () => { notify(); });
18
18
  world.add(counter);
19
19
  return () => world.remove(counter);
20
20
  };
21
21
 
22
22
  /** The clock as an observer run last read it. */
23
23
  const read = { tick: 0, now: 0 };
24
- const readClock = System.create({ observer: true }, ({ resources }) => {
24
+ const readClock = System.create({ readonly: true }, ({ resources }) => {
25
25
  read.tick = resources[clock].tick;
26
26
  read.now = resources[clock].now;
27
27
  });
@@ -36,7 +36,7 @@ export const ticksAfter = async (world: World, count: number): Promise<void> =>
36
36
  let { tick, now } = read;
37
37
  let seen = 0;
38
38
  await new Promise<void>(resolve => {
39
- const counter = System.create({ observer: true }, ({ resources }) => {
39
+ const counter = System.create({ readonly: true }, ({ resources }) => {
40
40
  const current = resources[clock];
41
41
  if (current.tick === tick && current.now === now) return;
42
42
  tick = current.tick;
@@ -52,7 +52,7 @@ export const ticksAfter = async (world: World, count: number): Promise<void> =>
52
52
  /** The entities with every component of `has` and none of `not`, in storage order, read by an observer run. */
53
53
  export const entitiesWith = async (world: World, has: readonly Component.Key[], not: readonly Component.Key[] = []): Promise<Entity[]> => {
54
54
  const found: Entity[] = [];
55
- await world.run(System.create({ observer: true, components: { with: [...has], not } }, row => { found.push(row.entity); }));
55
+ await world.run(System.create({ readonly: true, components: { with: [...has], not } }, row => { found.push(row.entity); }));
56
56
  return found;
57
57
  };
58
58
 
@@ -78,7 +78,7 @@ const lookups = async (system: System): Promise<{ readonly best: number; readonl
78
78
  /** Every entity a seeded world holds. */
79
79
  const entitiesOf = async (world: World): Promise<readonly Entity[]> => {
80
80
  const found: Entity[] = [];
81
- await world.run(System.create({ observer: true, components: { with: [value] } }, row => { found.push(row.entity); }));
81
+ await world.run(System.create({ readonly: true, components: { with: [value] } }, row => { found.push(row.entity); }));
82
82
  return found;
83
83
  };
84
84
 
@@ -124,7 +124,7 @@ export const readBenchmarks: readonly Benchmark[] = [
124
124
  run: async () => {
125
125
  const world = await seeded(10_000);
126
126
  copied = 0;
127
- const best = await fastest(() => world.run(System.create({ observer: true }, ({ resources }) => { copied = resources[level]; })), 300);
127
+ const best = await fastest(() => world.run(System.create({ readonly: true }, ({ resources }) => { copied = resources[level]; })), 300);
128
128
  check(copied === 3, "read run observer: wrong value");
129
129
  await world.dispose();
130
130
  return best;
@@ -49,7 +49,7 @@ export const reorder = System.create([flip, reverse]);
49
49
  export const observerSeen = { last: 0, notified: 0 };
50
50
 
51
51
  /** On a joined thread, after each sync point that changed it: the observed resource. */
52
- export const observerCounter = System.create({ observer: true, resources: { changed: [counter] } }, ({ resources }) => {
52
+ export const observerCounter = System.create({ readonly: true, resources: { changed: [counter] } }, ({ resources }) => {
53
53
  observerSeen.last = resources[counter];
54
54
  observerSeen.notified++;
55
55
  });
@@ -61,7 +61,7 @@ const sortedKeys = { keys: new Float64Array(0), entities: new Float64Array(0), p
61
61
  * On a joined thread, after each sync point: the entities in `order`, sorted from a scan of the index of every ranked
62
62
  * entity into reused arrays.
63
63
  */
64
- export const observerSelect = System.create({ observer: true, lookup: { all: { index: ranked } } }, ({ lookup }) => {
64
+ export const observerSelect = System.create({ readonly: true, lookup: { all: { index: ranked } } }, ({ lookup }) => {
65
65
  const list = lookup.all();
66
66
  const count = list.length;
67
67
  if (sortedKeys.keys.length < count) {
@@ -99,12 +99,12 @@ export const retext = System.create([count, recaption]);
99
99
  export const reamounted = System.create([count, reamount]);
100
100
 
101
101
  /** On a joined thread, after each sync point: every row's caption, out of its record. */
102
- export const observerCaptions = System.create({ observer: true, components: { with: [caption] } }, row => {
102
+ export const observerCaptions = System.create({ readonly: true, components: { with: [caption] } }, row => {
103
103
  observerSeen.last += row[caption].length;
104
104
  observerSeen.notified++;
105
105
  });
106
106
  /** On a joined thread, after each sync point: every row's amount. */
107
- export const observerAmounts = System.create({ observer: true, components: { with: [amount] } }, row => {
107
+ export const observerAmounts = System.create({ readonly: true, components: { with: [amount] } }, row => {
108
108
  observerSeen.last += row[amount];
109
109
  observerSeen.notified++;
110
110
  });
@@ -129,7 +129,7 @@ const joinedFile = "./joined.js";
129
129
  /** The observed resource, read by an observer run. */
130
130
  const counterOf = async (world: World): Promise<number> => {
131
131
  let value = 0;
132
- await world.run(System.create({ observer: true }, ({ resources }) => { value = resources[counter]; }));
132
+ await world.run(System.create({ readonly: true }, ({ resources }) => { value = resources[counter]; }));
133
133
  return value;
134
134
  };
135
135
 
@@ -80,7 +80,7 @@ const read = (system: System, name: string) => measure(system, async world => {
80
80
  /** Each row's text, as the last write left it. */
81
81
  const written = (name: string) => async (world: World): Promise<void> => {
82
82
  let wrong = 0;
83
- await world.run(System.create({ observer: true, components: { with: [text, boxed] } }, row => {
83
+ await world.run(System.create({ readonly: true, components: { with: [text, boxed] } }, row => {
84
84
  if (row[text] !== row[boxed].text && row[text] !== labelOf(row.entity, round) && row[boxed].text !== labelOf(row.entity, round)) wrong++;
85
85
  }));
86
86
  check(wrong === 0, `${name}: wrong text`);
@@ -12,6 +12,7 @@ export const flag = Symbol("structural/flag");
12
12
  export const amount = Symbol("structural/amount");
13
13
  export const extra = Symbol("structural/extra");
14
14
  export const spawned = Symbol("structural/spawned");
15
+ export const doomed = Symbol("structural/doomed");
15
16
  const source1 = Symbol("structural/source1");
16
17
  const source2 = Symbol("structural/source2");
17
18
  const t0 = Symbol("structural/t0");
@@ -27,6 +28,7 @@ declare module "@adobe/ecs" {
27
28
  [amount]: { type: F32 };
28
29
  [extra]: { type: F32 };
29
30
  [spawned]: { type: true };
31
+ [doomed]: { type: true };
30
32
  [source1]: { type: true };
31
33
  [source2]: { type: true };
32
34
  [t0]: { type: true };
@@ -128,6 +130,24 @@ export const removeFlagKernel = System.create({ kernel: true, components: { with
128
130
  commands.remove(row.entity, flag);
129
131
  });
130
132
 
133
+ const doomedList = [index, value, doomed] as const;
134
+ const doomedQuery = { with: [doomed] } as const;
135
+
136
+ /** `count` spawns of entities tagged `doomed`, beside the seeded ones. */
137
+ export const spawnDoomed = System.create({}, ({ commands }, input: { readonly count: number }) => {
138
+ for (let i = 0; i < input.count; i++) commands.spawn(doomedList)({ [index]: i, [value]: i });
139
+ });
140
+
141
+ /** Despawns every `doomed` entity, a command per row. */
142
+ export const despawnDoomedRows = System.create({ kernel: false, components: { with: [doomed] } }, (row, { commands }) => {
143
+ commands.despawn(row.entity);
144
+ });
145
+
146
+ /** Despawns every `doomed` entity, by one command. */
147
+ export const despawnDoomedWhere = System.create({}, ({ commands }) => {
148
+ commands.despawnWhere(doomedQuery);
149
+ });
150
+
131
151
  export const addAmount = System.create({ kernel: false, components: { with: [index], not: [amount, spawned] } }, (row, { commands }) => {
132
152
  commands.add(row.entity, amount, row[index]);
133
153
  });
@@ -196,7 +196,7 @@ export const idle = System.create({ components: { with: [unused] } }, () => {});
196
196
  /** A system that throws, so each run of it rejects. */
197
197
  export const failing = System.create({}, () => { throw new Error("suite: failing"); });
198
198
  /** An observer system no entity matches: a run of it alone has no tiers. */
199
- export const idleObserver = System.create({ observer: true, components: { with: [unused] } }, () => {});
199
+ export const idleObserver = System.create({ readonly: true, components: { with: [unused] } }, () => {});
200
200
 
201
201
  /** Fifty systems no entity matches, in one tier. */
202
202
  export const idles = System.create({
@@ -0,0 +1,107 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ import type { Entity } from "../interface/entity/entity.js";
3
+ import type { Vec3 } from "../interface/math/index.js";
4
+ import { workers } from "../interface/resources/workers.js";
5
+ import { System } from "../interface/system/system.js";
6
+ import type { Tables } from "../interface/system/tables.js";
7
+ import type { Table } from "../interface/world/table/table.js";
8
+ import { createWorld } from "../implementation/index.js";
9
+ import type { Benchmark } from "./benchmark.js";
10
+
11
+ // Spawning 1M entities into a fresh world in one sync point, each owned by an earlier one or by none: from tables, as
12
+ // loading a snapshot does, and from commands; and the same tables without the owner reference.
13
+
14
+ const position = Symbol("tables/position");
15
+ const owner = Symbol("tables/owner");
16
+ declare module "@adobe/ecs" {
17
+ export interface Components {
18
+ [position]: { type: Vec3 };
19
+ [owner]: { type: Entity; reference: "owner" };
20
+ }
21
+ }
22
+
23
+ const count = 1_000_000;
24
+
25
+ /** A root and `count - 1` entities, each owned by the one at half its position. */
26
+ const build = System.create(({ commands }) => {
27
+ const made: Entity[] = [commands.spawn([position])({ [position]: [0, 0, 0] })];
28
+ const owned = commands.spawn([position, owner]);
29
+ for (let i = 1; i < count; i++) made.push(owned({ [position]: [i, i, i], [owner]: made[i >> 1] }));
30
+ });
31
+
32
+ const tablesOf = (components: readonly (typeof position | typeof owner)[]) =>
33
+ System.create({ readonly: true, tables: { components } }, ({ tables }): Tables => tables);
34
+
35
+ const given: { tables: readonly Table[] } = { tables: [] };
36
+ const spawnGiven = System.create(({ commands }) => {
37
+ commands.spawnTables(given.tables);
38
+ });
39
+
40
+ /** The fastest of a few runs spawning `tables` into a fresh world. */
41
+ const spawnFastest = async (tables: readonly Table[]): Promise<number> => {
42
+ let best = Infinity;
43
+ for (let k = 0; k < 7; k++) {
44
+ const world = createWorld({ [workers]: 0 });
45
+ given.tables = tables;
46
+ const start = performance.now();
47
+ await world.run(spawnGiven);
48
+ const time = performance.now() - start;
49
+ // The first runs warm up.
50
+ if (k >= 2) best = Math.min(best, time);
51
+ await world.dispose();
52
+ }
53
+ given.tables = [];
54
+ return best;
55
+ };
56
+
57
+ const tables = async (components: readonly (typeof position | typeof owner)[]): Promise<number> => {
58
+ const world = createWorld({ [workers]: 0 });
59
+ await world.run(build);
60
+ const { archetypes } = await world.run(tablesOf(components));
61
+ await world.dispose();
62
+ return spawnFastest(archetypes);
63
+ };
64
+
65
+ /** The fastest of a few runs of `build` in a fresh world. */
66
+ const commanded = async (): Promise<number> => {
67
+ let best = Infinity;
68
+ for (let k = 0; k < 7; k++) {
69
+ const world = createWorld({ [workers]: 0 });
70
+ const start = performance.now();
71
+ await world.run(build);
72
+ const time = performance.now() - start;
73
+ if (k >= 2) best = Math.min(best, time);
74
+ await world.dispose();
75
+ }
76
+ return best;
77
+ };
78
+
79
+ export const tableBenchmarks: readonly Benchmark[] = [
80
+ {
81
+ name: "spawn tables 1M owner",
82
+ operation: "commands.spawnTables of 1M entities into a fresh world, each but one owned by another of them, and its sync point: loading a snapshot of a hierarchy.",
83
+ per: "spawns",
84
+ n: count,
85
+ typical: [1_000, 1_000_000],
86
+ best: { bytes: 36, basis: "Each entity copies its 12-byte position and 8-byte owner, and writes its 8-byte entity and location." },
87
+ run: () => tables([position, owner]),
88
+ },
89
+ {
90
+ name: "spawn tables 1M",
91
+ operation: "As `spawn tables 1M owner`, without the owner reference: its cost is the difference.",
92
+ per: "spawns",
93
+ n: count,
94
+ typical: [1_000, 1_000_000],
95
+ best: { bytes: 28, basis: "Each entity copies its 12-byte position, and writes its 8-byte entity and location." },
96
+ run: () => tables([position]),
97
+ },
98
+ {
99
+ name: "spawn owned 1M",
100
+ operation: "A main-thread system spawning 1M entities through one spawner, each but one owned by an earlier one, and its sync point.",
101
+ per: "spawns",
102
+ n: count,
103
+ typical: [1_000, 1_000_000],
104
+ best: { bytes: 36, basis: "Each entity writes its 12-byte position and 8-byte owner, its 8-byte entity and location." },
105
+ run: commanded,
106
+ },
107
+ ];
package/src/world.test.ts CHANGED
@@ -23,7 +23,7 @@ describe("World.create", () => {
23
23
  await world.run(spawn);
24
24
  await world.run(add);
25
25
  let seen = 0;
26
- await world.run(System.create({ observer: true, components: { with: [count] } }, row => { seen = row[count]; }));
26
+ await world.run(System.create({ readonly: true, components: { with: [count] } }, row => { seen = row[count]; }));
27
27
  expect(seen).toBe(2);
28
28
  });
29
29
  it("rejects a key that isn't a resource, as worlds the implementation creates do", () => {
@@ -40,7 +40,7 @@ describe("World.join", () => {
40
40
  await shared;
41
41
  await joined.run(raise);
42
42
  let seen = 0;
43
- await world.run(System.create({ observer: true }, ({ resources }) => { seen = resources[total]; }));
43
+ await world.run(System.create({ readonly: true }, ({ resources }) => { seen = resources[total]; }));
44
44
  expect(seen).toBe(7);
45
45
  });
46
46
  });
package/src/world.ts ADDED
@@ -0,0 +1,32 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ import { create as createWorld, join as joinWorld } from './implementation/index.js';
3
+ import type { Create } from './interface/world/create.js';
4
+ import type { Join } from './interface/world/join.js';
5
+
6
+ export type { Table } from './interface/world/table/table.js';
7
+
8
+ /**
9
+ * Creates a world on this thread, synchronously, with resources starting at the given values in place of their
10
+ * defaults. This thread is its `"world"` thread for the world's life: it runs the sync points, the ticks and the GPU
11
+ * device, and fills every logical thread no joined thread claims. Keys are the resources' symbols, built-in ones
12
+ * included (`{ [workers]: 0 }`); string keys are never a resource's, so they're ignored. Throws for a symbol that isn't
13
+ * a resource, or is one only the world writes. While a declaration's trait or default doesn't hold, each call fails to
14
+ * compile, once, naming each such key, its members and what's wrong with them.
15
+ * @function
16
+ */
17
+ export const create: Create = createWorld;
18
+
19
+ /**
20
+ * Joins, from another thread, a world that `share` let join through the other end of `port`, claiming the logical
21
+ * `threads` this thread fills: their objects live here, and the systems and finalizers reading them run here. Resolves,
22
+ * once the world accepted the claim, to the same world: its `run` runs systems there, and its `add` and `remove` take
23
+ * readonly systems, which run here after the world's sync points. Add them before awaiting anything else, so the
24
+ * world's `share` resolves with them in place. Its `dispose` disconnects this thread. Rejects with an `Error` saying
25
+ * why it didn't join: the world refused the claim, was disposed, or its thread ended first, as the platform reports it:
26
+ * the world's `Worker` failing to start or throwing first, a port closed, as in node, or, in a browser, the world's
27
+ * thread ending once it was shared through a port this thread listened on. A browser world's worker that the page ended
28
+ * before this thread heard from it isn't reported, so the join stays pending. Rejects too where memory can't be shared,
29
+ * as without cross-origin isolation.
30
+ * @function
31
+ */
32
+ export const join: Join = joinWorld;