@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.
- package/README.md +2 -2
- package/dist/implementation/compiler/body-access.d.ts +18 -1
- package/dist/implementation/compiler/body-access.js +104 -15
- package/dist/implementation/compiler/body-access.js.map +1 -1
- package/dist/implementation/compiler/describe.d.ts +1 -1
- package/dist/implementation/compiler/describe.js +3 -3
- package/dist/implementation/compiler/describe.js.map +1 -1
- package/dist/implementation/compiler/estree.d.ts +5 -2
- package/dist/implementation/compiler/estree.js +8 -5
- package/dist/implementation/compiler/estree.js.map +1 -1
- package/dist/implementation/compiler/inferred.js +4 -2
- package/dist/implementation/compiler/inferred.js.map +1 -1
- package/dist/implementation/compiler/signature.js +1 -1
- package/dist/implementation/compiler/transform.js +6 -5
- package/dist/implementation/compiler/transform.js.map +1 -1
- package/dist/implementation/world/activate.js +14 -2
- package/dist/implementation/world/activate.js.map +1 -1
- package/dist/implementation/world/buffer.d.ts +7 -0
- package/dist/implementation/world/buffer.js +7 -0
- package/dist/implementation/world/buffer.js.map +1 -1
- package/dist/implementation/world/cascade.d.ts +10 -3
- package/dist/implementation/world/cascade.js +48 -23
- package/dist/implementation/world/cascade.js.map +1 -1
- package/dist/implementation/world/commands.js +86 -2
- package/dist/implementation/world/commands.js.map +1 -1
- package/dist/implementation/world/create.js +53 -2
- package/dist/implementation/world/create.js.map +1 -1
- package/dist/implementation/world/entities.d.ts +2 -0
- package/dist/implementation/world/entities.js +6 -0
- package/dist/implementation/world/entities.js.map +1 -1
- package/dist/implementation/world/execute.d.ts +4 -4
- package/dist/implementation/world/execute.js +132 -83
- package/dist/implementation/world/execute.js.map +1 -1
- package/dist/implementation/world/finalizers.d.ts +2 -0
- package/dist/implementation/world/finalizers.js +24 -3
- package/dist/implementation/world/finalizers.js.map +1 -1
- package/dist/implementation/world/join.js +22 -4
- package/dist/implementation/world/join.js.map +1 -1
- package/dist/implementation/world/register.js +2 -2
- package/dist/implementation/world/resource-changes.d.ts +18 -0
- package/dist/implementation/world/resource-changes.js +66 -0
- package/dist/implementation/world/resource-changes.js.map +1 -0
- package/dist/implementation/world/rows.d.ts +1 -1
- package/dist/implementation/world/rows.js +23 -13
- package/dist/implementation/world/rows.js.map +1 -1
- package/dist/implementation/world/run-changes.d.ts +38 -0
- package/dist/implementation/world/run-changes.js +138 -0
- package/dist/implementation/world/run-changes.js.map +1 -0
- package/dist/implementation/world/runner.js +19 -15
- package/dist/implementation/world/runner.js.map +1 -1
- package/dist/implementation/world/sharing.js +1 -1
- package/dist/implementation/world/staging.d.ts +4 -0
- package/dist/implementation/world/staging.js +15 -3
- package/dist/implementation/world/staging.js.map +1 -1
- package/dist/implementation/world/state.d.ts +13 -0
- package/dist/implementation/world/state.js +13 -1
- package/dist/implementation/world/state.js.map +1 -1
- package/dist/implementation/world/synchronize.js +72 -22
- package/dist/implementation/world/synchronize.js.map +1 -1
- package/dist/implementation/world/tables-read.d.ts +9 -0
- package/dist/implementation/world/tables-read.js +72 -0
- package/dist/implementation/world/tables-read.js.map +1 -0
- package/dist/implementation/world/tables.d.ts +10 -0
- package/dist/implementation/world/tables.js +401 -0
- package/dist/implementation/world/tables.js.map +1 -0
- package/dist/implementation/world/thread-observer.js +1 -1
- package/dist/implementation/world/thread-observer.js.map +1 -1
- package/dist/implementation/world/thread-systems.js +11 -6
- package/dist/implementation/world/thread-systems.js.map +1 -1
- package/dist/implementation/world/up.js +7 -1
- package/dist/implementation/world/up.js.map +1 -1
- package/dist/implementation/world/where.d.ts +8 -0
- package/dist/implementation/world/where.js +40 -11
- package/dist/implementation/world/where.js.map +1 -1
- package/dist/index.d.ts +8 -28
- package/dist/index.js +4 -9
- package/dist/index.js.map +1 -1
- package/dist/interface/component/declarations.d.ts +4 -0
- package/dist/interface/component/declarations.js +8 -0
- package/dist/interface/component/declarations.js.map +1 -0
- package/dist/interface/component/declared.d.ts +12 -0
- package/dist/interface/component/declared.js +2 -0
- package/dist/interface/component/declared.js.map +1 -0
- package/dist/interface/component/index.d.ts +2 -0
- package/dist/interface/component/index.js +1 -1
- package/dist/interface/component/index.js.map +1 -1
- package/dist/interface/finalizer/body.d.ts +18 -3
- package/dist/interface/finalizer/create.d.ts +7 -5
- package/dist/interface/finalizer/create.js +8 -5
- package/dist/interface/finalizer/create.js.map +1 -1
- package/dist/interface/finalizer/declaration.d.ts +4 -2
- package/dist/interface/indexes/declaration-of.d.ts +17 -0
- package/dist/interface/indexes/declaration-of.js +20 -0
- package/dist/interface/indexes/declaration-of.js.map +1 -0
- package/dist/interface/indexes/index.d.ts +1 -0
- package/dist/interface/indexes/index.js +1 -0
- package/dist/interface/indexes/index.js.map +1 -1
- package/dist/interface/registry/names.d.ts +2 -0
- package/dist/interface/registry/names.js +9 -0
- package/dist/interface/registry/names.js.map +1 -1
- package/dist/interface/registry/schedule.d.ts +31 -4
- package/dist/interface/registry/schedule.js +185 -24
- package/dist/interface/registry/schedule.js.map +1 -1
- package/dist/interface/resource/declarations.d.ts +4 -0
- package/dist/interface/resource/declarations.js +8 -0
- package/dist/interface/resource/declarations.js.map +1 -0
- package/dist/interface/resource/index.d.ts +2 -0
- package/dist/interface/resource/index.js +1 -1
- package/dist/interface/resource/index.js.map +1 -1
- package/dist/interface/system/access.d.ts +15 -0
- package/dist/interface/system/body.d.ts +11 -6
- package/dist/interface/system/check.d.ts +8 -1
- package/dist/interface/system/check.js +42 -12
- package/dist/interface/system/check.js.map +1 -1
- package/dist/interface/system/commands.d.ts +37 -0
- package/dist/interface/system/create-resolved.js +1 -1
- package/dist/interface/system/create-resolved.js.map +1 -1
- package/dist/interface/system/create.d.ts +31 -15
- package/dist/interface/system/create.js +8 -7
- package/dist/interface/system/create.js.map +1 -1
- package/dist/interface/system/declaration.d.ts +9 -2
- package/dist/interface/system/describe.d.ts +120 -0
- package/dist/interface/system/describe.js +116 -0
- package/dist/interface/system/describe.js.map +1 -0
- package/dist/interface/system/index.d.ts +1 -0
- package/dist/interface/system/index.js +1 -0
- package/dist/interface/system/index.js.map +1 -1
- package/dist/interface/system/leaf.d.ts +6 -0
- package/dist/interface/system/render.js +1 -0
- package/dist/interface/system/render.js.map +1 -1
- package/dist/interface/system/resolve.d.ts +5 -1
- package/dist/interface/system/resolve.js +19 -1
- package/dist/interface/system/resolve.js.map +1 -1
- package/dist/interface/system/shared-only.d.ts +7 -0
- package/dist/interface/system/shared-only.js +2 -0
- package/dist/interface/system/shared-only.js.map +1 -0
- package/dist/interface/system/tables.d.ts +28 -0
- package/dist/interface/system/tables.js +2 -0
- package/dist/interface/system/tables.js.map +1 -0
- package/dist/interface/world/index.d.ts +1 -1
- package/dist/interface/world/table/index.d.ts +27 -0
- package/dist/interface/world/table/index.js +3 -0
- package/dist/interface/world/table/index.js.map +1 -0
- package/dist/interface/world/table/table.d.ts +11 -0
- package/dist/interface/world/table/table.js +2 -0
- package/dist/interface/world/table/table.js.map +1 -0
- package/dist/interface/world/world.d.ts +25 -10
- package/dist/world.d.ts +27 -0
- package/dist/world.js +27 -0
- package/dist/world.js.map +1 -0
- package/package.json +11 -2
- package/src/exports.test.ts +1 -1
- package/src/guide/00-keys.ts +1 -1
- package/src/guide/01-resources.md +1 -1
- package/src/guide/01-resources.ts +1 -1
- package/src/guide/02-entities.ts +2 -2
- package/src/guide/03-systems.md +2 -2
- package/src/guide/03-systems.out +1 -1
- package/src/guide/03-systems.ts +3 -3
- package/src/guide/04-ticks.md +1 -1
- package/src/guide/04-ticks.ts +3 -3
- package/src/guide/05-scheduling.ts +1 -1
- package/src/guide/06-sync-points.md +18 -7
- package/src/guide/06-sync-points.out +1 -2
- package/src/guide/06-sync-points.ts +10 -6
- package/src/guide/07-change-filters.ts +2 -2
- package/src/guide/08-input.ts +1 -1
- package/src/guide/09-rules.ts +1 -1
- package/src/guide/11-traits.ts +3 -3
- package/src/guide/12-lookups.ts +2 -2
- package/src/guide/13-indexes.ts +4 -4
- package/src/guide/14-kernels.ts +1 -1
- package/src/guide/15-reductions.ts +1 -1
- package/src/guide/16-gpu.ts +1 -1
- package/src/guide/17-hierarchies.ts +1 -1
- package/src/guide/18-identity.out +1 -1
- package/src/guide/19-threads.md +4 -2
- package/src/guide/19-threads.ts +2 -2
- package/src/guide/20-shared.ts +3 -3
- package/src/guide/21-dom.md +2 -2
- package/src/guide/21-dom.ts +1 -1
- package/src/guide/22-canvas.md +1 -1
- package/src/guide/22-canvas.ts +1 -1
- package/src/guide/CLAUDE.md +1 -1
- package/src/guide/guide.test.ts +9 -154
- package/src/implementation/compiler/body-access.ts +102 -12
- package/src/implementation/compiler/describe.test-helper.ts +1 -1
- package/src/implementation/compiler/describe.test.ts +1 -1
- package/src/implementation/compiler/describe.ts +3 -3
- package/src/implementation/compiler/estree.ts +9 -6
- package/src/implementation/compiler/hygiene.test.ts +1 -1
- package/src/implementation/compiler/inferred.test.ts +1 -1
- package/src/implementation/compiler/inferred.ts +4 -2
- package/src/implementation/compiler/plugin.test.ts +2 -2
- package/src/implementation/compiler/signature.ts +1 -1
- package/src/implementation/compiler/transform.test.ts +43 -23
- package/src/implementation/compiler/transform.ts +6 -4
- package/src/implementation/world/activate.ts +11 -2
- package/src/implementation/world/batches.test.ts +3 -3
- package/src/implementation/world/buffer.ts +7 -0
- package/src/implementation/world/cascade.ts +46 -22
- package/src/implementation/world/change-blocks.test.ts +1 -1
- package/src/implementation/world/commands.ts +76 -2
- package/src/implementation/world/create.test.ts +8 -8
- package/src/implementation/world/create.ts +57 -2
- package/src/implementation/world/defaults.test.ts +1 -1
- package/src/implementation/world/despawn-where.test.ts +287 -0
- package/src/implementation/world/entities.ts +6 -0
- package/src/implementation/world/execute.ts +125 -78
- package/src/implementation/world/finalizers.test.ts +1 -1
- package/src/implementation/world/finalizers.ts +27 -4
- package/src/implementation/world/ids.test.ts +1 -1
- package/src/implementation/world/index-changed.test.ts +2 -2
- package/src/implementation/world/index-joined.test.ts +5 -5
- package/src/implementation/world/index-order.test.ts +1 -1
- package/src/implementation/world/indexes.test.ts +2 -1
- package/src/implementation/world/join.test.ts +23 -4
- package/src/implementation/world/join.ts +26 -4
- package/src/implementation/world/joined-observer.test.ts +1 -1
- package/src/implementation/world/joining-worker.fixture.ts +1 -1
- package/src/implementation/world/joining.fixture.ts +1 -1
- package/src/implementation/world/joining.test-helper.ts +1 -1
- package/src/implementation/world/kernel-spawns.test.ts +1 -1
- package/src/implementation/world/names.test.ts +1 -2
- package/src/implementation/world/observe-factories.test.ts +2 -2
- package/src/implementation/world/observe-ticks-cases.test.ts +1 -1
- package/src/implementation/world/observe.test-helper.ts +1 -1
- package/src/implementation/world/observer-contained-worker.fixture.ts +1 -1
- package/src/implementation/world/observer-contained.test-helper.ts +1 -1
- package/src/implementation/world/observer-contained.test.ts +2 -2
- package/src/implementation/world/observer-joined.test-helper.ts +4 -4
- package/src/implementation/world/observer-joined.test.ts +23 -15
- package/src/implementation/world/observer.browser.test.ts +2 -2
- package/src/implementation/world/observer.test.ts +8 -8
- package/src/implementation/world/optional-workers.fixture.ts +1 -1
- package/src/implementation/world/optional.test.ts +7 -7
- package/src/implementation/world/perf.md +3 -1
- package/src/implementation/world/reclaim.test.ts +3 -3
- package/src/implementation/world/register.ts +2 -2
- package/src/implementation/world/remote-objects.test-helper.ts +1 -1
- package/src/implementation/world/resource-changes.test.ts +61 -0
- package/src/implementation/world/resource-changes.ts +67 -0
- package/src/implementation/world/rows.ts +24 -13
- package/src/implementation/world/rules.test.ts +37 -0
- package/src/implementation/world/run-changes.test.ts +88 -0
- package/src/implementation/world/run-changes.ts +160 -0
- package/src/implementation/world/run-results.test.ts +10 -10
- package/src/implementation/world/runner.ts +20 -16
- package/src/implementation/world/shared-gpu.browser.test.ts +2 -2
- package/src/implementation/world/shared-kernels.test.ts +2 -2
- package/src/implementation/world/shared-workers.fixture.ts +2 -2
- package/src/implementation/world/shared.test.ts +10 -10
- package/src/implementation/world/sharing.ts +1 -1
- package/src/implementation/world/staging.ts +16 -3
- package/src/implementation/world/state.ts +19 -0
- package/src/implementation/world/sync-points.test.ts +135 -0
- package/src/implementation/world/synchronize.ts +65 -18
- package/src/implementation/world/tables-read.ts +70 -0
- package/src/implementation/world/tables.test.ts +369 -0
- package/src/implementation/world/tables.ts +368 -0
- package/src/implementation/world/tails.test.ts +1 -1
- package/src/implementation/world/target.test.ts +2 -2
- package/src/implementation/world/thread-observer.ts +1 -1
- package/src/implementation/world/thread-systems.test-helper.ts +3 -3
- package/src/implementation/world/thread-systems.ts +11 -6
- package/src/implementation/world/up.test.ts +13 -1
- package/src/implementation/world/up.ts +6 -1
- package/src/implementation/world/where.test.ts +4 -4
- package/src/implementation/world/where.ts +38 -10
- package/src/implementation/world/workers.fixture.ts +2 -2
- package/src/index.ts +4 -29
- package/src/interface/component/declarations.ts +11 -0
- package/src/interface/component/declared.ts +9 -0
- package/src/interface/component/index.ts +2 -0
- package/src/interface/finalizer/body.ts +13 -3
- package/src/interface/finalizer/create.test.ts +3 -1
- package/src/interface/finalizer/create.ts +12 -7
- package/src/interface/finalizer/declaration.ts +4 -2
- package/src/interface/index.type-test.ts +2 -2
- package/src/interface/indexes/create.test.ts +10 -0
- package/src/interface/indexes/declaration-of.ts +34 -0
- package/src/interface/indexes/index.ts +1 -0
- package/src/interface/indexes/index.type-test.ts +1 -1
- package/src/interface/registry/names.ts +8 -0
- package/src/interface/registry/schedule.ts +200 -27
- package/src/interface/resource/declarations.ts +11 -0
- package/src/interface/resource/index.ts +2 -0
- package/src/interface/system/access.ts +16 -0
- package/src/interface/system/body.ts +7 -5
- package/src/interface/system/check.ts +37 -10
- package/src/interface/system/commands.ts +33 -0
- package/src/interface/system/commands.type-test.ts +31 -0
- package/src/interface/system/create-resolved.ts +1 -1
- package/src/interface/system/create.test.ts +21 -13
- package/src/interface/system/create.ts +24 -15
- package/src/interface/system/declaration.ts +9 -2
- package/src/interface/system/describe.test.ts +79 -0
- package/src/interface/system/describe.ts +122 -0
- package/src/interface/system/index.ts +1 -0
- package/src/interface/system/leaf.ts +6 -0
- package/src/interface/system/lists.type-test.ts +1 -1
- package/src/interface/system/optional.type-test.ts +1 -1
- package/src/interface/system/render.ts +1 -0
- package/src/interface/system/resolve.ts +25 -2
- package/src/interface/system/shared-only.ts +5 -0
- package/src/interface/system/shared.type-test.ts +1 -1
- package/src/interface/system/system.type-test.ts +26 -20
- package/src/interface/system/tables.ts +27 -0
- package/src/interface/system/up.type-test.ts +3 -3
- package/src/interface/world/index.ts +2 -2
- package/src/interface/world/run.type-test.ts +14 -4
- package/src/interface/world/table/index.ts +20 -0
- package/src/interface/world/table/table.ts +12 -0
- package/src/interface/world/world.ts +23 -10
- package/src/observe/entity.ts +2 -2
- package/src/observe/resource.ts +2 -2
- package/src/observe/select.ts +4 -4
- package/src/observe/world-state.ts +3 -3
- package/src/perftest/benchmarks.ts +19 -1
- package/src/perftest/indexes.ts +3 -3
- package/src/perftest/loop.ts +1 -1
- package/src/perftest/observer.ts +4 -4
- package/src/perftest/reads.ts +2 -2
- package/src/perftest/remote.ts +5 -5
- package/src/perftest/strings.ts +1 -1
- package/src/perftest/structural.ts +20 -0
- package/src/perftest/suite.ts +1 -1
- package/src/perftest/tables.ts +107 -0
- package/src/world.test.ts +2 -2
- package/src/world.ts +32 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
import { indexDefinitionOf } from '../registry/indexes.js';
|
|
3
|
+
import { labelOf } from '../registry/names.js';
|
|
4
|
+
import { requireDefinition } from '../registry/program.js';
|
|
5
|
+
import { isLeaf, placementsOf, scheduleOf, type Placement } from '../registry/schedule.js';
|
|
6
|
+
import type { Leaf } from './leaf.js';
|
|
7
|
+
import type { System } from './system.js';
|
|
8
|
+
|
|
9
|
+
type Key = symbol;
|
|
10
|
+
|
|
11
|
+
const none: readonly never[] = [];
|
|
12
|
+
|
|
13
|
+
/** A leaf as the system value it is. */
|
|
14
|
+
const asSystem = (leaf: Leaf): System<unknown, unknown> => leaf;
|
|
15
|
+
|
|
16
|
+
/** What the build inferred one system uses, and what placed it, as plain data. */
|
|
17
|
+
const describeLeaf = (leaf: Leaf, placements: readonly Placement[]) => {
|
|
18
|
+
const { components, resources, lookup, up, order } = leaf.declaration;
|
|
19
|
+
const { commands } = leaf;
|
|
20
|
+
return {
|
|
21
|
+
/** The system, the value its constructor returned. */
|
|
22
|
+
system: asSystem(leaf),
|
|
23
|
+
name: leaf.name,
|
|
24
|
+
components: {
|
|
25
|
+
read: [...components?.read ?? none, ...up === undefined ? none : [up.through]],
|
|
26
|
+
write: components?.write ?? none,
|
|
27
|
+
/** What it requires without reading: `with` members it doesn't use, `added` and `changed` filters' among them. */
|
|
28
|
+
has: components?.has ?? none,
|
|
29
|
+
optional: components?.optional ?? none,
|
|
30
|
+
not: components?.not ?? none,
|
|
31
|
+
added: components?.added ?? none,
|
|
32
|
+
changed: components?.changed ?? none,
|
|
33
|
+
/** What it reads of each row's parent, through `up`. */
|
|
34
|
+
parent: up?.read ?? none,
|
|
35
|
+
},
|
|
36
|
+
resources: {
|
|
37
|
+
read: resources?.read ?? none,
|
|
38
|
+
write: resources?.write ?? none,
|
|
39
|
+
reduce: resources?.reduce ?? none,
|
|
40
|
+
changed: resources?.changed ?? none,
|
|
41
|
+
is: resources?.is ?? none,
|
|
42
|
+
not: resources?.not ?? none,
|
|
43
|
+
},
|
|
44
|
+
lookups: Object.entries(lookup ?? {}).map(([name, used]) => ({
|
|
45
|
+
name,
|
|
46
|
+
/** The label of the index it looks up through, or null for a lookup by entity. */
|
|
47
|
+
index: "index" in used && used.index !== undefined ? indexDefinitionOf(used.index)?.name ?? null : null,
|
|
48
|
+
read: used.read ?? none,
|
|
49
|
+
})),
|
|
50
|
+
/** The label of the sorted index ordering its rows, or null. */
|
|
51
|
+
order: indexDefinitionOf(order)?.name ?? null,
|
|
52
|
+
/** What its commands name by kind, and whether it despawns; null where the build couldn't follow them. */
|
|
53
|
+
commands: commands === null ? null : {
|
|
54
|
+
spawn: commands?.spawn ?? none,
|
|
55
|
+
add: commands?.add ?? none,
|
|
56
|
+
remove: commands?.remove ?? none,
|
|
57
|
+
despawn: commands?.despawn === true,
|
|
58
|
+
},
|
|
59
|
+
/** Whether it takes its row whole (`components: { whole: true }`), every member written. */
|
|
60
|
+
whole: components?.whole === true,
|
|
61
|
+
/** The systems it follows directly, with what ordered it after each and the keys that need that order. */
|
|
62
|
+
after: placements.map(placement => ({ system: asSystem(placement.leaf), name: placement.leaf.name, because: placement.because, conflicts: placement.conflicts })),
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
type Described = ReturnType<typeof describeLeaf>;
|
|
67
|
+
|
|
68
|
+
const keys = (list: readonly Key[]): string => list.map(labelOf).join(" ");
|
|
69
|
+
|
|
70
|
+
/** A system's lines of text, after its name in a column of `width`. */
|
|
71
|
+
const linesOf = (system: Described, width: number): readonly string[] => {
|
|
72
|
+
const lines: string[] = [];
|
|
73
|
+
const add = (label: string, text: string): void => { if (text !== "") lines.push(`${label.padEnd(10)}${text}`); };
|
|
74
|
+
const { components, resources, commands } = system;
|
|
75
|
+
add("reads", keys(components.read));
|
|
76
|
+
add("writes", `${keys(components.write)}${system.whole ? " (its whole row)" : ""}`);
|
|
77
|
+
add("requires", [keys(components.has), components.not.length === 0 ? "" : `not ${keys(components.not)}`, components.optional.length === 0 ? "" : `optional ${keys(components.optional)}`].filter(text => text !== "").join(", "));
|
|
78
|
+
add("filters", [...components.added.map(key => `added ${labelOf(key)}`), ...components.changed.map(key => `changed ${labelOf(key)}`), ...resources.changed.map(key => `changed ${labelOf(key)}`)].join(", "));
|
|
79
|
+
add("parent", keys(components.parent));
|
|
80
|
+
add("resources", [resources.read.length === 0 ? "" : `reads ${keys(resources.read)}`, resources.write.length === 0 ? "" : `writes ${keys(resources.write)}`,
|
|
81
|
+
resources.reduce.length === 0 ? "" : `reduces ${keys(resources.reduce)}`, resources.is.length === 0 ? "" : `while ${keys(resources.is)}`,
|
|
82
|
+
resources.not.length === 0 ? "" : `unless ${keys(resources.not)}`].filter(text => text !== "").join(", "));
|
|
83
|
+
for (const lookup of system.lookups) add("looks up", `${lookup.name}${lookup.index === null ? "" : ` through ${lookup.index}`}${lookup.read.length === 0 ? "" : `: ${keys(lookup.read)}`}`);
|
|
84
|
+
if (system.order !== null) add("ordered", `by ${system.order}`);
|
|
85
|
+
if (commands === null) add("commands", "unknown: the build couldn't follow them");
|
|
86
|
+
else add("commands", [commands.spawn.length === 0 ? "" : `spawn ${keys(commands.spawn)}`, commands.add.length === 0 ? "" : `add ${keys(commands.add)}`,
|
|
87
|
+
commands.remove.length === 0 ? "" : `remove ${keys(commands.remove)}`, commands.despawn ? "despawn" : ""].filter(text => text !== "").join(", "));
|
|
88
|
+
add("after", system.after.map(placement => `${placement.name} (${placement.because}${placement.conflicts.length === 0 ? "" : `: ${keys(placement.conflicts)}`})`).join(", "));
|
|
89
|
+
const indent = " ".repeat(width);
|
|
90
|
+
return lines.length === 0 ? [system.name] : lines.map((line, i) => `${i === 0 ? system.name.padEnd(width) : indent}${line}`);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Describes how a system or composite runs: its tiers in run order, systems in one tier having no ordering path between
|
|
95
|
+
* them, each with the sync point after it, if any, and its readonly systems apart, which run after its last sync
|
|
96
|
+
* point. For each system, what the build inferred it reads, writes, looks up and commands, whether it takes its row
|
|
97
|
+
* whole, and the systems it follows directly, with what ordered it after each. `text` gives it all to read; the rest
|
|
98
|
+
* is data to assert on.
|
|
99
|
+
*/
|
|
100
|
+
export const describe = <I, R>(system: System<I, R>) => {
|
|
101
|
+
const definition = requireDefinition(system);
|
|
102
|
+
const schedule = scheduleOf(definition);
|
|
103
|
+
const placements = placementsOf(definition);
|
|
104
|
+
const tiers = schedule.tiers.map((tier, t) => ({
|
|
105
|
+
systems: tier.map(leaf => describeLeaf(leaf, placements.get(leaf) ?? none)),
|
|
106
|
+
/**
|
|
107
|
+
* Why a sync point follows the tier, applying the commands so far: each system whose commands may change what a
|
|
108
|
+
* later one that follows it uses, through `keys`, none listed where it despawns or a cascade could; null for none.
|
|
109
|
+
*/
|
|
110
|
+
sync: schedule.syncs[t]?.map(reason => ({ from: asSystem(reason.from), to: asSystem(reason.to), keys: reason.keys })) ?? null,
|
|
111
|
+
}));
|
|
112
|
+
const readonly = schedule.observer.flatMap(member => isLeaf(member) ? [describeLeaf(member, none)] : []);
|
|
113
|
+
const width = Math.max(0, ...[...tiers.flatMap(tier => tier.systems), ...readonly].map(described => described.name.length)) + 2;
|
|
114
|
+
const text = [
|
|
115
|
+
...tiers.flatMap((tier, t) => [
|
|
116
|
+
...tier.systems.flatMap((described, i) => linesOf(described, width).map((line, l) => `${(i === 0 && l === 0 ? `tier ${t}` : "").padEnd(8)}${line}`)),
|
|
117
|
+
...(tier.sync ?? none).map((reason, r) => `${(r === 0 ? "sync" : "").padEnd(8)}${reason.from.name}'s commands${reason.keys.length === 0 ? "" : ` of ${keys(reason.keys)}`}, for ${reason.to.name}`),
|
|
118
|
+
]),
|
|
119
|
+
...readonly.length === 0 ? none : ["readonly", ...readonly.flatMap(described => linesOf(described, width).map(line => `${"".padEnd(8)}${line}`))],
|
|
120
|
+
].map(line => line.trimEnd()).join("\n");
|
|
121
|
+
return { tiers, readonly, text };
|
|
122
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
2
|
import type { Schema } from '../schema/schema.js';
|
|
3
|
+
import type { CommandLists } from './access.js';
|
|
3
4
|
import type { Resolved } from './declaration.js';
|
|
4
5
|
import type { Name } from './name.js';
|
|
5
6
|
|
|
@@ -17,4 +18,9 @@ export type Leaf = {
|
|
|
17
18
|
readonly declaration: Resolved;
|
|
18
19
|
readonly input: InputSchema | undefined;
|
|
19
20
|
readonly body: (...parameters: never[]) => void;
|
|
21
|
+
/**
|
|
22
|
+
* What its body's commands name, as the build inferred them: null where it couldn't follow them, undefined for
|
|
23
|
+
* none. Apart from its declaration, so a module evaluated again with other commands declares the same system.
|
|
24
|
+
*/
|
|
25
|
+
readonly commands?: CommandLists | null;
|
|
20
26
|
};
|
|
@@ -23,7 +23,7 @@ export const Particle = [position, velocity] as const;
|
|
|
23
23
|
expectType<Equal<typeof Particle, readonly [typeof position, typeof velocity]>>();
|
|
24
24
|
export const one: readonly Component.Key[] = Particle;
|
|
25
25
|
export const move = System.create({ components: { with: [...Particle] } }, row => { row[position] += row[velocity]; });
|
|
26
|
-
export const read = System.create({
|
|
26
|
+
export const read = System.create({ readonly: true, components: { with: [...Particle] } }, row => { void row[velocity]; });
|
|
27
27
|
|
|
28
28
|
// Declared without `as const`, a list's keys widen to `symbol`, which no list of components takes.
|
|
29
29
|
const loose = [position, velocity];
|
|
@@ -36,7 +36,7 @@ System.create({
|
|
|
36
36
|
}, row => { void row[velocity]; });
|
|
37
37
|
|
|
38
38
|
// An observer system reads its optional components and writes nothing.
|
|
39
|
-
System.create({
|
|
39
|
+
System.create({ readonly: true, components: { with: [position], optional: [velocity] } }, row => {
|
|
40
40
|
void row[velocity];
|
|
41
41
|
// @ts-expect-error an observer system's row is read-only
|
|
42
42
|
row[velocity] = 1;
|
|
@@ -96,6 +96,7 @@ export const render = <const D extends RenderDeclaration, B extends Body<D>>(
|
|
|
96
96
|
},
|
|
97
97
|
input: undefined,
|
|
98
98
|
body,
|
|
99
|
+
commands: undefined,
|
|
99
100
|
};
|
|
100
101
|
if (uses.write.length > 0 || uses.reduce.length > 0) throw new Error(`${what} writes or reduces into resources; a render system only reads.`);
|
|
101
102
|
checkLeaf(system);
|
|
@@ -3,7 +3,7 @@ import { takeAccessOf } from '../registry/ids.js';
|
|
|
3
3
|
import { indexDefinitionOf } from '../registry/indexes.js';
|
|
4
4
|
import { declarationOf, labelOf } from '../registry/names.js';
|
|
5
5
|
import { isString, isStruct } from '../registry/schemas.js';
|
|
6
|
-
import type { ComponentAccess, ComponentLists, Lookup, SystemLookup, SystemLookupLists } from './access.js';
|
|
6
|
+
import type { CommandLists, ComponentAccess, ComponentLists, Lookup, SystemLookup, SystemLookupLists } from './access.js';
|
|
7
7
|
|
|
8
8
|
type Key = symbol;
|
|
9
9
|
|
|
@@ -23,11 +23,23 @@ export type Access = {
|
|
|
23
23
|
readonly up: readonly Key[] | null;
|
|
24
24
|
/** The resources the predicates of its declaration's `where` read; absent without predicates. */
|
|
25
25
|
readonly where?: readonly Key[] | null;
|
|
26
|
+
/** What its commands name, keys or lists of them, by kind, and whether it despawns; absent where no build said. */
|
|
27
|
+
readonly commands?: unknown;
|
|
26
28
|
readonly at: string;
|
|
27
29
|
};
|
|
28
30
|
|
|
31
|
+
type Named = readonly (Key | readonly Key[])[];
|
|
32
|
+
type Commanded = { readonly spawn: Named; readonly add: Named; readonly remove: Named; readonly despawn: boolean };
|
|
33
|
+
|
|
29
34
|
const isKeys = (value: unknown): value is readonly Key[] => Array.isArray(value) && value.every(key => typeof key === "symbol");
|
|
30
35
|
|
|
36
|
+
const isNamed = (value: unknown): value is Named => Array.isArray(value) && value.every(item => typeof item === "symbol" || isKeys(item));
|
|
37
|
+
|
|
38
|
+
/** A binding a command named that holds neither a key nor a list of them, as one declared below its system, isn't followed. */
|
|
39
|
+
const isCommanded = (value: unknown): value is Commanded => typeof value === "object" && value !== null
|
|
40
|
+
&& isNamed(Reflect.get(value, "spawn")) && isNamed(Reflect.get(value, "add")) && isNamed(Reflect.get(value, "remove"))
|
|
41
|
+
&& typeof Reflect.get(value, "despawn") === "boolean";
|
|
42
|
+
|
|
31
43
|
const isUsed = (value: unknown): value is Used => typeof value === "object" && value !== null
|
|
32
44
|
&& isKeys(Reflect.get(value, "read")) && isKeys(Reflect.get(value, "write"))
|
|
33
45
|
&& (Reflect.get(value, "mutate") === undefined || isKeys(Reflect.get(value, "mutate")));
|
|
@@ -106,7 +118,7 @@ export const componentLists = (components: ComponentAccess, used: Used | null, r
|
|
|
106
118
|
: used.write;
|
|
107
119
|
const read = used === null ? without(held, write) : without(used.read, write);
|
|
108
120
|
const has = [...without(members, read, write), ...without(filtered, read, write)];
|
|
109
|
-
return compact({ read, write, has, optional, not: components.not, added: components.added, changed: components.changed, where, whereReads });
|
|
121
|
+
return compact({ read, write, has, optional, not: components.not, added: components.added, changed: components.changed, where, whereReads, whole: components.whole });
|
|
110
122
|
};
|
|
111
123
|
|
|
112
124
|
/**
|
|
@@ -161,5 +173,16 @@ export const whereReadsOf = (what: string, components: ComponentAccess | undefin
|
|
|
161
173
|
return reads;
|
|
162
174
|
};
|
|
163
175
|
|
|
176
|
+
/** What a body's commands name, as the world takes it: null where the build couldn't follow them, nothing for none. */
|
|
177
|
+
export const commandLists = (access: Access): CommandLists | null | undefined => {
|
|
178
|
+
const { commands } = access;
|
|
179
|
+
if (commands === undefined) return undefined;
|
|
180
|
+
if (!isCommanded(commands)) return null;
|
|
181
|
+
if (!commands.despawn && commands.spawn.length + commands.add.length + commands.remove.length === 0) return undefined;
|
|
182
|
+
const flat = (named: Named): Key[] => [...new Set(named.flat())];
|
|
183
|
+
const lists = compact({ spawn: flat(commands.spawn), add: flat(commands.add), remove: flat(commands.remove), despawn: commands.despawn ? true as const : undefined });
|
|
184
|
+
return Object.keys(lists).length === 0 ? undefined : lists;
|
|
185
|
+
};
|
|
186
|
+
|
|
164
187
|
/** What a body reads of its parent's values. */
|
|
165
188
|
export const upReads = (what: string, access: Access): readonly Key[] => followed(what, access.up, "its parent's values", access);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
import type { Shared } from '../component/value.js';
|
|
3
|
+
|
|
4
|
+
/** A `where`'s keys that aren't shared components, each an error: `where` names only shared components. */
|
|
5
|
+
export type SharedOnly<W> = { readonly [K in Exclude<keyof W, Shared>]: { readonly "error: where names only shared components, which each archetype holds one value of": K } };
|
|
@@ -126,7 +126,7 @@ System.create({ components: { with: [health], optional: [team] } }, row => {
|
|
|
126
126
|
// @ts-expect-error nor an optional one
|
|
127
127
|
row[team] = "red";
|
|
128
128
|
});
|
|
129
|
-
System.create({
|
|
129
|
+
System.create({ readonly: true, components: { with: [team] } }, row => {
|
|
130
130
|
check<Equal<typeof row[typeof team], "red" | "blue">>();
|
|
131
131
|
});
|
|
132
132
|
|
|
@@ -367,7 +367,7 @@ System.render({ shader: { accessors: { place: position } } }, gpu => { void gpu.
|
|
|
367
367
|
|
|
368
368
|
// Observer systems read what a sync point left, after it: rows, resources, lookups and change filters, and no commands.
|
|
369
369
|
export const watchPositions = System.create({
|
|
370
|
-
|
|
370
|
+
readonly: true,
|
|
371
371
|
components: { with: [position], changed: [position] },
|
|
372
372
|
lookup: { targets: { with: [velocity] } },
|
|
373
373
|
}, (row, context) => {
|
|
@@ -379,33 +379,39 @@ export const watchPositions = System.create({
|
|
|
379
379
|
void context.commands;
|
|
380
380
|
});
|
|
381
381
|
check<Equal<typeof watchPositions, System>>();
|
|
382
|
-
export const watchTime = System.create({
|
|
382
|
+
export const watchTime = System.create({ readonly: true, resources: { changed: [time] } }, ({ resources }) => { void resources[time]; });
|
|
383
383
|
// A composite may hold observer members, which run after its sync point.
|
|
384
384
|
export const withWatch = System.create([integrate, watchTime]);
|
|
385
385
|
check<Equal<typeof withWatch, System>>();
|
|
386
|
-
// @ts-expect-error observer
|
|
387
|
-
System.create({ observer:
|
|
388
|
-
|
|
389
|
-
|
|
386
|
+
// @ts-expect-error observer isn't a declaration's: readonly is
|
|
387
|
+
System.create({ observer: true }, () => {});
|
|
388
|
+
// @ts-expect-error readonly is true or left out
|
|
389
|
+
System.create({ readonly: false }, () => {});
|
|
390
|
+
System.create({ readonly: true, components: { with: [position] } }, (row, { resources }) => {
|
|
391
|
+
// @ts-expect-error a readonly system writes no components
|
|
390
392
|
row[position] = 0;
|
|
391
|
-
// @ts-expect-error
|
|
393
|
+
// @ts-expect-error a readonly system writes no resources
|
|
392
394
|
resources[score] = 0;
|
|
393
395
|
});
|
|
394
|
-
System.create({
|
|
395
|
-
// @ts-expect-error
|
|
396
|
+
System.create({ readonly: true }, context => {
|
|
397
|
+
// @ts-expect-error a readonly system reduces into nothing
|
|
396
398
|
void context.reduce;
|
|
399
|
+
// @ts-expect-error a readonly system has no commands
|
|
400
|
+
void context.commands;
|
|
397
401
|
});
|
|
398
|
-
// @ts-expect-error
|
|
399
|
-
System.create({
|
|
400
|
-
// @ts-expect-error
|
|
401
|
-
System.create({
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
System.create({
|
|
405
|
-
// @ts-expect-error
|
|
406
|
-
System.create({
|
|
407
|
-
// @ts-expect-error
|
|
408
|
-
System.create({
|
|
402
|
+
// @ts-expect-error a readonly system takes no input
|
|
403
|
+
System.create({ readonly: true }, (_context, input: { readonly count: number }) => { void input; });
|
|
404
|
+
// @ts-expect-error a readonly system isn't a kernel
|
|
405
|
+
System.create({ readonly: true, kernel: true, components: { with: [position] } }, () => {});
|
|
406
|
+
// @ts-expect-error a readonly system isn't a GPU kernel
|
|
407
|
+
System.create({ readonly: true, kernel: "gpu", components: { with: [position] } }, () => {});
|
|
408
|
+
System.create({ readonly: true, kernel: false, components: { with: [position] } }, () => {});
|
|
409
|
+
// @ts-expect-error a readonly system is ordered with nothing
|
|
410
|
+
System.create({ readonly: true, after: [integrate] }, () => {});
|
|
411
|
+
// @ts-expect-error a readonly system is ordered with nothing
|
|
412
|
+
System.create({ readonly: true, before: [integrate] }, () => {});
|
|
413
|
+
// @ts-expect-error a readonly system is ordered with nothing
|
|
414
|
+
System.create({ readonly: true, during: [integrate] }, () => {});
|
|
409
415
|
|
|
410
416
|
// A system on one entity: its input names the entity, and its body runs on that entity's row.
|
|
411
417
|
type OnOne = { readonly entity: Entity; readonly by: number };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
import type { Component } from '../component/component.js';
|
|
3
|
+
import type { Shared } from '../component/value.js';
|
|
4
|
+
import type { Resource } from '../resource/resource.js';
|
|
5
|
+
import type { Table } from '../world/table/table.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* What a readonly system's `tables` reads: of every archetype matching `with`, `not` and `where` that holds any of
|
|
9
|
+
* `components`, a table of those it holds; and, with `resources`, each resource's value.
|
|
10
|
+
*/
|
|
11
|
+
export type TablesDeclaration = {
|
|
12
|
+
readonly components: readonly Component.Key[];
|
|
13
|
+
readonly with?: readonly Component.Key[];
|
|
14
|
+
readonly not?: readonly Component.Key[];
|
|
15
|
+
/**
|
|
16
|
+
* Keeps the tables to the archetypes holding these values of shared components, each one required as `with`
|
|
17
|
+
* requires it, as a system's `where` of values keeps its system to them.
|
|
18
|
+
*/
|
|
19
|
+
readonly where?: { readonly [K in Shared]?: Component<K> };
|
|
20
|
+
readonly resources?: readonly Resource.Key[];
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/** A readonly system's `tables`: a table of each archetype its declaration reads, and the values of the resources it reads. */
|
|
24
|
+
export type Tables = {
|
|
25
|
+
readonly archetypes: readonly Table[];
|
|
26
|
+
readonly resources: { readonly [K in Resource.Key]?: Resource<K> };
|
|
27
|
+
};
|
|
@@ -62,6 +62,6 @@ System.create({
|
|
|
62
62
|
up: { through: parent },
|
|
63
63
|
}, () => {});
|
|
64
64
|
|
|
65
|
-
//
|
|
66
|
-
// @ts-expect-error
|
|
67
|
-
System.create({
|
|
65
|
+
// A readonly system reads what a sync point left, in no order.
|
|
66
|
+
// @ts-expect-error a readonly system has no up
|
|
67
|
+
System.create({ readonly: true, components: { with: [local] }, up: { through: parent } }, () => {});
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
-
// The `World` namespace's
|
|
3
|
-
export {};
|
|
2
|
+
// The `World` namespace's types.
|
|
3
|
+
export type { Table } from './table/table.js';
|
|
@@ -19,7 +19,7 @@ declare module '@adobe/ecs' {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
// A run resolves to its per-run body's value: a one-off read is an observer system returning it.
|
|
22
|
-
const count = System.create({
|
|
22
|
+
const count = System.create({ readonly: true }, ({ resources }) => resources[frames]);
|
|
23
23
|
check<Equal<typeof count, System<undefined, number>>>();
|
|
24
24
|
const counted: Promise<number> = world.run(count);
|
|
25
25
|
// @ts-expect-error a run of it resolves to a number
|
|
@@ -32,7 +32,7 @@ const scaled = System.create({}, ({ resources }, input: { readonly by: number })
|
|
|
32
32
|
const scaledValue: Promise<{ scaled: number }> = world.run(scaled, { by: 2 });
|
|
33
33
|
|
|
34
34
|
// A body that may be skipped, as one whose conditions or resource change filters can fail, may give undefined.
|
|
35
|
-
const changedFrames = System.create({
|
|
35
|
+
const changedFrames = System.create({ readonly: true, resources: { changed: [frames] } }, ({ resources }) => resources[frames]);
|
|
36
36
|
check<Equal<typeof changedFrames, System<undefined, number | undefined>>>();
|
|
37
37
|
const whileDevice = System.create({ resources: { is: [gpuDevice] } }, () => "drawn");
|
|
38
38
|
const drawn: Promise<string | undefined> = world.run(whileDevice);
|
|
@@ -62,13 +62,23 @@ void ecs.Report;
|
|
|
62
62
|
// @ts-expect-error no run numbers
|
|
63
63
|
const numbered: Promise<number> = world.run(main);
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
// Asked for its changes, after its input, a run resolves to them, in the shape changes finalizers take.
|
|
66
|
+
const changes = world.run(move, { distance: 1 }, { changes: true });
|
|
67
|
+
const alone = world.run(main, { changes: true });
|
|
68
|
+
check<Equal<typeof changes, typeof alone>>();
|
|
69
|
+
check<Equal<Awaited<typeof alone>["resources"][number]["key"], Ecs.Resource.Key>>();
|
|
70
|
+
// @ts-expect-error its input comes first
|
|
71
|
+
void world.run(move, { changes: true }, { distance: 1 });
|
|
72
|
+
// @ts-expect-error a run asks only for its changes
|
|
73
|
+
void world.run(main, { changes: false });
|
|
74
|
+
|
|
75
|
+
void [counted, notText, nextValue, scaledValue, drawn, rows, composite, input, nothing, numbered, time, changes];
|
|
66
76
|
|
|
67
77
|
// A body returns no promise: it starts the work, and a later run finishes it.
|
|
68
78
|
// @ts-expect-error a body returns no promise
|
|
69
79
|
System.create(() => Promise.resolve(1));
|
|
70
80
|
// @ts-expect-error a body returns no promise
|
|
71
|
-
System.create({
|
|
81
|
+
System.create({ readonly: true }, () => Promise.resolve(1));
|
|
72
82
|
// @ts-expect-error a body returns no promise, a row system's neither
|
|
73
83
|
System.create({ components: { with: [position] } }, () => Promise.resolve());
|
|
74
84
|
// @ts-expect-error a body returns no promise, a render system's neither
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* One component's values in a table, copies taken as a readonly system reads `tables`:
|
|
5
|
+
* - `bytes`: a struct's or number's, `stride` bytes per row in the world's layout for it, which is a function of the
|
|
6
|
+
* component's schema alone;
|
|
7
|
+
* - `values`: a string's or an object's, one per row;
|
|
8
|
+
* - `value`: one value every row holds, for a shared component or one whose schema is a constant.
|
|
9
|
+
*/
|
|
10
|
+
export type Column =
|
|
11
|
+
| { readonly kind: "bytes"; readonly bytes: Uint8Array; readonly stride: number }
|
|
12
|
+
| { readonly kind: "values"; readonly values: readonly unknown[] }
|
|
13
|
+
| { readonly kind: "value"; readonly value: unknown };
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* What `commands.spawnTables` spawned: each table's entities in its row order, the one spawned for each row, the one
|
|
17
|
+
* existing for a row its `existing` maps, and `Entity.none` for a row it didn't spawn; and how many rows it didn't,
|
|
18
|
+
* those owned by an entity outside the tables.
|
|
19
|
+
*/
|
|
20
|
+
export type Spawned = { readonly entities: readonly Float64Array[]; readonly dropped: number };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
import type { Component } from '../../component/component.js';
|
|
3
|
+
import type { Column } from './index.js';
|
|
4
|
+
|
|
5
|
+
/** The rows of one archetype: their entities in row order, and a column for each component asked for that it holds. */
|
|
6
|
+
export type Table = {
|
|
7
|
+
readonly count: number;
|
|
8
|
+
readonly entities: Float64Array;
|
|
9
|
+
readonly columns: { readonly [K in Component.Key]?: Column };
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type * as Table from './index.js';
|
|
@@ -1,35 +1,48 @@
|
|
|
1
1
|
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
2
|
import type { Input } from '../system/input.js';
|
|
3
3
|
import type { System } from '../system/system.js';
|
|
4
|
+
import type { ChangeSet } from '../finalizer/body.js';
|
|
5
|
+
import type { Essential } from '../component/value.js';
|
|
6
|
+
import type { Resource } from '../resource/resource.js';
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
|
-
* A world. Systems read and change it;
|
|
9
|
+
* A world. Systems read and change it; readonly systems read it after each run's and tick's sync point. Every member's
|
|
7
10
|
* type is named from the package root, so declarations a consumer emits for its results name no internal module.
|
|
8
11
|
*/
|
|
9
12
|
export type World = {
|
|
10
13
|
/**
|
|
11
14
|
* The one way to change state. Runs are sequential in call order; physically, any that don't conflict overlap. Each
|
|
12
|
-
* runs a system or composite, ending in a sync point, then
|
|
15
|
+
* runs a system or composite, ending in a sync point, then readonly systems, the run's and those added, and
|
|
13
16
|
* resolves to what a per-run body returns: a system without components resolves to its body's value, or
|
|
14
17
|
* `undefined` when its body was skipped, and any other system or composite to nothing. So a one-off read is a run
|
|
15
|
-
* of
|
|
16
|
-
* there before the run resolves, to its value as structured clone. A run of
|
|
18
|
+
* of a readonly system returning what it read; one that runs on a joined thread, as it reads objects there, runs
|
|
19
|
+
* there before the run resolves, to its value as structured clone. A run of readonly systems alone runs them once
|
|
17
20
|
* after an empty sync point, and doesn't wake the loop.
|
|
18
21
|
*
|
|
19
22
|
* When a system or finalizer of the run fails, later tiers are skipped, the sync point still applies what was done,
|
|
20
|
-
* and once its
|
|
23
|
+
* and once its readonly systems ran the run rejects with an `Error` naming each failure, its system's label and id,
|
|
21
24
|
* message and cause. It rejects too when the run can't happen or finish: registration fails, the world is disposed,
|
|
22
|
-
* its GPU device is lost, or its sync point can't apply a command's value. What fails after it,
|
|
25
|
+
* its GPU device is lost, or its sync point can't apply a command's value. What fails after it, a readonly system
|
|
23
26
|
* added or GPU work, is reported out of band, as ticks' failures are (see `add`).
|
|
24
27
|
*/
|
|
25
28
|
run<I, R>(system: System<I, R>, ...input: Input<NoInfer<I>>): Promise<R>;
|
|
29
|
+
/**
|
|
30
|
+
* Runs as `run` does, and resolves to this run's own changes, in the shape `Finalizer.changes` gives, rather than its
|
|
31
|
+
* value: the structural changes its commands made, rules and cascades included, to every component that isn't
|
|
32
|
+
* derived or an object a joined thread fills, the values its adds gave components entities had before and after, and
|
|
33
|
+
* each resource its systems write or reduce into whose value changed, compared as `Finalizer.changes` compares them;
|
|
34
|
+
* `previous` and `next` give values before and after the run. Commands the finalizers of an earlier sync point
|
|
35
|
+
* queued apply first, at a sync point of their own. Values written to rows aren't structural changes, so they're in
|
|
36
|
+
* none. The run pays for this only when asked. Only on the world's thread: a joined thread's rejects.
|
|
37
|
+
*/
|
|
38
|
+
run<I, R>(system: System<I, R>, ...input: [...Input<NoInfer<I>>, options: { readonly changes: true }]): Promise<ChangeSet<Essential, Resource.Key>>;
|
|
26
39
|
/**
|
|
27
40
|
* Adds a system to every tick, from the next one on, ordered with those already added by its constraints, as a
|
|
28
41
|
* composite's members are. A tick runs on a beat of the `cadence` resource where `always` is set, `wake` is due or
|
|
29
42
|
* a run arrived since the last tick, and only while some system is added. Throws for a system with no ordering path
|
|
30
|
-
* to one already added that conflicts with it.
|
|
43
|
+
* to one already added that conflicts with it. A readonly system runs after every run's and tick's sync point
|
|
31
44
|
* instead. It never conflicts and doesn't count as an added system for ticking. On a joined thread `add` takes only
|
|
32
|
-
*
|
|
45
|
+
* readonly systems, whose keys need ids: each runs on that thread after the world's sync points, once for the
|
|
33
46
|
* newest when the thread fell behind, reading structs and the objects of the threads it fills as of one sync point.
|
|
34
47
|
*
|
|
35
48
|
* What added systems throw, and what fails in ticks, is reported out of band on the world's thread, as an `Error`
|
|
@@ -43,14 +56,14 @@ export type World = {
|
|
|
43
56
|
* released. A system added both alone and within an added composite stays until both are removed; constraints
|
|
44
57
|
* naming a removed system are ignored. Removing a value that isn't added does nothing. Throws, removing nothing,
|
|
45
58
|
* when the systems left would conflict with no ordering path between them, as `add` does. On a joined thread it
|
|
46
|
-
* takes only
|
|
59
|
+
* takes only readonly systems.
|
|
47
60
|
*/
|
|
48
61
|
remove(system: System<void>): void;
|
|
49
62
|
/**
|
|
50
63
|
* Lets another thread join this world through `port`, one end of a channel to it, of the browser or node: a
|
|
51
64
|
* `MessagePort`, a `Worker`, or a worker's own scope, `self` or node's `parentPort`. That thread joins through the
|
|
52
65
|
* other end, claiming the logical threads it fills. Resolves once it has joined: the threads it claims are its own,
|
|
53
|
-
* and the
|
|
66
|
+
* and the readonly systems it added as its join resolved, before awaiting anything else, are added, so a run
|
|
54
67
|
* started afterwards runs on it and reaches them; one it adds later reaches the runs after its add arrived.
|
|
55
68
|
* Rejects with an `Error` saying why it didn't join: the world refused its claim, as of a thread claimed twice, the
|
|
56
69
|
* world was disposed, the port is shared already, or that thread ended first, as the platform reports it: a
|
package/src/observe/entity.ts
CHANGED
|
@@ -129,14 +129,14 @@ const createShape = (state: WorldState, byShape: Map<string, EntityShape>, id: s
|
|
|
129
129
|
let finders: Finders | null = null;
|
|
130
130
|
let source: object | null = null;
|
|
131
131
|
const system = optional.length === 0
|
|
132
|
-
? System.create(labelled({
|
|
132
|
+
? System.create(labelled({ readonly: true, lookup: { presence: { with: required, changed: required }, values: { with: required }, members: { index: membersOf(required), changed: required } } }, name, { lookup: { values: required } }), ({ lookup }) => {
|
|
133
133
|
if (source !== lookup || finders === null) {
|
|
134
134
|
source = lookup;
|
|
135
135
|
finders = { presence: lookup.presence, values: lookup.values, optional: noOptional };
|
|
136
136
|
}
|
|
137
137
|
pass(lookup.members().changed, finders);
|
|
138
138
|
})
|
|
139
|
-
: System.create(labelled({
|
|
139
|
+
: System.create(labelled({ readonly: true, lookup: optionalLookups(required, optional) }, name, { lookup: optionalReads(required, optional) }), ({ lookup }) => {
|
|
140
140
|
if (source !== lookup || finders === null) {
|
|
141
141
|
source = lookup;
|
|
142
142
|
finders = { presence: lookup.presence, values: lookup.values, optional: (index, entity) => lookup[index](entity) };
|
package/src/observe/resource.ts
CHANGED
|
@@ -31,14 +31,14 @@ const createShape = (state: WorldState, byKey: Map<Resource.Key, ResourceShape>,
|
|
|
31
31
|
const label = `Observe.resource(${key.description ?? "?"})`;
|
|
32
32
|
// On a joined thread, first values come once a run of this system resolved, never in its pass: none to skip.
|
|
33
33
|
const { joined } = state;
|
|
34
|
-
const notifier = System.create(labelled({
|
|
34
|
+
const notifier = System.create(labelled({ readonly: true, resources: { changed: [key] } }, label, { resources: [key] }), ({ resources }) => {
|
|
35
35
|
passed(state);
|
|
36
36
|
entry.value = resources[key];
|
|
37
37
|
entry.valid = true;
|
|
38
38
|
if (entry.stopped > 0) sweepSubscribers(entry);
|
|
39
39
|
throwFailed(give(entry, 0, true, state.pass, joined ? -1 : state.pass, none));
|
|
40
40
|
});
|
|
41
|
-
const reader = System.create(labelled({
|
|
41
|
+
const reader = System.create(labelled({ readonly: true }, label, { resources: [key] }), ({ resources }) => {
|
|
42
42
|
const through = shape.called.shift() ?? 0;
|
|
43
43
|
entry.value = resources[key];
|
|
44
44
|
entry.valid = true;
|
package/src/observe/select.ts
CHANGED
|
@@ -403,16 +403,16 @@ const createShape = (state: WorldState, byShape: Map<string, QueryShape>, id: st
|
|
|
403
403
|
};
|
|
404
404
|
|
|
405
405
|
const tracker = ordered !== null
|
|
406
|
-
? System.create(labelled({
|
|
406
|
+
? System.create(labelled({ readonly: true, lookup: { list: { index: ordered, changed: reads } } }, name, { lookup: { list: reads } }), ({ lookup }) => {
|
|
407
407
|
track(lookup.list(), null, fromSorted);
|
|
408
408
|
})
|
|
409
409
|
: byRange !== null
|
|
410
|
-
? System.create(labelled({
|
|
410
|
+
? System.create(labelled({ readonly: true, lookup: { list: { index: byRange, changed: reads } } }, name, { lookup: { list: reads } }), ({ lookup }) => {
|
|
411
411
|
// A range is of a `where` condition on the index's key, which the query's type checked against its
|
|
412
412
|
// values.
|
|
413
413
|
track(null, entry => ownRange(entry, range => lookup.list(range as Parameters<typeof lookup.list>[0]), () => lookup.list()), fromSorted);
|
|
414
414
|
})
|
|
415
|
-
: System.create(labelled({
|
|
415
|
+
: System.create(labelled({ readonly: true, lookup: { list: { index: keylessOf(has, not), changed: reads } } }, name, { lookup: { list: reads } }), ({ lookup }) => {
|
|
416
416
|
track(lookup.list(), null, fromKeyless);
|
|
417
417
|
});
|
|
418
418
|
|
|
@@ -466,7 +466,7 @@ const createShape = (state: WorldState, byShape: Map<string, QueryShape>, id: st
|
|
|
466
466
|
for (const entry of scan.entries) entry.gather.start();
|
|
467
467
|
accept = acceptOf(scan.entries);
|
|
468
468
|
};
|
|
469
|
-
const scanner = System.create(labelled({
|
|
469
|
+
const scanner = System.create(labelled({ readonly: true, components: { with: [...reads, ...required.filter(key => !reads.includes(key))], not } }, name, { components: reads }), row => {
|
|
470
470
|
if (accept === null) start();
|
|
471
471
|
if (direct === 0) {
|
|
472
472
|
accept?.(row);
|
|
@@ -116,7 +116,7 @@ const ignore = (): void => {
|
|
|
116
116
|
};
|
|
117
117
|
|
|
118
118
|
/** Runs nothing: a run of it resolves while its world lives. */
|
|
119
|
-
const alive = System.create(labelled({
|
|
119
|
+
const alive = System.create(labelled({ readonly: true }, "Observe/alive"), () => {});
|
|
120
120
|
|
|
121
121
|
/**
|
|
122
122
|
* What a rejected run of the factories' own does: nothing for a disposed world, whose first values never come, and for
|
|
@@ -232,7 +232,7 @@ export const stateOf = (world: World): WorldState => {
|
|
|
232
232
|
deliveries: [],
|
|
233
233
|
idle: [],
|
|
234
234
|
rejected: rejectedOf(world),
|
|
235
|
-
deliver: System.create(labelled({
|
|
235
|
+
deliver: System.create(labelled({ readonly: true }, "Observe/deliver"), () => {
|
|
236
236
|
const delivery = created.deliveries.shift();
|
|
237
237
|
if (delivery === undefined) return;
|
|
238
238
|
created.delivered = delivery.batch;
|
|
@@ -243,7 +243,7 @@ export const stateOf = (world: World): WorldState => {
|
|
|
243
243
|
states.set(world, created);
|
|
244
244
|
if (joined) return created;
|
|
245
245
|
// Added before any shape's system, so in each pass it counts the pass before they run.
|
|
246
|
-
world.add(System.create(labelled({
|
|
246
|
+
world.add(System.create(labelled({ readonly: true }, "Observe/maintain"), () => {
|
|
247
247
|
created.pass++;
|
|
248
248
|
if (created.idle.length > 0) sweepShapes(created);
|
|
249
249
|
}));
|