@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
|
@@ -57,16 +57,16 @@ const idle = System.create({}, () => {});
|
|
|
57
57
|
const totalPlus = System.create({}, ({ resources }, input: { readonly plus: number }) => resources[total] + input.plus);
|
|
58
58
|
const described = System.create(({ resources }) => ({ total: resources[total], names: resources[names] }));
|
|
59
59
|
const unpostable = System.create(() => () => "a function");
|
|
60
|
-
const readTotal = System.create({
|
|
60
|
+
const readTotal = System.create({ readonly: true }, ({ resources }) => resources[total]);
|
|
61
61
|
/** Writes levels, an object of joinTestAudio, so it runs on the thread claiming it. */
|
|
62
62
|
const mix = System.create({}, ({ resources }, input: { readonly level: number }) => {
|
|
63
63
|
resources[levels] = [...resources[levels], input.level];
|
|
64
64
|
});
|
|
65
65
|
/** An observer system recording each total it reads into `into`. */
|
|
66
|
-
const totalsInto = (into: number[]) => System.create({
|
|
66
|
+
const totalsInto = (into: number[]) => System.create({ readonly: true }, ({ resources }) => { into.push(resources[total]); });
|
|
67
67
|
/** An observer system recording how many levels it reads into `into`. */
|
|
68
|
-
const levelsInto = (into: number[]) => System.create({
|
|
69
|
-
const failingRead = System.create({
|
|
68
|
+
const levelsInto = (into: number[]) => System.create({ readonly: true }, ({ resources }) => { into.push(resources[levels].length); });
|
|
69
|
+
const failingRead = System.create({ readonly: true }, () => { throw new Error("joinTest read failure"); });
|
|
70
70
|
|
|
71
71
|
/** A world shared with a thread joined through a channel, and the joined world, once both say it joined. */
|
|
72
72
|
const joined = async (threads: readonly ("main")[] = []): Promise<{ readonly world: CreatedWorld; readonly remote: World; readonly channel: MessageChannel }> => {
|
|
@@ -33,6 +33,12 @@ const observerLeavesOf = (definition: Definition): readonly Leaf[] | null => {
|
|
|
33
33
|
return schedule.tiers.length === 0 && schedule.observer.length > 0 ? schedule.observer : null;
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
+
/** Throws for a readonly system reading tables, which only the world's thread gives. */
|
|
37
|
+
const refuseTables = (leaves: readonly Leaf[]): void => {
|
|
38
|
+
const reading = leaves.find(leaf => leaf.declaration.tables !== undefined);
|
|
39
|
+
if (reading !== undefined) throw new Error(`System "${reading.name}" reads tables, which only the world's thread gives: run or add it there.`);
|
|
40
|
+
};
|
|
41
|
+
|
|
36
42
|
/** A run not yet settled: how to settle it, and its rejection once posted. */
|
|
37
43
|
type Run = { readonly resolve: (value: unknown) => void; readonly reject: (error: unknown) => void; message: ToJoined | null; error: unknown; readonly local: Local | null };
|
|
38
44
|
|
|
@@ -659,6 +665,7 @@ export const joinWorld = (port: Parameters<World["share"]>[0], options: { readon
|
|
|
659
665
|
// This thread's own observer systems run here, after the sync point of an empty run of the world's.
|
|
660
666
|
const leaves = observerLeavesOf(definition);
|
|
661
667
|
if (leaves !== null) {
|
|
668
|
+
refuseTables(leaves);
|
|
662
669
|
if (failure !== null) return Promise.reject(failure);
|
|
663
670
|
const locals = leaves.map(localOf);
|
|
664
671
|
if (locals.length === 1 && definition.kind !== "composite") return runNumbered(locals[0].number, locals[0].leaf, undefined, locals[0]);
|
|
@@ -680,7 +687,8 @@ export const joinWorld = (port: Parameters<World["share"]>[0], options: { readon
|
|
|
680
687
|
add: system => {
|
|
681
688
|
const definition = requireDefinition(system);
|
|
682
689
|
const leaves = observerLeavesOf(definition);
|
|
683
|
-
if (leaves === null) throw new Error("
|
|
690
|
+
if (leaves === null) throw new Error(`System "${definition.name}" isn't readonly, and a joined thread adds only readonly systems: declare it readonly: true to read the world from this thread, or add it on the world's thread.`);
|
|
691
|
+
refuseTables(leaves);
|
|
684
692
|
if (added.has(definition) || failure !== null) return;
|
|
685
693
|
const locals = leaves.map(localOf);
|
|
686
694
|
added.set(definition, locals);
|
|
@@ -692,7 +700,7 @@ export const joinWorld = (port: Parameters<World["share"]>[0], options: { readon
|
|
|
692
700
|
},
|
|
693
701
|
remove: system => {
|
|
694
702
|
const definition = requireDefinition(system);
|
|
695
|
-
if (observerLeavesOf(definition) === null) throw new Error("
|
|
703
|
+
if (observerLeavesOf(definition) === null) throw new Error(`System "${definition.name}" isn't readonly, and a joined thread removes only readonly systems: others are removed on the world's thread.`);
|
|
696
704
|
const locals = added.get(definition);
|
|
697
705
|
if (locals === undefined) return;
|
|
698
706
|
added.delete(definition);
|
|
@@ -22,7 +22,7 @@ declare module "@adobe/ecs" {
|
|
|
22
22
|
|
|
23
23
|
/** What the observer system read, were it run: this test stands for the thread, so it never runs. */
|
|
24
24
|
const seen: number[] = [];
|
|
25
|
-
const watch = System.create({
|
|
25
|
+
const watch = System.create({ readonly: true, components: { with: [amount] } }, row => { seen.push(row[amount]); });
|
|
26
26
|
|
|
27
27
|
describe("an observer system on a joined thread", () => {
|
|
28
28
|
it("is waited for, not cancelled, by a sync point after a writer copied the rest of a post the thread claimed partway", async () => {
|
|
@@ -42,7 +42,7 @@ const channel = scopeChannel<FromJoiningWorker, ToJoiningWorker>();
|
|
|
42
42
|
let hosted: ReturnType<typeof createWorld> | null = null;
|
|
43
43
|
/** What the observer system `listen` adds heard. */
|
|
44
44
|
const heard: number[] = [];
|
|
45
|
-
const hear = System.create({
|
|
45
|
+
const hear = System.create({ readonly: true }, ({ resources }) => { heard.push(resources[total]); });
|
|
46
46
|
const messageOf = (error: unknown): string => error instanceof Error ? error.message : String(error);
|
|
47
47
|
/** Ends this thread at once: node's worker exits, and a browser worker closes, dropping the tasks it queued. */
|
|
48
48
|
const exit = (): void => {
|
|
@@ -16,7 +16,7 @@ import { add, drive, idle, names, shelf, total, until } from "./joining.test-hel
|
|
|
16
16
|
/** A system only this thread imports, which a world on a worker never did. */
|
|
17
17
|
const unimportedByWorker = System.create({}, () => {});
|
|
18
18
|
/** An observer system only this thread imports, reading an object of `main`, so it's placed on a thread claiming it. */
|
|
19
|
-
const shelvedHere = System.create({
|
|
19
|
+
const shelvedHere = System.create({ readonly: true }, ({ resources }) => resources[shelf].length);
|
|
20
20
|
|
|
21
21
|
type Worker = {
|
|
22
22
|
readonly ask: (message: ToJoiningWorker, transfer?: readonly unknown[]) => Promise<FromJoiningWorker>;
|
|
@@ -36,7 +36,7 @@ export const append = System.create({}, ({ resources }, input: { readonly name:
|
|
|
36
36
|
return resources[names].length;
|
|
37
37
|
});
|
|
38
38
|
/** Reads `total` from an observer system, which a joined thread runs itself. */
|
|
39
|
-
export const readTotal = System.create({
|
|
39
|
+
export const readTotal = System.create({ readonly: true }, ({ resources }) => resources[total]);
|
|
40
40
|
export const spawn = System.create({}, ({ commands }, input: { readonly x: number }) => {
|
|
41
41
|
commands.spawn([point, label, marked])({ [point]: [input.x, -input.x], [label]: `p${input.x}` });
|
|
42
42
|
});
|
|
@@ -63,7 +63,7 @@ const countAdded = System.create({ components: { added: [value], with: [made] }
|
|
|
63
63
|
|
|
64
64
|
/** Each made entity's value, in storage order. */
|
|
65
65
|
const madeValues: number[] = [];
|
|
66
|
-
const readMade = System.create({
|
|
66
|
+
const readMade = System.create({ readonly: true, components: { with: [value, made] } }, row => { madeValues.push(row[value]); });
|
|
67
67
|
const valuesOf = async (world: ReturnType<typeof createWorld>): Promise<number[]> => {
|
|
68
68
|
madeValues.length = 0;
|
|
69
69
|
await world.run(readMade);
|
|
@@ -37,7 +37,7 @@ const nothing = System.create({}, () => {});
|
|
|
37
37
|
|
|
38
38
|
/** Counts passes: the observer passes after each run's sync point. */
|
|
39
39
|
const passes = { count: 0 };
|
|
40
|
-
const counter = System.create({
|
|
40
|
+
const counter = System.create({ readonly: true }, () => { passes.count++; });
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
43
|
* A world of `count` entities, each amount its index and one more, at level 1: the runs below registered, so runners
|
|
@@ -184,7 +184,7 @@ describe("observer factories", () => {
|
|
|
184
184
|
it("rethrow only their own systems' failures from their runs, not those of observer systems the world added", async () => {
|
|
185
185
|
using reported = catchOutOfBand();
|
|
186
186
|
await using world = await seeded(3);
|
|
187
|
-
const failing = System.create({
|
|
187
|
+
const failing = System.create({ readonly: true }, () => { throw new Error("added failed"); });
|
|
188
188
|
world.add(failing);
|
|
189
189
|
const caught: unknown[] = [];
|
|
190
190
|
const original = globalThis.queueMicrotask;
|
|
@@ -29,7 +29,7 @@ const count = System.create({}, ({ resources }) => { resources[frames] += 1; });
|
|
|
29
29
|
const watch = System.create({}, ({ resources }) => { resources[seen] = resources[clock].tick; });
|
|
30
30
|
const fail = System.create({}, () => { throw new Error("tick failed"); });
|
|
31
31
|
const nothing = System.create({}, () => {});
|
|
32
|
-
const idle = System.create({
|
|
32
|
+
const idle = System.create({ readonly: true }, () => {});
|
|
33
33
|
const fast = System.create({}, ({ resources }) => {
|
|
34
34
|
resources[cadence] = { kind: "fixed", step: 1 };
|
|
35
35
|
resources[always] = true;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { System } from "../../interface/system/system.js";
|
|
3
3
|
import type { World } from "../../interface/world/world.js";
|
|
4
4
|
|
|
5
|
-
const settled = System.create({
|
|
5
|
+
const settled = System.create({ readonly: true }, () => {});
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Resolves once every observer factory subscribed before it had its first value: after the factories' flush, which the
|
|
@@ -24,7 +24,7 @@ export type FromContainedWorker =
|
|
|
24
24
|
|
|
25
25
|
const channel = scopeChannel<FromContainedWorker, ToContainedWorker>();
|
|
26
26
|
let remote: World | null = null;
|
|
27
|
-
const placed = System.create({
|
|
27
|
+
const placed = System.create({ readonly: true }, () => {});
|
|
28
28
|
|
|
29
29
|
channel.onMessage(async message => {
|
|
30
30
|
if ("protocol" in message) return;
|
|
@@ -32,7 +32,7 @@ export const setHolding = System.create({}, ({ resources }, input: { readonly ho
|
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
/** After each sync point that changed the tally: notes it, and, while `holding` says so, waits there to be released. */
|
|
35
|
-
export const watchTally = System.create({
|
|
35
|
+
export const watchTally = System.create({ readonly: true, resources: { changed: [tally] } }, ({ resources }) => {
|
|
36
36
|
const value = resources[tally];
|
|
37
37
|
tallies.push(value);
|
|
38
38
|
const { words } = gate;
|
|
@@ -29,10 +29,10 @@ const pause = System.create({}, ({ resources }, input: { readonly paused: boolea
|
|
|
29
29
|
});
|
|
30
30
|
|
|
31
31
|
const seen: { wide: number[][]; steps: number[] } = { wide: [], steps: [] };
|
|
32
|
-
const watchWide = System.create({
|
|
32
|
+
const watchWide = System.create({ readonly: true, resources: { changed: [wide] } }, ({ resources }) => {
|
|
33
33
|
seen.wide.push([...resources[wide]]);
|
|
34
34
|
});
|
|
35
|
-
const watchSteps = System.create({
|
|
35
|
+
const watchSteps = System.create({ readonly: true, resources: { not: [paused], changed: [steps] } }, ({ resources }) => {
|
|
36
36
|
seen.steps.push(resources[steps]);
|
|
37
37
|
});
|
|
38
38
|
|
|
@@ -103,7 +103,7 @@ export type Seen = { counter: number; rows: number; wrong: number };
|
|
|
103
103
|
export const seen: Seen[] = [];
|
|
104
104
|
|
|
105
105
|
/** Reads every row, which must all be of one tick, pausing at the end of its first chunk while `stall` says so. */
|
|
106
|
-
export const watchRows = System.create({
|
|
106
|
+
export const watchRows = System.create({ readonly: true, components: { with: [stamp, spot, word] } }, (row, { resources }) => {
|
|
107
107
|
const value = resources[counter];
|
|
108
108
|
let last = seen.at(-1);
|
|
109
109
|
if (last === undefined || last.counter !== value || last.rows === 1000) seen.push(last = { counter: value, rows: 0, wrong: 0 });
|
|
@@ -117,7 +117,7 @@ export const watchRows = System.create({ observer: true, components: { with: [st
|
|
|
117
117
|
export const looked: Entity[] = [];
|
|
118
118
|
|
|
119
119
|
/** Looks up every row by entity, which must all be of one tick, spinning halfway while `stall` says so. */
|
|
120
|
-
export const lookRows = System.create({
|
|
120
|
+
export const lookRows = System.create({ readonly: true, lookup: { found: { with: [stamp, spot] } } }, ({ resources, lookup }) => {
|
|
121
121
|
const value = resources[counter];
|
|
122
122
|
const run: Seen = { counter: value, rows: 0, wrong: 0 };
|
|
123
123
|
seen.push(run);
|
|
@@ -146,7 +146,7 @@ export const nextPhase = System.create({}, ({ resources }) => {
|
|
|
146
146
|
export const hot: number[] = [];
|
|
147
147
|
export const byHeat = Index.create({ key: [heat], sorted: true });
|
|
148
148
|
/** Counts the rows at least 150 hot, through the sorted index on what the GPU kernel writes. */
|
|
149
|
-
export const countHot = System.create({
|
|
149
|
+
export const countHot = System.create({ readonly: true, lookup: { hot: { index: byHeat } } }, ({ lookup }) => {
|
|
150
150
|
hot.push(lookup.hot({ ">=": 150 }).length);
|
|
151
151
|
});
|
|
152
152
|
|
|
@@ -164,7 +164,7 @@ export const mottos: { readonly round: number; readonly motto: string }[] = [];
|
|
|
164
164
|
* Reads only resources, a string among them: waits on the gate first, while `stall` says so, its round plus one, then
|
|
165
165
|
* reads the motto, which must be its round's.
|
|
166
166
|
*/
|
|
167
|
-
export const watchMotto = System.create({
|
|
167
|
+
export const watchMotto = System.create({ readonly: true }, ({ resources }) => {
|
|
168
168
|
const round = resources[mottoRound];
|
|
169
169
|
pauseAt(resources[stall], round + 1);
|
|
170
170
|
mottos.push({ round, motto: resources[motto] });
|
|
@@ -59,43 +59,43 @@ const reset = (): void => {
|
|
|
59
59
|
/** The entities the tests look up, by the joined thread's systems. */
|
|
60
60
|
const watched: Entity[] = [];
|
|
61
61
|
|
|
62
|
-
const watchLevel = System.create({
|
|
62
|
+
const watchLevel = System.create({ readonly: true, resources: { changed: [level] } }, ({ resources }) => {
|
|
63
63
|
seen.calls++;
|
|
64
64
|
seen.levels.push(resources[level]);
|
|
65
65
|
});
|
|
66
|
-
const watchAmounts = System.create({
|
|
66
|
+
const watchAmounts = System.create({ readonly: true, components: { with: [amount], changed: [amount] } }, row => {
|
|
67
67
|
(seen.amounts[seen.amounts.length - 1] ??= []).push(row[amount]);
|
|
68
68
|
});
|
|
69
|
-
const startAmounts = System.create({
|
|
69
|
+
const startAmounts = System.create({ readonly: true }, () => { seen.amounts.push([]); });
|
|
70
70
|
const amountsPass = System.create([startAmounts, watchAmounts]);
|
|
71
|
-
const lookChanged = System.create({
|
|
71
|
+
const lookChanged = System.create({ readonly: true, lookup: { found: { with: [amount], changed: [amount] } } }, ({ lookup }) => {
|
|
72
72
|
seen.looked.push(watched.flatMap(entity => {
|
|
73
73
|
const row = lookup.found(entity);
|
|
74
74
|
return row === null ? [] : [row[amount], row.changed];
|
|
75
75
|
}));
|
|
76
76
|
});
|
|
77
|
-
const countTagged = System.create({
|
|
77
|
+
const countTagged = System.create({ readonly: true, lookup: { all: { index: allTagged } } }, ({ lookup }) => {
|
|
78
78
|
seen.tagged.push(lookup.all().length);
|
|
79
79
|
});
|
|
80
|
-
const inCell = System.create({
|
|
80
|
+
const inCell = System.create({ readonly: true, lookup: { found: { index: byCell } } }, ({ lookup }) => {
|
|
81
81
|
const found = lookup.found(11);
|
|
82
82
|
seen.tagged.push(found.length === 0 ? 0 : found.length * 100 + (found.at(0)?.[amount] ?? -1));
|
|
83
83
|
});
|
|
84
|
-
const failing = System.create({
|
|
84
|
+
const failing = System.create({ readonly: true }, ({ resources }) => {
|
|
85
85
|
if (resources[level] > 1) throw new Error("observer joined failure");
|
|
86
86
|
});
|
|
87
|
-
const readLabels = System.create({
|
|
87
|
+
const readLabels = System.create({ readonly: true, components: { with: [label, amount] } }, row => {
|
|
88
88
|
seen.labels.push(`${row[label].text}${row[amount]}`);
|
|
89
89
|
});
|
|
90
90
|
|
|
91
91
|
/** On the joined thread, as it reads labels: each watched entity's label and amount, which the world's run resolves to. */
|
|
92
|
-
const labelsThere = System.create({
|
|
92
|
+
const labelsThere = System.create({ readonly: true, lookup: { labelled: { with: [label, amount] } } }, ({ lookup }) =>
|
|
93
93
|
watched.map(entity => {
|
|
94
94
|
const row = lookup.labelled(entity);
|
|
95
95
|
return row === null ? "none" : `${row[label].text}${row[amount]}`;
|
|
96
96
|
}));
|
|
97
97
|
/** On the joined thread, as it reads labels: throws there. */
|
|
98
|
-
const failsThere = System.create({
|
|
98
|
+
const failsThere = System.create({ readonly: true, lookup: { labelled: { with: [label] } } }, ({ lookup }) => {
|
|
99
99
|
if (lookup.labelled(Entity.none) === null) throw new Error("observerJoinedTest failure there");
|
|
100
100
|
return 0;
|
|
101
101
|
});
|
|
@@ -218,7 +218,7 @@ describe("observer systems on a joined thread", () => {
|
|
|
218
218
|
Index.create({ key: [cell], sorted: true, components: { with: [amount] } }),
|
|
219
219
|
];
|
|
220
220
|
const [madeTagged, madeByCell] = made();
|
|
221
|
-
const sizes = System.create({
|
|
221
|
+
const sizes = System.create({ readonly: true, lookup: { all: { index: madeTagged, changed: [cell] }, cells: { index: madeByCell } } }, ({ lookup }) => {
|
|
222
222
|
const all = lookup.all();
|
|
223
223
|
seen.looked.push([all.changed]);
|
|
224
224
|
const high = lookup.cells({ ">=": 5 });
|
|
@@ -284,10 +284,18 @@ describe("observer systems on a joined thread", () => {
|
|
|
284
284
|
await close();
|
|
285
285
|
});
|
|
286
286
|
|
|
287
|
-
it("refuse only systems that aren't
|
|
287
|
+
it("refuse only systems that aren't readonly, saying to declare them so", async () => {
|
|
288
288
|
const { remote, close } = await joined();
|
|
289
|
-
expect(() => remote.add(raise)).toThrow("
|
|
290
|
-
expect(() => remote.remove(raise)).toThrow("
|
|
289
|
+
expect(() => remote.add(raise)).toThrow(`System "raise" isn't readonly, and a joined thread adds only readonly systems: declare it readonly: true to read the world from this thread, or add it on the world's thread.`);
|
|
290
|
+
expect(() => remote.remove(raise)).toThrow(`System "raise" isn't readonly, and a joined thread removes only readonly systems: others are removed on the world's thread.`);
|
|
291
|
+
await close();
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it("refuse a readonly system reading tables, which only the world's thread gives", async () => {
|
|
295
|
+
const { remote, close } = await joined();
|
|
296
|
+
const readTables = System.create({ readonly: true, tables: { components: [label] } }, ({ tables }) => tables.archetypes.length);
|
|
297
|
+
expect(() => remote.add(readTables)).toThrow(`System "readTables" reads tables, which only the world's thread gives: run or add it there.`);
|
|
298
|
+
await expect(remote.run(readTables)).rejects.toThrow(`System "readTables" reads tables, which only the world's thread gives: run or add it there.`);
|
|
291
299
|
await close();
|
|
292
300
|
});
|
|
293
301
|
|
|
@@ -309,7 +317,7 @@ describe("observer systems on a joined thread", () => {
|
|
|
309
317
|
const { world, close } = await joined(["observerJoinedTest"]);
|
|
310
318
|
await world.run(spawnLabelled);
|
|
311
319
|
// Made in a function, so the build gives it no id.
|
|
312
|
-
const made = () => System.create({
|
|
320
|
+
const made = () => System.create({ readonly: true, components: { with: [label, amount] } }, row => {
|
|
313
321
|
seen.labels.push(`${row[label].text}${row[amount]}`);
|
|
314
322
|
});
|
|
315
323
|
const madeLabels = made();
|
|
@@ -27,11 +27,11 @@ const warm = System.create({ kernel: "gpu", components: { with: [rate, heat] } }
|
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
let total = 0;
|
|
30
|
-
const sum = System.create({
|
|
30
|
+
const sum = System.create({ readonly: true, components: { with: [heat] } }, row => { total += row[heat]; });
|
|
31
31
|
|
|
32
32
|
const byHeat = Index.create({ key: [heat], sorted: true });
|
|
33
33
|
let hot = 0;
|
|
34
|
-
const countHot = System.create({
|
|
34
|
+
const countHot = System.create({ readonly: true, lookup: { hot: { index: byHeat } } }, ({ lookup }) => {
|
|
35
35
|
hot = lookup.hot({ ">=": 150 }).length;
|
|
36
36
|
});
|
|
37
37
|
|
|
@@ -37,33 +37,33 @@ const grow = System.create({ components: { with: [amount] } }, row => { row[amou
|
|
|
37
37
|
const nothing = System.create({}, () => {});
|
|
38
38
|
|
|
39
39
|
// It keeps its context, which the build can't follow, so it's given what it reads.
|
|
40
|
-
const watchLevel = System.create(giveAccess({
|
|
40
|
+
const watchLevel = System.create(giveAccess({ readonly: true }, { resources: { read: [level] } }), context => {
|
|
41
41
|
seen.levels.push(context.resources[level]);
|
|
42
42
|
seen.contexts.push(context);
|
|
43
43
|
});
|
|
44
|
-
const watchAmounts = System.create({
|
|
45
|
-
const count = System.create({
|
|
46
|
-
const inCell = System.create({
|
|
44
|
+
const watchAmounts = System.create({ readonly: true, components: { with: [amount], changed: [amount] } }, row => { seen.amounts.push(row[amount]); });
|
|
45
|
+
const count = System.create({ readonly: true, components: { with: [amount] } }, () => { seen.counted.push(1); });
|
|
46
|
+
const inCell = System.create({ readonly: true, lookup: { found: { index: byCell } } }, ({ lookup }) => {
|
|
47
47
|
seen.cells.push(lookup.found(7).length);
|
|
48
48
|
});
|
|
49
|
-
const failing = System.create({
|
|
49
|
+
const failing = System.create({ readonly: true }, ({ resources }) => {
|
|
50
50
|
if (resources[level] > 0) throw new Error("observer failed");
|
|
51
51
|
});
|
|
52
52
|
const raiseThenWatch = System.create([raise, watchLevel]);
|
|
53
|
-
const watchLevelChanges = System.create({
|
|
53
|
+
const watchLevelChanges = System.create({ readonly: true, resources: { changed: [level] } }, ({ resources }) => {
|
|
54
54
|
seen.levels.push(resources[level]);
|
|
55
55
|
});
|
|
56
56
|
|
|
57
57
|
/** The world a removing body acts on. */
|
|
58
58
|
let current: World | null = null;
|
|
59
|
-
const removeLevel = System.create({
|
|
59
|
+
const removeLevel = System.create({ readonly: true }, () => {
|
|
60
60
|
seen.removals.push(1);
|
|
61
61
|
current?.remove(watchLevel);
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
/** Adds `watchLevel` to the world the test runs, during a run. */
|
|
65
65
|
const addWatch = System.create({}, () => { current?.add(watchLevel); });
|
|
66
|
-
const addLevel = System.create({
|
|
66
|
+
const addLevel = System.create({ readonly: true }, () => {
|
|
67
67
|
current?.add(watchLevel);
|
|
68
68
|
});
|
|
69
69
|
|
|
@@ -30,7 +30,7 @@ const assign = System.create({ kernel: true, components: { with: [position], opt
|
|
|
30
30
|
row[velocity] = row[position];
|
|
31
31
|
});
|
|
32
32
|
const totals = { moved: 0, still: 0 };
|
|
33
|
-
const sum = System.create({
|
|
33
|
+
const sum = System.create({ readonly: true, components: { with: [position], optional: [velocity] } }, row => {
|
|
34
34
|
if (row[velocity] === undefined) totals.still += row[position];
|
|
35
35
|
else totals.moved += row[position];
|
|
36
36
|
});
|
|
@@ -73,7 +73,7 @@ const copySpeedKernel = System.create({
|
|
|
73
73
|
if (found !== null) row[order] = found[velocity] === undefined ? -found[position] : found[velocity];
|
|
74
74
|
});
|
|
75
75
|
const speeds: number[] = [];
|
|
76
|
-
const readSpeeds = System.create({
|
|
76
|
+
const readSpeeds = System.create({ readonly: true, components: { with: [other, order] } }, row => { speeds.push(row[order]); });
|
|
77
77
|
|
|
78
78
|
/** What finalizers read of rows that stopped, on the world's thread and on the markers'. */
|
|
79
79
|
const stopped: string[] = [];
|
|
@@ -87,7 +87,7 @@ export const finalizeThere = Finalizer.create({ components: { with: [position, m
|
|
|
87
87
|
const despawnAll = System.create({ components: { with: [position] } }, (row, { commands }) => { commands.despawn(row.entity); });
|
|
88
88
|
|
|
89
89
|
let seen: (readonly [number, number | undefined, string | undefined])[] = [];
|
|
90
|
-
const read = System.create({
|
|
90
|
+
const read = System.create({ readonly: true, components: { with: [position], optional: [velocity, label] } }, row => {
|
|
91
91
|
seen.push([row[position], row[velocity], row[label]]);
|
|
92
92
|
});
|
|
93
93
|
const sorted = () => [...seen].sort((a, b) => a[0] - b[0]);
|
|
@@ -109,7 +109,7 @@ const lookedThere = System.create({ components: { with: [position, marker] }, lo
|
|
|
109
109
|
});
|
|
110
110
|
// An observer system there reads copies of rows across archetypes, which tell which have the optional component.
|
|
111
111
|
let watched: (readonly [number, number | undefined])[] = [];
|
|
112
|
-
const watchThere = System.create({
|
|
112
|
+
const watchThere = System.create({ readonly: true, components: { with: [position], optional: [velocity] } }, row => {
|
|
113
113
|
watched.push([row[position], row[velocity]]);
|
|
114
114
|
});
|
|
115
115
|
// A tail there: it writes only its thread's objects, so it runs after the sync point, reading copies of rows.
|
|
@@ -165,7 +165,7 @@ const markEither = System.create({ kernel: true, components: { with: [mark], opt
|
|
|
165
165
|
if (row[far] !== undefined) row[mark] = 2;
|
|
166
166
|
});
|
|
167
167
|
const marked: number[] = [];
|
|
168
|
-
const watchStamped = System.create({
|
|
168
|
+
const watchStamped = System.create({ readonly: true, components: { with: [mark], changed: [mark] } }, row => { marked.push(row[mark]); });
|
|
169
169
|
const clearNear = System.create({ kernel: false, components: { with: [mark], optional: [near] } }, row => {
|
|
170
170
|
if (row[near] !== undefined) row[near] = undefined;
|
|
171
171
|
});
|
|
@@ -195,7 +195,7 @@ describe("optional components", () => {
|
|
|
195
195
|
|
|
196
196
|
it("pass change filters on other components, and are stamped changed when written", async () => {
|
|
197
197
|
const changed: number[] = [];
|
|
198
|
-
const watch = System.create({
|
|
198
|
+
const watch = System.create({ readonly: true, components: { with: [velocity], changed: [velocity] } }, row => { changed.push(row[velocity]); });
|
|
199
199
|
const speedUp = System.create({ components: { with: [position], optional: [velocity] } }, row => {
|
|
200
200
|
const speed = row[velocity];
|
|
201
201
|
if (speed !== undefined && row[position] > 50) row[velocity] = speed + 1;
|
|
@@ -212,7 +212,7 @@ describe("optional components", () => {
|
|
|
212
212
|
it("run in an index's order across archetypes that have them and don't", async () => {
|
|
213
213
|
const byOrder = Index.create({ key: [order], sorted: true });
|
|
214
214
|
const ordered: (number | undefined)[] = [];
|
|
215
|
-
const list = System.create({
|
|
215
|
+
const list = System.create({ readonly: true, components: { with: [position], optional: [velocity] }, order: byOrder }, row => {
|
|
216
216
|
ordered.push(row[velocity]);
|
|
217
217
|
});
|
|
218
218
|
await using world = inline();
|
|
@@ -297,7 +297,7 @@ describe("optional components", () => {
|
|
|
297
297
|
await world.run(spawnMarked);
|
|
298
298
|
stoppedThere.length = 0;
|
|
299
299
|
await world.run(despawnAll);
|
|
300
|
-
await remote.run(System.create({
|
|
300
|
+
await remote.run(System.create({ readonly: true }, () => {}));
|
|
301
301
|
expect(stoppedThere.sort()).toEqual(["10:5:null", "1:none:null"]);
|
|
302
302
|
await remote.dispose();
|
|
303
303
|
await world.dispose();
|
|
@@ -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)));
|
|
@@ -42,7 +42,7 @@ const caught: number[] = [];
|
|
|
42
42
|
export const catchUp = System.create({ kernel: false, components: { with: [position], changed: [position], where: { [cell]: (at, { resources }) => at[0] === resources[camera] } } }, row => { caught.push(row[position]); });
|
|
43
43
|
// On a joined thread.
|
|
44
44
|
const heard: number[] = [];
|
|
45
|
-
export const hear = System.create({
|
|
45
|
+
export const hear = System.create({ readonly: true, components: { with: [cell, position], where: { [cell]: at => at[0] === 50 } } }, row => { heard.push(row[cell][0] * 1000 + row[position]); });
|
|
46
46
|
|
|
47
47
|
/** Moves an entity to cell `[x, 0]`. */
|
|
48
48
|
const step = System.create({}, ({ commands }, input: { readonly entity: Entity; readonly x: number }) => { commands.add(input.entity, cell, [input.x, 0]); });
|
|
@@ -59,7 +59,7 @@ const spawnAt = (world: ReturnType<typeof inline>, x: number, components: readon
|
|
|
59
59
|
/** Every row's cell and position, by position. */
|
|
60
60
|
const cellsOf = async (world: ReturnType<typeof inline>): Promise<readonly string[]> => {
|
|
61
61
|
const found: [number, string][] = [];
|
|
62
|
-
await world.run(System.create({
|
|
62
|
+
await world.run(System.create({ readonly: true, components: { with: [cell, position] } }, row => { found.push([row[position], `${row[cell][0]}:${row[position]}`]); }));
|
|
63
63
|
return found.sort((a, b) => a[0] - b[0]).map(([, text]) => text);
|
|
64
64
|
};
|
|
65
65
|
|
|
@@ -132,7 +132,7 @@ describe("reclaiming archetypes of shared values", () => {
|
|
|
132
132
|
found.length = 0;
|
|
133
133
|
await world.run(System.create({ kernel: false, components: { with: [cell, position] } }, row => { found.push(`body ${row[cell][0]}`); }));
|
|
134
134
|
await world.run(copyCell);
|
|
135
|
-
await world.run(System.create({
|
|
135
|
+
await world.run(System.create({ readonly: true, components: { with: [cell, seen] } }, row => { found.push(`kernel ${row[seen]}`); }));
|
|
136
136
|
await world.run(atFifty);
|
|
137
137
|
await world.run(atLast);
|
|
138
138
|
expect(found.sort()).toEqual(["body 50", `body ${emptyFor + 2}`, "kernel 50", `kernel ${emptyFor + 2}`, "value 0", "where 100"].sort());
|
|
@@ -132,7 +132,7 @@ const observerTargetOf = (state: State, leaf: Leaf): ObserverTarget | undefined
|
|
|
132
132
|
|
|
133
133
|
/** A runner for a leaf where it runs now: on the joined thread filling its thread, or that added it, or here. */
|
|
134
134
|
const placedRunner = (state: State, leaf: Leaf, since = 0): Runner => {
|
|
135
|
-
if (leaf.declaration.
|
|
135
|
+
if (leaf.declaration.readonly === true) {
|
|
136
136
|
const target = observerTargetOf(state, leaf);
|
|
137
137
|
return target === undefined ? createRunner(state, leaf, since) : createJoinedObserverRunner(state, leaf, target, since);
|
|
138
138
|
}
|
|
@@ -394,7 +394,7 @@ export const createRegistrar = (state: State): Registrar => {
|
|
|
394
394
|
|
|
395
395
|
const place = (): void => {
|
|
396
396
|
eachRunner((leaf, old, entry) => {
|
|
397
|
-
if (leaf.declaration.
|
|
397
|
+
if (leaf.declaration.readonly === true) {
|
|
398
398
|
if (observerTargetOf(state, leaf) === old.joined?.target) return;
|
|
399
399
|
replace(leaf, entry, old, placedRunner(state, leaf, old.lastRun()));
|
|
400
400
|
return;
|
|
@@ -72,7 +72,7 @@ export type Observers = "factory" | "observer";
|
|
|
72
72
|
const observer: { entity: Entity | null; seen: ((odd: boolean, held: boolean, text: string | null | undefined) => void) | null } = { entity: null, seen: null };
|
|
73
73
|
|
|
74
74
|
/** After each sync point, on the joined thread: the watched entity's phase and caption, and whether it's flagged. */
|
|
75
|
-
export const observerWatch = System.create({
|
|
75
|
+
export const observerWatch = System.create({ readonly: true, lookup: { values: { with: [phase, caption] }, flagged: { with: [flag] } } }, ({ lookup }) => {
|
|
76
76
|
if (observer.entity === null) return;
|
|
77
77
|
const row = lookup.values(observer.entity);
|
|
78
78
|
if (row === null) return;
|
|
@@ -12,6 +12,7 @@ import { ownTag } from './move-log.js';
|
|
|
12
12
|
import { resourceCell } from './resources.js';
|
|
13
13
|
import { labelOf } from '../../interface/registry/names.js';
|
|
14
14
|
import { idOf, type Key, type State } from './state.js';
|
|
15
|
+
import { readTables } from './tables-read.js';
|
|
15
16
|
import { combine, contributed, identityOf, type ReductionTarget } from './reductions.js';
|
|
16
17
|
import { createIndexLookup, type Since } from './index-lookups.js';
|
|
17
18
|
import type { WorldIndex } from './indexes.js';
|
|
@@ -506,8 +507,11 @@ const never: Since = { value: -1 };
|
|
|
506
507
|
const own = (): object => Object.create({});
|
|
507
508
|
|
|
508
509
|
/** Resource cells are resolved once per run: they move only at sync points. An unresolved resource throws on access. */
|
|
509
|
-
export const createContext = (state: State, declaration: Pick<Resolved, "lookup" | "resources">, { targets = [], commands, indexes, since = never }: Extras = {}): Context => {
|
|
510
|
+
export const createContext = (state: State, declaration: Pick<Resolved, "lookup" | "resources" | "tables">, { targets = [], commands, indexes, since = never }: Extras = {}): Context => {
|
|
510
511
|
const value: { up?: unknown } = own();
|
|
512
|
+
const { tables } = declaration;
|
|
513
|
+
// Each read copies the tables as they are then.
|
|
514
|
+
if (tables !== undefined) Object.defineProperty(value, "tables", { get: () => readTables(state, tables), enumerable: true });
|
|
511
515
|
const { lookup, resources } = declaration;
|
|
512
516
|
const names = Object.keys(lookup ?? {});
|
|
513
517
|
if (names.length > 0) {
|