@adobe/ecs 0.2.0 → 0.2.1
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/dist/implementation/world/create.js.map +1 -1
- package/dist/implementation/world/finalizers.js.map +1 -1
- package/dist/index.d.ts +20 -0
- 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/index.d.ts +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/resource/condition.d.ts +3 -3
- package/dist/interface/resource/index.d.ts +0 -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 +2 -2
- package/dist/interface/system/commands.d.ts +4 -4
- package/dist/interface/system/struct.d.ts +2 -2
- 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/join.d.ts +2 -10
- package/package.json +1 -1
- package/src/exports.test.ts +2 -2
- package/src/guide/03-systems.out +1 -1
- package/src/guide/11-traits.type-test.ts +1 -1
- package/src/guide/18-identity.out +1 -1
- package/src/implementation/world/create.ts +3 -3
- package/src/implementation/world/finalizers.ts +3 -3
- package/src/implementation/world/pool-size.cases.ts +3 -1
- package/src/implementation/world/reads.test-helper.ts +6 -6
- package/src/index.ts +24 -1
- package/src/interface/component/component.ts +7 -1
- package/src/interface/component/index.ts +1 -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 +1 -1
- package/src/interface/resource/condition.ts +3 -3
- package/src/interface/resource/index.ts +0 -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 +2 -2
- package/src/interface/system/commands.ts +4 -4
- package/src/interface/system/struct.ts +2 -2
- 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/join.ts +2 -10
- package/src/interface/world/loop.type-test.ts +6 -6
- package/src/observe/members.ts +3 -3
- package/src/observe/query.ts +1 -1
- package/src/observe/select.ts +1 -1
- package/src/observe/values.ts +2 -2
|
@@ -5,10 +5,10 @@ import type { Resource } from '../resource/resource.js';
|
|
|
5
5
|
import type { Comparison } from '../world/filter.js';
|
|
6
6
|
import type { Listed, Property } from './property.js';
|
|
7
7
|
export type ComponentValues = {
|
|
8
|
-
readonly [K in Component.Key]: Component
|
|
8
|
+
readonly [K in Component.Key]: Component<K>;
|
|
9
9
|
};
|
|
10
10
|
export type ResourceValues = {
|
|
11
|
-
readonly [K in Resource.Key]: Resource
|
|
11
|
+
readonly [K in Resource.Key]: Resource<K>;
|
|
12
12
|
};
|
|
13
13
|
/** `Read` values are read-only, `Write` values writable. */
|
|
14
14
|
export type Values<Read, Write, Keys extends PropertyKey, Types> = {
|
|
@@ -24,7 +24,7 @@ export type KeyOf<D> = D extends {
|
|
|
24
24
|
export type HeldBy<D> = KeyOf<D>[number] | Listed<Property<D, "components">, "with">;
|
|
25
25
|
/** The key's values, in key order. */
|
|
26
26
|
type KeyValues<K extends readonly Component.Key[]> = {
|
|
27
|
-
readonly [I in keyof K]: Component
|
|
27
|
+
readonly [I in keyof K]: Component<K[I] & Component.Key>;
|
|
28
28
|
};
|
|
29
29
|
/** A prefix of the key, possibly empty, whose last part may be a comparison. */
|
|
30
30
|
type Prefix<V extends readonly unknown[]> = V extends readonly [infer First, ...infer Rest] ? readonly [] | readonly [First | Comparison<First>] | readonly [First, ...Prefix<Rest>] : readonly [];
|
|
@@ -32,7 +32,7 @@ type Prefix<V extends readonly unknown[]> = V extends readonly [infer First, ...
|
|
|
32
32
|
type IndexRow<D> = {
|
|
33
33
|
readonly entity: Entity;
|
|
34
34
|
} & {
|
|
35
|
-
readonly [K in HeldBy<D> & Component.Key]: Component
|
|
35
|
+
readonly [K in HeldBy<D> & Component.Key]: Component<K>;
|
|
36
36
|
};
|
|
37
37
|
/**
|
|
38
38
|
* The rows a lookup found, in the index's order: by entity for a hash index, by key and then entity for a sorted one.
|
|
@@ -65,7 +65,7 @@ type IndexLookup<D> = D extends {
|
|
|
65
65
|
type EntityRow<L> = {
|
|
66
66
|
readonly [K in (Listed<L, "with"> & Component.Key) | (Exclude<Listed<L, "optional">, Listed<L, "with">> & Component.Key) | (L extends {
|
|
67
67
|
readonly changed: readonly Component.Key[];
|
|
68
|
-
} ? "changed" : never)]: K extends Component.Key ? K extends Listed<L, "with"> ? Component
|
|
68
|
+
} ? "changed" : never)]: K extends Component.Key ? K extends Listed<L, "with"> ? Component<K> : Component<K> | undefined : boolean;
|
|
69
69
|
};
|
|
70
70
|
export type LookupFunction<L> = L extends {
|
|
71
71
|
readonly index: Index<infer D>;
|
|
@@ -7,7 +7,7 @@ import type { Reduction } from '../resource/reduction.js';
|
|
|
7
7
|
import type { IsComparable, IsString, IsStruct } from '../schema/is-struct.js';
|
|
8
8
|
import type { Thread } from '../thread/thread.js';
|
|
9
9
|
/** The value type of the component or resource `N`. */
|
|
10
|
-
type ValueOf<N> = N extends Component.Key ? Component
|
|
10
|
+
type ValueOf<N> = N extends Component.Key ? Component<N> : N extends Resource.Key ? Resource<N> : never;
|
|
11
11
|
/**
|
|
12
12
|
* Whether each component's and resource's value is a struct, `"string"` for a string, computed once so a check is a
|
|
13
13
|
* lookup. Its keys are remapped (`as K`) so a generic key's lookup stays one: resolved as `IsStruct` of a generic value,
|
|
@@ -44,12 +44,12 @@ export interface Traits<N extends Component.Key | Resource.Key> {
|
|
|
44
44
|
readonly derived: true;
|
|
45
45
|
} | {
|
|
46
46
|
readonly shared: true;
|
|
47
|
-
} ? never : [Component
|
|
47
|
+
} ? never : [Component<N>] extends [Entity] ? "owner" | "weak" : never : never;
|
|
48
48
|
/**
|
|
49
49
|
* On a resource, the operator systems reduce into it with: each run that a contributor runs in starts it at the
|
|
50
50
|
* operator's identity and leaves it holding the fold of every contribution. Booleans fold bitwise.
|
|
51
51
|
*/
|
|
52
|
-
readonly reduce: N extends Resource.Key ? [Resource
|
|
52
|
+
readonly reduce: N extends Resource.Key ? [Resource<N>] extends [boolean] ? "&" | "|" | "^" : Structs[N] extends true ? Reduction : never : never;
|
|
53
53
|
/**
|
|
54
54
|
* The logical thread an object lives on, `"world"` without it. Not on a struct or a string, which live in shared
|
|
55
55
|
* memory. A DOM `Node` lives on `"main"`, and says so.
|
|
@@ -69,6 +69,6 @@ export interface Traits<N extends Component.Key | Resource.Key> {
|
|
|
69
69
|
readonly derived: true;
|
|
70
70
|
} | {
|
|
71
71
|
readonly reference: unknown;
|
|
72
|
-
} ? never : IsComparable<Component
|
|
72
|
+
} ? never : IsComparable<Component<N>> extends true ? true : never : never;
|
|
73
73
|
}
|
|
74
74
|
export {};
|
|
@@ -4,15 +4,11 @@ import type { InvalidDeclarations } from './declarations.js';
|
|
|
4
4
|
import type { World } from './world.js';
|
|
5
5
|
/** Values resources start at in place of their defaults: any but those only the world writes. */
|
|
6
6
|
export type InitialResources = {
|
|
7
|
-
readonly [K in Exclude<Resource.Key, WorldWritten>]?: Resource
|
|
7
|
+
readonly [K in Exclude<Resource.Key, WorldWritten>]?: Resource<K>;
|
|
8
8
|
};
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* device, and fills every logical thread no joined thread claims. Keys are the resources' symbols, built-in ones
|
|
13
|
-
* included (`{ [workers]: 0 }`); string keys are never a resource's, so they're ignored. Throws for a symbol that isn't
|
|
14
|
-
* a resource, or is one only the world writes. While a declaration's trait or default doesn't hold, each call fails to
|
|
15
|
-
* compile, once, naming each such key, its members and what's wrong with them.
|
|
10
|
+
* The type of `World.create`.
|
|
11
|
+
* @inline
|
|
16
12
|
*/
|
|
17
13
|
export type Create = (this: [InvalidDeclarations] extends [never] ? unknown : {
|
|
18
14
|
readonly "declarations don't hold": {
|
|
@@ -30,8 +30,8 @@ type Reported<K, Members extends PropertyKey> = [Members] extends [never] ? neve
|
|
|
30
30
|
* `World.create` reports these.
|
|
31
31
|
*/
|
|
32
32
|
export type InvalidDeclarations = {
|
|
33
|
-
readonly [K in Component.Key]: Reported<K, Invalid<K, Components[K], Component
|
|
33
|
+
readonly [K in Component.Key]: Reported<K, Invalid<K, Components[K], Component<K>>>;
|
|
34
34
|
}[Component.Key] | {
|
|
35
|
-
readonly [K in Resource.Key]: Reported<K, Invalid<K, Resources[K], Resource
|
|
35
|
+
readonly [K in Resource.Key]: Reported<K, Invalid<K, Resources[K], Resource<K>>>;
|
|
36
36
|
}[Resource.Key];
|
|
37
37
|
export {};
|
|
@@ -1,16 +1,8 @@
|
|
|
1
1
|
import type { Thread } from '../thread/thread.js';
|
|
2
2
|
import type { World } from './world.js';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* once the world accepted the claim, to the same world: its `run` runs systems there, and its `add` and `remove` take
|
|
7
|
-
* observer systems, which run here after the world's sync points. Add them before awaiting anything else, so the
|
|
8
|
-
* world's `share` resolves with them in place. Its `dispose` disconnects this thread. Rejects with an `Error` saying
|
|
9
|
-
* why it didn't join: the world refused the claim, was disposed, or its thread ended first, as the platform reports it:
|
|
10
|
-
* the world's `Worker` failing to start or throwing first, a port closed, as in node, or, in a browser, the world's
|
|
11
|
-
* thread ending once it was shared through a port this thread listened on. A browser world's worker that the page ended
|
|
12
|
-
* before this thread heard from it isn't reported, so the join stays pending. Rejects too where memory can't be shared,
|
|
13
|
-
* as without cross-origin isolation.
|
|
4
|
+
* The type of `World.join`.
|
|
5
|
+
* @inline
|
|
14
6
|
*/
|
|
15
7
|
export type Join = (port: Parameters<World["share"]>[0], options?: {
|
|
16
8
|
readonly threads?: readonly Exclude<Thread, "world">[];
|
package/package.json
CHANGED
package/src/exports.test.ts
CHANGED
|
@@ -152,7 +152,7 @@ const customTrait = creating(`
|
|
|
152
152
|
export const instanced = Symbol();
|
|
153
153
|
export const foo = Symbol();
|
|
154
154
|
declare module "@adobe/ecs" {
|
|
155
|
-
interface Traits<N> { readonly [instanced]: N extends Component.Key ? ([Component
|
|
155
|
+
interface Traits<N> { readonly [instanced]: N extends Component.Key ? ([Component<N>] extends [readonly number[]] ? true : never) : never }
|
|
156
156
|
interface Components { [foo]: { type: string; [instanced]: true } }
|
|
157
157
|
}`);
|
|
158
158
|
|
|
@@ -182,7 +182,7 @@ const severalTraits = creating(`
|
|
|
182
182
|
export const instanced = Symbol();
|
|
183
183
|
export const name = Symbol();
|
|
184
184
|
declare module "@adobe/ecs" {
|
|
185
|
-
interface Traits<N> { readonly [instanced]: N extends Component.Key ? ([Component
|
|
185
|
+
interface Traits<N> { readonly [instanced]: N extends Component.Key ? ([Component<N>] extends [readonly number[]] ? true : never) : never }
|
|
186
186
|
interface Components { [name]: { type: string; [instanced]: true; thread: "main" } }
|
|
187
187
|
}`);
|
|
188
188
|
|
package/src/guide/03-systems.out
CHANGED
|
@@ -5,7 +5,7 @@ import { Component } from "../index.js";
|
|
|
5
5
|
export const instanced = Symbol();
|
|
6
6
|
declare module "@adobe/ecs" {
|
|
7
7
|
interface Traits<N> {
|
|
8
|
-
readonly [instanced]: N extends Component.Key ? ([Component
|
|
8
|
+
readonly [instanced]: N extends Component.Key ? ([Component<N>] extends [readonly number[]] ? true : never) : never;
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
Error: System "explode" (@adobe/ecs@0.2.
|
|
1
|
+
Error: System "explode" (@adobe/ecs@0.2.1/src/guide/18-identity.ts#explode) failed: boom
|
|
@@ -46,7 +46,7 @@ import { createState, defaultOf, idOf, reside, startTicking, type Key } from './
|
|
|
46
46
|
import { indexOf } from './entities.js';
|
|
47
47
|
import { touchedHeld } from './up.js';
|
|
48
48
|
import { isHeld } from './transfers.js';
|
|
49
|
-
import type {
|
|
49
|
+
import type { ReadonlyType as ReadonlyResourceType } from '../../interface/resource/value.js';
|
|
50
50
|
import { labelOf, whyUnregistered } from '../../interface/registry/index.js';
|
|
51
51
|
|
|
52
52
|
type Queue = <T>(task: () => T | Promise<T>) => Promise<T>;
|
|
@@ -425,12 +425,12 @@ export const createWorld = (resources: InitialResources = {}, options: WorldOpti
|
|
|
425
425
|
const deviceId = idOf(state, gpuDevice);
|
|
426
426
|
const deviceDefault = defaultOf(gpuDevice);
|
|
427
427
|
/** The `gpuDevice` resource's value, read each run, so without allocating. */
|
|
428
|
-
const readDevice = ():
|
|
428
|
+
const readDevice = (): ReadonlyResourceType<typeof gpuDevice> => {
|
|
429
429
|
const index = state.resourceEntity < 0 ? -1 : indexOf(state.resourceEntity);
|
|
430
430
|
const storage = index < 0 ? undefined : state.archetypes[state.entities.archetype[index]]?.storageById[deviceId];
|
|
431
431
|
const value = storage !== undefined ? readCell(storage, state.entities.row[index]) : state.initial.has(gpuDevice) ? state.initial.get(gpuDevice) : deviceDefault;
|
|
432
432
|
// The resource's value matches its registered schema, `GPUDevice | null`.
|
|
433
|
-
return value as
|
|
433
|
+
return value as ReadonlyResourceType<typeof gpuDevice>;
|
|
434
434
|
};
|
|
435
435
|
|
|
436
436
|
/**
|
|
@@ -12,7 +12,7 @@ import { asEntity, indexOf, locate } from './entities.js';
|
|
|
12
12
|
import { createContext, rowBinder, type Context } from './rows.js';
|
|
13
13
|
import { idOf, type Key, type State } from './state.js';
|
|
14
14
|
import type { Queued } from './synchronize.js';
|
|
15
|
-
import type { Essential,
|
|
15
|
+
import type { Essential, ReadonlyType } from '../../interface/component/value.js';
|
|
16
16
|
import type { Leaf } from '../../interface/finalizer/created.js';
|
|
17
17
|
import { newestKeyOf, threadOf } from '../../interface/registry/index.js';
|
|
18
18
|
import type { Layout } from '../../interface/registry/layout.js';
|
|
@@ -369,8 +369,8 @@ const changesOf = (state: State, runner: FinalizerRunner, queued: readonly Queue
|
|
|
369
369
|
return {
|
|
370
370
|
groups: [...groups.values()],
|
|
371
371
|
// Stored and commanded values match each component's registered schema, so they have its type.
|
|
372
|
-
previous: <K extends Essential>(entity: Entity, key: K) => (bornHere(entity) ? null : live(entity, idOf(state, key))) as
|
|
373
|
-
next: <K extends Essential>(entity: Entity, key: K) => next(entity, idOf(state, key)) as
|
|
372
|
+
previous: <K extends Essential>(entity: Entity, key: K) => (bornHere(entity) ? null : live(entity, idOf(state, key))) as ReadonlyType<K> | null,
|
|
373
|
+
next: <K extends Essential>(entity: Entity, key: K) => next(entity, idOf(state, key)) as ReadonlyType<K> | null,
|
|
374
374
|
};
|
|
375
375
|
};
|
|
376
376
|
|
|
@@ -12,7 +12,9 @@ export const poolSizeCases = (load: () => Promise<typeof Fixture>) => describe("
|
|
|
12
12
|
});
|
|
13
13
|
it("honors a number, and null again gives the platform's choice", async () => {
|
|
14
14
|
const fixture = await load();
|
|
15
|
-
|
|
15
|
+
// The platform's choice starts a pool of its own unless it's the size the pool already is.
|
|
16
|
+
const platform = fixture.platformCount();
|
|
17
|
+
expect((await fixture.resized(1, [null])).started).toEqual([1, platform === 1 ? 1 : 1 + platform]);
|
|
16
18
|
});
|
|
17
19
|
it("starts the pool only once a kernel has rows enough for workers", async () => {
|
|
18
20
|
const fixture = await load();
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
2
|
import type { Component } from '../../interface/component/component.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type { ReadonlyType } from '../../interface/component/value.js';
|
|
4
4
|
import type { Entity } from '../../interface/entity/entity.js';
|
|
5
5
|
import type { Resource } from '../../interface/resource/resource.js';
|
|
6
|
-
import type {
|
|
6
|
+
import type { ReadonlyType as ReadonlyResourceType } from '../../interface/resource/value.js';
|
|
7
7
|
import type { World } from '../../interface/world/world.js';
|
|
8
8
|
import { Observe } from '../../observe/index.js';
|
|
9
9
|
|
|
10
10
|
// Tests' reads: each is the first value of an observer factory of `src/observe`.
|
|
11
11
|
|
|
12
12
|
/** One component of an entity, or `null` if it's dead or lacks it. */
|
|
13
|
-
export const read = async <C extends Component.Key>(world: World, entity: Entity, component: C): Promise<
|
|
14
|
-
const values: { readonly [K in C]:
|
|
13
|
+
export const read = async <C extends Component.Key>(world: World, entity: Entity, component: C): Promise<ReadonlyType<C> | null> => {
|
|
14
|
+
const values: { readonly [K in C]: ReadonlyType<K> } | null = await Observe.toPromise(Observe.entity(world, entity, { required: [component] }));
|
|
15
15
|
return values === null ? null : values[component];
|
|
16
16
|
};
|
|
17
17
|
|
|
@@ -19,12 +19,12 @@ export const read = async <C extends Component.Key>(world: World, entity: Entity
|
|
|
19
19
|
export const readEntity = <const C extends readonly Component.Key[]>(world: World, entity: Entity, components: C) =>
|
|
20
20
|
Observe.toPromise(Observe.entity(world, entity, { required: components }));
|
|
21
21
|
|
|
22
|
-
export const readResource = <R extends Resource.Key>(world: World, name: R): Promise<
|
|
22
|
+
export const readResource = <R extends Resource.Key>(world: World, name: R): Promise<ReadonlyResourceType<R>> => Observe.toPromise(Observe.resource(world, name));
|
|
23
23
|
|
|
24
24
|
export const select = <const R extends readonly Component.Key[] = []>(world: World, query: Observe.Query<R>): Promise<readonly Entity[]> => Observe.toPromise(Observe.select(world, query));
|
|
25
25
|
|
|
26
26
|
export const count = <const R extends readonly Component.Key[] = []>(world: World, query: Omit<Observe.Query<R>, "order">): Promise<number> => Observe.toPromise(Observe.count(world, query));
|
|
27
27
|
|
|
28
28
|
/** One component of each of these entities, `null` for one that's dead or lacks it. */
|
|
29
|
-
export const readEach = <C extends Component.Key>(world: World, entities: readonly Entity[], component: C): Promise<(
|
|
29
|
+
export const readEach = <C extends Component.Key>(world: World, entities: readonly Entity[], component: C): Promise<(ReadonlyType<C> | null)[]> =>
|
|
30
30
|
Promise.all(entities.map(entity => read(world, entity, component)));
|
package/src/index.ts
CHANGED
|
@@ -18,4 +18,27 @@ export type World = Created;
|
|
|
18
18
|
* values in place of their defaults; and how another thread joins one that shared itself with it,
|
|
19
19
|
* `World.join(port, { threads })`.
|
|
20
20
|
*/
|
|
21
|
-
export const World: {
|
|
21
|
+
export const World: {
|
|
22
|
+
/**
|
|
23
|
+
* Creates a world on this thread, synchronously, with resources starting at the given values in place of their
|
|
24
|
+
* defaults. This thread is its `"world"` thread for the world's life: it runs the sync points, the ticks and the GPU
|
|
25
|
+
* device, and fills every logical thread no joined thread claims. Keys are the resources' symbols, built-in ones
|
|
26
|
+
* included (`{ [workers]: 0 }`); string keys are never a resource's, so they're ignored. Throws for a symbol that isn't
|
|
27
|
+
* a resource, or is one only the world writes. While a declaration's trait or default doesn't hold, each call fails to
|
|
28
|
+
* compile, once, naming each such key, its members and what's wrong with them.
|
|
29
|
+
*/
|
|
30
|
+
readonly create: Create;
|
|
31
|
+
/**
|
|
32
|
+
* Joins, from another thread, a world that `share` let join through the other end of `port`, claiming the logical
|
|
33
|
+
* `threads` this thread fills: their objects live here, and the systems and finalizers reading them run here. Resolves,
|
|
34
|
+
* once the world accepted the claim, to the same world: its `run` runs systems there, and its `add` and `remove` take
|
|
35
|
+
* observer systems, which run here after the world's sync points. Add them before awaiting anything else, so the
|
|
36
|
+
* world's `share` resolves with them in place. Its `dispose` disconnects this thread. Rejects with an `Error` saying
|
|
37
|
+
* why it didn't join: the world refused the claim, was disposed, or its thread ended first, as the platform reports it:
|
|
38
|
+
* the world's `Worker` failing to start or throwing first, a port closed, as in node, or, in a browser, the world's
|
|
39
|
+
* thread ending once it was shared through a port this thread listened on. A browser world's worker that the page ended
|
|
40
|
+
* before this thread heard from it isn't reported, so the join stays pending. Rejects too where memory can't be shared,
|
|
41
|
+
* as without cross-origin isolation.
|
|
42
|
+
*/
|
|
43
|
+
readonly join: Join;
|
|
44
|
+
} = { create, join };
|
|
@@ -1,5 +1,11 @@
|
|
|
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
|
+
/**
|
|
6
|
+
* The type of component `K`'s value, as its record in `Components` declares it: `Component<typeof position>`. The
|
|
7
|
+
* namespace's `Key` and `With` name components' keys.
|
|
8
|
+
*/
|
|
9
|
+
export type Component<K extends Key> = Type<K>;
|
|
4
10
|
|
|
5
11
|
export * as Component from './index.js';
|
|
@@ -4,16 +4,16 @@ import type { Key as TraitKey } from "../trait/key.js";
|
|
|
4
4
|
import type { Key } from "./key.js";
|
|
5
5
|
import type { Components } from "./types.js";
|
|
6
6
|
|
|
7
|
-
/** Every component's value type, computed once so `
|
|
8
|
-
type
|
|
7
|
+
/** Every component's value type, computed once so `Type` is a lookup. */
|
|
8
|
+
type Types = { readonly [K in Key]: Components[K] extends { readonly type: infer T } ? T : never };
|
|
9
9
|
|
|
10
10
|
/** The value type of component `K`. */
|
|
11
|
-
export type
|
|
11
|
+
export type Type<K extends Key> = Types[K];
|
|
12
12
|
|
|
13
13
|
type Primitive = string | number | boolean | bigint | symbol | null | undefined;
|
|
14
14
|
|
|
15
15
|
/** Component `K`'s value as reads return it: read-only, with branded primitives such as `Entity` as they are. */
|
|
16
|
-
export type
|
|
16
|
+
export type ReadonlyType<K extends Key> = Type<K> extends Primitive ? Type<K> : Readonly<Type<K>>;
|
|
17
17
|
|
|
18
18
|
/** Components that declare trait `T`, with a value assignable to `V`. */
|
|
19
19
|
export type With<T extends TraitKey, V = unknown> = { [K in Key]: Components[K] extends { readonly [P in T]: V } ? K : never }[Key];
|
|
@@ -35,7 +35,7 @@ export type Shared = With<"shared">;
|
|
|
35
35
|
export type Writable = Exclude<Key, With<"reference"> | Shared>;
|
|
36
36
|
|
|
37
37
|
/** Every component's thread, computed once so `ThreadOf` is a lookup. */
|
|
38
|
-
type Threads = { readonly [K in Key]: ThreadOf<Components[K],
|
|
38
|
+
type Threads = { readonly [K in Key]: ThreadOf<Components[K], Type<K>> };
|
|
39
39
|
|
|
40
40
|
/** The thread component `K` lives on, or `never` for a struct, which lives in shared memory. */
|
|
41
41
|
export type ThreadOfComponent<K extends Key> = Threads[K];
|
|
@@ -1,6 +1,6 @@
|
|
|
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
|
/** Values for any subset of components, keyed by component. */
|
|
6
|
-
export type Values = { readonly [K in Key]?:
|
|
6
|
+
export type Values = { readonly [K in Key]?: Type<K> };
|
|
@@ -6,7 +6,7 @@ import type { Listed, Property } from '../system/property.js';
|
|
|
6
6
|
import type { LookupFunction } from '../system/values.js';
|
|
7
7
|
import type { WriteResources } from '../system/body.js';
|
|
8
8
|
import type { ChangesDeclaration, Declaration } from './declaration.js';
|
|
9
|
-
import type { Essential,
|
|
9
|
+
import type { Essential, ReadonlyType } from '../component/value.js';
|
|
10
10
|
import type { Elsewhere, ThreadsOf } from '../system/threads.js';
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -14,8 +14,8 @@ import type { Elsewhere, ThreadsOf } from '../system/threads.js';
|
|
|
14
14
|
* its `optional` where it had them, `undefined` where it didn't.
|
|
15
15
|
*/
|
|
16
16
|
export type Row<D extends Declaration> =
|
|
17
|
-
{ readonly entity: Entity } & { readonly [K in Listed<Property<D, "components">, "with"> & Component.Key]: Component
|
|
18
|
-
& { readonly [K in Exclude<Listed<Property<D, "components">, "optional">, Listed<Property<D, "components">, "with">> & Component.Key]: Component
|
|
17
|
+
{ readonly entity: Entity } & { readonly [K in Listed<Property<D, "components">, "with"> & Component.Key]: Component<K> }
|
|
18
|
+
& { readonly [K in Exclude<Listed<Property<D, "components">, "optional">, Listed<Property<D, "components">, "with">> & Component.Key]: Component<K> | undefined };
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Every resource, its lookups when declared, and the commands it issues for the next sync point: none on another
|
|
@@ -46,7 +46,7 @@ export type Group<C extends Component.Key> = {
|
|
|
46
46
|
export type ChangeSet<C extends Component.Key> = {
|
|
47
47
|
readonly groups: readonly Group<C>[];
|
|
48
48
|
/** The value before the sync point, or null if the entity didn't have it. */
|
|
49
|
-
previous<K extends C>(entity: Entity, component: K):
|
|
49
|
+
previous<K extends C>(entity: Entity, component: K): ReadonlyType<K> | null;
|
|
50
50
|
/** The value after it, or null if the entity won't have it. */
|
|
51
|
-
next<K extends C>(entity: Entity, component: K):
|
|
51
|
+
next<K extends C>(entity: Entity, component: K): ReadonlyType<K> | null;
|
|
52
52
|
};
|
|
@@ -36,7 +36,7 @@ export const added = world.add(read);
|
|
|
36
36
|
// Only types a consumer declares with are named; the rest are inferred where wanted.
|
|
37
37
|
// @ts-expect-error a thread's name is keyof Threads
|
|
38
38
|
export type NoThread = Ecs.Thread;
|
|
39
|
-
// @ts-expect-error the cadence's value is Resource
|
|
39
|
+
// @ts-expect-error the cadence's value is Resource<typeof cadence>
|
|
40
40
|
export type NoCadence = Ecs.Cadence;
|
|
41
41
|
// @ts-expect-error a rule is the value Rule.create returns: ReturnType<typeof Rule.create>
|
|
42
42
|
export const noRule: Rule = Rule.create({ components: { with: [position] }, derive: [slot] });
|
|
@@ -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>;
|
|
@@ -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
|
/**
|
|
@@ -23,7 +23,7 @@ type Reading<D> = D extends { readonly observer: true } | { readonly shader: obj
|
|
|
23
23
|
export type Optional<D> = Exclude<Listed<Property<D, "components">, "optional">, Members<D>>;
|
|
24
24
|
|
|
25
25
|
/** Each optional component's value or `undefined`, as `ComponentValues` gives members'. */
|
|
26
|
-
type OptionalValues = { readonly [K in Component.Key]: Component
|
|
26
|
+
type OptionalValues = { readonly [K in Component.Key]: Component<K> | undefined };
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Its members' values, writable but for references and shared components, and in a system that only reads; its
|
|
@@ -45,7 +45,7 @@ export type ReadResources = Values<Resource.Key, never, Resource.Key, ResourceVa
|
|
|
45
45
|
export type WriteResources = Values<Resource.Key, Writes, Resource.Key, ResourceValues>;
|
|
46
46
|
|
|
47
47
|
/** Each reduced resource, as a function contributing a value to the run's fold. */
|
|
48
|
-
type Reductions = { readonly [K in Reduced]: (value: Resource
|
|
48
|
+
type Reductions = { readonly [K in Reduced]: (value: Resource<K>) => void };
|
|
49
49
|
|
|
50
50
|
type Is<D> = Listed<Property<D, "resources">, "is">;
|
|
51
51
|
|
|
@@ -5,13 +5,13 @@ import type { Essential, Shared } from '../component/value.js';
|
|
|
5
5
|
import type { ValuesOf } from './values-of.js';
|
|
6
6
|
|
|
7
7
|
/** The value an `add` of one component takes: none for a tag. */
|
|
8
|
-
type ValueArgument<K extends Essential> = Component
|
|
8
|
+
type ValueArgument<K extends Essential> = Component<K> extends true ? [value?: true] : [value: Component<K>];
|
|
9
9
|
|
|
10
10
|
/** 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
|
|
11
|
+
type Preset<C extends readonly Essential[]> = { readonly [K in C[number]]?: Component<K> };
|
|
12
12
|
|
|
13
13
|
/** 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
|
|
14
|
+
type SharedPreset<C extends readonly Essential[]> = { readonly [K in C[number] & Shared]: Component<K> };
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Whether `C` names a shared component, so a spawner of it takes its constants: a list that isn't a tuple, whose
|
|
@@ -27,7 +27,7 @@ type Named<C extends readonly Essential[], P> = { readonly [K in Exclude<keyof P
|
|
|
27
27
|
* the spawn takes their values first.
|
|
28
28
|
*/
|
|
29
29
|
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
|
|
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<K> } } : C
|
|
31
31
|
: C;
|
|
32
32
|
|
|
33
33
|
/** Each component of `C` the constants `P` gave, whose value a spawn's values give again as a message naming it. */
|
|
@@ -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];
|
|
@@ -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> };
|
|
@@ -6,8 +6,8 @@ import type { Resource } from '../resource/resource.js';
|
|
|
6
6
|
import type { Comparison } from '../world/filter.js';
|
|
7
7
|
import type { Listed, Property } from './property.js';
|
|
8
8
|
|
|
9
|
-
export type ComponentValues = { readonly [K in Component.Key]: Component
|
|
10
|
-
export type ResourceValues = { readonly [K in Resource.Key]: Resource
|
|
9
|
+
export type ComponentValues = { readonly [K in Component.Key]: Component<K> };
|
|
10
|
+
export type ResourceValues = { readonly [K in Resource.Key]: Resource<K> };
|
|
11
11
|
|
|
12
12
|
/** `Read` values are read-only, `Write` values writable. */
|
|
13
13
|
export type Values<Read, Write, Keys extends PropertyKey, Types> =
|
|
@@ -21,7 +21,7 @@ export type KeyOf<D> = D extends { readonly key: infer K extends readonly Compon
|
|
|
21
21
|
export type HeldBy<D> = KeyOf<D>[number] | Listed<Property<D, "components">, "with">;
|
|
22
22
|
|
|
23
23
|
/** The key's values, in key order. */
|
|
24
|
-
type KeyValues<K extends readonly Component.Key[]> = { readonly [I in keyof K]: Component
|
|
24
|
+
type KeyValues<K extends readonly Component.Key[]> = { readonly [I in keyof K]: Component<K[I] & Component.Key> };
|
|
25
25
|
|
|
26
26
|
/** A prefix of the key, possibly empty, whose last part may be a comparison. */
|
|
27
27
|
type Prefix<V extends readonly unknown[]> = V extends readonly [infer First, ...infer Rest]
|
|
@@ -29,7 +29,7 @@ type Prefix<V extends readonly unknown[]> = V extends readonly [infer First, ...
|
|
|
29
29
|
: readonly [];
|
|
30
30
|
|
|
31
31
|
/** A row an index lookup gives: its entity and the values of what every entity the index holds has, read-only. */
|
|
32
|
-
type IndexRow<D> = { readonly entity: Entity } & { readonly [K in HeldBy<D> & Component.Key]: Component
|
|
32
|
+
type IndexRow<D> = { readonly entity: Entity } & { readonly [K in HeldBy<D> & Component.Key]: Component<K> };
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* The rows a lookup found, in the index's order: by entity for a hash index, by key and then entity for a sorted one.
|
|
@@ -65,7 +65,7 @@ type IndexLookup<D> =
|
|
|
65
65
|
type EntityRow<L> = {
|
|
66
66
|
readonly [K in (Listed<L, "with"> & Component.Key) | (Exclude<Listed<L, "optional">, Listed<L, "with">> & Component.Key) | (L extends { readonly changed: readonly Component.Key[] } ? "changed" : never)]:
|
|
67
67
|
// Its `optional` components are their values where the entity has them, `undefined` where it doesn't.
|
|
68
|
-
K extends Component.Key ? K extends Listed<L, "with"> ? Component
|
|
68
|
+
K extends Component.Key ? K extends Listed<L, "with"> ? Component<K> : Component<K> | undefined : boolean
|
|
69
69
|
};
|
|
70
70
|
|
|
71
71
|
export type LookupFunction<L> = L extends { readonly index: Index<infer D> }
|
|
@@ -55,7 +55,7 @@ declare module '@adobe/ecs' {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
check<Equal<Component
|
|
58
|
+
check<Equal<Component<typeof path>, readonly number[]>>();
|
|
59
59
|
check<Equal<ThreadOf<{ readonly thread: "main" }, string>, never>>();
|
|
60
60
|
check<Equal<ThreadOf<{ readonly thread: "main" }, "edit" | "view">, never>>();
|
|
61
61
|
check<Equal<ThreadOf<{ readonly thread: "main" }, readonly string[]>, "main">>();
|