@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
@@ -0,0 +1,369 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ import { describe, expect, it } from "vitest";
3
+ import { clock, Component, Entity, Resource, System, workers } from "../../index.js";
4
+ import type { Vec3 } from "../../interface/math/index.js";
5
+ import type { Tables } from "../../interface/system/tables.js";
6
+ import type { Table } from "../../interface/world/table/table.js";
7
+ import { createWorld } from "./create.js";
8
+
9
+ const position = Symbol("tablesTest/position");
10
+ const label = Symbol("tablesTest/label");
11
+ const tags = Symbol("tablesTest/tags");
12
+ const team = Symbol("tablesTest/team");
13
+ const pinned = Symbol("tablesTest/pinned");
14
+ const parent = Symbol("tablesTest/parent");
15
+ const target = Symbol("tablesTest/target");
16
+ const link = Symbol("tablesTest/link");
17
+ const friends = Symbol("tablesTest/friends");
18
+ const hidden = Symbol("tablesTest/hidden");
19
+ const buddy = Symbol("tablesTest/buddy");
20
+ const score = Symbol("tablesTest/score");
21
+ const title = Symbol("tablesTest/title");
22
+ declare module "@adobe/ecs" {
23
+ interface Components {
24
+ [position]: { type: Vec3 };
25
+ [label]: { type: string };
26
+ [tags]: { type: readonly string[] };
27
+ [team]: { type: "red" | "blue"; shared: true };
28
+ [pinned]: { type: true };
29
+ [parent]: { type: Entity; reference: "owner" };
30
+ [target]: { type: Entity; reference: "weak" };
31
+ [link]: { type: { readonly to: Entity; readonly weight: number } };
32
+ [friends]: { type: readonly Entity[] };
33
+ [hidden]: { type: true };
34
+ [buddy]: { type: Entity };
35
+ }
36
+ interface Resources {
37
+ [score]: { type: number; default: 0 };
38
+ [title]: { type: string; default: "" };
39
+ }
40
+ }
41
+
42
+ const inline = () => createWorld({ [workers]: 0 });
43
+
44
+ /** A readonly system's tables of these components. */
45
+ const tablesOf = (components: readonly Component.Key[], resources: readonly Resource.Key[] = [], not: readonly Component.Key[] = []) =>
46
+ System.create({ readonly: true, tables: { components, resources, not } }, ({ tables }): Tables => tables);
47
+
48
+ const spawnedBy: { spawned: Table.Spawned } = { spawned: { entities: [], dropped: 0 } };
49
+ const given: { tables: readonly Table[]; resources?: { readonly [key: symbol]: unknown }; existing?: ReadonlyMap<Entity, Entity> } = { tables: [] };
50
+ const spawnGiven = System.create(({ commands }) => {
51
+ spawnedBy.spawned = commands.spawnTables(given.tables, { resources: given.resources, existing: given.existing });
52
+ });
53
+ const spawnTables = async (world: ReturnType<typeof inline>, tables: readonly Table[], existing?: ReadonlyMap<Entity, Entity>): Promise<Table.Spawned> => {
54
+ given.tables = tables;
55
+ given.existing = existing;
56
+ try {
57
+ await world.run(spawnGiven);
58
+ }
59
+ finally {
60
+ given.existing = undefined;
61
+ }
62
+ return spawnedBy.spawned;
63
+ };
64
+
65
+ /** Spawns `labels` in `world`, giving each label's entity. */
66
+ const spawnLabels = async (world: ReturnType<typeof inline>, labels: readonly string[]): Promise<ReadonlyMap<string, Entity>> => {
67
+ const made = new Map<string, Entity>();
68
+ await world.run(System.create(({ commands }) => {
69
+ for (const text of labels) made.set(text, commands.spawn([label])({ [label]: text }));
70
+ }));
71
+ return made;
72
+ };
73
+
74
+ const values = System.create({ readonly: true, components: { with: [label], optional: [position, parent, target, link, friends, tags, buddy] } }, row => {
75
+ found.push({ entity: row.entity, label: row[label], position: row[position], parent: row[parent], target: row[target], link: row[link], friends: row[friends], tags: row[tags], buddy: row[buddy] });
76
+ });
77
+ type Found = { entity: Entity; label: string; position?: Vec3; parent?: Entity; target?: Entity; link?: { readonly to: Entity; readonly weight: number }; friends?: readonly Entity[]; tags?: readonly string[]; buddy?: Entity };
78
+ const found: Found[] = [];
79
+ const readAll = async (world: ReturnType<typeof inline>): Promise<Found[]> => {
80
+ found.length = 0;
81
+ await world.run(values);
82
+ return [...found].sort((a, b) => a.label.localeCompare(b.label));
83
+ };
84
+
85
+ describe("tables", () => {
86
+ it("give each archetype holding a component asked for, its entities, and a copy of each column in the world's layout", async () => {
87
+ await using world = inline();
88
+ await world.run(System.create(({ commands }) => {
89
+ commands.spawn([position, label])({ [position]: [1, 2, 3], [label]: "a" });
90
+ commands.spawn([position, label, tags])({ [position]: [4, 5, 6], [label]: "b", [tags]: ["x", "y"] });
91
+ commands.spawn([team, label], { [team]: "red" })({ [label]: "c" });
92
+ commands.spawn([pinned])();
93
+ }));
94
+ const { archetypes } = await world.run(tablesOf([position, label, tags, team, pinned]));
95
+ expect(archetypes.map(table => table.count)).toEqual([1, 1, 1, 1]);
96
+ const plain = archetypes.find(table => table.columns[tags] === undefined && table.columns[position] !== undefined);
97
+ const bytes = plain?.columns[position];
98
+ expect(bytes?.kind === "bytes" && [...new Float32Array(bytes.bytes.buffer, bytes.bytes.byteOffset, 3)]).toEqual([1, 2, 3]);
99
+ expect(bytes?.kind === "bytes" && bytes.stride).toBe(12);
100
+ const withTags = archetypes.find(table => table.columns[tags] !== undefined);
101
+ expect(withTags?.columns[tags]).toEqual({ kind: "values", values: [["x", "y"]] });
102
+ expect(withTags?.columns[label]).toEqual({ kind: "values", values: ["b"] });
103
+ const shared = archetypes.find(table => table.columns[team] !== undefined);
104
+ expect(shared?.columns[team]).toEqual({ kind: "value", value: "red" });
105
+ expect(archetypes.find(table => table.columns[pinned] !== undefined)?.columns[pinned]).toEqual({ kind: "value", value: true });
106
+ });
107
+
108
+ it("leave out archetypes not matching with and not, and give the values of resources asked for", async () => {
109
+ await using world = createWorld({ [workers]: 0, [score]: 7, [title]: "won" });
110
+ await world.run(System.create(({ commands }) => {
111
+ commands.spawn([label])({ [label]: "shown" });
112
+ commands.spawn([label, hidden])({ [label]: "hidden" });
113
+ }));
114
+ const { archetypes, resources } = await world.run(tablesOf([label], [score, title], [hidden]));
115
+ expect(archetypes.map(table => table.columns[label])).toEqual([{ kind: "values", values: ["shown"] }]);
116
+ expect(resources[score]).toBe(7);
117
+ expect(resources[title]).toBe("won");
118
+ });
119
+
120
+ it("spawn back as they were read, shared values and strings included", async () => {
121
+ await using from = inline();
122
+ await from.run(System.create(({ commands }) => {
123
+ commands.spawn([position, label, tags])({ [position]: [1, 2, 3], [label]: "a", [tags]: ["t"] });
124
+ commands.spawn([team, label], { [team]: "blue" })({ [label]: "b" });
125
+ }));
126
+ const { archetypes } = await from.run(tablesOf([position, label, tags, team]));
127
+ await using into = inline();
128
+ const { entities, dropped } = await spawnTables(into, archetypes);
129
+ expect([entities.map(list => list.length), dropped]).toEqual([[1, 1], 0]);
130
+ const again = await into.run(tablesOf([position, label, tags, team]));
131
+ expect(again.archetypes.map(table => ({ ...table, entities: [] }))).toEqual(archetypes.map(table => ({ ...table, entities: [] })));
132
+ expect(await readAll(into)).toMatchObject([{ label: "a", position: [1, 2, 3], tags: ["t"] }, { label: "b" }]);
133
+ });
134
+
135
+ it("rewrite entities in references, struct fields and arrays to those spawned for them, self references and cycles included", async () => {
136
+ await using from = inline();
137
+ await from.run(System.create(({ commands }) => {
138
+ const root = commands.spawn([label])({ [label]: "root" });
139
+ const child = commands.spawn([label, parent])({ [label]: "child", [parent]: root });
140
+ commands.spawn([label, target, link, friends])({ [label]: "other", [target]: child, [link]: { to: root, weight: 2 }, [friends]: [root, child] });
141
+ const a = commands.spawn([label, link])({ [label]: "self", [link]: { to: Entity.none, weight: 1 } });
142
+ commands.add(a, link, { to: a, weight: 1 });
143
+ }));
144
+ const before = await readAll(from);
145
+ const { archetypes } = await from.run(tablesOf([label, parent, target, link, friends]));
146
+ await using into = inline();
147
+ // Entities already in the world, so the new ones differ from the old.
148
+ await into.run(System.create(({ commands }) => { for (let i = 0; i < 5; i++) commands.spawn([pinned])(); }));
149
+ await spawnTables(into, archetypes);
150
+ const after = await readAll(into);
151
+ const old = new Map(before.map(entry => [entry.entity, entry.label]));
152
+ const now = new Map(after.map(entry => [entry.label, entry.entity]));
153
+ const translated = (entity: Entity | undefined): Entity | undefined => entity === undefined ? undefined : now.get(old.get(entity) ?? "");
154
+ expect(after.map(entry => entry.label)).toEqual(before.map(entry => entry.label));
155
+ for (const entry of before) {
156
+ const moved = after.find(other => other.label === entry.label);
157
+ expect(moved?.entity).not.toBe(entry.entity);
158
+ expect(moved?.parent).toBe(translated(entry.parent));
159
+ expect(moved?.target).toBe(translated(entry.target));
160
+ expect(moved?.link?.to).toBe(translated(entry.link?.to));
161
+ expect(moved?.friends).toEqual(entry.friends?.map(translated));
162
+ }
163
+ expect(after.find(entry => entry.label === "self")?.link?.to).toBe(now.get("self"));
164
+ });
165
+
166
+ it("leave out a weak reference to an entity outside the tables, its holder spawned without it", async () => {
167
+ await using world = inline();
168
+ await world.run(System.create(({ commands }) => {
169
+ const outside = commands.spawn([pinned])();
170
+ const inside = commands.spawn([label])({ [label]: "inside" });
171
+ commands.spawn([label, target])({ [label]: "away", [target]: outside });
172
+ commands.spawn([label, target])({ [label]: "near", [target]: inside });
173
+ }));
174
+ const { archetypes } = await world.run(tablesOf([label, target]));
175
+ await using other = inline();
176
+ expect((await spawnTables(other, archetypes)).dropped).toBe(0);
177
+ const read = await readAll(other);
178
+ expect(read.map(entry => [entry.label, entry.target === undefined ? "none" : read.find(found => found.entity === entry.target)?.label])).toEqual([["away", "none"], ["inside", "none"], ["near", "inside"]]);
179
+ });
180
+
181
+ it("spawn no row owned, directly or through others, by an entity outside the tables, saying how many", async () => {
182
+ await using world = inline();
183
+ await world.run(System.create(({ commands }) => {
184
+ const outside = commands.spawn([pinned])();
185
+ const top = commands.spawn([label, parent])({ [label]: "top", [parent]: outside });
186
+ const middle = commands.spawn([label, parent])({ [label]: "middle", [parent]: top });
187
+ commands.spawn([label, parent])({ [label]: "bottom", [parent]: middle });
188
+ const kept = commands.spawn([label])({ [label]: "kept" });
189
+ commands.spawn([label, parent, target])({ [label]: "kept child", [parent]: kept, [target]: middle });
190
+ }));
191
+ const { archetypes } = await world.run(tablesOf([label, parent, target]));
192
+ await using other = inline();
193
+ const { entities, dropped } = await spawnTables(other, archetypes);
194
+ expect(dropped).toBe(3);
195
+ expect(entities.flatMap(list => [...list]).filter(entity => entity === Entity.none).length).toBe(3);
196
+ const read = await readAll(other);
197
+ // The kept child's weak target was dropped, so it's spawned without it.
198
+ expect(read.map(entry => [entry.label, entry.parent === undefined ? null : read.find(found => found.entity === entry.parent)?.label, entry.target ?? null])).toEqual([["kept", null, null], ["kept child", "kept", null]]);
199
+ });
200
+
201
+ it("make any other entity value naming an entity outside the tables none: a whole component, a struct's field and an array's item", async () => {
202
+ await using world = inline();
203
+ await world.run(System.create(({ commands }) => {
204
+ const outside = commands.spawn([pinned])();
205
+ const inside = commands.spawn([label])({ [label]: "inside" });
206
+ commands.spawn([label, buddy, link, friends])({ [label]: "pointing", [buddy]: outside, [link]: { to: outside, weight: 1 }, [friends]: [outside, inside] });
207
+ }));
208
+ const { archetypes } = await world.run(tablesOf([label, buddy, link, friends]));
209
+ await using other = inline();
210
+ await spawnTables(other, archetypes);
211
+ const read = await readAll(other);
212
+ const inside = read.find(entry => entry.label === "inside")?.entity;
213
+ expect(read.find(entry => entry.label === "pointing")).toMatchObject({ buddy: Entity.none, link: { to: Entity.none, weight: 1 }, friends: [Entity.none, inside] });
214
+ });
215
+
216
+ it("spawn no row existing maps, giving the entity it maps to, which every entity value naming it names", async () => {
217
+ await using world = inline();
218
+ let steel = Entity.none;
219
+ await world.run(System.create(({ commands }) => {
220
+ steel = commands.spawn([label])({ [label]: "steel" });
221
+ commands.spawn([label, parent, target, buddy, link, friends])({ [label]: "body", [parent]: steel, [target]: steel, [buddy]: steel, [link]: { to: steel, weight: 2 }, [friends]: [steel] });
222
+ }));
223
+ const { archetypes } = await world.run(tablesOf([label, parent, target, buddy, link, friends]));
224
+ await using other = inline();
225
+ const existingSteel = (await spawnLabels(other, ["steel in other"])).get("steel in other") ?? Entity.none;
226
+ const { entities, dropped } = await spawnTables(other, archetypes, new Map([[steel, existingSteel]]));
227
+ expect(dropped).toBe(0);
228
+ expect(entities.flatMap(list => [...list])).toContain(existingSteel);
229
+ const read = await readAll(other);
230
+ // Steel isn't spawned again: the body's every entity value names the one already there.
231
+ expect(read.map(entry => entry.label)).toEqual(["body", "steel in other"]);
232
+ expect(read.find(entry => entry.label === "body")).toMatchObject({ parent: existingSteel, target: existingSteel, buddy: existingSteel, link: { to: existingSteel, weight: 2 }, friends: [existingSteel] });
233
+ });
234
+
235
+ it("let an entity existing maps stand for one outside the tables, so what it owns or points at is kept", async () => {
236
+ await using world = inline();
237
+ let steel = Entity.none;
238
+ await world.run(System.create(({ commands }) => {
239
+ steel = commands.spawn([pinned])();
240
+ commands.spawn([label, parent, target])({ [label]: "owned", [parent]: steel, [target]: steel });
241
+ }));
242
+ const { archetypes } = await world.run(tablesOf([label, parent, target]));
243
+ await using other = inline();
244
+ const existingSteel = (await spawnLabels(other, ["steel"])).get("steel") ?? Entity.none;
245
+ const { dropped } = await spawnTables(other, archetypes, new Map([[steel, existingSteel]]));
246
+ expect(dropped).toBe(0);
247
+ expect((await readAll(other)).find(entry => entry.label === "owned")).toMatchObject({ parent: existingSteel, target: existingSteel });
248
+ });
249
+
250
+ it("refuse existing mapping to an entity that isn't alive, spawning nothing", async () => {
251
+ await using world = inline();
252
+ const gone = (await spawnLabels(world, ["gone"])).get("gone") ?? Entity.none;
253
+ await world.run(System.create(({ commands }) => { commands.despawn(gone); }));
254
+ const { archetypes } = await world.run(tablesOf([label]));
255
+ await using source = inline();
256
+ const steel = (await spawnLabels(source, ["steel"])).get("steel") ?? Entity.none;
257
+ const { archetypes: tables } = await source.run(tablesOf([label]));
258
+ expect(archetypes).toEqual([]);
259
+ await expect(spawnTables(world, tables, new Map([[steel, gone]]))).rejects.toThrow(new RegExp(`as entity ${gone}, which isn't alive`));
260
+ expect(await readAll(world)).toEqual([]);
261
+ });
262
+
263
+ it("set the resources they're given at the sync point, beside the entities", async () => {
264
+ await using world = createWorld({ [workers]: 0, [score]: 7 });
265
+ given.tables = [];
266
+ given.resources = { [score]: 12, [title]: "loaded" };
267
+ try {
268
+ await world.run(spawnGiven);
269
+ }
270
+ finally {
271
+ given.resources = undefined;
272
+ }
273
+ const { resources } = await world.run(tablesOf([label], [score, title]));
274
+ expect(resources[score]).toBe(12);
275
+ expect(resources[title]).toBe("loaded");
276
+ });
277
+
278
+ it("refuse a resource only the world writes or a value its type doesn't take, spawning nothing", async () => {
279
+ await using world = inline();
280
+ const table: Table = { count: 1, entities: new Float64Array([1]), columns: { [label]: { kind: "values", values: ["kept out"] } } };
281
+ given.tables = [table];
282
+ try {
283
+ given.resources = { [clock]: { now: 0, delta: 0, tick: 0 } };
284
+ await expect(world.run(spawnGiven)).rejects.toThrow(/clock" is written only by the world/);
285
+ given.resources = { [score]: "twelve" };
286
+ await expect(world.run(spawnGiven)).rejects.toThrow(/score" is given a value its type doesn't take/);
287
+ given.resources = { [position]: [0, 0, 0] };
288
+ await expect(world.run(spawnGiven)).rejects.toThrow(/position" isn't a registered resource/);
289
+ }
290
+ finally {
291
+ given.resources = undefined;
292
+ }
293
+ expect(await readAll(world)).toEqual([]);
294
+ expect((await world.run(tablesOf([label], [score]))).resources[score]).toBe(0);
295
+ });
296
+
297
+ it("fail a column that doesn't fit its component's layout or its table's count", async () => {
298
+ await using world = inline();
299
+ const table = (columns: Table["columns"], count = 1): Table => ({ count, entities: new Float64Array(count).fill(1), columns });
300
+ given.tables = [table({ [position]: { kind: "bytes", bytes: new Uint8Array(8), stride: 8 } })];
301
+ await expect(world.run(spawnGiven)).rejects.toThrow(/stride of 12 bytes in this world, not 8/);
302
+ given.tables = [table({ [position]: { kind: "bytes", bytes: new Uint8Array(24), stride: 12 } })];
303
+ await expect(world.run(spawnGiven)).rejects.toThrow(/gives 24 bytes for 1 rows of 12/);
304
+ given.tables = [table({ [label]: { kind: "values", values: ["a", "b"] } })];
305
+ await expect(world.run(spawnGiven)).rejects.toThrow(/gives 2 values for 1 rows/);
306
+ given.tables = [table({ [team]: { kind: "values", values: ["red"] } })];
307
+ await expect(world.run(spawnGiven)).rejects.toThrow(/shared component, so its column is one value/);
308
+ // Two rows of one entity can't both be what a reference to it names.
309
+ given.tables = [table({ [friends]: { kind: "values", values: [[]] } }, 1), table({ [friends]: { kind: "values", values: [[]] } }, 1)];
310
+ await expect(world.run(spawnGiven)).rejects.toThrow(/entity 1 is in two rows/);
311
+ expect(await readAll(world)).toEqual([]);
312
+ });
313
+
314
+ it("keep to the archetypes holding where's shared values, each required as with requires it", async () => {
315
+ await using world = inline();
316
+ await world.run(System.create(({ commands }) => {
317
+ commands.spawn([team, label], { [team]: "red" })({ [label]: "red" });
318
+ commands.spawn([team, label, position], { [team]: "red" })({ [label]: "red placed", [position]: [1, 1, 1] });
319
+ commands.spawn([team, label], { [team]: "blue" })({ [label]: "blue" });
320
+ commands.spawn([label])({ [label]: "none" });
321
+ }));
322
+ const red = await world.run(System.create({ readonly: true, tables: { components: [label], where: { [team]: "red" } } }, ({ tables }): Tables => tables));
323
+ expect(red.archetypes.map(table => table.columns[label]).sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b))))
324
+ .toEqual([{ kind: "values", values: ["red placed"] }, { kind: "values", values: ["red"] }]);
325
+ const placed = await world.run(System.create({ readonly: true, tables: { components: [label], with: [position], where: { [team]: "red" } } }, ({ tables }): Tables => tables));
326
+ expect(placed.archetypes.map(table => table.columns[label])).toEqual([{ kind: "values", values: ["red placed"] }]);
327
+ // One system's tables follow its archetypes as they come and go.
328
+ const blue = System.create({ readonly: true, tables: { components: [label], where: { [team]: "blue" } } }, ({ tables }): Tables => tables);
329
+ expect((await world.run(blue)).archetypes.map(table => table.count)).toEqual([1]);
330
+ await world.run(System.create(({ commands }) => {
331
+ commands.spawn([team, label, position], { [team]: "blue" })({ [label]: "blue placed", [position]: [2, 2, 2] });
332
+ }));
333
+ expect((await world.run(blue)).archetypes.map(table => table.count)).toEqual([1, 1]);
334
+ });
335
+
336
+ it("give no table for a shared value no archetype holds", async () => {
337
+ await using world = inline();
338
+ await world.run(System.create(({ commands }) => { commands.spawn([team, label], { [team]: "red" })({ [label]: "red" }); }));
339
+ const blue = System.create({ readonly: true, tables: { components: [label], where: { [team]: "blue" } } }, ({ tables }): Tables => tables);
340
+ expect((await world.run(blue)).archetypes).toEqual([]);
341
+ await world.run(System.create(({ commands }) => { commands.spawn([team, label], { [team]: "blue" })({ [label]: "blue" }); }));
342
+ expect((await world.run(blue)).archetypes.map(table => table.columns[label])).toEqual([{ kind: "values", values: ["blue"] }]);
343
+ });
344
+
345
+ it("take only values of shared components in where", () => {
346
+ // @ts-expect-error: where names only shared components.
347
+ expect(() => System.create({ readonly: true, tables: { components: [label], where: { [label]: "a" } } }, () => {})).toThrow(/tables' where names "tablesTest\/label", which isn't a shared component/);
348
+ // @ts-expect-error: tables' where takes values, not predicates.
349
+ expect(() => System.create({ readonly: true, tables: { components: [label], where: { [team]: (value: string) => value === "red" } } }, () => {})).toThrow(/tables' where gives "tablesTest\/team" a predicate/);
350
+ // @ts-expect-error: a value of the component's type.
351
+ expect(() => System.create({ readonly: true, tables: { components: [label], where: { [team]: "green" } } }, () => {})).toThrow(/isn't one of its type's values/);
352
+ });
353
+
354
+ it("are read only by a readonly system without components", () => {
355
+ // @ts-expect-error: tables are read only by a readonly system, which the declaration's type says too.
356
+ expect(() => System.create({ tables: { components: [label] } }, () => {})).toThrow(/reads tables, which only a readonly system reads/);
357
+ });
358
+ });
359
+
360
+ describe("declarations", () => {
361
+ it("give every component and resource registered, with its schema and traits", () => {
362
+ const declared = Component.declarations().get(parent);
363
+ expect(declared?.type).toEqual({ type: "integer", format: "entity" });
364
+ expect(declared?.reference).toBe("owner");
365
+ expect(Resource.declarations().get(score)?.default).toBe(0);
366
+ const components: readonly symbol[] = [...Component.declarations().keys()];
367
+ expect(components.includes(score)).toBe(false);
368
+ });
369
+ });