@aethervtt/shard-protocol 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/ablate.d.ts +2 -0
- package/dist/ablate.js +62 -0
- package/dist/budgets.d.ts +2 -0
- package/dist/budgets.js +20 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/preview.js +1 -0
- package/dist/server.d.ts +6 -0
- package/dist/server.js +174 -11
- package/package.json +12 -12
package/dist/ablate.d.ts
ADDED
package/dist/ablate.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { defineSchema, Last, PostUpdate, t } from "@aethervtt/shard-core";
|
|
2
|
+
import { ablatePasses, Gpu, Graph, Views } from "@aethervtt/shard-render";
|
|
3
|
+
import { AppControlResource } from "@aethervtt/shard-runtime";
|
|
4
|
+
function renderOnly(app) {
|
|
5
|
+
app.runSchedule(PostUpdate);
|
|
6
|
+
app.runSchedule(Last);
|
|
7
|
+
}
|
|
8
|
+
function nextFrame(app) {
|
|
9
|
+
return new Promise((resolve) => {
|
|
10
|
+
const off = app.onFrame(() => {
|
|
11
|
+
off();
|
|
12
|
+
resolve();
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
const ABLATE_METHODS = [
|
|
17
|
+
{
|
|
18
|
+
name: "perf.ablate",
|
|
19
|
+
description: "Each render-graph pass's GPU cost, measured honestly on any GPU: rounds alternate the pass disabled and enabled, and its cost is the median drop in gpu:frame (first pass start to last pass end). Use it where per-pass timestamps overlap (tile-based GPUs such as Apple's, where pass times add up to more than the frame). A disabled pass still begins and ends, so what reads its textures stays valid; the image is wrong while it runs. Needs timestamp-query. It runs (passes + 2) \xD7 rounds \xD7 (frames + 4) frames: three passes at the defaults take about 50 s at 60 fps.",
|
|
20
|
+
params: defineSchema("protocol/AblateParams", {
|
|
21
|
+
passes: t.list(t.string, {
|
|
22
|
+
description: "Render-graph nodes to measure (render.describe lists them). Default: every node the last frame ran."
|
|
23
|
+
}),
|
|
24
|
+
frames: t.u32({ default: 120, min: 1, max: 1e4, description: "Frames per measurement." }),
|
|
25
|
+
rounds: t.u32({ default: 5, min: 1, max: 100, description: "Interleaved rounds." }),
|
|
26
|
+
together: t.bool({ description: "Also measure every pass disabled at once." })
|
|
27
|
+
}),
|
|
28
|
+
handler: async ({ app, world, options }, p) => {
|
|
29
|
+
const manual = (options.frames ?? "manual") === "manual" || world.resource(AppControlResource).paused;
|
|
30
|
+
const frame = () => manual ? renderOnly(app) : nextFrame(app);
|
|
31
|
+
const graph = world.resource(Graph);
|
|
32
|
+
const gpu = world.resource(Gpu);
|
|
33
|
+
for (let i = 0; i < 20; i++) {
|
|
34
|
+
await frame();
|
|
35
|
+
await gpu.pipelines.whenIdle();
|
|
36
|
+
if (i > 0 && gpu.pipelines.pending === 0 && gpu.pipelines.skipped === 0) break;
|
|
37
|
+
}
|
|
38
|
+
let passes = p.passes ?? [];
|
|
39
|
+
if (passes.length === 0) {
|
|
40
|
+
const ran = /* @__PURE__ */ new Set();
|
|
41
|
+
const perView = graph.describe().perView;
|
|
42
|
+
for (const view of world.resource(Views).list) {
|
|
43
|
+
for (const name of perView[view.name]?.order ?? []) ran.add(name);
|
|
44
|
+
}
|
|
45
|
+
passes = [...ran];
|
|
46
|
+
}
|
|
47
|
+
return ablatePasses(
|
|
48
|
+
world,
|
|
49
|
+
{
|
|
50
|
+
passes,
|
|
51
|
+
frames: p.frames,
|
|
52
|
+
rounds: p.rounds,
|
|
53
|
+
together: p.together
|
|
54
|
+
},
|
|
55
|
+
frame
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
];
|
|
60
|
+
export {
|
|
61
|
+
ABLATE_METHODS
|
|
62
|
+
};
|
package/dist/budgets.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defineSchema, t } from "@aethervtt/shard-core";
|
|
2
|
+
import { describeBudgets } from "@aethervtt/shard-runtime";
|
|
3
|
+
const BUDGET_METHODS = [
|
|
4
|
+
{
|
|
5
|
+
name: "perf.budgets",
|
|
6
|
+
description: "Performance budgets (0075, bench/perf/budgets.json) for this app: the named machine it runs on (detected from the CPU model where the host exposes it, and the GPU adapter; null with a perf/unknown-machine warning when it can't tell, and then numbers are the closest machine's), every budget resolved for it with the profiler's latest p95 against it, over budget first; and the scenario's slices (the app's App.perfScenario, or `scenario`): each slice's measured share of the frame (from the profiler's aggregates, gpu:frame for GPU slices, frame for CPU) next to its budget share, over first. Hosts supply the files: the CLI loads them from the repo or project, shard dev serves them to its page.",
|
|
7
|
+
params: defineSchema("protocol/BudgetsParams", {
|
|
8
|
+
scenario: t.string({
|
|
9
|
+
description: "A scenario of budgets.json to measure (scatter-walk, crowd, planet-descent, tabletop-max). Default: the app's own (App.perfScenario)."
|
|
10
|
+
})
|
|
11
|
+
}),
|
|
12
|
+
handler: ({ world }, p) => {
|
|
13
|
+
const scenario = p.scenario;
|
|
14
|
+
return describeBudgets(world, { scenario: scenario ? scenario : void 0 });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
];
|
|
18
|
+
export {
|
|
19
|
+
BUDGET_METHODS
|
|
20
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export { ABLATE_METHODS } from './ablate.js';
|
|
2
|
+
export { BUDGET_METHODS } from './budgets.js';
|
|
1
3
|
export { decodePng, encodePng, toBase64 } from './png.js';
|
|
2
4
|
export { type PreviewImage, previewAsset, previewKtx2 } from './preview.js';
|
|
3
5
|
export { createProtocolServer, type HandlerContext, type JsonRpcNotification, type JsonRpcRequest, type JsonRpcResponse, METHODS, type MethodDef, type ProtocolServer, type ProtocolServerOptions, type Topic, } from './server.js';
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ABLATE_METHODS } from "./ablate.js";
|
|
2
|
+
import { BUDGET_METHODS } from "./budgets.js";
|
|
1
3
|
import { decodePng, encodePng, toBase64 } from "./png.js";
|
|
2
4
|
import { previewAsset, previewKtx2 } from "./preview.js";
|
|
3
5
|
import {
|
|
@@ -6,6 +8,8 @@ import {
|
|
|
6
8
|
} from "./server.js";
|
|
7
9
|
import { connectToHub, DEFAULT_HUB_PORT } from "./transport.js";
|
|
8
10
|
export {
|
|
11
|
+
ABLATE_METHODS,
|
|
12
|
+
BUDGET_METHODS,
|
|
9
13
|
DEFAULT_HUB_PORT,
|
|
10
14
|
METHODS,
|
|
11
15
|
connectToHub,
|
package/dist/preview.js
CHANGED
package/dist/server.d.ts
CHANGED
|
@@ -43,6 +43,12 @@ export interface ProtocolServerOptions {
|
|
|
43
43
|
platform?: Platform;
|
|
44
44
|
/** Extra methods from the host (e.g. `project.reload`), added to the built-in ones. */
|
|
45
45
|
methods?: readonly MethodDef[];
|
|
46
|
+
/**
|
|
47
|
+
* The only methods this server answers (0061): everything else, `subscribe` included, fails with
|
|
48
|
+
* `protocol/method-not-allowed`. A host that forwards requests from hosted mods passes the few
|
|
49
|
+
* it allows; omit it for agents and tools, which get every method.
|
|
50
|
+
*/
|
|
51
|
+
allow?: readonly string[];
|
|
46
52
|
}
|
|
47
53
|
export declare const METHODS: MethodDef[];
|
|
48
54
|
export type Topic = 'log' | 'error' | 'frame' | 'project';
|
package/dist/server.js
CHANGED
|
@@ -9,12 +9,14 @@ import {
|
|
|
9
9
|
isPlainObject,
|
|
10
10
|
Last,
|
|
11
11
|
PostUpdate,
|
|
12
|
+
ProfilerResource,
|
|
12
13
|
ShardError,
|
|
13
14
|
t
|
|
14
15
|
} from "@aethervtt/shard-core";
|
|
15
16
|
import {
|
|
16
17
|
describeInput,
|
|
17
18
|
injectInput,
|
|
19
|
+
simulateGestures,
|
|
18
20
|
startRecording,
|
|
19
21
|
startReplay,
|
|
20
22
|
stopRecording
|
|
@@ -41,6 +43,8 @@ import {
|
|
|
41
43
|
} from "@aethervtt/shard-render";
|
|
42
44
|
import {
|
|
43
45
|
AppControlResource,
|
|
46
|
+
capturePerf,
|
|
47
|
+
describePerf,
|
|
44
48
|
LogResource,
|
|
45
49
|
Time
|
|
46
50
|
} from "@aethervtt/shard-runtime";
|
|
@@ -73,6 +77,8 @@ import {
|
|
|
73
77
|
Transform,
|
|
74
78
|
worldPosition64
|
|
75
79
|
} from "@aethervtt/shard-transform";
|
|
80
|
+
import { ABLATE_METHODS } from "./ablate.js";
|
|
81
|
+
import { BUDGET_METHODS } from "./budgets.js";
|
|
76
82
|
import { encodePng, toBase64 } from "./png.js";
|
|
77
83
|
const CAPTURE_DEBUG_VIEWS = ["clusters", "cascades", "lod", "culling", "none"];
|
|
78
84
|
import { previewAsset } from "./preview.js";
|
|
@@ -87,7 +93,7 @@ function resolveEntity(world, ref) {
|
|
|
87
93
|
hint: 'Pass an entity id from world.query, or a scene path like "ship/camera".'
|
|
88
94
|
});
|
|
89
95
|
}
|
|
90
|
-
function
|
|
96
|
+
function knownComponent(name) {
|
|
91
97
|
const def = findComponent(name);
|
|
92
98
|
if (!def) {
|
|
93
99
|
throw new ShardError("protocol/unknown-component", `Unknown component "${name}"`, {
|
|
@@ -96,6 +102,28 @@ function requireComponent(name) {
|
|
|
96
102
|
}
|
|
97
103
|
return def;
|
|
98
104
|
}
|
|
105
|
+
function requireOwner(world, name) {
|
|
106
|
+
const owner = world.owners.find(name);
|
|
107
|
+
if (!owner) {
|
|
108
|
+
throw new ShardError("protocol/unknown-owner", `No live owner "${name}"`, {
|
|
109
|
+
hint: "owners.describe with no name lists every live owner."
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return owner;
|
|
113
|
+
}
|
|
114
|
+
function requireComponent(name) {
|
|
115
|
+
const def = knownComponent(name);
|
|
116
|
+
if (def.hostOnly) {
|
|
117
|
+
throw new ShardError(
|
|
118
|
+
"core/owner-not-authorable",
|
|
119
|
+
`"${name}" can't be written through the protocol`,
|
|
120
|
+
{
|
|
121
|
+
hint: "Ownership comes from host code holding an Owner (world.owners)."
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
return def;
|
|
126
|
+
}
|
|
99
127
|
function toJson(value) {
|
|
100
128
|
const seen = /* @__PURE__ */ new WeakSet();
|
|
101
129
|
return JSON.parse(
|
|
@@ -346,7 +374,7 @@ const METHODS = [
|
|
|
346
374
|
params: s("SchemaGetParams", {
|
|
347
375
|
name: t.string({ required: true, description: 'Component name, e.g. "render/Camera3d".' })
|
|
348
376
|
}),
|
|
349
|
-
handler: (_, p) =>
|
|
377
|
+
handler: (_, p) => knownComponent(p.name).jsonSchema()
|
|
350
378
|
},
|
|
351
379
|
{
|
|
352
380
|
name: "world.stats",
|
|
@@ -439,6 +467,23 @@ const METHODS = [
|
|
|
439
467
|
return entityJson(world, entity);
|
|
440
468
|
}
|
|
441
469
|
},
|
|
470
|
+
{
|
|
471
|
+
name: "owners.describe",
|
|
472
|
+
description: "Owners (0061). With a name: its usage (entities, triangles, textures, bytes), limits, child owners, asset leases and GPU objects. Without: every live owner.",
|
|
473
|
+
params: s("OwnersDescribeParams", { name: t.string() }),
|
|
474
|
+
handler: ({ world }, p) => p.name ? world.owners.describe(requireOwner(world, p.name)) : { owners: world.owners.list() }
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
name: "owners.release",
|
|
478
|
+
description: "Releases an owner and its child owners: despawns their entities, drops their asset leases (unloading assets nobody else leases, with their GPU objects).",
|
|
479
|
+
params: s("OwnersReleaseParams", { name: t.string({ required: true }) }),
|
|
480
|
+
handler: ({ world }, p) => {
|
|
481
|
+
const owner = requireOwner(world, p.name);
|
|
482
|
+
const entities = world.owners.describe(owner).usage.entities;
|
|
483
|
+
world.owners.release(owner);
|
|
484
|
+
return { released: owner.name, entities };
|
|
485
|
+
}
|
|
486
|
+
},
|
|
442
487
|
{
|
|
443
488
|
name: "entity.despawn",
|
|
444
489
|
description: "Despawns an entity (and its children unless recursive is false).",
|
|
@@ -467,10 +512,19 @@ const METHODS = [
|
|
|
467
512
|
},
|
|
468
513
|
{
|
|
469
514
|
name: "resource.set",
|
|
470
|
-
description: "Merges fields into a plain-object resource (e.g. render/AmbientLight).",
|
|
515
|
+
description: "Merges fields into a plain-object resource (e.g. render/AmbientLight), marking it changed and waking an on-demand app.",
|
|
471
516
|
params: s("ResourceSetParams", { name: t.string({ required: true }), value: t.json() }),
|
|
472
517
|
handler: ({ world }, p) => {
|
|
473
518
|
const def = findResource(p.name);
|
|
519
|
+
if (def?.hostOnly) {
|
|
520
|
+
throw new ShardError(
|
|
521
|
+
"core/owner-not-authorable",
|
|
522
|
+
`Resource "${p.name}" is written by the host only`,
|
|
523
|
+
{
|
|
524
|
+
hint: "Host code sets it; the protocol can read it with resource.get."
|
|
525
|
+
}
|
|
526
|
+
);
|
|
527
|
+
}
|
|
474
528
|
const current = def && world.tryResource(def);
|
|
475
529
|
if (!def || !isPlainObject(current)) {
|
|
476
530
|
throw new ShardError(
|
|
@@ -485,7 +539,7 @@ const METHODS = [
|
|
|
485
539
|
throw new ShardError("schema/type-mismatch", '"value" must be an object', {
|
|
486
540
|
path: "/value"
|
|
487
541
|
});
|
|
488
|
-
|
|
542
|
+
world.patchResource(def, p.value);
|
|
489
543
|
return toJson(current);
|
|
490
544
|
}
|
|
491
545
|
},
|
|
@@ -502,10 +556,11 @@ const METHODS = [
|
|
|
502
556
|
name: "time.resume",
|
|
503
557
|
description: "Resumes a paused game.",
|
|
504
558
|
params: none,
|
|
505
|
-
handler: ({ world }) => {
|
|
559
|
+
handler: ({ app, world }) => {
|
|
506
560
|
const control = world.resource(AppControlResource);
|
|
507
561
|
control.paused = false;
|
|
508
562
|
control.pausedByError = false;
|
|
563
|
+
app.requestFrame();
|
|
509
564
|
return { paused: false, frame: world.resource(Time).frame };
|
|
510
565
|
}
|
|
511
566
|
},
|
|
@@ -549,7 +604,7 @@ const METHODS = [
|
|
|
549
604
|
description: "Limit overlays to entities with all these components."
|
|
550
605
|
}),
|
|
551
606
|
buffer: t.string({
|
|
552
|
-
description: "Raw floats of a render buffer instead of the display image: 'hdr' (luminance in cd/m\xB2, before post-processing and tonemapping), 'post-hdr' (after the HDR effects, pre-exposed), 'depth', 'velocity' (screen motion in uv units, TAA and motion blur), 'ssao' (ambient occlusion), 'bloom' (the glow's first level), 'dof-half' (color and signed CoC in pixels), or another graph texture. Returned as base64 little-endian rgba32float."
|
|
607
|
+
description: "Raw floats of a render buffer instead of the display image: 'hdr' (luminance in cd/m\xB2, before post-processing and tonemapping), 'post-hdr' (after the HDR effects, pre-exposed; bloom's glow is added by the tonemap, so it's not in it), 'depth', 'velocity' (screen motion in uv units, TAA and motion blur), 'ssao' (ambient occlusion and view depth, at half resolution), 'bloom' (the glow's first level), 'dof-half' (color and signed CoC in pixels), or another graph texture. Returned as base64 little-endian rgba32float."
|
|
553
608
|
})
|
|
554
609
|
}),
|
|
555
610
|
handler: async ({ app, world, options }, p) => {
|
|
@@ -732,6 +787,20 @@ const METHODS = [
|
|
|
732
787
|
return { queued: true };
|
|
733
788
|
}
|
|
734
789
|
},
|
|
790
|
+
{
|
|
791
|
+
name: "input.simulate",
|
|
792
|
+
description: "Plays pointer gestures a step per frame, in CSS pixels, so controls and drags run headless (0060): [{ drag: { from: [x, y], to: [x, y], button, pointer, modifiers, frames, cancel } }, { pinch: { center, from, to, twist, pan, frames } }, { wheel: { at, dy, dx, modifiers } }, { tap: [x, y] }, { wait: frames }]. Needs gesturesPlugin. Returns how many frames they take.",
|
|
793
|
+
params: s("SimulateParams", {
|
|
794
|
+
gestures: t.json({ required: true, description: "The gestures, played one after another." })
|
|
795
|
+
}),
|
|
796
|
+
handler: ({ world }, p) => {
|
|
797
|
+
if (!Array.isArray(p.gestures))
|
|
798
|
+
throw new ShardError("protocol/invalid-params", "gestures must be a list", {
|
|
799
|
+
path: "/gestures"
|
|
800
|
+
});
|
|
801
|
+
return { frames: simulateGestures(world, p.gestures) };
|
|
802
|
+
}
|
|
803
|
+
},
|
|
735
804
|
{
|
|
736
805
|
name: "input.record",
|
|
737
806
|
description: "Starts recording input, or stops and returns the recording.",
|
|
@@ -974,6 +1043,87 @@ const METHODS = [
|
|
|
974
1043
|
};
|
|
975
1044
|
}
|
|
976
1045
|
},
|
|
1046
|
+
{
|
|
1047
|
+
name: "perf.describe",
|
|
1048
|
+
description: 'Profiler aggregates (0074) over the last 120 runs of each span (last, avg, p95, max ms): the frame, each schedule and its command application, systems and other main-thread spans, GPU passes and the GPU frame ("unavailable" without timestamp queries), worker jobs, async spans; memory (GPU ledger by category, JS heap, ECS tables); and the clock (resolution, isolated, timings under four steps are "coarse").',
|
|
1049
|
+
params: s("PerfDescribeParams", {
|
|
1050
|
+
spans: t.list(t.string, {
|
|
1051
|
+
description: 'Only spans these keys cover: a key covers a span it equals or prefixes up to a "/" ("render" covers "render/opaque").'
|
|
1052
|
+
}),
|
|
1053
|
+
top: t.u32({ default: 30, min: 1, max: 1e3, description: "Most spans listed per track." })
|
|
1054
|
+
}),
|
|
1055
|
+
handler: ({ world }, p) => {
|
|
1056
|
+
const spans = p.spans;
|
|
1057
|
+
return toJson(
|
|
1058
|
+
describePerf(world, {
|
|
1059
|
+
spans: spans && spans.length > 0 ? spans : void 0,
|
|
1060
|
+
top: p.top
|
|
1061
|
+
})
|
|
1062
|
+
);
|
|
1063
|
+
}
|
|
1064
|
+
},
|
|
1065
|
+
{
|
|
1066
|
+
name: "perf.capture",
|
|
1067
|
+
description: 'Captures a span timeline (0074) over the next `frames` frames, or with `until.frameMs` a flight recorder that keeps `before` frames and stops `after` frames past the first frame over it. Returns a summary: frame CPU/GPU/interval p50/p95/max, the top spans, and the 5 worst frames, each with the spans most above their median ("over"): the lead for a hitch. The Chrome trace (Perfetto, chrome://tracing) goes to .shard/captures/ (tracePath) where the host can write, else inline (trace).',
|
|
1068
|
+
params: s("PerfCaptureParams", {
|
|
1069
|
+
frames: t.u32({ min: 0, max: 1e5, description: "Frames to capture (default 300)." }),
|
|
1070
|
+
until: t.struct({
|
|
1071
|
+
frameMs: t.f32({
|
|
1072
|
+
min: 0,
|
|
1073
|
+
description: "Flight recorder: stop after the first frame whose CPU time is over this."
|
|
1074
|
+
})
|
|
1075
|
+
}),
|
|
1076
|
+
before: t.u32({
|
|
1077
|
+
default: 120,
|
|
1078
|
+
max: 1e5,
|
|
1079
|
+
description: "Flight recorder: frames kept before."
|
|
1080
|
+
}),
|
|
1081
|
+
after: t.u32({ default: 30, max: 1e5, description: "Flight recorder: frames after." }),
|
|
1082
|
+
timeout: t.f32({
|
|
1083
|
+
default: 60,
|
|
1084
|
+
min: 0,
|
|
1085
|
+
description: "Flight recorder: seconds to wait for a slow frame."
|
|
1086
|
+
}),
|
|
1087
|
+
sample: t.bool({
|
|
1088
|
+
description: "Also sample JavaScript (V8's profiler in Node, JS Self-Profiling in browsers): the hottest functions join the summary."
|
|
1089
|
+
}),
|
|
1090
|
+
devtools: t.bool({
|
|
1091
|
+
description: "Also show spans in Chrome's Performance panel (performance.measure)."
|
|
1092
|
+
}),
|
|
1093
|
+
write: t.bool({ default: true, description: "Write the trace file when the host can." })
|
|
1094
|
+
}),
|
|
1095
|
+
handler: async ({ app, world, options }, p) => {
|
|
1096
|
+
const frameMs = p.until?.frameMs ?? 0;
|
|
1097
|
+
const manual = (options.frames ?? "manual") === "manual";
|
|
1098
|
+
const gpu = world.tryResource(Gpu) !== void 0;
|
|
1099
|
+
return toJson(
|
|
1100
|
+
await capturePerf(world, {
|
|
1101
|
+
frames: p.frames || 300,
|
|
1102
|
+
until: frameMs > 0 ? { frameMs } : void 0,
|
|
1103
|
+
before: p.before,
|
|
1104
|
+
after: p.after,
|
|
1105
|
+
timeout: p.timeout,
|
|
1106
|
+
sample: p.sample,
|
|
1107
|
+
devtools: p.devtools,
|
|
1108
|
+
write: p.write,
|
|
1109
|
+
// Headless: run the frames here, letting GPU readbacks land between them.
|
|
1110
|
+
step: manual ? async () => {
|
|
1111
|
+
app.update(1 / app.fixedHz);
|
|
1112
|
+
if (gpu) await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1113
|
+
} : void 0
|
|
1114
|
+
})
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
},
|
|
1118
|
+
{
|
|
1119
|
+
name: "perf.reset",
|
|
1120
|
+
description: "Clears the profiler's aggregates: perf.describe starts over.",
|
|
1121
|
+
params: none,
|
|
1122
|
+
handler: ({ world }) => {
|
|
1123
|
+
world.resource(ProfilerResource).reset();
|
|
1124
|
+
return null;
|
|
1125
|
+
}
|
|
1126
|
+
},
|
|
977
1127
|
{
|
|
978
1128
|
name: "log.tail",
|
|
979
1129
|
description: "Recent log entries (errors from systems, the GPU, and shaders land here).",
|
|
@@ -988,7 +1138,9 @@ const METHODS = [
|
|
|
988
1138
|
description: "Recent errors with code, path, and hint.",
|
|
989
1139
|
params: s("ErrorsParams", { count: t.u32({ default: 20, min: 1 }) }),
|
|
990
1140
|
handler: ({ world }, p) => world.resource(LogResource).errors(p.count)
|
|
991
|
-
}
|
|
1141
|
+
},
|
|
1142
|
+
...ABLATE_METHODS,
|
|
1143
|
+
...BUDGET_METHODS
|
|
992
1144
|
];
|
|
993
1145
|
function createProtocolServer(app, options = {}) {
|
|
994
1146
|
const ctx = { app, world: app.world, options };
|
|
@@ -1021,10 +1173,12 @@ function createProtocolServer(app, options = {}) {
|
|
|
1021
1173
|
}
|
|
1022
1174
|
};
|
|
1023
1175
|
byName.set(subscribe.name, subscribe);
|
|
1024
|
-
const
|
|
1025
|
-
|
|
1176
|
+
const allow = options.allow ? new Set(options.allow) : void 0;
|
|
1177
|
+
const lookup = (name) => allow && !allow.has(name) ? void 0 : byName.get(name) ?? app.methods.find((m) => m.name === name);
|
|
1178
|
+
const allowed = (m) => !allow || allow.has(m.name);
|
|
1179
|
+
const server = {
|
|
1026
1180
|
get methods() {
|
|
1027
|
-
return [...byName.values(), ...app.methods.filter((m) => !byName.has(m.name))];
|
|
1181
|
+
return [...byName.values(), ...app.methods.filter((m) => !byName.has(m.name))].filter(allowed);
|
|
1028
1182
|
},
|
|
1029
1183
|
async handle(request) {
|
|
1030
1184
|
const id = request.id ?? null;
|
|
@@ -1033,13 +1187,21 @@ function createProtocolServer(app, options = {}) {
|
|
|
1033
1187
|
if (request?.jsonrpc !== "2.0" || typeof request.method !== "string") {
|
|
1034
1188
|
return reply({ error: { code: ERROR.invalid, message: "Invalid JSON-RPC request" } });
|
|
1035
1189
|
}
|
|
1190
|
+
if (allow && !allow.has(request.method)) {
|
|
1191
|
+
const err = new ShardError(
|
|
1192
|
+
"protocol/method-not-allowed",
|
|
1193
|
+
`"${request.method}" isn't allowed on this server`,
|
|
1194
|
+
{ hint: `This host allows: ${[...allow].join(", ")}.` }
|
|
1195
|
+
);
|
|
1196
|
+
return reply({ error: { code: ERROR.shard, message: err.message, data: err.toJSON() } });
|
|
1197
|
+
}
|
|
1036
1198
|
const method = lookup(request.method);
|
|
1037
1199
|
if (!method) {
|
|
1038
1200
|
return reply({
|
|
1039
1201
|
error: {
|
|
1040
1202
|
code: ERROR.notFound,
|
|
1041
1203
|
message: `Unknown method "${request.method}"`,
|
|
1042
|
-
data: { methods:
|
|
1204
|
+
data: { methods: server.methods.map((m) => m.name) }
|
|
1043
1205
|
}
|
|
1044
1206
|
});
|
|
1045
1207
|
}
|
|
@@ -1079,6 +1241,7 @@ function createProtocolServer(app, options = {}) {
|
|
|
1079
1241
|
listeners.clear();
|
|
1080
1242
|
}
|
|
1081
1243
|
};
|
|
1244
|
+
return server;
|
|
1082
1245
|
}
|
|
1083
1246
|
export {
|
|
1084
1247
|
METHODS,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aethervtt/shard-protocol",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -16,16 +16,16 @@
|
|
|
16
16
|
"README.md"
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@aethervtt/shard-assets": "0.0
|
|
20
|
-
"@aethervtt/shard-core": "0.0
|
|
21
|
-
"@aethervtt/shard-input": "0.0
|
|
22
|
-
"@aethervtt/shard-platform": "0.0
|
|
23
|
-
"@aethervtt/shard-render": "0.0
|
|
24
|
-
"@aethervtt/shard-runtime": "0.0
|
|
25
|
-
"@aethervtt/shard-scene": "0.0
|
|
26
|
-
"@aethervtt/shard-text": "0.0
|
|
27
|
-
"@aethervtt/shard-texture": "0.0
|
|
28
|
-
"@aethervtt/shard-transform": "0.0
|
|
19
|
+
"@aethervtt/shard-assets": "0.2.0",
|
|
20
|
+
"@aethervtt/shard-core": "0.2.0",
|
|
21
|
+
"@aethervtt/shard-input": "0.2.0",
|
|
22
|
+
"@aethervtt/shard-platform": "0.2.0",
|
|
23
|
+
"@aethervtt/shard-render": "0.2.0",
|
|
24
|
+
"@aethervtt/shard-runtime": "0.2.0",
|
|
25
|
+
"@aethervtt/shard-scene": "0.2.0",
|
|
26
|
+
"@aethervtt/shard-text": "0.2.0",
|
|
27
|
+
"@aethervtt/shard-texture": "0.2.0",
|
|
28
|
+
"@aethervtt/shard-transform": "0.2.0"
|
|
29
29
|
},
|
|
30
30
|
"repository": {
|
|
31
31
|
"type": "git",
|
|
@@ -37,6 +37,6 @@
|
|
|
37
37
|
"provenance": true
|
|
38
38
|
},
|
|
39
39
|
"shard": {
|
|
40
|
-
"buildSha": "
|
|
40
|
+
"buildSha": "32c22a3944ed23f5d1baa90bf7cc9fae724f5fd8"
|
|
41
41
|
}
|
|
42
42
|
}
|