@adobe/ecs 0.2.1 → 0.2.4
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/body-access.d.ts +18 -1
- package/dist/implementation/compiler/body-access.js +104 -15
- package/dist/implementation/compiler/body-access.js.map +1 -1
- 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/estree.d.ts +5 -2
- package/dist/implementation/compiler/estree.js +8 -5
- package/dist/implementation/compiler/estree.js.map +1 -1
- package/dist/implementation/compiler/inferred.js +4 -2
- package/dist/implementation/compiler/inferred.js.map +1 -1
- package/dist/implementation/compiler/signature.js +1 -1
- package/dist/implementation/compiler/transform.js +6 -5
- package/dist/implementation/compiler/transform.js.map +1 -1
- package/dist/implementation/world/activate.js +14 -2
- package/dist/implementation/world/activate.js.map +1 -1
- package/dist/implementation/world/buffer.d.ts +7 -0
- package/dist/implementation/world/buffer.js +7 -0
- package/dist/implementation/world/buffer.js.map +1 -1
- package/dist/implementation/world/cascade.d.ts +10 -3
- package/dist/implementation/world/cascade.js +48 -23
- package/dist/implementation/world/cascade.js.map +1 -1
- package/dist/implementation/world/commands.js +86 -2
- package/dist/implementation/world/commands.js.map +1 -1
- package/dist/implementation/world/create.js +53 -2
- 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/execute.d.ts +4 -4
- package/dist/implementation/world/execute.js +132 -83
- package/dist/implementation/world/execute.js.map +1 -1
- package/dist/implementation/world/finalizers.d.ts +2 -0
- package/dist/implementation/world/finalizers.js +24 -3
- package/dist/implementation/world/finalizers.js.map +1 -1
- package/dist/implementation/world/join.js +22 -4
- package/dist/implementation/world/join.js.map +1 -1
- package/dist/implementation/world/register.js +2 -2
- package/dist/implementation/world/resource-changes.d.ts +18 -0
- package/dist/implementation/world/resource-changes.js +66 -0
- package/dist/implementation/world/resource-changes.js.map +1 -0
- package/dist/implementation/world/rows.d.ts +1 -1
- package/dist/implementation/world/rows.js +23 -13
- package/dist/implementation/world/rows.js.map +1 -1
- package/dist/implementation/world/run-changes.d.ts +38 -0
- package/dist/implementation/world/run-changes.js +138 -0
- package/dist/implementation/world/run-changes.js.map +1 -0
- package/dist/implementation/world/runner.js +19 -15
- 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/state.d.ts +13 -0
- package/dist/implementation/world/state.js +13 -1
- package/dist/implementation/world/state.js.map +1 -1
- package/dist/implementation/world/synchronize.js +72 -22
- package/dist/implementation/world/synchronize.js.map +1 -1
- package/dist/implementation/world/tables-read.d.ts +9 -0
- package/dist/implementation/world/tables-read.js +72 -0
- package/dist/implementation/world/tables-read.js.map +1 -0
- package/dist/implementation/world/tables.d.ts +10 -0
- package/dist/implementation/world/tables.js +401 -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 +11 -6
- package/dist/implementation/world/thread-systems.js.map +1 -1
- package/dist/implementation/world/up.js +7 -1
- package/dist/implementation/world/up.js.map +1 -1
- package/dist/implementation/world/where.d.ts +8 -0
- package/dist/implementation/world/where.js +40 -11
- package/dist/implementation/world/where.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/finalizer/body.d.ts +18 -3
- package/dist/interface/finalizer/create.d.ts +7 -5
- package/dist/interface/finalizer/create.js +8 -5
- package/dist/interface/finalizer/create.js.map +1 -1
- package/dist/interface/finalizer/declaration.d.ts +4 -2
- package/dist/interface/indexes/declaration-of.d.ts +17 -0
- package/dist/interface/indexes/declaration-of.js +20 -0
- package/dist/interface/indexes/declaration-of.js.map +1 -0
- package/dist/interface/indexes/index.d.ts +1 -0
- package/dist/interface/indexes/index.js +1 -0
- package/dist/interface/indexes/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 +31 -4
- package/dist/interface/registry/schedule.js +185 -24
- 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/access.d.ts +15 -0
- package/dist/interface/system/body.d.ts +11 -6
- package/dist/interface/system/check.d.ts +8 -1
- package/dist/interface/system/check.js +42 -12
- package/dist/interface/system/check.js.map +1 -1
- package/dist/interface/system/commands.d.ts +37 -0
- package/dist/interface/system/create-resolved.js +1 -1
- package/dist/interface/system/create-resolved.js.map +1 -1
- package/dist/interface/system/create.d.ts +31 -15
- package/dist/interface/system/create.js +8 -7
- package/dist/interface/system/create.js.map +1 -1
- package/dist/interface/system/declaration.d.ts +9 -2
- package/dist/interface/system/describe.d.ts +120 -0
- package/dist/interface/system/describe.js +116 -0
- package/dist/interface/system/describe.js.map +1 -0
- package/dist/interface/system/index.d.ts +1 -0
- package/dist/interface/system/index.js +1 -0
- package/dist/interface/system/index.js.map +1 -1
- package/dist/interface/system/leaf.d.ts +6 -0
- package/dist/interface/system/render.js +1 -0
- package/dist/interface/system/render.js.map +1 -1
- package/dist/interface/system/resolve.d.ts +5 -1
- package/dist/interface/system/resolve.js +19 -1
- package/dist/interface/system/resolve.js.map +1 -1
- package/dist/interface/system/shared-only.d.ts +7 -0
- package/dist/interface/system/shared-only.js +2 -0
- package/dist/interface/system/shared-only.js.map +1 -0
- package/dist/interface/system/tables.d.ts +28 -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 +27 -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 +25 -10
- package/dist/world.d.ts +27 -0
- package/dist/world.js +27 -0
- package/dist/world.js.map +1 -0
- package/package.json +11 -2
- 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 +18 -7
- package/src/guide/06-sync-points.out +1 -2
- package/src/guide/06-sync-points.ts +10 -6
- 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 +9 -154
- package/src/implementation/compiler/body-access.ts +102 -12
- 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/estree.ts +9 -6
- package/src/implementation/compiler/hygiene.test.ts +1 -1
- package/src/implementation/compiler/inferred.test.ts +1 -1
- package/src/implementation/compiler/inferred.ts +4 -2
- package/src/implementation/compiler/plugin.test.ts +2 -2
- package/src/implementation/compiler/signature.ts +1 -1
- package/src/implementation/compiler/transform.test.ts +43 -23
- package/src/implementation/compiler/transform.ts +6 -4
- package/src/implementation/world/activate.ts +11 -2
- package/src/implementation/world/batches.test.ts +3 -3
- package/src/implementation/world/buffer.ts +7 -0
- package/src/implementation/world/cascade.ts +46 -22
- package/src/implementation/world/change-blocks.test.ts +1 -1
- package/src/implementation/world/commands.ts +76 -2
- package/src/implementation/world/create.test.ts +8 -8
- package/src/implementation/world/create.ts +57 -2
- package/src/implementation/world/defaults.test.ts +1 -1
- package/src/implementation/world/despawn-where.test.ts +287 -0
- package/src/implementation/world/entities.ts +6 -0
- package/src/implementation/world/execute.ts +125 -78
- package/src/implementation/world/finalizers.test.ts +1 -1
- package/src/implementation/world/finalizers.ts +27 -4
- package/src/implementation/world/ids.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/indexes.test.ts +2 -1
- package/src/implementation/world/join.test.ts +23 -4
- package/src/implementation/world/join.ts +26 -4
- 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/names.test.ts +1 -2
- 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/perf.md +3 -1
- 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/resource-changes.test.ts +61 -0
- package/src/implementation/world/resource-changes.ts +67 -0
- package/src/implementation/world/rows.ts +24 -13
- package/src/implementation/world/rules.test.ts +37 -0
- package/src/implementation/world/run-changes.test.ts +88 -0
- package/src/implementation/world/run-changes.ts +160 -0
- package/src/implementation/world/run-results.test.ts +10 -10
- package/src/implementation/world/runner.ts +20 -16
- 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 +10 -10
- package/src/implementation/world/sharing.ts +1 -1
- package/src/implementation/world/staging.ts +16 -3
- package/src/implementation/world/state.ts +19 -0
- package/src/implementation/world/sync-points.test.ts +135 -0
- package/src/implementation/world/synchronize.ts +65 -18
- package/src/implementation/world/tables-read.ts +70 -0
- package/src/implementation/world/tables.test.ts +369 -0
- package/src/implementation/world/tables.ts +368 -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 +11 -6
- package/src/implementation/world/up.test.ts +13 -1
- package/src/implementation/world/up.ts +6 -1
- package/src/implementation/world/where.test.ts +4 -4
- package/src/implementation/world/where.ts +38 -10
- 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/finalizer/body.ts +13 -3
- package/src/interface/finalizer/create.test.ts +3 -1
- package/src/interface/finalizer/create.ts +12 -7
- package/src/interface/finalizer/declaration.ts +4 -2
- package/src/interface/index.type-test.ts +2 -2
- package/src/interface/indexes/create.test.ts +10 -0
- package/src/interface/indexes/declaration-of.ts +34 -0
- package/src/interface/indexes/index.ts +1 -0
- package/src/interface/indexes/index.type-test.ts +1 -1
- package/src/interface/registry/names.ts +8 -0
- package/src/interface/registry/schedule.ts +200 -27
- package/src/interface/resource/declarations.ts +11 -0
- package/src/interface/resource/index.ts +2 -0
- package/src/interface/system/access.ts +16 -0
- package/src/interface/system/body.ts +7 -5
- package/src/interface/system/check.ts +37 -10
- package/src/interface/system/commands.ts +33 -0
- package/src/interface/system/commands.type-test.ts +31 -0
- package/src/interface/system/create-resolved.ts +1 -1
- package/src/interface/system/create.test.ts +21 -13
- package/src/interface/system/create.ts +24 -15
- package/src/interface/system/declaration.ts +9 -2
- package/src/interface/system/describe.test.ts +79 -0
- package/src/interface/system/describe.ts +122 -0
- package/src/interface/system/index.ts +1 -0
- package/src/interface/system/leaf.ts +6 -0
- package/src/interface/system/lists.type-test.ts +1 -1
- package/src/interface/system/optional.type-test.ts +1 -1
- package/src/interface/system/render.ts +1 -0
- package/src/interface/system/resolve.ts +25 -2
- package/src/interface/system/shared-only.ts +5 -0
- 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 +27 -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 +14 -4
- package/src/interface/world/table/index.ts +20 -0
- package/src/interface/world/table/table.ts +12 -0
- package/src/interface/world/world.ts +23 -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 +19 -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/structural.ts +20 -0
- package/src/perftest/suite.ts +1 -1
- package/src/perftest/tables.ts +107 -0
- package/src/world.test.ts +2 -2
- package/src/world.ts +32 -0
|
@@ -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
|
}));
|
|
@@ -195,10 +195,10 @@ describe("a shared component", () => {
|
|
|
195
195
|
}));
|
|
196
196
|
expect(looked.sort()).toEqual(["1 sees red", "2 sees red"]);
|
|
197
197
|
const assign: (row: object) => void = row => { Reflect.set(row, team, "blue"); };
|
|
198
|
-
|
|
198
|
+
// Taken whole, the row is written: every member but shared ones and references.
|
|
199
|
+
const assigning = System.create({ kernel: false, components: { with: [team, health], whole: true } }, row => {
|
|
199
200
|
if (row[health] === 1) assign(row);
|
|
200
201
|
});
|
|
201
|
-
// The build takes the row as written whole, since it can't follow it: every member but shared ones and references.
|
|
202
202
|
expect((await runReported(world, assigning)).failures.map(failure => failure.message)).toEqual([
|
|
203
203
|
expect.stringContaining(`"sharedTest/team" is shared: its value is its archetype's, so give it another with commands.add rather than assigning it`),
|
|
204
204
|
]);
|
|
@@ -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;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
import type { Recording } from './run-changes.js';
|
|
2
3
|
import { createHolds, type Holds } from './stop-lists.js';
|
|
3
4
|
import type { Entity } from '../../interface/entity/entity.js';
|
|
4
5
|
import type { Leaf } from '../../interface/system/leaf.js';
|
|
@@ -126,6 +127,13 @@ export type State = {
|
|
|
126
127
|
reshapes: number;
|
|
127
128
|
/** Sync points applied, which reclaiming counts. */
|
|
128
129
|
syncs: number;
|
|
130
|
+
/**
|
|
131
|
+
* By id, each resource a changes finalizer watches: its value and change tick when its finalizers last ran, which a
|
|
132
|
+
* later write's report gives as its previous value. Empty while none watches one.
|
|
133
|
+
*/
|
|
134
|
+
readonly resourcesSeen: Map<number, { value: unknown; tick: number }>;
|
|
135
|
+
/** What the run in progress records of its changes, when it was asked for them; null otherwise. */
|
|
136
|
+
recording: Recording | null;
|
|
129
137
|
/** The records strings' columns name, in shared memory; null without it, where strings are held as objects. */
|
|
130
138
|
readonly strings: StringStore | null;
|
|
131
139
|
/** Whether each component is derived, so only rules add and remove it. */
|
|
@@ -317,6 +325,8 @@ export const createState = (shared?: boolean, wasm = true, simd = true, gpu = fa
|
|
|
317
325
|
membership: { log: [], base: 0 },
|
|
318
326
|
reshapes: 0,
|
|
319
327
|
syncs: 0,
|
|
328
|
+
resourcesSeen: new Map(),
|
|
329
|
+
recording: null,
|
|
320
330
|
strings: memory.shared ? createStrings(memory) : null,
|
|
321
331
|
derived: [],
|
|
322
332
|
transfers: [],
|
|
@@ -504,6 +514,15 @@ export const commandIdOf = (state: State, key: unknown): number => {
|
|
|
504
514
|
return id;
|
|
505
515
|
};
|
|
506
516
|
|
|
517
|
+
/** The id of a component a query names, derived ones included. Throws for a key that isn't a registered component. */
|
|
518
|
+
export const queryIdOf = (state: State, key: unknown): number => {
|
|
519
|
+
const known = typeof key === "symbol" ? state.ids.get(key) : undefined;
|
|
520
|
+
if (known !== undefined && !state.resources.has(known)) return known;
|
|
521
|
+
if (typeof key !== "symbol") throw new Error(`"${labelOf(key)}" isn't a component: a component's key is a symbol.`);
|
|
522
|
+
if (kindOf(key) === "resource") throw new Error(`"${labelOf(key)}" is a resource, not a component.`);
|
|
523
|
+
return idOf(state, key);
|
|
524
|
+
};
|
|
525
|
+
|
|
507
526
|
/**
|
|
508
527
|
* An archetype's key: its sorted component ids, each shared one with the number of its value, so archetypes of one
|
|
509
528
|
* component set but other values are others.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { Entity } from "../../interface/entity/entity.js";
|
|
4
|
+
import { Index } from "../../interface/indexes/index-type.js";
|
|
5
|
+
import { System } from "../../interface/system/system.js";
|
|
6
|
+
import { workers } from "../../interface/resources/workers.js";
|
|
7
|
+
import { createWorld } from "./create.js";
|
|
8
|
+
import { count as countOf, readEach, select } from "./reads.test-helper.js";
|
|
9
|
+
|
|
10
|
+
const position = Symbol("syncPointsTest/position");
|
|
11
|
+
const cell = Symbol("syncPointsTest/cell");
|
|
12
|
+
const found = Symbol("syncPointsTest/found");
|
|
13
|
+
const team = Symbol("syncPointsTest/team");
|
|
14
|
+
const member = Symbol("syncPointsTest/member");
|
|
15
|
+
const local = Symbol("syncPointsTest/local");
|
|
16
|
+
const total = Symbol("syncPointsTest/total");
|
|
17
|
+
const parent = Symbol("syncPointsTest/parent");
|
|
18
|
+
const seed = Symbol("syncPointsTest/seed");
|
|
19
|
+
const grown = Symbol("syncPointsTest/grown");
|
|
20
|
+
const children = Symbol("syncPointsTest/children");
|
|
21
|
+
|
|
22
|
+
declare module "@adobe/ecs" {
|
|
23
|
+
export interface Components {
|
|
24
|
+
[position]: { type: number };
|
|
25
|
+
[cell]: { type: number };
|
|
26
|
+
[team]: { type: true };
|
|
27
|
+
[member]: { type: Entity; reference: "owner" };
|
|
28
|
+
[local]: { type: number };
|
|
29
|
+
[total]: { type: number };
|
|
30
|
+
[parent]: { type: Entity; reference: "weak" };
|
|
31
|
+
[seed]: { type: number };
|
|
32
|
+
[grown]: { type: number };
|
|
33
|
+
}
|
|
34
|
+
export interface Resources {
|
|
35
|
+
[found]: { type: number };
|
|
36
|
+
[children]: { type: number; default: 0 };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const byCell = Index.create({ key: [cell] });
|
|
41
|
+
|
|
42
|
+
const spawn = System.create(({ commands }) => { for (let k = 0; k < 3; k++) commands.spawn([position])({ [position]: k }); });
|
|
43
|
+
const counted: number[] = [];
|
|
44
|
+
const count = System.create({ components: { with: [position] }, after: [spawn] }, row => { counted.push(row[position]); });
|
|
45
|
+
const changes: number[] = [];
|
|
46
|
+
const watchChanges = System.create({ components: { with: [position], changed: [position] }, after: [count] }, row => { changes.push(row[position]); });
|
|
47
|
+
const deriveCells = System.create({ components: { with: [position], not: [cell] }, after: [watchChanges] }, (row, { commands }) => { commands.add(row.entity, cell, row[position] % 2); });
|
|
48
|
+
const pick = System.create({ lookup: { cells: { index: byCell } }, after: [deriveCells] }, ({ lookup, resources }) => { resources[found] = lookup.cells(0).length; });
|
|
49
|
+
const readFound = System.create({ readonly: true }, ({ resources }) => resources[found]);
|
|
50
|
+
const frame = System.create([spawn, count, watchChanges, deriveCells, pick]);
|
|
51
|
+
|
|
52
|
+
const spawnTeams = System.create(({ commands }) => {
|
|
53
|
+
for (let k = 0; k < 3; k++) {
|
|
54
|
+
const owner = commands.spawn([team])();
|
|
55
|
+
for (let m = 0; m < 2; m++) commands.spawn([member])({ [member]: owner });
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
const disband = System.create({ components: { with: [team] }, after: [spawnTeams] }, (row, { commands }) => { commands.despawn(row.entity); });
|
|
59
|
+
const round = System.create([spawnTeams, disband]);
|
|
60
|
+
|
|
61
|
+
/** Spawns a root, `count` children of it and a child of each. */
|
|
62
|
+
const spawnTree = System.create({}, ({ commands }, input: { readonly count: number }) => {
|
|
63
|
+
const root = commands.spawn([local, total])({ [local]: 1, [total]: 0 });
|
|
64
|
+
for (let k = 0; k < input.count; k++) {
|
|
65
|
+
const child = commands.spawn([local, total, parent])({ [local]: 1, [total]: 0, [parent]: root });
|
|
66
|
+
commands.spawn([local, total, parent])({ [local]: 1, [total]: 0, [parent]: child });
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
const propagate = System.create({ components: { with: [local, total], changed: [local] }, up: { through: parent }, after: [spawnTree] }, (row, { up }) => {
|
|
70
|
+
row[total] = (up === null ? 0 : up[total]) + row[local];
|
|
71
|
+
});
|
|
72
|
+
const grow = System.create([spawnTree, propagate]);
|
|
73
|
+
|
|
74
|
+
const plant = System.create(({ commands }) => { for (let k = 0; k < 64; k++) commands.spawn([seed])({ [seed]: k }); });
|
|
75
|
+
const sprout = System.create({ kernel: true, components: { with: [seed] }, after: [plant] }, (row, { commands }) => {
|
|
76
|
+
commands.spawn([grown])({ [grown]: row[seed] * 2 });
|
|
77
|
+
});
|
|
78
|
+
const tally = System.create({ components: { with: [grown] }, after: [sprout] }, (row, { resources }) => { resources[children] += row[grown]; });
|
|
79
|
+
const garden = System.create([plant, sprout, tally]);
|
|
80
|
+
const readChildren = System.create({ readonly: true }, ({ resources }) => resources[children]);
|
|
81
|
+
|
|
82
|
+
describe("sync points within a run", () => {
|
|
83
|
+
it("apply commands before a later system that depends on what they change, which then sees them in the same run", async () => {
|
|
84
|
+
const world = createWorld({ [workers]: 0 });
|
|
85
|
+
await world.run(frame);
|
|
86
|
+
expect(counted).toEqual([0, 1, 2]);
|
|
87
|
+
expect(await world.run(readFound)).toBe(2);
|
|
88
|
+
await world.dispose();
|
|
89
|
+
});
|
|
90
|
+
it("keep each change seen once by change filters across runs", async () => {
|
|
91
|
+
const world = createWorld({ [workers]: 0 });
|
|
92
|
+
changes.length = 0;
|
|
93
|
+
await world.run(frame);
|
|
94
|
+
expect(changes).toEqual([0, 1, 2]);
|
|
95
|
+
changes.length = 0;
|
|
96
|
+
counted.length = 0;
|
|
97
|
+
await world.run(frame);
|
|
98
|
+
// The second run's spawns are new; the first run's three are seen again by count alone.
|
|
99
|
+
expect([counted.length, changes]).toEqual([6, [0, 1, 2]]);
|
|
100
|
+
expect(await world.run(readFound)).toBe(4);
|
|
101
|
+
await world.dispose();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("despawn what rows spawned at an earlier sync point of the run own, once a later tier despawns their owners", async () => {
|
|
105
|
+
expect(System.describe(round).tiers.map(tier => tier.sync === null)).toEqual([false, true]);
|
|
106
|
+
await using world = createWorld({ [workers]: 0 });
|
|
107
|
+
await world.run(round);
|
|
108
|
+
expect([await countOf(world, { required: [team] }), await countOf(world, { required: [member] })]).toEqual([0, 0]);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("bring an `up` tree up to date at each, built again or patched, for a tier after it in the same run", async () => {
|
|
112
|
+
expect(System.describe(grow).tiers.map(tier => tier.sync === null)).toEqual([false, true]);
|
|
113
|
+
await using world = createWorld({ [workers]: 0 });
|
|
114
|
+
let spawned = 0;
|
|
115
|
+
// Past the journal's worth a tree patches, then within it.
|
|
116
|
+
for (const count of [5_000, 3_000, 10]) {
|
|
117
|
+
await world.run(grow, { count });
|
|
118
|
+
spawned += count;
|
|
119
|
+
const totals = await readEach(world, await select(world, { required: [total] }), total);
|
|
120
|
+
const byDepth = [1, 2, 3].map(depth => totals.filter(value => value === depth).length);
|
|
121
|
+
expect([byDepth, totals.length]).toEqual([[totals.length - 2 * spawned, spawned, spawned], totals.length]);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("commit a kernel's staged spawns at a sync point within the run, for a later tier", async () => {
|
|
126
|
+
expect(System.describe(garden).tiers.map(tier => tier.sync === null)).toEqual([false, false, true]);
|
|
127
|
+
await using world = createWorld({ [workers]: 0 });
|
|
128
|
+
const sum = 63 * 64;
|
|
129
|
+
await world.run(garden);
|
|
130
|
+
expect(await world.run(readChildren)).toBe(sum);
|
|
131
|
+
// The second run's seeds are both runs' plantings, and its tally reads the first run's grown rows too.
|
|
132
|
+
await world.run(garden);
|
|
133
|
+
expect(await world.run(readChildren)).toBe(sum + sum + 2 * sum);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import type { Views } from '../../interface/registry/layout.js';
|
|
3
3
|
import { copyValue, ensureCapacity, swapRemove, writeF32, writeF32Lanes, writeF64, writeI32, writeU32, type Archetype, type Storage } from './archetype.js';
|
|
4
4
|
import { blockShift, chunkShift } from './kernel.js';
|
|
5
|
-
import { addCommand, clearBuffer, despawnCommand, endOf, referenceAt, removeCommand, spawnCommand, type CommandBuffer } from './buffer.js';
|
|
5
|
+
import { addCommand, clearBuffer, despawnCommand, despawnWhereCommand, despawnWhereHolds, endOf, referenceAt, removeCommand, spawnCommand, type CommandBuffer } from './buffer.js';
|
|
6
6
|
import { readCell, writeCell } from './cells.js';
|
|
7
7
|
import { commitBatches, commitStaged } from './batches.js';
|
|
8
|
-
import { applyIndexChanges, cascadeReferences, indexChangesOf, type IndexChanges, type Written } from './cascade.js';
|
|
8
|
+
import { applyIndexChanges, cascadeReferences, indexChangesOf, linkBorn, type IndexChanges, type Written } from './cascade.js';
|
|
9
9
|
import { refreshDirectories } from './directories.js';
|
|
10
10
|
import { createArrivals, logArrival } from './arrivals.js';
|
|
11
11
|
import { noteEdge, noteEmptied, reclaimEmptied } from './reclaim.js';
|
|
@@ -21,6 +21,8 @@ import type { Plan } from './plan.js';
|
|
|
21
21
|
import type { MoveLog } from './move-log.js';
|
|
22
22
|
import { archetypeOf, movesOf, sharedNumberOf, withComponent, withoutComponent, withShared, type State } from './state.js';
|
|
23
23
|
import { releaseValue } from './transfers.js';
|
|
24
|
+
import { resourcesWritten } from './resource-changes.js';
|
|
25
|
+
import { recordPlan } from './run-changes.js';
|
|
24
26
|
|
|
25
27
|
/** Buffers queued for the next sync point, in merge order. Commands in guarded buffers can't touch the resource entity. */
|
|
26
28
|
export type Queued = { readonly buffer: CommandBuffer; readonly guarded: boolean };
|
|
@@ -127,6 +129,56 @@ const releaseDropped = (state: State, buffer: CommandBuffer, command: number): v
|
|
|
127
129
|
}
|
|
128
130
|
};
|
|
129
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Whether `archetype` has each component of `ids` from `start` to `middle` and none from there to `end`, and holds the
|
|
134
|
+
* values `holds` tells it holds, when it isn't null.
|
|
135
|
+
*/
|
|
136
|
+
const matches = (archetype: Archetype, ids: Int32Array, start: number, middle: number, end: number, holds: Function | null): boolean => {
|
|
137
|
+
const { storageById } = archetype;
|
|
138
|
+
for (let k = start; k < middle; k++) if (storageById[ids[k]] === undefined) return false;
|
|
139
|
+
for (let k = middle; k < end; k++) if (storageById[ids[k]] !== undefined) return false;
|
|
140
|
+
return holds === null || Reflect.apply(holds, undefined, [archetype]) === true;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/** The archetype an entity planned into `archetype` ends up in once derived. */
|
|
144
|
+
const closedOf = (state: State, archetype: Archetype): Archetype => {
|
|
145
|
+
const cached = state.structure.closures[archetype.id] ?? -1;
|
|
146
|
+
return cached >= 0 ? state.archetypes[cached] : closureOf(state.structure, state.derived, archetype, (components, shared) => archetypeOf(state, components, shared), state.archetypes);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Despawns each entity a `despawnWhere` command selects given the plan so far: those planned, by where they're going,
|
|
151
|
+
* then the rest, by the archetype they're in. Never the resource entity.
|
|
152
|
+
*/
|
|
153
|
+
const despawnWhere = (state: State, buffer: CommandBuffer, command: number, within: boolean): void => {
|
|
154
|
+
const { plan, archetypes } = state;
|
|
155
|
+
const { ids } = buffer;
|
|
156
|
+
const start = buffer.starts[command];
|
|
157
|
+
const middle = start + buffer.entities[command];
|
|
158
|
+
const last = endOf(buffer, command);
|
|
159
|
+
const where = ids[last - 1] === despawnWhereHolds ? buffer.values[last - 1] : null;
|
|
160
|
+
const holds = typeof where === "function" ? where : null;
|
|
161
|
+
const end = holds === null ? last : last - 1;
|
|
162
|
+
for (let entry = 0; entry < plan.count; entry++) {
|
|
163
|
+
if (plan.despawned[entry] === 1 || plan.entity[entry] === state.resourceEntity) continue;
|
|
164
|
+
const to = archetypes[plan.to[entry]];
|
|
165
|
+
if (matches(within && plan.to[entry] !== plan.from[entry] ? closedOf(state, to) : to, ids, start, middle, end, holds)) plan.despawned[entry] = 1;
|
|
166
|
+
}
|
|
167
|
+
const { slots } = plan;
|
|
168
|
+
const count = archetypes.length;
|
|
169
|
+
for (let id = 0; id < count; id++) {
|
|
170
|
+
const archetype = archetypes[id];
|
|
171
|
+
if (archetype.resource || archetype.count === 0 || !matches(archetype, ids, start, middle, end, holds)) continue;
|
|
172
|
+
ensurePlanCapacity(state, plan.count + archetype.count);
|
|
173
|
+
const { entities } = archetype;
|
|
174
|
+
for (let row = 0; row < archetype.count; row++) {
|
|
175
|
+
const entity = entities[row];
|
|
176
|
+
const index = indexOf(entity);
|
|
177
|
+
if (slots[index] < 0) plan.despawned[addPlanned(state, entity, index, id, id)] = 1;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
130
182
|
/**
|
|
131
183
|
* Plan: the net change for each entity, in issue order. Commands against dead entities do nothing. Reference values
|
|
132
184
|
* written by commands go to `written`, to check once despawns are known.
|
|
@@ -146,6 +198,11 @@ const planOf = (state: State, queued: readonly Queued[], written: Written): void
|
|
|
146
198
|
ensurePlanCapacity(state, plan.count + buffer.count);
|
|
147
199
|
for (let command = 0; command < buffer.count; command++) {
|
|
148
200
|
const kind = kinds[command];
|
|
201
|
+
if (kind === despawnWhereCommand) {
|
|
202
|
+
despawnWhere(state, buffer, command, within);
|
|
203
|
+
ensurePlanCapacity(state, plan.count + buffer.count - command);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
149
206
|
const entity = entities[command];
|
|
150
207
|
if (guarded && entity === state.resourceEntity) continue;
|
|
151
208
|
const index = indexOf(entity);
|
|
@@ -631,7 +688,9 @@ const noneQueued: readonly Queued[] = [];
|
|
|
631
688
|
export const syncPending = (state: State, queued: readonly Queued[] = state.queued): boolean =>
|
|
632
689
|
anyCommands(queued) || reserved(state.entities) || state.carried !== null || state.structure.rederive
|
|
633
690
|
// Failures for its run, as a gone thread's stops it never finalized are.
|
|
634
|
-
|| state.unplaced.length > 0
|
|
691
|
+
|| state.unplaced.length > 0
|
|
692
|
+
// A watched resource written, which its changes finalizers see at a sync point.
|
|
693
|
+
|| (state.resourcesSeen.size > 0 && resourcesWritten(state));
|
|
635
694
|
|
|
636
695
|
/**
|
|
637
696
|
* The first half of a sync point: commits batch spawns, plans every command, cascades references and closes the plan
|
|
@@ -688,22 +747,9 @@ const preparePending = (state: State, queued: readonly Queued[], isolated: boole
|
|
|
688
747
|
if (deriving) for (let entry = 0; entry < plan.count; entry++) if (plan.despawned[entry] === 0) plan.to[entry] = close(state.archetypes[plan.to[entry]]).id;
|
|
689
748
|
let index: IndexChanges | null = null;
|
|
690
749
|
if (structure.referenceIds.length > 0) {
|
|
691
|
-
|
|
692
|
-
const archetype = state.archetypes[id];
|
|
693
|
-
for (const component of structure.referenceIds) {
|
|
694
|
-
const storage = archetype.storageById[component];
|
|
695
|
-
if (storage === undefined) continue;
|
|
696
|
-
for (let row = first; row < archetype.count; row++) written.push(archetype.entities[row], component, Number(readCell(storage, row)));
|
|
697
|
-
}
|
|
698
|
-
}
|
|
750
|
+
linkBorn(state, born, written, tick);
|
|
699
751
|
cascadeReferences(state, queued, written, entity => entryOf(state, entity));
|
|
700
|
-
|
|
701
|
-
const from = plan.from[entry];
|
|
702
|
-
if (from < 0) return null;
|
|
703
|
-
const first = born.get(from);
|
|
704
|
-
return first !== undefined && state.entities.row[indexOf(plan.entity[entry])] >= first ? null : state.archetypes[from];
|
|
705
|
-
};
|
|
706
|
-
index = indexChangesOf(state, queued, fromOf, [...born].map(([id, first]) => [state.archetypes[id], first] as const));
|
|
752
|
+
index = indexChangesOf(state, queued, entry => plan.from[entry] < 0 ? null : state.archetypes[plan.from[entry]]);
|
|
707
753
|
}
|
|
708
754
|
// References change only here: each component written to is stamped, and each holder journaled, as are spawns.
|
|
709
755
|
for (let k = 1; k < written.length; k += 3) state.referencesWritten[written[k]] = tick;
|
|
@@ -741,6 +787,7 @@ export const applySync = (state: State): readonly Thrown[] => {
|
|
|
741
787
|
let errors = nothingThrown;
|
|
742
788
|
try {
|
|
743
789
|
if (state.unplaced.length > 0 && !isolated) errors = state.unplaced.splice(0);
|
|
790
|
+
if (state.recording !== null && !isolated) recordPlan(state, state.recording, queued, born);
|
|
744
791
|
if (state.finalizers.length > 0 && !isolated) {
|
|
745
792
|
const thrown = runFinalizers(state, queued, born, tick);
|
|
746
793
|
errors = errors.length === 0 ? thrown : [...errors, ...thrown];
|
|
@@ -0,0 +1,70 @@
|
|
|
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 { Archetype, 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
|
+
import { holdsValuesOf } from './where.js';
|
|
10
|
+
|
|
11
|
+
// Reading tables, apart from spawning them: rows' contexts import it, and what spawning imports would make a cycle.
|
|
12
|
+
|
|
13
|
+
/** What an archetype's storage of a component gives a table: a copy of its rows `0` to `count`. */
|
|
14
|
+
const columnOf = (key: Key, storage: Storage, count: number): Table.Column => {
|
|
15
|
+
const { layout } = storage;
|
|
16
|
+
switch (layout.kind) {
|
|
17
|
+
case "linear": return { kind: "bytes", bytes: storage.views.u8.slice(0, count * layout.stride), stride: layout.stride };
|
|
18
|
+
case "constant": return { kind: "value", value: layout.value };
|
|
19
|
+
case "object":
|
|
20
|
+
if (storage.remote !== null) throw new Error(`Tables can't read "${labelOf(key)}": a joined thread fills its objects.`);
|
|
21
|
+
return { kind: "values", values: storage.values.slice(0, count) };
|
|
22
|
+
case "string": {
|
|
23
|
+
const values: unknown[] = new Array(count);
|
|
24
|
+
for (let row = 0; row < count; row++) values[row] = readCell(storage, row);
|
|
25
|
+
return { kind: "values", values };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A readonly system's `tables`, read by the function this returns, once per read: of each archetype matching `with`,
|
|
32
|
+
* `not` and `where` and holding any of `components`, its entities and a copy of the column of each of them it holds;
|
|
33
|
+
* and the value of each resource of `resources`. `where` holds its values as a system's does, and an archetype
|
|
34
|
+
* matches it as one matches a system's `where`, required as `with` requires them.
|
|
35
|
+
*/
|
|
36
|
+
export const tablesReader = (state: State, declaration: TablesDeclaration): (() => Tables) => {
|
|
37
|
+
const holds = declaration.where === undefined ? null : holdsValuesOf(state, declaration.where, true);
|
|
38
|
+
if (typeof holds === "symbol") throw new Error(`Tables' where names "${labelOf(holds)}", which isn't a shared component.`);
|
|
39
|
+
const where = declaration.where === undefined ? [] : Object.getOwnPropertySymbols(declaration.where);
|
|
40
|
+
return () => readTables(state, declaration, [...where, ...declaration.with ?? []], holds);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const readTables = (state: State, declaration: TablesDeclaration, requiring: readonly Key[], holds: ((archetype: Archetype) => boolean) | null): Tables => {
|
|
44
|
+
const asked = declaration.components.map(key => ({ key, id: idOf(state, key) }));
|
|
45
|
+
const required = requiring.map(key => idOf(state, key));
|
|
46
|
+
const excluded = (declaration.not ?? []).map(key => idOf(state, key));
|
|
47
|
+
const archetypes: Table[] = [];
|
|
48
|
+
for (const archetype of state.archetypes) {
|
|
49
|
+
const { count } = archetype;
|
|
50
|
+
if (count === 0 || archetype.reclaimed) continue;
|
|
51
|
+
const { storageById } = archetype;
|
|
52
|
+
if (required.some(id => storageById[id] === undefined) || excluded.some(id => storageById[id] !== undefined)) continue;
|
|
53
|
+
if (holds !== null && !holds(archetype)) continue;
|
|
54
|
+
const columns: { [key: symbol]: Table.Column } = {};
|
|
55
|
+
let held = false;
|
|
56
|
+
for (const { key, id } of asked) {
|
|
57
|
+
const storage = storageById[id];
|
|
58
|
+
if (storage === undefined) continue;
|
|
59
|
+
columns[key] = columnOf(key, storage, count);
|
|
60
|
+
held = true;
|
|
61
|
+
}
|
|
62
|
+
if (held) archetypes.push({ count, entities: archetype.entities.slice(0, count), columns });
|
|
63
|
+
}
|
|
64
|
+
const resources: { [key: symbol]: unknown } = {};
|
|
65
|
+
for (const key of declaration.resources ?? []) {
|
|
66
|
+
const cell = resourceCell(state, key);
|
|
67
|
+
resources[key] = readCell(cell.storage, cell.row);
|
|
68
|
+
}
|
|
69
|
+
return { archetypes, resources };
|
|
70
|
+
};
|