@adobe/ecs 0.2.1 → 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/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/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 -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/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/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/body.d.ts +11 -6
- 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 +16 -0
- 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/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/world/index.d.ts +1 -1
- 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 +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 +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/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/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/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/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 -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/index.type-test.ts +2 -2
- 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/declarations.ts +11 -0
- package/src/interface/resource/index.ts +2 -0
- package/src/interface/system/body.ts +7 -5
- package/src/interface/system/check.ts +22 -8
- package/src/interface/system/commands.ts +14 -0
- 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/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/world/index.ts +2 -2
- 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/resource.ts +2 -2
- package/src/observe/select.ts +4 -4
- 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
|
@@ -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 } }, () => {});
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
-
// The `World` namespace's
|
|
3
|
-
export {};
|
|
2
|
+
// The `World` namespace's types.
|
|
3
|
+
export type { Table } from './table/table.js';
|
|
@@ -19,7 +19,7 @@ declare module '@adobe/ecs' {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
// A run resolves to its per-run body's value: a one-off read is an observer system returning it.
|
|
22
|
-
const count = System.create({
|
|
22
|
+
const count = System.create({ readonly: true }, ({ resources }) => resources[frames]);
|
|
23
23
|
check<Equal<typeof count, System<undefined, number>>>();
|
|
24
24
|
const counted: Promise<number> = world.run(count);
|
|
25
25
|
// @ts-expect-error a run of it resolves to a number
|
|
@@ -32,7 +32,7 @@ const scaled = System.create({}, ({ resources }, input: { readonly by: number })
|
|
|
32
32
|
const scaledValue: Promise<{ scaled: number }> = world.run(scaled, { by: 2 });
|
|
33
33
|
|
|
34
34
|
// A body that may be skipped, as one whose conditions or resource change filters can fail, may give undefined.
|
|
35
|
-
const changedFrames = System.create({
|
|
35
|
+
const changedFrames = System.create({ readonly: true, resources: { changed: [frames] } }, ({ resources }) => resources[frames]);
|
|
36
36
|
check<Equal<typeof changedFrames, System<undefined, number | undefined>>>();
|
|
37
37
|
const whileDevice = System.create({ resources: { is: [gpuDevice] } }, () => "drawn");
|
|
38
38
|
const drawn: Promise<string | undefined> = world.run(whileDevice);
|
|
@@ -68,7 +68,7 @@ void [counted, notText, nextValue, scaledValue, drawn, rows, composite, input, n
|
|
|
68
68
|
// @ts-expect-error a body returns no promise
|
|
69
69
|
System.create(() => Promise.resolve(1));
|
|
70
70
|
// @ts-expect-error a body returns no promise
|
|
71
|
-
System.create({
|
|
71
|
+
System.create({ readonly: true }, () => Promise.resolve(1));
|
|
72
72
|
// @ts-expect-error a body returns no promise, a row system's neither
|
|
73
73
|
System.create({ components: { with: [position] } }, () => Promise.resolve());
|
|
74
74
|
// @ts-expect-error a body returns no promise, a render system's neither
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* One component's values in a table, copies taken as a readonly system reads `tables`:
|
|
5
|
+
* - `bytes`: a struct's or number's, `stride` bytes per row in the world's layout for it, which is a function of the
|
|
6
|
+
* component's schema alone;
|
|
7
|
+
* - `values`: a string's or an object's, one per row;
|
|
8
|
+
* - `value`: one value every row holds, for a shared component or one whose schema is a constant.
|
|
9
|
+
*/
|
|
10
|
+
export type Column =
|
|
11
|
+
| { readonly kind: "bytes"; readonly bytes: Uint8Array; readonly stride: number }
|
|
12
|
+
| { readonly kind: "values"; readonly values: readonly unknown[] }
|
|
13
|
+
| { readonly kind: "value"; readonly value: unknown };
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* What `commands.spawnTables` spawned: each table's new entities in its row order, `Entity.none` for a row it didn't
|
|
17
|
+
* spawn, and how many rows it didn't, those owned by an entity outside the tables.
|
|
18
|
+
*/
|
|
19
|
+
export type Spawned = { readonly entities: readonly Float64Array[]; readonly dropped: number };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
import type { Component } from '../../component/component.js';
|
|
3
|
+
import type { Column } from './index.js';
|
|
4
|
+
|
|
5
|
+
/** The rows of one archetype: their entities in row order, and a column for each component asked for that it holds. */
|
|
6
|
+
export type Table = {
|
|
7
|
+
readonly count: number;
|
|
8
|
+
readonly entities: Float64Array;
|
|
9
|
+
readonly columns: { readonly [K in Component.Key]?: Column };
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type * as Table from './index.js';
|
|
@@ -3,23 +3,23 @@ import type { Input } from '../system/input.js';
|
|
|
3
3
|
import type { System } from '../system/system.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* A world. Systems read and change it;
|
|
6
|
+
* A world. Systems read and change it; readonly systems read it after each run's and tick's sync point. Every member's
|
|
7
7
|
* type is named from the package root, so declarations a consumer emits for its results name no internal module.
|
|
8
8
|
*/
|
|
9
9
|
export type World = {
|
|
10
10
|
/**
|
|
11
11
|
* The one way to change state. Runs are sequential in call order; physically, any that don't conflict overlap. Each
|
|
12
|
-
* runs a system or composite, ending in a sync point, then
|
|
12
|
+
* runs a system or composite, ending in a sync point, then readonly systems, the run's and those added, and
|
|
13
13
|
* resolves to what a per-run body returns: a system without components resolves to its body's value, or
|
|
14
14
|
* `undefined` when its body was skipped, and any other system or composite to nothing. So a one-off read is a run
|
|
15
|
-
* of
|
|
16
|
-
* there before the run resolves, to its value as structured clone. A run of
|
|
15
|
+
* of a readonly system returning what it read; one that runs on a joined thread, as it reads objects there, runs
|
|
16
|
+
* there before the run resolves, to its value as structured clone. A run of readonly systems alone runs them once
|
|
17
17
|
* after an empty sync point, and doesn't wake the loop.
|
|
18
18
|
*
|
|
19
19
|
* When a system or finalizer of the run fails, later tiers are skipped, the sync point still applies what was done,
|
|
20
|
-
* and once its
|
|
20
|
+
* and once its readonly systems ran the run rejects with an `Error` naming each failure, its system's label and id,
|
|
21
21
|
* message and cause. It rejects too when the run can't happen or finish: registration fails, the world is disposed,
|
|
22
|
-
* its GPU device is lost, or its sync point can't apply a command's value. What fails after it,
|
|
22
|
+
* its GPU device is lost, or its sync point can't apply a command's value. What fails after it, a readonly system
|
|
23
23
|
* added or GPU work, is reported out of band, as ticks' failures are (see `add`).
|
|
24
24
|
*/
|
|
25
25
|
run<I, R>(system: System<I, R>, ...input: Input<NoInfer<I>>): Promise<R>;
|
|
@@ -27,9 +27,9 @@ export type World = {
|
|
|
27
27
|
* Adds a system to every tick, from the next one on, ordered with those already added by its constraints, as a
|
|
28
28
|
* composite's members are. A tick runs on a beat of the `cadence` resource where `always` is set, `wake` is due or
|
|
29
29
|
* a run arrived since the last tick, and only while some system is added. Throws for a system with no ordering path
|
|
30
|
-
* to one already added that conflicts with it.
|
|
30
|
+
* to one already added that conflicts with it. A readonly system runs after every run's and tick's sync point
|
|
31
31
|
* instead. It never conflicts and doesn't count as an added system for ticking. On a joined thread `add` takes only
|
|
32
|
-
*
|
|
32
|
+
* readonly systems, whose keys need ids: each runs on that thread after the world's sync points, once for the
|
|
33
33
|
* newest when the thread fell behind, reading structs and the objects of the threads it fills as of one sync point.
|
|
34
34
|
*
|
|
35
35
|
* What added systems throw, and what fails in ticks, is reported out of band on the world's thread, as an `Error`
|
|
@@ -43,14 +43,14 @@ export type World = {
|
|
|
43
43
|
* released. A system added both alone and within an added composite stays until both are removed; constraints
|
|
44
44
|
* naming a removed system are ignored. Removing a value that isn't added does nothing. Throws, removing nothing,
|
|
45
45
|
* when the systems left would conflict with no ordering path between them, as `add` does. On a joined thread it
|
|
46
|
-
* takes only
|
|
46
|
+
* takes only readonly systems.
|
|
47
47
|
*/
|
|
48
48
|
remove(system: System<void>): void;
|
|
49
49
|
/**
|
|
50
50
|
* Lets another thread join this world through `port`, one end of a channel to it, of the browser or node: a
|
|
51
51
|
* `MessagePort`, a `Worker`, or a worker's own scope, `self` or node's `parentPort`. That thread joins through the
|
|
52
52
|
* other end, claiming the logical threads it fills. Resolves once it has joined: the threads it claims are its own,
|
|
53
|
-
* and the
|
|
53
|
+
* and the readonly systems it added as its join resolved, before awaiting anything else, are added, so a run
|
|
54
54
|
* started afterwards runs on it and reaches them; one it adds later reaches the runs after its add arrived.
|
|
55
55
|
* Rejects with an `Error` saying why it didn't join: the world refused its claim, as of a thread claimed twice, the
|
|
56
56
|
* world was disposed, the port is shared already, or that thread ended first, as the platform reports it: a
|
package/src/observe/entity.ts
CHANGED
|
@@ -129,14 +129,14 @@ const createShape = (state: WorldState, byShape: Map<string, EntityShape>, id: s
|
|
|
129
129
|
let finders: Finders | null = null;
|
|
130
130
|
let source: object | null = null;
|
|
131
131
|
const system = optional.length === 0
|
|
132
|
-
? System.create(labelled({
|
|
132
|
+
? System.create(labelled({ readonly: true, lookup: { presence: { with: required, changed: required }, values: { with: required }, members: { index: membersOf(required), changed: required } } }, name, { lookup: { values: required } }), ({ lookup }) => {
|
|
133
133
|
if (source !== lookup || finders === null) {
|
|
134
134
|
source = lookup;
|
|
135
135
|
finders = { presence: lookup.presence, values: lookup.values, optional: noOptional };
|
|
136
136
|
}
|
|
137
137
|
pass(lookup.members().changed, finders);
|
|
138
138
|
})
|
|
139
|
-
: System.create(labelled({
|
|
139
|
+
: System.create(labelled({ readonly: true, lookup: optionalLookups(required, optional) }, name, { lookup: optionalReads(required, optional) }), ({ lookup }) => {
|
|
140
140
|
if (source !== lookup || finders === null) {
|
|
141
141
|
source = lookup;
|
|
142
142
|
finders = { presence: lookup.presence, values: lookup.values, optional: (index, entity) => lookup[index](entity) };
|
package/src/observe/resource.ts
CHANGED
|
@@ -31,14 +31,14 @@ const createShape = (state: WorldState, byKey: Map<Resource.Key, ResourceShape>,
|
|
|
31
31
|
const label = `Observe.resource(${key.description ?? "?"})`;
|
|
32
32
|
// On a joined thread, first values come once a run of this system resolved, never in its pass: none to skip.
|
|
33
33
|
const { joined } = state;
|
|
34
|
-
const notifier = System.create(labelled({
|
|
34
|
+
const notifier = System.create(labelled({ readonly: true, resources: { changed: [key] } }, label, { resources: [key] }), ({ resources }) => {
|
|
35
35
|
passed(state);
|
|
36
36
|
entry.value = resources[key];
|
|
37
37
|
entry.valid = true;
|
|
38
38
|
if (entry.stopped > 0) sweepSubscribers(entry);
|
|
39
39
|
throwFailed(give(entry, 0, true, state.pass, joined ? -1 : state.pass, none));
|
|
40
40
|
});
|
|
41
|
-
const reader = System.create(labelled({
|
|
41
|
+
const reader = System.create(labelled({ readonly: true }, label, { resources: [key] }), ({ resources }) => {
|
|
42
42
|
const through = shape.called.shift() ?? 0;
|
|
43
43
|
entry.value = resources[key];
|
|
44
44
|
entry.valid = true;
|
package/src/observe/select.ts
CHANGED
|
@@ -403,16 +403,16 @@ const createShape = (state: WorldState, byShape: Map<string, QueryShape>, id: st
|
|
|
403
403
|
};
|
|
404
404
|
|
|
405
405
|
const tracker = ordered !== null
|
|
406
|
-
? System.create(labelled({
|
|
406
|
+
? System.create(labelled({ readonly: true, lookup: { list: { index: ordered, changed: reads } } }, name, { lookup: { list: reads } }), ({ lookup }) => {
|
|
407
407
|
track(lookup.list(), null, fromSorted);
|
|
408
408
|
})
|
|
409
409
|
: byRange !== null
|
|
410
|
-
? System.create(labelled({
|
|
410
|
+
? System.create(labelled({ readonly: true, lookup: { list: { index: byRange, changed: reads } } }, name, { lookup: { list: reads } }), ({ lookup }) => {
|
|
411
411
|
// A range is of a `where` condition on the index's key, which the query's type checked against its
|
|
412
412
|
// values.
|
|
413
413
|
track(null, entry => ownRange(entry, range => lookup.list(range as Parameters<typeof lookup.list>[0]), () => lookup.list()), fromSorted);
|
|
414
414
|
})
|
|
415
|
-
: System.create(labelled({
|
|
415
|
+
: System.create(labelled({ readonly: true, lookup: { list: { index: keylessOf(has, not), changed: reads } } }, name, { lookup: { list: reads } }), ({ lookup }) => {
|
|
416
416
|
track(lookup.list(), null, fromKeyless);
|
|
417
417
|
});
|
|
418
418
|
|
|
@@ -466,7 +466,7 @@ const createShape = (state: WorldState, byShape: Map<string, QueryShape>, id: st
|
|
|
466
466
|
for (const entry of scan.entries) entry.gather.start();
|
|
467
467
|
accept = acceptOf(scan.entries);
|
|
468
468
|
};
|
|
469
|
-
const scanner = System.create(labelled({
|
|
469
|
+
const scanner = System.create(labelled({ readonly: true, components: { with: [...reads, ...required.filter(key => !reads.includes(key))], not } }, name, { components: reads }), row => {
|
|
470
470
|
if (accept === null) start();
|
|
471
471
|
if (direct === 0) {
|
|
472
472
|
accept?.(row);
|
|
@@ -116,7 +116,7 @@ const ignore = (): void => {
|
|
|
116
116
|
};
|
|
117
117
|
|
|
118
118
|
/** Runs nothing: a run of it resolves while its world lives. */
|
|
119
|
-
const alive = System.create(labelled({
|
|
119
|
+
const alive = System.create(labelled({ readonly: true }, "Observe/alive"), () => {});
|
|
120
120
|
|
|
121
121
|
/**
|
|
122
122
|
* What a rejected run of the factories' own does: nothing for a disposed world, whose first values never come, and for
|
|
@@ -232,7 +232,7 @@ export const stateOf = (world: World): WorldState => {
|
|
|
232
232
|
deliveries: [],
|
|
233
233
|
idle: [],
|
|
234
234
|
rejected: rejectedOf(world),
|
|
235
|
-
deliver: System.create(labelled({
|
|
235
|
+
deliver: System.create(labelled({ readonly: true }, "Observe/deliver"), () => {
|
|
236
236
|
const delivery = created.deliveries.shift();
|
|
237
237
|
if (delivery === undefined) return;
|
|
238
238
|
created.delivered = delivery.batch;
|
|
@@ -243,7 +243,7 @@ export const stateOf = (world: World): WorldState => {
|
|
|
243
243
|
states.set(world, created);
|
|
244
244
|
if (joined) return created;
|
|
245
245
|
// Added before any shape's system, so in each pass it counts the pass before they run.
|
|
246
|
-
world.add(System.create(labelled({
|
|
246
|
+
world.add(System.create(labelled({ readonly: true }, "Observe/maintain"), () => {
|
|
247
247
|
created.pass++;
|
|
248
248
|
if (created.idle.length > 0) sweepShapes(created);
|
|
249
249
|
}));
|
|
@@ -505,7 +505,7 @@ export const benchmarks: readonly Benchmark[] = [
|
|
|
505
505
|
run: async () => {
|
|
506
506
|
const { world, roots } = await filteredForest();
|
|
507
507
|
const found: { child: Entity | null } = { child: null };
|
|
508
|
-
await world.run(System.create({
|
|
508
|
+
await world.run(System.create({ readonly: true, components: { with: [hierarchyParent] } }, row => {
|
|
509
509
|
if (found.child === null && row[hierarchyParent] === roots[0]) found.child = row.entity;
|
|
510
510
|
}));
|
|
511
511
|
const { child } = found;
|
package/src/perftest/indexes.ts
CHANGED
|
@@ -307,13 +307,13 @@ const seedFlags = System.create({}, ({ commands }) => {
|
|
|
307
307
|
for (let i = 0; i < flagged; i++) commands.spawn([flag])({ [flag]: i % 2 === 0 });
|
|
308
308
|
});
|
|
309
309
|
/** A run of observer systems alone, which passes every one added. */
|
|
310
|
-
const pass = System.create({
|
|
310
|
+
const pass = System.create({ readonly: true }, () => {});
|
|
311
311
|
/** A count of each bucket, written only when the index's entries changed: by `changed: true`, or by its lists' `changed`. */
|
|
312
312
|
const countsWhenChanged = (filtered: boolean): System => filtered
|
|
313
|
-
? System.create({
|
|
313
|
+
? System.create({ readonly: true, lookup: { byFlag: { index: byFlag, changed: true } } }, ({ lookup }) => {
|
|
314
314
|
found += lookup.byFlag(false).length + lookup.byFlag(true).length;
|
|
315
315
|
})
|
|
316
|
-
: System.create({
|
|
316
|
+
: System.create({ readonly: true, lookup: { byFlag: { index: byFlag } } }, ({ lookup }) => {
|
|
317
317
|
const off = lookup.byFlag(false);
|
|
318
318
|
const on = lookup.byFlag(true);
|
|
319
319
|
if (!off.changed && !on.changed) return;
|
package/src/perftest/loop.ts
CHANGED
|
@@ -12,7 +12,7 @@ export const loopTicks = 10_000;
|
|
|
12
12
|
export const observerIdles = 100;
|
|
13
13
|
|
|
14
14
|
/** Observer systems no entity matches, each run after every tick. */
|
|
15
|
-
const idleObserver = Array.from({ length: observerIdles }, (_, i) => System.create({
|
|
15
|
+
const idleObserver = Array.from({ length: observerIdles }, (_, i) => System.create({ readonly: true, components: { with: [unused] } }, () => {}));
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* The fastest of `stretches` stretches of `loopTicks` ticks with nothing changed, after one to warm up: a world with
|
package/src/perftest/observer.ts
CHANGED
|
@@ -14,14 +14,14 @@ import type { World } from "../interface/world/world.js";
|
|
|
14
14
|
* first call only starts the stretch that warms up.
|
|
15
15
|
*/
|
|
16
16
|
export const eachTick = (world: World, notify: () => void): (() => void) => {
|
|
17
|
-
const counter = System.create({
|
|
17
|
+
const counter = System.create({ readonly: true, resources: { changed: [clock] } }, () => { notify(); });
|
|
18
18
|
world.add(counter);
|
|
19
19
|
return () => world.remove(counter);
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
/** The clock as an observer run last read it. */
|
|
23
23
|
const read = { tick: 0, now: 0 };
|
|
24
|
-
const readClock = System.create({
|
|
24
|
+
const readClock = System.create({ readonly: true }, ({ resources }) => {
|
|
25
25
|
read.tick = resources[clock].tick;
|
|
26
26
|
read.now = resources[clock].now;
|
|
27
27
|
});
|
|
@@ -36,7 +36,7 @@ export const ticksAfter = async (world: World, count: number): Promise<void> =>
|
|
|
36
36
|
let { tick, now } = read;
|
|
37
37
|
let seen = 0;
|
|
38
38
|
await new Promise<void>(resolve => {
|
|
39
|
-
const counter = System.create({
|
|
39
|
+
const counter = System.create({ readonly: true }, ({ resources }) => {
|
|
40
40
|
const current = resources[clock];
|
|
41
41
|
if (current.tick === tick && current.now === now) return;
|
|
42
42
|
tick = current.tick;
|
|
@@ -52,7 +52,7 @@ export const ticksAfter = async (world: World, count: number): Promise<void> =>
|
|
|
52
52
|
/** The entities with every component of `has` and none of `not`, in storage order, read by an observer run. */
|
|
53
53
|
export const entitiesWith = async (world: World, has: readonly Component.Key[], not: readonly Component.Key[] = []): Promise<Entity[]> => {
|
|
54
54
|
const found: Entity[] = [];
|
|
55
|
-
await world.run(System.create({
|
|
55
|
+
await world.run(System.create({ readonly: true, components: { with: [...has], not } }, row => { found.push(row.entity); }));
|
|
56
56
|
return found;
|
|
57
57
|
};
|
|
58
58
|
|
package/src/perftest/reads.ts
CHANGED
|
@@ -78,7 +78,7 @@ const lookups = async (system: System): Promise<{ readonly best: number; readonl
|
|
|
78
78
|
/** Every entity a seeded world holds. */
|
|
79
79
|
const entitiesOf = async (world: World): Promise<readonly Entity[]> => {
|
|
80
80
|
const found: Entity[] = [];
|
|
81
|
-
await world.run(System.create({
|
|
81
|
+
await world.run(System.create({ readonly: true, components: { with: [value] } }, row => { found.push(row.entity); }));
|
|
82
82
|
return found;
|
|
83
83
|
};
|
|
84
84
|
|
|
@@ -124,7 +124,7 @@ export const readBenchmarks: readonly Benchmark[] = [
|
|
|
124
124
|
run: async () => {
|
|
125
125
|
const world = await seeded(10_000);
|
|
126
126
|
copied = 0;
|
|
127
|
-
const best = await fastest(() => world.run(System.create({
|
|
127
|
+
const best = await fastest(() => world.run(System.create({ readonly: true }, ({ resources }) => { copied = resources[level]; })), 300);
|
|
128
128
|
check(copied === 3, "read run observer: wrong value");
|
|
129
129
|
await world.dispose();
|
|
130
130
|
return best;
|
package/src/perftest/remote.ts
CHANGED
|
@@ -49,7 +49,7 @@ export const reorder = System.create([flip, reverse]);
|
|
|
49
49
|
export const observerSeen = { last: 0, notified: 0 };
|
|
50
50
|
|
|
51
51
|
/** On a joined thread, after each sync point that changed it: the observed resource. */
|
|
52
|
-
export const observerCounter = System.create({
|
|
52
|
+
export const observerCounter = System.create({ readonly: true, resources: { changed: [counter] } }, ({ resources }) => {
|
|
53
53
|
observerSeen.last = resources[counter];
|
|
54
54
|
observerSeen.notified++;
|
|
55
55
|
});
|
|
@@ -61,7 +61,7 @@ const sortedKeys = { keys: new Float64Array(0), entities: new Float64Array(0), p
|
|
|
61
61
|
* On a joined thread, after each sync point: the entities in `order`, sorted from a scan of the index of every ranked
|
|
62
62
|
* entity into reused arrays.
|
|
63
63
|
*/
|
|
64
|
-
export const observerSelect = System.create({
|
|
64
|
+
export const observerSelect = System.create({ readonly: true, lookup: { all: { index: ranked } } }, ({ lookup }) => {
|
|
65
65
|
const list = lookup.all();
|
|
66
66
|
const count = list.length;
|
|
67
67
|
if (sortedKeys.keys.length < count) {
|
|
@@ -99,12 +99,12 @@ export const retext = System.create([count, recaption]);
|
|
|
99
99
|
export const reamounted = System.create([count, reamount]);
|
|
100
100
|
|
|
101
101
|
/** On a joined thread, after each sync point: every row's caption, out of its record. */
|
|
102
|
-
export const observerCaptions = System.create({
|
|
102
|
+
export const observerCaptions = System.create({ readonly: true, components: { with: [caption] } }, row => {
|
|
103
103
|
observerSeen.last += row[caption].length;
|
|
104
104
|
observerSeen.notified++;
|
|
105
105
|
});
|
|
106
106
|
/** On a joined thread, after each sync point: every row's amount. */
|
|
107
|
-
export const observerAmounts = System.create({
|
|
107
|
+
export const observerAmounts = System.create({ readonly: true, components: { with: [amount] } }, row => {
|
|
108
108
|
observerSeen.last += row[amount];
|
|
109
109
|
observerSeen.notified++;
|
|
110
110
|
});
|
|
@@ -129,7 +129,7 @@ const joinedFile = "./joined.js";
|
|
|
129
129
|
/** The observed resource, read by an observer run. */
|
|
130
130
|
const counterOf = async (world: World): Promise<number> => {
|
|
131
131
|
let value = 0;
|
|
132
|
-
await world.run(System.create({
|
|
132
|
+
await world.run(System.create({ readonly: true }, ({ resources }) => { value = resources[counter]; }));
|
|
133
133
|
return value;
|
|
134
134
|
};
|
|
135
135
|
|
package/src/perftest/strings.ts
CHANGED
|
@@ -80,7 +80,7 @@ const read = (system: System, name: string) => measure(system, async world => {
|
|
|
80
80
|
/** Each row's text, as the last write left it. */
|
|
81
81
|
const written = (name: string) => async (world: World): Promise<void> => {
|
|
82
82
|
let wrong = 0;
|
|
83
|
-
await world.run(System.create({
|
|
83
|
+
await world.run(System.create({ readonly: true, components: { with: [text, boxed] } }, row => {
|
|
84
84
|
if (row[text] !== row[boxed].text && row[text] !== labelOf(row.entity, round) && row[boxed].text !== labelOf(row.entity, round)) wrong++;
|
|
85
85
|
}));
|
|
86
86
|
check(wrong === 0, `${name}: wrong text`);
|
package/src/perftest/suite.ts
CHANGED
|
@@ -196,7 +196,7 @@ export const idle = System.create({ components: { with: [unused] } }, () => {});
|
|
|
196
196
|
/** A system that throws, so each run of it rejects. */
|
|
197
197
|
export const failing = System.create({}, () => { throw new Error("suite: failing"); });
|
|
198
198
|
/** An observer system no entity matches: a run of it alone has no tiers. */
|
|
199
|
-
export const idleObserver = System.create({
|
|
199
|
+
export const idleObserver = System.create({ readonly: true, components: { with: [unused] } }, () => {});
|
|
200
200
|
|
|
201
201
|
/** Fifty systems no entity matches, in one tier. */
|
|
202
202
|
export const idles = System.create({
|
package/src/world.test.ts
CHANGED
|
@@ -23,7 +23,7 @@ describe("World.create", () => {
|
|
|
23
23
|
await world.run(spawn);
|
|
24
24
|
await world.run(add);
|
|
25
25
|
let seen = 0;
|
|
26
|
-
await world.run(System.create({
|
|
26
|
+
await world.run(System.create({ readonly: true, components: { with: [count] } }, row => { seen = row[count]; }));
|
|
27
27
|
expect(seen).toBe(2);
|
|
28
28
|
});
|
|
29
29
|
it("rejects a key that isn't a resource, as worlds the implementation creates do", () => {
|
|
@@ -40,7 +40,7 @@ describe("World.join", () => {
|
|
|
40
40
|
await shared;
|
|
41
41
|
await joined.run(raise);
|
|
42
42
|
let seen = 0;
|
|
43
|
-
await world.run(System.create({
|
|
43
|
+
await world.run(System.create({ readonly: true }, ({ resources }) => { seen = resources[total]; }));
|
|
44
44
|
expect(seen).toBe(7);
|
|
45
45
|
});
|
|
46
46
|
});
|
package/src/world.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
import { create as createWorld, join as joinWorld } from './implementation/index.js';
|
|
3
|
+
import type { Create } from './interface/world/create.js';
|
|
4
|
+
import type { Join } from './interface/world/join.js';
|
|
5
|
+
|
|
6
|
+
export type { Table } from './interface/world/table/table.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Creates a world on this thread, synchronously, with resources starting at the given values in place of their
|
|
10
|
+
* defaults. This thread is its `"world"` thread for the world's life: it runs the sync points, the ticks and the GPU
|
|
11
|
+
* device, and fills every logical thread no joined thread claims. Keys are the resources' symbols, built-in ones
|
|
12
|
+
* included (`{ [workers]: 0 }`); string keys are never a resource's, so they're ignored. Throws for a symbol that isn't
|
|
13
|
+
* a resource, or is one only the world writes. While a declaration's trait or default doesn't hold, each call fails to
|
|
14
|
+
* compile, once, naming each such key, its members and what's wrong with them.
|
|
15
|
+
* @function
|
|
16
|
+
*/
|
|
17
|
+
export const create: Create = createWorld;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Joins, from another thread, a world that `share` let join through the other end of `port`, claiming the logical
|
|
21
|
+
* `threads` this thread fills: their objects live here, and the systems and finalizers reading them run here. Resolves,
|
|
22
|
+
* once the world accepted the claim, to the same world: its `run` runs systems there, and its `add` and `remove` take
|
|
23
|
+
* readonly systems, which run here after the world's sync points. Add them before awaiting anything else, so the
|
|
24
|
+
* world's `share` resolves with them in place. Its `dispose` disconnects this thread. Rejects with an `Error` saying
|
|
25
|
+
* why it didn't join: the world refused the claim, was disposed, or its thread ended first, as the platform reports it:
|
|
26
|
+
* the world's `Worker` failing to start or throwing first, a port closed, as in node, or, in a browser, the world's
|
|
27
|
+
* thread ending once it was shared through a port this thread listened on. A browser world's worker that the page ended
|
|
28
|
+
* before this thread heard from it isn't reported, so the join stays pending. Rejects too where memory can't be shared,
|
|
29
|
+
* as without cross-origin isolation.
|
|
30
|
+
* @function
|
|
31
|
+
*/
|
|
32
|
+
export const join: Join = joinWorld;
|