@adobe/ecs 0.2.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/implementation/compiler/augmentations.js +8 -1
- package/dist/implementation/compiler/augmentations.js.map +1 -1
- 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/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 +5 -2
- package/dist/implementation/compiler/inferred.js.map +1 -1
- package/dist/implementation/compiler/transform.js +5 -4
- package/dist/implementation/compiler/transform.js.map +1 -1
- package/dist/implementation/compiler/type-schemas.d.ts +1 -0
- package/dist/implementation/compiler/type-schemas.js +35 -2
- package/dist/implementation/compiler/type-schemas.js.map +1 -1
- package/dist/implementation/world/activate.d.ts +14 -3
- package/dist/implementation/world/activate.js +28 -10
- 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 +41 -5
- package/dist/implementation/world/commands.js.map +1 -1
- package/dist/implementation/world/create.js +57 -6
- package/dist/implementation/world/create.js.map +1 -1
- package/dist/implementation/world/execute.d.ts +4 -4
- package/dist/implementation/world/execute.js +228 -93
- package/dist/implementation/world/execute.js.map +1 -1
- package/dist/implementation/world/finalizers.d.ts +10 -0
- package/dist/implementation/world/finalizers.js +60 -10
- package/dist/implementation/world/finalizers.js.map +1 -1
- package/dist/implementation/world/join.js +12 -2
- package/dist/implementation/world/join.js.map +1 -1
- package/dist/implementation/world/program.js +5 -1
- package/dist/implementation/world/program.js.map +1 -1
- 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.js +21 -16
- 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 +16 -12
- package/dist/implementation/world/runner.js.map +1 -1
- package/dist/implementation/world/state.d.ts +15 -0
- package/dist/implementation/world/state.js +22 -1
- package/dist/implementation/world/state.js.map +1 -1
- package/dist/implementation/world/synchronize.js +81 -24
- package/dist/implementation/world/synchronize.js.map +1 -1
- package/dist/implementation/world/tables-read.d.ts +5 -3
- package/dist/implementation/world/tables-read.js +16 -4
- package/dist/implementation/world/tables-read.js.map +1 -1
- package/dist/implementation/world/tables.d.ts +4 -2
- package/dist/implementation/world/tables.js +137 -38
- package/dist/implementation/world/tables.js.map +1 -1
- 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 +10 -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/interface/finalizer/body.d.ts +40 -4
- package/dist/interface/finalizer/create.d.ts +8 -6
- package/dist/interface/finalizer/create.js +45 -10
- package/dist/interface/finalizer/create.js.map +1 -1
- package/dist/interface/finalizer/declaration.d.ts +26 -4
- 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/index.d.ts +1 -1
- package/dist/interface/registry/index.js +1 -1
- package/dist/interface/registry/index.js.map +1 -1
- package/dist/interface/registry/names.d.ts +4 -0
- package/dist/interface/registry/names.js +20 -0
- package/dist/interface/registry/names.js.map +1 -1
- package/dist/interface/registry/schedule.d.ts +29 -1
- package/dist/interface/registry/schedule.js +261 -25
- package/dist/interface/registry/schedule.js.map +1 -1
- package/dist/interface/system/access.d.ts +15 -0
- package/dist/interface/system/check.d.ts +7 -0
- package/dist/interface/system/check.js +21 -3
- package/dist/interface/system/check.js.map +1 -1
- package/dist/interface/system/commands.d.ts +31 -5
- package/dist/interface/system/create-resolved.js +2 -1
- package/dist/interface/system/create-resolved.js.map +1 -1
- package/dist/interface/system/create.d.ts +10 -6
- package/dist/interface/system/create.js +5 -4
- package/dist/interface/system/create.js.map +1 -1
- package/dist/interface/system/declaration.d.ts +11 -4
- 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 +7 -0
- package/dist/interface/system/render.js +2 -1
- package/dist/interface/system/render.js.map +1 -1
- package/dist/interface/system/resolve.d.ts +14 -1
- package/dist/interface/system/resolve.js +50 -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 +9 -1
- package/dist/interface/world/table/index.d.ts +3 -2
- package/dist/interface/world/world.d.ts +15 -0
- package/package.json +11 -2
- package/src/guide/01-resources.md +4 -3
- package/src/guide/03-systems.out +1 -1
- package/src/guide/05-scheduling.md +12 -6
- package/src/guide/05-scheduling.out +1 -0
- package/src/guide/05-scheduling.ts +17 -0
- package/src/guide/06-sync-points.md +20 -8
- package/src/guide/06-sync-points.out +3 -2
- package/src/guide/06-sync-points.ts +24 -5
- package/src/guide/10-finalizers.md +5 -0
- package/src/guide/18-identity.out +1 -1
- package/src/guide/guide.test.ts +6 -153
- package/src/implementation/compiler/augmentations.test.ts +28 -1
- package/src/implementation/compiler/augmentations.ts +5 -1
- package/src/implementation/compiler/body-access.ts +102 -12
- package/src/implementation/compiler/estree.ts +9 -6
- package/src/implementation/compiler/inferred.test.ts +1 -1
- package/src/implementation/compiler/inferred.ts +5 -2
- package/src/implementation/compiler/transform.test.ts +41 -21
- package/src/implementation/compiler/transform.ts +5 -3
- package/src/implementation/compiler/type-schemas.ts +28 -2
- package/src/implementation/world/activate.ts +32 -10
- 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/commands.ts +37 -5
- package/src/implementation/world/create.test.ts +8 -8
- package/src/implementation/world/create.ts +61 -6
- package/src/implementation/world/defaults.test.ts +24 -1
- package/src/implementation/world/despawn-where.test.ts +310 -0
- package/src/implementation/world/execute.ts +222 -86
- package/src/implementation/world/finalizers.ts +63 -11
- package/src/implementation/world/ids.test.ts +1 -1
- package/src/implementation/world/indexes.test.ts +2 -1
- package/src/implementation/world/join.test.ts +19 -0
- package/src/implementation/world/join.ts +16 -2
- package/src/implementation/world/lifetime.test-helper.ts +1 -1
- package/src/implementation/world/names.test.ts +1 -2
- package/src/implementation/world/passes.test.ts +61 -0
- package/src/implementation/world/perf.md +12 -1
- package/src/implementation/world/program.ts +4 -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 +21 -14
- 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 +17 -0
- package/src/implementation/world/runner.ts +17 -13
- package/src/implementation/world/schedule.test.ts +42 -1
- package/src/implementation/world/shared.test.ts +2 -2
- package/src/implementation/world/state.ts +27 -0
- package/src/implementation/world/sync-points.test.ts +147 -0
- package/src/implementation/world/synchronize.ts +73 -20
- package/src/implementation/world/tables-read.ts +16 -5
- package/src/implementation/world/tables.test.ts +139 -4
- package/src/implementation/world/tables.ts +118 -35
- package/src/implementation/world/thread-observer.ts +1 -1
- package/src/implementation/world/thread-systems.ts +10 -6
- package/src/implementation/world/up.test.ts +13 -1
- package/src/implementation/world/up.ts +6 -1
- package/src/implementation/world/watch-traits-late.test-helper.ts +14 -0
- package/src/implementation/world/watch-traits.test-helper.ts +10 -0
- package/src/implementation/world/watch-traits.test.ts +62 -0
- package/src/implementation/world/where.ts +38 -10
- package/src/interface/finalizer/body.ts +33 -4
- package/src/interface/finalizer/create.test.ts +6 -1
- package/src/interface/finalizer/create.ts +45 -12
- package/src/interface/finalizer/declaration.ts +31 -4
- 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/registry/index.ts +1 -1
- package/src/interface/registry/names.ts +19 -0
- package/src/interface/registry/schedule.ts +275 -29
- package/src/interface/rule/rule.type-test.ts +23 -1
- package/src/interface/system/access.ts +16 -0
- package/src/interface/system/check.ts +17 -2
- package/src/interface/system/commands.ts +28 -4
- package/src/interface/system/commands.type-test.ts +31 -0
- package/src/interface/system/create-resolved.ts +2 -1
- package/src/interface/system/create.test.ts +10 -2
- package/src/interface/system/create.ts +9 -5
- package/src/interface/system/declaration.ts +10 -4
- package/src/interface/system/describe.test.ts +98 -0
- package/src/interface/system/describe.ts +122 -0
- package/src/interface/system/index.ts +1 -0
- package/src/interface/system/leaf.ts +7 -0
- package/src/interface/system/render.ts +2 -1
- package/src/interface/system/resolve.test.ts +17 -1
- package/src/interface/system/resolve.ts +60 -2
- package/src/interface/system/shared-only.ts +5 -0
- package/src/interface/system/system.type-test.ts +6 -0
- package/src/interface/system/tables.ts +7 -1
- package/src/interface/world/run.type-test.ts +11 -1
- package/src/interface/world/table/index.ts +3 -2
- package/src/interface/world/world.ts +13 -0
- package/src/perftest/benchmarks.ts +20 -0
- package/src/perftest/chained.ts +108 -0
- package/src/perftest/structural.ts +20 -0
- package/src/perftest/tables.ts +107 -0
|
@@ -35,6 +35,8 @@ import { Observe } from "../observe/index.js";
|
|
|
35
35
|
import { entitiesWith } from "./observer.js";
|
|
36
36
|
import { invariantBenchmarks } from "./invariants.js";
|
|
37
37
|
import { journalBenchmarks } from "./journal.js";
|
|
38
|
+
import { tableBenchmarks } from "./tables.js";
|
|
39
|
+
import { chainedBenchmarks } from "./chained.js";
|
|
38
40
|
|
|
39
41
|
/** A world whose kernels run on this thread, which the benchmarks without a placement measure; `workers` ones use the pool. */
|
|
40
42
|
const inline = (): World => createWorld({ [workers]: 0 });
|
|
@@ -648,6 +650,22 @@ export const benchmarks: readonly Benchmark[] = [
|
|
|
648
650
|
return best;
|
|
649
651
|
},
|
|
650
652
|
},
|
|
653
|
+
...[10, 1_000, 100_000].flatMap(n => ([
|
|
654
|
+
["rows", "a command per row", structural.despawnDoomedRows, 24],
|
|
655
|
+
["where", "one `despawnWhere`", structural.despawnDoomedWhere, 16],
|
|
656
|
+
] as const).map(([how, by, system, bytes]): Benchmark => {
|
|
657
|
+
const name = `despawn tagged ${n >= 1_000 ? `${n / 1_000}K` : n} ${how}`;
|
|
658
|
+
return {
|
|
659
|
+
name,
|
|
660
|
+
operation: `A run despawning the ${n >= 1_000 ? `${n / 1_000}K` : n} entities of a tag beside 100K others, by ${by}: clearing one kind, such as expired effects.`,
|
|
661
|
+
per: "despawns",
|
|
662
|
+
n,
|
|
663
|
+
typical: [10, 100_000],
|
|
664
|
+
best: { bytes, basis: bytes === 16 ? "Each despawn frees its 8-byte location and entity; the archetype empties, so no row moves." : "Each row reads its entity; each despawn frees its location and entity." },
|
|
665
|
+
run: () => structuralBest(1, world => world.run(structural.spawnDoomed, { count: n }), world => world.run(system),
|
|
666
|
+
async world => await countOf(world, [structural.doomed]) === 0, name),
|
|
667
|
+
};
|
|
668
|
+
})),
|
|
651
669
|
...([
|
|
652
670
|
["add tag", "Adding a tag to 100K entities of one archetype: a state flag set on many at once.", 1, structural.removeFlag, structural.addFlag, [structural.flag], 16],
|
|
653
671
|
["add tag kernel", "The same adds from a kernel.", 1, structural.removeFlagKernel, structural.addFlagKernel, [structural.flag], 20],
|
|
@@ -705,4 +723,6 @@ export const benchmarks: readonly Benchmark[] = [
|
|
|
705
723
|
...sharedBenchmarks,
|
|
706
724
|
...invariantBenchmarks,
|
|
707
725
|
...journalBenchmarks,
|
|
726
|
+
...tableBenchmarks,
|
|
727
|
+
...chainedBenchmarks,
|
|
708
728
|
];
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
import { workers } from "../interface/resources/workers.js";
|
|
3
|
+
import { System } from "../interface/system/system.js";
|
|
4
|
+
import { createWorld } from "../implementation/index.js";
|
|
5
|
+
import type { Benchmark } from "./benchmark.js";
|
|
6
|
+
import { fastest } from "./suite.js";
|
|
7
|
+
|
|
8
|
+
// Eight systems over rows no entity has, each with a command its body never issues, run alone or ordered one after
|
|
9
|
+
// another: what orderings and the sync points they need cost a frame whose commands don't fire.
|
|
10
|
+
|
|
11
|
+
const event = Symbol("chained/event");
|
|
12
|
+
const note = Symbol("chained/note");
|
|
13
|
+
declare module "@adobe/ecs" {
|
|
14
|
+
export interface Components {
|
|
15
|
+
[event]: { type: true };
|
|
16
|
+
[note]: { type: true };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Never true, so no body issues its command. */
|
|
21
|
+
let firing = false;
|
|
22
|
+
/** Systems in each composite. */
|
|
23
|
+
const length = 8;
|
|
24
|
+
/** Runs in each timed sample. */
|
|
25
|
+
const runs = 100;
|
|
26
|
+
|
|
27
|
+
/** A system spawning, when firing, what each of the composite's systems matches. */
|
|
28
|
+
const commanding = (after: readonly System[]): System => System.create({ components: { with: [event] }, after }, (_row, { commands }) => {
|
|
29
|
+
if (firing) commands.spawn([event])();
|
|
30
|
+
});
|
|
31
|
+
/** A system spawning, when firing, what none of them matches. */
|
|
32
|
+
const noting = (after: readonly System[]): System => System.create({ components: { with: [event] }, after }, (_row, { commands }) => {
|
|
33
|
+
if (firing) commands.spawn([note])();
|
|
34
|
+
});
|
|
35
|
+
const quiet = (after: readonly System[]): System => System.create({ components: { with: [event] }, after }, () => {});
|
|
36
|
+
|
|
37
|
+
/** `length` systems made by `make`, each after the one before when `ordered`. */
|
|
38
|
+
const composite = (make: (after: readonly System[]) => System, ordered: boolean): System => {
|
|
39
|
+
const systems: System[] = [];
|
|
40
|
+
for (let k = 0; k < length; k++) {
|
|
41
|
+
const previous = systems[k - 1];
|
|
42
|
+
systems.push(make(ordered && previous !== undefined ? [previous] : []));
|
|
43
|
+
}
|
|
44
|
+
return System.create(systems);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const unordered = composite(commanding, false);
|
|
48
|
+
const chainedQuiet = composite(quiet, true);
|
|
49
|
+
const chainedNoting = composite(noting, true);
|
|
50
|
+
const chainedCommanding = composite(commanding, true);
|
|
51
|
+
|
|
52
|
+
const check = (condition: boolean, message: string): void => {
|
|
53
|
+
if (!condition) throw new Error(message);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const timed = (system: System, tiers: number, syncs: number) => async (): Promise<number> => {
|
|
57
|
+
const { tiers: scheduled } = System.describe(system);
|
|
58
|
+
check(scheduled.length === tiers && scheduled.filter(tier => tier.sync !== null).length === syncs, `chained: ${scheduled.length} tiers and ${scheduled.filter(tier => tier.sync !== null).length} sync points`);
|
|
59
|
+
firing = false;
|
|
60
|
+
const world = createWorld({ [workers]: 0 });
|
|
61
|
+
try {
|
|
62
|
+
return await fastest(async () => { for (let k = 0; k < runs; k++) await world.run(system); });
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
await world.dispose();
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const runBest = { bytes: 64, basis: "A run reads at least one cache line of its system's state." };
|
|
70
|
+
|
|
71
|
+
export const chainedBenchmarks: readonly Benchmark[] = [
|
|
72
|
+
{
|
|
73
|
+
name: "chained unordered",
|
|
74
|
+
operation: "Running eight systems no entity matches, each with a command it never issues, unordered: one tier, and a sync point at the run's end that applies nothing.",
|
|
75
|
+
per: "runs",
|
|
76
|
+
n: runs,
|
|
77
|
+
typical: [1, 1],
|
|
78
|
+
best: runBest,
|
|
79
|
+
run: timed(unordered, 1, 0),
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "chained quiet",
|
|
83
|
+
operation: "Running eight systems no entity matches, without commands, each after the one before: eight tiers and no sync point between them, as orderings that only settle conflicts make.",
|
|
84
|
+
per: "runs",
|
|
85
|
+
n: runs,
|
|
86
|
+
typical: [1, 1],
|
|
87
|
+
best: runBest,
|
|
88
|
+
run: timed(chainedQuiet, length, 0),
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: "chained noting",
|
|
92
|
+
operation: "Running eight systems no entity matches, each after the one before, each with a command it never issues of what none of them matches: eight tiers, no sync point between them.",
|
|
93
|
+
per: "runs",
|
|
94
|
+
n: runs,
|
|
95
|
+
typical: [1, 1],
|
|
96
|
+
best: runBest,
|
|
97
|
+
run: timed(chainedNoting, length, 0),
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: "chained commanding",
|
|
101
|
+
operation: "Running eight systems no entity matches, each after the one before, each with a command it never issues of what the next matches: eight tiers and seven sync points that apply nothing.",
|
|
102
|
+
per: "runs",
|
|
103
|
+
n: runs,
|
|
104
|
+
typical: [1, 1],
|
|
105
|
+
best: runBest,
|
|
106
|
+
run: timed(chainedCommanding, length, length - 1),
|
|
107
|
+
},
|
|
108
|
+
];
|
|
@@ -12,6 +12,7 @@ export const flag = Symbol("structural/flag");
|
|
|
12
12
|
export const amount = Symbol("structural/amount");
|
|
13
13
|
export const extra = Symbol("structural/extra");
|
|
14
14
|
export const spawned = Symbol("structural/spawned");
|
|
15
|
+
export const doomed = Symbol("structural/doomed");
|
|
15
16
|
const source1 = Symbol("structural/source1");
|
|
16
17
|
const source2 = Symbol("structural/source2");
|
|
17
18
|
const t0 = Symbol("structural/t0");
|
|
@@ -27,6 +28,7 @@ declare module "@adobe/ecs" {
|
|
|
27
28
|
[amount]: { type: F32 };
|
|
28
29
|
[extra]: { type: F32 };
|
|
29
30
|
[spawned]: { type: true };
|
|
31
|
+
[doomed]: { type: true };
|
|
30
32
|
[source1]: { type: true };
|
|
31
33
|
[source2]: { type: true };
|
|
32
34
|
[t0]: { type: true };
|
|
@@ -128,6 +130,24 @@ export const removeFlagKernel = System.create({ kernel: true, components: { with
|
|
|
128
130
|
commands.remove(row.entity, flag);
|
|
129
131
|
});
|
|
130
132
|
|
|
133
|
+
const doomedList = [index, value, doomed] as const;
|
|
134
|
+
const doomedQuery = { with: [doomed] } as const;
|
|
135
|
+
|
|
136
|
+
/** `count` spawns of entities tagged `doomed`, beside the seeded ones. */
|
|
137
|
+
export const spawnDoomed = System.create({}, ({ commands }, input: { readonly count: number }) => {
|
|
138
|
+
for (let i = 0; i < input.count; i++) commands.spawn(doomedList)({ [index]: i, [value]: i });
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
/** Despawns every `doomed` entity, a command per row. */
|
|
142
|
+
export const despawnDoomedRows = System.create({ kernel: false, components: { with: [doomed] } }, (row, { commands }) => {
|
|
143
|
+
commands.despawn(row.entity);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
/** Despawns every `doomed` entity, by one command. */
|
|
147
|
+
export const despawnDoomedWhere = System.create({}, ({ commands }) => {
|
|
148
|
+
commands.despawnWhere(doomedQuery);
|
|
149
|
+
});
|
|
150
|
+
|
|
131
151
|
export const addAmount = System.create({ kernel: false, components: { with: [index], not: [amount, spawned] } }, (row, { commands }) => {
|
|
132
152
|
commands.add(row.entity, amount, row[index]);
|
|
133
153
|
});
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
import type { Entity } from "../interface/entity/entity.js";
|
|
3
|
+
import type { Vec3 } from "../interface/math/index.js";
|
|
4
|
+
import { workers } from "../interface/resources/workers.js";
|
|
5
|
+
import { System } from "../interface/system/system.js";
|
|
6
|
+
import type { Tables } from "../interface/system/tables.js";
|
|
7
|
+
import type { Table } from "../interface/world/table/table.js";
|
|
8
|
+
import { createWorld } from "../implementation/index.js";
|
|
9
|
+
import type { Benchmark } from "./benchmark.js";
|
|
10
|
+
|
|
11
|
+
// Spawning 1M entities into a fresh world in one sync point, each owned by an earlier one or by none: from tables, as
|
|
12
|
+
// loading a snapshot does, and from commands; and the same tables without the owner reference.
|
|
13
|
+
|
|
14
|
+
const position = Symbol("tables/position");
|
|
15
|
+
const owner = Symbol("tables/owner");
|
|
16
|
+
declare module "@adobe/ecs" {
|
|
17
|
+
export interface Components {
|
|
18
|
+
[position]: { type: Vec3 };
|
|
19
|
+
[owner]: { type: Entity; reference: "owner" };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const count = 1_000_000;
|
|
24
|
+
|
|
25
|
+
/** A root and `count - 1` entities, each owned by the one at half its position. */
|
|
26
|
+
const build = System.create(({ commands }) => {
|
|
27
|
+
const made: Entity[] = [commands.spawn([position])({ [position]: [0, 0, 0] })];
|
|
28
|
+
const owned = commands.spawn([position, owner]);
|
|
29
|
+
for (let i = 1; i < count; i++) made.push(owned({ [position]: [i, i, i], [owner]: made[i >> 1] }));
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const tablesOf = (components: readonly (typeof position | typeof owner)[]) =>
|
|
33
|
+
System.create({ readonly: true, tables: { components } }, ({ tables }): Tables => tables);
|
|
34
|
+
|
|
35
|
+
const given: { tables: readonly Table[] } = { tables: [] };
|
|
36
|
+
const spawnGiven = System.create(({ commands }) => {
|
|
37
|
+
commands.spawnTables(given.tables);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/** The fastest of a few runs spawning `tables` into a fresh world. */
|
|
41
|
+
const spawnFastest = async (tables: readonly Table[]): Promise<number> => {
|
|
42
|
+
let best = Infinity;
|
|
43
|
+
for (let k = 0; k < 7; k++) {
|
|
44
|
+
const world = createWorld({ [workers]: 0 });
|
|
45
|
+
given.tables = tables;
|
|
46
|
+
const start = performance.now();
|
|
47
|
+
await world.run(spawnGiven);
|
|
48
|
+
const time = performance.now() - start;
|
|
49
|
+
// The first runs warm up.
|
|
50
|
+
if (k >= 2) best = Math.min(best, time);
|
|
51
|
+
await world.dispose();
|
|
52
|
+
}
|
|
53
|
+
given.tables = [];
|
|
54
|
+
return best;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const tables = async (components: readonly (typeof position | typeof owner)[]): Promise<number> => {
|
|
58
|
+
const world = createWorld({ [workers]: 0 });
|
|
59
|
+
await world.run(build);
|
|
60
|
+
const { archetypes } = await world.run(tablesOf(components));
|
|
61
|
+
await world.dispose();
|
|
62
|
+
return spawnFastest(archetypes);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** The fastest of a few runs of `build` in a fresh world. */
|
|
66
|
+
const commanded = async (): Promise<number> => {
|
|
67
|
+
let best = Infinity;
|
|
68
|
+
for (let k = 0; k < 7; k++) {
|
|
69
|
+
const world = createWorld({ [workers]: 0 });
|
|
70
|
+
const start = performance.now();
|
|
71
|
+
await world.run(build);
|
|
72
|
+
const time = performance.now() - start;
|
|
73
|
+
if (k >= 2) best = Math.min(best, time);
|
|
74
|
+
await world.dispose();
|
|
75
|
+
}
|
|
76
|
+
return best;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const tableBenchmarks: readonly Benchmark[] = [
|
|
80
|
+
{
|
|
81
|
+
name: "spawn tables 1M owner",
|
|
82
|
+
operation: "commands.spawnTables of 1M entities into a fresh world, each but one owned by another of them, and its sync point: loading a snapshot of a hierarchy.",
|
|
83
|
+
per: "spawns",
|
|
84
|
+
n: count,
|
|
85
|
+
typical: [1_000, 1_000_000],
|
|
86
|
+
best: { bytes: 36, basis: "Each entity copies its 12-byte position and 8-byte owner, and writes its 8-byte entity and location." },
|
|
87
|
+
run: () => tables([position, owner]),
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: "spawn tables 1M",
|
|
91
|
+
operation: "As `spawn tables 1M owner`, without the owner reference: its cost is the difference.",
|
|
92
|
+
per: "spawns",
|
|
93
|
+
n: count,
|
|
94
|
+
typical: [1_000, 1_000_000],
|
|
95
|
+
best: { bytes: 28, basis: "Each entity copies its 12-byte position, and writes its 8-byte entity and location." },
|
|
96
|
+
run: () => tables([position]),
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: "spawn owned 1M",
|
|
100
|
+
operation: "A main-thread system spawning 1M entities through one spawner, each but one owned by an earlier one, and its sync point.",
|
|
101
|
+
per: "spawns",
|
|
102
|
+
n: count,
|
|
103
|
+
typical: [1_000, 1_000_000],
|
|
104
|
+
best: { bytes: 36, basis: "Each entity writes its 12-byte position and 8-byte owner, its 8-byte entity and location." },
|
|
105
|
+
run: commanded,
|
|
106
|
+
},
|
|
107
|
+
];
|