@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
|
@@ -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();
|
|
@@ -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) {
|
|
@@ -30,11 +30,11 @@ const spawn = System.create(({ commands }) => {
|
|
|
30
30
|
const raise = System.create(({ resources }) => {
|
|
31
31
|
resources[level] += 1;
|
|
32
32
|
});
|
|
33
|
-
const count = System.create({
|
|
33
|
+
const count = System.create({ readonly: true, components: { with: [amount] } }, () => {
|
|
34
34
|
counted++;
|
|
35
35
|
});
|
|
36
36
|
let counted = 0;
|
|
37
|
-
const readLevel = System.create({
|
|
37
|
+
const readLevel = System.create({ readonly: true }, ({ resources }) => resources[level]);
|
|
38
38
|
const broken = System.create(() => {
|
|
39
39
|
throw new Error("broken");
|
|
40
40
|
});
|
|
@@ -57,7 +57,7 @@ describe("a run", () => {
|
|
|
57
57
|
expect(await world.run(System.create({ components: { with: [amount] } }, row => row[amount]))).toBeUndefined();
|
|
58
58
|
expect(await world.run(System.create([readLevel, raise]))).toBeUndefined();
|
|
59
59
|
expect(await world.run(raise)).toBeUndefined();
|
|
60
|
-
const whenChanged = System.create({
|
|
60
|
+
const whenChanged = System.create({ readonly: true, resources: { changed: [level] } }, ({ resources }) => resources[level]);
|
|
61
61
|
expect(await world.run(whenChanged)).toBe(2);
|
|
62
62
|
// Unchanged since it last ran: skipped.
|
|
63
63
|
expect(await world.run(whenChanged)).toBeUndefined();
|
|
@@ -94,7 +94,7 @@ describe("a run", () => {
|
|
|
94
94
|
it("rejects when its own observer system fails, and reports one added out of band", async () => {
|
|
95
95
|
using reported = catchOutOfBand();
|
|
96
96
|
await using world = inline();
|
|
97
|
-
const failingObserver = System.create({
|
|
97
|
+
const failingObserver = System.create({ readonly: true }, () => {
|
|
98
98
|
throw new Error("observer failed");
|
|
99
99
|
});
|
|
100
100
|
await expect(world.run(failingObserver)).rejects.toThrow(/observer failed/);
|
|
@@ -144,7 +144,7 @@ const spawnTwins = System.create(({ commands }) => {
|
|
|
144
144
|
commands.spawn([serial])({ [serial]: 1 });
|
|
145
145
|
commands.spawn([serial])({ [serial]: 1 });
|
|
146
146
|
});
|
|
147
|
-
const findTwin = System.create({
|
|
147
|
+
const findTwin = System.create({ readonly: true, lookup: { twins: { index: bySerial } } }, ({ lookup }) => lookup.twins(1)?.entity ?? null);
|
|
148
148
|
const setGate = System.create({}, ({ resources }, input: { readonly open: boolean }) => {
|
|
149
149
|
resources[gate] = input.open;
|
|
150
150
|
});
|
|
@@ -207,10 +207,10 @@ describe("a run's observer pass", () => {
|
|
|
207
207
|
it("fails the run for its own observer system, and reports one added out of band, in one run", async () => {
|
|
208
208
|
using reported = catchOutOfBand();
|
|
209
209
|
await using world = inline();
|
|
210
|
-
const ownFails = System.create({
|
|
210
|
+
const ownFails = System.create({ readonly: true }, () => {
|
|
211
211
|
throw new Error("own failed");
|
|
212
212
|
});
|
|
213
|
-
const addedFails = System.create({
|
|
213
|
+
const addedFails = System.create({ readonly: true }, () => {
|
|
214
214
|
throw new Error("added failed");
|
|
215
215
|
});
|
|
216
216
|
world.add(addedFails);
|
|
@@ -255,7 +255,7 @@ describe("out-of-band reports", () => {
|
|
|
255
255
|
it("aren't made again for a system skipped between its failures", async () => {
|
|
256
256
|
using reported = catchOutOfBand();
|
|
257
257
|
await using world = inline();
|
|
258
|
-
const failsWhenChanged = System.create({
|
|
258
|
+
const failsWhenChanged = System.create({ readonly: true, resources: { changed: [level] } }, () => {
|
|
259
259
|
throw new Error("changed failed");
|
|
260
260
|
});
|
|
261
261
|
world.add(failsWhenChanged);
|
|
@@ -294,7 +294,7 @@ describe("out-of-band reports", () => {
|
|
|
294
294
|
using reported = catchOutOfBand();
|
|
295
295
|
const world = inline();
|
|
296
296
|
let failing = false;
|
|
297
|
-
const failsAdded = System.create({
|
|
297
|
+
const failsAdded = System.create({ readonly: true }, () => {
|
|
298
298
|
if (failing) throw new Error("added failed");
|
|
299
299
|
});
|
|
300
300
|
world.add(failsAdded);
|
|
@@ -331,7 +331,7 @@ describe("out-of-band reports", () => {
|
|
|
331
331
|
it("aren't made again for a row system whose change filter passed no row between its failures", async () => {
|
|
332
332
|
using reported = catchOutOfBand();
|
|
333
333
|
await using world = inline();
|
|
334
|
-
const failsOnChange = System.create({
|
|
334
|
+
const failsOnChange = System.create({ readonly: true, components: { with: [amount], changed: [amount] } }, () => {
|
|
335
335
|
throw new Error("row failed");
|
|
336
336
|
});
|
|
337
337
|
const bump = System.create({ components: { with: [amount] } }, row => {
|
|
@@ -260,7 +260,7 @@ export const createRunner = (state: State, leaf: Leaf, since = 0): Runner => {
|
|
|
260
260
|
: createOrderedRender(indexLookups.ordered, ordered, accessors.map(accessor => idOf(state, accessor.component)), declaration.shader?.group ?? 0, `${leafNumber}:`);
|
|
261
261
|
const touches = [...new Set([...query?.read ?? [], ...query?.write ?? []])].map(key => idOf(state, key));
|
|
262
262
|
const writes = (query?.write ?? []).map(key => idOf(state, key));
|
|
263
|
-
const looks = [...new Set([...Object.values(declaration.lookup ?? {}).flatMap(lookup => lookup.read ?? []), ...declaration.up?.read ?? []])].map(key => idOf(state, key));
|
|
263
|
+
const looks = [...new Set([...Object.values(declaration.lookup ?? {}).flatMap(lookup => lookup.read ?? []), ...declaration.up?.read ?? [], ...declaration.tables?.components ?? []])].map(key => idOf(state, key));
|
|
264
264
|
/** Record keys by archetype. */
|
|
265
265
|
const keys = new Map<Archetype, string>();
|
|
266
266
|
const keyOf = (archetype: Archetype): string => {
|
|
@@ -339,7 +339,7 @@ export const createRunner = (state: State, leaf: Leaf, since = 0): Runner => {
|
|
|
339
339
|
let runInput: unknown;
|
|
340
340
|
const context = createContext(state, declaration, {
|
|
341
341
|
targets: kernel === null ? targets : [],
|
|
342
|
-
commands: leaf.kind === "render" || declaration.kernel === "gpu" || declaration.
|
|
342
|
+
commands: leaf.kind === "render" || declaration.kernel === "gpu" || declaration.readonly === true ? undefined : commands,
|
|
343
343
|
indexes: indexLookups.byName,
|
|
344
344
|
since: lookupSince,
|
|
345
345
|
});
|
|
@@ -1343,7 +1343,7 @@ export const createRunner = (state: State, leaf: Leaf, since = 0): Runner => {
|
|
|
1343
1343
|
return taken;
|
|
1344
1344
|
},
|
|
1345
1345
|
didRun: () => ran && (query === undefined || bodyRanOnRow()),
|
|
1346
|
-
restart: declaration.
|
|
1346
|
+
restart: declaration.readonly !== true || !holdsNothing(holdings) ? null : since => {
|
|
1347
1347
|
lastRun = since;
|
|
1348
1348
|
holdings.removed = false;
|
|
1349
1349
|
holdings.released = false;
|
|
@@ -59,7 +59,7 @@ const fractions = System.create({ kernel: "gpu", components: { with: [scale, val
|
|
|
59
59
|
});
|
|
60
60
|
const fractionsOf = async (world: ReturnType<typeof createWorld>): Promise<readonly (readonly number[])[]> => {
|
|
61
61
|
const read: [number, number[]][] = [];
|
|
62
|
-
await world.run(System.create({
|
|
62
|
+
await world.run(System.create({ readonly: true, components: { with: [value, copied, product, sum, compound] } }, row => { read.push([row[value], [row[copied], row[product], row[sum], row[compound], row[value]]]); }));
|
|
63
63
|
return read.sort((a, b) => a[0] - b[0]).map(([, values]) => values);
|
|
64
64
|
};
|
|
65
65
|
|
|
@@ -71,7 +71,7 @@ const device = async (): Promise<GPUDevice> => {
|
|
|
71
71
|
|
|
72
72
|
const results = async (world: ReturnType<typeof createWorld>): Promise<readonly string[]> => {
|
|
73
73
|
const read: [number, string][] = [];
|
|
74
|
-
await world.run(System.create({
|
|
74
|
+
await world.run(System.create({ readonly: true, components: { with: [value, out, steps] } }, row => { read.push([row[value], `${row[out]} ${row[steps]}`]); }));
|
|
75
75
|
return read.sort((a, b) => a[0] - b[0]).map(([, text]) => text);
|
|
76
76
|
};
|
|
77
77
|
|
|
@@ -62,7 +62,7 @@ const wideOnGpu = System.create({ kernel: "gpu", components: { with: [wide, out]
|
|
|
62
62
|
/** Every row's results, by its value. */
|
|
63
63
|
const results = async (world: ReturnType<typeof createWorld>): Promise<readonly string[]> => {
|
|
64
64
|
const read: [number, string][] = [];
|
|
65
|
-
await world.run(System.create({
|
|
65
|
+
await world.run(System.create({ readonly: true, components: { with: [value, out, steps] } }, row => { read.push([row[value], `${row[out]} ${row[steps]}`]); }));
|
|
66
66
|
return read.sort((a, b) => a[0] - b[0]).map(([, text]) => text);
|
|
67
67
|
};
|
|
68
68
|
|
|
@@ -98,7 +98,7 @@ describe("a kernel reading a shared component", () => {
|
|
|
98
98
|
await world.run(spawn);
|
|
99
99
|
expect((await runReported(world, moving)).failures).toEqual([]);
|
|
100
100
|
const scales: number[] = [];
|
|
101
|
-
await world.run(System.create({
|
|
101
|
+
await world.run(System.create({ readonly: true, components: { with: [scale] } }, row => { scales.push(row[scale]); }));
|
|
102
102
|
expect(new Set(scales)).toEqual(new Set([2]));
|
|
103
103
|
// A kernel built without knowing a component is shared, naming it in a command, fails where it registers.
|
|
104
104
|
const description = kernelOf(compute);
|
|
@@ -39,7 +39,7 @@ const written = System.create({ kernel: false, components: { with: [scale, cell,
|
|
|
39
39
|
row[steps] = x * 1000 + y;
|
|
40
40
|
});
|
|
41
41
|
const read: [number, string][] = [];
|
|
42
|
-
const results = System.create({
|
|
42
|
+
const results = System.create({ readonly: true, components: { with: [value, out, steps] } }, row => { read.push([row[value], `${row[out]} ${row[steps]}`]); });
|
|
43
43
|
|
|
44
44
|
const outcome = async (system: typeof compute, wasm: boolean): Promise<readonly string[]> => {
|
|
45
45
|
await using world = createWorld({ [workers]: 2 }, { minimumChunk: 2, wasm });
|
|
@@ -66,7 +66,7 @@ export const whereOnWorkers = async (wasm: boolean): Promise<{ readonly written:
|
|
|
66
66
|
await world.run(negative);
|
|
67
67
|
let written = 0;
|
|
68
68
|
let left = 0;
|
|
69
|
-
await world.run(System.create({
|
|
69
|
+
await world.run(System.create({ readonly: true, components: { with: [value, out] } }, row => {
|
|
70
70
|
if (row[out] === 0 && row[value] !== 0) left++;
|
|
71
71
|
else written++;
|
|
72
72
|
}));
|
|
@@ -39,7 +39,7 @@ const inline = () => createWorld({ [workers]: 0 });
|
|
|
39
39
|
|
|
40
40
|
// Systems a joined thread runs, which it names by the ids the build gives them at the top level.
|
|
41
41
|
const heard: string[] = [];
|
|
42
|
-
export const listen = System.create({
|
|
42
|
+
export const listen = System.create({ readonly: true, components: { with: [team, health] } }, row => { heard.push(`${row[team]} ${row[health]}`); });
|
|
43
43
|
export const spawnBanners = System.create(({ commands }) => {
|
|
44
44
|
commands.spawn([team, health, banner], { [team]: "red" })({ [health]: 1 });
|
|
45
45
|
commands.spawn([team, health, banner], { [team]: "blue" })({ [health]: 2 });
|
|
@@ -60,11 +60,11 @@ export const spawnThere = System.create({ components: { with: [banner] } }, (row
|
|
|
60
60
|
commands.spawn([health, marker], base)({ [marker]: 1 });
|
|
61
61
|
commands.spawn([health], base)({});
|
|
62
62
|
});
|
|
63
|
-
export const readBanners = System.create({
|
|
63
|
+
export const readBanners = System.create({ readonly: true, components: { with: [banner] } }, row => { banners.push(row[banner]?.text ?? "none"); });
|
|
64
64
|
|
|
65
65
|
/** Every entity's team and health, and whether it's armed, sorted by health. */
|
|
66
66
|
const rowsOf = async (world: ReturnType<typeof inline>): Promise<readonly string[]> => {
|
|
67
|
-
const read = System.create({
|
|
67
|
+
const read = System.create({ readonly: true, components: { with: [team, health], optional: [armed] } }, (row) => {
|
|
68
68
|
found.push(`${row[team]} ${row[health]}${row[armed] === true ? " armed" : ""}`);
|
|
69
69
|
});
|
|
70
70
|
const found: string[] = [];
|
|
@@ -116,7 +116,7 @@ describe("a shared component", () => {
|
|
|
116
116
|
// Three cells' worth of [1, 2] and [2, 1], three weights and one style.
|
|
117
117
|
expect((await world.tables()).archetypes - before).toBe(2 + 3 + 1);
|
|
118
118
|
const read: string[] = [];
|
|
119
|
-
await world.run(System.create({
|
|
119
|
+
await world.run(System.create({ readonly: true, components: { with: [health], optional: [cell, weight, style] } }, row => {
|
|
120
120
|
if (row[health] === 0) return;
|
|
121
121
|
read.push(`${row[health]}: ${JSON.stringify(row[cell] ?? row[style] ?? null)} ${String(row[weight])}`);
|
|
122
122
|
}));
|
|
@@ -216,8 +216,8 @@ describe("a shared component", () => {
|
|
|
216
216
|
commands.spawn([team, health], { [team]: "red" })({ [health]: 2 });
|
|
217
217
|
}));
|
|
218
218
|
const changes: string[] = [];
|
|
219
|
-
const watch = System.create({
|
|
220
|
-
const adds = System.create({
|
|
219
|
+
const watch = System.create({ readonly: true, components: { with: [health], changed: [team] } }, row => { changes.push(`changed ${row[health]}`); });
|
|
220
|
+
const adds = System.create({ readonly: true, components: { with: [health], added: [team] } }, row => { changes.push(`added ${row[health]}`); });
|
|
221
221
|
world.add(watch);
|
|
222
222
|
world.add(adds);
|
|
223
223
|
await world.run(System.create(() => {}));
|
|
@@ -294,7 +294,7 @@ describe("a shared component", () => {
|
|
|
294
294
|
}));
|
|
295
295
|
await world.run(System.create({ components: { with: [weight, health] } }, row => { row[health] *= row[weight]; }));
|
|
296
296
|
const read: number[] = [];
|
|
297
|
-
await world.run(System.create({
|
|
297
|
+
await world.run(System.create({ readonly: true, components: { with: [health] } }, row => { read.push(row[health]); }));
|
|
298
298
|
expect(read.sort()).toEqual([2, 3]);
|
|
299
299
|
});
|
|
300
300
|
|
|
@@ -338,7 +338,7 @@ describe("a shared component", () => {
|
|
|
338
338
|
await world.run(writeBanners);
|
|
339
339
|
await world.run(spawnThere);
|
|
340
340
|
const found: string[] = [];
|
|
341
|
-
await world.run(System.create({
|
|
341
|
+
await world.run(System.create({ readonly: true, components: { with: [health], optional: [marker, team] } }, row => {
|
|
342
342
|
if (row[team] === undefined) found.push(`${row[health]} ${row[marker] ?? "none"}`);
|
|
343
343
|
}));
|
|
344
344
|
expect(found.sort()).toEqual(["7 1", "7 1", "7 none", "7 none"]);
|
|
@@ -613,7 +613,7 @@ export const createSharing = (host: SharingHost) => {
|
|
|
613
613
|
const { components, resources } = declared;
|
|
614
614
|
// Unnamed, so other systems' labels stay as they are; its own is the thread's.
|
|
615
615
|
const declaration: Resolved = {
|
|
616
|
-
|
|
616
|
+
readonly: true,
|
|
617
617
|
// Its `where` runs on its thread; here, an empty one says it has one, which every archetype passes.
|
|
618
618
|
...components === null ? {} : { components: { ...listed([["read", components.read], ["has", components.has], ["optional", components.optional], ["not", components.not], ["added", components.added], ["changed", components.changed]]), ...components.where ? { where: {} } : {} } },
|
|
619
619
|
...resources === null ? {} : { resources: listed([["read", resources.read], ["changed", resources.changed], ["is", resources.is], ["not", resources.not]]) },
|
|
@@ -69,14 +69,15 @@ export const createBatch = (layouts: readonly Layout[], ids: readonly number[],
|
|
|
69
69
|
return { ids: sorted, shared, archetype: null, columns: sorted.map(id => columnOf(layouts[id], id, initialCapacity)), entities: new Float64Array(initialCapacity), count: 0, capacity: initialCapacity, revive: null, generation: 0 };
|
|
70
70
|
};
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
/** Grows a batch to hold at least `needed` rows: twice its capacity, or `needed` where that's more. */
|
|
73
|
+
const grow = (batch: SpawnBatch, needed = batch.count + 1): void => {
|
|
73
74
|
const { revive } = batch;
|
|
74
75
|
if (revive !== null) {
|
|
75
76
|
batch.revive = null;
|
|
76
77
|
revive();
|
|
77
|
-
if (
|
|
78
|
+
if (needed <= batch.capacity) return;
|
|
78
79
|
}
|
|
79
|
-
const capacity = batch.capacity * 2;
|
|
80
|
+
const capacity = Math.max(batch.capacity * 2, needed);
|
|
80
81
|
for (const column of batch.columns) {
|
|
81
82
|
const views = viewsOf(new ArrayBuffer(capacity * column.stride), 0, capacity * column.stride);
|
|
82
83
|
views.u8.set(column.views.u8.subarray(0, batch.count * column.stride));
|
|
@@ -146,6 +147,18 @@ export const openRow = (batch: SpawnBatch): number => {
|
|
|
146
147
|
return batch.count;
|
|
147
148
|
};
|
|
148
149
|
|
|
150
|
+
/** The first of the `n` rows the batch's next spawns write, growing it first to hold them. */
|
|
151
|
+
export const openRows = (batch: SpawnBatch, n: number): number => {
|
|
152
|
+
if (batch.count + n > batch.capacity) grow(batch, batch.count + n);
|
|
153
|
+
return batch.count;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/** Claims the open rows for `entities`, once all their values are written. */
|
|
157
|
+
export const claimRows = (batch: SpawnBatch, entities: Float64Array): void => {
|
|
158
|
+
batch.entities.set(entities, batch.count);
|
|
159
|
+
batch.count += entities.length;
|
|
160
|
+
};
|
|
161
|
+
|
|
149
162
|
/** Claims the open row for `entity`, once all its values are written. */
|
|
150
163
|
export const claim = (batch: SpawnBatch, entity: number): void => {
|
|
151
164
|
batch.entities[batch.count] = entity;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
import { labelOf } from '../../interface/registry/names.js';
|
|
3
|
+
import type { Tables, TablesDeclaration } from '../../interface/system/tables.js';
|
|
4
|
+
import type { Table } from '../../interface/world/table/table.js';
|
|
5
|
+
import type { Storage } from './archetype.js';
|
|
6
|
+
import { readCell } from './cells.js';
|
|
7
|
+
import { resourceCell } from './resources.js';
|
|
8
|
+
import { idOf, type Key, type State } from './state.js';
|
|
9
|
+
|
|
10
|
+
// Reading tables, apart from spawning them: rows' contexts import it, and what spawning imports would make a cycle.
|
|
11
|
+
|
|
12
|
+
/** What an archetype's storage of a component gives a table: a copy of its rows `0` to `count`. */
|
|
13
|
+
const columnOf = (key: Key, storage: Storage, count: number): Table.Column => {
|
|
14
|
+
const { layout } = storage;
|
|
15
|
+
switch (layout.kind) {
|
|
16
|
+
case "linear": return { kind: "bytes", bytes: storage.views.u8.slice(0, count * layout.stride), stride: layout.stride };
|
|
17
|
+
case "constant": return { kind: "value", value: layout.value };
|
|
18
|
+
case "object":
|
|
19
|
+
if (storage.remote !== null) throw new Error(`Tables can't read "${labelOf(key)}": a joined thread fills its objects.`);
|
|
20
|
+
return { kind: "values", values: storage.values.slice(0, count) };
|
|
21
|
+
case "string": {
|
|
22
|
+
const values: unknown[] = new Array(count);
|
|
23
|
+
for (let row = 0; row < count; row++) values[row] = readCell(storage, row);
|
|
24
|
+
return { kind: "values", values };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A readonly system's `tables`: of each archetype matching `with` and `not` and holding any of `components`, its
|
|
31
|
+
* entities and a copy of the column of each of them it holds; and the value of each resource of `resources`.
|
|
32
|
+
*/
|
|
33
|
+
export const readTables = (state: State, declaration: TablesDeclaration): Tables => {
|
|
34
|
+
const asked = declaration.components.map(key => ({ key, id: idOf(state, key) }));
|
|
35
|
+
const required = (declaration.with ?? []).map(key => idOf(state, key));
|
|
36
|
+
const excluded = (declaration.not ?? []).map(key => idOf(state, key));
|
|
37
|
+
const archetypes: Table[] = [];
|
|
38
|
+
for (const archetype of state.archetypes) {
|
|
39
|
+
const { count } = archetype;
|
|
40
|
+
if (count === 0 || archetype.reclaimed) continue;
|
|
41
|
+
const { storageById } = archetype;
|
|
42
|
+
if (required.some(id => storageById[id] === undefined) || excluded.some(id => storageById[id] !== undefined)) continue;
|
|
43
|
+
const columns: { [key: symbol]: Table.Column } = {};
|
|
44
|
+
let held = false;
|
|
45
|
+
for (const { key, id } of asked) {
|
|
46
|
+
const storage = storageById[id];
|
|
47
|
+
if (storage === undefined) continue;
|
|
48
|
+
columns[key] = columnOf(key, storage, count);
|
|
49
|
+
held = true;
|
|
50
|
+
}
|
|
51
|
+
if (held) archetypes.push({ count, entities: archetype.entities.slice(0, count), columns });
|
|
52
|
+
}
|
|
53
|
+
const resources: { [key: symbol]: unknown } = {};
|
|
54
|
+
for (const key of declaration.resources ?? []) {
|
|
55
|
+
const cell = resourceCell(state, key);
|
|
56
|
+
resources[key] = readCell(cell.storage, cell.row);
|
|
57
|
+
}
|
|
58
|
+
return { archetypes, resources };
|
|
59
|
+
};
|