@aethervtt/shard-core 0.0.1 → 0.2.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/ecs/host-write.d.ts +5 -0
- package/dist/ecs/host-write.js +24 -0
- package/dist/ecs/index.d.ts +1 -0
- package/dist/ecs/index.js +10 -0
- package/dist/ecs/owners.d.ts +113 -0
- package/dist/ecs/owners.js +297 -0
- package/dist/ecs/world.d.ts +31 -0
- package/dist/ecs/world.js +67 -0
- package/dist/math/index.d.ts +1 -0
- package/dist/math/index.js +2 -0
- package/dist/math/polygon.d.ts +17 -0
- package/dist/math/polygon.js +307 -0
- package/dist/scheduler/capture/capture.d.ts +192 -0
- package/dist/scheduler/capture/capture.js +410 -0
- package/dist/scheduler/capture/index.d.ts +3 -0
- package/dist/scheduler/capture/index.js +17 -0
- package/dist/scheduler/capture/recorder.d.ts +83 -0
- package/dist/scheduler/capture/recorder.js +224 -0
- package/dist/scheduler/capture/samples.d.ts +49 -0
- package/dist/scheduler/capture/samples.js +91 -0
- package/dist/scheduler/index.d.ts +3 -1
- package/dist/scheduler/index.js +29 -2
- package/dist/scheduler/profiler.d.ts +180 -5
- package/dist/scheduler/profiler.js +430 -17
- package/dist/scheduler/schedule.d.ts +5 -1
- package/dist/scheduler/schedule.js +33 -3
- package/dist/schema/component.d.ts +8 -0
- package/dist/schema/component.js +3 -2
- package/dist/schema/field.d.ts +12 -0
- package/dist/schema/field.js +1 -0
- package/dist/schema/resource.d.ts +9 -0
- package/dist/schema/resource.js +3 -1
- package/dist/test-env.d.ts +107 -10
- package/dist/test-env.js +72 -4
- package/package.json +6 -2
package/dist/test-env.d.ts
CHANGED
|
@@ -1,16 +1,114 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* How strict tests are about time, in one place.
|
|
2
|
+
* How strict tests are about time, in one place. Two tiers:
|
|
3
3
|
*
|
|
4
|
-
* - `pnpm bench` (`SHARD_BENCH`)
|
|
5
|
-
*
|
|
6
|
-
* - CI (`SHARD_CI`) checks correctness only.
|
|
7
|
-
*
|
|
4
|
+
* - `pnpm bench` (`SHARD_BENCH`) checks performance: the exact budgets from the specs and
|
|
5
|
+
* "allocates nothing", one file at a time on a machine doing nothing else.
|
|
6
|
+
* - `pnpm test`, locally and in CI (`SHARD_CI`), checks correctness only. Parallel files share the
|
|
7
|
+
* CPU and GPU, and CI renders on a software GPU, so wall-clock timings there say nothing about the
|
|
8
|
+
* engine: budgets are unlimited and allocation checks are off. Timeouts are 5× longer in CI.
|
|
9
|
+
*
|
|
10
|
+
* Write every timing assertion through `budget()`, `slack` or `allocationChecks`, and it lands in
|
|
11
|
+
* the right tier.
|
|
12
|
+
*
|
|
13
|
+
* Budgets are data (spec 0075): `bench/perf/budgets.json` holds a number per key and named machine
|
|
14
|
+
* (`bench/perf/machines.json`), and `budget(key)` reads it. Core can't read files, so the numbers
|
|
15
|
+
* arrive resolved, as JSON in the `SHARD_BUDGETS` environment variable:
|
|
16
|
+
*
|
|
17
|
+
* - `pnpm bench` (`scripts/bench.mjs`) detects the machine, resolves every key for it, and passes
|
|
18
|
+
* the result (`ResolvedBudgets`) to the test processes. On an unknown machine it passes the
|
|
19
|
+
* closest machine's numbers with `enforce: false`: budgets stay unlimited, and the report
|
|
20
|
+
* (`bench/perf/report.json`) compares against them.
|
|
21
|
+
* - `pnpm test` gets the keys with no machine from `testFiles()` (`scripts/test-shard.mjs`), so an
|
|
22
|
+
* unknown key fails there too (`perf/unknown-budget`) while every budget is unlimited.
|
|
23
|
+
*
|
|
24
|
+
* Under `pnpm bench`, `SHARD_PERF_REPORT` names a folder: each `budget(key)` call is noted on
|
|
25
|
+
* `globalThis.__shardBudget`, and `scripts/perf-report.setup.mjs` records the value the next
|
|
26
|
+
* comparison matcher (`toBeLessThan` and the like) checks against it.
|
|
8
27
|
*/
|
|
9
28
|
export type TimingMode = 'bench' | 'test' | 'ci';
|
|
10
29
|
export declare const timingMode: TimingMode;
|
|
11
|
-
/** The factor budgets are multiplied by (and rate floors divided by)
|
|
30
|
+
/** The factor budgets are multiplied by (and rate floors divided by): 1 under `pnpm bench`, else ∞. */
|
|
12
31
|
export declare const slack: number;
|
|
13
|
-
/**
|
|
32
|
+
/** One key of `budgets.json`, resolved for a machine. `value` is null without one. */
|
|
33
|
+
export interface ResolvedSpan {
|
|
34
|
+
kind: 'target' | 'unit' | 'rate' | 'guard';
|
|
35
|
+
value: number | null;
|
|
36
|
+
/** Guards: the limit is `value × (1 + margin)`. */
|
|
37
|
+
margin?: number;
|
|
38
|
+
/** Units and rates: what one item is ("prop", "point"). */
|
|
39
|
+
per?: string;
|
|
40
|
+
/** What the number measures when it isn't ms: 'frames', 'ratio'. */
|
|
41
|
+
in?: string;
|
|
42
|
+
}
|
|
43
|
+
export type Track = 'gpu' | 'cpu';
|
|
44
|
+
export interface ResolvedScenario {
|
|
45
|
+
/** The frame budget per track, in ms. */
|
|
46
|
+
frame: Record<Track, number | null>;
|
|
47
|
+
/** Each slice in ms (its share of the frame, or the machine's override). */
|
|
48
|
+
slices: Record<Track, Record<string, number | null>>;
|
|
49
|
+
}
|
|
50
|
+
/** What `SHARD_BUDGETS` carries. */
|
|
51
|
+
export interface ResolvedBudgets {
|
|
52
|
+
/** The machine the numbers are for; null outside `pnpm bench`. */
|
|
53
|
+
machine: string | null;
|
|
54
|
+
/** False on an unknown machine and outside `pnpm bench`: every budget is unlimited. */
|
|
55
|
+
enforce: boolean;
|
|
56
|
+
spans: Record<string, ResolvedSpan>;
|
|
57
|
+
scenarios: Record<string, ResolvedScenario>;
|
|
58
|
+
/**
|
|
59
|
+
* How the machine checks GPU slices (machines.json): per-pass timestamps, or ablation where
|
|
60
|
+
* passes overlap (tile-based GPUs). Absent outside `pnpm bench`.
|
|
61
|
+
*/
|
|
62
|
+
passTiming?: 'ablation' | 'timestamps' | null;
|
|
63
|
+
}
|
|
64
|
+
export interface BudgetOptions {
|
|
65
|
+
/** Unit costs: how many items the measurement covered (the budget is the unit cost × this). */
|
|
66
|
+
count?: number;
|
|
67
|
+
/** Scenarios: a slice's budget instead of the whole frame's. */
|
|
68
|
+
slice?: string;
|
|
69
|
+
/** Scenarios: 'gpu' or 'cpu'. Default: 'gpu' for a `gpu:` slice, else 'cpu'. */
|
|
70
|
+
track?: Track;
|
|
71
|
+
}
|
|
72
|
+
/** A key's number on the resolved machine, and the limit a test checks: ∞ (0 for rates) when off. */
|
|
73
|
+
export interface ResolvedLimit {
|
|
74
|
+
/** What the test compares against. */
|
|
75
|
+
limit: number;
|
|
76
|
+
/** The machine's number for the comparison (null without one), enforced or not. */
|
|
77
|
+
reference: number | null;
|
|
78
|
+
kind: ResolvedSpan['kind'] | 'slice';
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The limit for `key` from resolved budgets: a span's number (a unit's times `count`, a guard's
|
|
82
|
+
* plus its margin), or a scenario's frame or slice in ms. Unlimited unless `enforce`: ∞, or 0 for a
|
|
83
|
+
* rate (a floor). Throws `perf/unknown-budget` for a key, scenario or slice budgets.json lacks.
|
|
84
|
+
*/
|
|
85
|
+
export declare function resolveLimit(budgets: ResolvedBudgets | undefined, key: string, opts?: BudgetOptions, enforce?: boolean): ResolvedLimit;
|
|
86
|
+
/** The last `budget(key)` call, for `scripts/perf-report.setup.mjs` (under `pnpm bench` only). */
|
|
87
|
+
export interface BudgetCall extends ResolvedLimit {
|
|
88
|
+
key: string;
|
|
89
|
+
opts: BudgetOptions;
|
|
90
|
+
used: boolean;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* A scenario's slice keys per track, `headroom` aside (0075): what a scenario test measures. Throws
|
|
94
|
+
* `perf/unknown-budget` for a scenario budgets.json lacks, in `pnpm test` too.
|
|
95
|
+
*/
|
|
96
|
+
export declare function scenarioSlices(name: string): Record<Track, string[]>;
|
|
97
|
+
/**
|
|
98
|
+
* How GPU slices are measured on this machine (0075): `'ablation'` under `pnpm bench` on a machine
|
|
99
|
+
* whose passes overlap (machines.json's `passTiming`), else `'timestamps'`.
|
|
100
|
+
*/
|
|
101
|
+
export declare function passTiming(): 'ablation' | 'timestamps';
|
|
102
|
+
/**
|
|
103
|
+
* A time budget from `bench/perf/budgets.json`: the detected machine's number under `pnpm bench`,
|
|
104
|
+
* unlimited in `pnpm test`, CI, and on an unknown machine (∞; 0 for a rate, which is a floor).
|
|
105
|
+
* `{ count }` scales a unit cost; `{ slice, track }` reads a scenario's slice. An unknown key throws
|
|
106
|
+
* `perf/unknown-budget`.
|
|
107
|
+
*
|
|
108
|
+
* `budget(ms)`, a literal, still works (exact under `pnpm bench`): `scripts/budget-literals.mjs`
|
|
109
|
+
* lists those calls, and the goal is none.
|
|
110
|
+
*/
|
|
111
|
+
export declare function budget(key: string, opts?: BudgetOptions): number;
|
|
14
112
|
export declare function budget(ms: number): number;
|
|
15
113
|
/** A test or hook timeout in ms: 5× in CI, unchanged everywhere else. */
|
|
16
114
|
export declare function timeout(ms: number): number;
|
|
@@ -24,8 +122,7 @@ export declare function gcWindow(): {
|
|
|
24
122
|
end(): Promise<number>;
|
|
25
123
|
};
|
|
26
124
|
/**
|
|
27
|
-
* Whether "allocates nothing" checks run
|
|
28
|
-
*
|
|
29
|
-
* on the runner's CPU and timing.
|
|
125
|
+
* Whether "allocates nothing" checks run: under `pnpm bench` only. When V8 optimizes (and so whether
|
|
126
|
+
* a call boxes its result) depends on the CPU and on what else is running.
|
|
30
127
|
*/
|
|
31
128
|
export declare const allocationChecks: boolean;
|
package/dist/test-env.js
CHANGED
|
@@ -1,8 +1,73 @@
|
|
|
1
|
+
import { ShardError } from "./error.js";
|
|
1
2
|
const env = globalThis.process?.env ?? {};
|
|
2
3
|
const timingMode = env.SHARD_BENCH ? "bench" : env.SHARD_CI ? "ci" : "test";
|
|
3
|
-
const slack = timingMode === "bench" ? 1 :
|
|
4
|
-
function
|
|
5
|
-
return
|
|
4
|
+
const slack = timingMode === "bench" ? 1 : Number.POSITIVE_INFINITY;
|
|
5
|
+
function unknownBudget(key, detail = "") {
|
|
6
|
+
return new ShardError("perf/unknown-budget", `No budget "${key}"${detail} in budgets.json`, {
|
|
7
|
+
path: key,
|
|
8
|
+
hint: "Add it to bench/perf/budgets.json with a number for every machine and a note."
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
function resolveLimit(budgets2, key, opts = {}, enforce = true) {
|
|
12
|
+
if (!budgets2) {
|
|
13
|
+
if (enforce) {
|
|
14
|
+
throw new ShardError("perf/no-budgets", `No budgets to resolve "${key}" against`, {
|
|
15
|
+
hint: "pnpm bench passes them in SHARD_BUDGETS; run tests through it or through testFiles()."
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return { limit: Number.POSITIVE_INFINITY, reference: null, kind: "target" };
|
|
19
|
+
}
|
|
20
|
+
const on = enforce && budgets2.enforce;
|
|
21
|
+
const span = budgets2.spans[key];
|
|
22
|
+
if (span) {
|
|
23
|
+
let reference2 = span.value;
|
|
24
|
+
if (reference2 !== null && span.kind === "unit") reference2 *= opts.count ?? 1;
|
|
25
|
+
if (reference2 !== null && span.kind === "guard") reference2 *= 1 + (span.margin ?? 0);
|
|
26
|
+
const off = span.kind === "rate" ? 0 : Number.POSITIVE_INFINITY;
|
|
27
|
+
return { limit: on && reference2 !== null ? reference2 : off, reference: reference2, kind: span.kind };
|
|
28
|
+
}
|
|
29
|
+
const scenario = budgets2.scenarios[key];
|
|
30
|
+
if (!scenario) throw unknownBudget(key);
|
|
31
|
+
const track = opts.track ?? (opts.slice?.startsWith("gpu:") ? "gpu" : "cpu");
|
|
32
|
+
const slices = scenario.slices[track];
|
|
33
|
+
if (opts.slice !== void 0 && !(opts.slice in slices)) {
|
|
34
|
+
throw unknownBudget(key, ` slice "${opts.slice}" (${track})`);
|
|
35
|
+
}
|
|
36
|
+
const reference = opts.slice !== void 0 ? slices[opts.slice] : scenario.frame[track];
|
|
37
|
+
return {
|
|
38
|
+
limit: on && reference !== null ? reference : Number.POSITIVE_INFINITY,
|
|
39
|
+
reference,
|
|
40
|
+
kind: "slice"
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
let resolved;
|
|
44
|
+
let parsed = false;
|
|
45
|
+
function budgets() {
|
|
46
|
+
if (!parsed) {
|
|
47
|
+
parsed = true;
|
|
48
|
+
if (env.SHARD_BUDGETS) resolved = JSON.parse(env.SHARD_BUDGETS);
|
|
49
|
+
}
|
|
50
|
+
return resolved;
|
|
51
|
+
}
|
|
52
|
+
function scenarioSlices(name) {
|
|
53
|
+
const scenario = budgets()?.scenarios[name];
|
|
54
|
+
if (!scenario) throw unknownBudget(name);
|
|
55
|
+
const keys = (track) => Object.keys(scenario.slices[track]).filter((k) => k !== "headroom");
|
|
56
|
+
return { gpu: keys("gpu"), cpu: keys("cpu") };
|
|
57
|
+
}
|
|
58
|
+
function passTiming() {
|
|
59
|
+
return timingMode === "bench" && budgets()?.passTiming === "ablation" ? "ablation" : "timestamps";
|
|
60
|
+
}
|
|
61
|
+
function budget(key, opts = {}) {
|
|
62
|
+
if (typeof key === "number") return key * slack;
|
|
63
|
+
const bench = timingMode === "bench";
|
|
64
|
+
const data = budgets();
|
|
65
|
+
const result = resolveLimit(data, key, opts, bench);
|
|
66
|
+
if (bench && env.SHARD_PERF_REPORT) {
|
|
67
|
+
const call = { key, opts, used: false, ...result };
|
|
68
|
+
globalThis.__shardBudget = call;
|
|
69
|
+
}
|
|
70
|
+
return result.limit;
|
|
6
71
|
}
|
|
7
72
|
function timeout(ms) {
|
|
8
73
|
return timingMode === "ci" ? ms * 5 : ms;
|
|
@@ -26,11 +91,14 @@ function gcWindow() {
|
|
|
26
91
|
}
|
|
27
92
|
};
|
|
28
93
|
}
|
|
29
|
-
const allocationChecks = timingMode
|
|
94
|
+
const allocationChecks = timingMode === "bench";
|
|
30
95
|
export {
|
|
31
96
|
allocationChecks,
|
|
32
97
|
budget,
|
|
33
98
|
gcWindow,
|
|
99
|
+
passTiming,
|
|
100
|
+
resolveLimit,
|
|
101
|
+
scenarioSlices,
|
|
34
102
|
slack,
|
|
35
103
|
timeout,
|
|
36
104
|
timingMode
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aethervtt/shard-core",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
"types": "./dist/index.d.ts",
|
|
10
10
|
"default": "./dist/index.js"
|
|
11
11
|
},
|
|
12
|
+
"./capture": {
|
|
13
|
+
"types": "./dist/scheduler/capture/index.d.ts",
|
|
14
|
+
"default": "./dist/scheduler/capture/index.js"
|
|
15
|
+
},
|
|
12
16
|
"./math": {
|
|
13
17
|
"types": "./dist/math/index.d.ts",
|
|
14
18
|
"default": "./dist/math/index.js"
|
|
@@ -33,6 +37,6 @@
|
|
|
33
37
|
"provenance": true
|
|
34
38
|
},
|
|
35
39
|
"shard": {
|
|
36
|
-
"buildSha": "
|
|
40
|
+
"buildSha": "32c22a3944ed23f5d1baa90bf7cc9fae724f5fd8"
|
|
37
41
|
}
|
|
38
42
|
}
|