@adobe/ecs 0.2.0 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- 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/signature.js +1 -1
- package/dist/implementation/compiler/transform.js +3 -3
- package/dist/implementation/compiler/transform.js.map +1 -1
- package/dist/implementation/world/commands.js +50 -1
- package/dist/implementation/world/commands.js.map +1 -1
- 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/finalizers.js.map +1 -1
- package/dist/implementation/world/join.js +10 -2
- package/dist/implementation/world/join.js.map +1 -1
- package/dist/implementation/world/register.js +2 -2
- package/dist/implementation/world/rows.d.ts +1 -1
- package/dist/implementation/world/rows.js +5 -0
- package/dist/implementation/world/rows.js.map +1 -1
- package/dist/implementation/world/runner.js +3 -3
- 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/tables-read.d.ts +7 -0
- package/dist/implementation/world/tables-read.js +60 -0
- package/dist/implementation/world/tables-read.js.map +1 -0
- package/dist/implementation/world/tables.d.ts +9 -0
- package/dist/implementation/world/tables.js +355 -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 +1 -0
- package/dist/implementation/world/thread-systems.js.map +1 -1
- package/dist/index.d.ts +8 -8
- package/dist/index.js +4 -9
- package/dist/index.js.map +1 -1
- package/dist/interface/component/component.d.ts +7 -1
- package/dist/interface/component/component.js +0 -1
- package/dist/interface/component/component.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 +3 -1
- package/dist/interface/component/index.js +1 -1
- package/dist/interface/component/index.js.map +1 -1
- package/dist/interface/component/value.d.ts +5 -5
- package/dist/interface/component/values.d.ts +2 -2
- package/dist/interface/finalizer/body.d.ts +5 -5
- 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 +3 -3
- package/dist/interface/registry/schedule.js +3 -3
- package/dist/interface/registry/schedule.js.map +1 -1
- package/dist/interface/resource/condition.d.ts +3 -3
- 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 -1
- package/dist/interface/resource/index.js +1 -1
- package/dist/interface/resource/index.js.map +1 -1
- package/dist/interface/resource/resource.d.ts +4 -1
- package/dist/interface/resource/resource.js +0 -1
- package/dist/interface/resource/resource.js.map +1 -1
- package/dist/interface/resource/value.d.ts +5 -5
- package/dist/interface/resources/cadence.d.ts +1 -1
- package/dist/interface/system/access.d.ts +1 -1
- package/dist/interface/system/body.d.ts +13 -8
- package/dist/interface/system/check.d.ts +1 -1
- package/dist/interface/system/check.js +25 -9
- package/dist/interface/system/check.js.map +1 -1
- package/dist/interface/system/commands.d.ts +20 -4
- package/dist/interface/system/create.d.ts +21 -9
- package/dist/interface/system/create.js +3 -3
- package/dist/interface/system/create.js.map +1 -1
- package/dist/interface/system/declaration.d.ts +9 -2
- package/dist/interface/system/struct.d.ts +2 -2
- package/dist/interface/system/tables.d.ts +20 -0
- package/dist/interface/system/tables.js +2 -0
- package/dist/interface/system/tables.js.map +1 -0
- package/dist/interface/system/values-of.d.ts +4 -4
- package/dist/interface/system/values.d.ts +5 -5
- package/dist/interface/trait/types.d.ts +4 -4
- package/dist/interface/world/create.d.ts +3 -7
- package/dist/interface/world/declarations.d.ts +2 -2
- package/dist/interface/world/index.d.ts +1 -1
- package/dist/interface/world/join.d.ts +2 -10
- package/dist/interface/world/table/index.d.ts +26 -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 +10 -10
- package/dist/world.d.ts +27 -0
- package/dist/world.js +27 -0
- package/dist/world.js.map +1 -0
- package/package.json +1 -1
- package/src/exports.test.ts +3 -3
- 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 +9 -2
- package/src/guide/06-sync-points.ts +2 -2
- 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/11-traits.type-test.ts +1 -1
- 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 +7 -5
- 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/hygiene.test.ts +1 -1
- package/src/implementation/compiler/plugin.test.ts +2 -2
- package/src/implementation/compiler/signature.ts +1 -1
- package/src/implementation/compiler/transform.test.ts +3 -3
- package/src/implementation/compiler/transform.ts +3 -3
- package/src/implementation/world/change-blocks.test.ts +1 -1
- package/src/implementation/world/commands.ts +44 -1
- package/src/implementation/world/create.ts +3 -3
- package/src/implementation/world/defaults.test.ts +1 -1
- package/src/implementation/world/entities.ts +6 -0
- package/src/implementation/world/finalizers.test.ts +1 -1
- package/src/implementation/world/finalizers.ts +3 -3
- 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/join.test.ts +4 -4
- package/src/implementation/world/join.ts +10 -2
- 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/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/pool-size.cases.ts +3 -1
- package/src/implementation/world/reads.test-helper.ts +6 -6
- 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/rows.ts +5 -1
- package/src/implementation/world/run-results.test.ts +10 -10
- package/src/implementation/world/runner.ts +3 -3
- 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 +8 -8
- package/src/implementation/world/sharing.ts +1 -1
- package/src/implementation/world/staging.ts +16 -3
- package/src/implementation/world/tables-read.ts +59 -0
- package/src/implementation/world/tables.test.ts +267 -0
- package/src/implementation/world/tables.ts +330 -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 +1 -0
- package/src/implementation/world/where.test.ts +4 -4
- package/src/implementation/world/workers.fixture.ts +2 -2
- package/src/index.ts +4 -6
- package/src/interface/component/component.ts +7 -1
- package/src/interface/component/declarations.ts +11 -0
- package/src/interface/component/declared.ts +9 -0
- package/src/interface/component/index.ts +3 -1
- package/src/interface/component/value.ts +5 -5
- package/src/interface/component/values.ts +2 -2
- package/src/interface/finalizer/body.ts +5 -5
- package/src/interface/index.type-test.ts +3 -3
- package/src/interface/indexes/index.type-test.ts +1 -1
- package/src/interface/registry/names.ts +8 -0
- package/src/interface/registry/schedule.ts +4 -4
- package/src/interface/resource/condition.ts +3 -3
- package/src/interface/resource/declarations.ts +11 -0
- package/src/interface/resource/index.ts +2 -1
- package/src/interface/resource/resource.ts +4 -1
- package/src/interface/resource/value.ts +5 -5
- package/src/interface/resources/cadence.ts +1 -1
- package/src/interface/system/access.ts +1 -1
- package/src/interface/system/body.ts +9 -7
- package/src/interface/system/check.ts +22 -8
- package/src/interface/system/commands.ts +18 -4
- package/src/interface/system/create.test.ts +13 -13
- package/src/interface/system/create.ts +15 -10
- package/src/interface/system/declaration.ts +9 -2
- package/src/interface/system/lists.type-test.ts +1 -1
- package/src/interface/system/optional.type-test.ts +1 -1
- package/src/interface/system/shared.type-test.ts +1 -1
- package/src/interface/system/struct.ts +2 -2
- package/src/interface/system/system.type-test.ts +26 -20
- package/src/interface/system/tables.ts +21 -0
- package/src/interface/system/up.type-test.ts +3 -3
- package/src/interface/system/values-of.ts +4 -4
- package/src/interface/system/values.ts +5 -5
- package/src/interface/thread/thread.type-test.ts +1 -1
- package/src/interface/trait/trait.type-test.ts +4 -4
- package/src/interface/trait/types.ts +4 -4
- package/src/interface/world/create.ts +3 -7
- package/src/interface/world/declarations.ts +2 -2
- package/src/interface/world/index.ts +2 -2
- package/src/interface/world/join.ts +2 -10
- package/src/interface/world/loop.type-test.ts +6 -6
- package/src/interface/world/run.type-test.ts +3 -3
- package/src/interface/world/table/index.ts +19 -0
- package/src/interface/world/table/table.ts +12 -0
- package/src/interface/world/world.ts +10 -10
- package/src/observe/entity.ts +2 -2
- package/src/observe/members.ts +3 -3
- package/src/observe/query.ts +1 -1
- package/src/observe/resource.ts +2 -2
- package/src/observe/select.ts +5 -5
- package/src/observe/values.ts +2 -2
- package/src/observe/world-state.ts +3 -3
- package/src/perftest/benchmarks.ts +1 -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/suite.ts +1 -1
- package/src/world.test.ts +2 -2
- package/src/world.ts +32 -0
|
@@ -28,15 +28,15 @@ const checkInputs = (leaves: readonly Leaf[]): void => {
|
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* Leaves in run order, grouped into tiers. Leaves in one tier have no ordering path between them. `reductions` are the
|
|
31
|
-
* resources its leaves reduce into, each with how many of them contribute. `observer` are its
|
|
31
|
+
* resources its leaves reduce into, each with how many of them contribute. `observer` are its readonly leaves, in no
|
|
32
32
|
* tier: they run after its sync point, ordered with nothing.
|
|
33
33
|
*/
|
|
34
34
|
export type Schedule = { readonly tiers: readonly (readonly Leaf[])[]; readonly reductions: ReadonlyMap<Key, number>; readonly observer: readonly Leaf[] };
|
|
35
35
|
|
|
36
36
|
export const isLeaf = (system: Definition): system is Leaf => system.kind !== "composite";
|
|
37
37
|
|
|
38
|
-
/** Whether a system is
|
|
39
|
-
export const isObserver = (system: Definition): system is Leaf & { readonly declaration: { readonly
|
|
38
|
+
/** Whether a system is a readonly system leaf, which runs after a sync point rather than in a tier. */
|
|
39
|
+
export const isObserver = (system: Definition): system is Leaf & { readonly declaration: { readonly readonly: true } } => system.kind !== "composite" && system.declaration.readonly === true;
|
|
40
40
|
|
|
41
41
|
/** Each system is an interval, from its start node to its end node. */
|
|
42
42
|
const start = (unit: number): number => 2 * unit;
|
|
@@ -65,7 +65,7 @@ const graphOf = (root: Definition): Graph => {
|
|
|
65
65
|
};
|
|
66
66
|
visit(root);
|
|
67
67
|
const successors = units.flatMap(() => [new Set<number>(), new Set<number>()]);
|
|
68
|
-
//
|
|
68
|
+
// A readonly system is ordered with nothing, so constraints naming one are ignored.
|
|
69
69
|
const resolve = (systems: readonly object[] | undefined): readonly number[] =>
|
|
70
70
|
(systems ?? []).flatMap(system => {
|
|
71
71
|
const unit = indexOf.get(firstItemOf(system));
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
2
|
import type { Key } from "./key.js";
|
|
3
|
-
import type {
|
|
3
|
+
import type { Type } from './value.js';
|
|
4
4
|
|
|
5
5
|
/** The resources a system can run on the condition of: their type allows `null` or `false`. */
|
|
6
|
-
export type Condition = { [K in Key]: null extends
|
|
6
|
+
export type Condition = { [K in Key]: null extends Type<K> ? K : false extends Type<K> ? K : never }[Key];
|
|
7
7
|
|
|
8
8
|
/** A condition's value while `is` holds: neither `null` nor `false`. */
|
|
9
|
-
export type Holding<K extends Key> = Exclude<
|
|
9
|
+
export type Holding<K extends Key> = Exclude<Type<K>, null | false>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
import type { Declared } from '../component/declared.js';
|
|
3
|
+
import { declaredOf } from '../registry/names.js';
|
|
4
|
+
import type { Key } from './key.js';
|
|
5
|
+
|
|
6
|
+
/** Every resource its modules registered so far, with its declaration. */
|
|
7
|
+
export const declarations = (): ReadonlyMap<Key, Declared> => {
|
|
8
|
+
// Registration takes only keys declared in `Resources`, so each symbol registered as a resource is one of its keys.
|
|
9
|
+
const found = declaredOf("resource") as ReadonlyMap<Key, Declared>;
|
|
10
|
+
return found;
|
|
11
|
+
};
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
import type { Key } from './key.js';
|
|
3
|
+
import type { Type } from './value.js';
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
/** The type of resource `K`'s value, as its record in `Resources` declares it: `Resource<typeof clock>`. */
|
|
6
|
+
export type Resource<K extends Key> = Type<K>;
|
|
4
7
|
|
|
5
8
|
export * as Resource from './index.js';
|
|
@@ -3,19 +3,19 @@ import type { ThreadOf } from '../thread/of.js';
|
|
|
3
3
|
import type { Key } from "./key.js";
|
|
4
4
|
import type { Resources } from "./types.js";
|
|
5
5
|
|
|
6
|
-
/** Every resource's value type, computed once so `
|
|
7
|
-
type
|
|
6
|
+
/** Every resource's value type, computed once so `Type` is a lookup. */
|
|
7
|
+
type Types = { readonly [K in Key]: Resources[K] extends { readonly type: infer T } ? T : never };
|
|
8
8
|
|
|
9
9
|
/** The value type of resource `K`. */
|
|
10
|
-
export type
|
|
10
|
+
export type Type<K extends Key> = Types[K];
|
|
11
11
|
|
|
12
12
|
type Primitive = string | number | boolean | bigint | symbol | null | undefined;
|
|
13
13
|
|
|
14
14
|
/** Resource `K`'s value as reads return it: read-only, with branded primitives such as `Entity` as they are. */
|
|
15
|
-
export type
|
|
15
|
+
export type ReadonlyType<K extends Key> = Type<K> extends Primitive ? Type<K> : Readonly<Type<K>>;
|
|
16
16
|
|
|
17
17
|
/** Every resource's thread, computed once so `ThreadOf` is a lookup. */
|
|
18
|
-
type Threads = { readonly [K in Key]: ThreadOf<Resources[K],
|
|
18
|
+
type Threads = { readonly [K in Key]: ThreadOf<Resources[K], Type<K>> };
|
|
19
19
|
|
|
20
20
|
/** The thread resource `K` lives on, or `never` for a struct, which lives in shared memory. */
|
|
21
21
|
export type ThreadOfResource<K extends Key> = Threads[K];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
2
|
import { registerResource } from '../registry/declarations.js';
|
|
3
3
|
|
|
4
|
-
/** The cadence's value, for the implementation: `Resource
|
|
4
|
+
/** The cadence's value, for the implementation: `Resource<typeof cadence>`. */
|
|
5
5
|
export type Cadence = { readonly kind: "frame" } | { readonly kind: "fixed"; readonly step: number; readonly maxSteps?: number };
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -26,7 +26,7 @@ export type ComponentAccess = {
|
|
|
26
26
|
* that runs the system, once per archetype as it appears and again whenever a resource it reads changed. With
|
|
27
27
|
* change filters, an archetype it skipped keeps its rows unseen until it passes again.
|
|
28
28
|
*/
|
|
29
|
-
readonly where?: { readonly [K in Shared]?: Component
|
|
29
|
+
readonly where?: { readonly [K in Shared]?: Component<K> | ((value: Component<K>, context: { readonly resources: ReadResources }) => boolean) };
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
/**
|
|
@@ -10,20 +10,21 @@ 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>>;
|
|
24
25
|
|
|
25
26
|
/** Each optional component's value or `undefined`, as `ComponentValues` gives members'. */
|
|
26
|
-
type OptionalValues = { readonly [K in Component.Key]: Component
|
|
27
|
+
type OptionalValues = { readonly [K in Component.Key]: Component<K> | undefined };
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
* Its members' values, writable but for references and shared components, and in a system that only reads; its
|
|
@@ -45,14 +46,14 @@ export type ReadResources = Values<Resource.Key, never, Resource.Key, ResourceVa
|
|
|
45
46
|
export type WriteResources = Values<Resource.Key, Writes, Resource.Key, ResourceValues>;
|
|
46
47
|
|
|
47
48
|
/** Each reduced resource, as a function contributing a value to the run's fold. */
|
|
48
|
-
type Reductions = { readonly [K in Reduced]: (value: Resource
|
|
49
|
+
type Reductions = { readonly [K in Reduced]: (value: Resource<K>) => void };
|
|
49
50
|
|
|
50
51
|
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
|
|
@@ -147,15 +147,15 @@ const checkWhere = (leaf: Leaf): void => {
|
|
|
147
147
|
};
|
|
148
148
|
|
|
149
149
|
/**
|
|
150
|
-
* Throws for what
|
|
150
|
+
* Throws for what a readonly system can't declare: it only reads, after a sync point, so it writes and reduces into
|
|
151
151
|
* nothing, takes no input, isn't a kernel or a render system, has no `up`, and is ordered with nothing.
|
|
152
152
|
*/
|
|
153
|
-
const
|
|
153
|
+
const checkReadonly = (leaf: Leaf): void => {
|
|
154
154
|
const { name, declaration } = leaf;
|
|
155
|
-
const {
|
|
156
|
-
if (
|
|
157
|
-
const fail = (message: string): never => { throw new Error(`System "${name}" is
|
|
158
|
-
if (
|
|
155
|
+
const { readonly } = declaration;
|
|
156
|
+
if (readonly === undefined) return;
|
|
157
|
+
const fail = (message: string): never => { throw new Error(`System "${name}" is readonly, so it ${message}.`); };
|
|
158
|
+
if (readonly !== true) throw new Error(`System "${name}": readonly is true or left out.`);
|
|
159
159
|
if (leaf.kind === "render") fail("records no GPU work");
|
|
160
160
|
if (declaration.kernel === true || declaration.kernel === "gpu") fail("isn't a kernel");
|
|
161
161
|
if ((declaration.components?.write?.length ?? 0) + (declaration.resources?.write?.length ?? 0) > 0) fail("writes nothing");
|
|
@@ -165,9 +165,22 @@ const checkObserver = (leaf: Leaf): void => {
|
|
|
165
165
|
if ((declaration.before?.length ?? 0) + (declaration.after?.length ?? 0) + (declaration.during?.length ?? 0) > 0) fail("is ordered with nothing");
|
|
166
166
|
};
|
|
167
167
|
|
|
168
|
+
/** Throws for `tables` on a system that isn't readonly or has components, or naming keys that aren't registered as their kind. */
|
|
169
|
+
const checkTables = (leaf: Leaf): void => {
|
|
170
|
+
const { name, declaration } = leaf;
|
|
171
|
+
const { tables } = declaration;
|
|
172
|
+
if (tables === undefined) return;
|
|
173
|
+
if (declaration.readonly !== true) throw new Error(`System "${name}" reads tables, which only a readonly system reads.`);
|
|
174
|
+
if (declaration.components !== undefined) throw new Error(`System "${name}" reads tables, so it has no components.`);
|
|
175
|
+
requireRegistered("System", name, "component", tables.components);
|
|
176
|
+
requireRegistered("System", name, "component", tables.with);
|
|
177
|
+
requireRegistered("System", name, "component", tables.not);
|
|
178
|
+
requireRegistered("System", name, "resource", tables.resources);
|
|
179
|
+
};
|
|
180
|
+
|
|
168
181
|
/**
|
|
169
182
|
* 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
|
|
183
|
+
* `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
184
|
* reductions, conditions, `up` or placement can't hold.
|
|
172
185
|
*/
|
|
173
186
|
export const checkLeaf = (leaf: Leaf): void => {
|
|
@@ -190,6 +203,7 @@ export const checkLeaf = (leaf: Leaf): void => {
|
|
|
190
203
|
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
204
|
}
|
|
192
205
|
checkTarget(leaf);
|
|
206
|
+
checkTables(leaf);
|
|
193
207
|
threadOf(declaration, "System", name);
|
|
194
208
|
requireRegistered("System", name, "component", components?.has);
|
|
195
209
|
requireRegistered("System", name, "component", components?.not);
|
|
@@ -202,7 +216,7 @@ export const checkLeaf = (leaf: Leaf): void => {
|
|
|
202
216
|
if (declarationOf(key)?.reference !== undefined) throw new Error(`System "${name}" writes "${labelOf(key)}", a reference, which changes only through commands.`);
|
|
203
217
|
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
218
|
}
|
|
205
|
-
|
|
219
|
+
checkReadonly(leaf);
|
|
206
220
|
checkWhere(leaf);
|
|
207
221
|
checkReductions(leaf);
|
|
208
222
|
checkConditions(leaf);
|
|
@@ -2,16 +2,17 @@
|
|
|
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';
|
|
5
6
|
import type { ValuesOf } from './values-of.js';
|
|
6
7
|
|
|
7
8
|
/** The value an `add` of one component takes: none for a tag. */
|
|
8
|
-
type ValueArgument<K extends Essential> = Component
|
|
9
|
+
type ValueArgument<K extends Essential> = Component<K> extends true ? [value?: true] : [value: Component<K>];
|
|
9
10
|
|
|
10
11
|
/** Values of components of `C` a spawner's first call may give, constant for every entity it spawns. */
|
|
11
|
-
type Preset<C extends readonly Essential[]> = { readonly [K in C[number]]?: Component
|
|
12
|
+
type Preset<C extends readonly Essential[]> = { readonly [K in C[number]]?: Component<K> };
|
|
12
13
|
|
|
13
14
|
/** The value of each shared component of `C`, which a spawner's first call must give, since they choose the archetype. */
|
|
14
|
-
type SharedPreset<C extends readonly Essential[]> = { readonly [K in C[number] & Shared]: Component
|
|
15
|
+
type SharedPreset<C extends readonly Essential[]> = { readonly [K in C[number] & Shared]: Component<K> };
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Whether `C` names a shared component, so a spawner of it takes its constants: a list that isn't a tuple, whose
|
|
@@ -27,7 +28,7 @@ type Named<C extends readonly Essential[], P> = { readonly [K in Exclude<keyof P
|
|
|
27
28
|
* the spawn takes their values first.
|
|
28
29
|
*/
|
|
29
30
|
type Spawned<C extends readonly Essential[], P> = RequiresPreset<C> extends true
|
|
30
|
-
? [keyof P] extends [never] ? C & { readonly "error: a spawn of a shared component takes its value first, in its constants": { readonly [K in C[number] & Shared]: Component
|
|
31
|
+
? [keyof P] extends [never] ? C & { readonly "error: a spawn of a shared component takes its value first, in its constants": { readonly [K in C[number] & Shared]: Component<K> } } : C
|
|
31
32
|
: C;
|
|
32
33
|
|
|
33
34
|
/** Each component of `C` the constants `P` gave, whose value a spawn's values give again as a message naming it. */
|
|
@@ -68,4 +69,17 @@ export type Commands = {
|
|
|
68
69
|
add<const C extends readonly Essential[]>(entity: Entity, components: C, values: ValuesOf<C>): void;
|
|
69
70
|
remove(entity: Entity, component: Essential): void;
|
|
70
71
|
remove(entity: Entity, components: readonly Essential[]): void;
|
|
72
|
+
/**
|
|
73
|
+
* Spawns an entity for each row of `tables`, as a readonly system's `tables` gives them, of each table's components
|
|
74
|
+
* with its columns' values: a block copy per column. Entity values are rewritten as the world's references would
|
|
75
|
+
* leave them had the entities outside the tables despawned: each naming an entity of the tables, a reference, a
|
|
76
|
+
* struct's field or an array's item, names the one spawned for it; a row whose owner reference names an entity
|
|
77
|
+
* outside them isn't spawned, nor anything it owns, through owner references, as despawning its owner would; a
|
|
78
|
+
* weak reference to one is left out of its row, which is spawned without it; any other value naming one is
|
|
79
|
+
* `Entity.none`. Returns, at once, each table's new entities, alive at the sync point. A column's stride must be its
|
|
80
|
+
* component's in this world, and its length its table's count. `resources` sets each resource it names, by key, at
|
|
81
|
+
* the sync point, as commands apply, each value checked against its resource's schema; not one only the world
|
|
82
|
+
* writes, nor one living on another thread. On the world's thread only.
|
|
83
|
+
*/
|
|
84
|
+
spawnTables(tables: readonly Table[], resources?: { readonly [key: symbol]: unknown }): Table.Spawned;
|
|
71
85
|
};
|
|
@@ -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,9 @@ 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("runs
|
|
174
|
+
it("runs readonly members after its sync point, in no tier, never conflicting and ignoring constraints naming them", () => {
|
|
175
175
|
const write = System.create({ components: { with: [position] } }, row => { row[position] += 1; });
|
|
176
|
-
const watch = System.create({
|
|
176
|
+
const watch = System.create({ readonly: true, components: { with: [position] } }, row => { void row[position]; });
|
|
177
177
|
const later = System.create({ components: { with: [position] }, after: [watch, write] }, row => { void row[position]; });
|
|
178
178
|
const free = System.create({ after: [watch] }, ({ resources }) => { void resources[time]; });
|
|
179
179
|
const schedule = scheduleOf(requireDefinition(System.create([write, watch, later, free])));
|
|
@@ -205,6 +205,6 @@ describe("System.render", () => {
|
|
|
205
205
|
const render = (declaration: object, uses: Parameters<typeof giveAccess>[1] = {}): unknown => Reflect.apply(System.render, undefined, [giveAccess(declaration, uses), () => {}]);
|
|
206
206
|
expect(() => System.render({ shader: { accessors: { loop: height } } }, () => {})).toThrow(`can't render: "loop" isn't usable as a WGSL function name.`);
|
|
207
207
|
expect(() => render({ shader: { accessors: {} } }, { resources: { reduce: [total] } })).toThrow("writes or reduces into resources; a render system only reads");
|
|
208
|
-
expect(() => render({ shader: { accessors: {} },
|
|
208
|
+
expect(() => render({ shader: { accessors: {} }, readonly: true })).toThrow("is readonly, so it records no GPU work");
|
|
209
209
|
});
|
|
210
210
|
});
|
|
@@ -50,10 +50,10 @@ type GpuKernel = {
|
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
|
-
*
|
|
53
|
+
* A readonly system only reads, after a sync point: its row and resources are read-only, and it never compiles to a
|
|
54
54
|
* kernel, has no `up`, and is ordered with nothing. That it takes no input is checked at its body.
|
|
55
55
|
*/
|
|
56
|
-
type
|
|
56
|
+
type Readonly = {
|
|
57
57
|
readonly kernel?: false;
|
|
58
58
|
readonly up?: never;
|
|
59
59
|
readonly before?: never;
|
|
@@ -97,7 +97,12 @@ type Validate<D extends Declaration> =
|
|
|
97
97
|
// GPU kernels don't read components an archetype may lack yet.
|
|
98
98
|
& (D extends { readonly components: { readonly optional: readonly [unknown, ...unknown[]] } }
|
|
99
99
|
? { readonly kernel?: boolean | { readonly "error: a GPU kernel can't have optional components yet": true } } : {})
|
|
100
|
-
& (D extends { readonly
|
|
100
|
+
& (D extends { readonly readonly: true } ? Readonly : {})
|
|
101
|
+
// Tables are copies a readonly system without components reads.
|
|
102
|
+
& (D extends { readonly tables: object }
|
|
103
|
+
? (D extends { readonly readonly: true } ? {} : { readonly tables?: { readonly "error: tables are read by a readonly system": true } })
|
|
104
|
+
& (Property<D, "components"> extends undefined ? {} : { readonly tables?: { readonly "error: a system reading tables has no components": true } })
|
|
105
|
+
: {})
|
|
101
106
|
& (D extends { readonly target: string } ? Targeted : {})
|
|
102
107
|
& (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
108
|
// A body's input is the type of its parameter after its context, not part of its declaration.
|
|
@@ -109,7 +114,7 @@ type EntityFields<I> = { [K in keyof I]-?: [Exclude<I[K], undefined>] extends [E
|
|
|
109
114
|
/**
|
|
110
115
|
* What a declaration requires of the input its body takes, checked at the body, since the input is known only once the
|
|
111
116
|
* 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
|
|
117
|
+
* runs pass different input, nor is it a readonly system. Every check passes for `never`, the input before the body is
|
|
113
118
|
* inferred.
|
|
114
119
|
*/
|
|
115
120
|
type Checks<D, I> = [I] extends [never] ? unknown :
|
|
@@ -119,7 +124,7 @@ type Checks<D, I> = [I] extends [never] ? unknown :
|
|
|
119
124
|
: unknown)
|
|
120
125
|
& ([I] extends [undefined] ? unknown
|
|
121
126
|
: (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
|
|
127
|
+
& (D extends { readonly readonly: true } ? { readonly "error: a readonly system takes no input": true } : unknown));
|
|
123
128
|
|
|
124
129
|
/** The body of a system that declares nothing, which runs once per run with its resources and commands. */
|
|
125
130
|
type Alone = (context: Context<{}>) => void;
|
|
@@ -144,10 +149,10 @@ type Checked<C> = C extends Alone ? ReturnsNoPromise<C> : C extends CompositeDec
|
|
|
144
149
|
* input, with every field of theirs. Throws if a system names a key that isn't registered as its kind, its body writes
|
|
145
150
|
* 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
151
|
* 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
|
|
152
|
+
* different threads, or its reductions, conditions, `up`, `order` or placement can't hold, or it's a readonly system and
|
|
148
153
|
* writes, reduces, takes input, is a kernel, has `up` or is ordered; if the build couldn't follow how its body uses its
|
|
149
154
|
* 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
|
|
155
|
+
* declare an input field with different schemas. A composite's readonly members run after its sync point, ordered
|
|
151
156
|
* with nothing.
|
|
152
157
|
*
|
|
153
158
|
* A run of a system without components resolves to what its body returns, or `undefined` when its conditions, resource
|
|
@@ -180,13 +185,13 @@ export function create(first: Declaration | Leaf["body"] | readonly System<unkno
|
|
|
180
185
|
|
|
181
186
|
/** A declaration as the world takes it, with what the build inferred its body uses. */
|
|
182
187
|
const resolve = (what: string, declared: Declaration, access: ReturnType<typeof accessOf>): Resolved => {
|
|
183
|
-
const reading = declared.
|
|
188
|
+
const reading = declared.readonly === true;
|
|
184
189
|
const { components, lookup, up, resources, ...rest } = declared;
|
|
185
190
|
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
191
|
const whereReads = whereReadsOf(what, components, access);
|
|
188
192
|
const conditions: readonly symbol[] = resources?.is ?? [];
|
|
189
|
-
|
|
193
|
+
// What `where`'s predicates and `tables` read counts as the system's reads, for its order, conflicts and placement.
|
|
194
|
+
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
195
|
const resolved: Resolved = {
|
|
191
196
|
...rest,
|
|
192
197
|
...components === undefined ? {} : { components: componentLists(components, access.components, reading, whereReads) },
|
|
@@ -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. */
|
|
@@ -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;
|
|
@@ -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
|
|
|
@@ -4,7 +4,7 @@ import type { Resource } from '../resource/resource.js';
|
|
|
4
4
|
import type { IsStruct } from '../schema/is-struct.js';
|
|
5
5
|
|
|
6
6
|
/** Components whose values are structs, which kernels read and write. */
|
|
7
|
-
export type StructComponent = { [K in Component.Key]: IsStruct<Component
|
|
7
|
+
export type StructComponent = { [K in Component.Key]: IsStruct<Component<K>> extends true ? K : never }[Component.Key];
|
|
8
8
|
|
|
9
9
|
/** Resources whose values are structs, which kernels read. */
|
|
10
|
-
export type StructResource = { [K in Resource.Key]: IsStruct<Resource
|
|
10
|
+
export type StructResource = { [K in Resource.Key]: IsStruct<Resource<K>> extends true ? K : never }[Resource.Key];
|
|
@@ -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,21 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
import type { Component } from '../component/component.js';
|
|
3
|
+
import type { Resource } from '../resource/resource.js';
|
|
4
|
+
import type { Table } from '../world/table/table.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* What a readonly system's `tables` reads: of every archetype matching `with` and `not` that holds any of
|
|
8
|
+
* `components`, a table of those it holds; and, with `resources`, each resource's value.
|
|
9
|
+
*/
|
|
10
|
+
export type TablesDeclaration = {
|
|
11
|
+
readonly components: readonly Component.Key[];
|
|
12
|
+
readonly with?: readonly Component.Key[];
|
|
13
|
+
readonly not?: readonly Component.Key[];
|
|
14
|
+
readonly resources?: readonly Resource.Key[];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** A readonly system's `tables`: a table of each archetype its declaration reads, and the values of the resources it reads. */
|
|
18
|
+
export type Tables = {
|
|
19
|
+
readonly archetypes: readonly Table[];
|
|
20
|
+
readonly resources: { readonly [K in Resource.Key]?: Resource<K> };
|
|
21
|
+
};
|
|
@@ -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 } }, () => {});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { Component } from '../component/component.js';
|
|
3
3
|
|
|
4
4
|
/** Components a spawn or add may leave out: a tag, whose value is `true`, and one whose type allows `null`, its start. */
|
|
5
|
-
type Omissible<K extends Component.Key> = Component
|
|
5
|
+
type Omissible<K extends Component.Key> = Component<K> extends true ? K : null extends Component<K> ? K : never;
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* The values a spawn or add of the components `C` takes, keyed by component and exact against `C`, but those `Given`
|
|
@@ -12,6 +12,6 @@ type Omissible<K extends Component.Key> = Component.Value<K> extends true ? K :
|
|
|
12
12
|
*/
|
|
13
13
|
export type ValuesOf<C extends readonly Component.Key[], Given extends PropertyKey = never> = number extends C["length"]
|
|
14
14
|
// A list whose components aren't known until it runs, so any of them may be given.
|
|
15
|
-
? { readonly [K in Exclude<C[number], Given>]?: Component
|
|
16
|
-
: { readonly [K in Exclude<C[number], Given> as K extends Omissible<K> ? never : K]: Component
|
|
17
|
-
& { readonly [K in Exclude<C[number], Given> as K extends Omissible<K> ? K : never]?: Component
|
|
15
|
+
? { readonly [K in Exclude<C[number], Given>]?: Component<K> }
|
|
16
|
+
: { readonly [K in Exclude<C[number], Given> as K extends Omissible<K> ? never : K]: Component<K> }
|
|
17
|
+
& { readonly [K in Exclude<C[number], Given> as K extends Omissible<K> ? K : never]?: Component<K> };
|