@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
|
@@ -27,6 +27,12 @@ export type ComponentAccess = {
|
|
|
27
27
|
* change filters, an archetype it skipped keeps its rows unseen until it passes again.
|
|
28
28
|
*/
|
|
29
29
|
readonly where?: { readonly [K in Shared]?: Component<K> | ((value: Component<K>, context: { readonly resources: ReadResources }) => boolean) };
|
|
30
|
+
/**
|
|
31
|
+
* Takes its row whole, as a body passing it on does, where the build can't follow how it's used: every member counts
|
|
32
|
+
* as written, for order, conflicts and placement, but references and shared components, which change only through
|
|
33
|
+
* commands, as read. Without it, a body whose row the build can't follow fails the build.
|
|
34
|
+
*/
|
|
35
|
+
readonly whole?: true;
|
|
30
36
|
};
|
|
31
37
|
|
|
32
38
|
/**
|
|
@@ -85,6 +91,8 @@ export type ComponentLists = {
|
|
|
85
91
|
readonly where?: { readonly [key: symbol]: unknown };
|
|
86
92
|
/** The resources its `where`'s predicates read, which the build infers: when one changed, they're evaluated again. */
|
|
87
93
|
readonly whereReads?: readonly Key[];
|
|
94
|
+
/** Whether it takes its row whole, as its declaration said. */
|
|
95
|
+
readonly whole?: true;
|
|
88
96
|
};
|
|
89
97
|
|
|
90
98
|
/** A lookup as the world takes it, with what its body reads of its rows in `read`. */
|
|
@@ -105,3 +113,11 @@ export type ResourceLists = {
|
|
|
105
113
|
readonly is?: readonly Key[];
|
|
106
114
|
readonly not?: readonly Key[];
|
|
107
115
|
};
|
|
116
|
+
|
|
117
|
+
/** The components a system's commands name, by kind, and whether it despawns, as the build inferred them. */
|
|
118
|
+
export type CommandLists = {
|
|
119
|
+
readonly spawn?: readonly Key[];
|
|
120
|
+
readonly add?: readonly Key[];
|
|
121
|
+
readonly remove?: readonly Key[];
|
|
122
|
+
readonly despawn?: true;
|
|
123
|
+
};
|
|
@@ -10,14 +10,15 @@ import type { Commands } from './commands.js';
|
|
|
10
10
|
import type { Declaration } from './declaration.js';
|
|
11
11
|
import type { Listed, Property } from './property.js';
|
|
12
12
|
import type { RenderContext } from './render.js';
|
|
13
|
+
import type { Tables } from './tables.js';
|
|
13
14
|
import type { ComponentValues, LookupFunction, ResourceValues, Values } from './values.js';
|
|
14
15
|
|
|
15
16
|
/** The components every entity a declaration matches has: those in `with`, `added` and `changed`. */
|
|
16
17
|
export type Members<D> =
|
|
17
18
|
| Listed<Property<D, "components">, "with"> | Listed<Property<D, "components">, "added"> | Listed<Property<D, "components">, "changed">;
|
|
18
19
|
|
|
19
|
-
/** Whether a declaration only reads:
|
|
20
|
-
type Reading<D> = D extends { readonly
|
|
20
|
+
/** Whether a declaration only reads: one declared `readonly`, or a render system. */
|
|
21
|
+
type Reading<D> = D extends { readonly readonly: true } | { readonly shader: object } ? true : false;
|
|
21
22
|
|
|
22
23
|
/** The components a declaration's rows hold only where an entity has them. */
|
|
23
24
|
export type Optional<D> = Exclude<Listed<Property<D, "components">, "optional">, Members<D>>;
|
|
@@ -51,8 +52,8 @@ type Is<D> = Listed<Property<D, "resources">, "is">;
|
|
|
51
52
|
|
|
52
53
|
/**
|
|
53
54
|
* What a body uses besides its row: every resource, its lookups by name, the resources it may reduce into, its
|
|
54
|
-
* parent's values with `up`, and its commands, there for every system but a GPU kernel, a render system and
|
|
55
|
-
*
|
|
55
|
+
* parent's values with `up`, and its commands, there for every system but a GPU kernel, a render system and one
|
|
56
|
+
* declared `readonly`. The build infers which of them the body reads and writes.
|
|
56
57
|
*/
|
|
57
58
|
export type Context<D extends Declaration> =
|
|
58
59
|
{
|
|
@@ -66,7 +67,8 @@ export type Context<D extends Declaration> =
|
|
|
66
67
|
/** The parent's values the body reads, or null for a row without a parent or whose parent lacks one of them. */
|
|
67
68
|
readonly up: ComponentValues | null;
|
|
68
69
|
} : {})
|
|
69
|
-
& (D extends { readonly kernel: "gpu" } | { readonly shader: object } | { readonly
|
|
70
|
+
& (D extends { readonly kernel: "gpu" } | { readonly shader: object } | { readonly readonly: true } ? {} : { readonly commands: Commands })
|
|
71
|
+
& (D extends { readonly tables: object } ? { readonly tables: Tables } : {});
|
|
70
72
|
|
|
71
73
|
/**
|
|
72
74
|
* `(row, context, input)` for a system with `components`, `(context, input)` for one without, and `(gpu, context)` for
|
|
@@ -138,24 +138,36 @@ const checkWhere = (leaf: Leaf): void => {
|
|
|
138
138
|
const where = declaration.components?.where;
|
|
139
139
|
if (where === undefined) return;
|
|
140
140
|
if (declaration.up !== undefined || declaration.order !== undefined || declaration.target !== undefined) throw new Error(`System "${name}" uses where with up, order or a target, which run rows across archetypes.`);
|
|
141
|
+
checkWhereValues(`System "${name}"`, "where", "a system", where, true);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Throws for a `where`, the one `named` says of `subject`, which keeps what `keeps` says to archetypes, naming what
|
|
146
|
+
* isn't a shared component or giving one a value not of its type, or a predicate unless `predicates`.
|
|
147
|
+
*/
|
|
148
|
+
export const checkWhereValues = (subject: string, named: string, keeps: string, where: { readonly [key: symbol]: unknown }, predicates: boolean): void => {
|
|
141
149
|
for (const key of Object.getOwnPropertySymbols(where)) {
|
|
142
150
|
const found = declarationOf(key);
|
|
143
|
-
if (kindOf(key) !== "component" || found?.shared !== true) throw new Error(
|
|
151
|
+
if (kindOf(key) !== "component" || found?.shared !== true) throw new Error(`${subject}: ${named} names "${labelOf(key)}", which isn't a shared component: where keeps ${keeps} to archetypes by their shared values.`);
|
|
144
152
|
const value: unknown = Reflect.get(where, key);
|
|
145
|
-
if (typeof value
|
|
153
|
+
if (typeof value === "function") {
|
|
154
|
+
if (!predicates) throw new Error(`${subject}: ${named} gives "${labelOf(key)}" a predicate, and takes only values.`);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (found.type !== undefined && !matchesSchema(found.type, value)) throw new Error(`${subject}: ${named} gives "${labelOf(key)}" ${labelOf(value)}, which isn't one of its type's values.`);
|
|
146
158
|
}
|
|
147
159
|
};
|
|
148
160
|
|
|
149
161
|
/**
|
|
150
|
-
* Throws for what
|
|
162
|
+
* Throws for what a readonly system can't declare: it only reads, after a sync point, so it writes and reduces into
|
|
151
163
|
* nothing, takes no input, isn't a kernel or a render system, has no `up`, and is ordered with nothing.
|
|
152
164
|
*/
|
|
153
|
-
const
|
|
165
|
+
const checkReadonly = (leaf: Leaf): void => {
|
|
154
166
|
const { name, declaration } = leaf;
|
|
155
|
-
const {
|
|
156
|
-
if (
|
|
157
|
-
const fail = (message: string): never => { throw new Error(`System "${name}" is
|
|
158
|
-
if (
|
|
167
|
+
const { readonly } = declaration;
|
|
168
|
+
if (readonly === undefined) return;
|
|
169
|
+
const fail = (message: string): never => { throw new Error(`System "${name}" is readonly, so it ${message}.`); };
|
|
170
|
+
if (readonly !== true) throw new Error(`System "${name}": readonly is true or left out.`);
|
|
159
171
|
if (leaf.kind === "render") fail("records no GPU work");
|
|
160
172
|
if (declaration.kernel === true || declaration.kernel === "gpu") fail("isn't a kernel");
|
|
161
173
|
if ((declaration.components?.write?.length ?? 0) + (declaration.resources?.write?.length ?? 0) > 0) fail("writes nothing");
|
|
@@ -165,9 +177,23 @@ const checkObserver = (leaf: Leaf): void => {
|
|
|
165
177
|
if ((declaration.before?.length ?? 0) + (declaration.after?.length ?? 0) + (declaration.during?.length ?? 0) > 0) fail("is ordered with nothing");
|
|
166
178
|
};
|
|
167
179
|
|
|
180
|
+
/** Throws for `tables` on a system that isn't readonly or has components, or naming keys that aren't registered as their kind. */
|
|
181
|
+
const checkTables = (leaf: Leaf): void => {
|
|
182
|
+
const { name, declaration } = leaf;
|
|
183
|
+
const { tables } = declaration;
|
|
184
|
+
if (tables === undefined) return;
|
|
185
|
+
if (declaration.readonly !== true) throw new Error(`System "${name}" reads tables, which only a readonly system reads.`);
|
|
186
|
+
if (declaration.components !== undefined) throw new Error(`System "${name}" reads tables, so it has no components.`);
|
|
187
|
+
requireRegistered("System", name, "component", tables.components);
|
|
188
|
+
requireRegistered("System", name, "component", tables.with);
|
|
189
|
+
requireRegistered("System", name, "component", tables.not);
|
|
190
|
+
requireRegistered("System", name, "resource", tables.resources);
|
|
191
|
+
if (tables.where !== undefined) checkWhereValues(`System "${name}"`, "tables' where", "tables", tables.where, false);
|
|
192
|
+
};
|
|
193
|
+
|
|
168
194
|
/**
|
|
169
195
|
* Throws for a leaf that names a component or resource that isn't registered as one, lists a key in both `read` and
|
|
170
|
-
* `write`, writes a reference, has a lookup reading a component it writes, uses object components and resources of different threads, declares what
|
|
196
|
+
* `write`, writes a reference, has a lookup reading a component it writes, uses object components and resources of different threads, declares what a readonly system can't, or whose
|
|
171
197
|
* reductions, conditions, `up` or placement can't hold.
|
|
172
198
|
*/
|
|
173
199
|
export const checkLeaf = (leaf: Leaf): void => {
|
|
@@ -190,6 +216,7 @@ export const checkLeaf = (leaf: Leaf): void => {
|
|
|
190
216
|
if (writes.has(key)) throw new Error(`System "${name}": its rows are ordered by an index keyed by "${labelOf(key)}", which the system writes.`);
|
|
191
217
|
}
|
|
192
218
|
checkTarget(leaf);
|
|
219
|
+
checkTables(leaf);
|
|
193
220
|
threadOf(declaration, "System", name);
|
|
194
221
|
requireRegistered("System", name, "component", components?.has);
|
|
195
222
|
requireRegistered("System", name, "component", components?.not);
|
|
@@ -202,7 +229,7 @@ export const checkLeaf = (leaf: Leaf): void => {
|
|
|
202
229
|
if (declarationOf(key)?.reference !== undefined) throw new Error(`System "${name}" writes "${labelOf(key)}", a reference, which changes only through commands.`);
|
|
203
230
|
if (declarationOf(key)?.shared === true) throw new Error(`System "${name}" writes "${labelOf(key)}", a shared component, whose value is its archetype's: give it another with commands.add.`);
|
|
204
231
|
}
|
|
205
|
-
|
|
232
|
+
checkReadonly(leaf);
|
|
206
233
|
checkWhere(leaf);
|
|
207
234
|
checkReductions(leaf);
|
|
208
235
|
checkConditions(leaf);
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import type { Component } from '../component/component.js';
|
|
3
3
|
import type { Entity } from '../entity/entity.js';
|
|
4
4
|
import type { Essential, Shared } from '../component/value.js';
|
|
5
|
+
import type { Table } from '../world/table/table.js';
|
|
6
|
+
import type { SharedOnly } from './shared-only.js';
|
|
5
7
|
import type { ValuesOf } from './values-of.js';
|
|
6
8
|
|
|
7
9
|
/** The value an `add` of one component takes: none for a tag. */
|
|
@@ -62,10 +64,41 @@ export type Commands = {
|
|
|
62
64
|
...constant: RequiresPreset<C> extends true ? [constant?: P & SharedPreset<C> & Named<C, P>] : [constant?: P & Named<C, P>]
|
|
63
65
|
): (...values: SpawnValues<C, P>) => Entity;
|
|
64
66
|
despawn(entity: Entity): void;
|
|
67
|
+
/**
|
|
68
|
+
* Despawns, as a `despawn` of each would, every entity that has each component of `with` and none of `not` once the
|
|
69
|
+
* commands issued before it apply, and every entity spawned at the same sync point that does, whenever spawned.
|
|
70
|
+
* Derived components may be named. `where` keeps it to the archetypes holding these values of shared components,
|
|
71
|
+
* each one required as `with` requires it, as a system's `where` of values does. Never the resource entity. Only on
|
|
72
|
+
* the world's thread: a joined thread's throws.
|
|
73
|
+
*/
|
|
74
|
+
despawnWhere<const W extends object = {}>(query: {
|
|
75
|
+
readonly with: readonly [Component.Key, ...Component.Key[]];
|
|
76
|
+
readonly not?: readonly Component.Key[];
|
|
77
|
+
readonly where?: W & SharedOnly<NoInfer<W>> & { readonly [K in Shared]?: Component<K> };
|
|
78
|
+
}): void;
|
|
65
79
|
/** Adds or replaces one component's value, marking it changed. */
|
|
66
80
|
add<K extends Essential>(entity: Entity, component: K, ...value: ValueArgument<K>): void;
|
|
67
81
|
/** Adds or replaces the values of `components`, marking them changed. */
|
|
68
82
|
add<const C extends readonly Essential[]>(entity: Entity, components: C, values: ValuesOf<C>): void;
|
|
69
83
|
remove(entity: Entity, component: Essential): void;
|
|
70
84
|
remove(entity: Entity, components: readonly Essential[]): void;
|
|
85
|
+
/**
|
|
86
|
+
* Spawns an entity for each row of `tables`, as a readonly system's `tables` gives them, of each table's components
|
|
87
|
+
* with its columns' values: a block copy per column. Entity values are rewritten as the world's references would
|
|
88
|
+
* leave them had the entities outside the tables despawned: each naming an entity of the tables, a reference, a
|
|
89
|
+
* struct's field or an array's item, names the one spawned for it; a row whose owner reference names an entity
|
|
90
|
+
* outside them isn't spawned, nor anything it owns, through owner references, as despawning its owner would; a
|
|
91
|
+
* weak reference to one is left out of its row, which is spawned without it; any other value naming one is
|
|
92
|
+
* `Entity.none`. Returns, at once, each table's new entities, alive at the sync point. A column's stride must be its
|
|
93
|
+
* component's in this world, and its length its table's count. On the world's thread only.
|
|
94
|
+
* - `existing` maps entities to ones already alive in this world, which stand for them: a row whose entity it maps
|
|
95
|
+
* isn't spawned, its entry in the result is the entity it maps to, and every value naming one it maps names that
|
|
96
|
+
* entity, whether or not it's in the tables. Fails for one that isn't alive.
|
|
97
|
+
* - `resources` sets each resource it names, by key, at the sync point, as commands apply, each value checked
|
|
98
|
+
* against its resource's schema; not one only the world writes, nor one living on another thread.
|
|
99
|
+
*/
|
|
100
|
+
spawnTables(tables: readonly Table[], options?: {
|
|
101
|
+
readonly resources?: { readonly [key: symbol]: unknown };
|
|
102
|
+
readonly existing?: ReadonlyMap<Entity, Entity>;
|
|
103
|
+
}): Table.Spawned;
|
|
71
104
|
};
|
|
@@ -13,6 +13,7 @@ const label = Symbol("commandsTypeTest/label");
|
|
|
13
13
|
const player = Symbol("commandsTypeTest/player");
|
|
14
14
|
const slot = Symbol("commandsTypeTest/slot");
|
|
15
15
|
const time = Symbol("commandsTypeTest/time");
|
|
16
|
+
const chunk = Symbol("commandsTypeTest/chunk");
|
|
16
17
|
declare module '@adobe/ecs' {
|
|
17
18
|
export interface Components {
|
|
18
19
|
[position]: { type: number };
|
|
@@ -20,6 +21,7 @@ declare module '@adobe/ecs' {
|
|
|
20
21
|
[label]: { type: string | null };
|
|
21
22
|
[player]: { type: true };
|
|
22
23
|
[slot]: { type: number; derived: true };
|
|
24
|
+
[chunk]: { type: number; shared: true };
|
|
23
25
|
}
|
|
24
26
|
export interface Resources {
|
|
25
27
|
[time]: { type: number };
|
|
@@ -84,4 +86,33 @@ export const issue = (commands: Commands, entity: Entity): void => {
|
|
|
84
86
|
// @ts-expect-error commands never remove a derived component
|
|
85
87
|
commands.remove(entity, slot);
|
|
86
88
|
commands.despawn(entity);
|
|
89
|
+
|
|
90
|
+
// `despawnWhere` takes a query of at least one component, derived ones included.
|
|
91
|
+
commands.despawnWhere({ with: [player] });
|
|
92
|
+
commands.despawnWhere({ with: [position, slot], not: [player] });
|
|
93
|
+
commands.despawnWhere({ with: particle });
|
|
94
|
+
// @ts-expect-error `with` names at least one component
|
|
95
|
+
commands.despawnWhere({ with: [] });
|
|
96
|
+
// @ts-expect-error a query names components, not resources
|
|
97
|
+
commands.despawnWhere({ with: [time] });
|
|
98
|
+
// @ts-expect-error in `not` too
|
|
99
|
+
commands.despawnWhere({ with: [player], not: [time] });
|
|
100
|
+
// @ts-expect-error it takes a query, not an entity
|
|
101
|
+
commands.despawnWhere(entity);
|
|
102
|
+
// Its `where` gives values of shared components, as a system's `where` of values does.
|
|
103
|
+
commands.despawnWhere({ with: [position], where: { [chunk]: 3 } });
|
|
104
|
+
const unloaded = { [chunk]: 4 } as const;
|
|
105
|
+
commands.despawnWhere({ with: [position], where: unloaded });
|
|
106
|
+
// @ts-expect-error a value of the component's type
|
|
107
|
+
commands.despawnWhere({ with: [position], where: { [chunk]: "3" } });
|
|
108
|
+
// @ts-expect-error where names only shared components
|
|
109
|
+
commands.despawnWhere({ with: [position], where: { [position]: 3 } });
|
|
110
|
+
// @ts-expect-error where takes values, not predicates
|
|
111
|
+
commands.despawnWhere({ with: [position], where: { [chunk]: (value: number) => value > 3 } });
|
|
112
|
+
const placed = { [position]: 3 } as const;
|
|
113
|
+
// @ts-expect-error where names only shared components, in a constant too
|
|
114
|
+
commands.despawnWhere({ with: [position], where: placed });
|
|
115
|
+
const mixed = { [chunk]: 3, [position]: 3 } as const;
|
|
116
|
+
// @ts-expect-error and in a constant naming a shared one beside it
|
|
117
|
+
commands.despawnWhere({ with: [position], where: mixed });
|
|
87
118
|
};
|
|
@@ -14,7 +14,7 @@ import type { System } from './system.js';
|
|
|
14
14
|
export const createResolved = (declaration: Resolved, body: Leaf["body"], input?: unknown): System<unknown> => {
|
|
15
15
|
const name = nameOf(declaration, undefined, "system");
|
|
16
16
|
const schema = inputSchemaOf(name, body, declaration.components === undefined ? 1 : 2, input);
|
|
17
|
-
const system: Leaf & System<unknown> = { kind: "system", name, declaration, input: schema, body };
|
|
17
|
+
const system: Leaf & System<unknown> = { kind: "system", name, declaration, input: schema, body, commands: undefined };
|
|
18
18
|
checkLeaf(system);
|
|
19
19
|
claimSystem(system, undefined);
|
|
20
20
|
return system;
|
|
@@ -105,17 +105,17 @@ describe("System.create", () => {
|
|
|
105
105
|
[{ kernel: "gpu", components: { write: [position], changed: [position] } }, "can't run on the GPU: GPU kernels don't support change filters yet"],
|
|
106
106
|
[{ kernel: "gpu", components: { read: [position] }, lookup: { others: { read: [position] } } }, "can't run on the GPU: GPU kernels don't support lookups yet"],
|
|
107
107
|
[{ kernel: "gpu", components: { has: [position] }, resources: { reduce: [total] } }, "can't run on the GPU: GPU kernels don't support reductions yet"],
|
|
108
|
-
[{
|
|
109
|
-
[{
|
|
110
|
-
[{
|
|
111
|
-
[{
|
|
112
|
-
[{
|
|
113
|
-
[{
|
|
114
|
-
[{
|
|
115
|
-
[{
|
|
116
|
-
[{
|
|
108
|
+
[{ readonly: false, resources: { read: [time] } }, "readonly is true or left out"],
|
|
109
|
+
[{ readonly: true, components: { write: [position] } }, "is readonly, so it writes nothing"],
|
|
110
|
+
[{ readonly: true, resources: { write: [time] } }, "is readonly, so it writes nothing"],
|
|
111
|
+
[{ readonly: true, components: { has: [position] }, resources: { reduce: [total] } }, "is readonly, so it reduces into nothing"],
|
|
112
|
+
[{ readonly: true, kernel: true, components: { read: [position] } }, "is readonly, so it isn't a kernel"],
|
|
113
|
+
[{ readonly: true, components: { read: [position] }, up: { through: parent } }, "is readonly, so it has no up"],
|
|
114
|
+
[{ readonly: true, resources: { read: [time] }, after: [System.create(() => {})] }, "is readonly, so it is ordered with nothing"],
|
|
115
|
+
[{ readonly: true, resources: { read: [time] }, before: [System.create(() => {})] }, "is readonly, so it is ordered with nothing"],
|
|
116
|
+
[{ readonly: true, resources: { read: [time] }, during: [System.create(() => {})] }, "is readonly, so it is ordered with nothing"],
|
|
117
117
|
];
|
|
118
|
-
expect(() => createTaking({
|
|
118
|
+
expect(() => createTaking({ readonly: true }, { type: "object", properties: {} })).toThrow("is readonly, so it takes no input");
|
|
119
119
|
rejected.forEach(([declaration, message], index) => {
|
|
120
120
|
expect(() => create({ ...declaration })).toThrow(message);
|
|
121
121
|
});
|
|
@@ -171,9 +171,17 @@ describe("System.create of a composite", () => {
|
|
|
171
171
|
const looped = System.create({ before: [first], after: [second] }, () => {});
|
|
172
172
|
expect(() => System.create({ systems: [first, second, looped] })).toThrow("Ordering cycle");
|
|
173
173
|
});
|
|
174
|
-
it("
|
|
174
|
+
it("takes a row declared whole as written, every member but references, and says so in a conflict", () => {
|
|
175
|
+
const pass = (row: object): void => { void row; };
|
|
176
|
+
const whole = System.create({ components: { with: [position, parent], whole: true } }, row => { pass(row); });
|
|
177
|
+
const reader = System.create({ components: { with: [position] } }, row => { void row[position]; });
|
|
178
|
+
const definition = definitionOf(whole);
|
|
179
|
+
expect(definition?.kind === "system" ? definition.declaration.components : null).toEqual({ read: [parent], write: [position], whole: true });
|
|
180
|
+
expect(() => System.create([whole, reader])).toThrow('both access "typesSystemTest/position" and one writes it; order them with before or after. "whole" takes its row whole (components.whole), so it writes every member.');
|
|
181
|
+
});
|
|
182
|
+
it("runs readonly members after its sync point, in no tier, never conflicting and ignoring constraints naming them", () => {
|
|
175
183
|
const write = System.create({ components: { with: [position] } }, row => { row[position] += 1; });
|
|
176
|
-
const watch = System.create({
|
|
184
|
+
const watch = System.create({ readonly: true, components: { with: [position] } }, row => { void row[position]; });
|
|
177
185
|
const later = System.create({ components: { with: [position] }, after: [watch, write] }, row => { void row[position]; });
|
|
178
186
|
const free = System.create({ after: [watch] }, ({ resources }) => { void resources[time]; });
|
|
179
187
|
const schedule = scheduleOf(requireDefinition(System.create([write, watch, later, free])));
|
|
@@ -205,6 +213,6 @@ describe("System.render", () => {
|
|
|
205
213
|
const render = (declaration: object, uses: Parameters<typeof giveAccess>[1] = {}): unknown => Reflect.apply(System.render, undefined, [giveAccess(declaration, uses), () => {}]);
|
|
206
214
|
expect(() => System.render({ shader: { accessors: { loop: height } } }, () => {})).toThrow(`can't render: "loop" isn't usable as a WGSL function name.`);
|
|
207
215
|
expect(() => render({ shader: { accessors: {} } }, { resources: { reduce: [total] } })).toThrow("writes or reduces into resources; a render system only reads");
|
|
208
|
-
expect(() => render({ shader: { accessors: {} },
|
|
216
|
+
expect(() => render({ shader: { accessors: {} }, readonly: true })).toThrow("is readonly, so it records no GPU work");
|
|
209
217
|
});
|
|
210
218
|
});
|
|
@@ -16,8 +16,9 @@ import type { Listed, Property } from './property.js';
|
|
|
16
16
|
import type { System } from './system.js';
|
|
17
17
|
import type { OneThread, ThreadedBy, ThreadsOf } from './threads.js';
|
|
18
18
|
import type { HeldBy } from './values.js';
|
|
19
|
+
import type { SharedOnly } from './shared-only.js';
|
|
19
20
|
import type { Shared } from '../component/value.js';
|
|
20
|
-
import { accessOf, compact, componentLists, lookupsLists, resourceUses, upReads, whereReadsOf } from './resolve.js';
|
|
21
|
+
import { accessOf, commandLists, compact, componentLists, lookupsLists, resourceUses, upReads, whereReadsOf } from './resolve.js';
|
|
21
22
|
|
|
22
23
|
type Lookups<D> = Property<D, "lookup">;
|
|
23
24
|
|
|
@@ -50,10 +51,10 @@ type GpuKernel = {
|
|
|
50
51
|
};
|
|
51
52
|
|
|
52
53
|
/**
|
|
53
|
-
*
|
|
54
|
+
* A readonly system only reads, after a sync point: its row and resources are read-only, and it never compiles to a
|
|
54
55
|
* kernel, has no `up`, and is ordered with nothing. That it takes no input is checked at its body.
|
|
55
56
|
*/
|
|
56
|
-
type
|
|
57
|
+
type Readonly = {
|
|
57
58
|
readonly kernel?: false;
|
|
58
59
|
readonly up?: never;
|
|
59
60
|
readonly before?: never;
|
|
@@ -68,9 +69,10 @@ type Targeted = {
|
|
|
68
69
|
readonly order?: never;
|
|
69
70
|
};
|
|
70
71
|
|
|
72
|
+
|
|
71
73
|
/** `where` names only shared components, and keeps rows unordered across archetypes, so it's without `up`, `order` and a target. */
|
|
72
74
|
type Where<D> = D extends { readonly components: { readonly where: infer W } }
|
|
73
|
-
? { readonly components?: { readonly where?:
|
|
75
|
+
? { readonly components?: { readonly where?: SharedOnly<W> } }
|
|
74
76
|
& (D extends { readonly up: object } | { readonly order: object } | { readonly target: string }
|
|
75
77
|
? { readonly components?: { readonly where?: { readonly "error: where can't be used with up, order or a target, which run rows across archetypes": true } } } : {})
|
|
76
78
|
: {};
|
|
@@ -97,7 +99,13 @@ type Validate<D extends Declaration> =
|
|
|
97
99
|
// GPU kernels don't read components an archetype may lack yet.
|
|
98
100
|
& (D extends { readonly components: { readonly optional: readonly [unknown, ...unknown[]] } }
|
|
99
101
|
? { readonly kernel?: boolean | { readonly "error: a GPU kernel can't have optional components yet": true } } : {})
|
|
100
|
-
& (D extends { readonly
|
|
102
|
+
& (D extends { readonly readonly: true } ? Readonly : {})
|
|
103
|
+
// Tables are copies a readonly system without components reads.
|
|
104
|
+
& (D extends { readonly tables: { readonly where: infer W } } ? { readonly tables?: { readonly where?: SharedOnly<W> } } : {})
|
|
105
|
+
& (D extends { readonly tables: object }
|
|
106
|
+
? (D extends { readonly readonly: true } ? {} : { readonly tables?: { readonly "error: tables are read by a readonly system": true } })
|
|
107
|
+
& (Property<D, "components"> extends undefined ? {} : { readonly tables?: { readonly "error: a system reading tables has no components": true } })
|
|
108
|
+
: {})
|
|
101
109
|
& (D extends { readonly target: string } ? Targeted : {})
|
|
102
110
|
& (D extends { readonly target: string } ? Property<D, "components"> extends undefined ? { readonly target?: { readonly "error: a system on one entity runs on its row, so it needs components": true } } : {} : {})
|
|
103
111
|
// A body's input is the type of its parameter after its context, not part of its declaration.
|
|
@@ -109,7 +117,7 @@ type EntityFields<I> = { [K in keyof I]-?: [Exclude<I[K], undefined>] extends [E
|
|
|
109
117
|
/**
|
|
110
118
|
* What a declaration requires of the input its body takes, checked at the body, since the input is known only once the
|
|
111
119
|
* body is: a target names an entity field of it, and a system with input doesn't filter on added or changed, as its
|
|
112
|
-
* runs pass different input, nor is it
|
|
120
|
+
* runs pass different input, nor is it a readonly system. Every check passes for `never`, the input before the body is
|
|
113
121
|
* inferred.
|
|
114
122
|
*/
|
|
115
123
|
type Checks<D, I> = [I] extends [never] ? unknown :
|
|
@@ -119,7 +127,7 @@ type Checks<D, I> = [I] extends [never] ? unknown :
|
|
|
119
127
|
: unknown)
|
|
120
128
|
& ([I] extends [undefined] ? unknown
|
|
121
129
|
: (D extends { readonly components: { readonly added: object } | { readonly changed: object } } ? { readonly "error: a system with input can't filter on added or changed, as its runs pass different input; pass it through a resource": true } : unknown)
|
|
122
|
-
& (D extends { readonly
|
|
130
|
+
& (D extends { readonly readonly: true } ? { readonly "error: a readonly system takes no input": true } : unknown));
|
|
123
131
|
|
|
124
132
|
/** The body of a system that declares nothing, which runs once per run with its resources and commands. */
|
|
125
133
|
type Alone = (context: Context<{}>) => void;
|
|
@@ -144,10 +152,10 @@ type Checked<C> = C extends Alone ? ReturnsNoPromise<C> : C extends CompositeDec
|
|
|
144
152
|
* input, with every field of theirs. Throws if a system names a key that isn't registered as its kind, its body writes
|
|
145
153
|
* a reference, a lookup's rows are read where it writes them or it goes through an index keyed by what it writes, its
|
|
146
154
|
* rows are ordered by an index it isn't sorted or keyed by what it writes, its object components and resources live on
|
|
147
|
-
* different threads, or its reductions, conditions, `up`, `order` or placement can't hold, or it's
|
|
155
|
+
* different threads, or its reductions, conditions, `up`, `order` or placement can't hold, or it's a readonly system and
|
|
148
156
|
* writes, reduces, takes input, is a kernel, has `up` or is ordered; if the build couldn't follow how its body uses its
|
|
149
157
|
* resources, lookups or parent; or if a composite's member isn't a system a constructor returned, or two members
|
|
150
|
-
* declare an input field with different schemas. A composite's
|
|
158
|
+
* declare an input field with different schemas. A composite's readonly members run after its sync point, ordered
|
|
151
159
|
* with nothing.
|
|
152
160
|
*
|
|
153
161
|
* A run of a system without components resolves to what its body returns, or `undefined` when its conditions, resource
|
|
@@ -170,9 +178,10 @@ export function create(first: Declaration | Leaf["body"] | readonly System<unkno
|
|
|
170
178
|
const body = typeof first === "function" ? first : second;
|
|
171
179
|
if (body === undefined) throw new Error("A system needs a body, and a composite its systems.");
|
|
172
180
|
const name = nameOf(first, id, "system");
|
|
173
|
-
const
|
|
181
|
+
const access = accessOf(`System "${name}"`, first);
|
|
182
|
+
const declaration = resolve(`System "${name}"`, declared, access);
|
|
174
183
|
const schema = inputSchemaOf(name, body, declaration.components === undefined ? 1 : 2, input);
|
|
175
|
-
const system: Leaf & System<unknown> = { kind: "system", name, declaration, input: schema, body };
|
|
184
|
+
const system: Leaf & System<unknown> = { kind: "system", name, declaration, input: schema, body, commands: commandLists(access) };
|
|
176
185
|
checkLeaf(system);
|
|
177
186
|
claimSystem(system, id);
|
|
178
187
|
return system;
|
|
@@ -180,16 +189,16 @@ export function create(first: Declaration | Leaf["body"] | readonly System<unkno
|
|
|
180
189
|
|
|
181
190
|
/** A declaration as the world takes it, with what the build inferred its body uses. */
|
|
182
191
|
const resolve = (what: string, declared: Declaration, access: ReturnType<typeof accessOf>): Resolved => {
|
|
183
|
-
const reading = declared.
|
|
192
|
+
const reading = declared.readonly === true;
|
|
184
193
|
const { components, lookup, up, resources, ...rest } = declared;
|
|
185
194
|
const uses = resourceUses(what, access, resources?.is);
|
|
186
|
-
// What `where`'s predicates read counts as the system's reads, for its order, conflicts and placement.
|
|
187
195
|
const whereReads = whereReadsOf(what, components, access);
|
|
188
196
|
const conditions: readonly symbol[] = resources?.is ?? [];
|
|
189
|
-
|
|
197
|
+
// What `where`'s predicates and `tables` read counts as the system's reads, for its order, conflicts and placement.
|
|
198
|
+
for (const key of [...whereReads, ...declared.tables?.resources ?? []]) if (!uses.read.includes(key) && !uses.write.includes(key) && !conditions.includes(key)) uses.read.push(key);
|
|
190
199
|
const resolved: Resolved = {
|
|
191
200
|
...rest,
|
|
192
|
-
...components === undefined ? {} : { components: componentLists(components, access.components, reading, whereReads) },
|
|
201
|
+
...components === undefined ? {} : { components: componentLists(components, components.whole === true ? null : access.components, reading, whereReads) },
|
|
193
202
|
...lookup === undefined ? {} : { lookup: lookupsLists(lookup, access) },
|
|
194
203
|
...up === undefined ? {} : { up: { through: up.through, read: upReads(what, access) } },
|
|
195
204
|
};
|
|
@@ -4,6 +4,7 @@ import type { Resource } from '../resource/resource.js';
|
|
|
4
4
|
import type { ComponentAccess, ComponentLists, ResourceAccess, ResourceLists, SystemLookup, SystemLookupLists, Up, UpLists } from './access.js';
|
|
5
5
|
import type { Shader } from './render.js';
|
|
6
6
|
import type { System } from './system.js';
|
|
7
|
+
import type { TablesDeclaration } from './tables.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* A system never declares its thread: it runs on the one its object components and resources live on, those it
|
|
@@ -59,9 +60,15 @@ export type Declaration = {
|
|
|
59
60
|
readonly during?: readonly System<unknown>[];
|
|
60
61
|
/**
|
|
61
62
|
* Runs after the last sync point of each run and tick (a tick is a run of the added composite), reading the settled
|
|
62
|
-
* state; changes nothing
|
|
63
|
+
* state, on the thread that added or ran it; changes nothing, its row and resources typed read-only and its context
|
|
64
|
+
* without `commands`; takes no input; never ordered; doesn't keep the loop ticking. A joined thread adds only these.
|
|
63
65
|
*/
|
|
64
|
-
readonly
|
|
66
|
+
readonly readonly?: true;
|
|
67
|
+
/**
|
|
68
|
+
* For a readonly system without components: gives its context `tables`, copies of whole archetypes' columns, as
|
|
69
|
+
* `TablesDeclaration` says, read on the world's thread. Its components and resources count as what it reads.
|
|
70
|
+
*/
|
|
71
|
+
readonly tables?: TablesDeclaration;
|
|
65
72
|
};
|
|
66
73
|
|
|
67
74
|
/** A declaration as the world takes it: the build's inferred reads, writes and reductions joined with what it declares. */
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { Index } from "../indexes/index-type.js";
|
|
4
|
+
import { System } from "./system.js";
|
|
5
|
+
|
|
6
|
+
const position = Symbol("describeTest/position");
|
|
7
|
+
const cell = Symbol("describeTest/cell");
|
|
8
|
+
const speed = Symbol("describeTest/speed");
|
|
9
|
+
const hover = Symbol("describeTest/hover");
|
|
10
|
+
|
|
11
|
+
declare module "@adobe/ecs" {
|
|
12
|
+
export interface Components {
|
|
13
|
+
[position]: { type: number };
|
|
14
|
+
[cell]: { type: number };
|
|
15
|
+
}
|
|
16
|
+
export interface Resources {
|
|
17
|
+
[speed]: { type: number };
|
|
18
|
+
[hover]: { type: number };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const byCell = Index.create({ key: [cell] });
|
|
23
|
+
|
|
24
|
+
const spawnCells = System.create(({ commands }) => { commands.spawn([position])({ [position]: 1 }); });
|
|
25
|
+
const move = System.create({ components: { with: [position] }, after: [spawnCells] }, (row, { resources }) => { row[position] += resources[speed]; });
|
|
26
|
+
const deriveCells = System.create({ components: { with: [position], not: [cell] }, after: [move] }, (row, { commands }) => { commands.add(row.entity, cell, row[position]); });
|
|
27
|
+
const pick = System.create({ lookup: { cells: { index: byCell } }, after: [deriveCells] }, ({ lookup, resources }) => { resources[hover] = lookup.cells(1).length; });
|
|
28
|
+
const nudge = System.create({ components: { with: [position], whole: true } }, row => { Reflect.set(row, position, 0); });
|
|
29
|
+
const watch = System.create({ readonly: true, components: { with: [position] } }, row => { void row[position]; });
|
|
30
|
+
const frame = System.create([spawnCells, move, deriveCells, pick, watch]);
|
|
31
|
+
|
|
32
|
+
describe("System.describe", () => {
|
|
33
|
+
it("gives tiers in run order, readonly systems apart, and what each system uses, as data", () => {
|
|
34
|
+
const described = System.describe(frame);
|
|
35
|
+
expect(described.tiers.map(tier => tier.systems.map(system => system.name))).toEqual([["spawnCells"], ["move"], ["deriveCells"], ["pick"]]);
|
|
36
|
+
expect(described.readonly.map(system => system.system)).toEqual([watch]);
|
|
37
|
+
const [[spawning], [moving], [deriving], [picking]] = described.tiers.map(tier => tier.systems);
|
|
38
|
+
expect(spawning?.commands).toEqual({ spawn: [position], add: [], remove: [], despawn: false });
|
|
39
|
+
expect([moving?.components.write, moving?.resources.read, moving?.commands?.add]).toEqual([[position], [speed], []]);
|
|
40
|
+
expect([deriving?.components.read, deriving?.components.not, deriving?.commands?.add]).toEqual([[position], [cell], [cell]]);
|
|
41
|
+
expect([picking?.lookups, picking?.resources.write]).toEqual([[{ name: "cells", index: "byCell", read: [] }], [hover]]);
|
|
42
|
+
expect(moving?.after).toEqual([{ system: spawnCells, name: "spawnCells", because: "after", conflicts: [] }]);
|
|
43
|
+
expect(deriving?.after).toEqual([{ system: move, name: "move", because: "after", conflicts: [position] }]);
|
|
44
|
+
expect(System.describe(nudge).tiers[0]?.systems[0]?.whole).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
it("gives the fewest sync points between tiers whose commands a later system depends on, and why", () => {
|
|
47
|
+
expect(System.describe(frame).tiers.map(tier => tier.sync)).toEqual([
|
|
48
|
+
[{ from: spawnCells, to: move, keys: [position] }, { from: spawnCells, to: deriveCells, keys: [position] }],
|
|
49
|
+
null,
|
|
50
|
+
[{ from: deriveCells, to: pick, keys: [cell] }],
|
|
51
|
+
null,
|
|
52
|
+
]);
|
|
53
|
+
// Despawning may change anything a later system matches; writing values needs no sync point.
|
|
54
|
+
const clear = System.create({ components: { with: [cell] } }, (row, { commands }) => { commands.despawn(row.entity); });
|
|
55
|
+
const count = System.create({ components: { with: [position] }, after: [clear] }, row => { void row[position]; });
|
|
56
|
+
const tally = System.create({ after: [count] }, ({ resources }) => { resources[hover] = resources[speed]; });
|
|
57
|
+
expect(System.describe(System.create([clear, count, tally])).tiers.map(tier => tier.sync)).toEqual([[{ from: clear, to: count, keys: [] }], null, null]);
|
|
58
|
+
});
|
|
59
|
+
it("gives a text form to read", () => {
|
|
60
|
+
expect(System.describe(frame).text).toBe([
|
|
61
|
+
"tier 0 spawnCells commands spawn describeTest/position",
|
|
62
|
+
"sync spawnCells's commands of describeTest/position, for move",
|
|
63
|
+
" spawnCells's commands of describeTest/position, for deriveCells",
|
|
64
|
+
"tier 1 move writes describeTest/position",
|
|
65
|
+
" resources reads describeTest/speed",
|
|
66
|
+
" after spawnCells (after)",
|
|
67
|
+
"tier 2 deriveCells reads describeTest/position",
|
|
68
|
+
" requires not describeTest/cell",
|
|
69
|
+
" commands add describeTest/cell",
|
|
70
|
+
" after move (after: describeTest/position)",
|
|
71
|
+
"sync deriveCells's commands of describeTest/cell, for pick",
|
|
72
|
+
"tier 3 pick resources writes describeTest/hover",
|
|
73
|
+
" looks up cells through byCell",
|
|
74
|
+
" after deriveCells (after)",
|
|
75
|
+
"readonly",
|
|
76
|
+
" watch reads describeTest/position",
|
|
77
|
+
].join("\n"));
|
|
78
|
+
});
|
|
79
|
+
});
|