@field/cascade 0.7.1 → 0.10.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/AGENTS.md +17 -1
- package/CHANGELOG.md +260 -0
- package/DESIGN.md +14 -0
- package/README.md +41 -25
- package/dist/assets/CodeEditor-DSZBpzdy.js +623 -0
- package/dist/assets/CodePanel-sY6sZa7B.js +2 -0
- package/dist/assets/CustomNodeDialog-BRKnqZhp.js +1 -0
- package/dist/assets/{ExportDialog-BJu2Bpvv.js → ExportDialog-C7dVGua4.js} +2 -2
- package/dist/assets/OpenGraphDialog-ZsgL-iwr.js +1 -0
- package/dist/assets/ProjectSettings-Cw17nNe3.js +2 -0
- package/dist/assets/SettingsDialog-p25h3tj0.js +1 -0
- package/dist/assets/VersionHistoryDialog-DzFLUotB.js +3 -0
- package/dist/assets/{core-DlkL34JF.js → core-BICrEt0y.js} +8 -8
- package/dist/assets/{cssMode-DaSSFZYi.js → cssMode-D7TXw57I.js} +1 -1
- package/dist/assets/definitions-DUww2Rt-.js +1 -0
- package/dist/assets/{editor.api-CHUMRaMm.js → editor.api-DvkCO3ko.js} +1 -1
- package/dist/assets/{freemarker2-DQ604q2A.js → freemarker2-BA2lxsF1.js} +1 -1
- package/dist/assets/geo-C2RHOVHC.js +1 -0
- package/dist/assets/{handlebars-09pcWz8x.js → handlebars-BcDiyPd8.js} +1 -1
- package/dist/assets/{html-DtIKrZiD.js → html-C7O4TyhX.js} +1 -1
- package/dist/assets/{htmlMode-DgJ_4m1_.js → htmlMode-WaDGF3Mj.js} +1 -1
- package/dist/assets/{image-CtUE4_I-.js → image-DR4oeyJB.js} +3 -3
- package/dist/assets/index-7vae6YFC.css +1 -0
- package/dist/assets/index-DyvDC6pS.js +219 -0
- package/dist/assets/index-client-DqYmKstU.js +1 -0
- package/dist/assets/{javascript-kbIBYYGo.js → javascript-CpIEK7tz.js} +1 -1
- package/dist/assets/{jsonMode-DblCG64t.js → jsonMode-B2FltW0f.js} +1 -1
- package/dist/assets/legacy-CTEeXeqF.js +2 -0
- package/dist/assets/{liquid-D-qdTGhQ.js → liquid-CN6FFHZE.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-Bc6azcFQ.js → lspLanguageFeatures-QhV9XtLp.js} +1 -1
- package/dist/assets/math-DaDEgWKt.js +1 -0
- package/dist/assets/{mdx-TXg4xsyD.js → mdx-DE89pafV.js} +1 -1
- package/dist/assets/pop-pLH7L0PW.js +1 -0
- package/dist/assets/{python-C4SGyXJW.js → python-ByTllTB9.js} +1 -1
- package/dist/assets/{razor-BZ-lEgY9.js → razor-USYlRRCE.js} +1 -1
- package/dist/assets/studioScenes-8sq9FfFg.js +1 -0
- package/dist/assets/{tsMode-BoaO1D7x.js → tsMode-BXpN8iUz.js} +1 -1
- package/dist/assets/{typescript-D87dYk9k.js → typescript-DInBjGQQ.js} +1 -1
- package/dist/assets/{workers-ZrST7klJ.js → workers-C7Ye5B7D.js} +1 -1
- package/dist/assets/{xml-DT6r4F7G.js → xml-U0Uocqyo.js} +1 -1
- package/dist/assets/{yaml-DxmQ8XYb.js → yaml-CBw_PVYf.js} +1 -1
- package/dist/cli/index.js +20070 -3487
- package/dist/cli/runtime/stage.ts +70 -10
- package/dist/contracts/bundle.d.ts +75 -0
- package/dist/contracts/bundle.js +112 -0
- package/dist/contracts/definition.d.ts +67 -4
- package/dist/contracts/document.d.ts +29 -0
- package/dist/contracts/editor.d.ts +172 -0
- package/dist/contracts/editor.js +2 -0
- package/dist/contracts/index.d.ts +2 -0
- package/dist/contracts/index.js +2 -0
- package/dist/contracts/validate.js +59 -3
- package/dist/contracts/values.d.ts +3 -3
- package/dist/contracts/values.js +1 -0
- package/dist/index.html +6 -4
- package/dist/player/app.js +17 -5
- package/dist/player/editor.js +1894 -0
- package/dist/player/editorEngine.js +477 -0
- package/dist/runtime/builtins/core/bundle.d.ts +70 -0
- package/dist/runtime/builtins/core/bundle.js +81 -0
- package/dist/runtime/builtins/core/index.d.ts +3 -2
- package/dist/runtime/builtins/core/index.js +6 -2
- package/dist/runtime/builtins/core/routing.d.ts +27 -0
- package/dist/runtime/builtins/core/routing.js +28 -0
- package/dist/runtime/edit/document.d.ts +94 -0
- package/dist/runtime/edit/document.js +194 -0
- package/dist/runtime/edit/graphEditor.d.ts +223 -0
- package/dist/runtime/edit/graphEditor.js +893 -0
- package/dist/runtime/edit/index.d.ts +12 -0
- package/dist/runtime/edit/index.js +6 -0
- package/dist/runtime/edit/layout.d.ts +50 -0
- package/dist/runtime/edit/layout.js +126 -0
- package/dist/runtime/edit/operations.d.ts +65 -0
- package/dist/runtime/edit/operations.js +57 -0
- package/dist/runtime/edit/parameters.d.ts +75 -0
- package/dist/runtime/edit/parameters.js +284 -0
- package/dist/runtime/edit/read.d.ts +35 -0
- package/dist/runtime/edit/read.js +65 -0
- package/dist/runtime/editor/index.d.ts +104 -0
- package/dist/runtime/editor/index.js +439 -0
- package/dist/runtime/expressions/bundlePaths.d.ts +37 -0
- package/dist/runtime/expressions/bundlePaths.js +55 -0
- package/dist/runtime/expressions/index.d.ts +2 -0
- package/dist/runtime/expressions/index.js +1 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/params/animator.d.ts +5 -0
- package/dist/runtime/params/animator.js +16 -0
- package/dist/runtime/runtime.js +238 -12
- package/dist/runtime/seed.d.ts +29 -0
- package/dist/runtime/seed.js +52 -0
- package/dist/runtime/types.d.ts +12 -0
- package/dist/stage.js +40 -9
- package/dist/studio/panel.d.ts +2 -0
- package/doc/CLI_REFERENCE.md +100 -0
- package/doc/EDITOR_EXPORT.md +226 -0
- package/doc/HEADLESS_GPU.md +27 -0
- package/doc/MCP.md +53 -0
- package/doc/NODE_REFERENCE.md +66 -0
- package/doc/PROJECT_AUTHORING.md +137 -0
- package/package.json +15 -6
- package/dist/assets/CodeEditor-D-N0C2jT.js +0 -623
- package/dist/assets/CodePanel-EN411mp5.js +0 -2
- package/dist/assets/CustomNodeDialog-6z2n27VV.js +0 -1
- package/dist/assets/OpenGraphDialog-fMZjzIR3.js +0 -1
- package/dist/assets/ProjectSettings-BQ8PGNqo.js +0 -2
- package/dist/assets/SettingsDialog-IbYLm8Bx.js +0 -1
- package/dist/assets/VersionHistoryDialog-Bgh-q5Jg.js +0 -3
- package/dist/assets/definitions-CQBdsJYJ.js +0 -1
- package/dist/assets/geo-DcmMAcqM.js +0 -1
- package/dist/assets/index-CnylT98t.js +0 -212
- package/dist/assets/index-client-BoJgsN9M.js +0 -1
- package/dist/assets/index-ilgbOu6G.css +0 -1
- package/dist/assets/legacy-CAj2_7QE.js +0 -2
- package/dist/assets/math-CY11tkoN.js +0 -1
- package/dist/assets/pop-Dr5-ARqQ.js +0 -1
- package/dist/assets/projectSettingsApi-C1WvIwfP.js +0 -1
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
// src/player/editorEngine.ts
|
|
2
|
+
import { createBrowserRuntimeHost as createBrowserRuntimeHost2 } from "@field/cascade/runtime/browser";
|
|
3
|
+
import { createRuntime as createRuntime2, PREFLIGHT_WARNING_CODES as PREFLIGHT_WARNING_CODES2 } from "@field/cascade/runtime";
|
|
4
|
+
import { installIoBridge as installIoBridge2 } from "@field/cascade/io";
|
|
5
|
+
|
|
6
|
+
// src/browser/gpu.ts
|
|
7
|
+
import {
|
|
8
|
+
createGpuHost
|
|
9
|
+
} from "@field/cascade/runtime";
|
|
10
|
+
function createBrowserGpuHost() {
|
|
11
|
+
const gpu = globalThis.navigator?.gpu;
|
|
12
|
+
if (!gpu) return void 0;
|
|
13
|
+
return createGpuHost({
|
|
14
|
+
async acquire() {
|
|
15
|
+
const adapter = await gpu.requestAdapter();
|
|
16
|
+
if (!adapter) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
"No WebGPU adapter is available \u2014 the browser reports WebGPU but the system offers no GPU to render on."
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
const device = await adapter.requestDevice();
|
|
22
|
+
const info = adapter.info;
|
|
23
|
+
return {
|
|
24
|
+
device,
|
|
25
|
+
info: {
|
|
26
|
+
vendor: info?.vendor ?? "",
|
|
27
|
+
architecture: info?.architecture ?? "",
|
|
28
|
+
device: info?.device ?? "",
|
|
29
|
+
description: info?.description ?? ""
|
|
30
|
+
},
|
|
31
|
+
limits: device.limits ?? adapter.limits
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/player/app.ts
|
|
38
|
+
import {
|
|
39
|
+
CAMERA_DEFAULTS,
|
|
40
|
+
isScene,
|
|
41
|
+
sceneFromGeometry
|
|
42
|
+
} from "@field/cascade/contracts";
|
|
43
|
+
import { createBrowserRuntimeHost } from "@field/cascade/runtime/browser";
|
|
44
|
+
import { renderScene } from "@field/cascade/runtime/render";
|
|
45
|
+
import { createRuntime, PREFLIGHT_WARNING_CODES } from "@field/cascade/runtime";
|
|
46
|
+
import { sceneBounds } from "@field/cascade/runtime/scene";
|
|
47
|
+
import { installIoBridge } from "@field/cascade/io";
|
|
48
|
+
|
|
49
|
+
// src/player/assets.ts
|
|
50
|
+
var DEFAULT_MAX_BYTES = 256 * 1024 * 1024;
|
|
51
|
+
var DEFAULT_MAX_ENTRIES = 4096;
|
|
52
|
+
var GENERATED_ROOT = ".cascade-player/generated";
|
|
53
|
+
var PlayerAssetStore = class {
|
|
54
|
+
constructor(assets, options = {}) {
|
|
55
|
+
this.authored = /* @__PURE__ */ new Map();
|
|
56
|
+
this.generated = /* @__PURE__ */ new Map();
|
|
57
|
+
this.generatedByUrl = /* @__PURE__ */ new Map();
|
|
58
|
+
this.generatedBytes = 0;
|
|
59
|
+
this.sequence = 0;
|
|
60
|
+
this.disposed = false;
|
|
61
|
+
this.maxGeneratedBytes = finiteLimit(
|
|
62
|
+
options.maxGeneratedBytes,
|
|
63
|
+
DEFAULT_MAX_BYTES,
|
|
64
|
+
"maxGeneratedBytes"
|
|
65
|
+
);
|
|
66
|
+
this.maxGeneratedEntries = finiteLimit(
|
|
67
|
+
options.maxGeneratedEntries,
|
|
68
|
+
DEFAULT_MAX_ENTRIES,
|
|
69
|
+
"maxGeneratedEntries"
|
|
70
|
+
);
|
|
71
|
+
for (const [path, url] of Object.entries(assets)) {
|
|
72
|
+
const clean = cleanPath(path);
|
|
73
|
+
if (typeof url !== "string" || url.length === 0)
|
|
74
|
+
throw new TypeError(`Player asset ${clean} has no deployment URL`);
|
|
75
|
+
this.authored.set(clean, url);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async read(ref, options = {}) {
|
|
79
|
+
this.assertActive();
|
|
80
|
+
const path = cleanPath(typeof ref === "string" ? ref : ref.path);
|
|
81
|
+
if (typeof ref === "string") assertIdentityMediaOptions(path, options);
|
|
82
|
+
const signal = "signal" in options ? options.signal : void 0;
|
|
83
|
+
if (signal?.aborted) throw abortError(signal.reason);
|
|
84
|
+
const generated = this.generated.get(path);
|
|
85
|
+
if (generated) return generated.bytes.slice();
|
|
86
|
+
const url = this.authored.get(path);
|
|
87
|
+
if (!url) throw new Error(`Player asset not found: ${path}`);
|
|
88
|
+
const relay = signal ? relayAbort(signal) : void 0;
|
|
89
|
+
try {
|
|
90
|
+
const response = await fetch(url, {
|
|
91
|
+
...relay ? { signal: relay.signal } : {}
|
|
92
|
+
});
|
|
93
|
+
if (!response.ok)
|
|
94
|
+
throw new Error(`Could not read player asset ${path} (${response.status})`);
|
|
95
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
96
|
+
} finally {
|
|
97
|
+
relay?.dispose();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async write(first, second, third) {
|
|
101
|
+
this.assertActive();
|
|
102
|
+
const fromIo = typeof first === "string";
|
|
103
|
+
const data = fromIo ? second : first;
|
|
104
|
+
if (!(data instanceof Uint8Array))
|
|
105
|
+
throw new TypeError("Player asset writes require Uint8Array bytes");
|
|
106
|
+
const metadata = fromIo ? { suggestedName: first, mediaType: mediaTypeForName(first) } : second;
|
|
107
|
+
if (third?.signal.aborted) throw abortError(third.signal.reason);
|
|
108
|
+
const name = safeName(metadata.suggestedName);
|
|
109
|
+
const path = `${GENERATED_ROOT}/${String(++this.sequence).padStart(8, "0")}-${name}`;
|
|
110
|
+
const nextBytes = this.generatedBytes + data.byteLength;
|
|
111
|
+
const nextEntries = this.generated.size + 1;
|
|
112
|
+
if (nextBytes > this.maxGeneratedBytes) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`Player asset ${path} would use ${nextBytes} bytes; limit is ${this.maxGeneratedBytes} bytes`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
if (nextEntries > this.maxGeneratedEntries) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`Player asset ${path} would use ${nextEntries} entries; limit is ${this.maxGeneratedEntries} entries`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
const stored = data.slice();
|
|
123
|
+
this.generated.set(path, {
|
|
124
|
+
bytes: stored,
|
|
125
|
+
...metadata.mediaType ? { mediaType: metadata.mediaType } : {},
|
|
126
|
+
leases: 0
|
|
127
|
+
});
|
|
128
|
+
this.generatedBytes = nextBytes;
|
|
129
|
+
return fromIo ? path : { path, ...metadata.mediaType ? { mediaType: metadata.mediaType } : {} };
|
|
130
|
+
}
|
|
131
|
+
url(path, options = {}) {
|
|
132
|
+
this.assertActive();
|
|
133
|
+
assertIdentityMediaOptions(path, options);
|
|
134
|
+
const clean = cleanPath(path);
|
|
135
|
+
const authored = this.authored.get(clean);
|
|
136
|
+
if (authored) return authored;
|
|
137
|
+
const generated = this.generated.get(clean);
|
|
138
|
+
if (!generated) throw new Error(`Player asset not found: ${clean}`);
|
|
139
|
+
if (!generated.objectUrl) {
|
|
140
|
+
generated.objectUrl = URL.createObjectURL(blobFor(generated));
|
|
141
|
+
this.generatedByUrl.set(generated.objectUrl, clean);
|
|
142
|
+
}
|
|
143
|
+
return generated.objectUrl;
|
|
144
|
+
}
|
|
145
|
+
async resolveUrl(ref) {
|
|
146
|
+
this.assertActive();
|
|
147
|
+
const path = cleanPath(ref.path);
|
|
148
|
+
const generated = this.generated.get(path);
|
|
149
|
+
if (!generated) {
|
|
150
|
+
const authored = this.authored.get(path);
|
|
151
|
+
if (!authored) throw new Error(`Player asset not found: ${path}`);
|
|
152
|
+
return { value: authored, release: () => {
|
|
153
|
+
} };
|
|
154
|
+
}
|
|
155
|
+
generated.leases += 1;
|
|
156
|
+
let value;
|
|
157
|
+
try {
|
|
158
|
+
value = this.url(path, { raw: true });
|
|
159
|
+
} catch (error) {
|
|
160
|
+
generated.leases -= 1;
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
let released = false;
|
|
164
|
+
return {
|
|
165
|
+
value,
|
|
166
|
+
release: () => {
|
|
167
|
+
if (released) return;
|
|
168
|
+
released = true;
|
|
169
|
+
generated.leases = Math.max(0, generated.leases - 1);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/** Remove generated entries that no settled graph value or display lease owns. */
|
|
174
|
+
collect(roots) {
|
|
175
|
+
if (this.disposed) return;
|
|
176
|
+
const reachable = /* @__PURE__ */ new Set();
|
|
177
|
+
const seen = /* @__PURE__ */ new Set();
|
|
178
|
+
for (const root of roots)
|
|
179
|
+
visitPaths(root, reachable, seen, this.generated, this.generatedByUrl);
|
|
180
|
+
const errors = [];
|
|
181
|
+
for (const [path, entry] of this.generated) {
|
|
182
|
+
if (reachable.has(path) || entry.leases > 0) continue;
|
|
183
|
+
try {
|
|
184
|
+
this.drop(path, entry);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
errors.push(error);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
throwCollectedErrors(errors, "Player asset collection failed");
|
|
190
|
+
}
|
|
191
|
+
stats() {
|
|
192
|
+
return {
|
|
193
|
+
generatedBytes: this.generatedBytes,
|
|
194
|
+
generatedEntries: this.generated.size,
|
|
195
|
+
objectUrls: this.generatedByUrl.size
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
dispose() {
|
|
199
|
+
if (this.disposed) return;
|
|
200
|
+
this.disposed = true;
|
|
201
|
+
const errors = [];
|
|
202
|
+
for (const [path, entry] of this.generated) {
|
|
203
|
+
try {
|
|
204
|
+
this.drop(path, entry);
|
|
205
|
+
} catch (error) {
|
|
206
|
+
errors.push(error);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
throwCollectedErrors(errors, "Player asset disposal failed");
|
|
210
|
+
}
|
|
211
|
+
drop(path, entry) {
|
|
212
|
+
try {
|
|
213
|
+
if (entry.objectUrl) URL.revokeObjectURL(entry.objectUrl);
|
|
214
|
+
} finally {
|
|
215
|
+
if (entry.objectUrl) this.generatedByUrl.delete(entry.objectUrl);
|
|
216
|
+
this.generated.delete(path);
|
|
217
|
+
this.generatedBytes -= entry.bytes.byteLength;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
assertActive() {
|
|
221
|
+
if (this.disposed) throw new Error("Player asset store is disposed");
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
function finiteLimit(value, fallback, name) {
|
|
225
|
+
const resolved = value ?? fallback;
|
|
226
|
+
if (!Number.isSafeInteger(resolved) || resolved < 0)
|
|
227
|
+
throw new TypeError(`${name} must be a non-negative safe integer`);
|
|
228
|
+
return resolved;
|
|
229
|
+
}
|
|
230
|
+
function cleanPath(path) {
|
|
231
|
+
const clean = path.replace(/^\.\//, "").replace(/\\/g, "/");
|
|
232
|
+
if (!clean || clean.startsWith("/") || clean.split("/").some((part) => part === ".."))
|
|
233
|
+
throw new Error(`Invalid player asset path: ${path}`);
|
|
234
|
+
return clean;
|
|
235
|
+
}
|
|
236
|
+
function safeName(value) {
|
|
237
|
+
const source = (value?.trim() || "asset.bin").replace(/\\/g, "/").split("/").pop();
|
|
238
|
+
const safe = source.replace(/[^A-Za-z0-9._-]/g, "-").replace(/^\.+/, "");
|
|
239
|
+
return safe || "asset.bin";
|
|
240
|
+
}
|
|
241
|
+
function assertIdentityMediaOptions(path, options) {
|
|
242
|
+
if (options.width !== void 0 || options.format !== void 0 || options.quality !== void 0)
|
|
243
|
+
throw new Error(`Player asset transforms are unsupported for ${path}; request the raw asset`);
|
|
244
|
+
}
|
|
245
|
+
function blobFor(entry) {
|
|
246
|
+
return new Blob([entry.bytes.slice().buffer], {
|
|
247
|
+
type: entry.mediaType ?? "application/octet-stream"
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
function visitPaths(value, reachable, seen, generated, generatedByUrl) {
|
|
251
|
+
if (typeof value === "string") {
|
|
252
|
+
const path = value.replace(/^\.\//, "");
|
|
253
|
+
if (generated.has(path)) reachable.add(path);
|
|
254
|
+
else {
|
|
255
|
+
const generatedPath = generatedByUrl.get(value);
|
|
256
|
+
if (generatedPath) reachable.add(generatedPath);
|
|
257
|
+
}
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (typeof value !== "object" || value === null) return;
|
|
261
|
+
if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) return;
|
|
262
|
+
if (seen.has(value)) return;
|
|
263
|
+
seen.add(value);
|
|
264
|
+
if ("path" in value && typeof value.path === "string") {
|
|
265
|
+
const path = value.path.replace(/^\.\//, "");
|
|
266
|
+
if (generated.has(path)) reachable.add(path);
|
|
267
|
+
}
|
|
268
|
+
if (Array.isArray(value)) {
|
|
269
|
+
for (const item of value)
|
|
270
|
+
visitPaths(item, reachable, seen, generated, generatedByUrl);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const prototype = Object.getPrototypeOf(value);
|
|
274
|
+
if (prototype !== Object.prototype && prototype !== null) return;
|
|
275
|
+
for (const item of Object.values(value))
|
|
276
|
+
visitPaths(item, reachable, seen, generated, generatedByUrl);
|
|
277
|
+
}
|
|
278
|
+
function relayAbort(signal) {
|
|
279
|
+
const controller = new AbortController();
|
|
280
|
+
const abort = () => controller.abort(signal.reason);
|
|
281
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
282
|
+
if (signal.aborted) abort();
|
|
283
|
+
return {
|
|
284
|
+
signal: controller.signal,
|
|
285
|
+
dispose: () => signal.removeEventListener("abort", abort)
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
function abortError(reason) {
|
|
289
|
+
return reason instanceof Error ? reason : new Error(reason === void 0 ? "Aborted" : String(reason));
|
|
290
|
+
}
|
|
291
|
+
function mediaTypeForName(name) {
|
|
292
|
+
if (/\.png$/i.test(name)) return "image/png";
|
|
293
|
+
if (/\.svg$/i.test(name)) return "image/svg+xml";
|
|
294
|
+
if (/\.jpe?g$/i.test(name)) return "image/jpeg";
|
|
295
|
+
if (/\.webp$/i.test(name)) return "image/webp";
|
|
296
|
+
return void 0;
|
|
297
|
+
}
|
|
298
|
+
function throwCollectedErrors(errors, message) {
|
|
299
|
+
if (errors.length === 1) throw errors[0];
|
|
300
|
+
if (errors.length > 1) {
|
|
301
|
+
const combined = new Error(message);
|
|
302
|
+
Object.assign(combined, { errors });
|
|
303
|
+
throw combined;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// src/player/app.ts
|
|
308
|
+
async function cleanupAll(actions) {
|
|
309
|
+
const errors = [];
|
|
310
|
+
for (const action of actions) {
|
|
311
|
+
try {
|
|
312
|
+
await action();
|
|
313
|
+
} catch (error) {
|
|
314
|
+
errors.push(error);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
throwCollectedErrors(errors, "Cascade player cleanup failed");
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/player/editorEngine.ts
|
|
321
|
+
var never = { aborted: false, reason: void 0, addEventListener() {
|
|
322
|
+
}, removeEventListener() {
|
|
323
|
+
} };
|
|
324
|
+
var LocalEngine = class _LocalEngine {
|
|
325
|
+
constructor(graph, store, release) {
|
|
326
|
+
this.graph = graph;
|
|
327
|
+
this.store = store;
|
|
328
|
+
this.release = release;
|
|
329
|
+
}
|
|
330
|
+
static async create(options) {
|
|
331
|
+
const store = new PlayerAssetStore(options.assets);
|
|
332
|
+
const disposeIo = installIoBridge2(store);
|
|
333
|
+
const gpu = createBrowserGpuHost();
|
|
334
|
+
const registrations = new Map(options.registrations.map((item) => [item.moduleId, item]));
|
|
335
|
+
const runtime = createRuntime2({
|
|
336
|
+
host: createBrowserRuntimeHost2({
|
|
337
|
+
modules: { resolve: async (moduleId) => registrations.get(moduleId) ?? null },
|
|
338
|
+
assets: store,
|
|
339
|
+
...gpu ? { gpu } : {}
|
|
340
|
+
}),
|
|
341
|
+
nodes: options.registrations
|
|
342
|
+
});
|
|
343
|
+
const release = () => cleanupAll([() => runtime.dispose(), () => gpu?.dispose(), () => disposeIo(), () => store.dispose()]);
|
|
344
|
+
try {
|
|
345
|
+
const graph = await runtime.load(options.document);
|
|
346
|
+
const blocking = graph.preflight().find((item) => !PREFLIGHT_WARNING_CODES2.has(item.code));
|
|
347
|
+
if (blocking) throw new Error(blocking.message);
|
|
348
|
+
return new _LocalEngine(graph, store, async () => {
|
|
349
|
+
await graph.dispose();
|
|
350
|
+
await release();
|
|
351
|
+
});
|
|
352
|
+
} catch (error) {
|
|
353
|
+
await release().catch(() => {
|
|
354
|
+
});
|
|
355
|
+
throw error;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
async nodes() {
|
|
359
|
+
return this.graph.inspect().nodes.map((node) => ({
|
|
360
|
+
id: node.id,
|
|
361
|
+
moduleId: node.moduleId,
|
|
362
|
+
...node.parent ? { parent: node.parent } : {},
|
|
363
|
+
props: Object.fromEntries(Object.entries(node.props).map(([name, prop]) => [name, { type: prop.type, value: prop.value }]))
|
|
364
|
+
}));
|
|
365
|
+
}
|
|
366
|
+
async setProps(writes) {
|
|
367
|
+
for (const [node, prop, value] of writes) await this.graph.setProp(node, prop, value);
|
|
368
|
+
}
|
|
369
|
+
async run(options = {}) {
|
|
370
|
+
const result = await this.graph.run(options);
|
|
371
|
+
this.collect();
|
|
372
|
+
return { status: result.status, messages: result.diagnostics.map((item) => item.message) };
|
|
373
|
+
}
|
|
374
|
+
async output(node, port) {
|
|
375
|
+
return this.graph.getOutput(node, port);
|
|
376
|
+
}
|
|
377
|
+
async outputs() {
|
|
378
|
+
return new Map(this.graph.inspect().nodes.map((node) => [node.id, new Map(this.graph.getOutputs(node.id))]));
|
|
379
|
+
}
|
|
380
|
+
writeAsset(bytes, metadata) {
|
|
381
|
+
return this.store.write(bytes, metadata, { signal: never });
|
|
382
|
+
}
|
|
383
|
+
readAsset(ref) {
|
|
384
|
+
return this.store.read(ref, { signal: never });
|
|
385
|
+
}
|
|
386
|
+
dispose() {
|
|
387
|
+
return this.release();
|
|
388
|
+
}
|
|
389
|
+
/** Drop generated assets nothing reaches any more: not a prop, not an output. */
|
|
390
|
+
collect() {
|
|
391
|
+
const roots = [];
|
|
392
|
+
for (const node of this.graph.inspect().nodes) {
|
|
393
|
+
roots.push(...Object.values(node.props).map((prop) => prop.value));
|
|
394
|
+
roots.push(...this.graph.getOutputs(node.id).values());
|
|
395
|
+
}
|
|
396
|
+
try {
|
|
397
|
+
this.store.collect(roots);
|
|
398
|
+
} catch {
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
function serveEditorEngine(registrations) {
|
|
403
|
+
const scope = globalThis;
|
|
404
|
+
let engine;
|
|
405
|
+
scope.onmessage = async (event) => {
|
|
406
|
+
const { id, method, args } = event.data;
|
|
407
|
+
try {
|
|
408
|
+
if (method === "init") {
|
|
409
|
+
const [document, assets] = args;
|
|
410
|
+
engine = LocalEngine.create({ document, registrations, assets });
|
|
411
|
+
await engine;
|
|
412
|
+
scope.postMessage({ id, ok: true });
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
if (!engine) throw new Error("Editor engine is not initialised");
|
|
416
|
+
const target = await engine;
|
|
417
|
+
const value = await target[method].apply(target, [...args]);
|
|
418
|
+
const transfer = value instanceof Uint8Array ? [value.buffer] : [];
|
|
419
|
+
try {
|
|
420
|
+
scope.postMessage({ id, ok: true, value }, transfer);
|
|
421
|
+
} catch (error) {
|
|
422
|
+
scope.postMessage({ id, ok: true, value: describeUncloneable(value, error) });
|
|
423
|
+
}
|
|
424
|
+
} catch (error) {
|
|
425
|
+
scope.postMessage({ id, ok: false, error: error instanceof Error ? error.message : String(error) });
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
async function createWorkerEngine(url, options) {
|
|
430
|
+
const worker = new Worker(url, { type: "module", name: "cascade-editor-engine" });
|
|
431
|
+
let sequence = 0;
|
|
432
|
+
const pending = /* @__PURE__ */ new Map();
|
|
433
|
+
worker.onmessage = (event) => {
|
|
434
|
+
const entry = pending.get(event.data.id);
|
|
435
|
+
if (!entry) return;
|
|
436
|
+
pending.delete(event.data.id);
|
|
437
|
+
if (event.data.ok) entry.resolve(event.data.value);
|
|
438
|
+
else entry.reject(new Error(event.data.error));
|
|
439
|
+
};
|
|
440
|
+
worker.onerror = (event) => {
|
|
441
|
+
const error = new Error(event.message || "Editor engine worker failed");
|
|
442
|
+
for (const entry of pending.values()) entry.reject(error);
|
|
443
|
+
pending.clear();
|
|
444
|
+
};
|
|
445
|
+
const call = (method, ...args) => new Promise((resolve, reject) => {
|
|
446
|
+
const id = ++sequence;
|
|
447
|
+
pending.set(id, { resolve, reject });
|
|
448
|
+
worker.postMessage({ id, method, args });
|
|
449
|
+
});
|
|
450
|
+
const assets = Object.fromEntries(Object.entries(options.assets).map(([name, href]) => [name, new URL(href, location.href).href]));
|
|
451
|
+
await call("init", options.document, assets);
|
|
452
|
+
return {
|
|
453
|
+
nodes: () => call("nodes"),
|
|
454
|
+
setProps: (writes) => call("setProps", writes),
|
|
455
|
+
run: (runOptions = {}) => call("run", runOptions),
|
|
456
|
+
output: (node, port) => call("output", node, port),
|
|
457
|
+
outputs: () => call("outputs"),
|
|
458
|
+
writeAsset: (bytes, metadata) => call("writeAsset", bytes, metadata),
|
|
459
|
+
readAsset: (ref) => call("readAsset", ref),
|
|
460
|
+
async dispose() {
|
|
461
|
+
try {
|
|
462
|
+
await call("dispose");
|
|
463
|
+
} finally {
|
|
464
|
+
worker.terminate();
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
function describeUncloneable(value, error) {
|
|
470
|
+
const kind = value === null ? "null" : typeof value === "object" ? value.constructor?.name ?? "object" : typeof value;
|
|
471
|
+
return `(${kind} cannot be shown here: ${error instanceof Error ? error.message : String(error)})`;
|
|
472
|
+
}
|
|
473
|
+
export {
|
|
474
|
+
LocalEngine,
|
|
475
|
+
createWorkerEngine,
|
|
476
|
+
serveEditorEngine
|
|
477
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { DefinitionNodeRegistration } from "../../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Pick and Pack: the two adapters between bundles and single-value ports.
|
|
4
|
+
*
|
|
5
|
+
* A node that wants one number takes it from a bundle through Pick; a handful
|
|
6
|
+
* of loose values become a bundle through Pack. Pick is cheap to re-cook and
|
|
7
|
+
* its output only changes when the picked field does, so with early cutoff a
|
|
8
|
+
* Pick is also the way to depend on one field of a bundle whose consumer
|
|
9
|
+
* cannot declare its fields.
|
|
10
|
+
*/
|
|
11
|
+
declare const pickDefinition: {
|
|
12
|
+
readonly apiVersion: 1;
|
|
13
|
+
readonly runsOn: "portable";
|
|
14
|
+
readonly label: "Pick";
|
|
15
|
+
readonly description: "Reads one field of a bundle";
|
|
16
|
+
readonly icon: "ListFilter";
|
|
17
|
+
readonly inputs: {
|
|
18
|
+
readonly bundle: {
|
|
19
|
+
readonly kind: "data";
|
|
20
|
+
readonly type: "bundle";
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
readonly outputs: {
|
|
24
|
+
readonly value: {
|
|
25
|
+
readonly kind: "data";
|
|
26
|
+
readonly type: "any";
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
readonly props: {
|
|
30
|
+
readonly field: {
|
|
31
|
+
readonly type: "string";
|
|
32
|
+
readonly default: "";
|
|
33
|
+
readonly label: "Field";
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
declare const packDefinition: {
|
|
38
|
+
readonly apiVersion: 1;
|
|
39
|
+
readonly runsOn: "portable";
|
|
40
|
+
readonly label: "Pack";
|
|
41
|
+
readonly description: "Collects inputs into a bundle, one named field each";
|
|
42
|
+
readonly icon: "Package";
|
|
43
|
+
readonly inputs: {
|
|
44
|
+
readonly inputs: {
|
|
45
|
+
readonly kind: "data";
|
|
46
|
+
readonly type: "any";
|
|
47
|
+
readonly variadic: true;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
readonly outputs: {
|
|
51
|
+
readonly bundle: {
|
|
52
|
+
readonly kind: "data";
|
|
53
|
+
readonly type: "bundle";
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
readonly props: {
|
|
57
|
+
readonly names: {
|
|
58
|
+
readonly type: "string";
|
|
59
|
+
readonly default: "";
|
|
60
|
+
readonly label: "Field names";
|
|
61
|
+
readonly description: "Comma-separated, one per input in order; an unnamed input becomes field_<n>";
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
/** The field names Pack gives its inputs. */
|
|
66
|
+
export declare function packFieldNames(names: string, count: number): string[];
|
|
67
|
+
export declare const pickRegistration: DefinitionNodeRegistration<typeof pickDefinition>;
|
|
68
|
+
export declare const packRegistration: DefinitionNodeRegistration<typeof packDefinition>;
|
|
69
|
+
export {};
|
|
70
|
+
//# sourceMappingURL=bundle.d.ts.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pick and Pack: the two adapters between bundles and single-value ports.
|
|
3
|
+
*
|
|
4
|
+
* A node that wants one number takes it from a bundle through Pick; a handful
|
|
5
|
+
* of loose values become a bundle through Pack. Pick is cheap to re-cook and
|
|
6
|
+
* its output only changes when the picked field does, so with early cutoff a
|
|
7
|
+
* Pick is also the way to depend on one field of a bundle whose consumer
|
|
8
|
+
* cannot declare its fields.
|
|
9
|
+
*/
|
|
10
|
+
const pickDefinition = {
|
|
11
|
+
apiVersion: 1,
|
|
12
|
+
runsOn: "portable",
|
|
13
|
+
label: "Pick",
|
|
14
|
+
description: "Reads one field of a bundle",
|
|
15
|
+
icon: "ListFilter",
|
|
16
|
+
inputs: {
|
|
17
|
+
bundle: { kind: "data", type: "bundle" },
|
|
18
|
+
},
|
|
19
|
+
outputs: {
|
|
20
|
+
value: { kind: "data", type: "any" },
|
|
21
|
+
},
|
|
22
|
+
props: {
|
|
23
|
+
field: { type: "string", default: "", label: "Field" },
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
const packDefinition = {
|
|
27
|
+
apiVersion: 1,
|
|
28
|
+
runsOn: "portable",
|
|
29
|
+
label: "Pack",
|
|
30
|
+
description: "Collects inputs into a bundle, one named field each",
|
|
31
|
+
icon: "Package",
|
|
32
|
+
inputs: {
|
|
33
|
+
inputs: { kind: "data", type: "any", variadic: true },
|
|
34
|
+
},
|
|
35
|
+
outputs: {
|
|
36
|
+
bundle: { kind: "data", type: "bundle" },
|
|
37
|
+
},
|
|
38
|
+
props: {
|
|
39
|
+
names: {
|
|
40
|
+
type: "string",
|
|
41
|
+
default: "",
|
|
42
|
+
label: "Field names",
|
|
43
|
+
description: "Comma-separated, one per input in order; an unnamed input becomes field_<n>",
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
/** The field names Pack gives its inputs. */
|
|
48
|
+
export function packFieldNames(names, count) {
|
|
49
|
+
const declared = String(names ?? "")
|
|
50
|
+
.split(",")
|
|
51
|
+
.map((name) => name.trim());
|
|
52
|
+
return Array.from({ length: count }, (_, index) => declared[index] || `field_${index}`);
|
|
53
|
+
}
|
|
54
|
+
export const pickRegistration = {
|
|
55
|
+
kind: "definition-v1",
|
|
56
|
+
moduleId: "cascade.core.Pick",
|
|
57
|
+
definition: pickDefinition,
|
|
58
|
+
loadExecute: async () => ({ inputs, outputs, props }) => {
|
|
59
|
+
const bundle = inputs.bundle;
|
|
60
|
+
const field = props.field.trim();
|
|
61
|
+
outputs.value.set(bundle && field && Object.prototype.hasOwnProperty.call(bundle, field)
|
|
62
|
+
? bundle[field]
|
|
63
|
+
: null);
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
export const packRegistration = {
|
|
67
|
+
kind: "definition-v1",
|
|
68
|
+
moduleId: "cascade.core.Pack",
|
|
69
|
+
definition: packDefinition,
|
|
70
|
+
loadExecute: async () => ({ inputs, outputs, props }) => {
|
|
71
|
+
const values = inputs.inputs;
|
|
72
|
+
const names = packFieldNames(props.names, values.length);
|
|
73
|
+
const bundle = {};
|
|
74
|
+
names.forEach((name, index) => {
|
|
75
|
+
if (values[index] !== undefined)
|
|
76
|
+
bundle[name] = values[index];
|
|
77
|
+
});
|
|
78
|
+
outputs.bundle.set(bundle);
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
//# sourceMappingURL=bundle.js.map
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { DefinitionNodeRegistration } from "../../types.js";
|
|
2
|
+
import { packRegistration, pickRegistration } from "./bundle.js";
|
|
2
3
|
import { cameraRegistration } from "./camera.js";
|
|
3
4
|
import { randomRegistration } from "./random.js";
|
|
4
5
|
import { remapRegistration } from "./remap.js";
|
|
5
|
-
import { mergeRegistration, nullRegistration, selectRegistration, switchRegistration } from "./routing.js";
|
|
6
|
+
import { frozenRegistration, mergeRegistration, nullRegistration, selectRegistration, switchRegistration } from "./routing.js";
|
|
6
7
|
import { feedbackRegistration, inputRegistration, outputRegistration, previousRegistration, subnetRegistration } from "./structural.js";
|
|
7
8
|
import { timeRegistration } from "./time.js";
|
|
8
|
-
export { cameraRegistration, feedbackRegistration, previousRegistration, inputRegistration, mergeRegistration, nullRegistration, outputRegistration, randomRegistration, remapRegistration, selectRegistration, subnetRegistration, switchRegistration, timeRegistration, };
|
|
9
|
+
export { cameraRegistration, feedbackRegistration, frozenRegistration, previousRegistration, inputRegistration, mergeRegistration, nullRegistration, outputRegistration, packRegistration, pickRegistration, randomRegistration, remapRegistration, selectRegistration, subnetRegistration, switchRegistration, timeRegistration, };
|
|
9
10
|
export declare const coreNodeRegistrations: readonly DefinitionNodeRegistration[];
|
|
10
11
|
export declare function coreNodeRegistration(moduleId: string): DefinitionNodeRegistration | undefined;
|
|
11
12
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { packRegistration, pickRegistration } from "./bundle.js";
|
|
1
2
|
import { cameraRegistration } from "./camera.js";
|
|
2
3
|
import { randomRegistration } from "./random.js";
|
|
3
4
|
import { remapRegistration } from "./remap.js";
|
|
4
|
-
import { mergeRegistration, nullRegistration, selectRegistration, switchRegistration } from "./routing.js";
|
|
5
|
+
import { frozenRegistration, mergeRegistration, nullRegistration, selectRegistration, switchRegistration } from "./routing.js";
|
|
5
6
|
import { feedbackRegistration, inputRegistration, outputRegistration, previousRegistration, subnetRegistration, } from "./structural.js";
|
|
6
7
|
import { timeRegistration } from "./time.js";
|
|
7
|
-
export { cameraRegistration, feedbackRegistration, previousRegistration, inputRegistration, mergeRegistration, nullRegistration, outputRegistration, randomRegistration, remapRegistration, selectRegistration, subnetRegistration, switchRegistration, timeRegistration, };
|
|
8
|
+
export { cameraRegistration, feedbackRegistration, frozenRegistration, previousRegistration, inputRegistration, mergeRegistration, nullRegistration, outputRegistration, packRegistration, pickRegistration, randomRegistration, remapRegistration, selectRegistration, subnetRegistration, switchRegistration, timeRegistration, };
|
|
8
9
|
export const coreNodeRegistrations = Object.freeze([
|
|
9
10
|
subnetRegistration,
|
|
10
11
|
inputRegistration,
|
|
@@ -13,12 +14,15 @@ export const coreNodeRegistrations = Object.freeze([
|
|
|
13
14
|
mergeRegistration,
|
|
14
15
|
nullRegistration,
|
|
15
16
|
selectRegistration,
|
|
17
|
+
pickRegistration,
|
|
18
|
+
packRegistration,
|
|
16
19
|
randomRegistration,
|
|
17
20
|
remapRegistration,
|
|
18
21
|
cameraRegistration,
|
|
19
22
|
timeRegistration,
|
|
20
23
|
feedbackRegistration,
|
|
21
24
|
previousRegistration,
|
|
25
|
+
frozenRegistration,
|
|
22
26
|
]);
|
|
23
27
|
const registrationsById = new Map(coreNodeRegistrations.map((registration) => [registration.moduleId, registration]));
|
|
24
28
|
export function coreNodeRegistration(moduleId) {
|